'use strict'; // 校区地图:中国地图 + 学校点位(启用=涟漪点亮,停用/未定位=灰点)+ 右侧列表。 // 数据:GET /api/schools(JSON 种子 + 运行时合并);底图:/vendor/china-map.json (function () { const $ = (id) => document.getElementById(id); const CENTER = [104.5, 36]; const ZOOM = 1.55; let chart = null; let schools = []; let inited = false; let loading = false; let selectedId = null; function esc(value) { const div = document.createElement('div'); div.textContent = String(value == null ? '' : value); return div.innerHTML; } function fmtDate(ymd) { const s = String(ymd || ''); return /^\d{8}$/.test(s) ? `${Number(s.slice(4, 6))}/${Number(s.slice(6, 8))}` : ''; } function opsText(school) { if (!school.ops) return '运营数据不可用'; if (!school.ops.shopCount) return '未关联运营数据'; return `${school.ops.shopCount} 店${school.ops.lastDate ? ' · 最近 ' + fmtDate(school.ops.lastDate) : ''}`; } function toPoint(school) { return { name: school.name, value: [school.lng, school.lat], schoolId: school.id, }; } function buildOption() { const on = schools.filter((s) => s.enabled && s.lng != null && s.lat != null); const off = schools.filter((s) => !(s.enabled && s.lng != null && s.lat != null)); return { tooltip: { trigger: 'item', backgroundColor: 'rgba(255,255,255,.96)', borderColor: '#e8e2db', borderWidth: 1, padding: [8, 10], textStyle: { color: '#3f3a35', fontSize: 12 }, formatter(params) { const school = schools.find((s) => s.id === params.data.schoolId); if (!school) return params.name; const status = school.enabled ? '已启用' : '已停用'; const point = school.lng == null ? '未定位' : `${school.lng.toFixed(4)}, ${school.lat.toFixed(4)}`; return `${esc(school.name)}
${esc(school.province || '—')}${school.city ? ' · ' + esc(school.city) : ''}
${status} · ${opsText(school)}
${point}`; }, }, geo: { map: 'china', roam: true, center: CENTER, zoom: ZOOM, scaleLimit: { min: 1, max: 12 }, itemStyle: { areaColor: '#f7f5f2', borderColor: '#ddd6cd', borderWidth: 0.8 }, emphasis: { itemStyle: { areaColor: '#efeae4' }, label: { show: false } }, select: { disabled: true }, }, series: [ { id: 'on', type: 'effectScatter', coordinateSystem: 'geo', data: on.map(toPoint), symbolSize: 11, showEffectOn: 'render', rippleEffect: { brushType: 'stroke', scale: 3.4, period: 3.6 }, itemStyle: { color: '#ff6b35', shadowBlur: 8, shadowColor: 'rgba(255,107,53,.45)' }, label: { show: true, position: 'right', distance: 6, formatter: (p) => p.name, fontSize: 11, color: '#5b534c', }, emphasis: { scale: 1.4 }, zlevel: 2, }, { id: 'off', type: 'scatter', coordinateSystem: 'geo', data: off.map(toPoint), symbolSize: 6, itemStyle: { color: '#b8b2ad' }, label: { show: false }, zlevel: 1, }, ], }; } function renderMap(force) { const box = $('schoolsMap'); if (!box) return; if (!chart) chart = echarts.init(box); chart.setOption(buildOption(), !!force); } function renderList(data) { const list = $('schoolsList'); const count = $('schoolsCount'); if (!list) return; const enabled = data.enabledCount || 0; if (count) count.textContent = `${enabled} / ${schools.length} 已启用`; if (!schools.length) { list.innerHTML = '

还没有学校

内置学校缺失,请检查 server/schools.seed.json
'; return; } const groups = new Map(); for (const school of schools) { const key = school.province || '未标注省份'; if (!groups.has(key)) groups.set(key, []); groups.get(key).push(school); } list.innerHTML = [...groups.entries()].map(([province, items]) => { const onCount = items.filter((s) => s.enabled && s.lng != null).length; const rows = items.map((s) => `
${esc(s.name)} ${s.seed ? '内置' : ''} ${s.lng == null ? '未定位' : ''}
${esc(s.city || province)} ${esc(opsText(s))}
`).join(''); return `
${esc(province)} ${onCount}/${items.length}
${rows}`; }).join(''); list.querySelectorAll('[data-id]').forEach((el) => { el.addEventListener('click', () => selectSchool(el.dataset.id, true)); }); } function selectSchool(id, fly) { const school = schools.find((s) => s.id === id); if (!school) return; selectedId = id; renderList({ enabledCount: schools.filter((s) => s.enabled && s.lng != null).length }); if (chart && school.lng != null) { if (fly) chart.setOption({ geo: { center: [school.lng, school.lat], zoom: 8 } }); chart.dispatchAction({ type: 'highlight', seriesId: 'on', name: school.name }); } } function bind() { const reset = $('btnSchoolsReset'); if (reset) { reset.addEventListener('click', () => { if (chart) chart.setOption({ geo: { center: CENTER, zoom: ZOOM } }); }); } window.addEventListener('resize', () => { if (chart) chart.resize(); }); } async function load() { if (loading) return; loading = true; try { const [geoRes, listRes] = await Promise.all([ fetch('/vendor/china-map.json'), fetch('/api/schools'), ]); if (!geoRes.ok) throw new Error('底图加载失败'); const geo = await geoRes.json(); const data = await listRes.json(); echarts.registerMap('china', geo); schools = data.schools || []; renderList(data); renderMap(true); } catch (e) { const box = $('schoolsMap'); if (box) box.innerHTML = `

地图加载失败

${esc(e.message || e)}
`; } finally { loading = false; } } window.shanSchoolsInit = function () { if (!inited) { inited = true; bind(); load(); } else if (chart) { chart.resize(); } }; })();