|
@@ -0,0 +1,429 @@
|
|
|
+<template>
|
|
|
+ <div class="flex direction-column full">
|
|
|
+ <mark-header
|
|
|
+ :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']"
|
|
|
+ :paper-path="currentAiCheck?.filePath"
|
|
|
+ :secret-number="currentAiCheck?.secretNumber"
|
|
|
+ @click="onOperationClick"
|
|
|
+ >
|
|
|
+ <!-- <el-button type="primary" size="small" class="m-l-base" @click="onEditScore">修改给分</el-button> -->
|
|
|
+ <el-button type="primary" size="small" class="m-l-base m-r-auto" @click="onConfirm">提交确认</el-button>
|
|
|
+ </mark-header>
|
|
|
+ <div class="flex flex-1 overflow-hidden p-base mark-container">
|
|
|
+ <splitpanes class="default-theme" style="height: 100%" @resize="setPaneSize">
|
|
|
+ <pane
|
|
|
+ max-size="80"
|
|
|
+ :size="paneSize"
|
|
|
+ class="flex flex-1 direction-column radius-base fill-blank mark-content"
|
|
|
+ :class="{ 'text-center': center }"
|
|
|
+ :style="{ 'background-color': backgroundColor }"
|
|
|
+ >
|
|
|
+ <span class="preview" @click="onPreview">
|
|
|
+ <svg-icon name="preview"></svg-icon>
|
|
|
+ </span>
|
|
|
+ <p v-if="currentAiCheck" class="absolute mark-score">{{ currentAiCheck.markScore }}</p>
|
|
|
+ <!-- <right-button class="next-button" @click="checkNext" /> -->
|
|
|
+ <div class="flex-1 p-base scroll-auto mark-content-paper img-wrap">
|
|
|
+ <img :src="dataUrl" alt="" class="paper-img" :style="{ 'background-color': frontColor }" />
|
|
|
+ </div>
|
|
|
+ <!-- <scoring-panel-with-confirm
|
|
|
+ :id="currentAiCheck?.taskId"
|
|
|
+ v-model:visible="editScoreVisible"
|
|
|
+ v-model:score="modelScore"
|
|
|
+ :main-number="currentAiCheck?.mainNumber"
|
|
|
+ modal
|
|
|
+ :toggle-modal="false"
|
|
|
+ :auto-visible="false"
|
|
|
+ @submit="onSubmit"
|
|
|
+ ></scoring-panel-with-confirm> -->
|
|
|
+ <scoring-panel-with-confirm
|
|
|
+ :id="currentAiCheck?.taskId"
|
|
|
+ v-model:visible="editScoreVisible"
|
|
|
+ v-model:score="modelScore"
|
|
|
+ :main-number="currentAiCheck?.mainNumber"
|
|
|
+ :subject-code="formModel.subjectCode"
|
|
|
+ modal
|
|
|
+ :auto-visible="false"
|
|
|
+ @submit="onSubmit"
|
|
|
+ ></scoring-panel-with-confirm>
|
|
|
+ </pane>
|
|
|
+ <pane max-size="80" :size="100 - paneSize" class="p-base radius-base fill-blank scroll-auto table-view">
|
|
|
+ <splitpanes class="default-theme" horizontal style="height: 100%">
|
|
|
+ <pane max-size="100" size="70" style="display: flex; flex-direction: column">
|
|
|
+ <base-form
|
|
|
+ size="small"
|
|
|
+ :model="formModel"
|
|
|
+ :items="formItems"
|
|
|
+ :label-width="'80px'"
|
|
|
+ style="margin-top: 20px"
|
|
|
+ >
|
|
|
+ <template #form-item-search>
|
|
|
+ <el-button :loading="loading" type="primary" @click="onSearch">查询</el-button>
|
|
|
+ </template>
|
|
|
+ </base-form>
|
|
|
+ <div class="m-b-mini">
|
|
|
+ <el-button custom-1 size="small" class="detail-info-label">
|
|
|
+ <span class="">{{ statusText }}: 共</span>
|
|
|
+ <span class="m-l-extra-small detail-info-label-num">{{ tableData.length }}</span>
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <div class="flex-1 scroll-auto">
|
|
|
+ <base-table
|
|
|
+ ref="tableRef"
|
|
|
+ height="100%"
|
|
|
+ border
|
|
|
+ stripe
|
|
|
+ size="small"
|
|
|
+ :data="tableData"
|
|
|
+ :columns="columns"
|
|
|
+ highlight-current-row
|
|
|
+ @current-change="onCurrentChange"
|
|
|
+ ></base-table>
|
|
|
+ </div>
|
|
|
+ </pane>
|
|
|
+ <pane max-size="100" size="30">
|
|
|
+ <div v-if="currentAiCheck" style="height: 100%">
|
|
|
+ <div class="bottom-title">给分记录( {{ currentAiCheck?.secretNumber }} )</div>
|
|
|
+ <base-table
|
|
|
+ border
|
|
|
+ stripe
|
|
|
+ class="m-t-base"
|
|
|
+ size="small"
|
|
|
+ height="calc(100% - 20px)"
|
|
|
+ :data="currentHistoryData"
|
|
|
+ :columns="currentHistoryColumns"
|
|
|
+ :cell-style="{ padding: '6px 0' }"
|
|
|
+ >
|
|
|
+ <template #empty> 暂无数据 </template>
|
|
|
+ </base-table>
|
|
|
+ </div>
|
|
|
+ </pane>
|
|
|
+ </splitpanes>
|
|
|
+ </pane>
|
|
|
+ </splitpanes>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <image-preview v-model="previewModalVisible" :url="currentAiCheck?.filePath"></image-preview>
|
|
|
+ <mark-history-list
|
|
|
+ :id="currentViewHistory?.taskId"
|
|
|
+ v-model="visibleHistory"
|
|
|
+ :task="currentViewHistory"
|
|
|
+ ></mark-history-list>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts" name="QualityAiCheck">
|
|
|
+/** 主观题校验 */
|
|
|
+import { reactive, ref, computed, watch } from 'vue'
|
|
|
+import { ElButton, ElMessage } from 'element-plus'
|
|
|
+import { add } from '@/utils/common'
|
|
|
+import { useSetImgBg } from '@/hooks/useSetImgBg'
|
|
|
+import useFetch from '@/hooks/useFetch'
|
|
|
+import useVW from '@/hooks/useVW'
|
|
|
+import useForm from '@/hooks/useForm'
|
|
|
+import useOptions from '@/hooks/useOptions'
|
|
|
+import useMarkHeader from '@/hooks/useMarkHeader'
|
|
|
+import useTableCheck from '@/hooks/useTableCheck'
|
|
|
+import BaseForm from '@/components/element/BaseForm.vue'
|
|
|
+import BaseTable from '@/components/element/BaseTable.vue'
|
|
|
+import MarkHistoryList from '@/components/shared/MarkHistoryList.vue'
|
|
|
+import RightButton from '@/components/shared/RightButton.vue'
|
|
|
+import MarkHeader from '@/components/shared/MarkHeader.vue'
|
|
|
+import ScoringPanelWithConfirm from '@/components/shared/ScoringPanelWithConfirm.vue'
|
|
|
+import ImagePreview from '@/components/shared/ImagePreview.vue'
|
|
|
+import SvgIcon from '@/components/common/SvgIcon.vue'
|
|
|
+
|
|
|
+import type { SetImgBgOption } from '@/hooks/useSetImgBg'
|
|
|
+import type { ExtractMultipleApiResponse, ExtractApiParams } from '@/api/api'
|
|
|
+import type { MarkHeaderInstance, EpFormItem, EpTableColumn } from 'global-type'
|
|
|
+import { Splitpanes, Pane } from 'splitpanes'
|
|
|
+// import 'splitpanes/dist/splitpanes.css'
|
|
|
+import { setPaneSize } from '@/utils/common'
|
|
|
+import useMainStore from '@/store/main'
|
|
|
+const mainStore = useMainStore()
|
|
|
+const paneSize = computed(() => {
|
|
|
+ return mainStore.paneSizeConfig[location.pathname] || 60
|
|
|
+})
|
|
|
+type RowType = ExtractMultipleApiResponse<'getAiCheckList'> & { index: number }
|
|
|
+
|
|
|
+const { fetch: getMarkScoreHistoryListWithTask, result: scoreHistoryList } = useFetch('getMarkScoreHistoryListWithTask')
|
|
|
+const currentHistoryData = computed(() => {
|
|
|
+ return scoreHistoryList.value
|
|
|
+})
|
|
|
+
|
|
|
+const currentHistoryColumns = computed<any>(() => [
|
|
|
+ { label: '评卷员', prop: 'markerName', fixed: 'left' },
|
|
|
+ {
|
|
|
+ label: `分数`,
|
|
|
+ prop: 'markScore',
|
|
|
+ width: 48,
|
|
|
+ formatter(row: any) {
|
|
|
+ return `${row.markScore === null ? '' : row.markScore}`
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '类型',
|
|
|
+ prop: 'historyType',
|
|
|
+ width: 104,
|
|
|
+ formatter(row: any) {
|
|
|
+ return `${row.historyType}${row.markScore === null ? '-问题卷' : ''}`
|
|
|
+ },
|
|
|
+ },
|
|
|
+ { label: '路径', prop: 'source' },
|
|
|
+ { label: '评卷时间', prop: 'markTime', width: 130 },
|
|
|
+])
|
|
|
+
|
|
|
+/** 给分板 */
|
|
|
+const editScoreVisible = ref<boolean>(true)
|
|
|
+
|
|
|
+/** 图片预览 */
|
|
|
+const previewModalVisible = ref<boolean>(false)
|
|
|
+
|
|
|
+/** 分数 */
|
|
|
+const modelScore = ref<number[]>([])
|
|
|
+
|
|
|
+const {
|
|
|
+ rotate,
|
|
|
+ scale,
|
|
|
+ center,
|
|
|
+ frontColor,
|
|
|
+ backgroundColor,
|
|
|
+ onBack,
|
|
|
+ onScaleChange,
|
|
|
+ onCenter,
|
|
|
+ onRotate,
|
|
|
+ setBackgroundColor,
|
|
|
+ setFrontColor,
|
|
|
+ onViewStandard,
|
|
|
+} = useMarkHeader()
|
|
|
+
|
|
|
+/** 刷新 */
|
|
|
+const onRefresh = () => {
|
|
|
+ onSearch()
|
|
|
+}
|
|
|
+
|
|
|
+/** 预览试卷 */
|
|
|
+const onPreview = () => {
|
|
|
+ previewModalVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const onEditScore = () => {
|
|
|
+ editScoreVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+type OperationClick = MarkHeaderInstance['onClick']
|
|
|
+
|
|
|
+type OperationType = Parameters<Exclude<OperationClick, undefined>>[0]['type']
|
|
|
+
|
|
|
+const operationHandles: Partial<Record<OperationType, (...args: any) => void>> = {
|
|
|
+ back: onBack,
|
|
|
+ 'scale-change': onScaleChange,
|
|
|
+ center: onCenter,
|
|
|
+ rotate: onRotate,
|
|
|
+ 'front-color': setFrontColor,
|
|
|
+ 'background-color': setBackgroundColor,
|
|
|
+ refresh: onRefresh,
|
|
|
+ standard: onViewStandard,
|
|
|
+}
|
|
|
+
|
|
|
+const onOperationClick: OperationClick = ({ type, value }) => {
|
|
|
+ operationHandles[type]?.(value)
|
|
|
+}
|
|
|
+
|
|
|
+const { subjectList, mainQuestionList, dataModel, changeModelValue, onOptionInit, isExpert, isLeader } = useOptions([
|
|
|
+ 'subject',
|
|
|
+ 'question',
|
|
|
+])
|
|
|
+
|
|
|
+const formModel = reactive<ExtractApiParams<'getAiCheckList'>>({
|
|
|
+ subjectCode: dataModel.subject || '',
|
|
|
+ mainNumber: dataModel.question,
|
|
|
+ checked: false,
|
|
|
+ rule: 'RULE1',
|
|
|
+ pageNumber: 1,
|
|
|
+ pageSize: 9999999,
|
|
|
+})
|
|
|
+
|
|
|
+watch(dataModel, () => {
|
|
|
+ formModel.subjectCode = dataModel.subject || ''
|
|
|
+ formModel.mainNumber = dataModel.question
|
|
|
+})
|
|
|
+
|
|
|
+const { defineColumn, _ } = useForm()
|
|
|
+
|
|
|
+const span10 = defineColumn(_, '', { span: 10 })
|
|
|
+const span12 = defineColumn(_, '', { span: 12 })
|
|
|
+
|
|
|
+const rules = ['仲裁分大于等于10分', '仲裁分与机评分差两档及以上'].map((v, i) => ({
|
|
|
+ label: v,
|
|
|
+ value: `RULE${i + 1}`,
|
|
|
+}))
|
|
|
+
|
|
|
+const formItems = computed<EpFormItem[]>(() => [
|
|
|
+ span10({
|
|
|
+ rowKey: 'row-1',
|
|
|
+ label: '科目',
|
|
|
+ prop: 'subjectCode',
|
|
|
+ slotType: 'select',
|
|
|
+ slot: { options: subjectList.value, onChange: changeModelValue('subject'), disabled: !isExpert.value },
|
|
|
+ }),
|
|
|
+ span10({
|
|
|
+ rowKey: 'row-1',
|
|
|
+ label: '大题',
|
|
|
+ prop: 'mainNumber',
|
|
|
+ slotType: 'select',
|
|
|
+ slot: {
|
|
|
+ options: mainQuestionList.value,
|
|
|
+ onChange: changeModelValue('question'),
|
|
|
+ disabled: !isExpert.value && !isLeader.value,
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ { rowKey: 'row-1', slotName: 'search', labelWidth: '10px', colProp: { span: 4 } },
|
|
|
+ span12({
|
|
|
+ rowKey: 'row-2',
|
|
|
+ label: '校验规则',
|
|
|
+ prop: 'rule',
|
|
|
+ slotType: 'select',
|
|
|
+ slot: {
|
|
|
+ options: rules,
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ span10({
|
|
|
+ rowKey: 'row-3',
|
|
|
+ label: '状态',
|
|
|
+ prop: 'checked',
|
|
|
+ slotType: 'select',
|
|
|
+ slot: {
|
|
|
+ options: [
|
|
|
+ { label: '未处理', value: false },
|
|
|
+ { label: '已处理', value: true },
|
|
|
+ { label: '全部', value: '' },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ }),
|
|
|
+])
|
|
|
+
|
|
|
+/** 主观题校验 */
|
|
|
+const columns: EpTableColumn<RowType>[] = [
|
|
|
+ { label: '序号', type: 'index', width: 60 },
|
|
|
+ { label: '密号', prop: 'secretNumber', minWidth: 110 },
|
|
|
+ { label: '大题名称', prop: 'mainName', minWidth: 100 },
|
|
|
+ {
|
|
|
+ label: '成绩',
|
|
|
+ prop: 'markScore',
|
|
|
+ minWidth: 60,
|
|
|
+ // formatter(row: any) {
|
|
|
+ // return row.headerScore || row.markScore
|
|
|
+ // },
|
|
|
+ },
|
|
|
+ { label: '处理结果', prop: 'status', minWidth: 100 },
|
|
|
+ { label: '处理人', prop: 'headerName', minWidth: 100 },
|
|
|
+ { label: '处理时间', prop: 'updateTime', minWidth: 130 },
|
|
|
+]
|
|
|
+
|
|
|
+const { fetch: getAiCheckList, result: aiCheckList, loading } = useFetch('getAiCheckList')
|
|
|
+
|
|
|
+const {
|
|
|
+ tableRef,
|
|
|
+ tableData,
|
|
|
+ current: currentAiCheck,
|
|
|
+ currentView: currentViewHistory,
|
|
|
+ next: checkNext,
|
|
|
+ visibleHistory,
|
|
|
+ onDbClick,
|
|
|
+ onCurrentChange,
|
|
|
+ nextRow,
|
|
|
+} = useTableCheck(aiCheckList)
|
|
|
+watch(currentAiCheck, () => {
|
|
|
+ // getMarkScoreHistoryListWithTask({ taskId: currentAiCheck.value.taskId })
|
|
|
+ if (currentAiCheck.value) {
|
|
|
+ getMarkScoreHistoryListWithTask({ taskId: currentAiCheck.value.taskId })
|
|
|
+
|
|
|
+ useFetch('viewActiveCheck').fetch({ taskId: currentAiCheck.value.taskId })
|
|
|
+ }
|
|
|
+})
|
|
|
+const statusText = ref<string>('未处理')
|
|
|
+
|
|
|
+const onSearch = async () => {
|
|
|
+ getAiCheckList(formModel).then(() => {
|
|
|
+ statusText.value = formModel.checked === true ? '已处理' : formModel.checked === false ? '未处理' : '全部'
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+/** 给分 */
|
|
|
+const { fetch: aiCheckMark } = useFetch('aiCheckMark')
|
|
|
+const onSubmit = async () => {
|
|
|
+ if (currentAiCheck.value) {
|
|
|
+ const scores = JSON.parse(JSON.stringify(modelScore.value))
|
|
|
+ await aiCheckMark({ taskId: currentAiCheck.value.taskId, scores: modelScore.value })
|
|
|
+ currentAiCheck.value.markScore = add(...scores)
|
|
|
+ ElMessage.success('修改成功')
|
|
|
+ // editScoreVisible.value = false
|
|
|
+ // onSearch()
|
|
|
+ nextRow()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** 确认 */
|
|
|
+const { fetch: aiCheckConfirm } = useFetch('aiCheckConfirm')
|
|
|
+
|
|
|
+const onConfirm = async () => {
|
|
|
+ if (currentAiCheck.value) {
|
|
|
+ await aiCheckConfirm({ taskId: currentAiCheck.value.taskId })
|
|
|
+ await onSearch()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+onOptionInit(onSearch)
|
|
|
+
|
|
|
+const imgOption = computed<SetImgBgOption>(() => {
|
|
|
+ return {
|
|
|
+ image: currentAiCheck?.value?.filePath,
|
|
|
+ rotate: rotate.value,
|
|
|
+ scale: scale.value,
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const { drawing, dataUrl } = useSetImgBg(imgOption, frontColor, setFrontColor)
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.mark-container {
|
|
|
+ .mark-content {
|
|
|
+ position: relative;
|
|
|
+ .preview {
|
|
|
+ position: absolute;
|
|
|
+ cursor: pointer;
|
|
|
+ top: 20px;
|
|
|
+ right: 25px;
|
|
|
+ font-size: 38px;
|
|
|
+ }
|
|
|
+ .next-button {
|
|
|
+ position: absolute;
|
|
|
+ right: -20px;
|
|
|
+ top: 300px;
|
|
|
+ }
|
|
|
+ .mark-content-paper {
|
|
|
+ img {
|
|
|
+ max-width: 100%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .table-view {
|
|
|
+ // width: 580px;
|
|
|
+ .bottom-title {
|
|
|
+ color: #000;
|
|
|
+ margin-top: 10px;
|
|
|
+ }
|
|
|
+ .detail-info-label {
|
|
|
+ .detail-info-label-num {
|
|
|
+ min-width: 32px;
|
|
|
+ height: 24px;
|
|
|
+ line-height: 24px;
|
|
|
+ background: #00987b;
|
|
|
+ border-radius: 4px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|