# Movement Supervision 组件架构分析文档 ## 概述 `movement-supervision.vue` 是一个基于 Vue 3 的运动监控组件,主要用于实时监控和显示机器人的运动状态。该组件基于 Meta2d 2D 图形库构建,提供了机器人位置跟踪、路径显示、区域管理等功能。 ## 核心架构 ### 技术栈 - **Vue 3** - 采用 Composition API - **TypeScript** - 类型安全 - **Meta2d** - 2D 图形引擎 - **RxJS** - 响应式编程 - **WebSocket** - 实时通信 ### 依赖关系图 ``` movement-supervision.vue ├── EditorService (extends Meta2d) ├── Scene API (场景管理) ├── Robot API (机器人管理) ├── WebSocket (实时数据) └── UI Components ├── RobotGroups ├── PenGroups ├── RobotDetailCard ├── PointDetailCard ├── RouteDetailCard └── AreaDetailCard ``` ## 组件分析 ### 1. Props 定义 ```typescript type Props = { sid: string; // 场景ID id?: string; // 机器人组ID(可选) }; ``` ### 2. 核心状态管理 #### 2.1 编辑器实例 ```typescript const editor = shallowRef(); ``` - **作用**: 保存 2D 编辑器实例 - **生命周期**: 在 `onMounted` 中初始化 #### 2.2 WebSocket 连接 ```typescript const client = shallowRef(); ``` - **作用**: 维护与后端的实时连接 - **生命周期**: 在组件卸载时自动关闭 #### 2.3 当前选中对象 ```typescript const current = ref<{ type: 'robot' | 'point' | 'line' | 'area'; id: string }>(); ``` - **作用**: 跟踪当前选中的对象类型和ID - **用途**: 控制右侧详情面板的显示 ## 核心业务方法详解 ### 1. 场景加载 (`readScene`) ```typescript const readScene = async () => { const res = props.id ? await getSceneByGroupId(props.id, props.sid) : await getSceneById(props.sid); title.value = res?.label ?? ''; editor.value?.load(res?.json); }; ``` **功能**: 加载场景数据 **执行流程**: 1. 根据是否传入机器人组ID选择不同的API调用 2. 设置场景标题 3. 将场景JSON数据加载到编辑器中 **调用时机**: 组件挂载时 ### 2. 实时监控 (`monitorScene`) ```typescript const monitorScene = async () => { client.value?.close(); const ws = await monitorSceneById(props.sid); if (isNil(ws)) return; ws.onmessage = (e) => { const { id, x, y, active, angle, path, ...rest } = JSON.parse(e.data || '{}'); if (!editor.value?.checkRobotById(id)) return; editor.value?.updateRobot(id, rest); if (isNil(x) || isNil(y)) { editor.value.updatePen(id, { visible: false }); } else { editor.value.refreshRobot(id, { x, y, active, angle, path }); } }; client.value = ws; }; ``` **功能**: 建立WebSocket连接,接收机器人实时数据 **执行流程**: 1. 关闭现有连接 2. 创建新的WebSocket连接 3. 设置消息处理函数 4. 解析接收到的机器人数据 5. 更新编辑器中的机器人状态 **数据处理逻辑**: - 检查机器人是否存在于当前场景 - 更新机器人基本信息 - 根据坐标有效性控制机器人可见性 - 刷新机器人位置、状态、角度和路径 ### 3. 对象选择处理 ```typescript watch( () => editor.value?.selected.value[0], (v) => { const pen = editor.value?.getPenById(v); if (pen?.id) { current.value = { type: <'point' | 'line' | 'area'>pen.name, id: pen.id }; return; } if (current.value?.type === 'robot') return; current.value = undefined; }, ); ``` **功能**: 监听编辑器选中对象变化 **执行流程**: 1. 获取当前选中对象的ID 2. 根据对象类型设置当前状态 3. 特殊处理机器人选择(避免被覆盖) ### 4. 机器人选择 (`selectRobot`) ```typescript const selectRobot = (id: string) => { current.value = { type: 'robot', id }; editor.value?.inactive(); }; ``` **功能**: 选择特定机器人 **执行流程**: 1. 设置当前选中对象为机器人类型 2. 取消编辑器中的其他激活状态 ## EditorService 核心方法分析 ### 1. 机器人管理 #### 1.1 初始化机器人 (`initRobots`) ```typescript public async initRobots(): Promise { await Promise.all( this.robots.map(async ({ id, label, type }) => { const pen: MapPen = { ...this.#mapRobotImage(type, true), id, name: 'robot', tags: ['robot'], x: 0, y: 0, width: 74, height: 74, lineWidth: 1, robot: { type }, visible: false, text: label, textTop: -24, whiteSpace: 'nowrap', ellipsis: false, locked: LockState.Disable, }; await this.addPen(pen, false, true, true); }), ); } ``` **功能**: 批量初始化机器人图形对象 **核心逻辑**: 1. 遍历所有机器人数据 2. 创建对应的图形对象(Pen) 3. 设置机器人图片、尺寸、标签等属性 4. 添加到2D画布中(初始不可见) #### 1.2 刷新机器人状态 (`refreshRobot`) ```typescript public refreshRobot(id: RobotInfo['id'], info: Partial): void { const pen = this.getPenById(id); const { rotate: or, robot } = pen ?? {}; if (!robot?.type) return; const { x: ox, y: oy } = this.getPenRect(pen!); const { x: cx = 37, y: cy = 37, active, angle, path: points } = info; const x = cx - 37; const y = cy - 37; const rotate = angle ?? or; const path = points?.map((p) => ({ x: p.x - cx, y: p.y - cy })) ?? robot.path?.map((p) => ({ x: p.x + ox! - x, y: p.y + oy! - y })); const o = { ...robot, ...omitBy({ active, path }, isNil) }; if (isNil(active)) { this.setValue({ id, x, y, rotate, robot: o, visible: true }, { render: true, history: false, doEvent: false }); } else { this.setValue( { id, ...this.#mapRobotImage(robot.type, active), x, y, rotate, robot: o, visible: true }, { render: true, history: false, doEvent: false }, ); } } ``` **功能**: 更新机器人实时状态 **核心逻辑**: 1. 获取现有机器人对象 2. 计算相对坐标(以机器人中心为原点) 3. 处理路径点的坐标转换 4. 根据运行状态切换机器人图片 5. 更新位置、角度、路径等信息 ### 2. 场景数据处理 #### 2.1 场景加载 (`load`) ```typescript public async load(map?: string, editable = false, detail?: Partial): Promise { const scene: StandardScene = map ? JSON.parse(map) : {}; if (!isEmpty(detail?.group)) { scene.robotGroups = [detail.group]; scene.robots = detail.robots; } const { robotGroups, robots, points, routes, areas } = scene; this.open(); this.setState(editable); this.#loadRobots(robotGroups, robots); await this.#loadScenePoints(points); this.#loadSceneRoutes(routes); await this.#loadSceneAreas(areas); this.store.historyIndex = undefined; this.store.histories = []; } ``` **功能**: 完整加载场景数据 **执行流程**: 1. 解析场景JSON数据 2. 处理机器人组详情(如果有) 3. 分别加载机器人、点位、路线、区域 4. 重置历史记录 #### 2.2 场景保存 (`save`) ```typescript public save(): string { const scene: StandardScene = { robotGroups: this.robotGroups.value, robots: this.robots, points: this.points.value.map((v) => this.#mapScenePoint(v)).filter((v) => !isNil(v)), routes: this.routes.value.map((v) => this.#mapSceneRoute(v)).filter((v) => !isNil(v)), areas: this.areas.value.map((v) => this.#mapSceneArea(v)).filter((v) => !isNil(v)), blocks: [], }; return JSON.stringify(scene); } ``` **功能**: 将当前场景序列化为JSON **执行流程**: 1. 收集所有场景元素 2. 将图形对象转换为场景数据格式 3. 过滤无效数据 4. 序列化为JSON字符串 ### 3. 2D 渲染系统 #### 3.1 自定义渲染函数注册 ```typescript #register() { this.register({ line: () => new Path2D() }); this.registerCanvasDraw({ point: drawPoint, line: drawLine, area: drawArea, robot: drawRobot }); this.registerAnchors({ point: anchorPoint }); this.addDrawLineFn('bezier2', lineBezier2); this.addDrawLineFn('bezier3', lineBezier3); } ``` **功能**: 注册自定义渲染函数 **包含内容**: - 点位渲染 (`drawPoint`) - 线路渲染 (`drawLine`) - 区域渲染 (`drawArea`) - 机器人渲染 (`drawRobot`) - 贝塞尔曲线渲染 #### 3.2 机器人渲染函数 ```typescript function drawRobot(ctx: CanvasRenderingContext2D, pen: MapPen): void { const { robot } = pen; if (!robot?.path?.length) return; const { lineWidth = 1, strokeStyle = sTheme.color.primary } = pen; ctx.save(); ctx.beginPath(); ctx.strokeStyle = strokeStyle; ctx.lineWidth = lineWidth; ctx.setLineDash([6, 3]); robot.path.forEach(({ x, y }, i) => { if (i === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } }); ctx.stroke(); ctx.restore(); } ``` **功能**: 绘制机器人路径 **渲染逻辑**: 1. 检查路径数据有效性 2. 设置画笔样式(虚线) 3. 绘制路径连线 4. 恢复画布状态 ## 响应式数据流 ### 1. 数据观察者模式 ```typescript public readonly points = useObservable( this.#change$$.pipe( filter((v) => v), debounceTime(100), map(() => this.find('point')), ), { initialValue: new Array() }, ); ``` **功能**: 创建响应式点位数据流 **流程**: 1. 监听变化事件流 2. 过滤有效变化 3. 防抖处理(100ms) 4. 查找所有点位对象 5. 返回响应式数据 ### 2. 实时数据更新流程 ``` WebSocket 消息 → 解析 JSON → 验证机器人 → 更新状态 → 触发重渲染 ``` ## UI 交互设计 ### 1. 布局结构 ``` ┌─────────────────────────────────────────┐ │ 标题栏 │ ├─────────────┬───────────────────────────┤ │ 左侧面板 │ 2D 画布区域 │ │ ┌─────────┐ │ │ │ │机器人列表│ │ │ │ ├─────────┤ │ │ │ │ 库区列表 │ │ │ │ ├─────────┤ │ │ │ │高级组列表│ │ │ │ └─────────┘ │ │ └─────────────┴───────────────────────────┤ │ ┌─────────┐ │ │详情面板 │ │ └─────────┘ │ │ ``` ### 2. 交互逻辑 - **点击机器人列表** → 选中机器人 → 显示机器人详情 - **点击画布对象** → 选中对象 → 显示对应详情面板 - **实时数据更新** → 自动刷新机器人位置和路径 ## 性能优化要点 ### 1. 响应式数据优化 - 使用 `shallowRef` 避免深度响应式 - 防抖处理避免频繁更新 - 条件渲染减少不必要的组件创建 ### 2. 渲染优化 - 自定义渲染函数提升性能 - 按需更新,避免全量重绘 - 使用 Canvas 分层渲染 ### 3. 内存管理 - 组件卸载时清理 WebSocket 连接 - 合理使用 `onMounted` 和 `onUnmounted` ## 维护指南 ### 1. 添加新的机器人类型 1. 在 `RobotType` 枚举中添加新类型 2. 准备对应的图片资源 3. 在 `#mapRobotImage` 方法中添加映射逻辑 ### 2. 扩展实时数据字段 1. 更新 `RobotRealtimeInfo` 接口 2. 修改 `monitorScene` 中的数据解析逻辑 3. 更新 `refreshRobot` 方法处理新字段 ### 3. 添加新的地图元素 1. 定义新的类型接口 2. 实现对应的渲染函数 3. 在 `#register` 方法中注册 4. 添加相应的UI组件 ### 4. 调试技巧 - 使用浏览器开发者工具查看 Canvas 元素 - 通过 `editor.value.data()` 获取完整场景数据 - 监听 WebSocket 消息验证数据传输 - 使用 Vue DevTools 查看响应式数据状态 ## 常见问题排查 ### 1. 机器人不显示 - 检查 WebSocket 连接状态 - 验证机器人ID是否匹配 - 确认坐标数据有效性 ### 2. 路径不更新 - 检查路径数据格式 - 验证坐标转换逻辑 - 确认渲染函数正确注册 ### 3. 性能问题 - 检查是否有内存泄漏 - 优化频繁的数据更新 - 考虑分页或虚拟滚动 --- *此文档基于当前代码版本分析,如有代码更新请同步更新文档。*