dev已有「店铺运营」按学校分组看数据,但缺少一个全国视角:哪些学校在用、分布在哪、开了几家。
新增一级菜单 校区地图:
| 决策 | 结论 | 原因 |
|---|---|---|
| 位置来源 | 必须在地图上主动点选,不提供经纬度输入框 | 一个城市有多所高校,城市中心会重叠且偏差大;手填经纬度对使用者不友好 |
| 默认位置 | 没有默认位置,未落点不允许保存 | 避免"看起来创建成功、点却落在市中心" |
| 城市字段 | 只作为地图跳转的辅助(选城市 → 地图飞过去 → 再点精确位置),不决定落点 | 兼顾"快速定位到城市"和"精确到校区" |
| 省份 | 落点后自动识别(点在哪个省),也可手动改 | 避免坐标与省份对不上 |
| 坐标系 | 统一存 GCJ-02(底图坐标系) | 点选天然得到 GCJ-02;混用 WGS84 会有几百米偏差(见 §4.3) |
[新建学校] → 弹窗:学校名称
可选:省份 → 城市(仅用于把地图飞过去)
[取消] [在地图上选择位置]
↓
弹窗收起,地图进入「定位模式」:
顶部提示条:请在地图上点击学校所在位置 · Esc 取消
鼠标变十字;可选省份时先飞到该省/市(zoom 6~9)
↓
点击地图 → 落一个临时标记(脉冲动画)
提示条变成:已选位置(自动识别的省份) [重选] [确认创建]
↓
确认 → 保存并默认「启用」→ 点位变橙色涟漪,列表新增一条
要点:
列表项或点位 → 右侧详情卡:
┌ 中国计量大学 ────────────────┐
│ 浙江省 · 杭州市 │
│ 坐标 120.3626, 30.3187 │ ← 只读展示,不可编辑输入框
│ [重新定位] [停用] [删除] │
└──────────────────────────────┘
| 操作 | 行为 |
|---|---|
| 滚轮 / 双指 | 缩放(zoom 3 ~ 12) |
| 拖拽 | 平移 |
| 双击省份 | 放大到该省 |
| 悬停点位 | tooltip:学校名 · 省/市 · 状态 |
| 点击点位 | 选中并高亮,右侧滚动到对应卡片 |
| 搜索框 | 按名称过滤列表;点结果 → 地图飞到该点(zoom 9) |
| [回到全国] | 恢复默认视野(center 104, 35 / zoom 1.2) |
| 场景 | 展示 |
|---|---|
| 一所学校都没有 | 地图灰显 + 中央「新建第一所学校」按钮 + 一行说明 |
| 存储不可用 | 地图正常显示,顶部提示「学校数据暂不可用」+ 重试,创建按钮禁用 |
| 底图加载失败 | 提示「地图数据加载失败,请刷新」,不渲染空白画布 |
| 名称重复 | 保存时提示「已存在同名学校」,不创建 |
| 点位在境外 | 拒绝保存并提示重新选择 |
public/vendor/echarts.min.js),geo 组件 + effectScatter 系列public/vendor/china-map.json(569KB,35 个省级面,含南海诸岛)public/vendor/cn-cities.json(19KB,35 省 / 477 个城市区县的中心坐标,只用于"飞到城市")geo(底图,roam 可缩放平移)
├── effectScatter series「已启用」
│ symbolSize 10~14,颜色 #ff6b35
│ rippleEffect { brushType: 'stroke', scale: 3.2, period: 3.5 }
│ label { show: true, formatter: 学校名, position: right }
│
└── scatter series「未启用」
symbolSize 6,颜色 #b8b2ad,无涟漪、无标签
chart.on('click', params => selectSchool(params.data.id))中国互联网地图(阿里 DataV、高德)用的是 GCJ-02,而 GPS / OpenStreetMap 是 WGS84,两者在国内相差 300~600 米。
| 例子 | WGS84 | GCJ-02 | 偏差 |
|---|---|---|---|
| 中国计量大学 | 120.3580, 30.3210 | 120.3626, 30.3187 | 约 440m / 254m |
| 杭州市中心 | 120.1536, 30.2875 | 120.1583, 30.2851 | 约 452m / 257m |
| 天安门 | 116.3910, 39.9070 | 116.3972, 39.9084 | 约 533m / 155m |
结论:
wgs84ToGcj02() 工具函数与一条 scripts/ 小工具供批量导入复用现有 ops 存储(默认 SQLite,可切 PG),新增一张表:
CREATE TABLE IF NOT EXISTS ops_school (
id text PRIMARY KEY, -- 服务端生成,如 sch_20261020_a1b2c3
name text NOT NULL,
province text NOT NULL DEFAULT '',
city text NOT NULL DEFAULT '',
lng real NOT NULL, -- GCJ-02
lat real NOT NULL,
enabled integer NOT NULL DEFAULT 1,
created_at text NOT NULL,
updated_at text NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS ops_school_name_idx ON ops_school (name);
设计说明:
name 唯一,防止重复创建同一所学校province / city 为展示与分组用;province 由落点自动识别,city 取用户在下拉里选的值(允许为空)ops_shop.school 是字符串,按 名称相等 关联;创建时会提示「与 N 个运营数据中的学校匹配」,对不上也允许创建(先画图、后补数据)enabled 用 0/1,SQLite 与 PG 都兼容| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/schools |
列表(含 enabled、坐标、省市) |
| POST | /api/schools |
创建;{ name, province, city, lng, lat, enabled? } |
| POST | /api/schools/:id |
更新(改名、重新定位) |
| POST | /api/schools/:id/toggle |
启用/停用 { enabled } |
| DELETE | /api/schools/:id |
删除 |
校验:
name:去空白、1~60 字符、唯一lng / lat:必填;中国范围(lng 73~136、lat 3~54)之外拒绝province / city:可选,≤40 字符GET 返回 200 + { enabled:false, reason },写操作 503(与 ops 现有一致)┌──────────────────────────────────────────────────────────────┐
│ 校区地图 + 新建学校 │
│ 已启用 3 / 共 5 │
├───────────────────────────────────┬──────────────────────────┤
│ │ 🔍 搜索学校 │
│ 中国地图(可缩放平移) │ ┌──────────────────────┐ │
│ │ │● 中国计量大学 [开] │ │
│ ● 中国计量大学 │ │ 浙江 · 杭州 │ │
│ ● 浙江工商大学桐庐校区 │ ├──────────────────────┤ │
│ ○ 浙江科技学院(未启用) │ │○ 浙江科技学院 [关] │ │
│ │ │ 浙江 · 杭州 │ │
│ [回到全国] │ └──────────────────────┘ │
└───────────────────────────────────┴──────────────────────────┘
ops_shop.school 名称关联,为下一步「点击地图上的学校 → 打开店铺运营并筛选该学校」留好接口(本期不做跳转)enabled:false,POST 503GET /api/schools 200(只读,不新增写操作)
新建「中国计量大学」→ 选浙江省 → 地图飞到浙江 → 点下沙位置 → 保存 → 点位点亮并显示名称 → 刷新页面仍在 → 停用变灰 → 重新启用再点亮 → 删除后消失。
public/vendor/china-map.json、cn-cities.json(已就绪,需登记到静态白名单)ops_school 表与存储方法(SQLite + PG 两驱动)/api/schools 五个端点 + 校验 + 存储降级public/schools.js:地图渲染、点亮、定位模式、列表联动、创建/编辑弹窗index.html 菜单与页面骨架、app.js 视图注册、style.csswgs84ToGcj02() + 省份识别(点在多边形内)