feat(theme): 重构主题系统,新增 glass-pill 按钮样式
- ThemeConfig 接口扩展至 60+ 语义化属性 - 新增深浅主题预设 (glassPill overrides) - button-group 支持 glass-pill 样式变体 - 默认主题改为浅色 - 移除 toolbar 容器硬编码定位 - 统一组件 CSS 变量命名规范 - 暂时隐藏下拉箭头
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4492
dist/bim-engine-sdk.es.js
vendored
4492
dist/bim-engine-sdk.es.js
vendored
File diff suppressed because one or more lines are too long
2
dist/bim-engine-sdk.es.js.map
vendored
2
dist/bim-engine-sdk.es.js.map
vendored
File diff suppressed because one or more lines are too long
112
dist/bim-engine-sdk.umd.js
vendored
112
dist/bim-engine-sdk.umd.js
vendored
File diff suppressed because one or more lines are too long
2
dist/bim-engine-sdk.umd.js.map
vendored
2
dist/bim-engine-sdk.umd.js.map
vendored
File diff suppressed because one or more lines are too long
235
dist/index.d.ts
vendored
235
dist/index.d.ts
vendored
@@ -36,6 +36,16 @@ declare class BimButtonGroup implements IBimComponent {
|
|||||||
* 只会应用到没有被用户自定义的颜色属性上
|
* 只会应用到没有被用户自定义的颜色属性上
|
||||||
*/
|
*/
|
||||||
setTheme(theme: ThemeConfig): void;
|
setTheme(theme: ThemeConfig): void;
|
||||||
|
/**
|
||||||
|
* 应用主题系统的 CSS 变量到容器
|
||||||
|
* 供 glass-pill 等样式变体使用
|
||||||
|
*/
|
||||||
|
private applyThemeCssVars;
|
||||||
|
/**
|
||||||
|
* 同步 CSS 变量到所有 dropdown 元素
|
||||||
|
* dropdown 被添加到 body,无法继承容器的 CSS 变量
|
||||||
|
*/
|
||||||
|
private syncDropdownCssVars;
|
||||||
/**
|
/**
|
||||||
* 直接设置颜色(强制覆盖)
|
* 直接设置颜色(强制覆盖)
|
||||||
* 设置的颜色会被标记为自定义,后续的 setTheme 不会覆盖它们
|
* 设置的颜色会被标记为自定义,后续的 setTheme 不会覆盖它们
|
||||||
@@ -273,6 +283,12 @@ declare class BimTreeNode {
|
|||||||
destroy(): void;
|
destroy(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blur Token
|
||||||
|
* CSS backdrop-filter blur value (e.g., '24px')
|
||||||
|
*/
|
||||||
|
declare type BlurToken = string;
|
||||||
|
|
||||||
/** 按钮内部文字图标排列 */
|
/** 按钮内部文字图标排列 */
|
||||||
declare type ButtonAlign = 'vertical' | 'horizontal';
|
declare type ButtonAlign = 'vertical' | 'horizontal';
|
||||||
|
|
||||||
@@ -336,6 +352,8 @@ declare class ButtonGroupManager extends BimComponent {
|
|||||||
|
|
||||||
export declare interface ButtonGroupOptions extends ButtonGroupColors {
|
export declare interface ButtonGroupOptions extends ButtonGroupColors {
|
||||||
container: HTMLElement | string;
|
container: HTMLElement | string;
|
||||||
|
/** 样式类型 (默认 'default') */
|
||||||
|
type?: ButtonGroupType;
|
||||||
/** 屏幕位置 (如 top-left) */
|
/** 屏幕位置 (如 top-left) */
|
||||||
position?: GroupPosition;
|
position?: GroupPosition;
|
||||||
/** 按钮组排列方向 (默认 row) */
|
/** 按钮组排列方向 (默认 row) */
|
||||||
@@ -349,6 +367,9 @@ export declare interface ButtonGroupOptions extends ButtonGroupColors {
|
|||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 按钮组样式类型 */
|
||||||
|
declare type ButtonGroupType = 'default' | 'glass-pill';
|
||||||
|
|
||||||
declare type ButtonType = 'button' | 'menu';
|
declare type ButtonType = 'button' | 'menu';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -392,6 +413,24 @@ export declare interface CollapseOptions {
|
|||||||
onChange?: (activeIds: string[]) => void;
|
onChange?: (activeIds: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BIM Engine SDK - Unified Theme System
|
||||||
|
*
|
||||||
|
* Design Principles:
|
||||||
|
* 1. Semantic naming - colors describe PURPOSE, not appearance
|
||||||
|
* 2. Hierarchical structure - organized by category for scalability
|
||||||
|
* 3. Complete coverage - all UI states and component needs
|
||||||
|
* 4. Accessibility - ensures proper contrast ratios
|
||||||
|
* 5. Glassmorphism support - includes transparency and blur values
|
||||||
|
*
|
||||||
|
* Color Palette Reference: Tailwind CSS (slate, blue, red, green, amber)
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Semantic Color Token
|
||||||
|
* Represents a single color value with optional transparency
|
||||||
|
*/
|
||||||
|
declare type ColorToken = string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 底部工具栏管理器 (ToolbarManager)
|
* 底部工具栏管理器 (ToolbarManager)
|
||||||
* 仅负责管理底部工具栏实例。
|
* 仅负责管理底部工具栏实例。
|
||||||
@@ -1156,40 +1195,180 @@ declare class SectionPlaneDialogManager extends BimComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局主题配置接口
|
* Shadow Token
|
||||||
* 定义系统通用的语义化颜色
|
* CSS box-shadow value
|
||||||
|
*/
|
||||||
|
declare type ShadowToken = string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete Theme Configuration Interface
|
||||||
|
*
|
||||||
|
* All colors use semantic naming to describe their purpose.
|
||||||
|
* Components should map these to their internal CSS variables.
|
||||||
*/
|
*/
|
||||||
export declare interface ThemeConfig {
|
export declare interface ThemeConfig {
|
||||||
/** 主题名称 */
|
/** Theme identifier: 'dark' | 'light' | custom name */
|
||||||
name: string;
|
name: string;
|
||||||
/** 品牌色/主色 */
|
/** Primary brand color - used for key actions, links, active states */
|
||||||
primary: string;
|
primary: ColorToken;
|
||||||
/** 主色悬停/激活态 */
|
/** Primary color on hover */
|
||||||
primaryHover: string;
|
primaryHover: ColorToken;
|
||||||
/** 基础背景色 (应用整体背景) */
|
/** Primary color when pressed/active */
|
||||||
background: string;
|
primaryActive: ColorToken;
|
||||||
/** 面板背景色 (工具栏、弹窗背景) */
|
/** Subtle primary for backgrounds (e.g., selected row) */
|
||||||
panelBackground: string;
|
primarySubtle: ColorToken;
|
||||||
/** 主要文字颜色 */
|
/** Success color - confirmations, completed states */
|
||||||
textPrimary: string;
|
success: ColorToken;
|
||||||
/** 次要文字颜色 */
|
/** Success color on hover */
|
||||||
textSecondary: string;
|
successHover: ColorToken;
|
||||||
/** 边框/分割线颜色 */
|
/** Subtle success for backgrounds */
|
||||||
border: string;
|
successSubtle: ColorToken;
|
||||||
/** 图标默认颜色 */
|
/** Warning color - caution, pending states */
|
||||||
icon: string;
|
warning: ColorToken;
|
||||||
/** 图标激活颜色 */
|
/** Warning color on hover */
|
||||||
iconActive: string;
|
warningHover: ColorToken;
|
||||||
/** 交互组件背景 (如按钮默认背景) */
|
/** Subtle warning for backgrounds */
|
||||||
componentBackground: string;
|
warningSubtle: ColorToken;
|
||||||
/** 交互组件悬停背景 */
|
/** Danger/Error color - destructive actions, errors */
|
||||||
componentHover: string;
|
danger: ColorToken;
|
||||||
/** 交互组件激活背景 */
|
/** Danger color on hover */
|
||||||
componentActive: string;
|
dangerHover: ColorToken;
|
||||||
|
/** Subtle danger for backgrounds */
|
||||||
|
dangerSubtle: ColorToken;
|
||||||
|
/** Info color - informational states */
|
||||||
|
info: ColorToken;
|
||||||
|
/** Info color on hover */
|
||||||
|
infoHover: ColorToken;
|
||||||
|
/** Subtle info for backgrounds */
|
||||||
|
infoSubtle: ColorToken;
|
||||||
|
/** Base/canvas background - the deepest layer (app background) */
|
||||||
|
bgBase: ColorToken;
|
||||||
|
/** Elevated surface - panels, cards, dialogs floating above base */
|
||||||
|
bgElevated: ColorToken;
|
||||||
|
/** Overlay background - modals, dropdowns, popovers */
|
||||||
|
bgOverlay: ColorToken;
|
||||||
|
/** Inset/recessed background - inputs, wells, sunken areas */
|
||||||
|
bgInset: ColorToken;
|
||||||
|
/**
|
||||||
|
* Glassmorphism surface - semi-transparent with blur
|
||||||
|
* Used for floating toolbars, modern panels
|
||||||
|
*/
|
||||||
|
bgGlass: ColorToken;
|
||||||
|
/** Glassmorphism blur amount */
|
||||||
|
bgGlassBlur: BlurToken;
|
||||||
|
/** Primary text - headings, body text, high emphasis */
|
||||||
|
textPrimary: ColorToken;
|
||||||
|
/** Secondary text - descriptions, labels, medium emphasis */
|
||||||
|
textSecondary: ColorToken;
|
||||||
|
/** Tertiary text - placeholders, hints, low emphasis */
|
||||||
|
textTertiary: ColorToken;
|
||||||
|
/** Disabled text - inactive elements */
|
||||||
|
textDisabled: ColorToken;
|
||||||
|
/** Inverted text - text on primary/dark backgrounds */
|
||||||
|
textInverse: ColorToken;
|
||||||
|
/** Link text color */
|
||||||
|
textLink: ColorToken;
|
||||||
|
/** Link text on hover */
|
||||||
|
textLinkHover: ColorToken;
|
||||||
|
/** Default icon color */
|
||||||
|
iconDefault: ColorToken;
|
||||||
|
/** Icon on hover */
|
||||||
|
iconHover: ColorToken;
|
||||||
|
/** Active/selected icon */
|
||||||
|
iconActive: ColorToken;
|
||||||
|
/** Disabled icon */
|
||||||
|
iconDisabled: ColorToken;
|
||||||
|
/** Inverted icon (on colored backgrounds) */
|
||||||
|
iconInverse: ColorToken;
|
||||||
|
/** Default border - inputs, cards, containers */
|
||||||
|
borderDefault: ColorToken;
|
||||||
|
/** Subtle border - dividers, separators */
|
||||||
|
borderSubtle: ColorToken;
|
||||||
|
/** Strong border - focus rings, emphasis */
|
||||||
|
borderStrong: ColorToken;
|
||||||
|
/** Disabled border */
|
||||||
|
borderDisabled: ColorToken;
|
||||||
|
/** Divider color - horizontal/vertical separators */
|
||||||
|
divider: ColorToken;
|
||||||
|
/**
|
||||||
|
* Component Background States
|
||||||
|
* Used for buttons, list items, interactive elements
|
||||||
|
*/
|
||||||
|
/** Default component background (often transparent) */
|
||||||
|
componentBg: ColorToken;
|
||||||
|
/** Component background on hover */
|
||||||
|
componentBgHover: ColorToken;
|
||||||
|
/** Component background when pressed */
|
||||||
|
componentBgActive: ColorToken;
|
||||||
|
/** Component background when selected */
|
||||||
|
componentBgSelected: ColorToken;
|
||||||
|
/** Disabled component background */
|
||||||
|
componentBgDisabled: ColorToken;
|
||||||
|
/** Focus ring color */
|
||||||
|
focusRing: ColorToken;
|
||||||
|
/** Focus ring offset color (for contrast) */
|
||||||
|
focusRingOffset: ColorToken;
|
||||||
|
/** Selection background (text selection, highlighted items) */
|
||||||
|
selectionBg: ColorToken;
|
||||||
|
/** Selection text color */
|
||||||
|
selectionText: ColorToken;
|
||||||
|
/** Small shadow - subtle elevation (buttons, small cards) */
|
||||||
|
shadowSm: ShadowToken;
|
||||||
|
/** Medium shadow - moderate elevation (dropdowns, popovers) */
|
||||||
|
shadowMd: ShadowToken;
|
||||||
|
/** Large shadow - high elevation (modals, dialogs) */
|
||||||
|
shadowLg: ShadowToken;
|
||||||
|
/** Extra large shadow - maximum elevation */
|
||||||
|
shadowXl: ShadowToken;
|
||||||
|
/** Glow shadow for active/highlighted elements */
|
||||||
|
shadowGlow: ShadowToken;
|
||||||
|
/** Scrollbar track background */
|
||||||
|
scrollbarTrack: ColorToken;
|
||||||
|
/** Scrollbar thumb */
|
||||||
|
scrollbarThumb: ColorToken;
|
||||||
|
/** Scrollbar thumb on hover */
|
||||||
|
scrollbarThumbHover: ColorToken;
|
||||||
|
/**
|
||||||
|
* Component-specific overrides
|
||||||
|
* Only define these when absolutely necessary
|
||||||
|
*/
|
||||||
|
overrides?: {
|
||||||
|
/** Dialog specific colors */
|
||||||
|
dialog?: {
|
||||||
|
headerBg?: ColorToken;
|
||||||
|
footerBg?: ColorToken;
|
||||||
|
};
|
||||||
|
/** Toolbar specific colors */
|
||||||
|
toolbar?: {
|
||||||
|
bg?: ColorToken;
|
||||||
|
buttonBg?: ColorToken;
|
||||||
|
buttonBgHover?: ColorToken;
|
||||||
|
buttonBgActive?: ColorToken;
|
||||||
|
};
|
||||||
|
/** Input specific colors */
|
||||||
|
input?: {
|
||||||
|
bg?: ColorToken;
|
||||||
|
bgFocus?: ColorToken;
|
||||||
|
placeholder?: ColorToken;
|
||||||
|
};
|
||||||
|
/** Glass-pill button group specific colors */
|
||||||
|
glassPill?: {
|
||||||
|
sectionBg?: ColorToken;
|
||||||
|
sectionBorder?: ColorToken;
|
||||||
|
sectionShadow?: ShadowToken;
|
||||||
|
btnBg?: ColorToken;
|
||||||
|
btnBorder?: ColorToken;
|
||||||
|
btnShadow?: ShadowToken;
|
||||||
|
btnBgHover?: ColorToken;
|
||||||
|
btnShadowHover?: ShadowToken;
|
||||||
|
iconColor?: ColorToken;
|
||||||
|
iconColorHover?: ColorToken;
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主题类型定义
|
* Theme type definition
|
||||||
*/
|
*/
|
||||||
export declare type ThemeType = 'dark' | 'light' | 'custom';
|
export declare type ThemeType = 'dark' | 'light' | 'custom';
|
||||||
|
|
||||||
|
|||||||
805
docs/viewer-toolbar-implementation-guide.md
Normal file
805
docs/viewer-toolbar-implementation-guide.md
Normal file
@@ -0,0 +1,805 @@
|
|||||||
|
# 3D Viewer 工具栏实现文档
|
||||||
|
|
||||||
|
> **文档说明**: 本文档为 3D Viewer 工具栏的实现规范,包含 UI 设计规格、交互逻辑和参考代码片段,供开发人员复现和实现。
|
||||||
|
|
||||||
|
**版本**: v1.0
|
||||||
|
**更新日期**: 2026-01-16
|
||||||
|
**技术栈**: React + TypeScript + Ant Design + Tailwind CSS + Framer Motion
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
1. [整体布局概览](#1-整体布局概览)
|
||||||
|
2. [顶部中间工具栏](#2-顶部中间工具栏)
|
||||||
|
3. [底部右侧工具栏](#3-底部右侧工具栏)
|
||||||
|
4. [Hover 二级菜单](#4-hover-二级菜单)
|
||||||
|
5. [右侧抽屉面板](#5-右侧抽屉面板)
|
||||||
|
6. [状态管理](#6-状态管理)
|
||||||
|
7. [视觉规范速查表](#7-视觉规范速查表)
|
||||||
|
8. [组件清单](#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` |
|
||||||
|
|
||||||
|
#### 按钮状态
|
||||||
|
|
||||||
|
**默认状态**:
|
||||||
|
```css
|
||||||
|
text-slate-600 dark:text-slate-400
|
||||||
|
hover:bg-slate-100/60 dark:hover:bg-slate-700/60
|
||||||
|
```
|
||||||
|
|
||||||
|
**激活态** (对应面板已打开):
|
||||||
|
```css
|
||||||
|
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 参考代码片段
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// 文件: 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` |
|
||||||
|
|
||||||
|
#### 按钮状态
|
||||||
|
|
||||||
|
**默认状态**:
|
||||||
|
```css
|
||||||
|
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]
|
||||||
|
```
|
||||||
|
|
||||||
|
**激活态** (模式已启用):
|
||||||
|
```css
|
||||||
|
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 参考代码片段
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// 文件: 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 参考代码片段
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// 文件: 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 参考代码片段
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// 文件: 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 状态类型定义
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// 文件: 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 核心方法
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
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 使用示例
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
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 模式互斥逻辑
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// 设置工具模式时,自动退出其他模式
|
||||||
|
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 (错误状态)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档编写完成**
|
||||||
|
如有任何疑问或需要补充的内容,请联系开发团队。
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@fishdingding/bim-engine-sdk",
|
"name": "@fishdingding/bim-engine-sdk",
|
||||||
"version": "1.0.2",
|
"version": "1.0.3",
|
||||||
"description": "BIM Engine SDK for Vue2, Vue3, React and HTML",
|
"description": "BIM Engine SDK for Vue2, Vue3, React and HTML",
|
||||||
"main": "./dist/bim-engine-sdk.umd.js",
|
"main": "./dist/bim-engine-sdk.umd.js",
|
||||||
"module": "./dist/bim-engine-sdk.es.js",
|
"module": "./dist/bim-engine-sdk.es.js",
|
||||||
|
|||||||
@@ -3,22 +3,15 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
color: #bf1d1d;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
/* 防止内容溢出 */
|
background: linear-gradient(to bottom, rgb(214, 224, 235), rgb(246, 250, 255));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ... (中间代码不变) ... */
|
/* ... (中间代码不变) ... */
|
||||||
|
|
||||||
/* 操作按钮组容器 */
|
|
||||||
.bim-engine-opt-btn-container {
|
.bim-engine-opt-btn-container {
|
||||||
position: absolute;
|
|
||||||
/* 改为绝对定位 */
|
|
||||||
bottom: 20px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -124,7 +124,6 @@ export class BimEngine extends EventEmitter {
|
|||||||
|
|
||||||
private updateTheme(theme: ThemeConfig) {
|
private updateTheme(theme: ThemeConfig) {
|
||||||
if (this.wrapper) {
|
if (this.wrapper) {
|
||||||
this.wrapper.style.backgroundColor = theme.background;
|
|
||||||
this.wrapper.style.color = theme.textPrimary;
|
this.wrapper.style.color = theme.textPrimary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -306,4 +306,202 @@
|
|||||||
|
|
||||||
.bim-btn-group-root.is-bottom-toolbar .opt-btn {
|
.bim-btn-group-root.is-bottom-toolbar .opt-btn {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== type-glass-pill: 胶囊容器 + 毛玻璃 + 圆形按钮 ========== */
|
||||||
|
/* 使用主题系统 CSS 变量,支持深色/浅色主题切换 */
|
||||||
|
|
||||||
|
.bim-btn-group-root.type-glass-pill .bim-btn-group-section {
|
||||||
|
background: var(--bim-glass-pill-section-bg);
|
||||||
|
backdrop-filter: blur(24px);
|
||||||
|
-webkit-backdrop-filter: blur(24px);
|
||||||
|
border: 1px solid var(--bim-glass-pill-section-border);
|
||||||
|
border-radius: 9999px;
|
||||||
|
box-shadow: var(--bim-glass-pill-section-shadow);
|
||||||
|
padding: 8px;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-btn-group-root.type-glass-pill .opt-btn {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
min-width: 48px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: var(--bim-glass-pill-btn-bg);
|
||||||
|
border: 1px solid var(--bim-glass-pill-btn-border);
|
||||||
|
box-shadow: var(--bim-glass-pill-btn-shadow);
|
||||||
|
color: var(--bim-text-primary);
|
||||||
|
padding: 0;
|
||||||
|
transition: all 200ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-btn-group-root.type-glass-pill .opt-btn:hover {
|
||||||
|
background: var(--bim-glass-pill-btn-bg-hover);
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: var(--bim-glass-pill-btn-shadow-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-btn-group-root.type-glass-pill .opt-btn:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-btn-group-root.type-glass-pill .opt-btn.active {
|
||||||
|
background: var(--bim-primary);
|
||||||
|
border: 1px solid var(--bim-primary-active);
|
||||||
|
color: var(--bim-text-inverse);
|
||||||
|
box-shadow: var(--bim-shadow-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-btn-group-root.type-glass-pill .opt-btn.active:hover {
|
||||||
|
background: var(--bim-primary-hover);
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-btn-group-root.type-glass-pill .opt-btn.active:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-btn-group-root.type-glass-pill .opt-btn.active .opt-btn-icon {
|
||||||
|
color: var(--bim-icon-inverse);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-btn-group-root.type-glass-pill .opt-btn.disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-btn-group-root.type-glass-pill .opt-btn-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
color: var(--bim-glass-pill-icon-color);
|
||||||
|
transition: color 200ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-btn-group-root.type-glass-pill .opt-btn-icon svg {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-btn-group-root.type-glass-pill .opt-btn:hover .opt-btn-icon {
|
||||||
|
color: var(--bim-glass-pill-icon-color-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-btn-group-root.type-glass-pill .opt-btn-label {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-btn-group-root.type-glass-pill .opt-btn-text-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
right: 2px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-btn-group-root.type-glass-pill .opt-btn-arrow {
|
||||||
|
font-size: 8px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
color: var(--bim-text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* glass-pill 二级菜单 - 浅色主题 */
|
||||||
|
.opt-btn-dropdown.type-glass-pill {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
border: 1px solid rgba(226, 232, 240, 0.6);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: rgb(51, 65, 85);
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 400;
|
||||||
|
transition: all 100ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item:hover {
|
||||||
|
background: rgb(241, 245, 249);
|
||||||
|
}
|
||||||
|
|
||||||
|
.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item:active {
|
||||||
|
background: rgb(226, 232, 240);
|
||||||
|
}
|
||||||
|
|
||||||
|
.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item.active {
|
||||||
|
background: rgb(59, 130, 246);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item.active .opt-btn-icon {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item.active .opt-btn-dropdown-label {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item .opt-btn-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
color: rgb(51, 65, 85);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item .opt-btn-icon svg {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item .opt-btn-dropdown-label {
|
||||||
|
white-space: nowrap;
|
||||||
|
color: rgb(51, 65, 85);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* glass-pill 二级菜单 - 深色主题 */
|
||||||
|
.opt-btn-dropdown.type-glass-pill.theme-dark {
|
||||||
|
background: rgba(21, 34, 50, 0.98);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
border: 1px solid rgba(51, 65, 85, 0.5);
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item:hover {
|
||||||
|
background: #1f2d3e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item:active {
|
||||||
|
background: #2a3f54;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item.active {
|
||||||
|
background: rgb(59, 130, 246);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item .opt-btn-icon {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item .opt-btn-dropdown-label {
|
||||||
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
@@ -93,6 +93,10 @@ export class BimButtonGroup implements IBimComponent {
|
|||||||
this.container.classList.add(this.options.className);
|
this.container.classList.add(this.options.className);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.options.type && this.options.type !== 'default') {
|
||||||
|
this.container.classList.add(`type-${this.options.type}`);
|
||||||
|
}
|
||||||
|
|
||||||
this.updatePosition();
|
this.updatePosition();
|
||||||
|
|
||||||
// 添加事件拦截,防止点击穿透到 3D 引擎
|
// 添加事件拦截,防止点击穿透到 3D 引擎
|
||||||
@@ -217,17 +221,16 @@ export class BimButtonGroup implements IBimComponent {
|
|||||||
*/
|
*/
|
||||||
public setTheme(theme: ThemeConfig): void {
|
public setTheme(theme: ThemeConfig): void {
|
||||||
const themeColors: ButtonGroupColors = {
|
const themeColors: ButtonGroupColors = {
|
||||||
backgroundColor: theme.panelBackground,
|
backgroundColor: theme.bgElevated,
|
||||||
btnBackgroundColor: theme.componentBackground,
|
btnBackgroundColor: theme.componentBg,
|
||||||
btnHoverColor: theme.componentHover,
|
btnHoverColor: theme.componentBgHover,
|
||||||
btnActiveColor: theme.componentActive,
|
btnActiveColor: theme.componentBgActive,
|
||||||
iconColor: theme.icon,
|
iconColor: theme.iconDefault,
|
||||||
iconActiveColor: theme.iconActive,
|
iconActiveColor: theme.iconActive,
|
||||||
textColor: theme.textSecondary,
|
textColor: theme.textSecondary,
|
||||||
textActiveColor: theme.textPrimary
|
textActiveColor: theme.textPrimary
|
||||||
};
|
};
|
||||||
|
|
||||||
// 只应用没有被自定义的颜色
|
|
||||||
Object.entries(themeColors).forEach(([key, value]) => {
|
Object.entries(themeColors).forEach(([key, value]) => {
|
||||||
const colorKey = key as keyof ButtonGroupColors;
|
const colorKey = key as keyof ButtonGroupColors;
|
||||||
if (!this.customColors.has(colorKey)) {
|
if (!this.customColors.has(colorKey)) {
|
||||||
@@ -235,8 +238,90 @@ export class BimButtonGroup implements IBimComponent {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.container.classList.remove('theme-dark', 'theme-light');
|
||||||
|
this.container.classList.add(`theme-${theme.name}`);
|
||||||
|
|
||||||
this.applyStyles();
|
this.applyStyles();
|
||||||
this.setPrimaryColor(theme.primary);
|
this.setPrimaryColor(theme.primary);
|
||||||
|
this.applyThemeCssVars(theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用主题系统的 CSS 变量到容器
|
||||||
|
* 供 glass-pill 等样式变体使用
|
||||||
|
*/
|
||||||
|
private applyThemeCssVars(theme: ThemeConfig): void {
|
||||||
|
const style = this.container.style;
|
||||||
|
|
||||||
|
style.setProperty('--bim-primary', theme.primary);
|
||||||
|
style.setProperty('--bim-primary-hover', theme.primaryHover);
|
||||||
|
style.setProperty('--bim-primary-active', theme.primaryActive);
|
||||||
|
|
||||||
|
style.setProperty('--bim-bg-glass', theme.bgGlass);
|
||||||
|
style.setProperty('--bim-bg-glass-blur', theme.bgGlassBlur);
|
||||||
|
style.setProperty('--bim-bg-elevated', theme.bgElevated);
|
||||||
|
style.setProperty('--bim-bg-overlay', theme.bgOverlay);
|
||||||
|
style.setProperty('--bim-bg-inset', theme.bgInset);
|
||||||
|
|
||||||
|
style.setProperty('--bim-text-primary', theme.textPrimary);
|
||||||
|
style.setProperty('--bim-text-secondary', theme.textSecondary);
|
||||||
|
style.setProperty('--bim-text-tertiary', theme.textTertiary);
|
||||||
|
style.setProperty('--bim-text-inverse', theme.textInverse);
|
||||||
|
|
||||||
|
style.setProperty('--bim-icon-default', theme.iconDefault);
|
||||||
|
style.setProperty('--bim-icon-hover', theme.iconHover);
|
||||||
|
style.setProperty('--bim-icon-active', theme.iconActive);
|
||||||
|
style.setProperty('--bim-icon-inverse', theme.iconInverse);
|
||||||
|
|
||||||
|
style.setProperty('--bim-border-default', theme.borderDefault);
|
||||||
|
style.setProperty('--bim-border-subtle', theme.borderSubtle);
|
||||||
|
|
||||||
|
style.setProperty('--bim-component-bg-hover', theme.componentBgHover);
|
||||||
|
style.setProperty('--bim-component-bg-active', theme.componentBgActive);
|
||||||
|
|
||||||
|
style.setProperty('--bim-shadow-sm', theme.shadowSm);
|
||||||
|
style.setProperty('--bim-shadow-md', theme.shadowMd);
|
||||||
|
style.setProperty('--bim-shadow-lg', theme.shadowLg);
|
||||||
|
style.setProperty('--bim-shadow-glow', theme.shadowGlow);
|
||||||
|
|
||||||
|
const gp = theme.overrides?.glassPill;
|
||||||
|
if (gp) {
|
||||||
|
if (gp.sectionBg) style.setProperty('--bim-glass-pill-section-bg', gp.sectionBg);
|
||||||
|
if (gp.sectionBorder) style.setProperty('--bim-glass-pill-section-border', gp.sectionBorder);
|
||||||
|
if (gp.sectionShadow) style.setProperty('--bim-glass-pill-section-shadow', gp.sectionShadow);
|
||||||
|
if (gp.btnBg) style.setProperty('--bim-glass-pill-btn-bg', gp.btnBg);
|
||||||
|
if (gp.btnBorder) style.setProperty('--bim-glass-pill-btn-border', gp.btnBorder);
|
||||||
|
if (gp.btnShadow) style.setProperty('--bim-glass-pill-btn-shadow', gp.btnShadow);
|
||||||
|
if (gp.btnBgHover) style.setProperty('--bim-glass-pill-btn-bg-hover', gp.btnBgHover);
|
||||||
|
if (gp.btnShadowHover) style.setProperty('--bim-glass-pill-btn-shadow-hover', gp.btnShadowHover);
|
||||||
|
if (gp.iconColor) style.setProperty('--bim-glass-pill-icon-color', gp.iconColor);
|
||||||
|
if (gp.iconColorHover) style.setProperty('--bim-glass-pill-icon-color-hover', gp.iconColorHover);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.syncDropdownCssVars(theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步 CSS 变量到所有 dropdown 元素
|
||||||
|
* dropdown 被添加到 body,无法继承容器的 CSS 变量
|
||||||
|
*/
|
||||||
|
private syncDropdownCssVars(theme: ThemeConfig): void {
|
||||||
|
const dropdowns = document.querySelectorAll('.opt-btn-dropdown');
|
||||||
|
dropdowns.forEach(dropdown => {
|
||||||
|
const style = (dropdown as HTMLElement).style;
|
||||||
|
|
||||||
|
style.setProperty('--bim-primary', theme.primary);
|
||||||
|
style.setProperty('--bim-bg-overlay', theme.bgOverlay);
|
||||||
|
style.setProperty('--bim-bg-glass-blur', theme.bgGlassBlur);
|
||||||
|
style.setProperty('--bim-border-subtle', theme.borderSubtle);
|
||||||
|
style.setProperty('--bim-text-primary', theme.textPrimary);
|
||||||
|
style.setProperty('--bim-text-inverse', theme.textInverse);
|
||||||
|
style.setProperty('--bim-icon-default', theme.iconDefault);
|
||||||
|
style.setProperty('--bim-icon-inverse', theme.iconInverse);
|
||||||
|
style.setProperty('--bim-component-bg-hover', theme.componentBgHover);
|
||||||
|
style.setProperty('--bim-component-bg-active', theme.componentBgActive);
|
||||||
|
style.setProperty('--bim-shadow-lg', theme.shadowLg);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -395,12 +480,13 @@ export class BimButtonGroup implements IBimComponent {
|
|||||||
textWrapper.appendChild(label);
|
textWrapper.appendChild(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (button.children && button.children.length > 0) {
|
// TODO: 暂时隐藏下拉箭头
|
||||||
const arrow = document.createElement('span');
|
// if (button.children && button.children.length > 0) {
|
||||||
arrow.className = 'opt-btn-arrow';
|
// const arrow = document.createElement('span');
|
||||||
arrow.textContent = '▼';
|
// arrow.className = 'opt-btn-arrow';
|
||||||
textWrapper.appendChild(arrow);
|
// arrow.textContent = '▼';
|
||||||
}
|
// textWrapper.appendChild(arrow);
|
||||||
|
// }
|
||||||
|
|
||||||
// 只有当有内容时才添加 wrapper
|
// 只有当有内容时才添加 wrapper
|
||||||
if (textWrapper.hasChildNodes()) {
|
if (textWrapper.hasChildNodes()) {
|
||||||
@@ -543,6 +629,13 @@ export class BimButtonGroup implements IBimComponent {
|
|||||||
dropdown.style.flexDirection = 'row'; // 纵向按钮组,菜单横向排列
|
dropdown.style.flexDirection = 'row'; // 纵向按钮组,菜单横向排列
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.options.type && this.options.type !== 'default') {
|
||||||
|
dropdown.classList.add(`type-${this.options.type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTheme = themeManager.getTheme();
|
||||||
|
dropdown.classList.add(`theme-${currentTheme.name}`);
|
||||||
|
|
||||||
// 先添加到 DOM 以便计算尺寸
|
// 先添加到 DOM 以便计算尺寸
|
||||||
document.body.appendChild(dropdown);
|
document.body.appendChild(dropdown);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
export type ButtonType = 'button' | 'menu';
|
export type ButtonType = 'button' | 'menu';
|
||||||
|
|
||||||
|
/** 按钮组样式类型 */
|
||||||
|
export type ButtonGroupType = 'default' | 'glass-pill';
|
||||||
|
|
||||||
/** 按钮配置 */
|
/** 按钮配置 */
|
||||||
export interface ButtonConfig {
|
export interface ButtonConfig {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -76,6 +79,9 @@ export type ExpandDirection = 'up' | 'down' | 'left' | 'right';
|
|||||||
export interface ButtonGroupOptions extends ButtonGroupColors {
|
export interface ButtonGroupOptions extends ButtonGroupColors {
|
||||||
container: HTMLElement | string;
|
container: HTMLElement | string;
|
||||||
|
|
||||||
|
/** 样式类型 (默认 'default') */
|
||||||
|
type?: ButtonGroupType;
|
||||||
|
|
||||||
/** 屏幕位置 (如 top-left) */
|
/** 屏幕位置 (如 top-left) */
|
||||||
position?: GroupPosition;
|
position?: GroupPosition;
|
||||||
|
|
||||||
|
|||||||
@@ -213,16 +213,13 @@ export class BimCollapse implements IBimComponent {
|
|||||||
|
|
||||||
public setTheme(theme: ThemeConfig): void {
|
public setTheme(theme: ThemeConfig): void {
|
||||||
const style = this.element.style;
|
const style = this.element.style;
|
||||||
style.setProperty('--bim-bg-color', theme.panelBackground);
|
style.setProperty('--bim-bg-color', theme.bgElevated);
|
||||||
style.setProperty('--bim-border-color', theme.border);
|
style.setProperty('--bim-border-color', theme.borderDefault);
|
||||||
style.setProperty('--bim-text-color', theme.textPrimary);
|
style.setProperty('--bim-text-color', theme.textPrimary);
|
||||||
|
style.setProperty('--bim-header-bg-color', theme.componentBgHover);
|
||||||
// 头部默认背景色使用 componentBackground
|
style.setProperty('--bim-header-hover-bg-color', theme.componentBgActive);
|
||||||
style.setProperty('--bim-header-bg-color', theme.componentHover);
|
style.setProperty('--bim-content-bg-color', theme.bgElevated);
|
||||||
style.setProperty('--bim-header-hover-bg-color', theme.componentHover);
|
style.setProperty('--bim-disabled-color', theme.textDisabled);
|
||||||
|
|
||||||
style.setProperty('--bim-content-bg-color', theme.panelBackground);
|
|
||||||
style.setProperty('--bim-disabled-color', theme.textSecondary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public setLocales(): void {
|
public setLocales(): void {
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ export class BimDescription implements IBimComponent {
|
|||||||
style.setProperty('--bim-text-color', theme.textPrimary);
|
style.setProperty('--bim-text-color', theme.textPrimary);
|
||||||
style.setProperty('--bim-label-color', theme.textSecondary);
|
style.setProperty('--bim-label-color', theme.textSecondary);
|
||||||
style.setProperty('--bim-value-color', theme.textPrimary);
|
style.setProperty('--bim-value-color', theme.textPrimary);
|
||||||
style.setProperty('--bim-border-color', theme.border);
|
style.setProperty('--bim-border-color', theme.borderDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
public setLocales(): void {
|
public setLocales(): void {
|
||||||
|
|||||||
@@ -1,37 +1,29 @@
|
|||||||
:root {
|
|
||||||
--bim-dialog-bg: rgba(17, 17, 17, 0.95);
|
|
||||||
--bim-dialog-header-bg: #2a2a2a;
|
|
||||||
--bim-dialog-title-color: #fff;
|
|
||||||
--bim-dialog-text-color: #ccc;
|
|
||||||
--bim-dialog-border-color: #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bim-dialog {
|
.bim-dialog {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: var(--bim-dialog-bg);
|
background-color: var(--bim-dialog-bg, var(--bim-bg-elevated));
|
||||||
border: 1px solid var(--bim-dialog-border-color);
|
border: 1px solid var(--bim-dialog-border-color, var(--bim-border-default));
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
box-shadow: var(--bim-shadow-lg);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
z-index: 10001; /* 提高 z-index,确保在第三方 SDK (10000) 之上 */
|
z-index: 10001;
|
||||||
color: var(--bim-dialog-title-color);
|
color: var(--bim-dialog-title-color, var(--bim-text-primary));
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
pointer-events: auto; /* 确保弹窗可以接收事件 */
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bim-dialog-header {
|
.bim-dialog-header {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
background-color: var(--bim-dialog-header-bg);
|
background-color: var(--bim-dialog-header-bg, var(--bim-bg-inset));
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
border-bottom: 1px solid var(--bim-dialog-border-color);
|
border-bottom: 1px solid var(--bim-dialog-border-color, var(--bim-border-default));
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,20 +43,20 @@
|
|||||||
.bim-dialog-close {
|
.bim-dialog-close {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
color: #999;
|
color: var(--bim-text-tertiary);
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bim-dialog-close:hover {
|
.bim-dialog-close:hover {
|
||||||
color: #fff;
|
color: var(--bim-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bim-dialog-content {
|
.bim-dialog-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--bim-dialog-text-color);
|
color: var(--bim-dialog-text-color, var(--bim-text-secondary));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 缩放句柄 */
|
/* 缩放句柄 */
|
||||||
@@ -78,7 +70,6 @@
|
|||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 右下角装饰,类似斜线 */
|
|
||||||
.bim-dialog-resize-handle::after {
|
.bim-dialog-resize-handle::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -86,10 +77,10 @@
|
|||||||
right: 3px;
|
right: 3px;
|
||||||
width: 6px;
|
width: 6px;
|
||||||
height: 6px;
|
height: 6px;
|
||||||
border-right: 2px solid #666;
|
border-right: 2px solid var(--bim-text-tertiary);
|
||||||
border-bottom: 2px solid #666;
|
border-bottom: 2px solid var(--bim-text-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bim-dialog-resize-handle:hover::after {
|
.bim-dialog-resize-handle:hover::after {
|
||||||
border-color: #fff;
|
border-color: var(--bim-text-primary);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,11 +57,19 @@ export class BimDialog implements IBimComponent {
|
|||||||
*/
|
*/
|
||||||
public setTheme(theme: ThemeConfig) {
|
public setTheme(theme: ThemeConfig) {
|
||||||
const style = this.element.style;
|
const style = this.element.style;
|
||||||
if (!this.options.backgroundColor) style.setProperty('--bim-dialog-bg', theme.panelBackground);
|
if (!this.options.backgroundColor) style.setProperty('--bim-dialog-bg', theme.bgElevated);
|
||||||
if (!this.options.headerBackgroundColor) style.setProperty('--bim-dialog-header-bg', theme.componentHover);
|
if (!this.options.headerBackgroundColor) style.setProperty('--bim-dialog-header-bg', theme.bgInset);
|
||||||
if (!this.options.titleColor) style.setProperty('--bim-dialog-title-color', theme.textPrimary);
|
if (!this.options.titleColor) style.setProperty('--bim-dialog-title-color', theme.textPrimary);
|
||||||
if (!this.options.textColor) style.setProperty('--bim-dialog-text-color', theme.textPrimary);
|
if (!this.options.textColor) style.setProperty('--bim-dialog-text-color', theme.textSecondary);
|
||||||
if (!this.options.borderColor) style.setProperty('--bim-dialog-border-color', theme.border);
|
if (!this.options.borderColor) style.setProperty('--bim-dialog-border-color', theme.borderDefault);
|
||||||
|
|
||||||
|
style.setProperty('--bim-bg-elevated', theme.bgElevated);
|
||||||
|
style.setProperty('--bim-bg-inset', theme.bgInset);
|
||||||
|
style.setProperty('--bim-text-primary', theme.textPrimary);
|
||||||
|
style.setProperty('--bim-text-secondary', theme.textSecondary);
|
||||||
|
style.setProperty('--bim-text-tertiary', theme.textTertiary);
|
||||||
|
style.setProperty('--bim-border-default', theme.borderDefault);
|
||||||
|
style.setProperty('--bim-shadow-lg', theme.shadowLg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export class Engine implements IBimComponent {
|
|||||||
|
|
||||||
// 保存配置选项(设置默认值)
|
// 保存配置选项(设置默认值)
|
||||||
this.options = {
|
this.options = {
|
||||||
backgroundColor: options.backgroundColor ?? 'linear-gradient(to bottom, rgb(214, 224, 235), rgb(246, 250, 255))', // 固定背景渐变色
|
backgroundColor: 'linear-gradient(to bottom, rgb(214, 224, 235), rgb(246, 250, 255))', // 固定背景渐变色
|
||||||
version: options.version ?? 'v1', // 默认使用 v1 版本
|
version: options.version ?? 'v1', // 默认使用 v1 版本
|
||||||
showStats: options.showStats ?? false, // 默认不显示统计
|
showStats: options.showStats ?? false, // 默认不显示统计
|
||||||
showViewCube: options.showViewCube ?? true, // 默认显示视图立方体
|
showViewCube: options.showViewCube ?? true, // 默认显示视图立方体
|
||||||
|
|||||||
@@ -150,9 +150,9 @@ export class MeasurePanel implements IBimComponent {
|
|||||||
const style = this.element.style;
|
const style = this.element.style;
|
||||||
|
|
||||||
// 这些变量不会强制覆盖外部(Dialog)已有变量,只做兜底
|
// 这些变量不会强制覆盖外部(Dialog)已有变量,只做兜底
|
||||||
style.setProperty('--bim-measure-border', theme.border ?? 'rgba(255, 255, 255, 0.12)');
|
style.setProperty('--bim-measure-border', theme.borderDefault ?? 'rgba(255, 255, 255, 0.12)');
|
||||||
style.setProperty('--bim-measure-divider', theme.border ?? 'rgba(255, 255, 255, 0.10)');
|
style.setProperty('--bim-measure-divider', theme.borderDefault ?? 'rgba(255, 255, 255, 0.10)');
|
||||||
style.setProperty('--bim-measure-icon-color', theme.icon ?? '#ddd');
|
style.setProperty('--bim-measure-icon-color', theme.iconDefault ?? '#ddd');
|
||||||
style.setProperty('--bim-measure-label-color', theme.textSecondary ?? 'rgba(255, 255, 255, 0.70)');
|
style.setProperty('--bim-measure-label-color', theme.textSecondary ?? 'rgba(255, 255, 255, 0.70)');
|
||||||
style.setProperty('--bim-measure-value-color', theme.textPrimary ?? 'rgba(255, 255, 255, 0.90)');
|
style.setProperty('--bim-measure-value-color', theme.textPrimary ?? 'rgba(255, 255, 255, 0.90)');
|
||||||
|
|
||||||
@@ -161,9 +161,9 @@ export class MeasurePanel implements IBimComponent {
|
|||||||
// 设置面板“保存设置”按钮用主题色
|
// 设置面板“保存设置”按钮用主题色
|
||||||
style.setProperty('--bim-measure-primary', theme.primary ?? '#0078d4');
|
style.setProperty('--bim-measure-primary', theme.primary ?? '#0078d4');
|
||||||
style.setProperty('--bim-measure-primary-hover', theme.primaryHover ?? '#0063b1');
|
style.setProperty('--bim-measure-primary-hover', theme.primaryHover ?? '#0063b1');
|
||||||
style.setProperty('--bim-measure-btn-bg', theme.componentBackground ?? 'rgba(255, 255, 255, 0.06)');
|
style.setProperty('--bim-measure-btn-bg', theme.componentBg ?? 'rgba(255, 255, 255, 0.06)');
|
||||||
style.setProperty('--bim-measure-btn-hover-bg', theme.componentHover ?? 'rgba(255, 255, 255, 0.10)');
|
style.setProperty('--bim-measure-btn-hover-bg', theme.componentBgHover ?? 'rgba(255, 255, 255, 0.10)');
|
||||||
style.setProperty('--bim-measure-btn-active-bg', theme.componentActive ?? 'rgba(255, 255, 255, 0.14)');
|
style.setProperty('--bim-measure-btn-active-bg', theme.componentBgActive ?? 'rgba(255, 255, 255, 0.14)');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
.bim-menu {
|
.bim-menu {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: var(--bim-ui_bg_color, #2b2d30);
|
background: var(--bim-bg-elevated);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
margin: 0; /* 移除默认外边距 */
|
margin: 0;
|
||||||
list-style: none; /* 移除列表小圆点 */
|
list-style: none;
|
||||||
min-width: 160px;
|
min-width: 160px;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
box-shadow: var(--bim-shadow-lg);
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: var(--bim-ui_text_primary, #ffffff);
|
color: var(--bim-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bim-menu-group {
|
.bim-menu-group {
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
.bim-menu-divider {
|
.bim-menu-divider {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background-color: var(--bim-ui_border_color, #3e4145);
|
background-color: var(--bim-divider);
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,11 +32,11 @@
|
|||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
position: relative;
|
position: relative;
|
||||||
color: var(--bim-ui_text_primary, #ffffff);
|
color: var(--bim-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bim-menu-item:hover {
|
.bim-menu-item:hover {
|
||||||
background-color: var(--bim-ui_bg_hover, #3e4145);
|
background-color: var(--bim-component-bg-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bim-menu-item.disabled {
|
.bim-menu-item.disabled {
|
||||||
|
|||||||
@@ -49,10 +49,11 @@ export class BimMenu implements IBimComponent {
|
|||||||
*/
|
*/
|
||||||
public setTheme(theme: ThemeConfig) {
|
public setTheme(theme: ThemeConfig) {
|
||||||
const style = this.element.style;
|
const style = this.element.style;
|
||||||
style.setProperty('--bim-ui_bg_color', theme.panelBackground);
|
style.setProperty('--bim-bg-elevated', theme.bgElevated);
|
||||||
style.setProperty('--bim-ui_text_primary', theme.textPrimary);
|
style.setProperty('--bim-text-primary', theme.textPrimary);
|
||||||
style.setProperty('--bim-ui_border_color', theme.border);
|
style.setProperty('--bim-divider', theme.divider);
|
||||||
style.setProperty('--bim-ui_bg_hover', theme.componentHover);
|
style.setProperty('--bim-component-bg-hover', theme.componentBgHover);
|
||||||
|
style.setProperty('--bim-shadow-lg', theme.shadowLg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -69,11 +69,11 @@ export class SectionAxisPanel implements IBimComponent {
|
|||||||
*/
|
*/
|
||||||
public setTheme(theme: ThemeConfig): void {
|
public setTheme(theme: ThemeConfig): void {
|
||||||
const style = this.element.style;
|
const style = this.element.style;
|
||||||
style.setProperty('--bim-section-axis-btn-bg', theme.componentBackground ?? 'rgba(255, 255, 255, 0.06)');
|
style.setProperty('--bim-section-axis-btn-bg', theme.componentBg ?? 'rgba(255, 255, 255, 0.06)');
|
||||||
style.setProperty('--bim-section-axis-btn-hover', theme.componentHover ?? 'rgba(255, 255, 255, 0.10)');
|
style.setProperty('--bim-section-axis-btn-hover', theme.componentBgHover ?? 'rgba(255, 255, 255, 0.10)');
|
||||||
style.setProperty('--bim-section-axis-btn-active', theme.componentActive ?? 'rgba(255, 255, 255, 0.14)');
|
style.setProperty('--bim-section-axis-btn-active', theme.componentBgActive ?? 'rgba(255, 255, 255, 0.14)');
|
||||||
style.setProperty('--bim-primary-color', theme.primary ?? '#1890ff');
|
style.setProperty('--bim-primary-color', theme.primary ?? '#1890ff');
|
||||||
style.setProperty('--bim-icon-color', theme.icon ?? '#ccc');
|
style.setProperty('--bim-icon-color', theme.iconDefault ?? '#ccc');
|
||||||
style.setProperty('--bim-text-color', theme.textSecondary ?? 'rgba(255, 255, 255, 0.90)');
|
style.setProperty('--bim-text-color', theme.textSecondary ?? 'rgba(255, 255, 255, 0.90)');
|
||||||
style.setProperty('--bim-text-active-color', theme.textPrimary ?? '#fff');
|
style.setProperty('--bim-text-active-color', theme.textPrimary ?? '#fff');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -339,11 +339,11 @@ export class SectionBoxPanel implements IBimComponent {
|
|||||||
public setTheme(theme: ThemeConfig): void {
|
public setTheme(theme: ThemeConfig): void {
|
||||||
if (!this.element) return;
|
if (!this.element) return;
|
||||||
const style = this.element.style;
|
const style = this.element.style;
|
||||||
style.setProperty('--bim-section-box-btn-bg', theme.componentBackground ?? 'rgba(255, 255, 255, 0.06)');
|
style.setProperty('--bim-section-box-btn-bg', theme.componentBg ?? 'rgba(255, 255, 255, 0.06)');
|
||||||
style.setProperty('--bim-section-box-btn-hover', theme.componentHover ?? 'rgba(255, 255, 255, 0.10)');
|
style.setProperty('--bim-section-box-btn-hover', theme.componentBgHover ?? 'rgba(255, 255, 255, 0.10)');
|
||||||
style.setProperty('--bim-section-box-btn-active', theme.componentActive ?? 'rgba(255, 255, 255, 0.14)');
|
style.setProperty('--bim-section-box-btn-active', theme.componentBgActive ?? 'rgba(255, 255, 255, 0.14)');
|
||||||
style.setProperty('--bim-primary-color', theme.primary ?? '#1890ff');
|
style.setProperty('--bim-primary-color', theme.primary ?? '#1890ff');
|
||||||
style.setProperty('--bim-icon-color', theme.icon ?? '#ccc');
|
style.setProperty('--bim-icon-color', theme.iconDefault ?? '#ccc');
|
||||||
style.setProperty('--bim-text-color', theme.textSecondary ?? 'rgba(255, 255, 255, 0.90)');
|
style.setProperty('--bim-text-color', theme.textSecondary ?? 'rgba(255, 255, 255, 0.90)');
|
||||||
style.setProperty('--bim-text-active-color', theme.textPrimary ?? '#fff');
|
style.setProperty('--bim-text-active-color', theme.textPrimary ?? '#fff');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,11 +55,11 @@ export class SectionPlanePanel implements IBimComponent {
|
|||||||
*/
|
*/
|
||||||
public setTheme(theme: ThemeConfig): void {
|
public setTheme(theme: ThemeConfig): void {
|
||||||
const style = this.element.style;
|
const style = this.element.style;
|
||||||
style.setProperty('--bim-section-btn-bg', theme.componentBackground ?? 'rgba(255, 255, 255, 0.06)');
|
style.setProperty('--bim-section-btn-bg', theme.componentBg ?? 'rgba(255, 255, 255, 0.06)');
|
||||||
style.setProperty('--bim-section-btn-hover', theme.componentHover ?? 'rgba(255, 255, 255, 0.10)');
|
style.setProperty('--bim-section-btn-hover', theme.componentBgHover ?? 'rgba(255, 255, 255, 0.10)');
|
||||||
style.setProperty('--bim-section-btn-active', theme.componentActive ?? 'rgba(255, 255, 255, 0.14)');
|
style.setProperty('--bim-section-btn-active', theme.componentBgActive ?? 'rgba(255, 255, 255, 0.14)');
|
||||||
style.setProperty('--bim-primary-color', theme.primary ?? '#1890ff');
|
style.setProperty('--bim-primary-color', theme.primary ?? '#1890ff');
|
||||||
style.setProperty('--bim-icon-color', theme.icon ?? '#ccc');
|
style.setProperty('--bim-icon-color', theme.iconDefault ?? '#ccc');
|
||||||
style.setProperty('--bim-text-color', theme.textSecondary ?? 'rgba(255, 255, 255, 0.90)');
|
style.setProperty('--bim-text-color', theme.textSecondary ?? 'rgba(255, 255, 255, 0.90)');
|
||||||
style.setProperty('--bim-text-active-color', theme.textPrimary ?? '#fff');
|
style.setProperty('--bim-text-active-color', theme.textPrimary ?? '#fff');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,15 +189,15 @@ export class BimTab implements IBimComponent {
|
|||||||
*/
|
*/
|
||||||
public setTheme(theme: ThemeConfig): void {
|
public setTheme(theme: ThemeConfig): void {
|
||||||
const style = this.element.style;
|
const style = this.element.style;
|
||||||
style.setProperty('--bim-tab-bg', theme.panelBackground);
|
style.setProperty('--bim-tab-bg', theme.bgElevated);
|
||||||
style.setProperty('--bim-tab-nav-bg', theme.panelBackground);
|
style.setProperty('--bim-tab-nav-bg', theme.bgElevated);
|
||||||
style.setProperty('--bim-tab-text', theme.textPrimary);
|
style.setProperty('--bim-tab-text', theme.textPrimary);
|
||||||
style.setProperty('--bim-tab-text-secondary', theme.textSecondary);
|
style.setProperty('--bim-tab-text-secondary', theme.textSecondary);
|
||||||
style.setProperty('--bim-tab-text-active', theme.primary);
|
style.setProperty('--bim-tab-text-active', theme.primary);
|
||||||
style.setProperty('--bim-tab-border', theme.border);
|
style.setProperty('--bim-tab-border', theme.borderDefault);
|
||||||
style.setProperty('--bim-tab-hover-bg', theme.componentHover);
|
style.setProperty('--bim-tab-hover-bg', theme.componentBgHover);
|
||||||
style.setProperty('--bim-tab-active-bg', theme.componentActive);
|
style.setProperty('--bim-tab-active-bg', theme.componentBgActive);
|
||||||
style.setProperty('--bim-tab-icon', theme.icon);
|
style.setProperty('--bim-tab-icon', theme.iconDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
/* 树容器 */
|
|
||||||
.bim-tree {
|
.bim-tree {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden; /* 内部滚动 */
|
overflow: hidden;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--bim-ui_text_primary, #333);
|
color: var(--bim-text-primary);
|
||||||
user-select: none; /* 防止双击选中文字 */
|
user-select: none;
|
||||||
position: relative; /* 为下拉框定位 */
|
position: relative;
|
||||||
background: transparent; /* 保持透明背景 */
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 搜索区域 */
|
/* 搜索区域 */
|
||||||
@@ -34,8 +33,8 @@
|
|||||||
left: 8px;
|
left: 8px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
color: var(--bim-ui_text_secondary, #999);
|
color: var(--bim-text-secondary);
|
||||||
pointer-events: none; /* 让点击穿透到 input */
|
pointer-events: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -49,33 +48,30 @@
|
|||||||
.bim-tree-search-input {
|
.bim-tree-search-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
/* 左侧留出图标位置: 8px left + 16px icon + 4px gap = 28px */
|
|
||||||
padding: 4px 8px 4px 30px;
|
padding: 4px 8px 4px 30px;
|
||||||
border: 1px solid var(--bim-ui_border_color, #d9d9d9);
|
border: 1px solid var(--bim-border-default);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
outline: none;
|
outline: none;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
/* 输入框背景半透明或白色,看设计,通常输入框本身还是需要背景的,否则文字看不清 */
|
background-color: var(--bim-bg-elevated);
|
||||||
background-color: var(--bim-ui_bg_color, #fff);
|
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bim-tree-search-input:focus {
|
.bim-tree-search-input:focus {
|
||||||
border-color: var(--bim-primary_color, #1890ff);
|
border-color: var(--bim-primary);
|
||||||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
box-shadow: var(--bim-focus-ring);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 搜索结果下拉框 */
|
|
||||||
.bim-tree-search-results {
|
.bim-tree-search-results {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 100%;
|
top: 100%;
|
||||||
left: 8px;
|
left: 8px;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
background-color: var(--bim-ui_bg_color, #fff);
|
background-color: var(--bim-bg-elevated);
|
||||||
border: 1px solid var(--bim-ui_border_color, #eee);
|
border: 1px solid var(--bim-border-default);
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
box-shadow: var(--bim-shadow-md);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -98,7 +94,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bim-tree-search-item:hover {
|
.bim-tree-search-item:hover {
|
||||||
background-color: var(--bim-ui_bg_hover, #f5f5f5);
|
background-color: var(--bim-component-bg-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bim-tree-search-item-title {
|
.bim-tree-search-item-title {
|
||||||
@@ -108,7 +104,7 @@
|
|||||||
|
|
||||||
.bim-tree-search-item-path {
|
.bim-tree-search-item-path {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--bim-ui_text_secondary, #999);
|
color: var(--bim-text-secondary);
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@@ -139,10 +135,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bim-tree-node-content:hover {
|
.bim-tree-node-content:hover {
|
||||||
background-color: var(--bim-ui_bg_hover, rgba(0, 0, 0, 0.05)); /* 由主题提供,深色模式一致 */
|
background-color: var(--bim-component-bg-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 展开/折叠箭头 */
|
|
||||||
.bim-tree-switcher {
|
.bim-tree-switcher {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
@@ -150,7 +145,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--bim-ui_text_secondary, #999);
|
color: var(--bim-text-secondary);
|
||||||
transition: transform 0.2s;
|
transition: transform 0.2s;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
@@ -170,14 +165,13 @@
|
|||||||
visibility: hidden; /*叶子节点占位但不显示*/
|
visibility: hidden; /*叶子节点占位但不显示*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 复选框 */
|
|
||||||
.bim-tree-checkbox {
|
.bim-tree-checkbox {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
border: 1px solid var(--bim-ui_border_color, #d9d9d9);
|
border: 1px solid var(--bim-border-default);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
background-color: var(--bim-ui_bg_color, #fff);
|
background-color: var(--bim-bg-elevated);
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@@ -185,13 +179,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bim-tree-checkbox:hover {
|
.bim-tree-checkbox:hover {
|
||||||
border-color: var(--bim-primary_color, #1890ff);
|
border-color: var(--bim-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 选中状态 */
|
|
||||||
.bim-tree-checkbox.is-checked {
|
.bim-tree-checkbox.is-checked {
|
||||||
background-color: var(--bim-primary_color, #1890ff);
|
background-color: var(--bim-primary);
|
||||||
border-color: var(--bim-primary_color, #1890ff);
|
border-color: var(--bim-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bim-tree-checkbox.is-checked::after {
|
.bim-tree-checkbox.is-checked::after {
|
||||||
@@ -201,16 +194,15 @@
|
|||||||
left: 4px;
|
left: 4px;
|
||||||
width: 5px;
|
width: 5px;
|
||||||
height: 9px;
|
height: 9px;
|
||||||
border: 2px solid #fff;
|
border: 2px solid var(--bim-text-inverse);
|
||||||
border-top: 0;
|
border-top: 0;
|
||||||
border-left: 0;
|
border-left: 0;
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 半选状态 */
|
|
||||||
.bim-tree-checkbox.is-indeterminate {
|
.bim-tree-checkbox.is-indeterminate {
|
||||||
background-color: var(--bim-ui_bg_color, #fff);
|
background-color: var(--bim-bg-elevated);
|
||||||
border-color: var(--bim-primary_color, #1890ff);
|
border-color: var(--bim-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bim-tree-checkbox.is-indeterminate::after {
|
.bim-tree-checkbox.is-indeterminate::after {
|
||||||
@@ -220,22 +212,21 @@
|
|||||||
left: 3px;
|
left: 3px;
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
background-color: var(--bim-primary_color, #1890ff);
|
background-color: var(--bim-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 禁用状态 */
|
|
||||||
.bim-tree-node.is-disabled .bim-tree-checkbox {
|
.bim-tree-node.is-disabled .bim-tree-checkbox {
|
||||||
background-color: #f5f5f5;
|
background-color: var(--bim-component-bg-disabled);
|
||||||
border-color: #d9d9d9;
|
border-color: var(--bim-border-disabled);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bim-tree-node.is-disabled .bim-tree-checkbox.is-checked {
|
.bim-tree-node.is-disabled .bim-tree-checkbox.is-checked {
|
||||||
background-color: #d9d9d9;
|
background-color: var(--bim-text-disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bim-tree-node.is-disabled .bim-tree-node-content {
|
.bim-tree-node.is-disabled .bim-tree-node-content {
|
||||||
color: var(--bim-ui_text_disabled, #ccc);
|
color: var(--bim-text-disabled);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,10 +266,9 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 选中高亮状态 */
|
|
||||||
.bim-tree-node-content.is-selected {
|
.bim-tree-node-content.is-selected {
|
||||||
background-color: var(--bim-ui_bg_selected, rgba(24, 144, 255, 0.2));
|
background-color: var(--bim-component-bg-selected);
|
||||||
color: var(--bim-primary_color, #1890ff);
|
color: var(--bim-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 节点操作栏 */
|
/* 节点操作栏 */
|
||||||
|
|||||||
@@ -235,13 +235,20 @@ export class BimTree implements IBimComponent {
|
|||||||
*/
|
*/
|
||||||
public setTheme(theme: ThemeConfig): void {
|
public setTheme(theme: ThemeConfig): void {
|
||||||
const style = this.element.style;
|
const style = this.element.style;
|
||||||
style.setProperty('--bim-ui_bg_color', theme.panelBackground);
|
style.setProperty('--bim-primary', theme.primary);
|
||||||
style.setProperty('--bim-ui_text_primary', theme.textPrimary);
|
style.setProperty('--bim-bg-elevated', theme.bgElevated);
|
||||||
style.setProperty('--bim-ui_text_secondary', theme.textSecondary || '#999');
|
style.setProperty('--bim-text-primary', theme.textPrimary);
|
||||||
style.setProperty('--bim-ui_border_color', theme.border);
|
style.setProperty('--bim-text-secondary', theme.textSecondary);
|
||||||
style.setProperty('--bim-ui_bg_hover', theme.componentHover);
|
style.setProperty('--bim-text-tertiary', theme.textTertiary);
|
||||||
style.setProperty('--bim-primary_color', theme.primary);
|
style.setProperty('--bim-text-disabled', theme.textDisabled);
|
||||||
// style.setProperty('--bim-ui_text_disabled', theme.textDisabled); // 如果 ThemeConfig 有这个字段
|
style.setProperty('--bim-text-inverse', theme.textInverse);
|
||||||
|
style.setProperty('--bim-border-default', theme.borderDefault);
|
||||||
|
style.setProperty('--bim-border-disabled', theme.borderDisabled);
|
||||||
|
style.setProperty('--bim-component-bg-hover', theme.componentBgHover);
|
||||||
|
style.setProperty('--bim-component-bg-selected', theme.componentBgSelected);
|
||||||
|
style.setProperty('--bim-component-bg-disabled', theme.componentBgDisabled);
|
||||||
|
style.setProperty('--bim-shadow-md', theme.shadowMd);
|
||||||
|
style.setProperty('--bim-focus-ring', theme.focusRing);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -434,20 +434,20 @@ export class WalkControlPanel implements IBimComponent {
|
|||||||
public setTheme(theme: ThemeConfig): void {
|
public setTheme(theme: ThemeConfig): void {
|
||||||
if (!this.element) return;
|
if (!this.element) return;
|
||||||
const style = this.element.style;
|
const style = this.element.style;
|
||||||
style.setProperty('--bim-walk-control-bg', theme.panelBackground ?? 'rgba(0, 0, 0, 0.8)');
|
style.setProperty('--bim-walk-control-bg', theme.bgElevated ?? 'rgba(0, 0, 0, 0.8)');
|
||||||
style.setProperty('--bim-walk-btn-hover', theme.componentHover ?? 'rgba(255, 255, 255, 0.15)');
|
style.setProperty('--bim-walk-btn-hover', theme.componentBgHover ?? 'rgba(255, 255, 255, 0.15)');
|
||||||
style.setProperty('--bim-walk-btn-active', theme.componentActive ?? 'rgba(255, 255, 255, 0.3)');
|
style.setProperty('--bim-walk-btn-active', theme.componentBgActive ?? 'rgba(255, 255, 255, 0.3)');
|
||||||
style.setProperty('--bim-primary-color', theme.primary ?? '#1890ff');
|
style.setProperty('--bim-primary-color', theme.primary ?? '#1890ff');
|
||||||
style.setProperty('--bim-primary-hover', theme.primaryHover ?? '#40a9ff');
|
style.setProperty('--bim-primary-hover', theme.primaryHover ?? '#40a9ff');
|
||||||
style.setProperty('--bim-icon-color', theme.icon ?? '#ccc');
|
style.setProperty('--bim-icon-color', theme.iconDefault ?? '#ccc');
|
||||||
style.setProperty('--bim-text-color', theme.textPrimary ?? '#fff');
|
style.setProperty('--bim-text-color', theme.textPrimary ?? '#fff');
|
||||||
style.setProperty('--bim-divider-color', theme.border ?? 'rgba(255, 255, 255, 0.2)');
|
style.setProperty('--bim-divider-color', theme.borderDefault ?? 'rgba(255, 255, 255, 0.2)');
|
||||||
style.setProperty('--bim-speed-group-bg', theme.componentHover ?? 'rgba(255, 255, 255, 0.1)');
|
style.setProperty('--bim-speed-group-bg', theme.componentBgHover ?? 'rgba(255, 255, 255, 0.1)');
|
||||||
style.setProperty('--bim-speed-btn-bg', theme.componentHover ?? 'rgba(255, 255, 255, 0.1)');
|
style.setProperty('--bim-speed-btn-bg', theme.componentBgHover ?? 'rgba(255, 255, 255, 0.1)');
|
||||||
style.setProperty('--bim-speed-btn-hover', theme.componentActive ?? 'rgba(255, 255, 255, 0.2)');
|
style.setProperty('--bim-speed-btn-hover', theme.componentBgActive ?? 'rgba(255, 255, 255, 0.2)');
|
||||||
style.setProperty('--bim-select-bg', theme.componentHover ?? 'rgba(255, 255, 255, 0.1)');
|
style.setProperty('--bim-select-bg', theme.componentBgHover ?? 'rgba(255, 255, 255, 0.1)');
|
||||||
style.setProperty('--bim-select-border', theme.border ?? 'rgba(255, 255, 255, 0.2)');
|
style.setProperty('--bim-select-border', theme.borderDefault ?? 'rgba(255, 255, 255, 0.2)');
|
||||||
style.setProperty('--bim-select-option-bg', theme.panelBackground ?? '#333');
|
style.setProperty('--bim-select-option-bg', theme.bgElevated ?? '#333');
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy(): void {
|
public destroy(): void {
|
||||||
|
|||||||
@@ -28,11 +28,12 @@ export class ToolbarManager extends BimComponent {
|
|||||||
|
|
||||||
this.toolbar = new Toolbar({
|
this.toolbar = new Toolbar({
|
||||||
container: this.toolbarContainer,
|
container: this.toolbarContainer,
|
||||||
|
type: 'glass-pill',
|
||||||
showLabel: true,
|
showLabel: true,
|
||||||
direction: 'row',
|
direction: 'row',
|
||||||
position: 'bottom-center', // 底部居中
|
position: 'bottom-right',
|
||||||
align: 'vertical', // 图标在上
|
align: 'vertical',
|
||||||
expand: 'up' // 向上展开
|
expand: 'up'
|
||||||
});
|
});
|
||||||
|
|
||||||
// 注入 engine 到 Toolbar
|
// 注入 engine 到 Toolbar
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ type ThemeChangeListener = (theme: ThemeConfig) => void;
|
|||||||
* 主题管理器 (单例)
|
* 主题管理器 (单例)
|
||||||
*/
|
*/
|
||||||
export class ThemeManager {
|
export class ThemeManager {
|
||||||
private currentTheme: ThemeConfig = darkTheme;
|
private currentTheme: ThemeConfig = lightTheme;
|
||||||
private listeners: ThemeChangeListener[] = [];
|
private listeners: ThemeChangeListener[] = [];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|||||||
@@ -1,51 +1,237 @@
|
|||||||
import { ThemeConfig } from './types';
|
import { ThemeConfig } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 深色主题 (默认)
|
* Tailwind CSS Color Palette Reference (for maintainers)
|
||||||
|
*
|
||||||
|
* Slate: #f8fafc #f1f5f9 #e2e8f0 #cbd5e1 #94a3b8 #64748b #475569 #334155 #1e293b #0f172a
|
||||||
|
* Blue: #eff6ff #dbeafe #bfdbfe #93c5fd #60a5fa #3b82f6 #2563eb #1d4ed8 #1e40af #1e3a8a
|
||||||
|
* Green: #f0fdf4 #dcfce7 #bbf7d0 #86efac #4ade80 #22c55e #16a34a #15803d #166534 #14532d
|
||||||
|
* Amber: #fffbeb #fef3c7 #fde68a #fcd34d #fbbf24 #f59e0b #d97706 #b45309 #92400e #78350f
|
||||||
|
* Red: #fef2f2 #fee2e2 #fecaca #fca5a5 #f87171 #ef4444 #dc2626 #b91c1c #991b1b #7f1d1d
|
||||||
|
* Cyan: #ecfeff #cffafe #a5f3fc #67e8f9 #22d3ee #06b6d4 #0891b2 #0e7490 #155e75 #164e63
|
||||||
*/
|
*/
|
||||||
export const darkTheme: ThemeConfig = {
|
|
||||||
name: 'dark',
|
|
||||||
primary: '#0078d4',
|
|
||||||
primaryHover: '#0063b1',
|
|
||||||
|
|
||||||
// 修改:背景色统一为浅灰,不再跟随深色模式变黑
|
|
||||||
background: '#f5f5f5',
|
|
||||||
panelBackground: 'rgba(30, 30, 30, 0.9)',
|
|
||||||
|
|
||||||
textPrimary: '#ffffff',
|
|
||||||
textSecondary: '#cccccc',
|
|
||||||
|
|
||||||
border: '#444444',
|
|
||||||
|
|
||||||
icon: '#cccccc',
|
|
||||||
iconActive: '#ffffff',
|
|
||||||
|
|
||||||
componentBackground: 'transparent',
|
|
||||||
componentHover: '#4e4d4dff',
|
|
||||||
componentActive: 'rgba(255, 255, 255, 0.1)'
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 浅色主题
|
|
||||||
*/
|
|
||||||
export const lightTheme: ThemeConfig = {
|
export const lightTheme: ThemeConfig = {
|
||||||
name: 'light',
|
name: 'light',
|
||||||
primary: '#0078d4',
|
|
||||||
primaryHover: '#106ebe',
|
|
||||||
|
|
||||||
// 统一为浅灰
|
primary: '#2563eb',
|
||||||
background: '#f5f5f5',
|
primaryHover: '#1d4ed8',
|
||||||
panelBackground: '#ffffff',
|
primaryActive: '#1e40af',
|
||||||
|
primarySubtle: 'rgba(37, 99, 235, 0.1)',
|
||||||
|
|
||||||
textPrimary: '#333333',
|
success: '#16a34a',
|
||||||
textSecondary: '#666666',
|
successHover: '#15803d',
|
||||||
|
successSubtle: 'rgba(22, 163, 74, 0.1)',
|
||||||
|
|
||||||
border: '#e0e0e0',
|
warning: '#d97706',
|
||||||
|
warningHover: '#b45309',
|
||||||
|
warningSubtle: 'rgba(217, 119, 6, 0.1)',
|
||||||
|
|
||||||
icon: '#555555',
|
danger: '#dc2626',
|
||||||
iconActive: '#0078d4',
|
dangerHover: '#b91c1c',
|
||||||
|
dangerSubtle: 'rgba(220, 38, 38, 0.1)',
|
||||||
|
|
||||||
componentBackground: 'transparent',
|
info: '#0891b2',
|
||||||
componentHover: '#f0f0f0',
|
infoHover: '#0e7490',
|
||||||
componentActive: '#e0e0e0'
|
infoSubtle: 'rgba(8, 145, 178, 0.1)',
|
||||||
};
|
|
||||||
|
bgBase: '#f8fafc',
|
||||||
|
bgElevated: '#ffffff',
|
||||||
|
bgOverlay: 'rgba(255, 255, 255, 0.98)',
|
||||||
|
bgInset: '#f1f5f9',
|
||||||
|
bgGlass: 'rgba(255, 255, 255, 0.8)',
|
||||||
|
bgGlassBlur: '24px',
|
||||||
|
|
||||||
|
textPrimary: '#0f172a',
|
||||||
|
textSecondary: '#475569',
|
||||||
|
textTertiary: '#94a3b8',
|
||||||
|
textDisabled: '#cbd5e1',
|
||||||
|
textInverse: '#ffffff',
|
||||||
|
textLink: '#2563eb',
|
||||||
|
textLinkHover: '#1d4ed8',
|
||||||
|
|
||||||
|
iconDefault: '#64748b',
|
||||||
|
iconHover: '#334155',
|
||||||
|
iconActive: '#2563eb',
|
||||||
|
iconDisabled: '#cbd5e1',
|
||||||
|
iconInverse: '#ffffff',
|
||||||
|
|
||||||
|
borderDefault: '#e2e8f0',
|
||||||
|
borderSubtle: '#f1f5f9',
|
||||||
|
borderStrong: '#cbd5e1',
|
||||||
|
borderDisabled: '#f1f5f9',
|
||||||
|
divider: '#e2e8f0',
|
||||||
|
|
||||||
|
componentBg: 'transparent',
|
||||||
|
componentBgHover: 'rgba(15, 23, 42, 0.04)',
|
||||||
|
componentBgActive: 'rgba(15, 23, 42, 0.08)',
|
||||||
|
componentBgSelected: 'rgba(37, 99, 235, 0.08)',
|
||||||
|
componentBgDisabled: '#f8fafc',
|
||||||
|
|
||||||
|
focusRing: 'rgba(37, 99, 235, 0.5)',
|
||||||
|
focusRingOffset: '#ffffff',
|
||||||
|
selectionBg: 'rgba(37, 99, 235, 0.15)',
|
||||||
|
selectionText: '#0f172a',
|
||||||
|
|
||||||
|
shadowSm: '0 2px 8px rgba(0, 0, 0, 0.1), 0 4px 12px rgba(0, 0, 0, 0.08)',
|
||||||
|
shadowMd: '0 4px 12px rgba(0, 0, 0, 0.12), 0 6px 20px rgba(0, 0, 0, 0.1)',
|
||||||
|
shadowLg: '0 10px 25px rgba(0, 0, 0, 0.15), 0 6px 10px rgba(0, 0, 0, 0.1)',
|
||||||
|
shadowXl: '0 20px 40px rgba(0, 0, 0, 0.2), 0 10px 15px rgba(0, 0, 0, 0.1)',
|
||||||
|
shadowGlow: '0 2px 8px rgba(59, 130, 246, 0.3), 0 4px 16px rgba(59, 130, 246, 0.2)',
|
||||||
|
|
||||||
|
scrollbarTrack: '#f1f5f9',
|
||||||
|
scrollbarThumb: '#cbd5e1',
|
||||||
|
scrollbarThumbHover: '#94a3b8',
|
||||||
|
|
||||||
|
overrides: {
|
||||||
|
dialog: {
|
||||||
|
headerBg: '#f8fafc',
|
||||||
|
},
|
||||||
|
toolbar: {
|
||||||
|
bg: 'rgba(255, 255, 255, 0.9)',
|
||||||
|
buttonBg: 'rgba(255, 255, 255, 0.95)',
|
||||||
|
buttonBgHover: '#ffffff',
|
||||||
|
buttonBgActive: '#2563eb',
|
||||||
|
},
|
||||||
|
glassPill: {
|
||||||
|
sectionBg: 'rgba(255, 255, 255, 0.8)',
|
||||||
|
sectionBorder: 'rgba(226, 232, 240, 0.5)',
|
||||||
|
sectionShadow: '0 4px 24px rgba(0, 0, 0, 0.06)',
|
||||||
|
btnBg: 'rgba(255, 255, 255, 0.9)',
|
||||||
|
btnBorder: 'rgba(203, 213, 225, 0.6)',
|
||||||
|
btnShadow: '0 2px 8px rgba(0, 0, 0, 0.1), 0 4px 12px rgba(0, 0, 0, 0.08)',
|
||||||
|
btnBgHover: '#ffffff',
|
||||||
|
btnShadowHover: '0 4px 12px rgba(0, 0, 0, 0.12), 0 6px 20px rgba(0, 0, 0, 0.1)',
|
||||||
|
iconColor: '#334155',
|
||||||
|
iconColorHover: '#1e293b',
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
bg: '#ffffff',
|
||||||
|
bgFocus: '#ffffff',
|
||||||
|
placeholder: '#94a3b8',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const darkTheme: ThemeConfig = {
|
||||||
|
name: 'dark',
|
||||||
|
|
||||||
|
primary: '#3b82f6',
|
||||||
|
primaryHover: '#60a5fa',
|
||||||
|
primaryActive: '#2563eb',
|
||||||
|
primarySubtle: 'rgba(59, 130, 246, 0.15)',
|
||||||
|
|
||||||
|
success: '#22c55e',
|
||||||
|
successHover: '#4ade80',
|
||||||
|
successSubtle: 'rgba(34, 197, 94, 0.15)',
|
||||||
|
|
||||||
|
warning: '#f59e0b',
|
||||||
|
warningHover: '#fbbf24',
|
||||||
|
warningSubtle: 'rgba(245, 158, 11, 0.15)',
|
||||||
|
|
||||||
|
danger: '#ef4444',
|
||||||
|
dangerHover: '#f87171',
|
||||||
|
dangerSubtle: 'rgba(239, 68, 68, 0.15)',
|
||||||
|
|
||||||
|
info: '#06b6d4',
|
||||||
|
infoHover: '#22d3ee',
|
||||||
|
infoSubtle: 'rgba(6, 182, 212, 0.15)',
|
||||||
|
|
||||||
|
bgBase: '#152232',
|
||||||
|
bgElevated: '#1f2d3e',
|
||||||
|
bgOverlay: 'rgba(21, 34, 50, 0.98)',
|
||||||
|
bgInset: '#152232',
|
||||||
|
bgGlass: 'rgba(21, 34, 50, 0.85)',
|
||||||
|
bgGlassBlur: '24px',
|
||||||
|
|
||||||
|
textPrimary: '#ffffff',
|
||||||
|
textSecondary: '#94a3b8',
|
||||||
|
textTertiary: '#64748b',
|
||||||
|
textDisabled: '#475569',
|
||||||
|
textInverse: '#152232',
|
||||||
|
textLink: '#60a5fa',
|
||||||
|
textLinkHover: '#93c5fd',
|
||||||
|
|
||||||
|
iconDefault: '#ffffff',
|
||||||
|
iconHover: '#ffffff',
|
||||||
|
iconActive: '#3b82f6',
|
||||||
|
iconDisabled: '#475569',
|
||||||
|
iconInverse: '#152232',
|
||||||
|
|
||||||
|
borderDefault: 'rgba(51, 65, 85, 0.5)',
|
||||||
|
borderSubtle: 'rgba(51, 65, 85, 0.5)',
|
||||||
|
borderStrong: '#475569',
|
||||||
|
borderDisabled: '#1e293b',
|
||||||
|
divider: '#334155',
|
||||||
|
|
||||||
|
componentBg: 'transparent',
|
||||||
|
componentBgHover: 'rgba(248, 250, 252, 0.06)',
|
||||||
|
componentBgActive: 'rgba(248, 250, 252, 0.1)',
|
||||||
|
componentBgSelected: 'rgba(59, 130, 246, 0.2)',
|
||||||
|
componentBgDisabled: '#1e293b',
|
||||||
|
|
||||||
|
focusRing: 'rgba(59, 130, 246, 0.5)',
|
||||||
|
focusRingOffset: '#1e293b',
|
||||||
|
selectionBg: 'rgba(59, 130, 246, 0.3)',
|
||||||
|
selectionText: '#f8fafc',
|
||||||
|
|
||||||
|
shadowSm: '0 2px 8px rgba(0, 0, 0, 0.4), 0 4px 12px rgba(0, 0, 0, 0.3)',
|
||||||
|
shadowMd: '0 4px 12px rgba(0, 0, 0, 0.5), 0 6px 20px rgba(0, 0, 0, 0.4)',
|
||||||
|
shadowLg: '0 10px 25px rgba(0, 0, 0, 0.6), 0 6px 10px rgba(0, 0, 0, 0.4)',
|
||||||
|
shadowXl: '0 20px 40px rgba(0, 0, 0, 0.7), 0 10px 15px rgba(0, 0, 0, 0.5)',
|
||||||
|
shadowGlow: '0 2px 8px rgba(59, 130, 246, 0.4), 0 4px 16px rgba(59, 130, 246, 0.25)',
|
||||||
|
|
||||||
|
scrollbarTrack: '#1e293b',
|
||||||
|
scrollbarThumb: '#475569',
|
||||||
|
scrollbarThumbHover: '#64748b',
|
||||||
|
|
||||||
|
overrides: {
|
||||||
|
dialog: {
|
||||||
|
headerBg: '#0f172a',
|
||||||
|
},
|
||||||
|
toolbar: {
|
||||||
|
bg: 'rgba(30, 41, 59, 0.9)',
|
||||||
|
buttonBg: 'rgba(51, 65, 85, 0.6)',
|
||||||
|
buttonBgHover: 'rgba(71, 85, 105, 0.7)',
|
||||||
|
buttonBgActive: '#3b82f6',
|
||||||
|
},
|
||||||
|
glassPill: {
|
||||||
|
sectionBg: 'rgba(30, 41, 59, 0.8)',
|
||||||
|
sectionBorder: 'rgba(51, 65, 85, 0.5)',
|
||||||
|
sectionShadow: '0 4px 24px rgba(0, 0, 0, 0.15)',
|
||||||
|
btnBg: 'rgba(51, 65, 85, 0.5)',
|
||||||
|
btnBorder: 'rgba(71, 85, 105, 0.4)',
|
||||||
|
btnShadow: '0 2px 8px rgba(0, 0, 0, 0.25), 0 4px 12px rgba(0, 0, 0, 0.2)',
|
||||||
|
btnBgHover: 'rgba(71, 85, 105, 0.6)',
|
||||||
|
btnShadowHover: '0 4px 12px rgba(0, 0, 0, 0.3), 0 6px 20px rgba(0, 0, 0, 0.25)',
|
||||||
|
iconColor: '#e2e8f0',
|
||||||
|
iconColorHover: '#f8fafc',
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
bg: '#0f172a',
|
||||||
|
bgFocus: '#1e293b',
|
||||||
|
placeholder: '#64748b',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createThemeFromPartial(base: ThemeConfig, partial: Partial<ThemeConfig>): ThemeConfig {
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
...partial,
|
||||||
|
overrides: {
|
||||||
|
...base.overrides,
|
||||||
|
...partial.overrides,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getThemeByName(name: string): ThemeConfig {
|
||||||
|
switch (name) {
|
||||||
|
case 'light':
|
||||||
|
return lightTheme;
|
||||||
|
case 'dark':
|
||||||
|
default:
|
||||||
|
return darkTheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,43 +1,407 @@
|
|||||||
/**
|
/**
|
||||||
* 全局主题配置接口
|
* BIM Engine SDK - Unified Theme System
|
||||||
* 定义系统通用的语义化颜色
|
*
|
||||||
|
* Design Principles:
|
||||||
|
* 1. Semantic naming - colors describe PURPOSE, not appearance
|
||||||
|
* 2. Hierarchical structure - organized by category for scalability
|
||||||
|
* 3. Complete coverage - all UI states and component needs
|
||||||
|
* 4. Accessibility - ensures proper contrast ratios
|
||||||
|
* 5. Glassmorphism support - includes transparency and blur values
|
||||||
|
*
|
||||||
|
* Color Palette Reference: Tailwind CSS (slate, blue, red, green, amber)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Color Primitives (for reference, not exported directly)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Semantic Color Token
|
||||||
|
* Represents a single color value with optional transparency
|
||||||
|
*/
|
||||||
|
type ColorToken = string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shadow Token
|
||||||
|
* CSS box-shadow value
|
||||||
|
*/
|
||||||
|
type ShadowToken = string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blur Token
|
||||||
|
* CSS backdrop-filter blur value (e.g., '24px')
|
||||||
|
*/
|
||||||
|
type BlurToken = string;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Theme Configuration Interface
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete Theme Configuration Interface
|
||||||
|
*
|
||||||
|
* All colors use semantic naming to describe their purpose.
|
||||||
|
* Components should map these to their internal CSS variables.
|
||||||
*/
|
*/
|
||||||
export interface ThemeConfig {
|
export interface ThemeConfig {
|
||||||
/** 主题名称 */
|
// =========================================================================
|
||||||
|
// Meta
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/** Theme identifier: 'dark' | 'light' | custom name */
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
/** 品牌色/主色 */
|
|
||||||
primary: string;
|
|
||||||
/** 主色悬停/激活态 */
|
|
||||||
primaryHover: string;
|
|
||||||
|
|
||||||
/** 基础背景色 (应用整体背景) */
|
// =========================================================================
|
||||||
background: string;
|
// Brand / Primary Colors
|
||||||
/** 面板背景色 (工具栏、弹窗背景) */
|
// =========================================================================
|
||||||
panelBackground: string;
|
|
||||||
|
|
||||||
/** 主要文字颜色 */
|
/** Primary brand color - used for key actions, links, active states */
|
||||||
textPrimary: string;
|
primary: ColorToken;
|
||||||
/** 次要文字颜色 */
|
/** Primary color on hover */
|
||||||
textSecondary: string;
|
primaryHover: ColorToken;
|
||||||
|
/** Primary color when pressed/active */
|
||||||
|
primaryActive: ColorToken;
|
||||||
|
/** Subtle primary for backgrounds (e.g., selected row) */
|
||||||
|
primarySubtle: ColorToken;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Status / Semantic Colors
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
/** 边框/分割线颜色 */
|
/** Success color - confirmations, completed states */
|
||||||
border: string;
|
success: ColorToken;
|
||||||
|
/** Success color on hover */
|
||||||
|
successHover: ColorToken;
|
||||||
|
/** Subtle success for backgrounds */
|
||||||
|
successSubtle: ColorToken;
|
||||||
|
|
||||||
|
/** Warning color - caution, pending states */
|
||||||
|
warning: ColorToken;
|
||||||
|
/** Warning color on hover */
|
||||||
|
warningHover: ColorToken;
|
||||||
|
/** Subtle warning for backgrounds */
|
||||||
|
warningSubtle: ColorToken;
|
||||||
|
|
||||||
|
/** Danger/Error color - destructive actions, errors */
|
||||||
|
danger: ColorToken;
|
||||||
|
/** Danger color on hover */
|
||||||
|
dangerHover: ColorToken;
|
||||||
|
/** Subtle danger for backgrounds */
|
||||||
|
dangerSubtle: ColorToken;
|
||||||
|
|
||||||
|
/** Info color - informational states */
|
||||||
|
info: ColorToken;
|
||||||
|
/** Info color on hover */
|
||||||
|
infoHover: ColorToken;
|
||||||
|
/** Subtle info for backgrounds */
|
||||||
|
infoSubtle: ColorToken;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Background Colors (Layered System)
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
/** 图标默认颜色 */
|
/** Base/canvas background - the deepest layer (app background) */
|
||||||
icon: string;
|
bgBase: ColorToken;
|
||||||
/** 图标激活颜色 */
|
/** Elevated surface - panels, cards, dialogs floating above base */
|
||||||
iconActive: string;
|
bgElevated: ColorToken;
|
||||||
|
/** Overlay background - modals, dropdowns, popovers */
|
||||||
|
bgOverlay: ColorToken;
|
||||||
|
/** Inset/recessed background - inputs, wells, sunken areas */
|
||||||
|
bgInset: ColorToken;
|
||||||
|
|
||||||
/** 交互组件背景 (如按钮默认背景) */
|
/**
|
||||||
componentBackground: string;
|
* Glassmorphism surface - semi-transparent with blur
|
||||||
/** 交互组件悬停背景 */
|
* Used for floating toolbars, modern panels
|
||||||
componentHover: string;
|
*/
|
||||||
/** 交互组件激活背景 */
|
bgGlass: ColorToken;
|
||||||
componentActive: string;
|
/** Glassmorphism blur amount */
|
||||||
|
bgGlassBlur: BlurToken;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Text Colors
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/** Primary text - headings, body text, high emphasis */
|
||||||
|
textPrimary: ColorToken;
|
||||||
|
/** Secondary text - descriptions, labels, medium emphasis */
|
||||||
|
textSecondary: ColorToken;
|
||||||
|
/** Tertiary text - placeholders, hints, low emphasis */
|
||||||
|
textTertiary: ColorToken;
|
||||||
|
/** Disabled text - inactive elements */
|
||||||
|
textDisabled: ColorToken;
|
||||||
|
/** Inverted text - text on primary/dark backgrounds */
|
||||||
|
textInverse: ColorToken;
|
||||||
|
/** Link text color */
|
||||||
|
textLink: ColorToken;
|
||||||
|
/** Link text on hover */
|
||||||
|
textLinkHover: ColorToken;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Icon Colors
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/** Default icon color */
|
||||||
|
iconDefault: ColorToken;
|
||||||
|
/** Icon on hover */
|
||||||
|
iconHover: ColorToken;
|
||||||
|
/** Active/selected icon */
|
||||||
|
iconActive: ColorToken;
|
||||||
|
/** Disabled icon */
|
||||||
|
iconDisabled: ColorToken;
|
||||||
|
/** Inverted icon (on colored backgrounds) */
|
||||||
|
iconInverse: ColorToken;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Border / Divider Colors
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/** Default border - inputs, cards, containers */
|
||||||
|
borderDefault: ColorToken;
|
||||||
|
/** Subtle border - dividers, separators */
|
||||||
|
borderSubtle: ColorToken;
|
||||||
|
/** Strong border - focus rings, emphasis */
|
||||||
|
borderStrong: ColorToken;
|
||||||
|
/** Disabled border */
|
||||||
|
borderDisabled: ColorToken;
|
||||||
|
|
||||||
|
/** Divider color - horizontal/vertical separators */
|
||||||
|
divider: ColorToken;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Interactive Component States
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component Background States
|
||||||
|
* Used for buttons, list items, interactive elements
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Default component background (often transparent) */
|
||||||
|
componentBg: ColorToken;
|
||||||
|
/** Component background on hover */
|
||||||
|
componentBgHover: ColorToken;
|
||||||
|
/** Component background when pressed */
|
||||||
|
componentBgActive: ColorToken;
|
||||||
|
/** Component background when selected */
|
||||||
|
componentBgSelected: ColorToken;
|
||||||
|
/** Disabled component background */
|
||||||
|
componentBgDisabled: ColorToken;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Focus / Selection States
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/** Focus ring color */
|
||||||
|
focusRing: ColorToken;
|
||||||
|
/** Focus ring offset color (for contrast) */
|
||||||
|
focusRingOffset: ColorToken;
|
||||||
|
|
||||||
|
/** Selection background (text selection, highlighted items) */
|
||||||
|
selectionBg: ColorToken;
|
||||||
|
/** Selection text color */
|
||||||
|
selectionText: ColorToken;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Shadows
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/** Small shadow - subtle elevation (buttons, small cards) */
|
||||||
|
shadowSm: ShadowToken;
|
||||||
|
/** Medium shadow - moderate elevation (dropdowns, popovers) */
|
||||||
|
shadowMd: ShadowToken;
|
||||||
|
/** Large shadow - high elevation (modals, dialogs) */
|
||||||
|
shadowLg: ShadowToken;
|
||||||
|
/** Extra large shadow - maximum elevation */
|
||||||
|
shadowXl: ShadowToken;
|
||||||
|
|
||||||
|
/** Glow shadow for active/highlighted elements */
|
||||||
|
shadowGlow: ShadowToken;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Scrollbar Colors
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/** Scrollbar track background */
|
||||||
|
scrollbarTrack: ColorToken;
|
||||||
|
/** Scrollbar thumb */
|
||||||
|
scrollbarThumb: ColorToken;
|
||||||
|
/** Scrollbar thumb on hover */
|
||||||
|
scrollbarThumbHover: ColorToken;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Specific Component Overrides (Optional)
|
||||||
|
// These allow fine-grained control when semantic tokens aren't enough
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component-specific overrides
|
||||||
|
* Only define these when absolutely necessary
|
||||||
|
*/
|
||||||
|
overrides?: {
|
||||||
|
/** Dialog specific colors */
|
||||||
|
dialog?: {
|
||||||
|
headerBg?: ColorToken;
|
||||||
|
footerBg?: ColorToken;
|
||||||
|
};
|
||||||
|
/** Toolbar specific colors */
|
||||||
|
toolbar?: {
|
||||||
|
bg?: ColorToken;
|
||||||
|
buttonBg?: ColorToken;
|
||||||
|
buttonBgHover?: ColorToken;
|
||||||
|
buttonBgActive?: ColorToken;
|
||||||
|
};
|
||||||
|
/** Input specific colors */
|
||||||
|
input?: {
|
||||||
|
bg?: ColorToken;
|
||||||
|
bgFocus?: ColorToken;
|
||||||
|
placeholder?: ColorToken;
|
||||||
|
};
|
||||||
|
/** Glass-pill button group specific colors */
|
||||||
|
glassPill?: {
|
||||||
|
sectionBg?: ColorToken;
|
||||||
|
sectionBorder?: ColorToken;
|
||||||
|
sectionShadow?: ShadowToken;
|
||||||
|
btnBg?: ColorToken;
|
||||||
|
btnBorder?: ColorToken;
|
||||||
|
btnShadow?: ShadowToken;
|
||||||
|
btnBgHover?: ColorToken;
|
||||||
|
btnShadowHover?: ShadowToken;
|
||||||
|
iconColor?: ColorToken;
|
||||||
|
iconColorHover?: ColorToken;
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主题类型定义
|
* Partial theme config for customization
|
||||||
|
* Allows users to override only specific tokens
|
||||||
|
*/
|
||||||
|
export type PartialThemeConfig = Partial<ThemeConfig> & { name: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme type definition
|
||||||
*/
|
*/
|
||||||
export type ThemeType = 'dark' | 'light' | 'custom';
|
export type ThemeType = 'dark' | 'light' | 'custom';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// CSS Variable Mapping
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSS Variable Name Mapping
|
||||||
|
*
|
||||||
|
* All CSS variables follow the pattern: --bim-{category}-{property}
|
||||||
|
*
|
||||||
|
* Categories:
|
||||||
|
* - primary, success, warning, danger, info (semantic colors)
|
||||||
|
* - bg (backgrounds)
|
||||||
|
* - text (typography)
|
||||||
|
* - icon (iconography)
|
||||||
|
* - border (borders)
|
||||||
|
* - component (interactive states)
|
||||||
|
* - shadow (elevation)
|
||||||
|
* - focus (focus states)
|
||||||
|
* - scrollbar (scrollbar styling)
|
||||||
|
*/
|
||||||
|
export const CSS_VAR_MAP: Record<keyof Omit<ThemeConfig, 'name' | 'overrides'>, string> = {
|
||||||
|
// Primary
|
||||||
|
primary: '--bim-primary',
|
||||||
|
primaryHover: '--bim-primary-hover',
|
||||||
|
primaryActive: '--bim-primary-active',
|
||||||
|
primarySubtle: '--bim-primary-subtle',
|
||||||
|
|
||||||
|
// Success
|
||||||
|
success: '--bim-success',
|
||||||
|
successHover: '--bim-success-hover',
|
||||||
|
successSubtle: '--bim-success-subtle',
|
||||||
|
|
||||||
|
// Warning
|
||||||
|
warning: '--bim-warning',
|
||||||
|
warningHover: '--bim-warning-hover',
|
||||||
|
warningSubtle: '--bim-warning-subtle',
|
||||||
|
|
||||||
|
// Danger
|
||||||
|
danger: '--bim-danger',
|
||||||
|
dangerHover: '--bim-danger-hover',
|
||||||
|
dangerSubtle: '--bim-danger-subtle',
|
||||||
|
|
||||||
|
// Info
|
||||||
|
info: '--bim-info',
|
||||||
|
infoHover: '--bim-info-hover',
|
||||||
|
infoSubtle: '--bim-info-subtle',
|
||||||
|
|
||||||
|
// Backgrounds
|
||||||
|
bgBase: '--bim-bg-base',
|
||||||
|
bgElevated: '--bim-bg-elevated',
|
||||||
|
bgOverlay: '--bim-bg-overlay',
|
||||||
|
bgInset: '--bim-bg-inset',
|
||||||
|
bgGlass: '--bim-bg-glass',
|
||||||
|
bgGlassBlur: '--bim-bg-glass-blur',
|
||||||
|
|
||||||
|
// Text
|
||||||
|
textPrimary: '--bim-text-primary',
|
||||||
|
textSecondary: '--bim-text-secondary',
|
||||||
|
textTertiary: '--bim-text-tertiary',
|
||||||
|
textDisabled: '--bim-text-disabled',
|
||||||
|
textInverse: '--bim-text-inverse',
|
||||||
|
textLink: '--bim-text-link',
|
||||||
|
textLinkHover: '--bim-text-link-hover',
|
||||||
|
|
||||||
|
// Icons
|
||||||
|
iconDefault: '--bim-icon-default',
|
||||||
|
iconHover: '--bim-icon-hover',
|
||||||
|
iconActive: '--bim-icon-active',
|
||||||
|
iconDisabled: '--bim-icon-disabled',
|
||||||
|
iconInverse: '--bim-icon-inverse',
|
||||||
|
|
||||||
|
// Borders
|
||||||
|
borderDefault: '--bim-border-default',
|
||||||
|
borderSubtle: '--bim-border-subtle',
|
||||||
|
borderStrong: '--bim-border-strong',
|
||||||
|
borderDisabled: '--bim-border-disabled',
|
||||||
|
divider: '--bim-divider',
|
||||||
|
|
||||||
|
// Components
|
||||||
|
componentBg: '--bim-component-bg',
|
||||||
|
componentBgHover: '--bim-component-bg-hover',
|
||||||
|
componentBgActive: '--bim-component-bg-active',
|
||||||
|
componentBgSelected: '--bim-component-bg-selected',
|
||||||
|
componentBgDisabled: '--bim-component-bg-disabled',
|
||||||
|
|
||||||
|
// Focus
|
||||||
|
focusRing: '--bim-focus-ring',
|
||||||
|
focusRingOffset: '--bim-focus-ring-offset',
|
||||||
|
selectionBg: '--bim-selection-bg',
|
||||||
|
selectionText: '--bim-selection-text',
|
||||||
|
|
||||||
|
// Shadows
|
||||||
|
shadowSm: '--bim-shadow-sm',
|
||||||
|
shadowMd: '--bim-shadow-md',
|
||||||
|
shadowLg: '--bim-shadow-lg',
|
||||||
|
shadowXl: '--bim-shadow-xl',
|
||||||
|
shadowGlow: '--bim-shadow-glow',
|
||||||
|
|
||||||
|
// Scrollbar
|
||||||
|
scrollbarTrack: '--bim-scrollbar-track',
|
||||||
|
scrollbarThumb: '--bim-scrollbar-thumb',
|
||||||
|
scrollbarThumbHover: '--bim-scrollbar-thumb-hover',
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Legacy Compatibility Mapping
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps old ThemeConfig properties to new ones
|
||||||
|
* Used for backward compatibility during migration
|
||||||
|
*/
|
||||||
|
export interface LegacyThemeMapping {
|
||||||
|
/** Old property name -> New property name */
|
||||||
|
background: 'bgBase';
|
||||||
|
panelBackground: 'bgElevated';
|
||||||
|
border: 'borderDefault';
|
||||||
|
icon: 'iconDefault';
|
||||||
|
componentBackground: 'componentBg';
|
||||||
|
componentHover: 'componentBgHover';
|
||||||
|
componentActive: 'componentBgActive';
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user