QuestionPanel.vue 7.6 KB

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