金融产品设计及数字化营销沙盘
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.

738 lines
24 KiB

2 years ago
<template>
2 years ago
<div :class="['panel', { active: visible }]"
id="panel">
<el-container class="scrollbar"
id="container"
v-show="visible">
<el-header id="header">
<div class="panel-header"
id="panelHeader">
<div class="project">
2 years ago
<div class="inline-flex items-center">
2 years ago
<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="projectId"
2 years ago
placeholder="请选择"
class="select"
2 years ago
:disabled="per != 0"
2 years ago
@change="selectProject">
2 years ago
<el-option v-for="(item, i) in projectList"
2 years ago
:key="item.projectId"
2 years ago
:label="i + 1 + '. ' + item.projectName"
2 years ago
:value="item.projectId"></el-option>
</el-select>
</div>
2 years ago
<div class="item">
<div class="count">
实训{{ text }}时间 <span>{{ day }}</span> <span>{{ hour }}</span>小时 <span>{{ minutes }}</span> <span>{{ seconds }}</span>
2 years ago
</div>
</div>
2 years ago
<div class="item">
2 years ago
<div>
总得分
2 years ago
<span class="total-score">{{ grade }}</span>
2 years ago
</div>
</div>
<div>
<el-button @click="toReport"
2 years ago
v-if="isSubmit">查看实验报告</el-button>
<el-button class="reload"
2 years ago
@click="reload"
2 years ago
v-show="per == 0">重新开始</el-button>
<el-button type="primary"
class="submit btn"
@click="confirmSubmit"
:disabled="isSubmit || !projectList.length">提交</el-button>
2 years ago
</div>
</div>
</el-header>
2 years ago
<el-container id="infoContainer">
<el-aside id="aside"
width="30%">
2 years ago
<div class="aside-header">
2 years ago
<div class="p-title">
2 years ago
<i class="el-icon-s-order"></i>
<p>实验目标</p>
</div>
2 years ago
<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" />
2 years ago
</div>
</div>
2 years ago
<div class="aside-footer">
2 years ago
<div class="p-title">
2 years ago
<i class="el-icon-s-management"></i>
<p>实验任务</p>
</div>
<div>
<el-row>
<el-col :span="24">
2 years ago
<el-card shadow="never"
:border="false">
<el-table class="task-table"
:data="taskList"
:stripe="true">
2 years ago
<el-table-column type="index"></el-table-column>
<el-table-column prop="name"
label="判分点"
2 years ago
align="center"></el-table-column>
2 years ago
<el-table-column prop="score"
label="分值"
width="60"
align="center"></el-table-column>
2 years ago
<template v-if="!competitionId">
<el-table-column label="结果"
width="60"
align="center">
<template v-slot="scope">
<template v-if="isSubmit">
<template v-if="!competitionId">
<i v-if="scope.row.finishedResult"
class="el-icon-check right"></i>
<i v-else
class="el-icon-close wrong"></i>
</template>
<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">{{ competitionId ? '-' : scope.row.examScore }}</template>
</template>
</el-table-column>
</template>
2 years ago
</el-table>
</el-card>
</el-col>
</el-row>
</div>
</div>
</el-aside>
2 years ago
<el-main id="main">
<el-tabs class="info-tab"
v-model="pannelTab"
2 years ago
type="card">
2 years ago
<el-tab-pane label="项目背景"
2 years ago
name="first">
2 years ago
<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" />
2 years ago
</el-tab-pane>
<el-tab-pane label="实验要求"
name="second">
2 years ago
<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>
2 years ago
</template>
2 years ago
<div v-if="item.experimentalRequirementsType == 0 || !item.experimentalRequirementsType"
class="ql-editor"
2 years ago
v-html="item.experimentalRequirements"></div>
2 years ago
<mavon-editor v-else
class="md"
v-model="item.experimentalRequirements"
:ishljs="true"
:subfield="false"
:editable="false"
:toolbarsFlag="false"
:boxShadowStyle="none" />
2 years ago
</el-collapse-item>
</el-collapse>
</el-tab-pane>
<el-tab-pane label="实验提示"
2 years ago
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" />
2 years ago
</el-tab-pane>
</el-tabs>
</el-main>
</el-container>
</el-container>
2 years ago
2 years ago
<div class="toggle-panel absolute w-[40px] h-[175px] bg-[url('@/assets/images/panel/right.png')] bg-[length:100%_100%] bg-no-repeat cursor-pointer"
:class="{ active: visible }"
2 years ago
@click="visible = !visible">
2 years ago
</div>
</div>
</template>
2 years ago
<script setup lang="ts">
import { ref, onMounted, toRefs } from 'vue';
import { pageStuAssessment, getProjectBySystemId, getProjectDetail, getDetailById, getCompetition } from '@/api/system';
import { useRouter, useRoute } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
import Cookie from 'js-cookie';
import util from '@/libs/util';
import Setting from '@/setting';
// import { mavonEditor } from 'mavon-editor'
// import 'mavon-editor/dist/css/index.css'
const router = useRouter();
const route = useRoute();
2 years ago
const projectId = ref<string | number>(+route.query.projectId);
const { systemId, classId, cid, assessmentId, competitionId, stageId, teamId, mallId } = toRefs(route.query);
2 years ago
const curSystemId = ref<number>(1);
2 years ago
const per = ref<number>(0); // 项目权限(0、练习 1、考核 2、竞赛)
2 years ago
const isSubmit = ref<boolean>(Cookie.get('st-isSubmit') === 'true'); // 是否提交的标识
const entryTime = ref<any>(new Date());
const visible = 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<string>('');
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 isSelected = ref<boolean>(false); // 是否选择过项目的标识,选择了会置为true
const statusTimer = ref<any>(null);
const reportId = ref<string | number>('');
let countVal = <any>'';
onMounted(() => {
2 years ago
per.value = assessmentId?.value ? 1 : competitionId?.value ? 2 : 0;
if (assessmentId.value) {
getAssList();
} else {
getList();
if (competitionId?.value) {
clearInterval(statusTimer);
statusTimer.value = setInterval((_) => {
getCompetitionStatus();
}, 1000);
}
2 years ago
}
});
// 获取项目列表
2 years ago
const getList = async () => {
const data = {
systemId: systemId.value,
cId: cid?.value ?? '',
mallId: mallId?.value,
permissions: per.value,
};
const { projects } = await getProjectBySystemId(data);
projectList.value = projects;
console.log('🚀 ~ file: index.vue:280 ~ getList ~ projectList.value:', projectList.value);
if (!per.value && !projectId.value) projectId.value = projects[0]?.projectId ?? 0; // 默认取第一个项目
getProDetail();
2 years ago
};
2 years ago
2 years ago
// 获取项目详情
const getProDetail = () => {
return new Promise(async (resolve, reject) => {
const res = await getProjectDetail({
projectId: projectId.value,
stuAssessent: 1,
});
const points = res.projectJudgmentVos;
const project = res.projectManage;
// 考核/竞赛
if (per.value) {
projectList.value = [
{
projectId: projectId.value,
projectName: project.projectName,
},
];
}
curReq.value = points.map((e) => e.judgmentId); // 实验要求默认全部展开,通过judgmentId来选中item
points.value = points;
taskList.value = points; // 实验任务
judgmentId.value = points[0].judgmentId; // 默认取第一个判分点
pd.value = project;
hintOpen.value = project.founder ? !project.hintOpenBySchool : !project.hintOpen; // 0显示,1不显示,系统跟老师的禁用字段不一样
const isPrac = per.value === 0; // 是否是练习
text.value = isPrac ? '已用' : '剩余';
// 竞赛不需要
if (!competitionId.value) {
countVal = isPrac ? 0 : (new Date(endTime.value).getTime() - Date.now()) / 1000; // 如果是考核,取考核的结束时间减去当前时间去做倒计时,练习则直接给0做计时
startCount();
}
resolve();
});
};
2 years ago
2 years ago
// 设置isSubmit
// setSubmit(status) {
// this.isSubmit = status;
// newmain.$emit('isSubmit', status);
// Cookie.set('admin-isSubmit', status);
// },
// 获取考核列表来查询该考核是否已经考过
const getAssList = async () => {
const { list } = await pageStuAssessment({
pageNum: 1,
pageSize: 10000,
});
let done = false;
// 匹配到该考核,并且已经提交过(有reportId则说明有实验记录),并且classId跟当前用户的classId相同,就提示提交了考核然后返回上一页
if (list.find((e) => e.assessmentId == assessmentId.value && e.reportId && e.classId == classId.value)) {
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(assessmentId.value);
const done = data ? data.status === 2 : false; // 状态(0、待开始 1、进行中 2、已结束)
// 如果考核已结束,则清除查询考核状态的定时器,并且自动提交
if (done) {
clearInterval(statusTimer.value);
// this.$alert('考核时间已到,系统已自动交卷', '提示', {
// confirmButtonText: '确定',
// });
submit();
}
}
};
2 years ago
2 years ago
// 定时查询竞赛状态(查到竞赛如果结束后,直接提交竞赛)
const getCompetitionStatus = async () => {
// 未提交才需要查询状态
if (!isSubmit.value) {
const { competition } = await getCompetition(competitionId.value);
const stages = competition.competitionStage;
if (stages) {
const stage = stages.find((e) => e.stageId == stageId.value);
const endTime = new Date(stage.endTime).getTime();
const now = Date.now();
// 如果已经结束
if (now >= new Date(stage.endTime)) {
clearInterval(statusTimer.value);
// this.$alert('竞赛时间已到,系统已自动交卷', '提示', {
// confirmButtonText: '确定',
// });
submit();
2 years ago
} else {
2 years ago
// 没结束,则显示倒计时(竞赛才需要通过定时调接口来显示倒计时,因为中台可以修改结束时间,所以需要时刻获取最新的结束时间)
counter((endTime - now) / 1000);
2 years ago
}
}
}
};
2 years ago
// 项目选择回调
const selectProject = () => {
isSelected.value = true;
getProDetail();
// setSubmit(false);
countVal.value = 0;
grade.value = '00';
pannelTab.value = 'first';
clearReport();
};
// 重新开始
const reload = () => {
reloadCount();
grade.value = '00';
// this.setSubmit(false);
startCount();
};
// 提交
const submit = () => {
ElMessageBox.confirm('此操作将视为结束考试,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {})
.catch(() => {});
// if (isSubmit.value) return false;
// const date = new Date();
// const timeSum = Math.ceil((date.getTime() - entryTime.getTime()) / 60000); // 计算实验用时(分钟),向上取整
// const submitTime = util.formatDate('yyyy-MM-dd hh:mm:ss', date);
// const { projectId } = this;
// const pro = this.projectList.find((e) => e.projectId == projectId);
// const projectName = pro ? pro.projectName : ''; // 获取项目名称
// this.reloadCount();
// // 判分点参数
// const attributesReqList = [];
// pointList.map((e) => {
// attributesReqList.push({
// codeId: e.codeId,
// bcId: e.judgmentId,
// isSubmit: e.codeId ? 1 : 0,
// answer: e.answer,
// retResult: e.retResult,
// });
// });
// const data = {
// classId: this.classId ? this.classId : '',
// className: this.className ? this.className : '',
2 years ago
// curriculumId: this.cid,
2 years ago
// startTime: this.per ? this.startTime : util.formatDate('yyyy-MM-dd hh:mm:ss', entryTime), // 开始时间(考核:直接从职站取考核的开始时间;练习:取页面进入的时间)
// endTime: this.per ? this.endTime : submitTime, // 结束时间(考核:直接从职站取考核的结束时间;练习:取提交时间)
// submitTime, // 提交时间,即当前时间(这3个时间都是传完整的日期时间格式)
// timeSum,
// projectId,
// projectName,
// assessmentId: this.assessmentId ? this.assessmentId : '',
// totalScore: 100, // 判分点总分固定为100
// systemId: this.curSystemId,
// purpose: this.pd.experimentTarget, // 实验目的
// attributesReqList,
// competitionId: this.competitionId,
// stageId: this.stageId,
// teamId: this.teamId,
// mallId: this.mallId,
// };
// this.$post(this.api.submit, data)
// .then(({ retInfo, reportId }) => {
// localStorage.removeItem('codeCache');
// this.setSubmit(true);
// clearInterval(this.statusTimer);
// this.reportVisible = false;
// const list = retInfo;
// const { taskList } = this;
// let score = 0;
// // 给判分列表添加分数和运行结果
// taskList.map((e) => {
// const item = list.find((n) => n.judgmentPointsId === e.judgmentId);
// if (item) {
// e.examScore = item.score;
// e.finishedResult = item.finishedResult; // 1:正确,2:错误
// } else {
// e.examScore = 0;
// }
// score += e.examScore; // 计算总分
// });
// this.grade = util.handleZero(score); // 前置加0(竞赛不显示分数)
// this.reportId = reportId;
// this.$store.commit('setReportId', reportId);
// this.$store.commit('setTaskList', taskList);
// this.editReport(reportId);
// // 如果是竞赛,并且勾选了公布成绩详情的选项,则弹框提示
// this.competitionId &&
// this.resultsDetails == 0 &&
// this.$alert(`提交成功${this.resultAnnouncementTime != 0 ? `,成绩将在${this.resultAnnouncementTime}小时后发布,请去参赛信息模块查看` : ''}`, '提示', {
// confirmButtonText: '确定',
// callback: (action) => {
// this.$parent.back();
// },
// });
// })
// .catch((err) => {});
};
// 倒计时
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);
};
2 years ago
</script>
<style lang="scss" scoped>
2 years ago
.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;
2 years ago
}
2 years ago
.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 {
2 years ago
@apply p-[10px] text-sm text-center rounded-[6px] bg-[#e0e0e0];
2 years ago
}
.submit {
width: 106px;
font-size: 16px;
}
.reload {
color: #d0d0d0;
font-size: 16px;
background-color: #202020;
}
2 years ago
}
2 years ago
:deep(.des) {
2 years ago
font-size: 16px;
font-family: 'Microsoft YaHei';
img {
max-width: 100%;
}
2 years ago
}
2 years ago
:deep(.el-collapse-item__wrap) {
2 years ago
border-bottom: none;
}
2 years ago
:deep(.el-collapse-item__header) {
2 years ago
border-bottom: none;
2 years ago
}
2 years ago
:deep(.el-icon-s-ticket:before) {
2 years ago
padding: 5px;
font-size: 16px;
}
2 years ago
:deep(.el-collapse-item__arrow) {
2 years ago
margin: 0 5px 0 0;
}
2 years ago
:deep(.info-tab.el-tabs--card) {
2 years ago
.el-tabs__item {
font-size: 16px;
}
.el-tabs__item.is-active {
2 years ago
@apply text-white bg-[#568df2];
2 years ago
}
.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(60vh - 70px);
overflow: auto;
}
}
2 years ago
:deep(.el-collapse) {
2 years ago
border-bottom: none;
border-top: none;
}
.el-aside {
margin-bottom: 10px;
color: #333;
background-color: #fff;
}
2 years ago
.el-aside :deep([class*=' el-icon-']),
2 years ago
[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 {
2 years ago
@apply flex justify-center h-[40px] bg-[url('@/assets/images/panel/header.png')] bg-[length:100%_100%] bg-no-repeat;
2 years ago
p {
padding-left: 10px;
line-height: 40px;
font-size: 16px;
color: #fff;
}
i {
color: #fff;
}
}
2 years ago
:deep(.el-card__body) {
2 years ago
padding: 0;
}
2 years ago
:deep(.task-table) {
2 years ago
font-size: 12px;
thead {
2 years ago
@apply text-white text-[10px];
}
th.el-table__cell {
@apply bg-[#badfff];
2 years ago
}
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;
}
2 years ago
:deep(.select) {
@apply flex-1;
2 years ago
.el-select__caret:before {
2 years ago
// content: '\e78f';
2 years ago
padding: 3px;
2 years ago
font-size: 16px;
2 years ago
color: #fff;
2 years ago
background-color: #568df2;
2 years ago
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;
}
2 years ago
}
.panel {
2 years ago
z-index: 200;
position: fixed;
2 years ago
top: 200px;
bottom: 20px;
left: 0;
width: 0;
height: 0;
.toggle-panel {
2 years ago
@apply top-[200px];
2 years ago
&.active {
2 years ago
@apply top-[35%] left-[100%] bg-[url('@/assets/images/panel/left.png')];
2 years ago
}
2 years ago
}
&.active {
position: fixed;
width: 85%;
height: 70%;
}
}
2 years ago
:deep(.el-container) {
2 years ago
height: 100%;
&.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;
}
2 years ago
}
2 years ago
</style>