Эх сурвалжийг харах

refactor: refactor useMarkHeader

chenhao 2 жил өмнө
parent
commit
41b87a2e60

+ 14 - 0
src/api/statistics.ts

@@ -10,6 +10,13 @@ const StatisticsApi: DefineApiModule<Statistics.ApiMap> = {
   },
   /** 质量统计-自查一致性分析-离差列表 */
   /** 质量统计-抽查情况统计 */
+  statisticCheckOverview: '/api/statistic/check/overview',
+  /** 质量统计-主观题校验 */
+  getSubjectiveCheckList: '/api/subjective/check/page',
+  /** 质量统计-主观题校验打分 */
+  subjectiveCheckMark: '/api/subjective/check/mark',
+  /** 质量统计-主观题校验确认 */
+  subjectiveCheckConfirm: '/api/subjective/check/confirm',
   /** 决策分析-评卷进度统计(整体) */
   getMarkProgress: '/api/statistic/marking/progress',
   /** 决策分析-评卷进度统计(按小组) */
@@ -22,6 +29,13 @@ const StatisticsApi: DefineApiModule<Statistics.ApiMap> = {
     download: true,
   },
   /** 质量统计-科目进度收尾 */
+  subjectProgressEnd: '/api/statistic/marking/progress/ending',
+  /** 质量统计-收尾检查-评卷员未评卷列表(分页) */
+  unMarkPaperList: '/api/statistic/marking/progress/check/for/unmark/list',
+  /** 质量统计-收尾检查-未处理雷同卷列表 */
+  unProcessSimilarList: '/api/statistic/marking/progress/check/for/same/paper/list',
+  /** 质量统计-收尾检查-未处理问题卷列表 */
+  unProcessProblemList: '/api/statistic/marking/progress/check/for/problem/paper/list',
   /** 决策分析-小组监控 */
   getGroupMonitor: {
     url: '/api/statistic/monitor/for/group',

+ 4 - 3
src/api/user.ts

@@ -9,9 +9,10 @@ const UserApi: DefineApiModule<User.ApiMap> = {
   /** 登出 */
   userLogout: '/api/auth/logout',
   /** 导入用户 */
-  importUser: '/api/user/import',
-  /** 导入用户 */
-  exportUser: '/api/user/export',
+  exportUser: {
+    url: '/api/user/export',
+    download: true,
+  },
   /** 下载用户导入模板 */
   downloadUserTemplate: '/api/user/import/template',
   /** 查询当前用户信息 */

+ 9 - 1
src/components/common/ConfirmButton.vue

@@ -1,5 +1,11 @@
 <template>
-  <div class="flex items-center" :class="{ 'justify-between': props.between }">
+  <div
+    class="flex items-center"
+    :class="{
+      'justify-between': props.between,
+      'justify-around': props.around,
+    }"
+  >
     <el-button type="primary" :loading="props.loading" :size="size" @click="emit('confirm')">
       {{ props.okText }}
     </el-button>
@@ -19,12 +25,14 @@ const props = withDefaults(
     size?: 'large' | 'default' | 'small'
     loading?: boolean
     between?: boolean
+    around?: boolean
   }>(),
   {
     okText: '确认',
     cancelText: '取消',
     size: 'default',
     between: false,
+    around: false,
   }
 )
 

+ 24 - 17
src/components/shared/MarkHeader.vue

@@ -51,11 +51,13 @@ interface HeaderButton {
   type: ButtonType
 }
 
