Browse Source

v1.1.0 coding...

刘洋 1 year ago
parent
commit
6ab6906479

+ 3 - 0
src/components/shared/ScoringPanel.vue

@@ -25,6 +25,7 @@
           :question="question"
           :large="props.large"
           :allow-submit="allowSubmit"
+          :cannot-toggle="props.cannotToggle"
           @blur="() => onBlur(index)"
           @enter="() => onEnter(index)"
           @focused="() => onFocused(index)"
@@ -65,6 +66,7 @@ const props = withDefaults(
     id?: number | null
     autoVisible?: boolean | undefined
     large?: boolean
+    cannotToggle?: boolean
   }>(),
   {
     modal: false,
@@ -75,6 +77,7 @@ const props = withDefaults(
     id: null,
     autoVisible: true,
     large: true,
+    cannotToggle: false,
   }
 )
 

+ 3 - 2
src/components/shared/ScoringPanelItem.vue

@@ -66,7 +66,7 @@
         />
       </div>
     </toggle-dialog-render>
-    <toggle-dialog-render>
+    <toggle-dialog-render v-if="!props.cannotToggle">
       <!-- <svg-icon
         class="pointer toggle-icon"
         :class="{ visible: props.toggleModal }"
@@ -118,8 +118,9 @@ const props = withDefaults(
     large?: boolean
     allowSubmit: boolean
     showConfirmBtn?: boolean
+    cannotToggle?: boolean
   }>(),
-  { modal: false, toggleModal: true, score: void 0, scoreValidFail: false, large: true }
+  { modal: false, toggleModal: true, score: void 0, scoreValidFail: false, large: true, cannotToggle: false }
 )
 
 const emit = defineEmits(['focused', 'blur', 'toggle-click', 'enter', 'update:score'])

+ 2 - 0
src/components/shared/ScoringPanelWithConfirm.vue

@@ -6,6 +6,7 @@
     :main-number="props.mainNumber"
     :subject-code="props.subjectCode"
     :large="props.large ?? false"
+    :cannot-toggle="props.cannotToggle"
     @submit="onSubmit"
   ></scoring-panel>
   <base-dialog v-model="submitModalVisible" unless :width="260" center>
@@ -56,6 +57,7 @@ const props = defineProps<{
   mainNumber?: number | string
   subjectCode?: any
   large?: boolean
+  cannotToggle?: boolean
 }>()
 
 const attrs = useAttrs()

+ 343 - 85
src/modules/analysis/marker-statistics/index.vue

@@ -16,57 +16,142 @@
       </div>
     </div>
     <div class="flex-1 overflow-hidden flex direction-column">
-      <div class="fill-blank p-t-medium-base filter-header">
-        <base-form size="small" :items="formItems" :model="model">
-          <template #form-item-search>
+      <div class="fill-blank filter-header flex items-center">
+        <div class="flex-1 flex items-center">
+          <span class="m-name">评卷员:{{ query.markerName }}</span>
+          <base-form size="small" :items="formItems" :model="model">
+            <!-- <template #form-item-search>
             <el-button type="primary" :loading="loading1 || loading2" @click="onSearch">刷新</el-button>
-          </template>
-        </base-form>
+          </template> -->
+          </base-form>
+        </div>
+        <el-button type="primary" :loading="loading1 || loading2" size="small" @click="onSearch">刷新</el-button>
       </div>
