refactor: improve layout and styling in teacher view; enhance chart configurations and add dark mode support

fix/exam-question-access-material-excel-delete-draft
qinzhenpen 1 month ago
parent 6891005100
commit 5d3df886f7

@ -39,6 +39,8 @@ const classObj = computed(() => ({
mobile: device.value === "mobile",
teacherLayout: role.value === 3,
studentLayout: role.value !== 1 && role.value !== 2 && role.value !== 3,
// 教师端启用 Element Plus 暗色模式,让所有组件弹层(date-picker、select 等)自动变暗
dark: role.value === 3,
}));
const { width, height } = useWindowSize();

@ -1,42 +1,51 @@
<template>
<div class="teachers">
<div class="flex">
<el-card shadow="never">
<el-card shadow="never" class="chart-card trend-card">
<template #header>
<div class="card-header">班级平均成绩走势图</div>
<div class="card-header">
<span class="header-icon trend-icon"></span>
<span>班级平均成绩走势图</span>
</div>
</template>
<div class="el-card-body">
<div id="trend"></div>
<div class="trend-title">班级成绩统计分析</div>
<div id="trend" class="chart-container"></div>
<div class="trend-title">成绩走势趋势分析</div>
</div>
</el-card>
<el-card shadow="never">
<el-card shadow="never" class="chart-card statistics-card">
<template #header>
<div class="card-header">班级成绩统计分析</div>
<div class="card-header">
<span class="header-icon stats-icon"></span>
<span>班级成绩统计分析</span>
</div>
</template>
<div class="el-card-body">
<div class="serch">
<el-form :inline="true">
<el-form-item label="日期:">
<el-date-picker v-model="classParams.time" style="width: 185.6px" type="datetime" placeholder="请选择" format="YYYY/MM/DD HH:mm:ss" />
<div class="search-box">
<el-form :inline="true" class="search-form">
<el-form-item label="日期" class="form-item">
<el-date-picker v-model="classParams.time" class="custom-date-picker" type="datetime" placeholder="请选择" format="YYYY/MM/DD HH:mm:ss" />
</el-form-item>
<el-form-item label="班级:">
<el-select placeholder="全部" v-model="classParams.classId" style="width: 185.6px">
<el-form-item label="班级" class="form-item">
<el-select placeholder="全部" v-model="classParams.classId" class="custom-select">
<el-option v-for="item in userStore.classList" :key="item" :label="item.class_name" :value="item.class_id" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="warning" @click="getClassGradeStatistics">查看成绩</el-button>
<el-form-item class="form-item-btn">
<el-button type="primary" class="search-btn" @click="getClassGradeStatistics">
<span class="btn-icon"></span>
查看成绩
</el-button>
</el-form-item>
</el-form>
</div>
<div id="statistics"></div>
<div class="maxpoints">
<div class="maxpoints-item" v-for="item in circularData2" :key="item.name">
<!-- <img style="width: 55px; height: 55px" :src="getAssetsFile(`images/${item.name}.png`)" alt="" /> -->
<div class="ri">
<span style="color: #333333; font-weight: bold; font-size: 16px; letter-spacing: 4px">{{ item.name }}:</span>
<span style="font-family: var(--student-font-base); font-weight: bold; font-size: 20px; color: #ff6400">{{ item.value ?? 0 }}</span>
<div id="statistics" class="chart-container pie-chart"></div>
<div class="score-cards">
<div class="score-card" v-for="item in circularData2" :key="item.name">
<div class="score-icon" :class="getScoreIconClass(item.name)"></div>
<div class="score-content">
<span class="score-label">{{ item.name }}</span>
<span class="score-value">{{ item.value ?? 0 }}</span>
</div>
</div>
</div>
@ -60,6 +69,8 @@ const classParams = ref({
time: "",
});
const initstatus = ref(false);
let trendChart = null;
let statisticsChart = null;
const circularData = ref([
{ value: 0, name: "一般", key: "generalCount" },
{ value: 0, name: "优秀", key: "excellentCount" },
@ -71,75 +82,201 @@ const circularData2 = ref([
{ score: 0, name: "最低分", key: "minScore" },
{ score: 0, name: "平均分", key: "averageScore" },
]);
// 成绩图标样式
const getScoreIconClass = (name) => {
const iconMap = {
最高分: "icon-max",
最低分: "icon-min",
平均分: "icon-avg",
};
return iconMap[name] || "";
};
// 获取成绩走势图数据
const getTrendData = async () => {
var myinde = echarts.init(document.getElementById("trend"));
trendChart = trendChart || echarts.init(document.getElementById("trend"));
const myinde = trendChart;
myinde.setOption({
backgroundColor: "transparent",
tooltip: {
trigger: "axis",
backgroundColor: "rgba(0, 20, 40, 0.95)",
borderColor: "rgba(0, 170, 255, 0.5)",
borderWidth: 1,
textStyle: {
color: "#e0f0ff",
fontSize: 13,
},
axisPointer: {
type: "cross",
lineStyle: {
color: "rgba(0, 170, 255, 0.4)",
},
},
},
legend: {
data: className.value,
textStyle: {
color: "#8ec8f5",
fontSize: 12,
},
top: 5,
itemWidth: 20,
itemHeight: 10,
},
grid: {
left: "4%",
left: "3%",
right: "4%",
bottom: "1%",
bottom: "3%",
top: "15%",
containLabel: true,
},
xAxis: {
type: "category",
boundaryGap: false,
data: classTime.value,
axisLine: {
lineStyle: {
color: "rgba(0, 170, 255, 0.4)",
},
},
axisLabel: {
color: "#6aabde",
fontSize: 11,
},
axisTick: {
show: false,
},
},
yAxis: {
type: "value",
//显示0~100
min: 0,
max: 100,
splitLine: {
lineStyle: {
color: "rgba(0, 170, 255, 0.15)",
type: "dashed",
},
},
axisLine: {
show: false,
},
axisLabel: {
color: "#6aabde",
fontSize: 11,
},
},
series: classList.value,
});
// 自适应
window.addEventListener("resize", () => {
myinde.resize();
series: classList.value.map((item, index) => ({
...item,
smooth: true,
lineStyle: {
width: 3,
color: getLineColor(index),
},
itemStyle: {
color: getLineColor(index),
},
areaStyle: {
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: getAreaColor(index, 0.35) },
{ offset: 1, color: getAreaColor(index, 0.02) },
],
},
},
symbol: "circle",
symbolSize: 6,
})),
});
};
// 折线图颜色配置
const getLineColor = (index) => {
const colors = ["#00aaff", "#00ff88", "#ff7c7c", "#ffd93d", "#a78bfa", "#22d3ee"];
return colors[index % colors.length];
};
const getAreaColor = (index, alpha) => {
const colors = ["rgba(0, 170, 255,", "rgba(0, 255, 136,", "rgba(255, 124, 124,", "rgba(255, 217, 61,", "rgba(167, 139, 250,", "rgba(34, 211, 238,"];
return colors[index % colors.length] + alpha + ")";
};
// 成绩统计分析
const getStatisticsData = async () => {
var myChart = echarts.init(document.getElementById("statistics"));
statisticsChart = statisticsChart || echarts.init(document.getElementById("statistics"));
const myChart = statisticsChart;
myChart.setOption({
backgroundColor: "transparent",
tooltip: {
trigger: "item",
backgroundColor: "rgba(0, 20, 40, 0.95)",
borderColor: "rgba(0, 170, 255, 0.5)",
borderWidth: 1,
textStyle: {
color: "#e0f0ff",
fontSize: 13,
},
formatter: "{a} <br/>{b} : {c} ({d}%)",
},
legend: {
orient: "vertical",
left: "right",
right: "3%",
top: "center",
data: ["一般", "优秀", "良好", "不及格"],
itemWidth: 14,
itemHeight: 14,
itemGap: 14,
textStyle: {
color: "#8ec8f5",
fontSize: 13,
},
formatter: (name) => {
const item = circularData.value.find((d) => d.name === name);
return name + " " + (item?.value || 0) + "人";
},
},
series: [
{
type: "pie",
radius: "70%",
radius: ["45%", "70%"],
center: ["35%", "50%"],
avoidLabelOverlap: true,
itemStyle: {
borderRadius: 6,
borderColor: "rgba(0, 20, 40, 0.9)",
borderWidth: 3,
},
label: {
show: false,
},
emphasis: {
scale: true,
scaleSize: 8,
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: "rgba(0, 0, 0, 0.5)",
shadowBlur: 20,
shadowColor: "rgba(0, 170, 255, 0.6)",
},
},
data: circularData.value,
// 修改颜色
color: ["#f7b844", "#51bcb5", "#5d92f7", "#ca5038"],
data: circularData.value.map((item, index) => ({
...item,
itemStyle: {
color: getPieColor(index),
},
})),
},
],
});
// 自适应
window.addEventListener("resize", () => {
myChart.resize();
});
};
// 饼图颜色配置
const getPieColor = (index) => {
const colors = ["#f7b844", "#51bcb5", "#5d92f7", "#e05545"];
return colors[index % colors.length];
};
// 获取班级成绩走势函数功能函数
const getClassGradeTrend = () => {
@ -195,129 +332,351 @@ const getClassList = () => {
};
onMounted(() => {
getClassGradeTrend();
getClassGradeStatistics()
getClassList()
getClassGradeStatistics();
getClassList();
initstatus.value = true;
// 等布局稳定后再 resize 一次,避免高度百分比场景下 ECharts 拿不到正确尺寸
requestAnimationFrame(() => {
trendChart?.resize();
statisticsChart?.resize();
});
});
const handleResize = () => {
trendChart?.resize();
statisticsChart?.resize();
};
window.addEventListener("resize", handleResize);
onBeforeUnmount(() => {
window.removeEventListener("resize", handleResize);
trendChart?.dispose();
statisticsChart?.dispose();
trendChart = null;
statisticsChart = null;
});
</script>
<style lang='scss' scoped>
<style lang="scss" scoped>
.teachers {
padding: 16px;
display: flex;
flex-direction: column;
flex: 1;
min-height: calc(100vh - 96px);
.flex {
display: flex;
flex-wrap: wrap;
// gap: 15px;
:deep(.el-card) {
padding: 0px 15px;
height: calc(100vh - 80px);
border: none;
#trend {
height: 500px;
gap: 16px;
flex: 1;
min-height: 540px;
.chart-card {
background: rgba(10, 30, 60, 0.6) !important;
border: 1px solid rgba(0, 170, 255, 0.2) !important;
border-radius: 12px !important;
backdrop-filter: blur(10px);
transition: all 0.3s ease;
&:hover {
border-color: rgba(0, 170, 255, 0.4) !important;
box-shadow: 0 8px 32px rgba(0, 170, 255, 0.15);
}
#statistics {
height: 360px;
:deep(.el-card__header) {
padding: 16px 20px !important;
border-bottom: 1px solid rgba(0, 170, 255, 0.15) !important;
background: transparent !important;
flex-shrink: 0;
}
.maxpoints {
:deep(.el-card__body) {
padding: 16px 20px !important;
flex: 1;
display: flex;
justify-content: center;
gap: 60px;
margin-top: 70px;
.maxpoints-item {
flex-direction: column;
min-height: 0;
}
:deep(.el-card) {
height: 100%;
display: flex;
flex-direction: column;
}
.card-header {
display: flex;
align-items: center;
gap: 12px;
font-size: 18px;
font-weight: 600;
color: #e8f4ff;
letter-spacing: 1px;
.header-icon {
width: 24px;
height: 24px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
gap: 0;
width: 181px;
height: 50px;
background: #fff8e5;
border-radius: 10px;
border: 1px solid #ffb900;
.ri {
display: flex;
align-items: center;
gap: 15px;
}
.trend-icon {
background: linear-gradient(135deg, #00aaff, #0066cc);
box-shadow: 0 4px 12px rgba(0, 170, 255, 0.4);
&::after {
content: "";
width: 12px;
height: 12px;
background: url(@/assets/images/分析.png) no-repeat center;
background-size: contain;
}
}
.stats-icon {
background: linear-gradient(135deg, #51bcb5, #2d8a84);
box-shadow: 0 4px 12px rgba(81, 188, 181, 0.4);
&::after {
content: "";
width: 12px;
height: 12px;
background: url(@/assets/images/统计.png) no-repeat center;
background-size: contain;
}
}
}
.chart-container {
width: 100%;
}
#trend {
height: 100%;
flex: 1;
min-height: 360px;
}
#statistics {
height: 220px;
flex-shrink: 0;
}
.pie-chart {
flex: 1;
min-height: 200px;
}
.trend-title {
margin-top: 28px;
margin-top: 12px;
text-align: center;
font-family: var(--student-font-base);
font-weight: bold;
font-size: 16px;
color: #999999;
}
.serch {
font-size: 14px;
color: #6aabde;
letter-spacing: 2px;
flex-shrink: 0;
}
.el-card__header {
padding: 13px 15px 13px 21px !important;
border-bottom: 2px dashed #e5e5e5;
.search-box {
margin-bottom: 12px;
padding: 12px 14px;
background: rgba(0, 40, 80, 0.4);
border-radius: 10px;
border: 1px solid rgba(0, 170, 255, 0.1);
flex-shrink: 0;
.search-form {
display: flex;
flex-wrap: nowrap;
align-items: center;
gap: 0;
:deep(.el-form-item) {
margin-right: 12px !important;
margin-bottom: 0 !important;
}
:deep(.el-form-item:last-child) {
margin-right: 0 !important;
}
:deep(.el-form-item__label) {
color: #8ec8f5 !important;
font-weight: 500;
padding-right: 8px !important;
}
:deep(.el-input__wrapper) {
background: rgba(0, 30, 60, 0.6) !important;
border: 1px solid rgba(0, 170, 255, 0.3) !important;
box-shadow: none !important;
width: 160px !important;
&:hover,
&:focus {
border-color: rgba(0, 170, 255, 0.6) !important;
}
}
:deep(.el-input__inner) {
color: #e0f0ff !important;
font-size: 12px;
&::placeholder {
color: #5a8ab5 !important;
}
}
:deep(.el-select .el-input__wrapper) {
background: rgba(0, 30, 60, 0.6) !important;
width: 140px !important;
}
:deep(.el-select__caret) {
color: #8ec8f5 !important;
}
// 日期选择器下拉面板内部输入框(白色背景用黑色文字)
:deep(.el-date-picker) {
.el-input__inner {
color: #333 !important;
}
}
:deep(.el-button) {
background: linear-gradient(135deg, #0066cc, #00aaff) !important;
border: none !important;
color: #fff !important;
font-weight: 500;
padding: 8px 20px !important;
border-radius: 8px !important;
box-shadow: 0 4px 15px rgba(0, 170, 255, 0.3);
transition: all 0.3s ease;
font-size: 13px;
white-space: nowrap;
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 170, 255, 0.5);
}
}
}
}
.card-header {
font-family: var(--student-font-base);
font-weight: bold;
font-size: 20px;
color: #333333;
position: relative;
margin-left: 17px;
letter-spacing: 4px; /* 设置文字间隔为2像素 */
&::before {
content: "";
position: absolute;
left: -40px;
top: 25%;
display: inline-block;
width: 16px;
height: 16px;
background: url(@/assets/images/分析.png) no-repeat;
background-size: 100% 100%;
// border-radius: 4px;
// margin-right: 20px;
.score-cards {
display: flex;
justify-content: center;
gap: 16px;
margin-top: 12px;
flex-wrap: wrap;
flex-shrink: 0;
.score-card {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 20px;
background: linear-gradient(135deg, rgba(0, 40, 80, 0.8), rgba(0, 20, 50, 0.9));
border: 1px solid rgba(0, 170, 255, 0.25);
border-radius: 12px;
transition: all 0.3s ease;
min-width: 140px;
&:hover {
transform: translateY(-3px);
border-color: rgba(0, 170, 255, 0.5);
box-shadow: 0 8px 25px rgba(0, 170, 255, 0.2);
}
.score-icon {
width: 40px;
height: 40px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
&.icon-max {
background: linear-gradient(135deg, #ff6b6b, #ee5a5a);
box-shadow: 0 4px 12px rgba(255, 107, 107, 0.4);
&::after {
content: "▲";
color: #fff;
font-size: 16px;
}
}
&.icon-min {
background: linear-gradient(135deg, #5d92f7, #4a7dd4);
box-shadow: 0 4px 12px rgba(93, 146, 247, 0.4);
&::after {
content: "▼";
color: #fff;
font-size: 16px;
}
}
&.icon-avg {
background: linear-gradient(135deg, #ffd93d, #f0c828);
box-shadow: 0 4px 12px rgba(255, 217, 61, 0.4);
&::after {
content: "◆";
color: #fff;
font-size: 14px;
}
}
}
.score-content {
display: flex;
flex-direction: column;
gap: 2px;
.score-label {
font-size: 12px;
color: #6aabde;
font-weight: 500;
}
.score-value {
font-size: 22px;
font-weight: 700;
background: linear-gradient(135deg, #00ffcc, #00aaff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
}
}
}
}
.el-card:nth-child(1) {
width: calc(100% - 47% - 15px);
border-right: 2px dashed #e5e5e5;
.trend-card {
flex: 1.2;
display: flex;
flex-direction: column;
border-right: 1px solid rgba(0, 170, 255, 0.15);
:deep(.el-card__body) {
flex: 1;
display: flex;
flex-direction: column;
}
}
.el-card:nth-child(2) {
width: calc(100% - 53%);
.card-header {
font-family: var(--student-font-base);
font-weight: bold;
font-size: 20px;
color: #333333;
margin-left: 17px;
position: relative;
&::before {
content: "";
position: absolute;
left: -40px;
top: 25%;
display: inline-block;
width: 16px;
height: 16px;
// background: url(@/assets/images/统计.png) no-repeat;
// background-size: 100% 100%;
}
.statistics-card {
flex: 0.8;
display: flex;
flex-direction: column;
:deep(.el-card__body) {
flex: 1;
display: flex;
flex-direction: column;
}
}
}
}
.btnOne {
// background: url("@/assets/images/tea查看.png") no-repeat;
// background-size: 100% 100%;
border: none;
min-width: 118px;
padding-left: 30px;
height: 40px;
}
:deep(.el-form) {
.el-form-item:nth-child(2) {
margin-right: 15px !important;
}
.el-form-item:nth-child(3) {
margin-right: 0px !important;
}
}
</style>

Loading…
Cancel
Save