|
|
|
|
@ -1,349 +1,20 @@
|
|
|
|
|
<!--
|
|
|
|
|
* @Author: qinzhenpen qzp1807@126.com
|
|
|
|
|
* @Date: 2025-04-24 15:46:26
|
|
|
|
|
* @LastEditors: qinzhenpen qzp1807@126.com
|
|
|
|
|
* @LastEditTime: 2025-05-20 18:32:30
|
|
|
|
|
* @FilePath: \e-commerce-internet\src\views\test-report\index.vue
|
|
|
|
|
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
|
|
|
|
-->
|
|
|
|
|
<template>
|
|
|
|
|
<div class="test-report app-container">
|
|
|
|
|
<div class="report-title">
|
|
|
|
|
<h2><Strong>实验报告</Strong></h2>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="report-centent">
|
|
|
|
|
<el-row class="ptb10">
|
|
|
|
|
<el-col :span="15">
|
|
|
|
|
<el-row>
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<label>姓名:</label>
|
|
|
|
|
<span>{{ userInfo.name }}</span>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<label>学号:</label>
|
|
|
|
|
<span>{{ userInfo.username }}</span>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="11">
|
|
|
|
|
<label>班级:</label>
|
|
|
|
|
<span>{{ userInfo.className }}</span>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
<el-card class="score-card" style="margin-bottom: 20px">
|
|
|
|
|
<template #header>
|
|
|
|
|
<div class="score-header">
|
|
|
|
|
<span class="total-score">实时系统总成绩:{{ totalScore }}分</span>
|
|
|
|
|
<span class="class-rank ml20">班级排名:{{ rank || 0 }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<el-table :data="[scores]" border style="width: 100%" header-row-class-name="score-table-header">
|
|
|
|
|
<el-table-column v-for="item in scoreItems" :key="item.prop" :prop="item.prop" :label="`${item.label}(${item.weight})`" align="center">
|
|
|
|
|
<template #default="{ row }">{{ row[item.prop] }}分</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
<div class="evaluation">
|
|
|
|
|
<p>指导建议与评价:{{ scoreresult }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<h2>实验报告</h2>
|
|
|
|
|
<el-row class="student-info"><el-col :span="6">姓名:{{ userInfo.name }}</el-col><el-col :span="6">学号:{{ userInfo.username }}</el-col><el-col :span="8">班级:{{ report.teachingClassName || userInfo.className }}</el-col></el-row>
|
|
|
|
|
<el-card><template #header><span>实训总成绩:{{ report.totalScore || 0 }} 分</span><span class="rank">班级排名:{{ report.rank || '-' }}</span></template>
|
|
|
|
|
<el-table :data="report.modules || []" border><el-table-column prop="moduleName" label="实训模块"/><el-table-column label="权重"><template #default="{row}">{{ percent(row.weight) }}</template></el-table-column><el-table-column prop="score" label="模块成绩"/><el-table-column label="任务完成"><template #default="{row}">{{ row.completedTaskCount }} / {{ row.totalTaskCount }}</template></el-table-column></el-table>
|
|
|
|
|
</el-card>
|
|
|
|
|
<el-card class="score-card" style="margin-bottom: 20px">
|
|
|
|
|
<template #header>
|
|
|
|
|
<div class="score-header">
|
|
|
|
|
<span class="total-score">实训系统访问次数:{{ totalvisitCount }}次</span>
|
|
|
|
|
<span class="class-rank ml20">班级排名:{{ rankInfo.visitCountRank || 0 }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<el-table :data="[visitCount]" border style="width: 100%" header-row-class-name="score-table-header">
|
|
|
|
|
<el-table-column v-for="item in VisitCountItems" :key="item.prop" :prop="item.prop" :label="item.label" align="center">
|
|
|
|
|
<template #default="{ row }">{{ row[item.prop] }}次</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
<div class="evaluation">
|
|
|
|
|
<p>指导建议与评价:{{ visitCountresult || "当前暂无有效数据,无法生成评价建议" }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<el-card class="tasks"><template #header>实训任务明细</template>
|
|
|
|
|
<el-table :data="report.tasks || []" border><el-table-column prop="taskName" label="任务名称"/><el-table-column prop="moduleName" label="所属模块"/><el-table-column prop="progressStatus" label="完成状态"/><el-table-column prop="aiAssessmentScore" label="AI 评分"><template #default="{row}">{{ row.aiAssessmentScore ?? '-' }}</template></el-table-column></el-table>
|
|
|
|
|
</el-card>
|
|
|
|
|
<el-card class="score-card" style="margin-bottom: 20px">
|
|
|
|
|
<template #header>
|
|
|
|
|
<div class="score-header">
|
|
|
|
|
<span class="total-score">实训系统学习时长:{{ totalTime }}</span>
|
|
|
|
|
<span class="class-rank ml20">班级排名:{{ rankInfo.studyTimeRank || 0 }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<el-table :data="[times]" border style="width: 100%" header-row-class-name="score-table-header">
|
|
|
|
|
<el-table-column v-for="item in timeItems" :key="item.prop" :prop="item.prop" :label="item.label" align="center">
|
|
|
|
|
<template #default="{ row }">{{ row[item.prop] }}分钟</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
<div class="evaluation">
|
|
|
|
|
<p>指导建议与评价:{{ timeresult || "当前暂无有效数据,无法生成评价建议" }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</el-card>
|
|
|
|
|
<el-table
|
|
|
|
|
:data="tableData"
|
|
|
|
|
border
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
:header-cell-style="{
|
|
|
|
|
color: '#fff',
|
|
|
|
|
fontSize: '14px',
|
|
|
|
|
backgroundColor: '#02a7f0!important',
|
|
|
|
|
}"
|
|
|
|
|
>
|
|
|
|
|
<el-table-column prop="module" label="实训任务" align="center" />
|
|
|
|
|
<el-table-column prop="score" label="任务状态" align="center">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<span>已完成</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column prop="score" label="任务成绩" align="center" />
|
|
|
|
|
</el-table>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<script setup>
|
|
|
|
|
import * as indexApi from "@/api/index";
|
|
|
|
|
import useUserStore from "@/store/modules/user";
|
|
|
|
|
import { onMounted, ref } from "vue";
|
|
|
|
|
const userStore = useUserStore();
|
|
|
|
|
const userInfo = userStore.userInfo;
|
|
|
|
|
const totalScore = ref(0); // 实时系统总成绩
|
|
|
|
|
const rank = ref(0); // 班级排名
|
|
|
|
|
const scoreItems = ref([
|
|
|
|
|
{ prop: "market", label: "市场需求挖掘", weight: "25%", key: "oneScore", key1: "oneWeight" },
|
|
|
|
|
{ prop: "design", label: "产品规划", weight: "20%", key: "twoScore", key1: "twoWeight" },
|
|
|
|
|
{ prop: "test", label: "产品投放测试", weight: "20%", key: "threeScore", key1: "threeWeight" },
|
|
|
|
|
{ prop: "supply", label: "供应渠道管理", weight: "20%", key: "fourScore", key1: "fourWeight" },
|
|
|
|
|
{ prop: "evaluate", label: "产品评估与考核", weight: "15%", key: "fiveScore", key1: "fiveWeight" },
|
|
|
|
|
]);
|
|
|
|
|
const VisitCountItems = ref([
|
|
|
|
|
{ prop: "market", label: "市场需求挖掘" },
|
|
|
|
|
{ prop: "design", label: "产品规划" },
|
|
|
|
|
{ prop: "test", label: "产品投放测试" },
|
|
|
|
|
{ prop: "supply", label: "供应渠道管理" },
|
|
|
|
|
{ prop: "evaluate", label: "产品评估与考核" },
|
|
|
|
|
]);
|
|
|
|
|
const timeItems = ref([
|
|
|
|
|
{ prop: "market", label: "市场需求挖掘", key: "marketResearchTime" },
|
|
|
|
|
{ prop: "design", label: "产品规划", key: "productPlanningTime" },
|
|
|
|
|
{ prop: "test", label: "产品投放测试", key: "productLaunchTestTime" },
|
|
|
|
|
{ prop: "supply", label: "供应渠道管理", key: "supplyChannelTime" },
|
|
|
|
|
{ prop: "evaluate", label: "产品评估与考核", key: "productEvaluationTime" },
|
|
|
|
|
]);
|
|
|
|
|
const scores = ref({
|
|
|
|
|
market: 0,
|
|
|
|
|
design: 0,
|
|
|
|
|
test: 0,
|
|
|
|
|
supply: 0,
|
|
|
|
|
evaluate: 0,
|
|
|
|
|
});
|
|
|
|
|
const visitCount = ref({
|
|
|
|
|
market: 0,
|
|
|
|
|
design: 0,
|
|
|
|
|
test: 0,
|
|
|
|
|
supply: 0,
|
|
|
|
|
evaluate: 0,
|
|
|
|
|
});
|
|
|
|
|
const times = ref({
|
|
|
|
|
market: 0,
|
|
|
|
|
design: 0,
|
|
|
|
|
test: 0,
|
|
|
|
|
supply: 0,
|
|
|
|
|
evaluate: 0,
|
|
|
|
|
});
|
|
|
|
|
// 时间总和
|
|
|
|
|
const totalTime = computed(() => {
|
|
|
|
|
let time = 0;
|
|
|
|
|
for (let key in times.value) {
|
|
|
|
|
time += times.value[key];
|
|
|
|
|
}
|
|
|
|
|
return time;
|
|
|
|
|
});
|
|
|
|
|
// 时间总和
|
|
|
|
|
const totalvisitCount = computed(() => {
|
|
|
|
|
let time = 0;
|
|
|
|
|
for (let key in visitCount.value) {
|
|
|
|
|
time += visitCount.value[key];
|
|
|
|
|
}
|
|
|
|
|
return time;
|
|
|
|
|
});
|
|
|
|
|
const rankInfo = ref({});
|
|
|
|
|
const tableData = ref([]);
|
|
|
|
|
const scoreresult = ref("");
|
|
|
|
|
const visitCountresult = ref("");
|
|
|
|
|
const timeresult = ref("");
|
|
|
|
|
// 查询实训详情
|
|
|
|
|
const getTrainDetail = () => {
|
|
|
|
|
indexApi.getStuModuleDetail({ userId: userInfo.userId }).then((res) => {
|
|
|
|
|
if (!res.data) return;
|
|
|
|
|
tableData.value = res.data;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
// 查询学生权重
|
|
|
|
|
const getwight = () => {
|
|
|
|
|
indexApi
|
|
|
|
|
.getStuWeightScore({
|
|
|
|
|
userId: userStore.userInfo.userId,
|
|
|
|
|
classId: userStore.userInfo.classId,
|
|
|
|
|
schoolId: userStore.userInfo.schoolId,
|
|
|
|
|
})
|
|
|
|
|
.then((res) => {
|
|
|
|
|
if (!res.data) return;
|
|
|
|
|
rank.value = res.data?.stuRank;
|
|
|
|
|
scoreItems.value.forEach((item) => {
|
|
|
|
|
item.weight = res?.data[item.key1] * 100 + "%";
|
|
|
|
|
scores.value[item.prop] = res?.data[item.key];
|
|
|
|
|
});
|
|
|
|
|
for (const key in scores.value) {
|
|
|
|
|
totalScore.value += scores.value[key];
|
|
|
|
|
}
|
|
|
|
|
totalScore.value = totalScore.value.toFixed(2);
|
|
|
|
|
scoreresult.value = generateEvaluation(scores.value, scoreItems.value, `第${res.data.stuRank}名`);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
// 查询访问次数
|
|
|
|
|
const getAccessCount = () => {
|
|
|
|
|
indexApi
|
|
|
|
|
.getUserVisitCount({
|
|
|
|
|
userId: userStore.userInfo.userId,
|
|
|
|
|
schoolId: userStore.userInfo.schoolId,
|
|
|
|
|
})
|
|
|
|
|
.then((res) => {
|
|
|
|
|
if (!res.data) return;
|
|
|
|
|
res?.data.forEach((item) => {
|
|
|
|
|
let items = VisitCountItems.value.find((i) => i.label == item.project);
|
|
|
|
|
if (item) {
|
|
|
|
|
visitCount.value[items.prop] = item.visitCount;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
visitCountresult.value = generateVisitAnalysis(visitCount.value, VisitCountItems.value);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
// 查看学习时间
|
|
|
|
|
const getStudyTime = () => {
|
|
|
|
|
indexApi
|
|
|
|
|
.getStudyTime({
|
|
|
|
|
userId: userStore.userInfo.userId,
|
|
|
|
|
})
|
|
|
|
|
.then((res) => {
|
|
|
|
|
if (!res.data) return;
|
|
|
|
|
timeItems.value.forEach((item) => {
|
|
|
|
|
times.value[item.prop] = res?.data[item.key];
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
// 查询用户排名
|
|
|
|
|
const getRank = () => {
|
|
|
|
|
indexApi
|
|
|
|
|
.getUserRank({
|
|
|
|
|
userId: userStore.userInfo.userId,
|
|
|
|
|
})
|
|
|
|
|
.then((res) => {
|
|
|
|
|
if (!res.data) return;
|
|
|
|
|
rankInfo.value = res?.data;
|
|
|
|
|
timeresult.value = generateTimeAnalysis(times.value, timeItems.value, scores.value, scoreItems.value, `第${rankInfo.value.studyTimeRank}名`);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
function generateEvaluation(scores, scoreItems, rankText = "第1名") {
|
|
|
|
|
const entries = scoreItems.map((item) => ({
|
|
|
|
|
label: item.label,
|
|
|
|
|
score: scores[item.prop],
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const allZero = entries.every((e) => !e.score || e.score === 0);
|
|
|
|
|
if (allZero) return "当前暂无有效评分数据,无法生成评价建议。";
|
|
|
|
|
|
|
|
|
|
const maxScore = Math.max(...entries.map((e) => e.score));
|
|
|
|
|
const maxItems = entries.filter((e) => e.score === maxScore);
|
|
|
|
|
const minScore = Math.min(...entries.map((e) => e.score));
|
|
|
|
|
const minItems = entries.filter((e) => e.score === minScore);
|
|
|
|
|
const maxText = maxItems.map((e) => `**${e.label}(${e.score}分)**`).join("、");
|
|
|
|
|
const minText = minItems.map((e) => `**${e.label}(${e.score}分)**`).join("、");
|
|
|
|
|
return `该生总成绩在班级中排名为${rankText}。其中,${maxText}表现突出,体现了良好的能力。然而,在${minText}方面得分相对较低,建议加强相关方面的学习,以提升综合能力。`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function generateVisitAnalysis(visitCounts, visitItems) {
|
|
|
|
|
const entries = visitItems.map((item) => ({
|
|
|
|
|
label: item.label,
|
|
|
|
|
count: visitCounts[item.prop],
|
|
|
|
|
}));
|
|
|
|
|
const allZero = entries.every((e) => !e.count || e.count === 0);
|
|
|
|
|
if (allZero) return "当前暂无任何模块被访问,建议尽快引导用户进入系统进行操作。";
|
|
|
|
|
entries.sort((a, b) => b.count - a.count);
|
|
|
|
|
const maxCount = entries[0].count;
|
|
|
|
|
const minCount = entries[entries.length - 1].count;
|
|
|
|
|
const mostVisited = entries.filter((e) => e.count === maxCount);
|
|
|
|
|
const leastVisited = entries.filter((e) => e.count === minCount);
|
|
|
|
|
const mostText = mostVisited.map((e) => `“${e.label}”(${e.count}次)`).join("、");
|
|
|
|
|
const leastText = leastVisited.map((e) => `“${e.label}”(${e.count}次)`).join("、");
|
|
|
|
|
let result = `模块使用情况分析如下:其中,${mostText}访问频率最高,说明用户对此类内容关注较多。`;
|
|
|
|
|
if (minCount !== maxCount) {
|
|
|
|
|
result += ` 相对而言,${leastText}访问次数较少,建议关注其使用引导或内容优化。`;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
function generateTimeAnalysis(times, timeItems, scores, scoreItems, rankText = "第1名") {
|
|
|
|
|
const timeEntries = timeItems.map((item) => ({
|
|
|
|
|
prop: item.prop,
|
|
|
|
|
label: item.label,
|
|
|
|
|
time: times[item.prop] || 0,
|
|
|
|
|
score: scores[item.prop] ?? null,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const allZero = timeEntries.every((e) => !e.time || e.time === 0);
|
|
|
|
|
if (allZero) return "当前暂无有效学习时长数据,建议尽快进入系统进行学习。";
|
|
|
|
|
|
|
|
|
|
const maxTime = Math.max(...timeEntries.map((e) => e.time));
|
|
|
|
|
const maxTimeItems = timeEntries.filter((e) => e.time === maxTime);
|
|
|
|
|
|
|
|
|
|
const resultParts = [`该生实训系统学习时长在班级排名为${rankText}。`];
|
|
|
|
|
const timeText = maxTimeItems.map((e) => `**${e.label}**学习时长最长`).join(",");
|
|
|
|
|
resultParts.push(`其中,${timeText}。`);
|
|
|
|
|
|
|
|
|
|
maxTimeItems.forEach((item) => {
|
|
|
|
|
if (item.score !== null && item.score < 10) {
|
|
|
|
|
resultParts.push(`${item.label}的成绩与该模块的学习时长不匹配,建议优化学习策略,提高学习效率。`);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return resultParts.join("");
|
|
|
|
|
}
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
getTrainDetail();
|
|
|
|
|
getwight();
|
|
|
|
|
getAccessCount();
|
|
|
|
|
getStudyTime();
|
|
|
|
|
getRank();
|
|
|
|
|
});
|
|
|
|
|
import { getCurrentExperimentReport } from '@/api/index';
|
|
|
|
|
import useUserStore from '@/store/modules/user';
|
|
|
|
|
const userInfo=useUserStore().userInfo; const report=ref({modules:[],tasks:[]});
|
|
|
|
|
const percent=(weight)=>weight==null?'-':`${Number(weight)*100}%`;
|
|
|
|
|
onMounted(async()=>{const res=await getCurrentExperimentReport(); report.value=res?.data||{modules:[],tasks:[]};});
|
|
|
|
|
</script>
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.test-report {
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
.report-title {
|
|
|
|
|
margin: 2rem 0;
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
.report-centent {
|
|
|
|
|
padding: 0px 10rem;
|
|
|
|
|
.el-row {
|
|
|
|
|
.el-col {
|
|
|
|
|
label {
|
|
|
|
|
width: 100px !important;
|
|
|
|
|
margin-right: 10px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
:deep(.score-card) {
|
|
|
|
|
box-shadow: none !important;
|
|
|
|
|
border-color: #02a7f0 !important;
|
|
|
|
|
.el-card__header {
|
|
|
|
|
background: #02a7f0 !important;
|
|
|
|
|
}
|
|
|
|
|
.score-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
padding-bottom: 5px;
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
<style lang="scss" scoped>.student-info{margin:20px 0}.rank{margin-left:24px}.tasks{margin-top:20px}</style>
|
|
|
|
|
|