+type EventType = Exclude<ButtonType, 'scale-up' | 'scale-down'> | 'scale-change'
+
 const emits = defineEmits<{
-  (e: 'click', opt: { type: ButtonType; value?: number | string | number[] }): void
-  (e: ButtonType, val?: number): void
+  (e: 'click', opt: { type: EventType; value?: number | string | boolean | number[] }): void
+  (e: EventType, val: any): void
   (e: 'back'): void
-  (e: 'center'): void
+  (e: 'center', val: boolean): void
   (e: 'refresh'): void
   (e: 'remark'): void
   (e: 'problem'): void
@@ -63,8 +65,7 @@ const emits = defineEmits<{
   (e: 'standard'): void
   (e: 'delete'): void
   (e: 'bookmark'): void
-  (e: 'scale-up', val: number): void
-  (e: 'scale-down', val: number): void
+  (e: 'scale-change', val: number): void
   (e: 'rotate', val: number): void
   (e: 'front-color', color: number[]): void
   (e: 'background-color', color: number[]): void
@@ -109,6 +110,9 @@ const usedOperations = computed(() => {
 
 const attrs = useAttrs()
 
+/** center */
+const center = ref<boolean>(false)
+
 /** ratio */
 const ratio = ref<number>(1)
 
@@ -120,13 +124,15 @@ const frontColor = ref<string>('')
 /** background-color */
 const backgroundColor = ref<string>('')
 
-watch(frontColor, () => {
-  emitEvent('front-color', frontColor.value)
-})
+watch(center, () => emitEvent('center', center.value))
 
-watch(backgroundColor, () => {
-  emitEvent('background-color', backgroundColor.value)
-})
+watch(ratio, () => emitEvent('scale-change', ratio.value))
+
+watch(rotate, () => emitEvent('rotate', rotate.value))
+
+watch(frontColor, () => emitEvent('front-color', frontColor.value))
+
+watch(backgroundColor, () => emitEvent('background-color', backgroundColor.value))
 
 const validVal = (val: number, max: number, min: number) => {
   return Math.min(max, Math.max(val, min))
@@ -136,22 +142,23 @@ const onOperationClick = (button: HeaderButton) => {
   if (['front-color', 'background-color'].includes(button.type)) {
     return
   }
-  let val
   switch (button.type) {
     case 'scale-up':
-      val = ratio.value = validVal(ratio.value + 0.1, 2, 0.5)
+      ratio.value = validVal(ratio.value + 0.1, 2, 0.5)
       break
     case 'scale-down':
-      val = ratio.value = validVal(ratio.value - 0.1, 2, 0.5)
+      ratio.value = validVal(ratio.value - 0.1, 2, 0.5)
       break
     case 'rotate':
-      val = rotate.value = (rotate.value + 90) % 360
+      rotate.value = (rotate.value + 90) % 360
+      break
+    default:
+      emitEvent(button.type)
       break
   }
-  emitEvent(button.type, val)
 }
 
-const emitEvent = (type: ButtonType, val?: string | number | number[]) => {
+const emitEvent = (type: EventType, val?: string | number | boolean | number[]) => {
   if (attrs['on' + type.replace(/^\S/, (s) => s.toUpperCase())]) {
     emits(type, val as any)
   }

+ 3 - 9
src/hooks/useMarkHeader.ts

@@ -23,13 +23,8 @@ const useMarkHeader = () => {
     back()
   }
 
-  /** 放大 */
-  const onScaleUp = (val: number) => {
-    imgOperation.scale = val
-  }
-
-  /** 缩小 */
-  const onScaleDown = (val: number) => {
+  /** 放大/缩小 */
+  const onScaleChange = (val: number) => {
     imgOperation.scale = val
   }
 
@@ -66,8 +61,7 @@ const useMarkHeader = () => {
   return {
     ...toRefs(imgOperation),
     onBack,
-    onScaleUp,
-    onScaleDown,
+    onScaleChange,
     onCenter,
     onRotate,
     setFrontColor,

+ 1 - 7
src/modules/admin-user/manage/index.vue

@@ -57,7 +57,6 @@
             </template>
           </el-popconfirm>
           <el-button v-else size="small" type="primary" @click="checkSelected('disabled')">禁用</el-button>
-          <el-button size="small" custom-1 @click="onImportUsers">导入</el-button>
           <el-button size="small" custom-1 @click="onExportUsers">导出</el-button>
         </div>
         <base-table
@@ -196,14 +195,9 @@ async function handleDisableUsers() {
   }
 }
 
-/** 导入按钮 */
-function onImportUsers() {
-  console.log('导入用户')
-}
-
 /** 导出按钮 */
 function onExportUsers() {
-  console.log('导出用户')
+  useFetch('exportUser').fetch(requestModel)
 }
 
 /** table column 修改用户 */

+ 11 - 1
src/modules/analysis/marker-statistics/index.vue

@@ -1,10 +1,20 @@
 <template>
-  <div class="">评卷员明细统计</div>
+  <div class="full flex direction-column">
+    <div class="p-t-base p-l-base fill-blank">
+      <!-- <base-form :items="items" :model="model" :label-width="useVW(66)" size="small">
+        <template #form-item-button>
+          <el-button type="primary" @click="onSearch">查询</el-button>
+          <el-button special @click="onCreateExam">创建考试</el-button>
+        </template>
+      </base-form> -->
+    </div>
+  </div>
 </template>
 
 <script setup lang="ts" name="MarkerStatistics">
 /** 评卷员明细统计 */
 import { reactive, ref } from 'vue'
+import BaseForm from '@/components/element/BaseForm.vue'
 </script>
 
 <style scoped lang="scss"></style>

+ 2 - 4
src/modules/expert/assess/index.vue

@@ -98,8 +98,7 @@ const {
   frontColor,
   backgroundColor,
   onBack,
-  onScaleUp,
-  onScaleDown,
+  onScaleChange,
   onCenter,
   onRotate,
   setBackgroundColor,
@@ -138,8 +137,7 @@ type OperationType = Parameters<Exclude<OperationClick, undefined>>[0]['type']
 
 const operationHandles: Partial<Record<OperationType, (...args: any) => void>> = {
   back: onBack,
-  'scale-up': onScaleUp,
-  'scale-down': onScaleDown,
+  'scale-change': onScaleChange,
   center: onCenter,
   rotate: onRotate,
   'front-color': setFrontColor,

+ 2 - 4
src/modules/expert/expert/index.vue

@@ -98,8 +98,7 @@ const {
   frontColor,
   backgroundColor,
   onBack,
-  onScaleUp,
-  onScaleDown,
+  onScaleChange,
   onCenter,
   onRotate,
   setBackgroundColor,
@@ -145,8 +144,7 @@ type OperationType = Parameters<Exclude<OperationClick, undefined>>[0]['type']
 
 const operationHandles: Partial<Record<OperationType, (...args: any) => void>> = {
   back: onBack,
-  'scale-up': onScaleUp,
-  'scale-down': onScaleDown,
+  'scale-change': onScaleChange,
   center: onCenter,
   rotate: onRotate,
   'front-color': setFrontColor,

+ 2 - 4
src/modules/expert/sample/index.vue

@@ -120,8 +120,7 @@ const {
   frontColor,
   backgroundColor,
   onBack,
-  onScaleUp,
-  onScaleDown,
+  onScaleChange,
   onCenter,
   onRotate,
   setBackgroundColor,
@@ -160,8 +159,7 @@ type OperationType = Parameters<Exclude<OperationClick, undefined>>[0]['type']
 
 const operationHandles: Partial<Record<OperationType, (...args: any) => void>> = {
   back: onBack,
-  'scale-up': onScaleUp,
-  'scale-down': onScaleDown,
+  'scale-change': onScaleChange,
   center: onCenter,
   rotate: onRotate,
   'front-color': setFrontColor,

+ 3 - 4
src/modules/expert/training/index.vue

@@ -60,6 +60,7 @@ 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 ImagePreview from '@/components/shared/ImagePreview.vue'
 import SvgIcon from '@/components/common/SvgIcon.vue'
 
 import MockImg from '@/assets/mock/SAMPA-1.jpg'
@@ -79,8 +80,7 @@ const {
   frontColor,
   backgroundColor,
   onBack,
-  onScaleUp,
-  onScaleDown,
+  onScaleChange,
   onCenter,
   onRotate,
   setBackgroundColor,
@@ -115,8 +115,7 @@ type OperationType = Parameters<Exclude<OperationClick, undefined>>[0]['type']
 
 const operationHandles: Partial<Record<OperationType, (...args: any) => void>> = {
   back: onBack,
-  'scale-up': onScaleUp,
-  'scale-down': onScaleDown,
+  'scale-change': onScaleChange,
   center: onCenter,
   rotate: onRotate,
   'front-color': setFrontColor,

+ 2 - 4
src/modules/marking/arbitration/index.vue

@@ -85,8 +85,7 @@ const {
   frontColor,
   backgroundColor,
   onBack,
-  onScaleUp,
-  onScaleDown,
+  onScaleChange,
   onCenter,
   onRotate,
   setBackgroundColor,
@@ -133,8 +132,7 @@ type OperationType = Parameters<Exclude<OperationClick, undefined>>[0]['type']
 
 const operationHandles: Partial<Record<OperationType, (...args: any) => void>> = {
   back: onBack,
-  'scale-up': onScaleUp,
-  'scale-down': onScaleDown,
+  'scale-change': onScaleChange,
   center: onCenter,
   rotate: onRotate,
   'front-color': setFrontColor,

+ 2 - 4
src/modules/marking/mark/index.vue

@@ -89,8 +89,7 @@ const {
   frontColor,
   backgroundColor,
   onBack,
-  onScaleUp,
-  onScaleDown,
+  onScaleChange,
   onCenter,
   onRotate,
   setBackgroundColor,
@@ -208,8 +207,7 @@ type OperationType = Parameters<Exclude<OperationClick, undefined>>[0]['type']
 
 const operationHandles: Partial<Record<OperationType, (...args: any) => void>> = {
   back: onBack,
-  'scale-up': onScaleUp,
-  'scale-down': onScaleDown,
+  'scale-change': onScaleChange,
   center: onCenter,
   rotate: onRotate,
   'front-color': setFrontColor,

+ 2 - 4
src/modules/marking/problem/index.vue

@@ -85,8 +85,7 @@ const {
   frontColor,
   backgroundColor,
   onBack,
-  onScaleUp,
-  onScaleDown,
+  onScaleChange,
   onCenter,
   onRotate,
   setBackgroundColor,
@@ -146,8 +145,7 @@ type OperationType = Parameters<Exclude<OperationClick, undefined>>[0]['type']
 
 const operationHandles: Partial<Record<OperationType, (...args: any) => void>> = {
   back: onBack,
-  'scale-up': onScaleUp,
-  'scale-down': onScaleDown,
+  'scale-change': onScaleChange,
   center: onCenter,
   rotate: onRotate,
   'front-color': setFrontColor,

+ 2 - 4
src/modules/marking/repeat/index.vue

@@ -84,8 +84,7 @@ const {
   frontColor,
   backgroundColor,
   onBack,
-  onScaleUp,
-  onScaleDown,
+  onScaleChange,
   onCenter,
   onRotate,
   setBackgroundColor,
@@ -141,8 +140,7 @@ type OperationType = Parameters<Exclude<OperationClick, undefined>>[0]['type']
 
 const operationHandles: Partial<Record<OperationType, (...args: any) => void>> = {
   back: onBack,
-  'scale-up': onScaleUp,
-  'scale-down': onScaleDown,
+  'scale-change': onScaleChange,
   center: onCenter,
   rotate: onRotate,
   'front-color': setFrontColor,

+ 2 - 4
src/modules/marking/training-record/index.vue

@@ -56,8 +56,7 @@ const {
   frontColor,
   backgroundColor,
   onBack,
-  onScaleUp,
-  onScaleDown,
+  onScaleChange,
   onCenter,
   onRotate,
   setBackgroundColor,
@@ -87,8 +86,7 @@ type OperationType = Parameters<Exclude<OperationClick, undefined>>[0]['type']
 
 const operationHandles: Partial<Record<OperationType, (...args: any) => void>> = {
   back: onBack,
-  'scale-up': onScaleUp,
-  'scale-down': onScaleDown,
+  'scale-change': onScaleChange,
   center: onCenter,
   rotate: onRotate,
   'front-color': setFrontColor,

+ 2 - 4
src/modules/marking/view-sample/index.vue

@@ -56,8 +56,7 @@ const {
   frontColor,
   backgroundColor,
   onBack,
-  onScaleUp,
-  onScaleDown,
+  onScaleChange,
   onCenter,
   onRotate,
   setBackgroundColor,
@@ -87,8 +86,7 @@ type OperationType = Parameters<Exclude<OperationClick, undefined>>[0]['type']
 
 const operationHandles: Partial<Record<OperationType, (...args: any) => void>> = {
   back: onBack,
-  'scale-up': onScaleUp,
-  'scale-down': onScaleDown,
+  'scale-change': onScaleChange,
   center: onCenter,
   rotate: onRotate,
   'front-color': setFrontColor,

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

@@ -91,8 +91,7 @@ const {
   frontColor,
   backgroundColor,
   onBack,
-  onScaleUp,
-  onScaleDown,
+  onScaleChange,
   onCenter,
   onRotate,
   setBackgroundColor,
@@ -157,8 +156,7 @@ type OperationType = Parameters<Exclude<OperationClick, undefined>>[0]['type']
 
 const operationHandles: Partial<Record<OperationType, (...args: any) => void>> = {
   back: onBack,
-  'scale-up': onScaleUp,
-  'scale-down': onScaleDown,
+  'scale-change': onScaleChange,
   center: onCenter,
   rotate: onRotate,
   'front-color': setFrontColor,
@@ -238,9 +236,9 @@ const columns: EpTableColumn[] = [
   { label: '密号', prop: 'secretNumber' },
   { label: '评卷员', prop: 'markerName' },
   { label: '评卷员给分', prop: 'markScore' },
-  { label: '组长给分', prop: 'unknown', width: 70 },
+  { label: '组长给分', prop: 'headerScore', width: 70 },
   { label: '评卷时间', prop: 'markTime', width: 45 },
-  { label: '抽查次数', prop: 'unknown', width: 45 },
+  { label: '抽查次数', prop: 'checkCount', width: 45 },
 ]
 
 const { fetch: getSystemSpotList, result: systemSpotList } = useFetch('getSystemSpotList')

+ 168 - 0
src/modules/quality/ending-check/components/EndCheck.vue

@@ -0,0 +1,168 @@
+<template>
+  <div class="radius-base">
+    <div class="fill-blank radius-base p-t-base">
+      <base-form :items="items" :model="model" :label-width="useVW(66)" size="small">
+        <template #form-item-button>
+          <el-button type="primary" @click="onStartCheck">开始检查</el-button>
+        </template>
+      </base-form>
+    </div>
+    <div class="m-t-base flex">
+      <div class="radius-base fill-blank p-base overflow-hidden flex-1">
+        <div class="flex items-center m-b-base table-title">
+          <span class="label">在评卷员手中</span>
+          <span class="data-count">123</span>
+          <el-button type="primary" size="small" @click="onTaskChangeMarker">任务指定评卷员</el-button>
+        </div>
+        <base-table :columns="columns1"></base-table>
+      </div>
+      <div class="radius-base fill-blank p-base overflow-hidden m-l-base flex-1">
+        <div class="flex items-center m-b-base table-title">
+          <span class="label">未处理问题卷</span>
+          <span class="data-count">123</span>
+        </div>
+        <base-table :columns="columns2" :data="unProcessProblemList"></base-table>
+      </div>
+      <div class="radius-base fill-blank p-base overflow-hidden m-l-base flex-1">
+        <div class="flex items-center m-b-base table-title">
+          <span class="label">未处理雷同卷</span>
+          <span class="data-count">123</span>
+        </div>
+        <base-table :columns="columns3" :data="unProcessSimilarList"></base-table>
+      </div>
+    </div>
+  </div>
+  <base-dialog v-model="visibleChangeMarker" title="任务指定评卷员">
+    <base-form size="small" :model="changeMarkerModel" :items="changeMarkerItems"></base-form>
+    <template #footer>
+      <confirm-button around></confirm-button>
+    </template>
+  </base-dialog>
+</template>
+
+<script setup lang="ts" name="EndCheck">
+/** 收尾检查 */
+import { reactive, ref, computed, watch } from 'vue'
+import { ElButton } from 'element-plus'
+import BaseForm from '@/components/element/BaseForm.vue'
+import BaseTable from '@/components/element/BaseTable.vue'
+import BaseDialog from '@/components/element/BaseDialog.vue'
+import ConfirmButton from '@/components/common/ConfirmButton.vue'
+import useMainStore from '@/store/main'
+import useFetch from '@/hooks/useFetch'
+import useOptions from '@/hooks/useOptions'
+import useVW from '@/hooks/useVW'
+import useForm from '@/hooks/useForm'
+
+import type { ExtractApiParams } from 'api-type'
+import type { EpFormItem, EpTableColumn } from 'global-type'
+
+const mainStore = useMainStore()
+
+/** 指定评卷员 */
+const visibleChangeMarker = ref<boolean>(false)
+
+const changeMarkerModel = reactive({ loginName: '' })
+
+const {} = useFetch('addMainQuestion')
+
+const changeMarkerItems: EpFormItem[] = [{ label: '请输入指定评卷员账号', prop: 'loginName', slotType: 'input' }]
+
+/** 搜索 */
+const model = reactive<ExtractApiParams<'unProcessProblemList'>>({
+  markingGroupNumber: void 0,
+  questionMainNumber: void 0,
+  subjectCode: '',
+})
+
+const { fetch: getUnMarkPaperList } = useFetch('unMarkPaperList')
+const { fetch: getUnProcessProblemList, result: unProcessProblemList } = useFetch('unProcessProblemList')
+const { fetch: getUnProcessSimilarList, result: unProcessSimilarList } = useFetch('unProcessSimilarList')
+
+const { mainQuestionList, groupList, onOptionInit, dataModel, changeModelValue } = useOptions(['question', 'group'], {
+  subject: mainStore.myUserInfo?.subjectCode,
+})
+
+watch(dataModel, () => {
+  model.subjectCode = dataModel.subject || ''
+  model.questionMainNumber = dataModel.question
+  model.markingGroupNumber = dataModel.group
+})
+
+const { defineColumn, _ } = useForm()
+
+const OneRow = defineColumn(_, 'row-1', { span: 6 })
+
+const items = computed<EpFormItem[]>(() => [
+  OneRow({
+    label: '大题',
+    prop: 'questionMainNumber',
+    slotType: 'select',
+    slot: {
+      options: mainQuestionList.value,
+      onChange: changeModelValue('question'),
+    },
+  }),
+  OneRow({
+    label: '小组',
+    prop: 'markingGroupNumber',
+    slotType: 'select',
+    slot: {
+      options: groupList.value,
+      onChange: changeModelValue('group'),
+    },
+  }),
+  OneRow({
+    slotName: 'button',
+  }),
+])
+
+/** 未评卷 table */
+const columns1: EpTableColumn[] = [
+  { label: '评卷员', prop: 'markerName' },
+  { label: '密号', prop: 'secretNumber' },
+  { label: '大题', prop: 'questionMainName' },
+]
+/** 未处理问题卷table */
+const columns2: EpTableColumn[] = [
+  { label: '密号', prop: 'secretNumber' },
+  { label: '大题', prop: 'questionMainName' },
+]
+/** 未处理雷同卷table */
+const columns3: EpTableColumn[] = [
+  { label: '密号', prop: 'secretNumber' },
+  { label: '雷同密号', prop: 'sameSecretNumber' },
+  { label: '大题', prop: 'questionMainName' },
+]
+
+/** 开始检查 */
+const onStartCheck = () => {
+  getUnMarkPaperList({ pageNumber: 1, pageSize: 20, ...model })
+  getUnProcessProblemList(model)
+  getUnProcessSimilarList(model)
+}
+
+/** 任务指定评卷员 */
+const onTaskChangeMarker = () => {
+  visibleChangeMarker.value = true
+}
+
+onOptionInit(onStartCheck)
+</script>
+
+<style scoped lang="scss">
+.table-title {
+  .label {
+    font-size: $SmallFont;
+    color: $RegularFontColor;
+  }
+  .data-count {
+    padding: 4px 10px;
+    margin: 0 12px;
+    border: $OnePixelLine;
+    border-radius: 4px;
+    font-size: $MediumFont;
+    color: $NormalColor;
+  }
+}
+</style>

+ 212 - 0
src/modules/quality/ending-check/components/SubjectProgress.vue

@@ -0,0 +1,212 @@
+<template>
+  <div class="p-base">
+    <div class="fill-blank radius-base p-base chart-info">
+      <vue-e-charts class="full" :option="totalChartsOption" autoresize></vue-e-charts>
+    </div>
+    <div class="m-t-base fill-blank radius-base p-base">
+      <base-table :data="subjectProgressEndList" :columns="columns" :span-method="tableSpanMethod"></base-table>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts" name="SubjectProgress">
+/** 科目进度 */
+import { reactive, ref, computed } from 'vue'
+import { minus } from '@/utils/common'
+import useFetch from '@/hooks/useFetch'
+import useMainStore from '@/store/main'
+import VueECharts from 'vue-echarts'
+import BaseTable from '@/components/element/BaseTable.vue'
+
+import type { EChartsOption } from 'echarts'
+import type { ExtractApiResponse } from 'api-type'
+import type { EpTableColumn, InstanceTable } from 'global-type'
+
+type SubjectProgress = ExtractArrayValue<ExtractApiResponse<'subjectProgressEnd'>>
+
+const mainStore = useMainStore()
+
+const { fetch: subjectProgressEnd, result: subjectProgressEndList } = useFetch('subjectProgressEnd')
+
+subjectProgressEnd({ subjectCode: mainStore.myUserInfo?.subjectCode || '' })
+
+const getMainName = (row?: SubjectProgress) => {
+  const { questionMainName: name, questionMainNumber: number } = row || {}
+  return [number, name].filter(Boolean).join('-')
+}
+
+const getYAxisData = (field: keyof SubjectProgress | 'name', data?: SubjectProgress[]) => {
+  if (!data) {
+    return []
+  }
+  return data.map((v) => {
+    if (field === 'name') {
+      return getMainName(v)
+    }
+    return v[field]
+  })
+}
+
+const totalChartsOption = computed<EChartsOption>(() => {
+  return {
+    grid: {
+      top: 20,
+      bottom: -20,
+      left: 20,
+      right: 30,
+      containLabel: true,
+    },
+    legend: {
+      right: 0,
+      itemWidth: 14,
+      data: ['试卷总量', '已完成', '完成比'],
+    },
+    yAxis: {
+      axisLine: { show: false },
+      axisTick: { show: false },
+      splitLine: { show: false },
+      inverse: true,
+      axisLabel: {
+        align: 'right',
+        verticalAlign: 'bottom',
+      },
+      data: getYAxisData('name', subjectProgressEndList?.value),
+    },
+    xAxis: [
+      {
+        position: 'top',
+        type: 'value',
+        splitLine: { show: true },
+      },
+      {
+        position: 'bottom',
+        type: 'value',
+        show: false,
+        axisLabel: {
+          formatter: `{value}%`,
+        },
+        splitLine: { show: false },
+      },
+    ],
+    series: [
+      {
+        name: '试卷总量',
+        type: 'bar',
+        barWidth: 11,
+        barGap: '-200%',
+        itemStyle: {
+          color: '#3AD500',
+        },
+        data: getYAxisData('totalPaper', subjectProgressEndList?.value),
+      },
+      {
+        name: '已完成',
+        type: 'bar',
+        barWidth: 11,
+        barGap: '-200%',
+        itemStyle: {
+          color: '#0064FF',
+        },
+        data: getYAxisData('finishCount', subjectProgressEndList?.value),
+      },
+      {
+        name: '完成比',
+        type: 'bar',
+        barWidth: 44,
+        barGap: '-200%',
+        showBackground: true,
+        xAxisIndex: 1,
+        itemStyle: {
+          color: 'rgba(0, 186, 151,0.3)',
+        },
+        label: {
+          show: true,
+          color: '#444',
+          fontSize: 10,
+          formatter({ value }) {
+            return value > 0 ? `${value}%` : ''
+          },
+          position: 'insideTopRight',
+        },
+        data: getYAxisData('finishRate', subjectProgressEndList?.value),
+      },
+    ],
+  }
+})
+
+const columns: EpTableColumn<SubjectProgress>[] = [
+  {
+    label: '科目',
+    formatter() {
+      return mainStore.myUserInfo?.subjectCode + '-' + mainStore.myUserInfo?.subjectName
+    },
+  },
+  { label: '大题', prop: 'questionMainName' },
+  { label: '试卷总量', prop: 'totalPaper' },
+  { label: '已完成', prop: 'finishCount' },
+  {
+    label: '完成比',
+    prop: 'finishRate',
+    formatter(row) {
+      return `${row.finishRate}%`
+    },
+  },
+  {
+    label: '待完成',
+    formatter(row) {
+      return `${minus(row.totalPaper, row.finishCount)}`
+    },
+  },
+  {
+    label: '待完成比',
+    prop: 'totalPaper',
+    formatter(row) {
+      return `${minus(100, row.finishRate)}%`
+    },
+  },
+  { label: '问题卷待处理', prop: 'todoProblemPaperCount' },
+  { label: '雷同卷待处理', prop: 'todoSamePaperCount' },
+  { label: '主观题校验待处理', prop: 'subjectiveUnVerifyPaperCount' },
+  {
+    label: '抽查比例',
+    prop: 'checkPaperRate',
+    formatter(row) {
+      return `${row.checkPaperRate}%`
+    },
+  },
+  { label: '仲裁卷待处理', prop: 'todoArbitrationPaperCount' },
+  {
+    label: '仲裁率',
+    prop: 'arbitrationPaperRate',
+    formatter(row) {
+      return `${row.arbitrationPaperRate}%`
+    },
+  },
+]
+
+const tableSpanMethod: InstanceTable['spanMethod'] = (scope) => {
+  if (scope.columnIndex === 0) {
+    if (scope.rowIndex === 0) {
+      return {
+        rowspan: subjectProgressEndList.value?.length || 1,
+        colspan: 1,
+      }
+    } else {
+      return {
+        rowspan: 0,
+        colspan: 0,
+      }
+    }
+  }
+  return {
+    rowspan: 1,
+    colspan: 1,
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.chart-info {
+  height: 260px;
+}
+</style>

+ 15 - 2
src/modules/quality/ending-check/index.vue

@@ -1,10 +1,23 @@
 <template>
-  <div class="">科目进度收尾</div>
+  <div class="p-base full">
+    <el-radio-group v-model="viewType">
+      <el-radio-button label="progress">科目进度</el-radio-button>
+      <el-radio-button label="check">收尾检查</el-radio-button>
+    </el-radio-group>
+    <div class="p-t-base">
+      <component :is="viewType === 'progress' ? SubjectProgress : EndCheck"></component>
+    </div>
+  </div>
 </template>
 
 <script setup lang="ts" name="QualityEndingCheck">
 /** 科目进度收尾 */
-import { reactive, ref } from 'vue'
+import { ref } from 'vue'
+import { ElRadioGroup, ElRadioButton } from 'element-plus'
+import SubjectProgress from './components/SubjectProgress.vue'
+import EndCheck from './components/EndCheck.vue'
+
+const viewType = ref<'progress' | 'check'>('progress')
 </script>
 
 <style scoped lang="scss"></style>

+ 114 - 2
src/modules/quality/spot-check/index.vue

@@ -1,10 +1,122 @@
 <template>
-  <div class="">抽查1统计</div>
+  <div class="flex direction-column full">
+    <div class="p-l-base p-t-medium-base fill-blank">
+      <base-form size="small" :items="items" :model="model" :disabled="loading">
+        <template #form-item-operation>
+          <el-button type="primary" @click="onSearch">查询</el-button>
+        </template>
+      </base-form>
+    </div>
+    <div class="flex-1 p-base">
+      <div class="radius-base p-base fill-blank">
+        <base-table size="small" :data="result" :columns="columns"></base-table>
+      </div>
+    </div>
+  </div>
 </template>
 
 <script setup lang="ts" name="QualitySpotCheck">
 /** 抽查情况统计 */
-import { reactive, ref } from 'vue'
+import { reactive, computed, watch } from 'vue'
+import { ElButton } from 'element-plus'
+import BaseForm from '@/components/element/BaseForm.vue'
+import BaseTable from '@/components/element/BaseTable.vue'
+import useForm from '@/hooks/useForm'
+import useOptions from '@/hooks/useOptions'
+import useFetch from '@/hooks/useFetch'
+import useVW from '@/hooks/useVW'
+import useMainStore from '@/store/main'
+
+import type { ExtractApiParams, ExtractApiResponse, ExtractMultipleApiResponse } from 'api-type'
+import type { EpFormItem, EpTableColumn } from 'global-type'
+
+const model = reactive<ExtractApiParams<'statisticCheckOverview'>>({
+  subjectCode: '',
+  questionMainNumber: void 0,
+  markingGroupLeaderId: 0,
+})
+
+const { defineColumn, _ } = useForm()
+
+const mainStore = useMainStore()
+
+const { mainQuestionList, dataModel, onOptionInit } = useOptions(['question'], {
+  subject: mainStore?.myUserInfo?.subjectCode,
+})
+
+watch(dataModel, () => {
+  model.subjectCode = dataModel.subject || ''
+  model.questionMainNumber = dataModel.question
+})
+
+const { fetch: getUserList, result: userList } = useFetch('getUserList')
+watch(
+  () => model.questionMainNumber,
+  () => {
+    if (model.questionMainNumber) {
+      getUserList({ enable: true, mainNumber: model.questionMainNumber, role: 'DEPUTY', pageNumber: 1, pageSize: 9999 })
+    }
+  }
+)
+
+const OneRowSpan5 = defineColumn(_, 'row-1', { span: 5 })
+
+const transformUserOption = (userInfo: ExtractMultipleApiResponse<'getUserList'>) => {
+  const { loginName, name, id } = userInfo
+  return {
+    label: [loginName, name].filter(Boolean).join('-'),
+    value: id,
+  }
+}
+
+const items = computed<EpFormItem[]>(() => {
+  return [
+    OneRowSpan5({
+      label: '大题',
+      prop: 'questionMainNumber',
+      slotType: 'select',
+      slot: { options: mainQuestionList.value },
+    }),
+    OneRowSpan5({
+      label: '组长',
+      labelWidth: useVW(60),
+      prop: 'markingGroupLeaderId',
+      slotType: 'select',
+      slot: { options: [{ label: '全部', value: 0 }].concat(userList.value?.result?.map(transformUserOption) || []) },
+    }),
+    OneRowSpan5({ slotName: 'operation', labelWidth: useVW(20) }),
+  ]
+})
+
+const columns: EpTableColumn<ExtractArrayValue<ExtractApiResponse<'statisticCheckOverview'>>>[] = [
+  {
+    label: '评卷员',
+    prop: 'markerName',
+    formatter(row) {
+      return row.markerId === 0 ? '' : row.markerName
+    },
+  },
+  {
+    label: '大题名称',
+    prop: 'questionMainName',
+    formatter(row) {
+      return row.markerId === 0 ? '' : row.questionMainName
+    },
+  },
+  { label: '主动抽查次数', prop: 'customCheckCount', align: 'center' },
+  { label: '打回量', prop: 'rejectCount', align: 'center' },
+  { label: '主动抽查给分次数', prop: 'customCheckReScoreCount', align: 'center' },
+  { label: '系统抽查复核次数', prop: 'sysCheckCount', align: 'center' },
+  { label: '系统抽查复核给分次数', prop: 'sysCheckReScoreCount', align: 'center' },
+]
+
+const { fetch: statisticCheckOverview, result, loading } = useFetch('statisticCheckOverview')
+
+const onSearch = () => {
+  statisticCheckOverview({ ...model, markingGroupLeaderId: model.markingGroupLeaderId || void 0 })
+}
+
+onOptionInit(onSearch)
 </script>
 
 <style scoped lang="scss"></style>

+ 312 - 3
src/modules/quality/subjective-check/index.vue

@@ -1,10 +1,319 @@
 <template>
-  <div class="">主观题校验</div>
+  <div class="flex direction-column full">
+    <mark-header :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']" @click="onOperationClick">
+      <el-button type="primary" size="small" class="m-l-base" plain @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">
+      <div
+        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>
+        <right-button class="next-button" @click="checkNext" />
+        <div class="flex-1 p-base scroll-auto mark-content-paper">
+          <img :src="dataUrl" alt="" class="paper-img" :style="{ 'background-color': frontColor }" />
+        </div>
+      </div>
+      <div class="p-base radius-base fill-blank scroll-auto m-l-base problem-list">
+        <base-form size="small" :model="formModel" :items="formItems" :label-width="useVW(62)">
+          <template #form-item-search>
+            <el-button type="primary" @click="onSearch">查询</el-button>
+          </template>
+        </base-form>
+        <div class="flex items-center p-l-base">
+          <span>{{ statusText }}</span>
+          <span>: 共{{ tableData.length }}</span>
+        </div>
+        <base-table
+          ref="tableRef"
+          size="small"
+          height="45vh"
+          :data="tableData"
+          :columns="columns"
+          @current-change="onCurrentChange"
+        ></base-table>
+        <base-table
+          v-if="currentSubjectiveCheck"
+          class="m-t-base"
+          size="small"
+          height="150px"
+          :data="[currentSubjectiveCheck]"
+          :columns="subColumns"
+          @row-dblclick="onDbClick"
+        >
+          <template #empty> 暂无数据 </template>
+        </base-table>
+      </div>
+    </div>
+  </div>
+  <scoring-panel-with-confirm
+    v-model:visible="editScoreVisible"
+    v-model:score="modelScore"
+    :main-number="mockTask.mainNumber"
+    modal
+    :toggle-modal="false"
+    @submit="onSubmit"
+  ></scoring-panel-with-confirm>
+  <image-preview v-model="previewModalVisible" :url="MockImg"></image-preview>
+  <mark-history-list :id="currentViewHistory?.secretNumber" v-model="visibleHistory" type="secret"></mark-history-list>
 </template>
 
 <script setup lang="ts" name="QualitySubjectiveCheck">
 /** 主观题校验 */
-import { reactive, ref } from 'vue'
+import { reactive, ref, computed, watch } from 'vue'
+import { ElButton } from 'element-plus'
+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 MockImg from '@/assets/mock/SAMPA-1.jpg'
+import type { SetImgBgOption } from '@/hooks/useSetImgBg'
+import type { ExtractMultipleApiResponse, ExtractApiParams } from 'api-type'
+import type { MarkHeaderInstance, EpFormItem, EpTableColumn } from 'global-type'
+
+type RowType = ExtractMultipleApiResponse<'getSubjectiveCheckList'> & { index: number }
+
+/** 给分板 */
+const editScoreVisible = ref<boolean>(false)
+
+/** 图片预览 */
+const previewModalVisible = ref<boolean>(false)
+
+/** 分数 */
+const modelScore = ref<number[]>([])
+
+const mockTask = ref<{ mainNumber: number }>({
+  mainNumber: 1,
+})
+
+const {
+  rotate,
+  scale,
+  center,
+  frontColor,
+  backgroundColor,
+  onBack,
+  onScaleChange,
+  onCenter,
+  onRotate,
+  setBackgroundColor,
+  setFrontColor,
+  onViewStandard,
+} = useMarkHeader()
+
+const imgOption = computed<SetImgBgOption>(() => {
+  return {
+    image: MockImg,
+    immediate: true,
+    rotate: rotate.value,
+    scale: scale.value,
+  }
+})
+
+const { drawing, dataUrl } = useSetImgBg(imgOption)
+
+/** 刷新 */
+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 formModel = reactive<ExtractApiParams<'getSubjectiveCheckList'>>({
+  subjectCode: '',
+  mainNumber: void 0,
+  checked: false,
+  rule: 'RULE1',
+  pageNumber: 1,
+  pageSize: 9999999,
+})
+
+const { mainQuestionList, dataModel, changeModelValue, onOptionInit } = useOptions(['subject', 'question'])
+
+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 = [
+  '主观分有分,客观分为0',
+  '主观分为0分,客观分大于40分',
+  '主观分为1,2分,客观分大于45分',
+  '主观分大于10分,客观分小于20分',
+].map((v, i) => ({
+  label: v,
+  value: `RULE${i + 1}`,
+}))
+
+const formItems = computed<EpFormItem[]>(() => [
+  span10({
+    rowKey: 'row-1',
+    label: '科目',
+    prop: 'subjectCode',
+    slotType: 'select',
+    slot: { options: mainQuestionList.value, onChange: changeModelValue('subject') },
+  }),
+  span10({
+    rowKey: 'row-1',
+    label: '大题',
+    prop: 'mainNumber',
+    slotType: 'select',
+    slot: { options: mainQuestionList.value, onChange: changeModelValue('question') },
+  }),
+  { 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' },
+  { label: '密号', prop: 'secretNumber' },
+  { label: '大题名称', prop: 'mainName' },
+  { label: '处理结果', prop: 'status' },
+  { label: '处理人', prop: 'headerName' },
+  { label: '处理时间', prop: 'updateTime' },
+]
+
+const subColumns: EpTableColumn<RowType>[] = [
+  { label: '密号', prop: 'secretNumber' },
+  { label: '大题名称', prop: 'mainName' },
+  { label: '当前成绩', prop: 'markerScore' },
+  { label: '修改成绩', prop: 'headerScore' },
+]
+
+const { fetch: getSubjectiveCheckList, result: subjectiveCheckList } = useFetch('getSubjectiveCheckList')
+
+const {
+  tableRef,
+  tableData,
+  current: currentSubjectiveCheck,
+  currentView: currentViewHistory,
+  next: checkNext,
+  visibleHistory,
+  onDbClick,
+  onCurrentChange,
+} = useTableCheck(subjectiveCheckList)
+
+const statusText = ref<string>('未处理')
+
+const onSearch = async () => {
+  getSubjectiveCheckList(formModel).then(() => {
+    statusText.value = formModel.checked === true ? '已处理' : formModel.checked === false ? '未处理' : '全部'
+  })
+}
+
+/** 给分 */
+const { fetch: subjectiveCheckMark } = useFetch('subjectiveCheckMark')
+const onSubmit = () => {
+  if (currentSubjectiveCheck.value) {
+    subjectiveCheckMark({ taskId: currentSubjectiveCheck.value.taskId, scores: modelScore.value })
+  }
+}
+
+/** 确认 */
+const { fetch: subjectiveCheckConfirm } = useFetch('subjectiveCheckConfirm')
+
+const onConfirm = () => {
+  if (currentSubjectiveCheck.value) {
+    subjectiveCheckConfirm({ taskId: currentSubjectiveCheck.value.taskId })
+  }
+}
+
+onOptionInit(onSearch)
 </script>
 
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+.mark-container {
+  .mark-content {
+    position: relative;
+    .preview {
+      position: absolute;
+      cursor: pointer;
+      top: 10px;
+      right: 20px;
+      font-size: 24px;
+    }
+    .next-button {
+      position: absolute;
+      right: -20px;
+      top: 300px;
+    }
+    .mark-content-paper {
+      img {
+        max-width: 100%;
+      }
+    }
+  }
+  .problem-list {
+    width: 580px;
+  }
+}
+</style>

+ 11 - 11
src/router/quality.ts

@@ -30,17 +30,6 @@ const routes: RouteRecordRaw[] = [
           sort: 1,
         },
       },
-      {
-        name: 'QualitySubjectiveCheck',
-        path: 'subjective-check',
-        component: () => import('@/modules/quality/subjective-check/index.vue'),
-        meta: {
-          label: '主观题校验',
-          menu: true,
-          menuId: 'quality-subjective_check',
-          sort: 2,
-        },
-      },
       {
         name: 'QualityEndingCheck',
         path: 'ending-check',
@@ -65,6 +54,17 @@ const routes: RouteRecordRaw[] = [
       },
     ],
   },
+  {
+    name: 'QualitySubjectiveCheck',
+    path: '/subjective-check',
+    component: () => import('@/modules/quality/subjective-check/index.vue'),
+    meta: {
+      label: '主观题校验',
+      menu: true,
+      menuId: 'quality-subjective_check',
+      sort: 2,
+    },
+  },
   {
     name: 'QualitySelfCheckDetail',
     path: '/self-check-detail',

+ 10 - 2
src/utils/common.ts

@@ -143,8 +143,6 @@ export const getNumbers = (n: number, interval: number) => {
 }
 
 /**
- * @param { Number } a
- * @param { Number } b
  * @return { Number }
  */
 export const add = (...args: number[]) => {
@@ -154,6 +152,16 @@ export const add = (...args: number[]) => {
   return result.toNumber()
 }
 
+/**
+ * @return { Number }
+ */
+export const minus = (num: number, ...args: number[]) => {
+  const result = args.reduce((total, n) => {
+    return total.minus(n)
+  }, new Big(num))
+  return result.toNumber()
+}
+
 /**
  * @param { any } val
  * @return { Boolean } is HTMLElement

+ 123 - 12
types/api.d.ts

@@ -937,17 +937,15 @@ declare module 'api-type' {
       LoginResponse
     >
 
-    /** 导入用户 */
-    type ImportUser = BaseDefine<{
-      file: FormData
-    }>
-
     /** 导出用户 */
-    type ExportUser = BaseDefine<
-      MultipleQuery<{
-        enable: boolean
-      }>
-    >
+    type ExportUser = BaseDefine<{
+      enable?: boolean
+      role?: ROLE
+      loginName?: string
+      name?: string
+      mainNumber?: number
+      subjectCode?: string
+    }>
 
     interface BaseUserInfo {
       examId?: number
@@ -965,6 +963,7 @@ declare module 'api-type' {
       id: number
       createTime: string
       groupId: number
+      subjectName: string
       roleName: string
       updateTime: string
       updaterName: string
@@ -1079,7 +1078,6 @@ declare module 'api-type' {
     export interface ApiMap {
       userLogin: UserLogin
       userLogout: null
-      importUser: ImportUser
       exportUser: ExportUser
       downloadUserTemplate: null
       saveUserInfo: SaveUserInfo
@@ -1169,6 +1167,55 @@ declare module 'api-type' {
 
     type SelfCheckAnalysis = BaseDefine<SelfCheckAnalysisReq, SelfCheckAnalysisResponse[]>
 
+    /** 质量分析- 抽查情况统计 */
+    interface StatisticCheckInfo {
+      customCheckCount: number
+      customCheckReScoreCount: number
+      markerId: number
+      markerName: string
+      questionMainName: string
+      questionMainNumber: number
+      rejectCount: number
+      sysCheckCount: number
+      sysCheckReScoreCount: number
+    }
+
+    type StatisticCheckOverview = BaseDefine<
+      {
+        questionMainNumber?: number
+        subjectCode: string
+        markingGroupLeaderId?: number
+      },
+      StatisticCheckInfo[]
+    >
+    /** 质量统计-主观题校验列表 */
+    interface SubjectiveListItem {
+      filePath: string
+      headerName: string
+      headerScore: number
+      mainName: string
+      mainNumber: number
+      markerScore: number
+      secretNumber: number
+      status: string
+      taskId: number
+      updateTime: string
+    }
+
+    type GetSubjectiveCheckList = BaseDefine<
+      MultipleQuery<{
+        subjectCode: string
+        mainNumber?: number
+        checked?: boolean
+        rule: 'RULE1' | 'RULE2' | 'RULE3' | 'RULE4'
+      }>,
+      MultipleResult<SubjectiveListItem>
+    >
+
+    type SubjectiveCheckMark = BaseDefine<{ taskId?: number; scores: number[] }>
+
+    type SubjectiveCheckConfirm = BaseDefine<{ taskId?: number }>
+
     /** 决策分析-评卷进度统计(整体) */
     interface MarkProgressResponse {
       /** 已完成数 */
@@ -1220,6 +1267,57 @@ declare module 'api-type' {
       markingGroupNumber?: number
     }>
 
+    /** 科目进度收尾 */
+    interface SubjectProgressEndInfo {
+      arbitrationPaperRate: number
+      checkPaperCount: number
+      checkPaperRate: number
+      finishCount: number
+      finishRate: number
+      questionMainName: string
+      questionMainNumber: number
+      subjectiveUnVerifyPaperCount: number
+      todoArbitrationPaperCount: number
+      todoProblemPaperCount: number
+      todoSamePaperCount: number
+      totalPaper: number
+    }
+
+    type SubjectProgressEnd = BaseDefine<{ subjectCode: string }, SubjectProgressEndInfo[]>
+
+    interface BaseParams {
+      subjectCode: string
+      questionMainNumber?: number
+      markingGroupNumber?: number
+    }
+
+    /** 质量统计-收尾检查-评卷员未评卷列表(分页) */
+
+    interface UnMarkPaper {
+      markerId: number
+      markerName: string
+      questionMainName: string
+      questionMainNumber: number
+      secretNumber: string
+      taskId: number
+    }
+    type UnMarkPaperList = BaseDefine<MultipleQuery<BaseParams>, MultipleResult<UnMarkPaper>>
+    /** 质量统计-收尾检查-未处理雷同卷列表 */
+    interface UnProcessSimilar {
+      questionMainName: string
+      questionMainNumber: number
+      sameSecretNumber: string
+      secretNumber: string
+    }
+    type UnProcessSimilarList = BaseDefine<BaseParams, UnProcessSimilar[]>
+    /** 质量统计-收尾检查-未处理问题卷列表 */
+    interface UnProcessProblem {
+      questionMainName: string
+      questionMainNumber: number
+      secretNumber: string
+    }
+    type UnProcessProblemList = BaseDefine<BaseParams, UnProcessProblem[]>
+
     /** 小组监控 */
     interface GroupMonitor {
       /** 已浏览自定义抽查卷数 */
@@ -1397,6 +1495,13 @@ declare module 'api-type' {
       selfCheckAnalysis: SelfCheckAnalysis
       /** 质量统计-自查一致性分析-离差列表 */
       /** 质量统计-抽查情况统计 */
+      statisticCheckOverview: StatisticCheckOverview
+      /** 质量统计-主观题校验 */
+      getSubjectiveCheckList: GetSubjectiveCheckList
+      /** 质量统计-主观题校验打分 */
+      subjectiveCheckMark: SubjectiveCheckMark
+      /** 质量统计-主观题校验确认 */
+      subjectiveCheckConfirm: SubjectiveCheckConfirm
       /** 决策分析-评卷进度统计(整体) */
       getMarkProgress: GetMarkProgress
       /** 决策分析-评卷进度统计(按小组) */
@@ -1406,6 +1511,13 @@ declare module 'api-type' {
       /** 决策分析-评卷进度统计导出(按评卷员) */
       exportMarkProgressByMarker: ExportMarkProgressByMarker
       /** 质量统计-科目进度收尾 */
+      subjectProgressEnd: SubjectProgressEnd
+      /** 质量统计-收尾检查-评卷员未评卷列表(分页) */
+      unMarkPaperList: UnMarkPaperList
+      /** 质量统计-收尾检查-未处理雷同卷列表 */
+      unProcessSimilarList: UnProcessSimilarList
+      /** 质量统计-收尾检查-未处理问题卷列表 */
+      unProcessProblemList: UnProcessProblemList
       /** 决策分析-小组监控 */
       getGroupMonitor: GetGroupMonitor
       /** 决策分析-监控统计(整体) */
@@ -1416,7 +1528,6 @@ declare module 'api-type' {
       /** 决策分析-监控统计导出(按小组) */
       exportStatisticsByGroup: ExportStatisticsByGroup
       /** 决策分析-监控统计客观题&主观题分数分布 */
-
       /**培训监控 */
       getTrainingMonitor: GetTrainingMonitor
       /** 个人统计 */