QuestionPanel.vue 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. <template>
  2. <div class="question-panel">
  3. <a-descriptions v-if="info" class="panel-info" :column="10">
  4. <a-descriptions-item label="准考证号" :span="6">
  5. {{ info.examNumber }}
  6. </a-descriptions-item>
  7. <a-descriptions-item label="姓名" :span="4">
  8. {{ info.name }}
  9. </a-descriptions-item>
  10. <a-descriptions-item label="座位号" :span="6">
  11. {{ info.seatNumber }}
  12. </a-descriptions-item>
  13. <a-descriptions-item label="考点" :span="4">
  14. {{ info.examSite }}
  15. </a-descriptions-item>
  16. <a-descriptions-item label="卷型号" :span="6">
  17. <a-button class="ant-gray m-r-4px">{{ info.paperType }}</a-button>
  18. <a-button>
  19. <template #icon><SwapOutlined /></template>
  20. </a-button>
  21. </a-descriptions-item>
  22. <a-descriptions-item label="缺考" :span="4">
  23. <a-radio-group
  24. v-model:value="incomplete"
  25. name="incomplete"
  26. :options="booleanOptions"
  27. >
  28. </a-radio-group>
  29. </a-descriptions-item>
  30. </a-descriptions>
  31. <div ref="panelBodyRef" class="panel-body">
  32. <div class="panel-body-title">
  33. <h4>客观题</h4>
  34. <p>多于一个填涂显示>号,未填涂显示#号</p>
  35. </div>
  36. <div
  37. v-for="(item, index) in questionList"
  38. :key="index"
  39. :class="['question-item', `question-item-${index}`]"
  40. >
  41. <span>{{ getQuestionNo(index) }}:</span>
  42. <a-button
  43. :class="['ant-gray', { 'is-active': curQuestionIndex === index }]"
  44. @click="onEditQuestion(index)"
  45. >{{ getQuesionCont(item) }}</a-button
  46. >
  47. </div>
  48. <div
  49. v-if="quesionEditShow"
  50. class="queston-edit"
  51. :style="quesionEditStyle"
  52. v-ele-click-outside-directive="hideEditQuestion"
  53. @keyup.enter="onSaveQuesion"
  54. >
  55. <a-input v-model:value="curQuestion" style="width: 64px"></a-input>
  56. <a-button class="ant-simple m-l-8px" type="link" @click="onSaveQuesion"
  57. >保存(Enter)</a-button
  58. >
  59. </div>
  60. </div>
  61. </div>
  62. </template>
  63. <script setup lang="ts">
  64. import { computed, ref, watch } from "vue";
  65. import { message } from "ant-design-vue";
  66. import { SwapOutlined } from "@ant-design/icons-vue";
  67. import { booleanOptionList } from "@/constants/enumerate";
  68. import { vEleClickOutsideDirective } from "@/directives/eleClickOutside";
  69. defineOptions({
  70. name: "QuestionPanel",
  71. });
  72. interface QuestionInfo {
  73. examNumber: string;
  74. name: string;
  75. examSite: string;
  76. seatNumber: number;
  77. paperType: string;
  78. }
  79. const props = withDefaults(
  80. defineProps<{
  81. questions: string[];
  82. info: QuestionInfo;
  83. }>(),
  84. {
  85. questions: () => [],
  86. }
  87. );
  88. const emit = defineEmits(["update:questions", "change"]);
  89. const booleanOptions = ref(booleanOptionList);
  90. const incomplete = ref();
  91. const questionList = ref([]);
  92. const curQuestion = ref("");
  93. const curQuestionIndex = ref(-1);
  94. function getQuestionNo(index: number) {
  95. const no = index + 1;
  96. return no < 10 ? `0${no}` : `${no}`;
  97. }
  98. function getQuesionCont(cont: string) {
  99. if (!cont) return "#";
  100. if (cont.length > 1) return ">";
  101. return cont;
  102. }
  103. // question edit
  104. const quesionEditShow = ref(false);
  105. const quesionEditPos = ref({
  106. left: 0,
  107. top: 0,
  108. });
  109. const quesionEditStyle = computed(() => {
  110. return {
  111. top: `${quesionEditPos.value.top}px`,
  112. left: `${quesionEditPos.value.left}px`,
  113. };
  114. });
  115. const panelBodyRef = ref();
  116. function onEditQuestion(index: number) {
  117. curQuestionIndex.value = index;
  118. const qcont = questionList.value[curQuestionIndex.value];
  119. curQuestion.value = qcont.split("").join(",");
  120. quesionEditShow.value = true;
  121. updateQuestionEditPos(index);
  122. }
  123. function updateQuestionEditPos(index: number) {
  124. const panelBodyDom = panelBodyRef.value as HTMLDivElement;
  125. const itemDom = panelBodyDom.querySelector(
  126. `.question-item-${index}`
  127. ) as HTMLDivElement;
  128. let left = itemDom.offsetLeft + 30;
  129. left = Math.min(panelBodyDom.clientWidth - 165, left);
  130. quesionEditPos.value.left = left;
  131. quesionEditPos.value.top = itemDom.offsetTop - 54;
  132. }
  133. function hideEditQuestion() {
  134. quesionEditShow.value = false;
  135. curQuestionIndex.value = -1;
  136. }
  137. function onSaveQuesion() {
  138. if (!quesionEditShow.value) return;
  139. if (!curQuestion.value) {
  140. message.error("请输入答案!");
  141. return;
  142. }
  143. const questionCont = curQuestion.value.split(",").join("");
  144. if (!questionCont) {
  145. message.error("请输入答案!");
  146. return;
  147. }
  148. questionList.value[curQuestionIndex.value] = questionCont;
  149. quesionEditShow.value = false;
  150. emit("update:questions", questionList.value);
  151. emit("change", questionList.value);
  152. }
  153. watch(
  154. () => props.questions,
  155. (val) => {
  156. if (!val) return;
  157. questionList.value = [...val];
  158. },
  159. {
  160. immediate: true,
  161. }
  162. );
  163. </script>
  164. <style lang="less" scoped>
  165. .question-panel {
  166. .panel-body {
  167. margin: 15px -14px 0;
  168. padding: 0 14px;
  169. border-top: 1px solid @border-color1;
  170. position: relative;
  171. font-size: 0;
  172. &-title {
  173. padding: 12px 0 8px;
  174. display: flex;
  175. justify-content: space-between;
  176. align-items: center;
  177. font-size: 14px;
  178. > p {
  179. color: @text-color3;
  180. }
  181. }
  182. .question-item {
  183. display: inline-block;
  184. vertical-align: middle;
  185. width: 20%;
  186. font-size: 14px;
  187. margin-bottom: 12px;
  188. > span {
  189. display: inline-block;
  190. width: 30px;
  191. padding-right: 4px;
  192. text-align: right;
  193. font-size: 13px;
  194. }
  195. .ant-btn {
  196. padding-left: 10px;
  197. padding-right: 10px;
  198. &.is-active {
  199. border-color: @brand-color;
  200. }
  201. }
  202. }
  203. .queston-edit {
  204. position: absolute;
  205. width: 165px;
  206. background: #f2f3f5;
  207. box-shadow: 0px 4px 8px 0px rgba(54, 61, 89, 0.2);
  208. border-radius: 8px;
  209. padding: 8px;
  210. border: 1px solid @border-color1;
  211. z-index: 9;
  212. }
  213. }
  214. :deep(.ant-descriptions-row) {
  215. .ant-descriptions-item {
  216. padding-bottom: 8px;
  217. .ant-descriptions-item-label {
  218. display: block;
  219. color: @text-color2;
  220. }
  221. .ant-descriptions-item-label::after {
  222. margin-inline: 2px 4px;
  223. }
  224. &:first-child {
  225. .ant-descriptions-item-label {
  226. width: 66px;
  227. text-align: right;
  228. }
  229. }
  230. }
  231. .ant-radio-wrapper span.ant-radio + * {
  232. padding-inline-start: 4px;
  233. padding-inline-end: 4px;
  234. }
  235. }
  236. :deep(.ant-descriptions-row:nth-of-type(2)) {
  237. .ant-descriptions-item {
  238. padding-bottom: 4px;
  239. }
  240. }
  241. :deep(.ant-descriptions-row:last-child) {
  242. .ant-descriptions-item {
  243. padding-bottom: 0;
  244. .ant-descriptions-item-container {
  245. align-items: center;
  246. }
  247. .ant-descriptions-item-label {
  248. line-height: 28px;
  249. }
  250. .ant-btn {
  251. padding: 2px 6px;
  252. height: 28px;
  253. min-width: 28px;
  254. }
  255. }
  256. }
  257. }
  258. </style>