# parking-cloud-yudao 迁移手册 > 新工程根目录。老工程 `/Users/wangbiao/IdeaProjects/parking/parking-cloud` 与老服务 48082 **保持原样**,本工程的任何操作都不改它。 > 所有新文档都放在本目录下;设计稿见 `docs/superpowers/specs/2026-11-02-yudao-replatform-design.md`。 ## 0. 两个新仓库 | 仓库 | 路径 | 来源 | 版本 | |---|---|---|---| | 后端 | `parking-cloud-yudao` | `chifeng/ruoyi-vue-pro` 的 `master-jdk17` | commit `0b1889a8e8`,Boot 3.5.15 / JDK17 / MP 3.5.17 | | 前端 | `parking-cloud-yudao-ui` | `yudao-ui-admin-vue3` 最新 | `2026.10-snapshot`,Vue 3.5.34 / EP 2.13.7 | ## 1. 当前进度 - [x] 新建后端仓库(`master-jdk17`) - [x] 新建前端仓库(官方最新) - [x] 设计稿落盘 - [x] 数据库拷贝 + 上游表准备(`parking_cloud_yudao`,重名旧表改 `pcbak_system_*`) - [x] 上游模块裁剪(只保留 system / infra / iot;2026-11-04 进一步关闭 iot,见 §8.0) - [x] parking 业务模块搬迁(10 个模块编译通过) - [x] 系统兼容桥(`LoginUserBridge` 打通 yudao 登录态 → 存量 `UserInfo`) - [x] 前端业务页面搬迁(park/order/finance/report/ops/... 已进 `src/views`,构建通过) - [x] **后端单 Jar 在 48083 启动成功,登录 + 存量接口冒烟通过**(2026-11-02) - [x] **全页面运行巡检:58 个停车页 + 12 个系统页,`code != 0` 调用 0 个**(2026-11-02) - [x] **与老环境隔离:新服务 Redis 用 db1(老服务 db0)+ `scripts/warm-cache.sh` 缓存预热**(2026-11-02) - [x] **菜单 path 归一化:子页改相对路径,侧边栏不再出现 `/park/park/park-lot` 404**(2026-11-02,见 §7.4) - [x] **业务页全局样式回流:令牌/版式类映射到芋道主题(`src/styles/parking-pages.scss`)**(2026-11-02,见 §7.3 #5) - [x] **存量定时任务去重:`parking.legacy.scheduler.enabled` 总开关,默认 false,6 个活跃任务全部停跑**(2026-11-02,见 §7.5) - [x] **指挥中心:按「赤峰市智慧停车数据中心」大屏重做(通栏 7 KPI;左:供给结构/联网接入/重点车场/时长/收入排行;中:全市 12 旗县区地图(有实时数据的区按占用率着色 + 四角标注卡引线)+ 24h 车流;右:紧凑进出场列表/用户与支付(图标 + 环形图)/运行预警(图标);1280–4K 分辨率自适应),旧版大屏保留入口**(2026-11-02 版式,见 §7.6) - [x] **指挥中心接真实数据**:新增 `POST /park/command-center/overview` 单接口聚合(`ParkCommandCenterController/Mapper/ServiceImpl`,权限沿用 `park:dashboard:query`,不新增权限点、不授权角色),前端 `demo.ts` 删除、改 `mapper.ts` + `api/park/commandCenter`,60 秒轮询;支付构成取在线表 `pay_order`(**不是** `td_b_pay_order`),实时泊位/用户/重点车场无数据时整块「暂无数据」不补零(2026-11-06,见 §7.6) - [x] **菜单/权限收口:672 个权限串全覆盖,3 个角色模板 + 3 个联调账号,接口矩阵验证通过**(2026-11-02,见 `scripts/README-permissions.md`) - [x] **组织 id 空间统一:`sys_org` 真实 id 迁入 `system_org`,用户指向真实组织,去掉 `org-id-mapping` 换算**(2026-11-02,见 §7.7 与 `scripts/README-org.md`) - [x] **首页监管总览:`POST /park/dashboard/overview` 单接口聚合 8 个区块,前端按 `01-dashboard.html` 重做首页**(2026-11-02,见 §7.8) - [x] **合同缺口页:监管中心(磁贴卡片导航)、重点车场占用率监测(`/park/keyLotMonitor`)、实时停车流量监控(`/park/flowMonitor`)**;后者新增 `POST /park/flow-monitor/overview` 分钟级聚合接口(权限点 `park:flow-monitor:query`,菜单 100505/100506),窗口无数据时 `hasData=false` + `dataThrough` 提示数据停更(2026-11-05,见 `docs/superpowers/specs/2026-11-04-合同缺口与页面设计.md`) - [x] **综合监测 `/park/monitor`(接入真实接口)**:新增 `ParkMonitorController`(`POST /park/monitor/realtime-overview`,请求 `{region?, period?}`,权限沿用 `park:dashboard:query`,**仍不授权角色 2001**),一次返回汇总、12 旗县区榜、车场点位(含 `coordType`)与趋势;`ParkMonitorMapper` 取 `parking_lot` 在用且 `pl_city_name='赤峰市'` 的车场(963 个),实时泊位取 `parking_lot_berth_temp_info`,趋势取 `parking_running_statistic`(`data_source=10`,整点 40 / 按日 50)。**数据诚实**:无实时数据的车场占用率为 `null` → 页面「—」,区县榜固定补齐 12 区、缺数据是「无数据」而非 0,趋势无统计行时整段空态不补零;`dataThrough` 展示最近一条运行统计时间(本地库为 `2026-01-31 23:59:59`,实时快照仅 1 个车场有行)。前端 `mapper.ts` 做响应 → 页面模型(字符串数字、12 区补齐、百度/WGS84 → GCJ02);地图可缩放、点区县下钻、点车场开抽屉、30 秒自动刷新、全屏、Excel 导出(全量),右侧列表最多渲染 200 个车场;刷新失败保留旧数据并给重试。菜单 100507 / 权限按钮 100508(2026-11-06,见设计方案 §2.2) - [x] **车辆追溯 `/park/vehicleTrace`(接入真实接口)**:新增 `ParkVehicleTraceController`(`POST /park/vehicle-trace/query`、`POST /park/vehicle-trace/plate-suggest`,权限 `park:trace:query`,菜单 100509 / 按钮 100510,**不自动授权任何角色**);`ParkVehicleTraceMapper` 读 `order_park`(`data_state = 1`,`park_in_time` 落在所选范围),完整车牌走 `order_park_index_car_number`(≈5 ms),精确落空才做一次后缀匹配(`DISTINCT + LIMIT` 借索引提前结束,≈0.4 s),命中多辆返回 `ambiguous + candidates` 由用户选;金额取 `order_act_fee`(分)、时长按出场 − 入场现算、未出场为 `null`;车场名 / 区县取 `parking_lot`,坐标取一条有效 `parking_lot_geo`,`coordType` 由前端统一转 GCJ02。输入候选只扫范围内最近 2 万条记录(≈30–60 ms),故**只给最近入场时间、不给记录条数**;单次最多返回 5000 条(超出 `truncated = true`)。前端数据源改为 `mapper.ts`(TDD 门禁 `tests/verify-vehicle-trace-mapper.mjs`)+ `src/api/park/vehicleTrace`,默认范围改为「今天往前 30 天」,改范围 400ms 防抖自动重查,`?plate=` 深链自动查询,加载 / 失败(保留上次结果)/ 空结果 / 多车候选分别展示;`demo.ts`(车辆追溯 + 综合监测)已删除。已冒烟:蒙D72094 近 30 天 199 条 / ¥382、`2094` 唯一后缀命中、`8888` 命中 10 辆给候选、深链与改范围重查(2026-11-06,见设计方案 §2.7)。**地址栏同步坑**:`AppView.vue` 用 `route.meta.viewKey || route.fullPath` 当组件 key,页面里 `router.replace({ query })` 会换 key → 整页重挂载 → `onMounted` 再按 `?plate=` 查一次(一次点击两次查询)。车辆追溯改为 `resolve(...).href` + `window.history.replaceState` 只改地址栏,不再触发路由导航;新页面若要回写 query 需同样处理或给路由配稳定 `viewKey`。**16:9 一屏(2026-11-06)**:页头加「全屏 / 退出全屏」(`requestFullscreen` + `fullscreenchange` 里 `nextTick` 重算地图),`.vt:fullscreen` 铺满 100vw×100vh;`@media (max-height: 980px)` 改为紧凑定高(压 padding / 字号、隐藏 KPI 备注),页面始终一屏、时间轴与明细表内部滚动。**不要再把该断点改回 `height: auto`** —— 时间轴一条约 90px,200 条会把页面撑到约 19,000 px,底部明细表不可达。已实测 1366×768 / 1600×900 / 1920×1080 无页面滚动;同日复核其余 8 张大屏/监测页(监管中心、指挥中心、一张图、重点车场占用率、专题分析、实时流量、视频监控、综合监测)在同样尺寸下均已一屏适配,无需改动。**(2026-11-06 复核时该结论对监管中心不成立:当时量的是 `document.documentElement.scrollHeight`,页面实际在 `.el-scrollbar__wrap` 里溢出 182px / 83px,已修,见 §7.14。)**同日还修了 16:9 下暴露的两类版式问题:① `.stat-strip` 默认 5 列,只有 4 张卡时未加 `cols-4` 会在右侧留一格灰底空白(反馈管理、设备管理充电桩 / 设备原始数据 / 网关 / 诱导屏 / PDA 五个页签);② 表格列全部写死 `width` 时表格不铺满卡片,右侧出现无表头空白带(区域月卡、违停道路、违章记录、物联网卡流量四个表各留一列 `min-width`),四个表名下的列定义改完在 1366×768 与 1920×950 实测已铺满。规则见设计方案 §0.3.1。 - [x] **公告管理 `/park/notice`(接入真实 `sys_notice`)**:新增 `ParkNoticeController`(`/system/notice`,`park:notice:query|create|update|delete|publish`,**不自动授权任何角色**),读走存量 `SysNoticeService#queryNoticeForPage`、写走 `saveNotice`/`updateNotice`,只碰 `sys_notice` 的 `notice_big_type = 300` 行 —— 小程序 `POST /system/app-news/query-app-news-for-page` 一字未改,后台发布/下线即小程序可见/不可见。列表强制 `showAll`(含待发布、已下线),状态由 `eff_date`/`exp_date` 推导;`eff_date`/`exp_date` 在库里 NOT NULL,故**没有草稿态**:发布时间留空=立即发布、失效时间留空=发布+10 年;删除为逻辑删除 `data_state = 0`。存量表没有「置顶 / 附件」列,页面不再展示这两个概念;封面图可上传(复用 `/admin-api/infra/file/upload`)也可直接粘贴地址。类名带 `Park` 前缀是因为上游也有 `NoticeController`/`NoticeServiceImpl`,同组件扫描下同名会 `ConflictingBeanDefinitionException`。已冒烟:新增 → 小程序列表可见 → 下线不可见 → 再发布可见 → 删除后台/小程序均消失(2026-11-06,见设计方案 §2.10) - [x] **反馈管理 `/park/feedback`(接入真实 `td_c_cust_suggest` + `td_c_cust_suggest_detail`)**:新增 `ParkFeedbackController`(`/pay/feedback`,`park:feedback:query|reply`,**不自动授权任何角色**),读走存量 `TdCCustSuggestService#queryFeedbackAndSuggestForPage`(固定 `state = 1`、按 `create_date desc`)与 `TdCCustSuggestDetailQueryService`,写走存量 `TdCCustSuggestDetailOperService#saveTdCCustSuggestDetail` + `TdCCustSuggestService#updateSuggestIsAnswer(1)` + 极光推送 —— 与老门户「客户反馈建议管理」同一条链路,后台答复后小程序「我的反馈」看到同一份数据。**状态只有两种**(`is_answer` 0 待处理 / 1 已回复,存量没有「已处理」动作),页面已删掉「标记已处理」与对应 `park:feedback:handle` 用法。存量查询会把登录组织追加进条件,而 `system_users.org_id` 指向平台根组织、历史数据 `org_id` 是业务组织,照抄会查出 0 条,故**反馈不按登录组织过滤**(全市台账)。页面保留:4 项汇总(总数 / 待处理 / 已回复 / 超时未处理,口径不受筛选影响)、待处理优先排序、超 24 小时标「超时」、手机号脱敏、最新平台答复回填、详情抽屉展示真实图片(FastDFS 前缀 + `UrlHandler`)与车主/平台往来记录、可补充答复、Excel 导出(id 按字符串返回避免雪花号精度丢失;单页上限 500,前端自动翻页)。副表 `suggest_id` 是 varchar、主表 id 是 bigint,按字符串比较;平台侧答复批量取 `user_type = 2`、`create_date asc` 后最后一条为准。已冒烟:真实 501 条列表、抽屉、答复后状态转已回复、往来记录渲染(合成行已清理)(2026-11-06,见设计方案 §2.11) - [x] **大屏配色统一**:7 张在役大屏全部改用 `src/styles/bigscreen.scss` 的 `--bs-*` 色板与同一套「深空蓝 + 蓝色辉光 + 点阵」底纹,区县中心四区颜色对齐色板(2026-11-05,见设计方案 §0.1.1) - [x] **本地演示数据(其余 11 个旗县区)**:`scripts/gen-demo-district-data.py` 给本地副本库补 69 个演示车场 / 12,490 个泊位 + 当日订单 / 支付 / 实时泊位 / 运行统计,`--apply` 可重复执行、`--clean` 可整批回滚;红山区只补缓存与统计表、不动真实车场与订单。指挥中心因此从「红山区单区 + 空态」变成 12 区齐全(109 车场 / 16,210 泊位 / 今日 21,710 进场 / 收入 24,643 元)(2026-11-06,见 §7.11) - [x] **重点车场占用率监测接真实数据**:存量 `query-parkinglot-basic-facts-by-pl-nos` 的实时泊位在 Redis 快照缺失时由 `LegacyRealtimeBerthFiller` 从 `parking_lot_berth_temp_info` 兜底,今日进出改由 `order_park` 现算(Redis 有值时整段跳过,不改生产行为);页面 47 监测车场 / 11 超阈值 / 平均占用率 74.4%,Top 20、24 小时曲线、泊位周转与平均占用率均有值,1366×768 曲线不再被面板裁掉(2026-11-06,见 §7.12) - [x] **指挥中心「各区联网接入进度」12 个旗县区一屏看全**:原来「区名 / 已接入数」两行一列,1080p 下只能露 5~6 行;改成单行 + 自动分栏(列宽 ≥120px 就两栏),并给同心环补一块中心圆盘垫住数字,1728 以下单栏滚动时底部渐隐;同页还修了 1366×768 整页多出的 50px 滚动(矮屏定高漏减 `--app-footer-height`,把页脚顶出视口)(2026-11-06,见 §7.13) - [x] **综合监测「区县占用率」13 行一屏**:两行排压成「场数并入首行 + 进度条单独一行」,行高 47 → 29px,1080p 左栏 403=403 全部可见(原来只能看到 8 个区县,红山区等 5 个要滚动);≤960 高度加底部渐隐(2026-11-06,见 §7.17) - [x] **一张图 / 专题分析的演示夹具补齐 11 个旗县区**:夹具原来只有 3 个区 14 个车场,一张图 9 个区县标「未接入」、专题分析下拉 11 个区县「(暂无车场)」,与读库的指挥中心 / 综合监测对不上;按演示库原样补 `districtSeeds` 67 条(名称 / 坐标 / 泊位 / 空余 / 今日进出)→ 全市 81 车场,12 个区县全部有数;视频监控设备树顺带补齐为 12 个区县 / 112 台(2026-11-06,见 §7.18) - [x] **一张图入场动效(色调不变,3 秒加载完成)**:地图从中间「开花」铺开(`clip-path` 圆 + `scale(0.55→1)`),左右面板的卡片**从上到下一张张**滑入(左栏从左往右、右栏从右往左,每张错峰 140ms),底部卡片从下往上浮起;九宫格指标跟「资源总览」那张卡一起从 0 滚到真值,只播一次(切区县 / 刷新 / 轮询直接给值)。**地图改成铺满 `.om-body` 的整块背景**,去掉了原来的 1px 边框与底色,左右面板浮在上面(绝对定位后左右栏必须显式 `grid-column`,否则右栏会滑到中间盖住地图)。节奏全部由 `src/views/gov/shared/enter-motion.ts` 决定,`tests/verify-one-map-motion.mjs` 钉住顺序、逐张递增与 3 秒预算;实测刷新到全部完成 2992ms。配色仍是深空蓝,1920×1080 / 1366×768 均一屏(2026-11-06,见 §7.21) - [x] **三张高德地图换蓝色底图**:一张图 / 专题分析 / 视频监控原来用 `amap://styles/darkblue`,实测地块取色 **0,0,1(纯黑)**,跟深空蓝大屏对不上;改成高德内置的 `amap://styles/blue`(同一套矢量样式里唯一的蓝底,路网 / 水系仍可见),三处同时改,页面高度不变(2026-11-06,见 §7.20) - [x] **列表页本地分页(反馈 / 公告 / 视频设备)**:三个页面的接口都一次拉全量、页面上本地筛选,表格把全部行铺出来 —— 反馈 501 行一页 **21,483px**、视频设备 112 行溢出 428px、公告 19 行溢出 174px。新增共享 `src/utils/page.ts#pagedSlice`(越界页码夹到最后一页、每页 0 条按 1 条算),三页统一 **10 条/页 + 改筛选回第 1 页 + 条数变少夹回最后一页**,序号列跟着页码走;`tests/verify-feedback-manage.mjs` 守住切片边界。12 张演示页在 1920×1080 与 1366×768 实测:8 张大屏/监测页 `scrollHeight == clientHeight`,3 张列表页 1920 下一屏、1366 下按普通后台列表纵向滚动(2026-11-06,见 §7.19) - [x] **实时停车流量监控区分「停更」与「归零」**:库内最后一条比现在旧 5 分钟以上时,「近 5 分钟入场/出场」显示「—」+「数据停更」芯片,不再拿 0 冒充「确实没车」;纯函数 `staleMinutesOf` 走 `tests/verify-flow-monitor.mjs` TDD 门禁(2026-11-06,见 §7.16) - [x] **演示数据重灌流程 + 指挥中心占用率对齐**:演示订单一律写「当天」,隔天看板必空,演示前固定 `gen-demo-district-data.py --apply` + `scripts/warm-cache.sh` 两步;脚本修掉「当前小时订单挤进最后 30 分钟」造成的近 60 分钟假峰;指挥中心占用率与综合监测对齐到 **71.3%**(放宽 `freeBerth` 对 `berth_num` 的要求、分母回落到车场登记泊位,精度由 1 位小数改 3 位)(2026-11-06,见 §7.15) - [x] **监管中心三行磁贴一屏适配**:§7.6 里「监管中心已一屏适配」是误判(量的是 `document.documentElement`,页面实际在 `.el-scrollbar__wrap` 里滚);实测 1920×1080 溢出 83px、1600×900 溢出 79px、1366×768 溢出 182px。960 档只收标签行、820 档再收紧卡内留白与图标,全局再收图标上限 / 行高 / 网格间距,卡高 251 → 226,四档均 `scrollHeight == clientHeight`(2026-11-06,见 §7.14) - [x] **一张图 `/park-map` 接真实接口**:新增 `POST /park/resource-map/overview`(权限沿用 `park:park-lot-manage:query`,**不自动授权任何角色**),车场 / 泊位 / 台账 / 设备 / 进出场全部来自 `parking_cloud_yudao`;**台账与点位分开报**(路侧泊位台账 2,575 个无坐标,只报数不上图;地磁台账 2,097 台、能上图 673 台),60 秒静默刷新(2026-11-09,见 §7.28) - [x] **专题分析 `/park-map/analysis` 接真实接口**:新增 `POST /park/topic-analysis/{coverage|duration|flow|heat|hotspot|price|revenue}`(权限 `park:topic-analysis:query`,菜单 100501,**不自动授权任何角色**),7 个专题全部来自 `parking_cloud_yudao`;24 小时帧一次返回、热点网格仍由前端算;前端改 `mapper.ts` + `api/park/topicAnalysis`,下拉 12 个旗县区不再按夹具标「暂无车场」;366 天区间 flow 7.38 → 2.38 s、heat 3.37 → 1.27 s(2026-11-09,见 §7.29) - [x] **视频接入(WVP-PRO)**:WVP-PRO 3.0.0 已合并为 `parking-module-video`,随 `yudao-server` 启动,yudao 登录态即视频平台登录态(见 §8.1);前端实时预览 / 通道树 / 回放 / 云台 / 抓图 / 片段下载已移植到 `/video/monitor`;ZLMediaKit 仍需单独部署 - [x] **视频监控二级分组改读 WVP 业务分组 + 新增「通道分组」页(100532)**:左侧树二级节点由演示夹具的车场名改成通道 `gbBusinessGroupId` 翻出的分组名(区县仍按 `civilCode`,没挂分组的归「未分组」),并补上分组维护页(新建 215 / 子节点 216 / 改名 / 删除 / 挂通道);点播仍走设备→通道接口,不受分组影响(2026-11-09,见 §8.2.2) - [x] **一张图加「重点车场」「实时流量」入口 + 去掉「未定位资源」「全屏打开」**:`PAGE_NAV` 5 → 7 项,新两项和专题分析 / 视频监控 并列、同样在弹框里打开(权限沿用 `park:dashboard:query` / `park:flow-monitor:query`,**不新增权限、不授权任何角色**);资源清单里的「未定位资源」按钮 + 弹框 + 样式整块删除;弹框右下角的「全屏打开」按钮删除,只留关闭(2026-11-09,见 §7.30) - [x] **前端删掉上游「文档地址」广告条**:350 个 `.vue` 里 389 处 ``(「【功能权限】文档地址:https://doc.iocoder.cn/…」)连同组件一起删除,登录页推广区、用户菜单「文档」项、901 错误弹窗的教程链接同步去掉;新增 `tests/verify-no-doc-links.mjs` 守着,`src/` 里再出现 `=153 的 345 条 + parking 专有系统页)以 `id+100000` 导入,`parent_id` 按规则重映射。 6. 旧字典(5 类 / 17 条)以新 id 导入新 `system_dict_*`。 7. 原库 `parking_cloud` 完全不动,老服务 48082 继续可用。 脚本:`scripts/migrate-db.sh`(拷贝 + 建表)、`scripts/migrate-menus.sql`(菜单/字典/管理员)。 ## 4. 模块范围 ### 4.1 保留的上游模块 ``` yudao-dependencies yudao-framework # 全部 starter yudao-module-system yudao-module-infra yudao-module-iot # core / gateway / biz yudao-server ``` ### 4.2 搬迁的 parking 模块 ``` parking-framework/parking-common # 67 个,兼容 POJO parking-framework/parking-legacy-common # 567 个,存量工具 parking-module-api # 2581 个,存量契约 parking-module-park # 1150 parking-module-order # 1018 parking-module-pay # 977 parking-module-bill # 413 parking-module-charge # 67 parking-module-business # 100 parking-module-datacollect # 1(空实现) parking-module-system-legacy # 从 parking-module-system 剥出的停车专有系统代码 ``` ### 4.3 系统模块三分法 - **丢弃(上游已有)**:`auth / user / role / menu / permission / dict / notice / operate-log / oauth2`。 - **保留(停车专有)**:`org / refresh-cache / tb-work-order / tb-work-order-process / aliyuncs-ecs / message-center / app-news`。 - **桥接(存量引用)**:`UserDO / MenuDO / RoleDO / OrgDO / OperateLogDO / PermissionService` 等。 已核对冲突:parking 有 8 条 `/system/*` 路由与上游重名,全部属于「丢弃」类;保留类路径与上游不冲突。 ## 5. 兼容层清单(业务模块只引用 36 个唯一类) | 类别 | 处理 | |---|---| | `common.*`(CommonResult / PageResult / PageParam / ServiceException / 枚举 / 校验 / 工具) | 保留 `parking-common` 原类 | | `BaseDO / BaseMapperX / LambdaQueryWrapperX / QueryWrapperX` | 从 `parking-spring-boot-starter-mybatis` 摘出 4 个类,去掉自动配置,放进 `parking-common` 或 `parking-framework-compat` | | `SecurityFrameworkUtils` / `LoginUser` | 薄适配到上游 SecurityContext | | `OperateLog` / `OperateTypeEnum` | 先保留注解(不生效也不影响编译),后续映射到上游操作日志 | | `ExcelUtils` | 复制工具类 | | `common.biz.system.oauth2/permission DTO` | 薄适配到上游 system API | **注意**:不要保留 parking 的 `ParkingMybatisAutoConfiguration / ParkingSecurityAutoConfiguration / ParkingWebAutoConfiguration`,它们会和上游自动配置冲突。 ## 6. 启动类改造要点 新启动类 `cn.iocoder.yudao.server.YudaoServerApplication`: ```java @SpringBootApplication(scanBasePackages = { "com.clouds.common.redis", "com.clouds.common.threadpool", "com.clouds.common.web", "com.clouds.common.oss", "${yudao.info.base-package}.server", "${yudao.info.base-package}.module", "com.zteits.park", "com.zteits.order", "com.zteits.pay", "com.zteits.bill", "com.zteits.charge", "com.zteits.business", "com.zteits.sys", "com.zteits.ipay", "com.zteits.configuration", "com.zteits.wxcp", "com.zteits.glockcollect" }) @MapperScan(basePackages = { "com.zteits.park.mapper", "com.zteits.order.mapper", "com.zteits.pay.mapper", "com.zteits.bill.mapper", "com.zteits.charge.mapper", "com.zteits.business.mapper", "com.zteits.sys.mapper" }, nameGenerator = LegacyMapperBeanNameGenerator.class) ``` - `com.clouds.common.*` 必须排最前,否则存量 `CacheUtil` 静态字段被钉成 null,启动 NPE。 - `LegacyMapperBeanNameGenerator` 需要从老工程搬过来。 - 上游 `yudao.info.base-package=cn.iocoder.yudao` 保持不变。 - 租户关闭:`yudao.tenant.enable: false`。 - 审计字段:合并一个 `MetaObjectHandler`,同时填 `createTime/updateTime/createBy/updateBy` 与 `creator/updater`。 ## 7. 前端搬迁 从老 `parking-cloud-ui` 搬: ``` src/api/{park,order,pay,report,ops,system,index,login,ext} src/views/{park,order,finance,report,ops,member,assess,ext} src/components/ 下自研组件 src/locales/ 中停车文案 ``` 保留上游:`layout / components 基础组件 / utils / store / router / permission / config / styles`。 - 版本漂移:Vue 3.3→3.5、Element Plus 2.4→2.13、Vite 4→最新、UnoCSS 0.57→最新。 - 先搬高频 10 页跑通,再批量修剩余。 ### 7.1 兼容层 `src/utils/request.ts` 老 API 文件用 axios 原生风格 `request.post(url, data)`,URL 里已经带了 `/admin-api` (`ADMIN_API_PREFIX`);而上游 axios 实例的 baseURL 是 `VITE_BASE_URL + VITE_API_URL`,同样以 `/admin-api` 结尾。兼容层必须 **剥离 URL 里的前缀**,否则实际请求会变成 `/admin-api/admin-api/...` → 404。 (2026-11-02 实测踩到,已在 `stripApiPrefix` 里修掉。) ### 7.2 本地联调配置(`.env.local`) | 变量 | 值 | 说明 | |---|---|---| | `VITE_BASE_URL` | `http://localhost:48083` | 新后端 | | `VITE_APP_TENANT_ENABLE` | `false` | 后端 `yudao.tenant.enable=false` | | `VITE_APP_CAPTCHA_ENABLE` | `false` | 后端 `yudao.captcha.enable=false` | ⚠️ 端口:`VITE_PORT=80` 在 macOS 非 root 下绑不了;联调时用 `pnpm dev --port 5175`(5173/5174 可能被其他工程占用)。 ### 7.3 浏览器实测(2026-11-02) 用 playwright-cli 在 `http://localhost:5175` 实测: - 登录页 → `admin/admin123` → 进入 `/index`,无 401; - `/park/park-lot`:车场列表返回真实数据(中国传感网国际创新园停车场等 2700 泊位); - `/order/parking-record`、`/park/caryard-overview`、`/park/berth` 均有数据、无报错。 **全量页面巡检(同日)**:用 `page.goto` 逐页跑完 `system_menu` 里 55 个停车页面(订单 12 / 财务 11 / 运维 9 / 会员 4 / 报表 8 / 停车设施 6 / 考核·外部 2)+ 12 个系统页面,逐页收集 `console.error` 与 HTTP ≥400 的后端请求。结果:**0 个页面报错、0 个失败请求**;有数据的页面 (停车记录、车场、泊位、工单、巡检、会员卡、报表等)均渲染出真实行, 无数据的页面落「暂无数据」空态而非报错。 **第二轮巡检(同日,修正版)**:第一轮只看 `console.error` 与 HTTP ≥400,漏掉了 「HTTP 200 但响应体 `code=500`」的静默失败。第二轮改为捕获所有 `/admin-api` JSON 响应的 `code != 0`,58 个停车页面 + 12 个系统页面全部复跑, 修完后结果 **`[]`(0 个失败调用)**。详见 §8.1 的 #12–#15。 **侧边栏点击巡检(同日)**:不止按 URL 巡检,还从侧边栏逐项点进去。范围 = 迁移的 60 个停车页面(8 个停车目录 55 个 + 挂在芋道「系统管理」下的组织管理/收费员账号/ 消息中心/规则设置/运行概况 5 个),记录落地 `path`、是否出现「您访问的页面不存在」、 以及响应体 `code != 0`。结果:**60/60 正确落地,0 个 404、0 个失败调用**。 (脚本坑:路由切换后 el-menu 会自动收起子菜单,点击前必须重新展开;另 2 个报表项 因滚动/动画导致脚本点击超时,人工复核可直接进入,不是页面问题。) 巡检同时修掉 4 个「老页面写法 vs 上游组件契约」的前端兼容问题(改的都是共享层, 不是逐页改): | # | 现象 | 根因 | 修法 | |---|---|---|---| | 1 | 请求变成 `/admin-api/admin-api/...` 404 | 老 API 文件 URL 自带前缀,上游 axios baseURL 也带 | `request.ts` 加 `stripApiPrefix` | | 2 | `hasPermi` 指令抛 `permission.hasPermission` | 老页面写字符串(198 处),上游指令只收数组 | `hasPermi.ts` 同时接受 string / array | | 3 | `Missing required prop: "type"` | 老页面用 `` | 迁过来的页面统一 `` | | 4 | `value.trim is not a function`;表格里直接显示 `1506302273000` | 存量接口时间字段是**毫秒数字**,有时又是数字字符串;`formatDateTime` 原来只处理 `yyyy-MM-dd HH:mm:ss` 字符串 | `format.ts` 的 `formatDateTime` 同时识别 10/13 位数字与数字字符串,统一格式化为 `YYYY-MM-DD HH:mm:ss`,0/负数/null 显示 `-`;停车记录、异常记录、高位异常、违法记录、白名单、语音告警等时间列都改走它 | | 5 | 业务页面内容区**完全没有样式**(标题与数值竖排、表格无边框、无内边距) | 老工程 `main.ts` 全局引入了 `styles/tokens.css` + `styles/index.scss`(`--text-1`/`--fs-12`/`.stat-strip`/`.table-card`/`.search-field` 等 100+ 页共用的令牌与版式);新工程只搬了页面视图,两个全局文件没跟着搬 | 新增 `src/styles/parking-pages.scss` 并在 `src/styles/index.scss` 里 `@use`:业务令牌**映射到 Element Plus 变量**(`--text-1: var(--el-text-color-primary)`、`--brand-600: var(--el-color-primary)` …),页面随芋道主题(含暗色)走;`.stat-strip`/`.table-card`/`.search-field` 版式类原样保留,表格紧凑规则限定在 `.table-card` 内,不污染上游系统页。**没有**照搬老 tokens.css 对 `--el-*` 的整套覆盖(那会把整站改成政务蓝) | | 6 | 查询区还是老政务版式:label 在控件上方、控件通栏、按钮独占底部一行、空白过大 | 老 `SearchBar` 按「政务表单」设计;芋道标准是 `ContentWrap` + inline form(label 在左、控件 240px、搜索/重置跟在条件后同一行) | 重写 `components/SearchBar/index.vue` 外壳:flex 自动换行替代列数栅格、`搜索/重置` 带图标与条件同行;`styles/parking-pages.scss` 里 `.search-field` 改 inline-flex(label 90px 右对齐)、控件统一 `240px`。100+ 个页面用法不变,无需逐页改 | | 7 | 50 处 `userStore.userInfo?.nickname/username/orgId` 取到空值(打印/导出「操作人」为 `-`),`pnpm ts:check` 报 50 个 TS2339 | 老工程 store 暴露 `userInfo`;芋道 store 只有 `user`/`getUser()`,且 `get-permission-info` 只返回 nickname/deptId | `store/modules/user.ts` 新增兼容 getter `userInfo`(nickname 兜底 username、透出 id/deptId);**`orgId` 刻意不填**——老 orgId 是 `sys_org.id`,deptId 是另一套 id,把 deptId 当 orgId 传给存量接口会静默返回空数据,正确做法是不传、由后端 `LoginUserBridge` 换算 | ### 7.4 菜单 path 归一化(侧边栏 404 的根因) 现象:点侧边栏「停车设施 → 车场列表」,地址变成 **`/park/park/park-lot`**, 页面显示「抱歉,您访问的页面不存在」。 根因:老 `system_menu` 的子页 path 写的是**绝对路径**(`/park/park-lot`), 而 yudao 约定是「目录绝对路径 + 子页相对路径」(上游自己就是 `/system` + `user`)。 `src/utils/routerHelper.ts` 的 `pathResolve(parentPath, path)` 会无条件拼接: ```ts const childPath = path.startsWith('/') ? path : `/${path}` return `${parentPath}${childPath}`.replace(/\/+/g, '/') // /park + /park/park-lot ``` 所以只要子页 path 以 `/` 开头且目录已经有同名首段,就必然重复一段。 修法(两处,缺一不可): 1. **数据**:`system_menu` 里 `id>=100000` 的 60 条子页记录,path 去掉目录首段 (`/park/park-lot` → `park-lot`、`/order/parking-record` → `parking-record`、 `/system/org` → `org`、`/finance/bill` → `bill` …)。 2. **脚本**:`scripts/migrate-menus.sql` 的 INSERT 加 `CASE` 归一化, 重建库时不会再写回绝对路径: ```sql CASE WHEN type = 2 AND path LIKE '/%' AND LOCATE('/', path, 2) > 0 THEN SUBSTRING(path, LOCATE('/', path, 2) + 1) ELSE path END ``` (`type=2` 才是菜单页;`type=1` 目录保持绝对路径,`type=3` 按钮无 path。) 验证:重新登录后展开「停车设施」点「车场列表」,`location.pathname` = `/park/park-lot`,标题「芋道管理系统 - 车场列表」,0 条 console error。 ### 7.5 存量定时任务去重(`@Scheduled` 总开关) 老工程把不少业务逻辑挂在 Spring `@Scheduled` 上,搬进新工程后**默认会跟着启动**。 新旧服务同时在线时,同一份数据会被处理两遍(最危险的是往太极/西城等外部平台重复上报)。 代码里共 11 个 `@Scheduled`,实际会跑的只有 **6 个**(其余 `@Component` 或 `@Scheduled` 已被注释,等于停用): | 模块 | 类 | 频率 | 作用 | 状态 | |---|---|---|---|---| | park | `DealGeoInOutParkDataSchedule` | 1s | 处理地磁进出场接口日志(`TP_P_INTERFACE_LOG`) | 活跃 → 已加开关 | | park | `EqpOutDelayQueueMsgSchedule` | 10s | 地磁出场延迟队列(Redisson `RBlockingQueue`) | 活跃 → 已加开关 | | park | `TaiJiEqpTokenTask` | 30min | 西城太极设备 token 刷新 | 活跃 → 已加开关 | | park | `GetExceptionMsgSchedule` | — | 异常消息拉取 | 整类注释,未注册 | | order | `ArrearageSMSSendConfigTask` | 60s + 动态 cron | 刷新欠费催缴短信配置并按 cron 发短信 | 活跃 → 已加开关 | | order | `TaiJiUploadDeviceStatusTask` | 20min | 设备状态上报太极 | 活跃 → 已加开关 | | order | `TaiJiUploadRefreshTokenTask` | 30min | 太极 token 刷新 | 活跃 → 已加开关 | | order | `TaiJiUploadInOutParkRecordTask` | 5min | 进出场记录上报 | `@Component` 已注释 | | order | `TaiJiUploadXJCParkFlowTask` | 14min | 巡检车流水上报 | `@Component` 已注释(改用 xxl-job) | | order | `UserPayOutDelayQueueMsgSchedule` | — | 用户出场延迟队列 | 类在,`@Scheduled` 已注释 | | order | `OrderDelayQueueMsgOpServiceImpl` | — | 订单延迟队列 | 类在,`@Scheduled` 已注释 | **开关**(6 个活跃类统一加 `@ConditionalOnProperty`): ```java @ConditionalOnProperty(prefix = "parking.legacy.scheduler", name = "enabled", havingValue = "true") ``` ```yaml parking: legacy: scheduler: enabled: ${PARKING_LEGACY_SCHEDULER_ENABLED:false} # 默认关 ``` **默认 false 的理由**:本地/并行期 48082 仍在跑这些任务,新服务只做读写接口; 再加上新服务连的是 `parking_cloud_yudao` 副本库、Redis 用 db1,跑起来既无收益又可能 把副本库的数据当生产数据往外部平台推。注意有些任务自身还有业务开关 (`BJXC_TAIJI_UPLOAD` 为空、`DISTRIBUTED_TASK_PROP` 未配置时会提前 return), 但那是**数据层兜底**,不能替代结构性开关——副本库配置一变就会“复活”。 **验证(2026-11-02)**:重启前日志里 `延迟队列开始取数据` 每 10s 一条(累计 828 条)、 `欠费催缴短信…刷新` 每 60s 一条;加开关并重启(16:26:57 启动)后 70s 内上述关键字 **0 条**,其余 6 个任务也无任何调度线程日志,接口功能正常。 **正式切换步骤**(新服务独占生产时): 1. 停老服务(48082),确认没有其它实例还在连原库; 2. 把新服务数据源指到生产库,并把 Redis 切回生产者用的同一个 db (当前 local 用 db1 是为了并行期隔离;**只改 db 而不开开关,延迟队列仍然消费不到消息**); 3. 设 `PARKING_LEGACY_SCHEDULER_ENABLED=true` 并重启; 4. 按上表逐个确认日志:地磁延迟队列、太极 token/设备状态、欠费催缴刷新; 5. 多实例部署时**只在一个实例开**(这些任务没有统一分布式锁; `DealGeoInOutParkDataSchedule` 自带 `DISTRIBUTED_TASK_PROP` IP 取模,其余没有), 后续可整体迁到 yudao Quartz / xxl-job 再按任务开关。 ### 7.6 指挥中心(真实数据聚合) 原 iframe 壳已替换为自研页面,**菜单、路由、component 均未变**: `100498` / `/command-center` / `gov/commandCenter/index`。右上角「旧版大屏」仍指向 存量综合监测大屏(`VITE_APP_COMMAND_CENTER_URL`,缺省 `http://139.224.115.129/.../systemFirstPage.html`),过渡期可对照。 2026-11-02 按用户提供的「赤峰市智慧停车数据中心」大屏重做版式,随后按「站在政府角度、 重排整页比例」的要求调整为**通栏 KPI + 左(供给与规划)/ 中(城市态势)/ 右(运行与服务)** 三段式。**2026-11-06 起接真实数据**:`demo.ts` 已删除,页面只读后端聚合接口 `POST /park/command-center/overview`(一次返回整屏),60 秒轮询 + 手动「刷新」。 左上角标识由「演示数据」改为绿色「实时数据」,接口失败时变粉色「数据加载失败」并保留上次数据。 | 项 | 内容 | |---|---| | 页面 | `src/views/gov/commandCenter/index.vue`(视图) + `mapper.ts`(响应 → 页面模型) | | 接口 | `POST /admin-api/park/command-center/overview`;`ParkCommandCenterController`,权限沿用 `park:dashboard:query`(**不新增权限点、不授权任何角色**) | | 聚合 | `ParkCommandCenterServiceImpl`;SQL 在 `ParkCommandCenterMapper` + `mybatis/mapper/commandcenter/ParkCommandCenterMapper.xml`,车场/实时/订单汇总复用 `ParkDashboardMapper` | | 前端数据源 | `src/api/park/commandCenter/index.ts` → `queryCommandCenterOverview()`;TDD 门禁 `tests/verify-command-center-mapper.mjs` | | 路由 | `/command-center`(顶级菜单,type=2,parent_id=0,sort=5,`keep_alive=0`) | | 菜单 id / 种子 | `100498` / `scripts/add-command-center-menu.sql`(幂等) | | 角色授权 | `scripts/seed-role-templates.sql` 给运营(2001)显式加了 `100498`;超级管理员天然全量 | | 地图数据 | `src/assets/map/json/chifeng.json`(DataV 赤峰市 `150400_full`,12 个旗县区,随包发布) | **口径与诚实原则**(改这里之前先读): - 在用停车场 = `parking_lot.data_status = 1 AND state = 1`;订单 = `order_park.data_state = 1`, 取消(`-50`)不计入收入/欠费;金额字段是**分**,后端统一转元。 - 实时泊位取 `parking_lot_berth_temp_info`。**原始导入**状态下该表为空 → 剩余泊位 / 占用率 / 地图标注卡 / 重点车场 TOP10 一律 `null`,页面显「—」或「暂无数据」,**不写 0、不造演示值**; 同理无用户表 → 用户三项 `null`。后端用 `unavailableModules` 把这些模块名回传给页脚。 (2026-11-06 起本地跑过演示数据脚本,该表有值,页面不再是空态,见 §7.11。) - 支付构成取**在线支付表 `pay_order`**(`pay_order_state = 40` 成功、`order_big_type = 100` 停车订单、`create_date` 当日)。**不要改用 `td_b_pay_order`**:那是 2017 年的历史表 (`pay_order_state = 1`),且没有 `pl_no`,按车场关联会直接 SQL 报错。 `pay_order_success` 是归档表,同样不用。`pay_type` 33 本地未知,落到「其它」。 版式:**顶部通栏 7 个 KPI**(竖分隔线)+ 下方三栏城市态势。标题签仍为顶部居中的异形 (两端斜切)标签,面板为深空蓝半透明底 + 内描线 + 四角折线,页面底为星点纹理 + 顶部斜纹。 | 位置 | 面板 | 真实口径 | |---|---|---| | 通栏 | KPI ×7 | 停车场总数 / 停车泊位总数 / 今日进场车次 / 当前剩余泊位 / 泊位占用率 / 平均周转率 / 今日收入(≥1 万元切「万元」并保留 1 位小数,否则「元」取整) | | 左 | 泊位供给结构 | 总泊位数 / 路侧泊位 / 封闭泊位 + 「在用泊位合计」;副标题给 `dataThrough`(订单数据截止时间) | | 左 | 各区联网接入进度 | 按区县的**同心环**(外圈向内递减)+ 中心「已接入 40」+ 右侧「已接入/计划」列表与百分比;副标题「今日有订单 40 / 40(100%)」。分母是各区在用停车场,分子是当天有订单的车场 | | 左 | 重点区域占用率 | **TOP 10**(按占用率降序),每页 5 条、5 秒轮播,悬停暂停;无实时泊位数据时整块显示「暂无实时泊位数据」 | | 左 | 各区停车时长分布 | ≤30 分钟 / 30–60 / 1–2 小时 / >2 小时,按区县堆叠;时长优先 `order_park.parking_duration`,缺省用出场 − 入场现算 | | 左 | 各区今日收入排行 | 按区县今日收入降序(来源同上,含无实时数据的区县);副标题合计(万元,按千元取整) | | 中 | 全市停车占用率分布 | **12 旗县区完整轮廓**;只有拿到实时占用率的区县才按分档着色并出四角标注卡(车场数 / 泊位数 / 占用率 / 周转率 + 引线),其余深色底、弱化标签;无任何实时数据时整块「暂无实时占用率数据」 | | 中 | 24 小时进出场车流 | 进场(青)/ 出场(绿)柱 + 停车指数(黄)线(右轴 0–100%,无泊位数据时为空),右上「今日累计进场 … 次」 | | 右 | 实时进出场记录 | 最近 8 条(车牌 / 车场 / 时间 / 进·出标签),取 `order_park` 按 `COALESCE(park_out_time, park_in_time)` 倒序;60 秒随整屏刷新,不再本地模拟推送 | | 右 | 用户与支付 | 累计用户 / 今日活跃 / 昨日新增(本库无用户表 → 三项均「—」)+ 支付方式**环形图**,图例按当日笔数占比降序(本地:微信公众号 / 支付宝 / 微信 / 个人账户) | | 右 | 运行预警 | 设备离线 / 满位车场 / 数据延迟区县 / 欠费逃逸订单 四格:常规高度 2×2,视口高 ≥1100px 改 4 行通栏;图标按后端稳定键 `device/full/stale/arrears` 取,色调固定不随数据顺序跳动 | 三栏按 `1.05fr / 1.85fr / 1.05fr` 配比——**左右两栏等宽,地图栏才落在页面正中**; 左栏下限 300px、右栏下限 350px,保证记录列表四列不挤。左列 5 个面板按 `0.66 / 1.18 / 1.02 / 0.95 / 0.95` 分配高度,中列地图 `1.62` / 车流 `0.88`, 右列记录 `1.38` / 用户与支付 `1.15` / 预警 `0.87`。 **视觉规格(2026-11-02 定稿)**: - 标题 `SCREEN_TITLE = 赤峰市智慧停车数据中心`(换城市改这一处);顶栏左侧「实时监测 + 实时数据」、右侧日期/星期/时钟与「刷新 / 全屏 / 旧版大屏」。 - 配色:底 `#050C1C`、正文 `#DCEBF9`、弱化 `#8AA9C9`、描线 `#2D5C86`、主青 `#35D6FF`、 蓝 `#4D8DFF`、绿 `#5BE3B3`、黄 `#FFD166`、橙 `#FF9F43`、粉 `#FF8FB1`、紫 `#B79CFF`。 - 地图为**星空深空底**(`#05101e` + SVG 星点平铺 + 两团星云径向光晕,**不放横竖网格线**), 注册完整 12 区(`chifeng-full`,不动全局 `chifeng` 注册)。有数据的 4 区按占用率分档: `<50%` 绿 `#2BD9A4` / `50–70%` 青 `#35D6FF` / `70–85%` 黄 `#FFC11E` / `>85%` 红 `#FF5A5A`, 白色描边 + 同色外发光;其余 8 区 `#0C253C` 深底 + 弱化标签。红山 / 元宝山两块小区相邻, 标签按 `FOCUS_LABEL_OFFSET` 外移(红山 `[0, 8]`、元宝山 `[30, 12]`),避免两块相邻区县的 标签互相压字(此前红山偏移到了松山区内,地图上会看到两个「红山区」字样)。 - **地图悬停联动**:鼠标滑过任一有数据的区县 → 区域颜色提亮 + 白色描边加粗(2.6px)+ 同色外发光,区名标签放大到 13.5px/700;同时对应四角标注卡高亮(边框换成分档色、 上浮 2px)并与引线同步加粗发光(`is-active`)。反向从标注卡悬停 → `dispatchAction` 高亮地图区块;移出后统一 `downplay` 复原。无数据区县悬停仅轻微提亮、不弹卡。 - 四角标注卡为青边圆角卡(区名分隔线 + 2×2 指标:车场数 / 泊位数 / 占用率 / 周转率), 引线颜色跟随占用率分档;落点不是简单连形心——按「离卡片最近的外环顶点 → 形心」取中点, 避免红山 / 喀喇沁两条引线横穿相邻区县交叉。引线在 `ResizeObserver` / 窗口变化后 用 `convertToPixel` 重算,全屏与 1366 下都不会错位。 - 右列「用户与支付」用 ECharts 环形图(`#08182C` 分隔线、中心「N 种方式」由实际支付方式数决定) 替代原单条堆叠条,图例竖排;「运行预警」图标 + 数值按面板高度放大,名称在左、数值/单位在右 撑满整格;面板高度比从 `1.45/1.02/0.78` 调为 `1.38/1.15/0.87`,与左侧面板节奏对齐。 - **分辨率自适应**:卡片文字/图标统一用 `clamp(最小值, min(vw, vh), 最大值)`(宽或高任一方向变小时先收敛,1366 用最小值、 4K 用最大值兜底);面板内边距、进度条、圆环直径等按 `vh` 缩放;ECharts 不继承 CSS, 用 `chartPx()` 按 `min(宽比, 高比)` 同步缩放图例/轴标签/地图标签,窗口尺寸变化后 180ms 防抖重绘。`max-height: 820px` 保留 1366×768 紧凑规则;`max-height: 760px` 再收紧页头/列表。高屏(`min-height: 1100px`)预警改 4 行通栏,避免 2×2 大卡留白。 已验证 1280×720 / 1366×768 / 1600×900 / 1920×1080 / 1920×1200 / 2000×1253 / 2560×1440 / 3440×1440 / 3840×2160 均无页面滚动、无面板内容溢出。 - **保留的动效**:① 重点区域占用率取 TOP 10 分两页(5 条/页),`translateY` 每 5 秒翻页、 悬停暂停,右下角圆点指示当前页;② 面板进入时的数字/条形过渡;③ 60 秒整屏轮询后重绘, 不做本地模拟推送、不做 KPI 抖动——`demo.ts` 时期的模拟记录流与 KPI 波动已删除。 **接入结论(2026-11-06)**: 1. `demo.ts` 已删除,页面模型改由 `mapper.ts` 从接口响应生成;旧 `useCommandCenter.ts` 保留但本页不再引用。 2. 记录列表不展示抓拍图;如需巡检再按 `getOrderImages(orderId)`(键 `4` 进场 / `5` 出场)补图,车牌按存量规则打码。 3. 本地缺数据的模块(实时泊位/占用率、重点车场、用户指标)不补数,后端在 `unavailableModules` 里列明、页脚展示。 **接入时的数据陷阱(提前记录)**:存量实时接口对**没有实时缓存的车场**返回 「泊位数 + 其余全 0」,直接按 `(泊位-空闲)/泊位` 会算成 100%。新聚合接口只把 `parking_lot_berth_temp_info` 有行的车场计入占用率,无行即 `null`;改 SQL 时 不要用 `COALESCE(..., 0)` 兜底。 **两处已知缺口(沿用旧版结论)**: 1. 存量「本月各区车场评分排名」在本栈没有对应数据源。`ParkDashboardController` (`/park/dashboard/overview`)能算,但权限点 `park:dashboard:query` 在 `system_menu` 里没有记录,新增菜单并授权运营属于权限变更,未经确认不做。 **新版式已把「评分排行」从页面移除**(换成「各区今日收入排行」),该权限缺口不再阻塞本页。 2. 存量「当日 APP 服务次数」没有等价字段,右侧改用「今日活跃」(`todayActiveNum`, 按 `modfiy_date` 统计),并补「昨日新增 / 昨日活跃」。 **本地数据现状**:原始导入只有红山区 40 个在用车场、3,720 个泊位、 `parking_lot_berth_temp_info` 为空 → 实时泊位/占用率/重点车场全部空;当日进场 4,889 次、 出场 2,899 次、收入 2,464 元;支付构成 4 种(微信公众号 670 笔占 89.5%);欠费逃逸订单 546 单; 无用户表。这是本地数据覆盖范围,不是页面缺陷。跑过 §7.11 的演示数据脚本后变成 109 个在用车场 / 16,210 个泊位 / 12 个旗县区,红山区的车场与订单仍是真实导入数据。 **验证(2026-11-06)**:`POST /admin-api/park/command-center/overview` 返回 `code 0`, 字段与页面一致(40 / 3,720 / 4,889 / 2,464 元 / 红山区单区 / 支付 4 项 / 预警 0·0·0·546); 灌入 §7.11 演示数据后同一接口返回 109 / 16,210 / 21,710 / 4,655 / 70.0% / 1.34 / 24,643 元, 12 个旗县区齐全,预警 0·3·1·1,927,支付 5 种,1920×1080 与 1366×768 截面无串版 (**2026-11-06 复核后数字变为 71.3% / 28,349 元**:占用率口径与精度已修,见 §7.15; 收入随演示数据重灌而变化); `tests/verify-command-center-mapper.mjs` 通过,前端 16 个 verify 脚本全绿, `vue-tsc` 在 `commandCenter/*` 0 error,ESLint 干净;浏览器登录 admin 后实测 1920×1080 与 1366×768 均无页面滚动、控制台 0 error(仅 4 条既有 Vue Router 弃用告警), 空态显示「暂无实时泊位数据 / 暂无实时占用率数据 / —」。 **验证(2026-11-02,版式阶段)**:admin 与 ops001 登录后页面 6 个接口全部 HTTP 200, 控制台 0 error;8 种分辨率下截图核对,面板、地图标签、车流轴均无遮挡,页面无纵向滚动; `pnpm ts:check` 在 `commandCenter/index.vue` 0 error(仓库其他历史页面仍有既有类型告警), `pnpm build:local` 通过。 **权限注意**:运营角色直接拥有 `100426/100427`,接口 `@PreAuthorize` 走 `menu_role_ids` 缓存因此通过;但这两个按钮的父页面 `100424` 挂在「系统管理」下且 未授权给运营,`get-permission-info` 的权限串里不会出现 flow 项。本页未使用 `v-hasPermi`,所以不影响。以后若给页面按钮加 `v-hasPermi`,需同时授权父链。 ### 7.7 组织 id 空间统一(2026-11-02) **问题**:登录态 `deptId` 来自 `system_users.dept_id`(本地 103,芋道演示部门), 而存量业务表存的是老 `sys_org.id`(10003 等)。之前靠 `parking.legacy.org-id-mapping=103:10003` 在 `LoginUserBridge` 里硬换算, 切生产必须重配,配错会**静默查错组织的数据**(不报错、只是结果不对)。 **改造**(`scripts/migrate-org-ids.sql`,幂等): 1. `sys_org` → `system_org`,真实 id 原样保留(242 行,152 个有效;单个根 10001); 2. `system_users.dept_id` 的 `103` → `10003`(admin + 3 个联调账号); 3. `system_org` 写穿透到 `system_dept`(镜像)——yudao 用户模块仍读它; 4. 软删芋道演示部门 `100~125`,隐藏「部门管理」菜单(组织统一从「组织管理」维护); 5. `application-local.yaml` 的 `org-id-mapping` 置空 ⇒ 恒等映射,启动日志确认。 **为什么保留 `system_dept` 镜像**:`AdminUserServiceImpl` 仍通过 `DeptService` 读 `system_dept` 做用户表单组织下拉、用户列表子树过滤、保存用户时部门校验。 替换整条链路风险大;镜像 + 写穿透是这里最小、可回退的方案。镜像只在 `OrgServiceImpl` 的 create/update/delete 里同事务维护,读路径仍以 `system_org` 为准。 **顺带修掉的两个存量 bug**(改组织 CRUD 时暴露): - parking 的 `BaseDO` **没有生效的 `MetaObjectHandler`**:容器里只有 yudao 的填充器, 只认 `cn.iocoder` 的 BaseDO,导致 `system_org` 的 INSERT 显式写 `create_time=NULL` 触发非空约束 —— 「新增组织」原来是 500。现在 `OrgServiceImpl` 显式补审计字段。 - parking `UserDO` 的两个列名写错:`orgId` 实际列是 `dept_id`、`delFlag` 实际列是 `deleted`(`system_users` 沿用的是 yudao 表)。不修的话「删除组织」检查 「组织下是否有用户」时 SQL 直接报错。已用 `@TableField` 纠正。 **验证**:组织树 1 根 / 10003 下 11 个子组织;`dept/simple-list` 152 条; 四个用户 `deptId=10003`;组织新增/改名/删除时 `system_dept` 同 id 同步出现/消失; `ops001` 登录后 `get-permission-info` 的 `deptId` 为 10003,存量接口仍 `code=0`。 迁移后需清 `oauth2_access_token:*`(旧 token 里还是 103)。 ### 7.8 首页监管总览(2026-11-02) **页面**:`parking-cloud-yudao-ui/src/views/Home/Index.vue`(静态路由 `/index`,名称 `Index` 不变), 按老库原型 `parking-cloud-ui/docs/design/prototype/01-dashboard.html` 复刻 8 个区块。 样式全部收在 `.gov-dashboard` 下(`src/views/Home/dashboard/dashboard.scss`), 浅色用政务蓝 `#14539a`,暗色用 `--el-*` 暗色令牌 + 校验过的图表色,不影响站内其它芋道页面。 **后端**:`parking-module-park` 新增 `POST /admin-api/park/dashboard/overview`(`ParkDashboardController`,权限 `park:dashboard:query`), 一次返回六张指标卡、24 小时/逐日车流、三级预警、区县排名、考核指标、重点车场、设备在线率、数据时效。 实现类 `ParkDashboardServiceImpl`,SQL 在 `mybatis/mapper/dashboard/ParkDashboardMapper.xml`。 **口径**(页面底部同步展示): | 指标 | 来源 | |---|---| | 在用停车场 / 泊位 | `parking_lot`,`data_status=1 AND state=1` | | 订单 / 欠费追缴率 | `order_park`(排除 `-50`;欠费 52、已补缴 53) | | 工单闭环率 / 逾期 | `order_work`(`solve_state=3`;逾期 = 超 24h 未办结)。**JOIN `parking_lot` 后只统计在用车场**,与页面「接入车场」口径一致,避免把已停用车场的历史工单算进预警 | | 设备在线率 | `parking_lot_equipment`(`equipment_status=1`) | | 实时占用率 | `parking_lot_berth_temp_info`,无实时行返回 null(页面显示「—」) | | 车流 / 占用率曲线 | `parking_running_statistic`,`data_source=10`;今日 `time_cycle=40` 按小时,本周/本月 `50` 按天 | **三级预警是实时推导,不是预警表**:红 = 车场设备离线率 ≥30%(设备 ≥5 台)或占用率 ≥95%; 橙 = 占用率 ≥85% 或工单超 24h;黄 = 设备离线率 ≥10% 或区县欠费率 ≥15%。 阈值集中在 `ParkDashboardServiceImpl` 常量区,后续接预警规则表时替换 `buildAlarms` 即可。 页面显式标注「实时推导 · 非工单流转」,不伪造处置状态与响应时长。 **暂无数据源的模块**:数据上报及时率 / 上报记录(无对应表)。响应里用 `unavailableModules` 声明,页面显示「—」并在底部说明,**不使用模拟值**。 **权限**:`scripts/add-dashboard-permission.sql` 幂等插入隐藏目录 + 按钮 (`park:dashboard:query`,id 310900/310901),角色管理可勾选;超管天然可见。 **本地验证注意**:本地 `parking_cloud_yudao` 是稀疏样本(4 个在用车场、订单集中在 2017–2018), 因此「今日」指标多为 0、车流图为空态,属预期;页面必须能正常展示空态与「—」。 实测 `{}` / `{"region":"红山区","period":"month"}` / `{"period":"week"}` 三种入参均 `code=0`, 且切区县后 `regions` 仍返回全量区县(否则下拉只剩当前区县,回不到全市)。 已修的两个运行期问题: 1. `Map.entry(lot, null)` 抛 NPE(`Map.entry` 不接受 null 值),改用 `record LotRate` 先过滤再排序; 2. 逾期工单原本只按 `pl_country_name` 过滤,会把不在用车场(如丹阳市)的 2018 年工单算进来, 现统一 JOIN `parking_lot` + `activeLotCondition`,本地逾期数因此为 0(4 个在用车场无工单)。 ### 7.9 地图区县配色(2026-11-04) 「停车资源一张图」「专题分析」「指挥中心」三张地图的区县底色不再写死中心四区,改为**全市 12 个旗县区各一色**, 并且颜色与开关由后端参数控制,前端只做兜底。 | 项 | 内容 | |---|---| | 参数键 | `parking.map.district-theme`(基础设施 → 参数管理,`visible=1`,登录用户可读) | | 种子脚本 | `scripts/add-district-theme-config.sql`(幂等) | | 参数结构 | `enabled` 总开关(默认 `true`);`off` 关闭高亮的区县数组;`colors` 单区县颜色覆盖 | | 前端 | `parking-cloud-yudao-ui/src/views/gov/shared/district-theme.ts`(纯逻辑 + 默认色表)、`district-theme-loader.ts`(读参数、单次缓存) | | 接入页面 | 一张图 `oneMap/useOneMap.ts`、专题分析 `topicAnalysis/useTopicMap.ts`(高德 `Polygon`)、指挥中心 `commandCenter/index.vue`(ECharts `map`,用 `toRgba()` 输出带透明度 `areaColor`) | | 兜底 | 参数缺失 / JSON 损坏 / 接口失败 → 全部高亮 + 默认配色,地图不会失色 | 默认色(中心四区沿用智慧中心):红山区 `#FF5A5A`、松山区 `#FFC11E`、元宝山区 `#35D6FF`、 喀喇沁旗 `#2BD9A4`、阿鲁科尔沁旗 `#6C8DFF`、巴林左旗 `#B79CFF`、巴林右旗 `#FF9F43`、 林西县 `#FF7AC6`、克什克腾旗 `#A3E635`、翁牛特旗 `#2DD4BF`、宁城县 `#E879F9`、敖汉旗 `#9AA7C7`。 填充透明度:中心四区 0.74、其余区县 0.44(街区尺度分别淡出到 0.12 / 0.08), 保证 12 块颜色区分明显又不遮挡车场点位。指挥中心有实时数据的 4 个区仍用白描边、白色区名, 其余 8 个区填充更淡、标签弱化;关闭高亮后回到指挥中心原样式(有数据的区按占用率分档、其余深色底)。 参数改动在**下一次页面加载**生效(前端按页面生命周期缓存一次请求,避免两张地图重复取参数)。 ### 7.10 视频监控前端(演示数据版,2026-11-04) 方案见 `docs/superpowers/specs/2026-11-04-视频监控模块设计方案.md`,计划见 `docs/superpowers/plans/2026-11-04-视频监控前端实施计划.md`。**当前只做前端**,后端 `parking-module-video` (GB28181 / WVP-PRO + ZLMediaKit)待页面确认后再落地;接口契约、错误码、数据表已写进方案 §6,前端按契约类型消费演示数据。 | 页面 | 路由 | 组件 | 内容 | |---|---|---|---| | 视频监控 | `/video/monitor` | `gov/videoMonitor/index` | 1/4/9 宫格视频墙、三级设备树(12 区县 → 车场/路段 → 112 台设备)、收藏、双击最大化、宫格右键菜单、历史回放时间轴、地图点位 + 设备卡、云台控制、视频轮巡 / 巡逻方案 | | 视频设备 | `/video/device` | `gov/videoDevice/index` | 设备台账:区县 / 类型 / 在线状态筛选、分页、预览跳转、新增 / 编辑 / 删除 / 批量启停(内存态)、编辑抽屉 | - 前端目录 `parking-cloud-yudao-ui/src/views/gov/videoMonitor/`:`types.ts`(契约)、`model.ts`(纯函数: `deviceTree` 三级树、`favoriteDevices` 收藏、`buildPatrolOrder` 巡逻分组)、`demo.ts`(确定性演示数据,112 台)、 `useVideoMonitor.ts`(页面状态)、`components/`(设备树、视频墙、播放器、回放条、地图、云台、巡逻)、 `video-monitor.scss`(非 scoped,统一 `vm-*` 类)。 - 设备树为三级:区县 → 车场/路段(车场在前)→ 设备(在线优先、中文名排序);收藏存 `localStorage['video-monitor:favorites']`,只存设备 id,后端上线后迁到表。 - 「轮巡」与「巡逻方案」是两个功能:轮巡按当前筛选结果 + 宫格大小自动翻页(工具栏,15/30/60 秒), 巡逻方案是右栏预置设备集;两者互斥,手动播放 / 回放 / 切地图都会停止轮巡。 - 播放画面为 canvas 合成的示意监控画面(车场 / 路侧两种场景),不依赖网络与二进制样片;配置 `VITE_DEMO_VIDEO_URL` 可改用循环样片。协议无关边界已留好:`PlaySource.protocol`(`webrtc` / `flv` / `hls` / `demo`),后续只换 `VideoPlayer` 实现。 - 菜单种子 `scripts/add-video-menu.sql`:`system_menu` 100502 `/video/monitor`、100503 `/video/device`, `keep_alive=0`(页面带定时器与地图实例),同时授给运营角色 2001;幂等,已在 `parking_cloud_yudao` 执行。 - 验收:`parking-cloud-yudao-ui/tests/verify-video-monitor.mjs` 全绿;1920 / 1366 / 1200 宽无横向滚动; 宫格切换、最大化 + Esc、回放暂停与空录像日、地图模式 + 设备卡实时/回放、巡逻启动、轮巡推进与停止、 三级树折叠、收藏 + 只看收藏、宫格右键菜单、枪机云台置灰、设备台账新增/编辑/删除/批量启停均实测通过。 - 演示数据每周第 7 天(如 2026-11-05)无录像,用于验证「当日无录像」空态;设备坐标在区县锚点 ±0.15° 内展开, 便于在地图上看出点位分布。 ### 7.11 本地演示数据:11 个旗县区(2026-11-06) 本地副本库只导入了红山区,监管中心 / 指挥中心 / 综合监测里其余 11 个旗县区全是空白。 `scripts/gen-demo-district-data.py` 给**本地** `parking_cloud_yudao` 补一批可识别、可回滚的 演示数据,让 12 个旗县区的版式、图表、地图着色都能看到真实量级的效果。 ```bash python3 scripts/gen-demo-district-data.py --dry-run # 只打印计划(默认) python3 scripts/gen-demo-district-data.py --apply # 先清旧演示数据,再重建 python3 scripts/gen-demo-district-data.py --clean # 只删演示数据 ``` | 项 | 内容 | |---|---| | 规模 | 11 个旗县区共 **69 个演示车场 / 12,490 个泊位**;含红山区后全库 109 个在用车场、16,210 个泊位 | | 车场 | `parking_lot`:`pl_no` 以 `P1504` 开头、`remark = 本地演示数据(脚本生成)`;约 6 成路侧(`pl_type=1`)/ 4 成封闭(`pl_type=2`),占用率 0.35–0.94,每 19 个车场有 1 个 ≥95%(让「满位车场」预警非 0) | | 坐标 | `parking_lot_geo`,每个车场一条,取旗县区政府驻地 ±0.05° | | 实时泊位 | `parking_lot_berth_temp_info`,**覆盖全部在用车场**(红山区按 `pl_no` 稳定哈希给 0.62/0.74 基准占用率),页面不再落空态 | | 订单 | `order_park`,只写入演示车场、只写「今天 00:00–现在」,按 24 小时进车权重分布;状态分布 46% 完成(50) / 34% 在场(10) / 8% 待支付(20) / 10% 逃逸(52) / 2% 已补缴(53);每个车场额外补 1 条最近 25 分钟内的在场单,避免「数据延迟区县」误报 | | 支付 | `pay_order`,已完成且有费用的订单生成支付单(`order_big_type=100`、`pay_order_state=40`),5 种 `pay_type` | | 运行统计 | `parking_running_statistic`(`data_source=10`):`time_cycle=40` 按小时 1,526 行 + `time_cycle=50` 日汇总,由当天真实订单聚合 | **口径(与真实数据保持一致,改脚本前先看)**: - 金额单位是**分**,`order_park.parking_duration` 单位是**秒**(早期写成分钟会把时长全部落进 「≤30 分钟」一档)。 - 收入只算 `order_state = 50/53` 的已完成订单,取消(`-50`)不计;首 30 分钟免费, 之后按小时向上取整计费(路侧 200 分/时、封闭 300 分/时)。 - 演示车场 `state = 1`、`data_status = 1`,与真实车场同口径参与统计; 它们的 `remark` 与 `pl_no` 前缀是唯一识别标记。 **安全边界**: - 脚本只从 `application-local.yaml` 进程内解析本地库连接,不打印凭据,不连 `parking_cloud`; 共享库 / 生产库禁止执行 `--apply`。 - `--apply` 先按演示标记删旧数据(`order_park` / `pay_order` / `parking_lot_berth_temp_info` / `parking_lot_geo` / `parking_running_statistic` / `parking_lot`),再重建,可重复执行。 - 红山区只补 `parking_lot_berth_temp_info` 与 `parking_running_statistic` 两张 缓存 / 统计表,**不动它的 `parking_lot` 与 `order_park`**——那里是真实导入数据。 **已知副作用(诚实记录)**: 1. 红山区的实时泊位、运行统计是脚本合成的(真实车场没有实时上报),所以它的占用率 是估算值;订单、支付、车场清单仍是真实的。演示答辩时若被追问,按这个口径回答。 2. 演示订单只覆盖「今天」,**次日看板会回到接近空态**;演示前重跑一次 `--apply`。 3. 红山区真实订单流停在上次导入时刻,所以「数据延迟区县」恒为 1(红山区),这是真实缺口, 不是脚本 bug。 4. `scripts/warm-cache.sh` 与此无关(演示数据只进 MySQL),但改完真实车场数据后仍需预热缓存。 ### 7.12 重点车场占用率监测:实时数据兜底(2026-11-06) `/park/keyLotMonitor` 之前是「47 监测车场 / 0 行 / 暂无可计算的占用率」。原因不在前端: `ParkingLotQueryServiceImpl#queryParkinglotBasicFactsByPlNos` 的 `berthNum / berthFreeNum` 是从 Redis 实时快照(`PLRealTimeStatusCacheUtil`)整段 copy 的,而写这份快照的 XXL-Job `PLRealTimeStatusCacheJob` 在新栈不跑(§7.5),Redis 里没有 key。 处理方式:新增 `LegacyRealtimeBerthFiller` (`com.zteits.parking.module.park.service.dashboard`),在 `IndexRealTimeController` 的 查询与导出两个端点里兜底—— - **只在存量快照没值(`berthNum == null`)时补**;Redis 有值(生产环境)整段跳过,不改变存量行为; - 取 `parking_lot_berth_temp_info`(`ParkDashboardMapper#selectRealtime`),与指挥中心、综合监测同源, 同一时刻三张页面的占用率一致; - `freeBerthsStateDesc` 逐行照搬存量公式(`1 - round2(空余/总数)`,0 停满 / <0.20 紧张 / 其余充足)。 这个公式对全空车场会算出「停满」,但存量就是这口径,且前端只在 `caryardOverview` 的 TS 类型里 声明过该字段、从未渲染 —— 不顺手改口径; - 今日进出(`inCarNum / outCarNum`)同样在 Redis 快照缺失时兜底:用 `order_park` 现算「今日 00:00 至今」进场 / 出场台次(`ParkDashboardMapper#selectTodayFlowByLot`), 与指挥中心「今日进出场」同一张表、同一口径,**不是补零**。存量任务统计的是 `park_in_out_record`,两者在停车业务里一一对应;上线后 Redis 快照有值时不走这条兜底。 页面上另有两块指标「泊位周转(次/日)」「平均占用率」,接口取的是 `parking_running_statistic` 里**最近 10 分钟**(`time_cycle = 10`)的聚合行,本地没有这类行 → 恒为「—」。 前端 `occupancy.ts` 新增 `summarizeDaily`,在接口返回 null 时用同一次返回的今日小时统计兜底: 平均占用率 = 各小时 `1 - freeRatio` 的均值(与存量 SQL `AVG(1 - free_ratio)` 同口径), 周转 = 最后一条小时的 `turnoverRatio`(当日累计,取平均会低估)。接口有值时优先用接口值。 TDD 门禁:`tests/verify-key-lot-occupancy.mjs`(先失败后通过)。 验证(2026-11-06):用页面实际取到的 47 个车场请求 `query-parkinglot-basic-facts-by-pl-nos`, 47/47 都有 `berthNum / berthFreeNum`,今日进出与 `order_park` 逐车场核对一致 (如林西镇中心 317 / 166);页面变成 47 监测车场 / 11 超阈值 / 平均 74.4%, Top 20 排名、24 小时占用曲线、两块指标全部有值。1366×768 下右侧曲线原本被面板裁成一条底边 (`.klm-body` 只剩 155px,被 `.klm-table-wrap` 的 `max-height: 38vh` 挤压), 加 `@media (max-height: 900px) { .klm-table-wrap { max-height: 26vh; } }` 把高度让给曲线, 1366×768 与 1920×1080 均已实测。 注意:**演示数据 `--apply` 之后仍要跑一次 `scripts/warm-cache.sh`**。本页车场清单与 `plNo` 回填都走存量车场缓存(`ParkLotCacheUtil`),新加的车场不在缓存里时 DTO 的 `plNo` 为 null,接口会按存量口径返回空列表 —— 这次就是先被这个坑挡了一次,跑完预热才出数。 ### 7.13 指挥中心「各区联网接入进度」:12 个旗县区一屏看全(2026-11-06) 补完 11 个旗县区数据后,这块面板的缺陷才显出来:`dc-access` 是「同心环 + 明细列表」两栏, 列表每行把「区名」和「已接入数」上下两行堆叠,12 行共约 317px,而 1080p 下面板内容区只有 138px —— 一屏只能看到 5~6 个区,其余要在面板内滚动;大屏没人会去滚,等于丢了 6 个区。 改法(只动 `src/views/gov/commandCenter/index.vue` 的样式,接口与模板结构不变): - **行改单行**:`.dc-access-info` 由纵向 flex 改横向 baseline,「区名 3/5 60%」一行读完,行高 21px → 12px; - **列表自动分栏**:`grid-template-columns: repeat(auto-fill, minmax(120px, 1fr))`, 列宽够就两栏(1920×1080 起),不够自动退回一栏,不用写死断点; 行间距用 `align-content: space-between` 铺满面板高度,与同心环上下对齐; - **环那栏按环的实际直径取值**:`--dc-ring-size: clamp(96px, 11.7vh, 180px)`(与 `ringStyle()` 同一套 clamp),原来固定 1.05fr 分掉一半宽度、环两侧各空 40px,现在把宽度让给列表; - **同心环补中心圆盘**:`.dc-rings-center::before` 用同一套尺寸算出内径,盖住第 4~6 圈, 「109 / 已接入车场」不再被 20~48px 的小环压在下面(原来字和环糊在一起); - **1728 以下单栏滚动时底部渐隐**(`@media (max-width: 1710px)` 的 `.dc-access::after`): 一栏装不下 12 行,硬切会像渲染坏了,渐隐提示下面还有内容。 为什么不在窄屏也两栏:1366×768 左列只有 308px,扣掉环的 100px 后列表 190px, 两栏各 85px,「阿鲁科尔沁旗」只能显示成省略号 —— 宁可滚动也不截区名。大屏的标准尺寸是 1920×1080(及以上),那一档必须一屏看全。 **同页补修:1366×768 整页多出 50px 滚动**(2026-11-06,量 `v-layout-content-scrollbar` 的 `scrollHeight - clientHeight` 发现)。`@media (max-height: 820px)` 里给 `.command-center` 写的是 `height: calc(100vh - --top-tool-height - --tags-view-height)`, **漏减了 `--app-footer-height`**:页脚在内容之后是普通流,等于把 50px 的页脚顶出视口, 页面反而能滚 50px。补上 `- var(--app-footer-height)` 后与基础规则一致,页脚 718..768 完整可见、 `scrollHeight == clientHeight`;代价是矮屏面板内容区少 50px,「各区联网接入进度」由 138px 变 92px(12 行里完整可见 5~6 行 + 底部渐隐,本来就是这一档的降级形态)。 1920×1080 不受影响(列表仍 138px / 12 行全见)。 **同页补修:「各区今日收入排行」同样只露 5 个区**(2026-11-06)。12 行单列要 244px, 面板内容区 111px,第 6~12 名(宁城县、喀喇沁旗、巴林左旗、林西县、阿鲁科尔沁旗、巴林右旗、 克什克腾旗)要面板内滚动才能看到,而表头写的是「合计 2.8 万元」、屏上只能加出 1.7 万。 改法与联网接入进度一致:`.dc-revenues` 从 flex 列改成 `grid-template-columns: repeat(auto-fill, minmax(150px, 1fr))`(1920 两栏 6 行、2560 三栏 4 行、1366 自动退回一栏), `row-gap` 由 `0.6vh` 收到 `0.4vh` 才塞得下 6 行;1460 以下退回一栏时用 `mask-image` 压底部渐隐 —— **不能用 `.dc-panel--revenue::after`**(已被面板四角装饰占用), **也不能用 `.dc-revenues::after`**(滚动容器里的伪元素会跟着内容滚走)。 实测:1920×1080 2 栏 / 111=111、1600×900 2 栏 / 87=87、2560×1440 3 栏 / 159=159, 三档页面滚动均为 0;1366×768 单栏 74/216 + 底部渐隐,页面滚动 0。 实测(`playwright-cli`,登录态):1920×1080 列表 290px / 两栏各 134px / 12 行全部在内 (`scrollHeight == clientHeight`);2560×1440 两栏各 189px、行高 16px;1366×768 单栏、 可见 7.6 行 + 底部渐隐。同期复测其余 4 个左栏面板(供给结构、重点车场、时长分布、收入排行) 高度与内容均未变 —— 本次只改面板内部布局,没动 `.dc-panel--*` 的 flex 比例。 ### 7.14 监管中心:三行磁贴的 1920×1080 / 1366×768 一屏(2026-11-06) §7.6 里写「监管中心也已一屏适配」,**这条是错的**。复核时发现该页两档标准尺寸都在溢出: | 尺寸 | 内容高 | 可视高 | 溢出 | 表现 | |---|---|---|---|---| | 1920×1080 | 1077 | 994 | 83 | 第三行「进入模块」压到视口边缘,数据口径脚注和页脚都看不见 | | 1600×900 | 843 | 764 | 79 | 同上,第三行与页脚被切 | | 1366×768 | 864 | 682 | 182 | 第三行整行 + 脚注、页脚要滚动才能看到 | 原因是 8 张磁贴按 `span 3` / `span 6` 排成三行(01·02 宽卡一行,03·04·05、06·07·08 各一行), 每行高度由内容撑开(1080p 下 251px、768p 下 200px),三行加上页头、六格通栏指标就超了。 改法全部是样式(模板、数据、接口未动,文件 `src/views/gov/supervisionCenter/index.vue`): - **960 档(`@media (max-height: 960px)`,1600×900 这一档)只去掉 `.sc-tile-tags` 标签行** (模块说明已由 brief 承担),三行各少一行 chips 正好补上 79px;再收 1px 通栏指标留白。 这一档**不能**并进下面的 820 紧凑块 —— 那块带 `.supervision-center { min-height: 0 }`, 用在 900 高的窗口上内容只剩 576px,底部会空一大片; - **820 档(`@media (max-height: 820px)`)压内部而非压字号**:同样去掉标签行、 `.sc-tile-icon` 钉到 26px(卡头 36 → 26)、`--sc-tile-pad-y` 收到 8px、 卡内与入口条 padding、`.sc-main` / `.sc-tile-group` / `.sc-kpis` 间距各收一档; - **1080p 也是三行,所以还要动全局值**:图标 clamp 上限 52 → 44(`2.6vw` → `2.15vw`,1366 下仍是 min 30,不受影响)、 `--sc-tile-pad-y` 上限 24 → 21、`.sc-tile-brief` 行高 1.65 → 1.55、`.sc-tile-desc` 行高 1.75 → 1.6、 磁贴网格 gap `0.8vw` → `0.6vw`、卡内 gap `0.7vh` → `0.55vh`、入口条 padding 上限 11 → 9。 这些值改完卡高 251 → 226,信息密度更高但不再靠省略号裁字(brief 仍是 3 行 clamp)。 实测(`playwright-cli`,登录态,量 `scrollHeight` 与 `clientHeight`): 1920×1080 994 / 994(卡高 226,脚注底 1017);1600×900 764 / 764(卡高 179,标签行隐藏); 1366×768 682 / 682(卡高 141,脚注底 698);2560×1440 与 1280×800 同样一屏。 **复核大屏一屏适配时不要量 `document.documentElement.scrollHeight`**:应用内容在 `.el-scrollbar__wrap` 里滚,`documentElement.scrollHeight` 恒等于视口高度,永远量不出溢出 —— §7.6 的误判就是这么来的。正确写法: ```js const root = document.querySelector('.supervision-center') const wrap = root.closest('.el-scrollbar__wrap') // 注意是「内容页」所属的那个 wrap, // 不是侧边栏的(侧边栏 wrap 能 scrollTop 但页面不动) wrap.scrollHeight - wrap.clientHeight // > 0 才是真的溢出 ``` ### 7.15 演示数据重灌 + 指挥中心占用率口径/精度对齐(2026-11-06) **演示数据是「当日快照」,隔天必空**。§7.11 的订单只写「今天 00:00–现在」,第二天打开 指挥中心 / 实时流量监控就是一片「—」。演示前固定跑两步,缺一不可: ```bash python3 scripts/gen-demo-district-data.py --apply # 重建当天订单 / 支付 / 实时泊位 / 运行统计 ./scripts/warm-cache.sh # 预热存量车场缓存(§7.12 末尾已踩过这个坑) ``` 本次现场症状:实时流量监控 KPI 全是「—」,`dataThrough` 停在 13:37:58(上一次导入的时刻), 其余图表还在画上一批数据 —— 数据截止时间与页面刷新时间差了几个小时。重灌 + 预热后变成 在停 11,641 / 近 5 分钟入场 101 / 出场 44 / 平均停放 1 小时 33 分。 **脚本修正:当日订单的分钟分布**(`scripts/gen-demo-district-data.py`)。原来按小时权重取到 「当前这一小时」后,再在整小时里随机取分钟,`now - randrange(2, 30)` 的兜底把落在未来的记录 压回最后 30 分钟,于是「近 60 分钟进出场」图上是一段 4 倍高的假峰接断崖。现在: - 当前小时的权重按已过分钟数打折(`weights[-1] *= (now.minute + 1) / 60`),整小时的量不会 全挤进已过去的那几分钟; - 分钟只在 `[0, now.minute]` 里取,不再生成未来时间再往回压。 合成验证(导入模块造假车场):近 60 分钟 59 个桶都有值,峰值 22、中位数 14,曲线单调自然。 **指挥中心「泊位占用率」70.0% vs 综合监测 71.3%**:两处偏差,都在 `ParkCommandCenterServiceImpl`。 1. **口径**:实时空闲泊位的取值要求「同一行既有 `free_berth` 又有 `berth_num`」,缺 `berth_num` 的车场被整场跳过 —— 分子分母同时少算,分母因此比综合监测小(15,517 vs 16,210)。 现在 `freeBerth()` 只要求 `free_berth` 有值;分母 `occupancyBerths()` 优先用实时行的 `berth_num`,缺省回落到车场登记的 `pl_berth_num`,与综合监测(`ParkMonitorServiceImpl#toLot` 的 `firstNonNull`)完全同口径。KPI、各区分布、重点车场、满位车场预警四处全部改用它。 2. **精度**:占用率是 0~1 的比例,前端再乘 100 显示。原来复用金额的 `round1`,等于 **10% 一档**, 65%~74% 全显示成 `70.0%`;综合监测保留 3 位小数(71.3%),两页怎么都对不上。 新增 `round3()`(`BigDecimal.setScale(3, HALF_UP)`),只用于比例字段。 改完 `POST /admin-api/park/command-center/overview` 返回(数值随每次 `--apply` 的随机种子浮动, 车场 / 泊位 / 进场车次是确定的,收入有几百元级差异) `kpis { lotTotal 109, berthTotal 16,210, todayInCount 21,710, freeBerths 4,655, occupancy 0.713, turnover 1.34, todayIncome 28,349 }`,各区 松山区 0.721 / 敖汉旗 0.705 / 元宝山区 0.813 / 翁牛特旗 0.681,重点车场 0.986 / 0.979 / 0.973 / 0.944 / 0.939 / 0.919;与 `POST /park/monitor/realtime-overview` 的 `summary.occupyRate = 0.7128` 及逐车场值一致。 页面 DOM 校验 7 个 KPI 依次为 109 个 / 16,210 个 / 21,710 次 / 4,655 个 / **71.3%** / 1.3 次/日 / 2.8 万元。 > 同类问题排查入口:凡是「同一指标两个页面数字不同」,先比**分母口径**(哪张表给的容量、 > 缺字段的行是否被跳过),再比**舍入位数**。这次两个都中了。 ### 7.17 综合监测「区县占用率」13 行一屏(2026-11-06) 和 §7.13、§7.15 同一类:左栏 `mon-rail-body` 装「全市 + 12 个旗县区」13 行, 原来是两行排(区名/占用率 一行 + 进度条/场数 一行),每行 47px、合计 650px, 1080p 内容区 403px —— 一屏只看到 8 个,红山区、阿鲁科尔沁旗、翁牛特旗、巴林左旗、 宁城县这 5 个要在栏内滚动才看得到,而地图上 12 个区县是全部着色的。 改法(`src/views/park/monitor/index.vue`,样式 + 行内结构,数据与接口不动): 把「场数」并到首行,进度条单独占第二行并铺满整栏;行高 47 → 29px,13 行 377px。 实测 1920×1080 `mon-rail-body` 403 = 403,13 行全部在内、页面滚动 0。 1366×768 栏高只有 212px,仍要滚动,这档加 `.mon-rail-body` 底部 `mask-image` 渐隐; 1920 不受影响(渐隐在 `max-height: 960px` 内)。 > 顺带核对:右侧「车场实时状态」页脚「共 109 个车场」与 KPI「车场数 109」一致, > 接口 `lots` 也有 109 条 —— 之前的「100」是截图分辨率看错,不是缺陷。 ### 7.16 实时停车流量监控:区分「数据停更」和「流量归零」(2026-11-06) 演示数据只写到脚本运行的那一刻。跑完 5 分钟以后再打开 `/park/flowMonitor`, 「近 5 分钟入场 / 出场」两张卡是 **0**,60 分钟曲线末端是一段贴着 0 的断崖 —— 看上去像流量真的没了。原页面只在**整个窗口都没有记录**时才提示「数据停更」, 窗口里还有历史、只是尾部停了的情况没有任何提示。 改法: - `flow.ts` 新增纯函数 `staleMinutesOf(dataThrough, now)`:返回库内最后一条距今多少分钟, 解析不了返回 `null`,时钟误差(now 早于记录)夹到 0; - 页面 `staleMinutes >= 5`(正好是「近 5 分钟」这个窗口)时:两张卡显示「—」而不是 0, 备注改「数据停更 · 近 5 分钟无上报」,页头「数据停更 · 最后一条 …」芯片也一并出现 (原来只在 `!hasData` 时出现)。这样「0」只表示「确实没车」,「—」才表示「没数据」; - 10 秒轮询每次刷新 `data` 时重算,不需要额外定时器。 TDD 门禁:`tests/verify-flow-monitor.mjs` 先加断言(`staleMinutesOf is not a function` 失败)再实现。 实测:16:06 停更时两张卡为「—」+ 停更芯片;重灌演示数据(16:26)后变成 106 / 49,芯片消失。 **演示纪律**:`--apply` 之后 5 分钟内这两个指标才非 0,演示超过 10 分钟它们会重新变成「—」 (这是真实的数据停更状态,页面现在会明说)。要连续演示就中途再跑一次 `--apply`。 ### 7.18 一张图 / 专题分析的演示夹具补齐 11 个旗县区(2026-11-06) §7.11 已把 69 个车场灌进本地演示库,指挥中心、综合监测、监管中心都是读库、12 个区县都有数。 但「停车资源一张图」「专题分析」读的是前端夹具 `src/views/gov/oneMap/demo.ts`(不是接口), 里面只有 14 个手工车场、覆盖红山区 / 松山区 / 元宝山区 —— 于是同一场演示里两个页面对不上: - 一张图的地图上 9 个区县标「未接入」; - 专题分析的区县下拉里 11 个区县写「(暂无车场)」且不可选。 改法(只动夹具,接口与视图层不改):在 `curatedLotSeeds` 之后加一份 `districtSeeds` 元组表(`[名称, 区县, 经度, 纬度, 泊位, 空余, 今日进场, 今日出场, pl_type]`,67 条, 名称 / 坐标 / 泊位 / 实时空余 / 今日进出原样取自本地演示库,与综合监测同源), 再用 `districtLotSeeds` 展开成完整 `ParkingLot`: - id 从 101 起编、`code = PL0101…`,与手工夹具的 1–14 不撞号; - `pl_type` 1 → `type: 'roadside'` + `plTypeProp: '1101'`,2 → `'enclosed'` + `'2102'`; - 收费口径、营业时间、服务电话、分区、出入口按类型给合理默认值,路侧车场不给分区/出入口 (与手工夹具里路侧车场的写法一致); - 占用率 ≥95% 记 `special`,与指挥中心「满位车场」同口径; - 与手工夹具重名的两个(松山万达广场停车场、松山区政务服务中心停车场)不重复补。 最后 `const lotSeeds = [...curatedLotSeeds, ...districtLotSeeds]`,原有 `parkingLots = lotSeeds.map(...)` 的建号 / 造设备逻辑不动。 实测(1920×1080):一张图资源总览 **81 车场 / 16,220 泊位 / 4,236 空余 / 73.9% / 24,527 今日进场**, 12 个区县全部着色并带占用率;专题分析下拉 12 个区县全部可选、无「(暂无车场)」, 选阿鲁科尔沁旗得 4 车场 / 630 泊位且覆盖·流量·收费·营收四个专题都有图。 TDD 门禁 `tests/verify-topic-analysis.mjs` 同步更新:全市 14 → **81** 条;原来的空态断言 用阿鲁科尔沁旗,该区补齐后恒不为空,改为不存在的区县名(空态分支仍在,接口返回空时也会走到), 另加一条「阿鲁科尔沁旗 > 0」守住补齐结果。16 个 `tests/verify-*.mjs` 全绿,`eslint` 无告警。 留了两个**已知差异**,都不是缺陷,别当成 bug 去追: 1. **一张图 / 专题分析 81 个车场,综合监测 / 指挥中心 109 个**。前者是前端演示夹具,后者读 `parking_cloud_yudao`,本来就是两套数据源(一张图页面顶部固定展示「演示数据」标识)。 红山区库里那 40 个真实车场没有镜像进夹具:其中 2 个坐标在省外(116.36/39.92、119.55/32.00), 放进一张图会把视野拉到北京 / 镇江,而夹具里已有 10 个精修的红山区车场。是否改成读真实接口 由后续决定。 2. **「智能设备覆盖 100%」**。夹具给每个车场都造了设备(`buildLotDevices`),覆盖必然是 100%, 是指标口径,不是算错。 **附带修好的**:视频监控页的 `demo.ts` 直接 `import { parkingLots }`,夹具补齐后它的设备树 从原来只有红山区 / 元宝山区两个区县变成 **12 个区县 / 112 台设备(在线 101)**, 和页面注释里写的「12 个旗县区、约 130 台摄像头」终于对上了,不用再单独补数据; 树变长后在 `.vm-tree-list` 里内部滚动(4182px / 758px),页面本身仍一屏。 ### 7.19 三个列表页改本地分页(2026-11-06) 「反馈管理」「公告管理」「视频设备」和别的停车列表页不一样:接口一次拉全量回来,筛选在页面上本地做, 表格就把全部行铺出来。数据一多,页面高度直接失控 —— 实测(1920×1080,量的是 `.v-layout-content-scrollbar .el-scrollbar__wrap` 的 `scrollHeight - clientHeight`): | 页面 | 行数 | 修复前溢出 | 修复后 | |---|---|---|---| | 反馈管理 `/park/feedback` | 501(本地库全部待处理) | **21,483 px** | 0(994 = 994) | | 视频设备 `/video/device` | 112 | 428 px | 0(994 = 994) | | 公告管理 `/park/notice` | 19 | 174 px | 0(994 = 994) | 改法:新增共享 `src/utils/page.ts#pagedSlice(items, page, pageSize)`,把「页码夹到合法区间」 这件事收在一处 —— 筛选后条数变少、页码还停在旧值时会切出空表,用户会以为数据没了: - 页码 `< 1` 夹到第 1 页;大于总页数夹到最后一页;`pageSize` 为 0 按 1 算(避免除零); - 三个页面统一 `pageSize = 10`(与其它停车列表页一致,20 条在 1920 下就会溢出 370 px 左右); - `handleSearch()` 结尾 `page.value = 1`,换筛选条件回第一页; - `watch(rows, …)` 在条数变少时把页码夹回最后一页 —— 答复 / 发布 / 删除都会改状态、进而改排序; - 序号列用 `:index="rowIndexOf"`,第 2 页从 11 开始,不是每页都从 1 数; - 分页条统一 `.table-pager` + `layout="total, sizes, prev, pager, next, jumper"`。 TDD:`tests/verify-feedback-manage.mjs` 增加 8 条 `pagedSlice` 断言(先 RED:`pagedSlice is not a function`, 再 GREEN),覆盖首/末/越界/`page<1`/`pageSize=0`/空列表。16 个 `tests/verify-*.mjs` 全绿。 **1366×768 的取舍**:三张列表页在这个尺寸下仍分别溢出 294 / 193 / 363 px。这是**有意保留**的 —— 它们是有搜索区 + 工具条 + 表头的普通后台列表,把 10 行再压到 768px 内只能砍到 4~5 行/页,翻页次数翻倍; 其余 8 张演示页(监管中心、指挥中心、一张图、重点车场占用率、专题分析、实时流量、视频监控、综合监测) 在该尺寸下仍是 `scrollHeight == clientHeight`,一屏要求针对的是大屏与监测页。 页面数据与真实库一致,未造数据:反馈 501 条全部 `is_answer = 0`(库里就没有一条被答复过), 所以演示时「待处理 501 / 已回复 0 / 超时 501」是真实状态,不是算错,也没有为了好看伪造答复 (答复会触发真实极光推送)。 ### 7.20 三张高德地图的底图从近黑换成蓝色(2026-11-06) 一张图中间的底图看上去是黑的,和周围深空蓝的面板脱节。实测原因是高德内置样式的问题, 不是页面配色写错: - `features: ['bg','road','building']` 下地块颜色由样式决定; - `amap://styles/darkblue` 的地块取色 **rgb(0,0,1)**,等于纯黑,全屏裁图后几乎看不出边界; - 试过 `normal`(浅灰)、`grey`(灰)、`dark`(#313131 灰黑),都不是大屏要的蓝。 改成 `amap://styles/blue`:同为矢量样式,地块变成 rgb(1,69,79) 的深蓝绿,路网 / 水系仍然可见, 区县着色、气泡、标注在上面都还看得清。三处一起改(都写死同一个字符串): - `src/views/gov/oneMap/useOneMap.ts`(一张图) - `src/views/gov/topicAnalysis/useTopicMap.ts`(专题分析) - `src/views/gov/videoMonitor/components/DeviceMap.vue`(视频监控「地图」页签) 只改 `mapStyle` 一个取值,`features`、缩放、交互都没动;三页在 1920×1080 实测仍是一屏 (`scrollHeight == clientHeight`),一张图 12 个区县标注全部带占用率、无 `NaN / undefined`。 **别再改回 `darkblue`** —— 它是黑底,不是深蓝底。 ### 7.21 一张图加入场动效(2026-11-06) 参考浅色门户模板的 GIF(`大屏素材/01_大屏创意设计/0314_…/05.jpg`,实为 75 帧动图), **只借动态节奏、不借配色**:页面仍是深空蓝(`--bs-*` 一套色板),没有改成浅色模板。 **整页 3 秒加载完成**(`PAGE_ENTER_MS = 3000`),顺序按产品要求:地图先从中间 「开花」铺开,左右面板的卡片再从上到下一张张滑入,最后底部卡片浮起 —— | 顺序 | 区块 | 动作 | 延迟 / 时长 | |---|---|---|---| | 0 | 顶栏 | 淡入落下 | 0 / 500ms | | 1 | **地图** | 从圆心**开花**:`circle(4%) → circle(150%)` + `scale(0.55) → 1` + 淡入 | 0 / 1200ms | | 2 | **左栏第 1 张** | **从左往右**滑入(`translateX(-40px) → 0`) | 900 / 600ms | | 3 | 左栏第 2~6 张 | 同上,每张再延后 140ms(`PANEL_STAGGER_MS`) | 1040…1600 / 600ms | | 4 | **右栏第 1 张** | **从右往左**滑入(`translateX(40px) → 0`) | 1050 / 600ms | | 5 | 右栏第 2~3 块 | 每块再延后 140ms | 1190 / 1330 / 600ms | | 6 | **底部** | **从下往上**浮起(`translateY(24px) → 0`) | 1600 / 800ms | | — | 九宫格数字 | 0 → 真值滚动(跟左栏第 2 张「资源总览」一起出来) | 1040 / 1960ms | **地图改成整块背景,不再套框**:`.om-map-wrap` 从网格单元改成 `position: absolute; inset: 0`, 铺满 `.om-body`,左右面板(`z-index: 2`)浮在地图上;同时删掉原来的 `border: 1px solid rgba(84,140,200,.34)` 与 `background: #050c1c`。 **坑(记一笔)**:地图绝对定位后就不占网格单元了,`grid` 自动排布会把右栏塞进中间那一列、 直接盖住地图(右栏一度变成 1020px 宽)。所以 `.om-left` 必须显式 `grid-column: 1`、 `.om-right` 必须 `grid-column: 3`。 纯逻辑抽在 `src/views/gov/shared/enter-motion.ts`(`entranceDelay` / `entranceFade` / `panelItemDelay` / `entranceEnd` / `countUpTiming` / `easeOutCubic` / `planCountUp` / `formatCount`),`tests/verify-one-map-motion.mjs` 是门禁(TDD,先 RED `formatCount is not a function`):门禁钉住**顺序**(map ≤ left < right < foot)、 **逐张递增**(左栏 6 张 = 900/1040/…/1600)、**预算**(各区块落位 ≤ 3000ms, 数字延迟 + 时长恰好 = 3000ms、数字跟随「资源总览」那张卡)。 几个刻意的取舍: - **数字只滚一次**:切区县、点刷新、30 秒轮询都会重算汇总,每次从 0 重滚整页永远在抖; 首屏滚完 `played` 置真,之后直接给值。 - **格式口径不变**:`formatCount` 复刻原页面的 `fmtInt` / `fmtPct` —— 整数千分位四舍五入, 百分比默认一位小数。第一版把百分比按 0 位取整,占用率 `73.9%` 变成 `74%`,被门禁抓了。 - **节奏值只有一份**:`entranceVars` 把 `entranceDelay()` / `entranceFade()` / `panelItemDelay()` 注入成 `--om-delay-*` / `--om-fade-*` / `--om-item-left-N`, CSS 只取值,不在样式里另写一份毫秒数。 - **地图「开花」用 clip-path 不用滤镜**:`circle(4% → 150%)` 配合 `scale(0.55 → 1)`, 终态必须写成 `circle(150%)` 而不是 `none`,否则两端不可插值、会直接跳变。 - **不碰地图多边形**:区县色块「生长」动画高德没有 API,硬做会和重绘 / 缩放打架,没做。 实测(`performance.now()`,从导航开始算):476ms 开始开花(scale 0.55 / clip 4% / 透明度 0), 1676ms 地图完全铺开,此后左栏卡片 900+140n 逐张进场,**2992ms 数字收尾** —— 刷新到全部完成约 3 秒。 1920×1080 与 1366×768 仍一屏(`scrollHeight == clientHeight`,944 / 632), 17 个 `tests/verify-*.mjs` 全绿。 已知遗留(本次不动):1366×768 下顶栏中间的标题与右侧搜索框有横向重叠,是该页顶栏 `grid-template-columns: 1fr auto 1fr` 在窄屏的老问题,与动效无关。 ### 7.22 一张图左右资源清单重排 + 底部大屏导航(2026-11-06) 方案见 `docs/superpowers/specs/2026-11-06-停车资源一张图左右资源清单与底部导航设计方案.md`(方案 A)。 一句话:左栏从「按功能凑的 6 张卡」收敛成 **资产 3 张卡**(运行总览 / 资源清单 / 图层管理), 右栏只留 **1 张卡 4 个页签**(实时进出场 / 区县态势 / 重点车场 / 实时告警,选中对象整卡换详情), 页脚 40px 一条:左边 5 个大屏导航按钮,右边压缩图例(**导航已于 §7.24 移到地图上,页脚只留图例**)。 **资源清单(新增,7 行 + 1 行未定位)**,口径全在 `src/views/gov/oneMap/catalog.ts` 的 `buildCatalog()`: (2026-11-09 那 1 行「未定位资源」已整块删除,见 §7.30) | 行 | 数值 | 副信息 | 点击 | |---|---|---|---| | 停车场 | 81 个 | 占用率 73.9% | 只显示车场,右栏切「重点车场」 | | 泊位 | 16,220 个 | 空余 4,236 | 同车场 | | 路侧泊位点位 | 984 个 | 占用 620 / 空闲 282 | 只显示点位,缩放锁 16–17 | | 感知设备 | 961 台 | 离线 92 · 故障 17 | 只显示设备点,缩放锁 11–15 | | 视频设备 | 242 路 | 视频桩 121 · 摄像机 121 | 只显示视频点 | | 充电设施 | 4 站(后缀 36 桩) | 在线 3/4 | 只显示充电站 | | 实时告警 | 5 起 | 红 1 / 橙 2 / 黄 2 | 右栏切「实时告警」 | 点已选中的行 = 取消筛选、恢复全部图层并 `fitPoints` 回全市。 **口径坑(门禁抓到过两次)**: 1. **两个「泊位」不是一回事**:16,220 是车场登记泊位数(地图不逐位画),984 是已上图的路侧泊位点位。 合成一个数就是对外发布错的资源量,所以清单必须两行分开。 2. **视频设备 = 242,不是 121**:`devices.kind` 里 `video`(视频桩)与 `camera`(摄像机)各 121 台, 只数 `video` 一类会少一半。副信息分开写成「视频桩 121 · 摄像机 121」。 3. 告警级别以 `demo.ts` 实际数据为准(红 1 / 橙 2 / 黄 2),方案稿里估的 2/2/1 是错的。 **底部导航**:`src/views/gov/oneMap/nav.ts` 的 `PAGE_NAV` 5 项(一张图 `/park-map`、专题分析 `/park-map/analysis`、视频监控 `/video/monitor`、综合监测 `/park/monitor`、车辆追溯 `/park/vehicleTrace`),按权限码过滤后才渲染,**不新增权限、不调整任何角色授权**。 坑:当前页高亮必须用**路径全等**(`isNavActive`),不能用 `startsWith` —— `/park-map` 是 `/park-map/analysis` 的字符串前缀,前缀匹配会让「一张图」在专题分析页也亮着。 **入场动效跟着改**(`enter-motion.ts`):左栏卡片数 6 → 3(新增 `panelItemCount()`,门禁同步断言), 数字滚动改为跟**第 1 张卡「运行总览」**同拍(原为第 2 张),仍恰好第 3 秒收尾。 §7.21 里那张「左栏 6 张 900 / 1040 / … / 1600」的节奏表已被本节取代:现在是 900 / 1040 / 1180。 **实现取舍**:不拆 `ResourceCatalog.vue` / `PageNav.vue`。`index.vue` 的样式是 `scoped`,清单与导航大量 复用 `.om-sec` / `.om-cat*` / `.om-legend` 这些局部类,子组件拿不到这些样式,拆了只能复制一遍面板样式或改成全局。 纯逻辑(算数、路径判定、权限过滤)仍然抽成 `catalog.ts` / `nav.ts` 并被门禁覆盖。 **一屏(≤1500px 紧凑档)**:1366×768 下左栏原本需要 763px、只有 514px。加 `@media (max-width: 1500px)` 后把清单行压成一行(副信息右对齐、隐藏迷你条)、缩小面板 padding/gap、 KPI、图层行、导航按钮与右栏抓拍图,字号最低 9–11px。实测 1920:整页 944/944、左 813/813、右 813/813、图例 1238/1238; 1366:整页 632/632、左 514/514、右 514/514(4 个页签都 514/514)、图例 772/772。**左栏靠 `scrollHeight == clientHeight` 验收,不靠滚动条兜底。** 小坑:`.om-legend-note { display: none }` 在断点里写不生效 —— 基础规则 `.om-legend > span`(0,1,1) 优先级高于 `.om-legend-note`(0,1,0),必须写成 `.om-legend > .om-legend-note`。 门禁:新增 `tests/verify-one-map-catalog.mjs`(7 行顺序 / 口径 / 颜色 / 空数据 / 无站名兜底)、 `tests/verify-one-map-nav.mjs`(5 入口 / 路径全等 / 权限过滤),改 `tests/verify-one-map-motion.mjs`。 前端仓库共 **19 个 `tests/verify-*.mjs` 全绿**。 ### 7.23 一张图按「只有 3 个区有数据」重排:空区留白、有数据区铺满(2026-11-06) > 2026-11-07 追加喀喇沁旗为第 4 个有数据的旗县区,见 §7.25;本节其余口径不变。 背景:全市 12 个旗县区里,**只有红山区 / 松山区 / 元宝山区**真正接入数据,其余 9 个 后期也不会有数据接入。§7.18 当时把 11 个旗县区都补了演示车场,等于在大屏上对外宣称 全域已联网;页面又按「12 个区都有」排版,一旦按真实口径只剩 3 个区,右栏会空掉一大半。 **口径改成 3 个区,总量不变**: | 项 | 改前 | 改后 | |---|---|---| | 有数据的区县 | 12 | **3**(`COVERED_DISTRICTS`,`isCoveredDistrict()`) | | 车场 / 泊位总量 | 81 场 / 16,220 位 | **不变** | | 49 条补充种子 | 挂在 9 个空区 | 全部改挂到 3 个有数据的区(`relocationPlan`) | 搬迁不是改数字,是**重生成名称与坐标**:名字取自三个区的地名池(不能出现「经棚」「锦山」 这类别的旗县镇名),坐标按黄金角螺旋落在各区城区中心附近(半径 `0.01° × √(n+1)`, 最外圈约 5 km),保证点真的落在本区行政边界内。`tests/verify-one-map-districts.mjs` 断言 81 个车场全部落在所属区县 bbox 内(39 / 23 / 19 分布)。 **空区怎么呈现**:地图上只画灰蓝底 + 虚线边界,标签只有区名,**不写任何状态或排期字样**。 全平台新增门禁 `tests/verify-no-pending-wording.mjs`:扫 `src/` 下全部 `.vue/.ts/.mjs/.json/.scss` (含注释)里出现「待接入」即失败 —— 把这件事挂在页面上等于向使用方承诺一个不存在的排期。 空态统一用「暂无数据 / 暂未开放 / 暂未迁入 / 非实时数据」。 **页面怎么在只有 3 个区时不显空**(这是本节的主要工作量): 1. **KPI 4 → 6**:补「今日订单」和「覆盖旗县区 3/12」,一屏 6 个数字把「全市」讲完整。 2. **右栏区县态势**:从「12 行」改成「3 张有数据卡片 + 数据覆盖块」。卡片按 `flex: 1 1 0` 均分 右栏剩余高度(`justify-content: space-between` + 显式 `align-items: stretch`,否则 button 的 UA 样式会把三行缩成一小坨),内容是大号占用率 + 泊位 / 空余 + 进度条;底部「数据覆盖 3/12」 进度条 + 「其余 9 个旗县区暂无数据」+ 9 个灰色 chip。 3. **重点车场 / 实时告警**同样只有 6 / 5 条,行均分剩余高度;告警底部再加一块「告警分级」 (红 / 橙 / 黄条数,从同一份 `alerts` 算),把版式收住。 4. **地图取景改成数据驱动**:`fitAllDistricts()` 只按有数据的车场坐标取景(原来按区县多边形取景, 会把 9 个空区一起框进来,导致有数据的三区缩成一小块)。缩放取 `Math.floor` 整档 —— 高德按整数级渲染,四舍五入会多放大半级,最外侧的点被挤出屏幕。 5. **区县填充透明度下调**:`CORE_DISTRICT_FILL_OPACITY` 0.74 → 0.46、`OTHER` 0.44 → 0.34 (`gov/shared/district-theme.ts`),让底图和车场气泡透出来,否则三块色块把地图糊住。 6. **聚合改像素网格**:`renderCluster()` 原来按经纬度距离单链聚合,车场挪密之后会串成一个大球 (实测 39 / 60 / 62 三坨)。改成 `map.lngLatToContainer()` 换算成屏幕像素后按 60px 方格分组, 15 个气泡互不重叠。`render()` 的散点阈值也随之下调到 10 级。 **实测一屏**:1920×1080 左 813/813、右 813/813、整页 1080/1080,右栏 4 个页签底部余量 13px; 1366×768 左 514/514、右 514/514,区县态势 / 重点车场 / 实时告警余量 9px(实时进出场 8 条列表 允许滚动,超出 94px)。地图 15 个聚合气泡,三区全部在左右面板之间的可视区内。 门禁:新增 `tests/verify-no-pending-wording.mjs`;改 `tests/verify-one-map-districts.mjs` (`covered` / `uncovered` 分组、3 区 / 12 个、bbox 包含、39/23/19 分布)与 `tests/verify-map-theme.mjs`(填充透明度新值)。前端仓库共 **21 个 `tests/verify-*.mjs` 全绿**。 ### 7.24 一张图:导航压到地图上、点开走弹框、车场气泡改圆润泪滴(2026-11-07) 三件事,都在一张图页内完成,不新增路由、不改权限数据。 **1. 5 个导航中的 4 个移到地图下沿** `一张图` 是当前页,不再出现在按钮里(`mapNavItems()` 按 `isNavActive` 过滤)。剩下 专题分析 / 视频监控 / 综合监测 / 车辆追溯 从 40px 页脚挪进 `.om-map-wrap`, `.om-map-nav` 绝对定位 `bottom: 12px; left: 50%`,深色底条 + 描边。页脚只剩图例一条: - 1920×1080:按钮条 869→1251(382×42),`.om-foot` 只剩图例; - 1366×768:按钮条 624→942(318×40),与左下角 `.om-zoom-hint`(221→333)不重叠; - 地图下的点位会被按钮条盖住(实测底部中间偶尔压住一个气泡),属于浮层控件的正常代价, 不做避让 —— 避让会把按钮推到地图外,又回到页脚方案。 **2. 点按钮不跳页,用弹框装目标页** 纯逻辑在 `src/views/gov/oneMap/nav-embed.ts`: - `mapNavItems(items, routePath)`:过滤当前页,路径判定沿用 `isNavActive`(全等 + 尾斜杠归一); - `resolveRouteView(router, path)`:从 `router.resolve(path).matched` **从后往前**取第一条 `components.default`(前几条是 Layout / Parent 包装),懒加载函数 `await` 后取 `.default`; 解析失败、路由缺失、chunk 加载失败一律返回 `null`,页面显示「该页面没有注册到当前路由… 可以改用全屏打开」并保留「全屏打开」按钮,不白屏也不抛异常。 弹框头显示目标页名 + 「页内打开 · 不离开一张图」+「全屏打开」+「关闭」;遮罩点击、Esc 关闭。 连点两个入口时用 `embedToken` 递增丢弃先返回的那次解析结果,避免「后点的页面被前一次的 异步结果覆盖」。`` 渲染真实路由组件,不是截图/iframe。 实测 1920×1080 四个页面在弹框里都铺满 `.om-embed-body`(969px),`scrollHeight == clientHeight` 无内部滚动:专题分析 969、视频监控 944、综合监测 969、车辆追溯 969。样式上用 `:deep()` 把 `.topic-analysis / .vm-page / .mon / .vt` 的高度钉成 `100% !important`, 否则这些页面按整屏高度算会溢出弹框。 **3. 车场气泡换成圆润泪滴** 原来是一个圆 + 一个三角尾巴(`M20 30 L26 50 L32 30 Z`),放大后接缝处是折角。现在整块 SVG 抽到 `src/views/gov/oneMap/marker-svg.ts`(纯函数,门禁可直接断言): | 部件 | 改前 | 改后 | |---|---|---| | 外轮廓 | 圆 + 三角形两条路径 | 一条贝塞尔泪滴 `M26 50 C 26 50 10 34 10 20 A 16 16 0 1 1 42 20 C 42 34 26 50 26 50 Z`(只有 M/C/A) | | 头部 | 圆 r=15,圆心 (26,19) | 圆 r=16,圆心 (26,20);路侧从 30×30 rx=9 改成 32×32 rx=12 | | 选中圈 / 重点虚线圈 | r=20 / r=18,(26,19) | r=20 / r=19,(26,20) | | 尖点 | y=50,marker 锚 `bottom-center` | 不变(画布 52×52,`LOT_ICON_SIZE`) | 头部形状盖在泪滴上,露出来的只有平滑尾巴;描边 `stroke-linejoin="round"`,整体比三角尾巴 宽一圈、没有直边。聚合气泡(`clusterBubbleSvg`)仍是同心圆,只做了搬家。 **4. 顺带修 1366 顶栏重叠** `@media (max-width: 1500px)` 下右栏内容(搜索 118 + 区县 174 + 三个按钮 + 间距 ≈ 488px) 超过 1fr 列宽(≈396px),向左溢出压住居中的标题。改成 `grid-template-columns: minmax(0, 1fr) auto minmax(0, 1.25fr)`, 搜索框 96px、区县下拉 `max-width: 128px`;实测右栏首个控件 x=935,标题右边界 899,不再相交。 门禁:新增 `tests/verify-one-map-nav-embed.mjs`(导航过滤 + 路由组件解析/兜底)与 `tests/verify-one-map-lot-bubble.mjs`(泪滴路径无直线命令、尖点仍压 y=50、圆形/路侧两种头、 角标 999+、选中圈与虚线圈同心、聚合仍是圆)。前端仓库共 **23 个 `tests/verify-*.mjs` 全绿**, `eslint` / `prettier --check` 通过。 ### 7.25 喀喇沁旗纳入有数据区县 + 弹框去表头(2026-11-07) **1. 喀喇沁旗(第 4 个有数据的旗县区)** `demo.ts` 的 `COVERED_DISTRICTS` 从 3 个改成 4 个:`红山区 / 松山区 / 元宝山区 / 喀喇沁旗`。 喀喇沁旗**保留自己的 6 条种子**(锦山镇中心 / 喀喇沁旗医院 / 王爷府景区 / 锦山大街路侧 / 喀喇沁旗政府 / 美林谷景区),不再外迁;其余 43 条种子继续按原节奏迁进前三个区。 | 项 | 改前 | 改后 | |---|---|---| | 有数据的区县 | 3(红山 / 松山 / 元宝山) | **4**(+ 喀喇沁旗) | | 车场分布 | 39 / 23 / 19 | **36 / 21 / 18 / 6**(合计 81 不变) | | 泊位总量 | 16,220 | **不变** | | 覆盖率 | 3/12 = 25% | **4/12 = 33.3%** | 页面全部按数据推导,不需要改文案:KPI「覆盖旗县区 4/12」、右栏 4 张区县卡片、 「其余 8 个旗县区暂无数据」+ 8 个灰色 chip、区县下拉里喀喇沁旗由 disabled 变为可选。 地图取景按车场坐标算,自动把锦山镇一带纳入视野。 门禁:`tests/verify-one-map-districts.mjs`(covered 4 个、分布 36/21/18/6、喀喇沁旗 bbox、 「锦山」只允许出现在喀喇沁旗车场名里、覆盖率 4/12)、`tests/verify-topic-analysis.mjs` (红山区 36、喀喇沁旗 6)。 **2. 弹框去掉自己的表头** 目标页本来就有标题栏(监管中心 / 综合监测…),弹框再加一条 44px 表头,等于标题写两遍, 还把页面标题压掉一截。现在去掉 `.om-embed-head`,关闭收成右下角浮层「全屏打开 / ✕」, Esc、点遮罩仍然可关。 **3. 修弹框里页面标题被切的问题** 专题分析 / 综合监测 / 车辆追溯 / 视频监控 这四页都用 `margin: calc(-1 * var(--app-content-padding)) ... 0` 顶到 app 内容区边缘。放进弹框后 整页向左上偏 20px,「综合监测」四个字的上半截被弹框上沿切掉。在 `.om-embed-body` 里统一 `margin: 0 !important; width: 100% !important`,四页标题均完整显示, 弹框 body 高度 1013px、`scrollHeight == clientHeight`。 ### 7.26 一张图全屏后把空白吃掉(2026-11-07) 点「全屏」后顶栏消失,内容区比窗口模式高 136px(1920×1080:813 → 949),而三张左栏卡片 和右栏列表都是固定高度,多出来的一截就空白地堆在栏底;区县卡片右半边除了进度条也是空的。 四处一起改,全部用页面已有的数据补,不新造口径。 **1. 左栏三张卡分摊剩余高度** `.om-left > .om-sec` 改 `flex: 1 0 auto`(只吃剩余高度、**绝不压缩**),卡内 KPI 宫格 / 资源清单行 / 图层行改成 `flex: 1 1 auto` + `grid-auto-rows: 1fr` 均分。 全屏下卡片从 150/427/175 变成 229/408/255,正好铺到页脚。 > `flex-shrink` 必须是 0。第一版写的是 `flex: 1 1 auto` + `min-height: 0`, > 1366×768 下列高不够,三张卡被压到互相盖住,「覆盖旗县区」和「实时告警」两行直接压在 > 下一张卡的标题上。改回 0 之后,矮屏沿用原有行为:卡片保持自然高度,左栏内滚动。 **2. 右栏进出场:行均分 + 底部「进出场结构」** 一次刷新只有 7~8 条抓拍,`slice(0, 8)` 之后右栏底部空一大截。列表行 `flex: 1 1 auto` (`min-height: 46px`,保证 42px 抓拍图压不扁),底部再加一块结构统计: 进场 / 出场条数(占比条)、免费放行 / 收费条数、收费合计。 数字来自同一份列表,口径在 `src/views/gov/oneMap/access.ts#summarizeAccess`: `¥8.00` → 8、`—` → 没有金额,出场且无金额算免费;进场还没结算,不参与费用统计。 结构统计用**只按区县过滤**的那一份(`districtAccess`),页签切到「进场」时不会变成「出场 0」。 **3. 区县卡片右侧补空余环形** 卡片原来只有左半边有字,右半边空着。改成两栏:左边名称 / 占用率 / 「960 泊位 · 占用 758」, 右边一个环形,弧长 = 空余占比、颜色沿用全站「空闲」三档色、环心是空余泊位数 (喀喇沁旗 202、红山区 1,978、松山区 1,167、元宝山区 889)。 拆分口径在 `districts.ts#berthSplit`:占用 = 总泊位 − 空余,空余大于总数时占用兜底为 0, 总泊位为 0 时占比是 0 不是 NaN。 **4. 门禁** `tests/verify-one-map-panel-fill.mjs`:`berthSplit` 的占用/空余/占比、0 泊位与空余超总数的 边界;`summarizeAccess` 的进场/出场、免费/收费、`¥` 解析、只有进场、空数组、 出场缺金额字段;再接一遍演示数据确认不出现 NaN。前端门禁 23 → **24 个**。 实测:1920×1080 全屏下左栏 229/408/255 铺满、进出场 8 行各 86px + 结构块贴底 (卡片底 1022 / 页脚 1032);1366×768 左栏卡片不再互相压住,溢出交给栏内滚动。 ### 7.27 一张图地图上加「基本指标」浮条(2026-11-07) 地图上方原来只有图例和右下四个入口按钮,顶上正中一整条是空的。压一条 5 个指标的浮条: **车场 / 总泊位 / 剩余泊位 / 占用率 / 周转率**,左侧带当前范围(`全市` 或具体旗县)。 口径在 `src/views/gov/oneMap/map-kpi.ts#buildMapKpis`,由 `tests/verify-one-map-kpi.mjs` 守住: | 指标 | 算法 | 显示 | |---|---|---| | 车场 | `filteredLots.length` | 整数 +「个」 | | 总泊位 / 剩余泊位 | 车场逐个累加 | 整数 +「个」 | | 占用率 | (总泊位 − 剩余) / 总泊位,夹到 0~1 | 1 位小数 + `%` | | 周转率 | **按泊位加权**:Σ(周转率×泊位) / Σ泊位 | 2 位小数 +「次/日」 | - 周转率必须加权。车场之间泊位差两个量级(6 泊位 vs 2000 泊位),直接平均会让小场和大场 等权,全市数字没有意义;门禁里 100/300 两个车场的用例专门断言 2.25 而不是平均的 2.0。 0 泊位的车场不进分母,避免除零。 - 单位和小数位与「专题分析」一致(`次/日`、两位小数),占用率只留 1 位——浮条是扫一眼的, 多一位反而看不清。 - 数据源和左栏资源清单同一个 `filteredLots`,**跟着区县下拉一起变**,不另算一遍。 实测:全市 81 场 / 16,220 / 4,236 / 73.9% / 1.52 次/日;切到红山区 36 场 / 7,830 / 1,978 / 74.7% / 1.56 次/日,和红山区区县卡片的数字对得上。 - 第一次排出来 534px 宽,1366×768 下几乎贴到左栏。`@media (max-width: 1500px)` 把每格 `min-width` 96 → 74px、字号 20 → 17px,压到 415px(x=576,左栏右边界 511),不再打架。 前端门禁 24 → **25 个**。全屏(1920×1080)下浮条同样居中压在顶边,和「运行总览」卡片、 右侧进出场卡片都不重叠。 ### 7.28 一张图切到真实接口:109 个车场,台账和点位分开报(2026-11-09) 一张图原来读 `oneMap/demo.ts` 夹具(81 场 / 16,220 泊位),页面上挂着「演示数据」标记。 这一轮把数据源换成后端聚合接口,页面上所有数字都来自 `parking_cloud_yudao`。 #### 7.28.1 接口 | 项 | 值 | |---|---| | 路径 | `POST /park/resource-map/overview` | | 控制器 | `parking-module-park` · `ParkResourceMapController` | | 权限 | `park:park-lot-manage:query`(**不自动授权任何角色**,角色 2001 对 `park:dashboard:query` 的拒绝状态不动) | | 入参 | `{ region?: string }`,空 = 全市,否则按 `parking_lot.pl_country_name` 收窄 | | 出参 | `summary` / `districts` / `lots`(含坐标)/ `resources`(台账计数)/ `devices` / `access` | 聚合 SQL 全在 `ParkResourceMapMapper.xml`: - `selectLots`:`data_status = 1 AND state = 1 AND pl_city_name = '赤峰市'`; 每个车场只取一条 `parking_lot_geo`(按 GCJ02 → 百度 → WGS84 优先级), 0/0 或缺失坐标的车场由前端丢弃。 - `selectResourceCount`:路侧泊位(`tp_p_berths`)、地磁(`tp_p_eqp.eqp_type = 1`)、 充电桩(`tp_p_charging_pile`)六个计数子查询,**全部 `INNER JOIN parking_lot` 并跟随 `region`**,和车场、设备点位用同一套范围口径;不关联会报出别的旗县区的台账。 - `selectRecentAccess`:只扫当天订单,`ORDER BY GREATEST(park_in_time, park_out_time) DESC`, Service 把每条订单拆成进场 / 出场两条事件。 #### 7.28.2 数据实况(本地 2026-11-09) | 指标 | 数值 | |---|---| | 在用停车场 | **109**(12 个旗县区全有,红山区 40) | | 登记泊位 / 空余 | 16,210 / 4,655,占用率 **71.28%** | | 路侧泊位台账 | 2,575(占用 1,687),**`tp_p_berths` 没有坐标列**,一个都上不了图 | | 地磁设备 | 台账 2,097(在线 270 / 故障 119),**有坐标能上图的 673** | | 视频 / 充电桩 / 告警 | 0 / 0 / 0,接口如实返回 0,页面显示 0 与空态 | | 红山区今日进出 | 0:该区订单停在 2026-11-06,不是接口算错 | #### 7.28.3 台账 ≠ 点位,两套数字分开报 `catalog.ts#buildCatalog` 新增可选 `ledger`(接口的 `resources`): - **有台账时一律按台账报数**:路侧泊位 2,575、感知设备 2,097;副信息用 「占用 / 空闲」「离线 / 故障」,离线 = 总数 − 在线 − 故障,异常数据兜底成 0; - 没有台账时退回数组计数(演示夹具、单测); - 地图只画有坐标的点(673 台地磁),**台账数和点位数永远不互相代替**; - 点「路侧泊位」行不切图层,改为 toast: 「路侧泊位台账 2,575 个,暂无路段坐标,不能在图上定位」; - 左栏「未定位资源」弹窗列出 2 条:路侧泊位 2,575 个缺路段坐标、 地磁 1,424 台缺坐标(2,097 − 673),提示「补录坐标后自动上图」。 `tests/verify-one-map-catalog.mjs` 增加台账模式断言(2,575 / 1,687 / 888、2,097 / 1,708 / 119、 视频充电告警为 0、离线不为负),并把标签改成「路侧泊位」。 #### 7.28.4 前端改动 - `mapper.ts#toPageModel`:DTO → 页面模型;坐标统一转 GCJ02;12 个旗县区固定补齐, 接口没返回的按「0 车场」处理,不是「占用率 0」; - `index.vue`:去掉「演示数据」标记、去掉每秒轮换的假进出场,改为 `loadData()` + 60 秒静默刷新;失败时顶部显示「数据加载失败」,刷新按钮给 toast; - 区县下拉第一次用全市结果填充(`cityDistricts` 只在未选具体区县时覆盖), 选中一个区不会把另外 11 个标成暂无数据; - 「设备在线率」在台账为 0 时显示 `—`(松山区这类还没有地磁的区县不显示 0.0%); - 进出场记录没有抓拍图时显示「进 / 出」占位,不渲染裂图; - 区县态势 12 张卡片改为列表内滚动,「数据覆盖」块固定在底部; 未覆盖区县为 0 时不再输出「其余 0 个旗县区暂无数据」; - 车场聚合**去掉缩放下限**:全市视野的取级随容器尺寸在 6~8 级之间浮动, 写死 `>= 8` 会让车场整体消失;聚合本来就按 60px 屏幕格子分组,任何取级都不会叠压。 #### 7.28.5 类型与门禁 `vue-tsc` 在新 `oneMap/` 文件里报过的 6 个错误全部修掉(`PAGE_NAV` 未使用、 `berthPoints` 早返回后的死比较、`berths: []` 空元组被推断成 `never[]`、 `nav-embed` 的 `components` 未允许 null)。仓库其余页面原有类型错误不在本轮范围。 前端门禁 **26 个全绿**(含 `verify-no-pending-wording`,扫描 3,230 个文件), eslint 对改动文件无告警。 #### 7.28.6 实测 - 全市:109 场 / 16,210 泊位 / 4,655 空余 / 71.3% / 1.04 次/日; 资源清单 109 / 16,210 / 2,575(占用 1,687 · 空闲 888)/ 2,097(离线 1,708 · 故障 119) / 0 / 0(桩)/ 0; - 红山区:资源台账与全市相同(泊位和地磁全在红山区),今日进出场 0 条; - 松山区:资源台账全 0,设备在线率显示 `—`; - 区县态势 12/12、覆盖 100.0%,卡片可滚动、底部覆盖块不重叠; - 重点车场 Top6、实时告警空态、未定位资源弹窗、1366×768、全屏 1920×1080 均正常。 ### 7.29 专题分析切到真实接口:7 个专题一次一个聚合(2026-11-09) `/park-map/analysis` 是最后一张仍读夹具的大屏(视频监控除外,等视频基础设施)。本期按设计方案 §8 的 P2 契约落地:一个专题一个只读聚合接口,前端 `mapper.ts` 把 DTO 转成页面快照,KPI / 结论 / 排行 / 分布 / 散点仍由 `assembleSnapshot` 统一生成。 #### 7.29.1 接口 | 方法 | 路径 | 说明 | |---|---|---| | POST | `/admin-api/park/topic-analysis/coverage` | 车场、泊位、设备在线结构;忽略日期 | | POST | `/admin-api/park/topic-analysis/duration` | 订单数、已结算数、实收、欠费、平均时长、周转 | | POST | `/admin-api/park/topic-analysis/flow` | 车场进出场 + 全天汇总 + 24 小时帧 + 日趋势 | | POST | `/admin-api/park/topic-analysis/heat` | 车场结构 + 24 小时帧 | | POST | `/admin-api/park/topic-analysis/hotspot` | 车场点位 + 区间车次,热点网格前端算 | | POST | `/admin-api/park/topic-analysis/price` | 首小时价、当前占用;忽略日期 | | POST | `/admin-api/park/topic-analysis/revenue` | 实收、订单、日趋势 | - 请求:`{"district":"红山区","beginDate":"2026-11-01","endDate":"2026-11-07"}`;`coverage` / `price` 前端不发日期,后端也忽略。 - `district` 空串 = 全市;日期必须 `yyyy-MM-dd`,`beginDate <= endDate`,最长 366 天。参数错误由 Controller 自己翻成 `CommonResult.error(400, 中文提示)`,不交给 yudao 的 `GlobalExceptionHandler`(它会当未知异常返回 500,文案会丢)。 - **没有实现设计稿里的 `/overview`**:每个专题接口已经带回该专题全部车场明细,KPI / 结论文案由前端从同一份 `TopicLotMetric[]` 生成;再开一个 overview 会把同样的聚合算两遍,且两处口径容易漂。`unavailable` 字段保留在响应里,当前恒为空数组。 #### 7.29.2 数据源裁定:只读 `order_park` 设计稿 §8.3 原计划读 `park_in_out_record`、`parking_running_statistic`、`duration_statistic_park_*`、`income_statistic_park_*`。实测本地副本库: - `parking_running_statistic` 只有最近 4 天; - `duration_statistic_*` / `income_statistic_*` 只有 2018 年演示数据; - `order_park` 有 667,559 行(2026-09-06 起),12 个区县里红山区 650,738 行是历史,其余区县主要是 2026-11-09 当天 16,821 行。 **裁定:时间类专题统一读 `order_park`(`data_state = 1`)**。进出场分别按 `park_in_time` / `park_out_time` 落区间,营收、订单、时长按 `create_date` 落区间。代价是 366 天长区间的 `flow` / `heat` 会扫两遍时间列,上线前需按生产规模压测;本地 7 天区间实测 0.16–0.85 秒,`coverage` 89 毫秒。 #### 7.29.3 口径 - 在用停车场:`parking_lot.data_status = 1 AND state = 1 AND pl_city_name = '赤峰市'`,区县筛选走 `pl_country_name`。 - 泊位总数:`COALESCE(temp_berth_num, temp_pl_berth_num, pl_berth_num)`,与一张图一致。 - 实时空余:`parking_lot_berth_temp_info.free_berth`;实测 109 个在用场全部有行且非空。前端仍按「缺省 = 全部空闲」兜底,不把未知当满位。 - 实收:只算已结算状态 **40 / 50 / 53 / 70**,金额 `order_act_fee - COALESCE(refund_act_fee, 0)`。 - **`back_act_fee` 不计入**:本地库有明显超量样本(单笔 ¥1,448,同单应收 ¥15),加进来会污染营收。 - 欠费率:`arrearage_fee / (order_total_fee + arrearage_fee)`;应收、欠费都不进营收。 - 平均停车时长:`parking_duration > 0` 的订单加权,单位是**秒**。 - 周转率:区间进场次数 ÷ 泊位数 ÷ 区间天数(次/日)。 - 首小时价:每场取当前生效规则集(`data_state = 1`、`check_state = 3`、`effect_time <= NOW()`、未过期),小型车(`car_type = 2`)优先,汇总覆盖前 60 分钟的计时档位;没有规则返回 `null`,页面显示「未配置」。`t_billing_*` 三张表排序规则不一致,连接处显式 `COLLATE utf8mb4_bin`。 - 当前价格实况:109 个在用场里 **38 个首小时 ¥3(中档)**,71 个没有首小时计时价;价格极差 ¥0.00 是真实结果,不是页面算错。 - **71 个「未配置」的构成(2026-11-09 复核)**:69 个是 `gen-demo-district-data.py` 补的 11 个旗县区演示车场(全部不在红山区,本来就没有收费规则,不是生产缺口);2 个是红山区真实车场 —— `P1504022CK`(红山公园封闭车场,229 泊位 / 16,845 单)只有按次档位(`billing_config_type = 2`,¥3/次),按「首小时计时价」口径确实为空;`P15040410707`(西城市场封闭停车场,259 泊位 / 69,076 单)当前生效规则集里没有任何 `t_billing_rule` 行。**本地这个比例不能当成生产配置率**。若以后要把「按次」车场也纳入价格分布,需要单独定口径(首小时价与按次价混排会抬高/扭曲平均值),不是当前页面算错。 #### 7.29.4 前端 - `src/views/gov/topicAnalysis/demo.ts` 抽出 `assembleSnapshot(topic, query, metrics, hours, daily, hotspotCells, meta)`,演示夹具 `buildDemoSnapshot` 改为调用它;`tests/verify-topic-analysis.mjs` / `verify-topic-export.mjs` 的断言值不变。`DEMO_ENABLED` 仍导出但页面不再使用。 - 新增 `mapper.ts`:DTO → `ParkingLot` / `TopicLotMetric`;坐标按 `coordType` 1 百度 / 2 WGS84 / 3 GCJ02 统一转 GCJ02;首小时价回写成 `首 60 分钟 X 元`,档位仍由 `parsePriceTier` 判。 - 24 小时帧补成 **24 × 全部车场**:热度 = 占用率 ×(45% 底 + 55% 该小时活动占比),没有活动的车场仍有占用率底座,不会在地图上消失。后端只返回有活动的「车场 × 小时」。 - 日趋势按自然日补齐:区间里没订单的那天是 0,不是「消失」;长区间不抽样。 - 热点网格仍由 `aggregateHotspots` 算(约 1km 网格,取车次前 8)。 - 新增 `request.ts` 放纯函数 `buildRequestBody`(不带 axios / Element Plus),`src/api/park/topicAnalysis/index.ts` 负责请求与 `toSnapshot`。 - `useTopicAnalysis.load()` 改成真实请求,保留请求序号竞态保护;失败时清空快照并显示后端文案 + 「重试」。 - `index.vue` 不再从演示夹具 `parkingLots` 判断「暂无车场」——库里 12 个旗县区都有在用场,下拉直接全开。 #### 7.29.5 门禁与实测 - 新增 `tests/verify-topic-analysis-mapper.mjs`(Vite SSR):坐标转换、缺 `berthFree` 不判满位、价格文案与档位、24 帧补齐、热度公式、日趋势补零、热点网格、请求体(结构类不带日期)。全部 28 个 `tests/verify-*.mjs` 通过。 - `npx eslint src/views/gov/topicAnalysis src/api/park/topicAnalysis` 无告警;`npm run ts:check` 仍只有 §11 第 7 条那 28 个存量错误,本页 0 新增。 - 浏览器实测(admin,48083):7 个专题全部 `200`,页头无「演示数据」角标。 - coverage:109 场 / 16,210 泊位 / 平均 148.7 泊位 / 智能设备覆盖 13.8%; - flow(近 7 天):进场 51,703 / 出场 39,555,峰值 08:00 12,559,与独立 SQL 一致;24 小时与 7 天趋势均渲染; - duration:平均 58 分钟、中位 53、周转 0.88 次/日;时长分布 40 / 47 / 22 / 0 / 0; - heat:平均占用率 70.6%、高热车场 30 个、最热 08:00、当前空余 4,655; - hotspot:8 个网格、最热 963 车次、覆盖 10 场、周边空余 1,139; - price:38 场 ¥3、71 场未配置,平均 ¥3.00、极差 ¥0.00; - revenue:今日 ¥12,767 / 16,821 单;近 7 天 ¥50,127 / 51,703 单。 - 区县筛选(红山区,近 7 天)¥37,360 / 34,882 单;空区间(2026-09-01~02)KPI 全 0、日趋势两点补零、不报错、不显示演示角标;1366×768 与 1920×1080 均无新增版式问题。 - 权限:菜单 100501 已存在且只授予角色 2001,管理员走超管旁路;本次**没有新增权限点、没有授权任何其他角色**。 #### 7.29.6 366 天区间性能(2026-11-09) 初始实测(本地副本、一个 366 天区间、109 个车场):**flow 7.38 s / heat 3.37 s**、duration 1.88 s、hotspot 0.95 s、revenue 1.61 s —— flow、heat 超出设计方案 §13.6 的 3 秒预算。`EXPLAIN ANALYZE` 定位到两处: 1. **连接顺序选错**:`selectHourlyFlow` / `selectDailyFlow` 的出场分支被优化器改成「按 109 个车场逐个回查 `order_park`」(`Nested loop … re-planned for each iteration`,每场约 45,542 行、29.6 ms),单条 3.1–3.3 s。这两条 SQL 改用 `STRAIGHT_JOIN`(新片段 `orderLotJoinStraight`),固定「先扫 `order_park` 再连 `parking_lot`」,回到 hash join 后降到 1.2 s。按车场汇总的 `selectLotAgg` / `selectLotFlow` / `selectDailyRevenue` **不要**加 —— 实测直连版本分别慢 0.0 / 0.2 / 0.0 秒。 2. **重复扫描**:`flow` 原来同时查 `selectLotFlow`(按车场汇总)与 `selectHourlyFlow`(按车场 × 小时),后者逐小时求和与前者完全等价(对拍 0 差异)。服务层改为 `toFlowRows(hourRows)` 直接复用小时间行,少两次 `order_park` 扫描;`heat` 同样只查一次。 优化后(同一 366 天区间,缓存预热):**flow 2.38 s、heat 1.27 s**、duration 1.89 s、hotspot 0.95 s、revenue 1.61 s。5 个专题响应与优化前逐字段一致(仅 `updatedAt` 不同);109 个车场的逐小时进/出求和 = 车场汇总 = `allDay`(667,559 进 / 653,235 出)全部对齐;7 天区间 flow 0.20 s、heat 0.13 s、hotspot 0.10 s。 试过不采用:CTE 物化(MySQL 自动 merge,`NO_MERGE` 也未生效,三条聚合约 3.5 s,无收益)。可再优化项:`park_in_time` 目前只有单列索引,若加 `(park_in_time, pl_no, data_state)` 覆盖索引,进场分支可从 350 ms 降到约 110 ms —— 属于生产库 DDL,本次不动,按生产规模再评估。本地副本只有 2 个月 66.8 万行,这组数字是本地基准,生产需按 §13.6 复测。 ### 7.30 一张图加「重点车场」「实时流量」入口 + 去掉「未定位资源」+ 去掉弹框「全屏打开」(2026-11-09) **导航加两个入口**:`src/views/gov/oneMap/nav.ts` 的 `PAGE_NAV` 从 5 项变 7 项,新两项排在「专题分析」之后, 和专题分析 / 视频监控 同一条按钮栏: | 入口 | 路径 | 权限码 | 打开方式 | |---|---|---|---| | 重点车场 | `/park/keyLotMonitor` | `park:dashboard:query` | 弹框内打开 | | 实时流量 | `/park/flowMonitor` | `park:flow-monitor:query` | 弹框内打开 | **七个入口一律在弹框里打开**(先做过一版 `PageNavItem.direct` 让这两项换页,用户要求改回弹框,`direct` 字段与 `handleNav()` 已删除,纳维按钮回到 `@click="openNav(item)"`):目标页组件直接挂进 `.om-embed`, 路由不变,地图和左右面板都还在。 **权限取值**:重点车场页没有独立权限点(走存量车场接口),沿用监管类页面的 `park:dashboard:query` (菜单 100504 本身也没有 permission 串);实时流量复用已有的按钮权限 100506 `park:flow-monitor:query`。 **没有新增权限点,也没有给任何角色补授权** —— 无权限时按钮不渲染,符合「有权限才显示」的老口径。 **去掉「未定位资源」**:资源清单原来在 7 行之后挂了一个「未定位资源」按钮 + 弹框,点开列出没有坐标的 点位。真实接口接进来后台账与点位已经分开报(§7.28:路侧泊位 2,575 只报数不上图),这一个入口既没有 可信数据也没有明确用途,整块删掉:按钮、弹框、`unlocatedResources` 计算属性、`.om-unlocated*` 样式, 以及为了它「没有标题、顶部留白压平」而加的 `.om-left .om-sec.is-last { padding-top: 12px }`(基础档与紧凑档各一条)。 删掉后左栏最后一张卡是「图层管理」,它有自己的标题,用回正常的 30px / 紧凑档 24px 顶部留白。 **去掉弹框右下角「全屏打开」**:`.om-embed-tools` 里只留关闭按钮(✕ / Esc / 点遮罩三种关法不变), `openEmbedFull()` 与 `.om-embed-full*` 样式一并删除;路由解析失败时的提示语也从「可以改用全屏打开」 改成「请从左侧菜单打开」—— 弹框里已经没有换页出口了。目标页自带的「全屏」按钮(重点车场 / 实时流量页头) 属于目标页本身,不受影响。 门禁:`tests/verify-one-map-nav.mjs` 改成 7 项,并断言没有任何 `direct` 换页入口; `tests/verify-one-map-nav-embed.mjs` 的地图按钮列表(`mapNavItems`)同步为 6 项。 实测(1920×1080,admin):按钮栏顺序 专题分析 / 重点车场 / 实时流量 / 视频监控 / 综合监测 / 车辆追溯, 点「重点车场」「实时流量」都留在 `/park-map`、弹框 `aria-label` 对得上、页面正常渲染,弹框右下角只剩 ✕; 资源清单 7 行、无「未定位资源」。`node --test tests/*.mjs` 65 通过 / `npm run ts:check` 28(存量)/ `npm run build:local` 退出 0。 ### 7.31 前端删掉上游「文档地址」广告条与登录页推广区(2026-11-09) 上游 yudao 在几乎每个列表页顶上挂一条绿色横幅:``, 渲染成「【功能权限】文档地址:https://doc.iocoder.cn/resource-permission」,点了跳上游官网。这些既不是本项目文档, 也不该出现在交付给使用方的停车平台上,全部删除。 **删除范围**(前端仓库 `parking-cloud-yudao-ui`): | 位置 | 内容 | |---|---| | 350 个 `.vue` / 389 处 | `` 标签整块删除(多行写法一并处理) | | `src/components/DocAlert/index.vue` | 组件本身删除(删完已无引用) | | `src/views/Login/components/LoginForm.vue` | 「萌新必读」分割线 + 开发指南 / 视频教程 / 面试手册 / 外包咨询 四个外链 | | `src/layout/components/UserInfo/src/UserInfo.vue` | 用户下拉菜单的「文档」项与 `toDocument()` | | `src/config/axios/service.ts` | 901 错误弹窗里的「参考 https://doc.iocoder.cn/ 教程」一行 | | 注释 7 处 | `infra/server/index.vue` 的排查提示、`mall/promotion/*.data.ts` 的 CrudSchema 文档链接改为不带 URL 的说法 | **没有用 `VITE_APP_DOCALERT_ENABLE=false` 关掉**:那只是把横幅藏起来,字符串和组件还在,换个环境变量又会冒出来。 要的是删干净,所以连组件一起删了。`.env` 里那个开关保留(组件没了它也不再生效),不影响其它逻辑。 **门禁**:新增 `tests/verify-no-doc-links.mjs`,扫描整个 `src/`(含注释),` 0`)。 页面在 true 时提示换名、不发新增请求;查重接口自身失败**不拦**新增(后端新增本身不校验重名)。 - **组织**:新增接口无条件用登录人组织覆盖 `orgId/orgName`(本机 admin → 10001 中兴智能交通)。 表单不提供组织选择,只显示「保存后归属当前登录人组织」。 **(2026-11-10 后续已改为新增/修改都可选组织,见 §7.35。)** - **经纬度可选但必须成对**:后端 `saveParkGeo` 直接对 `lonId` 取 `doubleValue()`,且 `parking_lot_geo.lon_id/lat_id` 是 `NOT NULL`,缺坐标会 500。为此在 `ParkingLotServiceImpl.SaveParkingLotInfo` 加了与 `UpdateParkingLotInfo` 一致的判空—— **两个坐标都有才落地理点**(`⚠️ 手改(非搬迁原样)`,这是本项目第三处非照搬改造)。 前端:两个都空允许(该车场不上一张图,表单里有说明),只填一个直接拦截。 - **照片**:新增时隐藏照片区(照片必须绑定 `plNo`),提示「可在创建后点编辑上传」。 - **创建后自动按名称筛选**:列表查询 `queryParkLotByCountryAndParkName` 没有 `ORDER BY`(InnoDB 按主键升序), 新车场会落到最后一页。创建成功后把搜索框置为新车场名称并回到第 1 页,用户立刻能看到刚建的车场 (搜索框可见,不算隐式行为)。 纯逻辑(`createEmptyCreateForm` / `buildCreatePayload` / `validateGeoPair`)放在 `views/park/parkLotList/edit.ts`,由 `tests/verify-park-lot-edit.mjs` 断言(含「新增不能带 plId」 「0 是有效泊位数」「只填经度不合法」)。 **实测(admin,2026-11-10)**: - 新增「本地验证-新增车场A」(不带坐标)→ 成功,`pl_no=P15040490013`,`data_status=1`、`org_id=10001`, `parking_lot_geo` 无行(判空生效); - 新增「本地验证-新增车场B」(118.96 / 42.27)→ `pl_no=P15040490014`,`parking_lot_geo` 两条 (百度 `lon_lat_type=1` + 火星 `lon_lat_type=3`)、`park_free_berth` 一条; - 重名再提交 → 页面提示「车场名称已存在」,无新增;只填经度 → 表单拦截「经度和纬度必须同时填写」; - 测试数据已清理:`parking_lot` / `parking_lot_geo` / `park_free_berth` 各删 2 行(按主键 + 编码双条件), Redis db1 的 `zteits-park:parking_lot:plNo_parkLotInfo`、`park_free_berth:plNo_freeBerth_map` 字段 HDEL、两个 geo GEO zset 成员 ZREM;车场总数回到 109。 **验证**:`mvn -pl yudao-server -am -DskipTests package` 退出 0;`node tests/verify-park-lot-edit.mjs` 全部通过;`npm run ts:check` 仍是 28 条存量基线。 ## 8. 视频接入(项目要求) ### 8.0 物联网模块:评估后关闭(2026-11-04) **结论:不启用。** 本项目只有视频设备接入,而视频取流不经过 IoT(模块内无任何 GB28181/ONVIF/RTSP 代码),存量地磁/道闸又沿用 `tp_p_eqp`,启用 IoT 只会多出第三套设备台账。 评估过程记录如下,重新开启时按本节回退。 关闭动作(三处代码 + 一处数据): | 位置 | 改动 | |---|---| | 根 `pom.xml` | 注释 `yudao-module-iot` | | `yudao-server/pom.xml` | 注释 `yudao-module-iot-biz` 依赖(并删掉下方重复的旧注释块) | | `YudaoServerApplication` | 注释 `IotStorageInitRunner` 的 import 与 `excludeFilters` 过滤项 | | `application.yaml` | 注释 `yudao.iot.message-bus` / `yudao.iot.storage` | | `system_menu` | id=860 及其 73 个子菜单 `visible=b'0'`(父菜单隐藏整棵子树;`routerHelper.ts` 用 `hidden: !visible`,所以 0=隐藏) | 验证:`mvn -pl yudao-server -am -DskipTests package` 通过,重新打包的 jar 内 `yudao-module-iot` 条目数为 **0**;48083 重启后 `Started YudaoServerApplication`, 登录、`/system/menu/list`、`/system/user/page`、 `/park/area/query-area-and-block-tree` 均 `code=0`;侧边栏已无「IoT 物联网」; `/iot/**` 返回 501「已禁用」。启动日志只剩一条 banner 提示 `[IoT 物联网 yudao-module-iot - 已禁用]`,无异常。 数据库里的 17 张 `iot_*` 表**保留未删**:与停车表无外键关联,不影响任何现有功能; 真要清理时按同名单表 `DROP` 即可。
评估过程:导入后的实测结果(供将来启用时参考) `parking_cloud_yudao` 原先没有 `iot_*` 表,前端点进「IoT 物联网」显示 「[IoT 物联网 yudao-module-iot - 表结构未导入]」——这是 `GlobalExceptionHandler` 对 `BadSqlGrammarException` 里含 `iot_` 的统一兜底文案,不是菜单或模块开关问题。 已从 `docs/sql/iot-2026-11-02-传播违法.sql.zip` 导入 **17 张表**(`iot_device`、`iot_product`、 `iot_product_category`、`iot_device_group`、`iot_thing_model`、`iot_device_shadow`、 `iot_alert_config`、`iot_alert_record`、`iot_data_rule`、`iot_data_sink`、`iot_scene_rule`、 `iot_ota_firmware`、`iot_ota_task`、`iot_ota_task_record`、`iot_device_modbus_config`、 `iot_device_modbus_point`、`iot_rule_scene`)。导入前确认目标库**无同名表**,脚本只含 `iot_*` 的 `CREATE/DROP`,不含 `USE`、不碰 `system_*` / `infra_*` / 停车业务表。 导入后实测可用(`/admin-api/*`,admin + tenant-id 1): | 能力 | 端点 | 状态 | |---|---|---| | 产品分页 / 分类 / 物模型 | `/iot/product/page`、`/iot/product-category/*`、`/iot/thing-model/list` | ✅ | | 设备分页 / 精简列表 / 地图点位 / 影子 | `/iot/device/page`、`/simple-list`、`/location-list`、`/device/shadow/get` | ✅ | | 设备分组 | `/iot/device-group/page`、`/simple-list` | ✅ | | 告警配置 / 告警记录 | `/iot/alert-config/page`、`/iot/alert-record/*` | ✅ | | 规则引擎(场景联动 / 数据流转) | `/iot/scene-rule/*`、`/iot/data-rule/*` | ✅ | | 设备消息历史 / 属性历史 / 首页统计 | `/iot/device/message/page`、`/iot/statistics/get-summary` | ❌ 500 | 失败的两类是同一根因:`yudao.iot.storage.type: tdengine`,时序数据(`device_message`、 属性历史)走 TDengine/IoTDB,本机没有部署(启动类也按 D4 排除了 `IotStorageInitRunner`)。 `device_message` 表不在 MySQL 里,所以查询报 `Table 'parking_cloud_yudao.device_message' doesn't exist`。 `get-summary` 把 MySQL 计数和 TDengine 消息计数拼在一个接口里,一处失败整页显示 `--`。 **影响范围仅限「消息历史/属性历史/IoT 首页统计」三处**,台账、产品、物模型、告警、规则全部可用。 接真实设备(需要看上报报文)时再部署 TDengine,并把 `IotStorageInitRunner` 从 `excludeFilters` 移除。 另有两点须知: - `yudao-module-iot-gateway` 是**独立部署**的协议服务(TCP 8091 / UDP 8093 / WebSocket 8094 / MQTT 1883 / Modbus 503),`yudao-server` 只依赖 `iot-biz`,**没有启动任何协议端口**。 因此本地导进来的 28 台设备不会有真实连接,在线状态停留在数据快照里。 - IoT 设备管理页是卡片视图(不是表格),与停车侧 `el-table` 风格不同,属于上游原样。
#### 为什么不用 IoT 管存量的停车设备 | 指标 | 数字 | |---|---| | `tp_p_eqp`(地磁,全部 `eqp_type=1`) | 500 台,带 `berth_no` / `is_online` / 心跳 / `gateway_no` / `rssi` / `mac` | | `parking_lot_equipment`(车场设备树) | 161 条:LED 显示屏 114、摄像头 46、道闸 1(类型见 `parking_lot_equipment_type`) | | `tp_p_eqp_log` / `tp_p_eqp_inout_record` | 按月分表,已建到 2027-03 | | Java 里含 `Eqp` 的类 | 469 个(park 268 / api 149 / order 28 / pay 9) | | 引用 `tp_p_eqp` 的 mapper XML | 23 | 存量「设备管理」不是一张台账,是一条业务链:地磁上报 → `tp_p_eqp_log` → 泊位占用 → 停车记录 → 计费 → 在线率考核/巡查/异常稽核,横跨 park、order、pay 三个模块。 换成 IoT 物模型等于重做这条链,收益(产品/物模型/影子/OTA/规则)对已稳定运行的地磁没有增量价值。 `parking_lot_equipment` 里虽有 46 条 `equipment_type=2`(摄像头)记录,但只有 `equipment_name` / `channel_id` / `area_id`,没有码流地址、国标编码、云台能力, 不能当视频台账用——视频设备的独立建模理由在这里,不在「停车设备管理不好用」。 上游 `yudao-module-iot` **没有** GB28181 / ONVIF / RTSP 视频接入能力;它只有设备/产品/物模型/OTA/规则/告警和 TCP/UDP/HTTP/MQTT/EMQX/CoAP/Modbus 协议。 建议方案(待选型): - ~~先挂载 `yudao-module-iot` 作为设备接入底座~~ —— 2026-11-04 评估后关闭,见 §8.0; 取流不经过 IoT,设备台账也不需要 IoT; - 新增 `parking-module-video`,对接开源视频网关: - **WVP-PRO**(GB28181 信令 + 设备管理)+ **ZLMediaKit**(流媒体); - 或直接接 RTSP/ONVIF 的轻量网关。 - 该模块独立于 IoT,通过 `parking-module-api` 暴露视频分页/预览/回放接口。 若将来确实需要 IoT(接 MQTT 类新传感器、统一告警中心、跨设备规则联动), 按 §8.0 表格里的四处开关重新打开即可,数据表已经就位。 ### 8.1 WVP-PRO 合并为 Maven 模块(2026-11-09) 方案见 `docs/superpowers/specs/2026-11-09-WVP-PRO视频能力合并方案.md`。 **做法**:把 gitee `pan648540858/wvp-GB28181-pro` 3.0.0(commit `d8c95d0`,MIT)的 `src/main/java`(806 个类,包名 `com.genersoft.iot.vmp.*`)整体搬进新模块 `parking-module-video`,与 `parking-module-park` 平级,随 `yudao-server` 打进同一个 jar。 **不改 `YudaoServerApplication`**:模块自带 `WvpAutoConfiguration`(`@AutoConfiguration` + `@ComponentScan` + `@MapperScan`),经 `AutoConfiguration.imports` 注册。视频子系统可用 `parking.video.enabled=false` 整体关掉,关掉后 WVP 的 Controller/Mapper/定时任务一个都不进容器。 | 关键点 | 结论 | |---|---| | 同名类几十个(`ChannelController`、`DeviceMapper`…) | 扫描与 Mapper 扫描都用**全限定类名**做 Bean 名(`FullyQualifiedAnnotationBeanNameGenerator`),与存量 `LegacyMapperBeanNameGenerator` 同一思路 | | `@DependsOn("sipLayer")` 按名字引用 | 唯一受影响的按名引用:把 `SipLayer` 排除出扫描,在自动配置里声明 `@Bean sipLayer()` | | `interface-authentication` | **必须保持 `true`**。置 `false` 时 `JwtAuthenticationFilter` 会塞一个"principal 为 null 但 authenticated=true"的令牌,等于把 `/api/**` 全开 | | 视频平台的账号 | **不建 `wvp_user` 种子账号**。`/api/user/login` 免鉴权,admin/admin 等于公开后门;身份统一来自 yudao(`VmpYudaoSsoFilter`) | | Redis | 单实例跑内存门面(`InMemoryStore` + `LocalMsgBus`);`RedisRpcConfig` 的模式判断改读 `parking.video.redis-mode`,否则它会误判成 Redis 模式并对 `null` 模板发消息 | | JDK 21 API | 虚拟线程、`SequencedCollection` 共 6 处 → 新增 `VmpConcurrency`(有界平台线程池)替代 | | SIP 生命周期 | 新增 `@PreDestroy` 停协议栈。JAIN-SIP 起非守护线程,原版上下文启动失败后 JVM 退不出去,5060 被自己占住 | | `System.exit(1)` | `SipLayer` 里两处改抛 `IllegalStateException`,让 Spring 走正常关闭(原写法跳过数据源/Redis 收尾) | | 建表 | `docs/sql/2026-11-09-WVP视频接入-wvp表初始化.sql`,22 张 `wvp_*` 表,幂等,无 DROP/INSERT,只对 `parking_cloud_yudao` 执行 | **坑:WVP 原版 `WebSecurityConfig` 上的 `@EnableGlobalMethodSecurity` 必须删掉**(2026-11-09)。 它注册的是 Spring Security 5 时代的 `MethodSecurityInterceptor`,该拦截器**构造时就捕获了 当时的 `SecurityContextHolder` 策略**;本项目 yudao 与 parking 两个 starter 各注册了一个 `setStrategyName` 的 `MethodInvokingFactoryBean`,最终生效的策略与它捕获的不是同一个 ThreadLocal。表现是 yudao 的 `@EnableMethodSecurity` 校验通过后,遗留拦截器从另一个 holder 读到空认证,抛 `AuthenticationCredentialsNotFoundException`,**所有 `@PreAuthorize` 接口** (`/admin-api/park/**`、`system/user/page`、`system/role/page`、`infra/config/page`…)统统变成 `code=500 msg=系统异常`,与具体业务无关,很容易误判成数据问题。WVP 自身没有任何 `@PreAuthorize`(鉴权粒度只有"是否登录",见 `VmpYudaoSsoFilter`),删掉不影响视频平台, 方法级权限由 yudao 的 `@EnableMethodSecurity` 统一处理——**不要再加回 `@EnableGlobalMethodSecurity`**。 另注:`POST` 带非法请求体时返回 400 是 `@Valid` 参数解析先于方法级鉴权,这种 400 不能证明鉴权正常。 **验收**:打包通过;`Started YudaoServerApplication in 18.187 seconds`;`POST /api/user/userInfo` 带 yudao 令牌 → `code=0`,无令牌 → **401**;`GET /api/device/query/devices` → `code=0`; Ctrl-C 后 `[SIP SERVER] 协议栈已停止`,5060/48083 释放。 **外部依赖**:ZLMediaKit 不在本仓库(C++ 独立进程),未部署时启动日志每 30 秒一条 `[ZLM-尝试连接]失败`,属预期;设备注册与目录同步不受影响,只是点播不出图。 **前端**:实时预览 / 通道树 / 回放 / 云台已移植到 `parking-cloud-yudao-ui` 的 `/video/monitor`(`src/api/video/wvp.ts`、`videoMonitor/wvpSource.ts`、 `components/WvpPlayer.vue`)。接口走 WVP 原生 `/api/**` + yudao 令牌,用独立 axios 实例直连 `${VITE_BASE_URL}/api`,**不需要 Vite 代理**(后端 CORS 已放行)。播放器 WebRTC(WHEP)优先、HLS(video.js)回退,不引入上游 jessibuca。 行为要点:视频平台没有通道时页面自动退回演示夹具(角标「演示数据」),有通道时角标 「国标视频平台」;点播/停播按宫格变化对账,不轮询;回放先查真实录像清单再起流, 拖动走 `playback/seek`;页面被 keep-alive 缓存,切走用 `onDeactivated` 停流。 抓图与片段下载:真实画面把 `