You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
961 lines
31 KiB
961 lines
31 KiB
<template> |
|
<div v-if="!hidePanel" |
|
:class="['panel', { active: visible }]" |
|
id="panel" |
|
ref="container" |
|
:style="style"> |
|
<el-container class="scrollbar" |
|
id="container" |
|
v-show="visible"> |
|
<el-header id="header"> |
|
<div class="panel-header" |
|
id="panelHeader"> |
|
<div class="project"> |
|
<div class="inline-flex items-center"> |
|
<p>实训项目</p> |
|
<el-tooltip effect="dark" |
|
content="点击右侧“下三角”按钮可切换实验项目" |
|
placement="bottom"> |
|
<i class="info el-icon-warning" |
|
style="margin-left: 10px"></i> |
|
</el-tooltip> |
|
</div> |
|
<el-select v-model="param.projectId" |
|
placeholder="请选择" |
|
class="select" |
|
:disabled="per != 0" |
|
@change="getCache(1)"> |
|
<el-option v-for="(item, i) in projectList" |
|
:key="item.projectId" |
|
:label="i + 1 + '. ' + item.projectName" |
|
:value="item.projectId"></el-option> |
|
</el-select> |
|
</div> |
|
<div class="item"> |
|
<div class="count"> |
|
实训{{ text }}时间 <span>{{ day }}</span>天 <span>{{ hour }}</span>小时 <span>{{ minutes }}</span>分 <span>{{ seconds }}</span>秒 |
|
</div> |
|
</div> |
|
<div class="item"> |
|
<div> |
|
总得分: |
|
<span class="total-score">{{ grade }}</span> |
|
</div> |
|
</div> |
|
<div> |
|
<el-button class="h-[40px]" |
|
@click="toReport" |
|
v-if="isSubmit">查看实验报告</el-button> |
|
<el-button class="reload h-[40px]" |
|
@click="reload(1)" |
|
v-show="per == 0">重新开始</el-button> |
|
<el-button type="primary" |
|
class="submit btn h-[40px]" |
|
:loading="submiting" |
|
@click="submit" |
|
:disabled="isSubmit || !projectList.length">提交</el-button> |
|
</div> |
|
</div> |
|
</el-header> |
|
<el-container id="infoContainer"> |
|
<el-aside id="aside" |
|
width="30%"> |
|
<div class="aside-header"> |
|
<div class="p-title"> |
|
<i class="el-icon-s-order"></i> |
|
<p>实验目标</p> |
|
</div> |
|
<div class="goal"> |
|
<div v-if="pd.experimentTargetType == 0 || !pd.experimentTargetType" |
|
class="ql-editor" |
|
v-html="pd.experimentTarget"></div> |
|
|
|
<mavon-editor v-else |
|
class="md" |
|
v-model="pd.experimentTarget" |
|
:ishljs="true" |
|
:subfield="false" |
|
:editable="false" |
|
:toolbarsFlag="false" |
|
:boxShadowStyle="none" /> |
|
</div> |
|
</div> |
|
<div class="aside-footer"> |
|
<div class="p-title"> |
|
<i class="el-icon-s-management"></i> |
|
<p>实验任务</p> |
|
</div> |
|
<div> |
|
<el-row> |
|
<el-col :span="24"> |
|
<el-card shadow="never" |
|
:border="false"> |
|
<el-table class="task-table" |
|
:data="taskList" |
|
:stripe="true"> |
|
<el-table-column type="index"></el-table-column> |
|
<el-table-column prop="name" |
|
label="判分点" |
|
align="center"></el-table-column> |
|
<el-table-column prop="score" |
|
label="分值" |
|
width="60" |
|
align="center"></el-table-column> |
|
<template v-if="!param.competitionId"> |
|
<el-table-column label="结果" |
|
width="60" |
|
align="center"> |
|
<template v-slot="scope"> |
|
<template v-if="isSubmit"> |
|
<div v-if="!param.competitionId" |
|
class="flex justify-center items-center"> |
|
<el-icon v-if="scope.row.finishedResult" |
|
color="#15d500" |
|
:size="16"> |
|
<Check /> |
|
</el-icon> |
|
<el-icon v-else |
|
color="#f00" |
|
:size="16"> |
|
<Close /> |
|
</el-icon> |
|
</div> |
|
<template v-else>-</template> |
|
</template> |
|
</template> |
|
</el-table-column> |
|
<el-table-column prop="score" |
|
label="得分" |
|
width="60" |
|
align="center"> |
|
<template v-slot="scope"> |
|
<template v-if="isSubmit">{{ param.competitionId ? '-' : scope.row.examScore }}</template> |
|
</template> |
|
</el-table-column> |
|
</template> |
|
</el-table> |
|
</el-card> |
|
</el-col> |
|
</el-row> |
|
</div> |
|
</div> |
|
</el-aside> |
|
<el-main id="main"> |
|
<el-tabs class="info-tab" |
|
v-model="pannelTab" |
|
type="card"> |
|
<el-tab-pane label="项目背景" |
|
name="first"> |
|
<div v-if="pd.experimentDescriptionType == 0 || !pd.experimentDescriptionType" |
|
class="ql-editor" |
|
v-html="pd.experimentDescription"></div> |
|
|
|
<mavon-editor v-else |
|
class="md" |
|
v-model="pd.experimentDescription" |
|
:ishljs="true" |
|
:subfield="false" |
|
:editable="false" |
|
:toolbarsFlag="false" |
|
:boxShadowStyle="none" /> |
|
</el-tab-pane> |
|
<el-tab-pane label="实验要求" |
|
name="second"> |
|
<el-collapse v-model="curReq"> |
|
<el-collapse-item v-for="item in points" |
|
:name="item.judgmentId" |
|
:key="item.judgmentId"> |
|
<template v-slot:title> |
|
<i class="el-icon-s-ticket"></i> |
|
<div class="break-all des" |
|
v-html="item.name"></div> |
|
</template> |
|
<div v-if="item.experimentalRequirementsType == 0 || !item.experimentalRequirementsType" |
|
class="ql-editor" |
|
v-html="item.experimentalRequirements"></div> |
|
|
|
<mavon-editor v-else |
|
class="md" |
|
v-model="item.experimentalRequirements" |
|
:ishljs="true" |
|
:subfield="false" |
|
:editable="false" |
|
:toolbarsFlag="false" |
|
:boxShadowStyle="none" /> |
|
</el-collapse-item> |
|
</el-collapse> |
|
</el-tab-pane> |
|
<el-tab-pane label="实验提示" |
|
name="third" |
|
v-if="hintOpen"> |
|
<div v-if="pd.experimentHintType == 0 || !pd.experimentHintType" |
|
class="ql-editor" |
|
v-html="pd.experimentHint"></div> |
|
|
|
<mavon-editor v-else |
|
class="md" |
|
v-model="pd.experimentHint" |
|
:ishljs="true" |
|
:subfield="false" |
|
:editable="false" |
|
:toolbarsFlag="false" |
|
:boxShadowStyle="none" /> |
|
</el-tab-pane> |
|
</el-tabs> |
|
</el-main> |
|
</el-container> |
|
</el-container> |
|
|
|
<div :class="['toggle absolute top-[200px] text-center', visible ? 'top-[35%] left-[100%]' : '']"> |
|
<el-icon class="cursor-pointer" |
|
color="#f1772b" |
|
:size="24"> |
|
<Rank id="toggle" /> |
|
</el-icon> |
|
<div class="toggle-panel w-[40px] h-[175px] bg-[length:100%_100%] bg-no-repeat cursor-pointer" |
|
:class="{ active: visible }" |
|
ref="handle" |
|
@click="visible = !visible"></div> |
|
</div> |
|
</div> |
|
<div v-if="isSubmit && !isReport" |
|
class="z-[199] fixed top-[64px] right-0 bottom-0 left-0 bg-[rgba(0,0,0,.3)]"></div> |
|
</template> |
|
<script setup lang="ts"> |
|
import { ref, reactive, onMounted, inject, computed, watch } from 'vue'; |
|
import { submitOpe } from '@/api/bank'; |
|
import { getSandTableLastCache, deleteOperationData } from '@/api/judgment'; |
|
import { getProjectBySystemId, getProjectDetail, getDetailById, getCompetition, getStartTime } from '@/api/system'; |
|
import Settings from '@/settings'; |
|
import { useRouter, useRoute } from 'vue-router'; |
|
import type { Action } from 'element-plus'; |
|
import { ElMessage, ElMessageBox } from 'element-plus'; |
|
import { Close, Check, Rank } from '@element-plus/icons-vue'; |
|
import dayjs from 'dayjs'; |
|
import Cookies from 'js-cookie'; |
|
import { mavonEditor } from 'mavon-editor'; |
|
import 'mavon-editor/dist/css/index.css'; |
|
import '@vueup/vue-quill/dist/vue-quill.snow.css'; |
|
import { useDraggable } from '@vueuse/core'; |
|
import { logout } from '@/store/useCurrentUser'; |
|
import { getIds, getNow } from '@/utils/common'; |
|
|
|
const router = useRouter(); |
|
const route = useRoute(); |
|
const param = reactive<Record<string, any>>(route.query); |
|
const hidePanel = computed(() => Settings.hidePanelPath.includes(route.path)); |
|
const curSystemId = ref<number>(1); |
|
const per = ref<number>(0); // 项目权限(0、练习 1、考核 2、竞赛) |
|
const isSubmit = ref<boolean>(false); // 是否提交的标识 |
|
const entryTime = ref<any>(new Date()); |
|
const visible = ref<boolean>(true); |
|
const isReport = ref<boolean>(true); |
|
const grade = ref<string | number>('00'); |
|
const text = ref<string>(''); // 倒计时前面的文字,练习:所用;考核:剩余。练习是计时,考核是倒计时 |
|
const counterTimer = ref<any>(null); |
|
const day = ref<number | string>(0); |
|
const seconds = ref<number | string>(0); |
|
const minutes = ref<number | string>(0); |
|
const hour = ref<number | string>(0); |
|
const projectList = ref<Record<string, any>[]>([]); |
|
const pd = ref<Record<string, any>>({}); |
|
const hintOpen = ref<boolean>(false); |
|
const points = ref<Record<string, any>[]>([]); |
|
const judgmentId = ref<string | number>(''); |
|
const curReq = ref<Record<string, any>[]>([]); |
|
const taskList = ref<Record<string, any>[]>([]); |
|
const pannelTab = ref<string>('first'); |
|
const submiting = ref<boolean>(false); |
|
const reportId = ref<string | number>(''); |
|
const countVal = ref<any>(''); |
|
const getLevel = ref(); |
|
const container = ref<HTMLElement | null>(null); |
|
const handle = ref<HTMLElement | null>(null); |
|
const isFirst = ref<boolean>(true); |
|
// 实验面板拖拽 |
|
const { x, y, style } = useDraggable(container, { |
|
initialValue: { x: 0, y: 200 }, |
|
stopPropagation: true, |
|
handle: handle.value, |
|
onStart(position, e) { |
|
const { id } = e.target; |
|
if (id !== 'panelHeader' && id !== 'toggle') return false; |
|
}, |
|
}); |
|
|
|
if (param.token) { |
|
// 从url带进来的参数,存cookie里,其他页面直接取cookie |
|
param.token && Cookies.set('sand-token', param.token); |
|
Cookies.set('sand-projectId', param.projectId ?? ''); |
|
Cookies.set('sand-systemId', param.systemId ?? ''); |
|
Cookies.set('sand-classId', param.classId ?? ''); |
|
Cookies.set('sand-cid', param.cid ?? ''); |
|
Cookies.set('sand-assessmentId', param.assessmentId ?? ''); |
|
Cookies.set('sand-competitionId', param.competitionId ?? ''); |
|
Cookies.set('sand-stageId', param.stageId ?? ''); |
|
Cookies.set('sand-teamId', param.teamId ?? ''); |
|
Cookies.set('sand-mallId', param.mallId ?? ''); |
|
Cookies.set('sand-referrer', param.referrer ?? ''); |
|
Cookies.set('sand-className', param.className ?? ''); |
|
Cookies.set('sand-startTime', param.startTime ?? ''); |
|
Cookies.set('sand-resultsDetails', param.resultsDetails ?? ''); |
|
Cookies.set('sand-resultAnnouncementTime', param.resultAnnouncementTime ?? ''); |
|
Cookies.set('sand-curriculumName', param.curriculumName ?? ''); |
|
Cookies.set('sand-stopTime', param.stopTime ?? ''); |
|
Cookies.set('sand-userId', param.userId ?? ''); |
|
Cookies.set('sand-account', param.account ?? ''); |
|
Cookies.set('sand-admin', param.admin ?? ''); // 从中台进来的标识 |
|
Cookies.remove('sand-submit'); |
|
Cookies.remove('sand-loaded'); |
|
router.replace(route.path); |
|
} else { |
|
param.systemId = Cookies.get('sand-systemId'); |
|
param.projectId = Cookies.get('sand-projectId'); |
|
param.classId = Cookies.get('sand-classId'); |
|
param.cid = Cookies.get('sand-cid'); |
|
param.assessmentId = Cookies.get('sand-assessmentId'); |
|
param.competitionId = Cookies.get('sand-competitionId'); |
|
param.stageId = Cookies.get('sand-stageId'); |
|
param.teamId = Cookies.get('sand-teamId'); |
|
param.mallId = Cookies.get('sand-mallId'); |
|
param.className = Cookies.get('sand-className'); |
|
param.startTime = Cookies.get('sand-startTime'); |
|
param.resultsDetails = Cookies.get('sand-resultsDetails'); |
|
param.resultAnnouncementTime = Cookies.get('sand-resultAnnouncementTime'); |
|
param.curriculumName = Cookies.get('sand-curriculumName'); |
|
param.stopTime = Cookies.get('sand-stopTime'); |
|
param.userId = Cookies.get('sand-userId'); |
|
param.account = Cookies.get('sand-account'); |
|
isSubmit.value = Cookies.get('sand-submit') === 'true'; |
|
} |
|
if (param.projectId) param.projectId = +param.projectId; |
|
|
|
watch( |
|
route, |
|
() => { |
|
visible.value = route.path === '/'; |
|
isReport.value = route.path === '/report'; |
|
}, |
|
{ |
|
immediate: true, |
|
}, |
|
); |
|
|
|
// 获取上次实验的时间 |
|
const getSumTime = (reset?: number): Promise<any> => { |
|
return new Promise(async (resolve, reject) => { |
|
const res = await getStartTime({ |
|
permissions: per.value, |
|
projectId: param.projectId, |
|
reset, |
|
}); |
|
resolve(res.startTime ? new Date(res.startTime) : ''); |
|
}); |
|
}; |
|
// 获取进入时间 |
|
const getEntryTime = async (resetTime?: number) => { |
|
let now = await getSumTime(resetTime); // 获取上次进入实验的时间,如果没有,说明是第一次进入,然后直接从服务器获取当前时间 |
|
if (!now) now = await getNow(); |
|
entryTime.value = now; |
|
}; |
|
// 倒计时 |
|
const timeFormat = (num: number): string | number => { |
|
return num < 10 ? `0${num}` : num; |
|
}; |
|
// 清除时间 |
|
const reloadCount = () => { |
|
clearInterval(counterTimer.value); |
|
countVal.value = ''; |
|
day.value = '00'; |
|
seconds.value = '00'; |
|
minutes.value = '00'; |
|
hour.value = '00'; |
|
}; |
|
// 计时器(考核是倒计时,练习是计时) |
|
const counter = (counterTime: number) => { |
|
const leave1 = counterTime % (24 * 3600); // 计算天数后剩余的毫秒数 |
|
const leave2 = leave1 % 3600; // 计算小时数后剩余的毫秒数 |
|
const leave3 = leave2 % 60; // 计算分钟数后剩余的毫秒数 |
|
|
|
day.value = timeFormat(Math.floor(counterTime / (24 * 3600))); |
|
hour.value = timeFormat(Math.floor(leave1 / 3600)); |
|
minutes.value = timeFormat(Math.floor(leave2 / 60)); |
|
seconds.value = timeFormat(Math.round(leave3)); |
|
}; |
|
// 启动倒计时 |
|
const startCount = () => { |
|
clearInterval(counterTimer.value); |
|
counterTimer.value = setInterval(() => { |
|
counter(per.value ? countVal.value-- : countVal.value++); |
|
}, 1000); |
|
}; |
|
// 提交状态 |
|
const setSubmit = (val: boolean) => { |
|
isSubmit.value = val; |
|
Cookies.set('sand-submit', val); |
|
}; |
|
// 获取考核列表来查询该考核是否已经考过 |
|
const getAssList = async () => { |
|
await getAssStatus(); |
|
await getProDetail(); |
|
}; |
|
// 查询考核状态(查到考核如果结束后,直接提交考核) |
|
const getAssStatus = async () => { |
|
// 未提交才需要查询状态 |
|
if (!isSubmit.value) { |
|
const { data } = await getDetailById(param.assessmentId); |
|
const done = data ? data.status === 2 : false; // 状态(0、待开始 1、进行中 2、已结束) |
|
// 如果考核已结束,自动提交 |
|
if (done) { |
|
submit(); |
|
ElMessageBox.alert(`考核时间已到,系统已自动交卷`, '提示', { |
|
confirmButtonText: '确定', |
|
callback: (action: Action) => { |
|
logout(); |
|
}, |
|
}); |
|
} |
|
} |
|
}; |
|
|
|
// 查询竞赛状态(查到竞赛如果结束后,直接提交竞赛) |
|
const getCompetitionStatus = async () => { |
|
// 未提交才需要查询状态 |
|
if (!isSubmit.value) { |
|
const { competition } = await getCompetition(param.competitionId); |
|
const stages = competition.competitionStage; |
|
if (stages) { |
|
const stage = stages.find((e) => e.stageId == param.stageId); |
|
const endTime = new Date(stage.endTime); |
|
const now = await getNow(); |
|
// 如果已经结束 |
|
if (now >= endTime) { |
|
ElMessageBox.alert(`竞赛时间已到,系统已自动交卷`, '提示', { |
|
confirmButtonText: '确定', |
|
callback: (action: Action) => { |
|
logout(); |
|
}, |
|
}); |
|
submit(); |
|
} else { |
|
// 没结束,则显示倒计时 |
|
countVal.value = (endTime - now) / 1000; |
|
startCount(); |
|
} |
|
} |
|
} |
|
}; |
|
// 删除缓存 |
|
const delCache = async () => { |
|
await deleteOperationData({ |
|
cid: param.cid, |
|
projectId: param.projectId, |
|
assessmentId: param.assessmentId, |
|
competitionId: param.competitionId, |
|
}); |
|
}; |
|
// 项目切换完后的操作 |
|
const setNewProject = (reloadPage?: number) => { |
|
Cookies.set('sand-projectId', param.projectId); |
|
getProDetail(); |
|
setSubmit(false); |
|
countVal.value = 0; |
|
grade.value = '00'; |
|
pannelTab.value = 'first'; |
|
reload(); |
|
getEntryTime(); |
|
|
|
if (reloadPage) { |
|
// 在选择关卡的页面,直接刷新;否则调选择关卡页的获取关卡接口 |
|
if (route.path === '/') { |
|
location.reload(); |
|
} else { |
|
getLevel.value && getLevel.value(); |
|
} |
|
} |
|
}; |
|
// 项目选择回调 |
|
const getCache = async (reloadPage?: number) => { |
|
// 查询该项目是否有缓存记录 |
|
const res = await getSandTableLastCache({ |
|
cid: param.cid, |
|
projectId: param.projectId, |
|
assessmentId: param.assessmentId || '', |
|
competitionId: param.competitionId || '', |
|
}); |
|
// 如果有缓存 |
|
if (res.getLastCache) { |
|
ElMessageBox.confirm('是否要继续上次的操作记录?', '提示', { |
|
confirmButtonText: '是', |
|
cancelButtonText: '否', |
|
type: 'success', |
|
closeOnClickModal: false, |
|
}) |
|
.then(async () => { |
|
// 如果返回了关卡id,则存起来 |
|
if (res.checkpointId) { |
|
Cookies.set('sand-level', res.checkpointId); |
|
} |
|
|
|
setNewProject(reloadPage); |
|
}) |
|
.catch(async () => { |
|
Cookies.remove('sand-level'); |
|
setNewProject(); |
|
delCache(); |
|
// 在选择关卡的页面,直接刷新;否则调选择关卡页的获取关卡接口 |
|
if (reloadPage) { |
|
if (route.path === '/') { |
|
location.reload(); |
|
} else { |
|
getLevel.value && getLevel.value(); |
|
} |
|
} |
|
}); |
|
} else { |
|
Cookies.remove('sand-level'); |
|
setNewProject(); |
|
} |
|
}; |
|
// 查看实验报告 |
|
const toReport = () => { |
|
router.push('/report'); |
|
}; |
|
// 重新开始 |
|
const reload = async (fromReload?: number) => { |
|
if (fromReload) { |
|
getEntryTime(1); |
|
await delCache(); // 点了重新开始才需要删除缓存,切换了项目不需要删除缓存 |
|
Cookies.remove('sand-level'); |
|
} |
|
reloadCount(); |
|
grade.value = '00'; |
|
setSubmit(false); |
|
startCount(); |
|
router.push('/'); |
|
}; |
|
// 提交 |
|
const submit = async () => { |
|
if (isSubmit.value) return false; |
|
const checkpointId = Cookies.get('sand-level') ?? ''; |
|
if (!checkpointId) return ElMessage.error('请选择关卡'); |
|
ElMessageBox.confirm('此操作将视为结束考试,是否继续?', '提示', { |
|
confirmButtonText: '确定', |
|
cancelButtonText: '取消', |
|
type: 'warning', |
|
closeOnClickModal: false, |
|
}) |
|
.then(async () => { |
|
submiting.value = true; |
|
const date = new Date(); |
|
const timeSum = Math.ceil((date.getTime() - entryTime.value.getTime()) / 60000); // 计算实验用时(分钟),向上取整 |
|
const submitTime = dayjs(date).format('YYYY-MM-DD HH:mm:ss'); |
|
reloadCount(); |
|
const { retMap } = await submitOpe({ |
|
classId: param.classId ? param.classId : '', |
|
className: param.className ? param.className : '', |
|
curriculumId: param.cid, |
|
startTime: dayjs(entryTime.value).format('YYYY-MM-DD HH:mm:ss'), // 取页面进入的时间 |
|
endTime: per.value ? param.stopTime : submitTime, // 结束时间(考核:直接从职站取考核的结束时间;练习:取提交时间) |
|
submitTime, // 提交时间,即当前时间(这3个时间都是传完整的日期时间格式) |
|
timeSum, |
|
checkpointId, |
|
projectId: param.projectId, |
|
projectName: projectList.value.find((e) => e.projectId == param.projectId)?.projectName, |
|
lcId: curReq.value, |
|
systemId: curSystemId.value, |
|
purpose: pd.value.experimentTarget, // 实验目的 |
|
assessmentId: param.assessmentId, |
|
competitionId: param.competitionId, |
|
stageId: param.stageId, |
|
teamId: param.teamId, |
|
mallId: param.mallId, |
|
}); |
|
setSubmit(true); |
|
|
|
let score = 0; |
|
// 给判分列表添加分数和运行结果 |
|
taskList.value.map((e) => { |
|
const item = retMap?.scoreInfo.find((n) => n.lcId === e.judgmentId); |
|
try { |
|
if (item) { |
|
e.examScore = item.questionScore; |
|
e.finishedResult = item.calculate; // 1:正确,2:错误 |
|
score += item.questionScore; // 计算总分 |
|
} else { |
|
e.examScore = 0; |
|
} |
|
} catch (e) {} |
|
}); |
|
grade.value = score < 10 ? '0' + score : score; |
|
reportId.value = retMap.reportId; |
|
Cookies.set('sand-reportId', retMap.reportId); |
|
localStorage.setItem('sand-taskList', JSON.stringify(taskList.value)); |
|
delCache(); |
|
submiting.value = false; |
|
|
|
// 非练习 |
|
per.value && |
|
ElMessageBox.alert( |
|
`提交成功${param.resultsDetails == 0 && param.resultAnnouncementTime != 0 ? ',成绩将在' + param.resultAnnouncementTime + '小时后发布,请去参赛信息模块查看' : ''}`, |
|
'提示', |
|
{ |
|
confirmButtonText: '确定', |
|
callback: (action: Action) => { |
|
logout(); |
|
}, |
|
}, |
|
); |
|
}) |
|
.catch(() => { |
|
submiting.value = false; |
|
}); |
|
}; |
|
|
|
// 获取项目详情 |
|
const getProDetail = async () => { |
|
const res = await getProjectDetail({ |
|
projectId: param.projectId, |
|
stuAssessent: 1, |
|
}); |
|
const pointsList = res.projectJudgmentVos; |
|
const project = res.projectManage; |
|
Cookies.set('sand-projectId', param.projectId); |
|
// 考核/竞赛 |
|
if (per.value) { |
|
projectList.value = [ |
|
{ |
|
projectId: param.projectId, |
|
projectName: project.projectName, |
|
}, |
|
]; |
|
} |
|
curReq.value = pointsList.map((e) => e.judgmentId); // 实验要求默认全部展开,通过judgmentId来选中item |
|
points.value = pointsList; |
|
taskList.value = isSubmit.value ? JSON.parse(localStorage.getItem('sand-taskList')) : pointsList; // 实验任务 |
|
judgmentId.value = pointsList[0].judgmentId; // 默认取第一个判分点 |
|
pd.value = project; |
|
curSystemId.value = project.systemId; |
|
hintOpen.value = project.founder ? !project.hintOpenBySchool : !project.hintOpen; // 0显示,1不显示,系统跟老师的禁用字段不一样 |
|
const isPrac = per.value === 0; // 是否是练习 |
|
text.value = isPrac ? '已用' : '剩余'; |
|
// 竞赛不需要 |
|
if (!param.competitionId && !isSubmit.value) { |
|
const now = await getNow(); |
|
countVal.value = (isPrac ? now - entryTime.value : new Date(param.stopTime).getTime() - now) / 1000; // 如果是考核,取考核的结束时间减去当前时间去做倒计时,练习则直接取当前时间减去上次进来的时间做计时 |
|
startCount(); |
|
} |
|
}; |
|
|
|
// 获取项目列表 |
|
const getList = async () => { |
|
const { projects } = await getProjectBySystemId({ |
|
systemId: param.systemId, |
|
cId: param.cid ?? '', |
|
mallId: param.mallId, |
|
permissions: per.value, |
|
}); |
|
projectList.value = projects; |
|
if (!per.value && !param.projectId) param.projectId = projects[0]?.projectId ?? 0; // 默认取第一个项目 |
|
getProDetail(); |
|
}; |
|
|
|
// websocket获取考核及竞赛信息,用于自动提交 |
|
// socket连接成功 |
|
const open = () => { |
|
console.log('socket连接成功'); |
|
}; |
|
// socket连接失败 |
|
const error = () => { |
|
console.log('连接错误'); |
|
}; |
|
// 接收消息 |
|
const getMessage = (msg) => { |
|
console.log('==websocket接收数据=='); |
|
console.log(JSON.parse(msg.data)); |
|
const { content } = JSON.parse(msg.data); |
|
// 1赛事、2创业、3考核、4模型。-号拼接携带id |
|
if (content == 1) { |
|
getCompetitionStatus(); |
|
} else if (content.includes('3-')) { |
|
// 考核:3-考核id |
|
getAssStatus(); |
|
} |
|
}; |
|
// 关闭socket |
|
const close = () => { |
|
console.log('socket已经关闭'); |
|
}; |
|
// 初始化socket |
|
const initSocket = () => { |
|
// 实例化socket |
|
const socket = new WebSocket(`wss://${location.host}/nakadai/websocket/${param.userId}/${param.account}`); |
|
// this.socket = new WebSocket(`ws://121.37.12.51:9100/nakadai/websocket/${id}/${account}`) |
|
// 监听socket连接 |
|
socket.onopen = open; |
|
// 监听socket错误信息 |
|
socket.onerror = error; |
|
// 监听socket消息 |
|
socket.onmessage = getMessage; |
|
}; |
|
const handleCache = () => { |
|
// 没提交的情况下才需要查 |
|
if (!isSubmit.value) { |
|
param.cid && getEntryTime(); |
|
visible.value && !Cookies.get('sand-loaded') && getCache(0); // 选择关卡页面才需要弹出是否恢复缓存的弹框 |
|
} |
|
}; |
|
// 初始化 |
|
const init = async () => { |
|
getLevel.value = inject('getLevel'); // 关卡页面获取关卡方法 |
|
per.value = param.assessmentId ? 1 : param.competitionId ? 2 : 0; |
|
|
|
if (param.assessmentId) { |
|
// 考核 |
|
await getAssList(); |
|
handleCache(); |
|
initSocket(); |
|
} else if (param.competitionId) { |
|
// 竞赛 |
|
getCompetitionStatus(); |
|
handleCache(); |
|
initSocket(); |
|
} else { |
|
// 练习 |
|
param.cid && getList(); |
|
handleCache(); |
|
} |
|
Cookies.set('sand-loaded', 1); |
|
}; |
|
onMounted(init); |
|
</script> |
|
|
|
<style lang="scss" scoped> |
|
.el-main { |
|
width: 60%; |
|
background-color: #fff; |
|
color: #333; |
|
padding: 0; |
|
font-size: 16px; |
|
margin: 0px 20px 10px 10px; |
|
white-space: pre-wrap; |
|
overflow: hidden; |
|
} |
|
.panel-header { |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
.project { |
|
display: inline-flex; |
|
align-items: center; |
|
width: 28%; |
|
} |
|
.item { |
|
font-size: 16px; |
|
margin: 0 10px; |
|
padding: 20px 0; |
|
} |
|
.count { |
|
margin-left: -40px; |
|
span { |
|
padding: 5px 15px; |
|
margin: 0 5px; |
|
color: #333; |
|
font-size: 14px; |
|
text-align: center; |
|
background: #fff; |
|
border-radius: 18px; |
|
} |
|
} |
|
.total-score { |
|
@apply p-[10px] text-sm text-center rounded-[6px] bg-[#e0e0e0]; |
|
} |
|
.submit { |
|
width: 106px; |
|
font-size: 16px; |
|
} |
|
.reload { |
|
color: #d0d0d0; |
|
font-size: 16px; |
|
background-color: #202020; |
|
} |
|
} |
|
:deep(.des) { |
|
font-size: 16px; |
|
font-family: 'Microsoft YaHei'; |
|
img { |
|
max-width: 100%; |
|
} |
|
} |
|
:deep(.el-collapse-item__wrap) { |
|
border-bottom: none; |
|
} |
|
:deep(.el-collapse-item__header) { |
|
border-bottom: none; |
|
} |
|
:deep(.el-icon-s-ticket:before) { |
|
padding: 5px; |
|
font-size: 16px; |
|
} |
|
:deep(.el-collapse-item__arrow) { |
|
margin: 0 5px 0 0; |
|
} |
|
:deep(.info-tab.el-tabs--card) { |
|
.el-tabs__item { |
|
font-size: 16px; |
|
} |
|
.el-tabs__item.is-active { |
|
@apply text-white bg-[#568df2]; |
|
} |
|
.el-tabs__header .el-tabs__nav { |
|
border: none; |
|
} |
|
.el-tabs__header .el-tabs__item { |
|
border-left: none; |
|
} |
|
.el-tabs__header { |
|
padding: 5px 20px; |
|
border-bottom: none; |
|
} |
|
& > .el-tabs__content { |
|
margin: 0 20px; |
|
max-height: calc(70vh - 210px); |
|
overflow: auto; |
|
} |
|
} |
|
:deep(.el-collapse) { |
|
border-bottom: none; |
|
border-top: none; |
|
} |
|
.el-aside { |
|
margin-bottom: 10px; |
|
color: #333; |
|
background-color: #fff; |
|
} |
|
.el-aside :deep([class*=' el-icon-']), |
|
[class^='el-icon-'] { |
|
line-height: 40px; |
|
font-size: 16px; |
|
} |
|
.aside-header { |
|
margin: 0px 10px 10px 10px; |
|
background-color: #fff; |
|
} |
|
.aside-footer { |
|
margin: 0px 10px 10px 10px; |
|
background-color: #fff; |
|
} |
|
.p-title { |
|
@apply flex justify-center h-[40px] bg-[url('@/assets/images/panel/header.png')] bg-[length:100%_100%] bg-no-repeat; |
|
p { |
|
padding-left: 10px; |
|
line-height: 40px; |
|
font-size: 16px; |
|
color: #fff; |
|
} |
|
i { |
|
color: #fff; |
|
} |
|
} |
|
:deep(.el-card__body) { |
|
padding: 0; |
|
} |
|
:deep(.task-table) { |
|
font-size: 12px; |
|
thead { |
|
@apply text-white text-[10px]; |
|
} |
|
th.el-table__cell { |
|
@apply bg-[#badfff]; |
|
} |
|
th > .cell { |
|
font-weight: 100; |
|
} |
|
td, |
|
th.is-leaf { |
|
border-bottom: 0 !important; |
|
} |
|
.el-table__cell { |
|
padding: 6px 0; |
|
} |
|
} |
|
.goal { |
|
padding: 10px 0; |
|
margin: 0 10px; |
|
font-size: 14px; |
|
} |
|
:deep(.select) { |
|
@apply flex-1; |
|
.el-select__caret:before { |
|
// content: '\e78f'; |
|
padding: 3px; |
|
font-size: 16px; |
|
color: #fff; |
|
background-color: #568df2; |
|
border-radius: 50%; |
|
} |
|
.el-input__icon { |
|
line-height: 60px; |
|
} |
|
.el-input { |
|
padding: 10px 0; |
|
} |
|
.el-input--suffix .el-input__inner { |
|
height: 40px !important; |
|
padding-right: 50px; |
|
margin-left: 15px; |
|
color: #333; |
|
font-size: 14px; |
|
border-radius: 30px; |
|
border: none; |
|
background-color: #fff; |
|
overflow: hidden; |
|
text-overflow: ellipsis; |
|
white-space: nowrap; |
|
} |
|
} |
|
.panel { |
|
z-index: 200; |
|
position: fixed; |
|
top: 200px; |
|
bottom: 20px; |
|
left: 0; |
|
width: 0; |
|
height: 0; |
|
.toggle-panel { |
|
@apply bg-[url('@/assets/images/panel/right.png')]; |
|
&.active { |
|
@apply bg-[url('@/assets/images/panel/left.png')]; |
|
} |
|
} |
|
&.active { |
|
position: fixed; |
|
width: 85%; |
|
height: 70%; |
|
} |
|
} |
|
:deep(.el-container) { |
|
height: calc(100% - 60px); |
|
&.is-vertical { |
|
background-color: #f5f5f5; |
|
} |
|
} |
|
.right { |
|
color: #00af00; |
|
font-size: 20px; |
|
} |
|
.wrong { |
|
color: #f00; |
|
font-size: 20px; |
|
} |
|
.info { |
|
color: #bfbfbf; |
|
cursor: pointer; |
|
&:hover { |
|
opacity: 0.9; |
|
} |
|
} |
|
.ql-editor { |
|
@apply text-sm; |
|
} |
|
</style>
|
|
|