-      <div class="flex-1 flex overflow-hidden p-base">
-        <div class="flex-1 flex direction-column overflow-hidden">
-          <div class="fill-blank text-center p-t-base table-title">{{ query.markerName }}主观题给分分布</div>
-          <div class="flex-1 overflow-hidden fill-blank m-b-base table-box">
-            <base-table
-              size="small"
-              border
-              stripe
-              height="100%"
-              :columns="columns"
-              :data="subjectTableData"
-              @row-dblclick="onDbClick"
-            ></base-table>
-          </div>
-          <div class="flex-1 overflow-hidden radius-base fill-blank p-base chart-box">
-            <vue-echarts class="full" :option="markerSubjectiveChartsOption"></vue-echarts>
-          </div>
+      <div class="flex-1 p-base flex direction-column overflow-hidden">
+        <div class="flex-1 overflow-hidden mark-container">
+          <splitpanes class="default-theme" style="height: 100%" @resize="setPaneSize">
+            <pane
+              max-size="80"
+              :size="paneSize"
+              class="flex flex-1 direction-column radius-base fill-blank mark-content"
+              :class="{ 'text-center': center }"
+              :style="{ 'background-color': backgroundColor }"
+            >
+              <span class="preview" @click="onPreview">
+                <svg-icon name="preview"></svg-icon>
+              </span>
+              <p v-if="current" class="absolute mark-score">{{ current.markScore }}</p>
+              <!-- <right-button class="next-button" @click="checkNext" /> -->
+              <div class="flex-1 p-base scroll-auto mark-content-paper img-wrap">
+                <img :src="dataUrl" alt="" class="paper-img" :style="{ 'background-color': frontColor }" />
+              </div>
+
+              <div class="flex items-center p-t-base">
+                <el-button
+                  :disabled="!current?.taskId"
+                  class="m-l-base m-r-auto"
+                  size="small"
+                  type="primary"
+                  style="margin-bottom: 4px"
+                  @click="onSendBack"
+                  >打回</el-button
+                >
+                <div class="flex-1 flex justify-end items-center">
+                  <scoring-panel-with-confirm
+                    :id="current?.taskId"
+                    v-model:visible="editScoreVisible"
+                    v-model:score="modelScore"
+                    :main-number="current?.mainNumber"
+                    :subject-code="query.subjectCode"
+                    modal
+                    :auto-visible="false"
+                    :cannot-toggle="true"
+                    @submit="onSubmit"
+                  ></scoring-panel-with-confirm>
+                </div>
+              </div>
+            </pane>
+            <pane
+              max-size="80"
+              :size="100 - paneSize"
+              class="flex direction-column p-base radius-base fill-blank 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">
+                <div class="choose-score-info"></div>
+                <div class="my-pagination">
+                  <div class="page-toggle-btn prev-100"></div>
+                  <div class="page-toggle-btn next-100 m-l-base"></div>
+                </div>
+
+                <!-- <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="currentPage"
+                  size="small"
+                  class="m-t-unset"
+                  background
+                  right
+                  hide-on-single-page
+                  :pager-count="3"
+                  small
+                ></el-pagination> -->
+              </div>
+              <div class="flex-1 scroll-auto m-t-mini">
+                <base-table
+                  ref="tableRef"
+                  border
+                  stripe
+                  size="small"
+                  v-bind="pagination"
+                  height="100%"
+                  :data="tableData"
+                  :columns="columns"
+                  highlight-current-row
+                  :cell-style="{ padding: '6px 0' }"
+                  @current-change="onCurrentChange"
+                  @row-dblclick="onDbClick"
+                ></base-table>
+              </div>
+            </pane>
+          </splitpanes>
         </div>
-        <div class="flex-1 flex direction-column overflow-hidden m-l-base">
-          <div class="fill-blank text-center p-t-base table-title">{{ query.markerName }}客观题给分分布</div>
-          <div class="flex-1 overflow-hidden fill-blank m-b-base table-box">
-            <base-table
-              size="small"
-              border
-              stripe
-              height="100%"
-              :columns="columns"
-              :data="objectiveTableData"
-            ></base-table>
+        <div class="chart-space m-t-base flex justify-between items-center">
+          <div class="new-chart-box radius-base">
+            <vue-echarts class="full" :option="markerSubjectiveChartsOption"></vue-echarts>
           </div>
-          <div class="flex-1 overflow-hidden radius-base fill-blank p-base chart-box">
+          <div class="new-chart-box radius-base">
             <vue-echarts class="full" :option="markerObjectiveChartsOption"></vue-echarts>
           </div>
         </div>
       </div>
     </div>
   </div>
