Explorar o código

feat: 完成新页面

chenhao %!s(int64=2) %!d(string=hai) anos
pai
achega
2b5d290df8
Modificáronse 43 ficheiros con 1274 adicións e 230 borrados
  1. 1 1
      electron-builder.json5
  2. 2 0
      electron-plugin.ts
  3. 1 1
      jenkins.sh
  4. 2 0
      src/api/marking.ts
  5. 31 3
      src/api/statistics.ts
  6. 1 1
      src/assets/styles/element/custom.scss
  7. 1 1
      src/components/common/Empty.vue
  8. 56 13
      src/components/shared/MarkHistoryList.vue
  9. 11 3
      src/components/shared/SendBackMark.vue
  10. 0 15
      src/hooks/useSetImgBg.ts
  11. 14 3
      src/hooks/useTable.ts
  12. 0 1
      src/hooks/useTableCheck.ts
  13. 2 2
      src/modules/admin-subject/edit-main-question/index.vue
  14. 0 39
      src/modules/admin-user/manage/hooks/useUserManageTable.ts
  15. 163 2
      src/modules/analysis/group-monitoring-detail/index.vue
  16. 43 4
      src/modules/analysis/group-monitoring/index.vue
  17. 12 6
      src/modules/analysis/monitoring/index.vue
  18. 12 6
      src/modules/analysis/statistics/index.vue
  19. 235 3
      src/modules/analysis/view-marked-detail/index.vue
  20. 69 67
      src/modules/example/ImageModify.vue
  21. 2 2
      src/modules/expert/assess/index.vue
  22. 2 2
      src/modules/expert/expert/index.vue
  23. 2 2
      src/modules/expert/sample/index.vue
  24. 2 2
      src/modules/expert/standard/index.vue
  25. 2 2
      src/modules/expert/training/index.vue
  26. 2 2
      src/modules/marking/arbitration/index.vue
  27. 11 2
      src/modules/marking/mark/index.vue
  28. 2 2
      src/modules/marking/problem/index.vue
  29. 4 4
      src/modules/marking/repeat/index.vue
  30. 2 2
      src/modules/marking/similar/index.vue
  31. 103 8
      src/modules/marking/submit-similar/index.vue
  32. 2 2
      src/modules/marking/training-record/index.vue
  33. 2 2
      src/modules/marking/view-sample/index.vue
  34. 2 2
      src/modules/monitor/system-check/index.vue
  35. 142 2
      src/modules/monitor/training-monitoring-detail/index.vue
  36. 187 2
      src/modules/quality/self-check-detail/index.vue
  37. 2 2
      src/modules/quality/subjective-check/index.vue
  38. 12 11
      src/router/analysis.ts
  39. 4 0
      src/router/example.ts
  40. 2 1
      src/router/marking.ts
  41. 126 4
      types/api.d.ts
  42. 1 1
      types/app.d.ts
  43. 2 0
      vite.config.ts

+ 1 - 1
electron-builder.json5

@@ -5,7 +5,7 @@
   directories: {
     output: 'release',
   },
-  files: ['dist/**/*'],
+  files: ['dist/electron/**/*'],
   win: {
     target: 'portable',
     requestedExecutionLevel: 'highestAvailable',

+ 2 - 0
electron-plugin.ts

@@ -5,10 +5,12 @@ const useElectronPlugin: () => PluginOption = () =>
   VitePluginELectron({
     main: {
       entry: 'electron/main/main',
+      build: { outDir: 'dist/electron/main', emptyOutDir: true },
       publicDir: 'electron/main/public',
     },
     preload: {
       entry: 'electron/preload/preload',
+      build: { outDir: 'dist/electron/preload', emptyOutDir: true },
       publicDir: 'electron/preload/public',
     },
   })

+ 1 - 1
jenkins.sh

@@ -22,6 +22,6 @@ if [ -d "dist" ]; then
   rm -rf dist/*
 fi
 
-mv temp/dist .
+mv temp/dist/web .
 rm -rf temp
 echo "ok..."

+ 2 - 0
src/api/marking.ts

@@ -57,6 +57,8 @@ const MarkingApi: DefineApiModule<Marking.ApiMap> = {
     url: '/api/same/paper/export',
     download: true,
   },
+  /** 提交雷同卷 */
+  submitSimilarPaper: '/api/mark/same',
 }
 
 export default MarkingApi

+ 31 - 3
src/api/statistics.ts

@@ -15,6 +15,14 @@ const StatisticsApi: DefineApiModule<Statistics.ApiMap> = {
       'Content-Type': 'application/json',
     },
   },
+  /** 质量统计-自查数据详情 */
+  getSelfCheckDataDetail: {
+    url: '/api/self/check/list',
+  },
+  /** 质量统计-自查数据打分 */
+  markSelfCheckData: '/api/self/check/mark',
+  /** 质量统计-自查数据打回 */
+  rejectSelfCheckData: '/api/self/check/reject',
   /** 质量统计-抽查情况统计 */
   statisticCheckOverview: '/api/statistic/check/overview',
   /** 质量统计-主观题校验 */
@@ -51,6 +59,14 @@ const StatisticsApi: DefineApiModule<Statistics.ApiMap> = {
       'Content-Type': 'application/json',
     },
   },
