Files
bim_engine/docs/viewer-toolbar-implementation-guide.md
yuding 19f7e3ffbc feat(theme): 重构主题系统,新增 glass-pill 按钮样式
- ThemeConfig 接口扩展至 60+ 语义化属性
- 新增深浅主题预设 (glassPill overrides)
- button-group 支持 glass-pill 样式变体
- 默认主题改为浅色
- 移除 toolbar 容器硬编码定位
- 统一组件 CSS 变量命名规范
- 暂时隐藏下拉箭头
2026-01-21 15:50:07 +08:00

28 KiB
Raw Blame History

3D Viewer 工具栏实现文档

文档说明: 本文档为 3D Viewer 工具栏的实现规范,包含 UI 设计规格、交互逻辑和参考代码片段,供开发人员复现和实现。

版本: v1.0 更新日期: 2026-01-16 技术栈: React + TypeScript + Ant Design + Tailwind CSS + Framer Motion


目录

  1. 整体布局概览
  2. 顶部中间工具栏
  3. 底部右侧工具栏
  4. Hover 二级菜单
  5. 右侧抽屉面板
  6. 状态管理
  7. 视觉规范速查表
  8. 组件清单

1. 整体布局概览

1.1 ASCII 原型图

┌─────────────────────────────────────────────────────────────────────────────┐
│ 顶栏:项目名/状态 | 标签栏:工作台 | 建筑主体.rvt | ... | 成员/协同/分享           │
├────┬────────────────────────────────────────────────────────────────────────┤
│导航│ 二级面板(已载明,默认展开,可折叠)                                   │
│    ├────────────────────────────────────────────────────────────────────────┤
│    │ 主工作区Viewer                                                       │
│    │                                                                       │
│    │   ① 顶部中间工具(面板/列表开关,水平居中浮层)                        │
│    │   ┌────────────────────────────────────────────────┐                 │
│    │   │  📋 视图列表   📄 属性   🔍 过滤   📌 图钉   ⤢ 全屏  │                 │
│    │   └────────────────────────────────────────────────┘                 │
│    │                                                                       │
│    │   [  3D Viewer 渲染区域(加载中显示文件卡片占位)  ]                   │
│    │   - 右侧抽屉:视图树/属性/过滤/图钉面板(点击顶部按钮打开/收起)        │
│    │                                                                       │
│    │                                     ② 底部右侧工具(模型交互,圆形按钮)│
│    │                                     ┌──────────────┐                  │
│    │                                     │ 🧊 主视图    │                  │
│    │                                     │ 📏 测量      │                  │
│    │                                     │ 🪟 剖切      │                  │
│    │                                     │ 🚶 漫游      │                  │
│    │                                     │ ⚙️ 环境设置  │                  │
│    │                                     └──────────────┘                  │
│    └────────────────────────────────────────────────────────────────────────┘
└────┴────────────────────────────────────────────────────────────────────────┘

1.2 工具栏分区说明

① 顶部中间工具栏 (面板/列表开关):

  • 位置: 水平居中,顶部浮动
  • 功能: 控制右侧抽屉面板的打开/关闭
  • 按钮数量: 5个视图列表、属性、过滤、图钉、全屏
  • 交互特点: 开关型按钮,同时只允许一个面板高亮

② 底部右侧工具栏 (模型交互工具):

  • 位置: 右下角固定
  • 功能: 模型交互工具集
  • 按钮数量: 5个主视图、测量、剖切、漫游、环境设置
  • 交互特点: 圆形按钮Hover 显示二级菜单,支持模式互斥

2. 顶部中间工具栏

2.1 布局示意

┌────────────────────────────────────────────────┐
│  📋 视图列表   📄 属性   🔍 过滤   📌 图钉   ⤢ 全屏  │
└────────────────────────────────────────────────┘

2.2 容器规范

属性 Tailwind CSS
定位 顶部居中浮动 absolute top-3 left-1/2 -translate-x-1/2
层级 800 z-[800]
背景 白色半透明 bg-white/80 dark:bg-slate-800/80
毛玻璃 10px 模糊 backdrop-blur-xl
圆角 6px rounded-md
阴影 轻微阴影 shadow-[0_4px_24px_rgba(0,0,0,0.04)]
边框 半透明边框 border border-slate-200/50 dark:border-slate-700/50
内边距 水平 6px, 垂直 6px px-1.5 py-1.5
布局 水平弹性布局 flex items-center gap-1.5

