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

28 KiB
Raw Blame History

路径漫游功能开发

概述

目标: 实现路径漫游功能,支持单条路径的漫游点管理和播放

交付物:

  • 漫游点管理(添加、删除、跳转)
  • 路径设置(漫游时间、循环播放)
  • 漫游播放(播放时高亮当前点)

预估工作量: 中等 执行方式: 顺序执行


原型交互图

┌─────────────────────────────────────┐
│  路径漫游                      [×]  │
├─────────────────────────────────────┤
│                                     │
│  ─────────── 路径设置 ───────────   │
│                                     │
│  漫游时间                           │
│  ┌───────────────────────┐          │
│  │ 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 部分):

path: {
    /** 对话框标题 */
    dialogTitle: string;
    /** 漫游时间标签 */
    duration: string;
    /** 时间单位 */
    durationUnit: string;
    /** 循环播放 */
    loop: string;
    /** 添加漫游点按钮 */
    addPoint: string;
    /** 删除全部按钮 */
    deleteAll: string;
    /** 漫游点前缀 */
    point: string;
    /** 播放按钮 */
    play: string;
    /** 无漫游点提示 */
    noPoints: string;
};

zh-CN.ts 修改内容:

path: {
    dialogTitle: '路径漫游',
    duration: '漫游时间',
    durationUnit: '秒',
    loop: '循环播放',
    addPoint: '添加漫游点',
    deleteAll: '删除全部',
    point: '漫游点',
    play: '播放漫游',
    noPoints: '暂无漫游点,请添加'
}

en-US.ts 修改内容:

path: {
    dialogTitle: 'Path Roaming',
    duration: 'Duration',
    durationUnit: 's',
    loop: 'Loop',
    addPoint: 'Add Point',
    deleteAll: 'Delete All',
    point: 'Point',
    play: 'Play',
    noPoints: 'No points yet'
}

验收标准:

  • 三个文件都已修改
  • 构建无错误

任务 2: 创建类型定义

新建文件: src/components/walk-path-panel/types.ts

/**
 * 漫游点接口
 * 表示路径中的一个漫游点
 */
export interface RoamingPoint {
    /** 漫游点索引 */
    index: number;
}

/**
 * 播放选项接口
 * 配置漫游播放的参数
 */
export interface PlayOptions {
    /** 总播放时长(毫秒),不包括停留时间 */
    duration?: number;
    /** 是否循环播放 */
    loop?: boolean;
    /** 播放完成的回调 */
    onComplete?: () => void;
    /** 每个点播放完成的回调,用于高亮当前点 */
    onPointComplete?: (pointIndex: number) => void;
}

验收标准:

  • 文件已创建
  • 包含所有接口定义和注释

任务 3: Engine 添加 pathRoaming 方法

修改文件: src/components/engine/index.ts

添加位置: 在 getEngineInfo() 方法后面添加以下代码

// ==================== 路径漫游 ====================

/**
 * 添加漫游点
 * 将当前相机位置添加为一个漫游点
 */
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);
}

// ==================== 结束:路径漫游 ====================

验收标准:

  • 6个方法已添加
  • 每个方法都有 JSDoc 注释
  • 构建无错误

任务 4: EngineManager 添加代理方法

修改文件: src/managers/engine-manager.ts

添加位置: 在 getEngineInfo() 方法后面添加以下代码

// ==================== 路径漫游 ====================

/**
 * 添加漫游点
 * 将当前相机位置添加为一个漫游点
 */
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);
}

// ==================== 结束:路径漫游 ====================

验收标准:

  • 6个代理方法已添加
  • 每个方法都有 JSDoc 注释
  • 构建无错误

任务 5: 实现 WalkPathPanel 组件

修改文件: src/components/walk-path-panel/index.ts

完整替换为以下代码:

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);
        }
    }
}

验收标准:

  • 面板显示正确
  • 打开时加载已有漫游点
  • 添加/删除漫游点功能正常
  • 播放时高亮当前点
  • 每个方法都有注释

任务 6: 添加样式文件

新建文件: src/components/walk-path-panel/index.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;
}

验收标准:

  • 样式文件已创建
  • 使用 CSS 变量适配主题
  • 播放中的点有高亮样式
  • 悬浮效果正常
  • 每个样式块都有注释

任务 7: 构建验证

执行命令:

npm run build

验收标准:

  • 构建成功
  • 无 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