+  <image-preview v-model="previewModalVisible" :url="current?.filePath"></image-preview>
+  <send-back-mark
+    :id="current?.taskId"
+    v-model="sendBackVisible"
+    type="custom-check"
+    @rejected="onRejected"
+  ></send-back-mark>
+  <mark-history-list
+    :id="currentViewHistory?.taskId"
+    v-model="visibleHistory"
+    :task="currentViewHistory"
+  ></mark-history-list>
 </template>
 
 <script setup lang="tsx" name="AnalysisPersonnelStatisticsMarker">
 /** 评卷员明细统计 */
-import { computed, reactive } from 'vue'
+import { computed, reactive, ref, watch } from 'vue'
 import { useRouter, useRoute } from 'vue-router'
-import { ElButton } from 'element-plus'
+import { ElButton, ElMessage } from 'element-plus'
 import dayjs from 'dayjs'
 import VueEcharts from 'vue-echarts'
 import Message from '@/components/shared/message/Message.vue'
@@ -74,14 +159,122 @@ import UserInfo from '@/components/shared/UserInfo.vue'
 import BaseTable from '@/components/element/BaseTable.vue'
 import BaseForm from '@/components/element/BaseForm.vue'
 import useFetch from '@/hooks/useFetch'
+import useTable from '@/hooks/useTable'
+import useTableCheck from '@/hooks/useTableCheck'
 import LockEntry from '@/components/common/LockEntry.vue'
-
+import { Splitpanes, Pane } from 'splitpanes'
+import { setPaneSize } from '@/utils/common'
+import SendBackMark from '@/components/shared/SendBackMark.vue'
+import useMarkHeader from '@/hooks/useMarkHeader'
+import MarkHistoryList from '@/components/shared/MarkHistoryList.vue'
+import { add } from '@/utils/common'
+import { useSetImgBg } from '@/hooks/useSetImgBg'
+import SvgIcon from '@/components/common/SvgIcon.vue'
+import useMainStore from '@/store/main'
+import ScoringPanelWithConfirm from '@/components/shared/ScoringPanelWithConfirm.vue'
+import ImagePreview from '@/components/shared/ImagePreview.vue'
 import type { EChartsOption } from 'echarts'
 import type { ExtractApiResponse } from '@/api/api'
 import type { EpTableColumn, EpFormItem } from 'global-type'
-
+import type { SetImgBgOption } from '@/hooks/useSetImgBg'
+const mainStore = useMainStore()
+const paneSize = computed(() => {
+  return mainStore.paneSizeConfig[location.pathname] || 70
+})
 const { back, push } = useRouter()
 const { query } = useRoute()
