盘点时间:2026-09-16 · 范围:
ai-frontend/src(后端ai-server仅列需要配合的改动) 盘点方式:全仓electron|ipcRenderer|isElectron命中点逐个读源码确认用途 结论:共 14 项,其中 3 项「直接删」、6 项「前端改造即可」、5 项「需后端配合」;4 项存在 Web 天然能力缺失,给了降级方案进度(2026-09-16 晚):第 3、4、5 项已完成(详见文末「已实施记录」)。
| 删除对象 | 说明 |
|---|---|
core/layouts/default/setting/components/OfflineMapModal.vue |
离线地图目录选择弹窗(ipc selectFolder) |
core/layouts/default/setting/components/CellLocalModal.vue |
基站包路径选择弹窗(ipc selectBinFile + 读前 16 字节) |
core/utils/caseGlobalConfig.ts |
仅为把这两条路径通过 Electron ipc 写到本机配置文件,随选择功能一起删除 |
SettingFooter.vue 两个入口按钮/弹窗/引导事件监听、components/index.ts 两个导出 |
一并清理 |
caseConfigGuide.ts 的 hydratePersistedCaseGlobalConfig() 调用与两个弹窗事件常量 |
保留 initGlobalCaseConfig()(现在只从后端读配置)与 OPEN_SETTING_DRAWER_EVENT |
删除后路径怎么配(重要):两个路径本来就存在服务端配置里(SystemConfManager → QingJian/conf/c5e503ad.json 的 mapLocal / cellLocal 字段),
前端只是通过 /sys/getMaplocal、/sys/getCellLocal 读回来缓存。所以 UI 删掉后有两种设置方式:
GET /js/a/sys/mapLocal?path=...、GET /js/a/sys/cellLocal?path=...(/sys/cellLocal 会校验文件存在且非目录)QingJian/conf/c5e503ad.json:{"mapLocal":"E:/map","cellLocal":"E:/cell/xxx.bin"}(改完需重启后端,配置是启动时读入内存的)后端(SystemController + SystemService)
GET /sys/map/pmtiles:从 mapLocal 配置目录读 china.pmtiles,支持 HTTP Range(无 Range → 200 全量 + Accept-Ranges: bytes;单区间 → 206 + ResourceRegion;多区间/非法/越界 → 416)GET /sys/map/style:返回配置目录里的自定义样式 behavior-spacetime-style.json(没有则 404,前端自动回退内置样式)Result(响应体是文件内容),文件名是服务端固定白名单常量、不接受调用方传参mapPmtiles / mapStyle / resolveMapFile / serveLocalFile),controller 仅转发前端
core/utils/map/offlineMap.ts:删除 ElectronPmtilesSource(ipc 读本机文件),新增 HttpPmtilesSource(fetch + Range 头取块)、getOfflineMapPmtilesUrl()、getOfflineMapStyleUrl()、getBuiltinPmtilesUrl()、resolveOfflinePmtilesUrls()BaseStationQueryMap.vue、StationMapModal.vue、behaviorSpacetime/index.vue):
new ElectronPmtilesSource(本地路径) → new HttpPmtilesSource(后端 URL);自定义样式从 ipc 读文件改为 fetch /sys/map/style;未配置服务端目录时自动回退到前端静态资源 public/resource/map/china.pmtiles(内置样式本来就指向 pmtiles://resource/map/china.pmtiles,现在会把该地址也注册成瓦片源)mapLocal 目录(走后端 Range);②瓦片放前端构建产物 public/resource/map/(走静态服务,需该静态服务支持 Range)验证
mvn -pl ai-server -B compile → BUILD SUCCESS0-99/100-/-100/999-999 → 206;0-1,5-6/abc/2000-3000/-0 → 416;无 Range → 200)全通过offlineMap.ts + 起本地 Range 服务,跑 10 项断言(URL 拼装、Range 头、返回字节、空参数不发请求、404 返回空)全通过eslint --max-warnings 0 0 错误;vue-tsc 全仓仍 103(无新增)| # | 功能 | 现状(Electron 依赖) | 迁移方案 | 后端改动 | 难度 |
|---|---|---|---|---|---|
| 1 | 窗口最小化/最大化/关闭 | window.electron.windowMinimize/Maximize/Close + window:maximize-change 事件 |
✅ 已完成:4 个组件 + useElectronWindow 已删除(本就没有页面挂载它们,纯死代码) |
无 | — |
| 2 | 版本号展示 | useElectronWindow().getVersion() |
✅ 已完成:AppLogo/ProductInfoModal 改用构建期 __APP_INFO__.pkg.version(vite define 已注入) |
无 | — |
| 3 | 离线地图瓦片读取 | ElectronPmtilesSource.getBytes() 走 ipc readBinaryFileRange |
✅ 已完成:后端 GET /sys/map/pmtiles(支持 Range)+ 前端 HttpPmtilesSource;未配置目录时回退前端静态 resource/map/china.pmtiles |
已实现 | — |
| 4 | 离线地图目录选择 | ipc controller/os/selectFolder |
✅ 已删除功能;路径改由服务端配置(/sys/mapLocal 或 QingJian/conf/c5e503ad.json) |
已实现 | — |
| 5 | 基站库(.bin)选择 | ipc selectBinFile + 读前 16 字节校验 |
✅ 已删除功能;路径改由服务端配置(/sys/cellLocal 或同一配置文件) |
已实现 | — |
| 6 | 案件全局配置持久化 | ipc readCaseGlobalConfig / writeCaseGlobalConfig |
改存后端(按 caseId)——/sys/getMaplocal、/sys/cellLocal 接口已存在,直接复用;纯本机偏好可退化为 localStorage |
复用现有接口 | ★ |
| 7 | 授权文件选择 | ipc controller/effect/selectFile → 传服务端路径给 /sys/noNet |
✅ 已完成:<input type=file accept=".xlts"> + POST /sys/noNet(multipart),后端落盘到 LICENSE_PATH 并校验激活 |
已实现 | — |
| 8 | 升级包选择 | ipc controller/effect/selectFile → /sys/upgrade?path= |
✅ 已完成:<input type=file accept=".zip"> + POST /sys/uploadUpgrade(multipart,带进度),后端落盘临时 zip 后复用原 upgrade 流程并清理 |
已实现 | — |
| 9 | 图谱历史文件读写 | ipc readTextFile / writeTextFile(路径来自后端 graph_his,本质是服务端文件) |
✅ 已完成:新增 GET/POST /graph/history/{id}/content(服务端校验路径必须在 GRAPH_HIS_PATH 内),前端按历史ID读写 |
已实现 | — |
| 10 | 基站 xls 选择 + 打开目录 | ipc selectXlsFile / openDirectory |
✅ 已完成:POST /tower/covFile(multipart 上传,结果以附件流返回浏览器下载);模板改为 GET /tower/template 附件下载;"打开文件夹"交互删除 |
已实现 | — |
| 11 | 头像本地路径 | window.electron.getPathForFile(file) 存本机路径;PersonAvatar 再用 ipc 读图 |
改为上传图片→后端存盘并返回 URL(可复用 LocalFileStorageUtil + base-url 配置),前端 <img :src> 直显;预览用 URL.createObjectURL(file) |
需要(新增图片上传接口) | ★★★ |
| 12 | file: 协议兜底 baseURL |
governApi.ts 的 getPackagedFallbackOrigin()(location.protocol === 'file:' → http://localhost:8980) |
✅ 已完成:常量与函数已删,host 兜底链只剩同源 | 无 | — |
| 13 | window.electron 类型声明 |
types/global.d.ts |
第 1~12 项做完后删除 | 无 | ★ |
| 14 | preFile(本地路径数组)注释 |
governApi.ts 注释「Electron 本地路径方式,保留兼容」 |
代码实际只被 importList 用服务端路径调用(重新清洗),逻辑本身没问题,仅更新注释 | 无 | ★ |
现状:ElectronPmtilesSource.getBytes(offset, length) 用 ipc 读本机文件片段,PMTiles 库靠它按需取瓦片。
三个地图组件构造 Source:BaseStationQueryMap.vue:107、behaviorSpacetime/index.vue:2425、StationMapModal.vue:154。
// 方案 A(推荐,瓦片放服务器):后端加一个支持 Range 的接口
// GET /sys/map/pmtiles → 从 mapLocal 配置的目录流式返回 china.pmtiles
// Spring 的 ResourceHttpRequestHandler 原生支持 Range(206),无需手写
export class HttpPmtilesSource {
constructor(private url: string) {}
getKey() { return this.url; }
async getBytes(offset: number, length: number) {
const res = await fetch(this.url, { headers: { Range: `bytes=${offset}-${offset + length - 1}` } });
return { data: await res.arrayBuffer() };
}
}
// 方案 B(瓦片在用户本机):<input type=file accept=".pmtiles"> 选一次,之后纯前端切片
export class FilePmtilesSource {
constructor(private file: File) {}
getKey() { return `${this.file.name}:${this.file.size}`; }
async getBytes(offset: number, length: number) {
return { data: await this.file.slice(offset, offset + length).arrayBuffer() };
}
}
两个类都满足 PMTiles 的 Source 接口,替换 new ElectronPmtilesSource(path) 即可;方案 B 完全不需要后端。
这三处的路径最终都存到服务端(/sys/mapLocal、/sys/cellLocal、/sys/upgrade),Electron 里只是用对话框拿到一个字符串。Web 里最贴近的做法是后端提供受限的目录浏览:
GET /sys/browse?path=&root= → 返回该目录下的子目录/文件(必须白名单根目录 + normalize 防 ../ 穿越,参考 AgentPythonFileController.assertSafeSegment 的写法)<input type="file" accept=".xlts"> → const text = await file.text()/sys/noNet 从「收 license 路径」改为「收文本内容或 multipart」,服务端自行写入 PathConst.LICENSE_PATH 再走原有校验POST /case/avatar 之类)→ 后端用 LocalFileStorageUtil 存盘 → 返回可访问 URL(file-url.base-url 已配置为 http://localhost:8980/js/a)→ 库表里存 URLfile:///C:/...png 这种本机绝对路径,浏览器读不到 → 需要一次性迁移脚本(把这些行标记为无头像,显示占位图),或让用户重新上传PersonAvatar.loadLocalImage() 里的 ipc 读图整段删除,改为直接绑定 URL| 批次 | 内容 | 说明 |
|---|---|---|
| P0(无依赖,随时可做) | 1、2、6、12、14 | 纯删除/替换,风险最低,先把「Electron 存在感」降下来 |
| P1(前端改造即可) | 3-方案B、7、9、10 | 3B/9/10 体验与 Electron 版基本等价;7 需后端小改(收内容) |
| P2(需后端补接口) | 3-方案A ✅、4 ✅、5 ✅、8 | 3/4/5 已完成(见文末已实施记录);8 仍在待办 |
| P3(涉及存量数据) | 11 | 需要迁移策略与用户沟通 |
| 收尾 | 13(删类型声明)+ 全仓 grep 归零 | 验收:grep -ri "electron\|ipcRenderer" ai-frontend/src 为 0 |
grep -rn "electron\|ipcRenderer\|isElectron" ai-frontend/src 只剩注释里的历史说明(无任何 window.electron / ipc 调用)types/global.d.ts 里 window.electron 声明已删.xlts 文件走通一次授权成功file:// 数据不报错(显示占位图)eslint(改动文件)0 错误;vue-tsc 不新增错误