123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415 |
- <template>
- <div class="analysis-monitor-view">
- <div class="flex items-center justify-end p-l-base p-r-base fill-blank view-header">
- <div class="pointer circle-button" custom-1 @click="toggleSetting(true)"><svg-icon name="setting" /></div>
- <el-button type="primary" :loading="loading" @click="onRefresh">刷新</el-button>
- </div>
- <div class="content-wrap p-base flex">
- <div class="data-card-view">
- <div
- v-for="card in cards"
- :key="card.dataField"
- class="radius-base full fill-blank data-card"
- :class="{ active: activeCard?.dataField === card.dataField }"
- @click="setActiveCard(card)"
- >
- <div class="flex direction-column radius-base full fill-blank data-card-content">
- <div class="p-base card-title">{{ card.title }}</div>
- <div class="p-l-extra-base p-r-mini p-b-extra-small flex-1 overflow-hidden">
- <base-table
- border
- v-bind="getTableProps(card.hasAll)"
- size="small"
- height="100%"
- :columns="getColumns(card.valueLabel)"
- :data="getData(sortableResult[card.dataField], card.hasAll)"
- >
- <template #empty>
- <empty :image-size="100"></empty>
- </template>
- </base-table>
- </div>
- </div>
- </div>
- </div>
- <div class="p-base radius-base m-l-auto fill-blank data-detail-view">
- <div class="m-b-mini card-title">系统监控</div>
- <base-table
- v-if="activeCard"
- border
- stripe
- size="small"
- :columns="getColumns(activeCard.valueLabel)"
- :data="sortableResult[activeCard.dataField]"
- >
- </base-table>
- </div>
- </div>
- <base-dialog v-model="visibleSetting" title="参数设置" :footer="false" destroy-on-close>
- <base-form :label-width="'88px'" :model="model" :items="formItems">
- <el-form-item>
- <confirm-button @confirm="onConfirm" @cancel="toggleSetting(false)"></confirm-button>
- </el-form-item>
- </base-form>
- </base-dialog>
- </div>
- </template>
- <script setup lang="tsx" name="AnalysisMonitoring">
- /** 决策分析-监控 */
- import { computed, reactive, ref, watch, nextTick } from 'vue'
- import { useRouter } from 'vue-router'
- import { ElButton, ElFormItem } from 'element-plus'
- import { omit } from 'lodash-es'
- import { useIntervalFn } from '@vueuse/core'
- import { localStorage } from '@/plugins/storage'
- import useFetch from '@/hooks/useFetch'
- import useOptions from '@/hooks/useOptions'
- import useVW from '@/hooks/useVW'
- import SvgIcon from '@/components/common/SvgIcon.vue'
- import BaseTable from '@/components/element/BaseTable.vue'
- import BaseDialog from '@/components/element/BaseDialog.vue'
- import BaseForm from '@/components/element/BaseForm.vue'
- import ConfirmButton from '@/components/common/ConfirmButton.vue'
- import Empty from '@/components/common/Empty.vue'
- import type { EpTableColumn, EpFormItem, EpTableProps } from 'global-type'
- import type { ExtractApiParams, ExtractApiResponse } from '@/api/api'
- const { push } = useRouter()
- const { subjectList, mainQuestionList, groupListWithAll, dataModel, changeModelValue, initFinish, onOptionInit } =
- useOptions(['subject', 'question', 'group'])
- const model = reactive<
- Omit<ExtractApiParams<'getStatistics'>, 'markingGroupNumbers'> & {
- refresh: number
- markingGroupNumbers?: number
- }
- >({
- subjectCode: dataModel.subject || '',
- questionMainNumber: dataModel.question,
- markingGroupNumbers: dataModel.group,
- refresh: localStorage.get('MONITORING_REFRESH_RATE') || 0,
- })
- const modelToFetchModel = () => {
- return Object.assign({}, model, {
- markingGroupNumbers:
- typeof model.markingGroupNumbers === 'number' ? [model.markingGroupNumbers] : model.markingGroupNumbers,
- })
- }
- const fetchModel = reactive<ExtractApiParams<'getStatistics'> & { refresh: number }>(modelToFetchModel())
- watch(fetchModel, () => {
- localStorage.set('MONITORING_REFRESH_RATE', fetchModel.refresh)
- })
- watch(
- dataModel,
- () => {
- model.subjectCode = dataModel.subject
- model.questionMainNumber = dataModel.question
- model.markingGroupNumbers = dataModel.group
- },
- { immediate: true }
- )
- const formItems = computed<EpFormItem[]>(() => [
- {
- label: '科目',
- slotType: 'select',
- prop: 'subjectCode',
- slot: {
- options: subjectList.value,
- disabled: true,
- },
- },
- {
- label: '大题',
- slotType: 'select',
- prop: 'questionMainNumber',
- slot: {
- options: mainQuestionList.value,
- disabled: true,
- },
- },
- {
- label: '小组',
- slotType: 'select',
- prop: 'markingGroupNumbers',
- slot: {
- options: groupListWithAll.value,
- onChange: changeModelValue('group'),
- },
- },
- {
- label: '自动刷新',
- slotType: 'select',
- prop: 'refresh',
- slot: {
- options: [3, 5, 10, 0].map((n) => ({ value: n, label: n ? `${n}分钟` : '不自动刷新' })),
- },
- },
- ])
- interface Card {
- dataField: string
- title: string
- valueLabel: string
- hasAll: boolean
- }
- const cards: Card[] = [
- {
- dataField: 'totalPaperList',
- title: '评卷份数',
- valueLabel: '份数',
- hasAll: true,
- },
- {
- dataField: 'xyRelateList',
- title: '相关系数',
- valueLabel: '相关系数',
- hasAll: true,
- },
- {
- dataField: 'avgList',
- title: '平均分',
- valueLabel: '平均分',
- hasAll: true,
- },
- {
- dataField: 'stdList',
- title: '标准差',
- valueLabel: '标准差',
- hasAll: true,
- },
- {
- dataField: 'scoreTopLowList',
- title: '近5分钟最高最低分',
- valueLabel: '分数',
- hasAll: false,
- },
- {
- dataField: 'objSubRateList',
- title: '近5分钟客主比',
- valueLabel: '最高',
- hasAll: false,
- },
- {
- dataField: 'objSubAvgRateList',
- title: '平均客主比',
- valueLabel: '平均最高',
- hasAll: false,
- },
- {
- dataField: 'integrationList',
- title: '综合',
- valueLabel: '综合指数',
- hasAll: false,
- },
- ]
- /** 跳转抽查详情 */
- const viewMarkDetail = (row: ExtractArrayValue<ExtractRecordValue<ExtractApiResponse<'getStatistics'>>>) => {
- push({
- name: 'AnalysisViewMarked',
- params: {
- markerId: row.markerId,
- },
- query: {
- markerName: row.markerName,
- },
- })
- }
- const getTableProps: (hasAll: boolean) => EpTableProps = (hasAll) => {
- return {
- highlightCurrentRow: false,
- rowClassName({ rowIndex }) {
- const startIndex = hasAll ? 1 : 0
- const splitIndex = hasAll ? 3 : 2
- if (rowIndex >= startIndex && rowIndex <= splitIndex) {
- return 'top-three-row'
- } else if (rowIndex >= splitIndex + 1 && rowIndex <= splitIndex + 3) {
- return 'last-three-row'
- }
- return ''
- },
- }
- }
- const getColumns = (
- valueLabel: string
- ): EpTableColumn<ExtractArrayValue<ExtractRecordValue<ExtractApiResponse<'getStatistics'>>>>[] => {
- return [
- {
- label: '老师ID',
- align: 'center',
- formatter(row) {
- return row.markerId === 0 ? (
- '全体'
- ) : (
- <ElButton type="primary" link onClick={() => viewMarkDetail(row)}>
- {row.markerName}
- </ElButton>
- )
- },
- },
- { label: valueLabel, prop: 'value', align: 'center' },
- ]
- }
- const activeCard = ref<Card>()
- const setActiveCard = (v: Card) => {
- activeCard.value = v
- }
- const visibleSetting = ref<boolean>(false)
- const toggleSetting = (visible: boolean) => {
- visibleSetting.value = visible
- }
- const interval = computed(() => fetchModel.refresh * 60 * 1000)
- const { fetch, result, loading } = useFetch('getStatistics')
- const getData = (data: ExtractRecordValue<ExtractApiResponse<'getStatistics'>>, hasAll: boolean) => {
- return data?.slice(0, hasAll ? 4 : 3).concat(data?.slice(hasAll ? 4 : 3).slice(-3)) || []
- }
- const sortableResult = computed<typeof result.value>(() => {
- if (!result.value) return {}
- return cards.reduce((final, { dataField, hasAll }) => {
- let resultData = result.value[dataField]?.filter((v) => hasAll || v.markerId) || []
- if (dataField === 'scoreTopLowList') {
- const scoreTopList = result.value?.['scoreTopList']?.filter((v) => hasAll || v.markerId) || []
- const scoreLowList = result.value?.['scoreLowList']?.filter((v) => hasAll || v.markerId) || []
- resultData = scoreTopList
- .concat(scoreLowList)
- ?.filter((d, i, arr) => d && i === arr.findIndex((v) => v.markerId === d.markerId))
- }
- const arr = [...resultData]
- const totalIndex = arr.findIndex((v) => v.markerId === 0)
- if (totalIndex >= 0) {
- const [total] = arr.splice(totalIndex, 1)
- total && arr.unshift(total)
- }
- final[dataField] = arr
- return final
- }, {} as typeof result.value)
- })
- const onRefresh = () => {
- let date = new Date()
- let year = date.getFullYear()
- let month: number | string = date.getMonth() + 1
- let day: number | string = date.getDate()
- month < 10 && (month = '0' + month)
- day < 10 && (day = '0' + day)
- if (fetchModel.subjectCode && fetchModel.questionMainNumber) {
- fetch({
- ...omit(fetchModel, 'refresh'),
- startTime: `${year}${month}${day}000000`,
- endTime: `${year}${month}${day}235959`,
- })
- }
- }
- const onConfirm = () => {
- Object.assign(fetchModel, modelToFetchModel())
- onRefresh()
- toggleSetting(false)
- }
- onOptionInit(onConfirm)
- const { pause, isActive, resume } = useIntervalFn(onRefresh, interval, { immediate: false })
- watch([interval, initFinish], () => {
- if (!initFinish.value) {
- return
- }
- if (interval.value <= 0 && isActive.value) {
- pause()
- } else {
- resume()
- }
- })
- </script>
- <style scoped lang="scss">
- .analysis-monitor-view {
- .content-wrapper {
- height: 784px;
- }
- .view-header {
- height: 52px;
- .circle-button {
- width: 32px;
- height: 32px;
- border-radius: 50%;
- background-color: #eee;
- color: #666;
- display: grid;
- place-items: center;
- margin-right: $ExtraSmallGapSpace;
- }
- }
- .data-card-view {
- // width: 1118px;
- width: calc(((100% - 40px) / 5) * 4 + 30px);
- height: 706px;
- flex-wrap: wrap;
- align-content: flex-start;
- display: flex;
- justify-content: space-between;
- .data-card {
- // width: 272px;
- width: calc((100% - 30px) / 4);
- height: 348px;
- padding-top: 2px;
- &.active {
- background-color: #32c5ff;
- box-shadow: 0px 16px 16px 0px rgba(0, 0, 0, 0.1);
- }
- &:nth-child(n + 5) {
- margin-top: $ExtraSmallGapSpace;
- }
- }
- }
- .data-detail-view {
- width: calc((100% - 40px) / 5);
- height: 706px;
- overflow: auto;
- }
- .card-title {
- color: $BlockTitleColor;
- font-size: $BaseFont;
- line-height: 1;
- }
- ::v-deep(.el-table) {
- .el-table__body-wrapper {
- .el-table__body {
- .top-three-row {
- background: rgba(0, 186, 151, 0.1);
- }
- .last-three-row {
- background: rgba(255, 114, 59, 0.1);
- }
- .el-table__cell {
- padding: 5px 0;
- }
- }
- }
- }
- }
- </style>
|