+/** 打回弹窗 */
+const sendBackVisible = ref<boolean>(false)
+/** 打回 */
+const onSendBack = () => {
+  sendBackVisible.value = true
+}
+/** 打回成功 */
+const onRejected = () => {
+  // onRefresh()
+  ElMessage.success('打回成功')
+}
+/** 给分板 */
+const editScoreVisible = ref<boolean>(true)
+/** 图片预览 */
+const previewModalVisible = ref<boolean>(false)
+
+/** 分数 */
+const modelScore = ref<number[]>([])
+const {
+  rotate,
+  scale,
+  center,
+  frontColor,
+  backgroundColor,
+  onBack,
+  onScaleChange,
+  onCenter,
+  onRotate,
+  setBackgroundColor,
+  setFrontColor,
+  onViewStandard,
+} = useMarkHeader()
+/** 刷新 */
+const onRefresh = () => {
+  fetchTable()
+}
+/** 预览试卷 */
+const onPreview = () => {
+  previewModalVisible.value = true
+}
+
+const tableModel = reactive<any>({
+  markerId: query.markerId,
+  score: '',
+  pageSize: 100,
+  subjectCode: query.subjectCode as string,
+  mainNumber: query.questionMainNumber as string,
+})
+const { pagination, currentPage, data, fetchTable } = useTable('getPersonalMarkDetail', tableModel)
+const {
+  tableRef,
+  tableData,
+  current,
+  currentView: currentViewHistory,
+  next: checkNext,
+  visibleHistory,
+  onDbClick,
+  onCurrentChange,
+  nextRow,
+} = useTableCheck(data)
+/** 确定给分 */
+const { fetch: updatePersonalMarkDetailScore } = useFetch('updatePersonalMarkDetailScore')
+const onSubmit = async () => {
+  if (current.value) {
+    const scores = JSON.parse(JSON.stringify(modelScore.value))
+    await updatePersonalMarkDetailScore({
+      taskId: current.value.taskId,
+      scores: modelScore.value,
+      source: (query.source as string) || '',
+    })
+    // current.value.markerScore = add(...scores)
+    current.value.markScore = add(...scores)
+    ElMessage.success('修改成功')
+    // editScoreVisible.value = false
+    // onRefresh()
+    nextRow()
+  }
+}
+watch(current, () => {
+  if (current.value) {
+    useFetch('viewActiveCheck').fetch({ taskId: current.value?.taskId })
+  }
+})
+const imgOption = computed<SetImgBgOption>(() => {
+  return {
+    image: current?.value?.filePath || '',
+    rotate: rotate.value,
+    scale: scale.value,
+  }
+})
+const { drawing, dataUrl } = useSetImgBg(imgOption, frontColor, setFrontColor)
+onRefresh()
 
 const model = reactive({
   type: 'total',
@@ -99,18 +292,18 @@ const formItems: EpFormItem[] = [
         { label: 'today', slotLabel: '当天' },
       ],
     },
-    colProp: {
-      span: 4,
-      offset: 18,
-    },
-  },
-  {
-    slotName: 'search',
-    rowKey: 'row-1',
-    colProp: {
-      span: 2,
-    },
+    // colProp: {
+    //   span: 4,
+    //   offset: 18,
+    // },
   },
+  // {
+  //   slotName: 'search',
+  //   rowKey: 'row-1',
+  //   colProp: {
+  //     span: 2,
+  //   },
+  // },
 ]
 
 const {
@@ -170,22 +363,30 @@ const objectiveTableData = computed<TableDataType[]>(() => {
   }))
 })
 
