|
@@ -1,411 +0,0 @@
|
|
-<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>
|
|
|