index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. <template>
  2. <div class="flex direction-column full">
  3. <mark-header
  4. :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']"
  5. :paper-path="currentSubjectiveCheck?.filePath"
  6. :secret-number="currentSubjectiveCheck?.secretNumber"
  7. @click="onOperationClick"
  8. >
  9. <!-- <el-button type="primary" size="small" class="m-l-base" @click="onEditScore">修改给分</el-button> -->
  10. <el-button
  11. type="primary"
  12. size="small"
  13. class="m-l-base m-r-auto"
  14. :disabled="currentSubjectiveCheck"
  15. :loading="confirmLoading"
  16. @click="onConfirm"
  17. >提交确认</el-button
  18. >
  19. </mark-header>
  20. <div class="flex flex-1 overflow-hidden p-base mark-container">
  21. <splitpanes class="default-theme" style="height: 100%" @resize="setPaneSize">
  22. <pane
  23. max-size="80"
  24. :size="paneSize"
  25. class="flex flex-1 direction-column radius-base fill-blank mark-content"
  26. :class="{ 'text-center': center }"
  27. :style="{ 'background-color': backgroundColor }"
  28. >
  29. <span class="preview" @click="onPreview">
  30. <svg-icon name="preview"></svg-icon>
  31. </span>
  32. <p v-if="currentSubjectiveCheck" class="absolute question-info">
  33. {{ currentSubjectiveCheck.mainNumber + '-' + currentSubjectiveCheck.mainName }}
  34. </p>
  35. <p v-if="currentSubjectiveCheck" class="absolute mark-score">{{ currentSubjectiveCheck.markScore }}</p>
  36. <!-- <right-button class="next-button" @click="checkNext" /> -->
  37. <div class="flex-1 p-base scroll-auto mark-content-paper img-wrap">
  38. <img :src="dataUrl" alt="" class="paper-img" :style="{ 'background-color': frontColor }" />
  39. </div>
  40. <!-- <scoring-panel-with-confirm
  41. :id="currentSubjectiveCheck?.taskId"
  42. v-model:visible="editScoreVisible"
  43. v-model:score="modelScore"
  44. :main-number="currentSubjectiveCheck?.mainNumber"
  45. modal
  46. :toggle-modal="false"
  47. :auto-visible="false"
  48. @submit="onSubmit"
  49. ></scoring-panel-with-confirm> -->
  50. <scoring-panel-with-confirm
  51. :id="currentSubjectiveCheck?.taskId"
  52. v-model:visible="editScoreVisible"
  53. v-model:score="modelScore"
  54. :main-number="currentSubjectiveCheck?.mainNumber"
  55. :subject-code="formModel.subjectCode"
  56. modal
  57. :auto-visible="false"
  58. @submit="onSubmit"
  59. ></scoring-panel-with-confirm>
  60. </pane>
  61. <pane max-size="80" :size="100 - paneSize" class="p-base radius-base fill-blank scroll-auto table-view">
  62. <splitpanes class="default-theme" horizontal style="height: 100%">
  63. <pane max-size="100" size="70" style="display: flex; flex-direction: column">
  64. <base-form
  65. size="small"
  66. :model="formModel"
  67. :items="formItems"
  68. :label-width="'80px'"
  69. style="margin-top: 20px"
  70. >
  71. <template #form-item-search>
  72. <el-button :loading="loading" type="primary" :disabled="confirmLoading" @click="onSearch"
  73. >查询</el-button
  74. >
  75. </template>
  76. </base-form>
  77. <div class="m-b-mini">
  78. <el-button custom-1 size="small" class="detail-info-label">
  79. <span class="">{{ statusText }}: 共</span>
  80. <span class="m-l-extra-small detail-info-label-num">{{ tableData.length }}</span>
  81. </el-button>
  82. </div>
  83. <div class="flex-1 scroll-auto">
  84. <base-table
  85. ref="tableRef"
  86. height="100%"
  87. border
  88. stripe
  89. size="small"
  90. :data="tableData"
  91. :columns="columns"
  92. highlight-current-row
  93. :memory-column="true"
  94. @current-change="onCurrentChange"
  95. ></base-table>
  96. </div>
  97. </pane>
  98. <pane max-size="100" size="30">
  99. <!-- <base-table
  100. v-if="currentSubjectiveCheck"
  101. border
  102. stripe
  103. class="m-t-base"
  104. size="small"
  105. height="150px"
  106. :data="[currentSubjectiveCheck]"
  107. :columns="subColumns"
  108. :cell-style="{ padding: '6px 0' }"
  109. @row-dblclick="onDbClick"
  110. >
  111. <template #empty> 暂无数据 </template>
  112. </base-table> -->
  113. <div v-if="currentSubjectiveCheck" style="height: 100%">
  114. <div class="bottom-title">给分记录( {{ currentSubjectiveCheck?.secretNumber }} )</div>
  115. <base-table
  116. border
  117. stripe
  118. class="m-t-base"
  119. size="small"
  120. height="calc(100% - 20px)"
  121. :data="currentHistoryData"
  122. :columns="currentHistoryColumns"
  123. :cell-style="{ padding: '6px 0' }"
  124. >
  125. <template #empty> 暂无数据 </template>
  126. </base-table>
  127. </div>
  128. </pane>
  129. </splitpanes>
  130. </pane>
  131. </splitpanes>
  132. </div>
  133. </div>
  134. <image-preview v-model="previewModalVisible" :url="currentSubjectiveCheck?.filePath"></image-preview>
  135. <mark-history-list
  136. :id="currentViewHistory?.taskId"
  137. v-model="visibleHistory"
  138. :task="currentViewHistory"
  139. ></mark-history-list>
  140. </template>
  141. <script setup lang="ts" name="QualitySubjectiveCheck">
  142. /** 主观题校验 */
  143. import { reactive, ref, computed, watch } from 'vue'
  144. import { ElButton, ElMessage } from 'element-plus'
  145. import { add } from '@/utils/common'
  146. import { useSetImgBg } from '@/hooks/useSetImgBg'
  147. import useFetch from '@/hooks/useFetch'
  148. import useVW from '@/hooks/useVW'
  149. import useForm from '@/hooks/useForm'
  150. import useOptions from '@/hooks/useOptions'
  151. import useMarkHeader from '@/hooks/useMarkHeader'
  152. import useTableCheck from '@/hooks/useTableCheck'
  153. import BaseForm from '@/components/element/BaseForm.vue'
  154. import BaseTable from '@/components/element/BaseTable.vue'
  155. import MarkHistoryList from '@/components/shared/MarkHistoryList.vue'
  156. import RightButton from '@/components/shared/RightButton.vue'
  157. import MarkHeader from '@/components/shared/MarkHeader.vue'
  158. import ScoringPanelWithConfirm from '@/components/shared/ScoringPanelWithConfirm.vue'
  159. import ImagePreview from '@/components/shared/ImagePreview.vue'
  160. import SvgIcon from '@/components/common/SvgIcon.vue'
  161. import type { SetImgBgOption } from '@/hooks/useSetImgBg'
  162. import type { ExtractMultipleApiResponse, ExtractApiParams } from '@/api/api'
  163. import type { MarkHeaderInstance, EpFormItem, EpTableColumn } from 'global-type'
  164. import { Splitpanes, Pane } from 'splitpanes'
  165. // import 'splitpanes/dist/splitpanes.css'
  166. import { setPaneSize } from '@/utils/common'
  167. import useMainStore from '@/store/main'
  168. const mainStore = useMainStore()
  169. const paneSize = computed(() => {
  170. return mainStore.paneSizeConfig[location.pathname] || 60
  171. })
  172. type RowType = ExtractMultipleApiResponse<'getSubjectiveCheckList'> & { index: number }
  173. const { fetch: getMarkScoreHistoryListWithTask, result: scoreHistoryList } = useFetch('getMarkScoreHistoryListWithTask')
  174. const currentHistoryData = computed(() => {
  175. return scoreHistoryList.value
  176. })
  177. const currentHistoryColumns = computed<any>(() => [
  178. { label: '评卷员', prop: 'markerName', fixed: 'left' },
  179. {
  180. label: `分数`,
  181. prop: 'markScore',
  182. width: 48,
  183. formatter(row: any) {
  184. return `${row.markScore === null ? '' : row.markScore}`
  185. },
  186. },
  187. {
  188. label: '类型',
  189. prop: 'historyType',
  190. width: 104,
  191. formatter(row: any) {
  192. return `${row.historyType}${row.markScore === null ? '-问题卷' : ''}`
  193. },
  194. },
  195. { label: '路径', prop: 'source' },
  196. { label: '评卷时间', prop: 'markTime', width: 130 },
  197. ])
  198. /** 给分板 */
  199. const editScoreVisible = ref<boolean>(true)
  200. /** 图片预览 */
  201. const previewModalVisible = ref<boolean>(false)
  202. /** 分数 */
  203. const modelScore = ref<number[]>([])
  204. const {
  205. rotate,
  206. scale,
  207. center,
  208. frontColor,
  209. backgroundColor,
  210. onBack,
  211. onScaleChange,
  212. onCenter,
  213. onRotate,
  214. setBackgroundColor,
  215. setFrontColor,
  216. onViewStandard,
  217. } = useMarkHeader()
  218. /** 刷新 */
  219. const onRefresh = () => {
  220. onSearch()
  221. }
  222. /** 预览试卷 */
  223. const onPreview = () => {
  224. previewModalVisible.value = true
  225. }
  226. const onEditScore = () => {
  227. editScoreVisible.value = true
  228. }
  229. type OperationClick = MarkHeaderInstance['onClick']
  230. type OperationType = Parameters<Exclude<OperationClick, undefined>>[0]['type']
  231. const operationHandles: Partial<Record<OperationType, (...args: any) => void>> = {
  232. back: onBack,
  233. 'scale-change': onScaleChange,
  234. center: onCenter,
  235. rotate: onRotate,
  236. 'front-color': setFrontColor,
  237. 'background-color': setBackgroundColor,
  238. refresh: onRefresh,
  239. standard: onViewStandard,
  240. }
  241. const onOperationClick: OperationClick = ({ type, value }) => {
  242. operationHandles[type]?.(value)
  243. }
  244. const { subjectList, mainQuestionList, dataModel, changeModelValue, onOptionInit, isExpert, isLeader } = useOptions([
  245. 'subject',
  246. 'question',
  247. ])
  248. const formModel = reactive<ExtractApiParams<'getSubjectiveCheckList'>>({
  249. subjectCode: dataModel.subject || '',
  250. mainNumber: dataModel.question,
  251. checked: false,
  252. rule: 'RULE1',
  253. pageNumber: 1,
  254. pageSize: 9999999,
  255. })
  256. watch(dataModel, () => {
  257. formModel.subjectCode = dataModel.subject || ''
  258. formModel.mainNumber = dataModel.question
  259. })
  260. const { defineColumn, _ } = useForm()
  261. const span10 = defineColumn(_, '', { span: 10 })
  262. const span12 = defineColumn(_, '', { span: 12 })
  263. const rules = [
  264. '主观分有分,客观分为0',
  265. '主观分为0分,客观分大于40分',
  266. '主观分为1,2分,客观分大于45分',
  267. '主观分大于10分,客观分小于20分',
  268. ].map((v, i) => ({
  269. label: v,
  270. value: `RULE${i + 1}`,
  271. }))
  272. const formItems = computed<EpFormItem[]>(() => [
  273. span10({
  274. rowKey: 'row-1',
  275. label: '科目',
  276. prop: 'subjectCode',
  277. slotType: 'select',
  278. slot: { options: subjectList.value, onChange: changeModelValue('subject'), disabled: !isExpert.value },
  279. }),
  280. span10({
  281. rowKey: 'row-1',
  282. label: '大题',
  283. prop: 'mainNumber',
  284. slotType: 'select',
  285. slot: {
  286. options: mainQuestionList.value,
  287. onChange: changeModelValue('question'),
  288. disabled: !isExpert.value && !isLeader.value,
  289. },
  290. }),
  291. { rowKey: 'row-1', slotName: 'search', labelWidth: '10px', colProp: { span: 4 } },
  292. span12({
  293. rowKey: 'row-2',
  294. label: '校验规则',
  295. prop: 'rule',
  296. slotType: 'select',
  297. slot: {
  298. options: rules,
  299. },
  300. }),
  301. span10({
  302. rowKey: 'row-3',
  303. label: '状态',
  304. prop: 'checked',
  305. slotType: 'select',
  306. slot: {
  307. options: [
  308. { label: '未处理', value: false },
  309. { label: '已处理', value: true },
  310. { label: '全部', value: '' },
  311. ],
  312. },
  313. }),
  314. ])
  315. /** 主观题校验 */
  316. const columns: EpTableColumn<RowType>[] = [
  317. { label: '序号', type: 'index', width: 60 },
  318. { label: '密号', prop: 'secretNumber', minWidth: 110 },
  319. { label: '大题名称', prop: 'mainName', minWidth: 100 },
  320. {
  321. label: '成绩',
  322. prop: 'markScore',
  323. minWidth: 60,
  324. // formatter(row: any) {
  325. // return row.headerScore || row.markScore
  326. // },
  327. },
  328. { label: '处理结果', prop: 'status', minWidth: 100 },
  329. { label: '处理人', prop: 'headerName', minWidth: 100 },
  330. { label: '处理时间', prop: 'updateTime', minWidth: 130 },
  331. ]
  332. const subColumns: EpTableColumn<RowType>[] = [
  333. { label: '密号', prop: 'secretNumber', minWidth: 110 },
  334. { label: '大题名称', prop: 'mainName', minWidth: 100 },
  335. { label: '当前成绩', prop: 'markScore', minWidth: 100 },
  336. { label: '修改成绩', prop: 'headerScore', minWidth: 100 },
  337. ]
  338. const { fetch: getSubjectiveCheckList, result: subjectiveCheckList, loading } = useFetch('getSubjectiveCheckList')
  339. const {
  340. tableRef,
  341. tableData,
  342. current: currentSubjectiveCheck,
  343. currentView: currentViewHistory,
  344. next: checkNext,
  345. visibleHistory,
  346. onDbClick,
  347. onCurrentChange,
  348. nextRow,
  349. } = useTableCheck(subjectiveCheckList)
  350. watch(currentSubjectiveCheck, () => {
  351. // getMarkScoreHistoryListWithTask({ taskId: currentSubjectiveCheck.value.taskId })
  352. if (currentSubjectiveCheck.value) {
  353. getMarkScoreHistoryListWithTask({ taskId: currentSubjectiveCheck.value.taskId })
  354. useFetch('viewActiveCheck').fetch({ taskId: currentSubjectiveCheck.value.taskId })
  355. }
  356. })
  357. const statusText = ref<string>('未处理')
  358. const onSearch = async () => {
  359. getSubjectiveCheckList(formModel).then(() => {
  360. statusText.value = formModel.checked === true ? '已处理' : formModel.checked === false ? '未处理' : '全部'
  361. })
  362. }
  363. /** 给分 */
  364. const { fetch: subjectiveCheckMark } = useFetch('subjectiveCheckMark')
  365. const onSubmit = async () => {
  366. if (currentSubjectiveCheck.value) {
  367. const scores = JSON.parse(JSON.stringify(modelScore.value))
  368. await subjectiveCheckMark({ taskId: currentSubjectiveCheck.value.taskId, scores: modelScore.value })
  369. currentSubjectiveCheck.value.markScore = add(...scores)
  370. ElMessage.success('修改成功')
  371. // editScoreVisible.value = false
  372. // onSearch()
  373. nextRow()
  374. }
  375. }
  376. /** 确认 */
  377. const { fetch: subjectiveCheckConfirm } = useFetch('subjectiveCheckConfirm')
  378. const confirmLoading = ref(false)
  379. const hideLoading = () => {
  380. setTimeout(() => {
  381. confirmLoading.value = false
  382. })
  383. }
  384. const onConfirm = async () => {
  385. if (currentSubjectiveCheck.value) {
  386. confirmLoading.value = true
  387. try {
  388. await subjectiveCheckConfirm({ taskId: currentSubjectiveCheck.value.taskId })
  389. await onSearch()
  390. hideLoading()
  391. } catch (e) {
  392. hideLoading()
  393. }
  394. }
  395. }
  396. onOptionInit(onSearch)
  397. const imgOption = computed<SetImgBgOption>(() => {
  398. return {
  399. image: currentSubjectiveCheck?.value?.filePath,
  400. rotate: rotate.value,
  401. scale: scale.value,
  402. }
  403. })
  404. const { drawing, dataUrl } = useSetImgBg(imgOption, frontColor, setFrontColor)
  405. </script>
  406. <style scoped lang="scss">
  407. .mark-container {
  408. .mark-content {
  409. position: relative;
  410. .preview {
  411. position: absolute;
  412. cursor: pointer;
  413. top: 20px;
  414. right: 25px;
  415. font-size: 38px;
  416. }
  417. .next-button {
  418. position: absolute;
  419. right: -20px;
  420. top: 300px;
  421. }
  422. .mark-content-paper {
  423. img {
  424. max-width: 100%;
  425. }
  426. }
  427. }
  428. .table-view {
  429. // width: 580px;
  430. .bottom-title {
  431. color: #000;
  432. margin-top: 10px;
  433. }
  434. .detail-info-label {
  435. .detail-info-label-num {
  436. min-width: 32px;
  437. height: 24px;
  438. line-height: 24px;
  439. background: #00987b;
  440. border-radius: 4px;
  441. }
  442. }
  443. }
  444. }
  445. </style>