QuestionPanel.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  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.packageCode }}
  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">{{
  22. info.paperType || "#"
  23. }}</a-button>
  24. <a-button v-if="paperTypeArea && editable" @click="onEditPaperType">
  25. <template #icon><SwapOutlined /></template>
  26. </a-button>
  27. </template>
  28. </a-descriptions-item>
  29. <a-descriptions-item v-if="!simple && editable" label="缺考" :span="4">
  30. <a-radio-group
  31. v-model:value="examStatus"
  32. name="examStatus"
  33. @change="onExamStatusChange"
  34. >
  35. <a-radio
  36. v-for="(item, index) in examStatusOptions"
  37. :key="index"
  38. :value="item.value"
  39. >{{ item.label }}</a-radio
  40. >
  41. </a-radio-group>
  42. </a-descriptions-item>
  43. </a-descriptions>
  44. <div v-if="!simple" ref="panelBodyRef" class="panel-body">
  45. <div class="panel-body-title">
  46. <h4>客观题</h4>
  47. <p>多于一个填涂显示>号,未填涂显示#号</p>
  48. </div>
  49. <div
  50. v-for="(item, index) in questionList"
  51. :key="index"
  52. :class="['question-item', `question-item-${index}`]"
  53. >
  54. <span>{{ getQuestionNo(index) }}:</span>
  55. <a-button
  56. v-if="editable || item.length <= 1"
  57. :class="['ant-gray', { 'is-active': curQuestionIndex === index }]"
  58. :disabled="!editable"
  59. @click="onEditQuestion(index)"
  60. >{{ getQuesionCont(item) }}</a-button
  61. >
  62. <a-tooltip v-else placement="top">
  63. <template #title>
  64. <span>{{ item.split("").join(",") }}</span>
  65. </template>
  66. <a-button disabled>{{ getQuesionCont(item) }}</a-button>
  67. </a-tooltip>
  68. </div>
  69. <div
  70. v-if="quesionEditShow && editable"
  71. class="queston-edit"
  72. :style="quesionEditStyle"
  73. v-ele-click-outside-directive="hideEditQuestion"
  74. @keyup.enter="onSaveQuesion"
  75. >
  76. <a-input
  77. v-model:value="curQuestion"
  78. style="width: 64px"
  79. @change="toUpperCase"
  80. ></a-input>
  81. <a-button class="ant-simple m-l-8px" type="link" @click="onSaveQuesion"
  82. >保存(Enter)</a-button
  83. >
  84. </div>
  85. </div>
  86. </div>
  87. <ModifyPaperType
  88. ref="modifyPaperTypeRef"
  89. :area-img="paperTypeImg"
  90. :area-result="paperTypeResult"
  91. @confirm="paperTypeModified"
  92. />
  93. </template>
  94. <script setup lang="ts" name="QuestionPanel">
  95. import { computed, ref, watch } from "vue";
  96. import { message } from "ant-design-vue";
  97. import { SwapOutlined } from "@ant-design/icons-vue";
  98. import { QuestionInfo } from "./types";
  99. import { parseRecogData } from "@/utils/recog/recog";
  100. import useDictOption from "@/hooks/dictOption";
  101. import ModifyPaperType from "./ModifyPaperType.vue";
  102. import { useDataCheckStore } from "@/store";
  103. import { vEleClickOutsideDirective } from "@/directives/eleClickOutside";
  104. import { getSliceFileUrl } from "@/utils/tool";
  105. // defineOptions({
  106. // name: "QuestionPanel",
  107. // });
  108. const props = withDefaults(
  109. defineProps<{
  110. questions: string[];
  111. info: QuestionInfo;
  112. simple: boolean;
  113. editable?: boolean;
  114. }>(),
  115. {
  116. questions: () => [],
  117. simple: false,
  118. editable: true,
  119. }
  120. );
  121. const emit = defineEmits(["update:questions", "change", "examStatusChange"]);
  122. const dataCheckStore = useDataCheckStore();
  123. const { optionList: examStatusOptions } = useDictOption("EXAM_SIMPLE_STATUS");
  124. const examStatus = ref("");
  125. const questionList = ref([] as string[]);
  126. const curQuestion = ref("");
  127. const curQuestionIndex = ref(-1);
  128. const toUpperCase = () => {
  129. curQuestion.value = curQuestion.value.toUpperCase();
  130. };
  131. function onExamStatusChange() {
  132. emit("examStatusChange", examStatus.value);
  133. }
  134. function getQuestionNo(index: number) {
  135. const no = index + 1;
  136. return no < 10 ? `0${no}` : `${no}`;
  137. }
  138. function getQuesionCont(cont: string) {
  139. if (!cont) return "#";
  140. if (cont.length > 1) return ">";
  141. return cont;
  142. }
  143. // question edit
  144. const quesionEditShow = ref(false);
  145. const quesionEditPos = ref({
  146. left: 0,
  147. top: 0,
  148. });
  149. const quesionEditStyle = computed(() => {
  150. return {
  151. top: `${quesionEditPos.value.top}px`,
  152. left: `${quesionEditPos.value.left}px`,
  153. };
  154. });
  155. const panelBodyRef = ref();
  156. function onEditQuestion(index: number) {
  157. curQuestionIndex.value = index;
  158. const qcont = questionList.value[curQuestionIndex.value];
  159. curQuestion.value = qcont.split("").join(",");
  160. quesionEditShow.value = true;
  161. updateQuestionEditPos(index);
  162. }
  163. function updateQuestionEditPos(index: number) {
  164. const panelBodyDom = panelBodyRef.value as HTMLDivElement;
  165. const itemDom = panelBodyDom.querySelector(
  166. `.question-item-${index}`
  167. ) as HTMLDivElement;
  168. let left = itemDom.offsetLeft + 30;
  169. left = Math.min(panelBodyDom.clientWidth - 165, left);
  170. quesionEditPos.value.left = left;
  171. quesionEditPos.value.top = itemDom.offsetTop - 54;
  172. }
  173. function hideEditQuestion() {
  174. quesionEditShow.value = false;
  175. curQuestionIndex.value = -1;
  176. }
  177. function onSaveQuesion() {
  178. if (!quesionEditShow.value) return;
  179. if (!curQuestion.value) {
  180. message.error("请输入答案!");
  181. return;
  182. }
  183. const questionCont = curQuestion.value.split(",").join("");
  184. if (!questionCont) {
  185. message.error("请输入答案!");
  186. return;
  187. }
  188. questionList.value[curQuestionIndex.value] = questionCont;
  189. quesionEditShow.value = false;
  190. emit("update:questions", questionList.value);
  191. emit("change", questionList.value);
  192. }
  193. // edit paper
  194. const modifyPaperTypeRef = ref();
  195. const paperTypeArea = ref<AreaSize | null>(null);
  196. const paperTypeImg = ref("");
  197. const paperTypeResult = ref("");
  198. async function onEditPaperType() {
  199. if (!dataCheckStore.curPage) return;
  200. if (paperTypeArea.value) {
  201. paperTypeImg.value = await getSliceFileUrl(
  202. dataCheckStore.curPage.sheetUri,
  203. paperTypeArea.value
  204. );
  205. paperTypeResult.value = dataCheckStore.curPage.paperType.result;
  206. } else {
  207. paperTypeImg.value = "";
  208. }
  209. modifyPaperTypeRef.value?.open();
  210. }
  211. const curPage = computed(() => dataCheckStore.curPage);
  212. async function paperTypeModified(paperType: string) {
  213. if (!dataCheckStore.curPage) return;
  214. dataCheckStore.modifyPaperType({
  215. paperIndex: curPage.value?.paperIndex as number,
  216. pageIndex: curPage.value?.pageIndex as number,
  217. paperType: paperType || "#",
  218. });
  219. await dataCheckStore.updateField({
  220. field: "PAPER_TYPE",
  221. value: JSON.stringify({
  222. ...dataCheckStore.curPage.paperType,
  223. result: paperType || "#",
  224. }),
  225. });
  226. dataCheckStore.curPage.paperType.result = paperType;
  227. }
  228. watch(
  229. () => dataCheckStore.curPage?.recogData,
  230. (val) => {
  231. paperTypeArea.value = null;
  232. if (!val) return;
  233. const regdata = parseRecogData(val);
  234. if (!regdata) return;
  235. const rect = regdata.paperType.rect || null;
  236. if (!rect) {
  237. paperTypeArea.value = null;
  238. return;
  239. }
  240. const hasArea = rect.some((item) => item);
  241. paperTypeArea.value = hasArea
  242. ? {
  243. x: rect[0],
  244. y: rect[1],
  245. w: rect[2],
  246. h: rect[3],
  247. }
  248. : null;
  249. },
  250. {
  251. immediate: true,
  252. }
  253. );
  254. watch(
  255. () => props.questions,
  256. (val) => {
  257. if (!val) return;
  258. questionList.value = [...val];
  259. },
  260. {
  261. immediate: true,
  262. }
  263. );
  264. watch(
  265. () => props.info,
  266. (val) => {
  267. examStatus.value = val.examStatus;
  268. }
  269. );
  270. </script>
  271. <style lang="less" scoped>
  272. .question-panel {
  273. .panel-body {
  274. margin: 15px -14px 0;
  275. padding: 0 14px;
  276. border-top: 1px solid @border-color1;
  277. position: relative;
  278. font-size: 0;
  279. &-title {
  280. padding: 12px 0 8px;
  281. display: flex;
  282. justify-content: space-between;
  283. align-items: center;
  284. font-size: 14px;
  285. > p {
  286. color: @text-color3;
  287. }
  288. }
  289. .question-item {
  290. display: inline-block;
  291. vertical-align: middle;
  292. width: 20%;
  293. font-size: 14px;
  294. margin-bottom: 12px;
  295. > span {
  296. display: inline-block;
  297. width: 30px;
  298. padding-right: 4px;
  299. text-align: right;
  300. font-size: 13px;
  301. }
  302. .ant-btn {
  303. padding-left: 10px;
  304. padding-right: 10px;
  305. &.is-active {
  306. border-color: @brand-color;
  307. }
  308. }
  309. }
  310. .queston-edit {
  311. position: absolute;
  312. width: 165px;
  313. background: #f2f3f5;
  314. box-shadow: 0px 4px 8px 0px rgba(54, 61, 89, 0.2);
  315. border-radius: 8px;
  316. padding: 8px;
  317. border: 1px solid @border-color1;
  318. z-index: 9;
  319. }
  320. }
  321. :deep(.ant-descriptions-row) {
  322. .ant-descriptions-item {
  323. padding-bottom: 8px;
  324. .ant-descriptions-item-label {
  325. display: block;
  326. color: @text-color2;
  327. }
  328. .ant-descriptions-item-label::after {
  329. margin-inline: 2px 4px;
  330. }
  331. &:first-child {
  332. .ant-descriptions-item-label {
  333. width: 66px;
  334. text-align: right;
  335. }
  336. }
  337. }
  338. .ant-descriptions-item-content {
  339. align-items: center;
  340. }
  341. .ant-radio-wrapper span.ant-radio + * {
  342. padding-inline-start: 4px;
  343. padding-inline-end: 4px;
  344. }
  345. }
  346. :deep(.ant-descriptions-row:nth-of-type(2)) {
  347. .ant-descriptions-item {
  348. padding-bottom: 4px;
  349. }
  350. }
  351. :deep(.ant-descriptions-row:last-child) {
  352. .ant-descriptions-item {
  353. padding-bottom: 0;
  354. .ant-descriptions-item-container {
  355. align-items: center;
  356. }
  357. .ant-descriptions-item-label {
  358. line-height: 28px;
  359. }
  360. .ant-btn {
  361. padding: 2px 6px;
  362. height: 28px;
  363. min-width: 28px;
  364. }
  365. }
  366. }
  367. }
  368. </style>