web-map/movement-supervision-analysis.md

12 KiB
Raw Blame History

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 定义

type Props = {
  sid: string;  // 场景ID
  id?: string;  // 机器人组ID可选
};

2. 核心状态管理

2.1 编辑器实例

const editor = shallowRef<EditorService>();
  • 作用: 保存 2D 编辑器实例
  • 生命周期: 在 onMounted 中初始化

2.2 WebSocket 连接

const client = shallowRef<WebSocket>();
  • 作用: 维护与后端的实时连接
  • 生命周期: 在组件卸载时自动关闭

2.3 当前选中对象

const current = ref<{ type: 'robot' | 'point' | 'line' | 'area'; id: string }>();
  • 作用: 跟踪当前选中的对象类型和ID
  • 用途: 控制右侧详情面板的显示

核心业务方法详解

1. 场景加载 (readScene)

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)

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. 对象选择处理

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)

const selectRobot = (id: string) => {
  current.value = { type: 'robot', id };
  editor.value?.inactive();
};

功能: 选择特定机器人 执行流程:

  1. 设置当前选中对象为机器人类型
  2. 取消编辑器中的其他激活状态

EditorService 核心方法分析

1. 机器人管理

1.1 初始化机器人 (initRobots)

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)

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)

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)

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 自定义渲染函数注册

#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 机器人渲染函数

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. 数据观察者模式

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 连接
  • 合理使用 onMountedonUnmounted

维护指南

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. 性能问题

  • 检查是否有内存泄漏
  • 优化频繁的数据更新
  • 考虑分页或虚拟滚动

此文档基于当前代码版本分析,如有代码更新请同步更新文档。