Browse Source

v1.1.0 coding...

刘洋 1 year ago
parent
commit
41d1994f37

+ 2 - 2
server.config.ts

@@ -3,11 +3,11 @@ import type { ServerOptions } from 'vite'
 const server: ServerOptions = {
   proxy: {
     '^/?(api|file)/': {
-      // target: 'http://192.168.10.41:8200',
+      target: 'http://192.168.10.41:8200',
       // target: 'http://192.168.10.178:8200',
       // target: 'http://192.168.10.108:8200',
       // target: 'http://cet-test.markingtool.cn',
-      target: 'http://192.168.10.136:80',
+      // target: 'http://192.168.10.136:80',
       // target: 'http://cet-dev.markingtool.cn:8200',
     },
   },

+ 4 - 0
src/api/api-types/exam.d.ts

@@ -55,6 +55,8 @@ export namespace Exam {
   /** 启用/禁用考试 */
   type ToggleEnableExam = BaseDefine<{ ids: number[]; enable: boolean }>
   type RecoveryTask = BaseDefine<any, any>
+  type GetSyncCount = BaseDefine<any, any>
+  type SyncSubmit = BaseDefine<any, any>
   export interface ApiMap {
     /** 删除考试 */
     deleteExam: DeleteExam
@@ -67,5 +69,7 @@ export namespace Exam {
     /** 启用/禁用考试 */
     toggleEnableExam: ToggleEnableExam
     recoveryTask: RecoveryTask
+    getSyncCount: GetSyncCount
+    syncSubmit: SyncSubmit
   }
 }

+ 1 - 0
src/api/api-types/question.d.ts

@@ -100,6 +100,7 @@ export namespace Question {
     subjectCode: string
     /** 成绩表对应题目类型,可用值:WRITING,TRANSLATION */
     category?: QuestionCategory
+    relationMainNumber?: any
   }
 
   /** 新增大题 */

+ 6 - 0
src/api/api-types/statistics.d.ts

@@ -552,6 +552,9 @@ export namespace Statistics {
 
   type GetProvinceProcess = BaseDefine<any, any>
   type GetCompareList = BaseDefine<any, any>
+  type GetAiCheckList = BaseDefine<any, any>
+  type AiCheckConfirm = BaseDefine<any, any>
+  type AiCheckMark = BaseDefine<any, any>
 
   interface ApiMap {
     /** 质量统计-自查一致性分析 */
@@ -568,10 +571,13 @@ export namespace Statistics {
     statisticCheckOverview: StatisticCheckOverview
     /** 质量统计-主观题校验 */
     getSubjectiveCheckList: GetSubjectiveCheckList
+    getAiCheckList: GetAiCheckList
     /** 质量统计-主观题校验打分 */
     subjectiveCheckMark: SubjectiveCheckMark
     /** 质量统计-主观题校验确认 */
     subjectiveCheckConfirm: SubjectiveCheckConfirm
+    aiCheckConfirm: AiCheckConfirm
+    aiCheckMark: AiCheckMark
     /** 决策分析-评卷进度统计(整体) */
     getMarkProgress: GetMarkProgress
     /** 决策分析-评卷进度统计(按小组) */

+ 2 - 0
src/api/exam.ts

@@ -13,6 +13,8 @@ const ExamApi: DefineApiModule<Exam.ApiMap> = {
   /** 启用/禁用考试 */
   toggleEnableExam: '/api/exam/toggle',
   recoveryTask: '/api/question/main/task/update',
+  getSyncCount: '/api/question/main/task/sync/count',
+  syncSubmit: '/api/question/main/task/sync',
 }
 
 export default ExamApi

+ 5 - 0
src/api/statistics.ts

@@ -28,10 +28,15 @@ const StatisticsApi: DefineApiModule<Statistics.ApiMap> = {
   statisticCheckOverview: '/api/statistic/check/overview',
   /** 质量统计-主观题校验 */
   getSubjectiveCheckList: '/api/subjective/check/page',
+  /** 质量统计-人机异常确认 */
+  getAiCheckList: '/api/ai/check/page',
   /** 质量统计-主观题校验打分 */
   subjectiveCheckMark: '/api/subjective/check/mark',
+  aiCheckMark: '/api/ai/check/mark',
   /** 质量统计-主观题校验确认 */
   subjectiveCheckConfirm: '/api/subjective/check/confirm',
+  /** 质量统计-人机异常确认 */
+  aiCheckConfirm: '/api/ai/check/confirm',
   /** 决策分析-评卷进度统计(整体) */
   getMarkProgress: '/api/statistic/marking/progress',
   /** 决策分析-评卷进度统计(按小组) */

+ 203 - 152
src/modules/admin-subject/edit-main-question/index.vue

@@ -21,7 +21,18 @@
           <el-button type="primary" link @click="expand = !expand">高级设置</el-button>
         </template>
         <template #form-item-operation>
-          <confirm-button ok-text="保存" @confirm="onSubmit" @cancel="onCancel"></confirm-button>
+          <confirm-button
+            :loading="adding || editing"
+            ok-text="保存"
+            @confirm="onSubmit"
+            @cancel="onCancel"
+          ></confirm-button>
+        </template>
+        <template #form-item-compare>
+          <el-select v-model="compare">
+            <el-option value="1" label="启用"></el-option>
+            <el-option value="0" label="不启用"></el-option>
+          </el-select>
         </template>
       </base-form>
     </el-card>
@@ -61,7 +72,7 @@
 /** 添加大题 */
 import { computed, reactive, ref } from 'vue'
 import { useRouter } from 'vue-router'
-import { ElCard, ElButton, ElMessage, ElInput, ElInputNumber } from 'element-plus'
+import { ElCard, ElButton, ElMessage, ElInput, ElInputNumber, ElSelect, ElOption } from 'element-plus'
 import { omit } from 'lodash-es'
 import ConfirmButton from '@/components/common/ConfirmButton.vue'
 import BaseDialog from '@/components/element/BaseDialog.vue'
@@ -72,11 +83,22 @@ import useVW from '@/hooks/useVW'
 
 import type { ExtractApiParams } from '@/api/api'
 import type { EpFormItem, EpFormRules, FormGroup } from 'global-type'
-
+const { fetch: getMainQuestionList, result: mainQuestionListResult } = useFetch('getMainQuestionList')
 const { back } = useRouter()
 
 const props = defineProps<{ subjectCode: string; mainNumber?: number | string }>()
-
+getMainQuestionList({ subjectCode: props.subjectCode })
+const mainQuestionOptions = computed(() => {
+  return (
+    mainQuestionListResult.value?.map((v: any) => {
+      return {
+        ...v,
+        label: `${v.mainNumber}-${v.title}`,
+        value: v.mainNumber,
+      }
+    }) || []
+  )
+})
 const isEdit = !!props.mainNumber
 
 // const levelRanges = [
@@ -126,6 +148,7 @@ const model = reactive<ExtractApiParams<'addMainQuestion'>>({
   selfRate: void 0,
   systemRate: void 0,
   startNumber: 1,
+  relationMainNumber: void 0,
 })
 
 const levelRangeView = computed(() => {
@@ -169,10 +192,12 @@ const rules = computed<EpFormRules>(() => {
     remarkNumber: [{ required: true, message: '请输入回评设置' }],
     startNumber: [{ required: true, message: '请输入小题起始号' }],
     systemRate: [{ required: !!model.levelRange?.filter(Boolean)?.length, message: '请设置系统抽查比例' }],
+    relationMainNumber: [{ required: true, message: '请选择关联大题' }],
   }
 })
 
 const expand = ref<boolean>(false)
+const compare = ref('0')
 
 const groups = computed<FormGroup[]>(() => {
   return [
@@ -180,7 +205,7 @@ const groups = computed<FormGroup[]>(() => {
       rowKeys: Array.from({ length: 10 }).map((_, i) => `row-${i + 1}`),
     },
     {
-      rowKeys: ['row-11', 'row-12'],
+      rowKeys: ['row-11', 'row-12', 'row-13'],
       groupTitle: '高级设置',
       hidden: !expand.value,
     },
@@ -193,160 +218,186 @@ const groups = computed<FormGroup[]>(() => {
 
 const Span8 = defineColumn(_, _, { span: 8 })
 
-const items = computed<EpFormItem[]>(() => [
-  Span8(
-    {
-      label: '大题号',
-      slotType: 'inputNumber',
-      prop: 'mainNumber',
-      slot: {
-        placeholder: '大题号',
-        disabled: isEdit,
+const items = computed<any>(() =>
+  [
+    Span8(
+      {
+        label: '大题号',
+        slotType: 'inputNumber',
+        prop: 'mainNumber',
+        slot: {
+          placeholder: '大题号',
+          disabled: isEdit,
+        },
       },
-    },
-    'row-1'
-  ),
-  Span8({ label: '大题名称', slotType: 'input', prop: 'mainTitle', slot: { placeholder: '大题名称' } }, 'row-2'),
-  Span8(
-    {
-      label: '成绩表对应字段',
-      slotType: 'select',
-      prop: 'category',
-      slot: {
-        placeholder: '成绩表对应字段',
-        clearable: true,
-        options: [
-          { label: '作文分', value: 'WRITING' },
-          { label: '翻译分', value: 'TRANSLATION' },
-        ],
+      'row-1'
+    ),
+    Span8({ label: '大题名称', slotType: 'input', prop: 'mainTitle', slot: { placeholder: '大题名称' } }, 'row-2'),
+    Span8(
+      {
+        label: '成绩表对应字段',
+        slotType: 'select',
+        prop: 'category',
+        slot: {
+          placeholder: '成绩表对应字段',
+          clearable: true,
+          options: [
+            { label: '作文分', value: 'WRITING' },
+            { label: '翻译分', value: 'TRANSLATION' },
+          ],
+        },
       },
-    },
-    'row-2'
-  ),
-  Span8(
-    {
-      label: '小题起始号',
-      slotType: 'inputNumber',
-      prop: 'startNumber',
-      slot: { placeholder: '小题起始号', disabled: isEdit, stepStrictly: true, step: 1, min: 0, max: 999999 },
-    },
-    'row-3'
-  ),
-  Span8(
-    {
-      label: '小题数量',
-      slotType: 'inputNumber',
-      prop: 'questionCount',
-      slot: { placeholder: '小题数量', stepStrictly: true, step: 1, min: 0, max: 999999 },
-    },
-    'row-4'
-  ),
-  Span8(
-    {
-      label: '小题满分',
-      slotType: 'inputNumber',
-      prop: 'questionScore',
-      slot: { placeholder: '小题满分', stepStrictly: true, step: 1, min: 0, max: 999999 },
-    },
-    'row-5'
-  ),
-  Span8(
-    {
-      label: '间隔分',
-      slotType: 'inputNumber',
-      prop: 'intervalScore',
-      slot: { placeholder: '间隔分', stepStrictly: true, step: 1, min: 0, max: 999999 },
-    },
-    'row-6'
-  ),
-  Span8(
-    {
-      label: '最小阅卷时长(秒)',
-      slotType: 'inputNumber',
-      prop: 'minMarkTime',
-      slot: { placeholder: '最小阅卷时长(秒)', stepStrictly: true, step: 1, min: 0, max: 9999999 },
-    },
-    'row-7'
-  ),
-  Span8(
-    {
-      label: '评卷小组数量',
-      slotType: 'inputNumber',
-      prop: 'groupNumber',
-      slot: { placeholder: '评卷小组数量', stepStrictly: true, step: 1, min: 0, max: 999999 },
-    },
-    'row-8'
-  ),
-  Span8(
-    {
-      label: '回评设置',
-      slotType: 'select',
-      prop: 'remarkType',
-      slot: {
-        placeholder: '回评设置',
-        disabled: isEdit,
-        options: [
-          { value: 'QUANTITY', label: '按数量' },
-          { value: 'TIME', label: '按时间' },
-        ],
+      'row-2'
+    ),
+    Span8(
+      {
+        label: '小题起始号',
+        slotType: 'inputNumber',
+        prop: 'startNumber',
+        slot: { placeholder: '小题起始号', disabled: isEdit, stepStrictly: true, step: 1, min: 0, max: 999999 },
       },
-    },
-    'row-9'
-  ),
-  Span8(
-    {
-      label: model.remarkType === 'QUANTITY' ? '数量' : '时间:(近N分钟)',
-      prop: 'remarkNumber',
-      slotType: 'inputNumber',
-      slot: {
-        stepStrictly: true,
-        step: 1,
-        min: 0,
-        max: 120,
+      'row-3'
+    ),
+    Span8(
+      {
+        label: '小题数量',
+        slotType: 'inputNumber',
+        prop: 'questionCount',
+        slot: { placeholder: '小题数量', stepStrictly: true, step: 1, min: 0, max: 999999 },
       },
-    },
-    'row-10'
-  ),
-  Span8(
-    {
-      label: '标准卷分发频度',
-      slotType: 'inputNumber',
-      prop: 'standardRate',
-      slot: { placeholder: '标准卷分发频度', stepStrictly: true, step: 1 },
-    },
-    'row-11'
-  ),
-  Span8(
-    {
-      label: '自查卷分发频度',
-      slotType: 'inputNumber',
-      prop: 'selfRate',
-      slot: { placeholder: '自查卷分发频度', stepStrictly: true, step: 1 },
-    },
-    'row-11'
-  ),
-  Span8(
-    {
-      label: '系统抽查卷比例',
-      slotType: 'inputNumber',
-      prop: 'systemRate',
-      slot: { placeholder: '系统抽查卷比例', stepStrictly: true, step: 1 },
-    },
-    'row-12'
-  ),
-  Span8(
-    {
-      label: '档次抽查比例',
-      slotName: 'setLevelRange',
-    },
-    'row-12'
-  ),
-  Span8({ slotName: 'expand' }, 'expand'),
-  Span8({ slotName: 'operation' }, 'operation'),
-])
+      'row-4'
+    ),
+    Span8(
+      {
+        label: '小题满分',
+        slotType: 'inputNumber',
+        prop: 'questionScore',
+        slot: { placeholder: '小题满分', stepStrictly: true, step: 1, min: 0, max: 999999 },
+      },
+      'row-5'
+    ),
+    Span8(
+      {
+        label: '间隔分',
+        slotType: 'inputNumber',
+        prop: 'intervalScore',
+        slot: { placeholder: '间隔分', stepStrictly: true, step: 1, min: 0, max: 999999 },
+      },
+      'row-6'
+    ),
+    Span8(
+      {
+        label: '最小阅卷时长(秒)',
+        slotType: 'inputNumber',
+        prop: 'minMarkTime',
+        slot: { placeholder: '最小阅卷时长(秒)', stepStrictly: true, step: 1, min: 0, max: 9999999 },
+      },
+      'row-7'
+    ),
+    Span8(
+      {
+        label: '评卷小组数量',
+        slotType: 'inputNumber',
+        prop: 'groupNumber',
+        slot: { placeholder: '评卷小组数量', stepStrictly: true, step: 1, min: 0, max: 999999 },
+      },
+      'row-8'
+    ),
+    Span8(
+      {
+        label: '回评设置',
+        slotType: 'select',
+        prop: 'remarkType',
+        slot: {
+          placeholder: '回评设置',
+          disabled: isEdit,
+          options: [
+            { value: 'QUANTITY', label: '按数量' },
+            { value: 'TIME', label: '按时间' },
+          ],
+        },
+      },
+      'row-9'
+    ),
+    Span8(
+      {
+        label: model.remarkType === 'QUANTITY' ? '数量' : '时间:(近N分钟)',
+        prop: 'remarkNumber',
+        slotType: 'inputNumber',
+        slot: {
+          stepStrictly: true,
+          step: 1,
+          min: 0,
+          max: 120,
+        },
+      },
+      'row-10'
+    ),
+    Span8(
+      {
+        label: '标准卷分发频度',
+        slotType: 'inputNumber',
+        prop: 'standardRate',
+        slot: { placeholder: '标准卷分发频度', stepStrictly: true, step: 1 },
+      },
+      'row-11'
+    ),
+    Span8(
+      {
+        label: '自查卷分发频度',
+        slotType: 'inputNumber',
+        prop: 'selfRate',
+        slot: { placeholder: '自查卷分发频度', stepStrictly: true, step: 1 },
+      },
+      'row-11'
+    ),
+    Span8(
+      {
+        label: '系统抽查卷比例',
+        slotType: 'inputNumber',
+        prop: 'systemRate',
+        slot: { placeholder: '系统抽查卷比例', stepStrictly: true, step: 1 },
+      },
+      'row-12'
+    ),
+    Span8(
+      {
+        label: '档次抽查比例',
+        slotName: 'setLevelRange',
+      },
+      'row-12'
+    ),
+    Span8(
+      {
+        label: '人机抽查比对',
+        slotName: 'compare',
+      },
+      'row-13'
+    ),
+    compare.value == '1'
+      ? Span8(
+          {
+            label: '关联大题',
+            slotType: 'select',
+            prop: 'relationMainNumber',
+            slot: {
+              clearable: true,
+              options: mainQuestionOptions.value,
+            },
+          },
+          'row-13'
+        )
+      : null,
+    Span8({ slotName: 'expand' }, 'expand'),
+    Span8({ slotName: 'operation' }, 'operation'),
+  ].filter((v) => !!v)
+)
 
 if (isEdit) {
   getMainQuestionInfo({ subjectCode: props.subjectCode, mainNumber: +props.mainNumber }).then((result) => {
+    if (!!result.relationMainNumber) {
+      compare.value = '1'
+    }
     if (result.remarkType === 'TIME') {
       result.remarkNumber = (result.remarkNumber || 0) / 60
     }

+ 76 - 9
src/modules/admin-subject/struct/index.vue

@@ -14,6 +14,15 @@
     <div class="flex-1 p-base">
       <el-card shadow="never">
         <base-table size="small" border stripe :data="tableData" :columns="columns">
+          <template #column-mainNumber="{ row }">
+            <p v-if="!!row.relationMainNumber" style="display: flex; align-items: center; justify-content: center">
+              <span>{{ row.mainNumber }}</span>
+              <el-icon :size="16" color="#0091ff" style="margin-left: 2px">
+                <flag />
+              </el-icon>
+            </p>
+            <span v-else>{{ row.mainNumber }}</span>
+          </template>
           <template #column-operation="{ row }">
             <el-button type="primary" link @click="onEditSubQuestion(row)">编辑</el-button>
             <el-popconfirm :width="'220px'" hide-icon :title="`确认删除题目?`" @confirm="onDelete(row)">
@@ -23,6 +32,7 @@
             </el-popconfirm>
             <el-button v-if="row.firstMain" type="primary" link @click="onEditMainQuestion(row)">大题设置</el-button>
             <el-button v-if="row.firstMain" type="primary" link @click="recovery(row)">手动回收</el-button>
+            <el-button v-if="!row.relationMainNumber" type="primary" link @click="syncTask(row)">同步任务</el-button>
           </template>
         </base-table>
       </el-card>
@@ -43,31 +53,42 @@
         </el-form-item>
       </base-form>
     </base-dialog>
+    <base-dialog v-model="showSyncTaskDialog" destroy-on-close :width="400" title="同步任务">
+      <div class="dialog-text">
+        待同步任务:<span class="red">{{ syncCount?.count }}</span>
+      </div>
+      <div class="dialog-text">是否确认从关联大题中 <b>同步人机仲裁</b> 任务</div>
+      <template #footer>
+        <div class="flex justify-center">
+          <confirm-button @confirm="syncSubmit" @cancel="showSyncTaskDialog = false"></confirm-button>
+        </div>
+      </template>
+    </base-dialog>
   </div>
 </template>
 
-<script setup lang="ts" name="SubjectStructManage">
+<script setup lang="tsx" name="SubjectStructManage">
 /** 科目试卷结构管理 */
 import { reactive, ref, computed } from 'vue'
 import { useRouter } from 'vue-router'
-import { ElButton, ElCard, ElFormItem, ElPopconfirm, ElMessage } from 'element-plus'
+import { ElButton, ElCard, ElFormItem, ElPopconfirm, ElMessage, ElIcon } from 'element-plus'
 import ConfirmButton from '@/components/common/ConfirmButton.vue'
 import BaseTable from '@/components/element/BaseTable.vue'
 import BaseForm from '@/components/element/BaseForm.vue'
 import BaseDialog from '@/components/element/BaseDialog.vue'
 import useFetch from '@/hooks/useFetch'
 import useForm from '@/hooks/useForm'
-import useVW from '@/hooks/useVW'
-
+import { Flag } from '@element-plus/icons-vue'
 import type { EpTableColumn, EpFormItem, EpFormRules } from 'global-type'
 import type { ExtractApiResponse } from '@/api/api'
-
+const showSyncTaskDialog = ref(false)
 const { back, push } = useRouter()
 
 const props = defineProps<{ id: number | string }>()
 
 const { fetch: getSubjectInfo, result: subjectInfo } = useFetch('getSubjectInfo')
 const { fetch: getSubQuestionList, result: subQuestionList } = useFetch('getSubQuestionList')
+const { fetch: getSyncCount, result: syncCount } = useFetch('getSyncCount')
 
 type SubQuestion = ExtractArrayValue<ExtractApiResponse<'getSubQuestionList'>>
 type WithFirstMainTag = SubQuestion & { firstMain: boolean }
@@ -83,12 +104,28 @@ const tableData = computed<any>(() => {
 })
 
 const columns: EpTableColumn[] = [
-  { label: '大题号', prop: 'mainNumber' },
+  {
+    label: '大题号',
+    // prop: 'mainNumber',
+    slotName: 'mainNumber',
+    // formatter(row: any) {
+    //   return row.relationMainNumber ? (
+    //     row.mainNumber
+    //   ) : (
+    //     <p style="display:flex;align-items:center;justify-content:center">
+    //       <span>{row.mainNumber}</span>
+    //       <el-icon>
+    //         <Flag />
+    //       </el-icon>
+    //     </p>
+    //   )
+    // },
+  },
   { label: '大题名称', prop: 'mainTitle' },
   { label: '小题号', prop: 'subNumber' },
   { label: '小题满分', prop: 'totalScore' },
   { label: '间隔分', prop: 'intervalScore' },
-  { label: '操作', slotName: 'operation', width: 262 },
+  { label: '操作', slotName: 'operation', width: 320 },
 ]
 
 if (props.id) {
@@ -117,7 +154,26 @@ function recovery(row: any) {
       ElMessage.success(`回收成功`)
     })
 }
-
+const curRow = ref<any>()
+function syncTask(row: any) {
+  getSyncCount({
+    mainNumber: row.mainNumber,
+    subjectCode: subjectInfo.value.code,
+  }).then(() => {
+    showSyncTaskDialog.value = true
+    curRow.value = row
+  })
+}
+const { fetch: syncFetch, loading: syncLoading } = useFetch('syncSubmit')
+const syncSubmit = () => {
+  syncFetch({
+    mainNumber: curRow.value.mainNumber,
+    subjectCode: subjectInfo.value.code,
+  }).then(() => {
+    showSyncTaskDialog.value = false
+    ElMessage.success(`同步成功`)
+  })
+}
 const { fetch: editSubQuestion, loading: saving } = useFetch('editSubQuestion')
 
 const editSubInfo = reactive<Partial<ExtractArrayValue<ExtractApiResponse<'getSubQuestionList'>>>>({})
@@ -177,4 +233,15 @@ function onDelete(row: ExtractArrayValue<ExtractApiResponse<'getSubQuestionList'
 }
 </script>
 
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+.dialog-text {
+  font-size: 14px;
+  color: #333;
+  line-height: 1.8;
+  .red {
+    color: red;
+    font-size: 16px;
+    font-weight: bold;
+  }
+}
+</style>

+ 9 - 2
src/modules/analysis/marker-statistics/index.vue

@@ -173,7 +173,7 @@ import SvgIcon from '@/components/common/SvgIcon.vue'
 import useMainStore from '@/store/main'
 import ScoringPanelWithConfirm from '@/components/shared/ScoringPanelWithConfirm.vue'
 import ImagePreview from '@/components/shared/ImagePreview.vue'
-import BtnPagination from '@/components/common/BtnPagination'
+import BtnPagination from '@/components/common/BtnPagination.vue'
 import { ElIcon } from 'element-plus'
 import { Close } from '@element-plus/icons-vue'
 import type { EChartsOption } from 'echarts'
@@ -253,13 +253,20 @@ const { fetch: updatePersonalMarkDetailScore } = useFetch('updatePersonalMarkDet
 const onSubmit = async () => {
   if (current.value) {
     const scores = JSON.parse(JSON.stringify(modelScore.value))
-    await updatePersonalMarkDetailScore({
+    let res: any = await updatePersonalMarkDetailScore({
       taskId: current.value.taskId,
       scores: modelScore.value,
       source: (query.source as string) || '',
     })
     // current.value.markerScore = add(...scores)
     current.value.markScore = add(...scores)
+    current.value.checked = res.checked
+    current.value.corrected = res.corrected
+    current.value.markerScore = res.markerScore
+    current.value.objectiveScore = res.objectiveScore
+    current.value.markerRatio = res.ratio
+    current.value.markTime = res.markTime
+    current.value.markerScore = res.markerScore
     ElMessage.success('修改成功')
     // editScoreVisible.value = false
     // onRefresh()

+ 9 - 3
src/modules/analysis/view-marked-detail/index.vue

@@ -140,7 +140,7 @@ import type { MarkHeaderInstance, EpTableColumn } from 'global-type'
 import { Splitpanes, Pane } from 'splitpanes'
 import { setPaneSize } from '@/utils/common'
 import useMainStore from '@/store/main'
-import BtnPagination from '@/components/common/BtnPagination'
+import BtnPagination from '@/components/common/BtnPagination.vue'
 type RowType = ExtractMultipleApiResponse<'getPersonalMarkDetail'> & { index: number }
 const mainStore = useMainStore()
 const paneSize = computed(() => {
@@ -277,13 +277,19 @@ const { fetch: updatePersonalMarkDetailScore } = useFetch('updatePersonalMarkDet
 const onSubmit = async () => {
   if (current.value) {
     const scores = JSON.parse(JSON.stringify(modelScore.value))
-    await updatePersonalMarkDetailScore({
+    let res: any = await updatePersonalMarkDetailScore({
       taskId: current.value.taskId,
       scores: modelScore.value,
       source: (query.source as string) || '',
     })
-    // current.value.markerScore = add(...scores)
     current.value.markScore = add(...scores)
+    current.value.checked = res.checked
+    current.value.corrected = res.corrected
+    current.value.markerScore = res.markerScore
+    current.value.objectiveScore = res.objectiveScore
+    current.value.markerRatio = res.ratio
+    current.value.markTime = res.markTime
+    current.value.markerScore = res.markerScore
     ElMessage.success('修改成功')
     // editScoreVisible.value = false
     // onRefresh()

+ 11 - 1
src/modules/marking/inquiry-result/index.vue

@@ -446,10 +446,20 @@ const onSubmit = async () => {
   if (current.value) {
     const scores = JSON.parse(JSON.stringify(modelScore.value))
 
-    await useFetch('updateCustomPaperScore').fetch({ taskId: current.value.taskId, scores: modelScore.value })
+    let res: any = await useFetch('updateCustomPaperScore').fetch({
+      taskId: current.value.taskId,
+      scores: modelScore.value,
+    })
 
     current.value.markScore = current.value.headerScore = add(...scores)
     current.value.markScores = scores
+    current.value.checked = res.checked
+    current.value.corrected = res.corrected
+    current.value.headerScore = res.headerScore
+    current.value.objectiveScore = res.objectiveScore
+    current.value.ratio = res.ratio
+    current.value.markTime = res.markTime
+    current.value.markerScore = res.markerScore
     // onRefresh()
     ElMessage.success('修改成功')
     // editScoreVisible.value = false

+ 6 - 1
src/modules/monitor/system-check/index.vue

@@ -368,9 +368,14 @@ const { fetch: markSystemSpotPaper } = useFetch('markSystemSpotPaper')
 const onSubmit = async () => {
   if (currentSystemCheckPaper.value) {
     const scores = JSON.parse(JSON.stringify(modelScore.value))
-    await markSystemSpotPaper({ id: currentSystemCheckPaper.value.id, scores: modelScore.value })
+    let res: any = await markSystemSpotPaper({ id: currentSystemCheckPaper.value.id, scores: modelScore.value })
     currentSystemCheckPaper.value.markScore = currentSystemCheckPaper.value.headerScore = add(...scores)
     currentSystemCheckPaper.value.markScores = scores
+    currentSystemCheckPaper.value.checked = res.checked
+    currentSystemCheckPaper.value.corrected = res.corrected
+    currentSystemCheckPaper.value.objectiveScore = res.objectiveScore
+    currentSystemCheckPaper.value.ratio = res.ratio
+    currentSystemCheckPaper.value.markerScore = res.markerScore
     ElMessage.success('修改成功')
     // scoringPanelVisible.value = false
     // onSearch()

+ 429 - 0
src/modules/quality/ai-check/index.vue

@@ -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>

+ 11 - 0
src/router/quality.ts

@@ -65,6 +65,17 @@ const routes: RouteRecordRaw[] = [
       sort: 2,
     },
   },
+  {
+    name: 'QualityAiCheck',
+    path: '/quality/ai-check',
+    component: () => import('@/modules/quality/ai-check/index.vue'),
+    meta: {
+      label: '人机异常确认',
+      menu: true,
+      menuId: 'quality-ai_check',
+      sort: 5,
+    },
+  },
   {
     name: 'QualitySelfCheckDetail',
     path: '/self-check-detail',