Files
lofyerandfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> 71db82393a Initial commit
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-07-13 15:38:41 +08:00

385 lines
16 KiB
HTML

<script>
// ===== A. 学校画像卡(仪表盘 + 红绿灯) =====
const profileData = {{ profile_card_data | tojson }};
if (profileData && profileData.total_score) {
(function() {
const chart = echarts.init(document.getElementById('profile-card-chart'));
const score = profileData.total_score;
const districtAvg = profileData.district_avg;
const signals = profileData.dim_signals;
const levelCounts = profileData.level_counts;
// 信号灯颜色映射
const signalColor = function(minLevel) {
if (minLevel >= 4) return '#10b981';
if (minLevel >= 3) return '#3b82f6';
if (minLevel >= 2) return '#f59e0b';
return '#ef4444';
};
const option = {
title: [
{ text: profileData.school_name, left: 'center', top: 6, textStyle: { fontSize: 16, fontWeight: 'bold', color: '#1e3a5f' } },
{ text: profileData.school_type + ' · 区内第' + profileData.rank + '名', left: 'center', top: 28, textStyle: { fontSize: 11, color: '#6b7280' } },
],
series: [
// 中央仪表盘
{
type: 'gauge',
center: ['50%', '60%'],
radius: '55%',
startAngle: 210,
endAngle: -30,
min: 30,
max: 70,
splitNumber: 4,
itemStyle: { color: score >= districtAvg ? '#2563eb' : '#f59e0b' },
progress: { show: true, width: 18, roundCap: true },
pointer: { show: false },
axisLine: { lineStyle: { width: 18, color: [[0.25, '#fee2e2'], [0.5, '#fef3c7'], [0.75, '#dbeafe'], [1, '#d1fae5']] } },
axisTick: { show: false },
splitLine: { show: false },
axisLabel: { show: true, distance: 25, fontSize: 10, color: '#6b7280',
formatter: function(v) { return v.toFixed(0); } },
detail: {
valueAnimation: true, offsetCenter: [0, '-5%'],
fontSize: 36, fontWeight: 'bold', color: '#1e3a5f',
formatter: function(v) { return v.toFixed(1); },
},
data: [{ value: score }],
},
// 底部饼图:20个三级维度的水平分布
{
type: 'pie',
center: ['50%', '92%'],
radius: ['0%', '0%'], // 隐藏饼图,只用graphic展示
silent: true,
data: [],
},
],
graphic: (function() {
var items = [];
// 7个维度信号灯(底部横排)
var startX = 50 - (signals.length - 1) * 6;
signals.forEach(function(sig, i) {
var x = startX + i * 12;
// 圆点
items.push({
type: 'circle', shape: { cx: 0, cy: 0, r: 8 },
left: x + '%', top: '78%',
style: { fill: signalColor(sig.min_level), shadowBlur: 6, shadowColor: signalColor(sig.min_level) },
});
// 标签
items.push({
type: 'text',
left: x + '%', top: '84%',
style: {
text: sig.name.replace('力', ''),
textAlign: 'center', fontSize: 9, fill: '#374151',
},
});
});
// 水平分布摘要文字
var summaryText = '水平分布: ';
summaryText += 'Lv4=' + levelCounts[4] + ' Lv3=' + levelCounts[3] + ' Lv2=' + levelCounts[2] + ' Lv1=' + levelCounts[1];
items.push({
type: 'text',
left: 'center', top: '93%',
style: { text: summaryText, textAlign: 'center', fontSize: 10, fill: '#6b7280' },
});
return items;
})(),
};
chart.setOption(option);
window.addEventListener('resize', () => chart.resize());
})();
}
// ===== B. 优势-短板象限图 =====
const quadrantData = {{ quadrant_data | tojson }};
if (quadrantData && quadrantData.items && quadrantData.items.length > 0) {
(function() {
const chart = echarts.init(document.getElementById('quadrant-chart'));
const items = quadrantData.items;
// 颜色映射(按父维度)
const dimColors = {
'课程领导力': '#2563eb', '教学变革力': '#7c3aed', '学生发展指导力': '#059669',
'教师发展支持力': '#d97706', '教育质量评估力': '#dc2626',
'教育条件保障力': '#0891b2', '数字化赋能力': '#be185d',
};
const scatterData = items.map(function(item) {
return {
value: [item.score, item.diff],
name: item.name,
parent: item.parent,
level: item.level,
itemStyle: { color: dimColors[item.parent] || '#6b7280' },
};
});
const option = {
tooltip: {
formatter: function(p) {
var d = p.data;
return '<strong>' + d.name + '</strong> (' + d.parent + ')<br/>' +
'得分: ' + d.value[0] + '分<br/>' +
'差异: ' + (d.value[1] > 0 ? '+' : '') + d.value[1] + '分<br/>' +
'水平: ' + d.level;
}
},
grid: { left: '12%', right: '5%', bottom: '12%', top: '8%' },
xAxis: {
name: '得分', nameLocation: 'center', nameGap: 28,
nameTextStyle: { fontSize: 12 },
min: 20, max: 80,
splitLine: { show: true, lineStyle: { type: 'dashed', color: '#e5e7eb' } },
},
yAxis: {
name: '与区均值差异', nameLocation: 'center', nameGap: 40,
nameTextStyle: { fontSize: 12 },
splitLine: { show: true, lineStyle: { type: 'dashed', color: '#e5e7eb' } },
},
series: [{
type: 'scatter',
data: scatterData,
symbolSize: function(val, params) { return Math.abs(val[1]) * 1.5 + 10; },
label: {
show: true, position: 'right', fontSize: 9,
formatter: function(p) {
var n = p.data.name;
return n.length > 5 ? n.substring(0, 5) + '…' : n;
},
color: '#374151',
},
}],
// 象限标注
graphic: [
// 均值十字线
{ type: 'line', shape: { x1: 0, y1: 0, x2: 0, y2: 0 }, silent: true },
// 象限标签
{ type: 'text', right: '8%', top: '10%',
style: { text: '✦ 核心优势', fill: '#059669', fontSize: 11, fontWeight: 'bold' } },
{ type: 'text', left: '14%', bottom: '14%',
style: { text: '⚠ 急需改进', fill: '#dc2626', fontSize: 11, fontWeight: 'bold' } },
{ type: 'text', right: '8%', bottom: '14%',
style: { text: '⊙ 隐性风险', fill: '#d97706', fontSize: 11, fontWeight: 'bold' } },
{ type: 'text', left: '14%', top: '10%',
style: { text: '↗ 潜力项', fill: '#2563eb', fontSize: 11, fontWeight: 'bold' } },
],
// 均值参考线
markLine: {},
};
// 加十字参考线
option.series[0].markLine = {
silent: true, symbol: 'none',
lineStyle: { color: '#9ca3af', type: 'dashed', width: 1 },
data: [
{ xAxis: 50, label: { formatter: '区均值', fontSize: 10, position: 'start' } },
{ yAxis: 0, label: { show: false } },
],
};
chart.setOption(option);
window.addEventListener('resize', () => chart.resize());
})();
}
// ===== C. 温度计条形图 =====
const thermoData = {{ thermometer_data | tojson }};
if (thermoData && thermoData.data) {
(function() {
// 为每个 thermo-{partId}-{subIndex} 容器渲染
Object.keys(thermoData.data).forEach(function(sdName) {
var d = thermoData.data[sdName];
// 查找匹配的容器(遍历所有 thermo- 开头的元素)
var allThermos = document.querySelectorAll('[id^="thermo-"]');
allThermos.forEach(function(el) {
if (el.getAttribute('data-rendered')) return;
// 找到未渲染的容器,按顺序匹配
});
});
// 用更简单的方式:直接在维度渲染时按 partId 和 subIndex 匹配
var partSubMap = {};
{% for dim_name, dim_data in dimensions.items() %}
{% set part_names_map = {"课程领导力": "part3", "教学变革力": "part4", "学生发展指导力": "part5", "教师发展支持力": "part6", "教育质量评估力": "part7", "教育条件保障力": "part8", "数字化赋能力": "part9"} %}
{% set pid = part_names_map[dim_name] %}
{% for sub_dim in framework[dim_name].sub_dimensions %}
{% set sd = sub_dimensions.get(sub_dim, {}) %}
{% if sd %}
partSubMap['{{ pid }}-{{ loop.index }}'] = '{{ sub_dim }}';
{% endif %}
{% endfor %}
{% endfor %}
Object.keys(partSubMap).forEach(function(key) {
var sdName = partSubMap[key];
var d = thermoData.data[sdName];
if (!d) return;
var el = document.getElementById('thermo-' + key);
if (!el) return;
var chart = echarts.init(el);
var score = d.score;
var distAvg = d.district_avg;
var sameTypeAvg = d.same_type_avg;
var th = d.thresholds;
var option = {
grid: { left: '3%', right: '3%', top: '25%', bottom: '25%' },
xAxis: {
type: 'value', min: 20, max: 80,
axisLabel: { show: false },
axisTick: { show: false },
axisLine: { show: false },
splitLine: { show: false },
},
yAxis: {
type: 'category', data: [''],
axisLabel: { show: false },
axisTick: { show: false },
axisLine: { show: false },
},
series: [
// 背景条(水平区间着色:低分端=水平四红色 → 高分端=水平一绿色)
{ type: 'bar', data: [th.level2 - 20], stack: 'bg', barWidth: '60%',
itemStyle: { color: '#fee2e2', borderRadius: [4, 0, 0, 4] }, silent: true },
{ type: 'bar', data: [th.level3 - th.level2], stack: 'bg', barWidth: '60%',
itemStyle: { color: '#fef3c7' }, silent: true },
{ type: 'bar', data: [th.level4 - th.level3], stack: 'bg', barWidth: '60%',
itemStyle: { color: '#dbeafe' }, silent: true },
{ type: 'bar', data: [80 - th.level4], stack: 'bg', barWidth: '60%',
itemStyle: { color: '#d1fae5', borderRadius: [0, 4, 4, 0] }, silent: true },
],
// 指针标注
graphic: (function() {
var items = [];
var toPercent = function(val) { return ((val - 20) / 60 * 94 + 3) + '%'; };
// 本校得分指针(三角形 + 标签)
items.push({
type: 'text', left: toPercent(score), top: '2%',
style: { text: '▼ ' + score, textAlign: 'center', fontSize: 11, fontWeight: 'bold',
fill: '#1e3a5f' },
});
// 区均值指针
items.push({
type: 'text', left: toPercent(distAvg), bottom: '0%',
style: { text: '▲区均', textAlign: 'center', fontSize: 9, fill: '#6b7280' },
});
// 同类学校均值指针
if (Math.abs(sameTypeAvg - distAvg) > 1) {
items.push({
type: 'text', left: toPercent(sameTypeAvg), bottom: '0%',
style: { text: '▲同类', textAlign: 'center', fontSize: 9, fill: '#d97706' },
});
}
return items;
})(),
};
chart.setOption(option);
window.addEventListener('resize', () => chart.resize());
});
})();
}
// ===== D. 进步空间瀑布图 =====
const waterfallData = {{ waterfall_data | tojson }};
if (waterfallData && waterfallData.steps && waterfallData.steps.length > 1) {
(function() {
var el = document.getElementById('waterfall-chart');
if (!el) return;
var chart = echarts.init(el);
var steps = waterfallData.steps;
var categories = [];
var baseData = []; // 透明基底
var gainData = []; // 增长部分
var totalData = []; // 起止柱
var runningBase = 0;
steps.forEach(function(step, i) {
var shortName = step.name.length > 6 ? step.name.substring(0, 6) + '…' : step.name;
categories.push(shortName);
if (step.type === 'current') {
baseData.push(0);
gainData.push(0);
totalData.push(step.value);
runningBase = step.value;
} else if (step.type === 'gain') {
baseData.push(runningBase);
gainData.push(step.value);
totalData.push(0);
runningBase += step.value;
} else if (step.type === 'potential') {
baseData.push(0);
gainData.push(0);
totalData.push(step.value);
}
});
var option = {
tooltip: {
trigger: 'axis', axisPointer: { type: 'shadow' },
formatter: function(params) {
var idx = params[0].dataIndex;
var step = steps[idx];
if (step.type === 'current') return '当前总分: <strong>' + step.value + '</strong>分';
if (step.type === 'potential') return '潜在总分: <strong>' + step.value + '</strong>分 (+' + waterfallData.total_gain + ')';
return '<strong>' + step.name + '</strong><br/>' +
(step.detail || '') + '<br/>预估总分贡献: +' + step.value + '分';
},
},
grid: { left: '5%', right: '5%', bottom: '18%', top: '8%', containLabel: true },
xAxis: {
type: 'category', data: categories,
axisLabel: { fontSize: 10, rotate: 20 },
},
yAxis: {
type: 'value',
min: Math.floor(waterfallData.current_total - 2),
max: Math.ceil(waterfallData.potential_total + 2),
axisLabel: { fontSize: 11 },
},
series: [
// 透明基底
{
type: 'bar', stack: 'waterfall', data: baseData,
itemStyle: { color: 'transparent' }, barWidth: '45%',
emphasis: { itemStyle: { color: 'transparent' } },
},
// 增长部分(绿色)
{
type: 'bar', stack: 'waterfall', data: gainData,
itemStyle: { color: '#10b981', borderRadius: [4, 4, 0, 0] },
barWidth: '45%',
label: {
show: true, position: 'top', fontSize: 10, color: '#059669',
formatter: function(p) { return p.value > 0 ? '+' + p.value.toFixed(2) : ''; },
},
},
// 起止柱(蓝色)
{
type: 'bar', data: totalData,
itemStyle: {
color: function(p) {
return p.dataIndex === 0 ? '#2563eb' : '#0891b2';
},
borderRadius: [4, 4, 0, 0],
},
barWidth: '45%',
label: {
show: true, position: 'top', fontSize: 12, fontWeight: 'bold',
color: '#1e3a5f',
formatter: function(p) { return p.value > 0 ? p.value.toFixed(1) : ''; },
},
},
],
};
chart.setOption(option);
window.addEventListener('resize', () => chart.resize());
})();
}
</script>