-const subjectTableData = computed<TableDataType[]>(() => {
-  return subjectiveByMarker?.value?.segmentsByUser.map((d) => ({
-    ...d,
-    ...subjectiveGroupData.value?.[d.scoreStart],
-    ...subjectiveAllData.value?.[d.scoreStart],
-  }))
-})
-
-const columns: EpTableColumn<TableDataType>[] = [
-  { label: '分数', prop: 'scoreStart' },
-  { label: '人数', prop: 'count' },
-  { label: '百分比', prop: 'rate', formatter: (row) => `${row.rate}%` },
-  { label: '本组人数', prop: 'groupCount' },
-  { label: '百分比', prop: 'groupRate', formatter: (row) => `${row.groupRate}%` },
-  { label: '全体人数', prop: 'allCount' },
-  { label: '百分比', prop: 'allRate', formatter: (row) => `${row.allRate}%` },
+const columns: EpTableColumn<any>[] = [
+  {
+    label: '评卷员',
+    prop: 'markerName',
+    minWidth: 90,
+    // formatter(row: any) {
+    //   return (
+    //     <SecNumberStatus
+    //       secretNumber={row.markerName}
+    //       checked={row.checked}
+    //       corrected={row.corrected}
+    //     ></SecNumberStatus>
+    //   )
+    // },
+  },
+  {
+    label: '密号',
+    prop: 'secretNumber',
+    minWidth: 110,
+  },
+  { label: '给分', prop: 'markerScore', minWidth: 70 },
+  { label: '客观分', prop: 'objectiveScore', minWidth: 70 },
+  { label: '客主比', prop: 'markerRatio', minWidth: 80 },
+  { label: '成绩', prop: 'markScore', minWidth: 70 },
 ]
 
 const onSearch = () => {
@@ -204,24 +405,6 @@ const onSearch = () => {
   }
 }
 
-const onDbClick = (row: TableDataType) => {
-  if (query.markerId) {
-    push({
-      name: 'AnalysisViewMarked',
-      params: {
-        markerId: query.markerId as string,
-      },
-      query: {
-        markerName: query.markerName,
-        score: row.scoreStart,
-        source: (query.source as string) || '',
-        subjectCode: query.subjectCode,
-        questionMainNumber: query.questionMainNumber,
-      },
-    })
-  }
-}
-
 type StatisticObjectiveByMarker = ExtractApiResponse<'getStatisticObjectiveByMarker'>
 
 type StatisticObjectiveByMarkerValues = StatisticObjectiveByMarker['segmentsByAll']
@@ -241,7 +424,15 @@ const getXAxisData = <K extends keyof ExtractArrayValue<StatisticObjectiveByMark
 
 const markerSubjectiveChartsOption = computed<EChartsOption>(() => {
   return {
+    grid: {
+      top: 50,
+      bottom: 15,
+      left: 30,
+      right: 30,
+      containLabel: true,
+    },
     legend: {
+      top: 10,
       itemWidth: 14,
       data: ['评卷员主观分布', '小组主观分布', '题组主观分布'],
     },
@@ -297,7 +488,15 @@ const markerSubjectiveChartsOption = computed<EChartsOption>(() => {
 
 const markerObjectiveChartsOption = computed<EChartsOption>(() => {
   return {
+    grid: {
+      top: 50,
+      bottom: 15,
+      left: 30,
+      right: 30,
+      containLabel: true,
+    },
     legend: {
+      top: 10,
       itemWidth: 14,
       data: ['评卷员客观分布', '小组客观分布', '题组客观分布'],
     },
@@ -401,11 +600,70 @@ onSearch()
     }
   }
 }
+.chart-space {
+  height: 220px;
+}
 .filter-header {
   border-top: 1px solid #eee;
   border-bottom: 1px solid #eee;
+  padding: 8px 15px;
+
+  .m-name {
+    font-size: 12px;
+    color: #666;
+    margin-right: 40px;
+    font-weight: bold;
+  }
+  :deep(.el-form-item--small) {
+    margin-bottom: 0;
+  }
 }
-.chart-box {
-  height: 350px;
+.new-chart-box {
+  height: 100%;
+  width: calc(50% - 6px);
+  background-color: #fff;
+}
+
+.mark-container {
+  .mark-content {
+    position: relative;
+    .preview {
+      position: absolute;
+      cursor: pointer;
+      top: 20px;
+      right: 25px;
+      font-size: 38px;
+    }
+    .next-button {
+      position: absolute;
+      right: -20px;
+      top: 300px;
+    }
+    .mark-content-paper {
+      img {
+        max-width: 100%;
+      }
+    }
+  }
+  .table-view {
+    // width: 580px;
+    .detail-info-table-header {
+      .choose-score-info {
+        background: rgba(0, 186, 151, 0.1);
+      }
+    }
+    .marker-name {
+      border-bottom: $OnePixelLine;
+    }
+    .detail-info-label {
+      .detail-info-label-num {
+        min-width: 32px;
+        height: 24px;
+        line-height: 24px;
+        background: #00987b;
+        border-radius: 4px;
+      }
+    }
+  }
 }
 </style>

+ 411 - 0
src/modules/analysis/marker-statistics/old.vue

@@ -0,0 +1,411 @@
+<template>
+  <div class="full flex direction-column">
+    <div class="flex items-center p-extra-small fill-blank header-view">
+      <el-button class="m-r-auto" size="small" plain @click="back()">返回</el-button>
+
+      <div class="data-item">
+        <user-info></user-info>
+      </div>
+      <div class="data-item">
+        <div class="icon-item">
+          <lock-entry />
+        </div>
+        <div class="icon-item">
+          <message></message>
+        </div>
+      </div>
+    </div>
+    <div class="flex-1 overflow-hidden flex direction-column">
+      <div class="fill-blank p-t-medium-base filter-header">
+        <base-form size="small" :items="formItems" :model="model">
+          <template #form-item-search>
+            <el-button type="primary" :loading="loading1 || loading2" @click="onSearch">刷新</el-button>
+          </template>
+        </base-form>
+      </div>
+      <div class="flex-1 flex overflow-hidden p-base">
+        <div class="flex-1 flex direction-column overflow-hidden">
+          <div class="fill-blank text-center p-t-base table-title">{{ query.markerName }}主观题给分分布</div>
+          <div class="flex-1 overflow-hidden fill-blank m-b-base table-box">
+            <base-table
+              size="small"
+              border
+              stripe
+              height="100%"
+              :columns="columns"
+              :data="subjectTableData"
+              @row-dblclick="onDbClick"
+            ></base-table>
+          </div>
+          <div class="flex-1 overflow-hidden radius-base fill-blank p-base chart-box">
+            <vue-echarts class="full" :option="markerSubjectiveChartsOption"></vue-echarts>
+          </div>
+        </div>
+        <div class="flex-1 flex direction-column overflow-hidden m-l-base">
+          <div class="fill-blank text-center p-t-base table-title">{{ query.markerName }}客观题给分分布</div>
+          <div class="flex-1 overflow-hidden fill-blank m-b-base table-box">
+            <base-table
+              size="small"
+              border
+              stripe
+              height="100%"
+              :columns="columns"
+              :data="objectiveTableData"
+            ></base-table>
+          </div>
+          <div class="flex-1 overflow-hidden radius-base fill-blank p-base chart-box">
+            <vue-echarts class="full" :option="markerObjectiveChartsOption"></vue-echarts>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="tsx" name="AnalysisPersonnelStatisticsMarker">
+/** 评卷员明细统计 */
+import { computed, reactive } from 'vue'
+import { useRouter, useRoute } from 'vue-router'
+import { ElButton } from 'element-plus'
+import dayjs from 'dayjs'
+import VueEcharts from 'vue-echarts'
+import Message from '@/components/shared/message/Message.vue'
+import UserInfo from '@/components/shared/UserInfo.vue'
+import BaseTable from '@/components/element/BaseTable.vue'
+import BaseForm from '@/components/element/BaseForm.vue'
+import useFetch from '@/hooks/useFetch'
+import LockEntry from '@/components/common/LockEntry.vue'
+
+import type { EChartsOption } from 'echarts'
+import type { ExtractApiResponse } from '@/api/api'
+import type { EpTableColumn, EpFormItem } from 'global-type'
+
+const { back, push } = useRouter()
+const { query } = useRoute()
+
+const model = reactive({
+  type: 'total',
+})
+
+const formItems: EpFormItem[] = [
+  {
+    label: '统计方式',
+    prop: 'type',
+    slotType: 'radio',
+    rowKey: 'row-1',
+    slot: {
+      options: [
+        { label: 'total', slotLabel: '积累' },
+        { label: 'today', slotLabel: '当天' },
+      ],
+    },
+    colProp: {
+      span: 4,
+      offset: 18,
+    },
+  },
+  {
+    slotName: 'search',
+    rowKey: 'row-1',
+    colProp: {
+      span: 2,
+    },
+  },
+]
+
+const {
+  fetch: getStatisticObjectiveByMarker,
+  result: objectiveByMarker,
+  loading: loading1,
+} = useFetch('getStatisticObjectiveByMarker')
+const {
+  fetch: getStatisticSubjectiveByMarker,
+  result: subjectiveByMarker,
+  loading: loading2,
+} = useFetch('getStatisticSubjectiveByMarker')
+
+type TableDataType = ExtractArrayValue<ExtractApiResponse<'getStatisticObjectiveByMarker'>['segmentsByAll']> & {
+  groupCount: number
+  groupRate: number
+  allCount: number
+  allRate: number
+}
+
+type GroupData = Record<number, { groupCount: number; groupRate: number }>
+type AllData = Record<number, { allCount: number; allRate: number }>
+
+const objectiveGroupData = computed<GroupData>(() => {
+  return objectiveByMarker.value?.segmentsByGroup?.reduce((result, data) => {
+    result[data.scoreStart] = { groupCount: data.count, groupRate: data.rate }
+    return result
+  }, {} as GroupData)
+})
+
+const objectiveAllData = computed<AllData>(() => {
+  return objectiveByMarker.value?.segmentsByAll?.reduce((result, data) => {
+    result[data.scoreStart] = { allCount: data.count, allRate: data.rate }
+    return result
+  }, {} as AllData)
+})
+
+const subjectiveGroupData = computed<GroupData>(() => {
+  return subjectiveByMarker.value?.segmentsByGroup?.reduce((result, data) => {
+    result[data.scoreStart] = { groupCount: data.count, groupRate: data.rate }
+    return result
+  }, {} as GroupData)
+})
+
+const subjectiveAllData = computed<AllData>(() => {
+  return subjectiveByMarker.value?.segmentsByAll?.reduce((result, data) => {
+    result[data.scoreStart] = { allCount: data.count, allRate: data.rate }
+    return result
+  }, {} as AllData)
+})
+
+const objectiveTableData = computed<TableDataType[]>(() => {
+  return objectiveByMarker?.value?.segmentsByUser.map((d) => ({
+    ...d,
+    ...objectiveGroupData.value?.[d.scoreStart],
+    ...objectiveAllData.value?.[d.scoreStart],
+  }))
+})
+
+const subjectTableData = computed<TableDataType[]>(() => {
+  return subjectiveByMarker?.value?.segmentsByUser.map((d) => ({
+    ...d,
+    ...subjectiveGroupData.value?.[d.scoreStart],
+    ...subjectiveAllData.value?.[d.scoreStart],
+  }))
+})
+
+const columns: EpTableColumn<TableDataType>[] = [
+  { label: '分数', prop: 'scoreStart' },
+  { label: '人数', prop: 'count' },
+  { label: '百分比', prop: 'rate', formatter: (row) => `${row.rate}%` },
+  { label: '本组人数', prop: 'groupCount' },
+  { label: '百分比', prop: 'groupRate', formatter: (row) => `${row.groupRate}%` },
+  { label: '全体人数', prop: 'allCount' },
+  { label: '百分比', prop: 'allRate', formatter: (row) => `${row.allRate}%` },
+]
+
+const onSearch = () => {
+  if (query.markerId) {
+    const startTime = model.type === 'today' ? dayjs().startOf('day').format('YYYYMMDDHHmmss') : ''
+    getStatisticObjectiveByMarker({
+      markerId: query.markerId as string,
+      startTime,
+      endTime: '',
+    })
+    getStatisticSubjectiveByMarker({
+      markerId: query.markerId as string,
+      startTime,
+      endTime: '',
+    })
+  }
+}
+
+const onDbClick = (row: TableDataType) => {
+  if (query.markerId) {
+    push({
+      name: 'AnalysisViewMarked',
+      params: {
+        markerId: query.markerId as string,
+      },
+      query: {
+        markerName: query.markerName,
+        score: row.scoreStart,
+        source: (query.source as string) || '',
+        subjectCode: query.subjectCode,
+        questionMainNumber: query.questionMainNumber,
+      },
+    })
+  }
+}
+
+type StatisticObjectiveByMarker = ExtractApiResponse<'getStatisticObjectiveByMarker'>
+
+type StatisticObjectiveByMarkerValues = StatisticObjectiveByMarker['segmentsByAll']
+
+const getXAxisData = <K extends keyof ExtractArrayValue<StatisticObjectiveByMarkerValues>>(
+  field: K,
+  data?: StatisticObjectiveByMarkerValues
+) => {
+  if (!data) {
+    return []
+  }
+  const getValue = (key: K, item: ExtractArrayValue<StatisticObjectiveByMarkerValues>) => {
+    return item[key]
+  }
+  return data?.map((v) => getValue(field, v))
+}
+
+const markerSubjectiveChartsOption = computed<EChartsOption>(() => {
+  return {
+    legend: {
+      itemWidth: 14,
+      data: ['评卷员主观分布', '小组主观分布', '题组主观分布'],
+    },
+    xAxis: {
+      axisLine: { show: false },
+      axisTick: { show: false },
+      splitLine: { show: false },
+      axisLabel: {
+        align: 'right',
+      },
+      data: getXAxisData('scoreStart', subjectiveByMarker?.value?.segmentsByAll),
+    },
+    yAxis: [
+      {
+        type: 'value',
+      },
+      {
+        type: 'value',
+        axisLabel: {
+          formatter: `{value}%`,
+        },
+        splitLine: { show: false },
+      },
+    ],
+    series: [
+      {
+        name: '评卷员主观分布',
+        type: 'line',
+        itemStyle: {
+          color: '#8ED14B',
+        },
+        data: getXAxisData('rate', subjectiveByMarker?.value?.segmentsByUser),
+      },
+      {
+        name: '小组主观分布',
+        type: 'line',
+        itemStyle: {
+          color: '#3B99D4',
+        },
+        data: getXAxisData('rate', subjectiveByMarker?.value?.segmentsByGroup),
+      },
+      {
+        name: '题组主观分布',
+        type: 'line',
+        itemStyle: {
+          color: '#ff0000',
+        },
+        data: getXAxisData('rate', subjectiveByMarker?.value?.segmentsByAll),
+      },
+    ],
+  }
+})
+
+const markerObjectiveChartsOption = computed<EChartsOption>(() => {
+  return {
+    legend: {
+      itemWidth: 14,
+      data: ['评卷员客观分布', '小组客观分布', '题组客观分布'],
+    },
+    xAxis: {
+      axisLine: { show: false },
+      axisTick: { show: false },
+      splitLine: { show: false },
+      axisLabel: {
+        align: 'right',
+      },
+      data: getXAxisData('scoreStart', objectiveByMarker?.value?.segmentsByAll),
+    },
+    yAxis: [
+      {
+        type: 'value',
+      },
+      {
+        type: 'value',
+        axisLabel: {
+          formatter: `{value}%`,
+        },
+        splitLine: { show: false },
+      },
+    ],
+    series: [
+      {
+        name: '评卷员客观分布',
+        type: 'line',
+        itemStyle: {
+          color: '#8ED14B',
+        },
+        data: getXAxisData('rate', objectiveByMarker?.value?.segmentsByUser),
+      },
+      {
+        name: '小组客观分布',
+        type: 'line',
+        itemStyle: {
+          color: '#3B99D4',
+        },
+        data: getXAxisData('rate', objectiveByMarker?.value?.segmentsByGroup),
+      },
+      {
+        name: '题组客观分布',
+        type: 'line',
+        itemStyle: {
+          color: '#ff0000',
+        },
+        data: getXAxisData('rate', objectiveByMarker?.value?.segmentsByAll),
+      },
+    ],
+  }
+})
+
+onSearch()
+</script>
+
+<style scoped lang="scss">
+.header-view {
+  background-color: #333;
+  height: 60px;
+  ::v-deep(.data-item) {
+    // padding-left: 20px;
+    padding-left: 15px;
+    padding-right: 15px;
+    position: relative;
+    height: 100%;
+    display: flex;
+    align-items: center;
+    color: #8a8a8a;
+    .icon-item {
+      // width: 45px;
+      padding: 0 10px;
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      justify-content: flex-end;
+    }
+    &:first-child {
+      margin-left: auto;
+    }
+    &:not(:last-child) {
+      &:after {
+        content: '';
+        position: absolute;
+        right: 0;
+        top: 0;
+        width: 1px;
+        height: 100%;
+        background-color: #464646;
+      }
+    }
+    &.is-last:after {
+      width: 0;
+    }
+    .main-ques-info {
+      text-align: center;
+      font-weight: bold;
+      color: $color--primary;
+      margin-bottom: 5px;
+      max-width: 130px;
+    }
+  }
+}
+.filter-header {
+  border-top: 1px solid #eee;
+  border-bottom: 1px solid #eee;
+}
+.chart-box {
+  height: 350px;
+}
+</style>