2.3 按钮规范

通用样式

属性 Tailwind CSS
尺寸 不固定(自适应内容) px-2 py-2 lg:px-3 lg:py-2
圆角 6px rounded-md
图标大小 18px size={18}
字体 12px 中等字重 text-xs font-medium
过渡 200ms 平滑过渡 transition-all duration-200

按钮状态

默认状态:

text-slate-600 dark:text-slate-400
hover:bg-slate-100/60 dark:hover:bg-slate-700/60

激活态 (对应面板已打开):

bg-blue-500 dark:bg-blue-600
text-white
shadow-md
border border-blue-600 dark:border-blue-500

2.4 按钮列表

序号 按钮名称 图标 功能说明 面板类型
1 视图列表 List (lucide-react) 打开视图树面板,显示 3D/2D 视角列表 views
2 属性 FileText 打开属性面板,显示选中构件属性 properties
3 过滤 Filter 打开过滤面板,按楼层/专业/类别筛选 filter
4 图钉 Pin 打开图钉列表面板,支持状态筛选和搜索 pins
5 全屏 Maximize / Minimize 直接切换全屏模式(不走抽屉) -

2.5 交互规则

  1. 点击同一按钮: toggle 打开/关闭抽屉
  2. 点击不同按钮: 切换到对应面板,原面板自动关闭
  3. 抽屉关闭时: 所有按钮(除全屏外)取消高亮
  4. 同时只允许一个面板高亮

2.6 参考代码片段

// 文件: TopCenterToolbar.tsx
export function TopCenterToolbar() {
  const { state, togglePanel } = useViewerState();

  const buttons = [
    {
      key: 'views' as ViewerPanel,
      label: '视图列表',
      icon: List,
      onClick: () => togglePanel('views'),
    },
    // ... 其他按钮配置
  ];

  return (
    <div className="absolute top-3 left-1/2 -translate-x-1/2 z-[800]">
      <div className="flex items-center gap-1.5 bg-white/80 dark:bg-slate-800/80 backdrop-blur-xl rounded-md shadow-[0_4px_24px_rgba(0,0,0,0.04)] border border-slate-200/50 px-1.5 py-1.5">
        {buttons.map((button) => {
          const Icon = button.icon;
          const isActive = state.activePanel === button.key && state.drawerOpen;

          return (
            <button
              key={button.key}
              onClick={button.onClick}
              className={clsx(
                'flex items-center gap-1.5 rounded-md transition-all duration-200',
                'text-xs font-medium px-2 py-2 lg:px-3 lg:py-2',
                isActive
                  ? 'bg-blue-500 text-white shadow-md border border-blue-600'
                  : 'text-slate-600 hover:bg-slate-100/60'
              )}
            >
              <Icon size={18} strokeWidth={1.5} />
              <span className="hidden lg:inline">{button.label}</span>
            </button>
          );
        })}
      </div>
    </div>
  );
}

3. 底部右侧工具栏

3.1 布局示意

  Bottom Right (横向布局)
┌───────────────────────────────┐
│ 🏠 📏 ✂️ 🚶 ⚙️                │
└───────────────────────────────┘
  ↑  ↑  ↑  ↑  ↑
  主 测 剖 漫 环
  视 量 切 游 境

3.2 容器规范

属性 Tailwind CSS
定位 右下角固定 absolute bottom-4 right-4
层级 800 z-[800]
布局 水平弹性布局 flex items-center gap-2.5
背景 白色半透明 bg-white/80 dark:bg-slate-800/80
毛玻璃 10px 模糊 backdrop-blur-xl
圆角 完全圆角 rounded-full
阴影 轻微阴影 shadow-[0_4px_24px_rgba(0,0,0,0.06)]
边框 半透明边框 border border-slate-200/50
内边距 8px px-2 py-2

3.3 按钮规范

通用样式

属性 Tailwind CSS
尺寸 48px × 48px w-12 h-12
形状 圆形 rounded-full
图标大小 18px size={18}
布局 居中 flex items-center justify-center
过渡 200ms 平滑 transition-all duration-200 ease-out

按钮状态

默认状态:

bg-white/90 dark:bg-slate-700/50
border border-slate-300/60 dark:border-slate-600/40
text-slate-700 dark:text-slate-200
shadow-[0_2px_8px_rgba(0,0,0,0.1),0_4px_12px_rgba(0,0,0,0.08)]
hover:bg-white
hover:shadow-[0_4px_12px_rgba(0,0,0,0.12),0_6px_20px_rgba(0,0,0,0.1)]
hover:scale-[1.05]

