Files
bim_engine/.sisyphus/plans/path-roaming.md
yuding 191c571f40 refactor: sync managers and section box actions
Wire section box scale/reverse/reset to clipping APIs and sync demo artifacts.
2026-02-04 18:20:30 +08:00

1038 lines
28 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.

# 路径漫游功能开发
## 概述
> **目标**: 实现路径漫游功能,支持单条路径的漫游点管理和播放
>
> **交付物**:
> - 漫游点管理(添加、删除、跳转)
> - 路径设置(漫游时间、循环播放)
> - 漫游播放(播放时高亮当前点)
>
> **预估工作量**: 中等
> **执行方式**: 顺序执行
---
## 原型交互图
```
┌─────────────────────────────────────┐
│ 路径漫游 [×] │
├─────────────────────────────────────┤
│ │
│ ─────────── 路径设置 ─────────── │
│ │
│ 漫游时间 │
│ ┌───────────────────────┐ │
│ │ 10 │ 秒 │
│ └───────────────────────┘ │
│ │
│ ☑ 循环播放 │
│ │
│ ─────────── 漫游点 ──────────── │
│ │
│ ┌────────────┐ ┌────────────┐ │
│ │ +添加漫游点│ │ 删除全部 │ │
│ └────────────┘ └────────────┘ │
│ │
│ ┌─────────────────────────────┐ │
│ │ 漫游点0 [▶][×]│ │ ← 播放时高亮
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ 漫游点1 [▶][×]│ │
│ └─────────────────────────────┘ │
│ ┌─────────────────────────────┐ │
│ │ 漫游点2 [▶][×]│ │
│ └─────────────────────────────┘ │
│ │
│ ┌─────────────────────────────┐ │
│ │ ▶ 播放漫游 │ │
│ └─────────────────────────────┘ │
│ │
└─────────────────────────────────────┘
```
**关键交互**:
- 打开弹窗时:调用 `getPoints()` 获取已有漫游点
- 添加漫游点:调用 `addPoint()` 添加当前位置
- 删除漫游点:调用 `removePoint(index)`
- 跳转到点:调用 `jumpToPoint(index)`
- 播放漫游:调用 `play(options)`,通过 `onPointComplete` 高亮当前点
---
## 调用链路
```
用户操作 (WalkPathPanel)
registry.engine3d.pathRoamingXxx()
EngineManager (managers/engine-manager.ts)
this.engineInstance.pathRoamingXxx()
Engine (components/engine/index.ts)
this.engine.pathRoaming.xxx()
底层引擎 SDK
```
---
## 任务列表
### 任务 1: 添加国际化文本
**修改文件**:
- `src/locales/types.ts`
- `src/locales/zh-CN.ts`
- `src/locales/en-US.ts`
**types.ts 修改内容** (替换 `walkControl.path` 部分):
```typescript
path: {
/** 对话框标题 */
dialogTitle: string;
/** 漫游时间标签 */
duration: string;
/** 时间单位 */
durationUnit: string;
/** 循环播放 */
loop: string;
/** 添加漫游点按钮 */
addPoint: string;
/** 删除全部按钮 */
deleteAll: string;
/** 漫游点前缀 */
point: string;
/** 播放按钮 */
play: string;
/** 无漫游点提示 */
noPoints: string;
};
```
**zh-CN.ts 修改内容**:
```typescript
path: {
dialogTitle: '路径漫游',
duration: '漫游时间',
durationUnit: '秒',
loop: '循环播放',
addPoint: '添加漫游点',
deleteAll: '删除全部',
point: '漫游点',
play: '播放漫游',
noPoints: '暂无漫游点,请添加'
}
```
**en-US.ts 修改内容**:
```typescript
path: {
dialogTitle: 'Path Roaming',
duration: 'Duration',
durationUnit: 's',
loop: 'Loop',
addPoint: 'Add Point',
deleteAll: 'Delete All',
point: 'Point',
play: 'Play',
noPoints: 'No points yet'
}
```
**验收标准**:
- [x] 三个文件都已修改
- [x] 构建无错误
---
### 任务 2: 创建类型定义
**新建文件**: `src/components/walk-path-panel/types.ts`
```typescript
/**
* 漫游点接口
* 表示路径中的一个漫游点
*/
export interface RoamingPoint {
/** 漫游点索引 */
index: number;
}
/**
* 播放选项接口
* 配置漫游播放的参数
*/
export interface PlayOptions {
/** 总播放时长(毫秒),不包括停留时间 */
duration?: number;
/** 是否循环播放 */
loop?: boolean;
/** 播放完成的回调 */
onComplete?: () => void;
/** 每个点播放完成的回调,用于高亮当前点 */
onPointComplete?: (pointIndex: number) => void;
}
```
**验收标准**:
- [x] 文件已创建
- [x] 包含所有接口定义和注释
---
### 任务 3: Engine 添加 pathRoaming 方法
**修改文件**: `src/components/engine/index.ts`
**添加位置**: 在 `getEngineInfo()` 方法后面添加以下代码
```typescript
// ==================== 路径漫游 ====================
/**
* 添加漫游点
* 将当前相机位置添加为一个漫游点
*/
public pathRoamingAddPoint(): void {
// 检查引擎是否已初始化
if (!this._isInitialized || !this.engine?.pathRoaming) {
console.warn('[Engine] pathRoaming not available');
return;
}
// 调用底层 API 添加点位
this.engine.pathRoaming.addPoint(0);
}
/**
* 删除指定索引的漫游点
* @param index 要删除的漫游点索引
*/
public pathRoamingRemovePoint(index: number): void {
if (!this._isInitialized || !this.engine?.pathRoaming) {
console.warn('[Engine] pathRoaming not available');
return;
}
this.engine.pathRoaming.removePoint(index);
}
/**
* 清除所有漫游点
*/
public pathRoamingClearPoints(): void {
if (!this._isInitialized || !this.engine?.pathRoaming) {
console.warn('[Engine] pathRoaming not available');
return;
}
this.engine.pathRoaming.clearPoints();
}
/**
* 获取所有漫游点
* @returns 漫游点数组
*/
public pathRoamingGetPoints(): any[] {
if (!this._isInitialized || !this.engine?.pathRoaming) {
console.warn('[Engine] pathRoaming not available');
return [];
}
return this.engine.pathRoaming.getPoints() ?? [];
}
/**
* 跳转到指定漫游点
* @param index 目标漫游点索引
*/
public pathRoamingJumpToPoint(index: number): void {
if (!this._isInitialized || !this.engine?.pathRoaming) {
console.warn('[Engine] pathRoaming not available');
return;
}
this.engine.pathRoaming.jumpToPoint(index);
}
/**
* 播放漫游
* @param options 播放选项,包含时长、循环、回调等配置
*/
public pathRoamingPlay(options?: {
duration?: number;
loop?: boolean;
onComplete?: () => void;
onPointComplete?: (pointIndex: number) => void;
}): void {
if (!this._isInitialized || !this.engine?.pathRoaming) {
console.warn('[Engine] pathRoaming not available');
return;
}
this.engine.pathRoaming.play(options);
}
// ==================== 结束:路径漫游 ====================
```
**验收标准**:
- [x] 6个方法已添加
- [x] 每个方法都有 JSDoc 注释
- [x] 构建无错误
---
### 任务 4: EngineManager 添加代理方法
**修改文件**: `src/managers/engine-manager.ts`
**添加位置**: 在 `getEngineInfo()` 方法后面添加以下代码
```typescript
// ==================== 路径漫游 ====================
/**
* 添加漫游点
* 将当前相机位置添加为一个漫游点
*/
public pathRoamingAddPoint(): void {
this.engineInstance?.pathRoamingAddPoint();
}
/**
* 删除指定索引的漫游点
* @param index 要删除的漫游点索引
*/
public pathRoamingRemovePoint(index: number): void {
this.engineInstance?.pathRoamingRemovePoint(index);
}
/**
* 清除所有漫游点
*/
public pathRoamingClearPoints(): void {
this.engineInstance?.pathRoamingClearPoints();
}
/**
* 获取所有漫游点
* @returns 漫游点数组
*/
public pathRoamingGetPoints(): any[] {
return this.engineInstance?.pathRoamingGetPoints() ?? [];
}
/**
* 跳转到指定漫游点
* @param index 目标漫游点索引
*/
public pathRoamingJumpToPoint(index: number): void {
this.engineInstance?.pathRoamingJumpToPoint(index);
}
/**
* 播放漫游
* @param options 播放选项,包含时长、循环、回调等配置
*/
public pathRoamingPlay(options?: {
duration?: number;
loop?: boolean;
onComplete?: () => void;
onPointComplete?: (pointIndex: number) => void;
}): void {
this.engineInstance?.pathRoamingPlay(options);
}
// ==================== 结束:路径漫游 ====================
```
**验收标准**:
- [x] 6个代理方法已添加
- [x] 每个方法都有 JSDoc 注释
- [x] 构建无错误
---
### 任务 5: 实现 WalkPathPanel 组件
**修改文件**: `src/components/walk-path-panel/index.ts`
**完整替换为以下代码**:
```typescript
import './index.css';
import type { ThemeConfig } from '../../themes/types';
import { IBimComponent } from '../../types/component';
import { localeManager, t } from '../../services/locale';
import { themeManager } from '../../services/theme';
import { ManagerRegistry } from '../../core/manager-registry';
import type { RoamingPoint } from './types';
/**
* 路径漫游面板组件
* 提供漫游点的添加、删除、跳转和播放功能
*/
export class WalkPathPanel implements IBimComponent {
public element!: HTMLElement;
/** 管理器注册表实例 */
private registry = ManagerRegistry.getInstance();
/** 国际化订阅取消函数 */
private unsubscribeLocale: (() => void) | null = null;
/** 主题订阅取消函数 */
private unsubscribeTheme: (() => void) | null = null;
/** 漫游点列表 */
private points: RoamingPoint[] = [];
/** 漫游时间(毫秒) */
private duration: number = 10000;
/** 是否循环播放 */
private loop: boolean = false;
/** 当前播放中的点索引,-1 表示未播放 */
private playingPointIndex: number = -1;
/** 是否正在播放 */
private isPlaying: boolean = false;
/**
* 初始化组件
* 创建 DOM 元素,订阅国际化和主题变化,加载已有漫游点
*/
public init(): void {
// 创建根元素
this.element = document.createElement('div');
this.element.className = 'walk-path-panel';
// 订阅国际化变化
this.unsubscribeLocale = localeManager.subscribe(() => this.render());
// 订阅主题变化
this.unsubscribeTheme = themeManager.subscribe((theme) => this.setTheme(theme));
// 从引擎加载已有的漫游点
this.loadPointsFromEngine();
// 渲染界面
this.render();
// 应用当前主题
this.setTheme(themeManager.getTheme());
}
/**
* 从引擎加载已有的漫游点
* 在面板打开时调用,获取底层引擎中已存在的漫游点
*/
private loadPointsFromEngine(): void {
const enginePoints = this.registry.engine3d?.pathRoamingGetPoints() ?? [];
// 将引擎返回的点转换为本地格式
this.points = enginePoints.map((_: any, index: number) => ({
index: index
}));
}
/**
* 渲染整个面板
* 清空现有内容并重新构建界面
*/
private render(): void {
this.element.innerHTML = '';
// 渲染路径设置区域
const settings = this.createSettingsSection();
this.element.appendChild(settings);
// 渲染漫游点区域
const pointsSection = this.createPointsSection();
this.element.appendChild(pointsSection);
// 渲染播放按钮
const playBtn = this.createPlayButton();
this.element.appendChild(playBtn);
}
/**
* 创建路径设置区域
* 包含漫游时间和循环播放设置
*/
private createSettingsSection(): HTMLElement {
const section = document.createElement('div');
section.className = 'walk-path-settings';
// ===== 漫游时间 =====
const durationGroup = document.createElement('div');
durationGroup.className = 'walk-path-form-group';
const durationLabel = document.createElement('label');
durationLabel.textContent = t('walkControl.path.duration');
const durationWrapper = document.createElement('div');
durationWrapper.className = 'walk-path-input-wrapper';
const durationInput = document.createElement('input');
durationInput.type = 'number';
durationInput.className = 'walk-path-input';
durationInput.value = String(this.duration / 1000);
durationInput.min = '1';
durationInput.oninput = (e) => {
// 更新漫游时间,转换为毫秒
const val = parseInt((e.target as HTMLInputElement).value) || 1;
this.duration = val * 1000;
};
const durationUnit = document.createElement('span');
durationUnit.className = 'walk-path-unit';
durationUnit.textContent = t('walkControl.path.durationUnit');
durationWrapper.appendChild(durationInput);
durationWrapper.appendChild(durationUnit);
durationGroup.appendChild(durationLabel);
durationGroup.appendChild(durationWrapper);
// ===== 循环播放 =====
const loopGroup = document.createElement('div');
loopGroup.className = 'walk-path-form-group walk-path-form-group-inline';
const loopCheckbox = document.createElement('input');
loopCheckbox.type = 'checkbox';
loopCheckbox.id = 'walk-path-loop-checkbox';
loopCheckbox.className = 'walk-path-checkbox';
loopCheckbox.checked = this.loop;
loopCheckbox.onchange = (e) => {
// 更新循环播放状态
this.loop = (e.target as HTMLInputElement).checked;
};
const loopLabel = document.createElement('label');
loopLabel.htmlFor = 'walk-path-loop-checkbox';
loopLabel.textContent = t('walkControl.path.loop');
loopGroup.appendChild(loopCheckbox);
loopGroup.appendChild(loopLabel);
section.appendChild(durationGroup);
section.appendChild(loopGroup);
return section;
}
/**
* 创建漫游点区域
* 包含操作按钮和漫游点列表
*/
private createPointsSection(): HTMLElement {
const section = document.createElement('div');
section.className = 'walk-path-points-section';
// ===== 操作栏 =====
const toolbar = document.createElement('div');
toolbar.className = 'walk-path-points-toolbar';
// 添加漫游点按钮
const addBtn = document.createElement('button');
addBtn.className = 'walk-path-btn walk-path-btn-small';
addBtn.textContent = `+ ${t('walkControl.path.addPoint')}`;
addBtn.onclick = () => this.addPoint();
// 删除全部按钮
const deleteAllBtn = document.createElement('button');
deleteAllBtn.className = 'walk-path-btn walk-path-btn-small walk-path-btn-danger';
deleteAllBtn.textContent = t('walkControl.path.deleteAll');
deleteAllBtn.onclick = () => this.deleteAllPoints();
toolbar.appendChild(addBtn);
toolbar.appendChild(deleteAllBtn);
section.appendChild(toolbar);
// ===== 漫游点列表 =====
const list = document.createElement('div');
list.className = 'walk-path-points-list';
if (this.points.length === 0) {
// 空状态提示
const empty = document.createElement('div');
empty.className = 'walk-path-empty';
empty.textContent = t('walkControl.path.noPoints');
list.appendChild(empty);
} else {
// 渲染每个漫游点
this.points.forEach((_, index) => {
const item = this.createPointItem(index);
list.appendChild(item);
});
}
section.appendChild(list);
return section;
}
/**
* 创建单个漫游点项
* @param index 漫游点索引
*/
private createPointItem(index: number): HTMLElement {
const item = document.createElement('div');
item.className = 'walk-path-point-item';
// 如果是当前播放的点,添加高亮样式
if (this.playingPointIndex === index) {
item.classList.add('walk-path-point-item-active');
}
// 漫游点名称
const name = document.createElement('span');
name.className = 'walk-path-point-name';
name.textContent = `${t('walkControl.path.point')}${index}`;
// 操作按钮容器
const actions = document.createElement('div');
actions.className = 'walk-path-point-actions';
// 跳转按钮
const jumpBtn = document.createElement('button');
jumpBtn.className = 'walk-path-btn-icon';
jumpBtn.innerHTML = '▶';
jumpBtn.title = t('walkControl.path.play');
jumpBtn.onclick = (e) => {
e.stopPropagation();
this.jumpToPoint(index);
};
// 删除按钮
const deleteBtn = document.createElement('button');
deleteBtn.className = 'walk-path-btn-icon walk-path-btn-icon-danger';
deleteBtn.innerHTML = '×';
deleteBtn.onclick = (e) => {
e.stopPropagation();
this.deletePoint(index);
};
actions.appendChild(jumpBtn);
actions.appendChild(deleteBtn);
item.appendChild(name);
item.appendChild(actions);
return item;
}
/**
* 创建播放按钮
*/
private createPlayButton(): HTMLElement {
const playBtn = document.createElement('button');
playBtn.className = 'walk-path-btn walk-path-btn-play';
playBtn.textContent = `▶ ${t('walkControl.path.play')}`;
// 如果没有漫游点,禁用按钮
playBtn.disabled = this.points.length === 0;
playBtn.onclick = () => this.playPath();
return playBtn;
}
// ==================== 操作方法 ====================
/**
* 添加漫游点
* 将当前相机位置添加为新的漫游点
*/
private addPoint(): void {
// 调用引擎添加漫游点
this.registry.engine3d?.pathRoamingAddPoint();
// 更新本地状态
const newIndex = this.points.length;
this.points.push({ index: newIndex });
// 重新渲染
this.render();
}
/**
* 删除指定漫游点
* @param index 要删除的漫游点索引
*/
private deletePoint(index: number): void {
// 调用引擎删除漫游点
this.registry.engine3d?.pathRoamingRemovePoint(index);
// 从本地列表中移除
this.points.splice(index, 1);
// 重新索引
this.points.forEach((p, i) => p.index = i);
// 重新渲染
this.render();
}
/**
* 删除所有漫游点
*/
private deleteAllPoints(): void {
// 调用引擎清除所有漫游点
this.registry.engine3d?.pathRoamingClearPoints();
// 清空本地列表
this.points = [];
// 重新渲染
this.render();
}
/**
* 跳转到指定漫游点
* @param index 目标漫游点索引
*/
private jumpToPoint(index: number): void {
this.registry.engine3d?.pathRoamingJumpToPoint(index);
}
/**
* 播放漫游
* 按顺序播放所有漫游点
*/
private playPath(): void {
// 检查是否有漫游点
if (this.points.length === 0) return;
// 标记开始播放
this.isPlaying = true;
// 调用引擎播放
this.registry.engine3d?.pathRoamingPlay({
duration: this.duration,
loop: this.loop,
onPointComplete: (pointIndex: number) => {
// 更新当前播放点索引
this.playingPointIndex = pointIndex;
// 重新渲染以更新高亮状态
this.render();
},
onComplete: () => {
// 播放完成,重置状态
this.isPlaying = false;
this.playingPointIndex = -1;
// 重新渲染以移除高亮
this.render();
}
});
}
// ==================== 生命周期 ====================
/**
* 应用主题
* @param theme 主题配置
*/
public setTheme(theme: ThemeConfig): void {
if (!this.element) return;
// 设置 CSS 变量
this.element.style.setProperty('--bim-text-primary', theme.textPrimary ?? '#fff');
this.element.style.setProperty('--bim-text-secondary', theme.textSecondary ?? '#94a3b8');
this.element.style.setProperty('--bim-bg-elevated', theme.bgElevated ?? '#1f2d3e');
this.element.style.setProperty('--bim-border-default', theme.borderDefault ?? '#334155');
this.element.style.setProperty('--bim-primary', theme.primary ?? '#3b82f6');
}
/**
* 销毁组件
* 清理订阅和 DOM 元素
*/
public destroy(): void {
// 取消订阅
this.unsubscribeLocale?.();
this.unsubscribeTheme?.();
// 移除 DOM 元素
if (this.element?.parentElement) {
this.element.parentElement.removeChild(this.element);
}
}
}
```
**验收标准**:
- [x] 面板显示正确
- [x] 打开时加载已有漫游点
- [x] 添加/删除漫游点功能正常
- [x] 播放时高亮当前点
- [x] 每个方法都有注释
---
### 任务 6: 添加样式文件
**新建文件**: `src/components/walk-path-panel/index.css`
```css
/* 路径漫游面板根容器 */
.walk-path-panel {
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
height: 100%;
box-sizing: border-box;
}
/* ==================== 按钮样式 ==================== */
/* 按钮通用样式 */
.walk-path-btn {
padding: 10px 16px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
}
/* 播放按钮 */
.walk-path-btn-play {
background: var(--bim-primary, #3b82f6);
color: #fff;
width: 100%;
}
.walk-path-btn-play:hover:not(:disabled) {
opacity: 0.9;
}
.walk-path-btn-play:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* 小按钮 */
.walk-path-btn-small {
padding: 6px 12px;
font-size: 12px;
background: var(--bim-bg-elevated, #1f2d3e);
color: var(--bim-text-primary, #fff);
border: 1px solid var(--bim-border-default, #334155);
}
.walk-path-btn-small:hover {
background: var(--bim-border-default, #334155);
}
/* 危险按钮(删除) */
.walk-path-btn-danger {
color: #ef4444;
}
/* ==================== 表单样式 ==================== */
/* 设置区域 */
.walk-path-settings {
display: flex;
flex-direction: column;
gap: 12px;
}
/* 表单组 */
.walk-path-form-group {
display: flex;
flex-direction: column;
gap: 6px;
}
.walk-path-form-group label {
font-size: 12px;
color: var(--bim-text-secondary, #94a3b8);
}
/* 行内表单组(复选框) */
.walk-path-form-group-inline {
flex-direction: row;
align-items: center;
}
/* 输入框 */
.walk-path-input {
padding: 8px 12px;
border: 1px solid var(--bim-border-default, #334155);
border-radius: 4px;
background: var(--bim-bg-elevated, #1f2d3e);
color: var(--bim-text-primary, #fff);
font-size: 14px;
width: 100%;
box-sizing: border-box;
}
.walk-path-input:focus {
outline: none;
border-color: var(--bim-primary, #3b82f6);
}
/* 输入框包装器(带单位) */
.walk-path-input-wrapper {
display: flex;
align-items: center;
gap: 8px;
}
.walk-path-input-wrapper .walk-path-input {
flex: 1;
}
/* 单位文本 */
.walk-path-unit {
color: var(--bim-text-secondary, #94a3b8);
font-size: 14px;
}
/* 复选框 */
.walk-path-checkbox {
width: 16px;
height: 16px;
margin-right: 8px;
}
/* ==================== 漫游点区域 ==================== */
/* 漫游点区域容器 */
.walk-path-points-section {
flex: 1;
display: flex;
flex-direction: column;
gap: 12px;
min-height: 0;
}
/* 操作栏 */
.walk-path-points-toolbar {
display: flex;
gap: 8px;
}
/* 漫游点列表 */
.walk-path-points-list {
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 8px;
}
/* 空状态提示 */
.walk-path-empty {
text-align: center;
padding: 32px 16px;
color: var(--bim-text-secondary, #94a3b8);
font-size: 14px;
}
/* ==================== 漫游点项 ==================== */
/* 漫游点项容器 */
.walk-path-point-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
background: var(--bim-bg-elevated, #1f2d3e);
border-radius: 4px;
transition: all 0.2s;
}
.walk-path-point-item:hover {
background: var(--bim-border-default, #334155);
}
/* 悬浮时显示操作按钮 */
.walk-path-point-item:hover .walk-path-point-actions {
opacity: 1;
}
/* 播放中的点高亮样式 */
.walk-path-point-item-active {
background: var(--bim-primary, #3b82f6);
}
.walk-path-point-item-active .walk-path-point-name {
color: #fff;
}
.walk-path-point-item-active .walk-path-point-actions {
opacity: 1;
}
/* 漫游点名称 */
.walk-path-point-name {
font-size: 14px;
color: var(--bim-text-primary, #fff);
}
/* 操作按钮容器 */
.walk-path-point-actions {
display: flex;
gap: 8px;
opacity: 0;
transition: opacity 0.2s;
}
/* 图标按钮 */
.walk-path-btn-icon {
width: 28px;
height: 28px;
border: none;
border-radius: 4px;
background: transparent;
color: var(--bim-text-primary, #fff);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
}
.walk-path-btn-icon:hover {
background: rgba(255, 255, 255, 0.1);
}
/* 危险图标按钮 */
.walk-path-btn-icon-danger:hover {
background: rgba(239, 68, 68, 0.2);
color: #ef4444;
}
```
**验收标准**:
- [x] 样式文件已创建
- [x] 使用 CSS 变量适配主题
- [x] 播放中的点有高亮样式
- [x] 悬浮效果正常
- [x] 每个样式块都有注释
---
### 任务 7: 构建验证
**执行命令**:
```bash
npm run build
```
**验收标准**:
- [x] 构建成功
- [x] 无 TypeScript 错误
---
## 文件修改清单
| 文件 | 操作 | 说明 |
|------|------|------|
| `src/locales/types.ts` | 修改 | 添加 path 字段类型 |
| `src/locales/zh-CN.ts` | 修改 | 添加中文翻译 |
| `src/locales/en-US.ts` | 修改 | 添加英文翻译 |
| `src/components/walk-path-panel/types.ts` | 新建 | 类型定义 |
| `src/components/engine/index.ts` | 修改 | 添加 6 个 pathRoaming 方法 |
| `src/managers/engine-manager.ts` | 修改 | 添加 6 个代理方法 |
| `src/components/walk-path-panel/index.ts` | 重写 | 完整面板实现 |
| `src/components/walk-path-panel/index.css` | 新建 | 样式文件 |
---
## 提交策略
| 任务 | 提交信息 |
|------|----------|
| 1 | `feat(walk-path): add locale strings` |
| 2 | `feat(walk-path): add types` |
| 3-4 | `feat(engine): add pathRoaming methods` |
| 5-6 | `feat(walk-path): implement panel with highlight` |