diff --git a/movement-supervision-analysis.md b/movement-supervision-analysis.md deleted file mode 100644 index 01ff2b9..0000000 --- a/movement-supervision-analysis.md +++ /dev/null @@ -1,448 +0,0 @@ -# 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. 性能问题 -- 检查是否有内存泄漏 -- 优化频繁的数据更新 -- 考虑分页或虚拟滚动 - ---- - -*此文档基于当前代码版本分析,如有代码更新请同步更新文档。* \ No newline at end of file