+  /** 决策分析-小组监控-抽查详情 */
+  getGroupMonitorDetail: {
+    url: '/api/mark/task/check/detail',
+  },
+  /** 决策分析-小组监控-抽查详情-给分 */
+  markMonitorDetailTask: {
+    url: '/api/mark/task/check/detail/mark',
+  },
   /** 决策分析-监控统计(整体) */
   getStatistics: {
     url: '/api/statistic/monitor/list',
@@ -70,11 +86,18 @@ const StatisticsApi: DefineApiModule<Statistics.ApiMap> = {
     url: '/api/statistic/monitor/list/by/group/export',
     download: true,
   },
-  /** 决策分析-监控统计(按评卷员) */
-  /** 决策分析-监控统计导出(按评卷员) */
-  /** 决策分析-监控统计客观题&主观题分数分布 */
+
   /** 培训监控 */
   getTrainingMonitor: '/api/train/monitor/list',
+  /**培训监控 - 培训卷调卷详情 */
+  getTrainingMonitorDetail: {
+    url: '/api/train/monitor/sample/detail',
+    headers: {
+      'Content-Type': 'application/json',
+    },
+  },
+  /**培训监控 - 强制考核卷调卷详情 */
+  getAssessMonitorDetail: '/api/train/monitor/force/detail',
   /** 个人统计(评卷员) */
   getPersonalStatistic: {
     url: '/api/statistic/personal/result',
@@ -82,6 +105,10 @@ const StatisticsApi: DefineApiModule<Statistics.ApiMap> = {
       'Content-Type': 'application/json',
     },
   },
+  /** 提取阅卷明细 */
+  getPersonalMarkDetail: '/api/mark/task/detail',
+  /** 提取阅卷明细 - 给分 */
+  updatePersonalMarkDetailScore: '/api/mark/task/detail/mark',
   /** 系统抽查卷 */
   getSystemSpotList: '/api/system/check/page',
   /** 系统抽查卷打分 */
@@ -90,6 +117,7 @@ const StatisticsApi: DefineApiModule<Statistics.ApiMap> = {
   rejectSystemSpotPaper: '/api/system/check/reject',
   /** 系统抽查卷浏览 */
   viewSystemSpotPaper: '/api/system/check/view',
+  /** 决策分析-监控统计-客观题分数分布(按小组) */
 }
 
 export default StatisticsApi

+ 1 - 1
src/assets/styles/element/custom.scss

@@ -81,7 +81,7 @@ button.el-button {
 }
 
 /** 自定义pagination 样式 */
-.el-pagination {
+.el-pagination:not(.m-t-unset) {
   margin-top: map-get($cst-gap-space, 'base');
 }
 

+ 1 - 1
src/components/common/Empty.vue

@@ -20,7 +20,7 @@ const emptyAttrs = computed(() => {
   return Object.assign<TEmptyProps, TEmptyProps>(
     {
       image: emptyImage,
-      imageSize: 200,
+      imageSize: 180,
       description: '暂无数据',
     },
     props

+ 56 - 13
src/components/shared/MarkHistoryList.vue

@@ -1,25 +1,60 @@
 <template>
-  <base-dialog v-model="visible" class="mark-history-list" title="给分记录">
-    <base-table :data="tableData" :columns="columns"></base-table>
-  </base-dialog>
+  <scoring-panel-container v-model="visible" class="mark-history-list" title="给分记录">
+    <template #default>
+      <slot :data="tableData">
+        <base-table size="small" :data="tableData" :columns="columns"></base-table>
+      </slot>
+    </template>
+  </scoring-panel-container>
 </template>
 
 <script setup lang="ts" name="MarkHistoryList">
 /** 给分记录 */
-import { computed, withDefaults, watch } from 'vue'
+import { computed, withDefaults, watch, defineComponent, useSlots } from 'vue'
 import BaseDialog from '@/components/element/BaseDialog.vue'
 import BaseTable from '@/components/element/BaseTable.vue'
 import useVModel from '@/hooks/useVModel'
 import useFetch from '@/hooks/useFetch'
 
+import type { ExtractApiResponse } from 'api-type'
 import type { EpTableColumn } from 'global-type'
 
-const props = withDefaults(defineProps<{ modelValue: boolean; type?: 'task' | 'secret'; id: number | undefined }>(), {
-  type: 'task',
-})
+const props = withDefaults(
+  defineProps<{
+    modelValue: boolean
+    type?: 'task' | 'secret'
+    id: number | string | undefined
+    modal: boolean
+    showLevel: boolean
+  }>(),
+  {
+    type: 'task',
+    modelValue: true,
+    modal: true,
+    showLevel: false,
+  }
+)
 
 const visible = useVModel(props)
 
+const LessRenderComponent = defineComponent({
+  name: 'LessRender',
+  inheritAttrs: false,
+  props: {
+    modelValue: {
+      type: Boolean,
+      default: false,
+    },
+  },
+  render() {
+    return this.modelValue ? useSlots()?.default?.() : null
+  },
+})
+
+const ScoringPanelContainer = computed(() => {
+  return props.modal ? BaseDialog : LessRenderComponent
+})
+
 watch(
   () => props.id,
   () => {
@@ -41,12 +76,20 @@ const tableData = computed(() => {
   return props.type === 'task' ? scoreHistoryList.value : scoreHistoryListWithSecret.value
 })
 
-const columns: EpTableColumn[] = [
-  { label: '评卷员' },
-  { label: '分数', prop: 'markScore' },
-  { label: '类型' },
-  { label: '评卷时间', prop: 'markTime' },
-]
+const columns = computed<EpTableColumn<ExtractArrayValue<ExtractApiResponse<'getMarkScoreHistoryListWithTask'>>>[]>(
+  () => [
+    { label: '评卷员' },
+    {
+      label: `分数${props.showLevel ? '(档次)' : ''}`,
+      prop: 'markScore',
+      formatter(row) {
+        return `${row.markScore}${props.showLevel ? `(${row.scoreLevel})` : ''}`
+      },
+    },
+    { label: '类型' },
+    { label: '评卷时间', prop: 'markTime' },
+  ]
+)
 </script>
 
 <style scoped lang="scss"></style>

+ 11 - 3
src/components/shared/SendBackMark.vue

@@ -30,7 +30,8 @@ const props = withDefaults(
   defineProps<{
     modelValue: boolean
     id: number | undefined
-    type?: 'problem' | 'system-check'
+    // 问题卷 | 系统抽查 | 自查卷
+    type?: 'problem' | 'system-check' | 'self-check'
   }>(),
   { type: 'problem' }
 )
@@ -62,6 +63,14 @@ const { fetch: rejectMarkHistory } = useFetch('rejectMarkHistory')
 
 const { fetch: rejectSystemSpotPaper } = useFetch('rejectSystemSpotPaper')
 
+const { fetch: rejectSelfCheckData } = useFetch('rejectSelfCheckData')
+
+const ApiMap = {
+  problem: rejectMarkHistory,
+  'system-check': rejectSystemSpotPaper,
+  'self-check': rejectSelfCheckData,
+}
+
 const onSendBack = async () => {
   try {
     if (!props.id) {
@@ -69,8 +78,7 @@ const onSendBack = async () => {
     }
     const valid = await elFormRef?.value?.validate()
     if (valid) {
-      const api = props.type === 'system-check' ? rejectSystemSpotPaper : rejectMarkHistory
-      await api({ description: model.description, reason: model.reason, id: props.id })
+      await ApiMap[props.type]({ description: model.description, reason: model.reason, id: props.id, taskId: props.id })
     }
     visible.value = false
     emits('rejected')

+ 0 - 15
src/hooks/useSetImgBg.ts

@@ -269,7 +269,6 @@ interface TintImageMarkOption {
 const start = (arr: Uint8ClampedArray, func: (index: number) => void) => {
   let i = 0
   const run = () => {
-    console.log('run')
     if (i < arr.length) {
       func(i++)
       requestAnimationFrame(run)
@@ -334,19 +333,14 @@ const tintImage = async ({ mainColor, imageData, distance }: TintImageOption) =>
   //   })
   // })
   const targetColor = [255, 255, 255, 0]
-  console.time('tintImage-log')
-  console.log(targetColor)
   if (isSimilar(targetColor, mainColor, distance)) {
-    console.timeLog('tintImage-log')
     return imageData
   }
-  console.timeLog('tintImage-log')
   start(imageData.data, (index) => {
     if (isSimilar(getColorFromIndex(index, imageData.data), mainColor, distance)) {
       setColorFromIndex(index, imageData.data, targetColor)
     }
   })
-  console.timeLog('tintImage-log')
   return imageData
 }
 
@@ -398,7 +392,6 @@ export const useSetImgBg = (option: Ref<SetImgBgOption>) => {
 
   /** 获取主色 */
   watch(rawContext, () => {
-    console.log('watch-imageData-change')
     rawPixelPoints.value = []
     rawMainColor.value = []
     if (imageData.value && rawCanvas.value) {
@@ -414,7 +407,6 @@ export const useSetImgBg = (option: Ref<SetImgBgOption>) => {
 
   /** 锐化 */
   watch([imageData, () => option.value.enableSharpen], () => {
-    console.log('watch-sharpen')
     if (imageData.value && option.value.enableSharpen) {
       sharpenImage({ imageData: imageData.value, distance: option.value.distance || DISTANCE })
       updateCanvas()
@@ -423,8 +415,6 @@ export const useSetImgBg = (option: Ref<SetImgBgOption>) => {
 
   // /** 移除背景色 */
   watch(rawMainColor, () => {
-    console.log('watch-tintImage', rawMainColor)
-    console.time('tintImage')
     if (imageData.value && rawMainColor.value?.length) {
       tintImage({
         distance: option.value.distance || DISTANCE,
@@ -440,22 +430,17 @@ export const useSetImgBg = (option: Ref<SetImgBgOption>) => {
   watch(
     () => option.value.rotate,
     () => {
-      console.log('watch-rotate', option.value.rotate)
       updateCanvas(option.value.rotate)
     }
   )
 
   const updateCanvas = (rotate = option.value.rotate, immediate = false) => {
-    console.log('updateCanvas')
     if (task) {
-      console.log('clear task')
       clearTimeout(task)
     }
     const update = () => {
-      console.log('real update ===========>')
       if (rawCanvas.value) {
         rawCanvas.value = canvasRotate(rawCanvas.value, rotate)
-        console.log(rawCanvas.value.width)
         dataUrl.value = rawCanvas.value.toDataURL()
       }
     }

+ 14 - 3
src/hooks/useTable.ts

@@ -16,12 +16,12 @@ import type { InstanceTable, InstanceElTable } from 'global-type'
 
 type P = Parameters<typeof useFetch>
 
-type ReturnTable<T extends ApiKeys, D extends boolean = true> = {
+type ReturnTable<T extends ApiKeys, D extends boolean | OptionalPage = true> = {
   tableRef: Ref<InstanceTable | undefined>
   elTableRef: Ref<InstanceElTable | undefined>
   pagination: Ref<Partial<PaginationProps>>
   loading: Ref<boolean>
-  data: Ref<D extends true ? ExtractMultipleApiResponse<T>[] : ExtractApiResponse<T>>
+  data: Ref<D extends false ? ExtractApiResponse<T> : ExtractMultipleApiResponse<T>[]>
   error: unknown
   hasSelected?: Ref<boolean>
   onSectionChange: (rows: ExtractMultipleApiResponse<T>[]) => void
@@ -48,7 +48,11 @@ function isPageQuery(c: any): c is OptionalPage {
   return typeOf(c) === 'object'
 }
 
-const useTable = <T extends ApiKeys, C extends Partial<ModelType<T>> = Partial<ModelType<T>>, D extends boolean = true>(
+const useTable = <
+  T extends ApiKeys,
+  C extends Partial<ModelType<T>> = Partial<ModelType<T>>,
+  D extends boolean | OptionalPage = true
+>(
   api: T,
   additional?: C,
   pageQuery?: D,
@@ -86,6 +90,12 @@ const useTable = <T extends ApiKeys, C extends Partial<ModelType<T>> = Partial<M
     return multipleType ? (result.value as MultipleResult<ExtractApiResponse<T>>)?.pageCount || 1 : 1
   })
 
+  const total = computed(() => {
+    return multipleType
+      ? (result.value as MultipleResult<ExtractApiResponse<T>>)?.totalCount || 0
+      : (result.value as Array<any>).length || 0
+  })
+
   const data = computed(() => {
     return multipleType
       ? (result.value as MultipleResult<ExtractMultipleApiResponse<T>>)?.result || []
@@ -97,6 +107,7 @@ const useTable = <T extends ApiKeys, C extends Partial<ModelType<T>> = Partial<M
       currentPage: baseParams.pageNumber,
       pageSize: baseParams.pageSize,
       pageCount: pageCount.value,
+      total: total,
     })
   })
 

+ 0 - 1
src/hooks/useTableCheck.ts

@@ -34,7 +34,6 @@ const useTableCheck = <T extends TableDataType<InputDataType>>(data: T, auto = t
     const d = unref(data)
     let result: RowType<T>[] = []
     if (d) {
-      console.log(d)
       if (isMultipleData(d)) {
         result = d?.result?.map((d, index) => ({ ...d, index: d.index ?? index })) as RowType<T>[]
       } else {

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

@@ -51,7 +51,7 @@ const { fetch: editMainQuestion, loading: editing } = useFetch('editMainQuestion
 const model = reactive<ExtractApiParams<'addMainQuestion'>>({
   subjectCode: props.subjectCode,
   groupNumber: void 0,
-  category: 'COMPOSITION',
+  category: 'WRITING',
   intervalScore: void 0,
   mainNumber: void 0,
   mainTitle: '',
@@ -129,7 +129,7 @@ const items = computed<EpFormItem[]>(() => [
       slot: {
         placeholder: '成绩表对应字段',
         options: [
-          { label: '作文分', value: 'COMPOSITION' },
+          { label: '作文分', value: 'WRITING' },
           { label: '翻译分', value: 'TRANSLATE' },
         ],
       },

+ 0 - 39
src/modules/admin-user/manage/hooks/useUserManageTable.ts

@@ -4,45 +4,6 @@ import { ROLE } from '@/constants/dicts'
 
 import type { EpTableProps, EpTableColumn } from 'global-type'
 
-// function mockNumber(min = 1, max = 10000) {
-//   return Math.random() * (max - min + 1) + min
-// }
-
-// function mockBoolean() {
-//   return Math.random() > 0.5
-// }
-
-// function mockString() {
-//   return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
-//     const r = (Math.random() * 16) | 0,
-//       v = c == 'x' ? r : (r & 0x3) | 0x8
-//     return v.toString(16)
-//   })
-// }
-
-// function mockRole() {
-//   return Object.keys(ROLE)[((Math.random() * 10000) | 0) % 4] as ROLE
-// }
-
-// const defaultUserList = Array.from({ length: 10 }).map((_) => {
-//   const role = mockRole()
-//   return {
-//     id: mockNumber(1, 30),
-//     enable: mockBoolean(),
-//     examId: mockNumber(),
-//     groupId: mockNumber(),
-//     loginName: mockString(),
-//     markingGroupNumber: mockNumber(),
-//     name: mockString(),
-//     role,
-//     roleName: ROLE[role],
-//     subjectCode: mockString(),
-//     createTime: '2022-10-17 09:22:33',
-//     updateTime: '2022-10-17 09:22:33',
-//     updaterName: mockString(),
-//   } as MultipleResponseType<'getUserList'>
-// })
-
 const useUserManageTable = (model?: ModelType<'getUserList'>) => {
   const { data, ...other } = useTable('getUserList', model)
 

+ 163 - 2
src/modules/analysis/group-monitoring-detail/index.vue

@@ -1,10 +1,171 @@
 <template>
-  <div class="">小组监控数据详情</div>
+  <div class="flex direction-column full">
+    <div class="flex items-center p-extra-small fill-blank header-view">
+      <el-button size="small" plain @click="back">返回</el-button>
+      <message class="m-l-auto m-r-base"></message>
+      <user-info></user-info>
+    </div>
+    <div class="flex fill-blank detail-info">
+      <div class="flex-1 flex p-base detail-info-chart">
+        <div class="flex-1">
+          <vue-echarts></vue-echarts>
+        </div>
+        <div class="flex-1 m-l-base">
+          <vue-echarts></vue-echarts>
+        </div>
+      </div>
+      <div class="flex-1 p-extra-small">
+        <div class="flex items-center justify-between detail-info-table-header">
+          <el-button custom-1 size="small" class="detail-info-label">
+            <span class="">已给分试卷总数:</span>
+            <span class="m-l-extra-small detail-info-label-num">14</span>
+          </el-button>
+          <el-pagination
+            v-bind="pagination"
+            v-model:current-page="pagination.currentPage"
+            size="small"
+            class="m-t-unset"
+            background
+            right
+            :hide-on-single-page="false"
+          ></el-pagination>
+        </div>
+        <base-table ref="tableRef" :data="tableData" :columns="tableColumn" @current-change="onCurrentChange">
+          <template #empty> 暂无数据 </template>
+        </base-table>
+      </div>
+    </div>
+    <div class="flex-1 p-base flex overflow-hidden">
+      <div class="flex direction-column flex-1 radius-base fill-blank relative paper-view">
+        <div class="flex-1 p-extra-small scroll-auto">
+          <img :src="MockImg" alt="" class="paper-img" />
+        </div>
+        <span class="preview" @click="onPreview">
+          <svg-icon name="preview"></svg-icon>
+        </span>
+        <scoring-panel-with-confirm-vue
+          v-model:visible="scoringPanelVisible"
+          v-model:score="modelScore"
+          :main-number="mockTask.mainNumber"
+          @submit="onSubmit"
+        ></scoring-panel-with-confirm-vue>
+      </div>
+      <div class="radius-base p-extra-small fill-blank m-l-base overflow-auto paper-mark-record">
+        <mark-history-list :id="current?.taskId" :model-value="true" :modal="false"></mark-history-list>
+      </div>
+    </div>
+  </div>
+  <image-preview v-model="previewModalVisible" :url="MockImg"></image-preview>
 </template>
 
 <script setup lang="ts" name="GroupMonitoringDetail">
 /** 小组监控数据详情 */
 import { reactive, ref } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { ElButton, ElPagination } from 'element-plus'
+import useFetch from '@/hooks/useFetch'
+import useTable from '@/hooks/useTable'
+import useTableCheck from '@/hooks/useTableCheck'
+import VueEcharts from 'vue-echarts'
+import BaseTable from '@/components/element/BaseTable.vue'
+import Message from '@/components/shared/Message.vue'
+import UserInfo from '@/components/shared/UserInfo.vue'
+import ScoringPanelWithConfirmVue from '@/components/shared/ScoringPanelWithConfirm.vue'
+import ImagePreview from '@/components/shared/ImagePreview.vue'
+import MarkHistoryList from '@/components/shared/MarkHistoryList.vue'
+import SvgIcon from '@/components/common/SvgIcon.vue'
+
+import MockImg from '@/assets/mock/SAMPA-1.jpg'
+
+import type { ExtractMultipleApiResponse } from 'api-type'
+import type { EpTableColumn } from 'global-type'
+
+const { back } = useRouter()
+const { query } = useRoute()
+
+/** 给分板 */
+const scoringPanelVisible = ref<boolean>(true)
+
+/** 图片预览 */
+const previewModalVisible = ref<boolean>(false)
+
+/** 分数 */
+const modelScore = ref<number[]>([])
+
+const mockTask = ref<{ mainNumber: number }>({
+  mainNumber: 1,
+})
+
+/** 预览试卷 */
+const onPreview = () => {
+  previewModalVisible.value = true
+}
+
+/** 抽查详情列表 */
+const { pagination, data } = useTable(
+  'getGroupMonitorDetail',
+  {
+    operateType: query.operateType as 'VIEW' | 'MARK',
+    headerId: query.headerId as string,
+  },
+  { pageSize: 4 }
+)
+
+const { tableRef, current, onCurrentChange, tableData } = useTableCheck(data)
+
+/** 抽查详情表格配置 */
+const tableColumn: EpTableColumn<ExtractMultipleApiResponse<'getGroupMonitorDetail'>>[] = [
+  { label: '密号', prop: 'secretNumber' },
+  { label: '评卷员', prop: 'markerName' },
+  { label: '给分', prop: 'markerScore' },
+  { label: '组长给分', prop: 'headerScore' },
+  { label: '客观分', prop: 'objectiveScore' },
+  { label: '客主比', prop: 'headerRatio' },
+  { label: '分档', prop: 'scoreLevel' },
+  { label: '评卷时间', prop: 'markTime' },
+]
+
+const onSubmit = async () => {
+  try {
+    if (!current.value?.taskId) {
+      return
+    }
+    await useFetch('markMonitorDetailTask').fetch({ taskId: current.value.taskId, scores: modelScore.value })
+  } catch (error) {
+    console.error(error)
+  }
+}
 </script>
 
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+.header-view {
+  border-bottom: $OnePixelLine;
+}
+.detail-info {
+  height: 280px;
+  .detail-info-chart {
+    border-right: $OnePixelLine;
+  }
+  .detail-info-label {
+    .detail-info-label-num {
+      width: 32px;
+      height: 24px;
+      line-height: 24px;
+      background: #00987b;
+      border-radius: 4px;
+    }
+  }
+}
+.paper-view {
+  .preview {
+    position: absolute;
+    cursor: pointer;
+    top: 10px;
+    right: 20px;
+    font-size: 24px;
+  }
+}
+.paper-mark-record {
+  width: 340px;
+}
+</style>

+ 43 - 4
src/modules/analysis/group-monitoring/index.vue

@@ -15,8 +15,9 @@
   </div>
 </template>
 
-<script setup lang="ts" name="GroupMonitoring">
+<script setup lang="tsx" name="GroupMonitoring">
 /** 小组监控 */
+import { useRouter } from 'vue-router'
 import { ElButton } from 'element-plus'
 import BaseForm from '@/components/element/BaseForm.vue'
 import BaseTable from '@/components/element/BaseTable.vue'
@@ -25,12 +26,15 @@ import useVW from '@/hooks/useVW'
 import useFormFilter from './hooks/useFormFilter'
 
 import type { EpTableColumn } from 'global-type'
+import type { ExtractApiResponse } from 'api-type'
+
+const { push } = useRouter()
 
 const { model, formModel, items, onOptionInit } = useFormFilter()
 
 const { loading, fetch: getGroupMonitor, result } = useFetch('getGroupMonitor')
 
-const columns: EpTableColumn[] = [
+const columns: EpTableColumn<ExtractArrayValue<ExtractApiResponse<'getGroupMonitor'>>>[] = [
   { label: '组长', prop: 'markingGroupLeader' },
   { label: '已浏览系统抽查卷数', prop: 'sysCheckCount' },
   { label: '已给分系统抽查卷数', prop: 'sysCheckReScoreCount' },
@@ -38,8 +42,28 @@ const columns: EpTableColumn[] = [
   { label: '已给分问题卷数', prop: 'problemReScoreCount' },
   { label: '已浏览自定义抽查卷数', prop: 'customCheckCount' },
   { label: '已给分自定抽查卷数', prop: 'customCheckReScoreCount' },
-  { label: '已浏览试卷总数', prop: 'totalCount' },
-  { label: '已给分试卷总数', prop: 'totalReScoreCount' },
+  {
+    label: '已浏览试卷总数',
+    prop: 'totalCount',
+    formatter(row) {
+      return (
+        <ElButton type="primary" link onClick={() => viewMonitoringDetail(row, 'VIEW')}>
+          {row.totalCount}
+        </ElButton>
+      )
+    },
+  },
+  {
+    label: '已给分试卷总数',
+    prop: 'totalReScoreCount',
+    formatter(row) {
+      return (
+        <ElButton type="primary" link onClick={() => viewMonitoringDetail(row, 'MARK')}>
+          {row.totalReScoreCount}
+        </ElButton>
+      )
+    },
+  },
 ]
 
 /** 刷新按钮 */
@@ -47,6 +71,21 @@ function onSearch() {
   getGroupMonitor(formModel.value)
 }
 
+/** 查看抽查详情 */
+
+function viewMonitoringDetail(
+  row: ExtractArrayValue<ExtractApiResponse<'getGroupMonitor'>>,
+  operateType: 'VIEW' | 'MARK'
+) {
+  push({
+    name: 'AnalysisGroupDetail',
+    query: {
+      operateType,
+      headerId: row.markingGroupLeaderId,
+    },
+  })
+}
+
 onOptionInit(onSearch)
 </script>
 

+ 12 - 6
src/modules/analysis/monitoring/index.vue

@@ -200,9 +200,14 @@ const cards: Card[] = [
 ]
 
 /** 跳转抽查详情 */
-const viewMarkDetail = (row: ExtractRecordValue<ExtractApiResponse<'getStatistics'>>) => {
-  console.log('跳转抽查详情', row)
-  push({ name: 'AnalysisViewMarked' })
+const viewMarkDetail = (row: ExtractArrayValue<ExtractRecordValue<ExtractApiResponse<'getStatistics'>>>) => {
+  push({
+    name: 'AnalysisViewMarked',
+    params: {
+      markerId: row.markerId,
+      markerName: row.markerName,
+    },
+  })
 }
 
 const tableProps: EpTableProps = {
@@ -217,17 +222,18 @@ const tableProps: EpTableProps = {
   },
 }
 
-const getColumns = (valueLabel: string): EpTableColumn[] => {
+const getColumns = (
+  valueLabel: string
+): EpTableColumn<ExtractArrayValue<ExtractRecordValue<ExtractApiResponse<'getStatistics'>>>>[] => {
   return [
     {
       label: '老师ID',
-      prop: 'markerId',
       formatter(row) {
         return row.markerId === 0 ? (
           '全体'
         ) : (
           <ElButton type="primary" link onClick={() => viewMarkDetail(row)}>
-            {row.markerId}
+            {row.markerName?.split('-')?.[0]}
           </ElButton>
         )
       },

+ 12 - 6
src/modules/analysis/statistics/index.vue

@@ -187,22 +187,28 @@ const cards: Card[] = [
 ]
 
 /** 跳转抽查详情 */
-const viewMarkDetail = (row: ExtractRecordValue<ExtractApiResponse<'getStatistics'>>) => {
-  console.log('跳转抽查详情', row)
-  push({ name: 'AnalysisViewMarked' })
+const viewMarkDetail = (row: ExtractArrayValue<ExtractRecordValue<ExtractApiResponse<'getStatistics'>>>) => {
+  push({
+    name: 'AnalysisViewMarked',
+    params: {
+      markerId: row.markerId,
+      markerName: row.markerName,
+    },
+  })
 }
 
-const getColumns = (valueLabel: string): EpTableColumn[] => {
+const getColumns = (
+  valueLabel: string
+): EpTableColumn<ExtractArrayValue<ExtractRecordValue<ExtractApiResponse<'getStatistics'>>>>[] => {
   return [
     {
       label: '老师ID',
-      prop: 'markerId',
       formatter(row) {
         return row.markerId === 0 ? (
           '全体'
         ) : (
           <ElButton type="primary" link onClick={() => viewMarkDetail(row)}>
-            {row.markerId}
+            {row.markerName?.split('-')?.[0]}
           </ElButton>
         )
       },

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

@@ -1,10 +1,242 @@
 <template>
-  <div class="">提取阅卷明细</div>
+  <div class="flex direction-column full">
+    <mark-header :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']" @click="onOperationClick">
+      <el-button class="m-l-base m-r-auto" size="small" type="primary" @click="onEditScore">修改给分</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 table-view">
+        <div class="flex items-center p-b-base m-b-base marker-name">
+          <span>评卷员</span>
+          <span>{{ query.markerName }}</span>
+        </div>
+        <div class="flex items-center justify-between detail-info-table-header">
+          <el-button custom-1 size="small" class="detail-info-label">
+            <span class="">总数:</span>
+            <span class="m-l-extra-small detail-info-label-num">{{ pagination.total }}</span>
+          </el-button>
+          <el-pagination
+            v-bind="pagination"
+            v-model:current-page="pagination.currentPage"
+            size="small"
+            class="m-t-unset"
+            background
+            right
+            :hide-on-single-page="false"
+          ></el-pagination>
+        </div>
+        <base-table
+          ref="tableRef"
+          size="small"
+          v-bind="pagination"
+          :data="tableData"
+          :columns="columns"
+          @current-change="onCurrentChange"
+          @row-dblclick="onDbClick"
+        ></base-table>
+      </div>
+    </div>
+  </div>
+  <image-preview v-model="previewModalVisible" :url="MockImg"></image-preview>
+  <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>
+  <mark-history-list :id="currentViewHistory?.secretNumber" v-model="visibleHistory" type="secret"></mark-history-list>
 </template>
 
 <script setup lang="ts" name="ViewMarkedDetail">
 /** 提取阅卷明细 */
-import { reactive, ref } from 'vue'
+import { reactive, ref, computed } from 'vue'
+import { useRoute } from 'vue-router'
+import { ElButton } from 'element-plus'
+import { useSetImgBg } from '@/hooks/useSetImgBg'
+import useFetch from '@/hooks/useFetch'
+import useTable from '@/hooks/useTable'
+import useTableCheck from '@/hooks/useTableCheck'
+import useMarkHeader from '@/hooks/useMarkHeader'
+import MarkHeader from '@/components/shared/MarkHeader.vue'
+import BaseTable from '@/components/element/BaseTable.vue'
+import MarkHistoryList from '@/components/shared/MarkHistoryList.vue'
+import RightButton from '@/components/shared/RightButton.vue'
+import SvgIcon from '@/components/common/SvgIcon.vue'
+import ScoringPanelWithConfirm from '@/components/shared/ScoringPanelWithConfirm.vue'
+import ImagePreview from '@/components/shared/ImagePreview.vue'
+
+import MockImg from '@/assets/mock/SAMPA-1.jpg'
+
+import type { SetImgBgOption } from '@/hooks/useSetImgBg'
+import type { ExtractMultipleApiResponse, ExtractApiParams, ExtractApiResponse } from 'api-type'
+import type { MarkHeaderInstance, EpTableColumn } from 'global-type'
+
+type RowType = ExtractMultipleApiResponse<'getPersonalMarkDetail'> & { index: number }
+
+const props = defineProps<{
+  markerId: string
+}>()
+
+const { query } = useRoute()
+
+/** 给分板 */
+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 = () => {
+  fetchTable()
+}
+
+/** 预览试卷 */
+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 columns: EpTableColumn<RowType>[] = [
+  { label: '评卷员', prop: 'markerName' },
+  { label: '密号', prop: 'secretNumber' },
+  { label: '给分', prop: 'markerScore' },
+  { label: '客观分', prop: 'objectiveScore' },
+  { label: '客主比', prop: 'markerRatio' },
+  { label: '评卷时间', prop: 'markTime' },
+]
+
+const { pagination, data, fetchTable } = useTable('getPersonalMarkDetail', {
+  markerId: props.markerId,
+  score: (query.score as string) || '',
+})
+
+const {
+  tableRef,
+  tableData,
+  current,
+  currentView: currentViewHistory,
+  next: checkNext,
+  visibleHistory,
+  onDbClick,
+  onCurrentChange,
+} = useTableCheck(data)
+
+/** 确定给分 */
+const { fetch: updatePersonalMarkDetailScore } = useFetch('updatePersonalMarkDetailScore')
+
+const onSubmit = () => {
+  if (current.value) {
+    updatePersonalMarkDetailScore({ taskId: current.value.taskId, scores: modelScore.value })
+  }
+}
 </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%;
+      }
+    }
+  }
+  .table-view {
+    width: 580px;
+
+    .marker-name {
+      border-bottom: $OnePixelLine;
+    }
+    .detail-info-label {
+      .detail-info-label-num {
+        width: 32px;
+        height: 24px;
+        line-height: 24px;
+        background: #00987b;
+        border-radius: 4px;
+      }
+    }
+  }
+}
+</style>

+ 69 - 67
src/modules/example/ImageModify.vue

@@ -1,85 +1,87 @@
 <template>
-  <div class="text-center">
-    <!-- <h1 class="" :style="`color: ${drawing ? 'red' : 'blue'}`">修改图片背景色</h1>
-    <div class="flex justify-center">
-      <el-button type="primary" class="" @click="reset">Reset</el-button>
-      <el-color-picker v-model="color" show-alpha color-format="rgb" size="large">
-        <el-button>选择颜色</el-button>
-      </el-color-picker>
-      <div class="flex">
-        <el-progress :percentage="distance / 255"> {{ distance }} </el-progress>
-        <el-button-group>
-          <el-button :icon="Minus" @click="decrease" />
-          <el-button :icon="Plus" @click="increase" />
-        </el-button-group>
+  <div class="flex direction-column full">
+    <mark-header :exclude-operations="['delete', 'bookmark']" @click="onOperationClick"> </mark-header>
+    <div class="flex-1 overflow-hidden p-base mark-container">
+      <div
+        class="p-base radius-base full fill-blank scroll-auto mark-content"
+        :class="{ 'text-center': center }"
+        :style="{ 'background-color': backgroundColor }"
+      >
+        <span class="preview" @click="onPreview">
+          <svg-icon name="preview"></svg-icon>
+        </span>
+        <img :src="dataUrl" alt="" class="paper-img" :style="{ 'background-color': frontColor }" />
       </div>
     </div>
-    <div class="">
-      <el-upload v-model:file-list="imageList" list-type="picture-card" :auto-upload="false">上传</el-upload>
-    </div>
-
-    <div class="flex items-center">
-      <div class="flex-1">
-        <h4 class="">Preview</h4>
-        <div class="image">
-          <img v-if="imageList[0]?.url" ref="image" :src="imageList[0]?.url" alt="" />
-        </div>
-      </div>
-      <div class="flex-1">
-        <h4 class="">Canvas</h4>
-        <div class="image">
-          <img :src="dataUrl" alt="" />
-        </div>
-      </div>
-    </div> -->
   </div>
+  <image-preview v-model="previewModalVisible" :url="MockImg"></image-preview>
 </template>
 
 <script setup lang="ts" name="ImageModify">
-// import { ref, computed, watch } from 'vue'
-// import { ElButton, ElButtonGroup, ElUpload, ElColorPicker, UploadFiles, ElProgress } from 'element-plus'
-// import { convertColor, BLACK, DISTANCE, useSetImgBg } from '@/hooks/useSetImgBg'
-// import { Minus, Plus } from '@element-plus/icons-vue'
+import { computed, reactive, ref, watch } from 'vue'
+import { useSetImgBg } from '@/hooks/useSetImgBg'
+import useMarkHeader from '@/hooks/useMarkHeader'
+import MarkHeader from '@/components/shared/MarkHeader.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 { MarkHeaderInstance } from 'global-type'
 
-// import type { SetImgBgOption } from '@/hooks/useSetImgBg'
+/** 图片预览 */
+const previewModalVisible = ref<boolean>(false)
 
-// const canvas = ref<HTMLCanvasElement | null>(null)
-// const image = ref<HTMLImageElement | null>(null)
-// const color = ref('rgba(95, 228, 83, 255)')
-// const imageList = ref<UploadFiles>([])
+const {
+  rotate,
+  scale,
+  center,
+  frontColor,
+  backgroundColor,
+  onBack,
+  onScaleChange,
+  onCenter,
+  onRotate,
+  setBackgroundColor,
+  setFrontColor,
+  onViewSample,
+  onViewStandard,
+} = useMarkHeader()
 
-// let distance = ref(DISTANCE)
+const imgOption = computed<SetImgBgOption>(() => {
+  return {
+    image: MockImg,
+    immediate: true,
+    rotate: rotate.value,
+    scale: scale.value,
+  }
+})
 
-// const P = computed<SetImgBgOption>(() => ({
-//   image: image.value,
-//   canvas: canvas.value,
-//   useNaturalSize: false,
-//   distance: distance.value,
-//   markColor: BLACK,
-//   bgColor: convertColor(color.value),
-// }))
+const { drawing, dataUrl } = useSetImgBg(imgOption)
 
-// const { drawing, dataUrl } = useSetImgBg(P)
+/** 预览试卷 */
+const onPreview = () => {
+  previewModalVisible.value = true
+}
 
-// const reset = () => {
-//   color.value = 'rgba(95, 228, 83, 1)'
-//   imageList.value = []
-// }
+type OperationClick = MarkHeaderInstance['onClick']
 
-// const increase = () => {
-//   distance.value += 10
-//   if (distance.value > 255) {
-//     distance.value = 255
-//   }
-// }
-// const decrease = () => {
-//   distance.value -= 10
-//   if (distance.value < 0) {
-//     distance.value = 0
-//   }
-// }
+type OperationType = Parameters<Exclude<OperationClick, undefined>>[0]['type']
 
-// useResizeObserver(image, update)
+const operationHandles: Partial<Record<OperationType, (...args: any) => void>> = {
+  back: onBack,
+  'scale-change': onScaleChange,
+  center: onCenter,
+  rotate: onRotate,
+  'front-color': setFrontColor,
+  'background-color': setBackgroundColor,
+  example: onViewSample,
+  standard: onViewStandard,
+}
+
+const onOperationClick: OperationClick = ({ type, value }) => {
+  operationHandles[type]?.(value)
+}
 </script>
 
 <style scoped lang="scss">

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

@@ -17,7 +17,7 @@
           <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">
+      <div class="p-base radius-base fill-blank scroll-auto m-l-base table-view">
         <base-form size="small" :model="formModel" :items="formItems" :label-width="useVW(40)">
           <template #form-item-search>
             <el-button type="primary" @click="onSearch">查询</el-button>
@@ -262,7 +262,7 @@ onOptionInit(onSearch)
       }
     }
   }
-  .problem-list {
+  .table-view {
     width: 580px;
   }
 }

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

@@ -17,7 +17,7 @@
           <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">
+      <div class="p-base radius-base fill-blank scroll-auto m-l-base table-view">
         <base-form size="small" :model="formModel" :items="formItems" :label-width="useVW(60)">
           <template #form-item-search>
             <el-button type="primary" @click="onSearch">查询</el-button>
@@ -314,7 +314,7 @@ onOptionInit(onSearch)
       }
     }
   }
-  .problem-list {
+  .table-view {
     width: 580px;
   }
 }

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

@@ -17,7 +17,7 @@
           <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">
+      <div class="p-base radius-base fill-blank scroll-auto m-l-base table-view">
         <base-form size="small" :model="formModel" :items="formItems" :label-width="useVW(40)">
           <template #form-item-search>
             <el-button type="primary" @click="onSearch">查询</el-button>
@@ -264,7 +264,7 @@ onOptionInit(onSearch)
       }
     }
   }
-  .problem-list {
+  .table-view {
     width: 580px;
   }
 }

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

@@ -13,7 +13,7 @@
           <img :src="dataUrl" alt="" class="paper-img" />
         </div>
       </div>
-      <div class="p-base radius-base fill-blank scroll-auto m-l-base problem-list">
+      <div class="p-base radius-base fill-blank scroll-auto m-l-base table-view">
         <base-form size="small" :model="formModel" :items="formItems" :label-width="useVW(40)">
           <template #form-item-search>
             <el-button type="primary" @click="onSearch">查询</el-button>
@@ -216,7 +216,7 @@ onOptionInit(onSearch)
       }
     }
   }
-  .problem-list {
+  .table-view {
     width: 580px;
   }
 }

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

@@ -18,7 +18,7 @@
           <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">
+      <div class="p-base radius-base fill-blank scroll-auto m-l-base table-view">
         <base-form size="small" :model="formModel" :items="formItems" :label-width="useVW(40)">
           <template #form-item-search>
             <el-button type="primary" @click="onSearch">查询</el-button>
@@ -224,7 +224,7 @@ onOptionInit(onSearch)
       }
     }
   }
-  .problem-list {
+  .table-view {
     width: 580px;
   }
 }

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

@@ -24,7 +24,7 @@
           @submit="onSubmit"
         ></scoring-panel-with-confirm>
       </div>
-      <div class="p-base radius-base fill-blank scroll-auto m-l-base problem-list">
+      <div class="p-base radius-base fill-blank scroll-auto m-l-base table-view">
         <base-form size="small" :model="formModel" :items="formItems" :label-width="useVW(62)">
           <template #form-item-search>
             <el-button type="primary" @click="onSearch">查询</el-button>
@@ -267,7 +267,7 @@ onOptionInit(onSearch)
       }
     }
   }
-  .problem-list {
+  .table-view {
     width: 580px;
   }
 }

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

@@ -198,10 +198,19 @@ const onSubmit: InstanceType<typeof ScoringPanelWithConfirm>['onSubmit'] = async
 /** 提交问题卷 */
 const onConfirmProblem = async () => {
   try {
+    if (!currentTask.value) {
+      return
+    }
     problemVisible.value = false
     if (problemClass.value === 'similar') {
-      push({ name: 'SubmitSimilar' })
-    } else if (currentTask.value) {
+      push({
+        name: 'SubmitSimilar',
+        params: { taskId: currentTask.value.taskId },
+        query: {
+          secretNumber: currentTask.value.secretNumber,
+        },
+      })
+    } else {
       await submitMarkTask({
         markScore: 0,
         markScores: [],

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

@@ -23,7 +23,7 @@
           @submit="onSubmit"
         ></scoring-panel-with-confirm>
       </div>
-      <div class="p-base radius-base fill-blank scroll-auto m-l-base problem-list">
+      <div class="p-base radius-base fill-blank scroll-auto m-l-base table-view">
         <base-form size="small" :model="formModel" :items="formItems" :label-width="useVW(62)">
           <template #form-item-search>
             <el-button type="primary" @click="onSearch">查询</el-button>
@@ -282,7 +282,7 @@ onOptionInit(onSearch)
       }
     }
   }
-  .problem-list {
+  .table-view {
     width: 480px;
   }
 }

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

@@ -23,7 +23,7 @@
           @submit="onSubmit"
         ></scoring-panel-with-confirm>
       </div>
-      <div class="p-base radius-base fill-blank scroll-auto m-l-base problem-list">
+      <div class="p-base radius-base fill-blank scroll-auto m-l-base table-view">
         <base-form size="small" :model="formModel" :items="formItems" :label-width="useVW(62)">
           <template #form-item-search>
             <el-button type="primary" @click="onSearch">查询</el-button>
@@ -155,7 +155,7 @@ const onOperationClick: OperationClick = ({ type, value }) => {
 
 const formModel = reactive<ExtractApiParams<'getReMarkPaperList'>>({
   mainNumber: void 0,
-  status: false,
+  confirm: false,
   pageNumber: 1,
   pageSize: 9999999,
 })
@@ -186,7 +186,7 @@ const formItems = computed<EpFormItem[]>(() => [
     rowKey: 'row-1',
     label: '状态',
     labelWidth: useVW(40),
-    prop: 'status',
+    prop: 'confirm',
     slotType: 'select',
     slot: {
       options: [
@@ -259,7 +259,7 @@ onOptionInit(onSearch)
       }
     }
   }
-  .problem-list {
+  .table-view {
     width: 480px;
   }
 }

+ 2 - 2
src/modules/marking/similar/index.vue

@@ -15,7 +15,7 @@
           <img :src="dataUrl" alt="" class="paper-img" />
         </div>
       </div>
-      <div class="p-base radius-base fill-blank scroll-auto m-l-base problem-list">
+      <div class="p-base radius-base fill-blank scroll-auto m-l-base table-view">
         <base-form size="small" :model="formModel" :items="formItems">
           <template #form-item-search>
             <el-button type="primary" @click="onSearch">查询</el-button>
@@ -204,7 +204,7 @@ onOptionInit(onSearch)
       }
     }
   }
-  .problem-list {
+  .table-view {
     width: 580px;
   }
 }

+ 103 - 8
src/modules/marking/submit-similar/index.vue

@@ -1,23 +1,118 @@
 <template>
-  <div class="flex submit-similar">
-    <div class="view-paper">
-      <div class="paper-1"></div>
-      <div class="paper-2"></div>
+  <div class="flex p-base full submit-similar">
+    <div class="flex-1 full-y p-extra-small fill-blank radius-base scroll-auto view-paper">
+      <img :src="current?.url || MockImg" alt="" />
+    </div>
+    <div class="m-l-base p-base fill-blank radius-base history-view">
+      <mark-history-list :id="props.taskId" #default="{ data }" :modal="false">
+        <base-table
+          ref="tableRef"
+          :data="filterSelfTask(data)"
+          :columns="columns"
+          class="history-table"
+          @current-change="onCurrentChange"
+        ></base-table>
+      </mark-history-list>
+      <div class="flex items-center p-l-base">
+        <span>雷同卷号:</span>
+        <span>{{ query.secretNumber || 111 }}</span>
+      </div>
+      <div class="flex items-center p-l-base m-t-extra-small">
+        <span class="checked-secret-number">{{ query.secretNumber || 111 }}</span>
+      </div>
+      <div class="p-l-base m-t-base">
+        <confirm-button class="confirm-buttons" size="small" @confirm="submit" @cancel="onCancel"></confirm-button>
+      </div>
     </div>
-    <div class="m-l-base history-table"></div>
   </div>
 </template>
 
-<script setup lang="ts" name="SubmitSimilar">
+<script setup lang="tsx" name="SubmitSimilar">
 /** 提交雷同卷 */
-import { reactive, ref } from 'vue'
+import { reactive, ref, computed } from 'vue'
+import { useRouter, useRoute } from 'vue-router'
+import { ElRadio } from 'element-plus'
 import useFetch from '@/hooks/useFetch'
+import useTableCheck from '@/hooks/useTableCheck'
 import BaseTable from '@/components/element/BaseTable.vue'
+import MarkHistoryList from '@/components/shared/MarkHistoryList.vue'
+import ConfirmButton from '@/components/common/ConfirmButton.vue'
+
+import MockImg from '@/assets/mock/SAMPA-1.jpg'
+
+import type { ExtractApiResponse } from 'api-type'
+import type { EpTableColumn } from 'global-type'
+
+const props = defineProps<{
+  taskId: number | string
+}>()
+
+const { query } = useRoute()
+const { back } = useRouter()
+
+let historyData = ref<ExtractApiResponse<'getMarkScoreHistoryListWithTask'>>([])
+
+/** 过滤自己 */
+const filterSelfTask = (data?: ExtractApiResponse<'getMarkScoreHistoryListWithTask'>) => {
+  historyData.value =
+    data?.filter((d) => `${d.taskId}` !== `${props.taskId}`).map((d, index) => ({ ...d, index })) || []
+  return historyData.value
+}
 
-const { fetch: getMarkHistory, result } = useFetch('getMarkHistory')
+const { tableRef, current, onCurrentChange } = useTableCheck(historyData)
+
+const columns = computed<EpTableColumn<ExtractArrayValue<ExtractApiResponse<'getMarkScoreHistoryListWithTask'>>>[]>(
+  () => [
+    {
+      label: '选中雷同',
+      formatter(row) {
+        return <ElRadio modelValue={current.value?.secretNumber === row.secretNumber}></ElRadio>
+      },
+      align: 'center',
+    },
+    { label: '密号', prop: 'secretNumber', align: 'center' },
+    { label: '分数', prop: 'markScore', align: 'center' },
+  ]
+)
+
+/** 提交 */
+const submit = async () => {
+  try {
+    await useFetch('submitSimilarPaper').fetch({ taskId: props.taskId, sameTaskId: 2 })
+    back()
+  } catch (error) {
+    console.error(error)
+  }
+}
+
+const onCancel = () => {
+  back()
+}
 </script>
 
 <style scoped lang="scss">
 .submit-similar {
 }
+
+.history-view {
+  width: 360px;
+  font-size: $MediumFont;
+  .history-table {
+    max-height: 65vh;
+  }
+  .checked-secret-number {
+    margin-left: 74px;
+    display: inline-block;
+    padding: 6px 12px;
+    border: $OnePixelLine;
+    color: #e02020;
+    border-radius: 4px;
+  }
+  .confirm-buttons {
+    margin-left: 74px;
+    ::v-deep(.el-button) {
+      width: 80px;
+    }
+  }
+}
 </style>

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

@@ -15,7 +15,7 @@
           <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">
+      <div class="p-base radius-base fill-blank scroll-auto m-l-base table-view">
         <base-table
           ref="tableRef"
           size="small"
@@ -157,7 +157,7 @@ viewTrainingRecord()
       }
     }
   }
-  .problem-list {
+  .table-view {
     width: 580px;
   }
 }

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

@@ -15,7 +15,7 @@
           <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">
+      <div class="p-base radius-base fill-blank scroll-auto m-l-base table-view">
         <base-table
           ref="tableRef"
           size="small"
@@ -146,7 +146,7 @@ viewSamplePaper()
       }
     }
   }
-  .problem-list {
+  .table-view {
     width: 580px;
   }
 }

+ 2 - 2
src/modules/monitor/system-check/index.vue

@@ -20,7 +20,7 @@
           <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">
+      <div class="p-base radius-base fill-blank scroll-auto m-l-base table-view">
         <base-form size="small" :model="formModel" :items="formItems" :label-width="useVW(40)">
           <template #form-item-search>
             <el-button type="primary" @click="onSearch">查询</el-button>
@@ -298,7 +298,7 @@ onOptionInit(onSearch)
       }
     }
   }
-  .problem-list {
+  .table-view {
     width: 480px;
   }
 }

+ 142 - 2
src/modules/monitor/training-monitoring-detail/index.vue

@@ -1,10 +1,150 @@
 <template>
-  <div class="">培训监控调卷详情</div>
+  <div class="flex direction-column full">
+    <mark-header :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']" @click="onOperationClick">
+    </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 table-view">
+        <div class="flex items-center justify-between detail-info-table-header">
+          <el-button custom-1 size="small" class="detail-info-label">
+            <span class="">培训卷共:</span>
+            <span class="m-l-extra-small detail-info-label-num">{{ monitorDetail?.length }}</span>
+          </el-button>
+        </div>
+        <base-table
+          ref="tableRef"
+          size="small"
+          :data="tableData"
+          :columns="columns"
+          @current-change="onCurrentChange"
+          @row-dblclick="onDbClick"
+        ></base-table>
+      </div>
+    </div>
+  </div>
+  <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="TrainingMonitoringDetail">
 /** 培训监控调卷详情 */
-import { reactive, ref } from 'vue'
+import { reactive, ref, computed } from 'vue'
+import { useRoute } from 'vue-router'
+import { ElButton } from 'element-plus'
+import { useSetImgBg } from '@/hooks/useSetImgBg'
+import useFetch from '@/hooks/useFetch'
+import useMarkHeader from '@/hooks/useMarkHeader'
+import useTableCheck from '@/hooks/useTableCheck'
+import MarkHeader from '@/components/shared/MarkHeader.vue'
+import BaseTable from '@/components/element/BaseTable.vue'
+import ImagePreview from '@/components/shared/ImagePreview.vue'
+import RightButton from '@/components/shared/RightButton.vue'
+import SvgIcon from '@/components/common/SvgIcon.vue'
+
+import MockImg from '@/assets/mock/SAMPA-1.jpg'
+import type { SetImgBgOption } from '@/hooks/useSetImgBg'
+import type { ExtractApiResponse } from 'api-type'
+import type { MarkHeaderInstance, EpTableColumn } from 'global-type'
+
+type RowType = ExtractApiResponse<'getTrainingMonitorDetail'> & { index: number }
+
+const { query } = useRoute()
+
+/** 图片预览 */
+const previewModalVisible = ref<boolean>(false)
+
+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 = () => {
+  console.log('刷新')
+}
+
+/** 预览试卷 */
+const onPreview = () => {
+  previewModalVisible.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 columns: EpTableColumn<RowType>[] = [
+  { label: '密号', prop: 'secretNumber' },
+  { label: '标准分', prop: 'score' },
+  { label: '评卷给分', prop: 'scoreLevel' },
+  { label: '分组', prop: 'createName' },
+  { label: '评卷员', prop: 'createTime' },
+  { label: '评卷时间', prop: 'createTime' },
+]
+
+const { fetch: getTrainingMonitorDetail, result: trainingMonitorDetail } = useFetch('getTrainingMonitorDetail')
+const { fetch: getAssessMonitorDetail, result: assessMonitorDetail } = useFetch('getAssessMonitorDetail')
+
+const monitorDetail = computed(() => {
+  return query.type === 'training' ? trainingMonitorDetail.value : assessMonitorDetail.value
+})
+
+const {
+  tableRef,
+  tableData,
+  current,
+  currentView: currentViewHistory,
+  next: checkNext,
+  visibleHistory,
+  onDbClick,
+  onCurrentChange,
+} = useTableCheck(monitorDetail)
 </script>
 
 <style scoped lang="scss"></style>

+ 187 - 2
src/modules/quality/self-check-detail/index.vue

@@ -1,10 +1,195 @@
 <template>
-  <div class="">自查数据详情</div>
+  <div class="flex direction-column full">
+    <mark-header :exclude-operations="['remark', 'problem', 'example', 'delete', 'bookmark']" @click="onOperationClick">
+      <el-button class="m-l-base" size="small" type="primary" plain @click="onEditScore">修改给分</el-button>
+      <el-button class="m-l-base m-r-auto" size="small" type="primary" @click="onSendBack">打回</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 table-view">
+        <div class="flex items-center justify-between detail-info-table-header">
+          <el-button custom-1 size="small" class="detail-info-label">
+            <span class="">自查卷共:</span>
+            <span class="m-l-extra-small detail-info-label-num">{{ selfCheckDataDetail?.length }}</span>
+          </el-button>
+        </div>
+        <base-table
+          ref="tableRef"
+          size="small"
+          :data="tableData"
+          :columns="columns"
+          @current-change="onCurrentChange"
+          @row-dblclick="onDbClick"
+        ></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>
+  <send-back-mark :id="current?.taskId" v-model="sendBackVisible" @rejected="onRejected"></send-back-mark>
 </template>
 
 <script setup lang="ts" name="SelfCheckDetail">
 /** 自查数据详情 */
-import { reactive, ref } from 'vue'
+import { reactive, ref, computed } from 'vue'
+import { useRoute } from 'vue-router'
+import { ElButton } from 'element-plus'
+import { useSetImgBg } from '@/hooks/useSetImgBg'
+import useFetch from '@/hooks/useFetch'
+import useMarkHeader from '@/hooks/useMarkHeader'
+import useTableCheck from '@/hooks/useTableCheck'
+import MarkHeader from '@/components/shared/MarkHeader.vue'
+import BaseTable from '@/components/element/BaseTable.vue'
+import ImagePreview from '@/components/shared/ImagePreview.vue'
+import RightButton from '@/components/shared/RightButton.vue'
+import SvgIcon from '@/components/common/SvgIcon.vue'
+import ScoringPanelWithConfirm from '@/components/shared/ScoringPanelWithConfirm.vue'
+import MarkHistoryList from '@/components/shared/MarkHistoryList.vue'
+import SendBackMark from '@/components/shared/SendBackMark.vue'
+
+import MockImg from '@/assets/mock/SAMPA-1.jpg'
+import type { SetImgBgOption } from '@/hooks/useSetImgBg'
+import type { ExtractApiResponse } from 'api-type'
+import type { MarkHeaderInstance, EpTableColumn } from 'global-type'
+
+type RowType = ExtractApiResponse<'getSelfCheckDataDetail'> & { index: number }
+
+const { query } = useRoute()
+
+/** 给分板 */
+const editScoreVisible = ref<boolean>(false)
+
+/** 图片预览 */
+const previewModalVisible = ref<boolean>(false)
+
+/** 打回弹窗 */
+const sendBackVisible = 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 = () => {
+  console.log('刷新')
+}
+
+/** 预览试卷 */
+const onPreview = () => {
+  previewModalVisible.value = true
+}
+
+/** 修改给分 */
+const onEditScore = () => {
+  editScoreVisible.value = true
+}
+
+/** 打回 */
+const onSendBack = () => {
+  sendBackVisible.value = true
+}
+
+/** 打回成功 */
+const onRejected = () => {
+  checkNext()
+}
+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 columns: EpTableColumn<RowType>[] = [
+  { label: '密号', prop: 'secretNumber' },
+  { label: '标准分', prop: 'score' },
+  { label: '评卷给分', prop: 'scoreLevel' },
+  { label: '分组', prop: 'createName' },
+  { label: '评卷员', prop: 'createTime' },
+  { label: '评卷时间', prop: 'createTime' },
+]
+
+const { fetch: getSelfCheckDataDetail, result: selfCheckDataDetail } = useFetch('getSelfCheckDataDetail')
+
+const {
+  tableRef,
+  tableData,
+  current,
+  currentView: currentViewHistory,
+  next: checkNext,
+  visibleHistory,
+  onDbClick,
+  onCurrentChange,
+} = useTableCheck(selfCheckDataDetail)
+
+/** 修改给分 */
+const { fetch: markSelfCheckData } = useFetch('markSelfCheckData')
+
+const onSubmit = () => {
+  if (current.value) {
+    markSelfCheckData({ taskId: current.value.taskId, scores: modelScore.value })
+  }
+}
 </script>
 
 <style scoped lang="scss"></style>

+ 2 - 2
src/modules/quality/subjective-check/index.vue

@@ -18,7 +18,7 @@
           <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">
+      <div class="p-base radius-base fill-blank scroll-auto m-l-base table-view">
         <base-form size="small" :model="formModel" :items="formItems" :label-width="useVW(62)">
           <template #form-item-search>
             <el-button type="primary" @click="onSearch">查询</el-button>
@@ -312,7 +312,7 @@ onOptionInit(onSearch)
       }
     }
   }
-  .problem-list {
+  .table-view {
     width: 580px;
   }
 }

+ 12 - 11
src/router/analysis.ts

@@ -80,21 +80,22 @@ const routes: RouteRecordRaw[] = [
           sort: 5,
         },
       },
-      {
-        name: 'AnalysisGroupDetail',
-        path: '/analysis/group-detail',
-        component: () => import('@/modules/analysis/group-monitoring-detail/index.vue'),
-        meta: {
-          label: '小组监控抽查详情',
-          menu: false,
-          menuId: 'analysis-group_monitoring-detail',
-        },
-      },
     ],
   },
+  {
+    name: 'AnalysisGroupDetail',
+    path: '/analysis/group-detail',
+    component: () => import('@/modules/analysis/group-monitoring-detail/index.vue'),
+    meta: {
+      label: '小组监控抽查详情',
+      menu: false,
+      menuId: 'analysis-group_monitoring-detail',
+    },
+  },
   {
     name: 'AnalysisViewMarked',
-    path: '/analysis/view-marked',
+    path: '/analysis/view-marked/:markerId',
+    props: true,
     component: () => import('@/modules/analysis/view-marked-detail/index.vue'),
     meta: {
       label: '提取阅卷明细',

+ 4 - 0
src/router/example.ts

@@ -18,6 +18,7 @@ const routes: RouteRecordRaw[] = [
         component: () => import('@/modules/example/Icon.vue'),
         meta: {
           label: '图标预览',
+          auth: false,
           menu: true,
           menuId: 'example-icons',
         },
@@ -28,6 +29,7 @@ const routes: RouteRecordRaw[] = [
         component: () => import('@/modules/example/Image.vue'),
         meta: {
           label: '图片预览',
+          auth: false,
           menu: true,
           menuId: 'example-images',
         },
@@ -38,6 +40,7 @@ const routes: RouteRecordRaw[] = [
         component: () => import('@/modules/example/Components.vue'),
         meta: {
           label: '组件预览',
+          auth: false,
           menu: true,
           menuId: 'example-components',
         },
@@ -47,6 +50,7 @@ const routes: RouteRecordRaw[] = [
         path: 'image-modify',
         component: () => import('@/modules/example/ImageModify.vue'),
         meta: {
+          auth: false,
           label: '更换图片背景色',
           menu: true,
           menuId: 'example-image_modify',

+ 2 - 1
src/router/marking.ts

@@ -122,7 +122,8 @@ const markingRoutes: RouteRecordRaw[] = [
   },
   {
     name: 'SubmitSimilar',
-    path: '/marking/submit-similar',
+    path: '/marking/submit-similar/:taskId',
+    props: true,
     component: () => import('@/modules/marking/submit-similar/index.vue'),
     meta: {
       label: '提交雷同卷',

+ 126 - 4
types/api.d.ts

@@ -474,6 +474,7 @@ declare module 'api-type' {
       id: number
       markScore: number
       markerName: string
+      scoreLevel: 'LEVEL_1' | 'LEVEL_2' | 'LEVEL_3' | 'LEVEL_4' | 'LEVEL_5'
       markTime: string
       secretNumber: string
       status: string
@@ -481,9 +482,9 @@ declare module 'api-type' {
       url: string
     }
 
-    type GetMarkScoreHistoryListWithTask = BaseDefine<{ taskId: number }, MarkScoreHistory[]>
+    type GetMarkScoreHistoryListWithTask = BaseDefine<{ taskId: number | string }, MarkScoreHistory[]>
 
-    type GetMarkScoreHistoryListWithSecret = BaseDefine<{ secretNumber: number }, MarkScoreHistory[]>
+    type GetMarkScoreHistoryListWithSecret = BaseDefine<{ secretNumber: number | string }, MarkScoreHistory[]>
 
     type RejectMarkHistory = BaseDefine<{ description: string; id: number; reason: string }>
 
@@ -537,7 +538,7 @@ declare module 'api-type' {
     }
 
     type GetReMarkPaperList = BaseDefine<
-      MultipleQuery<{ mainNumber?: number; status: boolean }>,
+      MultipleQuery<{ mainNumber?: number; confirm: boolean }>,
       MultipleResult<ReMarkPaperListItem>
     >
 
@@ -605,6 +606,8 @@ declare module 'api-type' {
       }>
     >
 
+    type SubmitSimilarPaper = BaseDefine<{ sameTaskId: number | string; taskId: number | string }>
+
     export interface ApiMap {
       /** 自定义查询 - 抽查 */
       getCustomQueryTasks: GetCustomQueryTasks
@@ -654,6 +657,8 @@ declare module 'api-type' {
       confirmIsSimilar: ConfirmIsSimilar
       /** 导出雷同 */
       exportSimilarPaper: ExportSimilarPaper
+      /** 提交雷同卷 */
+      submitSimilarPaper: SubmitSimilarPaper
     }
   }
 
@@ -752,7 +757,7 @@ declare module 'api-type' {
       remarkType: RemarkType
       /** 科目代码 */
       subjectCode: string
-      /** 成绩表对应题目类型,可用值:COMPOSITION,TRANSLATE */
+      /** 成绩表对应题目类型,可用值:WRITING,TRANSLATE */
       category: QuestionCategory
     }
 
@@ -1193,6 +1198,36 @@ declare module 'api-type' {
       SelfCheckAnalysisDiffListItem[]
     >
 
+    /** 质量分析 - 自查数据详情 */
+    interface SelfCheckDataDetail {
+      diff: number
+      filePath: string
+      mainName: string
+      mainNumber: number
+      markTime: string
+      markerName: string
+      markerScore: number
+      secretNumber: string
+      selfScore: number
+      taskId: number
+    }
+    type GetSelfCheckDataDetail = BaseDefine<
+      {
+        subjectCode: string
+        mainNumber: number | string
+        markingGroupNumber: number | string
+        startTime: string
+        endTime: string
+      },
+      SelfCheckDataDetail[]
+    >
+
+    /** 质量统计-自查数据打分 */
+    type MarkSelfCheckData = BaseDefine<{ taskId?: number; scores: number[] }>
+
+    /** 质量统计-自查数据打回 */
+    type RejectSelfCheckData = BaseDefine<{ taskId?: number; reason: string; description: string }>
+
     /** 质量分析- 抽查情况统计 */
     interface StatisticCheckInfo {
       customCheckCount: number
@@ -1356,6 +1391,8 @@ declare module 'api-type' {
       sysCheckCount: number
       /** 组长 */
       markingGroupLeader: string
+      /** 组长ID */
+      markingGroupLeaderId: string
       /** 小组号 */
       markingGroupNumber: number
       /** 已给分自定义抽查卷数 */
@@ -1371,6 +1408,29 @@ declare module 'api-type' {
     }
     type GetGroupMonitor = BaseDefine<BaseFilterOption, GroupMonitor[]>
 
+    /** 小组监控 - 抽查详情 */
+    interface GroupMonitorDetail {
+      filePath: string
+      headerRatio: string
+      headerScore: number
+      mainName: string
+      mainNumber: number
+      markTime: string
+      markerName: string
+      markerScore: number
+      objectiveScore: number
+      scoreLevel: string
+      secretNumber: string
+      taskId: number
+    }
+
+    type GetGroupMonitorDetail = BaseDefine<
+      MultipleQuery<{ operateType: 'VIEW' | 'MARK'; headerId: string | number }>,
+      MultipleResult<GroupMonitorDetail>
+    >
+
+    type MarkMonitorDetailTask = BaseDefine<{ taskId?: number; scores: number[] }>
+
     /** 决策分析- 统计 */
     interface StatisticItem {
       markerId: number
@@ -1475,6 +1535,25 @@ declare module 'api-type' {
     }
     type GetTrainingMonitor = BaseDefine<TrainingMonitorParams, { data: TrainingMonitorResponse[]; header: number[] }>
 
+    interface MonitorDetail {
+      fileName: string
+      filePath: string
+      forceGroupNumber: number
+      group: string
+      mainNumber: number
+      markTime: string
+      markerName: string
+      markScore: number
+      score: number
+      secretNumber: string
+      taskId: number
+    }
+
+    /**培训监控 - 培训卷调卷详情 */
+    type GetTrainingMonitorDetail = BaseDefine<{ markerId: number; taskType: 'SAMPLE_A' | 'SAMPLE_B' }, MonitorDetail[]>
+
+    /**培训监控 - 强制考核卷调卷详情 */
+    type GetAssessMonitorDetail = BaseDefine<{ forceGroupMarkerId: number }, MonitorDetail[]>
     /** 个人统计 */
     interface PersonalStatistic {
       avg: number
@@ -1487,6 +1566,31 @@ declare module 'api-type' {
     }
     type GetPersonalStatistic = BaseDefine<{ startTime: string; endTime: string; markerId?: number }, PersonalStatistic>
 
+    /** 提取阅卷明细 */
+    interface PersonalMarkDetail {
+      filePath: string
+      mainName: string
+      mainNumber: number
+      markTime: string
+      markerName: string
+      markerRatio: string
+      markerScore: number
+      objectiveScore: number
+      secretNumber: string
+      status: string
+      taskId: number
+    }
+
+    type GetPersonalMarkDetail = BaseDefine<
+      MultipleQuery<{
+        markerId?: number | string
+        score?: number | string
+      }>,
+      MultipleResult<PersonalMarkDetail>
+    >
+
+    type UpdatePersonalMarkDetailScore = BaseDefine<{ taskId?: number; scores: number[] }>
+
     interface SystemSpotListItem {
       examNumber: string
       filePath: string
@@ -1523,6 +1627,12 @@ declare module 'api-type' {
       selfCheckAnalysis: SelfCheckAnalysis
       /** 质量统计-自查一致性分析-离差列表 */
       selfCheckAnalysisDiffList: SelfCheckAnalysisDiffList
+      /** 质量统计- 自查数据详情 */
+      getSelfCheckDataDetail: GetSelfCheckDataDetail
+      /** 质量统计-自查数据打分 */
+      markSelfCheckData: MarkSelfCheckData
+      /** 质量统计-自查数据打回 */
+      rejectSelfCheckData: RejectSelfCheckData
       /** 质量统计-抽查情况统计 */
       statisticCheckOverview: StatisticCheckOverview
       /** 质量统计-主观题校验 */
@@ -1551,6 +1661,10 @@ declare module 'api-type' {
       changeTaskMarker: ChangeTaskMarker
       /** 决策分析-小组监控 */
       getGroupMonitor: GetGroupMonitor
+      /** 决策分析-小组监控-抽查详情 */
+      getGroupMonitorDetail: GetGroupMonitorDetail
+      /** 决策分析-小组监控-抽查详情给分 */
+      markMonitorDetailTask: MarkMonitorDetailTask
       /** 决策分析-监控统计(整体) */
       getStatistics: GetStatistics
 
@@ -1561,8 +1675,16 @@ declare module 'api-type' {
       /** 决策分析-监控统计客观题&主观题分数分布 */
       /**培训监控 */
       getTrainingMonitor: GetTrainingMonitor
+      /**培训监控 - 培训卷调卷详情 */
+      getTrainingMonitorDetail: GetTrainingMonitorDetail
+      /**培训监控 - 强制考核卷调卷详情 */
+      getAssessMonitorDetail: GetAssessMonitorDetail
       /** 个人统计 */
       getPersonalStatistic: GetPersonalStatistic
+      /** 提取阅卷明细 */
+      getPersonalMarkDetail: GetPersonalMarkDetail
+      /** 提取阅卷明细 - 给分 */
+      updatePersonalMarkDetailScore: UpdatePersonalMarkDetailScore
       /** 系统抽查卷 */
       getSystemSpotList: GetSystemSpotList
       /** 系统抽查卷打分 */

+ 1 - 1
types/app.d.ts

@@ -14,7 +14,7 @@ type PaperType = 'SAMPLE_A' | 'SAMPLE_B' | 'RF' | 'MARK_STANDARD' | 'STANDARD' |
 type TaskType = 'FORMAL' | 'SAMPLE_A' | 'SAMPLE_B' | 'FORCE' | 'STANDARD' | 'SELF_CHECK'
 
 /** 题目类型 */
-type QuestionCategory = 'COMPOSITION' | 'TRANSLATE'
+type QuestionCategory = 'WRITING' | 'TRANSLATE'
 
 /** 回评方式 */
 type RemarkType = 'TIME' | 'QUANTITY'

+ 2 - 0
vite.config.ts

@@ -43,6 +43,8 @@ export default defineConfig({
     },
   },
   build: {
+    outDir: 'dist/web',
+    emptyOutDir: true,
     rollupOptions: {
       output: {
         assetFileNames(assetInfo) {