激活态 (模式已启用):

bg-blue-600
border-blue-700
text-white
shadow-[0_2px_8px_rgba(59,130,246,0.3),0_4px_16px_rgba(59,130,246,0.2)]

3.4 按钮列表(从左到右)

序号 按钮名称 图标 功能说明 二级菜单 模式类型
1 主视图 Home 快捷视角切换 充满屏幕/缩放/爆炸图 -
2 测量 Ruler 测量工具集 线性捕捉/垂直净高/高程测量/清除 measure
3 剖切 Scissors 剖切平面工具 任意剖切/单面/多面 section
4 漫游 PersonStanding 第一人称漫游 直接切换模式 walk
5 环境设置 Settings 渲染和环境配置 弹出 Modal -

3.5 模式互斥规则

互斥的模式:

  • 测量模式 (measure)
  • 剖切模式 (section)
  • 创建图钉模式 (pin)
  • 漫游模式 (walk)

互斥策略: 启用某模式时,自动退出其他模式,并清理对应临时 UI。

示例: 启用测量模式时,如果剖切模式正在运行,则自动退出剖切并清除剖切平面。

3.6 参考代码片段

// 文件: BottomRightToolbar.tsx
export function BottomRightToolbar({ onOpenSettings }: BottomRightToolbarProps) {
  const { state, setMode, exitMode } = useViewerState();

  const toolButtons = [
    {
      key: 'mainView',
      label: '主视图',
      icon: Home,
      menuItems: mainViewMenuItems, // Hover 二级菜单
    },
    {
      key: 'measure',
      label: '测量',
      icon: Ruler,
      menuItems: measureMenuItems,
      mode: 'measure' as const,
    },
    // ... 其他按钮
  ];

  return (
    <div className="absolute bottom-4 right-4 z-[800]">
      <div className="flex items-center gap-2.5 bg-white/80 backdrop-blur-xl rounded-full shadow-[0_4px_24px_rgba(0,0,0,0.06)] border border-slate-200/50 px-2 py-2">
        {toolButtons.map((button) => {
          const Icon = button.icon;
          const isActive = button.mode && state.activeMode === button.mode;

          // 带 Hover 菜单的按钮
          return (
            <HoverMenu
              key={button.key}
              items={button.menuItems!}
              placement="top"
              trigger={
                <button
                  className={clsx(
                    'w-12 h-12 rounded-full flex items-center justify-center',
                    'border transition-all duration-200',
                    isActive
                      ? 'bg-blue-600 border-blue-700 text-white'
                      : 'bg-white/90 border-slate-300/60 text-slate-700 hover:scale-[1.05]'
                  )}
                >
                  <Icon size={18} strokeWidth={2} />
                </button>
              }
            />
          );
        })}
      </div>
    </div>
  );
}

4. Hover 二级菜单

4.1 布局示意

    ┌───────────────────┐
    │ 📏 线性捕捉      1 │  ← 菜单项 (hover 高亮)
    │ 📐 垂直净高      2 │
    │ 📈 高程测量      3 │
    │ 🗑️  清除测量      4 │
    └───────────────────┘
           ↑
    ┌──────────┐
    │    📏    │  ← 触发按钮
    └──────────┘

4.2 容器规范

属性 Tailwind CSS
背景 白色半透明 bg-white/95 dark:bg-[rgba(30,41,59,0.98)]
毛玻璃 10px 模糊 backdrop-blur-[10px]
圆角 12px rounded-xl
阴影 较强阴影 shadow-[0_8px_24px_rgba(0,0,0,0.12)]
边框 半透明边框 border border-slate-200/60
内边距 6px px-1.5 py-1.5
布局 垂直列表 flex flex-col gap-0

4.3 菜单项规范

属性 Tailwind CSS
布局 水平弹性布局 flex items-center gap-2.5
内边距 水平 12px, 垂直 8px px-3 py-2
圆角 8px rounded-lg
字体 13px 普通字重 text-[13px] font-normal
文字颜色 深灰色 text-slate-700 dark:text-[rgba(255,255,255,0.85)]
Hover 背景 浅灰色 hover:bg-slate-100 dark:hover:bg-[rgba(51,65,85,0.8)]
过渡 100ms 平滑 transition-all duration-100 ease-out

4.4 触发时机

