学生实践
This commit is contained in:
parent
99e7e43e9d
commit
0562efd6e8
@ -74,6 +74,13 @@ export const xsKsccApi = async (params: any) => {
|
||||
export const xsKscjApi = async (params: any) => {
|
||||
return await get("/mobile/jz/kscj", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据考试场次ID和学生ID查询评价(班主任寄语)
|
||||
*/
|
||||
export const ksccPjFindByKsccAndXsApi = async (params: { ksccId: string; xsId: string }) => {
|
||||
return await get("/api/ksccPj/findByKsccAndXs", params);
|
||||
};
|
||||
/**
|
||||
* 家长接龙查询
|
||||
*/
|
||||
|
||||
@ -111,14 +111,15 @@
|
||||
</view>
|
||||
<view class="file-info">
|
||||
<text class="file-name">{{ file.originalName || file.name }}</text>
|
||||
<text class="file-size" v-if="file.size">{{ formatFileSize(file.size) }}</text>
|
||||
<text class="file-type">{{ file.extension?.toUpperCase() || 'FILE' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="file-actions">
|
||||
<view class="delete-btn" @click="removeFile(index)">
|
||||
<view class="delete-btn" @click.stop="removeFile(index)">
|
||||
<uni-icons type="close" size="16" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<view class="download-btn" @click.stop="downloadFileItem(index)">
|
||||
<uni-icons type="download" size="16" color="#fff"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -140,7 +141,8 @@ import { ref, computed, watch } from 'vue'
|
||||
import { imagUrl } from '@/utils'
|
||||
import {
|
||||
previewFile as previewFileUtil,
|
||||
previewVideo as previewVideoUtil
|
||||
previewVideo as previewVideoUtil,
|
||||
downloadFile as downloadFileUtil
|
||||
} from '@/utils/filePreview'
|
||||
|
||||
// 接口定义
|
||||
@ -736,6 +738,24 @@ const previewFile = (index: number) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 下载文件(与选项统计导出一致:H5 用 location.href,APP/小程序用 uni.downloadFile)
|
||||
const downloadFileItem = (index: number) => {
|
||||
const file = fileList.value[index]
|
||||
if (!file) return
|
||||
const pathOrUrl = (file as any).url ?? (file as any).filePath
|
||||
if (!pathOrUrl) {
|
||||
showToast({ title: '文件未上传完成,无法下载', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const fileUrl = /^https?:\/\//i.test(pathOrUrl) ? pathOrUrl : imagUrl(pathOrUrl)
|
||||
const baseName = file.originalName || file.name || '下载文件'
|
||||
const ext = file.extension || String(pathOrUrl).split('.').pop() || ''
|
||||
const fileName = ext && !baseName.includes('.') ? `${baseName}.${ext}` : baseName
|
||||
downloadFileUtil(fileUrl, fileName).catch(() => {
|
||||
showToast({ title: '下载失败', icon: 'none' })
|
||||
})
|
||||
}
|
||||
|
||||
// 移除文件
|
||||
const removeFile = (index: number) => {
|
||||
fileList.value.splice(index, 1)
|
||||
@ -1253,13 +1273,13 @@ defineExpose({
|
||||
border-radius: 8rpx;
|
||||
padding: 16rpx;
|
||||
border: 1rpx solid #e9ecef;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.file-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -1289,38 +1309,37 @@ defineExpose({
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
white-space: normal;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.file-size {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
display: block;
|
||||
margin-bottom: 2rpx;
|
||||
}
|
||||
|
||||
.file-type {
|
||||
font-size: 20rpx;
|
||||
color: #666;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 右侧操作区单独一列,与文件名不重叠 */
|
||||
.file-actions {
|
||||
position: absolute;
|
||||
top: 8rpx;
|
||||
right: 8rpx;
|
||||
flex-shrink: 0;
|
||||
margin-left: 24rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.file-actions .download-btn,
|
||||
.file-actions .delete-btn {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
background: rgba(255, 59, 48, 0.8);
|
||||
border-radius: 50%;
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.file-actions .download-btn {
|
||||
background: rgba(0, 122, 255, 0.9);
|
||||
}
|
||||
|
||||
.file-actions .delete-btn {
|
||||
background: rgba(255, 59, 48, 0.9);
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,34 +1,39 @@
|
||||
<template>
|
||||
<view class="grades-page flex flex-col">
|
||||
<!-- 顶部蓝色背景 -->
|
||||
<!-- 数据加载遮罩 -->
|
||||
<view v-if="pageLoading" class="page-loading-mask">
|
||||
<view class="page-loading-box">
|
||||
<view class="page-loading-spinner"></view>
|
||||
<text class="page-loading-text">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 顶部蓝色背景(与教师端一致) -->
|
||||
<view class="blue-header">
|
||||
<view class="header-content">
|
||||
<text class="title">{{ kscc.ksmc }}</text>
|
||||
<text class="subtitle">教学质量监测成绩报告</text>
|
||||
|
||||
<view class="color-bar"></view>
|
||||
|
||||
<text class="total-score">共{{ksccKmList.length}}科(满分{{ totalKmFs }}分)</text>
|
||||
|
||||
<text class="grade">{{ curKsdj.dj }}</text>
|
||||
|
||||
<view class="grade-info">
|
||||
<text class="grade-explanation" @click="showGradeInfo">等级说明</text>
|
||||
<view class="grade-box">
|
||||
<text class="grade-label">区域等级</text>
|
||||
<text class="grade-value" :style="{ color: curKsdj.djclr }">{{ curKsdj.djBx }}</text>
|
||||
<text class="exam-title">{{ kscc.ksmc || '考试场次' }}</text>
|
||||
<view class="student-info-section">
|
||||
<text class="student-name">{{ curXs.xm || curXs.name || '学生姓名' }}</text>
|
||||
<text class="student-class" v-if="curXs.njmc || curXs.bjmc">
|
||||
{{ [curXs.njmc, curXs.bjmc].filter(Boolean).join(' ') }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="score-info-section">
|
||||
<view class="score-left">
|
||||
<text class="total-score">共{{ ksccKmList.length }}科</text>
|
||||
<text class="full-score">满分{{ totalKmFs }}分</text>
|
||||
</view>
|
||||
<text class="grade">{{ curKsdj.dj || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 白色内容区 -->
|
||||
<view class="flex-1" style="background-color: #f6f6f6; position: relative">
|
||||
<view style="background-color: #f6f6f6;">
|
||||
<view class="content-area">
|
||||
<!-- 选项卡 -->
|
||||
<!-- 选项卡(去掉分数趋势) -->
|
||||
<view class="tabs">
|
||||
<view
|
||||
v-if="ksccKmList.length > 2"
|
||||
v-if="ksccKmList && ksccKmList.length > 2"
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === 'diagnosis' }"
|
||||
@click="switchTab('diagnosis')"
|
||||
@ -42,52 +47,38 @@
|
||||
>
|
||||
<text>学科成绩</text>
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === 'trend' }"
|
||||
@click="switchTab('trend')"
|
||||
>
|
||||
<text>分数趋势</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 学科成绩视图 -->
|
||||
<view class="score-view flex-1 po-re" v-if="activeTab === 'scores'">
|
||||
<view class="po-ab inset-0 px-15" style="overflow: auto" v-if="ksccKscjList.length">
|
||||
<!-- 学科成绩视图(与教师端布局一致,分数按 sfXsFs 显示) -->
|
||||
<view class="score-view" v-if="activeTab === 'scores'">
|
||||
<view v-if="ksccKscjList.length > 0" class="score-content">
|
||||
<view
|
||||
class="subject-item"
|
||||
v-for="(kscj, index) in ksccKscjList"
|
||||
:key="index"
|
||||
>
|
||||
<view class="subject-header">
|
||||
<text class="subject-name">{{ kscj.km.kmmc }}</text>
|
||||
<text v-if="kscc.sfXsFs && kscj.dj">(分数{{ kscj.ksfs }})</text>
|
||||
<text class="subject-name">{{ kscj.km?.kmmc || kscj.kmmc || '未知科目' }}</text>
|
||||
<text class="detail-btn" :style="{ color: kscj.djclr || '#909399' }" v-if="kscj.dj">{{ kscj.djBx }}</text>
|
||||
</view>
|
||||
<view class="subject-body">
|
||||
<text class="subject-grade">{{ kscj.dj }}</text>
|
||||
<text class="detail-btn" :style="{ color: kscj.djclr }" v-if="kscj.dj">{{ kscj.djBx }}</text>
|
||||
<text class="subject-grade" :style="{ color: kscj.djclr || '#303133' }">{{ kscj.dj || '-' }}</text>
|
||||
<view class="grade-info-wrapper">
|
||||
<text v-if="kscc.sfXsFs && kscj.ksfs" class="subject-score">{{ kscj.ksfs }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分数趋势视图 -->
|
||||
<view class="trend-view flex-1 po-re" v-if="activeTab === 'trend'">
|
||||
<view class="tab-placeholder">
|
||||
<!-- 选项卡 -->
|
||||
<BasicTabs
|
||||
ref="tabsRef" :list="xqKmmcList" bar-width="60px" scroll-count="4"
|
||||
:current="curKmIndex" @change="switchKm"
|
||||
/>
|
||||
<view v-else class="empty-scores">
|
||||
<text>暂无成绩数据</text>
|
||||
</view>
|
||||
<view style="flex: 1 0 1px; overflow: auto">
|
||||
<view class="radar-placeholder" id="chart-container1">
|
||||
<canvas
|
||||
style="width: 100%; height: 100%"
|
||||
canvas-id="trendCanvas"
|
||||
id="trendCanvas"
|
||||
class="charts"
|
||||
/>
|
||||
<!-- 班主任寄语(家长端只读展示) -->
|
||||
<view class="comment-section">
|
||||
<view class="comment-header">
|
||||
<text class="comment-title">班主任寄语</text>
|
||||
</view>
|
||||
<view class="comment-text-wrap">
|
||||
<text class="comment-text">{{ pjBzr || '暂无寄语' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -103,57 +94,9 @@
|
||||
class="charts"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 诊断评语 -->
|
||||
<!-- <view class="diagnosis-comment">
|
||||
<text class="comment-title">尊敬的家长,您好</text>
|
||||
<text class="comment-text"
|
||||
>向您祝贺,朱信权同学本次考试成绩位于较优秀之列!</text
|
||||
>
|
||||
<text class="comment-text"
|
||||
>这次考试中道德与法治学科表现最优秀,继续保持学科优势!相对来说英语学科表现最弱,不过也有27道题的答题表现超出同层次的平均。</text
|
||||
>
|
||||
</view> -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 等级说明弹窗 -->
|
||||
<u-popup
|
||||
:show="gradeInfoPopupVisible"
|
||||
mode="center"
|
||||
round="10"
|
||||
:closeable="true"
|
||||
@close="hideGradeInfo"
|
||||
>
|
||||
<view class="grade-info-popup">
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">等级说明</text>
|
||||
<u-icon
|
||||
name="close"
|
||||
size="20"
|
||||
color="#999"
|
||||
@click="hideGradeInfo"
|
||||
></u-icon>
|
||||
</view>
|
||||
<view class="popup-content">
|
||||
<view
|
||||
class="grade-item"
|
||||
v-for="(item, index) in gradeInfoList"
|
||||
:key="index"
|
||||
>
|
||||
<view
|
||||
class="grade-label"
|
||||
:style="{ backgroundColor: item.color }"
|
||||
>{{ item.grade }}</view
|
||||
>
|
||||
<view class="grade-desc">
|
||||
<text class="grade-title">{{ item.desc }}</text>
|
||||
<text class="grade-detail">{{ item.description }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@ -162,7 +105,7 @@
|
||||
import { ref, onMounted, watch, nextTick } from "vue";
|
||||
import uCharts from "@/components/charts/u-charts.js";
|
||||
import dayjs from "dayjs";
|
||||
import { xsKscjApi } from "@/api/base/server";
|
||||
import { xsKscjApi, ksccPjFindByKsccAndXsApi } from "@/api/base/server";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { useDataStore } from "@/store/modules/data";
|
||||
const { getCurXs } = useUserStore();
|
||||
@ -173,19 +116,18 @@ const kmTabsRef = ref<any>(null)
|
||||
|
||||
const fsScale = 100;
|
||||
|
||||
const curXs = ref<any>({})
|
||||
const curXs = ref<any>({})
|
||||
const kscc = ref<any>({})
|
||||
|
||||
const kscjList = ref<any>([])
|
||||
const curKsdj = ref<any>({})
|
||||
|
||||
const xqKmList = ref<any>([])
|
||||
const curKm = ref<any>({})
|
||||
const curKmIndex = ref<number>(0)
|
||||
const xqKmmcList = ref<string[]>([])
|
||||
|
||||
// 当前选中的选项卡
|
||||
const activeTab = ref("scores");
|
||||
// 页面数据加载中(显示遮罩)
|
||||
const pageLoading = ref<boolean>(false);
|
||||
// 班主任寄语(家长端只读)
|
||||
const pjBzr = ref<string>("");
|
||||
|
||||
// 切换选项卡
|
||||
const switchTab = (tab: string) => {
|
||||
@ -195,12 +137,13 @@ const switchTab = (tab: string) => {
|
||||
type ColorMapType = {
|
||||
[key: string]: string;
|
||||
};
|
||||
// 等级颜色与教师端一致
|
||||
const colorMap: ColorMapType = {
|
||||
"A": "#FFD700", // 优秀 - 金黄色
|
||||
"B": "#00FF00", // 良好 - 亮绿色
|
||||
"C": "#FF8C00", // 中等 - 橙色
|
||||
"D": "#666666", // 及格 - 灰色
|
||||
"E": "#FF0000", // 不及格 - 红色
|
||||
"A": "#4bb604", // 优秀 - 绿色
|
||||
"B": "#FF8C00", // 良好 - 橙色
|
||||
"C": "#FFD700", // 中等 - 黄色
|
||||
"D": "#FF0000", // 及格 - 红色
|
||||
"E": "#666666", // 不及格 - 灰色
|
||||
};
|
||||
|
||||
// 雷达图数据
|
||||
@ -209,17 +152,6 @@ const radarData = {
|
||||
series: [{ name: "分数", data: [85, 90, 88, 92, 86, 91, 89] }],
|
||||
};
|
||||
|
||||
// 趋势图数据
|
||||
let trendData = {
|
||||
categories: ["01-04", "02-04", "03-04", "04-04", "05-04"],
|
||||
series: [
|
||||
{
|
||||
name: "成绩趋势",
|
||||
data: [84, 81, 89, 92, 99],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// 所有科目列表
|
||||
const sysKmList = ref<any>([]);
|
||||
// 当前考试科目列表
|
||||
@ -233,26 +165,6 @@ const kmDjList = ref<any>([]);
|
||||
const djList = ref<any>([]);
|
||||
// 考试场次成绩列表
|
||||
const ksccKscjList = ref<any>([]);
|
||||
// 学期成绩
|
||||
const xqKscjList = ref<any>([]);
|
||||
|
||||
const switchKm = (index : number) => {
|
||||
curKmIndex.value = index;
|
||||
curKm.value = xqKmList.value[index];
|
||||
trendData = {
|
||||
categories: curKm.value.rqList,
|
||||
series: [
|
||||
{
|
||||
name: "成绩趋势",
|
||||
data: curKm.value.fsList,
|
||||
},
|
||||
],
|
||||
};
|
||||
if (activeTab.value === "trend") {
|
||||
// 重绘趋势图
|
||||
drawTrendChart();
|
||||
}
|
||||
};
|
||||
|
||||
// 绘制雷达图
|
||||
const drawRadarChart = () => {
|
||||
@ -310,85 +222,10 @@ const drawRadarChart = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// 绘制趋势图
|
||||
const drawTrendChart = () => {
|
||||
nextTick(() => {
|
||||
try {
|
||||
// 获取容器信息
|
||||
const query = uni.createSelectorQuery();
|
||||
query
|
||||
.select("#chart-container1")
|
||||
.boundingClientRect((data) => {
|
||||
if (!data) {
|
||||
console.error("未找到趋势图容器");
|
||||
return;
|
||||
}
|
||||
|
||||
// 安全类型判断
|
||||
const rect = data as any;
|
||||
const canvasWidth = rect.width || 300;
|
||||
const canvasHeight = rect.height || 300;
|
||||
|
||||
const ctx = uni.createCanvasContext("trendCanvas");
|
||||
|
||||
// 绘制折线图配置
|
||||
const options = {
|
||||
type: "line",
|
||||
context: ctx,
|
||||
width: canvasWidth,
|
||||
height: canvasHeight,
|
||||
categories: trendData.categories,
|
||||
series: trendData.series,
|
||||
animation: true,
|
||||
background: "#FFFFFF",
|
||||
padding: [15, 15, 0, 15],
|
||||
dataLabel: kscc.value.sfXsFs, // 是否显示分数
|
||||
dataPointShape: true,
|
||||
enableScroll: false,
|
||||
legend: {
|
||||
show: false,
|
||||
},
|
||||
xAxis: {
|
||||
disableGrid: true,
|
||||
axisLine: true,
|
||||
axisLineColor: "#CCCCCC",
|
||||
},
|
||||
yAxis: {
|
||||
gridType: "dash",
|
||||
dashLength: 2,
|
||||
data: [
|
||||
{
|
||||
min: 0,
|
||||
max: 100,
|
||||
},
|
||||
],
|
||||
format: (val : number) => {
|
||||
return val == 0 || val == 100 ? val : '';
|
||||
},
|
||||
},
|
||||
extra: {
|
||||
line: {
|
||||
type: "straight",
|
||||
width: 2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
new uCharts(options);
|
||||
})
|
||||
.exec();
|
||||
} catch (error) {
|
||||
console.error("绘制趋势图出错:", error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 监听选项卡变化,重新渲染对应图表
|
||||
watch(activeTab, (newValue) => {
|
||||
if (newValue === "diagnosis") {
|
||||
setTimeout(drawRadarChart, 50);
|
||||
} else if (newValue === "trend") {
|
||||
setTimeout(drawTrendChart, 50);
|
||||
}
|
||||
});
|
||||
|
||||
@ -418,38 +255,6 @@ const initKsdj = () => {
|
||||
singleFlag = maxDj < maxKmDj + 100;
|
||||
};
|
||||
|
||||
// 构建当前学期所有科目成绩(对象是按照{ 科目id:{ 日期:成绩 } }的结构拼接)
|
||||
const buildXqKmKscjList = () => {
|
||||
const xqKscjMap = new Map<string, any>();
|
||||
const len = xqKscjList.value.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
const exam = xqKscjList.value[i];
|
||||
exam.ksrq = dayjs(exam.kskstime).format('MM-DD');
|
||||
if (!xqKscjMap.has(exam.kmId)) {
|
||||
xqKscjMap.set(exam.kmId, {});
|
||||
}
|
||||
xqKscjMap.get(exam.kmId)[exam.ksrq] = exam.ksfs;
|
||||
}
|
||||
xqKmList.value = [];
|
||||
// 遍历map
|
||||
const keys = Array.from(xqKscjMap.keys());
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
const value = xqKscjMap.get(key);
|
||||
// 目前value是Object,改成map
|
||||
const dataMap = new Map(Object.entries(value));
|
||||
let km = {
|
||||
id: key,
|
||||
kmmc: sysKmList.value.find((item: any) => item.id === key)?.kmmc,
|
||||
rqList: Array.from(dataMap.keys()).map(String),
|
||||
fsList: Array.from(dataMap.values()).map(Number)
|
||||
};
|
||||
xqKmList.value.push(km);
|
||||
}
|
||||
xqKmmcList.value = xqKmList.value.map((item: any) => item.kmmc);
|
||||
switchKm(0);
|
||||
}
|
||||
|
||||
const rebuildData = () => {
|
||||
let ksfsList: any[] = [];
|
||||
let totalFs = 0.00;
|
||||
@ -474,100 +279,101 @@ const rebuildData = () => {
|
||||
totalFs = totalFs / ksccKmList.value.length;
|
||||
}
|
||||
curKsdj.value = djList.value.find((item: any) => item.zdf <= totalFs && item.zgf >= totalFs) || {};
|
||||
console.log("考试场次等级", curKsdj.value);
|
||||
// 构建趋势图所需的成绩列表信息
|
||||
buildXqKmKscjList();
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
curXs.value = getCurXs;
|
||||
kscc.value = getData;
|
||||
const res = await xsKscjApi({
|
||||
xsId: getCurXs.id,
|
||||
ksccId: getData.id,
|
||||
njmcId: getData.njmcId
|
||||
});
|
||||
if (res.resultCode == 1) {
|
||||
sysKmList.value = res.result.kmList;
|
||||
ksccKmList.value = res.result.ksccKmList;
|
||||
kmDjList.value = res.result.kmDjList;
|
||||
djList.value = res.result.djList;
|
||||
ksccKscjList.value = res.result.ksccKscjList;
|
||||
xqKscjList.value = res.result.xqKscjList;
|
||||
// 初始化考试等级
|
||||
initKsdj();
|
||||
// 重构显示数据
|
||||
rebuildData();
|
||||
}
|
||||
// 默认绘制学科成绩视图
|
||||
// 延迟执行确保DOM已经渲染
|
||||
setTimeout(() => {
|
||||
if (activeTab.value === "diagnosis") {
|
||||
drawRadarChart();
|
||||
} else if (activeTab.value === "trend") {
|
||||
drawTrendChart();
|
||||
pageLoading.value = true;
|
||||
try {
|
||||
curXs.value = getCurXs;
|
||||
kscc.value = getData;
|
||||
const res = await xsKscjApi({
|
||||
xsId: getCurXs.id,
|
||||
ksccId: getData.id,
|
||||
njmcId: getData.njmcId
|
||||
});
|
||||
if (res.resultCode == 1) {
|
||||
sysKmList.value = res.result.kmList || [];
|
||||
ksccKmList.value = res.result.ksccKmList || [];
|
||||
kmDjList.value = res.result.kmDjList || [];
|
||||
djList.value = res.result.djList || [];
|
||||
ksccKscjList.value = res.result.ksccKscjList || [];
|
||||
initKsdj();
|
||||
rebuildData();
|
||||
}
|
||||
}, 300);
|
||||
// 加载班主任寄语
|
||||
try {
|
||||
const pjRes = await ksccPjFindByKsccAndXsApi({
|
||||
ksccId: getData.id,
|
||||
xsId: getCurXs.id,
|
||||
});
|
||||
if (pjRes && pjRes.resultCode === 1 && pjRes.result && pjRes.result.pjBzr) {
|
||||
pjBzr.value = pjRes.result.pjBzr;
|
||||
}
|
||||
} catch (_) {}
|
||||
setTimeout(() => {
|
||||
if (activeTab.value === "diagnosis") {
|
||||
drawRadarChart();
|
||||
}
|
||||
}, 300);
|
||||
} finally {
|
||||
pageLoading.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
// 等级说明弹窗状态
|
||||
const gradeInfoPopupVisible = ref(false);
|
||||
|
||||
// 等级说明数据
|
||||
const gradeInfoList = ref([
|
||||
{
|
||||
grade: "A+",
|
||||
color: "#FF6B6B",
|
||||
desc: "优异",
|
||||
description: "超过90%同学,处于年级前列",
|
||||
},
|
||||
{
|
||||
grade: "A",
|
||||
color: "#4D96FF",
|
||||
desc: "相对较好",
|
||||
description: "超过80%同学,表现良好",
|
||||
},
|
||||
{
|
||||
grade: "B",
|
||||
color: "#6BCB77",
|
||||
desc: "良好",
|
||||
description: "超过60%同学,达到年级中上水平",
|
||||
},
|
||||
{
|
||||
grade: "C",
|
||||
color: "#FFD93D",
|
||||
desc: "一般",
|
||||
description: "达到年级平均水平",
|
||||
},
|
||||
{
|
||||
grade: "D",
|
||||
color: "#B8B8B8",
|
||||
desc: "待提高",
|
||||
description: "未达到年级平均水平,需要加强",
|
||||
},
|
||||
]);
|
||||
|
||||
// 显示等级说明弹窗
|
||||
function showGradeInfo() {
|
||||
gradeInfoPopupVisible.value = true;
|
||||
}
|
||||
|
||||
// 隐藏等级说明弹窗
|
||||
function hideGradeInfo() {
|
||||
gradeInfoPopupVisible.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.grades-page {
|
||||
background-color: #f8f8f8;
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
/* 顶部蓝色背景 */
|
||||
/* 数据加载遮罩 */
|
||||
.page-loading-mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.page-loading-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 24px 32px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 12px;
|
||||
}
|
||||
.page-loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid #e8e8e8;
|
||||
border-top-color: #2672ff;
|
||||
border-radius: 50%;
|
||||
animation: page-loading-spin 0.8s linear infinite;
|
||||
}
|
||||
.page-loading-text {
|
||||
margin-top: 12px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
@keyframes page-loading-spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 顶部蓝色背景(与教师端一致) */
|
||||
.blue-header {
|
||||
background-color: #2672ff;
|
||||
padding: 15px 15px 50px 15px;
|
||||
padding: 15px 15px 20px 15px;
|
||||
position: relative;
|
||||
color: white;
|
||||
box-sizing: border-box;
|
||||
@ -585,110 +391,88 @@ function hideGradeInfo() {
|
||||
}
|
||||
|
||||
.header-content {
|
||||
padding-top: 30px;
|
||||
padding-top: 15px;
|
||||
padding-bottom: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
font-size: 20px;
|
||||
.exam-title {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.color-bar {
|
||||
width: 180px;
|
||||
height: 15px;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
#29b6f6,
|
||||
#4caf50,
|
||||
#ff9800,
|
||||
#f57c00,
|
||||
#ffab91,
|
||||
#ffe0b2
|
||||
);
|
||||
border-radius: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.total-score {
|
||||
font-size: 16px;
|
||||
margin-bottom: 10px;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.grade {
|
||||
font-size: 50px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.grade-info {
|
||||
.student-info-section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
gap: 8px;
|
||||
|
||||
.student-name {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.student-class {
|
||||
font-size: 15px;
|
||||
text-align: center;
|
||||
opacity: 0.9;
|
||||
padding: 4px 12px;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.score-info-section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
padding: 0 20px;
|
||||
gap: 30px;
|
||||
|
||||
.grade-explanation {
|
||||
font-size: 14px;
|
||||
margin-right: 15px;
|
||||
position: relative;
|
||||
padding-bottom: 2px;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
.grade-box {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
padding: 8px 20px;
|
||||
border-radius: 6px;
|
||||
.score-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.grade-label {
|
||||
.total-score {
|
||||
font-size: 14px;
|
||||
margin-bottom: 5px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.grade-value {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
.full-score {
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
.grade {
|
||||
font-size: 48px;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
/* 内容区域(与教师端一致) */
|
||||
.content-area {
|
||||
background-color: white;
|
||||
padding: 20px 20px 40px 20px;
|
||||
margin-top: -20px;
|
||||
border-top-left-radius: 20px;
|
||||
border-top-right-radius: 20px;
|
||||
background-color: white;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
top: -70rpx;
|
||||
left: 30rpx;
|
||||
right: 30rpx;
|
||||
bottom: 30rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: calc(100vh - 200px);
|
||||
|
||||
/* 选项卡 */
|
||||
.tabs {
|
||||
@ -720,18 +504,41 @@ function hideGradeInfo() {
|
||||
}
|
||||
}
|
||||
|
||||
/* 学科成绩视图 */
|
||||
/* 学科成绩视图(与教师端一致) */
|
||||
.score-view {
|
||||
padding: 15px;
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.score-content {
|
||||
padding: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.subject-item {
|
||||
padding-top: 30rpx;
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.subject-header {
|
||||
margin-bottom: 5px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.subject-name {
|
||||
font-size: 15px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.detail-btn {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
@ -739,48 +546,67 @@ function hideGradeInfo() {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
padding-bottom: 15px;
|
||||
|
||||
.subject-grade {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.detail-btn {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
.grade-info-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
|
||||
.subject-score {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-scores {
|
||||
padding: 50px 15px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 班主任寄语(家长端只读) */
|
||||
.comment-section {
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
background-color: #fff;
|
||||
|
||||
.comment-header {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.comment-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.comment-text-wrap {
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.comment-text {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 分数趋势视图 */
|
||||
.trend-view {
|
||||
padding: 15px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.tab-placeholder {
|
||||
|
||||
}
|
||||
|
||||
.km-info {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.km-mc {
|
||||
flex: 1 0 1px;
|
||||
}
|
||||
.switch-btn {
|
||||
padding: 4px 12px;
|
||||
background-color: rgba(25, 118, 210, 0.8);
|
||||
color: #fff;
|
||||
border-radius: 20px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.radar-placeholder {
|
||||
height: 300px;
|
||||
background-color: #f8f8f8;
|
||||
@ -824,84 +650,6 @@ function hideGradeInfo() {
|
||||
}
|
||||
}
|
||||
|
||||
/* 等级说明弹窗 */
|
||||
.grade-info-popup {
|
||||
width: 280px;
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
.popup-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
|
||||
.popup-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
padding: 10px 15px 20px;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
|
||||
.grade-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12px;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.grade-label {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.grade-desc {
|
||||
flex: 1;
|
||||
|
||||
.grade-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.grade-detail {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 学生选择器弹窗样式 */
|
||||
.student-selector {
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8faff 100%);
|
||||
|
||||
@ -166,7 +166,7 @@ const menuItems = ref([
|
||||
permissionKey: "school-bjkb", // 班级课表权限编码
|
||||
},
|
||||
{
|
||||
title: "成绩查询",
|
||||
title: "学业监测",
|
||||
icon: "/static/base/home/file-search-line.png",
|
||||
path: "/pages/base/grades/list",
|
||||
permissionKey: "school-cjcx", // 成绩查询权限编码
|
||||
|
||||
@ -131,30 +131,109 @@ export const previewFile = (fileUrl: string, fileName: string, fileType: string)
|
||||
});
|
||||
};
|
||||
|
||||
// 下载文件
|
||||
const downloadFile = (fileUrl: string, fileName: string) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.downloadFile({
|
||||
url: fileUrl,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
uni.saveFile({
|
||||
tempFilePath: res.tempFilePath,
|
||||
success: (saveRes) => {
|
||||
uni.showToast({ title: '文件已保存', icon: 'success' });
|
||||
resolve(saveRes);
|
||||
},
|
||||
fail: reject
|
||||
});
|
||||
// 判断是否为浏览器环境(含微信内置浏览器、手Q 等 H5)
|
||||
const isBrowserEnv = () =>
|
||||
typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
||||
|
||||
// 下载文件(导出给 ImageVideoUpload 等组件使用)
|
||||
export const downloadFile = (fileUrl: string, fileName: string) => {
|
||||
// 微信/手Q 等内置浏览器中 platform 可能非 'web',统一按浏览器用 H5 下载,避免走原生路径触发“不支持文件系统管理器”
|
||||
if (isBrowserEnv() || uni.getSystemInfoSync().platform === 'web') {
|
||||
return downloadForWeb(fileUrl, fileName);
|
||||
}
|
||||
return downloadForNative(fileUrl, fileName);
|
||||
};
|
||||
|
||||
// H5 平台下载(与选项统计导出一致:直接跳转真实 URL,支持微信内、外部浏览器打开后下载)
|
||||
const downloadForWeb = (url: string, _filename: string) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
uni.showToast({ title: "正在下载...", icon: "success", duration: 1500 });
|
||||
setTimeout(() => {
|
||||
if (typeof window !== 'undefined' && window.location) {
|
||||
window.location.href = url;
|
||||
resolve();
|
||||
} else {
|
||||
uni.showToast({ title: "下载失败", icon: "none" });
|
||||
reject(new Error("非浏览器环境"));
|
||||
}
|
||||
}, 100);
|
||||
} catch (err) {
|
||||
console.error("H5下载失败:", err);
|
||||
uni.showToast({ title: "下载失败", icon: "none" });
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 原生平台下载(APP/小程序)
|
||||
const downloadForNative = (url: string, filename: string) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.showLoading({ title: "下载中..." });
|
||||
uni.downloadFile({
|
||||
url: url,
|
||||
success: async (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
try {
|
||||
const saved = await saveFile(res.tempFilePath, filename);
|
||||
if (saved) {
|
||||
uni.openDocument({
|
||||
filePath: saved,
|
||||
success: () => {
|
||||
uni.showToast({ title: "文件已打开", icon: "success" });
|
||||
resolve(true);
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ title: "文件已下载,但无法打开", icon: "none" });
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reject(new Error('保存失败'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('处理下载文件时出错:', error);
|
||||
reject(error);
|
||||
}
|
||||
} else {
|
||||
uni.showToast({ title: "下载失败", icon: "none" });
|
||||
reject(new Error('下载失败'));
|
||||
}
|
||||
},
|
||||
fail: reject
|
||||
fail: (err) => {
|
||||
console.error("下载失败:", err);
|
||||
uni.showToast({ title: "下载失败", icon: "none" });
|
||||
reject(err);
|
||||
},
|
||||
complete: () => uni.hideLoading()
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 保存文件到本地(仅原生端使用)
|
||||
const saveFile = (tempPath: string, filename: string): Promise<string | null> => {
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
if (typeof uni.getFileSystemManager === 'function') {
|
||||
const fs = uni.getFileSystemManager();
|
||||
const appPath = `${filename}`;
|
||||
fs.rename({
|
||||
oldPath: tempPath,
|
||||
newPath: appPath,
|
||||
success: () => resolve(appPath),
|
||||
fail: () => resolve(tempPath)
|
||||
});
|
||||
} else {
|
||||
resolve(tempPath);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存文件时出错:', error);
|
||||
uni.showToast({ title: "保存失败", icon: "none" });
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 视频预览
|
||||
export const previewVideo = (videoUrl: string, videoTitle: string) => {
|
||||
console.log('=== 视频预览调试信息 ===');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user