feat(platform): 部署配置向导 + 地图管理,地图编辑器接入统一存取与 AI 助手

- 配置向导:登录按 deployment 画像引导平台选型(导航方式/模块/场景),未完成则路由守卫强制进入 /wizard;选型驱动菜单按需裁剪,并联动 SimpleLite 写 plugins/active-scenes.json + 透传 --scenes 选择性加载导航场景插件
- 地图管理页:服务器地图列表/使用/重命名/删除、地图合并、多地图连接管理
- 地图编辑器:项目存取改为存入地图管理统一目录(同名替换确认),支持 ?map=/?new= 进入,新增右侧可停靠 AI 助手面板
- 集成 PTL 拣选模块;新增车队分配面板(运维总览/筛选联动)
- SimpleLiteBuildSync 同步运行时依赖 DLL;重新构建前端静态资源

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zhaowei.huang
2026-06-03 09:37:07 +08:00
co-authored by Cursor
parent e2269e430d
commit 7382e85598
109 changed files with 3481 additions and 398 deletions
@@ -280,3 +280,136 @@ export const aiConfigApi = {
get: () => unwrap<AiConfig>(http.get(`${AI_BASE}/`)),
save: (cfg: AiConfig) => unwrap<{ saved: boolean }>(http.post(`${AI_BASE}/`, cfg))
}
// ──────────────────────────────────────────────────────────────────────────
// 地图管理(固定文件夹 + 按名称列表 / 保存 / 删除 / 使用 / 编辑打开)
// 对应 SimpleLite MapEditApiController 的 /map-edit/maps* 接口。
// ──────────────────────────────────────────────────────────────────────────
export interface MapListItem {
/** 地图名称(文件名去掉 .json 后缀),列表展示与各操作都以它为标识。 */
name: string
fileName: string
sizeBytes: number
modified: string
/** 是否为当前项目默认使用的地图(列表高亮)。 */
isCurrent: boolean
}
export interface MapListResult {
directory: string
currentFileName: string | null
maps: MapListItem[]
}
export interface MapLoadSummary {
name: string
fileName: string
path: string
sites: number
tracks: number
specials: number
missions: number
}
export interface SceneTaskBusyCar {
id: number
name: string
reasons: string[]
}
export interface SceneTaskStatus {
hasTask: boolean
busyCount: number
busyCars: SceneTaskBusyCar[]
}
export interface MapSaveResult {
name: string
fileName: string
path: string
savedAt: string
}
/** 保存结果:success 正常返回 dataconflict=true 表示同名地图已存在,调用方应弹「替换」确认。 */
export type MapSaveOutcome =
| { ok: true; data: MapSaveResult }
| { ok: false; conflict: boolean; message: string }
export interface MapMergeResult {
name: string
fileName: string
path: string
/** 底图 = 合并时的「当前使用地图」名。 */
baseMap: string
/** 合并进来的源地图名(去重、且不含当前地图自身,按叠加顺序)。 */
sources: string[]
sourceCount: number
/** target 与当前地图同名 → 本次合并覆盖了当前地图。 */
overwroteCurrent: boolean
/** 合并后统计(用于结果提示)。 */
sites: number
tracks: number
specials: number
missions: number
savedAt: string
}
/** 合并结果:conflict=true 仅表示目标地图同名已存在,调用方应弹「替换」确认后重试。 */
export type MapMergeOutcome =
| { ok: true; data: MapMergeResult }
| { ok: false; conflict: boolean; message: string }
export const mapsApi = {
list: () => unwrap<MapListResult>(http.get(`${BASE}/maps`)),
sceneTaskStatus: () => unwrap<SceneTaskStatus>(http.get(`${BASE}/maps/scene-task-status`)),
/**
* 保存当前场景为固定文件夹内的地图。overwrite=false 且同名已存在时后端回 409,
* 这里翻译为 { ok:false, conflict:true },让调用方弹「是否替换原地图」确认框。
*/
async save(name: string, overwrite = false): Promise<MapSaveOutcome> {
const { data } = await http.post<MapEditEnvelope<MapSaveResult>>(`${BASE}/maps/save`, { name, overwrite })
if (data?.success) return { ok: true, data: data.data as MapSaveResult }
return { ok: false, conflict: data?.code === 409, message: data?.message ?? '保存失败' }
},
/** 加载指定地图到场景以供编辑(不校验任务、不改当前使用地图)。 */
open: (name: string) => unwrap<MapLoadSummary>(http.post(`${BASE}/maps/open`, { name })),
/**
* 设为当前使用地图并加载。后端切换前会校验场景无车辆任务;存在任务回 409,
* unwrap 会抛出携带后端友好文案的 Error,调用方 try/catch 提示即可。
*/
use: (name: string) => unwrap<MapLoadSummary>(http.post(`${BASE}/maps/use`, { name })),
delete: (name: string) =>
unwrap<{ name: string; fileName: string; deleted: boolean }>(
http.delete(`${BASE}/maps/${encodeURIComponent(name)}`)
),
/** 重命名 maps 目录下的地图文件(from / to 均为不含扩展名的地图名)。 */
rename: (from: string, to: string) =>
unwrap<{ from: string; to: string; fileName: string; path: string }>(
http.post(`${BASE}/maps/rename`, { from, to })
),
/**
* 把选中的地图合并进「当前使用地图」后另存为新地图(语义同桌面端「合并」= SimpleProject.ImportFile)。
* sources 为要合并进来的源地图名数组(≥1,当前地图自身会被后端忽略),以「当前使用地图」为底图依次叠加。
* target 与当前地图同名 + overwrite 即覆盖当前地图。
* 目标同名且 overwrite=false 时后端回 409 → { ok:false, conflict:true },调用方弹「替换」确认;
* 当前未设置使用地图(400)/ 场景内有车辆任务(409)时 message 不含「已存在」,conflict=false,调用方直接提示。
*/
async merge(sources: string[], target: string, overwrite = false): Promise<MapMergeOutcome> {
const { data } = await http.post<MapEditEnvelope<MapMergeResult>>(`${BASE}/maps/merge`, {
sources,
target,
overwrite
})
if (data?.success) return { ok: true, data: data.data as MapMergeResult }
const conflict = data?.code === 409 && (data?.message ?? '').includes('已存在')
return { ok: false, conflict, message: data?.message ?? '合并失败' }
}
}