|
|
|
<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="selectProject">
|
|
|
|
<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"
|
|
|
|
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 { deleteCache } from '@/api/judgment';
|
|
|
|
import { pageStuAssessment, getProjectBySystemId, getProjectDetail, getDetailById, getCompetition } 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';
|
|
|
|
|
|
|
|
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 statusTimer = ref<any>(null);
|
|
|
|
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 { 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-endTime', param.endTime ?? '');
|
|
|
|
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.remove('sand-submit');
|
|
|
|
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.endTime = Cookies.get('sand-endTime');
|
|
|
|
param.className = Cookies.get('sand-className');
|
|
|
|
param.startTime = Cookies.get('sand-startTime');
|
|
|
|
param.resultsDetails = Cookies.get('sand-resultsDetails');
|
|
|
|
param.resultAnnouncementTime = Cookies.get('sand-resultAnnouncementTime');
|
|
|
|
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 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 () => {
|
|
|
|
const { list } = await pageStuAssessment({
|
|
|
|
pageNum: 1,
|
|
|
|
pageSize: 10000,
|
|
|
|
});
|
|
|
|
let done = false;
|
|
|
|
// 匹配到该考核,并且已经提交过(有reportId则说明有实验记录),并且classId跟当前用户的classId相同,就提示提交了考核然后返回上一页
|
|
|
|
if (list.find((e) => e.assessmentId == param.assessmentId && e.reportId && e.classId == param.classId)) {
|
|
|
|
done = true;
|
|
|
|
setSubmit(true);
|
|
|
|
ElMessage.error('你已经提交过该考核!');
|
|
|
|
setTimeout((_) => {
|
|
|
|
window.history.back(); // 直接返回上一页面
|
|
|
|
}, 1500);
|
|
|
|
}
|
|
|
|
statusTimer.value = setInterval((_) => {
|
|
|
|
getAssStatus();
|
|
|
|
}, 1000);
|
|
|
|
};
|
|
|
|
// 定时查询考核状态(查到考核如果结束后,直接提交考核)
|
|
|
|
const getAssStatus = async () => {
|
|
|
|
// 未提交才需要查询状态
|
|
|
|
if (!isSubmit.value) {
|
|
|
|
const { data } = await getDetailById(param.assessmentId);
|
|
|
|
const done = data ? data.status === 2 : false; // 状态(0、待开始 1、进行中 2、已结束)
|
|
|
|
// 如果考核已结束,则清除查询考核状态的定时器,并且自动提交
|
|
|
|
if (done) {
|
|
|
|
clearInterval(statusTimer.value);
|
|
|
|
// this.$alert('考核时间已到,系统已自动交卷', '提示', {
|
|
|
|
// confirmButtonText: '确定',
|
|
|
|
// });
|
|
|
|
submit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// 定时查询竞赛状态(查到竞赛如果结束后,直接提交竞赛)
|
|
|
|
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).getTime();
|
|
|
|
const now = Date.now();
|
|
|
|
// 如果已经结束
|
|
|
|
if (now >= new Date(stage.endTime)) {
|
|
|
|
clearInterval(statusTimer.value);
|
|
|
|
// this.$alert('竞赛时间已到,系统已自动交卷', '提示', {
|
|
|
|
// confirmButtonText: '确定',
|
|
|
|
// });
|
|
|
|
submit();
|
|
|
|
} else {
|
|
|
|
// 没结束,则显示倒计时(竞赛才需要通过定时调接口来显示倒计时,因为中台可以修改结束时间,所以需要时刻获取最新的结束时间)
|
|
|
|
counter((endTime - now) / 1000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// 项目选择回调
|
|
|
|
const selectProject = () => {
|
|
|
|
Cookies.set('sand-projectId', param.projectId);
|
|
|
|
getProDetail();
|
|
|
|
setSubmit(false);
|
|
|
|
countVal.value = 0;
|
|
|
|
grade.value = '00';
|
|
|
|
pannelTab.value = 'first';
|
|
|
|
reload();
|
|
|
|
// 在选择关卡的页面,直接刷新;否则调选择关卡页的获取关卡接口
|
|
|
|
if (route.path === '/') {
|
|
|
|
location.reload();
|
|
|
|
} else {
|
|
|
|
getLevel.value && getLevel.value();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// 查看实验报告
|
|
|
|
const toReport = () => {
|
|
|
|
router.push('/report');
|
|
|
|
};
|
|
|
|
// 重新开始
|
|
|
|
const reload = async () => {
|
|
|
|
await deleteCache(param.projectId, Cookies.get('sand-level') ?? '');
|
|
|
|
reloadCount();
|
|
|
|
grade.value = '00';
|
|
|
|
setSubmit(false);
|
|
|
|
startCount();
|
|
|
|
router.push('/');
|
|
|
|
};
|
|
|
|
// 提交
|
|
|
|
const submit = async () => {
|
|
|
|
if (isSubmit.value) return false;
|
|
|
|
ElMessageBox.confirm('此操作将视为结束考试,是否继续?', '提示', {
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
type: 'warning',
|
|
|
|
})
|
|
|
|
.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: per.value ? param.startTime : dayjs(entryTime.value).format('YYYY-MM-DD HH:mm:ss'), // 开始时间(考核:直接从职站取考核的开始时间;练习:取页面进入的时间)
|
|
|
|
endTime: per.value ? param.endTime : submitTime, // 结束时间(考核:直接从职站取考核的结束时间;练习:取提交时间)
|
|
|
|
submitTime, // 提交时间,即当前时间(这3个时间都是传完整的日期时间格式)
|
|
|
|
timeSum,
|
|
|
|
checkpointId: Cookies.get('sand-level') ?? '',
|
|
|
|
projectId: param.projectId,
|
|
|
|
projectName: projectList.value.find((e) => e.projectId == param.projectId)?.projectName,
|
|
|
|
lcId: curReq.value,
|
|
|
|
assessmentId: param.assessmentId,
|
|
|
|
systemId: curSystemId.value,
|
|
|
|
purpose: pd.value.experimentTarget, // 实验目的
|
|
|
|
competitionId: param.competitionId,
|
|
|
|
stageId: param.stageId,
|
|
|
|
teamId: param.teamId,
|
|
|
|
mallId: param.mallId,
|
|
|
|
});
|
|
|
|
setSubmit(true);
|
|
|
|
clearInterval(statusTimer.value);
|
|
|
|
|
|
|
|
let score = 0;
|
|
|
|
// 给判分列表添加分数和运行结果
|
|
|
|
taskList.value.map((e) => {
|
|
|
|
const item = retMap?.scoreInfo.find((n) => n.lcId === e.judgmentId);
|
|
|
|
console.log('🚀 ~ taskList.value.map ~ item:', item);
|
|
|
|
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));
|
|
|
|
submiting.value = false;
|
|
|
|
|
|
|
|
// 如果是竞赛,并且勾选了公布成绩详情的选项,则弹框提示
|
|
|
|
param.competitionId &&
|
|
|
|
param.resultsDetails == 0 &&
|
|
|
|
ElMessageBox.alert(`提交成功${param.resultAnnouncementTime != 0 ? ',成绩将在' + param.resultAnnouncementTime + '小时后发布,请去参赛信息模块查看' : ''}`, '提示', {
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
callback: (action: Action) => {
|
|
|
|
ElMessage({
|
|
|
|
type: 'info',
|
|
|
|
message: `action: ${action}`,
|
|
|
|
});
|
|
|
|
},
|
|
|
|
});
|
|
|
|
})
|
|
|
|
.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) {
|
|
|
|
countVal.value = isPrac ? 0 : (new Date(param.endTime).getTime() - Date.now()) / 1000; // 如果是考核,取考核的结束时间减去当前时间去做倒计时,练习则直接给0做计时
|
|
|
|
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();
|
|
|
|
};
|
|
|
|
onMounted(() => {
|
|
|
|
getLevel.value = inject('getLevel'); // 关卡页面获取关卡方法
|
|
|
|
per.value = param.assessmentId ? 1 : param.competitionId ? 2 : 0;
|
|
|
|
if (param.assessmentId) {
|
|
|
|
getAssList();
|
|
|
|
} else {
|
|
|
|
param.cid && getList();
|
|
|
|
if (param.competitionId) {
|
|
|
|
clearInterval(statusTimer);
|
|
|
|
statusTimer.value = setInterval((_) => {
|
|
|
|
getCompetitionStatus();
|
|
|
|
}, 1000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
</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>
|