事件 延迟时间 说明
Hover 进入 100ms 鼠标进入按钮后 100ms 显示菜单
Hover 离开 250ms 鼠标离开「按钮+菜单」区域后 250ms 隐藏菜单

4.5 键盘快捷键

  • 1-9 键: 菜单展开时,按数字键触发对应菜单项
  • Esc 键: 关闭菜单并退出当前模式

4.6 参考代码片段

// 文件: HoverMenu.tsx
export function HoverMenu({
  trigger,
  items,
  placement = 'top',
  openDelay = 100,
  closeDelay = 250,
}: HoverMenuProps) {
  const [isOpen, setIsOpen] = useState(false);

  const handleMouseEnter = () => {
    openTimerRef.current = setTimeout(() => {
      setIsOpen(true);
    }, openDelay);
  };

  const handleMouseLeave = () => {
    closeTimerRef.current = setTimeout(() => {
      setIsOpen(false);
    }, closeDelay);
  };

  return (
    <div onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
      {trigger}

      <AnimatePresence>
        {isOpen && (
          <motion.div
            initial={{ opacity: 0, y: 10 }}
            animate={{ opacity: 1, y: 0 }}
            exit={{ opacity: 0, y: 10 }}
            transition={{ duration: 0.15 }}
            className="absolute bottom-full mb-3"
          >
            <div className="bg-white/95 backdrop-blur-[10px] rounded-xl shadow-[0_8px_24px_rgba(0,0,0,0.12)] border border-slate-200/60 px-1.5 py-1.5">
              {items.map((item) => (
                <button
                  key={item.key}
                  onClick={() => item.onClick()}
                  className="flex items-center gap-2.5 px-3 py-2 rounded-lg hover:bg-slate-100 transition-all"
                >
                  {item.icon}
                  <span>{item.label}</span>
                </button>
              ))}
            </div>
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  );
}

5. 右侧抽屉面板

5.1 布局示意

                        ┌────────────────────────┐
                        │ 视图列表         [×]   │  ← 标题栏 (48px)
                        ├────────────────────────┤
                        │                        │
                        │   [面板内容区域]        │  ← 滚动区域
                        │                        │
                        │                        │
                        └────────────────────────┘
                        ← 320px 宽度(可拖拽 280-400px

5.2 抽屉通用规范

属性 Tailwind CSS
定位 右侧固定 fixed top-0 bottom-0 right-0 (或 left-0)
宽度 320px可拖拽调整 w-[320px]
背景 纯白色 bg-white dark:bg-slate-900
阴影 向左投影 shadow-2xl
边框 左侧边框 border-l border-slate-200 (或右侧)
层级 800 z-[800]
动画 300ms 弹簧动画 Framer Motion spring
布局 垂直弹性布局 flex flex-col

5.3 标题栏规范

属性 Tailwind CSS
高度 48px h-12
布局 水平两端对齐 flex items-center justify-between
内边距 水平 16px px-4
背景 浅灰色 bg-slate-50 dark:bg-slate-800/50
边框 底部边框 border-b border-slate-200
标题字体 14px 粗体 text-sm font-semibold

5.4 Tab 管理规则

  1. 同时只能打开一个 Tab
  2. 切换 Tab: 点击顶部工具栏不同按钮,抽屉内容切换,原 Tab 自动替换
  3. 关闭抽屉: 点击标题栏关闭按钮 / 点击已激活的顶部按钮 toggle 关闭

5.5 面板列表

面板名称 激活条件 主要功能
视图列表 activePanel === 'views' 显示 3D/2D 视角列表,支持保存视点、搜索
属性面板 activePanel === 'properties' 显示选中构件的属性信息(几何、材质等)
过滤面板 activePanel === 'filter' 按楼层、专业、类别/系统筛选模型
图钉列表 activePanel === 'pins' 显示和管理问题图钉,支持状态筛选和搜索

5.6 参考代码片段

// 文件: ViewerDrawer.tsx
export function ViewerDrawer() {
  const { state, closePanel } = useViewerState();

  const renderActivePanel = () => {
    switch (state.activePanel) {
      case 'views':
        return <ViewListPanel />;
      case 'properties':
        return <PropertiesPanel />;
      case 'filter':
        return <FilterPanel />;
      case 'pins':
        return <PinsPanel />;
      default:
        return null;
    }
  };

  const isLeftSide = state.activePanel === 'views';
  const isRightSide = state.activePanel === 'pins';
  const showDrawer = state.drawerOpen && (isLeftSide || isRightSide);

  return (
    <AnimatePresence>
      {showDrawer && (
        <motion.div
          initial={{ x: isLeftSide ? '-100%' : '100%' }}
          animate={{ x: 0 }}
          exit={{ x: isLeftSide ? '-100%' : '100%' }}
          transition={{ type: 'spring', damping: 30, stiffness: 300 }}
          className={clsx(
            'fixed top-0 bottom-0 w-[320px] bg-white shadow-2xl z-[800]',
            isLeftSide ? 'left-0 border-r' : 'right-0 border-l'
          )}
        >
          {/* 标题栏 */}
          <div className="flex items-center justify-between px-4 py-3 border-b">
            <h3 className="text-sm font-semibold">{getPanelTitle()}</h3>
            <button onClick={closePanel}>
              <X size={18} />
            </button>
          </div>

          {/* 面板内容 */}
          <div className="flex-1 overflow-hidden">
            {renderActivePanel()}
          </div>
        </motion.div>
      )}
    </AnimatePresence>
  );
}

6. 状态管理

6.1 状态类型定义

// 文件: use-viewer-state.tsx

/** Viewer 抽屉面板类型 */
export type ViewerPanel = 'views' | 'properties' | 'filter' | 'pins' | null;

/** Viewer 工具模式类型 */
export type ViewerMode = 'measure' | 'section' | 'pin' | 'walk' | null;

/** Viewer 状态接口 */
export interface ViewerState {
  // 抽屉状态
  drawerOpen: boolean;        // 抽屉是否打开
  activePanel: ViewerPanel;   // 当前激活的面板
  drawerWidth: number;        // 抽屉宽度(可拖拽调整)

  // 工具模式
  activeMode: ViewerMode;     // 当前激活的工具模式

  // 全屏状态
  isFullscreen: boolean;      // 是否全屏

  // 选中构件
  selectedElementId: string | null;  // 选中构件的 ID
}

6.2 核心方法

interface ViewerContextValue {
  state: ViewerState;

  // 面板控制方法
  openPanel: (panel: ViewerPanel) => void;    // 打开指定面板
  closePanel: () => void;                     // 关闭面板
  togglePanel: (panel: ViewerPanel) => void;  // 切换面板(同一个则关闭)

  // 模式控制方法
  setMode: (mode: ViewerMode) => void;        // 设置工具模式(互斥)
  exitMode: () => void;                       // 退出当前模式

  // 全屏控制
  toggleFullscreen: () => void;               // 切换全屏

  // 构件选择
  setSelectedElement: (id: string | null) => void;  // 设置选中构件
}

6.3 使用示例

import { useViewerState } from '@/hooks/use-viewer-state';

function MyComponent() {
  const { state, togglePanel, setMode } = useViewerState();

  // 打开/关闭面板
  const handleOpenProperties = () => {
    togglePanel('properties');
  };

  // 启用测量模式
  const handleStartMeasure = () => {
    setMode('measure');
  };

  // 检查当前状态
  const isPropertiesPanelOpen = state.activePanel === 'properties' && state.drawerOpen;
  const isMeasureMode = state.activeMode === 'measure';

  return (
    <div>
      {/* 组件内容 */}
    </div>
  );
}

6.4 模式互斥逻辑

// 设置工具模式时,自动退出其他模式
const setMode = useCallback((mode: ViewerMode) => {
  setState(prev => ({ ...prev, activeMode: mode }));
}, []);

// 按 Esc 键退出当前模式
useEffect(() => {
  const handleKeyDown = (e: KeyboardEvent) => {
    if (e.key === 'Escape' && state.activeMode) {
      exitMode();
    }
  };
  window.addEventListener('keydown', handleKeyDown);
  return () => window.removeEventListener('keydown', handleKeyDown);
}, [state.activeMode, exitMode]);

7. 视觉规范速查表

7.1 尺寸规范

元素 尺寸 Tailwind CSS
顶部工具栏按钮 自适应高度 36px py-2
底部工具栏按钮 48px × 48px w-12 h-12
顶部工具栏图标 18px size={18}
底部工具栏图标 18px size={18}
二级菜单图标 14px size={14}
抽屉宽度 320px w-[320px]
标题栏高度 48px h-12

7.2 圆角规范

元素 圆角 Tailwind CSS
顶部工具栏容器 6px rounded-md
顶部工具栏按钮 6px rounded-md
底部工具栏容器 完全圆角 rounded-full
底部工具栏按钮 完全圆角 rounded-full
二级菜单容器 12px rounded-xl
二级菜单项 8px rounded-lg

7.3 阴影规范

元素 阴影 Tailwind CSS
顶部工具栏 轻微阴影 shadow-[0_4px_24px_rgba(0,0,0,0.04)]
底部工具栏 轻微阴影 shadow-[0_4px_24px_rgba(0,0,0,0.06)]
二级菜单 较强阴影 shadow-[0_8px_24px_rgba(0,0,0,0.12)]
抽屉面板 较强阴影 shadow-2xl

7.4 颜色规范

元素 亮色模式 暗色模式
工具栏背景 rgba(255,255,255,0.8) rgba(30,41,59,0.8)
按钮默认文字 rgb(71,85,105) rgba(148,163,184)
按钮激活背景 rgb(59,130,246) rgb(37,99,235)
按钮激活文字 rgb(255,255,255) rgb(255,255,255)
Hover 背景 rgba(241,245,249,0.6) rgba(51,65,85,0.6)
边框 rgba(226,232,240,0.5) rgba(71,85,105,0.5)

7.5 间距规范

元素 间距 Tailwind CSS
顶部工具栏按钮间距 6px gap-1.5
底部工具栏按钮间距 10px gap-2.5
二级菜单项间距 0px gap-0
二级菜单与按钮距离 12px mb-3

7.6 动画规范

动画类型 时长 缓动函数
按钮状态切换 200ms ease-out
二级菜单出现 150ms [0.16, 1, 0.3, 1] (cubic-bezier)
抽屉滑入 300ms spring (damping: 30, stiffness: 300)
Hover 缩放 200ms ease-out

8. 组件清单

8.1 核心组件文件

组件名称 文件路径 说明
TopCenterToolbar viewer/TopCenterToolbar.tsx 顶部中间工具栏组件
BottomRightToolbar viewer/BottomRightToolbar.tsx 底部右侧工具栏组件
HoverMenu viewer/HoverMenu.tsx Hover 二级菜单组件
ViewerDrawer viewer/ViewerDrawer.tsx 右侧抽屉面板容器
ViewListPanel viewer/ViewListPanel.tsx 视图列表面板
PropertiesPanel viewer/PropertiesPanel.tsx 属性面板
FilterPanel viewer/FilterPanel.tsx 过滤面板
PinsPanel viewer/PinsPanel.tsx 图钉列表面板
ViewerStates viewer/ViewerStates.tsx Viewer 各种状态组件
EnvironmentSettingsModal viewer/EnvironmentSettingsModal.tsx 环境设置弹窗
ModeIndicator viewer/ModeIndicator.tsx 模式提示组件

8.2 状态管理

Hook/Provider 文件路径 说明
useViewerState hooks/use-viewer-state.tsx Viewer 状态管理 Hook
ViewerProvider hooks/use-viewer-state.tsx Viewer 状态 Context Provider

8.3 依赖库

库名称 版本 用途
React ^18.x UI 框架
TypeScript ^5.x 类型系统
Ant Design ^5.x UI 组件库
Tailwind CSS ^3.x 样式框架
Framer Motion ^11.x 动画库
lucide-react ^0.x 图标库
clsx ^2.x 类名组合工具
next-intl ^3.x 国际化

附录:完整组件层次结构

ViewerContainer
├── TopCenterToolbar          (顶部中间工具栏)
│   └── Button × 5            (视图/属性/过滤/图钉/全屏)
│
├── BottomRightToolbar        (底部右侧工具栏)
│   ├── HoverMenu × 4         (主视图/测量/剖切/漫游)
│   │   └── MenuItem[]        (二级菜单项列表)
│   └── Button                (环境设置)
│
├── ViewerDrawer              (右侧抽屉)
│   ├── ViewListPanel         (视图列表面板)
│   ├── PropertiesPanel       (属性面板)
│   ├── FilterPanel           (过滤面板)
│   └── PinsPanel             (图钉列表面板)
│
├── EnvironmentSettingsModal  (环境设置弹窗)
│
├── ModeIndicator             (模式提示)
│
└── ViewerStates              (各种状态组件)
    ├── EmptyState            (空状态)
    ├── LoadingState          (加载中)
    └── ErrorState            (错误状态)

文档编写完成 如有任何疑问或需要补充的内容,请联系开发团队。