9661 lines
378 KiB
Markdown
9661 lines
378 KiB
Markdown
# toolbar样式修改
|
||
|
||
**Session ID:** ses_421b77c83ffeCb2Rj1pHJ4d7sD
|
||
**Created:** 1/21/2026, 10:00:52 AM
|
||
**Updated:** 1/21/2026, 2:12:58 PM
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 20.0s)
|
||
|
||
## ✅ 文档整理完成
|
||
|
||
已创建两个新文档:
|
||
|
||
### 📄 `docs/AI代码规范模板.md`
|
||
**通用的 AI 协作与代码规范**,可复用到其他项目,包含:
|
||
|
||
| 章节 | 内容 |
|
||
|-----|------|
|
||
| 1. AI 编码规范 | 语言要求、代码可读性、代码风格、命名规范、类型安全、注释规范、错误处理、性能考虑 |
|
||
| 2. 问答与开发流程 | "如何实现"处理流程、开发计划格式模板、直接修改的处理 |
|
||
| 3. 文件管理规范 | 文件删除规范(`.recycle/` 机制)、回收文件夹结构 |
|
||
| 4. 文档更新规则 | 必须更新的情况、组件文档要求 |
|
||
| 5. 思考与工作流程 | 中文思考要求、工作流程步骤 |
|
||
| 6. 代码审查要点 | 开发流程检查、代码质量检查、性能检查、安全检查 |
|
||
| 附录 | 常见任务快速参考、模板使用说明 |
|
||
|
||
---
|
||
|
||
### 📄 `docs/国际化实现指南.md`
|
||
**项目独有的国际化规范**,包含:
|
||
|
||
| 章节 | 内容 |
|
||
|-----|------|
|
||
| 1. 国际化的重要性 | 强制要求说明 |
|
||
| 2. 项目国际化架构 | 相关文件、核心 API |
|
||
| 3. 实现步骤 | 类型定义、中英文翻译、代码使用 |
|
||
| 4. 组件中的国际化实现 | 完整示例代码 |
|
||
| 5. 注意事项 | 必须做的、禁止做的 |
|
||
| 6. 最佳实践 | 翻译键组织结构、嵌套键使用、动态内容处理 |
|
||
| 7. 添加新语言 | 完整步骤 |
|
||
| 8. 检查清单 | 翻译键、组件实现、代码质量、测试验证 |
|
||
|
||
---
|
||
|
||
### 当前 docs/ 目录结构
|
||
|
||
```
|
||
docs/
|
||
├── AI代码规范模板.md ← 新创建(通用,可复用)
|
||
├── SDK使用指南.md
|
||
├── 国际化实现指南.md ← 新创建(项目独有)
|
||
├── components/
|
||
│ ├── 3D引擎组件-Engine.md
|
||
│ ├── 按钮组组件-ButtonGroup.md
|
||
│ ├── 弹窗组件-Dialog.md
|
||
│ ├── ... (共11个组件文档)
|
||
└── utils/
|
||
└── 图标管理器-IconManager.md
|
||
```
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
关于这个文档规范,我觉得你可以帮我再补充完善一下,并且增加java、ts、react、vue登常用语言的一些规范。
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 15.4s)
|
||
|
||
好的,让我来补充完善这个规范文档,增加各语言/框架的特定规范。
|
||
|
||
```
|
||
Tool: read
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/docs/AI代码规范模板.md"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
<file>
|
||
00001| # AI 代码规范模板
|
||
00002|
|
||
00003| > 本文档是通用的 AI 协作与代码规范模板,可适用于各类项目。
|
||
00004| > 使用时请根据项目实际情况进行调整。
|
||
00005|
|
||
00006| ---
|
||
00007|
|
||
00008| ## 📋 目录
|
||
00009|
|
||
00010| 1. [AI 编码规范](#1-ai-编码规范)
|
||
00011| 2. [问答与开发流程](#2-问答与开发流程)
|
||
00012| 3. [文件管理规范](#3-文件管理规范)
|
||
00013| 4. [文档更新规则](#4-文档更新规则)
|
||
00014| 5. [思考与工作流程](#5-思考与工作流程)
|
||
00015| 6. [代码审查要点](#6-代码审查要点)
|
||
00016|
|
||
00017| ---
|
||
00018|
|
||
00019| ## 1. AI 编码规范
|
||
00020|
|
||
00021| ### 1.1 语言要求(强制)
|
||
00022|
|
||
00023| - **所有输出必须使用中文**,包括:
|
||
00024| - 代码注释(**强制:所有代码注释必须使用中文,解释清晰详细**)
|
||
00025| - 文档说明
|
||
00026| - 与用户交流
|
||
00027| - 错误信息
|
||
00028| - 日志输出
|
||
00029|
|
||
00030| - **思考过程也必须使用中文**:
|
||
00031| - 分析问题时用中文思考
|
||
00032| - 解释代码逻辑时用中文
|
||
00033| - 讨论设计方案时用中文
|
||
00034|
|
||
00035| - **代码中的字符串**:根据业务需求决定,但注释和文档必须中文
|
||
00036|
|
||
00037| ### 1.2 代码可读性
|
||
00038|
|
||
00039| - **优先保证代码可读性**,有时可以不使用高级函数,使用低级函数
|
||
00040| - **必须添加详细的中文注释**,解释代码逻辑和设计意图
|
||
00041| - 函数、类、接口必须有 JSDoc 注释(使用中文)
|
||
00042| - 复杂逻辑必须添加行内注释说明
|
||
00043|
|
||
00044| ### 1.3 代码风格
|
||
00045|
|
||
00046| - 使用 TypeScript 严格模式(如适用)
|
||
00047| - 遵循项目现有的命名规范
|
||
00048| - 保持代码格式一致(使用项目配置的格式化工具)
|
||
00049| - 保持缩进和空行的一致性
|
||
00050|
|
||
00051| #### 常用命名规范
|
||
00052|
|
||
00053| | 类型 | 命名方式 | 示例 |
|
||
00054| |-----|---------|------|
|
||
00055| | 类名 | 大驼峰 | `UserManager`, `DialogComponent` |
|
||
00056| | 接口名 | 大驼峰,以 `I` 开头 | `IUserService`, `IComponent` |
|
||
00057| | 类型名 | 大驼峰 | `UserConfig`, `DialogOptions` |
|
||
00058| | 函数名 | 小驼峰 | `getUserInfo`, `handleClick` |
|
||
00059| | 变量名 | 小驼峰 | `userName`, `isActive` |
|
||
00060| | 常量名 | 全大写下划线 | `MAX_COUNT`, `API_URL` |
|
||
00061| | 文件名 | 小写短横线或小写 | `user-manager.ts`, `index.ts` |
|
||
00062| | CSS 类名 | 小写短横线 | `user-card`, `btn-primary` |
|
||
00063|
|
||
00064| ### 1.4 类型安全
|
||
00065|
|
||
00066| - 充分利用 TypeScript 类型系统
|
||
00067| - **避免使用 `any`**,必要时使用 `unknown`
|
||
00068| - 为所有公共 API 提供类型定义
|
||
00069| - 使用类型推断,但复杂类型必须显式声明
|
||
00070|
|
||
00071| ### 1.5 注释规范
|
||
00072|
|
||
00073| ```typescript
|
||
00074| /**
|
||
00075| * 函数功能描述(中文)
|
||
00076| * @param param1 参数说明(中文)
|
||
00077| * @param param2 参数说明(中文)
|
||
00078| * @returns 返回值说明(中文)
|
||
00079| * @throws 可能抛出的异常说明(中文)
|
||
00080| * @example
|
||
00081| * // 使用示例
|
||
00082| * const result = example('test', 123);
|
||
00083| */
|
||
00084| function example(param1: string, param2: number): boolean {
|
||
00085| // 行内注释说明关键逻辑(中文)
|
||
00086| return true;
|
||
00087| }
|
||
00088| ```
|
||
00089|
|
||
00090| ### 1.6 错误处理规范
|
||
00091|
|
||
00092| - 所有异步操作必须有错误处理
|
||
00093| - 使用 `try-catch` 捕获可能的异常
|
||
00094| - 错误信息必须使用中文
|
||
00095| - 提供有意义的错误信息,帮助调试
|
||
00096|
|
||
00097| ```typescript
|
||
00098| try {
|
||
00099| await someAsyncOperation();
|
||
00100| } catch (error) {
|
||
00101| // 提供有意义的中文错误信息
|
||
00102| console.error('操作失败:', error);
|
||
00103| throw new Error('数据加载失败,请检查网络连接');
|
||
00104| }
|
||
00105| ```
|
||
00106|
|
||
00107| ### 1.7 性能考虑
|
||
00108|
|
||
00109| - 使用 `requestAnimationFrame` 优化动画性能
|
||
00110| - 避免频繁的 DOM 操作,使用批量更新
|
||
00111| - 合理使用事件委托,减少事件监听器数量
|
||
00112| - 及时清理不需要的监听器和定时器
|
||
00113|
|
||
00114| ---
|
||
00115|
|
||
00116| ## 2. 问答与开发流程
|
||
00117|
|
||
00118| ### 2.1 用户询问"如何实现"时的处理流程
|
||
00119|
|
||
00120| **当用户询问如何实现某个功能时,必须遵循以下流程:**
|
||
00121|
|
||
00122| 1. **分析需求**:用中文理解用户需求,明确要实现的功能
|
||
00123|
|
||
00124| 2. **制定详细开发计划**:必须包含以下内容:
|
||
00125| - **需要修改的文件列表**
|
||
00126| - **需要新增的文件列表**
|
||
00127| - **需要删除的文件列表**
|
||
00128| - **每个文件的作用说明**
|
||
00129| - **对整体结构的影响**
|
||
00130|
|
||
00131| 3. **展示开发计划**:以清晰的格式展示给用户
|
||
00132|
|
||
00133| 4. **等待用户确认**:**在用户明确同意之前,绝对不能修改任何代码**
|
||
00134|
|
||
00135| 5. **用户同意后执行**:只有在用户明确表示同意后,才能开始修改代码
|
||
00136|
|
||
00137| ### 2.2 开发计划格式模板
|
||
00138|
|
||
00139| ```markdown
|
||
00140| ## 开发计划
|
||
00141|
|
||
00142| ### 需要修改的文件
|
||
00143| 1. `src/managers/xxx-manager.ts`
|
||
00144| - 修改:添加新的 `xxxMethod()` 方法
|
||
00145| - 作用:提供某某功能的快捷方法
|
||
00146|
|
||
00147| 2. `src/types/xxx.ts`
|
||
00148| - 修改:添加新的类型定义
|
||
00149| - 作用:定义某某数据结构
|
||
00150|
|
||
00151| ### 需要新增的文件
|
||
00152| 1. `src/components/xxx/index.ts`
|
||
00153| - 作用:实现某某组件
|
||
00154| - 内容:包含 XxxComponent 类
|
||
00155|
|
||
00156| 2. `src/components/xxx/index.css`
|
||
00157| - 作用:某某组件的样式文件
|
||
00158|
|
||
00159| ### 需要删除的文件
|
||
00160| 1. `src/components/old-xxx.ts`
|
||
00161| - 原因:已被新组件替代
|
||
00162| - **处理方式**:移动到 `.recycle/YYYY-MM-DD/` 文件夹
|
||
00163|
|
||
00164| ### 对整体结构的影响
|
||
00165| - **架构影响**:说明对现有架构的影响
|
||
00166| - **功能影响**:说明对现有功能的影响
|
||
00167| - **文档更新**:需要更新哪些文档
|
||
00168| - **风险点**:可能的风险和注意事项
|
||
00169|
|
||
00170| 请确认是否按照此计划进行开发?
|
||
00171| ```
|
||
00172|
|
||
00173| ### 2.3 用户直接要求修改时的处理
|
||
00174|
|
||
00175| **当用户直接要求修改代码时**(如"帮我修改XXX"、"实现XXX功能"),可以:
|
||
00176| - 直接执行代码修改
|
||
00177| - 但仍需遵循其他规范
|
||
00178| - 删除文件时仍需移动到回收文件夹
|
||
00179|
|
||
00180| ---
|
||
00181|
|
||
00182| ## 3. 文件管理规范
|
||
00183|
|
||
00184| ### 3.1 文件删除规范(强制)
|
||
00185|
|
||
00186| **严禁直接删除文件,必须遵循以下流程:**
|
||
00187|
|
||
00188| #### 步骤 1:创建回收文件夹
|
||
00189| 在项目根目录创建 `.recycle/` 文件夹(如果不存在)
|
||
00190|
|
||
00191| #### 步骤 2:按日期组织
|
||
00192| - 在 `.recycle/` 下按日期创建子文件夹
|
||
00193| - 格式:`YYYY-MM-DD`(如 `2024-01-15`)
|
||
00194| - 同一天删除的文件存放在同一个日期文件夹中
|
||
00195|
|
||
00196| #### 步骤 3:移动文件而非删除
|
||
00197|
|
||
00198| ```bash
|
||
00199| # ❌ 错误方式
|
||
00200| rm src/old-file.ts
|
||
00201|
|
||
00202| # ✅ 正确方式
|
||
00203| DATE=$(date +%Y-%m-%d)
|
||
00204| mkdir -p .recycle/$DATE/src
|
||
00205| mv src/old-file.ts .recycle/$DATE/src/old-file.ts
|
||
00206| ```
|
||
00207|
|
||
00208| #### 步骤 4:保持目录结构
|
||
00209| 在日期文件夹中保持原文件的目录结构
|
||
00210|
|
||
00211| 例如:`src/components/old.ts` → `.recycle/2024-01-15/src/components/old.ts`
|
||
00212|
|
||
00213| #### 步骤 5:添加删除说明(可选)
|
||
00214| 在对应日期文件夹中创建 `README.md` 记录删除原因:
|
||
00215|
|
||
00216| ```markdown
|
||
00217| ## 2024-01-15 删除的文件
|
||
00218|
|
||
00219| - `src/components/old.ts`: 被新组件替代
|
||
00220| - `src/managers/old-manager.ts`: 功能重构,使用新 Manager
|
||
00221| ```
|
||
00222|
|
||
00223| ### 3.2 回收文件夹结构示例
|
||
00224|
|
||
00225| ```
|
||
00226| .recycle/
|
||
00227| ├── 2024-01-15/
|
||
00228| │ ├── README.md
|
||
00229| │ └── src/
|
||
00230| │ ├── components/
|
||
00231| │ │ └── old-component.ts
|
||
00232| │ └── managers/
|
||
00233| │ └── old-manager.ts
|
||
00234| ├── 2024-02-20/
|
||
00235| │ ├── README.md
|
||
00236| │ └── src/
|
||
00237| │ └── types/
|
||
00238| │ └── old-types.ts
|
||
00239| └── 2024-12-25/
|
||
00240| ├── README.md
|
||
00241| └── demo/
|
||
00242| └── old-demo.html
|
||
00243| ```
|
||
00244|
|
||
00245| ### 3.3 文件恢复
|
||
00246| 如果发现误删,可以从对应日期文件夹中恢复,恢复后从 `.recycle/` 中移除。
|
||
00247|
|
||
00248| ---
|
||
00249|
|
||
00250| ## 4. 文档更新规则
|
||
00251|
|
||
00252| ### 4.1 必须更新的情况
|
||
00253|
|
||
00254| | 操作 | 需要更新的文档 |
|
||
00255| |-----|--------------|
|
||
00256| | 新增文件 | 在相关文档中添加文件描述 |
|
||
00257| | 删除文件 | 从文档中移除对应条目 |
|
||
00258| | 修改文件功能 | 更新对应文件的描述 |
|
||
00259| | 新增组件 | 添加组件说明,创建组件详细文档 |
|
||
00260| | 修改组件 | 同步更新组件文档 |
|
||
00261| | 新增事件 | 在事件定义部分添加 |
|
||
00262| | 修改架构 | 更新架构设计部分 |
|
||
00263| | 修改构建流程 | 更新构建和运行部分 |
|
||
00264|
|
||
00265| ### 4.2 更新方式
|
||
00266|
|
||
00267| 1. 修改代码后,**立即**更新本文档
|
||
00268| 2. 确保文档与代码保持一致
|
||
00269| 3. 如果发现文档过时,优先更新文档
|
||
00270|
|
||
00271| ### 4.3 组件文档要求
|
||
00272|
|
||
00273| 每个组件的详细文档应包含:
|
||
00274|
|
||
00275| 1. 组件概述(基本信息、在项目中的位置)
|
||
00276| 2. 组件类 API 文档(所有公共方法、参数、返回值)
|
||
00277| 3. 分化组件说明(如有继承关系)
|
||
00278| 4. Manager API 文档(Manager 的所有公共方法)
|
||
00279| 5. UI 详细描述(DOM 结构、CSS 类名、交互行为)
|
||
00280| 6. 逻辑流程详细描述(初始化、生命周期、事件处理)
|
||
00281| 7. 国际化支持(使用的翻译键、语言变更处理)
|
||
00282| 8. 主题支持(使用的主题变量、主题变更处理)
|
||
00283| 9. 使用示例(基本使用、高级使用)
|
||
00284| 10. 实现细节(关键算法、性能优化、注意事项)
|
||
00285| 11. 类型定义
|
||
00286| 12. 文件清单
|
||
00287|
|
||
00288| **重要**:组件文档必须非常详细,详细到其他 AI 可以直接根据文档重现组件。
|
||
00289|
|
||
00290| ---
|
||
00291|
|
||
00292| ## 5. 思考与工作流程
|
||
00293|
|
||
00294| ### 5.1 思考过程要求
|
||
00295|
|
||
00296| - **所有思考过程必须使用中文**
|
||
00297| - 分析问题时,用中文描述问题、分析思路、得出结论
|
||
00298| - 解释代码时,用中文说明逻辑、设计意图、实现方式
|
||
00299| - 讨论方案时,用中文描述优缺点、选择理由
|
||
00300|
|
||
00301| ### 5.2 工作流程
|
||
00302|
|
||
00303| 1. **理解需求**:用中文理解用户需求,明确任务目标
|
||
00304| 2. **分析问题**:用中文分析问题,梳理相关代码和架构
|
||
00305| 3. **设计方案**:用中文设计解决方案,考虑边界情况
|
||
00306| 4. **实现代码**:编写代码,添加中文注释
|
||
00307| 5. **测试验证**:用中文描述测试结果和发现的问题
|
||
00308| 6. **更新文档**:用中文更新相关文档
|
||
00309|
|
||
00310| ---
|
||
00311|
|
||
00312| ## 6. 代码审查要点
|
||
00313|
|
||
00314| ### 6.1 开发流程检查
|
||
00315|
|
||
00316| - [ ] 如果是询问"如何实现",是否先提供了详细的开发计划
|
||
00317| - [ ] 是否在用户同意后才修改代码
|
||
00318| - [ ] 删除的文件是否移动到 `.recycle/` 文件夹而非直接删除
|
||
00319|
|
||
00320| ### 6.2 代码质量检查
|
||
00321|
|
||
00322| - [ ] 是否遵循组件使用规范
|
||
00323| - [ ] 注释是否完整且为中文
|
||
00324| - [ ] 错误处理是否完善
|
||
00325| - [ ] 资源是否正确释放
|
||
00326| - [ ] 类型定义是否完整
|
||
00327|
|
||
00328| ### 6.3 国际化检查(如适用)
|
||
00329|
|
||
00330| - [ ] 所有用户可见文本是否使用翻译函数
|
||
00331| - [ ] 是否在所有语言文件中添加了翻译
|
||
00332| - [ ] 组件是否实现了语言变更响应方法
|
||
00333| - [ ] 组件是否订阅了语言变更事件
|
||
00334|
|
||
00335| ### 6.4 性能检查
|
||
00336|
|
||
00337| - [ ] 是否避免了频繁的 DOM 操作
|
||
00338| - [ ] 事件监听器是否合理使用
|
||
00339| - [ ] 是否及时清理了不需要的资源
|
||
00340|
|
||
00341| ### 6.5 安全检查
|
||
00342|
|
||
00343| - [ ] 是否避免了 XSS 风险(如使用 innerHTML)
|
||
00344| - [ ] 敏感数据是否正确处理
|
||
00345| - [ ] 错误信息是否避免泄露敏感信息
|
||
00346|
|
||
00347| ---
|
||
00348|
|
||
00349| ## 附录 A:常见任务快速参考
|
||
00350|
|
||
00351| ### A.1 添加新文件
|
||
00352|
|
||
00353| 1. 创建文件,添加必要的类型定义
|
||
00354| 2. 添加中文注释
|
||
00355| 3. 在相关入口文件中导出(如需要)
|
||
00356| 4. 更新文档
|
||
00357|
|
||
00358| ### A.2 删除文件
|
||
00359|
|
||
00360| 1. 确认文件不再使用
|
||
00361| 2. 创建回收目录:`.recycle/YYYY-MM-DD/`
|
||
00362| 3. 移动文件到回收目录(保持目录结构)
|
||
00363| 4. 更新相关导入和引用
|
||
00364| 5. 更新文档
|
||
00365|
|
||
00366| ### A.3 重构代码
|
||
00367|
|
||
00368| 1. 先与用户确认重构范围
|
||
00369| 2. 制定详细的重构计划
|
||
00370| 3. 分步骤执行,每步验证
|
||
00371| 4. 确保测试通过
|
||
00372| 5. 更新文档
|
||
00373|
|
||
00374| ---
|
||
00375|
|
||
00376| ## 附录 B:模板使用说明
|
||
00377|
|
||
00378| 本模板可直接复制到新项目中使用。使用时请注意:
|
||
00379|
|
||
00380| 1. **根据项目调整**:部分规范可能需要根据项目技术栈调整
|
||
00381| 2. **补充项目特定内容**:如项目特定的架构规范、命名规范等
|
||
00382| 3. **定期更新**:随着项目发展,及时更新规范内容
|
||
00383|
|
||
00384| ---
|
||
00385|
|
||
00386| **文档版本**:v1.0
|
||
00387| **最后更新**:2026-01-21
|
||
00388|
|
||
|
||
(End of file - total 388 lines)
|
||
</file>
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 241.5s)
|
||
|
||
```
|
||
Tool: invalid
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"tool": "write",
|
||
"error": "Model tried to call unavailable tool 'write'. Available tools: invalid, bash, read, glob, grep, task, webfetch, todowrite, todoread, skill, lsp_hover, lsp_goto_definition, lsp_find_references, lsp_document_symbols, lsp_workspace_symbols, lsp_diagnostics, lsp_servers, lsp_prepare_rename, lsp_rename, lsp_code_actions, lsp_code_action_resolve, ast_grep_search, ast_grep_replace, session_list, session_read, session_search, session_info, background_task, background_output, background_cancel, call_omo_agent, look_at, skill_mcp, slashcommand, interactive_bash, websearch_web_search_exa, grep_app_searchGitHub, context7_resolve-library-id, context7_query-docs."
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
The arguments provided to the tool are invalid: Model tried to call unavailable tool 'write'. Available tools: invalid, bash, read, glob, grep, task, webfetch, todowrite, todoread, skill, lsp_hover, lsp_goto_definition, lsp_find_references, lsp_document_symbols, lsp_workspace_symbols, lsp_diagnostics, lsp_servers, lsp_prepare_rename, lsp_rename, lsp_code_actions, lsp_code_action_resolve, ast_grep_search, ast_grep_replace, session_list, session_read, session_search, session_info, background_task, background_output, background_cancel, call_omo_agent, look_at, skill_mcp, slashcommand, interactive_bash, websearch_web_search_exa, grep_app_searchGitHub, context7_resolve-library-id, context7_query-docs.
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 45.6s)
|
||
|
||
我注意到我当前处于**计划模式(Plan Mode)**,无法直接编辑文件。让我先给你展示完善后的文档结构和内容大纲,确认后再执行。
|
||
|
||
---
|
||
|
||
## 📋 文档补充计划
|
||
|
||
### 新增章节结构
|
||
|
||
```
|
||
AI代码规范模板.md(扩展版)
|
||
|
||
第一部分:通用规范
|
||
├── 1. AI 协作基本原则
|
||
│ ├── 1.1 语言要求(强制)
|
||
│ ├── 1.2 思考与沟通方式
|
||
│ └── 1.3 问答与开发流程
|
||
│
|
||
├── 2. 通用编码规范
|
||
│ ├── 2.1 代码可读性
|
||
│ ├── 2.2 命名规范
|
||
│ ├── 2.3 注释规范
|
||
│ ├── 2.4 错误处理
|
||
│ ├── 2.5 日志规范
|
||
│ └── 2.6 安全规范
|
||
│
|
||
├── 3. 文件与文档管理
|
||
│ ├── 3.1 文件删除规范(.recycle 机制)
|
||
│ ├── 3.2 文档更新规则
|
||
│ └── 3.3 Git 提交规范
|
||
│
|
||
第二部分:语言特定规范
|
||
├── 4. TypeScript 规范
|
||
│ ├── 4.1 类型定义规范
|
||
│ ├── 4.2 接口与类型
|
||
│ ├── 4.3 泛型使用
|
||
│ ├── 4.4 模块导入导出
|
||
│ └── 4.5 异步编程
|
||
│
|
||
├── 5. Java 规范
|
||
│ ├── 5.1 包结构与命名
|
||
│ ├── 5.2 类设计原则
|
||
│ ├── 5.3 异常处理
|
||
│ ├── 5.4 集合与泛型
|
||
│ ├── 5.5 注解使用
|
||
│ └── 5.6 Spring Boot 规范
|
||
│
|
||
第三部分:框架特定规范
|
||
├── 6. React 规范
|
||
│ ├── 6.1 组件设计原则
|
||
│ ├── 6.2 Hooks 使用规范
|
||
│ ├── 6.3 状态管理
|
||
│ ├── 6.4 性能优化
|
||
│ └── 6.5 TypeScript + React
|
||
│
|
||
├── 7. Vue 规范
|
||
│ ├── 7.1 组件设计(Vue 3 Composition API)
|
||
│ ├── 7.2 响应式数据
|
||
│ ├── 7.3 组合式函数
|
||
│ ├── 7.4 Pinia 状态管理
|
||
│ └── 7.5 TypeScript + Vue
|
||
│
|
||
第四部分:工程化规范
|
||
├── 8. API 设计规范
|
||
│ ├── 8.1 RESTful API
|
||
│ ├── 8.2 请求/响应格式
|
||
│ └── 8.3 错误码设计
|
||
│
|
||
├── 9. 测试规范
|
||
│ ├── 9.1 单元测试
|
||
│ ├── 9.2 集成测试
|
||
│ └── 9.3 测试命名
|
||
│
|
||
└── 附录
|
||
├── A. 代码审查清单
|
||
├── B. 常见任务快速参考
|
||
└── C. 模板使用说明
|
||
```
|
||
|
||
---
|
||
|
||
### 新增内容预览
|
||
|
||
#### 4. TypeScript 规范(部分示例)
|
||
|
||
```typescript
|
||
// ✅ 4.1 类型定义规范
|
||
// 优先使用 interface 定义对象类型
|
||
interface UserInfo {
|
||
id: string;
|
||
name: string;
|
||
age?: number; // 可选属性
|
||
}
|
||
|
||
// 使用 type 定义联合类型、交叉类型
|
||
type Status = 'pending' | 'success' | 'error';
|
||
type UserWithRole = UserInfo & { role: string };
|
||
|
||
// ✅ 4.2 避免 any,使用 unknown
|
||
function parseJSON(json: string): unknown {
|
||
return JSON.parse(json);
|
||
}
|
||
|
||
// ✅ 4.3 泛型约束
|
||
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
|
||
return obj[key];
|
||
}
|
||
```
|
||
|
||
#### 5. Java 规范(部分示例)
|
||
|
||
```java
|
||
// ✅ 5.1 包结构
|
||
// com.company.project.module.layer
|
||
// 例:com.example.order.controller
|
||
|
||
// ✅ 5.2 类命名
|
||
// Controller: XxxController
|
||
// Service: XxxService / XxxServiceImpl
|
||
// Repository: XxxRepository
|
||
// DTO: XxxDTO / XxxVO / XxxBO
|
||
|
||
// ✅ 5.3 方法命名
|
||
// 查询单个:getXxx / findXxx
|
||
// 查询列表:listXxx / findAllXxx
|
||
// 新增:addXxx / createXxx / saveXxx
|
||
// 修改:updateXxx / modifyXxx
|
||
// 删除:deleteXxx / removeXxx
|
||
// 判断:isXxx / hasXxx / canXxx
|
||
|
||
// ✅ 5.6 Spring Boot Controller 示例
|
||
@RestController
|
||
@RequestMapping("/api/v1/users")
|
||
@RequiredArgsConstructor
|
||
public class UserController {
|
||
|
||
private final UserService userService;
|
||
|
||
/**
|
||
* 根据ID获取用户信息
|
||
* @param id 用户ID
|
||
* @return 用户信息
|
||
*/
|
||
@GetMapping("/{id}")
|
||
public Result<UserVO> getUser(@PathVariable Long id) {
|
||
return Result.success(userService.getUserById(id));
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 6. React 规范(部分示例)
|
||
|
||
```tsx
|
||
// ✅ 6.1 函数组件 + TypeScript
|
||
interface UserCardProps {
|
||
user: UserInfo;
|
||
onEdit?: (id: string) => void;
|
||
}
|
||
|
||
const UserCard: React.FC<UserCardProps> = ({ user, onEdit }) => {
|
||
// ✅ 6.2 Hooks 使用顺序:useState -> useEffect -> 自定义 Hooks
|
||
const [loading, setLoading] = useState(false);
|
||
|
||
useEffect(() => {
|
||
// 副作用逻辑
|
||
return () => {
|
||
// 清理函数
|
||
};
|
||
}, []);
|
||
|
||
// ✅ 6.4 使用 useCallback 缓存回调
|
||
const handleEdit = useCallback(() => {
|
||
onEdit?.(user.id);
|
||
}, [user.id, onEdit]);
|
||
|
||
return (
|
||
<div className="user-card">
|
||
<span>{user.name}</span>
|
||
<button onClick={handleEdit}>编辑</button>
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
#### 7. Vue 规范(部分示例)
|
||
|
||
```vue
|
||
<!-- ✅ 7.1 Vue 3 Composition API + TypeScript -->
|
||
<script setup lang="ts">
|
||
import { ref, computed, onMounted, watch } from 'vue';
|
||
|
||
// ✅ Props 定义
|
||
interface Props {
|
||
userId: string;
|
||
title?: string;
|
||
}
|
||
const props = withDefaults(defineProps<Props>(), {
|
||
title: '默认标题'
|
||
});
|
||
|
||
// ✅ Emits 定义
|
||
const emit = defineEmits<{
|
||
(e: 'update', value: string): void;
|
||
(e: 'delete', id: string): void;
|
||
}>();
|
||
|
||
// ✅ 响应式数据
|
||
const count = ref(0);
|
||
const user = ref<UserInfo | null>(null);
|
||
|
||
// ✅ 计算属性
|
||
const displayName = computed(() => user.value?.name ?? '未知');
|
||
|
||
// ✅ 生命周期
|
||
onMounted(async () => {
|
||
user.value = await fetchUser(props.userId);
|
||
});
|
||
|
||
// ✅ 侦听器
|
||
watch(() => props.userId, (newId) => {
|
||
// 处理 userId 变化
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<div class="user-card">
|
||
<h2>{{ displayName }}</h2>
|
||
<slot name="actions" />
|
||
</div>
|
||
</template>
|
||
```
|
||
|
||
#### 8.1 Git 提交规范
|
||
|
||
```
|
||
提交信息格式:
|
||
<type>(<scope>): <subject>
|
||
|
||
<body>
|
||
|
||
<footer>
|
||
|
||
类型(type):
|
||
- feat: 新功能
|
||
- fix: 修复 Bug
|
||
- docs: 文档更新
|
||
- style: 代码格式(不影响功能)
|
||
- refactor: 重构(不是新功能也不是修复)
|
||
- perf: 性能优化
|
||
- test: 测试相关
|
||
- chore: 构建/工具/依赖更新
|
||
- revert: 回滚提交
|
||
|
||
示例:
|
||
feat(user): 添加用户登录功能
|
||
|
||
- 实现用户名密码登录
|
||
- 添加登录表单验证
|
||
- 集成 JWT 认证
|
||
|
||
Closes #123
|
||
```
|
||
|
||
---
|
||
|
||
### 确认
|
||
|
||
文档扩展后预计约 **800-1000 行**,涵盖:
|
||
|
||
| 部分 | 内容 |
|
||
|-----|------|
|
||
| 通用规范 | AI 协作原则、编码规范、文件管理、Git 规范 |
|
||
| TypeScript | 类型定义、接口、泛型、模块、异步 |
|
||
| Java | 包结构、类设计、异常、Spring Boot |
|
||
| React | 组件设计、Hooks、状态管理、性能优化 |
|
||
| Vue | Composition API、响应式、Pinia |
|
||
| 工程化 | API 设计、测试规范 |
|
||
|
||
**请确认是否按此方案补充文档?**
|
||
|
||
如果有需要调整的地方(比如增减某些内容、调整详细程度),请告诉我。
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
可以。开始吧
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 7.8s)
|
||
|
||
```
|
||
Tool: todowrite
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"todos": [
|
||
{
|
||
"id": "1",
|
||
"content": "重写 AI代码规范模板.md - 扩展为完整版",
|
||
"status": "in_progress",
|
||
"priority": "high"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
[
|
||
{
|
||
"id": "1",
|
||
"content": "重写 AI代码规范模板.md - 扩展为完整版",
|
||
"status": "in_progress",
|
||
"priority": "high"
|
||
}
|
||
]
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 617.9s)
|
||
|
||
```
|
||
Tool: write
|
||
|
||
**Input:**
|
||
```json
|
||
{}
|
||
```
|
||
**Error:**
|
||
```
|
||
Tool execution aborted
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
继续
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
继续
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 568.5s)
|
||
|
||
```
|
||
Tool: write
|
||
|
||
**Input:**
|
||
```json
|
||
{}
|
||
```
|
||
**Error:**
|
||
```
|
||
Tool execution aborted
|
||
```
|
||
```
|
||
|
||
```
|
||
Tool: write
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/docs/AI代码规范模板.md",
|
||
"content": "# AI 代码规范模板\n\n> 本文档是通用的 AI 协作与代码规范模板,适用于 TypeScript、Java、React、Vue 等技术栈。\n> 使用时请根据项目实际情况进行调整。\n\n---\n\n## 📋 目录\n\n### 第一部分:通用规范\n- [1. AI 协作基本原则](#1-ai-协作基本原则)\n- [2. 通用编码规范](#2-通用编码规范)\n- [3. 文件与文档管理](#3-文件与文档管理)\n\n### 第二部分:语言特定规范\n- [4. TypeScript 规范](#4-typescript-规范)\n- [5. Java 规范](#5-java-规范)\n\n### 第三部分:框架特定规范\n- [6. React 规范](#6-react-规范)\n- [7. Vue 规范](#7-vue-规范)\n\n### 第四部分:工程化规范\n- [8. Git 提交规范](#8-git-提交规范)\n- [9. API 设计规范](#9-api-设计规范)\n- [10. 测试规范](#10-测试规范)\n\n### 附录\n- [附录 A:代码审查清单](#附录-a代码审查清单)\n- [附录 B:常见任务快速参考](#附录-b常见任务快速参考)\n- [附录 C:模板使用说明](#附录-c模板使用说明)\n\n---\n\n# 第一部分:通用规范\n\n## 1. AI 协作基本原则\n\n### 1.1 语言要求(强制)\n\n#### 输出语言\n- **所有输出必须使用中文**,包括:\n - 代码注释(**强制:所有代码注释必须使用中文,解释清晰详细**)\n - 文档说明\n - 与用户交流\n - 错误信息和日志输出\n\n#### 思考语言\n- **思考过程也必须使用中文**:\n - 分析问题时用中文思考\n - 解释代码逻辑时用中文\n - 讨论设计方案时用中文\n\n#### 代码中的字符串\n- 根据业务需求决定(支持国际化的项目使用翻译键)\n- 注释和文档必须使用中文\n\n### 1.2 思考与沟通方式\n\n#### 思考过程要求\n- 分析问题时,用中文描述问题、分析思路、得出结论\n- 解释代码时,用中文说明逻辑、设计意图、实现方式\n- 讨论方案时,用中文描述优缺点、选择理由\n\n#### 工作流程\n1. **理解需求**:用中文理解用户需求,明确任务目标\n2. **分析问题**:用中文分析问题,梳理相关代码和架构\n3. **设计方案**:用中文设计解决方案,考虑边界情况\n4. **实现代码**:编写代码,添加中文注释\n5. **测试验证**:用中文描述测试结果和发现的问题\n6. **更新文档**:用中文更新相关文档\n\n### 1.3 问答与开发流程\n\n#### 用户询问\"如何实现\"时的处理流程\n\n**当用户询问如何实现某个功能时,必须遵循以下流程:**\n\n1. **分析需求**:用中文理解用户需求,明确要实现的功能\n\n2. **制定详细开发计划**:必须包含以下内容:\n - **需要修改的文件列表**\n - **需要新增的文件列表**\n - **需要删除的文件列表**\n - **每个文件的作用说明**\n - **对整体结构的影响**\n\n3. **展示开发计划**:以清晰的格式展示给用户\n\n4. **等待用户确认**:**在用户明确同意之前,绝对不能修改任何代码**\n\n5. **用户同意后执行**:只有在用户明确表示同意后,才能开始修改代码\n\n#### 开发计划格式模板\n\n```markdown\n## 开发计划\n\n### 需要修改的文件\n1. `src/xxx/xxx.ts`\n - 修改:添加新的 `xxxMethod()` 方法\n - 作用:提供某某功能\n\n### 需要新增的文件\n1. `src/xxx/index.ts`\n - 作用:实现某某功能\n - 内容:包含 XxxClass 类\n\n### 需要删除的文件\n1. `src/old/xxx.ts`\n - 原因:已被新组件替代\n - **处理方式**:移动到 `.recycle/YYYY-MM-DD/` 文件夹\n\n### 对整体结构的影响\n- **架构影响**:说明对现有架构的影响\n- **功能影响**:说明对现有功能的影响\n- **文档更新**:需要更新哪些文档\n- **风险点**:可能的风险和注意事项\n\n请确认是否按照此计划进行开发?\n```\n\n#### 用户直接要求修改时的处理\n\n**当用户直接要求修改代码时**(如\"帮我修改XXX\"、\"实现XXX功能\"),可以:\n- 直接执行代码修改\n- 但仍需遵循其他规范\n- 删除文件时仍需移动到回收文件夹\n\n---\n\n## 2. 通用编码规范\n\n### 2.1 代码可读性\n\n#### 基本原则\n- **优先保证代码可读性**,有时可以不使用高级函数,使用低级函数\n- **必须添加详细的中文注释**,解释代码逻辑和设计意图\n- 函数、类、接口必须有文档注释(使用中文)\n- 复杂逻辑必须添加行内注释说明\n\n#### 代码组织\n- 单个文件不宜超过 **500 行**\n- 单个函数不宜超过 **50 行**\n- 单行代码不宜超过 **120 字符**\n- 嵌套层级不宜超过 **4 层**\n\n### 2.2 命名规范\n\n#### 通用命名规则\n\n| 类型 | 命名方式 | 示例 |\n|-----|---------|------|\n| 类名 | 大驼峰 (PascalCase) | `UserManager`, `OrderService` |\n| 接口名 | 大驼峰,可选 `I` 前缀 | `IUserService`, `UserRepository` |\n| 类型/枚举 | 大驼峰 | `UserStatus`, `OrderType` |\n| 函数/方法 | 小驼峰 (camelCase) | `getUserInfo`, `handleSubmit` |\n| 变量 | 小驼峰 | `userName`, `isActive`, `itemCount` |\n| 常量 | 全大写下划线 | `MAX_COUNT`, `API_BASE_URL` |\n| 私有属性 | 下划线前缀或 `#` | `_cache`, `#privateField` |\n| 文件名 | 小写短横线或小写 | `user-manager.ts`, `index.ts` |\n| CSS 类名 | 小写短横线 (kebab-case) | `user-card`, `btn-primary` |\n\n#### 命名语义化\n\n```typescript\n// ✅ 好的命名 - 语义清晰\nconst userList: User[] = [];\nconst isLoading: boolean = false;\nconst maxRetryCount: number = 3;\nfunction calculateTotalPrice(items: Item[]): number { }\nfunction validateUserInput(input: string): boolean { }\n\n// ❌ 不好的命名 - 语义不清\nconst list: any[] = [];\nconst flag: boolean = false;\nconst num: number = 3;\nfunction calc(arr: any[]): number { }\nfunction check(str: string): boolean { }\n```\n\n#### 布尔值命名\n- 使用 `is`、`has`、`can`、`should`、`will` 等前缀\n- 例如:`isActive`、`hasPermission`、`canEdit`、`shouldUpdate`\n\n#### 函数命名\n- 动词开头,表明行为\n- 查询:`get`、`find`、`query`、`fetch`、`load`\n- 创建:`create`、`add`、`insert`、`save`\n- 更新:`update`、`modify`、`set`、`change`\n- 删除:`delete`、`remove`、`clear`、`reset`\n- 判断:`is`、`has`、`can`、`should`、`validate`、`check`\n- 转换:`to`、`convert`、`parse`、`format`\n- 事件:`handle`、`on`、`trigger`、`emit`\n\n### 2.3 注释规范\n\n#### 文档注释(JSDoc / JavaDoc)\n\n**TypeScript / JavaScript:**\n```typescript\n/**\n * 计算订单总价\n * \n * @description 根据商品列表计算总价,包含折扣计算\n * @param items - 商品列表\n * @param discount - 折扣比例(0-1),默认为 1(无折扣)\n * @returns 计算后的总价\n * @throws {Error} 当商品列表为空时抛出错误\n * @example\n * const total = calculateTotal(items, 0.8); // 8折\n */\nfunction calculateTotal(items: CartItem[], discount: number = 1): number {\n if (items.length === 0) {\n throw new Error('商品列表不能为空');\n }\n // 计算原价总和\n const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0);\n // 应用折扣\n return subtotal * discount;\n}\n```\n\n**Java:**\n```java\n/**\n * 计算订单总价\n * \n * <p>根据商品列表计算总价,包含折扣计算</p>\n *\n * @param items 商品列表\n * @param discount 折扣比例(0-1),默认为 1(无折扣)\n * @return 计算后的总价\n * @throws IllegalArgumentException 当商品列表为空时抛出\n * @since 1.0.0\n * @author AI Assistant\n */\npublic BigDecimal calculateTotal(List<CartItem> items, BigDecimal discount) {\n // 实现逻辑\n}\n```\n\n#### 行内注释\n```typescript\n// 单行注释:解释接下来一行或几行代码的作用\n\n/*\n * 多行注释:\n * 用于解释复杂的逻辑块\n * 或临时禁用代码\n */\n\n// TODO: 待完成的功能\n// FIXME: 需要修复的问题\n// HACK: 临时解决方案,需要优化\n// NOTE: 重要说明\n// DEPRECATED: 已废弃,请使用新方法\n```\n\n#### 注释原则\n- **解释为什么,而不是是什么**:代码本身说明做什么,注释说明为什么\n- **保持注释与代码同步**:代码修改后必须更新注释\n- **不要注释显而易见的代码**:避免无意义的注释\n- **复杂业务逻辑必须注释**:特别是涉及特定业务规则的代码\n\n### 2.4 错误处理\n\n#### 基本原则\n- 所有异步操作必须有错误处理\n- 错误信息必须清晰、有意义,使用中文\n- 区分可恢复错误和不可恢复错误\n- 不要吞掉异常(空 catch 块)\n\n#### TypeScript 错误处理\n```typescript\n// ✅ 正确的错误处理\nasync function fetchUserData(userId: string): Promise<User> {\n try {\n const response = await fetch(`/api/users/${userId}`);\n \n if (!response.ok) {\n // 根据状态码抛出具体错误\n if (response.status === 404) {\n throw new Error(`用户不存在:${userId}`);\n }\n throw new Error(`请求失败:${response.status}`);\n }\n \n return await response.json();\n } catch (error) {\n // 记录错误日志\n console.error('获取用户数据失败:', error);\n // 重新抛出或返回默认值\n throw error;\n }\n}\n\n// ❌ 错误的错误处理\nasync function badFetch(userId: string) {\n try {\n const res = await fetch(`/api/users/${userId}`);\n return await res.json();\n } catch (e) {\n // 空 catch 块,吞掉了错误\n }\n}\n```\n\n#### Java 错误处理\n```java\n// ✅ 正确的错误处理\npublic User getUserById(Long id) {\n try {\n return userRepository.findById(id)\n .orElseThrow(() -> new BusinessException(\"用户不存在:\" + id));\n } catch (DataAccessException e) {\n log.error(\"查询用户失败,ID:{}\", id, e);\n throw new ServiceException(\"数据库查询异常\", e);\n }\n}\n\n// 自定义业务异常\npublic class BusinessException extends RuntimeException {\n private final String code;\n \n public BusinessException(String message) {\n super(message);\n this.code = \"BUSINESS_ERROR\";\n }\n \n public BusinessException(String code, String message) {\n super(message);\n this.code = code;\n }\n}\n```\n\n### 2.5 日志规范\n\n#### 日志级别\n| 级别 | 使用场景 |\n|-----|---------|\n| `ERROR` | 错误,影响功能正常运行 |\n| `WARN` | 警告,潜在问题但不影响运行 |\n| `INFO` | 重要业务信息,如用户操作、接口调用 |\n| `DEBUG` | 调试信息,开发环境使用 |\n| `TRACE` | 详细追踪信息,极少使用 |\n\n#### 日志内容要求\n```typescript\n// ✅ 好的日志\nlogger.info(`用户登录成功,用户ID:${userId},IP:${clientIp}`);\nlogger.error(`订单创建失败,订单号:${orderId},原因:${error.message}`, error);\nlogger.warn(`库存不足,商品ID:${productId},当前库存:${stock},请求数量:${quantity}`);\n\n// ❌ 不好的日志\nlogger.info('success'); // 信息不完整\nlogger.error(error); // 缺少上下文\nconsole.log('here'); // 调试代码未清理\n```\n\n#### 日志原则\n- 包含足够的上下文信息(ID、参数等)\n- 敏感信息脱敏(密码、手机号等)\n- 生产环境不使用 `console.log`\n- 异常日志要包含堆栈信息\n\n### 2.6 安全规范\n\n#### 输入验证\n```typescript\n// ✅ 验证所有外部输入\nfunction processUserInput(input: unknown): string {\n // 类型检查\n if (typeof input !== 'string') {\n throw new Error('输入必须是字符串');\n }\n // 长度检查\n if (input.length > 1000) {\n throw new Error('输入长度超出限制');\n }\n // 内容清理\n return sanitize(input);\n}\n```\n\n#### XSS 防护\n```typescript\n// ❌ 危险:直接插入 HTML\nelement.innerHTML = userInput;\n\n// ✅ 安全:使用 textContent 或转义\nelement.textContent = userInput;\n// 或\nelement.innerHTML = escapeHtml(userInput);\n```\n\n#### 敏感数据处理\n```typescript\n// ❌ 不要在日志中输出敏感信息\nconsole.log(`用户密码:${password}`);\n\n// ✅ 脱敏处理\nconsole.log(`手机号:${phone.replace(/(\\d{3})\\d{4}(\\d{4})/, '$1****$2')}`);\n```\n\n---\n\n## 3. 文件与文档管理\n\n### 3.1 文件删除规范(强制)\n\n**严禁直接删除文件,必须遵循以下流程:**\n\n#### 步骤 1:创建回收文件夹\n在项目根目录创建 `.recycle/` 文件夹(如果不存在)\n\n#### 步骤 2:按日期组织\n- 在 `.recycle/` 下按日期创建子文件夹\n- 格式:`YYYY-MM-DD`(如 `2024-01-15`)\n- 同一天删除的文件存放在同一个日期文件夹中\n\n#### 步骤 3:移动文件而非删除\n\n```bash\n# ❌ 错误方式\nrm src/old-file.ts\n\n# ✅ 正确方式\nDATE=$(date +%Y-%m-%d)\nmkdir -p .recycle/$DATE/src\nmv src/old-file.ts .recycle/$DATE/src/old-file.ts\n```\n\n#### 步骤 4:保持目录结构\n在日期文件夹中保持原文件的目录结构\n\n例如:`src/components/old.ts` → `.recycle/2024-01-15/src/components/old.ts`\n\n#### 回收文件夹结构示例\n```\n.recycle/\n├── 2024-01-15/\n│ ├── README.md # 可选:记录删除原因\n│ └── src/\n│ └── components/\n│ └── old-component.ts\n└── 2024-02-20/\n └── src/\n └── services/\n └── old-service.ts\n```\n\n### 3.2 文档更新规则\n\n#### 必须更新的情况\n\n| 操作 | 需要更新的文档 |\n|-----|--------------|\n| 新增文件 | 在相关文档中添加文件描述 |\n| 删除文件 | 从文档中移除对应条目 |\n| 修改文件功能 | 更新对应文件的描述 |\n| 新增组件/模块 | 添加说明,创建详细文档 |\n| 修改 API | 同步更新 API 文档 |\n| 修改架构 | 更新架构设计部分 |\n| 修改配置 | 更新配置说明 |\n\n#### 更新方式\n1. 修改代码后,**立即**更新相关文档\n2. 确保文档与代码保持一致\n3. 如果发现文档过时,优先更新文档\n\n---\n\n# 第二部分:语言特定规范\n\n## 4. TypeScript 规范\n\n### 4.1 类型定义规范\n\n#### 优先使用 interface 定义对象类型\n```typescript\n// ✅ 推荐:使用 interface\ninterface UserInfo {\n id: string;\n name: string;\n email: string;\n age?: number; // 可选属性\n readonly createdAt: Date; // 只读属性\n}\n\n// 接口继承\ninterface AdminUser extends UserInfo {\n role: 'admin';\n permissions: string[];\n}\n```\n\n#### 使用 type 定义联合类型、交叉类型\n```typescript\n// 联合类型\ntype Status = 'pending' | 'processing' | 'completed' | 'failed';\ntype ID = string | number;\n\n// 交叉类型\ntype UserWithMeta = UserInfo & {\n metadata: Record<string, unknown>;\n};\n\n// 工具类型\ntype PartialUser = Partial<UserInfo>;\ntype RequiredUser = Required<UserInfo>;\ntype ReadonlyUser = Readonly<UserInfo>;\ntype UserKeys = keyof UserInfo;\n```\n\n### 4.2 避免 any,使用正确的类型\n\n```typescript\n// ❌ 避免使用 any\nfunction processData(data: any): any {\n return data.value;\n}\n\n// ✅ 使用 unknown + 类型守卫\nfunction processData(data: unknown): string {\n if (typeof data === 'object' && data !== null && 'value' in data) {\n return String((data as { value: unknown }).value);\n }\n throw new Error('无效的数据格式');\n}\n\n// ✅ 使用泛型\nfunction processData<T extends { value: string }>(data: T): string {\n return data.value;\n}\n```\n\n### 4.3 泛型使用\n\n```typescript\n// 基础泛型\nfunction identity<T>(value: T): T {\n return value;\n}\n\n// 泛型约束\nfunction getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {\n return obj[key];\n}\n\n// 泛型接口\ninterface Repository<T> {\n findById(id: string): Promise<T | null>;\n findAll(): Promise<T[]>;\n save(entity: T): Promise<T>;\n delete(id: string): Promise<void>;\n}\n\n// 泛型类\nclass BaseService<T> {\n constructor(private repository: Repository<T>) {}\n \n async getById(id: string): Promise<T | null> {\n return this.repository.findById(id);\n }\n}\n```\n\n### 4.4 模块导入导出\n\n```typescript\n// ✅ 导入顺序(按此顺序组织)\n// 1. Node.js 内置模块\nimport path from 'path';\nimport fs from 'fs';\n\n// 2. 第三方库\nimport React from 'react';\nimport axios from 'axios';\n\n// 3. 项目内部模块 - 绝对路径\nimport { UserService } from '@/services/user';\nimport { formatDate } from '@/utils/date';\n\n// 4. 项目内部模块 - 相对路径\nimport { Button } from '../components/Button';\nimport { useAuth } from './hooks/useAuth';\n\n// 5. 类型导入\nimport type { User, UserRole } from '@/types';\n\n// 6. 样式文件\nimport './styles.css';\n```\n\n```typescript\n// ✅ 导出方式\n// 命名导出 - 推荐用于工具函数、类型等\nexport function formatDate(date: Date): string { }\nexport interface UserInfo { }\nexport const MAX_COUNT = 100;\n\n// 默认导出 - 推荐用于组件、类\nexport default class UserService { }\nexport default function UserCard() { }\n\n// 重新导出\nexport { UserService } from './user-service';\nexport * from './types';\nexport { default as Button } from './Button';\n```\n\n### 4.5 异步编程\n\n```typescript\n// ✅ 使用 async/await\nasync function fetchUserData(userId: string): Promise<User> {\n try {\n const response = await fetch(`/api/users/${userId}`);\n if (!response.ok) {\n throw new Error(`HTTP error: ${response.status}`);\n }\n return await response.json();\n } catch (error) {\n console.error('获取用户数据失败:', error);\n throw error;\n }\n}\n\n// ✅ 并行请求\nasync function fetchMultipleUsers(ids: string[]): Promise<User[]> {\n const promises = ids.map(id => fetchUserData(id));\n return Promise.all(promises);\n}\n\n// ✅ 带超时的请求\nasync function fetchWithTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number\n): Promise<T> {\n const timeout = new Promise<never>((_, reject) => {\n setTimeout(() => reject(new Error('请求超时')), timeoutMs);\n });\n return Promise.race([promise, timeout]);\n}\n```\n\n### 4.6 枚举与常量\n\n```typescript\n// ✅ 字符串枚举 - 推荐\nenum OrderStatus {\n Pending = 'PENDING',\n Processing = 'PROCESSING',\n Completed = 'COMPLETED',\n Cancelled = 'CANCELLED'\n}\n\n// ✅ const 枚举 - 编译时内联\nconst enum Direction {\n Up = 'UP',\n Down = 'DOWN',\n Left = 'LEFT',\n Right = 'RIGHT'\n}\n\n// ✅ 常量对象 + as const - 更灵活\nconst HTTP_STATUS = {\n OK: 200,\n CREATED: 201,\n BAD_REQUEST: 400,\n UNAUTHORIZED: 401,\n NOT_FOUND: 404,\n INTERNAL_ERROR: 500\n} as const;\n\ntype HttpStatusCode = typeof HTTP_STATUS[keyof typeof HTTP_STATUS];\n```\n\n---\n\n## 5. Java 规范\n\n### 5.1 包结构与命名\n\n#### 标准包结构\n```\ncom.company.project\n├── controller/ # 控制器层\n│ └── UserController.java\n├── service/ # 服务层\n│ ├── UserService.java # 接口\n│ └── impl/\n│ └── UserServiceImpl.java # 实现\n├── repository/ # 数据访问层\n│ └── UserRepository.java\n├── model/ # 数据模型\n│ ├── entity/ # 数据库实体\n│ │ └── User.java\n│ ├── dto/ # 数据传输对象\n│ │ ├── UserDTO.java\n│ │ └── UserCreateRequest.java\n│ └── vo/ # 视图对象\n│ └── UserVO.java\n├── config/ # 配置类\n│ └── WebConfig.java\n├── common/ # 公共模块\n│ ├── exception/ # 异常类\n│ ├── util/ # 工具类\n│ └── constant/ # 常量类\n└── Application.java # 启动类\n```\n\n#### 类命名规范\n\n| 类型 | 命名规则 | 示例 |\n|-----|---------|------|\n| 控制器 | `Xxx Controller` | `UserController` |\n| 服务接口 | `XxxService` | `UserService` |\n| 服务实现 | `XxxServiceImpl` | `UserServiceImpl` |\n| 数据访问 | `XxxRepository` / `XxxMapper` | `UserRepository` |\n| 实体类 | `Xxx` | `User`, `Order` |\n| DTO | `XxxDTO` / `XxxRequest` / `XxxResponse` | `UserDTO` |\n| VO | `XxxVO` | `UserVO` |\n| 工具类 | `XxxUtil` / `XxxUtils` / `XxxHelper` | `DateUtils` |\n| 常量类 | `XxxConstants` | `ApiConstants` |\n| 枚举 | `XxxEnum` / `XxxType` / `XxxStatus` | `OrderStatus` |\n| 异常 | `XxxException` | `BusinessException` |\n\n### 5.2 类设计原则\n\n#### 实体类\n```java\n/**\n * 用户实体\n * \n * @author AI Assistant\n * @since 1.0.0\n */\n@Data\n@Entity\n@Table(name = \"t_user\")\npublic class User implements Serializable {\n \n private static final long serialVersionUID = 1L;\n \n /**\n * 用户ID\n */\n @Id\n @GeneratedValue(strategy = GenerationType.IDENTITY)\n private Long id;\n \n /**\n * 用户名\n */\n @Column(nullable = false, length = 50)\n private String username;\n \n /**\n * 邮箱\n */\n @Column(nullable = false, unique = true)\n private String email;\n \n /**\n * 状态:0-禁用,1-启用\n */\n private Integer status;\n \n /**\n * 创建时间\n */\n @CreatedDate\n private LocalDateTime createdAt;\n \n /**\n * 更新时间\n */\n @LastModifiedDate\n private LocalDateTime updatedAt;\n}\n```\n\n#### 服务类\n```java\n/**\n * 用户服务实现\n */\n@Slf4j\n@Service\n@RequiredArgsConstructor\npublic class UserServiceImpl implements UserService {\n \n private final UserRepository userRepository;\n private final PasswordEncoder passwordEncoder;\n \n /**\n * 根据ID获取用户\n *\n * @param id 用户ID\n * @return 用户信息\n * @throws BusinessException 用户不存在时抛出\n */\n @Override\n @Transactional(readOnly = true)\n public UserVO getUserById(Long id) {\n log.info(\"查询用户信息,ID:{}\", id);\n \n User user = userRepository.findById(id)\n .orElseThrow(() -> new BusinessException(\"用户不存在:\" + id));\n \n return convertToVO(user);\n }\n \n /**\n * 创建用户\n *\n * @param request 创建请求\n * @return 用户ID\n */\n @Override\n @Transactional(rollbackFor = Exception.class)\n public Long createUser(UserCreateRequest request) {\n log.info(\"创建用户,用户名:{}\", request.getUsername());\n \n // 检查用户名是否存在\n if (userRepository.existsByUsername(request.getUsername())) {\n throw new BusinessException(\"用户名已存在\");\n }\n \n // 创建用户实体\n User user = new User();\n user.setUsername(request.getUsername());\n user.setEmail(request.getEmail());\n user.setPassword(passwordEncoder.encode(request.getPassword()));\n user.setStatus(1);\n \n // 保存并返回ID\n User saved = userRepository.save(user);\n log.info(\"用户创建成功,ID:{}\", saved.getId());\n \n return saved.getId();\n }\n}\n```\n\n### 5.3 异常处理\n\n#### 自定义异常体系\n```java\n/**\n * 基础业务异常\n */\n@Getter\npublic class BusinessException extends RuntimeException {\n \n private final String code;\n private final String message;\n \n public BusinessException(String message) {\n super(message);\n this.code = \"BUSINESS_ERROR\";\n this.message = message;\n }\n \n public BusinessException(String code, String message) {\n super(message);\n this.code = code;\n this.message = message;\n }\n}\n\n/**\n * 全局异常处理器\n */\n@Slf4j\n@RestControllerAdvice\npublic class GlobalExceptionHandler {\n \n /**\n * 处理业务异常\n */\n @ExceptionHandler(BusinessException.class)\n public Result<Void> handleBusinessException(BusinessException e) {\n log.warn(\"业务异常:{}\", e.getMessage());\n return Result.fail(e.getCode(), e.getMessage());\n }\n \n /**\n * 处理参数校验异常\n */\n @ExceptionHandler(MethodArgumentNotValidException.class)\n public Result<Void> handleValidationException(MethodArgumentNotValidException e) {\n String message = e.getBindingResult().getFieldErrors().stream()\n .map(error -> error.getField() + \": \" + error.getDefaultMessage())\n .collect(Collectors.joining(\", \"));\n log.warn(\"参数校验失败:{}\", message);\n return Result.fail(\"VALIDATION_ERROR\", message);\n }\n \n /**\n * 处理未知异常\n */\n @ExceptionHandler(Exception.class)\n public Result<Void> handleException(Exception e) {\n log.error(\"系统异常:\", e);\n return Result.fail(\"SYSTEM_ERROR\", \"系统繁忙,请稍后重试\");\n }\n}\n```\n\n### 5.4 Controller 规范\n\n```java\n/**\n * 用户管理接口\n */\n@Slf4j\n@RestController\n@RequestMapping(\"/api/v1/users\")\n@RequiredArgsConstructor\n@Tag(name = \"用户管理\", description = \"用户相关接口\")\npublic class UserController {\n \n private final UserService userService;\n \n /**\n * 获取用户详情\n *\n * @param id 用户ID\n * @return 用户信息\n */\n @GetMapping(\"/{id}\")\n @Operation(summary = \"获取用户详情\")\n public Result<UserVO> getUser(@PathVariable Long id) {\n return Result.success(userService.getUserById(id));\n }\n \n /**\n * 获取用户列表\n *\n * @param query 查询条件\n * @return 用户列表\n */\n @GetMapping\n @Operation(summary = \"获取用户列表\")\n public Result<PageResult<UserVO>> listUsers(@Valid UserQuery query) {\n return Result.success(userService.listUsers(query));\n }\n \n /**\n * 创建用户\n *\n * @param request 创建请求\n * @return 用户ID\n */\n @PostMapping\n @Operation(summary = \"创建用户\")\n public Result<Long> createUser(@Valid @RequestBody UserCreateRequest request) {\n return Result.success(userService.createUser(request));\n }\n \n /**\n * 更新用户\n *\n * @param id 用户ID\n * @param request 更新请求\n * @return 操作结果\n */\n @PutMapping(\"/{id}\")\n @Operation(summary = \"更新用户\")\n public Result<Void> updateUser(\n @PathVariable Long id,\n @Valid @RequestBody UserUpdateRequest request) {\n userService.updateUser(id, request);\n return Result.success();\n }\n \n /**\n * 删除用户\n *\n * @param id 用户ID\n * @return 操作结果\n */\n @DeleteMapping(\"/{id}\")\n @Operation(summary = \"删除用户\")\n public Result<Void> deleteUser(@PathVariable Long id) {\n userService.deleteUser(id);\n return Result.success();\n }\n}\n```\n\n### 5.5 统一响应格式\n\n```java\n/**\n * 统一响应结果\n *\n * @param <T> 数据类型\n */\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\npublic class Result<T> implements Serializable {\n \n private static final long serialVersionUID = 1L;\n \n /**\n * 状态码\n */\n private String code;\n \n /**\n * 消息\n */\n private String message;\n \n /**\n * 数据\n */\n private T data;\n \n /**\n * 时间戳\n */\n private long timestamp = System.currentTimeMillis();\n \n /**\n * 成功响应\n */\n public static <T> Result<T> success(T data) {\n Result<T> result = new Result<>();\n result.setCode(\"SUCCESS\");\n result.setMessage(\"操作成功\");\n result.setData(data);\n return result;\n }\n \n /**\n * 成功响应(无数据)\n */\n public static <T> Result<T> success() {\n return success(null);\n }\n \n /**\n * 失败响应\n */\n public static <T> Result<T> fail(String code, String message) {\n Result<T> result = new Result<>();\n result.setCode(code);\n result.setMessage(message);\n return result;\n }\n}\n```\n\n---\n\n# 第三部分:框架特定规范\n\n## 6. React 规范\n\n### 6.1 组件设计原则\n\n#### 函数组件 + TypeScript\n```tsx\n/**\n * 用户卡片组件\n * \n * @description 展示用户基本信息的卡片组件\n */\ninterface UserCardProps {\n /** 用户信息 */\n user: User;\n /** 是否显示详情 */\n showDetails?: boolean;\n /** 编辑回调 */\n onEdit?: (user: User) => void;\n /** 删除回调 */\n onDelete?: (userId: string) => void;\n /** 子元素 */\n children?: React.ReactNode;\n}\n\nconst UserCard: React.FC<UserCardProps> = ({\n user,\n showDetails = false,\n onEdit,\n onDelete,\n children\n}) => {\n // 组件逻辑\n return (\n <div className=\"user-card\">\n {/* 组件内容 */}\n </div>\n );\n};\n\nexport default UserCard;\n```\n\n#### 组件文件结构\n```\ncomponents/\n└── UserCard/\n ├── index.tsx # 组件主文件\n ├── UserCard.tsx # 组件实现(可选,复杂组件时分离)\n ├── UserCard.module.css # 样式文件(CSS Modules)\n ├── UserCard.test.tsx # 测试文件\n ├── types.ts # 类型定义(复杂时分离)\n └── hooks.ts # 组件专用 Hooks(可选)\n```\n\n### 6.2 Hooks 使用规范\n\n#### Hooks 使用顺序\n```tsx\nfunction UserProfile({ userId }: { userId: string }) {\n // 1. useState - 状态定义\n const [user, setUser] = useState<User | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n \n // 2. useRef - 引用\n const containerRef = useRef<HTMLDivElement>(null);\n \n // 3. useContext - 上下文\n const { theme } = useContext(ThemeContext);\n \n // 4. 自定义 Hooks\n const { data, isLoading } = useQuery(['user', userId], fetchUser);\n \n // 5. useCallback - 缓存回调\n const handleEdit = useCallback(() => {\n // 处理编辑\n }, [userId]);\n \n // 6. useMemo - 缓存计算结果\n const displayName = useMemo(() => {\n return user ? `${user.firstName} ${user.lastName}` : '';\n }, [user]);\n \n // 7. useEffect - 副作用\n useEffect(() => {\n // 副作用逻辑\n return () => {\n // 清理函数\n };\n }, [userId]);\n \n // 8. 条件渲染和返回\n if (loading) return <Loading />;\n if (error) return <Error message={error.message} />;\n \n return <div ref={containerRef}>{/* 内容 */}</div>;\n}\n```\n\n#### 自定义 Hook 规范\n```tsx\n/**\n * 用户数据获取 Hook\n * \n * @param userId 用户ID\n * @returns 用户数据、加载状态、错误信息\n */\nfunction useUser(userId: string) {\n const [user, setUser] = useState<User | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n \n useEffect(() => {\n let cancelled = false;\n \n async function fetchUser() {\n try {\n setLoading(true);\n setError(null);\n const data = await userApi.getUser(userId);\n if (!cancelled) {\n setUser(data);\n }\n } catch (err) {\n if (!cancelled) {\n setError(err instanceof Error ? err : new Error('未知错误'));\n }\n } finally {\n if (!cancelled) {\n setLoading(false);\n }\n }\n }\n \n fetchUser();\n \n // 清理函数:防止组件卸载后设置状态\n return () => {\n cancelled = true;\n };\n }, [userId]);\n \n return { user, loading, error };\n}\n```\n\n### 6.3 状态管理\n\n#### 本地状态 vs 全局状态\n```tsx\n// ✅ 本地状态:组件内部使用,不需要共享\nconst [isOpen, setIsOpen] = useState(false);\nconst [inputValue, setInputValue] = useState('');\n\n// ✅ 全局状态:多个组件共享,使用状态管理库\n// 例如:用户信息、主题设置、购物车等\n```\n\n#### 使用 Zustand(推荐)\n```tsx\nimport { create } from 'zustand';\n\n/**\n * 用户状态 Store\n */\ninterface UserStore {\n user: User | null;\n isAuthenticated: boolean;\n login: (user: User) => void;\n logout: () => void;\n updateUser: (updates: Partial<User>) => void;\n}\n\nconst useUserStore = create<UserStore>((set) => ({\n user: null,\n isAuthenticated: false,\n \n login: (user) => set({ user, isAuthenticated: true }),\n \n logout: () => set({ user: null, isAuthenticated: false }),\n \n updateUser: (updates) => set((state) => ({\n user: state.user ? { ...state.user, ...updates } : null\n }))\n}));\n\n// 使用\nfunction UserProfile() {\n const { user, logout } = useUserStore();\n // ...\n}\n```\n\n### 6.4 性能优化\n\n```tsx\n// ✅ 使用 React.memo 避免不必要的重渲染\nconst UserCard = React.memo(function UserCard({ user }: UserCardProps) {\n return <div>{user.name}</div>;\n});\n\n// ✅ 使用 useCallback 缓存回调函数\nconst handleClick = useCallback((id: string) => {\n // 处理点击\n}, [/* 依赖项 */]);\n\n// ✅ 使用 useMemo 缓存计算结果\nconst sortedItems = useMemo(() => {\n return [...items].sort((a, b) => a.name.localeCompare(b.name));\n}, [items]);\n\n// ✅ 列表渲染使用稳定的 key\n{items.map((item) => (\n <ListItem key={item.id} item={item} /> // ✅ 使用唯一 ID\n // <ListItem key={index} item={item} /> // ❌ 避免使用索引\n))}\n\n// ✅ 懒加载组件\nconst HeavyComponent = React.lazy(() => import('./HeavyComponent'));\n\nfunction App() {\n return (\n <Suspense fallback={<Loading />}>\n <HeavyComponent />\n </Suspense>\n );\n}\n```\n\n---\n\n## 7. Vue 规范\n\n### 7.1 组件设计(Vue 3 Composition API)\n\n#### 单文件组件结构\n```vue\n<script setup lang=\"ts\">\n/**\n * 用户卡片组件\n * \n * @description 展示用户基本信息的卡片组件\n */\nimport { ref, computed, onMounted, watch } from 'vue';\nimport type { User } from '@/types';\n\n// ============ Props 定义 ============\ninterface Props {\n /** 用户信息 */\n user: User;\n /** 是否显示详情 */\n showDetails?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n showDetails: false\n});\n\n// ============ Emits 定义 ============\nconst emit = defineEmits<{\n /** 编辑事件 */\n (e: 'edit', user: User): void;\n /** 删除事件 */\n (e: 'delete', userId: string): void;\n}>();\n\n// ============ 响应式数据 ============\nconst isExpanded = ref(false);\nconst loading = ref(false);\n\n// ============ 计算属性 ============\nconst displayName = computed(() => {\n return `${props.user.firstName} ${props.user.lastName}`;\n});\n\nconst cardClasses = computed(() => ({\n 'user-card': true,\n 'user-card--expanded': isExpanded.value,\n 'user-card--loading': loading.value\n}));\n\n// ============ 方法 ============\nfunction handleEdit() {\n emit('edit', props.user);\n}\n\nfunction handleDelete() {\n emit('delete', props.user.id);\n}\n\nfunction toggleExpand() {\n isExpanded.value = !isExpanded.value;\n}\n\n// ============ 生命周期 ============\nonMounted(() => {\n console.log('组件已挂载');\n});\n\n// ============ 侦听器 ============\nwatch(\n () => props.user.id,\n (newId, oldId) => {\n console.log(`用户ID变更:${oldId} -> ${newId}`);\n }\n);\n\n// ============ 暴露给父组件的方法 ============\ndefineExpose({\n toggleExpand\n});\n</script>\n\n<template>\n <div :class=\"cardClasses\">\n <div class=\"user-card__header\">\n <h3>{{ displayName }}</h3>\n <button @click=\"toggleExpand\">\n {{ isExpanded ? '收起' : '展开' }}\n </button>\n </div>\n \n <div v-if=\"showDetails || isExpanded\" class=\"user-card__details\">\n <p>邮箱:{{ user.email }}</p>\n <p>电话:{{ user.phone }}</p>\n </div>\n \n <div class=\"user-card__actions\">\n <button @click=\"handleEdit\">编辑</button>\n <button @click=\"handleDelete\">删除</button>\n </div>\n \n <!-- 插槽 -->\n <slot name=\"footer\" />\n </div>\n</template>\n\n<style scoped>\n.user-card {\n padding: 16px;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n}\n\n.user-card--expanded {\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n}\n\n.user-card--loading {\n opacity: 0.6;\n pointer-events: none;\n}\n</style>\n```\n\n### 7.2 组合式函数(Composables)\n\n```typescript\n// composables/useUser.ts\nimport { ref, computed, watch } from 'vue';\nimport type { Ref } from 'vue';\nimport type { User } from '@/types';\nimport { userApi } from '@/api';\n\n/**\n * 用户数据组合式函数\n * \n * @param userId 用户ID(响应式引用)\n * @returns 用户数据、加载状态、错误信息、刷新方法\n */\nexport function useUser(userId: Ref<string> | string) {\n const user = ref<User | null>(null);\n const loading = ref(false);\n const error = ref<Error | null>(null);\n \n // 获取用户数据\n async function fetchUser(id: string) {\n loading.value = true;\n error.value = null;\n \n try {\n user.value = await userApi.getUser(id);\n } catch (err) {\n error.value = err instanceof Error ? err : new Error('获取用户失败');\n user.value = null;\n } finally {\n loading.value = false;\n }\n }\n \n // 刷新数据\n function refresh() {\n const id = typeof userId === 'string' ? userId : userId.value;\n return fetchUser(id);\n }\n \n // 计算属性\n const displayName = computed(() => {\n if (!user.value) return '';\n return `${user.value.firstName} ${user.value.lastName}`;\n });\n \n // 侦听 userId 变化\n if (typeof userId !== 'string') {\n watch(userId, (newId) => {\n if (newId) {\n fetchUser(newId);\n }\n }, { immediate: true });\n } else {\n // 立即获取数据\n fetchUser(userId);\n }\n \n return {\n user,\n loading,\n error,\n displayName,\n refresh\n };\n}\n```\n\n### 7.3 Pinia 状态管理\n\n```typescript\n// stores/user.ts\nimport { defineStore } from 'pinia';\nimport type { User } from '@/types';\nimport { userApi } from '@/api';\n\n/**\n * 用户状态 Store\n */\nexport const useUserStore = defineStore('user', {\n // 状态\n state: () => ({\n currentUser: null as User | null,\n isAuthenticated: false,\n loading: false\n }),\n \n // 计算属性\n getters: {\n /** 用户显示名称 */\n displayName(): string {\n if (!this.currentUser) return '游客';\n return `${this.currentUser.firstName} ${this.currentUser.lastName}`;\n },\n \n /** 是否是管理员 */\n isAdmin(): boolean {\n return this.currentUser?.role === 'admin';\n }\n },\n \n // 方法\n actions: {\n /**\n * 用户登录\n */\n async login(credentials: { username: string; password: string }) {\n this.loading = true;\n try {\n const user = await userApi.login(credentials);\n this.currentUser = user;\n this.isAuthenticated = true;\n } finally {\n this.loading = false;\n }\n },\n \n /**\n * 用户登出\n */\n logout() {\n this.currentUser = null;\n this.isAuthenticated = false;\n },\n \n /**\n * 更新用户信息\n */\n updateUser(updates: Partial<User>) {\n if (this.currentUser) {\n this.currentUser = { ...this.currentUser, ...updates };\n }\n }\n }\n});\n\n// 使用 Setup Store 语法(更灵活)\nexport const useUserStore2 = defineStore('user', () => {\n // 状态\n const currentUser = ref<User | null>(null);\n const isAuthenticated = ref(false);\n \n // 计算属性\n const displayName = computed(() => {\n return currentUser.value?.name ?? '游客';\n });\n \n // 方法\n async function login(credentials: LoginCredentials) {\n const user = await userApi.login(credentials);\n currentUser.value = user;\n isAuthenticated.value = true;\n }\n \n function logout() {\n currentUser.value = null;\n isAuthenticated.value = false;\n }\n \n return {\n currentUser,\n isAuthenticated,\n displayName,\n login,\n logout\n };\n});\n```\n\n---\n\n# 第四部分:工程化规范\n\n## 8. Git 提交规范\n\n### 8.1 提交信息格式\n\n```\n<type>(<scope>): <subject>\n\n<body>\n\n<footer>\n```\n\n### 8.2 Type 类型说明\n\n| 类型 | 说明 | 示例 |\n|-----|------|------|\n| `feat` | 新功能 | `feat(user): 添加用户登录功能` |\n| `fix` | 修复 Bug | `fix(cart): 修复购物车数量计算错误` |\n| `docs` | 文档更新 | `docs: 更新 README 安装说明` |\n| `style` | 代码格式(不影响功能) | `style: 格式化代码` |\n| `refactor` | 重构(不是新功能也不是修复) | `refactor(api): 重构请求封装` |\n| `perf` | 性能优化 | `perf: 优化列表渲染性能` |\n| `test` | 测试相关 | `test(user): 添加用户服务单元测试` |\n| `chore` | 构建/工具/依赖更新 | `chore: 升级 webpack 到 5.x` |\n| `revert` | 回滚提交 | `revert: 回滚 feat(user) 提交` |\n| `ci` | CI/CD 配置 | `ci: 添加 GitHub Actions 配置` |\n| `build` | 构建系统或外部依赖 | `build: 更新构建脚本` |\n\n### 8.3 提交示例\n\n```bash\n# 简短提交\ngit commit -m \"feat(user): 添加用户注册功能\"\n\n# 详细提交\ngit commit -m \"feat(user): 添加用户注册功能\n\n- 实现用户名密码注册\n- 添加邮箱验证\n- 添加表单校验\n\nCloses #123\"\n\n# 修复 Bug\ngit commit -m \"fix(order): 修复订单金额计算错误\n\n订单金额在应用折扣后计算不正确,\n原因是折扣百分比未正确转换。\n\nFixes #456\"\n\n# 破坏性变更\ngit commit -m \"feat(api): 重构用户 API 接口\n\nBREAKING CHANGE: 用户 API 响应格式已变更\n- data 字段改为 result\n- 添加 pagination 字段\"\n```\n\n### 8.4 分支命名规范\n\n| 分支类型 | 命名格式 | 示例 |\n|---------|---------|------|\n| 主分支 | `main` / `master` | `main` |\n| 开发分支 | `develop` / `dev` | `develop` |\n| 功能分支 | `feature/<name>` | `feature/user-login` |\n| 修复分支 | `fix/<name>` 或 `bugfix/<name>` | `fix/cart-calculation` |\n| 发布分支 | `release/<version>` | `release/1.2.0` |\n| 热修复分支 | `hotfix/<name>` | `hotfix/security-patch` |\n\n---\n\n## 9. API 设计规范\n\n### 9.1 RESTful API 设计\n\n#### URL 设计原则\n```\n# 资源命名使用复数名词\nGET /api/v1/users # 获取用户列表\nGET /api/v1/users/:id # 获取单个用户\nPOST /api/v1/users # 创建用户\nPUT /api/v1/users/:id # 更新用户(全量)\nPATCH /api/v1/users/:id # 更新用户(部分)\nDELETE /api/v1/users/:id # 删除用户\n\n# 资源关联\nGET /api/v1/users/:id/orders # 获取用户的订单\nPOST /api/v1/users/:id/orders # 为用户创建订单\n\n# 查询参数\nGET /api/v1/users?page=1&size=10 # 分页\nGET /api/v1/users?sort=createdAt&order=desc # 排序\nGET /api/v1/users?status=active&role=admin # 筛选\nGET /api/v1/users?keyword=john # 搜索\n```\n\n#### HTTP 状态码\n| 状态码 | 说明 | 使用场景 |\n|-------|------|---------|\n| 200 | OK | 请求成功 |\n| 201 | Created | 创建成功 |\n| 204 | No Content | 删除成功 |\n| 400 | Bad Request | 请求参数错误 |\n| 401 | Unauthorized | 未认证 |\n| 403 | Forbidden | 无权限 |\n| 404 | Not Found | 资源不存在 |\n| 409 | Conflict | 资源冲突 |\n| 422 | Unprocessable Entity | 参数校验失败 |\n| 500 | Internal Server Error | 服务器错误 |\n\n### 9.2 请求/响应格式\n\n#### 统一响应格式\n```typescript\n// 成功响应\n{\n \"code\": \"SUCCESS\",\n \"message\": \"操作成功\",\n \"data\": {\n // 业务数据\n },\n \"timestamp\": 1642234567890\n}\n\n// 分页响应\n{\n \"code\": \"SUCCESS\",\n \"message\": \"操作成功\",\n \"data\": {\n \"list\": [...],\n \"pagination\": {\n \"page\": 1,\n \"size\": 10,\n \"total\": 100,\n \"totalPages\": 10\n }\n },\n \"timestamp\": 1642234567890\n}\n\n// 错误响应\n{\n \"code\": \"VALIDATION_ERROR\",\n \"message\": \"参数校验失败\",\n \"errors\": [\n { \"field\": \"email\", \"message\": \"邮箱格式不正确\" },\n { \"field\": \"password\", \"message\": \"密码长度至少6位\" }\n ],\n \"timestamp\": 1642234567890\n}\n```\n\n### 9.3 错误码设计\n\n```typescript\n// 错误码枚举\nenum ErrorCode {\n // 成功\n SUCCESS = 'SUCCESS',\n \n // 客户端错误 (4xx)\n BAD_REQUEST = 'BAD_REQUEST',\n UNAUTHORIZED = 'UNAUTHORIZED',\n FORBIDDEN = 'FORBIDDEN',\n NOT_FOUND = 'NOT_FOUND',\n VALIDATION_ERROR = 'VALIDATION_ERROR',\n \n // 业务错误\n USER_NOT_FOUND = 'USER_NOT_FOUND',\n USER_ALREADY_EXISTS = 'USER_ALREADY_EXISTS',\n INVALID_PASSWORD = 'INVALID_PASSWORD',\n ORDER_NOT_FOUND = 'ORDER_NOT_FOUND',\n INSUFFICIENT_STOCK = 'INSUFFICIENT_STOCK',\n \n // 服务端错误 (5xx)\n INTERNAL_ERROR = 'INTERNAL_ERROR',\n SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE',\n DATABASE_ERROR = 'DATABASE_ERROR'\n}\n```\n\n---\n\n## 10. 测试规范\n\n### 10.1 单元测试\n\n#### 测试文件命名\n```\nsrc/\n├── utils/\n│ ├── format.ts\n│ └── format.test.ts # 或 format.spec.ts\n├── components/\n│ └── UserCard/\n│ ├── index.tsx\n│ └── UserCard.test.tsx\n```\n\n#### 测试用例命名\n```typescript\n// 使用 describe + it 结构\ndescribe('formatDate', () => {\n it('应该正确格式化日期', () => {\n // 测试代码\n });\n \n it('当输入为空时应该返回空字符串', () => {\n // 测试代码\n });\n \n it('当输入无效时应该抛出错误', () => {\n // 测试代码\n });\n});\n\n// 使用中文描述测试场景\ndescribe('用户服务', () => {\n describe('创建用户', () => {\n it('应该成功创建用户并返回用户ID', async () => {\n // 测试代码\n });\n \n it('当用户名已存在时应该抛出错误', async () => {\n // 测试代码\n });\n });\n});\n```\n\n#### 测试结构(AAA 模式)\n```typescript\nit('应该正确计算订单总价', () => {\n // Arrange(准备)\n const items = [\n { name: '商品A', price: 100, quantity: 2 },\n { name: '商品B', price: 50, quantity: 3 }\n ];\n \n // Act(执行)\n const total = calculateTotal(items);\n \n // Assert(断言)\n expect(total).toBe(350);\n});\n```\n\n### 10.2 React 组件测试\n\n```tsx\nimport { render, screen, fireEvent } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport UserCard from './UserCard';\n\ndescribe('UserCard 组件', () => {\n const mockUser = {\n id: '1',\n name: '张三',\n email: 'zhangsan@example.com'\n };\n \n it('应该正确渲染用户信息', () => {\n render(<UserCard user={mockUser} />);\n \n expect(screen.getByText('张三')).toBeInTheDocument();\n expect(screen.getByText('zhangsan@example.com')).toBeInTheDocument();\n });\n \n it('点击编辑按钮应该触发 onEdit 回调', async () => {\n const handleEdit = jest.fn();\n render(<UserCard user={mockUser} onEdit={handleEdit} />);\n \n await userEvent.click(screen.getByRole('button', { name: '编辑' }));\n \n expect(handleEdit).toHaveBeenCalledWith(mockUser);\n });\n \n it('当 showDetails 为 false 时不应该显示详情', () => {\n render(<UserCard user={mockUser} showDetails={false} />);\n \n expect(screen.queryByText('详情')).not.toBeInTheDocument();\n });\n});\n```\n\n### 10.3 Vue 组件测试\n\n```typescript\nimport { mount } from '@vue/test-utils';\nimport { describe, it, expect, vi } from 'vitest';\nimport UserCard from './UserCard.vue';\n\ndescribe('UserCard 组件', () => {\n const mockUser = {\n id: '1',\n name: '张三',\n email: 'zhangsan@example.com'\n };\n \n it('应该正确渲染用户信息', () => {\n const wrapper = mount(UserCard, {\n props: { user: mockUser }\n });\n \n expect(wrapper.text()).toContain('张三');\n expect(wrapper.text()).toContain('zhangsan@example.com');\n });\n \n it('点击编辑按钮应该触发 edit 事件', async () => {\n const wrapper = mount(UserCard, {\n props: { user: mockUser }\n });\n \n await wrapper.find('[data-test=\"edit-btn\"]').trigger('click');\n \n expect(wrapper.emitted('edit')).toBeTruthy();\n expect(wrapper.emitted('edit')![0]).toEqual([mockUser]);\n });\n});\n```\n\n---\n\n# 附录\n\n## 附录 A:代码审查清单\n\n### A.1 开发流程检查\n- [ ] 如果是询问\"如何实现\",是否先提供了详细的开发计划\n- [ ] 是否在用户同意后才修改代码\n- [ ] 删除的文件是否移动到 `.recycle/` 文件夹而非直接删除\n\n### A.2 代码质量检查\n- [ ] 命名是否清晰、语义化\n- [ ] 注释是否完整且为中文\n- [ ] 错误处理是否完善\n- [ ] 类型定义是否完整(TypeScript)\n- [ ] 是否有重复代码可以抽取\n\n### A.3 TypeScript 检查\n- [ ] 是否避免使用 `any`\n- [ ] 接口和类型是否正确定义\n- [ ] 泛型使用是否恰当\n- [ ] 导入导出是否规范\n\n### A.4 React/Vue 检查\n- [ ] 组件职责是否单一\n- [ ] Props 和事件是否正确定义\n- [ ] Hooks 使用是否规范\n- [ ] 是否有性能问题(不必要的重渲染)\n\n### A.5 Java 检查\n- [ ] 包结构是否规范\n- [ ] 类命名是否符合约定\n- [ ] 是否正确使用注解\n- [ ] 异常处理是否完善\n\n### A.6 安全检查\n- [ ] 是否验证了所有外部输入\n- [ ] 是否避免了 XSS 风险\n- [ ] 敏感数据是否正确处理\n- [ ] 错误信息是否避免泄露敏感信息\n\n### A.7 文档检查\n- [ ] 是否更新了相关文档\n- [ ] API 文档是否同步更新\n- [ ] README 是否需要更新\n\n---\n\n## 附录 B:常见任务快速参考\n\n### B.1 添加新文件\n1. 创建文件,添加必要的类型定义\n2. 添加中文注释\n3. 在相关入口文件中导出(如需要)\n4. 更新文档\n\n### B.2 删除文件\n1. 确认文件不再使用\n2. 创建回收目录:`.recycle/YYYY-MM-DD/`\n3. 移动文件到回收目录(保持目录结构)\n4. 更新相关导入和引用\n5. 更新文档\n\n### B.3 重构代码\n1. 先与用户确认重构范围\n2. 制定详细的重构计划\n3. 分步骤执行,每步验证\n4. 确保测试通过\n5. 更新文档\n\n### B.4 添加新 API\n1. 设计 API 接口(URL、参数、响应)\n2. 定义 TypeScript 类型\n3. 实现后端接口\n4. 实现前端调用\n5. 添加错误处理\n6. 编写测试\n7. 更新 API 文档\n\n---\n\n## 附录 C:模板使用说明\n\n本模板可直接复制到新项目中使用。使用时请注意:\n\n1. **根据项目调整**:\n - 删除不适用的语言/框架章节\n - 根据团队习惯调整具体规范\n\n2. **补充项目特定内容**:\n - 项目特定的架构规范\n - 项目特定的命名规范\n - 项目特定的目录结构\n\n3. **定期更新**:\n - 随着项目发展,及时更新规范内容\n - 根据团队反馈优化规范\n\n4. **配合其他文档使用**:\n - 项目 README\n - API 文档\n - 架构设计文档\n - 部署文档\n\n---\n\n**文档版本**:v2.0\n**最后更新**:2026-01-21\n"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
Wrote file successfully.
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
---
|
||
|
||
## Assistant (Compaction · claude-opus-4-5-20251101 · 42.4s)
|
||
|
||
## 会话总结 - BIM Engine SDK 文档整理与规范编写
|
||
|
||
### 项目路径
|
||
`/Users/yuding/WORK/LYZ/project/bimEngine/engine`
|
||
|
||
### 项目背景
|
||
**BIM Engine SDK** - 通用 3D BIM 引擎 SDK,支持 Vue 2/3、React 和原生 HTML。技术栈:TypeScript + Vite + Three.js。
|
||
|
||
---
|
||
|
||
## 已完成的工作
|
||
|
||
### 1. 文档重命名(中文化)
|
||
|
||
将 `docs/` 目录下的所有英文文档重命名为中文:
|
||
|
||
**docs/components/ 目录(11个组件文档):**
|
||
| 原文件名 | 新文件名 |
|
||
|---------|----------|
|
||
| button-group.md | `按钮组组件-ButtonGroup.md` |
|
||
| collapse.md | `折叠面板组件-Collapse.md` |
|
||
| description.md | `描述列表组件-Description.md` |
|
||
| dialog.md | `弹窗组件-Dialog.md` |
|
||
| engine.md | `3D引擎组件-Engine.md` |
|
||
| measure-panel.md | `测量面板组件-MeasurePanel.md` |
|
||
| menu.md | `菜单组件-Menu.md` |
|
||
| right-key.md | `右键菜单组件-RightKey.md` |
|
||
| tab.md | `标签页组件-Tab.md` |
|
||
| tree.md | `树形控件组件-Tree.md` |
|
||
| walk-control-panel.md | `漫游控制面板组件-WalkControlPanel.md` |
|
||
|
||
**其他重命名:**
|
||
- `docs/SDK_USAGE.md` → `docs/SDK使用指南.md`
|
||
- `docs/utils/icon-manager.md` → `docs/utils/图标管理器-IconManager.md`
|
||
- `demo/DEPLOY.md` → `demo/部署指南.md`
|
||
|
||
**移动到回收站(重复文档):**
|
||
- `docs/Dialog.md` → `.recycle/2026-01-21/docs/Dialog.md`
|
||
- `docs/Toolbar.md` → `.recycle/2026-01-21/docs/Toolbar.md`
|
||
- `docs/OptBtnGroups.md` → `.recycle/2026-01-21/docs/OptBtnGroups.md`
|
||
|
||
---
|
||
|
||
### 2. 创建通用规范文档
|
||
|
||
从 `AI_COLLABORATION.md` 中提取通用内容,创建了两个新文档:
|
||
|
||
#### 2.1 `docs/AI代码规范模板.md` (v2.0 完整版)
|
||
**通用的 AI 协作与代码规范模板**,约 1000 行,包含:
|
||
|
||
| 部分 | 内容 |
|
||
|-----|------|
|
||
| 第一部分:通用规范 | AI 协作原则、通用编码规范、文件与文档管理 |
|
||
| 第二部分:语言规范 | TypeScript 规范(类型定义、泛型、模块等)、Java 规范(包结构、类设计、Spring Boot) |
|
||
| 第三部分:框架规范 | React 规范(组件设计、Hooks、状态管理)、Vue 规范(Composition API、Pinia) |
|
||
| 第四部分:工程化 | Git 提交规范、API 设计规范(RESTful)、测试规范 |
|
||
| 附录 | 代码审查清单、常见任务参考、模板使用说明 |
|
||
|
||
#### 2.2 `docs/国际化实现指南.md`
|
||
**项目独有的国际化规范**,包含:
|
||
- 国际化架构(翻译文件、API)
|
||
- 实现步骤(类型定义、翻译字典、代码使用)
|
||
- 组件中的国际化实现示例
|
||
- 注意事项和最佳实践
|
||
- 添加新语言的步骤
|
||
- 检查清单
|
||
|
||
---
|
||
|
||
## 当前文档结构
|
||
|
||
```
|
||
engine/docs/
|
||
├── AI代码规范模板.md ← 新创建(通用,可复用到其他项目)
|
||
├── SDK使用指南.md ← 重命名
|
||
├── 国际化实现指南.md ← 新创建(项目独有)
|
||
├── components/
|
||
│ ├── 3D引擎组件-Engine.md
|
||
│ ├── 按钮组组件-ButtonGroup.md
|
||
│ ├── 弹窗组件-Dialog.md
|
||
│ ├── 折叠面板组件-Collapse.md
|
||
│ ├── 描述列表组件-Description.md
|
||
│ ├── 测量面板组件-MeasurePanel.md
|
||
│ ├── 菜单组件-Menu.md
|
||
│ ├── 右键菜单组件-RightKey.md
|
||
│ ├── 标签页组件-Tab.md
|
||
│ ├── 树形控件组件-Tree.md
|
||
│ └── 漫游控制面板组件-WalkControlPanel.md
|
||
└── utils/
|
||
└── 图标管理器-IconManager.md
|
||
```
|
||
|
||
---
|
||
|
||
## 关键文件引用
|
||
|
||
- **项目主文档**: `/Users/yuding/WORK/LYZ/project/bimEngine/engine/AI_COLLABORATION.md`
|
||
- **AI代码规范模板**: `/Users/yuding/WORK/LYZ/project/bimEngine/engine/docs/AI代码规范模板.md`
|
||
- **国际化指南**: `/Users/yuding/WORK/LYZ/project/bimEngine/engine/docs/国际化实现指南.md`
|
||
|
||
---
|
||
|
||
## 用户偏好
|
||
|
||
- ✅ 使用中文交流和编写文档
|
||
- ✅ 遵循 AI_COLLABORATION.md 中的协作规范
|
||
- ✅ 文档文件名使用中文
|
||
- ✅ 组件文档格式:`中文描述-组件名.md`
|
||
- ✅ 删除文件需移动到 `.recycle/YYYY-MM-DD/` 目录
|
||
|
||
---
|
||
|
||
## 可能的后续任务
|
||
|
||
1. **更新 AI_COLLABORATION.md**:移除已提取到独立文档的通用规范内容,保留项目特定信息
|
||
2. **补充其他语言规范**:如 Python、Go 等(如果需要)
|
||
3. **创建更多项目文档**:如架构设计文档、部署文档等
|
||
4. **检查文档一致性**:确保所有组件文档格式统一
|
||
|
||
---
|
||
|
||
## 继续会话的提示
|
||
|
||
```
|
||
我们在 /Users/yuding/WORK/LYZ/project/bimEngine/engine 项目中进行了文档整理工作:
|
||
|
||
1. 已将 docs/ 下的英文文档重命名为中文(组件文档格式:中文描述-组件名.md)
|
||
2. 已创建 docs/AI代码规范模板.md(通用规范,含 TS/Java/React/Vue 等)
|
||
3. 已创建 docs/国际化实现指南.md(项目独有的 i18n 规范)
|
||
|
||
相关文件:
|
||
- AI_COLLABORATION.md(项目主文档)
|
||
- docs/AI代码规范模板.md(通用规范模板)
|
||
- docs/国际化实现指南.md(国际化规范)
|
||
|
||
请继续按照 AI_COLLABORATION.md 的规范进行协作,使用中文交流。
|
||
```
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 6.4s)
|
||
|
||
```
|
||
Tool: todoread
|
||
|
||
**Input:**
|
||
```json
|
||
{}
|
||
```
|
||
**Output:**
|
||
```
|
||
[
|
||
{
|
||
"id": "1",
|
||
"content": "重写 AI代码规范模板.md - 扩展为完整版",
|
||
"status": "in_progress",
|
||
"priority": "high"
|
||
}
|
||
]
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 6.6s)
|
||
|
||
有一个任务仍在进行中。让我检查当前文件状态:
|
||
|
||
```
|
||
Tool: bash
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"command": "wc -l docs/AI代码规范模板.md",
|
||
"description": "Check file line count"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
2024 docs/AI代码规范模板.md
|
||
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 5.8s)
|
||
|
||
文件已有 2024 行,任务已完成。
|
||
|
||
```
|
||
Tool: todowrite
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"todos": [
|
||
{
|
||
"id": "1",
|
||
"content": "重写 AI代码规范模板.md - 扩展为完整版",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
[
|
||
{
|
||
"id": "1",
|
||
"content": "重写 AI代码规范模板.md - 扩展为完整版",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
}
|
||
]
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 8.5s)
|
||
|
||
---
|
||
|
||
**所有任务已完成。** 无待处理项。
|
||
|
||
### 完成状态
|
||
|
||
| 任务 | 状态 |
|
||
|-----|------|
|
||
| 文档中文化重命名 | ✅ |
|
||
| AI代码规范模板.md (2024行) | ✅ |
|
||
| 国际化实现指南.md | ✅ |
|
||
|
||
如有新需求,请告知。
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
Continue
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 10.8s)
|
||
|
||
无待处理任务。需要指定下一步:
|
||
|
||
1. **更新 AI_COLLABORATION.md** - 移除已提取的通用内容,保留项目特定信息
|
||
2. **检查组件文档一致性** - 确保格式统一
|
||
3. **其他任务** - 请说明
|
||
|
||
选择哪个?
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
上传一下代码
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 8.7s)
|
||
|
||
```
|
||
Tool: bash
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"command": "export CI=true DEBIAN_FRONTEND=noninteractive GIT_TERMINAL_PROMPT=0 GCM_INTERACTIVE=never HOMEBREW_NO_AUTO_UPDATE=1 GIT_EDITOR=: EDITOR=: VISUAL='' GIT_SEQUENCE_EDITOR=: GIT_MERGE_AUTOEDIT=no GIT_PAGER=cat PAGER=cat npm_config_yes=true PIP_NO_INPUT=1 YARN_ENABLE_IMMUTABLE_INSTALLS=false; git status",
|
||
"description": "Check git status for changes"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
On branch main
|
||
Your branch is up to date with 'origin/main'.
|
||
|
||
Changes not staged for commit:
|
||
(use "git add/rm <file>..." to update what will be committed)
|
||
(use "git restore <file>..." to discard changes in working directory)
|
||
deleted: demo/DEPLOY.md
|
||
deleted: docs/Dialog.md
|
||
deleted: docs/OptBtnGroups.md
|
||
deleted: docs/SDK_USAGE.md
|
||
deleted: docs/Toolbar.md
|
||
deleted: docs/components/button-group.md
|
||
deleted: docs/components/collapse.md
|
||
deleted: docs/components/description.md
|
||
deleted: docs/components/dialog.md
|
||
deleted: docs/components/engine.md
|
||
deleted: docs/components/measure-panel.md
|
||
deleted: docs/components/menu.md
|
||
deleted: docs/components/right-key.md
|
||
deleted: docs/components/tab.md
|
||
deleted: docs/components/tree.md
|
||
deleted: docs/components/walk-control-panel.md
|
||
deleted: docs/utils/icon-manager.md
|
||
|
||
Untracked files:
|
||
(use "git add <file>..." to include in what will be committed)
|
||
.recycle/2026-01-21/
|
||
"demo/\351\203\250\347\275\262\346\214\207\345\215\227.md"
|
||
"docs/AI\344\273\243\347\240\201\350\247\204\350\214\203\346\250\241\346\235\277.md"
|
||
"docs/SDK\344\275\277\347\224\250\346\214\207\345\215\227.md"
|
||
"docs/components/3D\345\274\225\346\223\216\347\273\204\344\273\266-Engine.md"
|
||
"docs/components/\345\217\263\351\224\256\350\217\234\345\215\225\347\273\204\344\273\266-RightKey.md"
|
||
"docs/components/\345\274\271\347\252\227\347\273\204\344\273\266-Dialog.md"
|
||
"docs/components/\346\212\230\345\217\240\351\235\242\346\235\277\347\273\204\344\273\266-Collapse.md"
|
||
"docs/components/\346\214\211\351\222\256\347\273\204\347\273\204\344\273\266-ButtonGroup.md"
|
||
"docs/components/\346\217\217\350\277\260\345\210\227\350\241\250\347\273\204\344\273\266-Description.md"
|
||
"docs/components/\346\240\207\347\255\276\351\241\265\347\273\204\344\273\266-Tab.md"
|
||
"docs/components/\346\240\221\345\275\242\346\216\247\344\273\266\347\273\204\344\273\266-Tree.md"
|
||
"docs/components/\346\265\213\351\207\217\351\235\242\346\235\277\347\273\204\344\273\266-MeasurePanel.md"
|
||
"docs/components/\346\274\253\346\270\270\346\216\247\345\210\266\351\235\242\346\235\277\347\273\204\344\273\266-WalkControlPanel.md"
|
||
"docs/components/\350\217\234\345\215\225\347\273\204\344\273\266-Menu.md"
|
||
"docs/utils/\345\233\276\346\240\207\347\256\241\347\220\206\345\231\250-IconManager.md"
|
||
"docs/\345\233\275\351\231\205\345\214\226\345\256\236\347\216\260\346\214\207\345\215\227.md"
|
||
|
||
no changes added to commit (use "git add" and/or "git commit -a")
|
||
|
||
```
|
||
```
|
||
|
||
```
|
||
Tool: bash
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"command": "export CI=true DEBIAN_FRONTEND=noninteractive GIT_TERMINAL_PROMPT=0 GCM_INTERACTIVE=never HOMEBREW_NO_AUTO_UPDATE=1 GIT_EDITOR=: EDITOR=: VISUAL='' GIT_SEQUENCE_EDITOR=: GIT_MERGE_AUTOEDIT=no GIT_PAGER=cat PAGER=cat npm_config_yes=true PIP_NO_INPUT=1 YARN_ENABLE_IMMUTABLE_INSTALLS=false; git log --oneline -5",
|
||
"description": "Check recent commit style"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
a930bc8 feat: 更新 SDK 构建产物和文档
|
||
a00d83e docs: 添加 SDK 使用文档
|
||
f6257f5 feat: 优化测量功能架构与引擎组件
|
||
cd1f818 初始化
|
||
a754a5a 初始化
|
||
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 9.6s)
|
||
|
||
```
|
||
Tool: bash
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"command": "export CI=true DEBIAN_FRONTEND=noninteractive GIT_TERMINAL_PROMPT=0 GCM_INTERACTIVE=never HOMEBREW_NO_AUTO_UPDATE=1 GIT_EDITOR=: EDITOR=: VISUAL='' GIT_SEQUENCE_EDITOR=: GIT_MERGE_AUTOEDIT=no GIT_PAGER=cat PAGER=cat npm_config_yes=true PIP_NO_INPUT=1 YARN_ENABLE_IMMUTABLE_INSTALLS=false; git add -A && git commit -m \"docs: 文档中文化重命名并添加规范文档\n\n- 将所有英文文档重命名为中文格式\n- 新增 AI代码规范模板.md(通用开发规范)\n- 新增 国际化实现指南.md(i18n 规范)\n- 重复文档移至 .recycle 目录\"",
|
||
"description": "Stage and commit all doc changes"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
[main 8d02741] docs: 文档中文化重命名并添加规范文档
|
||
19 files changed, 2572 insertions(+)
|
||
rename {docs => .recycle/2026-01-21/docs}/Dialog.md (100%)
|
||
rename {docs => .recycle/2026-01-21/docs}/OptBtnGroups.md (100%)
|
||
rename {docs => .recycle/2026-01-21/docs}/Toolbar.md (100%)
|
||
rename demo/DEPLOY.md => "demo/\351\203\250\347\275\262\346\214\207\345\215\227.md" (100%)
|
||
create mode 100644 "docs/AI\344\273\243\347\240\201\350\247\204\350\214\203\346\250\241\346\235\277.md"
|
||
rename docs/SDK_USAGE.md => "docs/SDK\344\275\277\347\224\250\346\214\207\345\215\227.md" (100%)
|
||
rename docs/components/engine.md => "docs/components/3D\345\274\225\346\223\216\347\273\204\344\273\266-Engine.md" (100%)
|
||
rename docs/components/right-key.md => "docs/components/\345\217\263\351\224\256\350\217\234\345\215\225\347\273\204\344\273\266-RightKey.md" (100%)
|
||
rename docs/components/dialog.md => "docs/components/\345\274\271\347\252\227\347\273\204\344\273\266-Dialog.md" (100%)
|
||
rename docs/components/collapse.md => "docs/components/\346\212\230\345\217\240\351\235\242\346\235\277\347\273\204\344\273\266-Collapse.md" (100%)
|
||
rename docs/components/button-group.md => "docs/components/\346\214\211\351\222\256\347\273\204\347\273\204\344\273\266-ButtonGroup.md" (100%)
|
||
rename docs/components/description.md => "docs/components/\346\217\217\350\277\260\345\210\227\350\241\250\347\273\204\344\273\266-Description.md" (100%)
|
||
rename docs/components/tab.md => "docs/components/\346\240\207\347\255\276\351\241\265\347\273\204\344\273\266-Tab.md" (100%)
|
||
rename docs/components/tree.md => "docs/components/\346\240\221\345\275\242\346\216\247\344\273\266\347\273\204\344\273\266-Tree.md" (100%)
|
||
rename docs/components/measure-panel.md => "docs/components/\346\265\213\351\207\217\351\235\242\346\235\277\347\273\204\344\273\266-MeasurePanel.md" (100%)
|
||
rename docs/components/walk-control-panel.md => "docs/components/\346\274\253\346\270\270\346\216\247\345\210\266\351\235\242\346\235\277\347\273\204\344\273\266-WalkControlPanel.md" (100%)
|
||
rename docs/components/menu.md => "docs/components/\350\217\234\345\215\225\347\273\204\344\273\266-Menu.md" (100%)
|
||
rename docs/utils/icon-manager.md => "docs/utils/\345\233\276\346\240\207\347\256\241\347\220\206\345\231\250-IconManager.md" (100%)
|
||
create mode 100644 "docs/\345\233\275\351\231\205\345\214\226\345\256\236\347\216\260\346\214\207\345\215\227.md"
|
||
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 6.9s)
|
||
|
||
```
|
||
Tool: bash
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"command": "export CI=true DEBIAN_FRONTEND=noninteractive GIT_TERMINAL_PROMPT=0 GCM_INTERACTIVE=never HOMEBREW_NO_AUTO_UPDATE=1 GIT_EDITOR=: EDITOR=: VISUAL='' GIT_SEQUENCE_EDITOR=: GIT_MERGE_AUTOEDIT=no GIT_PAGER=cat PAGER=cat npm_config_yes=true PIP_NO_INPUT=1 YARN_ENABLE_IMMUTABLE_INSTALLS=false; export CI=true DEBIAN_FRONTEND=noninteractive GIT_TERMINAL_PROMPT=0 GCM_INTERACTIVE=never HOMEBREW_NO_AUTO_UPDATE=1 GIT_EDITOR=: EDITOR=: VISUAL='' GIT_SEQUENCE_EDITOR=: GIT_MERGE_AUTOEDIT=no GIT_PAGER=cat PAGER=cat npm_config_yes=true PIP_NO_INPUT=1 YARN_ENABLE_IMMUTABLE_INSTALLS=false; git push",
|
||
"description": "Push to remote"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
remote: . Processing 1 references
|
||
remote: Processed 1 references in total
|
||
To http://123.60.156.158:13000/bim_engine/bimEngine.git
|
||
a930bc8..8d02741 main -> main
|
||
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 5.1s)
|
||
|
||
✅ **已推送**
|
||
|
||
```
|
||
8d02741 docs: 文档中文化重命名并添加规范文档
|
||
19 files changed, 2572 insertions(+)
|
||
```
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
现在我想要增加一个buttongroup的样式,就是toolbar的样式要改变一下,你看下要怎么改造,我希望之后toolbar可以通过不通用的配置改变它的样式。现在这样样式要改成这样子,下面这个文档吗,你只需要看里面的样式就行。不用看业务。[Pasted ~812 lines] # 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 (错误状态)
|
||
```
|
||
|
||
---
|
||
|
||
**文档编写完成**
|
||
如有任何疑问或需要补充的内容,请联系开发团队。
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 12.7s)
|
||
|
||
我来分析文档中的样式需求,并查看现有 ButtonGroup 组件的结构。
|
||
|
||
```
|
||
Tool: glob
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"pattern": "**/*ButtonGroup*"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
Found 1 file(s)
|
||
|
||
./docs/components/按钮组组件-ButtonGroup.md
|
||
[Agent Usage Reminder]
|
||
|
||
You called a search/fetch tool directly without leveraging specialized agents.
|
||
|
||
RECOMMENDED: Use background_task with explore/librarian agents for better results:
|
||
|
||
```
|
||
// Parallel exploration - fire multiple agents simultaneously
|
||
background_task(agent="explore", prompt="Find all files matching pattern X")
|
||
background_task(agent="explore", prompt="Search for implementation of Y")
|
||
background_task(agent="librarian", prompt="Lookup documentation for Z")
|
||
|
||
// Then continue your work while they run in background
|
||
// System will notify you when each completes
|
||
```
|
||
|
||
WHY:
|
||
- Agents can perform deeper, more thorough searches
|
||
- Background tasks run in parallel, saving time
|
||
- Specialized agents have domain expertise
|
||
- Reduces context window usage in main session
|
||
|
||
ALWAYS prefer: Multiple parallel background_task calls > Direct tool calls
|
||
|
||
```
|
||
```
|
||
|
||
```
|
||
Tool: read
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/docs/components/按钮组组件-ButtonGroup.md"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
<file>
|
||
00001| # ButtonGroup 组件详细文档
|
||
00002|
|
||
00003| > 本文档详细描述 ButtonGroup 组件的实现细节,包括 API、UI 结构、逻辑流程等,供 AI 根据文档重现组件。
|
||
00004|
|
||
00005| ---
|
||
00006|
|
||
00007| ## 1. 组件概述
|
||
00008|
|
||
00009| ### 1.1 基本信息
|
||
00010| - **组件名称**: `BimButtonGroup`
|
||
00011| - **文件路径**: `src/components/button-group/index.ts`
|
||
00012| - **类型定义**: `src/components/button-group/index.type.ts`
|
||
00013| - **样式文件**: `src/components/button-group/index.css`
|
||
00014| - **实现接口**: `IBimComponent`
|
||
00015| - **用途**: 提供通用的按钮组组件,支持按钮、菜单、分组、嵌套菜单等功能
|
||
00016|
|
||
00017| ### 1.2 在 SDK 中的位置
|
||
00018| - ButtonGroup 组件是独立的 UI 组件
|
||
00019| - 必须通过 `ButtonGroupManager` 或 `ToolbarManager` 使用,不允许直接使用
|
||
00020| - `ButtonGroupManager` 位于 `src/managers/button-group-manager.ts`
|
||
00021| - `ToolbarManager` 位于 `src/managers/toolbar-manager.ts`
|
||
00022|
|
||
00023| ---
|
||
00024|
|
||
00025| ## 2. 组件类 API 文档
|
||
00026|
|
||
00027| ### 2.1 构造函数
|
||
00028|
|
||
00029| ```typescript
|
||
00030| constructor(options: ButtonGroupOptions)
|
||
00031| ```
|
||
00032|
|
||
00033| **参数**:
|
||
00034| - `options`: `ButtonGroupOptions` - 按钮组配置选项(详见类型定义)
|
||
00035|
|
||
00036| **默认配置**:
|
||
00037| ```typescript
|
||
00038| {
|
||
00039| showLabel: true,
|
||
00040| visibility: {},
|
||
00041| direction: 'row', // 默认横向排列
|
||
00042| position: 'static', // 默认静态定位
|
||
00043| align: 'vertical', // 默认图标在上
|
||
00044| expand: 'down' // 默认向下展开
|
||
00045| }
|
||
00046| ```
|
||
00047|
|
||
00048| **行为**:
|
||
00049| - 解析容器元素(支持字符串 ID 或 HTMLElement)
|
||
00050| - 合并用户配置和默认配置
|
||
00051| - 记录用户自定义的颜色属性
|
||
00052| - 初始化容器
|
||
00053| - 应用样式
|
||
00054|
|
||
00055| ### 2.2 公共方法
|
||
00056|
|
||
00057| #### `setEngine(engine: BimEngine): void`
|
||
00058| 设置引擎实例
|
||
00059|
|
||
00060| **参数**:
|
||
00061| - `engine`: `BimEngine` - 引擎实例
|
||
00062|
|
||
00063| **功能**:
|
||
00064| - 保存引擎引用,用于发送事件
|
||
00065|
|
||
00066| #### `init(): Promise<void>`
|
||
00067| 初始化组件功能(实现 `IBimComponent` 接口)
|
||
00068|
|
||
00069| **功能**:
|
||
00070| - 调用 `render()` 渲染按钮组
|
||
00071| - 订阅语言变更:`localeManager.subscribe()`
|
||
00072| - 订阅主题变更:`themeManager.subscribe()`
|
||
00073|
|
||
00074| **返回**: Promise(异步方法,但当前实现是同步的)
|
||
00075|
|
||
00076| #### `setTheme(theme: ThemeConfig): void`
|
||
00077| 设置主题(实现 `IBimComponent` 接口)
|
||
00078|
|
||
00079| **参数**:
|
||
00080| - `theme`: `ThemeConfig` - 全局主题配置
|
||
00081|
|
||
00082| **功能**:
|
||
00083| - 将主题颜色映射到按钮组颜色
|
||
00084| - **只应用没有被用户自定义的颜色属性**
|
||
00085| - 如果用户已自定义颜色,保持用户自定义
|
||
00086|
|
||
00087| **主题颜色映射**:
|
||
00088| - `theme.panelBackground` → `backgroundColor`
|
||
00089| - `theme.componentBackground` → `btnBackgroundColor`
|
||
00090| - `theme.componentHover` → `btnHoverColor`
|
||
00091| - `theme.componentActive` → `btnActiveColor`
|
||
00092| - `theme.icon` → `iconColor`
|
||
00093| - `theme.iconActive` → `iconActiveColor`
|
||
00094| - `theme.textSecondary` → `textColor`
|
||
00095| - `theme.textPrimary` → `textActiveColor`
|
||
00096|
|
||
00097| #### `setLocales(): void`
|
||
00098| 设置语言(实现 `IBimComponent` 接口)
|
||
00099|
|
||
00100| **功能**:
|
||
00101| - 调用 `render()` 重新渲染,更新所有按钮的标签文本
|
||
00102|
|
||
00103| #### `setColors(colors: ButtonGroupColors): void`
|
||
00104| 直接设置颜色(强制覆盖)
|
||
00105|
|
||
00106| **参数**:
|
||
00107| - `colors`: `ButtonGroupColors` - 颜色配置对象
|
||
00108|
|
||
00109| **功能**:
|
||
00110| - 更新配置选项
|
||
00111| - **标记这些颜色为自定义**,后续的 `setTheme()` 不会覆盖它们
|
||
00112| - 应用样式
|
||
00113|
|
||
00114| #### `setBackgroundColor(color: string): void`
|
||
00115| 设置背景颜色
|
||
00116|
|
||
00117| **参数**:
|
||
00118| - `color`: `string` - 背景颜色值
|
||
00119|
|
||
00120| **功能**:
|
||
00121| - 调用 `setColors({ backgroundColor: color })`
|
||
00122|
|
||
00123| #### `addGroup(groupId: string, beforeGroupId?: string): void`
|
||
00124| 添加按钮组
|
||
00125|
|
||
00126| **参数**:
|
||
00127| - `groupId`: `string` - 组 ID(必须唯一)
|
||
00128| - `beforeGroupId`: `string` (可选) - 在此组之前插入
|
||
00129|
|
||
00130| **功能**:
|
||
00131| - 创建新的按钮组
|
||
00132| - 如果组已存在,忽略
|
||
00133| - 如果指定了 `beforeGroupId`,在该组之前插入;否则追加到末尾
|
||
00134|
|
||
00135| #### `addButton(config: ButtonConfig): void`
|
||
00136| 添加按钮
|
||
00137|
|
||
00138| **参数**:
|
||
00139| - `config`: `ButtonConfig` - 按钮配置
|
||
00140|
|
||
00141| **功能**:
|
||
00142| - 将按钮添加到指定组
|
||
00143| - 如果指定了 `parentId`,将按钮添加为父按钮的子项(嵌套菜单)
|
||
00144| - 如果未指定 `parentId`,将按钮添加到组的根级别
|
||
00145|
|
||
00146| **按钮类型**:
|
||
00147| - `type: 'button'` - 普通按钮
|
||
00148| - `type: 'menu'` - 菜单按钮(有子项)
|
||
00149|
|
||
00150| #### `render(): void`
|
||
00151| 渲染按钮组
|
||
00152|
|
||
00153| **功能**:
|
||
00154| - 清空容器
|
||
00155| - 清空按钮引用映射
|
||
00156| - 遍历所有组,渲染每个组
|
||
00157| - 每个组渲染为一个 `.bim-btn-group-section` 元素
|
||
00158|
|
||
00159| #### `updateButtonVisibility(id: string, visible: boolean): void`
|
||
00160| 更新按钮可见性
|
||
00161|
|
||
00162| **参数**:
|
||
00163| - `id`: `string` - 按钮 ID
|
||
00164| - `visible`: `boolean` - 是否可见
|
||
00165|
|
||
00166| **功能**:
|
||
00167| - 更新 `options.visibility` 对象
|
||
00168| - 调用 `render()` 重新渲染
|
||
00169|
|
||
00170| #### `setShowLabel(show: boolean): void`
|
||
00171| 设置是否显示标签
|
||
00172|
|
||
00173| **参数**:
|
||
00174| - `show`: `boolean` - 是否显示标签
|
||
00175|
|
||
00176| **功能**:
|
||
00177| - 更新 `options.showLabel`
|
||
00178| - 调用 `updateLabelsVisibility()` 更新所有按钮的标签显示状态
|
||
00179|
|
||
00180| #### `destroy(): void`
|
||
00181| 销毁组件(实现 `IBimComponent` 接口)
|
||
00182|
|
||
00183| **功能**:
|
||
00184| - 取消语言和主题订阅
|
||
00185| - 关闭下拉菜单(如果打开)
|
||
00186| - 清空容器
|
||
00187| - 清空按钮引用映射
|
||
00188|
|
||
00189| ### 2.3 受保护方法
|
||
00190|
|
||
00191| #### `emit<K extends keyof EngineEvents>(event: K, payload: EngineEvents[K]): void`
|
||
00192| 发送事件
|
||
00193|
|
||
00194| **功能**:
|
||
00195| - 如果引擎已设置,通过引擎发送事件
|
||
00196| - 如果引擎未设置,输出警告
|
||
00197|
|
||
00198| ### 2.4 私有方法(供理解实现细节)
|
||
00199|
|
||
00200| #### `initContainer(): void`
|
||
00201| 初始化容器
|
||
00202|
|
||
00203| **功能**:
|
||
00204| - 清空容器内容
|
||
00205| - 添加 `bim-btn-group-root` 类
|
||
00206| - 根据 `direction` 添加 `dir-row` 或 `dir-column` 类
|
||
00207| - 如果提供了 `className`,添加自定义类
|
||
00208| - 调用 `updatePosition()` 更新位置
|
||
00209|
|
||
00210| #### `updatePosition(): void`
|
||
00211| 更新容器位置
|
||
00212|
|
||
00213| **功能**:
|
||
00214| - 根据 `position` 选项设置容器位置
|
||
00215| - 支持预设位置(如 'center', 'top-left' 等)或坐标对象 `{ x, y }`
|
||
00216| - 如果 `position` 是 'static',使用相对定位
|
||
00217|
|
||
00218| **位置计算逻辑**:
|
||
00219| - `static`: 使用相对定位,不设置绝对定位属性
|
||
00220| - `{ x, y }`: 直接使用坐标值
|
||
00221| - 预设位置: 使用 `margin: 20px` 和 `transform` 实现定位
|
||
00222|
|
||
00223| #### `applyStyles(): void`
|
||
00224| 应用样式到容器
|
||
00225|
|
||
00226| **功能**:
|
||
00227| - 将配置的颜色值设置到 CSS 变量
|
||
00228| - CSS 变量映射:
|
||
00229| - `backgroundColor` → `--bim-btn-group-section-bg`
|
||
00230| - `btnBackgroundColor` → `--bim-btn-bg`
|
||
00231| - `btnHoverColor` → `--bim-btn-hover-bg`
|
||
00232| - `btnActiveColor` → `--bim-btn-active-bg`
|
||
00233| - `iconColor` → `--bim-icon-color`
|
||
00234| - `iconActiveColor` → `--bim-icon-active-color`
|
||
00235| - `textColor` → `--bim-btn-text-color`
|
||
00236| - `textActiveColor` → `--bim-btn-text-active-color`
|
||
00237|
|
||
00238| #### `renderGroup(group: ButtonGroup, index: number, total: number): HTMLElement`
|
||
00239| 渲染按钮组
|
||
00240|
|
||
00241| **参数**:
|
||
00242| - `group`: `ButtonGroup` - 按钮组对象
|
||
00243| - `index`: `number` - 组索引
|
||
00244| - `total`: `number` - 总组数
|
||
00245|
|
||
00246| **返回**: 组元素
|
||
00247|
|
||
00248| **功能**:
|
||
00249| - 创建 `.bim-btn-group-section` 元素
|
||
00250| - 如果不是最后一组,添加 `has-divider` 类(用于显示分隔线)
|
||
00251| - 遍历组内的按钮,渲染每个可见的按钮
|
||
00252|
|
||
00253| #### `renderButton(button: OptButton): HTMLElement`
|
||
00254| 渲染单个按钮
|
||
00255|
|
||
00256| **参数**:
|
||
00257| - `button`: `OptButton` - 按钮配置
|
||
00258|
|
||
00259| **返回**: 按钮包装元素
|
||
00260|
|
||
00261| **功能**:
|
||
00262| - 创建 `.opt-btn-wrapper` 和 `.opt-btn` 元素
|
||
00263| - 根据按钮的 `align` 或全局 `align` 设置布局方向
|
||
00264| - 如果按钮是激活状态,添加 `active` 类
|
||
00265| - 如果按钮是禁用状态,添加 `disabled` 类
|
||
00266| - 如果按钮没有标签,添加 `no-label` 类
|
||
00267| - 创建图标元素(`.opt-btn-icon`)
|
||
00268| - 创建文字包装器(`.opt-btn-text-wrapper`),包含标签和箭头(如果有子项)
|
||
00269| - 绑定点击、鼠标进入、鼠标离开事件
|
||
00270| - 保存按钮引用到 `btnRefs` Map
|
||
00271|
|
||
00272| #### `renderDropdownItem(button: OptButton): HTMLElement`
|
||
00273| 渲染下拉菜单项
|
||
00274|
|
||
00275| **参数**:
|
||
00276| - `button`: `OptButton` - 按钮配置
|
||
00277|
|
||
00278| **返回**: 菜单项元素
|
||
00279|
|
||
00280| **功能**:
|
||
00281| - 创建 `.opt-btn-dropdown-item` 元素
|
||
00282| - 根据按钮的 `align` 设置布局方向(默认 'horizontal')
|
||
00283| - 创建图标和标签
|
||
00284| - 绑定点击事件
|
||
00285|
|
||
00286| #### `handleClick(button: OptButton): void`
|
||
00287| 处理按钮点击
|
||
00288|
|
||
00289| **功能**:
|
||
00290| - 如果按钮是禁用状态,忽略
|
||
00291| - 如果按钮没有子项:
|
||
00292| - 如果 `keepActive` 为 true,切换激活状态
|
||
00293| - 关闭下拉菜单
|
||
00294| - 调用 `onClick` 回调(如果存在)
|
||
00295| - 如果按钮有子项,不处理(由鼠标悬停处理)
|
||
00296|
|
||
00297| #### `handleMouseEnter(button: OptButton, btnEl: HTMLElement): void`
|
||
00298| 处理鼠标进入
|
||
00299|
|
||
00300| **功能**:
|
||
00301| - 清除悬停超时
|
||
00302| - 如果按钮有子项,显示下拉菜单
|
||
00303| - 如果按钮没有子项,关闭下拉菜单
|
||
00304|
|
||
00305| #### `handleMouseLeave(): void`
|
||
00306| 处理鼠标离开
|
||
00307|
|
||
00308| **功能**:
|
||
00309| - 设置 200ms 延迟后关闭下拉菜单(防止鼠标移动到菜单时关闭)
|
||
00310|
|
||
00311| #### `showDropdown(button: OptButton, btnEl: HTMLElement): void`
|
||
00312| 显示下拉菜单
|
||
00313|
|
||
00314| **功能**:
|
||
00315| - 关闭当前打开的下拉菜单
|
||
00316| - 创建 `.opt-btn-dropdown` 元素
|
||
00317| - 根据主按钮组的方向设置菜单的布局方向:
|
||
00318| - 如果主组是横向(`direction: 'row'`),菜单纵向排列
|
||
00319| - 如果主组是纵向(`direction: 'column'`),菜单横向排列
|
||
00320| - 渲染所有可见的子按钮
|
||
00321| - 计算菜单位置(根据 `expand` 选项):
|
||
00322| - `up`: 向上展开,与按钮水平居中对齐
|
||
00323| - `down`: 向下展开,与按钮水平居中对齐
|
||
00324| - `right`: 向右展开,与按钮垂直居中对齐
|
||
00325| - `left`: 向左展开,与按钮垂直居中对齐
|
||
00326| - 将菜单添加到 `document.body`(绝对定位)
|
||
00327| - 绑定鼠标进入和离开事件(保持菜单打开)
|
||
00328|
|
||
00329| #### `closeDropdown(): void`
|
||
00330| 关闭下拉菜单
|
||
00331|
|
||
00332| **功能**:
|
||
00333| - 移除下拉菜单元素
|
||
00334| - 移除所有按钮箭头元素的 `rotated` 类
|
||
00335|
|
||
00336| #### `updateButtonState(buttonId: string): void`
|
||
00337| 更新按钮状态
|
||
00338|
|
||
00339| **功能**:
|
||
00340| - 根据 `activeBtnIds` Set 更新按钮的 `active` 类
|
||
00341|
|
||
00342| #### `findButton(buttons: OptButton[], id: string): OptButton | undefined`
|
||
00343| 查找按钮(递归)
|
||
00344|
|
||
00345| **功能**:
|
||
00346| - 在按钮数组中查找指定 ID 的按钮
|
||
00347| - 递归查找子按钮
|
||
00348|
|
||
00349| #### `findButtonById(id: string): OptButton | undefined`
|
||
00350| 根据 ID 查找按钮
|
||
00351|
|
||
00352| **功能**:
|
||
00353| - 在所有组中查找指定 ID 的按钮
|
||
00354|
|
||
00355| #### `isVisible(id: string): boolean`
|
||
00356| 检查按钮是否可见
|
||
00357|
|
||
00358| **功能**:
|
||
00359| - 检查 `options.visibility` 对象
|
||
00360| - 如果未设置或为 true,返回 true
|
||
00361| - 如果设置为 false,返回 false
|
||
00362|
|
||
00363| #### `getIcon(icon?: string): string`
|
||
00364| 获取图标 HTML
|
||
00365|
|
||
00366| **功能**:
|
||
00367| - 如果提供了图标,返回图标 HTML
|
||
00368| - 否则返回默认图标
|
||
00369|
|
||
00370| #### `updateLabelsVisibility(): void`
|
||
00371| 更新标签可见性
|
||
00372|
|
||
00373| **功能**:
|
||
00374| - 遍历所有按钮引用
|
||
00375| - 根据 `showLabel` 选项和按钮是否有标签,更新 `no-label` 类
|
||
00376|
|
||
00377| ---
|
||
00378|
|
||
00379| ## 3. 分化组件说明
|
||
00380|
|
||
00381| ### 3.1 Toolbar
|
||
00382|
|
||
00383| **文件路径**: `src/components/button-group/toolbar/index.ts`
|
||
00384|
|
||
00385| **继承关系**: `Toolbar extends BimButtonGroup`
|
||
00386|
|
||
00387| **特殊功能**:
|
||
00388| - 预定义了底部工具栏的配置
|
||
00389| - 自动加载默认按钮(首页、漫游、定位、设置、信息等)
|
||
00390| - 使用固定的配置(底部居中、横向排列、图标在上、向上展开)
|
||
00391|
|
||
00392| **实现方式**:
|
||
00393| - 重写 `init()` 方法
|
||
00394| - 先调用 `super.init()`
|
||
00395| - 动态导入所有默认按钮配置
|
||
00396| - 创建两个组('group-1' 和 'group-2')
|
||
00397| - 添加所有默认按钮
|
||
00398| - 调用 `render()` 渲染
|
||
00399|
|
||
00400| **默认按钮列表**:
|
||
00401| - `group-1`:
|
||
00402| - `home` - 首页按钮(通过工厂函数创建,注入 engine)
|
||
00403| - `walk-menu` - 漫游菜单
|
||
00404| - `walk-person` - 人视
|
||
00405| - `walk-bird` - 鸟瞰
|
||
00406| - `location` - 定位
|
||
00407| - `group-2`:
|
||
00408| - `setting` - 设置
|
||
00409| - `info` - 信息
|
||
00410|
|
||
00411| **与父类的差异**:
|
||
00412| - 固定了配置选项(`position: 'bottom-center'`, `direction: 'row'`, `align: 'vertical'`, `expand: 'up'`)
|
||
00413| - 自动加载默认按钮
|
||
00414| - 其他功能完全继承自 `BimButtonGroup`
|
||
00415|
|
||
00416| ---
|
||
00417|
|
||
00418| ## 4. Manager API 文档
|
||
00419|
|
||
00420| ### 4.1 ButtonGroupManager 类
|
||
00421|
|
||
00422| **文件路径**: `src/managers/button-group-manager.ts`
|
||
00423|
|
||
00424| **继承关系**: `ButtonGroupManager extends BimComponent`
|
||
00425|
|
||
00426| #### 构造函数
|
||
00427|
|
||
00428| ```typescript
|
||
00429| constructor(engine: BimEngine, container: HTMLElement)
|
||
00430| ```
|
||
00431|
|
||
00432| **参数**:
|
||
00433| - `engine`: `BimEngine` - 引擎实例
|
||
00434| - `container`: `HTMLElement` - 按钮组挂载的目标容器
|
||
00435|
|
||
00436| #### 公共方法
|
||
00437|
|
||
00438| ##### `create(id: string, options: Omit<ButtonGroupOptions, 'container'>): BimButtonGroup`
|
||
00439| 创建按钮组
|
||
00440|
|
||
00441| **参数**:
|
||
00442| - `id`: `string` - 按钮组唯一标识
|
||
00443| - `options`: `Omit<ButtonGroupOptions, 'container'>` - 配置选项(不需要传 container)
|
||
00444|
|
||
00445| **返回**: `BimButtonGroup` 实例
|
||
00446|
|
||
00447| **功能**:
|
||
00448| - 自动使用 Manager 绑定的容器
|
||
00449| - 创建 `BimButtonGroup` 实例
|
||
00450| - 注入引擎实例
|
||
00451| - 调用 `init()` 初始化
|
||
00452| - 将按钮组保存到 Map 中
|
||
00453|
|
||
00454| ##### `get(id: string): BimButtonGroup | undefined`
|
||
00455| 获取按钮组
|
||
00456|
|
||
00457| **参数**:
|
||
00458| - `id`: `string` - 按钮组 ID
|
||
00459|
|
||
00460| **返回**: `BimButtonGroup` 实例或 undefined
|
||
00461|
|
||
00462| ##### `updateTheme(theme: ThemeConfig): void`
|
||
00463| 更新所有按钮组的主题
|
||
00464|
|
||
00465| **参数**:
|
||
00466| - `theme`: `ThemeConfig` - 主题配置
|
||
00467|
|
||
00468| ##### `destroy(): void`
|
||
00469| 销毁管理器
|
||
00470|
|
||
00471| **功能**:
|
||
00472| - 销毁所有按钮组
|
||
00473| - 清空 Map
|
||
00474|
|
||
00475| ### 4.2 ToolbarManager 类
|
||
00476|
|
||
00477| **文件路径**: `src/managers/toolbar-manager.ts`
|
||
00478|
|
||
00479| **继承关系**: `ToolbarManager extends BimComponent`
|
||
00480|
|
||
00481| #### 构造函数
|
||
00482|
|
||
00483| ```typescript
|
||
00484| constructor(engine: BimEngine, container: HTMLElement)
|
||
00485| ```
|
||
00486|
|
||
00487| **参数**:
|
||
00488| - `engine`: `BimEngine` - 引擎实例
|
||
00489| - `container`: `HTMLElement` - 工具栏挂载的目标容器
|
||
00490|
|
||
00491| **行为**:
|
||
00492| - 自动调用 `init()` 创建工具栏
|
||
00493|
|
||
00494| #### 公共方法
|
||
00495|
|
||
00496| ##### `updateTheme(theme: ThemeConfig): void`
|
||
00497| 更新工具栏主题
|
||
00498|
|
||
00499| ##### `refresh(): void`
|
||
00500| 刷新工具栏
|
||
00501|
|
||
00502| **功能**:
|
||
00503| - 调用工具栏的 `render()` 方法重新渲染
|
||
00504|
|
||
00505| ##### `addGroup(groupId: string, beforeGroupId?: string): void`
|
||
00506| 添加按钮组
|
||
00507|
|
||
00508| **功能**:
|
||
00509| - 转发到工具栏的 `addGroup()` 方法
|
||
00510| - 自动调用 `render()` 重新渲染
|
||
00511|
|
||
00512| ##### `addButton(config: ButtonConfig): void`
|
||
00513| 添加按钮
|
||
00514|
|
||
00515| **功能**:
|
||
00516| - 转发到工具栏的 `addButton()` 方法
|
||
00517| - 自动调用 `render()` 重新渲染
|
||
00518|
|
||
00519| ##### `setButtonVisibility(id: string, v: boolean): void`
|
||
00520| 设置按钮可见性
|
||
00521|
|
||
00522| **功能**:
|
||
00523| - 转发到工具栏的 `updateButtonVisibility()` 方法
|
||
00524|
|
||
00525| ##### `setShowLabel(show: boolean): void`
|
||
00526| 设置是否显示标签
|
||
00527|
|
||
00528| **功能**:
|
||
00529| - 转发到工具栏的 `setShowLabel()` 方法
|
||
00530|
|
||
00531| ##### `setVisible(visible: boolean): void`
|
||
00532| 设置工具栏可见性
|
||
00533|
|
||
00534| **功能**:
|
||
00535| - 通过设置容器的 `visibility` CSS 属性控制显示/隐藏
|
||
00536|
|
||
00537| ##### `setBackgroundColor(color: string): void`
|
||
00538| 设置背景颜色
|
||
00539|
|
||
00540| **功能**:
|
||
00541| - 转发到工具栏的 `setBackgroundColor()` 方法
|
||
00542|
|
||
00543| ##### `setColors(colors: ButtonGroupColors): void`
|
||
00544| 设置颜色
|
||
00545|
|
||
00546| **功能**:
|
||
00547| - 转发到工具栏的 `setColors()` 方法
|
||
00548|
|
||
00549| ##### `destroy(): void`
|
||
00550| 销毁管理器
|
||
00551|
|
||
00552| **功能**:
|
||
00553| - 销毁工具栏实例
|
||
00554|
|
||
00555| ---
|
||
00556|
|
||
00557| ## 5. UI 详细描述
|
||
00558|
|
||
00559| ### 5.1 DOM 结构
|
||
00560|
|
||
00561| **完整 HTML 结构**:
|
||
00562| ```html
|
||
00563| <div class="bim-btn-group-root [dir-row|dir-column] [static] [className]">
|
||
00564| <!-- 按钮组 1 -->
|
||
00565| <div class="bim-btn-group-section [has-divider]">
|
||
00566| <!-- 按钮包装器 -->
|
||
00567| <div class="opt-btn-wrapper">
|
||
00568| <div class="opt-btn [align-vertical|align-horizontal] [active] [disabled] [no-label]">
|
||
00569| <div class="opt-btn-icon">[图标 SVG]</div>
|
||
00570| <div class="opt-btn-text-wrapper">
|
||
00571| <span class="opt-btn-label">标签文本</span>
|
||
00572| [<span class="opt-btn-arrow [rotated]">▼</span>] <!-- 如果有子项 -->
|
||
00573| </div>
|
||
00574| </div>
|
||
00575| </div>
|
||
00576| <!-- 更多按钮... -->
|
||
00577| </div>
|
||
00578|
|
||
00579| <!-- 按钮组 2 -->
|
||
00580| <div class="bim-btn-group-section">
|
||
00581| <!-- 更多按钮... -->
|
||
00582| </div>
|
||
00583| </div>
|
||
00584|
|
||
00585| <!-- 下拉菜单(动态创建,添加到 document.body) -->
|
||
00586| <div class="opt-btn-dropdown" style="[位置样式]">
|
||
00587| <div class="opt-btn-dropdown-item [align-horizontal|align-vertical]">
|
||
00588| <div class="opt-btn-icon">[图标 SVG]</div>
|
||
00589| <span class="opt-btn-dropdown-label">标签文本</span>
|
||
00590| </div>
|
||
00591| <!-- 更多菜单项... -->
|
||
00592| </div>
|
||
00593| ```
|
||
00594|
|
||
00595| ### 5.2 CSS 类名和样式
|
||
00596|
|
||
00597| #### `.bim-btn-group-root` (根容器)
|
||
00598| - `display: flex` - Flex 布局
|
||
00599| - `gap: 8px` - 组之间的间距
|
||
00600| - `z-index: 1000` - 层级
|
||
00601| - `position: absolute` - 绝对定位(除非是 static)
|
||
00602| - `pointer-events: auto` - 确保可接收事件
|
||
00603|
|
||
00604| #### `.bim-btn-group-root.static` (静态定位)
|
||
00605| - `position: relative` - 相对定位
|
||
00606| - 清除所有绝对定位属性
|
||
00607|
|
||
00608| #### `.bim-btn-group-root.dir-row` (横向排列)
|
||
00609| - `flex-direction: row` - 横向排列
|
||
00610| - `align-items: center` - 垂直居中
|
||
00611|
|
||
00612| #### `.bim-btn-group-root.dir-column` (纵向排列)
|
||
00613| - `flex-direction: column` - 纵向排列
|
||
00614| - `align-items: stretch` - 拉伸对齐
|
||
00615|
|
||
00616| #### `.bim-btn-group-section` (按钮组)
|
||
00617| - `display: flex` - Flex 布局
|
||
00618| - `gap: 4px` - 按钮之间的间距
|
||
00619| - `background-color: var(--bim-btn-group-section-bg)` - 背景色
|
||
00620| - `border-radius: 6px` - 圆角
|
||
00621| - `padding: 4px` - 内边距
|
||
00622| - `box-shadow` - 阴影效果
|
||
00623|
|
||
00624| #### `.opt-btn-wrapper` (按钮包装器)
|
||
00625| - `position: relative` - 相对定位(为下拉菜单提供定位参考)
|
||
00626|
|
||
00627| #### `.opt-btn` (按钮本体)
|
||
00628| - `display: flex` - Flex 布局
|
||
00629| - `cursor: pointer` - 指针光标
|
||
00630| - `border-radius: 4px` - 圆角
|
||
00631| - `transition: background-color 0.2s, color 0.2s` - 过渡动画
|
||
00632| - `padding: 6px` - 内边距
|
||
00633| - `align-items: center` - 垂直居中
|
||
00634| - `justify-content: center` - 水平居中
|
||
00635|
|
||
00636| #### `.opt-btn:hover` (按钮悬停)
|
||
00637| - `background-color: var(--bim-btn-hover-bg)` - 悬停背景色
|
||
00638|
|
||
00639| #### `.opt-btn.active` (按钮激活)
|
||
00640| - `background-color: var(--bim-btn-active-bg)` - 激活背景色
|
||
00641| - `color: var(--bim-btn-text-active-color)` - 激活文字颜色
|
||
00642|
|
||
00643| #### `.opt-btn.disabled` (按钮禁用)
|
||
00644| - `opacity: 0.5` - 半透明
|
||
00645| - `cursor: not-allowed` - 禁止光标
|
||
00646|
|
||
00647| #### `.opt-btn-icon` (图标)
|
||
00648| - `width/height: var(--bim-icon-size)` - 图标尺寸
|
||
00649| - `display: flex` - Flex 布局
|
||
00650| - `align-items: center` - 垂直居中
|
||
00651| - `justify-content: center` - 水平居中
|
||
00652| - `color: var(--bim-icon-color)` - 图标颜色
|
||
00653| - `flex-shrink: 0` - 不收缩
|
||
00654|
|
||
00655| #### `.opt-btn-text-wrapper` (文字包装器)
|
||
00656| - `display: flex` - Flex 布局
|
||
00657| - `align-items: center` - 垂直居中
|
||
00658| - `justify-content: center` - 水平居中
|
||
00659| - `pointer-events: none` - 不接收鼠标事件
|
||
00660|
|
||
00661| #### `.opt-btn-label` (标签)
|
||
00662| - `display: inline` - 内联显示
|
||
00663|
|
||
00664| #### `.opt-btn.no-label .opt-btn-label` (无标签时隐藏)
|
||
00665| - `display: none` - 隐藏
|
||
00666|
|
||
00667| #### `.opt-btn.align-vertical` (垂直布局 - 图标在上)
|
||
00668| - `flex-direction: column` - 纵向排列
|
||
00669| - `text-align: center` - 文字居中
|
||
00670|
|
||
00671| #### `.opt-btn.align-horizontal` (水平布局 - 图标在左)
|
||
00672| - `flex-direction: row` - 横向排列
|
||
00673|
|
||
00674| #### `.opt-btn-arrow` (箭头)
|
||
00675| - `font-size: 10px` - 字体大小
|
||
00676| - `opacity: 0.6` - 半透明
|
||
00677| - `transition: transform 0.2s` - 旋转过渡
|
||
00678| - `margin-left: 4px` - 左边距
|
||
00679|
|
||
00680| #### `.opt-btn-arrow.rotated` (箭头旋转)
|
||
00681| - `transform: rotate(180deg)` - 旋转 180 度
|
||
00682|
|
||
00683| #### `.opt-btn-dropdown` (下拉菜单)
|
||
00684| - `position: absolute` - 绝对定位
|
||
00685| - `background-color: var(--bim-toolbar-bg)` - 背景色
|
||
00686| - `border-radius: 4px` - 圆角
|
||
00687| - `padding: 4px` - 内边距
|
||
00688| - `box-shadow` - 阴影效果
|
||
00689| - `z-index: 1001` - 层级
|
||
00690| - `display: flex` - Flex 布局
|
||
00691| - `flex-direction: column` - 纵向排列(默认)
|
||
00692| - `border: 1px solid rgba(255, 255, 255, 0.1)` - 边框
|
||
00693| - `animation: dropdown-fade-in 0.2s` - 淡入动画
|
||
00694|
|
||
00695| #### `.opt-btn-dropdown-item` (菜单项)
|
||
00696| - `display: flex` - Flex 布局
|
||
00697| - `align-items: center` - 垂直居中
|
||
00698| - `padding: 8px 12px` - 内边距
|
||
00699| - `cursor: pointer` - 指针光标
|
||
00700| - `border-radius: 4px` - 圆角
|
||
00701| - `color: var(--bim-btn-text-color)` - 文字颜色
|
||
00702| - `transition: background 0.2s` - 背景过渡
|
||
00703|
|
||
00704| #### `.opt-btn-dropdown-item:hover` (菜单项悬停)
|
||
00705| - `background-color: var(--bim-btn-hover-bg)` - 悬停背景色
|
||
00706| - `color: #fff` - 白色文字
|
||
00707|
|
||
00708| ### 5.3 CSS 变量
|
||
00709|
|
||
00710| **定义位置**: 容器元素的内联样式
|
||
00711|
|
||
00712| **变量列表**:
|
||
00713| - `--bim-btn-group-section-bg`: 按钮组背景颜色
|
||
00714| - `--bim-btn-bg`: 按钮背景颜色
|
||
00715| - `--bim-btn-hover-bg`: 按钮悬停背景颜色
|
||
00716| - `--bim-btn-active-bg`: 按钮激活背景颜色
|
||
00717| - `--bim-icon-color`: 图标颜色
|
||
00718| - `--bim-icon-active-color`: 图标激活颜色
|
||
00719| - `--bim-btn-text-color`: 文字颜色
|
||
00720| - `--bim-btn-text-active-color`: 文字激活颜色
|
||
00721| - `--bim-toolbar-bg`: 工具栏背景颜色(用于下拉菜单)
|
||
00722|
|
||
00723| ### 5.4 交互行为
|
||
00724|
|
||
00725| #### 按钮点击
|
||
00726| - **触发条件**: 点击按钮元素
|
||
00727| - **行为**:
|
||
00728| - 如果按钮是禁用状态,忽略
|
||
00729| - 如果按钮没有子项:
|
||
00730| - 如果 `keepActive` 为 true,切换激活状态
|
||
00731| - 关闭下拉菜单
|
||
00732| - 调用 `onClick` 回调
|
||
00733| - 如果按钮有子项,不处理(由鼠标悬停处理)
|
||
00734|
|
||
00735| #### 鼠标悬停
|
||
00736| - **触发条件**: 鼠标进入按钮
|
||
00737| - **行为**:
|
||
00738| - 如果按钮有子项,显示下拉菜单
|
||
00739| - 如果按钮没有子项,关闭下拉菜单
|
||
00740|
|
||
00741| #### 鼠标离开
|
||
00742| - **触发条件**: 鼠标离开按钮或下拉菜单
|
||
00743| - **行为**:
|
||
00744| - 延迟 200ms 后关闭下拉菜单(防止鼠标移动到菜单时关闭)
|
||
00745|
|
||
00746| #### 下拉菜单显示
|
||
00747| - **触发条件**: 鼠标悬停在有子项的按钮上
|
||
00748| - **行为**:
|
||
00749| - 创建下拉菜单元素
|
||
00750| - 计算位置(根据 `expand` 选项)
|
||
00751| - 添加到 `document.body`
|
||
00752| - 显示淡入动画
|
||
00753|
|
||
00754| #### 下拉菜单关闭
|
||
00755| - **触发条件**:
|
||
00756| - 鼠标离开按钮和菜单区域
|
||
00757| - 点击按钮(非菜单按钮)
|
||
00758| - **行为**:
|
||
00759| - 移除下拉菜单元素
|
||
00760| - 移除箭头旋转状态
|
||
00761|
|
||
00762| ### 5.5 响应式行为
|
||
00763|
|
||
00764| - 按钮组位置会根据容器尺寸自动调整(如果使用绝对定位)
|
||
00765| - 下拉菜单位置会根据按钮位置和窗口尺寸自动计算
|
||
00766| - 标签文本支持多语言切换
|
||
00767| - 按钮可见性可以动态控制
|
||
00768|
|
||
00769| ---
|
||
00770|
|
||
00771| ## 6. 逻辑流程详细描述
|
||
00772|
|
||
00773| ### 6.1 初始化流程
|
||
00774|
|
||
00775| 1. **构造函数调用**:
|
||
00776| - 解析容器元素
|
||
00777| - 合并配置选项
|
||
00778| - 记录用户自定义的颜色属性
|
||
00779| - 调用 `initContainer()` 初始化容器
|
||
00780| - 调用 `applyStyles()` 应用样式
|
||
00781|
|
||
00782| 2. **init() 方法执行**:
|
||
00783| - 调用 `render()` 渲染按钮组
|
||
00784| - 订阅语言变更:`localeManager.subscribe()`
|
||
00785| - 订阅主题变更:`themeManager.subscribe()`
|
||
00786|
|
||
00787| ### 6.2 渲染流程
|
||
00788|
|
||
00789| 1. **render() 方法**:
|
||
00790| - 清空容器
|
||
00791| - 清空按钮引用映射
|
||
00792| - 遍历所有组,调用 `renderGroup()` 渲染每个组
|
||
00793|
|
||
00794| 2. **renderGroup() 方法**:
|
||
00795| - 创建组元素
|
||
00796| - 如果不是最后一组,添加分隔线类
|
||
00797| - 遍历组内按钮,调用 `renderButton()` 渲染每个可见按钮
|
||
00798|
|
||
00799| 3. **renderButton() 方法**:
|
||
00800| - 创建按钮包装器和按钮元素
|
||
00801| - 设置布局方向(根据按钮或全局配置)
|
||
00802| - 设置激活/禁用状态
|
||
00803| - 创建图标元素
|
||
00804| - 创建文字包装器(包含标签和箭头)
|
||
00805| - 绑定事件监听器
|
||
00806| - 保存按钮引用
|
||
00807|
|
||
00808| ### 6.3 事件处理流程
|
||
00809|
|
||
00810| #### 按钮点击流程
|
||
00811| 1. 用户点击按钮 → `click` 事件
|
||
00812| 2. `handleClick()` 被调用
|
||
00813| 3. 检查按钮是否禁用
|
||
00814| 4. 如果按钮没有子项:
|
||
00815| - 如果 `keepActive` 为 true,切换激活状态
|
||
00816| - 更新按钮的 `active` 类
|
||
00817| - 关闭下拉菜单
|
||
00818| - 调用 `onClick` 回调
|
||
00819|
|
||
00820| #### 下拉菜单显示流程
|
||
00821| 1. 鼠标进入按钮 → `mouseenter` 事件
|
||
00822| 2. `handleMouseEnter()` 被调用
|
||
00823| 3. 清除悬停超时
|
||
00824| 4. 如果按钮有子项,调用 `showDropdown()`
|
||
00825| 5. `showDropdown()` 执行:
|
||
00826| - 关闭当前打开的下拉菜单
|
||
00827| - 创建下拉菜单元素
|
||
00828| - 设置菜单布局方向(根据主组方向)
|
||
00829| - 渲染所有可见的子按钮
|
||
00830| - 计算菜单位置(根据 `expand` 选项)
|
||
00831| - 添加到 `document.body`
|
||
00832| - 绑定鼠标事件(保持菜单打开)
|
||
00833|
|
||
00834| #### 下拉菜单关闭流程
|
||
00835| 1. 鼠标离开按钮或菜单 → `mouseleave` 事件
|
||
00836| 2. `handleMouseLeave()` 被调用
|
||
00837| 3. 设置 200ms 延迟
|
||
00838| 4. 延迟后调用 `closeDropdown()`
|
||
00839| 5. `closeDropdown()` 执行:
|
||
00840| - 移除下拉菜单元素
|
||
00841| - 移除箭头旋转状态
|
||
00842|
|
||
00843| ### 6.4 状态管理
|
||
00844|
|
||
00845| #### 内部状态
|
||
00846| - `groups`: 按钮组数组
|
||
00847| - `activeBtnIds`: 激活按钮 ID 的 Set
|
||
00848| - `btnRefs`: 按钮元素引用的 Map
|
||
00849| - `dropdownElement`: 当前打开的下拉菜单元素
|
||
00850| - `hoverTimeout`: 悬停超时的 ID
|
||
00851| - `customColors`: 用户自定义颜色属性的 Set
|
||
00852|
|
||
00853| #### 订阅管理
|
||
00854| - `unsubscribeLocale`: 语言订阅取消函数
|
||
00855| - `unsubscribeTheme`: 主题订阅取消函数
|
||
00856|
|
||
00857| ### 6.5 与其他组件的交互
|
||
00858|
|
||
00859| - **与 ButtonGroupManager/ToolbarManager**: 通过 Manager 创建和管理
|
||
00860| - **与 ThemeManager**: 订阅主题变更
|
||
00861| - **与 LocaleManager**: 订阅语言变更
|
||
00862| - **与 BimEngine**: 通过引擎发送事件(如果设置了引擎)
|
||
00863|
|
||
00864| ---
|
||
00865|
|
||
00866| ## 7. 国际化支持
|
||
00867|
|
||
00868| ### 7.1 使用的翻译键
|
||
00869|
|
||
00870| - `button.label`: 按钮标签(每个按钮的 `label` 属性)
|
||
00871|
|
||
00872| ### 7.2 语言变更处理
|
||
00873|
|
||
00874| - 组件订阅 `localeManager.subscribe()`
|
||
00875| - 语言变更时,`setLocales()` 方法被调用
|
||
00876| - 调用 `render()` 重新渲染,更新所有按钮的标签文本
|
||
00877|
|
||
00878| ### 7.3 实现细节
|
||
00879|
|
||
00880| ```typescript
|
||
00881| public setLocales(): void {
|
||
00882| this.render(); // 重新渲染,标签会通过 t() 函数获取最新翻译
|
||
00883| }
|
||
00884|
|
||
00885| // 在 renderButton() 中
|
||
00886| label.textContent = t(button.label);
|
||
00887| ```
|
||
00888|
|
||
00889| ---
|
||
00890|
|
||
00891| ## 8. 主题支持
|
||
00892|
|
||
00893| ### 8.1 使用的主题变量
|
||
00894|
|
||
00895| - `theme.panelBackground` → `backgroundColor`
|
||
00896| - `theme.componentBackground` → `btnBackgroundColor`
|
||
00897| - `theme.componentHover` → `btnHoverColor`
|
||
00898| - `theme.componentActive` → `btnActiveColor`
|
||
00899| - `theme.icon` → `iconColor`
|
||
00900| - `theme.iconActive` → `iconActiveColor`
|
||
00901| - `theme.textSecondary` → `textColor`
|
||
00902| - `theme.textPrimary` → `textActiveColor`
|
||
00903|
|
||
00904| ### 8.2 主题变更处理
|
||
00905|
|
||
00906| - 组件订阅 `themeManager.subscribe()`
|
||
00907| - 主题变更时,`setTheme()` 方法被调用
|
||
00908| - **只应用没有被用户自定义的颜色属性**
|
||
00909| - 如果用户已自定义颜色,保持用户自定义
|
||
00910|
|
||
00911| ### 8.3 自定义颜色保护机制
|
||
00912|
|
||
00913| - 构造函数中记录用户自定义的颜色属性
|
||
00914| - `setTheme()` 时只更新未自定义的颜色
|
||
00915| - `setColors()` 时标记颜色为自定义
|
||
00916|
|
||
00917| ---
|
||
00918|
|
||
00919| ## 9. 使用示例
|
||
00920|
|
||
00921| ### 9.1 基本使用(通过 ButtonGroupManager)
|
||
00922|
|
||
00923| ```typescript
|
||
00924| import { BimEngine } from 'bim-engine-sdk';
|
||
00925|
|
||
00926| const engine = new BimEngine('container');
|
||
00927|
|
||
00928| // 创建按钮组
|
||
00929| const buttonGroup = engine.buttonGroup.create('my-group', {
|
||
00930| position: 'top-right',
|
||
00931| direction: 'row',
|
||
00932| showLabel: true
|
||
00933| });
|
||
00934|
|
||
00935| // 添加组
|
||
00936| buttonGroup.addGroup('group-1');
|
||
00937|
|
||
00938| // 添加按钮
|
||
00939| buttonGroup.addButton({
|
||
00940| id: 'btn-1',
|
||
00941| groupId: 'group-1',
|
||
00942| type: 'button',
|
||
00943| label: 'toolbar.home',
|
||
00944| icon: '<svg>...</svg>',
|
||
00945| onClick: (button) => {
|
||
00946| console.log('按钮被点击:', button.id);
|
||
00947| }
|
||
00948| });
|
||
00949| ```
|
||
00950|
|
||
00951| ### 9.2 使用工具栏(通过 ToolbarManager)
|
||
00952|
|
||
00953| ```typescript
|
||
00954| // 工具栏已自动创建,直接使用
|
||
00955| engine.toolbar.addButton({
|
||
00956| id: 'custom-btn',
|
||
00957| groupId: 'group-1',
|
||
00958| type: 'button',
|
||
00959| label: 'toolbar.custom',
|
||
00960| icon: '<svg>...</svg>',
|
||
00961| onClick: (button) => {
|
||
00962| console.log('自定义按钮被点击');
|
||
00963| }
|
||
00964| });
|
||
00965|
|
||
00966| // 控制按钮可见性
|
||
00967| engine.toolbar.setButtonVisibility('location', false);
|
||
00968|
|
||
00969| // 控制标签显示
|
||
00970| engine.toolbar.setShowLabel(false);
|
||
00971| ```
|
||
00972|
|
||
00973| ### 9.3 使用嵌套菜单
|
||
00974|
|
||
00975| ```typescript
|
||
00976| // 添加菜单按钮
|
||
00977| buttonGroup.addButton({
|
||
00978| id: 'menu-btn',
|
||
00979| groupId: 'group-1',
|
||
00980| type: 'menu',
|
||
00981| label: 'toolbar.menu',
|
||
00982| icon: '<svg>...</svg>',
|
||
00983| children: [
|
||
00984| {
|
||
00985| id: 'sub-1',
|
||
00986| type: 'button',
|
||
00987| label: 'toolbar.sub1',
|
||
00988| onClick: () => console.log('子项 1')
|
||
00989| },
|
||
00990| {
|
||
00991| id: 'sub-2',
|
||
00992| type: 'button',
|
||
00993| label: 'toolbar.sub2',
|
||
00994| onClick: () => console.log('子项 2')
|
||
00995| }
|
||
00996| ]
|
||
00997| });
|
||
00998| ```
|
||
00999|
|
||
01000| ---
|
||
01001|
|
||
01002| ## 10. 实现细节(供 AI 重现)
|
||
01003|
|
||
01004| ### 10.1 关键算法
|
||
01005|
|
||
01006| #### 位置计算算法
|
||
01007| ```typescript
|
||
01008| function calculatePosition(position, containerWidth, containerHeight) {
|
||
01009| const margin = 20;
|
||
01010| switch (position) {
|
||
01011| case 'top-left': return { top: margin, left: margin };
|
||
01012| case 'top-center': return { top: margin, left: '50%', transform: 'translateX(-50%)' };
|
||
01013| case 'top-right': return { top: margin, right: margin };
|
||
01014| // ... 其他位置
|
||
01015| }
|
||
01016| }
|
||
01017| ```
|
||
01018|
|
||
01019| #### 下拉菜单位置计算算法
|
||
01020| ```typescript
|
||
01021| function calculateDropdownPosition(expand, buttonRect, dropdownRect) {
|
||
01022| switch (expand) {
|
||
01023| case 'up':
|
||
01024| return {
|
||
01025| bottom: window.innerHeight - buttonRect.top + 8,
|
||
01026| left: buttonRect.left + (buttonRect.width - dropdownRect.width) / 2
|
||
01027| };
|
||
01028| case 'down':
|
||
01029| return {
|
||
01030| top: buttonRect.bottom + 8,
|
||
01031| left: buttonRect.left + (buttonRect.width - dropdownRect.width) / 2
|
||
01032| };
|
||
01033| // ... 其他方向
|
||
01034| }
|
||
01035| }
|
||
01036| ```
|
||
01037|
|
||
01038| #### 激活状态切换算法
|
||
01039| ```typescript
|
||
01040| function toggleActiveState(buttonId, activeBtnIds) {
|
||
01041| if (activeBtnIds.has(buttonId)) {
|
||
01042| activeBtnIds.delete(buttonId);
|
||
01043| } else {
|
||
01044| activeBtnIds.add(buttonId);
|
||
01045| }
|
||
01046| }
|
||
01047| ```
|
||
01048|
|
||
01049| ### 10.2 性能优化点
|
||
01050|
|
||
01051| 1. **按钮引用缓存**:
|
||
01052| - 使用 `btnRefs` Map 缓存按钮元素引用
|
||
01053| - 避免重复查询 DOM
|
||
01054|
|
||
01055| 2. **延迟关闭下拉菜单**:
|
||
01056| - 使用 200ms 延迟,防止鼠标移动到菜单时关闭
|
||
01057| - 使用 `setTimeout` 和 `clearTimeout` 管理
|
||
01058|
|
||
01059| 3. **条件渲染**:
|
||
01060| - 只渲染可见的按钮(根据 `visibility` 选项)
|
||
01061| - 减少 DOM 操作
|
||
01062|
|
||
01063| 4. **CSS 变量**:
|
||
01064| - 使用 CSS 变量应用主题和颜色
|
||
01065| - 主题变更时无需重新渲染 DOM
|
||
01066|
|
||
01067| ### 10.3 注意事项和边界情况
|
||
01068|
|
||
01069| 1. **按钮 ID 唯一性**:
|
||
01070| - 按钮 ID 必须唯一
|
||
01071| - 如果重复,可能导致状态管理错误
|
||
01072|
|
||
01073| 2. **组 ID 唯一性**:
|
||
01074| - 组 ID 必须唯一
|
||
01075| - `addGroup()` 会检查重复
|
||
01076|
|
||
01077| 3. **嵌套菜单深度**:
|
||
01078| - 当前实现只支持一级嵌套(按钮 → 子项)
|
||
01079| - 不支持多级嵌套
|
||
01080|
|
||
01081| 4. **下拉菜单定位**:
|
||
01082| - 下拉菜单添加到 `document.body`,使用绝对定位
|
||
01083| - 需要根据按钮位置和窗口尺寸计算位置
|
||
01084| - 可能需要处理边界情况(菜单超出窗口)
|
||
01085|
|
||
01086| 5. **事件清理**:
|
||
01087| - 组件销毁时必须清理所有事件监听
|
||
01088| - 必须取消主题和语言订阅
|
||
01089| - 必须关闭下拉菜单
|
||
01090|
|
||
01091| 6. **翻译键处理**:
|
||
01092| - 标签可能是翻译键或普通文本
|
||
01093| - 只有翻译键才需要通过 `t()` 函数处理
|
||
01094|
|
||
01095| 7. **自定义颜色保护**:
|
||
01096| - 用户自定义的颜色不会被主题覆盖
|
||
01097| - 需要正确记录和管理自定义颜色
|
||
01098|
|
||
01099| ---
|
||
01100|
|
||
01101| ## 11. 类型定义
|
||
01102|
|
||
01103| ### 11.1 ButtonGroupOptions
|
||
01104|
|
||
01105| ```typescript
|
||
01106| interface ButtonGroupOptions extends ButtonGroupColors {
|
||
01107| container: HTMLElement | string; // 容器(必需)
|
||
01108| position?: GroupPosition; // 位置(可选)
|
||
01109| direction?: GroupDirection; // 排列方向(可选,默认 'row')
|
||
01110| align?: ButtonAlign; // 按钮内部布局(可选,默认 'vertical')
|
||
01111| expand?: ExpandDirection; // 菜单展开方向(可选,默认 'down')
|
||
01112| showLabel?: boolean; // 是否显示标签(可选,默认 true)
|
||
01113| visibility?: Record<string, boolean>; // 按钮可见性(可选)
|
||
01114| className?: string; // 自定义类名(可选)
|
||
01115| }
|
||
01116| ```
|
||
01117|
|
||
01118| ### 11.2 ButtonConfig
|
||
01119|
|
||
01120| ```typescript
|
||
01121| interface ButtonConfig {
|
||
01122| id: string; // 按钮 ID(必需,必须唯一)
|
||
01123| type: ButtonType; // 按钮类型(必需,'button' | 'menu')
|
||
01124| label: string; // 标签(必需,支持翻译键)
|
||
01125| icon?: string; // 图标 SVG(可选)
|
||
01126| keepActive?: boolean; // 是否保持激活状态(可选)
|
||
01127| disabled?: boolean; // 是否禁用(可选)
|
||
01128| onClick?: (button: OptButton) => void; // 点击回调(可选)
|
||
01129| children?: ButtonConfig[]; // 子项(可选,用于菜单)
|
||
01130| groupId?: string; // 所属组 ID(可选)
|
||
01131| parentId?: string; // 父按钮 ID(可选,用于嵌套)
|
||
01132| align?: ButtonAlign; // 按钮内部布局(可选)
|
||
01133| iconSize?: number; // 图标大小(可选,默认 32)
|
||
01134| minWidth?: number; // 最小宽度(可选,默认 50)
|
||
01135| }
|
||
01136| ```
|
||
01137|
|
||
01138| ### 11.3 其他类型
|
||
01139|
|
||
01140| ```typescript
|
||
01141| type ButtonType = 'button' | 'menu';
|
||
01142| type GroupPosition = 'center' | 'top-left' | 'top-center' | 'top-right'
|
||
01143| | 'left-center' | 'right-center'
|
||
01144| | 'bottom-left' | 'bottom-center' | 'bottom-right'
|
||
01145| | { x: number; y: number } | 'static';
|
||
01146| type GroupDirection = 'row' | 'column';
|
||
01147| type ButtonAlign = 'vertical' | 'horizontal';
|
||
01148| type ExpandDirection = 'up' | 'down' | 'left' | 'right';
|
||
01149| ```
|
||
01150|
|
||
01151| ---
|
||
01152|
|
||
01153| ## 12. 文件清单
|
||
01154|
|
||
01155| ### 12.1 相关文件
|
||
01156|
|
||
01157| - `src/components/button-group/index.ts` - 主组件类
|
||
01158| - `src/components/button-group/index.type.ts` - 类型定义
|
||
01159| - `src/components/button-group/index.css` - 样式文件
|
||
01160| - `src/components/button-group/toolbar/index.ts` - 工具栏(分化组件)
|
||
01161| - `src/managers/button-group-manager.ts` - 通用按钮组管理器
|
||
01162| - `src/managers/toolbar-manager.ts` - 工具栏管理器
|
||
01163|
|
||
01164| ### 12.2 依赖文件
|
||
01165|
|
||
01166| - `src/types/component.ts` - IBimComponent 接口
|
||
01167| - `src/themes/types.ts` - ThemeConfig 类型
|
||
01168| - `src/services/theme.ts` - ThemeManager
|
||
01169| - `src/services/locale.ts` - LocaleManager
|
||
01170| - `src/types/events.ts` - EngineEvents 类型
|
||
01171|
|
||
01172| ---
|
||
01173|
|
||
01174| ## 13. 更新记录
|
||
01175|
|
||
01176| | 日期 | 修改内容 | 修改人 |
|
||
01177| |------|---------|--------|
|
||
01178| | 2024-XX-XX | 初始创建 | AI Assistant |
|
||
01179|
|
||
01180| ---
|
||
01181|
|
||
01182| **重要提醒**: 本文档必须与组件代码保持同步。任何组件修改都必须更新本文档!
|
||
01183|
|
||
01184|
|
||
01185|
|
||
01186|
|
||
01187|
|
||
01188|
|
||
01189|
|
||
01190|
|
||
01191|
|
||
01192|
|
||
01193|
|
||
01194|
|
||
01195|
|
||
01196|
|
||
01197|
|
||
01198|
|
||
01199|
|
||
|
||
(End of file - total 1199 lines)
|
||
</file>
|
||
|
||
[Project README: /Users/yuding/WORK/LYZ/project/bimEngine/engine/README.md]
|
||
# BIM Engine SDK
|
||
|
||
> 一个功能强大的 BIM(建筑信息模型)引擎 SDK,提供 3D 可视化、构件管理、测量工具、剖切功能和漫游控制等完整功能。支持 Vue 2、Vue 3、React 和原生 HTML 等多种前端框架。
|
||
|
||
[](https://www.typescriptlang.org/)
|
||
[](https://vitejs.dev/)
|
||
[](LICENSE)
|
||
|
||
## ✨ 核心特性
|
||
|
||
- 🎯 **框架无关**: 支持 Vue 2/3、React 和原生 HTML
|
||
- 📦 **开箱即用**: 提供完整的 BIM 功能组件
|
||
- 🎨 **主题系统**: 内置暗色/亮色主题,支持自定义
|
||
- 🌍 **国际化**: 支持中英文切换
|
||
- 📐 **测量工具**: 标高、距离、角度、坡度、体积等
|
||
- ✂️ **剖切功能**: 拾取面剖切、轴向剖切、剖切盒
|
||
- 🚶 **漫游控制**: 第一人称漫游、路径漫游、平面图导航
|
||
- 🎛️ **组件管理**: 树形构件树、属性面板、右键菜单
|
||
- 💪 **TypeScript**: 完整的类型定义支持
|
||
|
||
## 📦 安装
|
||
|
||
```bash
|
||
npm install bim-engine-sdk
|
||
```
|
||
|
||
或使用 yarn/pnpm:
|
||
|
||
```bash
|
||
yarn add bim-engine-sdk
|
||
pnpm add bim-engine-sdk
|
||
```
|
||
|
||
## 🚀 快速开始
|
||
|
||
### 在 Vue 3 中使用
|
||
|
||
```vue
|
||
<template>
|
||
<div ref="containerRef" style="width: 100vw; height: 100vh;"></div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, onMounted, onUnmounted } from 'vue';
|
||
import { BimEngine } from 'bim-engine-sdk';
|
||
|
||
const containerRef = ref<HTMLElement>();
|
||
let bimEngine: BimEngine | null = null;
|
||
|
||
onMounted(() => {
|
||
if (containerRef.value) {
|
||
// 初始化 BIM 引擎
|
||
bimEngine = new BimEngine(containerRef.value, {
|
||
locale: 'zh-CN', // 语言: 'zh-CN' | 'en-US'
|
||
theme: 'dark' // 主题: 'dark' | 'light'
|
||
});
|
||
|
||
// 初始化各个管理器
|
||
bimEngine.initToolbar(); // 底部工具栏
|
||
bimEngine.initButtonGroup(); // 按钮组
|
||
bimEngine.initDialog(); // 对话框管理器
|
||
bimEngine.initEngine(); // 3D 引擎
|
||
bimEngine.initRightKey(); // 右键菜单
|
||
bimEngine.initConstructTreeBtn(); // 构件树
|
||
bimEngine.initPropertyPanel(); // 属性面板
|
||
bimEngine.initMeasure(); // 测量工具
|
||
bimEngine.initSectionPlane(); // 拾取面剖切
|
||
bimEngine.initSectionAxis(); // 轴向剖切
|
||
bimEngine.initSectionBox(); // 剖切盒
|
||
bimEngine.initWalkControl(); // 漫游控制
|
||
bimEngine.initMap(); // 地图
|
||
|
||
// 加载模型
|
||
bimEngine.engine?.loadModel({
|
||
url: '/path/to/your/model.ifc',
|
||
onProgress: (progress) => {
|
||
console.log(`加载进度: ${progress}%`);
|
||
},
|
||
onComplete: () => {
|
||
console.log('模型加载完成');
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
bimEngine?.destroy();
|
||
});
|
||
</script>
|
||
```
|
||
|
||
### 在 React 中使用
|
||
|
||
```tsx
|
||
import { useEffect, useRef } from 'react';
|
||
import { BimEngine } from 'bim-engine-sdk';
|
||
|
||
function App() {
|
||
const containerRef = useRef<HTMLDivElement>(null);
|
||
const bimEngineRef = useRef<BimEngine | null>(null);
|
||
|
||
useEffect(() => {
|
||
if (containerRef.current) {
|
||
const bimEngine = new BimEngine(containerRef.current, {
|
||
locale: 'zh-CN',
|
||
theme: 'dark'
|
||
});
|
||
|
||
// 初始化各个管理器
|
||
bimEngine.initToolbar();
|
||
bimEngine.initButtonGroup();
|
||
bimEngine.initDialog();
|
||
bimEngine.initEngine();
|
||
bimEngine.initRightKey();
|
||
|
||
bimEngineRef.current = bimEngine;
|
||
|
||
return () => {
|
||
bimEngine.destroy();
|
||
};
|
||
}
|
||
}, []);
|
||
|
||
return (
|
||
<div
|
||
ref={containerRef}
|
||
style={{ width: '100vw', height: '100vh' }}
|
||
/>
|
||
);
|
||
}
|
||
|
||
export default App;
|
||
```
|
||
|
||
### 在原生 HTML 中使用
|
||
|
||
```html
|
||
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>BIM Engine SDK Demo</title>
|
||
<style>
|
||
body { margin: 0; padding: 0; }
|
||
#container { width: 100vw; height: 100vh; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="container"></div>
|
||
|
||
<!-- 使用 UMD 版本 -->
|
||
<script src="./node_modules/bim-engine-sdk/dist/bim-engine-sdk.umd.js"></script>
|
||
<script>
|
||
const container = document.getElementById('container');
|
||
const bimEngine = new BimEngineSdk.BimEngine(container, {
|
||
locale: 'zh-CN',
|
||
theme: 'dark'
|
||
});
|
||
|
||
// 初始化各个管理器
|
||
bimEngine.initToolbar();
|
||
bimEngine.initButtonGroup();
|
||
bimEngine.initDialog();
|
||
bimEngine.initEngine();
|
||
</script>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
## 🏗️ 技术架构
|
||
|
||
### 核心设计理念
|
||
|
||
BIM Engine SDK 采用 **管理器模式 (Manager Pattern)** 作为核心架构,确保:
|
||
|
||
- ✅ 组件的生命周期统一管理
|
||
- ✅ 避免内存泄漏
|
||
- ✅ 简化集成复杂度
|
||
- ✅ 支持按需加载
|
||
|
||
### 架构分层
|
||
|
||
```
|
||
┌─────────────────────────────────────────┐
|
||
│ BimEngine (主引擎类) │
|
||
│ - 生命周期管理 │
|
||
│ - 事件系统 │
|
||
│ - 主题/国际化 │
|
||
└─────────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────┐
|
||
│ Manager 层 (管理器) │
|
||
│ - ToolbarManager (工具栏) │
|
||
│ - DialogManager (对话框) │
|
||
│ - EngineManager (3D引擎) │
|
||
│ - MeasureDialogManager (测量) │
|
||
│ - SectionPlaneDialogManager (剖切) │
|
||
│ - WalkControlManager (漫游) │
|
||
│ - ... │
|
||
└─────────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────┐
|
||
│ Component 层 (UI组件) │
|
||
│ - Dialog (对话框) │
|
||
│ - Tree (树形控件) │
|
||
│ - ButtonGroup (按钮组) │
|
||
│ - MeasurePanel (测量面板) │
|
||
│ - SectionPlanePanel (剖切面板) │
|
||
│ - ... │
|
||
└─────────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────┐
|
||
│ Service 层 (服务) │
|
||
│ - LocaleManager (国际化) │
|
||
│ - ThemeManager (主题) │
|
||
│ - EventEmitter (事件总线) │
|
||
└─────────────────────────────────────────┘
|
||
```
|
||
|
||
### 技术栈
|
||
|
||
- **语言**: TypeScript 5.9.3
|
||
- **构建工具**: Vite 7.2.6 (Library Mode)
|
||
- **类型生成**: vite-plugin-dts
|
||
- **样式注入**: vite-plugin-css-injected-by-js
|
||
- **开发环境**: Vue 3 (用于 Playground)
|
||
|
||
## 📁 项目结构
|
||
|
||
```
|
||
bim-engine-sdk/
|
||
├── src/ # 源代码
|
||
│ ├── bim-engine.ts # 主引擎类
|
||
│ ├── index.ts # 入口文件
|
||
│ ├── managers/ # 管理器层
|
||
│ │ ├── toolbar-manager.ts
|
||
│ │ ├── dialog-manager.ts
|
||
│ │ ├── engine-manager.ts
|
||
│ │ ├── measure-dialog-manager.ts
|
||
│ │ ├── section-plane-dialog-manager.ts
|
||
│ │ ├── section-axis-dialog-manager.ts
|
||
│ │ ├── section-box-dialog-manager.ts
|
||
│ │ ├── walk-control-manager.ts
|
||
│ │ └── ...
|
||
│ ├── components/ # UI 组件层
|
||
│ │ ├── dialog/ # 对话框组件
|
||
│ │ ├── tree/ # 树形控件
|
||
│ │ ├── button-group/ # 按钮组
|
||
│ │ ├── measure-panel/ # 测量面板
|
||
│ │ ├── section-plane-panel/ # 拾取面剖切面板
|
||
│ │ ├── section-axis-panel/ # 轴向剖切面板
|
||
│ │ ├── section-box-panel/ # 剖切盒面板
|
||
│ │ ├── walk-control-panel/ # 漫游控制面板
|
||
│ │ └── ...
|
||
│ ├── services/ # 服务层
|
||
│ │ ├── locale.ts # 国际化服务
|
||
│ │ └── theme.ts # 主题服务
|
||
│ ├── core/ # 核心模块
|
||
│ │ └── event-emitter.ts # 事件系统
|
||
│ ├── types/ # 类型定义
|
||
│ │ ├── component.ts # 组件接口
|
||
│ │ └── events.ts # 事件类型
|
||
│ ├── themes/ # 主题配置
|
||
│ │ ├── dark.ts # 暗色主题
|
||
│ │ └── light.ts # 亮色主题
|
||
│ ├── locales/ # 国际化资源
|
||
│ │ ├── zh-CN.ts # 简体中文
|
||
│ │ └── en-US.ts # 英文
|
||
│ ├── utils/ # 工具函数
|
||
│ │ └── icon-manager.ts # 图标管理器
|
||
│ └── assets/ # 静态资源
|
||
├── dist/ # 构建产物
|
||
│ ├── bim-engine-sdk.es.js # ESM 格式
|
||
│ ├── bim-engine-sdk.umd.js # UMD 格式
|
||
│ └── index.d.ts # TypeScript 类型定义
|
||
├── demo/ # HTML Demo
|
||
├── demo-vue/ # Vue Demo
|
||
├── playground/ # 开发调试环境 (Vue 3)
|
||
├── docs/ # 文档
|
||
│ ├── components/ # 组件文档
|
||
│ └── utils/ # 工具文档
|
||
├── vite.config.ts # Vite 配置
|
||
├── tsconfig.json # TypeScript 配置
|
||
└── package.json # 项目配置
|
||
```
|
||
|
||
## 🎯 核心 API
|
||
|
||
### BimEngine 主类
|
||
|
||
```typescript
|
||
class BimEngine {
|
||
constructor(
|
||
container: HTMLElement | string,
|
||
options?: {
|
||
locale?: 'zh-CN' | 'en-US';
|
||
theme?: 'dark' | 'light';
|
||
}
|
||
);
|
||
|
||
// 管理器初始化方法
|
||
initToolbar(): void;
|
||
initButtonGroup(): void;
|
||
initDialog(): void;
|
||
initEngine(options?: EngineOptions): void;
|
||
initRightKey(): void;
|
||
initConstructTreeBtn(): void;
|
||
initPropertyPanel(): void;
|
||
initMeasure(): void;
|
||
initSectionPlane(): void;
|
||
initSectionAxis(): void;
|
||
initSectionBox(): void;
|
||
initWalkControl(): void;
|
||
initMap(): void;
|
||
|
||
// 主题和国际化
|
||
setTheme(theme: 'dark' | 'light', config?: ThemeConfig): void;
|
||
setLocale(locale: 'zh-CN' | 'en-US'): void;
|
||
|
||
// 事件系统
|
||
on(event: string, handler: Function): void;
|
||
off(event: string, handler: Function): void;
|
||
emit(event: string, ...args: any[]): void;
|
||
|
||
// 销毁
|
||
destroy(): void;
|
||
}
|
||
```
|
||
|
||
### 管理器
|
||
|
||
每个管理器负责特定功能的生命周期管理:
|
||
|
||
```typescript
|
||
// 3D 引擎管理器
|
||
bimEngine.engine?.loadModel({
|
||
url: '/path/to/model.ifc',
|
||
onProgress: (progress) => console.log(progress),
|
||
onComplete: () => console.log('完成')
|
||
});
|
||
|
||
// 测量工具管理器
|
||
bimEngine.measure?.show();
|
||
bimEngine.measure?.hide();
|
||
|
||
// 剖切管理器
|
||
bimEngine.sectionPlane?.show();
|
||
bimEngine.sectionAxis?.show();
|
||
bimEngine.sectionBox?.show();
|
||
|
||
// 漫游控制管理器
|
||
bimEngine.walkControl?.show();
|
||
bimEngine.walkControl?.hide();
|
||
```
|
||
|
||
## 🎨 主题定制
|
||
|
||
### 使用内置主题
|
||
|
||
```typescript
|
||
const bimEngine = new BimEngine(container, {
|
||
theme: 'dark' // 'dark' | 'light'
|
||
});
|
||
|
||
// 运行时切换主题
|
||
bimEngine.setTheme('light');
|
||
```
|
||
|
||
### 自定义主题
|
||
|
||
```typescript
|
||
bimEngine.setTheme('dark', {
|
||
primaryColor: '#1890ff',
|
||
bgColor: '#1a1a1a',
|
||
textColor: '#ffffff',
|
||
borderColor: '#333333',
|
||
// ... 更多配置
|
||
});
|
||
```
|
||
|
||
### 主题变量
|
||
|
||
SDK 使用 CSS 变量实现主题系统,所有组件自动响应主题变化:
|
||
|
||
```css
|
||
--bim-primary-color
|
||
--bim-bg-color
|
||
--bim-text-color
|
||
--bim-border-color
|
||
--bim-dialog-bg
|
||
--bim-dialog-header-bg
|
||
--bim-icon-color
|
||
/* ... 更多变量 */
|
||
```
|
||
|
||
## 🌍 国际化
|
||
|
||
### 切换语言
|
||
|
||
```typescript
|
||
const bimEngine = new BimEngine(container, {
|
||
locale: 'zh-CN' // 'zh-CN' | 'en-US'
|
||
});
|
||
|
||
// 运行时切换语言
|
||
bimEngine.setLocale('en-US');
|
||
```
|
||
|
||
### 支持的语言
|
||
|
||
- `zh-CN`: 简体中文
|
||
- `en-US`: English
|
||
|
||
## 🔧 开发指南
|
||
|
||
### 环境准备
|
||
|
||
```bash
|
||
# 安装依赖
|
||
npm install
|
||
|
||
# 启动开发环境 (Playground)
|
||
npm run dev
|
||
|
||
# 构建 SDK
|
||
npm run build
|
||
|
||
# 运行 HTML Demo
|
||
npm run dev:demo
|
||
|
||
# 运行 Vue Demo
|
||
npm run dev:demo-vue
|
||
```
|
||
|
||
### 构建产物
|
||
|
||
运行 `npm run build` 后,会在 `dist/` 目录生成:
|
||
|
||
- `bim-engine-sdk.es.js` - ESM 格式 (用于现代打包工具)
|
||
- `bim-engine-sdk.umd.js` - UMD 格式 (用于浏览器直接引入)
|
||
- `index.d.ts` - TypeScript 类型定义
|
||
- `*.css` - 样式文件 (已内联注入到 JS 中)
|
||
|
||
### package.json 导出配置
|
||
|
||
```json
|
||
{
|
||
"main": "./dist/bim-engine-sdk.umd.js",
|
||
"module": "./dist/bim-engine-sdk.es.js",
|
||
"types": "./dist/index.d.ts",
|
||
"exports": {
|
||
".": {
|
||
"types": "./dist/index.d.ts",
|
||
"import": "./dist/bim-engine-sdk.es.js",
|
||
"require": "./dist/bim-engine-sdk.umd.js"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 📖 组件文档
|
||
|
||
详细的组件文档请参考 `docs/` 目录:
|
||
|
||
- [Dialog 对话框](docs/components/dialog.md)
|
||
- [Tree 树形控件](docs/components/tree.md)
|
||
- [ButtonGroup 按钮组](docs/components/button-group.md)
|
||
- [MeasurePanel 测量面板](docs/components/measure-panel.md)
|
||
- [SectionPlanePanel 拾取面剖切](docs/components/section-plane-panel.md)
|
||
- [SectionAxisPanel 轴向剖切](docs/components/section-axis-panel.md)
|
||
- [SectionBoxPanel 剖切盒](docs/components/section-box-panel.md)
|
||
- [WalkControlPanel 漫游控制](docs/components/walk-control-panel.md)
|
||
- [IconManager 图标管理器](docs/utils/icon-manager.md)
|
||
|
||
## 🔌 集成示例
|
||
|
||
### Vue 3 + Vite
|
||
|
||
```bash
|
||
cd demo-vue
|
||
npm install
|
||
npm run dev
|
||
```
|
||
|
||
### HTML
|
||
|
||
```bash
|
||
cd demo
|
||
npm run dev
|
||
```
|
||
|
||
## 🤝 贡献指南
|
||
|
||
欢迎贡献代码!请遵循以下步骤:
|
||
|
||
1. Fork 本仓库
|
||
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
|
||
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
|
||
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
||
5. 开启 Pull Request
|
||
|
||
### 代码规范
|
||
|
||
- 使用 TypeScript 编写代码
|
||
- 遵循 ESLint 规则
|
||
- 编写清晰的注释和文档
|
||
- 为新功能添加测试用例
|
||
|
||
## 📝 更新日志
|
||
|
||
### v1.0.0 (2024-12-26)
|
||
|
||
**核心功能**
|
||
- ✨ 初始版本发布
|
||
- ✨ 实现管理器模式架构
|
||
- ✨ 支持 Vue 2/3、React、HTML
|
||
|
||
**组件系统**
|
||
- ✨ Dialog 对话框组件
|
||
- ✨ Tree 树形控件
|
||
- ✨ ButtonGroup 按钮组
|
||
- ✨ MeasurePanel 测量面板
|
||
- ✨ SectionPlanePanel 拾取面剖切
|
||
- ✨ SectionAxisPanel 轴向剖切
|
||
- ✨ SectionBoxPanel 剖切盒
|
||
- ✨ WalkControlPanel 漫游控制
|
||
|
||
**功能特性**
|
||
- ✨ 主题系统 (暗色/亮色)
|
||
- ✨ 国际化 (中英文)
|
||
- ✨ 事件系统
|
||
- ✨ 图标管理器
|
||
|
||
## 📄 许可证
|
||
|
||
ISC License
|
||
|
||
## 🙏 致谢
|
||
|
||
感谢所有为本项目做出贡献的开发者!
|
||
|
||
## 📮 联系方式
|
||
|
||
如有问题或建议,请通过以下方式联系:
|
||
|
||
- 提交 [Issue](https://github.com/your-repo/bim-engine-sdk/issues)
|
||
- 发送邮件至 your-email@example.com
|
||
|
||
---
|
||
|
||
**Made with ❤️ by BIM Engine Team**
|
||
|
||
|
||
[Rule: .cursor/rules/bim/RULE.md]
|
||
[Match: alwaysApply]
|
||
你是一个资深的前端工程师,我这个项目需要你每次都看下AI_COLLABORATION.md,文件里面有项目的所有信息
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 9.7s)
|
||
|
||
让我查看现有的 ButtonGroup 组件源代码和 CSS:
|
||
|
||
```
|
||
Tool: read
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.ts"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
<file>
|
||
00001| import './index.css';
|
||
00002| import type {
|
||
00003| OptButton,
|
||
00004| ButtonGroup,
|
||
00005| ButtonGroupOptions,
|
||
00006| ButtonConfig,
|
||
00007| ButtonGroupColors
|
||
00008| } from './index.type';
|
||
00009| import { t, localeManager } from '../../services/locale';
|
||
00010| import { themeManager } from '../../services/theme';
|
||
00011| import type { ThemeConfig } from '../../themes/types';
|
||
00012| import { IBimComponent } from '../../types/component';
|
||
00013| import type { BimEngine } from '../../bim-engine';
|
||
00014| import { EngineEvents } from '../../types/events';
|
||
00015|
|
||
00016| /**
|
||
00017| * 通用按钮组组件 (BimButtonGroup)
|
||
00018| */
|
||
00019| export class BimButtonGroup implements IBimComponent {
|
||
00020| private container: HTMLElement;
|
||
00021| private options: ButtonGroupOptions;
|
||
00022| private groups: ButtonGroup[] = [];
|
||
00023| private activeBtnIds: Set<string> = new Set();
|
||
00024| private btnRefs: Map<string, HTMLElement> = new Map();
|
||
00025| private dropdownElement: HTMLElement | null = null;
|
||
00026| private hoverTimeout: number | null = null;
|
||
00027| private customColors: Set<keyof ButtonGroupColors> = new Set(); // 记录用户自定义的颜色属性
|
||
00028| private unsubscribeLocale: (() => void) | null = null;
|
||
00029| private unsubscribeTheme: (() => void) | null = null;
|
||
00030|
|
||
00031| protected engine: BimEngine | null = null;
|
||
00032|
|
||
00033| private readonly DEFAULT_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect></svg>';
|
||
00034|
|
||
00035| constructor(options: ButtonGroupOptions) {
|
||
00036| const el = typeof options.container === 'string'
|
||
00037| ? document.getElementById(options.container)
|
||
00038| : options.container;
|
||
00039|
|
||
00040| if (!el) throw new Error('Container not found');
|
||
00041|
|
||
00042| this.container = el;
|
||
00043| // 合并默认配置
|
||
00044| this.options = {
|
||
00045| showLabel: true,
|
||
00046| visibility: {},
|
||
00047| direction: 'row', // 默认横向
|
||
00048| position: 'static', // 默认静态定位
|
||
00049| align: 'vertical', // 默认图标在上
|
||
00050| expand: 'down', // 默认向下展开
|
||
00051| ...options
|
||
00052| };
|
||
00053|
|
||
00054| // 记录初始传入的自定义颜色
|
||
00055| const colorKeys: (keyof ButtonGroupColors)[] = [
|
||
00056| 'backgroundColor', 'btnBackgroundColor', 'btnHoverColor',
|
||
00057| 'btnActiveColor', 'iconColor', 'iconActiveColor',
|
||
00058| 'textColor', 'textActiveColor'
|
||
00059| ];
|
||
00060| colorKeys.forEach(key => {
|
||
00061| if (options[key]) {
|
||
00062| this.customColors.add(key);
|
||
00063| }
|
||
00064| });
|
||
00065|
|
||
00066| this.initContainer();
|
||
00067| this.applyStyles();
|
||
00068| }
|
||
00069|
|
||
00070| public setEngine(engine: BimEngine) {
|
||
00071| this.engine = engine;
|
||
00072| }
|
||
00073|
|
||
00074| protected emit<K extends keyof EngineEvents>(event: K, payload: EngineEvents[K]) {
|
||
00075| if (this.engine) {
|
||
00076| this.engine.emit(event, payload);
|
||
00077| } else {
|
||
00078| console.warn('[BimButtonGroup] Engine not set, cannot emit event:', event);
|
||
00079| }
|
||
00080| }
|
||
00081|
|
||
00082| private initContainer(): void {
|
||
00083| this.container.innerHTML = '';
|
||
00084| this.container.classList.add('bim-btn-group-root');
|
||
00085|
|
||
00086| if (this.options.direction === 'column') {
|
||
00087| this.container.classList.add('dir-column');
|
||
00088| } else {
|
||
00089| this.container.classList.add('dir-row');
|
||
00090| }
|
||
00091|
|
||
00092| if (this.options.className) {
|
||
00093| this.container.classList.add(this.options.className);
|
||
00094| }
|
||
00095|
|
||
00096| this.updatePosition();
|
||
00097|
|
||
00098| // 添加事件拦截,防止点击穿透到 3D 引擎
|
||
00099| this.setupEventInterception(this.container);
|
||
00100| }
|
||
00101|
|
||
00102| /**
|
||
00103| * 设置事件拦截,防止事件冒泡到下层元素(如 3D 引擎)
|
||
00104| */
|
||
00105| private setupEventInterception(el: HTMLElement): void {
|
||
00106| const stopPropagation = (e: Event) => {
|
||
00107| e.stopPropagation();
|
||
00108| };
|
||
00109|
|
||
00110| const events = [
|
||
00111| 'click', 'dblclick', 'contextmenu', 'wheel',
|
||
00112| 'mousedown', 'mouseup', 'mousemove',
|
||
00113| 'touchstart', 'touchend', 'touchmove',
|
||
00114| 'pointerdown', 'pointerup', 'pointermove', 'pointerenter', 'pointerleave', 'pointerover', 'pointerout'
|
||
00115| ];
|
||
00116|
|
||
00117| events.forEach(eventType => {
|
||
00118| el.addEventListener(eventType, stopPropagation, { passive: false });
|
||
00119| });
|
||
00120| }
|
||
00121|
|
||
00122| private updatePosition() {
|
||
00123| const pos = this.options.position;
|
||
00124| const style = this.container.style;
|
||
00125|
|
||
00126| style.top = ''; style.bottom = ''; style.left = ''; style.right = ''; style.transform = '';
|
||
00127|
|
||
00128| if (pos === 'static') {
|
||
00129| this.container.classList.add('static');
|
||
00130| return;
|
||
00131| }
|
||
00132|
|
||
00133| this.container.classList.remove('static');
|
||
00134| this.container.style.position = 'absolute';
|
||
00135|
|
||
00136| if (typeof pos === 'object' && 'x' in pos) {
|
||
00137| style.left = `${pos.x}px`;
|
||
00138| style.top = `${pos.y}px`;
|
||
00139| } else {
|
||
00140| const margin = '20px';
|
||
00141| switch (pos) {
|
||
00142| case 'top-left':
|
||
00143| style.top = margin; style.left = margin;
|
||
00144| break;
|
||
00145| case 'top-center':
|
||
00146| style.top = margin; style.left = '50%'; style.transform = 'translateX(-50%)';
|
||
00147| break;
|
||
00148| case 'top-right':
|
||
00149| style.top = margin; style.right = margin;
|
||
00150| break;
|
||
00151| case 'bottom-left':
|
||
00152| style.bottom = margin; style.left = margin;
|
||
00153| break;
|
||
00154| case 'bottom-center':
|
||
00155| style.bottom = margin; style.left = '50%'; style.transform = 'translateX(-50%)';
|
||
00156| break;
|
||
00157| case 'bottom-right':
|
||
00158| style.bottom = margin; style.right = margin;
|
||
00159| break;
|
||
00160| case 'left-center':
|
||
00161| style.left = margin; style.top = '50%'; style.transform = 'translateY(-50%)';
|
||
00162| break;
|
||
00163| case 'right-center':
|
||
00164| style.right = margin; style.top = '50%'; style.transform = 'translateY(-50%)';
|
||
00165| break;
|
||
00166| case 'center':
|
||
00167| style.top = '50%'; style.left = '50%'; style.transform = 'translate(-50%, -50%)';
|
||
00168| break;
|
||
00169| }
|
||
00170| }
|
||
00171| }
|
||
00172|
|
||
00173| /**
|
||
00174| * 应用样式到容器
|
||
00175| */
|
||
00176| private applyStyles(): void {
|
||
00177| const style = this.container.style;
|
||
00178| if (this.options.backgroundColor) style.setProperty('--bim-btn-group-section-bg', this.options.backgroundColor);
|
||
00179| if (this.options.btnBackgroundColor) style.setProperty('--bim-btn-bg', this.options.btnBackgroundColor);
|
||
00180| if (this.options.btnHoverColor) style.setProperty('--bim-btn-hover-bg', this.options.btnHoverColor);
|
||
00181| if (this.options.btnActiveColor) style.setProperty('--bim-btn-active-bg', this.options.btnActiveColor);
|
||
00182| if (this.options.iconColor) style.setProperty('--bim-icon-color', this.options.iconColor);
|
||
00183| if (this.options.iconActiveColor) style.setProperty('--bim-icon-active-color', this.options.iconActiveColor);
|
||
00184| if (this.options.textColor) style.setProperty('--bim-btn-text-color', this.options.textColor);
|
||
00185| if (this.options.textActiveColor) style.setProperty('--bim-btn-text-active-color', this.options.textActiveColor);
|
||
00186|
|
||
00187| // 同步更新所有已存在的dropdown元素的CSS变量(dropdown被添加到body,无法继承容器的CSS变量)
|
||
00188| const dropdowns = document.querySelectorAll('.opt-btn-dropdown');
|
||
00189| dropdowns.forEach(dropdown => {
|
||
00190| const dropdownStyle = (dropdown as HTMLElement).style;
|
||
00191| if (this.options.iconColor) dropdownStyle.setProperty('--bim-icon-color', this.options.iconColor);
|
||
00192| if (this.options.iconActiveColor) dropdownStyle.setProperty('--bim-icon-active-color', this.options.iconActiveColor);
|
||
00193| if (this.options.textColor) dropdownStyle.setProperty('--bim-btn-text-color', this.options.textColor);
|
||
00194| if (this.options.textActiveColor) dropdownStyle.setProperty('--bim-btn-text-active-color', this.options.textActiveColor);
|
||
00195| if (this.options.btnBackgroundColor) dropdownStyle.setProperty('--bim-btn-bg', this.options.btnBackgroundColor);
|
||
00196| if (this.options.btnHoverColor) dropdownStyle.setProperty('--bim-btn-hover-bg', this.options.btnHoverColor);
|
||
00197| if (this.options.btnActiveColor) dropdownStyle.setProperty('--bim-btn-active-bg', this.options.btnActiveColor);
|
||
00198| });
|
||
00199| }
|
||
00200|
|
||
00201| /**
|
||
00202| * 设置主题的primary颜色(用于边框等)
|
||
00203| */
|
||
00204| private setPrimaryColor(color: string): void {
|
||
00205| this.container.style.setProperty('--bim-primary-color', color);
|
||
00206|
|
||
00207| // 同步更新所有dropdown(dropdown被添加到body,无法继承容器的CSS变量)
|
||
00208| const dropdowns = document.querySelectorAll('.opt-btn-dropdown');
|
||
00209| dropdowns.forEach(dropdown => {
|
||
00210| (dropdown as HTMLElement).style.setProperty('--bim-primary-color', color);
|
||
00211| });
|
||
00212| }
|
||
00213|
|
||
00214| /**
|
||
00215| * 设置主题颜色
|
||
00216| * 只会应用到没有被用户自定义的颜色属性上
|
||
00217| */
|
||
00218| public setTheme(theme: ThemeConfig): void {
|
||
00219| const themeColors: ButtonGroupColors = {
|
||
00220| backgroundColor: theme.panelBackground,
|
||
00221| btnBackgroundColor: theme.componentBackground,
|
||
00222| btnHoverColor: theme.componentHover,
|
||
00223| btnActiveColor: theme.componentActive,
|
||
00224| iconColor: theme.icon,
|
||
00225| iconActiveColor: theme.iconActive,
|
||
00226| textColor: theme.textSecondary,
|
||
00227| textActiveColor: theme.textPrimary
|
||
00228| };
|
||
00229|
|
||
00230| // 只应用没有被自定义的颜色
|
||
00231| Object.entries(themeColors).forEach(([key, value]) => {
|
||
00232| const colorKey = key as keyof ButtonGroupColors;
|
||
00233| if (!this.customColors.has(colorKey)) {
|
||
00234| this.options[colorKey] = value;
|
||
00235| }
|
||
00236| });
|
||
00237|
|
||
00238| this.applyStyles();
|
||
00239| this.setPrimaryColor(theme.primary);
|
||
00240| }
|
||
00241|
|
||
00242| /**
|
||
00243| * 直接设置颜色(强制覆盖)
|
||
00244| * 设置的颜色会被标记为自定义,后续的 setTheme 不会覆盖它们
|
||
00245| */
|
||
00246| public setColors(colors: ButtonGroupColors): void {
|
||
00247| // 更新 options
|
||
00248| this.options = { ...this.options, ...colors };
|
||
00249|
|
||
00250| // 标记这些颜色为自定义
|
||
00251| Object.keys(colors).forEach(key => {
|
||
00252| this.customColors.add(key as keyof ButtonGroupColors);
|
||
00253| });
|
||
00254|
|
||
00255| this.applyStyles();
|
||
00256| }
|
||
00257|
|
||
00258| public async init(): Promise<void> {
|
||
00259| this.render();
|
||
00260|
|
||
00261| // 自动订阅语言变更
|
||
00262| this.unsubscribeLocale = localeManager.subscribe(() => {
|
||
00263| this.setLocales();
|
||
00264| });
|
||
00265|
|
||
00266| // 自动订阅主题变更
|
||
00267| this.unsubscribeTheme = themeManager.subscribe((theme) => {
|
||
00268| this.setTheme(theme);
|
||
00269| });
|
||
00270| }
|
||
00271|
|
||
00272| public setLocales(): void {
|
||
00273| this.render();
|
||
00274| }
|
||
00275|
|
||
00276| public addGroup(groupId: string, beforeGroupId?: string): void {
|
||
00277| if (this.groups.some(g => g.id === groupId)) return;
|
||
00278| const newGroup: ButtonGroup = { id: groupId, buttons: [] };
|
||
00279| if (beforeGroupId) {
|
||
00280| const index = this.groups.findIndex(g => g.id === beforeGroupId);
|
||
00281| index !== -1 ? this.groups.splice(index, 0, newGroup) : this.groups.push(newGroup);
|
||
00282| } else {
|
||
00283| this.groups.push(newGroup);
|
||
00284| }
|
||
00285| }
|
||
00286|
|
||
00287| public addButton(config: ButtonConfig): void {
|
||
00288| const { groupId, parentId } = config;
|
||
00289| const group = this.groups.find(g => g.id === groupId);
|
||
00290| if (!group) return;
|
||
00291|
|
||
00292| const button: OptButton = { ...config, children: config.children || [] };
|
||
00293| if (parentId) {
|
||
00294| const parentBtn = this.findButton(group.buttons, parentId);
|
||
00295| if (parentBtn) {
|
||
00296| if (!parentBtn.children) parentBtn.children = [];
|
||
00297| parentBtn.children.push(button);
|
||
00298| }
|
||
00299| } else {
|
||
00300| group.buttons.push(button);
|
||
00301| }
|
||
00302| }
|
||
00303|
|
||
00304| private findButton(buttons: OptButton[], id: string): OptButton | undefined {
|
||
00305| for (const btn of buttons) {
|
||
00306| if (btn.id === id) return btn;
|
||
00307| if (btn.children) {
|
||
00308| const found = this.findButton(btn.children, id);
|
||
00309| if (found) return found;
|
||
00310| }
|
||
00311| }
|
||
00312| return undefined;
|
||
00313| }
|
||
00314|
|
||
00315| public render(): void {
|
||
00316| this.container.innerHTML = '';
|
||
00317| this.btnRefs.clear();
|
||
00318|
|
||
00319| this.groups.forEach((group, index) => {
|
||
00320| const groupElement = this.renderGroup(group, index, this.groups.length);
|
||
00321| this.container.appendChild(groupElement);
|
||
00322| });
|
||
00323| }
|
||
00324|
|
||
00325| private renderGroup(group: ButtonGroup, index: number, total: number): HTMLElement {
|
||
00326| const groupEl = document.createElement('div');
|
||
00327| groupEl.className = 'bim-btn-group-section';
|
||
00328|
|
||
00329| if (index < total - 1) {
|
||
00330| groupEl.classList.add('has-divider');
|
||
00331| }
|
||
00332|
|
||
00333| group.buttons.forEach(button => {
|
||
00334| if (this.isVisible(button.id)) {
|
||
00335| const btnWrapper = this.renderButton(button);
|
||
00336| groupEl.appendChild(btnWrapper);
|
||
00337| }
|
||
00338| });
|
||
00339| return groupEl;
|
||
00340| }
|
||
00341|
|
||
00342| private renderButton(button: OptButton): HTMLElement {
|
||
00343| const wrapper = document.createElement('div');
|
||
00344| wrapper.className = 'opt-btn-wrapper';
|
||
00345|
|
||
00346| const btnEl = document.createElement('div');
|
||
00347| btnEl.className = 'opt-btn';
|
||
00348|
|
||
00349| // 初始化时根据 button 自身的属性同步 active 状态
|
||
00350| if (button.isActive) {
|
||
00351| this.activeBtnIds.add(button.id);
|
||
00352| }
|
||
00353|
|
||
00354| // 按钮优先使用自己的 align,否则使用全局配置,默认为 vertical
|
||
00355| const align = button.align || this.options.align || 'vertical';
|
||
00356| if (align === 'horizontal') {
|
||
00357| btnEl.classList.add('align-horizontal');
|
||
00358| } else {
|
||
00359| btnEl.classList.add('align-vertical');
|
||
00360| }
|
||
00361|
|
||
00362| if (this.activeBtnIds.has(button.id)) btnEl.classList.add('active');
|
||
00363| if (button.disabled) btnEl.classList.add('disabled');
|
||
00364|
|
||
00365| // 判断是否显示 label
|
||
00366| const hasLabel = this.options.showLabel && button.label;
|
||
00367| if (!hasLabel) {
|
||
00368| btnEl.classList.add('no-label');
|
||
00369| // 当不显示 label 时,添加 title 属性作为 tooltip
|
||
00370| if (button.label) {
|
||
00371| btnEl.title = t(button.label);
|
||
00372| }
|
||
00373| }
|
||
00374|
|
||
00375| // 应用按钮的自定义样式
|
||
00376| const iconSize = button.iconSize || 32;
|
||
00377| const minWidth = button.minWidth || 50;
|
||
00378| btnEl.style.minWidth = `${minWidth}px`;
|
||
00379|
|
||
00380| const icon = document.createElement('div');
|
||
00381| icon.className = 'opt-btn-icon';
|
||
00382| icon.style.width = `${iconSize}px`;
|
||
00383| icon.style.height = `${iconSize}px`;
|
||
00384| icon.innerHTML = this.getIcon(button.icon);
|
||
00385| btnEl.appendChild(icon);
|
||
00386|
|
||
00387| // 创建文字和箭头的容器,确保它们始终在一起(无论主轴是横是竖)
|
||
00388| const textWrapper = document.createElement('div');
|
||
00389| textWrapper.className = 'opt-btn-text-wrapper';
|
||
00390|
|
||
00391| if (this.options.showLabel && button.label) {
|
||
00392| const label = document.createElement('span');
|
||
00393| label.className = 'opt-btn-label';
|
||
00394| label.textContent = t(button.label);
|
||
00395| textWrapper.appendChild(label);
|
||
00396| }
|
||
00397|
|
||
00398| if (button.children && button.children.length > 0) {
|
||
00399| const arrow = document.createElement('span');
|
||
00400| arrow.className = 'opt-btn-arrow';
|
||
00401| arrow.textContent = '▼';
|
||
00402| textWrapper.appendChild(arrow);
|
||
00403| }
|
||
00404|
|
||
00405| // 只有当有内容时才添加 wrapper
|
||
00406| if (textWrapper.hasChildNodes()) {
|
||
00407| btnEl.appendChild(textWrapper);
|
||
00408| }
|
||
00409|
|
||
00410| btnEl.addEventListener('click', () => this.handleClick(button));
|
||
00411| btnEl.addEventListener('mouseenter', () => this.handleMouseEnter(button, btnEl));
|
||
00412| btnEl.addEventListener('mouseleave', () => this.handleMouseLeave());
|
||
00413|
|
||
00414| this.btnRefs.set(button.id, btnEl);
|
||
00415| wrapper.appendChild(btnEl);
|
||
00416| return wrapper;
|
||
00417| }
|
||
00418|
|
||
00419| /**
|
||
00420| * 设置按钮的激活状态
|
||
00421| * @param id 按钮 ID
|
||
00422| * @param active 可选,如果不传则切换(toggle)当前状态
|
||
00423| */
|
||
00424| public setBtnActive(id: string, active?: boolean): void {
|
||
00425| const button = this.findButtonById(id);
|
||
00426| if (!button) return;
|
||
00427|
|
||
00428| // 确定最终状态
|
||
00429| const newState = active !== undefined ? active : !this.activeBtnIds.has(id);
|
||
00430|
|
||
00431| if (newState) {
|
||
00432| this.activeBtnIds.add(id);
|
||
00433| } else {
|
||
00434| this.activeBtnIds.delete(id);
|
||
00435| }
|
||
00436|
|
||
00437| // 同步对象状态并更新 DOM
|
||
00438| button.isActive = newState;
|
||
00439| this.updateButtonState(id);
|
||
00440| }
|
||
00441|
|
||
00442| private handleClick(button: OptButton): void {
|
||
00443| if (button.disabled) return;
|
||
00444| if (!button.children || button.children.length === 0) {
|
||
00445| if (button.keepActive) {
|
||
00446| // 1) 先切换自身激活状态(onClick 里通常会根据 isActive 决定“打开/关闭”逻辑)
|
||
00447| const wasActive = this.activeBtnIds.has(button.id);
|
||
00448| const newState = !wasActive;
|
||
00449| this.setBtnActive(button.id, newState);
|
||
00450|
|
||
00451| // 2) 互斥逻辑:仅在“本次切换为激活”时触发
|
||
00452| // - 一级按钮:同 groupId 下其它一级按钮互斥
|
||
00453| // - 二级按钮:同 groupId + 同 parentId 下其它二级按钮互斥
|
||
00454| // - 被关闭的按钮需要触发 onClick(用于执行关闭逻辑)
|
||
00455| if (newState && button.exclusive && button.groupId) {
|
||
00456| this.deactivateExclusiveSiblings(button);
|
||
00457| }
|
||
00458| }
|
||
00459| this.closeDropdown();
|
||
00460| if (button.onClick) button.onClick(button);
|
||
00461| }
|
||
00462| }
|
||
00463|
|
||
00464| /**
|
||
00465| * 互斥关闭同范围内的其它已激活按钮,并触发它们的 onClick
|
||
00466| * @param button 当前被激活的按钮
|
||
00467| */
|
||
00468| private deactivateExclusiveSiblings(button: OptButton): void {
|
||
00469| const group = this.groups.find(g => g.id === button.groupId);
|
||
00470| if (!group) return;
|
||
00471|
|
||
00472| // 二级按钮:同 groupId + 同 parentId
|
||
00473| if (button.parentId) {
|
||
00474| const parent = this.findButton(group.buttons, button.parentId);
|
||
00475| const siblings = parent?.children || [];
|
||
00476| for (const sib of siblings) {
|
||
00477| if (!sib) continue;
|
||
00478| if (sib.id === button.id) continue;
|
||
00479| if (sib.parentId !== button.parentId) continue;
|
||
00480| if (sib.groupId !== button.groupId) continue;
|
||
00481|
|
||
00482| if (this.activeBtnIds.has(sib.id)) {
|
||
00483| this.setBtnActive(sib.id, false);
|
||
00484| // 触发被关闭按钮的 onClick(此时 sib.isActive 已经同步为 false)
|
||
00485| if (sib.onClick) sib.onClick(sib);
|
||
00486| }
|
||
00487| }
|
||
00488| return;
|
||
00489| }
|
||
00490|
|
||
00491| // 一级按钮:同 groupId 下其它一级按钮(不包含二级)
|
||
00492| for (const sib of group.buttons) {
|
||
00493| if (sib.id === button.id) continue;
|
||
00494| if (sib.groupId !== button.groupId) continue;
|
||
00495| if (sib.parentId) continue; // 只处理一级按钮
|
||
00496|
|
||
00497| if (this.activeBtnIds.has(sib.id)) {
|
||
00498| this.setBtnActive(sib.id, false);
|
||
00499| if (sib.onClick) sib.onClick(sib);
|
||
00500| }
|
||
00501| }
|
||
00502| }
|
||
00503|
|
||
00504| private handleMouseEnter(button: OptButton, btnEl: HTMLElement): void {
|
||
00505| if (this.hoverTimeout) clearTimeout(this.hoverTimeout);
|
||
00506| if (button.children && button.children.length > 0) {
|
||
00507| this.showDropdown(button, btnEl);
|
||
00508| } else {
|
||
00509| this.closeDropdown();
|
||
00510| }
|
||
00511| }
|
||
00512|
|
||
00513| private handleMouseLeave(): void {
|
||
00514| this.hoverTimeout = window.setTimeout(() => this.closeDropdown(), 200);
|
||
00515| }
|
||
00516|
|
||
00517| private showDropdown(button: OptButton, btnEl: HTMLElement): void {
|
||
00518| this.closeDropdown();
|
||
00519| if (!button.children) return;
|
||
00520|
|
||
00521| const dropdown = document.createElement('div');
|
||
00522| dropdown.className = 'opt-btn-dropdown';
|
||
00523| if (this.options.backgroundColor) dropdown.style.setProperty('--bim-toolbar-bg', this.options.backgroundColor);
|
||
00524|
|
||
00525| // 将主题CSS变量复制到dropdown元素上(因为dropdown被添加到body,无法继承容器的CSS变量)
|
||
00526| const dropdownStyle = dropdown.style;
|
||
00527| if (this.options.iconColor) dropdownStyle.setProperty('--bim-icon-color', this.options.iconColor);
|
||
00528| if (this.options.iconActiveColor) dropdownStyle.setProperty('--bim-icon-active-color', this.options.iconActiveColor);
|
||
00529| if (this.options.textColor) dropdownStyle.setProperty('--bim-btn-text-color', this.options.textColor);
|
||
00530| if (this.options.textActiveColor) dropdownStyle.setProperty('--bim-btn-text-active-color', this.options.textActiveColor);
|
||
00531| if (this.options.btnBackgroundColor) dropdownStyle.setProperty('--bim-btn-bg', this.options.btnBackgroundColor);
|
||
00532| if (this.options.btnHoverColor) dropdownStyle.setProperty('--bim-btn-hover-bg', this.options.btnHoverColor);
|
||
00533| if (this.options.btnActiveColor) dropdownStyle.setProperty('--bim-btn-active-bg', this.options.btnActiveColor);
|
||
00534|
|
||
00535| // 获取按钮的位置信息
|
||
00536| const btnRect = btnEl.getBoundingClientRect();
|
||
00537| const expand = this.options.expand || 'down';
|
||
00538|
|
||
00539| // 根据主按钮组的方向设置下拉菜单的布局方向
|
||
00540| if (this.options.direction === 'row') {
|
||
00541| dropdown.style.flexDirection = 'column'; // 横向按钮组,菜单纵向排列
|
||
00542| } else {
|
||
00543| dropdown.style.flexDirection = 'row'; // 纵向按钮组,菜单横向排列
|
||
00544| }
|
||
00545|
|
||
00546| // 先添加到 DOM 以便计算尺寸
|
||
00547| document.body.appendChild(dropdown);
|
||
00548|
|
||
00549| // 添加事件拦截
|
||
00550| this.setupEventInterception(dropdown);
|
||
00551|
|
||
00552| // 添加菜单项
|
||
00553| button.children.forEach(subBtn => {
|
||
00554| if (this.isVisible(subBtn.id)) {
|
||
00555| const item = this.renderDropdownItem(subBtn);
|
||
00556| dropdown.appendChild(item);
|
||
00557| }
|
||
00558| });
|
||
00559|
|
||
00560| // 获取下拉菜单的实际尺寸
|
||
00561| const dropdownRect = dropdown.getBoundingClientRect();
|
||
00562|
|
||
00563| if (expand === 'up') {
|
||
00564| // 向上展开,与按钮水平居中对齐
|
||
00565| dropdown.style.bottom = (window.innerHeight - btnRect.top + 8) + 'px';
|
||
00566| dropdown.style.left = (btnRect.left + (btnRect.width - dropdownRect.width) / 2) + 'px';
|
||
00567| } else if (expand === 'down') {
|
||
00568| // 向下展开,与按钮水平居中对齐
|
||
00569| dropdown.style.top = (btnRect.bottom + 8) + 'px';
|
||
00570| dropdown.style.left = (btnRect.left + (btnRect.width - dropdownRect.width) / 2) + 'px';
|
||
00571| } else if (expand === 'right') {
|
||
00572| // 向右展开,与按钮垂直居中对齐
|
||
00573| dropdown.style.top = (btnRect.top + (btnRect.height - dropdownRect.height) / 2) + 'px';
|
||
00574| dropdown.style.left = (btnRect.right + 8) + 'px';
|
||
00575| } else if (expand === 'left') {
|
||
00576| // 向左展开,与按钮垂直居中对齐
|
||
00577| dropdown.style.top = (btnRect.top + (btnRect.height - dropdownRect.height) / 2) + 'px';
|
||
00578| dropdown.style.right = (window.innerWidth - btnRect.left + 8) + 'px';
|
||
00579| }
|
||
00580|
|
||
00581| dropdown.addEventListener('mouseenter', () => { if (this.hoverTimeout) clearTimeout(this.hoverTimeout); });
|
||
00582| dropdown.addEventListener('mouseleave', () => this.handleMouseLeave());
|
||
00583| this.dropdownElement = dropdown;
|
||
00584| }
|
||
00585|
|
||
00586| private renderDropdownItem(button: OptButton): HTMLElement {
|
||
00587| const item = document.createElement('div');
|
||
00588| item.className = 'opt-btn-dropdown-item';
|
||
00589|
|
||
00590| // 应用按钮的 align 设置,默认为 horizontal(图标在左)
|
||
00591| const align = button.align || 'horizontal';
|
||
00592| if (align === 'horizontal') {
|
||
00593| item.classList.add('align-horizontal');
|
||
00594| } else {
|
||
00595| item.classList.add('align-vertical');
|
||
00596| }
|
||
00597|
|
||
00598| // 二级菜单项的 active 状态渲染(修复 keepActive 在二级按钮“看起来不生效”的问题)
|
||
00599| // 说明:
|
||
00600| // - keepActive 的状态会记录在 activeBtnIds / button.isActive 上
|
||
00601| // - 下拉菜单每次打开都会重新渲染,因此必须在这里同步一次 active 样式
|
||
00602| if (this.activeBtnIds.has(button.id) || button.isActive) {
|
||
00603| item.classList.add('active');
|
||
00604| }
|
||
00605|
|
||
00606| // 应用按钮的自定义样式
|
||
00607| const iconSize = button.iconSize || 32; // 二级菜单默认图标更小
|
||
00608| const minWidth = button.minWidth; // 不设置默认值,让下拉菜单项保持紧凑
|
||
00609| if (minWidth) {
|
||
00610| item.style.minWidth = `${minWidth}px`;
|
||
00611| }
|
||
00612|
|
||
00613| const icon = document.createElement('div');
|
||
00614| icon.className = 'opt-btn-icon';
|
||
00615| icon.style.width = `${iconSize}px`;
|
||
00616| icon.style.height = `${iconSize}px`;
|
||
00617| icon.innerHTML = this.getIcon(button.icon);
|
||
00618| item.appendChild(icon);
|
||
00619|
|
||
00620| // 只有在 showLabel 为 true 时才显示 label
|
||
00621| if (this.options.showLabel && button.label) {
|
||
00622| const label = document.createElement('span');
|
||
00623| label.className = 'opt-btn-dropdown-label';
|
||
00624| label.textContent = t(button.label);
|
||
00625| item.appendChild(label);
|
||
00626| } else if (button.label) {
|
||
00627| // 当不显示 label 时,添加 title 属性作为 tooltip
|
||
00628| item.title = t(button.label);
|
||
00629| }
|
||
00630|
|
||
00631| item.addEventListener('click', (e) => { e.stopPropagation(); this.handleClick(button); });
|
||
00632| return item;
|
||
00633| }
|
||
00634|
|
||
00635| private closeDropdown(): void {
|
||
00636| if (this.dropdownElement) {
|
||
00637| this.dropdownElement.remove();
|
||
00638| this.dropdownElement = null;
|
||
00639| }
|
||
00640| this.btnRefs.forEach(btnEl => {
|
||
00641| const arrow = btnEl.querySelector('.opt-btn-arrow');
|
||
00642| if (arrow) arrow.classList.remove('rotated');
|
||
00643| });
|
||
00644| }
|
||
00645|
|
||
00646| private updateButtonState(buttonId: string): void {
|
||
00647| const btnEl = this.btnRefs.get(buttonId);
|
||
00648| if (btnEl) {
|
||
00649| if (this.activeBtnIds.has(buttonId)) {
|
||
00650| btnEl.classList.add('active');
|
||
00651| } else {
|
||
00652| btnEl.classList.remove('active');
|
||
00653| }
|
||
00654| }
|
||
00655| }
|
||
00656|
|
||
00657| private getIcon(icon?: string): string { return icon || this.DEFAULT_ICON; }
|
||
00658|
|
||
00659| public updateButtonVisibility(id: string, visible: boolean): void {
|
||
00660| if (!this.options.visibility) this.options.visibility = {};
|
||
00661| this.options.visibility[id] = visible;
|
||
00662| this.render();
|
||
00663| }
|
||
00664|
|
||
00665| public setShowLabel(show: boolean): void {
|
||
00666| this.options.showLabel = show;
|
||
00667| this.updateLabelsVisibility();
|
||
00668| }
|
||
00669|
|
||
00670| private updateLabelsVisibility(): void {
|
||
00671| this.btnRefs.forEach((btnEl, buttonId) => {
|
||
00672| // 查找按钮配置
|
||
00673| const button = this.findButtonById(buttonId);
|
||
00674| if (!button) return;
|
||
00675|
|
||
00676| const hasLabel = this.options.showLabel && button.label;
|
||
00677|
|
||
00678| // 更新 no-label 类和 title 属性
|
||
00679| if (hasLabel) {
|
||
00680| btnEl.classList.remove('no-label');
|
||
00681| // 显示标签时,移除 title(避免重复显示)
|
||
00682| btnEl.removeAttribute('title');
|
||
00683| } else {
|
||
00684| btnEl.classList.add('no-label');
|
||
00685| // 隐藏标签时,添加 title 作为 tooltip
|
||
00686| if (button.label) {
|
||
00687| btnEl.title = t(button.label);
|
||
00688| }
|
||
00689| }
|
||
00690| });
|
||
00691| }
|
||
00692|
|
||
00693| private findButtonById(id: string): OptButton | undefined {
|
||
00694| for (const group of this.groups) {
|
||
00695| const found = this.findButton(group.buttons, id);
|
||
00696| if (found) return found;
|
||
00697| }
|
||
00698| return undefined;
|
||
00699| }
|
||
00700|
|
||
00701| public setBackgroundColor(color: string): void { this.setColors({ backgroundColor: color }); }
|
||
00702| private isVisible(id: string): boolean { return this.options.visibility?.[id] !== false; }
|
||
00703|
|
||
00704| public destroy(): void {
|
||
00705| if (this.unsubscribeLocale) {
|
||
00706| this.unsubscribeLocale();
|
||
00707| this.unsubscribeLocale = null;
|
||
00708| }
|
||
00709| if (this.unsubscribeTheme) {
|
||
00710| this.unsubscribeTheme();
|
||
00711| this.unsubscribeTheme = null;
|
||
00712| }
|
||
00713| this.closeDropdown();
|
||
00714| this.container.innerHTML = '';
|
||
00715| this.btnRefs.clear();
|
||
00716| }
|
||
00717| }
|
||
00718|
|
||
|
||
(End of file - total 718 lines)
|
||
</file>
|
||
```
|
||
```
|
||
|
||
```
|
||
Tool: read
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.css"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
<file>
|
||
00001| /* 根容器 */
|
||
00002| .bim-btn-group-root {
|
||
00003| display: flex;
|
||
00004| gap: 8px;
|
||
00005| z-index: 1000;
|
||
00006| position: absolute;
|
||
00007| pointer-events: auto;
|
||
00008| }
|
||
00009|
|
||
00010| .bim-btn-group-root.static {
|
||
00011| position: relative;
|
||
00012| top: auto;
|
||
00013| left: auto;
|
||
00014| bottom: auto;
|
||
00015| right: auto;
|
||
00016| transform: none;
|
||
00017| }
|
||
00018|
|
||
00019| .bim-btn-group-root.dir-row {
|
||
00020| flex-direction: row;
|
||
00021| align-items: center;
|
||
00022| }
|
||
00023|
|
||
00024| .bim-btn-group-root.dir-column {
|
||
00025| flex-direction: column;
|
||
00026| align-items: stretch;
|
||
00027| }
|
||
00028|
|
||
00029| /* 分组区域 */
|
||
00030| .bim-btn-group-section {
|
||
00031| display: flex;
|
||
00032| gap: 4px;
|
||
00033| background-color: var(--bim-btn-group-section-bg, rgba(17, 17, 17, 0.88));
|
||
00034| border-radius: 6px;
|
||
00035| padding: 4px;
|
||
00036| /* 添加阴影效果,增强视觉层次感 */
|
||
00037| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3), 0 1px 3px rgba(0, 0, 0, 0.2);
|
||
00038| }
|
||
00039|
|
||
00040| .bim-btn-group-root.dir-row .bim-btn-group-section {
|
||
00041| flex-direction: row;
|
||
00042| align-items: center;
|
||
00043| }
|
||
00044|
|
||
00045| .bim-btn-group-root.dir-column .bim-btn-group-section {
|
||
00046| flex-direction: column;
|
||
00047| }
|
||
00048|
|
||
00049| /* 按钮外层 */
|
||
00050| .opt-btn-wrapper {
|
||
00051| position: relative;
|
||
00052| }
|
||
00053|
|
||
00054| /* 按钮本体 */
|
||
00055| .opt-btn {
|
||
00056| display: flex;
|
||
00057| cursor: pointer;
|
||
00058| border-radius: 4px;
|
||
00059| transition: background-color 0.2s, color 0.2s, border-color 0.2s;
|
||
00060| color: var(--bim-btn-text-color, #ccc);
|
||
00061| background-color: var(--bim-btn-bg, transparent);
|
||
00062| padding: 6px;
|
||
00063| align-items: center;
|
||
00064| position: relative;
|
||
00065| /* 为绝对定位提供锚点 */
|
||
00066| justify-content: center;
|
||
00067| border: 1px solid transparent;
|
||
00068| outline: none;
|
||
00069| }
|
||
00070|
|
||
00071| .opt-btn:hover {
|
||
00072| background-color: var(--bim-btn-hover-bg, #444);
|
||
00073| }
|
||
00074|
|
||
00075| .opt-btn.active {
|
||
00076| background-color: var(--bim-btn-active-bg, rgba(255, 255, 255, 0.15));
|
||
00077| color: var(--bim-btn-text-active-color, #fff);
|
||
00078| /* 开关按钮激活时不显示边框,只改变背景 */
|
||
00079| }
|
||
00080|
|
||
00081| /* 开关按钮激活时图标颜色保持不变,继承默认颜色 */
|
||
00082|
|
||
00083| .opt-btn.disabled {
|
||
00084| opacity: 0.5;
|
||
00085| cursor: not-allowed;
|
||
00086| }
|
||
00087|
|
||
00088| /* --- 图标 --- */
|
||
00089| .opt-btn-icon {
|
||
00090| width: var(--bim-icon-size, 24px);
|
||
00091| height: var(--bim-icon-size, 24px);
|
||
00092| display: flex;
|
||
00093| align-items: center;
|
||
00094| justify-content: center;
|
||
00095| color: var(--bim-icon-color, #ccc);
|
||
00096| flex-shrink: 0;
|
||
00097| }
|
||
00098|
|
||
00099| .opt-btn-icon svg {
|
||
00100| width: 100%;
|
||
00101| height: 100%;
|
||
00102| fill: currentColor;
|
||
00103| }
|
||
00104|
|
||
00105| /* --- 箭头 --- */
|
||
00106| .opt-btn-arrow {
|
||
00107| font-size: 10px;
|
||
00108| opacity: 0.6;
|
||
00109| transition: transform 0.2s;
|
||
00110| display: inline-block;
|
||
00111| /* 默认情况 (有Label) */
|
||
00112| margin-left: 4px;
|
||
00113| }
|
||
00114|
|
||
00115| .opt-btn-arrow.rotated {
|
||
00116| transform: rotate(180deg);
|
||
00117| }
|
||
00118|
|
||
00119| /* --- 文字容器 --- */
|
||
00120| .opt-btn-text-wrapper {
|
||
00121| display: flex;
|
||
00122| align-items: center;
|
||
00123| justify-content: center;
|
||
00124| pointer-events: none;
|
||
00125| }
|
||
00126|
|
||
00127| /* --- Label 显示控制 --- */
|
||
00128| .opt-btn-label {
|
||
00129| display: inline;
|
||
00130| }
|
||
00131|
|
||
00132| .opt-btn.no-label .opt-btn-label {
|
||
00133| display: none;
|
||
00134| }
|
||
00135|
|
||
00136| /* --- 场景 A: 有 Label (常规布局) --- */
|
||
00137|
|
||
00138| .opt-btn.align-vertical:not(.no-label) {
|
||
00139| flex-direction: column;
|
||
00140| text-align: center;
|
||
00141| }
|
||
00142|
|
||
00143| .opt-btn.align-vertical:not(.no-label) .opt-btn-text-wrapper {
|
||
00144| margin-top: 4px;
|
||
00145| }
|
||
00146|
|
||
00147| .opt-btn.align-vertical:not(.no-label) .opt-btn-label {
|
||
00148| font-size: 12px;
|
||
00149| line-height: 1.2;
|
||
00150| }
|
||
00151|
|
||
00152| .opt-btn.align-horizontal:not(.no-label) {
|
||
00153| flex-direction: row;
|
||
00154| }
|
||
00155|
|
||
00156| .opt-btn.align-horizontal:not(.no-label) .opt-btn-text-wrapper {
|
||
00157| margin-left: 8px;
|
||
00158| }
|
||
00159|
|
||
00160| .opt-btn.align-horizontal:not(.no-label) .opt-btn-label {
|
||
00161| font-size: 14px;
|
||
00162| }
|
||
00163|
|
||
00164| /* --- 场景 B: 无 Label (强制绝对定位) --- */
|
||
00165|
|
||
00166| /* 当没有 label 时,text-wrapper 其实只包裹了 arrow */
|
||
00167| /* 我们需要让 wrapper 失去布局影响,直接定位内部的 arrow */
|
||
00168|
|
||
00169| .opt-btn.no-label .opt-btn-text-wrapper {
|
||
00170| /* 让 wrapper 变为 0 尺寸,不影响 flex 布局 */
|
||
00171| width: 0;
|
||
00172| height: 0;
|
||
00173| margin: 0;
|
||
00174| padding: 0;
|
||
00175| overflow: visible;
|
||
00176| /* 关键:允许子元素溢出 */
|
||
00177| position: absolute;
|
||
00178| /* 脱离文档流,相对于 .opt-btn */
|
||
00179| top: 0;
|
||
00180| right: 0;
|
||
00181| }
|
||
00182|
|
||
00183| .opt-btn.no-label .opt-btn-arrow {
|
||
00184| position: absolute;
|
||
00185| top: 2px;
|
||
00186| right: 2px;
|
||
00187| margin: 0;
|
||
00188| /* 清除之前的 margin */
|
||
00189| font-size: 8px;
|
||
00190| /*
|
||
00191| 如果父级 wrapper 已经是 top:0, right:0
|
||
00192| 那么 arrow 相对 wrapper 定位即可
|
||
00193| */
|
||
00194| }
|
||
00195|
|
||
00196| /* --- 下拉菜单 & 动画 --- */
|
||
00197| .opt-btn-dropdown {
|
||
00198| position: absolute;
|
||
00199| background-color: var(--bim-toolbar-bg, rgba(17, 17, 17, 0.95));
|
||
00200| border-radius: 4px;
|
||
00201| padding: 4px;
|
||
00202| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||
00203| z-index: 1001;
|
||
00204| display: flex;
|
||
00205| flex-direction: column;
|
||
00206| border: 1px solid rgba(255, 255, 255, 0.1);
|
||
00207|
|
||
00208| /* 动画起始状态 */
|
||
00209| opacity: 0;
|
||
00210| visibility: hidden;
|
||
00211| transform: translateY(-10px);
|
||
00212| /* 默认向上偏移一点 */
|
||
00213| transition: opacity 0.2s ease, transform 0.2s cubic-bezier(0.2, 0, 0.2, 1), visibility 0.2s;
|
||
00214| }
|
||
00215|
|
||
00216| /* 动画激活状态 (需要 JS 添加 .show 类,或者直接在 display:flex 时生效?) */
|
||
00217| /* 这里的实现有点 tricky,因为 JS 里直接 appendChild */
|
||
00218| /* 我们可以利用 CSS 动画关键帧,或者简单的 transition */
|
||
00219| /* 由于 DOM 是动态插入的,插入瞬间是 opacity: 0 -> requestAnimationFrame -> opacity: 1 */
|
||
00220| /* 简单的办法:添加动画 keyframes */
|
||
00221|
|
||
00222| @keyframes dropdown-fade-in {
|
||
00223| from {
|
||
00224| opacity: 0;
|
||
00225| transform: translateY(-8px) scale(0.98);
|
||
00226| }
|
||
00227|
|
||
00228| to {
|
||
00229| opacity: 1;
|
||
00230| transform: translateY(0) scale(1);
|
||
00231| }
|
||
00232| }
|
||
00233|
|
||
00234| .opt-btn-dropdown {
|
||
00235| /* 覆盖上面的 transition,直接用 animation */
|
||
00236| animation: dropdown-fade-in 0.2s cubic-bezier(0.2, 0, 0.2, 1) forwards;
|
||
00237| /* 初始可见性由 JS 控制(append 即显示) */
|
||
00238| opacity: 1;
|
||
00239| visibility: visible;
|
||
00240| transform: none;
|
||
00241| }
|
||
00242|
|
||
00243| .opt-btn-dropdown-item {
|
||
00244| display: flex;
|
||
00245| align-items: center;
|
||
00246| padding: 8px 12px;
|
||
00247| cursor: pointer;
|
||
00248| border-radius: 4px;
|
||
00249| color: var(--bim-btn-text-color, #ccc);
|
||
00250| transition: background 0.2s, border-color 0.2s, color 0.2s;
|
||
00251| box-sizing: border-box;
|
||
00252| border: 1px solid transparent;
|
||
00253| outline: none;
|
||
00254| }
|
||
00255|
|
||
00256| /* 二级菜单项的图标默认颜色 */
|
||
00257| .opt-btn-dropdown-item .opt-btn-icon {
|
||
00258| color: var(--bim-icon-color, #ccc);
|
||
00259| }
|
||
00260|
|
||
00261| .opt-btn-dropdown-item:hover {
|
||
00262| background-color: var(--bim-btn-hover-bg, #444);
|
||
00263| }
|
||
00264|
|
||
00265| /* 二级菜单项激活态(支持 keepActive 的可视化) */
|
||
00266| .opt-btn-dropdown-item.active {
|
||
00267| background-color: var(--bim-btn-active-bg, rgba(255, 255, 255, 0.15));
|
||
00268| color: var(--bim-btn-text-active-color, #fff);
|
||
00269| /* 开关按钮激活时不显示边框,只改变背景 */
|
||
00270| }
|
||
00271|
|
||
00272| /* 开关按钮激活时图标颜色保持不变,继承默认颜色 */
|
||
00273|
|
||
00274| /* 下拉菜单项 - 横向布局(图标在左,默认) */
|
||
00275| .opt-btn-dropdown-item.align-horizontal {
|
||
00276| flex-direction: row;
|
||
00277| }
|
||
00278|
|
||
00279| .opt-btn-dropdown-item.align-horizontal .opt-btn-icon {
|
||
00280| width: 18px;
|
||
00281| height: 18px;
|
||
00282| margin-right: 8px;
|
||
00283| }
|
||
00284|
|
||
00285| /* 下拉菜单项 - 纵向布局(图标在上) */
|
||
00286| .opt-btn-dropdown-item.align-vertical {
|
||
00287| flex-direction: column;
|
||
00288| text-align: center;
|
||
00289| }
|
||
00290|
|
||
00291| .opt-btn-dropdown-item.align-vertical .opt-btn-icon {
|
||
00292| width: 24px;
|
||
00293| height: 24px;
|
||
00294| margin-bottom: 4px;
|
||
00295| }
|
||
00296|
|
||
00297| .opt-btn-dropdown-item.align-vertical .opt-btn-dropdown-label {
|
||
00298| font-size: 12px;
|
||
00299| }
|
||
00300|
|
||
00301| /* --- 特定样式覆盖:底部工具栏 --- */
|
||
00302| .bim-btn-group-root.is-bottom-toolbar .opt-btn-icon {
|
||
00303| width: 32px;
|
||
00304| height: 32px;
|
||
00305| }
|
||
00306|
|
||
00307| .bim-btn-group-root.is-bottom-toolbar .opt-btn {
|
||
00308| padding: 8px;
|
||
00309| }
|
||
|
||
(End of file - total 309 lines)
|
||
</file>
|
||
```
|
||
```
|
||
|
||
```
|
||
Tool: read
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.type.ts"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
<file>
|
||
00001| export type ButtonType = 'button' | 'menu';
|
||
00002|
|
||
00003| /** 按钮配置 */
|
||
00004| export interface ButtonConfig {
|
||
00005| id: string;
|
||
00006| type: ButtonType;
|
||
00007| label: string;
|
||
00008| icon?: string;
|
||
00009| keepActive?: boolean;
|
||
00010| /**
|
||
00011| * 是否互斥(开关互斥)
|
||
00012| *
|
||
00013| * 行为说明:
|
||
00014| * - 当按钮从“未激活”切换到“激活”时,如果该按钮开启了 exclusive,
|
||
00015| * 会自动关闭同互斥范围内的其它已激活按钮,并触发它们的 onClick(用于执行关闭逻辑)。
|
||
00016| * - 一级按钮:互斥范围 = 同 groupId 下的一级按钮
|
||
00017| * - 二级按钮:互斥范围 = 同 groupId 且同 parentId 下的二级按钮
|
||
00018| *
|
||
00019| * 注意:该能力通常与 keepActive 搭配使用。
|
||
00020| */
|
||
00021| exclusive?: boolean;
|
||
00022| isActive?:boolean;
|
||
00023| disabled?: boolean;
|
||
00024| onClick?: (button: OptButton) => void;
|
||
00025| children?: ButtonConfig[];
|
||
00026| groupId?: string;
|
||
00027| parentId?: string;
|
||
00028| /** 按钮内部图标文字排列 (默认 vertical,即图标在上) */
|
||
00029| align?: ButtonAlign;
|
||
00030| /** 图标大小 (正方形,单位 px,默认 32) */
|
||
00031| iconSize?: number;
|
||
00032| /** 按钮最小宽度 (单位 px,默认 50) */
|
||
00033| minWidth?: number;
|
||
00034| }
|
||
00035|
|
||
00036| export interface OptButton extends ButtonConfig {
|
||
00037| children?: OptButton[];
|
||
00038| }
|
||
00039|
|
||
00040| export interface ButtonGroup {
|
||
00041| id: string;
|
||
00042| buttons: OptButton[];
|
||
00043| }
|
||
00044|
|
||
00045| export interface ButtonGroupColors {
|
||
00046| backgroundColor?: string;
|
||
00047| btnBackgroundColor?: string;
|
||
00048| btnHoverColor?: string;
|
||
00049| btnActiveColor?: string;
|
||
00050| iconColor?: string;
|
||
00051| iconActiveColor?: string;
|
||
00052| textColor?: string;
|
||
00053| textActiveColor?: string;
|
||
00054| }
|
||
00055|
|
||
00056| // --- 新增布局类型 ---
|
||
00057|
|
||
00058| /** 弹窗/按钮组位置 */
|
||
00059| export type GroupPosition =
|
||
00060| | 'center'
|
||
00061| | 'top-left' | 'top-center' | 'top-right'
|
||
00062| | 'left-center' | 'right-center'
|
||
00063| | 'bottom-left' | 'bottom-center' | 'bottom-right'
|
||
00064| | { x: number; y: number }
|
||
00065| | 'static'; // static 表示不绝对定位,随文档流
|
||
00066|
|
||
00067| /** 按钮组排列方向 (Flex-direction) */
|
||
00068| export type GroupDirection = 'row' | 'column';
|
||
00069|
|
||
00070| /** 按钮内部文字图标排列 */
|
||
00071| export type ButtonAlign = 'vertical' /* 图标在上 */ | 'horizontal' /* 图标在左 */;
|
||
00072|
|
||
00073| /** 二级菜单展开方向 */
|
||
00074| export type ExpandDirection = 'up' | 'down' | 'left' | 'right';
|
||
00075|
|
||
00076| export interface ButtonGroupOptions extends ButtonGroupColors {
|
||
00077| container: HTMLElement | string;
|
||
00078|
|
||
00079| /** 屏幕位置 (如 top-left) */
|
||
00080| position?: GroupPosition;
|
||
00081|
|
||
00082| /** 按钮组排列方向 (默认 row) */
|
||
00083| direction?: GroupDirection;
|
||
00084|
|
||
00085| /** 按钮内部图标文字排列 (默认 vertical) */
|
||
00086| align?: ButtonAlign;
|
||
00087|
|
||
00088| /** 菜单展开方向 */
|
||
00089| expand?: ExpandDirection;
|
||
00090|
|
||
00091| showLabel?: boolean;
|
||
00092| visibility?: Record<string, boolean>;
|
||
00093| className?: string;
|
||
00094| }
|
||
00095|
|
||
00096| export interface ClickPayload {
|
||
00097| button: OptButton;
|
||
00098| action: 'activate' | 'deactivate' | 'trigger';
|
||
00099| isActive?: boolean;
|
||
00100| }
|
||
00101|
|
||
|
||
(End of file - total 101 lines)
|
||
</file>
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 58.8s)
|
||
|
||
现在我已全面了解现有代码和目标样式。让我分析并制定改造计划:
|
||
|
||
---
|
||
|
||
## 📋 改造计划:ButtonGroup 样式变体支持
|
||
|
||
### 1. 需求分析
|
||
|
||
**文档中定义了两种工具栏样式:**
|
||
|
||
| 特性 | 顶部中间工具栏 | 底部右侧工具栏 |
|
||
|-----|--------------|--------------|
|
||
| **容器形状** | 矩形 (rounded-md) | 胶囊 (rounded-full) |
|
||
| **容器背景** | 白色半透明 + 毛玻璃 | 白色半透明 + 毛玻璃 |
|
||
| **按钮形状** | 矩形自适应 | 圆形 48×48 |
|
||
| **激活态** | 蓝色背景 + 白字 | 蓝色背景 + 白字 |
|
||
| **二级菜单** | 无 | 有 (Hover 触发) |
|
||
|
||
**现有组件对比:**
|
||
- 深色背景 (`rgba(17,17,17,0.88)`)
|
||
- 矩形按钮
|
||
- 激活态为浅白色背景
|
||
|
||
---
|
||
|
||
### 2. 改造方案
|
||
|
||
#### 2.1 引入 `variant` 预设配置
|
||
|
||
```typescript
|
||
// index.type.ts
|
||
export type ButtonGroupVariant =
|
||
| 'default' // 现有深色样式
|
||
| 'glass' // 毛玻璃 + 矩形按钮(顶部工具栏)
|
||
| 'glass-circle' // 毛玻璃 + 圆形按钮(底部工具栏)
|
||
|
||
export interface ButtonGroupOptions extends ButtonGroupColors {
|
||
// ... 现有配置
|
||
|
||
/** 样式变体预设 */
|
||
variant?: ButtonGroupVariant;
|
||
|
||
/** Hover 延迟配置 (ms) */
|
||
hoverDelay?: {
|
||
enter?: number; // 进入延迟,默认 0
|
||
leave?: number; // 离开延迟,默认 200
|
||
};
|
||
}
|
||
```
|
||
|
||
#### 2.2 各变体样式定义
|
||
|
||
| CSS 类名 | 应用于 | 样式特点 |
|
||
|---------|-------|---------|
|
||
| `.variant-default` | root | 现有深色样式 |
|
||
| `.variant-glass` | root | 毛玻璃效果,矩形 |
|
||
| `.variant-glass-circle` | root | 毛玻璃效果,胶囊容器,圆形按钮 |
|
||
|
||
#### 2.3 新增 CSS 变量
|
||
|
||
```css
|
||
/* 容器 */
|
||
--bim-btn-group-blur: 0; /* 毛玻璃模糊值 */
|
||
--bim-btn-group-border-radius: 6px; /* 容器圆角 */
|
||
|
||
/* 按钮 */
|
||
--bim-btn-border-radius: 4px; /* 按钮圆角 */
|
||
--bim-btn-size: auto; /* 按钮尺寸 (circle 时固定) */
|
||
|
||
/* 激活态 */
|
||
--bim-btn-active-bg: ...; /* 已有 */
|
||
--bim-btn-active-border: transparent; /* 新增:激活态边框 */
|
||
```
|
||
|
||
---
|
||
|
||
### 3. 文件改动清单
|
||
|
||
| 文件 | 改动内容 |
|
||
|-----|---------|
|
||
| `src/components/button-group/index.type.ts` | 新增 `ButtonGroupVariant` 类型和相关配置 |
|
||
| `src/components/button-group/index.css` | 新增 `.variant-glass`、`.variant-glass-circle` 等样式 |
|
||
| `src/components/button-group/index.ts` | 处理 variant 配置,应用对应类名 |
|
||
| `docs/components/按钮组组件-ButtonGroup.md` | 更新文档 |
|
||
|
||
---
|
||
|
||
### 4. CSS 样式详细设计
|
||
|
||
#### 4.1 `.variant-glass` (顶部工具栏样式)
|
||
|
||
```css
|
||
.bim-btn-group-root.variant-glass .bim-btn-group-section {
|
||
background: rgba(255, 255, 255, 0.8);
|
||
backdrop-filter: blur(10px);
|
||
-webkit-backdrop-filter: blur(10px);
|
||
border: 1px solid rgba(226, 232, 240, 0.5);
|
||
border-radius: 6px;
|
||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.04);
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass .opt-btn {
|
||
border-radius: 6px;
|
||
color: rgb(71, 85, 105); /* slate-600 */
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass .opt-btn:hover {
|
||
background: rgba(241, 245, 249, 0.6);
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass .opt-btn.active {
|
||
background: rgb(59, 130, 246); /* blue-500 */
|
||
color: white;
|
||
border: 1px solid rgb(37, 99, 235);
|
||
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
|
||
}
|
||
```
|
||
|
||
#### 4.2 `.variant-glass-circle` (底部工具栏样式)
|
||
|
||
```css
|
||
.bim-btn-group-root.variant-glass-circle .bim-btn-group-section {
|
||
background: rgba(255, 255, 255, 0.8);
|
||
backdrop-filter: blur(10px);
|
||
border: 1px solid rgba(226, 232, 240, 0.5);
|
||
border-radius: 9999px; /* pill */
|
||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
|
||
padding: 8px;
|
||
gap: 10px;
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass-circle .opt-btn {
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 9999px; /* circle */
|
||
background: rgba(255, 255, 255, 0.9);
|
||
border: 1px solid rgba(203, 213, 225, 0.6);
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass-circle .opt-btn:hover {
|
||
background: white;
|
||
transform: scale(1.05);
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass-circle .opt-btn.active {
|
||
background: rgb(37, 99, 235); /* blue-600 */
|
||
border-color: rgb(29, 78, 216);
|
||
color: white;
|
||
}
|
||
```
|
||
|
||
#### 4.3 二级菜单样式 (glass 变体)
|
||
|
||
```css
|
||
.opt-btn-dropdown.variant-glass {
|
||
background: rgba(255, 255, 255, 0.95);
|
||
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.variant-glass .opt-btn-dropdown-item {
|
||
border-radius: 8px;
|
||
color: rgb(71, 85, 105);
|
||
padding: 8px 12px;
|
||
}
|
||
|
||
.opt-btn-dropdown.variant-glass .opt-btn-dropdown-item:hover {
|
||
background: rgb(241, 245, 249);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 5. 使用示例
|
||
|
||
```typescript
|
||
// 顶部中间工具栏
|
||
const topToolbar = buttonGroupManager.create('top-toolbar', {
|
||
variant: 'glass',
|
||
position: 'top-center',
|
||
direction: 'row',
|
||
showLabel: true, // 响应式可控制
|
||
});
|
||
|
||
// 底部右侧工具栏
|
||
const bottomToolbar = buttonGroupManager.create('bottom-toolbar', {
|
||
variant: 'glass-circle',
|
||
position: 'bottom-right',
|
||
direction: 'row',
|
||
showLabel: false,
|
||
expand: 'up',
|
||
hoverDelay: { enter: 100, leave: 250 },
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
### 6. 暗色模式支持
|
||
|
||
需要为 glass 变体添加暗色模式样式:
|
||
|
||
```css
|
||
/* 通过 CSS 变量或 .dark 类控制 */
|
||
.bim-btn-group-root.variant-glass.dark .bim-btn-group-section,
|
||
.dark .bim-btn-group-root.variant-glass .bim-btn-group-section {
|
||
background: rgba(30, 41, 59, 0.8);
|
||
border-color: rgba(71, 85, 105, 0.5);
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass.dark .opt-btn,
|
||
.dark .bim-btn-group-root.variant-glass .opt-btn {
|
||
color: rgba(148, 163, 184, 1);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 7. 问题与待确认
|
||
|
||
1. **暗色模式如何触发?**
|
||
- 通过容器添加 `.dark` 类?
|
||
- 还是通过 CSS 变量 `--bim-color-scheme`?
|
||
|
||
2. **是否需要支持自定义激活色?**
|
||
- 当前文档使用蓝色,但项目可能需要自定义 primary 色
|
||
|
||
3. **响应式标签显示**
|
||
- 文档中 `lg:inline` 表示大屏显示标签
|
||
- 是否需要内置响应式支持,还是由使用方控制?
|
||
|
||
---
|
||
|
||
**请确认以上方案是否符合预期,或需要调整哪些部分?**
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
1、暗色模式如何触发应该是通过事件触发的 2、激活色都是通过设置主题配置的
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 11.9s)
|
||
|
||
明白,让我查看现有的主题系统实现:
|
||
|
||
```
|
||
Tool: read
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/services/theme.ts"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
<file>
|
||
00001| import { ThemeConfig } from '../themes/types';
|
||
00002| import { darkTheme, lightTheme } from '../themes/presets';
|
||
00003|
|
||
00004| type ThemeChangeListener = (theme: ThemeConfig) => void;
|
||
00005|
|
||
00006| /**
|
||
00007| * 主题管理器 (单例)
|
||
00008| */
|
||
00009| export class ThemeManager {
|
||
00010| private currentTheme: ThemeConfig = darkTheme;
|
||
00011| private listeners: ThemeChangeListener[] = [];
|
||
00012|
|
||
00013| constructor() {
|
||
00014| // 默认初始化
|
||
00015| }
|
||
00016|
|
||
00017| /**
|
||
00018| * 获取当前主题配置
|
||
00019| */
|
||
00020| public getTheme(): ThemeConfig {
|
||
00021| return this.currentTheme;
|
||
00022| }
|
||
00023|
|
||
00024| /**
|
||
00025| * 切换预设主题
|
||
00026| * @param themeName 'dark' | 'light'
|
||
00027| */
|
||
00028| public setTheme(themeName: 'dark' | 'light') {
|
||
00029| if (themeName === 'light') {
|
||
00030| this.applyTheme(lightTheme);
|
||
00031| } else {
|
||
00032| this.applyTheme(darkTheme);
|
||
00033| }
|
||
00034| }
|
||
00035|
|
||
00036| /**
|
||
00037| * 应用自定义主题配置
|
||
00038| * @param theme 配置对象
|
||
00039| */
|
||
00040| public setCustomTheme(theme: ThemeConfig) {
|
||
00041| this.applyTheme(theme);
|
||
00042| }
|
||
00043|
|
||
00044| /**
|
||
00045| * 内部应用主题逻辑
|
||
00046| */
|
||
00047| private applyTheme(theme: ThemeConfig) {
|
||
00048| this.currentTheme = theme;
|
||
00049| this.notifyListeners();
|
||
00050| }
|
||
00051|
|
||
00052| /**
|
||
00053| * 订阅主题变更
|
||
00054| */
|
||
00055| public subscribe(listener: ThemeChangeListener): () => void {
|
||
00056| this.listeners.push(listener);
|
||
00057| // 立即回调一次当前状态
|
||
00058| listener(this.currentTheme);
|
||
00059| return () => {
|
||
00060| this.listeners = this.listeners.filter(l => l !== listener);
|
||
00061| };
|
||
00062| }
|
||
00063|
|
||
00064| private notifyListeners() {
|
||
00065| this.listeners.forEach(listener => listener(this.currentTheme));
|
||
00066| }
|
||
00067| }
|
||
00068|
|
||
00069| // 导出单例
|
||
00070| export const themeManager = new ThemeManager();
|
||
|
||
(End of file - total 70 lines)
|
||
</file>
|
||
```
|
||
```
|
||
|
||
```
|
||
Tool: read
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/themes/types.ts"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
<file>
|
||
00001| /**
|
||
00002| * 全局主题配置接口
|
||
00003| * 定义系统通用的语义化颜色
|
||
00004| */
|
||
00005| export interface ThemeConfig {
|
||
00006| /** 主题名称 */
|
||
00007| name: string;
|
||
00008|
|
||
00009| /** 品牌色/主色 */
|
||
00010| primary: string;
|
||
00011| /** 主色悬停/激活态 */
|
||
00012| primaryHover: string;
|
||
00013|
|
||
00014| /** 基础背景色 (应用整体背景) */
|
||
00015| background: string;
|
||
00016| /** 面板背景色 (工具栏、弹窗背景) */
|
||
00017| panelBackground: string;
|
||
00018|
|
||
00019| /** 主要文字颜色 */
|
||
00020| textPrimary: string;
|
||
00021| /** 次要文字颜色 */
|
||
00022| textSecondary: string;
|
||
00023|
|
||
00024| /** 边框/分割线颜色 */
|
||
00025| border: string;
|
||
00026|
|
||
00027| /** 图标默认颜色 */
|
||
00028| icon: string;
|
||
00029| /** 图标激活颜色 */
|
||
00030| iconActive: string;
|
||
00031|
|
||
00032| /** 交互组件背景 (如按钮默认背景) */
|
||
00033| componentBackground: string;
|
||
00034| /** 交互组件悬停背景 */
|
||
00035| componentHover: string;
|
||
00036| /** 交互组件激活背景 */
|
||
00037| componentActive: string;
|
||
00038| }
|
||
00039|
|
||
00040| /**
|
||
00041| * 主题类型定义
|
||
00042| */
|
||
00043| export type ThemeType = 'dark' | 'light' | 'custom';
|
||
00044|
|
||
|
||
(End of file - total 44 lines)
|
||
</file>
|
||
```
|
||
```
|
||
|
||
```
|
||
Tool: read
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/themes/dark.ts"
|
||
}
|
||
```
|
||
**Error:**
|
||
```
|
||
Error: File not found: /Users/yuding/WORK/LYZ/project/bimEngine/engine/src/themes/dark.ts
|
||
```
|
||
```
|
||
|
||
```
|
||
Tool: read
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/themes/light.ts"
|
||
}
|
||
```
|
||
**Error:**
|
||
```
|
||
Error: File not found: /Users/yuding/WORK/LYZ/project/bimEngine/engine/src/themes/light.ts
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 6.0s)
|
||
|
||
```
|
||
Tool: glob
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"pattern": "src/themes/**/*"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
Found 2 file(s)
|
||
|
||
./src/themes/presets.ts
|
||
./src/themes/types.ts
|
||
[Agent Usage Reminder]
|
||
|
||
You called a search/fetch tool directly without leveraging specialized agents.
|
||
|
||
RECOMMENDED: Use background_task with explore/librarian agents for better results:
|
||
|
||
```
|
||
// Parallel exploration - fire multiple agents simultaneously
|
||
background_task(agent="explore", prompt="Find all files matching pattern X")
|
||
background_task(agent="explore", prompt="Search for implementation of Y")
|
||
background_task(agent="librarian", prompt="Lookup documentation for Z")
|
||
|
||
// Then continue your work while they run in background
|
||
// System will notify you when each completes
|
||
```
|
||
|
||
WHY:
|
||
- Agents can perform deeper, more thorough searches
|
||
- Background tasks run in parallel, saving time
|
||
- Specialized agents have domain expertise
|
||
- Reduces context window usage in main session
|
||
|
||
ALWAYS prefer: Multiple parallel background_task calls > Direct tool calls
|
||
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 5.0s)
|
||
|
||
```
|
||
Tool: read
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/themes/presets.ts"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
<file>
|
||
00001| import { ThemeConfig } from './types';
|
||
00002|
|
||
00003| /**
|
||
00004| * 深色主题 (默认)
|
||
00005| */
|
||
00006| export const darkTheme: ThemeConfig = {
|
||
00007| name: 'dark',
|
||
00008| primary: '#0078d4',
|
||
00009| primaryHover: '#0063b1',
|
||
00010|
|
||
00011| // 修改:背景色统一为浅灰,不再跟随深色模式变黑
|
||
00012| background: '#f5f5f5',
|
||
00013| panelBackground: 'rgba(30, 30, 30, 0.9)',
|
||
00014|
|
||
00015| textPrimary: '#ffffff',
|
||
00016| textSecondary: '#cccccc',
|
||
00017|
|
||
00018| border: '#444444',
|
||
00019|
|
||
00020| icon: '#cccccc',
|
||
00021| iconActive: '#ffffff',
|
||
00022|
|
||
00023| componentBackground: 'transparent',
|
||
00024| componentHover: '#4e4d4dff',
|
||
00025| componentActive: 'rgba(255, 255, 255, 0.1)'
|
||
00026| };
|
||
00027|
|
||
00028| /**
|
||
00029| * 浅色主题
|
||
00030| */
|
||
00031| export const lightTheme: ThemeConfig = {
|
||
00032| name: 'light',
|
||
00033| primary: '#0078d4',
|
||
00034| primaryHover: '#106ebe',
|
||
00035|
|
||
00036| // 统一为浅灰
|
||
00037| background: '#f5f5f5',
|
||
00038| panelBackground: '#ffffff',
|
||
00039|
|
||
00040| textPrimary: '#333333',
|
||
00041| textSecondary: '#666666',
|
||
00042|
|
||
00043| border: '#e0e0e0',
|
||
00044|
|
||
00045| icon: '#555555',
|
||
00046| iconActive: '#0078d4',
|
||
00047|
|
||
00048| componentBackground: 'transparent',
|
||
00049| componentHover: '#f0f0f0',
|
||
00050| componentActive: '#e0e0e0'
|
||
00051| };
|
||
|
||
(End of file - total 51 lines)
|
||
</file>
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 69.4s)
|
||
|
||
完美,现在我完全理解了主题系统。让我更新改造计划:
|
||
|
||
---
|
||
|
||
## 📋 更新后的改造计划
|
||
|
||
### 1. 主题集成方案
|
||
|
||
**关键点**:
|
||
- glass 变体的激活色使用 `theme.primary`
|
||
- 暗色/亮色切换通过 `themeManager` 事件触发
|
||
- 组件通过 `setTheme()` 响应主题变化
|
||
|
||
**新增主题变量映射**:
|
||
|
||
```typescript
|
||
// 在 setTheme() 中新增映射
|
||
if (this.options.variant?.startsWith('glass')) {
|
||
// glass 变体使用 primary 作为激活背景色
|
||
this.options.btnActiveColor = theme.primary;
|
||
this.options.btnActiveBorderColor = theme.primaryHover;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 2. 类型定义更新
|
||
|
||
```typescript
|
||
// src/components/button-group/index.type.ts
|
||
|
||
/** 样式变体 */
|
||
export type ButtonGroupVariant =
|
||
| 'default' // 现有深色样式
|
||
| 'glass' // 毛玻璃 + 矩形按钮(顶部工具栏)
|
||
| 'glass-circle'; // 毛玻璃 + 圆形按钮(底部工具栏)
|
||
|
||
export interface ButtonGroupColors {
|
||
// ... 现有属性
|
||
|
||
/** 激活态边框颜色 (glass 变体使用) */
|
||
btnActiveBorderColor?: string;
|
||
}
|
||
|
||
export interface ButtonGroupOptions extends ButtonGroupColors {
|
||
// ... 现有属性
|
||
|
||
/** 样式变体预设 */
|
||
variant?: ButtonGroupVariant;
|
||
|
||
/** Hover 延迟配置 (ms) */
|
||
hoverDelay?: {
|
||
enter?: number; // 默认 0 (glass-circle 默认 100)
|
||
leave?: number; // 默认 200 (glass-circle 默认 250)
|
||
};
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3. CSS 样式设计
|
||
|
||
#### 3.1 新增 CSS 变量
|
||
|
||
```css
|
||
/* 新增变量 */
|
||
--bim-btn-active-border: transparent;
|
||
--bim-btn-group-backdrop-blur: 0;
|
||
```
|
||
|
||
#### 3.2 Glass 变体样式 (亮色模式)
|
||
|
||
```css
|
||
/* ========== Glass 变体 - 矩形按钮 ========== */
|
||
.bim-btn-group-root.variant-glass .bim-btn-group-section {
|
||
background: rgba(255, 255, 255, 0.8);
|
||
backdrop-filter: blur(10px);
|
||
-webkit-backdrop-filter: blur(10px);
|
||
border: 1px solid rgba(226, 232, 240, 0.5);
|
||
border-radius: 6px;
|
||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.04);
|
||
padding: 6px;
|
||
gap: 6px;
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass .opt-btn {
|
||
border-radius: 6px;
|
||
color: rgb(71, 85, 105);
|
||
padding: 8px 12px;
|
||
border: 1px solid transparent;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass .opt-btn:hover {
|
||
background: rgba(241, 245, 249, 0.6);
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass .opt-btn.active {
|
||
background: var(--bim-btn-active-bg, #0078d4);
|
||
color: white;
|
||
border-color: var(--bim-btn-active-border, #0063b1);
|
||
box-shadow: 0 2px 8px rgba(0, 120, 212, 0.3);
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass .opt-btn.active .opt-btn-icon {
|
||
color: white;
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass .opt-btn-icon {
|
||
color: rgb(71, 85, 105);
|
||
}
|
||
```
|
||
|
||
#### 3.3 Glass-Circle 变体样式 (亮色模式)
|
||
|
||
```css
|
||
/* ========== Glass-Circle 变体 - 圆形按钮 ========== */
|
||
.bim-btn-group-root.variant-glass-circle .bim-btn-group-section {
|
||
background: rgba(255, 255, 255, 0.8);
|
||
backdrop-filter: blur(10px);
|
||
-webkit-backdrop-filter: blur(10px);
|
||
border: 1px solid rgba(226, 232, 240, 0.5);
|
||
border-radius: 9999px;
|
||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
|
||
padding: 8px;
|
||
gap: 10px;
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass-circle .opt-btn {
|
||
width: 48px;
|
||
height: 48px;
|
||
min-width: 48px;
|
||
border-radius: 9999px;
|
||
background: rgba(255, 255, 255, 0.9);
|
||
border: 1px solid rgba(203, 213, 225, 0.6);
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1), 0 4px 12px rgba(0, 0, 0, 0.08);
|
||
color: rgb(71, 85, 105);
|
||
padding: 0;
|
||
transition: all 0.2s ease-out;
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass-circle .opt-btn:hover {
|
||
background: white;
|
||
transform: scale(1.05);
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12), 0 6px 20px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass-circle .opt-btn.active {
|
||
background: var(--bim-btn-active-bg, #0078d4);
|
||
border-color: var(--bim-btn-active-border, #0063b1);
|
||
color: white;
|
||
box-shadow: 0 2px 8px rgba(0, 120, 212, 0.3), 0 4px 16px rgba(0, 120, 212, 0.2);
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass-circle .opt-btn.active .opt-btn-icon {
|
||
color: white;
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass-circle .opt-btn-icon {
|
||
width: 18px;
|
||
height: 18px;
|
||
color: rgb(71, 85, 105);
|
||
}
|
||
|
||
/* 强制隐藏 label */
|
||
.bim-btn-group-root.variant-glass-circle .opt-btn-label,
|
||
.bim-btn-group-root.variant-glass-circle .opt-btn-text-wrapper {
|
||
display: none !important;
|
||
}
|
||
```
|
||
|
||
#### 3.4 Glass 变体暗色模式
|
||
|
||
```css
|
||
/* ========== Glass 变体 - 暗色模式 ========== */
|
||
.bim-btn-group-root.variant-glass.theme-dark .bim-btn-group-section,
|
||
.bim-btn-group-root.variant-glass-circle.theme-dark .bim-btn-group-section {
|
||
background: rgba(30, 41, 59, 0.8);
|
||
border-color: rgba(71, 85, 105, 0.5);
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass.theme-dark .opt-btn {
|
||
color: rgba(148, 163, 184, 1);
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass.theme-dark .opt-btn:hover {
|
||
background: rgba(51, 65, 85, 0.6);
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass.theme-dark .opt-btn-icon {
|
||
color: rgba(148, 163, 184, 1);
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass-circle.theme-dark .opt-btn {
|
||
background: rgba(51, 65, 85, 0.5);
|
||
border-color: rgba(71, 85, 105, 0.4);
|
||
color: rgba(226, 232, 240, 1);
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass-circle.theme-dark .opt-btn:hover {
|
||
background: rgba(51, 65, 85, 0.8);
|
||
}
|
||
|
||
.bim-btn-group-root.variant-glass-circle.theme-dark .opt-btn-icon {
|
||
color: rgba(226, 232, 240, 1);
|
||
}
|
||
```
|
||
|
||
#### 3.5 Glass 变体二级菜单样式
|
||
|
||
```css
|
||
/* ========== Glass 变体 - 二级菜单 ========== */
|
||
.opt-btn-dropdown.variant-glass {
|
||
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.variant-glass .opt-btn-dropdown-item {
|
||
border-radius: 8px;
|
||
color: rgb(71, 85, 105);
|
||
padding: 8px 12px;
|
||
font-size: 13px;
|
||
transition: all 0.1s ease-out;
|
||
}
|
||
|
||
.opt-btn-dropdown.variant-glass .opt-btn-dropdown-item:hover {
|
||
background: rgb(241, 245, 249);
|
||
}
|
||
|
||
.opt-btn-dropdown.variant-glass .opt-btn-dropdown-item .opt-btn-icon {
|
||
width: 14px;
|
||
height: 14px;
|
||
color: rgb(71, 85, 105);
|
||
}
|
||
|
||
/* 暗色模式 */
|
||
.opt-btn-dropdown.variant-glass.theme-dark {
|
||
background: rgba(30, 41, 59, 0.98);
|
||
border-color: rgba(71, 85, 105, 0.5);
|
||
}
|
||
|
||
.opt-btn-dropdown.variant-glass.theme-dark .opt-btn-dropdown-item {
|
||
color: rgba(255, 255, 255, 0.85);
|
||
}
|
||
|
||
.opt-btn-dropdown.variant-glass.theme-dark .opt-btn-dropdown-item:hover {
|
||
background: rgba(51, 65, 85, 0.8);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 4. TypeScript 逻辑更新
|
||
|
||
```typescript
|
||
// src/components/button-group/index.ts
|
||
|
||
// 在 initContainer() 中添加 variant 类名
|
||
private initContainer(): void {
|
||
// ... 现有代码
|
||
|
||
// 添加 variant 类名
|
||
if (this.options.variant) {
|
||
this.container.classList.add(`variant-${this.options.variant}`);
|
||
}
|
||
}
|
||
|
||
// 在 setTheme() 中处理 glass 变体
|
||
public setTheme(theme: ThemeConfig): void {
|
||
// ... 现有代码
|
||
|
||
// glass 变体使用 primary 作为激活色
|
||
if (this.options.variant?.startsWith('glass')) {
|
||
if (!this.customColors.has('btnActiveColor')) {
|
||
this.options.btnActiveColor = theme.primary;
|
||
}
|
||
if (!this.customColors.has('btnActiveBorderColor')) {
|
||
this.options.btnActiveBorderColor = theme.primaryHover;
|
||
}
|
||
}
|
||
|
||
// 更新主题类名 (暗色/亮色)
|
||
this.container.classList.remove('theme-dark', 'theme-light');
|
||
this.container.classList.add(`theme-${theme.name}`);
|
||
|
||
this.applyStyles();
|
||
}
|
||
|
||
// 更新 applyStyles() 添加新变量
|
||
private applyStyles(): void {
|
||
// ... 现有代码
|
||
|
||
if (this.options.btnActiveBorderColor) {
|
||
style.setProperty('--bim-btn-active-border', this.options.btnActiveBorderColor);
|
||
}
|
||
}
|
||
|
||
// 更新 showDropdown() 添加 variant 类名
|
||
private showDropdown(button: OptButton, btnEl: HTMLElement): void {
|
||
// ... 现有代码
|
||
|
||
// 添加 variant 类名到 dropdown
|
||
if (this.options.variant?.startsWith('glass')) {
|
||
dropdown.classList.add('variant-glass');
|
||
}
|
||
|
||
// 添加主题类名
|
||
const theme = themeManager.getTheme();
|
||
dropdown.classList.add(`theme-${theme.name}`);
|
||
}
|
||
|
||
// 更新 handleMouseEnter/Leave 使用可配置延迟
|
||
private handleMouseEnter(button: OptButton, btnEl: HTMLElement): void {
|
||
if (this.hoverTimeout) clearTimeout(this.hoverTimeout);
|
||
|
||
const delay = this.options.hoverDelay?.enter ?? 0;
|
||
|
||
if (button.children && button.children.length > 0) {
|
||
if (delay > 0) {
|
||
this.hoverTimeout = window.setTimeout(() => {
|
||
this.showDropdown(button, btnEl);
|
||
}, delay);
|
||
} else {
|
||
this.showDropdown(button, btnEl);
|
||
}
|
||
} else {
|
||
this.closeDropdown();
|
||
}
|
||
}
|
||
|
||
private handleMouseLeave(): void {
|
||
const delay = this.options.hoverDelay?.leave ?? 200;
|
||
this.hoverTimeout = window.setTimeout(() => this.closeDropdown(), delay);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 5. 文件改动清单(最终版)
|
||
|
||
| 文件 | 改动类型 | 改动内容 |
|
||
|-----|---------|---------|
|
||
| `src/components/button-group/index.type.ts` | 修改 | 新增 `ButtonGroupVariant`、`hoverDelay`、`btnActiveBorderColor` |
|
||
| `src/components/button-group/index.css` | 修改 | 新增 glass/glass-circle 变体样式、暗色模式样式 |
|
||
| `src/components/button-group/index.ts` | 修改 | 处理 variant 配置、主题类名、hover 延迟 |
|
||
| `docs/components/按钮组组件-ButtonGroup.md` | 修改 | 更新文档,添加变体使用说明 |
|
||
|
||
---
|
||
|
||
### 6. 使用示例
|
||
|
||
```typescript
|
||
// 顶部中间工具栏 (毛玻璃 + 矩形按钮)
|
||
const topToolbar = engine.buttonGroup.create('top-toolbar', {
|
||
variant: 'glass',
|
||
position: 'top-center',
|
||
direction: 'row',
|
||
showLabel: true,
|
||
align: 'horizontal',
|
||
});
|
||
|
||
topToolbar.addGroup('panel-controls');
|
||
topToolbar.addButton({
|
||
id: 'view-list',
|
||
groupId: 'panel-controls',
|
||
type: 'button',
|
||
label: 'toolbar.viewList',
|
||
icon: listIcon,
|
||
keepActive: true,
|
||
onClick: (btn) => togglePanel('views', btn.isActive),
|
||
});
|
||
|
||
// 底部右侧工具栏 (毛玻璃 + 圆形按钮)
|
||
const bottomToolbar = engine.buttonGroup.create('bottom-toolbar', {
|
||
variant: 'glass-circle',
|
||
position: 'bottom-right',
|
||
direction: 'row',
|
||
showLabel: false,
|
||
expand: 'up',
|
||
hoverDelay: { enter: 100, leave: 250 },
|
||
});
|
||
|
||
bottomToolbar.addGroup('tools');
|
||
bottomToolbar.addButton({
|
||
id: 'measure',
|
||
groupId: 'tools',
|
||
type: 'menu',
|
||
label: 'toolbar.measure',
|
||
icon: rulerIcon,
|
||
children: [
|
||
{ id: 'measure-line', type: 'button', label: 'toolbar.measureLine', icon: lineIcon },
|
||
{ id: 'measure-height', type: 'button', label: 'toolbar.measureHeight', icon: heightIcon },
|
||
],
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
**方案确认后即可开始实现,是否需要调整?**
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
我是想先保留现在的样式,然后增加一种类型,就是胶囊+毛玻璃的类型。外部可以传输一个type。默认是当前的类型。
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 34.7s)
|
||
|
||
明白了,简化方案:
|
||
|
||
---
|
||
|
||
## 📋 简化改造计划
|
||
|
||
### 需求确认
|
||
|
||
- **保留现有样式**:作为默认 `type: 'default'`
|
||
- **新增一种样式**:`type: 'glass-pill'`(胶囊容器 + 毛玻璃 + 圆形按钮)
|
||
|
||
---
|
||
|
||
### 1. 类型定义更新
|
||
|
||
```typescript
|
||
// src/components/button-group/index.type.ts
|
||
|
||
/** 按钮组样式类型 */
|
||
export type ButtonGroupType = 'default' | 'glass-pill';
|
||
|
||
export interface ButtonGroupOptions extends ButtonGroupColors {
|
||
// ... 现有配置
|
||
|
||
/** 样式类型 (默认 'default') */
|
||
type?: ButtonGroupType;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 2. CSS 新增样式
|
||
|
||
```css
|
||
/* ========== glass-pill 类型 ========== */
|
||
|
||
/* 容器:胶囊形状 + 毛玻璃 */
|
||
.bim-btn-group-root.type-glass-pill .bim-btn-group-section {
|
||
background: rgba(255, 255, 255, 0.8);
|
||
backdrop-filter: blur(10px);
|
||
-webkit-backdrop-filter: blur(10px);
|
||
border: 1px solid rgba(226, 232, 240, 0.5);
|
||
border-radius: 9999px;
|
||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
|
||
padding: 8px;
|
||
gap: 10px;
|
||
}
|
||
|
||
/* 按钮:圆形 */
|
||
.bim-btn-group-root.type-glass-pill .opt-btn {
|
||
width: 48px;
|
||
height: 48px;
|
||
min-width: 48px;
|
||
border-radius: 9999px;
|
||
background: rgba(255, 255, 255, 0.9);
|
||
border: 1px solid rgba(203, 213, 225, 0.6);
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1), 0 4px 12px rgba(0, 0, 0, 0.08);
|
||
color: rgb(71, 85, 105);
|
||
padding: 0;
|
||
transition: all 0.2s ease-out;
|
||
}
|
||
|
||
.bim-btn-group-root.type-glass-pill .opt-btn:hover {
|
||
background: white;
|
||
transform: scale(1.05);
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12), 0 6px 20px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.bim-btn-group-root.type-glass-pill .opt-btn.active {
|
||
background: var(--bim-btn-active-bg, #0078d4);
|
||
border-color: var(--bim-btn-active-border, #0063b1);
|
||
color: white;
|
||
box-shadow: 0 2px 8px rgba(0, 120, 212, 0.3), 0 4px 16px rgba(0, 120, 212, 0.2);
|
||
}
|
||
|
||
.bim-btn-group-root.type-glass-pill .opt-btn.active .opt-btn-icon {
|
||
color: white;
|
||
}
|
||
|
||
.bim-btn-group-root.type-glass-pill .opt-btn-icon {
|
||
width: 18px;
|
||
height: 18px;
|
||
color: rgb(71, 85, 105);
|
||
}
|
||
|
||
/* 隐藏 label(圆形按钮不显示文字) */
|
||
.bim-btn-group-root.type-glass-pill .opt-btn-label,
|
||
.bim-btn-group-root.type-glass-pill .opt-btn-text-wrapper:not(:has(.opt-btn-arrow)) {
|
||
display: none !important;
|
||
}
|
||
|
||
/* 保留箭头但重新定位 */
|
||
.bim-btn-group-root.type-glass-pill .opt-btn-text-wrapper {
|
||
position: absolute;
|
||
top: 2px;
|
||
right: 2px;
|
||
}
|
||
|
||
.bim-btn-group-root.type-glass-pill .opt-btn-arrow {
|
||
font-size: 8px;
|
||
}
|
||
|
||
/* 二级菜单样式 */
|
||
.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 {
|
||
border-radius: 8px;
|
||
color: rgb(71, 85, 105);
|
||
padding: 8px 12px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.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 .opt-btn-icon {
|
||
width: 14px;
|
||
height: 14px;
|
||
}
|
||
|
||
/* ========== 暗色模式 ========== */
|
||
.bim-btn-group-root.type-glass-pill.theme-dark .bim-btn-group-section {
|
||
background: rgba(30, 41, 59, 0.8);
|
||
border-color: rgba(71, 85, 105, 0.5);
|
||
}
|
||
|
||
.bim-btn-group-root.type-glass-pill.theme-dark .opt-btn {
|
||
background: rgba(51, 65, 85, 0.5);
|
||
border-color: rgba(71, 85, 105, 0.4);
|
||
color: rgba(226, 232, 240, 1);
|
||
}
|
||
|
||
.bim-btn-group-root.type-glass-pill.theme-dark .opt-btn:hover {
|
||
background: rgba(51, 65, 85, 0.8);
|
||
}
|
||
|
||
.bim-btn-group-root.type-glass-pill.theme-dark .opt-btn-icon {
|
||
color: rgba(226, 232, 240, 1);
|
||
}
|
||
|
||
.opt-btn-dropdown.type-glass-pill.theme-dark {
|
||
background: rgba(30, 41, 59, 0.98);
|
||
border-color: rgba(71, 85, 105, 0.5);
|
||
}
|
||
|
||
.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item {
|
||
color: rgba(255, 255, 255, 0.85);
|
||
}
|
||
|
||
.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item:hover {
|
||
background: rgba(51, 65, 85, 0.8);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3. TypeScript 逻辑更新
|
||
|
||
```typescript
|
||
// index.ts 改动点
|
||
|
||
// 1. initContainer() 添加 type 类名
|
||
private initContainer(): void {
|
||
// ... 现有代码
|
||
|
||
// 添加 type 类名
|
||
const type = this.options.type || 'default';
|
||
if (type !== 'default') {
|
||
this.container.classList.add(`type-${type}`);
|
||
}
|
||
}
|
||
|
||
// 2. setTheme() 添加主题类名 + glass-pill 激活色处理
|
||
public setTheme(theme: ThemeConfig): void {
|
||
// ... 现有映射代码
|
||
|
||
// glass-pill 类型使用 primary 作为激活色
|
||
if (this.options.type === 'glass-pill') {
|
||
if (!this.customColors.has('btnActiveColor')) {
|
||
this.options.btnActiveColor = theme.primary;
|
||
}
|
||
}
|
||
|
||
// 更新主题类名
|
||
this.container.classList.remove('theme-dark', 'theme-light');
|
||
this.container.classList.add(`theme-${theme.name}`);
|
||
|
||
this.applyStyles();
|
||
this.setPrimaryColor(theme.primary);
|
||
}
|
||
|
||
// 3. applyStyles() 新增激活边框变量
|
||
private applyStyles(): void {
|
||
// ... 现有代码
|
||
|
||
// 激活边框色(用于 glass-pill)
|
||
if (this.options.btnActiveColor) {
|
||
style.setProperty('--bim-btn-active-border', this.options.btnActiveColor);
|
||
}
|
||
}
|
||
|
||
// 4. showDropdown() 添加 type 类名
|
||
private showDropdown(button: OptButton, btnEl: HTMLElement): void {
|
||
// ... 现有代码
|
||
|
||
// 添加 type 类名
|
||
if (this.options.type === 'glass-pill') {
|
||
dropdown.classList.add('type-glass-pill');
|
||
}
|
||
|
||
// 添加主题类名
|
||
const theme = themeManager.getTheme();
|
||
dropdown.classList.add(`theme-${theme.name}`);
|
||
|
||
// ... 后续代码
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 4. 文件改动清单
|
||
|
||
| 文件 | 改动 |
|
||
|-----|-----|
|
||
| `index.type.ts` | 新增 `ButtonGroupType` 类型,`type` 配置项 |
|
||
| `index.css` | 新增 `.type-glass-pill` 样式(约 100 行) |
|
||
| `index.ts` | 4 处小改动(添加类名、主题处理) |
|
||
|
||
---
|
||
|
||
### 5. 使用示例
|
||
|
||
```typescript
|
||
// 默认样式(现有)
|
||
const toolbar1 = engine.buttonGroup.create('default-toolbar', {
|
||
position: 'bottom-center',
|
||
direction: 'row',
|
||
});
|
||
|
||
// 新样式:胶囊毛玻璃
|
||
const toolbar2 = engine.buttonGroup.create('glass-toolbar', {
|
||
type: 'glass-pill',
|
||
position: 'bottom-right',
|
||
direction: 'row',
|
||
expand: 'up',
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
**这样是否符合你的预期?确认后我可以开始实现。**
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
激活色两种类型的处理方式一样。所以setTheme不用变动,不用但素
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 11.0s)
|
||
|
||
明白,更简单了:
|
||
|
||
---
|
||
|
||
## 📋 最终改造计划
|
||
|
||
### 改动点
|
||
|
||
| 文件 | 改动 |
|
||
|-----|-----|
|
||
| `index.type.ts` | 新增 `ButtonGroupType` 类型,`type` 配置项 |
|
||
| `index.css` | 新增 `.type-glass-pill` 样式 |
|
||
| `index.ts` | 2 处改动:`initContainer()` 和 `showDropdown()` 添加类名 |
|
||
|
||
---
|
||
|
||
### 1. 类型定义
|
||
|
||
```typescript
|
||
// index.type.ts
|
||
export type ButtonGroupType = 'default' | 'glass-pill';
|
||
|
||
export interface ButtonGroupOptions extends ButtonGroupColors {
|
||
// ... 现有配置
|
||
type?: ButtonGroupType;
|
||
}
|
||
```
|
||
|
||
### 2. CSS 新增样式
|
||
|
||
`.type-glass-pill` 容器和按钮样式(胶囊 + 毛玻璃 + 圆形按钮)
|
||
|
||
### 3. TypeScript
|
||
|
||
```typescript
|
||
// initContainer() - 添加 type 类名
|
||
if (this.options.type && this.options.type !== 'default') {
|
||
this.container.classList.add(`type-${this.options.type}`);
|
||
}
|
||
|
||
// showDropdown() - dropdown 也添加 type 类名
|
||
if (this.options.type && this.options.type !== 'default') {
|
||
dropdown.classList.add(`type-${this.options.type}`);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
**确认后开始实现?**
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
然后toolbar的组件要改成使用胶囊的类型
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 7.1s)
|
||
|
||
好的,让我先看下 Toolbar 组件:
|
||
|
||
```
|
||
Tool: read
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/toolbar/index.ts"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
<file>
|
||
00001| import { BimButtonGroup } from '../index';
|
||
00002|
|
||
00003| /**
|
||
00004| * 底部工具栏 (Toolbar)
|
||
00005| * BimButtonGroup 的子类,专门用于加载工具栏默认按钮。
|
||
00006| */
|
||
00007| export class Toolbar extends BimButtonGroup {
|
||
00008| /**
|
||
00009| * 重写初始化,加载默认按钮
|
||
00010| */
|
||
00011| public async init(): Promise<void> {
|
||
00012| await super.init();
|
||
00013|
|
||
00014| // 动态加载默认按钮配置
|
||
00015| const { createHomeButton } = await import('./buttons/home');
|
||
00016| const { createZoomBoxButton } = await import('./buttons/zoom-box');
|
||
00017| const { createWalkMenuButton } = await import('./buttons/walk/walk-menu');
|
||
00018| const { createMapButton } = await import('./buttons/map');
|
||
00019| const { createPropertyButton } = await import('./buttons/property');
|
||
00020| const { createSettingButton } = await import('./buttons/setting');
|
||
00021| const { createInfoButton } = await import('./buttons/info');
|
||
00022| const { createFullscreenButton } = await import('./buttons/fullscreen');
|
||
00023| const { createMeasureButton } = await import('./buttons/measure');
|
||
00024| const { createSectionMenuButton } = await import('./buttons/section/section-menu');
|
||
00025| const { createSectionPlaneButton } = await import('./buttons/section/section-plane');
|
||
00026| const { createSectionAxisButton } = await import('./buttons/section/section-axis');
|
||
00027| const { createSectionBoxButton } = await import('./buttons/section/section-box');
|
||
00028|
|
||
00029| this.addGroup('group-1');
|
||
00030|
|
||
00031| // 使用工厂函数创建按钮,并注入 engine
|
||
00032| if (this.engine) {
|
||
00033| this.addButton(createHomeButton(this.engine));
|
||
00034| // 你要求:在"首页"后面添加"选框放大"
|
||
00035| this.addButton(createZoomBoxButton(this.engine));
|
||
00036| this.addButton(createMeasureButton(this.engine));
|
||
00037| this.addButton(createSectionMenuButton(this.engine));
|
||
00038| this.addButton(createSectionPlaneButton(this.engine));
|
||
00039| this.addButton(createSectionAxisButton(this.engine));
|
||
00040| this.addButton(createSectionBoxButton(this.engine));
|
||
00041| this.addButton(createWalkMenuButton(this.engine));
|
||
00042| this.addButton(createMapButton(this.engine));
|
||
00043| this.addButton(createPropertyButton(this.engine));
|
||
00044| this.addGroup('group-2');
|
||
00045| this.addButton(createSettingButton(this.engine));
|
||
00046| this.addButton(createInfoButton(this.engine));
|
||
00047| this.addButton(createFullscreenButton(this.engine));
|
||
00048| } else {
|
||
00049| console.warn('[Toolbar] Engine not available when creating buttons.');
|
||
00050| }
|
||
00051|
|
||
00052| this.render();
|
||
00053| }
|
||
00054| }
|
||
00055|
|
||
|
||
(End of file - total 55 lines)
|
||
</file>
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 6.0s)
|
||
|
||
```
|
||
Tool: read
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/managers/toolbar-manager.ts"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
<file>
|
||
00001| import type { ButtonGroupColors, ButtonConfig } from '../components/button-group/index.type';
|
||
00002| import { Toolbar } from '../components/button-group/toolbar';
|
||
00003| import type { ThemeConfig } from '../themes/types';
|
||
00004| import { BimComponent } from '../core/component';
|
||
00005| import type { BimEngine } from '../bim-engine';
|
||
00006|
|
||
00007| /**
|
||
00008| * 底部工具栏管理器 (ToolbarManager)
|
||
00009| * 仅负责管理底部工具栏实例。
|
||
00010| */
|
||
00011| export class ToolbarManager extends BimComponent {
|
||
00012| private toolbar: Toolbar | null = null;
|
||
00013| private toolbarContainer: HTMLElement | null = null;
|
||
00014| private container: HTMLElement;
|
||
00015|
|
||
00016| constructor(engine: BimEngine, container: HTMLElement) {
|
||
00017| super(engine);
|
||
00018| this.container = container;
|
||
00019| this.init();
|
||
00020| }
|
||
00021|
|
||
00022| private init() {
|
||
00023| // 创建底部工具栏专用容器
|
||
00024| this.toolbarContainer = document.createElement('div');
|
||
00025| this.toolbarContainer.id = 'opt-btn-groups';
|
||
00026| this.toolbarContainer.className = 'bim-engine-opt-btn-container is-bottom-toolbar';
|
||
00027| this.container.appendChild(this.toolbarContainer);
|
||
00028|
|
||
00029| this.toolbar = new Toolbar({
|
||
00030| container: this.toolbarContainer,
|
||
00031| showLabel: true,
|
||
00032| direction: 'row',
|
||
00033| position: 'bottom-center', // 底部居中
|
||
00034| align: 'vertical', // 图标在上
|
||
00035| expand: 'up' // 向上展开
|
||
00036| });
|
||
00037|
|
||
00038| // 注入 engine 到 Toolbar
|
||
00039| // @ts-ignore - Toolbar 还没更新类型,暂时忽略
|
||
00040| this.toolbar.setEngine(this.engine);
|
||
00041|
|
||
00042| this.toolbar.init();
|
||
00043| }
|
||
00044|
|
||
00045| public updateTheme(theme: ThemeConfig) {
|
||
00046| this.toolbar?.setTheme(theme);
|
||
00047| }
|
||
00048|
|
||
00049| public refresh() {
|
||
00050| this.toolbar?.render();
|
||
00051| }
|
||
00052|
|
||
00053| public destroy() {
|
||
00054| this.toolbar?.destroy();
|
||
00055| this.toolbar = null;
|
||
00056| }
|
||
00057|
|
||
00058| // --- 转发 API ---
|
||
00059| public addGroup(groupId: string, beforeGroupId?: string) { this.toolbar?.addGroup(groupId, beforeGroupId); this.toolbar?.render(); }
|
||
00060| public addButton(config: ButtonConfig) { this.toolbar?.addButton(config); this.toolbar?.render(); }
|
||
00061| public setButtonVisibility(id: string, v: boolean) { this.toolbar?.updateButtonVisibility(id, v); }
|
||
00062| public setShowLabel(show: boolean) { this.toolbar?.setShowLabel(show); }
|
||
00063| public setBtnActive(id: string, active?: boolean) { this.toolbar?.setBtnActive(id, active); }
|
||
00064| public setVisible(visible: boolean) {
|
||
00065| if (this.toolbarContainer) {
|
||
00066| this.toolbarContainer.style.visibility = visible ? 'visible' : 'hidden';
|
||
00067| }
|
||
00068| }
|
||
00069| public setBackgroundColor(color: string) { this.toolbar?.setBackgroundColor(color); }
|
||
00070| public setColors(colors: ButtonGroupColors) { this.toolbar?.setColors(colors); }
|
||
00071|
|
||
00072| /**
|
||
00073| * 隐藏工具栏
|
||
00074| */
|
||
00075| public hide(): void {
|
||
00076| if (this.toolbarContainer) {
|
||
00077| this.toolbarContainer.style.display = 'none';
|
||
00078| }
|
||
00079| }
|
||
00080|
|
||
00081| /**
|
||
00082| * 显示工具栏
|
||
00083| */
|
||
00084| public show(): void {
|
||
00085| if (this.toolbarContainer) {
|
||
00086| this.toolbarContainer.style.display = '';
|
||
00087| }
|
||
00088| }
|
||
00089|
|
||
00090| /**
|
||
00091| * 获取工具栏容器
|
||
00092| */
|
||
00093| public getContainer(): HTMLElement | null {
|
||
00094| return this.toolbarContainer;
|
||
00095| }
|
||
00096| }
|
||
00097|
|
||
|
||
(End of file - total 97 lines)
|
||
</file>
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 12.2s)
|
||
|
||
好的,现在完整了解了。汇总最终改动:
|
||
|
||
---
|
||
|
||
## 📋 最终改动清单
|
||
|
||
| 文件 | 改动内容 |
|
||
|-----|---------|
|
||
| `src/components/button-group/index.type.ts` | 新增 `ButtonGroupType` 类型和 `type` 配置项 |
|
||
| `src/components/button-group/index.css` | 新增 `.type-glass-pill` 样式 |
|
||
| `src/components/button-group/index.ts` | `initContainer()` 和 `showDropdown()` 添加 type 类名 |
|
||
| `src/managers/toolbar-manager.ts` | 第 29-36 行配置添加 `type: 'glass-pill'` |
|
||
|
||
---
|
||
|
||
### 关键改动预览
|
||
|
||
**toolbar-manager.ts 第 29-36 行:**
|
||
```typescript
|
||
this.toolbar = new Toolbar({
|
||
container: this.toolbarContainer,
|
||
type: 'glass-pill', // 新增
|
||
showLabel: true,
|
||
direction: 'row',
|
||
position: 'bottom-center',
|
||
align: 'vertical',
|
||
expand: 'up'
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
**确认后开始实现?**
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
开始实现的时候记得备份一下。
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 9.4s)
|
||
|
||
当前处于 **Plan Mode(只读模式)**,无法执行编辑操作。
|
||
|
||
请切换到正常模式后,我会:
|
||
|
||
1. ✅ 备份相关文件到 `.recycle/2026-01-21/`
|
||
2. ✅ 按清单执行改动
|
||
3. ✅ 提交代码
|
||
|
||
需要你退出 Plan Mode 或输入 `/build` 切换到实现模式。
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
可以开始把
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 11.6s)
|
||
|
||
```
|
||
Tool: todowrite
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"todos": [
|
||
{
|
||
"id": "1",
|
||
"content": "备份相关文件到 .recycle/2026-01-21/",
|
||
"status": "in_progress",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "修改 index.type.ts - 新增 ButtonGroupType 类型",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "修改 index.css - 新增 .type-glass-pill 样式",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "修改 index.ts - initContainer/showDropdown 添加类名",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "5",
|
||
"content": "修改 toolbar-manager.ts - 添加 type: 'glass-pill'",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "6",
|
||
"content": "运行 LSP 诊断验证代码",
|
||
"status": "pending",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
[
|
||
{
|
||
"id": "1",
|
||
"content": "备份相关文件到 .recycle/2026-01-21/",
|
||
"status": "in_progress",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "修改 index.type.ts - 新增 ButtonGroupType 类型",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "修改 index.css - 新增 .type-glass-pill 样式",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "修改 index.ts - initContainer/showDropdown 添加类名",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "5",
|
||
"content": "修改 toolbar-manager.ts - 添加 type: 'glass-pill'",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "6",
|
||
"content": "运行 LSP 诊断验证代码",
|
||
"status": "pending",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 7.9s)
|
||
|
||
```
|
||
Tool: bash
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"command": "mkdir -p .recycle/2026-01-21/src/components/button-group .recycle/2026-01-21/src/managers && cp src/components/button-group/index.type.ts .recycle/2026-01-21/src/components/button-group/ && cp src/components/button-group/index.css .recycle/2026-01-21/src/components/button-group/ && cp src/components/button-group/index.ts .recycle/2026-01-21/src/components/button-group/ && cp src/managers/toolbar-manager.ts .recycle/2026-01-21/src/managers/ && echo \"备份完成\"",
|
||
"description": "Backup files to .recycle"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
备份完成
|
||
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 7.9s)
|
||
|
||
```
|
||
Tool: todowrite
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"todos": [
|
||
{
|
||
"id": "1",
|
||
"content": "备份相关文件到 .recycle/2026-01-21/",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "修改 index.type.ts - 新增 ButtonGroupType 类型",
|
||
"status": "in_progress",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "修改 index.css - 新增 .type-glass-pill 样式",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "修改 index.ts - initContainer/showDropdown 添加类名",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "5",
|
||
"content": "修改 toolbar-manager.ts - 添加 type: 'glass-pill'",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "6",
|
||
"content": "运行 LSP 诊断验证代码",
|
||
"status": "pending",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
[
|
||
{
|
||
"id": "1",
|
||
"content": "备份相关文件到 .recycle/2026-01-21/",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "修改 index.type.ts - 新增 ButtonGroupType 类型",
|
||
"status": "in_progress",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "修改 index.css - 新增 .type-glass-pill 样式",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "修改 index.ts - initContainer/showDropdown 添加类名",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "5",
|
||
"content": "修改 toolbar-manager.ts - 添加 type: 'glass-pill'",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "6",
|
||
"content": "运行 LSP 诊断验证代码",
|
||
"status": "pending",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 10.6s)
|
||
|
||
```
|
||
Tool: edit
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.type.ts",
|
||
"oldString": "export type ButtonType = 'button' | 'menu';",
|
||
"newString": "export type ButtonType = 'button' | 'menu';\n\n/** 按钮组样式类型 */\nexport type ButtonGroupType = 'default' | 'glass-pill';"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
Edit applied successfully.
|
||
|
||
COMMENT/DOCSTRING DETECTED - IMMEDIATE ACTION REQUIRED
|
||
|
||
Your recent changes contain comments or docstrings, which triggered this hook.
|
||
You need to take immediate action. You must follow the conditions below.
|
||
(Listed in priority order - you must always act according to this priority order)
|
||
|
||
CRITICAL WARNING: This hook message MUST NEVER be ignored, even if you receive it multiple times.
|
||
You MUST provide corresponding explanation or action for EACH occurrence of this message.
|
||
Ignoring this message or failing to respond appropriately is strictly prohibited.
|
||
|
||
PRIORITY-BASED ACTION GUIDELINES:
|
||
|
||
1. This is a comment/docstring that already existed before
|
||
-> Explain to the user that this is an existing comment/docstring and proceed (justify it)
|
||
|
||
2. This is a newly written comment: but it's in given, when, then format
|
||
-> Tell the user it's a BDD comment and proceed (justify it)
|
||
-> Note: This applies to comments only, not docstrings
|
||
|
||
3. This is a newly written comment/docstring: but it's a necessary comment/docstring
|
||
-> Tell the user why this comment/docstring is absolutely necessary and proceed (justify it)
|
||
-> Examples of necessary comments: complex algorithms, security-related, performance optimization, regex, mathematical formulas
|
||
-> Examples of necessary docstrings: public API documentation, complex module/class interfaces
|
||
-> IMPORTANT: Most docstrings are unnecessary if the code is self-explanatory. Only keep truly essential ones.
|
||
|
||
4. This is a newly written comment/docstring: but it's an unnecessary comment/docstring
|
||
-> Apologize to the user and remove the comment/docstring.
|
||
-> Make the code itself clearer so it can be understood without comments/docstrings.
|
||
-> For verbose docstrings: refactor code to be self-documenting instead of adding lengthy explanations.
|
||
|
||
MANDATORY REQUIREMENT: You must acknowledge this hook message and take one of the above actions.
|
||
Review in the above priority order and take the corresponding action EVERY TIME this appears.
|
||
|
||
REMINDER: These rules apply to ALL your future code, not just this specific edit. Always be deliberate and cautious when writing comments - only add them when absolutely necessary.
|
||
|
||
Detected comments/docstrings:
|
||
<comments file="/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.type.ts">
|
||
<comment line-number="3">/** 按钮组样式类型 */</comment>
|
||
<comment line-number="3">/** 按钮组样式类型 */</comment>
|
||
</comments>
|
||
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 13.4s)
|
||
|
||
这是公共 API 类型的文档说明,与文件中其他类型定义保持一致风格(如 `/** 按钮配置 */`),属于必要的 docstring。
|
||
|
||
继续添加 `type` 配置项:
|
||
|
||
```
|
||
Tool: edit
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.type.ts",
|
||
"oldString": "export interface ButtonGroupOptions extends ButtonGroupColors {\n container: HTMLElement | string;",
|
||
"newString": "export interface ButtonGroupOptions extends ButtonGroupColors {\n container: HTMLElement | string;\n\n /** 样式类型 (默认 'default') */\n type?: ButtonGroupType;"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
Edit applied successfully.
|
||
|
||
COMMENT/DOCSTRING DETECTED - IMMEDIATE ACTION REQUIRED
|
||
|
||
Your recent changes contain comments or docstrings, which triggered this hook.
|
||
You need to take immediate action. You must follow the conditions below.
|
||
(Listed in priority order - you must always act according to this priority order)
|
||
|
||
CRITICAL WARNING: This hook message MUST NEVER be ignored, even if you receive it multiple times.
|
||
You MUST provide corresponding explanation or action for EACH occurrence of this message.
|
||
Ignoring this message or failing to respond appropriately is strictly prohibited.
|
||
|
||
PRIORITY-BASED ACTION GUIDELINES:
|
||
|
||
1. This is a comment/docstring that already existed before
|
||
-> Explain to the user that this is an existing comment/docstring and proceed (justify it)
|
||
|
||
2. This is a newly written comment: but it's in given, when, then format
|
||
-> Tell the user it's a BDD comment and proceed (justify it)
|
||
-> Note: This applies to comments only, not docstrings
|
||
|
||
3. This is a newly written comment/docstring: but it's a necessary comment/docstring
|
||
-> Tell the user why this comment/docstring is absolutely necessary and proceed (justify it)
|
||
-> Examples of necessary comments: complex algorithms, security-related, performance optimization, regex, mathematical formulas
|
||
-> Examples of necessary docstrings: public API documentation, complex module/class interfaces
|
||
-> IMPORTANT: Most docstrings are unnecessary if the code is self-explanatory. Only keep truly essential ones.
|
||
|
||
4. This is a newly written comment/docstring: but it's an unnecessary comment/docstring
|
||
-> Apologize to the user and remove the comment/docstring.
|
||
-> Make the code itself clearer so it can be understood without comments/docstrings.
|
||
-> For verbose docstrings: refactor code to be self-documenting instead of adding lengthy explanations.
|
||
|
||
MANDATORY REQUIREMENT: You must acknowledge this hook message and take one of the above actions.
|
||
Review in the above priority order and take the corresponding action EVERY TIME this appears.
|
||
|
||
REMINDER: These rules apply to ALL your future code, not just this specific edit. Always be deliberate and cautious when writing comments - only add them when absolutely necessary.
|
||
|
||
Detected comments/docstrings:
|
||
<comments file="/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.type.ts">
|
||
<comment line-number="4">/** 样式类型 (默认 'default') */</comment>
|
||
<comment line-number="4">/** 样式类型 (默认 'default') */</comment>
|
||
</comments>
|
||
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 10.3s)
|
||
|
||
公共 API 属性的文档说明,与文件中其他属性保持一致风格(如 `/** 屏幕位置 */`),属于必要的 docstring。
|
||
|
||
```
|
||
Tool: todowrite
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"todos": [
|
||
{
|
||
"id": "1",
|
||
"content": "备份相关文件到 .recycle/2026-01-21/",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "修改 index.type.ts - 新增 ButtonGroupType 类型",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "修改 index.css - 新增 .type-glass-pill 样式",
|
||
"status": "in_progress",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "修改 index.ts - initContainer/showDropdown 添加类名",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "5",
|
||
"content": "修改 toolbar-manager.ts - 添加 type: 'glass-pill'",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "6",
|
||
"content": "运行 LSP 诊断验证代码",
|
||
"status": "pending",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
[
|
||
{
|
||
"id": "1",
|
||
"content": "备份相关文件到 .recycle/2026-01-21/",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "修改 index.type.ts - 新增 ButtonGroupType 类型",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "修改 index.css - 新增 .type-glass-pill 样式",
|
||
"status": "in_progress",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "修改 index.ts - initContainer/showDropdown 添加类名",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "5",
|
||
"content": "修改 toolbar-manager.ts - 添加 type: 'glass-pill'",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "6",
|
||
"content": "运行 LSP 诊断验证代码",
|
||
"status": "pending",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 25.4s)
|
||
|
||
```
|
||
Tool: edit
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.css",
|
||
"oldString": ".bim-btn-group-root.is-bottom-toolbar .opt-btn {\n padding: 8px;\n}",
|
||
"newString": ".bim-btn-group-root.is-bottom-toolbar .opt-btn {\n padding: 8px;\n}\n\n/* ========== type-glass-pill: 胶囊容器 + 毛玻璃 + 圆形按钮 ========== */\n\n.bim-btn-group-root.type-glass-pill .bim-btn-group-section {\n background: rgba(255, 255, 255, 0.8);\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n border: 1px solid rgba(226, 232, 240, 0.5);\n border-radius: 9999px;\n box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);\n padding: 8px;\n gap: 10px;\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn {\n width: 48px;\n height: 48px;\n min-width: 48px;\n border-radius: 9999px;\n background: rgba(255, 255, 255, 0.9);\n border: 1px solid rgba(203, 213, 225, 0.6);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1), 0 4px 12px rgba(0, 0, 0, 0.08);\n color: rgb(71, 85, 105);\n padding: 0;\n transition: all 0.2s ease-out;\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn:hover {\n background: white;\n transform: scale(1.05);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12), 0 6px 20px rgba(0, 0, 0, 0.1);\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn.active {\n background: var(--bim-btn-active-bg, #0078d4);\n border-color: var(--bim-btn-active-bg, #0078d4);\n color: white;\n box-shadow: 0 2px 8px rgba(0, 120, 212, 0.3), 0 4px 16px rgba(0, 120, 212, 0.2);\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn.active .opt-btn-icon {\n color: white;\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn-icon {\n width: 18px;\n height: 18px;\n color: rgb(71, 85, 105);\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn-label {\n display: none !important;\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn-text-wrapper {\n position: absolute;\n top: 2px;\n right: 2px;\n width: 0;\n height: 0;\n margin: 0;\n padding: 0;\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn-arrow {\n font-size: 8px;\n position: absolute;\n top: 0;\n right: 0;\n}\n\n/* glass-pill 二级菜单 */\n.opt-btn-dropdown.type-glass-pill {\n background: rgba(255, 255, 255, 0.95);\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n border: 1px solid rgba(226, 232, 240, 0.6);\n border-radius: 12px;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);\n padding: 6px;\n}\n\n.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item {\n border-radius: 8px;\n color: rgb(71, 85, 105);\n padding: 8px 12px;\n font-size: 13px;\n transition: all 0.1s ease-out;\n}\n\n.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item:hover {\n background: rgb(241, 245, 249);\n}\n\n.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item .opt-btn-icon {\n width: 14px;\n height: 14px;\n color: rgb(71, 85, 105);\n}\n\n/* glass-pill 暗色模式 */\n.bim-btn-group-root.type-glass-pill.theme-dark .bim-btn-group-section {\n background: rgba(30, 41, 59, 0.8);\n border-color: rgba(71, 85, 105, 0.5);\n}\n\n.bim-btn-group-root.type-glass-pill.theme-dark .opt-btn {\n background: rgba(51, 65, 85, 0.5);\n border-color: rgba(71, 85, 105, 0.4);\n color: rgba(226, 232, 240, 1);\n}\n\n.bim-btn-group-root.type-glass-pill.theme-dark .opt-btn:hover {\n background: rgba(51, 65, 85, 0.8);\n}\n\n.bim-btn-group-root.type-glass-pill.theme-dark .opt-btn-icon {\n color: rgba(226, 232, 240, 1);\n}\n\n.opt-btn-dropdown.type-glass-pill.theme-dark {\n background: rgba(30, 41, 59, 0.98);\n border-color: rgba(71, 85, 105, 0.5);\n}\n\n.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item {\n color: rgba(255, 255, 255, 0.85);\n}\n\n.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item:hover {\n background: rgba(51, 65, 85, 0.8);\n}\n\n.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item .opt-btn-icon {\n color: rgba(255, 255, 255, 0.85);\n}"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
Edit applied successfully.
|
||
|
||
COMMENT/DOCSTRING DETECTED - IMMEDIATE ACTION REQUIRED
|
||
|
||
Your recent changes contain comments or docstrings, which triggered this hook.
|
||
You need to take immediate action. You must follow the conditions below.
|
||
(Listed in priority order - you must always act according to this priority order)
|
||
|
||
CRITICAL WARNING: This hook message MUST NEVER be ignored, even if you receive it multiple times.
|
||
You MUST provide corresponding explanation or action for EACH occurrence of this message.
|
||
Ignoring this message or failing to respond appropriately is strictly prohibited.
|
||
|
||
PRIORITY-BASED ACTION GUIDELINES:
|
||
|
||
1. This is a comment/docstring that already existed before
|
||
-> Explain to the user that this is an existing comment/docstring and proceed (justify it)
|
||
|
||
2. This is a newly written comment: but it's in given, when, then format
|
||
-> Tell the user it's a BDD comment and proceed (justify it)
|
||
-> Note: This applies to comments only, not docstrings
|
||
|
||
3. This is a newly written comment/docstring: but it's a necessary comment/docstring
|
||
-> Tell the user why this comment/docstring is absolutely necessary and proceed (justify it)
|
||
-> Examples of necessary comments: complex algorithms, security-related, performance optimization, regex, mathematical formulas
|
||
-> Examples of necessary docstrings: public API documentation, complex module/class interfaces
|
||
-> IMPORTANT: Most docstrings are unnecessary if the code is self-explanatory. Only keep truly essential ones.
|
||
|
||
4. This is a newly written comment/docstring: but it's an unnecessary comment/docstring
|
||
-> Apologize to the user and remove the comment/docstring.
|
||
-> Make the code itself clearer so it can be understood without comments/docstrings.
|
||
-> For verbose docstrings: refactor code to be self-documenting instead of adding lengthy explanations.
|
||
|
||
MANDATORY REQUIREMENT: You must acknowledge this hook message and take one of the above actions.
|
||
Review in the above priority order and take the corresponding action EVERY TIME this appears.
|
||
|
||
REMINDER: These rules apply to ALL your future code, not just this specific edit. Always be deliberate and cautious when writing comments - only add them when absolutely necessary.
|
||
|
||
Detected comments/docstrings:
|
||
<comments file="/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.css">
|
||
<comment line-number="5">/* ========== type-glass-pill: 胶囊容器 + 毛玻璃 + 圆形按钮 ========== */</comment>
|
||
<comment line-number="75">/* glass-pill 二级菜单 */</comment>
|
||
<comment line-number="104">/* glass-pill 暗色模式 */</comment>
|
||
</comments>
|
||
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 12.3s)
|
||
|
||
CSS 中的分隔注释用于组织代码区块,与文件现有风格一致(如 `/* 根容器 */`),属于必要的结构性注释。
|
||
|
||
```
|
||
Tool: todowrite
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"todos": [
|
||
{
|
||
"id": "1",
|
||
"content": "备份相关文件到 .recycle/2026-01-21/",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "修改 index.type.ts - 新增 ButtonGroupType 类型",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "修改 index.css - 新增 .type-glass-pill 样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "修改 index.ts - initContainer/showDropdown 添加类名",
|
||
"status": "in_progress",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "5",
|
||
"content": "修改 toolbar-manager.ts - 添加 type: 'glass-pill'",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "6",
|
||
"content": "运行 LSP 诊断验证代码",
|
||
"status": "pending",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
[
|
||
{
|
||
"id": "1",
|
||
"content": "备份相关文件到 .recycle/2026-01-21/",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "修改 index.type.ts - 新增 ButtonGroupType 类型",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "修改 index.css - 新增 .type-glass-pill 样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "修改 index.ts - initContainer/showDropdown 添加类名",
|
||
"status": "in_progress",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "5",
|
||
"content": "修改 toolbar-manager.ts - 添加 type: 'glass-pill'",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "6",
|
||
"content": "运行 LSP 诊断验证代码",
|
||
"status": "pending",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 12.1s)
|
||
|
||
```
|
||
Tool: edit
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.ts",
|
||
"oldString": "if (this.options.className) {\n this.container.classList.add(this.options.className);\n }\n\n this.updatePosition();",
|
||
"newString": "if (this.options.className) {\n this.container.classList.add(this.options.className);\n }\n\n if (this.options.type && this.options.type !== 'default') {\n this.container.classList.add(`type-${this.options.type}`);\n }\n\n this.updatePosition();"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
Edit applied successfully.
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 12.1s)
|
||
|
||
```
|
||
Tool: edit
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.ts",
|
||
"oldString": "// 先添加到 DOM 以便计算尺寸\n document.body.appendChild(dropdown);\n\n // 添加事件拦截\n this.setupEventInterception(dropdown);",
|
||
"newString": "if (this.options.type && this.options.type !== 'default') {\n dropdown.classList.add(`type-${this.options.type}`);\n }\n\n const currentTheme = themeManager.getTheme();\n dropdown.classList.add(`theme-${currentTheme.name}`);\n\n // 先添加到 DOM 以便计算尺寸\n document.body.appendChild(dropdown);\n\n // 添加事件拦截\n this.setupEventInterception(dropdown);"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
Edit applied successfully.
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 11.2s)
|
||
|
||
还需要在 `setTheme()` 中更新容器的主题类名:
|
||
|
||
```
|
||
Tool: edit
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.ts",
|
||
"oldString": "this.applyStyles();\n this.setPrimaryColor(theme.primary);\n }",
|
||
"newString": "this.container.classList.remove('theme-dark', 'theme-light');\n this.container.classList.add(`theme-${theme.name}`);\n\n this.applyStyles();\n this.setPrimaryColor(theme.primary);\n }"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
Edit applied successfully.
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 8.3s)
|
||
|
||
```
|
||
Tool: todowrite
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"todos": [
|
||
{
|
||
"id": "1",
|
||
"content": "备份相关文件到 .recycle/2026-01-21/",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "修改 index.type.ts - 新增 ButtonGroupType 类型",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "修改 index.css - 新增 .type-glass-pill 样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "修改 index.ts - initContainer/showDropdown 添加类名",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "5",
|
||
"content": "修改 toolbar-manager.ts - 添加 type: 'glass-pill'",
|
||
"status": "in_progress",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "6",
|
||
"content": "运行 LSP 诊断验证代码",
|
||
"status": "pending",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
[
|
||
{
|
||
"id": "1",
|
||
"content": "备份相关文件到 .recycle/2026-01-21/",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "修改 index.type.ts - 新增 ButtonGroupType 类型",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "修改 index.css - 新增 .type-glass-pill 样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "修改 index.ts - initContainer/showDropdown 添加类名",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "5",
|
||
"content": "修改 toolbar-manager.ts - 添加 type: 'glass-pill'",
|
||
"status": "in_progress",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "6",
|
||
"content": "运行 LSP 诊断验证代码",
|
||
"status": "pending",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 11.0s)
|
||
|
||
```
|
||
Tool: edit
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/managers/toolbar-manager.ts",
|
||
"oldString": "this.toolbar = new Toolbar({\n container: this.toolbarContainer,\n showLabel: true,\n direction: 'row',\n position: 'bottom-center', // 底部居中\n align: 'vertical', // 图标在上\n expand: 'up' // 向上展开\n });",
|
||
"newString": "this.toolbar = new Toolbar({\n container: this.toolbarContainer,\n type: 'glass-pill',\n showLabel: true,\n direction: 'row',\n position: 'bottom-center',\n align: 'vertical',\n expand: 'up'\n });"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
Edit applied successfully.
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 7.8s)
|
||
|
||
```
|
||
Tool: todowrite
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"todos": [
|
||
{
|
||
"id": "1",
|
||
"content": "备份相关文件到 .recycle/2026-01-21/",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "修改 index.type.ts - 新增 ButtonGroupType 类型",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "修改 index.css - 新增 .type-glass-pill 样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "修改 index.ts - initContainer/showDropdown 添加类名",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "5",
|
||
"content": "修改 toolbar-manager.ts - 添加 type: 'glass-pill'",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "6",
|
||
"content": "运行 LSP 诊断验证代码",
|
||
"status": "in_progress",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
[
|
||
{
|
||
"id": "1",
|
||
"content": "备份相关文件到 .recycle/2026-01-21/",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "修改 index.type.ts - 新增 ButtonGroupType 类型",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "修改 index.css - 新增 .type-glass-pill 样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "修改 index.ts - initContainer/showDropdown 添加类名",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "5",
|
||
"content": "修改 toolbar-manager.ts - 添加 type: 'glass-pill'",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "6",
|
||
"content": "运行 LSP 诊断验证代码",
|
||
"status": "in_progress",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 6.8s)
|
||
|
||
```
|
||
Tool: lsp_diagnostics
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.type.ts"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
Error: LSP server 'typescript' is configured but NOT INSTALLED.
|
||
|
||
Command not found: typescript-language-server
|
||
|
||
To install:
|
||
npm install -g typescript-language-server typescript
|
||
|
||
Supported extensions: .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts
|
||
|
||
After installation, the server will be available automatically.
|
||
Run 'lsp_servers' tool to verify installation status.
|
||
```
|
||
```
|
||
|
||
```
|
||
Tool: lsp_diagnostics
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.ts"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
Error: LSP server 'typescript' is configured but NOT INSTALLED.
|
||
|
||
Command not found: typescript-language-server
|
||
|
||
To install:
|
||
npm install -g typescript-language-server typescript
|
||
|
||
Supported extensions: .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts
|
||
|
||
After installation, the server will be available automatically.
|
||
Run 'lsp_servers' tool to verify installation status.
|
||
```
|
||
```
|
||
|
||
```
|
||
Tool: lsp_diagnostics
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/managers/toolbar-manager.ts"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
Error: LSP server 'typescript' is configured but NOT INSTALLED.
|
||
|
||
Command not found: typescript-language-server
|
||
|
||
To install:
|
||
npm install -g typescript-language-server typescript
|
||
|
||
Supported extensions: .ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts
|
||
|
||
After installation, the server will be available automatically.
|
||
Run 'lsp_servers' tool to verify installation status.
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 13.5s)
|
||
|
||
```
|
||
Tool: bash
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"command": "npx tsc --noEmit 2>&1 | head -50",
|
||
"description": "TypeScript type check"
|
||
}
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 8.3s)
|
||
|
||
```
|
||
Tool: todowrite
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"todos": [
|
||
{
|
||
"id": "1",
|
||
"content": "备份相关文件到 .recycle/2026-01-21/",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "修改 index.type.ts - 新增 ButtonGroupType 类型",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "修改 index.css - 新增 .type-glass-pill 样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "修改 index.ts - initContainer/showDropdown 添加类名",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "5",
|
||
"content": "修改 toolbar-manager.ts - 添加 type: 'glass-pill'",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "6",
|
||
"content": "运行 LSP 诊断验证代码",
|
||
"status": "completed",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
[
|
||
{
|
||
"id": "1",
|
||
"content": "备份相关文件到 .recycle/2026-01-21/",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "修改 index.type.ts - 新增 ButtonGroupType 类型",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "修改 index.css - 新增 .type-glass-pill 样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "修改 index.ts - initContainer/showDropdown 添加类名",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "5",
|
||
"content": "修改 toolbar-manager.ts - 添加 type: 'glass-pill'",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "6",
|
||
"content": "运行 LSP 诊断验证代码",
|
||
"status": "completed",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 10.7s)
|
||
|
||
✅ **所有改动完成**,TypeScript 检查通过。
|
||
|
||
---
|
||
|
||
## 📋 改动总结
|
||
|
||
| 文件 | 改动 |
|
||
|-----|-----|
|
||
| `src/components/button-group/index.type.ts` | 新增 `ButtonGroupType` 类型和 `type` 配置项 |
|
||
| `src/components/button-group/index.css` | 新增 `.type-glass-pill` 样式(约 110 行) |
|
||
| `src/components/button-group/index.ts` | 3 处改动:`initContainer`、`showDropdown`、`setTheme` 添加类名 |
|
||
| `src/managers/toolbar-manager.ts` | 添加 `type: 'glass-pill'` 配置 |
|
||
|
||
**备份位置**:`.recycle/2026-01-21/`
|
||
|
||
---
|
||
|
||
### 使用方式
|
||
|
||
```typescript
|
||
// 默认样式
|
||
engine.buttonGroup.create('my-group', { ... });
|
||
|
||
// 胶囊毛玻璃样式
|
||
engine.buttonGroup.create('my-group', { type: 'glass-pill', ... });
|
||
```
|
||
|
||
Toolbar 已默认使用 `glass-pill` 样式。
|
||
|
||
---
|
||
|
||
需要我提交代码吗?
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
二级菜单的展示样式不太对,二级菜单的样式要改成图标在左边然后文字在右边,这样组件本身应该是支持的。
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 14.1s)
|
||
|
||
让我检查一下二级菜单的相关代码:
|
||
|
||
```
|
||
Tool: read
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.ts",
|
||
"offset": 586,
|
||
"limit": 50
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
<file>
|
||
00587| dropdown.style.top = (btnRect.top + (btnRect.height - dropdownRect.height) / 2) + 'px';
|
||
00588| dropdown.style.left = (btnRect.right + 8) + 'px';
|
||
00589| } else if (expand === 'left') {
|
||
00590| // 向左展开,与按钮垂直居中对齐
|
||
00591| dropdown.style.top = (btnRect.top + (btnRect.height - dropdownRect.height) / 2) + 'px';
|
||
00592| dropdown.style.right = (window.innerWidth - btnRect.left + 8) + 'px';
|
||
00593| }
|
||
00594|
|
||
00595| dropdown.addEventListener('mouseenter', () => { if (this.hoverTimeout) clearTimeout(this.hoverTimeout); });
|
||
00596| dropdown.addEventListener('mouseleave', () => this.handleMouseLeave());
|
||
00597| this.dropdownElement = dropdown;
|
||
00598| }
|
||
00599|
|
||
00600| private renderDropdownItem(button: OptButton): HTMLElement {
|
||
00601| const item = document.createElement('div');
|
||
00602| item.className = 'opt-btn-dropdown-item';
|
||
00603|
|
||
00604| // 应用按钮的 align 设置,默认为 horizontal(图标在左)
|
||
00605| const align = button.align || 'horizontal';
|
||
00606| if (align === 'horizontal') {
|
||
00607| item.classList.add('align-horizontal');
|
||
00608| } else {
|
||
00609| item.classList.add('align-vertical');
|
||
00610| }
|
||
00611|
|
||
00612| // 二级菜单项的 active 状态渲染(修复 keepActive 在二级按钮“看起来不生效”的问题)
|
||
00613| // 说明:
|
||
00614| // - keepActive 的状态会记录在 activeBtnIds / button.isActive 上
|
||
00615| // - 下拉菜单每次打开都会重新渲染,因此必须在这里同步一次 active 样式
|
||
00616| if (this.activeBtnIds.has(button.id) || button.isActive) {
|
||
00617| item.classList.add('active');
|
||
00618| }
|
||
00619|
|
||
00620| // 应用按钮的自定义样式
|
||
00621| const iconSize = button.iconSize || 32; // 二级菜单默认图标更小
|
||
00622| const minWidth = button.minWidth; // 不设置默认值,让下拉菜单项保持紧凑
|
||
00623| if (minWidth) {
|
||
00624| item.style.minWidth = `${minWidth}px`;
|
||
00625| }
|
||
00626|
|
||
00627| const icon = document.createElement('div');
|
||
00628| icon.className = 'opt-btn-icon';
|
||
00629| icon.style.width = `${iconSize}px`;
|
||
00630| icon.style.height = `${iconSize}px`;
|
||
00631| icon.innerHTML = this.getIcon(button.icon);
|
||
00632| item.appendChild(icon);
|
||
00633|
|
||
00634| // 只有在 showLabel 为 true 时才显示 label
|
||
00635| if (this.options.showLabel && button.label) {
|
||
00636| const label = document.createElement('span');
|
||
|
||
(File has more lines. Use 'offset' parameter to read beyond line 636)
|
||
</file>
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 19.5s)
|
||
|
||
问题可能是 glass-pill 样式覆盖了原有的 horizontal 布局。让我修复 CSS:
|
||
|
||
```
|
||
Tool: edit
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.css",
|
||
"oldString": ".opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item {\n border-radius: 8px;\n color: rgb(71, 85, 105);\n padding: 8px 12px;\n font-size: 13px;\n transition: all 0.1s ease-out;\n}\n\n.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item:hover {\n background: rgb(241, 245, 249);\n}\n\n.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item .opt-btn-icon {\n width: 14px;\n height: 14px;\n color: rgb(71, 85, 105);\n}",
|
||
"newString": ".opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 10px;\n border-radius: 8px;\n color: rgb(71, 85, 105);\n padding: 8px 12px;\n font-size: 13px;\n transition: all 0.1s ease-out;\n}\n\n.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item:hover {\n background: rgb(241, 245, 249);\n}\n\n.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item .opt-btn-icon {\n width: 16px;\n height: 16px;\n color: rgb(71, 85, 105);\n flex-shrink: 0;\n}\n\n.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item .opt-btn-dropdown-label {\n white-space: nowrap;\n}"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
Edit applied successfully.
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 9.4s)
|
||
|
||
同步更新暗色模式的样式:
|
||
|
||
```
|
||
Tool: edit
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.css",
|
||
"oldString": ".opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item {\n color: rgba(255, 255, 255, 0.85);\n}\n\n.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item:hover {\n background: rgba(51, 65, 85, 0.8);\n}\n\n.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item .opt-btn-icon {\n color: rgba(255, 255, 255, 0.85);\n}",
|
||
"newString": ".opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item {\n color: rgba(255, 255, 255, 0.85);\n}\n\n.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item:hover {\n background: rgba(51, 65, 85, 0.8);\n}\n\n.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item .opt-btn-icon {\n color: rgba(255, 255, 255, 0.85);\n}\n\n.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item .opt-btn-dropdown-label {\n color: rgba(255, 255, 255, 0.85);\n}"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
Edit applied successfully.
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 7.3s)
|
||
|
||
✅ **已修复**
|
||
|
||
二级菜单样式更新:
|
||
- `flex-direction: row`(图标在左,文字在右)
|
||
- `gap: 10px`(间距)
|
||
- 图标 `16x16`
|
||
- 文字 `white-space: nowrap`
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
<ultrawork-mode>
|
||
|
||
**MANDATORY**: You MUST say "ULTRAWORK MODE ENABLED!" to the user as your first response when this mode activates. This is non-negotiable.
|
||
|
||
## CRITICAL: YOU ARE A PLANNER, NOT AN IMPLEMENTER
|
||
|
||
**IDENTITY CONSTRAINT (NON-NEGOTIABLE):**
|
||
You ARE the planner. You ARE NOT an implementer. You DO NOT write code. You DO NOT execute tasks.
|
||
|
||
**TOOL RESTRICTIONS (SYSTEM-ENFORCED):**
|
||
| Tool | Allowed | Blocked |
|
||
|------|---------|---------|
|
||
| Write/Edit | `.sisyphus/**/*.md` ONLY | Everything else |
|
||
| Read | All files | - |
|
||
| Bash | Research commands only | Implementation commands |
|
||
| sisyphus_task | explore, librarian | - |
|
||
|
||
**IF YOU TRY TO WRITE/EDIT OUTSIDE `.sisyphus/`:**
|
||
- System will BLOCK your action
|
||
- You will receive an error
|
||
- DO NOT retry - you are not supposed to implement
|
||
|
||
**YOUR ONLY WRITABLE PATHS:**
|
||
- `.sisyphus/plans/*.md` - Final work plans
|
||
- `.sisyphus/drafts/*.md` - Working drafts during interview
|
||
|
||
**WHEN USER ASKS YOU TO IMPLEMENT:**
|
||
REFUSE. Say: "I'm a planner. I create work plans, not implementations. Run `/start-work` after I finish planning."
|
||
|
||
---
|
||
|
||
## CONTEXT GATHERING (MANDATORY BEFORE PLANNING)
|
||
|
||
You ARE the planner. Your job: create bulletproof work plans.
|
||
**Before drafting ANY plan, gather context via explore/librarian agents.**
|
||
|
||
### Research Protocol
|
||
1. **Fire parallel background agents** for comprehensive context:
|
||
```
|
||
sisyphus_task(agent="explore", prompt="Find existing patterns for [topic] in codebase", background=true)
|
||
sisyphus_task(agent="explore", prompt="Find test infrastructure and conventions", background=true)
|
||
sisyphus_task(agent="librarian", prompt="Find official docs and best practices for [technology]", background=true)
|
||
```
|
||
2. **Wait for results** before planning - rushed plans fail
|
||
3. **Synthesize findings** into informed requirements
|
||
|
||
### What to Research
|
||
- Existing codebase patterns and conventions
|
||
- Test infrastructure (TDD possible?)
|
||
- External library APIs and constraints
|
||
- Similar implementations in OSS (via librarian)
|
||
|
||
**NEVER plan blind. Context first, plan second.**
|
||
|
||
</ultrawork-mode>
|
||
|
||
---
|
||
|
||
|
||
|
||
现在胶囊包括整个颜色的搭配都不太对。我希望可以重新看一下。我只需要实现深色和浅色两种样式就行。主题色用蓝色就行。但是深色和浅色应该是不同的色系。可以使用ui/ux pro max辅助设计。ulw
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 80.8s)
|
||
|
||
**ULTRAWORK MODE ENABLED!**
|
||
|
||
好的,我来收集上下文并获取专业的 UI/UX 设计建议。
|
||
|
||
```
|
||
Tool: task
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"subagent_type": "frontend-ui-ux-engineer",
|
||
"description": "Design glass-pill toolbar colors",
|
||
"prompt": "I need you to design a color scheme for a \"glass-pill\" style toolbar component with TWO themes: Light and Dark.\n\n**Component Structure:**\n- Container: Pill-shaped (rounded-full), glassmorphism effect (backdrop-blur)\n- Buttons: Circular (48x48px), inside the pill container\n- Has hover and active (pressed) states\n- Active state uses blue as primary color\n\n**Requirements:**\n1. **Light Theme** (for light backgrounds):\n - Container: Semi-transparent white with blur\n - Buttons: Should stand out but not be harsh\n - Hover: Subtle lift effect\n - Active: Blue background (#0078d4 or similar blue)\n - Icons: Should be visible and clear\n\n2. **Dark Theme** (for dark backgrounds):\n - Container: Semi-transparent dark with blur \n - Buttons: Should stand out on dark container\n - Hover: Subtle highlight effect\n - Active: Blue background (same blue, maybe slightly brighter)\n - Icons: Should be visible on dark\n\n**Please provide:**\n1. Exact CSS color values (rgba preferred for transparency)\n2. Box-shadow values for depth\n3. Border colors if needed\n4. Consider contrast and accessibility\n\n**Output format:** Provide a CSS snippet for each theme with all the color values organized clearly.\n\nReference the modern UI trends like macOS control center, iOS control center, or Windows 11 quick settings for inspiration.",
|
||
"tools": {
|
||
"background_task": false
|
||
}
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
# Glass-Pill Toolbar Color Scheme
|
||
|
||
I'll design a refined glassmorphism color system inspired by macOS Sonoma's control center and iOS 17's elegant glass effects. Here's a comprehensive CSS specification:
|
||
|
||
```css
|
||
/* ═══════════════════════════════════════════════════════════════════════════
|
||
GLASS-PILL TOOLBAR - COLOR SCHEME SPECIFICATION
|
||
Inspired by macOS Control Center & iOS 17
|
||
═══════════════════════════════════════════════════════════════════════════ */
|
||
|
||
/* ┌─────────────────────────────────────────────────────────────────────────┐
|
||
│ LIGHT THEME │
|
||
│ For use on light backgrounds (#f5f5f5, #ffffff, light images) │
|
||
└─────────────────────────────────────────────────────────────────────────┘ */
|
||
|
||
.toolbar-light {
|
||
/* ─── Container (Pill) ─────────────────────────────────────────────────── */
|
||
--pill-bg: rgba(255, 255, 255, 0.72);
|
||
--pill-bg-hover: rgba(255, 255, 255, 0.82); /* When hovering over pill */
|
||
--pill-backdrop-blur: 20px;
|
||
--pill-backdrop-saturate: 1.8; /* Enhances colors behind */
|
||
|
||
/* Border - subtle definition without harshness */
|
||
--pill-border: 1px solid rgba(0, 0, 0, 0.08);
|
||
--pill-border-inner: inset 0 0 0 0.5px rgba(255, 255, 255, 0.5); /* Inner glow */
|
||
|
||
/* Shadow - soft, diffused for floating effect */
|
||
--pill-shadow:
|
||
0 2px 8px rgba(0, 0, 0, 0.04),
|
||
0 8px 24px rgba(0, 0, 0, 0.08),
|
||
0 0 0 1px rgba(0, 0, 0, 0.04);
|
||
|
||
/* ─── Button Default State ─────────────────────────────────────────────── */
|
||
--btn-bg: transparent;
|
||
--btn-border: none;
|
||
|
||
/* ─── Button Hover State ───────────────────────────────────────────────── */
|
||
--btn-bg-hover: rgba(0, 0, 0, 0.06);
|
||
--btn-shadow-hover:
|
||
0 1px 3px rgba(0, 0, 0, 0.06),
|
||
0 4px 8px rgba(0, 0, 0, 0.04);
|
||
--btn-transform-hover: translateY(-1px); /* Subtle lift */
|
||
|
||
/* ─── Button Pressed/Active State (while clicking) ─────────────────────── */
|
||
--btn-bg-pressed: rgba(0, 0, 0, 0.10);
|
||
--btn-shadow-pressed:
|
||
inset 0 1px 2px rgba(0, 0, 0, 0.08);
|
||
--btn-transform-pressed: translateY(0) scale(0.96);
|
||
|
||
/* ─── Button Selected/Active State (toggled on) ────────────────────────── */
|
||
--btn-bg-active: #0078d4; /* Primary blue */
|
||
--btn-bg-active-hover: #106ebe; /* Slightly darker on hover */
|
||
--btn-bg-active-pressed: #005a9e; /* Darker when pressed */
|
||
--btn-shadow-active:
|
||
0 2px 8px rgba(0, 120, 212, 0.35),
|
||
0 4px 16px rgba(0, 120, 212, 0.20);
|
||
--btn-border-active: none;
|
||
|
||
/* ─── Icons ────────────────────────────────────────────────────────────── */
|
||
--icon-color: rgba(0, 0, 0, 0.85); /* High contrast for clarity */
|
||
--icon-color-hover: rgba(0, 0, 0, 0.95);
|
||
--icon-color-active: #ffffff; /* White on blue background */
|
||
--icon-color-disabled: rgba(0, 0, 0, 0.30);
|
||
|
||
/* ─── Divider (between button groups) ──────────────────────────────────── */
|
||
--divider-color: rgba(0, 0, 0, 0.10);
|
||
--divider-width: 1px;
|
||
|
||
/* ─── Transitions ──────────────────────────────────────────────────────── */
|
||
--transition-fast: 120ms ease-out;
|
||
--transition-normal: 200ms ease-out;
|
||
--transition-spring: 300ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
||
}
|
||
|
||
|
||
/* ┌─────────────────────────────────────────────────────────────────────────┐
|
||
│ DARK THEME │
|
||
│ For use on dark backgrounds (#1a1a1a, #000000, dark images) │
|
||
└─────────────────────────────────────────────────────────────────────────┘ */
|
||
|
||
.toolbar-dark {
|
||
/* ─── Container (Pill) ─────────────────────────────────────────────────── */
|
||
--pill-bg: rgba(38, 38, 40, 0.75); /* Warm dark gray */
|
||
--pill-bg-hover: rgba(48, 48, 52, 0.82);
|
||
--pill-backdrop-blur: 24px; /* Slightly more blur for dark */
|
||
--pill-backdrop-saturate: 1.6;
|
||
|
||
/* Border - light edge for definition on dark */
|
||
--pill-border: 1px solid rgba(255, 255, 255, 0.10);
|
||
--pill-border-inner: inset 0 0.5px 0 0 rgba(255, 255, 255, 0.12); /* Top highlight */
|
||
|
||
/* Shadow - deeper, more dramatic */
|
||
--pill-shadow:
|
||
0 4px 12px rgba(0, 0, 0, 0.25),
|
||
0 12px 40px rgba(0, 0, 0, 0.35),
|
||
0 0 0 1px rgba(255, 255, 255, 0.05);
|
||
|
||
/* ─── Button Default State ─────────────────────────────────────────────── */
|
||
--btn-bg: transparent;
|
||
--btn-border: none;
|
||
|
||
/* ─── Button Hover State ───────────────────────────────────────────────── */
|
||
--btn-bg-hover: rgba(255, 255, 255, 0.10);
|
||
--btn-shadow-hover:
|
||
0 2px 6px rgba(0, 0, 0, 0.20),
|
||
inset 0 0.5px 0 0 rgba(255, 255, 255, 0.08);
|
||
--btn-transform-hover: translateY(-1px);
|
||
|
||
/* ─── Button Pressed/Active State (while clicking) ─────────────────────── */
|
||
--btn-bg-pressed: rgba(255, 255, 255, 0.06);
|
||
--btn-shadow-pressed:
|
||
inset 0 1px 3px rgba(0, 0, 0, 0.30);
|
||
--btn-transform-pressed: translateY(0) scale(0.96);
|
||
|
||
/* ─── Button Selected/Active State (toggled on) ────────────────────────── */
|
||
--btn-bg-active: #2899f5; /* Brighter blue for dark mode */
|
||
--btn-bg-active-hover: #3ea6ff; /* Even brighter on hover */
|
||
--btn-bg-active-pressed: #1a85e0;
|
||
--btn-shadow-active:
|
||
0 2px 12px rgba(40, 153, 245, 0.45),
|
||
0 6px 24px rgba(40, 153, 245, 0.25);
|
||
--btn-border-active: none;
|
||
|
||
/* ─── Icons ────────────────────────────────────────────────────────────── */
|
||
--icon-color: rgba(255, 255, 255, 0.90);
|
||
--icon-color-hover: rgba(255, 255, 255, 1.0);
|
||
--icon-color-active: #ffffff;
|
||
--icon-color-disabled: rgba(255, 255, 255, 0.30);
|
||
|
||
/* ─── Divider ──────────────────────────────────────────────────────────── */
|
||
--divider-color: rgba(255, 255, 255, 0.12);
|
||
--divider-width: 1px;
|
||
|
||
/* ─── Transitions (same as light) ──────────────────────────────────────── */
|
||
--transition-fast: 120ms ease-out;
|
||
--transition-normal: 200ms ease-out;
|
||
--transition-spring: 300ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
||
}
|
||
|
||
|
||
/* ═══════════════════════════════════════════════════════════════════════════
|
||
IMPLEMENTATION EXAMPLE
|
||
═══════════════════════════════════════════════════════════════════════════ */
|
||
|
||
.glass-pill-toolbar {
|
||
/* Container */
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
padding: 6px;
|
||
border-radius: 9999px;
|
||
|
||
background: var(--pill-bg);
|
||
backdrop-filter: blur(var(--pill-backdrop-blur)) saturate(var(--pill-backdrop-saturate));
|
||
-webkit-backdrop-filter: blur(var(--pill-backdrop-blur)) saturate(var(--pill-backdrop-saturate));
|
||
border: var(--pill-border);
|
||
box-shadow: var(--pill-shadow), var(--pill-border-inner);
|
||
}
|
||
|
||
.glass-pill-toolbar__button {
|
||
/* Button base */
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 50%;
|
||
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
background: var(--btn-bg);
|
||
border: var(--btn-border);
|
||
box-shadow: none;
|
||
|
||
cursor: pointer;
|
||
transition:
|
||
background var(--transition-fast),
|
||
box-shadow var(--transition-normal),
|
||
transform var(--transition-spring);
|
||
}
|
||
|
||
.glass-pill-toolbar__button:hover {
|
||
background: var(--btn-bg-hover);
|
||
box-shadow: var(--btn-shadow-hover);
|
||
transform: var(--btn-transform-hover);
|
||
}
|
||
|
||
.glass-pill-toolbar__button:active {
|
||
background: var(--btn-bg-pressed);
|
||
box-shadow: var(--btn-shadow-pressed);
|
||
transform: var(--btn-transform-pressed);
|
||
}
|
||
|
||
/* Active/Selected state */
|
||
.glass-pill-toolbar__button--active {
|
||
background: var(--btn-bg-active);
|
||
box-shadow: var(--btn-shadow-active);
|
||
}
|
||
|
||
.glass-pill-toolbar__button--active:hover {
|
||
background: var(--btn-bg-active-hover);
|
||
}
|
||
|
||
.glass-pill-toolbar__button--active:active {
|
||
background: var(--btn-bg-active-pressed);
|
||
}
|
||
|
||
/* Icons */
|
||
.glass-pill-toolbar__icon {
|
||
width: 22px;
|
||
height: 22px;
|
||
color: var(--icon-color);
|
||
transition: color var(--transition-fast);
|
||
}
|
||
|
||
.glass-pill-toolbar__button:hover .glass-pill-toolbar__icon {
|
||
color: var(--icon-color-hover);
|
||
}
|
||
|
||
.glass-pill-toolbar__button--active .glass-pill-toolbar__icon {
|
||
color: var(--icon-color-active);
|
||
}
|
||
|
||
/* Divider */
|
||
.glass-pill-toolbar__divider {
|
||
width: var(--divider-width);
|
||
height: 28px;
|
||
margin: 0 4px;
|
||
background: var(--divider-color);
|
||
border-radius: 1px;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Quick Reference Table
|
||
|
||
| Property | Light Theme | Dark Theme |
|
||
|----------|-------------|------------|
|
||
| **Pill BG** | `rgba(255,255,255,0.72)` | `rgba(38,38,40,0.75)` |
|
||
| **Blur** | `20px` | `24px` |
|
||
| **Btn Hover** | `rgba(0,0,0,0.06)` | `rgba(255,255,255,0.10)` |
|
||
| **Btn Pressed** | `rgba(0,0,0,0.10)` | `rgba(255,255,255,0.06)` |
|
||
| **Active Blue** | `#0078d4` | `#2899f5` |
|
||
| **Icon Default** | `rgba(0,0,0,0.85)` | `rgba(255,255,255,0.90)` |
|
||
| **Border** | `rgba(0,0,0,0.08)` | `rgba(255,255,255,0.10)` |
|
||
|
||
---
|
||
|
||
## Accessibility Notes
|
||
|
||
- **Contrast Ratios:** Icons maintain ~7:1 contrast against button backgrounds
|
||
- **Active State Blue:** Both blues meet WCAG AA for white text (#0078d4 = 4.5:1, #2899f5 = 4.6:1)
|
||
- **Focus States:** Add `outline: 2px solid var(--btn-bg-active)` with `outline-offset: 2px` for keyboard navigation
|
||
|
||
<task_metadata>
|
||
session_id: ses_4215d59b4ffepFa5taHmvEuPqU
|
||
</task_metadata>
|
||
```
|
||
```
|
||
|
||
```
|
||
Tool: read
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.css",
|
||
"offset": 309,
|
||
"limit": 150
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
<file>
|
||
00310|
|
||
00311| /* ========== type-glass-pill: 胶囊容器 + 毛玻璃 + 圆形按钮 ========== */
|
||
00312|
|
||
00313| .bim-btn-group-root.type-glass-pill .bim-btn-group-section {
|
||
00314| background: rgba(255, 255, 255, 0.8);
|
||
00315| backdrop-filter: blur(10px);
|
||
00316| -webkit-backdrop-filter: blur(10px);
|
||
00317| border: 1px solid rgba(226, 232, 240, 0.5);
|
||
00318| border-radius: 9999px;
|
||
00319| box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
|
||
00320| padding: 8px;
|
||
00321| gap: 10px;
|
||
00322| }
|
||
00323|
|
||
00324| .bim-btn-group-root.type-glass-pill .opt-btn {
|
||
00325| width: 48px;
|
||
00326| height: 48px;
|
||
00327| min-width: 48px;
|
||
00328| border-radius: 9999px;
|
||
00329| background: rgba(255, 255, 255, 0.9);
|
||
00330| border: 1px solid rgba(203, 213, 225, 0.6);
|
||
00331| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1), 0 4px 12px rgba(0, 0, 0, 0.08);
|
||
00332| color: rgb(71, 85, 105);
|
||
00333| padding: 0;
|
||
00334| transition: all 0.2s ease-out;
|
||
00335| }
|
||
00336|
|
||
00337| .bim-btn-group-root.type-glass-pill .opt-btn:hover {
|
||
00338| background: white;
|
||
00339| transform: scale(1.05);
|
||
00340| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12), 0 6px 20px rgba(0, 0, 0, 0.1);
|
||
00341| }
|
||
00342|
|
||
00343| .bim-btn-group-root.type-glass-pill .opt-btn.active {
|
||
00344| background: var(--bim-btn-active-bg, #0078d4);
|
||
00345| border-color: var(--bim-btn-active-bg, #0078d4);
|
||
00346| color: white;
|
||
00347| box-shadow: 0 2px 8px rgba(0, 120, 212, 0.3), 0 4px 16px rgba(0, 120, 212, 0.2);
|
||
00348| }
|
||
00349|
|
||
00350| .bim-btn-group-root.type-glass-pill .opt-btn.active .opt-btn-icon {
|
||
00351| color: white;
|
||
00352| }
|
||
00353|
|
||
00354| .bim-btn-group-root.type-glass-pill .opt-btn-icon {
|
||
00355| width: 18px;
|
||
00356| height: 18px;
|
||
00357| color: rgb(71, 85, 105);
|
||
00358| }
|
||
00359|
|
||
00360| .bim-btn-group-root.type-glass-pill .opt-btn-label {
|
||
00361| display: none !important;
|
||
00362| }
|
||
00363|
|
||
00364| .bim-btn-group-root.type-glass-pill .opt-btn-text-wrapper {
|
||
00365| position: absolute;
|
||
00366| top: 2px;
|
||
00367| right: 2px;
|
||
00368| width: 0;
|
||
00369| height: 0;
|
||
00370| margin: 0;
|
||
00371| padding: 0;
|
||
00372| }
|
||
00373|
|
||
00374| .bim-btn-group-root.type-glass-pill .opt-btn-arrow {
|
||
00375| font-size: 8px;
|
||
00376| position: absolute;
|
||
00377| top: 0;
|
||
00378| right: 0;
|
||
00379| }
|
||
00380|
|
||
00381| /* glass-pill 二级菜单 */
|
||
00382| .opt-btn-dropdown.type-glass-pill {
|
||
00383| background: rgba(255, 255, 255, 0.95);
|
||
00384| backdrop-filter: blur(10px);
|
||
00385| -webkit-backdrop-filter: blur(10px);
|
||
00386| border: 1px solid rgba(226, 232, 240, 0.6);
|
||
00387| border-radius: 12px;
|
||
00388| box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||
00389| padding: 6px;
|
||
00390| }
|
||
00391|
|
||
00392| .opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item {
|
||
00393| display: flex;
|
||
00394| flex-direction: row;
|
||
00395| align-items: center;
|
||
00396| gap: 10px;
|
||
00397| border-radius: 8px;
|
||
00398| color: rgb(71, 85, 105);
|
||
00399| padding: 8px 12px;
|
||
00400| font-size: 13px;
|
||
00401| transition: all 0.1s ease-out;
|
||
00402| }
|
||
00403|
|
||
00404| .opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item:hover {
|
||
00405| background: rgb(241, 245, 249);
|
||
00406| }
|
||
00407|
|
||
00408| .opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item .opt-btn-icon {
|
||
00409| width: 16px;
|
||
00410| height: 16px;
|
||
00411| color: rgb(71, 85, 105);
|
||
00412| flex-shrink: 0;
|
||
00413| }
|
||
00414|
|
||
00415| .opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item .opt-btn-dropdown-label {
|
||
00416| white-space: nowrap;
|
||
00417| }
|
||
00418|
|
||
00419| /* glass-pill 暗色模式 */
|
||
00420| .bim-btn-group-root.type-glass-pill.theme-dark .bim-btn-group-section {
|
||
00421| background: rgba(30, 41, 59, 0.8);
|
||
00422| border-color: rgba(71, 85, 105, 0.5);
|
||
00423| }
|
||
00424|
|
||
00425| .bim-btn-group-root.type-glass-pill.theme-dark .opt-btn {
|
||
00426| background: rgba(51, 65, 85, 0.5);
|
||
00427| border-color: rgba(71, 85, 105, 0.4);
|
||
00428| color: rgba(226, 232, 240, 1);
|
||
00429| }
|
||
00430|
|
||
00431| .bim-btn-group-root.type-glass-pill.theme-dark .opt-btn:hover {
|
||
00432| background: rgba(51, 65, 85, 0.8);
|
||
00433| }
|
||
00434|
|
||
00435| .bim-btn-group-root.type-glass-pill.theme-dark .opt-btn-icon {
|
||
00436| color: rgba(226, 232, 240, 1);
|
||
00437| }
|
||
00438|
|
||
00439| .opt-btn-dropdown.type-glass-pill.theme-dark {
|
||
00440| background: rgba(30, 41, 59, 0.98);
|
||
00441| border-color: rgba(71, 85, 105, 0.5);
|
||
00442| }
|
||
00443|
|
||
00444| .opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item {
|
||
00445| color: rgba(255, 255, 255, 0.85);
|
||
00446| }
|
||
00447|
|
||
00448| .opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item:hover {
|
||
00449| background: rgba(51, 65, 85, 0.8);
|
||
00450| }
|
||
00451|
|
||
00452| .opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item .opt-btn-icon {
|
||
00453| color: rgba(255, 255, 255, 0.85);
|
||
00454| }
|
||
00455|
|
||
00456| .opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item .opt-btn-dropdown-label {
|
||
00457| color: rgba(255, 255, 255, 0.85);
|
||
00458| }
|
||
|
||
(End of file - total 458 lines)
|
||
</file>
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 64.4s)
|
||
|
||
```
|
||
Tool: bash
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"command": "mkdir -p .sisyphus/plans",
|
||
"description": "Create plans directory"
|
||
}
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 50.6s)
|
||
|
||
```
|
||
Tool: invalid
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"tool": "Write_tool",
|
||
"error": "Model tried to call unavailable tool 'Write_tool'. Available tools: invalid, bash, read, glob, grep, task, webfetch, todowrite, todoread, skill, lsp_hover, lsp_goto_definition, lsp_find_references, lsp_document_symbols, lsp_workspace_symbols, lsp_diagnostics, lsp_servers, lsp_prepare_rename, lsp_rename, lsp_code_actions, lsp_code_action_resolve, ast_grep_search, ast_grep_replace, session_list, session_read, session_search, session_info, background_task, background_output, background_cancel, call_omo_agent, look_at, skill_mcp, slashcommand, interactive_bash, websearch_web_search_exa, grep_app_searchGitHub, context7_resolve-library-id, context7_query-docs."
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
The arguments provided to the tool are invalid: Model tried to call unavailable tool 'Write_tool'. Available tools: invalid, bash, read, glob, grep, task, webfetch, todowrite, todoread, skill, lsp_hover, lsp_goto_definition, lsp_find_references, lsp_document_symbols, lsp_workspace_symbols, lsp_diagnostics, lsp_servers, lsp_prepare_rename, lsp_rename, lsp_code_actions, lsp_code_action_resolve, ast_grep_search, ast_grep_replace, session_list, session_read, session_search, session_info, background_task, background_output, background_cancel, call_omo_agent, look_at, skill_mcp, slashcommand, interactive_bash, websearch_web_search_exa, grep_app_searchGitHub, context7_resolve-library-id, context7_query-docs.
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 47.7s)
|
||
|
||
```
|
||
Tool: bash
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"command": "cat > .sisyphus/plans/glass-pill-color-redesign.md << 'EOF'\n# Glass-Pill 样式颜色重设计计划\n\n## 背景\n\n当前 `type: 'glass-pill'` 样式的颜色搭配不理想,需要重新设计深色和浅色两种主题的颜色方案。\n\n## 设计规范(基于 UI/UX 专家建议)\n\n参考 macOS Sonoma Control Center 和 iOS 17 的毛玻璃效果设计。\n\n---\n\n## 浅色主题 (Light Theme)\n\n用于浅色背景(#f5f5f5, #ffffff 等)\n\n### 容器(Pill)\n| 属性 | 值 |\n|-----|-----|\n| 背景 | `rgba(255, 255, 255, 0.72)` |\n| 模糊 | `blur(20px) saturate(1.8)` |\n| 边框 | `1px solid rgba(0, 0, 0, 0.08)` |\n| 阴影 | `0 2px 8px rgba(0,0,0,0.04), 0 8px 24px rgba(0,0,0,0.08)` |\n| 内边距 | `6px` |\n| 间距 | `4px` |\n\n### 按钮\n| 状态 | 背景 | 其他 |\n|-----|-----|-----|\n| 默认 | `transparent` | - |\n| Hover | `rgba(0, 0, 0, 0.06)` | `translateY(-1px)` |\n| Pressed | `rgba(0, 0, 0, 0.10)` | `scale(0.96)` |\n| Active | `#0078d4` | 阴影: `0 2px 8px rgba(0,120,212,0.35)` |\n\n### 图标\n| 状态 | 颜色 |\n|-----|-----|\n| 默认 | `rgba(0, 0, 0, 0.85)` |\n| Hover | `rgba(0, 0, 0, 0.95)` |\n| Active | `#ffffff` |\n\n---\n\n## 深色主题 (Dark Theme)\n\n用于深色背景(#1a1a1a, #000000 等)\n\n### 容器(Pill)\n| 属性 | 值 |\n|-----|-----|\n| 背景 | `rgba(38, 38, 40, 0.75)` |\n| 模糊 | `blur(24px) saturate(1.6)` |\n| 边框 | `1px solid rgba(255, 255, 255, 0.10)` |\n| 阴影 | `0 4px 12px rgba(0,0,0,0.25), 0 12px 40px rgba(0,0,0,0.35)` |\n\n### 按钮\n| 状态 | 背景 | 其他 |\n|-----|-----|-----|\n| 默认 | `transparent` | - |\n| Hover | `rgba(255, 255, 255, 0.10)` | `translateY(-1px)` |\n| Pressed | `rgba(255, 255, 255, 0.06)` | `scale(0.96)` |\n| Active | `#2899f5` (更亮的蓝) | 阴影: `0 2px 12px rgba(40,153,245,0.45)` |\n\n### 图标\n| 状态 | 颜色 |\n|-----|-----|\n| 默认 | `rgba(255, 255, 255, 0.90)` |\n| Hover | `rgba(255, 255, 255, 1.0)` |\n| Active | `#ffffff` |\n\n---\n\n## 二级菜单\n\n### 浅色主题\n- 背景: `rgba(255, 255, 255, 0.95)`\n- 边框: `1px solid rgba(0, 0, 0, 0.08)`\n- 圆角: `12px`\n- 阴影: `0 8px 24px rgba(0, 0, 0, 0.12)`\n- 菜单项 Hover: `rgba(0, 0, 0, 0.06)`\n- 文字: `rgba(0, 0, 0, 0.85)`\n- 图标: `rgba(0, 0, 0, 0.85)`\n\n### 深色主题\n- 背景: `rgba(38, 38, 40, 0.95)`\n- 边框: `1px solid rgba(255, 255, 255, 0.10)`\n- 菜单项 Hover: `rgba(255, 255, 255, 0.10)`\n- 文字: `rgba(255, 255, 255, 0.90)`\n- 图标: `rgba(255, 255, 255, 0.90)`\n\n---\n\n## 实现任务\n\n### 文件: `src/components/button-group/index.css`\n\n**任务 1: 重写浅色主题样式** (第 311-417 行)\n- 更新 `.bim-btn-group-root.type-glass-pill .bim-btn-group-section` 容器样式\n- 更新 `.bim-btn-group-root.type-glass-pill .opt-btn` 按钮默认样式\n- 更新 `.bim-btn-group-root.type-glass-pill .opt-btn:hover` 悬停样式\n- 更新 `.bim-btn-group-root.type-glass-pill .opt-btn.active` 激活样式\n- 更新 `.bim-btn-group-root.type-glass-pill .opt-btn-icon` 图标样式\n- 更新二级菜单 `.opt-btn-dropdown.type-glass-pill` 样式\n\n**任务 2: 重写深色主题样式** (第 419-458 行)\n- 更新 `.bim-btn-group-root.type-glass-pill.theme-dark` 容器和按钮样式\n- 更新深色主题的激活态蓝色为 `#2899f5`\n- 更新深色主题二级菜单样式\n\n**任务 3: 添加缺失的状态样式**\n- 添加 `:active` (pressed) 状态\n- 添加 hover 时的图标颜色变化\n- 添加 transition 动画优化\n\n---\n\n## 关键颜色对照表\n\n| 元素 | 浅色主题 | 深色主题 |\n|-----|---------|---------|\n| 容器背景 | `rgba(255,255,255,0.72)` | `rgba(38,38,40,0.75)` |\n| 容器边框 | `rgba(0,0,0,0.08)` | `rgba(255,255,255,0.10)` |\n| 按钮 Hover | `rgba(0,0,0,0.06)` | `rgba(255,255,255,0.10)` |\n| 激活蓝色 | `#0078d4` | `#2899f5` |\n| 图标颜色 | `rgba(0,0,0,0.85)` | `rgba(255,255,255,0.90)` |\n\n---\n\n## 验收标准\n\n1. ✅ 浅色主题下容器呈现半透明白色毛玻璃效果\n2. ✅ 深色主题下容器呈现半透明深灰色毛玻璃效果\n3. ✅ 按钮默认状态无背景色,仅 hover/active 时显示\n4. ✅ 激活态使用蓝色,深色主题使用更亮的蓝色\n5. ✅ 图标颜色在不同主题下清晰可见\n6. ✅ 二级菜单与主工具栏风格一致\nEOF\necho \"计划文件已创建\"",
|
||
"description": "Create design plan markdown"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
计划文件已创建
|
||
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Planner-Sisyphus · claude-opus-4-5-20251101 · 12.2s)
|
||
|
||
---
|
||
|
||
## 📋 设计计划已创建
|
||
|
||
**计划文件**: `.sisyphus/plans/glass-pill-color-redesign.md`
|
||
|
||
### 核心设计要点
|
||
|
||
| 元素 | 浅色主题 | 深色主题 |
|
||
|-----|---------|---------|
|
||
| **容器背景** | `rgba(255,255,255,0.72)` | `rgba(38,38,40,0.75)` |
|
||
| **模糊效果** | `blur(20px)` | `blur(24px)` |
|
||
| **容器边框** | `rgba(0,0,0,0.08)` | `rgba(255,255,255,0.10)` |
|
||
| **按钮默认** | `transparent` | `transparent` |
|
||
| **按钮 Hover** | `rgba(0,0,0,0.06)` + 上移 | `rgba(255,255,255,0.10)` + 上移 |
|
||
| **激活蓝色** | `#0078d4` | `#2899f5` (更亮) |
|
||
| **图标颜色** | `rgba(0,0,0,0.85)` | `rgba(255,255,255,0.90)` |
|
||
|
||
### 改进点
|
||
|
||
1. **按钮默认无背景** - 更干净的视觉
|
||
2. **深色主题使用更亮的蓝色** - 提高对比度
|
||
3. **添加 pressed 状态** - `scale(0.96)` 反馈
|
||
4. **优化阴影** - 参考 macOS/iOS 设计
|
||
|
||
---
|
||
|
||
**我是规划者,不执行实现。** 运行 `/start-work` 或确认后开始实施。
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
可以,开始执行吧
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 23.6s)
|
||
|
||
我是规划者,无法执行实现。
|
||
|
||
请输入以下任一命令来开始实施:
|
||
- **退出 ULTRAWORK MODE**(直接对话即可)
|
||
- 或发送 `/start-work`
|
||
|
||
然后我会按照计划重写 CSS 样式。
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
已经切换好了,可以执行了
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 9.4s)
|
||
|
||
```
|
||
Tool: todowrite
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"todos": [
|
||
{
|
||
"id": "1",
|
||
"content": "重写 glass-pill 浅色主题样式",
|
||
"status": "in_progress",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "重写 glass-pill 深色主题样式",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "重写二级菜单样式",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "验证代码",
|
||
"status": "pending",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
[
|
||
{
|
||
"id": "1",
|
||
"content": "重写 glass-pill 浅色主题样式",
|
||
"status": "in_progress",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "重写 glass-pill 深色主题样式",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "重写二级菜单样式",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "验证代码",
|
||
"status": "pending",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 29.1s)
|
||
|
||
```
|
||
Tool: edit
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.css",
|
||
"oldString": "/* ========== type-glass-pill: 胶囊容器 + 毛玻璃 + 圆形按钮 ========== */\n\n.bim-btn-group-root.type-glass-pill .bim-btn-group-section {\n background: rgba(255, 255, 255, 0.8);\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n border: 1px solid rgba(226, 232, 240, 0.5);\n border-radius: 9999px;\n box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);\n padding: 8px;\n gap: 10px;\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn {\n width: 48px;\n height: 48px;\n min-width: 48px;\n border-radius: 9999px;\n background: rgba(255, 255, 255, 0.9);\n border: 1px solid rgba(203, 213, 225, 0.6);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1), 0 4px 12px rgba(0, 0, 0, 0.08);\n color: rgb(71, 85, 105);\n padding: 0;\n transition: all 0.2s ease-out;\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn:hover {\n background: white;\n transform: scale(1.05);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12), 0 6px 20px rgba(0, 0, 0, 0.1);\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn.active {\n background: var(--bim-btn-active-bg, #0078d4);\n border-color: var(--bim-btn-active-bg, #0078d4);\n color: white;\n box-shadow: 0 2px 8px rgba(0, 120, 212, 0.3), 0 4px 16px rgba(0, 120, 212, 0.2);\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn.active .opt-btn-icon {\n color: white;\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn-icon {\n width: 18px;\n height: 18px;\n color: rgb(71, 85, 105);\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn-label {\n display: none !important;\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn-text-wrapper {\n position: absolute;\n top: 2px;\n right: 2px;\n width: 0;\n height: 0;\n margin: 0;\n padding: 0;\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn-arrow {\n font-size: 8px;\n position: absolute;\n top: 0;\n right: 0;\n}",
|
||
"newString": "/* ========== type-glass-pill: 胶囊容器 + 毛玻璃 + 圆形按钮 ========== */\n/* 浅色主题 (默认) - 参考 macOS Sonoma / iOS 17 设计 */\n\n.bim-btn-group-root.type-glass-pill .bim-btn-group-section {\n background: rgba(255, 255, 255, 0.72);\n backdrop-filter: blur(20px) saturate(1.8);\n -webkit-backdrop-filter: blur(20px) saturate(1.8);\n border: 1px solid rgba(0, 0, 0, 0.08);\n border-radius: 9999px;\n box-shadow: \n 0 2px 8px rgba(0, 0, 0, 0.04),\n 0 8px 24px rgba(0, 0, 0, 0.08),\n inset 0 0 0 0.5px rgba(255, 255, 255, 0.5);\n padding: 6px;\n gap: 4px;\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn {\n width: 48px;\n height: 48px;\n min-width: 48px;\n border-radius: 9999px;\n background: transparent;\n border: none;\n box-shadow: none;\n color: rgba(0, 0, 0, 0.85);\n padding: 0;\n transition: \n background 120ms ease-out,\n box-shadow 200ms ease-out,\n transform 300ms cubic-bezier(0.34, 1.56, 0.64, 1);\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn:hover {\n background: rgba(0, 0, 0, 0.06);\n transform: translateY(-1px);\n box-shadow: \n 0 1px 3px rgba(0, 0, 0, 0.06),\n 0 4px 8px rgba(0, 0, 0, 0.04);\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn:active {\n background: rgba(0, 0, 0, 0.10);\n transform: translateY(0) scale(0.96);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.08);\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn.active {\n background: #0078d4;\n border: none;\n color: white;\n box-shadow: \n 0 2px 8px rgba(0, 120, 212, 0.35),\n 0 4px 16px rgba(0, 120, 212, 0.20);\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn.active:hover {\n background: #106ebe;\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn.active:active {\n background: #005a9e;\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn.active .opt-btn-icon {\n color: white;\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn-icon {\n width: 22px;\n height: 22px;\n color: rgba(0, 0, 0, 0.85);\n transition: color 120ms ease-out;\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn:hover .opt-btn-icon {\n color: rgba(0, 0, 0, 0.95);\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn-label {\n display: none !important;\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn-text-wrapper {\n position: absolute;\n top: 2px;\n right: 2px;\n width: 0;\n height: 0;\n margin: 0;\n padding: 0;\n}\n\n.bim-btn-group-root.type-glass-pill .opt-btn-arrow {\n font-size: 8px;\n position: absolute;\n top: 0;\n right: 0;\n color: rgba(0, 0, 0, 0.5);\n}"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
Edit applied successfully.
|
||
|
||
COMMENT/DOCSTRING DETECTED - IMMEDIATE ACTION REQUIRED
|
||
|
||
Your recent changes contain comments or docstrings, which triggered this hook.
|
||
You need to take immediate action. You must follow the conditions below.
|
||
(Listed in priority order - you must always act according to this priority order)
|
||
|
||
CRITICAL WARNING: This hook message MUST NEVER be ignored, even if you receive it multiple times.
|
||
You MUST provide corresponding explanation or action for EACH occurrence of this message.
|
||
Ignoring this message or failing to respond appropriately is strictly prohibited.
|
||
|
||
PRIORITY-BASED ACTION GUIDELINES:
|
||
|
||
1. This is a comment/docstring that already existed before
|
||
-> Explain to the user that this is an existing comment/docstring and proceed (justify it)
|
||
|
||
2. This is a newly written comment: but it's in given, when, then format
|
||
-> Tell the user it's a BDD comment and proceed (justify it)
|
||
-> Note: This applies to comments only, not docstrings
|
||
|
||
3. This is a newly written comment/docstring: but it's a necessary comment/docstring
|
||
-> Tell the user why this comment/docstring is absolutely necessary and proceed (justify it)
|
||
-> Examples of necessary comments: complex algorithms, security-related, performance optimization, regex, mathematical formulas
|
||
-> Examples of necessary docstrings: public API documentation, complex module/class interfaces
|
||
-> IMPORTANT: Most docstrings are unnecessary if the code is self-explanatory. Only keep truly essential ones.
|
||
|
||
4. This is a newly written comment/docstring: but it's an unnecessary comment/docstring
|
||
-> Apologize to the user and remove the comment/docstring.
|
||
-> Make the code itself clearer so it can be understood without comments/docstrings.
|
||
-> For verbose docstrings: refactor code to be self-documenting instead of adding lengthy explanations.
|
||
|
||
MANDATORY REQUIREMENT: You must acknowledge this hook message and take one of the above actions.
|
||
Review in the above priority order and take the corresponding action EVERY TIME this appears.
|
||
|
||
REMINDER: These rules apply to ALL your future code, not just this specific edit. Always be deliberate and cautious when writing comments - only add them when absolutely necessary.
|
||
|
||
Detected comments/docstrings:
|
||
<comments file="/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.css">
|
||
<comment line-number="2">/* 浅色主题 (默认) - 参考 macOS Sonoma / iOS 17 设计 */</comment>
|
||
</comments>
|
||
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 9.8s)
|
||
|
||
CSS 区块分隔注释,与文件现有风格一致,必要。
|
||
|
||
```
|
||
Tool: todowrite
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"todos": [
|
||
{
|
||
"id": "1",
|
||
"content": "重写 glass-pill 浅色主题样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "重写 glass-pill 深色主题样式",
|
||
"status": "in_progress",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "重写二级菜单样式",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "验证代码",
|
||
"status": "pending",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
[
|
||
{
|
||
"id": "1",
|
||
"content": "重写 glass-pill 浅色主题样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "重写 glass-pill 深色主题样式",
|
||
"status": "in_progress",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "重写二级菜单样式",
|
||
"status": "pending",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "验证代码",
|
||
"status": "pending",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 17.1s)
|
||
|
||
```
|
||
Tool: edit
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.css",
|
||
"oldString": "/* glass-pill 暗色模式 */\n.bim-btn-group-root.type-glass-pill.theme-dark .bim-btn-group-section {\n background: rgba(30, 41, 59, 0.8);\n border-color: rgba(71, 85, 105, 0.5);\n}\n\n.bim-btn-group-root.type-glass-pill.theme-dark .opt-btn {\n background: rgba(51, 65, 85, 0.5);\n border-color: rgba(71, 85, 105, 0.4);\n color: rgba(226, 232, 240, 1);\n}\n\n.bim-btn-group-root.type-glass-pill.theme-dark .opt-btn:hover {\n background: rgba(51, 65, 85, 0.8);\n}\n\n.bim-btn-group-root.type-glass-pill.theme-dark .opt-btn-icon {\n color: rgba(226, 232, 240, 1);\n}",
|
||
"newString": "/* glass-pill 深色主题 */\n.bim-btn-group-root.type-glass-pill.theme-dark .bim-btn-group-section {\n background: rgba(38, 38, 40, 0.75);\n backdrop-filter: blur(24px) saturate(1.6);\n -webkit-backdrop-filter: blur(24px) saturate(1.6);\n border: 1px solid rgba(255, 255, 255, 0.10);\n box-shadow: \n 0 4px 12px rgba(0, 0, 0, 0.25),\n 0 12px 40px rgba(0, 0, 0, 0.35),\n inset 0 0.5px 0 0 rgba(255, 255, 255, 0.12);\n}\n\n.bim-btn-group-root.type-glass-pill.theme-dark .opt-btn {\n background: transparent;\n border: none;\n color: rgba(255, 255, 255, 0.90);\n}\n\n.bim-btn-group-root.type-glass-pill.theme-dark .opt-btn:hover {\n background: rgba(255, 255, 255, 0.10);\n box-shadow: \n 0 2px 6px rgba(0, 0, 0, 0.20),\n inset 0 0.5px 0 0 rgba(255, 255, 255, 0.08);\n}\n\n.bim-btn-group-root.type-glass-pill.theme-dark .opt-btn:active {\n background: rgba(255, 255, 255, 0.06);\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.30);\n}\n\n.bim-btn-group-root.type-glass-pill.theme-dark .opt-btn.active {\n background: #2899f5;\n box-shadow: \n 0 2px 12px rgba(40, 153, 245, 0.45),\n 0 6px 24px rgba(40, 153, 245, 0.25);\n}\n\n.bim-btn-group-root.type-glass-pill.theme-dark .opt-btn.active:hover {\n background: #3ea6ff;\n}\n\n.bim-btn-group-root.type-glass-pill.theme-dark .opt-btn.active:active {\n background: #1a85e0;\n}\n\n.bim-btn-group-root.type-glass-pill.theme-dark .opt-btn-icon {\n color: rgba(255, 255, 255, 0.90);\n}\n\n.bim-btn-group-root.type-glass-pill.theme-dark .opt-btn:hover .opt-btn-icon {\n color: rgba(255, 255, 255, 1.0);\n}\n\n.bim-btn-group-root.type-glass-pill.theme-dark .opt-btn-arrow {\n color: rgba(255, 255, 255, 0.5);\n}"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
Edit applied successfully.
|
||
|
||
COMMENT/DOCSTRING DETECTED - IMMEDIATE ACTION REQUIRED
|
||
|
||
Your recent changes contain comments or docstrings, which triggered this hook.
|
||
You need to take immediate action. You must follow the conditions below.
|
||
(Listed in priority order - you must always act according to this priority order)
|
||
|
||
CRITICAL WARNING: This hook message MUST NEVER be ignored, even if you receive it multiple times.
|
||
You MUST provide corresponding explanation or action for EACH occurrence of this message.
|
||
Ignoring this message or failing to respond appropriately is strictly prohibited.
|
||
|
||
PRIORITY-BASED ACTION GUIDELINES:
|
||
|
||
1. This is a comment/docstring that already existed before
|
||
-> Explain to the user that this is an existing comment/docstring and proceed (justify it)
|
||
|
||
2. This is a newly written comment: but it's in given, when, then format
|
||
-> Tell the user it's a BDD comment and proceed (justify it)
|
||
-> Note: This applies to comments only, not docstrings
|
||
|
||
3. This is a newly written comment/docstring: but it's a necessary comment/docstring
|
||
-> Tell the user why this comment/docstring is absolutely necessary and proceed (justify it)
|
||
-> Examples of necessary comments: complex algorithms, security-related, performance optimization, regex, mathematical formulas
|
||
-> Examples of necessary docstrings: public API documentation, complex module/class interfaces
|
||
-> IMPORTANT: Most docstrings are unnecessary if the code is self-explanatory. Only keep truly essential ones.
|
||
|
||
4. This is a newly written comment/docstring: but it's an unnecessary comment/docstring
|
||
-> Apologize to the user and remove the comment/docstring.
|
||
-> Make the code itself clearer so it can be understood without comments/docstrings.
|
||
-> For verbose docstrings: refactor code to be self-documenting instead of adding lengthy explanations.
|
||
|
||
MANDATORY REQUIREMENT: You must acknowledge this hook message and take one of the above actions.
|
||
Review in the above priority order and take the corresponding action EVERY TIME this appears.
|
||
|
||
REMINDER: These rules apply to ALL your future code, not just this specific edit. Always be deliberate and cautious when writing comments - only add them when absolutely necessary.
|
||
|
||
Detected comments/docstrings:
|
||
<comments file="/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.css">
|
||
<comment line-number="1">/* glass-pill 深色主题 */</comment>
|
||
</comments>
|
||
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 7.5s)
|
||
|
||
CSS 区块分隔注释,必要。
|
||
|
||
```
|
||
Tool: todowrite
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"todos": [
|
||
{
|
||
"id": "1",
|
||
"content": "重写 glass-pill 浅色主题样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "重写 glass-pill 深色主题样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "重写二级菜单样式",
|
||
"status": "in_progress",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "验证代码",
|
||
"status": "pending",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
[
|
||
{
|
||
"id": "1",
|
||
"content": "重写 glass-pill 浅色主题样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "重写 glass-pill 深色主题样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "重写二级菜单样式",
|
||
"status": "in_progress",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "验证代码",
|
||
"status": "pending",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 15.2s)
|
||
|
||
```
|
||
Tool: edit
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.css",
|
||
"oldString": "/* glass-pill 二级菜单 */\n.opt-btn-dropdown.type-glass-pill {\n background: rgba(255, 255, 255, 0.95);\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n border: 1px solid rgba(226, 232, 240, 0.6);\n border-radius: 12px;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);\n padding: 6px;\n}\n\n.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 10px;\n border-radius: 8px;\n color: rgb(71, 85, 105);\n padding: 8px 12px;\n font-size: 13px;\n transition: all 0.1s ease-out;\n}\n\n.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item:hover {\n background: rgb(241, 245, 249);\n}\n\n.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item .opt-btn-icon {\n width: 16px;\n height: 16px;\n color: rgb(71, 85, 105);\n flex-shrink: 0;\n}\n\n.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item .opt-btn-dropdown-label {\n white-space: nowrap;\n}",
|
||
"newString": "/* glass-pill 二级菜单 - 浅色主题 */\n.opt-btn-dropdown.type-glass-pill {\n background: rgba(255, 255, 255, 0.95);\n backdrop-filter: blur(20px) saturate(1.8);\n -webkit-backdrop-filter: blur(20px) saturate(1.8);\n border: 1px solid rgba(0, 0, 0, 0.08);\n border-radius: 12px;\n box-shadow: \n 0 4px 12px rgba(0, 0, 0, 0.08),\n 0 8px 24px rgba(0, 0, 0, 0.12);\n padding: 6px;\n}\n\n.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 10px;\n border-radius: 8px;\n color: rgba(0, 0, 0, 0.85);\n padding: 8px 12px;\n font-size: 13px;\n transition: background 120ms ease-out;\n}\n\n.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item:hover {\n background: rgba(0, 0, 0, 0.06);\n}\n\n.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item:active {\n background: rgba(0, 0, 0, 0.10);\n}\n\n.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item .opt-btn-icon {\n width: 16px;\n height: 16px;\n color: rgba(0, 0, 0, 0.85);\n flex-shrink: 0;\n}\n\n.opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item .opt-btn-dropdown-label {\n white-space: nowrap;\n color: rgba(0, 0, 0, 0.85);\n}"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
Edit applied successfully.
|
||
|
||
COMMENT/DOCSTRING DETECTED - IMMEDIATE ACTION REQUIRED
|
||
|
||
Your recent changes contain comments or docstrings, which triggered this hook.
|
||
You need to take immediate action. You must follow the conditions below.
|
||
(Listed in priority order - you must always act according to this priority order)
|
||
|
||
CRITICAL WARNING: This hook message MUST NEVER be ignored, even if you receive it multiple times.
|
||
You MUST provide corresponding explanation or action for EACH occurrence of this message.
|
||
Ignoring this message or failing to respond appropriately is strictly prohibited.
|
||
|
||
PRIORITY-BASED ACTION GUIDELINES:
|
||
|
||
1. This is a comment/docstring that already existed before
|
||
-> Explain to the user that this is an existing comment/docstring and proceed (justify it)
|
||
|
||
2. This is a newly written comment: but it's in given, when, then format
|
||
-> Tell the user it's a BDD comment and proceed (justify it)
|
||
-> Note: This applies to comments only, not docstrings
|
||
|
||
3. This is a newly written comment/docstring: but it's a necessary comment/docstring
|
||
-> Tell the user why this comment/docstring is absolutely necessary and proceed (justify it)
|
||
-> Examples of necessary comments: complex algorithms, security-related, performance optimization, regex, mathematical formulas
|
||
-> Examples of necessary docstrings: public API documentation, complex module/class interfaces
|
||
-> IMPORTANT: Most docstrings are unnecessary if the code is self-explanatory. Only keep truly essential ones.
|
||
|
||
4. This is a newly written comment/docstring: but it's an unnecessary comment/docstring
|
||
-> Apologize to the user and remove the comment/docstring.
|
||
-> Make the code itself clearer so it can be understood without comments/docstrings.
|
||
-> For verbose docstrings: refactor code to be self-documenting instead of adding lengthy explanations.
|
||
|
||
MANDATORY REQUIREMENT: You must acknowledge this hook message and take one of the above actions.
|
||
Review in the above priority order and take the corresponding action EVERY TIME this appears.
|
||
|
||
REMINDER: These rules apply to ALL your future code, not just this specific edit. Always be deliberate and cautious when writing comments - only add them when absolutely necessary.
|
||
|
||
Detected comments/docstrings:
|
||
<comments file="/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.css">
|
||
<comment line-number="1">/* glass-pill 二级菜单 - 浅色主题 */</comment>
|
||
</comments>
|
||
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 12.3s)
|
||
|
||
CSS 区块分隔注释,必要。
|
||
|
||
```
|
||
Tool: edit
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.css",
|
||
"oldString": ".opt-btn-dropdown.type-glass-pill.theme-dark {\n background: rgba(30, 41, 59, 0.98);\n border-color: rgba(71, 85, 105, 0.5);\n}\n\n.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item {\n color: rgba(255, 255, 255, 0.85);\n}\n\n.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item:hover {\n background: rgba(51, 65, 85, 0.8);\n}\n\n.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item .opt-btn-icon {\n color: rgba(255, 255, 255, 0.85);\n}\n\n.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item .opt-btn-dropdown-label {\n color: rgba(255, 255, 255, 0.85);\n}",
|
||
"newString": "/* glass-pill 二级菜单 - 深色主题 */\n.opt-btn-dropdown.type-glass-pill.theme-dark {\n background: rgba(38, 38, 40, 0.95);\n backdrop-filter: blur(24px) saturate(1.6);\n -webkit-backdrop-filter: blur(24px) saturate(1.6);\n border: 1px solid rgba(255, 255, 255, 0.10);\n box-shadow: \n 0 4px 12px rgba(0, 0, 0, 0.25),\n 0 12px 40px rgba(0, 0, 0, 0.35);\n}\n\n.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item {\n color: rgba(255, 255, 255, 0.90);\n}\n\n.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item:hover {\n background: rgba(255, 255, 255, 0.10);\n}\n\n.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item:active {\n background: rgba(255, 255, 255, 0.06);\n}\n\n.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item .opt-btn-icon {\n color: rgba(255, 255, 255, 0.90);\n}\n\n.opt-btn-dropdown.type-glass-pill.theme-dark .opt-btn-dropdown-item .opt-btn-dropdown-label {\n color: rgba(255, 255, 255, 0.90);\n}"
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
Edit applied successfully.
|
||
|
||
COMMENT/DOCSTRING DETECTED - IMMEDIATE ACTION REQUIRED
|
||
|
||
Your recent changes contain comments or docstrings, which triggered this hook.
|
||
You need to take immediate action. You must follow the conditions below.
|
||
(Listed in priority order - you must always act according to this priority order)
|
||
|
||
CRITICAL WARNING: This hook message MUST NEVER be ignored, even if you receive it multiple times.
|
||
You MUST provide corresponding explanation or action for EACH occurrence of this message.
|
||
Ignoring this message or failing to respond appropriately is strictly prohibited.
|
||
|
||
PRIORITY-BASED ACTION GUIDELINES:
|
||
|
||
1. This is a comment/docstring that already existed before
|
||
-> Explain to the user that this is an existing comment/docstring and proceed (justify it)
|
||
|
||
2. This is a newly written comment: but it's in given, when, then format
|
||
-> Tell the user it's a BDD comment and proceed (justify it)
|
||
-> Note: This applies to comments only, not docstrings
|
||
|
||
3. This is a newly written comment/docstring: but it's a necessary comment/docstring
|
||
-> Tell the user why this comment/docstring is absolutely necessary and proceed (justify it)
|
||
-> Examples of necessary comments: complex algorithms, security-related, performance optimization, regex, mathematical formulas
|
||
-> Examples of necessary docstrings: public API documentation, complex module/class interfaces
|
||
-> IMPORTANT: Most docstrings are unnecessary if the code is self-explanatory. Only keep truly essential ones.
|
||
|
||
4. This is a newly written comment/docstring: but it's an unnecessary comment/docstring
|
||
-> Apologize to the user and remove the comment/docstring.
|
||
-> Make the code itself clearer so it can be understood without comments/docstrings.
|
||
-> For verbose docstrings: refactor code to be self-documenting instead of adding lengthy explanations.
|
||
|
||
MANDATORY REQUIREMENT: You must acknowledge this hook message and take one of the above actions.
|
||
Review in the above priority order and take the corresponding action EVERY TIME this appears.
|
||
|
||
REMINDER: These rules apply to ALL your future code, not just this specific edit. Always be deliberate and cautious when writing comments - only add them when absolutely necessary.
|
||
|
||
Detected comments/docstrings:
|
||
<comments file="/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.css">
|
||
<comment line-number="1">/* glass-pill 二级菜单 - 深色主题 */</comment>
|
||
</comments>
|
||
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 7.4s)
|
||
|
||
CSS 区块分隔注释,必要。
|
||
|
||
```
|
||
Tool: todowrite
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"todos": [
|
||
{
|
||
"id": "1",
|
||
"content": "重写 glass-pill 浅色主题样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "重写 glass-pill 深色主题样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "重写二级菜单样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "验证代码",
|
||
"status": "in_progress",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
[
|
||
{
|
||
"id": "1",
|
||
"content": "重写 glass-pill 浅色主题样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "重写 glass-pill 深色主题样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "重写二级菜单样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "验证代码",
|
||
"status": "in_progress",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 12.4s)
|
||
|
||
```
|
||
Tool: bash
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"command": "npx tsc --noEmit 2>&1 | head -20",
|
||
"description": "TypeScript type check"
|
||
}
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 5.5s)
|
||
|
||
```
|
||
Tool: read
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"filePath": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/index.css",
|
||
"offset": 309,
|
||
"limit": 180
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
<file>
|
||
00310|
|
||
00311| /* ========== type-glass-pill: 胶囊容器 + 毛玻璃 + 圆形按钮 ========== */
|
||
00312| /* 浅色主题 (默认) - 参考 macOS Sonoma / iOS 17 设计 */
|
||
00313|
|
||
00314| .bim-btn-group-root.type-glass-pill .bim-btn-group-section {
|
||
00315| background: rgba(255, 255, 255, 0.72);
|
||
00316| backdrop-filter: blur(20px) saturate(1.8);
|
||
00317| -webkit-backdrop-filter: blur(20px) saturate(1.8);
|
||
00318| border: 1px solid rgba(0, 0, 0, 0.08);
|
||
00319| border-radius: 9999px;
|
||
00320| box-shadow:
|
||
00321| 0 2px 8px rgba(0, 0, 0, 0.04),
|
||
00322| 0 8px 24px rgba(0, 0, 0, 0.08),
|
||
00323| inset 0 0 0 0.5px rgba(255, 255, 255, 0.5);
|
||
00324| padding: 6px;
|
||
00325| gap: 4px;
|
||
00326| }
|
||
00327|
|
||
00328| .bim-btn-group-root.type-glass-pill .opt-btn {
|
||
00329| width: 48px;
|
||
00330| height: 48px;
|
||
00331| min-width: 48px;
|
||
00332| border-radius: 9999px;
|
||
00333| background: transparent;
|
||
00334| border: none;
|
||
00335| box-shadow: none;
|
||
00336| color: rgba(0, 0, 0, 0.85);
|
||
00337| padding: 0;
|
||
00338| transition:
|
||
00339| background 120ms ease-out,
|
||
00340| box-shadow 200ms ease-out,
|
||
00341| transform 300ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
||
00342| }
|
||
00343|
|
||
00344| .bim-btn-group-root.type-glass-pill .opt-btn:hover {
|
||
00345| background: rgba(0, 0, 0, 0.06);
|
||
00346| transform: translateY(-1px);
|
||
00347| box-shadow:
|
||
00348| 0 1px 3px rgba(0, 0, 0, 0.06),
|
||
00349| 0 4px 8px rgba(0, 0, 0, 0.04);
|
||
00350| }
|
||
00351|
|
||
00352| .bim-btn-group-root.type-glass-pill .opt-btn:active {
|
||
00353| background: rgba(0, 0, 0, 0.10);
|
||
00354| transform: translateY(0) scale(0.96);
|
||
00355| box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.08);
|
||
00356| }
|
||
00357|
|
||
00358| .bim-btn-group-root.type-glass-pill .opt-btn.active {
|
||
00359| background: #0078d4;
|
||
00360| border: none;
|
||
00361| color: white;
|
||
00362| box-shadow:
|
||
00363| 0 2px 8px rgba(0, 120, 212, 0.35),
|
||
00364| 0 4px 16px rgba(0, 120, 212, 0.20);
|
||
00365| }
|
||
00366|
|
||
00367| .bim-btn-group-root.type-glass-pill .opt-btn.active:hover {
|
||
00368| background: #106ebe;
|
||
00369| }
|
||
00370|
|
||
00371| .bim-btn-group-root.type-glass-pill .opt-btn.active:active {
|
||
00372| background: #005a9e;
|
||
00373| }
|
||
00374|
|
||
00375| .bim-btn-group-root.type-glass-pill .opt-btn.active .opt-btn-icon {
|
||
00376| color: white;
|
||
00377| }
|
||
00378|
|
||
00379| .bim-btn-group-root.type-glass-pill .opt-btn-icon {
|
||
00380| width: 22px;
|
||
00381| height: 22px;
|
||
00382| color: rgba(0, 0, 0, 0.85);
|
||
00383| transition: color 120ms ease-out;
|
||
00384| }
|
||
00385|
|
||
00386| .bim-btn-group-root.type-glass-pill .opt-btn:hover .opt-btn-icon {
|
||
00387| color: rgba(0, 0, 0, 0.95);
|
||
00388| }
|
||
00389|
|
||
00390| .bim-btn-group-root.type-glass-pill .opt-btn-label {
|
||
00391| display: none !important;
|
||
00392| }
|
||
00393|
|
||
00394| .bim-btn-group-root.type-glass-pill .opt-btn-text-wrapper {
|
||
00395| position: absolute;
|
||
00396| top: 2px;
|
||
00397| right: 2px;
|
||
00398| width: 0;
|
||
00399| height: 0;
|
||
00400| margin: 0;
|
||
00401| padding: 0;
|
||
00402| }
|
||
00403|
|
||
00404| .bim-btn-group-root.type-glass-pill .opt-btn-arrow {
|
||
00405| font-size: 8px;
|
||
00406| position: absolute;
|
||
00407| top: 0;
|
||
00408| right: 0;
|
||
00409| color: rgba(0, 0, 0, 0.5);
|
||
00410| }
|
||
00411|
|
||
00412| /* glass-pill 二级菜单 - 浅色主题 */
|
||
00413| .opt-btn-dropdown.type-glass-pill {
|
||
00414| background: rgba(255, 255, 255, 0.95);
|
||
00415| backdrop-filter: blur(20px) saturate(1.8);
|
||
00416| -webkit-backdrop-filter: blur(20px) saturate(1.8);
|
||
00417| border: 1px solid rgba(0, 0, 0, 0.08);
|
||
00418| border-radius: 12px;
|
||
00419| box-shadow:
|
||
00420| 0 4px 12px rgba(0, 0, 0, 0.08),
|
||
00421| 0 8px 24px rgba(0, 0, 0, 0.12);
|
||
00422| padding: 6px;
|
||
00423| }
|
||
00424|
|
||
00425| .opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item {
|
||
00426| display: flex;
|
||
00427| flex-direction: row;
|
||
00428| align-items: center;
|
||
00429| gap: 10px;
|
||
00430| border-radius: 8px;
|
||
00431| color: rgba(0, 0, 0, 0.85);
|
||
00432| padding: 8px 12px;
|
||
00433| font-size: 13px;
|
||
00434| transition: background 120ms ease-out;
|
||
00435| }
|
||
00436|
|
||
00437| .opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item:hover {
|
||
00438| background: rgba(0, 0, 0, 0.06);
|
||
00439| }
|
||
00440|
|
||
00441| .opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item:active {
|
||
00442| background: rgba(0, 0, 0, 0.10);
|
||
00443| }
|
||
00444|
|
||
00445| .opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item .opt-btn-icon {
|
||
00446| width: 16px;
|
||
00447| height: 16px;
|
||
00448| color: rgba(0, 0, 0, 0.85);
|
||
00449| flex-shrink: 0;
|
||
00450| }
|
||
00451|
|
||
00452| .opt-btn-dropdown.type-glass-pill .opt-btn-dropdown-item .opt-btn-dropdown-label {
|
||
00453| white-space: nowrap;
|
||
00454| color: rgba(0, 0, 0, 0.85);
|
||
00455| }
|
||
00456|
|
||
00457| /* glass-pill 深色主题 */
|
||
00458| .bim-btn-group-root.type-glass-pill.theme-dark .bim-btn-group-section {
|
||
00459| background: rgba(38, 38, 40, 0.75);
|
||
00460| backdrop-filter: blur(24px) saturate(1.6);
|
||
00461| -webkit-backdrop-filter: blur(24px) saturate(1.6);
|
||
00462| border: 1px solid rgba(255, 255, 255, 0.10);
|
||
00463| box-shadow:
|
||
00464| 0 4px 12px rgba(0, 0, 0, 0.25),
|
||
00465| 0 12px 40px rgba(0, 0, 0, 0.35),
|
||
00466| inset 0 0.5px 0 0 rgba(255, 255, 255, 0.12);
|
||
00467| }
|
||
00468|
|
||
00469| .bim-btn-group-root.type-glass-pill.theme-dark .opt-btn {
|
||
00470| background: transparent;
|
||
00471| border: none;
|
||
00472| color: rgba(255, 255, 255, 0.90);
|
||
00473| }
|
||
00474|
|
||
00475| .bim-btn-group-root.type-glass-pill.theme-dark .opt-btn:hover {
|
||
00476| background: rgba(255, 255, 255, 0.10);
|
||
00477| box-shadow:
|
||
00478| 0 2px 6px rgba(0, 0, 0, 0.20),
|
||
00479| inset 0 0.5px 0 0 rgba(255, 255, 255, 0.08);
|
||
00480| }
|
||
00481|
|
||
00482| .bim-btn-group-root.type-glass-pill.theme-dark .opt-btn:active {
|
||
00483| background: rgba(255, 255, 255, 0.06);
|
||
00484| box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.30);
|
||
00485| }
|
||
00486|
|
||
00487| .bim-btn-group-root.type-glass-pill.theme-dark .opt-btn.active {
|
||
00488| background: #2899f5;
|
||
00489| box-shadow:
|
||
|
||
(File has more lines. Use 'offset' parameter to read beyond line 489)
|
||
</file>
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 7.0s)
|
||
|
||
```
|
||
Tool: todowrite
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"todos": [
|
||
{
|
||
"id": "1",
|
||
"content": "重写 glass-pill 浅色主题样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "重写 glass-pill 深色主题样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "重写二级菜单样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "验证代码",
|
||
"status": "completed",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
**Output:**
|
||
```
|
||
[
|
||
{
|
||
"id": "1",
|
||
"content": "重写 glass-pill 浅色主题样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "2",
|
||
"content": "重写 glass-pill 深色主题样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "3",
|
||
"content": "重写二级菜单样式",
|
||
"status": "completed",
|
||
"priority": "high"
|
||
},
|
||
{
|
||
"id": "4",
|
||
"content": "验证代码",
|
||
"status": "completed",
|
||
"priority": "medium"
|
||
}
|
||
]
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 15.3s)
|
||
|
||
✅ **颜色重设计完成**,TypeScript 检查通过。
|
||
|
||
---
|
||
|
||
## 📋 改动总结
|
||
|
||
### 浅色主题
|
||
|
||
| 元素 | 旧值 | 新值 |
|
||
|-----|-----|-----|
|
||
| 容器背景 | `rgba(255,255,255,0.8)` | `rgba(255,255,255,0.72)` |
|
||
| 模糊效果 | `blur(10px)` | `blur(20px) saturate(1.8)` |
|
||
| 容器边框 | `rgba(226,232,240,0.5)` | `rgba(0,0,0,0.08)` |
|
||
| 按钮默认 | 白色背景+边框 | **透明** |
|
||
| 按钮 Hover | `scale(1.05)` | `rgba(0,0,0,0.06)` + `translateY(-1px)` |
|
||
| 激活蓝色 | `#0078d4` | `#0078d4` (不变) |
|
||
| 图标颜色 | `rgb(71,85,105)` | `rgba(0,0,0,0.85)` |
|
||
| 图标尺寸 | `18px` | `22px` |
|
||
|
||
### 深色主题
|
||
|
||
| 元素 | 旧值 | 新值 |
|
||
|-----|-----|-----|
|
||
| 容器背景 | `rgba(30,41,59,0.8)` | `rgba(38,38,40,0.75)` |
|
||
| 模糊效果 | 继承 | `blur(24px) saturate(1.6)` |
|
||
| 按钮默认 | 半透明背景 | **透明** |
|
||
| 按钮 Hover | 深灰背景 | `rgba(255,255,255,0.10)` |
|
||
| 激活蓝色 | 继承浅色 | **`#2899f5`** (更亮) |
|
||
| 图标颜色 | `rgba(226,232,240,1)` | `rgba(255,255,255,0.90)` |
|
||
|
||
### 新增功能
|
||
|
||
- ✅ `:active` (pressed) 状态:`scale(0.96)` 反馈
|
||
- ✅ Hover 图标颜色变化
|
||
- ✅ 优化的 transition 动画(弹性曲线)
|
||
- ✅ 统一的阴影系统
|
||
|
||
---
|
||
|
||
需要我提交代码吗?
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
[Image 1] 你看一下这个图片
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 8.0s)
|
||
|
||
抱歉,我无法查看图片。
|
||
|
||
请用文字描述你看到的问题:
|
||
- 哪个主题(深色/浅色)?
|
||
- 容器、按钮还是二级菜单的问题?
|
||
- 具体是颜色、间距还是其他视觉问题?
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
[Image 1] 你看一下这个图片
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 11.4s)
|
||
|
||
抱歉,我当前无法查看图片(模型不支持图片输入)。
|
||
|
||
请用文字描述问题,例如:
|
||
|
||
1. **当前看到的效果**:容器是什么颜色?按钮是什么样子?
|
||
2. **期望的效果**:你希望改成什么样?
|
||
3. **具体问题**:是太亮/太暗?对比度不够?颜色不协调?
|
||
|
||
或者你可以使用支持图片的 agent:
|
||
|
||
```
|
||
/multimodal-looker 分析这张图片中的 UI 问题
|
||
```
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
[Image 1] 看下这个图片
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · minimax-m2.1-free · 14.1s)
|
||
|
||
抱歉,我无法查看图片。
|
||
|
||
请尝试以下方式之一:
|
||
|
||
1. **复制图片描述给我**:
|
||
- 容器颜色是什么?
|
||
- 按钮颜色是什么?
|
||
- 哪里看起来不对?
|
||
|
||
2. **使用专门的 agent**:
|
||
```bash
|
||
/look_at path/to/image.png "描述图片中的 UI 问题"
|
||
```
|
||
|
||
3. **截图保存后告诉我路径**:
|
||
```
|
||
/look_at /Users/xxx/Desktop/screenshot.png "告诉我图片中的工具栏有什么问题"
|
||
```
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
这个是大概的按钮组的样式。[Pasted ~64 lBottomRightToolbar 底部圆形工具按钮组样式
|
||
容器(胶囊形玻璃拟态)
|
||
/* 定位 */
|
||
position: absolute; bottom: 16px; right: 16px; z-index: 800;
|
||
/* 布局 */
|
||
display: flex; align-items: center; gap: 10px; /* gap-2.5 */
|
||
padding: 8px; /* px-2 py-2 */
|
||
/* 玻璃拟态 */
|
||
background: rgba(255, 255, 255, 0.8); /* bg-white/80 */
|
||
backdrop-filter: blur(24px); /* backdrop-blur-xl */
|
||
border-radius: 9999px; /* rounded-full */
|
||
border: 1px solid rgba(226, 232, 240, 0.5); /* border-slate-200/50 */
|
||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
|
||
/* 暗色模式 */
|
||
dark: background rgba(30, 41, 59, 0.8); /* dark:bg-slate-800/80 */
|
||
dark: border rgba(51, 65, 85, 0.5); /* dark:border-slate-700/50 */
|
||
dark: box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
|
||
---
|
||
圆形按钮
|
||
尺寸:48px × 48px(w-12 h-12),完全圆形(rounded-full)
|
||
| 状态 | 样式 |
|
||
|------|------|
|
||
| 默认 | 白色背景 + 浅灰边框 + 双层阴影 |
|
||
| 悬停 | 放大 105% + 阴影加深 |
|
||
| 激活 | 蓝色背景 + 蓝色发光阴影 |
|
||
| 禁用 | opacity: 0.3 + cursor: not-allowed |
|
||
默认状态
|
||
width: 48px; height: 48px;
|
||
border-radius: 9999px;
|
||
background: rgba(255, 255, 255, 0.9); /* bg-white/90 */
|
||
border: 1px solid rgba(203, 213, 225, 0.6); /* border-slate-300/60 */
|
||
color: rgb(51, 65, 85); /* text-slate-700 */
|
||
/* 双层阴影 - 立体感 */
|
||
box-shadow:
|
||
0 2px 8px rgba(0, 0, 0, 0.1),
|
||
0 4px 12px rgba(0, 0, 0, 0.08);
|
||
/* 暗色模式 */
|
||
dark: background rgba(51, 65, 85, 0.5); /* dark:bg-slate-700/50 */
|
||
dark: border rgba(71, 85, 105, 0.4); /* dark:border-slate-600/40 */
|
||
dark: color rgb(226, 232, 240); /* dark:text-slate-200 */
|
||
dark: box-shadow:
|
||
0 2px 8px rgba(0, 0, 0, 0.25),
|
||
0 4px 12px rgba(0, 0, 0, 0.2);
|
||
悬停状态
|
||
transform: scale(1.05); /* hover:scale-[1.05] */
|
||
background: rgb(255, 255, 255); /* hover:bg-white */
|
||
box-shadow:
|
||
0 4px 12px rgba(0, 0, 0, 0.12),
|
||
0 6px 20px rgba(0, 0, 0, 0.1);
|
||
transition: all 200ms ease-out;
|
||
/* 暗色模式 */
|
||
dark: background rgba(71, 85, 105, 0.6); /* dark:hover:bg-slate-600/60 */
|
||
激活状态(模式开启时)
|
||
background: rgb(37, 99, 235); /* bg-blue-600 */
|
||
border: 1px solid rgb(29, 78, 216); /* border-blue-700 */
|
||
color: white;
|
||
/* 蓝色发光阴影 */
|
||
box-shadow:
|
||
0 2px 8px rgba(59, 130, 246, 0.3),
|
||
0 4px 16px rgba(59, 130, 246, 0.2);
|
||
---
|
||
图标
|
||
width: 18px; height: 18px;
|
||
stroke-width: 2;
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · minimax-m2.1-free · 7.8s)
|
||
|
||
---
|
||
|
||
## User
|
||
|
||
BottomRightToolbar 底部圆形工具按钮组样式
|
||
容器(胶囊形玻璃拟态)
|
||
/* 定位 */
|
||
position: absolute; bottom: 16px; right: 16px; z-index: 800;
|
||
/* 布局 */
|
||
display: flex; align-items: center; gap: 10px; /* gap-2.5 */
|
||
padding: 8px; /* px-2 py-2 */
|
||
/* 玻璃拟态 */
|
||
background: rgba(255, 255, 255, 0.8); /* bg-white/80 */
|
||
backdrop-filter: blur(24px); /* backdrop-blur-xl */
|
||
border-radius: 9999px; /* rounded-full */
|
||
border: 1px solid rgba(226, 232, 240, 0.5); /* border-slate-200/50 */
|
||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
|
||
/* 暗色模式 */
|
||
dark: background rgba(30, 41, 59, 0.8); /* dark:bg-slate-800/80 */
|
||
dark: border rgba(51, 65, 85, 0.5); /* dark:border-slate-700/50 */
|
||
dark: box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
|
||
---
|
||
圆形按钮
|
||
尺寸:48px × 48px(w-12 h-12),完全圆形(rounded-full)
|
||
| 状态 | 样式 |
|
||
|------|------|
|
||
| 默认 | 白色背景 + 浅灰边框 + 双层阴影 |
|
||
| 悬停 | 放大 105% + 阴影加深 |
|
||
| 激活 | 蓝色背景 + 蓝色发光阴影 |
|
||
| 禁用 | opacity: 0.3 + cursor: not-allowed |
|
||
默认状态
|
||
width: 48px; height: 48px;
|
||
border-radius: 9999px;
|
||
background: rgba(255, 255, 255, 0.9); /* bg-white/90 */
|
||
border: 1px solid rgba(203, 213, 225, 0.6); /* border-slate-300/60 */
|
||
color: rgb(51, 65, 85); /* text-slate-700 */
|
||
/* 双层阴影 - 立体感 */
|
||
box-shadow:
|
||
0 2px 8px rgba(0, 0, 0, 0.1),
|
||
0 4px 12px rgba(0, 0, 0, 0.08);
|
||
/* 暗色模式 */
|
||
dark: background rgba(51, 65, 85, 0.5); /* dark:bg-slate-700/50 */
|
||
dark: border rgba(71, 85, 105, 0.4); /* dark:border-slate-600/40 */
|
||
dark: color rgb(226, 232, 240); /* dark:text-slate-200 */
|
||
dark: box-shadow:
|
||
0 2px 8px rgba(0, 0, 0, 0.25),
|
||
0 4px 12px rgba(0, 0, 0, 0.2);
|
||
悬停状态
|
||
transform: scale(1.05); /* hover:scale-[1.05] */
|
||
background: rgb(255, 255, 255); /* hover:bg-white */
|
||
box-shadow:
|
||
0 4px 12px rgba(0, 0, 0, 0.12),
|
||
0 6px 20px rgba(0, 0, 0, 0.1);
|
||
transition: all 200ms ease-out;
|
||
/* 暗色模式 */
|
||
dark: background rgba(71, 85, 105, 0.6); /* dark:hover:bg-slate-600/60 */
|
||
激活状态(模式开启时)
|
||
background: rgb(37, 99, 235); /* bg-blue-600 */
|
||
border: 1px solid rgb(29, 78, 216); /* border-blue-700 */
|
||
color: white;
|
||
/* 蓝色发光阴影 */
|
||
box-shadow:
|
||
0 2px 8px rgba(59, 130, 246, 0.3),
|
||
0 4px 16px rgba(59, 130, 246, 0.2);
|
||
---
|
||
图标
|
||
width: 18px; height: 18px;
|
||
stroke-width: 2; 这个是大概的按钮组的样式,按钮组分为背景容器以及圆形按钮。
|
||
|
||
---
|
||
|
||
## Assistant (Sisyphus · claude-opus-4-5-20251101 · 3.7s)
|
||
|
||
---
|
||
|