web-map/movement-supervision-analysis.md

448 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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<EditorService>();
```
- **作用**: 保存 2D 编辑器实例
- **生命周期**: 在 `onMounted` 中初始化
#### 2.2 WebSocket 连接
```typescript
const client = shallowRef<WebSocket>();
```
- **作用**: 维护与后端的实时连接
- **生命周期**: 在组件卸载时自动关闭
#### 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 } = <RobotRealtimeInfo>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<void> {
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<RobotRealtimeInfo>): 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<GroupSceneDetail>): Promise<void> {
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<MapPen[], MapPen[]>(
this.#change$$.pipe(
filter((v) => v),
debounceTime(100),
map(() => this.find('point')),
),
{ initialValue: new Array<MapPen>() },
);
```
**功能**: 创建响应式点位数据流
**流程**:
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. 性能问题
- 检查是否有内存泄漏
- 优化频繁的数据更新
- 考虑分页或虚拟滚动
---
*此文档基于当前代码版本分析,如有代码更新请同步更新文档。*