提交 f450256c authored 作者: 王鹏飞's avatar 王鹏飞

chore: 新增大赛监控

上级 a7cbd6a2
...@@ -73,48 +73,48 @@ const listOptions = $computed(() => { ...@@ -73,48 +73,48 @@ const listOptions = $computed(() => {
{ label: '已评分', prop: 'checked_flag_name' }, { label: '已评分', prop: 'checked_flag_name' },
{ label: '得分', prop: 'score_name', slots: 'table-score' }, { label: '得分', prop: 'score_name', slots: 'table-score' },
{ label: '更新时间', prop: 'updated_time' }, { label: '更新时间', prop: 'updated_time' },
{ // {
label: '模块一', // label: '模块一',
prop: 'module_1', // prop: 'module_1',
computed({ row }: { row: any }) { // computed({ row }: { row: any }) {
return getModuleStatus(row, 0) // return getModuleStatus(row, 0)
} // }
}, // },
{ // {
label: '模块二', // label: '模块二',
prop: 'module_2', // prop: 'module_2',
computed({ row }: { row: any }) { // computed({ row }: { row: any }) {
return getModuleStatus(row, 1) // return getModuleStatus(row, 1)
} // }
}, // },
{ // {
label: '模块三', // label: '模块三',
prop: 'module_3', // prop: 'module_3',
computed({ row }: { row: any }) { // computed({ row }: { row: any }) {
return getModuleStatus(row, 2) // return getModuleStatus(row, 2)
} // }
}, // },
{ // {
label: '模块四', // label: '模块四',
prop: 'module_4', // prop: 'module_4',
computed({ row }: { row: any }) { // computed({ row }: { row: any }) {
return getModuleStatus(row, 3) // return getModuleStatus(row, 3)
} // }
}, // },
{ // {
label: '模块五', // label: '模块五',
prop: 'module_5', // prop: 'module_5',
computed({ row }: { row: any }) { // computed({ row }: { row: any }) {
return getModuleStatus(row, 4) // return getModuleStatus(row, 4)
} // }
}, // },
{ // {
label: '模块六', // label: '模块六',
prop: 'module_6', // prop: 'module_6',
computed({ row }: { row: any }) { // computed({ row }: { row: any }) {
return getModuleStatus(row, 5) // return getModuleStatus(row, 5)
} // }
}, // },
{ label: '操作', slots: 'table-x', width: 100 } { label: '操作', slots: 'table-x', width: 100 }
] ]
} }
...@@ -128,19 +128,19 @@ function onUpdateSuccess() { ...@@ -128,19 +128,19 @@ function onUpdateSuccess() {
appList?.refetch() appList?.refetch()
} }
function getModuleStatus(row: any, index: number) { // function getModuleStatus(row: any, index: number) {
const [first] = row.student_module_status_list // const [first] = row.student_module_status_list
try { // try {
const data: any = JSON.parse(first.data) // const data: any = JSON.parse(first.data)
const status: number = data[index].status // const status: number = data[index].status
if (status == 0) return '未开始' // if (status == 0) return '未开始'
if (status == 1) return '<span class="is-inProgress">进行中</span>' // if (status == 1) return '<span class="is-inProgress">进行中</span>'
if (status == 2) return '<span class="is-completed">已完成</span>' // if (status == 2) return '<span class="is-completed">已完成</span>'
} catch (error) { // } catch (error) {
// console.log(error) // // console.log(error)
} // }
return '未开始' // return '未开始'
} // }
</script> </script>
<template> <template>
......
import httpRequest from '@/utils/axios'
// 获取赛项列表
export function getCompetitionItems() {
return httpRequest.get('/api/lab/v1/expert/competition/items')
}
// 获取赛项的统计详情
export function getCompetitionStatistics(params: { competition_id: string; platform_key: string }) {
return httpRequest.get('/api/lab/v1/expert/competition/statistics', { params })
}
// 获取所有的参赛学员列表
export function getCompetitionCompetitors(params: { competition_id: string; platform_key: string }) {
return httpRequest.get('/api/lab/v1/expert/competition/competitors', { params })
}
// 获取平台标识列表
export function getPlatformKeys(params?: { competition_id: string }) {
return httpRequest.get('/api/resource/v1/backend/competition-book/platform-keys', { params })
}
import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/admin/contest/dashboard',
component: AppLayout,
children: [{ path: '', component: () => import('./views/Index.vue') }]
}
]
<script setup lang="ts">
import AppList from '@/components/base/AppList.vue'
import { getCompetitionItems, getCompetitionStatistics, getCompetitionCompetitors } from '../api'
import { useNow } from '@vueuse/core'
import dayjs from 'dayjs'
import duration from 'dayjs/plugin/duration'
dayjs.extend(duration)
const appList = ref<InstanceType<typeof AppList> | null>(null)
const competitionList = ref<Array<any>>([])
const currentCompetition = ref()
const countdown = computed(() => {
const timestamp = currentCompetition.value?.end_at * 1000 - useNow().value.getTime()
if (timestamp > 0) {
return dayjs.duration(timestamp).format('HH:mm:ss')
}
return '00:00:00'
})
async function fetchCompetition() {
const res = await getCompetitionItems()
competitionList.value = res.data.items
if (res.data.items.length) {
currentCompetition.value = res.data.items[0]
}
}
onMounted(() => {
fetchCompetition()
})
const platformKey = ref('career_data_analysis')
const platformKeys = ref([
{ platform_key: 'career_data_analysis', name: '商业数据分析实验' },
{ platform_key: 'data_marketing', name: '数据营销实操' }
])
// async function fetchPlatformKeys(competition_id: string) {
// const res = await getPlatformKeys({ competition_id })
// platformKeys.value = res.data.items
// }
// watchEffect(() => {
// if (currentCompetition.value?.id) fetchPlatformKeys(currentCompetition.value.id)
// })
function fetchInfo() {
if (currentCompetition.value?.id && platformKey.value) {
fetchStatistics(currentCompetition.value.id, platformKey.value)
fetchCompetitors(currentCompetition.value.id, platformKey.value)
}
}
let timer: null | number = null
onMounted(() => {
timer = setInterval(() => {
fetchInfo()
}, 5000)
})
onUnmounted(() => {
timer && clearInterval(timer)
})
const statistics = reactive({ competitor_count: 0, complete_answer_competitor_count: 0, starting_answer_competitor_count: 0 })
async function fetchStatistics(competition_id: string, platform_key = 'career_data_analysis') {
const res = await getCompetitionStatistics({ competition_id, platform_key })
Object.assign(statistics, res.data.detail)
}
const competitorsList = ref([])
async function fetchCompetitors(competition_id: string, platform_key = 'career_data_analysis') {
const res = await getCompetitionCompetitors({ competition_id, platform_key })
competitorsList.value = res.data.items
}
watchEffect(() => {
fetchInfo()
})
// 已提交选手
const submittedListOptions = computed(() => {
return {
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '选手姓名', prop: 'student.name' },
{ label: '参赛ID', prop: 'login_id' },
{ label: '所在学校', prop: 'student.organ_name' },
{ label: '所在专业', prop: 'student.specialty_name' },
{ label: '所在班级', prop: 'student.class_name' },
{
label: '提交时间',
prop: 'commit_time',
computed({ row }: { row: any }) {
return dayjs.unix(row.commit_time).format('YYYY-MM-DD HH:mm:ss')
}
},
{
label: '作答用时',
prop: 'commit_time',
computed({ row }: { row: any }) {
const timestamp = (row.commit_time * 1000 || useNow().value.getTime()) - row.start_answer_time * 1000
const duration = dayjs.duration(timestamp)
return duration.hours() === 0 ? duration.format("m[']s") : duration.format("H[h]m[']s")
}
}
],
data: competitorsList.value.filter((item: any) => item.commit_status == 3)
}
})
// 正在作答选手
const answeringListOptions = computed(() => {
return {
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '选手姓名', prop: 'student.name' },
{ label: '参赛ID', prop: 'login_id' },
{ label: '所在学校', prop: 'student.organ_name' },
{ label: '所在专业', prop: 'student.specialty_name' },
{ label: '所在班级', prop: 'student.class_name' },
{
label: '提交时间',
prop: 'commit_time',
computed({ row }: { row: any }) {
return dayjs.unix(row.commit_time).format('YYYY-MM-DD HH:mm:ss')
}
},
{
label: '作答用时',
prop: 'commit_time',
computed({ row }: { row: any }) {
const timestamp = (row.commit_time * 1000 || useNow().value.getTime()) - row.start_answer_time * 1000
const duration = dayjs.duration(timestamp)
return duration.hours() === 0 ? duration.format("m[']s") : duration.format("H[h]m[']s")
}
}
],
data: competitorsList.value.filter((item: any) => item.commit_status == 2 || item.commit_status == 4)
}
})
// 选手答题情况
const listOptions = computed(() => {
return {
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '选手姓名', prop: 'student.name' },
{ label: '参赛ID', prop: 'login_id' },
{ label: '性别', prop: 'student.gender' },
{ label: '所在学校', prop: 'student.organ_name' },
{ label: '所在专业', prop: 'student.specialty_name' },
{ label: '所在班级', prop: 'student.class_name' },
{
label: '模块一',
prop: 'module_1',
computed({ row }: { row: any }) {
return getModuleStatus(row, 0)
}
},
{
label: '模块二',
prop: 'module_2',
computed({ row }: { row: any }) {
return getModuleStatus(row, 1)
}
},
{
label: '模块三',
prop: 'module_3',
computed({ row }: { row: any }) {
return getModuleStatus(row, 2)
}
},
{
label: '模块四',
prop: 'module_4',
computed({ row }: { row: any }) {
return getModuleStatus(row, 3)
}
},
{
label: '模块五',
prop: 'module_5',
computed({ row }: { row: any }) {
return getModuleStatus(row, 4)
}
},
{
label: '模块六',
prop: 'module_6',
computed({ row }: { row: any }) {
return getModuleStatus(row, 5)
}
}
],
data: competitorsList.value
}
})
function getModuleStatus(row: any, index: number) {
try {
const [first] = JSON.parse(row.module_status_data) || []
const status: number = first[index].status
if (status == 0) return '<span class="not-started">未开始</span>'
if (status == 1) return '<span class="is-inProgress">进行中</span>'
if (status == 2) return '<span class="is-completed">已完成</span>'
} catch (error) {
// console.log(error)
}
return '<span class="not-started">未开始</span>'
}
</script>
<template>
<AppCard>
<h2 class="end-countdown">
结束倒计时:<span>{{ countdown }}</span>
</h2>
<el-row justify="center">
<el-select v-model="currentCompetition" value-key="id" size="large" style="margin-right: 20px">
<el-option v-for="item in competitionList" :key="item.id" :value="item" :label="item.name"></el-option>
</el-select>
<el-select v-model="platformKey" size="large">
<el-option v-for="item in platformKeys" :key="item.platform_key" :value="item.platform_key" :label="item.name"></el-option>
</el-select>
</el-row>
<ul class="statistics">
<li>
<h6>
<span>{{ statistics.competitor_count }}</span>
<em></em>
</h6>
<p>参赛人数</p>
</li>
<li>
<h6>
<span>{{ statistics.starting_answer_competitor_count }}</span>
<em></em>
</h6>
<p>正在答题人数</p>
</li>
<li>
<h6>
<span>{{ statistics.complete_answer_competitor_count }}</span>
<em></em>
</h6>
<p>已完成人数</p>
</li>
</ul>
<el-row :gutter="20">
<el-col :span="12">
<h2 class="h2-title">已提交选手</h2>
<AppList border v-bind="submittedListOptions" ref="appList"></AppList
></el-col>
<el-col :span="12">
<h2 class="h2-title">正在作答选手</h2>
<AppList border v-bind="answeringListOptions" ref="appList"></AppList
></el-col>
</el-row>
<h2 class="h2-title">选手答题情况</h2>
<AppList border v-bind="listOptions" ref="appList"></AppList>
</AppCard>
</template>
<style lang="scss">
.statistics {
display: flex;
align-items: center;
justify-content: center;
column-gap: 140px;
margin: 90px 0;
li {
width: 212px;
height: 212px;
background-color: rgba(247, 247, 247, 1);
border-radius: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
h6 {
color: rgba(178, 15, 60, 1);
span {
font-size: 40px;
}
em {
font-size: 18px;
}
}
p {
margin-top: 20px;
font-size: 18px;
color: rgba(96, 96, 96, 1);
}
}
}
.is-completed {
display: inline-block;
width: 60px;
height: 24px;
border-radius: 4px;
font-size: 12px;
line-height: 24px;
text-align: center;
background-color: rgba(211, 242, 216, 1);
color: rgba(32, 176, 48, 1);
}
.is-inProgress {
display: inline-block;
width: 60px;
height: 24px;
border-radius: 4px;
font-size: 12px;
line-height: 24px;
text-align: center;
background-color: rgba(251, 219, 225, 1);
color: rgba(171, 42, 65, 1);
}
.not-started {
display: inline-block;
width: 60px;
height: 24px;
border-radius: 4px;
font-size: 12px;
line-height: 24px;
text-align: center;
color: rgba(144, 144, 144, 1);
background-color: rgba(228, 228, 230, 1);
}
.h2-title {
padding-left: 5px;
font-size: 18px;
font-weight: 500;
line-height: 1;
margin: 20px 0;
border-left: 3px solid #aa1941;
}
.end-countdown {
float: right;
color: rgba(109, 110, 111, 1);
font-size: 14px;
span {
color: rgba(65, 80, 88, 1);
font-size: 30px;
}
}
</style>
...@@ -40,6 +40,7 @@ const adminMenus: IMenuItem[] = [ ...@@ -40,6 +40,7 @@ const adminMenus: IMenuItem[] = [
{ name: '参赛选手管理', path: '/admin/contest/contestants', tag: 'competition-competitor' }, { name: '参赛选手管理', path: '/admin/contest/contestants', tag: 'competition-competitor' },
{ name: '评分专家管理', path: '/admin/contest/experts', tag: 'expert' }, { name: '评分专家管理', path: '/admin/contest/experts', tag: 'expert' },
{ name: '大赛训练答疑', path: '/admin/contest/discuss', tag: 'v1-teacher-train-discussion' }, { name: '大赛训练答疑', path: '/admin/contest/discuss', tag: 'v1-teacher-train-discussion' },
{ name: '大赛监控', path: '/admin/contest/dashboard', tag: 'v1-expert-statistic' },
{ name: '大赛评分', path: '/admin/contest/check', tag: 'v1-expert-check' }, { name: '大赛评分', path: '/admin/contest/check', tag: 'v1-expert-check' },
{ name: '大赛发布成绩', path: '/admin/contest/score', tag: 'v1-expert-score' } { name: '大赛发布成绩', path: '/admin/contest/score', tag: 'v1-expert-score' }
] ]
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论