You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dianshang-qianduan/src/views/teacherEnd/index.vue

1012 lines
27 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="grade-center">
<!-- 页面头部:极简标题 -->
<header class="page-head">
<div class="head-left">
<h1 class="page-title">成绩管理中心</h1>
<p class="page-desc">班级成绩走势 · 等级分布 · 关键指标概览</p>
</div>
<div class="head-meta">
<span class="meta-dot"></span>
<span class="meta-text">数据实时同步</span>
</div>
</header>
<!-- KPI 统计卡:4 个关键指标 -->
<section class="kpi-grid">
<div class="kpi-card">
<div class="kpi-icon">
<svg viewBox="0 0 24 24" fill="none">
<path d="M3 7L12 3L21 7L12 11L3 7Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>
<path d="M6 9V15C6 15 7.5 17 12 17C16.5 17 18 15 18 15V9" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21 7V13" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</div>
<div class="kpi-info">
<span class="kpi-label">班级总数</span>
<div class="kpi-value-wrap">
<span class="kpi-value">{{ kpiData.classCount }}</span>
<span class="kpi-unit">个</span>
</div>
</div>
</div>
<div class="kpi-card">
<div class="kpi-icon icon-blue">
<svg viewBox="0 0 24 24" fill="none">
<path d="M3 3V21H21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 14L11 10L15 14L21 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="kpi-info">
<span class="kpi-label">平均分</span>
<div class="kpi-value-wrap">
<span class="kpi-value">{{ kpiData.avg }}</span>
<span class="kpi-unit">分</span>
</div>
</div>
</div>
<div class="kpi-card">
<div class="kpi-icon icon-mint">
<svg viewBox="0 0 24 24" fill="none">
<path d="M7 17L17 7M17 7H9M17 7V15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="kpi-info">
<span class="kpi-label">最高分</span>
<div class="kpi-value-wrap">
<span class="kpi-value">{{ kpiData.max }}</span>
<span class="kpi-unit">分</span>
</div>
</div>
</div>
<div class="kpi-card">
<div class="kpi-icon icon-amber">
<svg viewBox="0 0 24 24" fill="none">
<path d="M7 7L17 17M17 17H9M17 17V9" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="kpi-info">
<span class="kpi-label">最低分</span>
<div class="kpi-value-wrap">
<span class="kpi-value">{{ kpiData.min }}</span>
<span class="kpi-unit">分</span>
</div>
</div>
</div>
</section>
<!-- 图表双栏 -->
<section class="chart-grid">
<!-- 走势图 -->
<div class="chart-card">
<div class="chart-head">
<div class="chart-title-wrap">
<h2 class="chart-title">班级平均成绩走势</h2>
<span class="chart-sub">多班级横向对比</span>
</div>
<div class="chart-legend">
<span class="legend-item" v-for="(name, idx) in className" :key="idx">
<span class="legend-dot" :style="{ background: getLineColor(idx) }"></span>
{{ name }}
</span>
</div>
</div>
<div class="chart-body">
<div id="trend" class="chart-area"></div>
</div>
</div>
<!-- 班级统计 -->
<div class="chart-card">
<div class="chart-head">
<div class="chart-title-wrap">
<h2 class="chart-title">班级成绩等级分布</h2>
<span class="chart-sub">按分数段统计人数</span>
</div>
</div>
<!-- 筛选 -->
<div class="filter-bar">
<div class="filter-item">
<label class="filter-label">日期筛选</label>
<el-date-picker
v-model="classParams.time"
class="custom-date-picker"
type="datetime"
placeholder="选择日期时间"
format="YYYY/MM/DD HH:mm"
:clearable="true"
/>
</div>
<div class="filter-item">
<label class="filter-label">班级选择</label>
<el-select placeholder="全部班级" v-model="classParams.classId" class="custom-select" :clearable="true">
<el-option v-for="item in userStore.classList" :key="item" :label="item.class_name" :value="item.class_id" />
</el-select>
</div>
<el-button type="primary" class="search-btn" @click="getClassGradeStatistics">
<svg viewBox="0 0 24 24" fill="none" class="btn-svg">
<circle cx="11" cy="11" r="8" stroke="currentColor" stroke-width="2"/>
<path d="M21 21L16.65 16.65" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
查询
</el-button>
</div>
<div class="stats-body">
<div class="pie-wrap">
<div id="statistics" class="pie-chart"></div>
</div>
<div class="score-list">
<div class="score-row" v-for="item in circularData2" :key="item.name" :class="getScoreCardClass(item.name)">
<span class="score-bar"></span>
<div class="score-row-info">
<span class="score-row-label">{{ item.name }}</span>
<span class="score-row-value">{{ item.value ?? 0 }}<small>分</small></span>
</div>
<span class="score-row-arrow">{{ getIndicatorArrow(item.name) }}</span>
</div>
</div>
</div>
</div>
</section>
</div>
</template>
<script setup>
import * as echarts from "echarts";
import * as teacherApi from "@/api/teacher.js";
const { proxy } = getCurrentInstance();
import { getAssetsFile } from "@/utils/index.js";
import useUserStore from "@/store/modules/user";
const userStore = useUserStore();
const classTime = ref([]);
const className = ref([]);
const classList = ref([]);
const classParams = ref({
classId: "",
time: "",
});
const initstatus = ref(false);
let trendChart = null;
let statisticsChart = null;
const circularData = ref([
{ value: 0, name: "一般", key: "generalCount" },
{ value: 0, name: "优秀", key: "excellentCount" },
{ value: 0, name: "良好", key: "goodCount" },
{ value: 0, name: "不及格", key: "failCount" },
]);
const circularData2 = ref([
{ score: 0, name: "最高分", key: "maxScore" },
{ score: 0, name: "最低分", key: "minScore" },
{ score: 0, name: "平均分", key: "averageScore" },
]);
// KPI 概览数据(纯展示组装,不调接口)
const kpiData = computed(() => {
const find = (n) => circularData2.value.find((i) => i.name === n)?.value ?? 0;
return {
classCount: classList.value.length,
avg: find("平均分"),
max: find("最高分"),
min: find("最低分"),
};
});
// 成绩卡片样式
const getScoreCardClass = (name) => {
const map = {
"最高分": "row-max",
"最低分": "row-min",
"平均分": "row-avg",
};
return map[name] || "";
};
const getIndicatorClass = (name) => {
const map = {
"最高分": "ind-max",
"最低分": "ind-min",
"平均分": "ind-avg",
};
return map[name] || "";
};
const getIndicatorArrow = (name) => {
const map = {
"最高分": "▲",
"最低分": "▼",
"平均分": "◆",
};
return map[name] || "●";
};
// 折线图配色方案(教育蓝绿系:蓝 / 薄荷 / 天青 / 靛 / 紫 / 青绿,克制收敛)
const getLineColor = (index) => {
const colors = [
{ main: "#3b82f6", light: "#bfdbfe" },
{ main: "#52c4a4", light: "#b8e6d5" },
{ main: "#5db8d5", light: "#b8e0eb" },
{ main: "#6366f1", light: "#c7d2fe" },
{ main: "#8b5cf6", light: "#ddd6fe" },
{ main: "#14b8a6", light: "#99f6e4" },
];
return colors[index % colors.length].main;
};
// 获取成绩走势图数据
const getTrendData = async () => {
trendChart = trendChart || echarts.init(document.getElementById("trend"));
const myinde = trendChart;
myinde.setOption({
backgroundColor: "transparent",
tooltip: {
trigger: "axis",
backgroundColor: "rgba(255, 255, 255, 0.98)",
borderColor: "rgba(59, 130, 246, 0.2)",
borderWidth: 1,
padding: [10, 14],
textStyle: {
color: "#1f2937",
fontSize: 13,
},
axisPointer: {
type: "cross",
lineStyle: {
color: "rgba(59, 130, 246, 0.3)",
type: "dashed",
},
crossStyle: {
color: "rgba(59, 130, 246, 0.45)",
},
},
formatter: function(params) {
let result = `<div style="font-weight:600;margin-bottom:8px;color:#1f2937;">${params[0].axisValue}</div>`;
params.forEach(param => {
result += `<div style="display:flex;align-items:center;gap:8px;margin:3px 0;">
<span style="width:8px;height:8px;border-radius:50%;background:${param.color};"></span>
<span style="flex:1;color:#6b7280;">${param.seriesName}</span>
<span style="font-weight:600;color:${param.color};">${param.value}分</span>
</div>`;
});
return result;
},
},
legend: {
show: false,
},
grid: {
left: "3%",
right: "4%",
bottom: "6%",
top: "6%",
containLabel: true,
},
xAxis: {
type: "category",
boundaryGap: false,
data: classTime.value,
axisLine: {
lineStyle: {
color: "rgba(59, 130, 246, 0.2)",
},
},
axisLabel: {
color: "#9ca3af",
fontSize: 11,
margin: 12,
},
axisTick: {
show: false,
},
// 悬停时 X 轴高亮日期标签:深蓝底白字,保证清晰可读
axisPointer: {
label: {
backgroundColor: "#2563eb",
color: "#ffffff",
fontSize: 11,
fontWeight: 600,
padding: [4, 8],
borderRadius: 4,
margin: 10,
shadowColor: "transparent",
shadowBlur: 0,
},
},
},
yAxis: {
type: "value",
min: 0,
max: 100,
splitLine: {
lineStyle: {
color: "rgba(59, 130, 246, 0.08)",
type: "dashed",
},
},
axisLine: {
show: false,
},
axisLabel: {
color: "#9ca3af",
fontSize: 11,
formatter: "{value}",
},
// 悬停时 Y 轴数值标签同样深蓝底白字
axisPointer: {
label: {
backgroundColor: "#2563eb",
color: "#ffffff",
fontSize: 11,
fontWeight: 600,
padding: [4, 8],
borderRadius: 4,
shadowColor: "transparent",
shadowBlur: 0,
},
},
},
series: classList.value.map((item, index) => {
const colorSet = [
{ main: "#3b82f6", light: "rgba(59, 130, 246, 0.18)" },
{ main: "#52c4a4", light: "rgba(82, 196, 164, 0.18)" },
{ main: "#5db8d5", light: "rgba(93, 184, 213, 0.18)" },
{ main: "#6366f1", light: "rgba(99, 102, 241, 0.18)" },
{ main: "#8b5cf6", light: "rgba(139, 92, 246, 0.18)" },
{ main: "#14b8a6", light: "rgba(20, 184, 166, 0.18)" },
][index % 6];
return {
...item,
smooth: 0.4,
lineStyle: {
width: 2.5,
color: colorSet.main,
shadowColor: colorSet.main,
shadowBlur: 6,
},
itemStyle: {
color: colorSet.main,
},
areaStyle: {
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: colorSet.light },
{ offset: 1, color: "rgba(0, 0, 0, 0)" },
],
},
},
symbol: "circle",
symbolSize: 5,
emphasis: {
scale: true,
scaleSize: 9,
},
};
}),
});
};
// 饼图配色方案(成绩等级语义色:优秀蓝 / 良好薄荷 / 一般暖黄 / 不及格珊瑚)
const getPieColor = (index) => {
const colors = [
{ main: "#3b82f6", light: "rgba(59, 130, 246, 0.2)" },
{ main: "#52c4a4", light: "rgba(82, 196, 164, 0.2)" },
{ main: "#f5b945", light: "rgba(245, 185, 69, 0.2)" },
{ main: "#ef8a76", light: "rgba(239, 138, 118, 0.2)" },
];
return colors[index % colors.length].main;
};
// 成绩统计分析
const getStatisticsData = async () => {
statisticsChart = statisticsChart || echarts.init(document.getElementById("statistics"));
const myChart = statisticsChart;
myChart.setOption({
backgroundColor: "transparent",
tooltip: {
trigger: "item",
backgroundColor: "rgba(255, 255, 255, 0.98)",
borderColor: "rgba(59, 130, 246, 0.2)",
borderWidth: 1,
padding: [10, 14],
textStyle: {
color: "#1f2937",
fontSize: 13,
},
formatter: "{a} <br/>{b} : {c}人 ({d}%)",
},
legend: {
orient: "horizontal",
bottom: 2,
left: "center",
itemWidth: 10,
itemHeight: 10,
itemGap: 12,
textStyle: {
color: "#6b7280",
fontSize: 12,
rich: {
name: { color: "#6b7280", fontSize: 12 },
num: { color: "#3b82f6", fontSize: 12, fontWeight: 600 },
},
},
formatter: (name) => {
const item = circularData.value.find((d) => d.name === name);
const num = item?.value || 0;
return `{name|${name}} {num|${num}人}`;
},
},
series: [
{
type: "pie",
radius: [56, 98],
center: ["50%", "44%"],
avoidLabelOverlap: true,
itemStyle: {
borderRadius: 6,
borderColor: "#ffffff",
borderWidth: 3,
},
label: {
show: false,
},
emphasis: {
scale: true,
scaleSize: 10,
itemStyle: {
shadowBlur: 14,
shadowColor: "rgba(59, 130, 246, 0.22)",
},
},
data: circularData.value.map((item, index) => ({
...item,
itemStyle: {
color: {
type: "linear",
x: 0, y: 0, x2: 1, y2: 1,
colorStops: [
{ offset: 0, color: getPieColor(index) },
{ offset: 1, color: adjustColor(getPieColor(index), -28) },
],
},
},
})),
},
],
});
};
// 颜色调整函数
const adjustColor = (hex, amount) => {
const num = parseInt(hex.replace("#", ""), 16);
const r = Math.max(0, Math.min(255, (num >> 16) + amount));
const g = Math.max(0, Math.min(255, ((num >> 8) & 0x00FF) + amount));
const b = Math.max(0, Math.min(255, (num & 0x0000FF) + amount));
return "#" + (0x1000000 + (r << 16) + (g << 8) + b).toString(16).slice(1);
};
// 获取班级成绩走势函数功能函数
const getClassGradeTrend = () => {
teacherApi.getAverageScoreTrend().then((res) => {
const trends = Array.isArray(res) ? res : [];
classTime.value = trends[0]?.startTime || [];
className.value = trends.map((item) => item.className);
classList.value = trends.map((item) => {
return {
name: item.className,
type: "line",
stack: "Total",
data: item.classAverageScore,
};
});
getTrendData();
});
};
// 获取班级成绩统计分析函数功能函数
const getClassGradeStatistics = () => {
if (!classParams.value.time) {
classParams.value.time = new Date().toString();
} else {
classParams.value.time = classParams.value.time.toString();
}
if (initstatus.value && !classParams.value.classId) {
proxy.$modal.msgWarning("请选择班级");
}
teacherApi.getScoreStatisticsTrend(classParams.value).then((res) => {
circularData.value.forEach((item, index) => {
item.value = res[item.key];
if (res.hasOwnProperty(item.key)) {
item.value = res[item.key];
}
});
circularData2.value.forEach((item, index) => {
item.value = res[item.key];
if (res.hasOwnProperty(item.key)) {
item.value = res[item.key];
}
});
getStatisticsData();
});
};
// 获取班级
const getClassList = () => {
teacherApi.getOwnedTeachingClasses().then((res) => {
userStore.classList = (res.data || []).map((item) => ({
class_name: item.className,
class_id: item.schoolClassId,
}));
});
};
onMounted(() => {
getClassGradeTrend();
getClassGradeStatistics();
getClassList();
initstatus.value = true;
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>
// ============================================================
// 教师端 · 极简 SaaS 教育风 设计令牌(单一事实来源)
// 颜色:教育蓝单主色 + 中性灰,语义色仅用于状态/等级
// 字号:6 级紧凑字阶;间距:4px 基准刻度;圆角:2 档;按钮:36px 控件
// ============================================================
.grade-center {
/* —— 颜色 —— */
--edu-mint: #52c4a4; // 语义:在线/良好
--edu-amber: #f5b945; // 语义:一般/警示
--edu-coral: #ef8a76; // 语义:不及格/最高
--edu-text: #1f2937; // 主文字
--edu-text-2: #6b7280; // 次文字
--edu-text-3: #9ca3af; // 辅助/占位
--edu-border: #e5e7eb; // 卡片/控件描边
--edu-border-soft: #f0f1f3; // 分区线
--edu-bg: #ffffff; // 画布/卡片底
/* —— 字号(6 级紧凑字阶)—— */
--fs-display: 17px; // 页面标题 700
--fs-h2: 14px; // 卡片标题 700
--fs-caption: 11px; // 标签/副标题/徽章
--fs-data-lg: 26px; // KPI 大数字
--fs-data: 20px; // 行内数字
/* —— 字重 —— */
--fw-title: 700;
--fw-emph: 600;
--fw-body: 400;
/* —— 行高 —— */
--lh-title: 1.35;
--lh-body: 1.5;
--lh-data: 1;
/* —— 控件与按钮 —— */
--control-h: 36px; // 输入框、按钮统一高度
--btn-pad-x: 16px; // 按钮横向内边距
--field-w: 200px; // 输入框/下拉统一宽度
--pie-h: 300px; // 饼图固定高度
--score-list-w: 168px; // 分数列表固定宽度
--chart-min-h: 320px; // 走势图最小高度
/* —— 图标 —— */
--icon-box: 40px; // 图标底盒
--icon-glyph: 20px; // 图标笔画尺寸
--btn-icon: 14px; // 按钮内图标
/* —— 阴影 —— */
--edu-shadow: 0 1px 3px rgba(17, 24, 39, 0.04);
--edu-shadow-hover: 0 4px 14px rgba(17, 24, 39, 0.08);
display: flex;
flex-direction: column;
gap: var(--sp-4);
line-height: var(--lh-body);
}
// ===== 页面头部:极简标题 =====
.page-head {
display: flex;
align-items: flex-end;
justify-content: space-between;
.head-left {
.page-title {
font-size: var(--fs-display);
font-weight: var(--fw-title);
line-height: var(--lh-title);
color: var(--edu-text);
margin: 0;
}
.page-desc {
font-size: var(--fs-body);
color: var(--edu-text-3);
margin: var(--sp-1) 0 0;
}
}
.head-meta {
display: flex;
align-items: center;
gap: var(--sp-2);
.meta-dot {
width: var(--sp-2);
height: var(--sp-2);
border-radius: 50%;
background: var(--edu-mint);
box-shadow: 0 0 0 3px rgba(82, 196, 164, 0.18);
}
.meta-text {
font-size: var(--fs-caption);
color: var(--edu-text-3);
}
}
}
// ===== KPI 统计卡:4 个关键指标 =====
.kpi-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--sp-4);
}
.kpi-card {
display: flex;
align-items: center;
gap: var(--sp-3);
padding: var(--sp-4);
background: var(--edu-bg);
border: 1px solid var(--edu-border);
border-radius: var(--r-card);
box-shadow: var(--edu-shadow);
transition: box-shadow 0.2s ease, border-color 0.2s ease;
&:hover {
border-color: rgba(59, 130, 246, 0.3);
box-shadow: var(--edu-shadow-hover);
}
.kpi-icon {
width: var(--icon-box);
height: var(--icon-box);
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--r-control);
background: var(--edu-primary-soft);
color: var(--edu-primary);
flex-shrink: 0;
svg {
width: var(--icon-glyph);
height: var(--icon-glyph);
}
// 统一教育蓝浅底,不再用多色变体
&.icon-blue,
&.icon-mint,
&.icon-amber {
background: var(--edu-primary-soft);
color: var(--edu-primary);
}
}
.kpi-info {
display: flex;
flex-direction: column;
gap: var(--sp-1);
min-width: 0;
.kpi-label {
font-size: var(--fs-caption);
color: var(--edu-text-3);
}
.kpi-value-wrap {
display: flex;
align-items: baseline;
gap: var(--sp-1);
.kpi-value {
font-size: var(--fs-data-lg);
font-weight: var(--fw-title);
line-height: var(--lh-data);
color: var(--edu-text);
font-feature-settings: "tnum";
}
.kpi-unit {
font-size: var(--fs-caption);
color: var(--edu-text-3);
}
}
}
}
// ===== 图表双栏 =====
.chart-grid {
display: grid;
grid-template-columns: 1.3fr 1fr;
gap: var(--sp-4);
flex: 1;
min-height: 0;
}
.chart-card {
display: flex;
flex-direction: column;
background: var(--edu-bg);
border: 1px solid var(--edu-border);
border-radius: var(--r-card);
box-shadow: var(--edu-shadow);
overflow: hidden;
}
.chart-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--sp-3);
padding: var(--sp-3) var(--sp-4);
border-bottom: 1px solid var(--edu-border-soft);
flex-shrink: 0;
.chart-title-wrap {
.chart-title {
font-size: var(--fs-h2);
font-weight: var(--fw-title);
line-height: var(--lh-title);
color: var(--edu-text);
margin: 0;
}
.chart-sub {
display: block;
margin-top: var(--sp-1);
font-size: var(--fs-caption);
color: var(--edu-text-3);
}
}
.chart-legend {
display: flex;
flex-wrap: wrap;
gap: var(--sp-2) var(--sp-3);
justify-content: flex-end;
max-width: 50%;
.legend-item {
display: flex;
align-items: center;
gap: var(--sp-1);
font-size: var(--fs-caption);
color: var(--edu-text-2);
.legend-dot {
width: var(--sp-2);
height: var(--sp-2);
border-radius: 50%;
}
}
}
}
.chart-body {
flex: 1;
min-height: var(--chart-min-h);
padding: var(--sp-2) var(--sp-3) var(--sp-3);
.chart-area {
width: 100%;
height: 100%;
min-height: var(--chart-min-h);
}
}
// ===== 筛选条:紧凑横排(控件外观统一走 teacher-light.scss 全局规范)=====
.filter-bar {
display: flex;
align-items: flex-end;
gap: var(--sp-3);
padding: var(--sp-3) var(--sp-4);
border-bottom: 1px solid var(--edu-border-soft);
flex-shrink: 0;
.filter-item {
display: flex;
flex-direction: column;
gap: var(--sp-1);
.filter-label {
font-size: var(--fs-caption);
color: var(--edu-text-3);
}
}
/* 查询按钮:外观(方案 A 静奢深蓝)由 teacher-light.scss 全局定义,
此处只保留布局 —— 页面 scoped 无法越过 #app id 特异性,勿在此写外观 */
.search-btn {
margin-left: auto;
gap: var(--sp-2); // 图标与文字 8px 间距
height: var(--control-h);
.btn-svg {
width: var(--btn-icon);
height: var(--btn-icon);
}
}
/* 字段统一宽度(布局尺寸;外观由全局规范控制)*/
:deep(.el-date-editor),
:deep(.el-select) {
width: var(--field-w) !important;
}
}
// ===== 统计主体:饼图 + 分数行 =====
.stats-body {
flex: 1;
display: flex;
gap: var(--sp-3);
padding: var(--sp-3) var(--sp-4) var(--sp-4);
min-height: 0;
.pie-wrap {
flex: 1;
min-width: 0;
align-self: center;
.pie-chart {
width: 100%;
height: var(--pie-h);
min-height: var(--pie-h);
}
}
.score-list {
flex: 0 0 var(--score-list-w);
display: flex;
flex-direction: column;
gap: var(--sp-2);
justify-content: center;
}
}
.score-row {
display: flex;
align-items: center;
gap: var(--sp-3);
padding: var(--sp-3);
background: var(--edu-bg);
border: 1px solid var(--edu-border);
border-radius: var(--r-control);
position: relative;
overflow: hidden;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
&:hover {
box-shadow: var(--edu-shadow-hover);
}
.score-bar {
width: 3px;
height: var(--sp-5);
border-radius: 3px;
background: var(--edu-border);
flex-shrink: 0;
}
.score-row-info {
display: flex;
flex-direction: column;
gap: var(--sp-1);
flex: 1;
min-width: 0;
.score-row-label {
font-size: var(--fs-caption);
color: var(--edu-text-3);
}
.score-row-value {
font-size: var(--fs-data);
font-weight: var(--fw-title);
line-height: var(--lh-data);
color: var(--edu-text);
font-feature-settings: "tnum";
small {
font-size: var(--fs-caption);
font-weight: var(--fw-body);
color: var(--edu-text-3);
margin-left: var(--sp-1);
}
}
}
.score-row-arrow {
font-size: var(--fs-caption);
color: var(--edu-text-3);
}
&.row-max {
.score-bar { background: var(--edu-coral); }
.score-row-arrow { color: var(--edu-coral); }
&:hover { border-color: rgba(239, 138, 118, 0.35); }
}
&.row-min {
.score-bar { background: var(--edu-primary); }
.score-row-arrow { color: var(--edu-primary); }
&:hover { border-color: rgba(59, 130, 246, 0.35); }
}
&.row-avg {
.score-bar { background: var(--edu-amber); }
.score-row-arrow { color: var(--edu-amber); }
&:hover { border-color: rgba(245, 185, 69, 0.35); }
}
}
// ===== 响应式 =====
@media (max-width: 1200px) {
.kpi-grid {
grid-template-columns: repeat(2, 1fr);
}
.chart-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.kpi-grid {
grid-template-columns: 1fr;
}
.page-head {
flex-direction: column;
align-items: flex-start;
gap: var(--sp-2);
}
.filter-bar {
flex-direction: column;
align-items: stretch;
.search-btn {
margin-left: 0;
width: 100%;
justify-content: center;
}
}
.stats-body {
flex-direction: column;
.score-list {
flex: none;
}
}
}
</style>