初始化
33
.idea/workspace.xml
generated
@@ -4,19 +4,22 @@
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="728b1ce9-7308-4507-bebd-62399c54bf21" name="更改" comment="添加折叠面板">
|
||||
<list default="true" id="728b1ce9-7308-4507-bebd-62399c54bf21" name="更改" comment="增加测量窗口">
|
||||
<change beforePath="$PROJECT_DIR$/dist/bim-engine-sdk.es.js" beforeDir="false" afterPath="$PROJECT_DIR$/dist/bim-engine-sdk.es.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/dist/bim-engine-sdk.es.js.map" beforeDir="false" afterPath="$PROJECT_DIR$/dist/bim-engine-sdk.es.js.map" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/dist/bim-engine-sdk.umd.js" beforeDir="false" afterPath="$PROJECT_DIR$/dist/bim-engine-sdk.umd.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/dist/bim-engine-sdk.umd.js.map" beforeDir="false" afterPath="$PROJECT_DIR$/dist/bim-engine-sdk.umd.js.map" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/dist/index.d.ts" beforeDir="false" afterPath="$PROJECT_DIR$/dist/index.d.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/collapse/index.css" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/collapse/index.css" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/tab/index.css" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/tab/index.css" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/index.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/docs/components/button-group.md" beforeDir="false" afterPath="$PROJECT_DIR$/docs/components/button-group.md" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/docs/components/engine.md" beforeDir="false" afterPath="$PROJECT_DIR$/docs/components/engine.md" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/docs/components/measure-panel.md" beforeDir="false" afterPath="$PROJECT_DIR$/docs/components/measure-panel.md" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/measure-panel/index.css" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/measure-panel/index.css" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/measure-panel/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/measure-panel/index.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/measure-panel/types.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/measure-panel/types.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/locales/en-US.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/locales/en-US.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/locales/types.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/locales/types.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/locales/zh-CN.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/locales/zh-CN.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/managers/property-panel-manager.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/managers/property-panel-manager.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/managers/measure-dialog-manager.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/managers/measure-dialog-manager.ts" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -44,7 +47,7 @@
|
||||
"git-widget-placeholder": "main",
|
||||
"go.import.settings.migrated": "true",
|
||||
"kotlin-language-version-configured": "true",
|
||||
"last_opened_file_path": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/managers",
|
||||
"last_opened_file_path": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/components/button-group/toolbar/buttons/rule",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
@@ -57,6 +60,7 @@
|
||||
}</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$/src/components/button-group/toolbar/buttons/rule" />
|
||||
<recent name="$PROJECT_DIR$/src/managers" />
|
||||
</key>
|
||||
</component>
|
||||
@@ -90,7 +94,9 @@
|
||||
<workItem from="1765943119364" duration="2464000" />
|
||||
<workItem from="1766108012524" duration="1315000" />
|
||||
<workItem from="1766371049964" duration="671000" />
|
||||
<workItem from="1766385054791" duration="7532000" />
|
||||
<workItem from="1766385054791" duration="15099000" />
|
||||
<workItem from="1766453884590" duration="6000" />
|
||||
<workItem from="1766454569112" duration="3202000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="添加测试信息">
|
||||
<option name="closed" value="true" />
|
||||
@@ -140,7 +146,15 @@
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1766389199604</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="7" />
|
||||
<task id="LOCAL-00007" summary="增加测量窗口">
|
||||
<option name="closed" value="true" />
|
||||
<created>1766400520027</created>
|
||||
<option name="number" value="00007" />
|
||||
<option name="presentableId" value="LOCAL-00007" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1766400520027</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="8" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
@@ -150,7 +164,8 @@
|
||||
<MESSAGE value="添加测试信息" />
|
||||
<MESSAGE value="修改" />
|
||||
<MESSAGE value="添加折叠面板" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="添加折叠面板" />
|
||||
<MESSAGE value="增加测量窗口" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="增加测量窗口" />
|
||||
</component>
|
||||
<component name="VgoProject">
|
||||
<settings-migrated>true</settings-migrated>
|
||||
|
||||
@@ -303,6 +303,7 @@ interface IBimComponent {
|
||||
- **`toolbar/index.ts`**: `Toolbar` 类 - 底部工具栏(继承 `BimButtonGroup`)
|
||||
- **`toolbar/buttons/`**: 工具栏按钮配置
|
||||
- `home/`: 首页按钮
|
||||
- `zoom-box/`: 选框放大按钮(占位,事件暂未实现)
|
||||
- `info/`: 信息按钮
|
||||
- `location/`: 定位按钮
|
||||
- `setting/`: 设置按钮
|
||||
@@ -513,7 +514,7 @@ const dialog = engine.dialog.create({
|
||||
### 4.1 Manager 类清单
|
||||
|
||||
| 类名 | 文件路径 | 功能 | 继承关系 |
|
||||
|------|---------|------|---------|
|
||||
| ---------------------- | ---------------------------------------- | ---------------------------------- | -------------- |
|
||||
| `DialogManager` | `src/managers/dialog-manager.ts` | 管理弹窗实例 | `BimComponent` |
|
||||
| `ToolbarManager` | `src/managers/toolbar-manager.ts` | 管理底部工具栏 | `BimComponent` |
|
||||
| `ButtonGroupManager` | `src/managers/button-group-manager.ts` | 管理通用按钮组 | `BimComponent` |
|
||||
@@ -526,7 +527,7 @@ const dialog = engine.dialog.create({
|
||||
### 4.2 组件类清单
|
||||
|
||||
| 类名 | 文件路径 | 功能 | 实现接口 |
|
||||
|------|---------|------|---------|
|
||||
| ---------------- | ---------------------------------------------- | ------------------------ | --------------------- |
|
||||
| `BimDialog` | `src/components/dialog/index.ts` | 通用弹窗组件 | `IBimComponent` |
|
||||
| `BimInfoDialog` | `src/components/dialog/bimInfoDialog/index.ts` | 信息弹窗组件 | 继承 `BimDialog` |
|
||||
| `BimButtonGroup` | `src/components/button-group/index.ts` | 通用按钮组组件 | `IBimComponent` |
|
||||
@@ -543,7 +544,7 @@ const dialog = engine.dialog.create({
|
||||
### 4.3 服务类清单
|
||||
|
||||
| 类名 | 文件路径 | 功能 | 模式 |
|
||||
|------|---------|------|------|
|
||||
| --------------- | ------------------------ | -------- | ---- |
|
||||
| `ThemeManager` | `src/services/theme.ts` | 主题管理 | 单例 |
|
||||
| `LocaleManager` | `src/services/locale.ts` | 语言管理 | 单例 |
|
||||
|
||||
@@ -645,7 +646,7 @@ this.on('ui:open-dialog', (payload) => {
|
||||
### 4.5 核心基类
|
||||
|
||||
| 类名 | 文件路径 | 功能 | 继承/实现 |
|
||||
|------|---------|------|----------|
|
||||
| -------------- | --------------------------- | -------------- | ------------------- |
|
||||
| `EventEmitter` | `src/core/event-emitter.ts` | 事件总线基础类 | - |
|
||||
| `BimComponent` | `src/core/component.ts` | 组件基类 | 抽象类 |
|
||||
| `BimEngine` | `src/bim-engine.ts` | 主引擎类 | 继承 `EventEmitter` |
|
||||
@@ -1335,7 +1336,7 @@ function example(param1: string, param2: number): boolean {
|
||||
## 📝 文档维护记录
|
||||
|
||||
| 日期 | 修改内容 | 修改人 |
|
||||
|------|---------|--------|
|
||||
| ---------- | -------- | ------------ |
|
||||
| 2024-XX-XX | 初始创建 | AI Assistant |
|
||||
|
||||
---
|
||||
|
||||
10741
dist/bim-engine-sdk.es.js
vendored
2
dist/bim-engine-sdk.es.js.map
vendored
402
dist/bim-engine-sdk.umd.js
vendored
2
dist/bim-engine-sdk.umd.js.map
vendored
165
dist/index.d.ts
vendored
@@ -27,6 +27,10 @@ declare class BimButtonGroup implements IBimComponent {
|
||||
* 应用样式到容器
|
||||
*/
|
||||
private applyStyles;
|
||||
/**
|
||||
* 设置主题的primary颜色(用于边框等)
|
||||
*/
|
||||
private setPrimaryColor;
|
||||
/**
|
||||
* 设置主题颜色
|
||||
* 只会应用到没有被用户自定义的颜色属性上
|
||||
@@ -52,6 +56,11 @@ declare class BimButtonGroup implements IBimComponent {
|
||||
*/
|
||||
setBtnActive(id: string, active?: boolean): void;
|
||||
private handleClick;
|
||||
/**
|
||||
* 互斥关闭同范围内的其它已激活按钮,并触发它们的 onClick
|
||||
* @param button 当前被激活的按钮
|
||||
*/
|
||||
private deactivateExclusiveSiblings;
|
||||
private handleMouseEnter;
|
||||
private handleMouseLeave;
|
||||
private showDropdown;
|
||||
@@ -179,6 +188,9 @@ export declare class BimEngine extends EventEmitter {
|
||||
rightKey: RightKeyManager | null;
|
||||
propertyPanel: PropertyPanelManager | null;
|
||||
measure: MeasureDialogManager | null;
|
||||
sectionPlane: SectionPlaneDialogManager | null;
|
||||
sectionAxis: SectionAxisDialogManager | null;
|
||||
sectionBox: SectionBoxDialogManager | null;
|
||||
constructor(container: HTMLElement | string, options?: {
|
||||
locale?: LocaleType;
|
||||
theme?: ThemeType;
|
||||
@@ -269,6 +281,18 @@ export declare interface ButtonConfig {
|
||||
label: string;
|
||||
icon?: string;
|
||||
keepActive?: boolean;
|
||||
/**
|
||||
* 是否互斥(开关互斥)
|
||||
*
|
||||
* 行为说明:
|
||||
* - 当按钮从“未激活”切换到“激活”时,如果该按钮开启了 exclusive,
|
||||
* 会自动关闭同互斥范围内的其它已激活按钮,并触发它们的 onClick(用于执行关闭逻辑)。
|
||||
* - 一级按钮:互斥范围 = 同 groupId 下的一级按钮
|
||||
* - 二级按钮:互斥范围 = 同 groupId 且同 parentId 下的二级按钮
|
||||
*
|
||||
* 注意:该能力通常与 keepActive 搭配使用。
|
||||
*/
|
||||
exclusive?: boolean;
|
||||
isActive?: boolean;
|
||||
disabled?: boolean;
|
||||
onClick?: (button: OptButton) => void;
|
||||
@@ -717,10 +741,11 @@ declare class MeasureDialogManager extends BimComponent {
|
||||
*/
|
||||
switchMode(mode: MeasureMode): void;
|
||||
/**
|
||||
* 设置测量结果(由外部注入,仅用于显示)
|
||||
* 设置测量结果(推荐使用的新方法名)
|
||||
* 说明:内部直接调用 MeasurePanel.setResult()
|
||||
* @param result 测量结果;传 null 表示清空
|
||||
*/
|
||||
setResult(result: MeasureResult | null): void;
|
||||
setMeasureResult(result: MeasureResult | null): void;
|
||||
/**
|
||||
* 获取测量配置(单位/精度)
|
||||
* - 如果面板存在:返回面板当前配置
|
||||
@@ -914,6 +939,142 @@ declare class RightKeyManager extends BimComponent {
|
||||
private handleContextMenu;
|
||||
}
|
||||
|
||||
/**
|
||||
* 轴向类型
|
||||
*/
|
||||
declare type SectionAxis = 'x' | 'y' | 'z';
|
||||
|
||||
/**
|
||||
* 轴向剖切弹窗管理器
|
||||
*/
|
||||
declare class SectionAxisDialogManager extends BimComponent {
|
||||
private dialogId;
|
||||
private dialog;
|
||||
private panel;
|
||||
constructor(engine: BimEngine);
|
||||
init(): void;
|
||||
/**
|
||||
* 显示弹窗
|
||||
*/
|
||||
show(): void;
|
||||
/**
|
||||
* 隐藏弹窗
|
||||
*/
|
||||
hide(): void;
|
||||
/**
|
||||
* 获取隐藏状态
|
||||
*/
|
||||
getHiddenState(): boolean;
|
||||
/**
|
||||
* 设置隐藏状态
|
||||
*/
|
||||
setHiddenState(isHidden: boolean): void;
|
||||
/**
|
||||
* 获取当前激活的轴向
|
||||
*/
|
||||
getActiveAxis(): SectionAxis;
|
||||
/**
|
||||
* 设置激活的轴向
|
||||
*/
|
||||
setActiveAxis(axis: SectionAxis): void;
|
||||
/**
|
||||
* 销毁弹窗和面板
|
||||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 剖切盒轴向范围
|
||||
*/
|
||||
declare interface SectionBoxAxisRange {
|
||||
/** 最小值(0-100的百分比) */
|
||||
min: number;
|
||||
/** 最大值(0-100的百分比) */
|
||||
max: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 剖切盒弹窗管理器
|
||||
*/
|
||||
declare class SectionBoxDialogManager extends BimComponent {
|
||||
private dialogId;
|
||||
private dialog;
|
||||
private panel;
|
||||
constructor(engine: BimEngine);
|
||||
init(): void;
|
||||
/**
|
||||
* 显示弹窗
|
||||
*/
|
||||
show(): void;
|
||||
/**
|
||||
* 隐藏弹窗
|
||||
*/
|
||||
hide(): void;
|
||||
/**
|
||||
* 获取隐藏状态
|
||||
*/
|
||||
getHiddenState(): boolean;
|
||||
/**
|
||||
* 设置隐藏状态
|
||||
*/
|
||||
setHiddenState(isHidden: boolean): void;
|
||||
/**
|
||||
* 获取反向状态
|
||||
*/
|
||||
getReversedState(): boolean;
|
||||
/**
|
||||
* 设置反向状态
|
||||
*/
|
||||
setReversedState(isReversed: boolean): void;
|
||||
/**
|
||||
* 获取范围值
|
||||
*/
|
||||
getRange(): SectionBoxRange | null;
|
||||
/**
|
||||
* 设置范围值
|
||||
*/
|
||||
setRange(range: Partial<SectionBoxRange>): void;
|
||||
/**
|
||||
* 销毁弹窗和面板
|
||||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 剖切盒范围数据
|
||||
*/
|
||||
declare interface SectionBoxRange {
|
||||
/** X轴范围 */
|
||||
x: SectionBoxAxisRange;
|
||||
/** Y轴范围 */
|
||||
y: SectionBoxAxisRange;
|
||||
/** Z轴范围 */
|
||||
z: SectionBoxAxisRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拾取面剖切弹窗管理器
|
||||
*/
|
||||
declare class SectionPlaneDialogManager extends BimComponent {
|
||||
private dialogId;
|
||||
private dialog;
|
||||
private panel;
|
||||
constructor(engine: BimEngine);
|
||||
init(): void;
|
||||
/**
|
||||
* 显示拾取面剖切弹窗
|
||||
*/
|
||||
show(): void;
|
||||
/**
|
||||
* 隐藏弹窗
|
||||
*/
|
||||
hide(): void;
|
||||
/**
|
||||
* 销毁弹窗
|
||||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局主题配置接口
|
||||
* 定义系统通用的语义化颜色
|
||||
|
||||
@@ -1194,3 +1194,4 @@ type ExpandDirection = 'up' | 'down' | 'left' | 'right';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -612,3 +612,4 @@ interface ModelLoadOptions {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
13
src/assets/icons/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
## 图标资源目录说明
|
||||
|
||||
该目录用于存放项目内使用的 **SVG 图标资源**(原始文件)。
|
||||
|
||||
### 命名建议
|
||||
- 使用小写短横线:例如 `zoom-box.svg`、`measure-distance.svg`
|
||||
- 尽量语义化,避免 `icon1.svg` 这类不可读命名
|
||||
|
||||
### 使用建议
|
||||
- **优先保留原始 SVG**,便于后续替换/优化
|
||||
- 组件里如果需要内联 SVG,可将文件内容复制到 `icon` 字符串中,或后续扩展为统一的图标加载器
|
||||
|
||||
|
||||
20
src/assets/icons/体积.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
clip-path: url(#clip-体积);
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
<clipPath id="clip-体积">
|
||||
<rect width="32" height="32"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g id="体积" class="cls-1">
|
||||
<rect class="cls-2" width="32" height="32"/>
|
||||
<path id="路径_10" data-name="路径 10" d="M94.74,86.658V71.189a.371.371,0,0,1,.2-.329l13.869-7.22a.371.371,0,0,1,.344,0l13.053,6.891h0l.819.431a.371.371,0,0,1,.2.328v15.3a.371.371,0,0,1-.2.328l-13.872,7.255a.371.371,0,0,1-.342,0L94.94,86.987a.371.371,0,0,1-.2-.329Zm2.119-.837,11.2,5.8a.024.024,0,0,0,.035-.022V79.483a.371.371,0,0,0-.2-.328l-11.2-5.909a.024.024,0,0,0-.035.021V85.492A.371.371,0,0,0,96.859,85.821Zm13.151-6.459v12a.12.12,0,0,0,.176.106L114,89.474l3.334-1.745,3.771-1.978a.371.371,0,0,0,.2-.328V73.5a.193.193,0,0,0-.284-.171l-10.812,5.708A.371.371,0,0,0,110.01,79.362ZM97.925,71.725l10.839,5.72a.371.371,0,0,0,.346,0L119.8,71.808a.214.214,0,0,0,0-.378l-10.649-5.621a.371.371,0,0,0-.344,0L97.925,71.47A.144.144,0,0,0,97.925,71.725Z" transform="translate(-92.982 -62.907)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
20
src/assets/icons/坡度.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
clip-path: url(#clip-坡度);
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
<clipPath id="clip-坡度">
|
||||
<rect width="32" height="32"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g id="坡度" class="cls-1">
|
||||
<rect class="cls-2" width="32" height="32"/>
|
||||
<path id="路径_15" data-name="路径 15" d="M202.1,188.337l2.629-2.191-8.447-3.106,1.533,8.871,2.629-2.194,9.341,11.209,1.656-1.379Zm-13.726-.435a1.075,1.075,0,0,0-1.07-.341,1.057,1.057,0,0,0-.5.277l-5.11,4.08a1.08,1.08,0,0,0-.406.84l-.007,17.386a1.079,1.079,0,0,0,1.077,1.077L205.7,211.2a1.078,1.078,0,0,0,.822-1.774Zm-4.934,21.164.007-15.788,3.968-3.171,15.974,18.941Z" transform="translate(-180.36 -181.131)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 876 B |
26
src/assets/icons/最小距离.svg
Normal file
@@ -0,0 +1,26 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
stroke: rgba(0,0,0,0);
|
||||
stroke-miterlimit: 10;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
clip-path: url(#clip-最小距离);
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
<clipPath id="clip-最小距离">
|
||||
<rect width="32" height="32"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g id="最小距离" class="cls-2">
|
||||
<rect class="cls-3" width="32" height="32"/>
|
||||
<path id="减去_1" data-name="减去 1" class="cls-1" d="M-5.839,24.8H-34.16A1.875,1.875,0,0,1-36,22.933V16.52a1.887,1.887,0,0,1,1.9-1.871H-5.9A1.887,1.887,0,0,1-4,16.52v6.412A1.875,1.875,0,0,1-5.839,24.8ZM-34.1,16.252a.27.27,0,0,0-.272.268v6.412a.27.27,0,0,0,.272.267H-5.9a.269.269,0,0,0,.271-.267V16.52a.27.27,0,0,0-.271-.268H-8.61V21.33H-9.695V16.252h-1.085v2.939h-1.085V16.252h-1.085v2.939h-1.085V16.252h-1.084v2.939H-16.2V16.252h-1.085v2.939h-1.085V16.252h-1.084V21.33h-1.084V16.252h-1.085v2.939h-1.085V16.252H-23.8v2.939h-1.085V16.252h-1.085v2.939h-1.085V16.252h-1.084v2.939H-29.22V16.252H-30.3V21.33H-31.39V16.252Z" transform="translate(36 2)"/>
|
||||
<path id="交叉_1" data-name="交叉 1" d="M23.716,7.947V4.875c0-.8-.232-1.085-.765-1.085a1.573,1.573,0,0,0-1.133.585V7.947H20.4V2.75h1.163l.1.687H21.7a2.547,2.547,0,0,1,1.763-.817c1.172,0,1.676.78,1.676,2.089V7.947Zm-7.26,0V2.62h1.58V7.947Zm-3.8,0V4.875c0-.8-.243-1.085-.76-1.085a1.606,1.606,0,0,0-1.049.585V7.947H9.421V4.875c0-.8-.243-1.085-.758-1.085a1.608,1.608,0,0,0-1.05.585V7.947H6.194V2.75H7.36l.1.7H7.5A2.326,2.326,0,0,1,9.169,2.62a1.486,1.486,0,0,1,1.5.91A2.445,2.445,0,0,1,12.4,2.62c1.156,0,1.691.78,1.691,2.089V7.947Zm3.8-6.849a.79.79,0,0,1,1.58,0,.79.79,0,0,1-1.58,0Z" transform="translate(0.333 3.053)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
20
src/assets/icons/标高.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
clip-path: url(#clip-标高);
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
<clipPath id="clip-标高">
|
||||
<rect width="32" height="32"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g id="标高" class="cls-1">
|
||||
<rect class="cls-2" width="32" height="32"/>
|
||||
<path id="路径_8" data-name="路径 8" d="M84.131,193.119a1.056,1.056,0,0,1,1.116.982v7.857a1.056,1.056,0,0,1-1.116.982H54.367a1.056,1.056,0,0,1-1.116-.982V194.1a1.056,1.056,0,0,1,1.116-.982Zm-1.116,1.964H55.483v5.893H83.015Zm1.116-13.749a1.064,1.064,0,0,1,1.114.935,1.032,1.032,0,0,1-1.007,1.025l-.107,0H71.2l-7.858,6.914a1.227,1.227,0,0,1-1.578,0l-8.185-7.2-.018-.016-.032-.031.049.047a1.107,1.107,0,0,1-.092-.092l-.011-.014a.869.869,0,0,1-.182-.857l0-.008L53.31,182l.012-.029.02-.045.019-.035a1.1,1.1,0,0,1,.891-.552h.007q.053,0,.107,0ZM68.043,183.3H57.06l5.492,4.831Z" transform="translate(-53.247 -176.136)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
23
src/assets/icons/激光边距.svg
Normal file
@@ -0,0 +1,23 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
clip-path: url(#clip-激光边距);
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
<clipPath id="clip-激光边距">
|
||||
<rect width="32" height="32"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g id="激光边距" class="cls-1">
|
||||
<rect class="cls-2" width="32" height="32"/>
|
||||
<g id="组_4" data-name="组 4" transform="translate(0 -1.293)">
|
||||
<path id="路径_13" data-name="路径 13" d="M0,1.293v31.96H32V1.293ZM30.97,32.182H1.03V2.323H30.97Z"/>
|
||||
<path id="路径_14" data-name="路径 14" d="M160.026,291.9l1.6,1.6,7.305-7.305-7.305-7.305-1.6,1.6,4.794,4.566h-6.392v2.283h6.392Zm-5.251,0-4.566-4.566h6.164v-2.283H150.21l4.566-4.566-1.37-1.6L146.1,286.19l7.305,7.305Z" transform="translate(-141.535 -268.917)"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 934 B |
23
src/assets/icons/空间体积.svg
Normal file
@@ -0,0 +1,23 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
clip-path: url(#clip-空间体积);
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
<clipPath id="clip-空间体积">
|
||||
<rect width="32" height="32"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g id="空间体积" class="cls-1">
|
||||
<rect class="cls-2" width="32" height="32"/>
|
||||
<g id="组_5" data-name="组 5" transform="translate(-106.35 -97.661)">
|
||||
<path id="路径_16" data-name="路径 16" d="M125.977,128.829l13.076-7.363v-13.6l-13.076,6.8Zm-3.126-15.655a.565.565,0,0,1-.258-.064L109.3,106.323a.567.567,0,0,1-.011-1L122.578,98a.567.567,0,0,1,.55,0l13.288,7.325a.567.567,0,0,1-.011,1l-13.292,6.79A.63.63,0,0,1,122.851,113.174ZM110.773,105.8l12.078,6.172,12.078-6.172-12.078-6.657Z" transform="translate(-1.922)"/>
|
||||
<path id="路径_17" data-name="路径 17" d="M120.649,322.52a.58.58,0,0,1-.262-.064l-13.08-6.8a.573.573,0,0,1-.307-.5V301a.566.566,0,0,1,.273-.486.573.573,0,0,1,.558-.019l13.076,6.8a.573.573,0,0,1,.307.5v14.161a.57.57,0,0,1-.565.569Zm-12.511-7.708,11.942,6.206V308.136l-11.942-6.206Zm15.917,9.408a.585.585,0,0,1-.288-.076.567.567,0,0,1-.281-.489V309.49a.562.562,0,0,1,.307-.5l13.076-6.8a.573.573,0,0,1,.558.019.562.562,0,0,1,.273.486v13.6a.568.568,0,0,1-.288.493l-13.076,7.359A.557.557,0,0,1,124.055,324.22Zm.569-14.385V322.68l11.942-6.722V303.629Z" transform="translate(0 -194.822)"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
20
src/assets/icons/角度.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
clip-path: url(#clip-角度);
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
<clipPath id="clip-角度">
|
||||
<rect width="32" height="32"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g id="角度" class="cls-1">
|
||||
<rect class="cls-2" width="32" height="32"/>
|
||||
<path id="路径_7" data-name="路径 7" d="M39.587,50.766h13.7a1,1,0,0,1,0,2H23.171a1,1,0,0,1,0-2h1.418l6.582-7.006v-.006a.517.517,0,0,1,.14-.357.456.456,0,0,1,.337-.144l12.1-12.876a.451.451,0,0,1,.665,0,.524.524,0,0,1,0,.708L32.883,43.355a8.3,8.3,0,0,1,6.7,7.411Zm-.949,0a7.254,7.254,0,0,0-6.611-6.5l-6.108,6.5Z" transform="translate(-22.229 -26.489)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 816 B |
35
src/assets/icons/距离.svg
Normal file
@@ -0,0 +1,35 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
clip-path: url(#clip-距离);
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
clip-path: url(#clip-path);
|
||||
}
|
||||
|
||||
.cls-4 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
<clipPath id="clip-path">
|
||||
<rect id="矩形_1" data-name="矩形 1" class="cls-1" width="32" height="23.606" transform="translate(0 0)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip-距离">
|
||||
<rect width="32" height="32"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g id="距离" class="cls-2">
|
||||
<rect class="cls-4" width="32" height="32"/>
|
||||
<g id="组_2" data-name="组 2" transform="translate(0 4.197)">
|
||||
<g id="组_1" data-name="组 1" class="cls-3">
|
||||
<path id="路径_1" data-name="路径 1" d="M29.692,3.03,27.55.919a.529.529,0,0,1-.014-.756A.549.549,0,0,1,28.3.15l.014.013,3.067,3.023a.529.529,0,0,1,0,.756L28.317,6.966a.549.549,0,0,1-.767.013.529.529,0,0,1-.014-.756l.014-.013L29.692,4.1H2.31L4.452,6.21a.528.528,0,0,1,.013.756.547.547,0,0,1-.766.013l-.014-.013L.616,3.942a.531.531,0,0,1,0-.756L3.685.163a.548.548,0,0,1,.767.014.528.528,0,0,1,0,.742L2.31,3.03ZM24.136,15.055H23.051V18H21.966v-2.94H20.882V18H19.8v-2.94H18.712V18H17.627v-2.94H16.543v5.078H15.458V15.055H14.373V18H13.288v-2.94H12.2V18H11.119v-2.94H10.034V18H8.949v-2.94H7.865V18H6.78v-2.94H5.7v5.078H4.61V15.055H1.9a.27.27,0,0,0-.272.268v6.413A.269.269,0,0,0,1.9,22H30.1a.268.268,0,0,0,.271-.267V15.323a.269.269,0,0,0-.271-.268H27.39v5.078H26.305V15.055H25.221V18H24.136Zm5.966-1.6A1.884,1.884,0,0,1,32,15.323v6.413a1.885,1.885,0,0,1-1.9,1.871H1.9A1.885,1.885,0,0,1,0,21.736V15.323a1.885,1.885,0,0,1,1.9-1.871Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -7,6 +7,9 @@ import {RightKeyManager} from './managers/right-key-manager';
|
||||
import {ConstructTreeManagerBtn} from './managers/construct-tree-manager-btn';
|
||||
import {PropertyPanelManager} from './managers/property-panel-manager';
|
||||
import {MeasureDialogManager} from './managers/measure-dialog-manager';
|
||||
import {SectionPlaneDialogManager} from './managers/section-plane-dialog-manager';
|
||||
import {SectionAxisDialogManager} from './managers/section-axis-dialog-manager';
|
||||
import {SectionBoxDialogManager} from './managers/section-box-dialog-manager';
|
||||
import type {EngineOptions, ModelLoadOptions} from './components/engine';
|
||||
import {localeManager} from './services/locale';
|
||||
import {themeManager} from './services/theme';
|
||||
@@ -29,6 +32,9 @@ export class BimEngine extends EventEmitter {
|
||||
public rightKey: RightKeyManager | null = null; // 右键菜单管理器
|
||||
public propertyPanel: PropertyPanelManager | null = null; // 属性面板 (演示 Collapse)
|
||||
public measure: MeasureDialogManager | null = null; // 测量面板
|
||||
public sectionPlane: SectionPlaneDialogManager | null = null; // 拾取面剖切面板
|
||||
public sectionAxis: SectionAxisDialogManager | null = null; // 轴向剖切面板
|
||||
public sectionBox: SectionBoxDialogManager | null = null; // 剖切盒面板
|
||||
|
||||
|
||||
constructor(
|
||||
@@ -95,6 +101,9 @@ export class BimEngine extends EventEmitter {
|
||||
this.constructTreeBtn = new ConstructTreeManagerBtn(this, this.wrapper);
|
||||
this.propertyPanel = new PropertyPanelManager(this);
|
||||
this.measure = new MeasureDialogManager(this);
|
||||
this.sectionPlane = new SectionPlaneDialogManager(this);
|
||||
this.sectionAxis = new SectionAxisDialogManager(this);
|
||||
this.sectionBox = new SectionBoxDialogManager(this);
|
||||
|
||||
// 初始主题
|
||||
this.updateTheme(themeManager.getTheme());
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
transition: background-color 0.2s, color 0.2s, border-color 0.2s;
|
||||
color: var(--bim-btn-text-color, #ccc);
|
||||
background-color: var(--bim-btn-bg, transparent);
|
||||
padding: 6px;
|
||||
@@ -64,6 +64,8 @@
|
||||
position: relative;
|
||||
/* 为绝对定位提供锚点 */
|
||||
justify-content: center;
|
||||
border: 1px solid transparent;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.opt-btn:hover {
|
||||
@@ -73,11 +75,10 @@
|
||||
.opt-btn.active {
|
||||
background-color: var(--bim-btn-active-bg, rgba(255, 255, 255, 0.15));
|
||||
color: var(--bim-btn-text-active-color, #fff);
|
||||
/* 开关按钮激活时不显示边框,只改变背景 */
|
||||
}
|
||||
|
||||
.opt-btn.active .opt-btn-icon {
|
||||
color: var(--bim-icon-active-color, #fff);
|
||||
}
|
||||
/* 开关按钮激活时图标颜色保持不变,继承默认颜色 */
|
||||
|
||||
.opt-btn.disabled {
|
||||
opacity: 0.5;
|
||||
@@ -246,15 +247,30 @@
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
color: var(--bim-btn-text-color, #ccc);
|
||||
transition: background 0.2s;
|
||||
transition: background 0.2s, border-color 0.2s, color 0.2s;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid transparent;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* 二级菜单项的图标默认颜色 */
|
||||
.opt-btn-dropdown-item .opt-btn-icon {
|
||||
color: var(--bim-icon-color, #ccc);
|
||||
}
|
||||
|
||||
.opt-btn-dropdown-item:hover {
|
||||
background-color: var(--bim-btn-hover-bg, #444);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 二级菜单项激活态(支持 keepActive 的可视化) */
|
||||
.opt-btn-dropdown-item.active {
|
||||
background-color: var(--bim-btn-active-bg, rgba(255, 255, 255, 0.15));
|
||||
color: var(--bim-btn-text-active-color, #fff);
|
||||
/* 开关按钮激活时不显示边框,只改变背景 */
|
||||
}
|
||||
|
||||
/* 开关按钮激活时图标颜色保持不变,继承默认颜色 */
|
||||
|
||||
/* 下拉菜单项 - 横向布局(图标在左,默认) */
|
||||
.opt-btn-dropdown-item.align-horizontal {
|
||||
flex-direction: row;
|
||||
|
||||
@@ -183,6 +183,32 @@ export class BimButtonGroup implements IBimComponent {
|
||||
if (this.options.iconActiveColor) style.setProperty('--bim-icon-active-color', this.options.iconActiveColor);
|
||||
if (this.options.textColor) style.setProperty('--bim-btn-text-color', this.options.textColor);
|
||||
if (this.options.textActiveColor) style.setProperty('--bim-btn-text-active-color', this.options.textActiveColor);
|
||||
|
||||
// 同步更新所有已存在的dropdown元素的CSS变量(dropdown被添加到body,无法继承容器的CSS变量)
|
||||
const dropdowns = document.querySelectorAll('.opt-btn-dropdown');
|
||||
dropdowns.forEach(dropdown => {
|
||||
const dropdownStyle = (dropdown as HTMLElement).style;
|
||||
if (this.options.iconColor) dropdownStyle.setProperty('--bim-icon-color', this.options.iconColor);
|
||||
if (this.options.iconActiveColor) dropdownStyle.setProperty('--bim-icon-active-color', this.options.iconActiveColor);
|
||||
if (this.options.textColor) dropdownStyle.setProperty('--bim-btn-text-color', this.options.textColor);
|
||||
if (this.options.textActiveColor) dropdownStyle.setProperty('--bim-btn-text-active-color', this.options.textActiveColor);
|
||||
if (this.options.btnBackgroundColor) dropdownStyle.setProperty('--bim-btn-bg', this.options.btnBackgroundColor);
|
||||
if (this.options.btnHoverColor) dropdownStyle.setProperty('--bim-btn-hover-bg', this.options.btnHoverColor);
|
||||
if (this.options.btnActiveColor) dropdownStyle.setProperty('--bim-btn-active-bg', this.options.btnActiveColor);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置主题的primary颜色(用于边框等)
|
||||
*/
|
||||
private setPrimaryColor(color: string): void {
|
||||
this.container.style.setProperty('--bim-primary-color', color);
|
||||
|
||||
// 同步更新所有dropdown(dropdown被添加到body,无法继承容器的CSS变量)
|
||||
const dropdowns = document.querySelectorAll('.opt-btn-dropdown');
|
||||
dropdowns.forEach(dropdown => {
|
||||
(dropdown as HTMLElement).style.setProperty('--bim-primary-color', color);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,6 +236,7 @@ export class BimButtonGroup implements IBimComponent {
|
||||
});
|
||||
|
||||
this.applyStyles();
|
||||
this.setPrimaryColor(theme.primary);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -339,6 +366,10 @@ export class BimButtonGroup implements IBimComponent {
|
||||
const hasLabel = this.options.showLabel && button.label;
|
||||
if (!hasLabel) {
|
||||
btnEl.classList.add('no-label');
|
||||
// 当不显示 label 时,添加 title 属性作为 tooltip
|
||||
if (button.label) {
|
||||
btnEl.title = t(button.label);
|
||||
}
|
||||
}
|
||||
|
||||
// 应用按钮的自定义样式
|
||||
@@ -412,13 +443,64 @@ export class BimButtonGroup implements IBimComponent {
|
||||
if (button.disabled) return;
|
||||
if (!button.children || button.children.length === 0) {
|
||||
if (button.keepActive) {
|
||||
this.setBtnActive(button.id);
|
||||
// 1) 先切换自身激活状态(onClick 里通常会根据 isActive 决定“打开/关闭”逻辑)
|
||||
const wasActive = this.activeBtnIds.has(button.id);
|
||||
const newState = !wasActive;
|
||||
this.setBtnActive(button.id, newState);
|
||||
|
||||
// 2) 互斥逻辑:仅在“本次切换为激活”时触发
|
||||
// - 一级按钮:同 groupId 下其它一级按钮互斥
|
||||
// - 二级按钮:同 groupId + 同 parentId 下其它二级按钮互斥
|
||||
// - 被关闭的按钮需要触发 onClick(用于执行关闭逻辑)
|
||||
if (newState && button.exclusive && button.groupId) {
|
||||
this.deactivateExclusiveSiblings(button);
|
||||
}
|
||||
}
|
||||
this.closeDropdown();
|
||||
if (button.onClick) button.onClick(button);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 互斥关闭同范围内的其它已激活按钮,并触发它们的 onClick
|
||||
* @param button 当前被激活的按钮
|
||||
*/
|
||||
private deactivateExclusiveSiblings(button: OptButton): void {
|
||||
const group = this.groups.find(g => g.id === button.groupId);
|
||||
if (!group) return;
|
||||
|
||||
// 二级按钮:同 groupId + 同 parentId
|
||||
if (button.parentId) {
|
||||
const parent = this.findButton(group.buttons, button.parentId);
|
||||
const siblings = parent?.children || [];
|
||||
for (const sib of siblings) {
|
||||
if (!sib) continue;
|
||||
if (sib.id === button.id) continue;
|
||||
if (sib.parentId !== button.parentId) continue;
|
||||
if (sib.groupId !== button.groupId) continue;
|
||||
|
||||
if (this.activeBtnIds.has(sib.id)) {
|
||||
this.setBtnActive(sib.id, false);
|
||||
// 触发被关闭按钮的 onClick(此时 sib.isActive 已经同步为 false)
|
||||
if (sib.onClick) sib.onClick(sib);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 一级按钮:同 groupId 下其它一级按钮(不包含二级)
|
||||
for (const sib of group.buttons) {
|
||||
if (sib.id === button.id) continue;
|
||||
if (sib.groupId !== button.groupId) continue;
|
||||
if (sib.parentId) continue; // 只处理一级按钮
|
||||
|
||||
if (this.activeBtnIds.has(sib.id)) {
|
||||
this.setBtnActive(sib.id, false);
|
||||
if (sib.onClick) sib.onClick(sib);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleMouseEnter(button: OptButton, btnEl: HTMLElement): void {
|
||||
if (this.hoverTimeout) clearTimeout(this.hoverTimeout);
|
||||
if (button.children && button.children.length > 0) {
|
||||
@@ -440,6 +522,16 @@ export class BimButtonGroup implements IBimComponent {
|
||||
dropdown.className = 'opt-btn-dropdown';
|
||||
if (this.options.backgroundColor) dropdown.style.setProperty('--bim-toolbar-bg', this.options.backgroundColor);
|
||||
|
||||
// 将主题CSS变量复制到dropdown元素上(因为dropdown被添加到body,无法继承容器的CSS变量)
|
||||
const dropdownStyle = dropdown.style;
|
||||
if (this.options.iconColor) dropdownStyle.setProperty('--bim-icon-color', this.options.iconColor);
|
||||
if (this.options.iconActiveColor) dropdownStyle.setProperty('--bim-icon-active-color', this.options.iconActiveColor);
|
||||
if (this.options.textColor) dropdownStyle.setProperty('--bim-btn-text-color', this.options.textColor);
|
||||
if (this.options.textActiveColor) dropdownStyle.setProperty('--bim-btn-text-active-color', this.options.textActiveColor);
|
||||
if (this.options.btnBackgroundColor) dropdownStyle.setProperty('--bim-btn-bg', this.options.btnBackgroundColor);
|
||||
if (this.options.btnHoverColor) dropdownStyle.setProperty('--bim-btn-hover-bg', this.options.btnHoverColor);
|
||||
if (this.options.btnActiveColor) dropdownStyle.setProperty('--bim-btn-active-bg', this.options.btnActiveColor);
|
||||
|
||||
// 获取按钮的位置信息
|
||||
const btnRect = btnEl.getBoundingClientRect();
|
||||
const expand = this.options.expand || 'down';
|
||||
@@ -503,6 +595,14 @@ export class BimButtonGroup implements IBimComponent {
|
||||
item.classList.add('align-vertical');
|
||||
}
|
||||
|
||||
// 二级菜单项的 active 状态渲染(修复 keepActive 在二级按钮“看起来不生效”的问题)
|
||||
// 说明:
|
||||
// - keepActive 的状态会记录在 activeBtnIds / button.isActive 上
|
||||
// - 下拉菜单每次打开都会重新渲染,因此必须在这里同步一次 active 样式
|
||||
if (this.activeBtnIds.has(button.id) || button.isActive) {
|
||||
item.classList.add('active');
|
||||
}
|
||||
|
||||
// 应用按钮的自定义样式
|
||||
const iconSize = button.iconSize || 32; // 二级菜单默认图标更小
|
||||
const minWidth = button.minWidth; // 不设置默认值,让下拉菜单项保持紧凑
|
||||
@@ -523,6 +623,9 @@ export class BimButtonGroup implements IBimComponent {
|
||||
label.className = 'opt-btn-dropdown-label';
|
||||
label.textContent = t(button.label);
|
||||
item.appendChild(label);
|
||||
} else if (button.label) {
|
||||
// 当不显示 label 时,添加 title 属性作为 tooltip
|
||||
item.title = t(button.label);
|
||||
}
|
||||
|
||||
item.addEventListener('click', (e) => { e.stopPropagation(); this.handleClick(button); });
|
||||
@@ -572,11 +675,17 @@ export class BimButtonGroup implements IBimComponent {
|
||||
|
||||
const hasLabel = this.options.showLabel && button.label;
|
||||
|
||||
// 只需要更新 no-label 类,CSS 会处理显示/隐藏
|
||||
// 更新 no-label 类和 title 属性
|
||||
if (hasLabel) {
|
||||
btnEl.classList.remove('no-label');
|
||||
// 显示标签时,移除 title(避免重复显示)
|
||||
btnEl.removeAttribute('title');
|
||||
} else {
|
||||
btnEl.classList.add('no-label');
|
||||
// 隐藏标签时,添加 title 作为 tooltip
|
||||
if (button.label) {
|
||||
btnEl.title = t(button.label);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,6 +7,18 @@ export interface ButtonConfig {
|
||||
label: string;
|
||||
icon?: string;
|
||||
keepActive?: boolean;
|
||||
/**
|
||||
* 是否互斥(开关互斥)
|
||||
*
|
||||
* 行为说明:
|
||||
* - 当按钮从“未激活”切换到“激活”时,如果该按钮开启了 exclusive,
|
||||
* 会自动关闭同互斥范围内的其它已激活按钮,并触发它们的 onClick(用于执行关闭逻辑)。
|
||||
* - 一级按钮:互斥范围 = 同 groupId 下的一级按钮
|
||||
* - 二级按钮:互斥范围 = 同 groupId 且同 parentId 下的二级按钮
|
||||
*
|
||||
* 注意:该能力通常与 keepActive 搭配使用。
|
||||
*/
|
||||
exclusive?: boolean;
|
||||
isActive?:boolean;
|
||||
disabled?: boolean;
|
||||
onClick?: (button: OptButton) => void;
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import { ButtonConfig } from '../../../index.type';
|
||||
import type { ButtonConfig } from '../../../index.type';
|
||||
import type { BimEngine } from '../../../../../bim-engine';
|
||||
import { infoIcon } from './icon';
|
||||
|
||||
export const infoButton: ButtonConfig = {
|
||||
/**
|
||||
* 信息按钮配置
|
||||
* 说明:当前仍保留 demo 的事件触发方式;engine 已注入,便于未来替换为 SDK 内部逻辑。
|
||||
*/
|
||||
export const createInfoButton = (_engine: BimEngine): ButtonConfig => {
|
||||
return {
|
||||
id: 'toolbar-info',
|
||||
groupId: 'group-2',
|
||||
type: 'button',
|
||||
label: 'toolbar.info',
|
||||
icon: infoIcon,
|
||||
@@ -10,4 +17,5 @@ export const infoButton: ButtonConfig = {
|
||||
// WORKAROUND: Dispatch a standard custom event on document
|
||||
document.dispatchEvent(new CustomEvent('bim-demo:open-property-panel'));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { ButtonConfig } from '../../../index.type';
|
||||
import type { BimEngine } from '../../../../../bim-engine';
|
||||
|
||||
/**
|
||||
* 定位按钮配置
|
||||
*/
|
||||
export const locationButton: ButtonConfig = {
|
||||
export const createLocationButton = (_engine: BimEngine): ButtonConfig => {
|
||||
return {
|
||||
id: 'location',
|
||||
groupId: 'group-1',
|
||||
type: 'button',
|
||||
@@ -11,6 +13,8 @@ export const locationButton: ButtonConfig = {
|
||||
icon: '<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M9 13h2v-2.75h2V13h2V8.25l-3-2l-3 2zm3 9q-4.025-3.425-6.012-6.362T4 10.2q0-3.75 2.413-5.975T12 2t5.588 2.225T20 10.2q0 2.5-1.987 5.438T12 22"/></svg>',
|
||||
keepActive: false,
|
||||
onClick: (button) => {
|
||||
// 预留:未来接入定位逻辑(此处已注入 engine)
|
||||
console.log('定位按钮被点击:', button.id);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { ButtonConfig } from '../../../../index.type';
|
||||
import type { BimEngine } from '../../../../../../bim-engine';
|
||||
|
||||
/**
|
||||
* 轴向剖切按钮配置
|
||||
*/
|
||||
export const createSectionAxisButton = (engine: BimEngine): ButtonConfig => {
|
||||
return {
|
||||
id: 'section-axis',
|
||||
groupId: 'group-1',
|
||||
parentId: 'section',
|
||||
type: 'button',
|
||||
keepActive: true,
|
||||
exclusive: true,
|
||||
align: 'vertical',
|
||||
label: 'toolbar.sectionAxis',
|
||||
icon: '<svg width="32" height="32" viewBox="0 0 24 24"><circle cx="12" cy="12" r="6" fill="currentColor"/></svg>',
|
||||
onClick: (button) => {
|
||||
if (button.isActive) {
|
||||
engine.sectionAxis?.show();
|
||||
} else {
|
||||
engine.sectionAxis?.destroy();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { ButtonConfig } from '../../../../index.type';
|
||||
import type { BimEngine } from '../../../../../../bim-engine';
|
||||
|
||||
/**
|
||||
* 剖切盒按钮配置
|
||||
*/
|
||||
export const createSectionBoxButton = (engine: BimEngine): ButtonConfig => {
|
||||
return {
|
||||
id: 'section-box',
|
||||
groupId: 'group-1',
|
||||
parentId: 'section',
|
||||
type: 'button',
|
||||
keepActive: true,
|
||||
exclusive: true,
|
||||
align: 'vertical',
|
||||
label: 'toolbar.sectionBox',
|
||||
icon: '<svg width="32" height="32" viewBox="0 0 24 24"><circle cx="12" cy="12" r="6" fill="currentColor"/></svg>',
|
||||
onClick: (button) => {
|
||||
console.log('剖切盒被点击:', button.id, '激活状态:', button.isActive);
|
||||
if (button.isActive) {
|
||||
// 激活时显示弹窗
|
||||
engine.sectionBox?.show();
|
||||
} else {
|
||||
// 关闭时隐藏弹窗
|
||||
engine.sectionBox?.hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { ButtonConfig } from '../../../../index.type';
|
||||
import type { BimEngine } from '../../../../../../bim-engine';
|
||||
|
||||
/**
|
||||
* 剖切菜单按钮配置
|
||||
*/
|
||||
export const createSectionMenuButton = (_engine: BimEngine): ButtonConfig => {
|
||||
return {
|
||||
id: 'section',
|
||||
groupId: 'group-1',
|
||||
type: 'menu',
|
||||
label: 'toolbar.section',
|
||||
align: 'vertical',
|
||||
icon: '<svg width="32" height="32" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8" fill="currentColor"/></svg>',
|
||||
keepActive: true,
|
||||
onClick: (button) => {
|
||||
console.log('剖切按钮被点击:', button.id);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { ButtonConfig } from '../../../../index.type';
|
||||
import type { BimEngine } from '../../../../../../bim-engine';
|
||||
|
||||
/**
|
||||
* 拾取面剖切按钮配置
|
||||
*/
|
||||
export const createSectionPlaneButton = (engine: BimEngine): ButtonConfig => {
|
||||
return {
|
||||
id: 'section-plane',
|
||||
groupId: 'group-1',
|
||||
parentId: 'section',
|
||||
type: 'button',
|
||||
keepActive: true,
|
||||
exclusive: true,
|
||||
align: 'vertical',
|
||||
label: 'toolbar.sectionPlane',
|
||||
icon: '<svg width="32" height="32" viewBox="0 0 24 24"><circle cx="12" cy="12" r="6" fill="currentColor"/></svg>',
|
||||
onClick: (button) => {
|
||||
console.log('拾取面剖切被点击:', button.id, '激活状态:', button.isActive);
|
||||
if (button.isActive) {
|
||||
// 激活时显示弹窗
|
||||
engine.sectionPlane?.show();
|
||||
} else {
|
||||
// 关闭时隐藏弹窗
|
||||
engine.sectionPlane?.hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { ButtonConfig } from '../../../index.type';
|
||||
import type { BimEngine } from '../../../../../bim-engine';
|
||||
|
||||
/**
|
||||
* 定位按钮配置
|
||||
* 设置按钮配置
|
||||
*/
|
||||
export const settingButton: ButtonConfig = {
|
||||
export const createSettingButton = (_engine: BimEngine): ButtonConfig => {
|
||||
return {
|
||||
id: 'setting',
|
||||
groupId: 'group-2',
|
||||
type: 'button',
|
||||
@@ -11,6 +13,8 @@ export const settingButton: ButtonConfig = {
|
||||
icon: '<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="m9.25 22l-.4-3.2q-.325-.125-.612-.3t-.563-.375L4.7 19.375l-2.75-4.75l2.575-1.95Q4.5 12.5 4.5 12.338v-.675q0-.163.025-.338L1.95 9.375l2.75-4.75l2.975 1.25q.275-.2.575-.375t.6-.3l.4-3.2h5.5l.4 3.2q.325.125.613.3t.562.375l2.975-1.25l2.75 4.75l-2.575 1.95q.025.175.025.338v.674q0 .163-.05.338l2.575 1.95l-2.75 4.75l-2.95-1.25q-.275.2-.575.375t-.6.3l-.4 3.2zM11 20h1.975l.35-2.65q.775-.2 1.438-.587t1.212-.938l2.475 1.025l.975-1.7l-2.15-1.625q.125-.35.175-.737T17.5 12t-.05-.787t-.175-.738l2.15-1.625l-.975-1.7l-2.475 1.05q-.55-.575-1.212-.962t-1.438-.588L13 4h-1.975l-.35 2.65q-.775.2-1.437.588t-1.213.937L5.55 7.15l-.975 1.7l2.15 1.6q-.125.375-.175.75t-.05.8q0 .4.05.775t.175.75l-2.15 1.625l.975 1.7l2.475-1.05q.55.575 1.213.963t1.437.587zm1.05-4.5q1.45 0 2.475-1.025T15.55 12t-1.025-2.475T12.05 8.5q-1.475 0-2.488 1.025T8.55 12t1.013 2.475T12.05 15.5M12 12"/></svg>',
|
||||
keepActive: false,
|
||||
onClick: (button) => {
|
||||
// 预留:未来接入设置逻辑(此处已注入 engine)
|
||||
console.log('设置按钮被点击:', button.id);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
import type { ButtonConfig } from '../../../../index.type';
|
||||
import type { BimEngine } from '../../../../../../bim-engine';
|
||||
|
||||
export const walkBirdButton: ButtonConfig = {
|
||||
/**
|
||||
* 第三人称(鸟瞰)漫游按钮配置
|
||||
*/
|
||||
export const createWalkBirdButton = (_engine: BimEngine): ButtonConfig => {
|
||||
return {
|
||||
id: 'walk-bird',
|
||||
groupId: 'group-1',
|
||||
parentId: 'walk',
|
||||
align: 'vertical',
|
||||
keepActive: true,
|
||||
exclusive: true,
|
||||
type: 'button',
|
||||
label: 'toolbar.walkBird',
|
||||
icon: '<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M9 22V8.775q-2.275-.6-3.637-2.512T4 2h2q0 2.075 1.338 3.538T10.75 7h2.5q.75 0 1.4.275t1.175.8L20.35 12.6l-1.4 1.4L15 10.05V22h-2v-6h-2v6zm3-16q-.825 0-1.412-.587T10 4t.588-1.412T12 2t1.413.588T14 4t-.587 1.413T12 6"/></svg>',
|
||||
onClick: (button) => {
|
||||
console.log('鸟瞰漫游被点击:', button.id);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { ButtonConfig } from '../../../../index.type';
|
||||
import type { BimEngine } from '../../../../../../bim-engine';
|
||||
|
||||
/**
|
||||
* 漫游菜单按钮配置
|
||||
*/
|
||||
export const walkMenuButton: ButtonConfig = {
|
||||
export const createWalkMenuButton = (_engine: BimEngine): ButtonConfig => {
|
||||
return {
|
||||
id: 'walk',
|
||||
groupId: 'group-1',
|
||||
type: 'menu',
|
||||
@@ -14,4 +16,5 @@ export const walkMenuButton: ButtonConfig = {
|
||||
onClick: (button) => {
|
||||
console.log('漫游按钮被点击:', button.id);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
import type { ButtonConfig } from '../../../../index.type';
|
||||
import type { BimEngine } from '../../../../../../bim-engine';
|
||||
|
||||
export const walkPersonButton: ButtonConfig = {
|
||||
/**
|
||||
* 第一人称漫游按钮配置
|
||||
*/
|
||||
export const createWalkPersonButton = (_engine: BimEngine): ButtonConfig => {
|
||||
return {
|
||||
id: 'walk-person',
|
||||
groupId: 'group-1',
|
||||
parentId: 'walk',
|
||||
type: 'button',
|
||||
keepActive: true,
|
||||
exclusive: true,
|
||||
align: 'vertical',
|
||||
label: 'toolbar.walkPerson',
|
||||
icon: '<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M9 22V8.775q-2.275-.6-3.637-2.512T4 2h2q0 2.075 1.338 3.538T10.75 7h2.5q.75 0 1.4.275t1.175.8L20.35 12.6l-1.4 1.4L15 10.05V22h-2v-6h-2v6zm3-16q-.825 0-1.412-.587T10 4t.588-1.412T12 2t1.413.588T14 4t-.587 1.413T12 6"/></svg>',
|
||||
onClick: (button) => {
|
||||
console.log('人视漫游被点击:', button.id);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { ButtonConfig } from '../../../index.type';
|
||||
import type { BimEngine } from '../../../../../bim-engine';
|
||||
|
||||
/**
|
||||
* 选框放大按钮配置
|
||||
*
|
||||
* 说明:
|
||||
* - 当前仅添加 UI 按钮,点击事件先留空(后续接入引擎能力再实现)
|
||||
* - 使用工厂函数模式注入 engine,便于未来调用 engine API
|
||||
*/
|
||||
export const createZoomBoxButton = (_engine: BimEngine): ButtonConfig => {
|
||||
return {
|
||||
id: 'zoom-box',
|
||||
groupId: 'group-1',
|
||||
keepActive: true,
|
||||
type: 'button',
|
||||
label: 'toolbar.zoomBox',
|
||||
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M16.5 20q-1.875 0-3.187-1.312T12 15.5t1.313-3.187T16.5 11t3.188 1.313T21 15.5q0 .65-.187 1.25T20.3 17.9l2 2q.275.275.275.7t-.275.7t-.7.275t-.7-.275l-2-2q-.55.325-1.15.513T16.5 20m0-2q1.05 0 1.775-.725T19 15.5t-.725-1.775T16.5 13t-1.775.725T14 15.5t.725 1.775T16.5 18M4 18V9v1v-4zm0 2q-.825 0-1.412-.587T2 18V6q0-.825.588-1.412T4 4h16q.825 0 1.413.588T22 6v3q0 .425-.288.713T21 10h-8V6H4v12h5q.425 0 .713.288T10 19t-.288.713T9 20z"/></svg>',
|
||||
onClick: () => {
|
||||
// 事件先留空:后续实现“框选放大/框选缩放”能力时再接入
|
||||
// 这里不做任何动作,避免误触影响用户操作
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -13,32 +13,42 @@ export class Toolbar extends BimButtonGroup {
|
||||
|
||||
// 动态加载默认按钮配置
|
||||
const { createHomeButton } = await import('./buttons/home');
|
||||
const { locationButton } = await import('./buttons/location');
|
||||
const { walkMenuButton } = await import('./buttons/walk/walk-menu');
|
||||
const { walkPersonButton } = await import('./buttons/walk/walk-person');
|
||||
const { walkBirdButton } = await import('./buttons/walk/walk-bird');
|
||||
const { settingButton } = await import('./buttons/setting');
|
||||
const { infoButton } = await import('./buttons/info');
|
||||
const { createZoomBoxButton } = await import('./buttons/zoom-box');
|
||||
const { createLocationButton } = await import('./buttons/location');
|
||||
const { createWalkMenuButton } = await import('./buttons/walk/walk-menu');
|
||||
const { createWalkPersonButton } = await import('./buttons/walk/walk-person');
|
||||
const { createWalkBirdButton } = await import('./buttons/walk/walk-bird');
|
||||
const { createSettingButton } = await import('./buttons/setting');
|
||||
const { createInfoButton } = await import('./buttons/info');
|
||||
const { createMeasureButton } = await import('./buttons/measure');
|
||||
const { createSectionMenuButton } = await import('./buttons/section/section-menu');
|
||||
const { createSectionPlaneButton } = await import('./buttons/section/section-plane');
|
||||
const { createSectionAxisButton } = await import('./buttons/section/section-axis');
|
||||
const { createSectionBoxButton } = await import('./buttons/section/section-box');
|
||||
|
||||
this.addGroup('group-1');
|
||||
|
||||
// 使用工厂函数创建按钮,并注入 engine
|
||||
if (this.engine) {
|
||||
this.addButton(createHomeButton(this.engine));
|
||||
// 你要求:在"首页"后面添加"选框放大"
|
||||
this.addButton(createZoomBoxButton(this.engine));
|
||||
this.addButton(createMeasureButton(this.engine));
|
||||
this.addButton(createSectionMenuButton(this.engine));
|
||||
this.addButton(createSectionPlaneButton(this.engine));
|
||||
this.addButton(createSectionAxisButton(this.engine));
|
||||
this.addButton(createSectionBoxButton(this.engine));
|
||||
this.addButton(createWalkMenuButton(this.engine));
|
||||
this.addButton(createWalkPersonButton(this.engine));
|
||||
this.addButton(createWalkBirdButton(this.engine));
|
||||
this.addButton(createLocationButton(this.engine));
|
||||
this.addGroup('group-2');
|
||||
this.addButton(createSettingButton(this.engine));
|
||||
this.addButton(createInfoButton(this.engine));
|
||||
} else {
|
||||
console.warn('[Toolbar] Engine not available when creating buttons.');
|
||||
}
|
||||
|
||||
this.addButton(walkMenuButton);
|
||||
this.addButton(walkPersonButton);
|
||||
this.addButton(walkBirdButton);
|
||||
this.addButton(locationButton);
|
||||
this.addGroup('group-2');
|
||||
this.addButton(settingButton);
|
||||
this.addButton(infoButton);
|
||||
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,8 +148,8 @@
|
||||
}
|
||||
|
||||
.bim-measure-tool-icon {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -162,6 +162,8 @@
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
/* 说明:测量方式图标已在 TS 中“瘦身”为纯 path(currentColor),一般无需额外隐藏背景 rect。 */
|
||||
|
||||
.bim-measure-toggle {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@@ -5,6 +5,25 @@ import { localeManager, t } from '../../services/locale';
|
||||
import { themeManager } from '../../services/theme';
|
||||
import type { MeasureConfig, MeasureMode, MeasurePanelOptions, MeasurePrecision, MeasureResult, MeasureUnit } from './types';
|
||||
|
||||
/**
|
||||
* 测量方式图标(SVG)
|
||||
*
|
||||
* 说明:
|
||||
* - 你上传的 SVG 原文件放在 `src/assets/icons/` 目录
|
||||
* - 原始 SVG 含 defs/clipPath/style/背景 rect,直接内联时容易出现渲染/裁剪异常(尤其多个图标同时出现)
|
||||
* - 这里把图标“瘦身”为纯 path,并统一使用 currentColor,确保稳定渲染
|
||||
*/
|
||||
const MEASURE_MODE_ICON_SVGS: Record<MeasureMode, string> = {
|
||||
distance: `<svg viewBox="0 0 32 32" aria-hidden="true"><g transform="translate(0 4.197)"><path fill="currentColor" d="M29.692,3.03,27.55.919a.529.529,0,0,1-.014-.756A.549.549,0,0,1,28.3.15l.014.013,3.067,3.023a.529.529,0,0,1,0,.756L28.317,6.966a.549.549,0,0,1-.767.013.529.529,0,0,1-.014-.756l.014-.013L29.692,4.1H2.31L4.452,6.21a.528.528,0,0,1,.013.756.547.547,0,0,1-.766.013l-.014-.013L.616,3.942a.531.531,0,0,1,0-.756L3.685.163a.548.548,0,0,1,.767.014.528.528,0,0,1,0,.742L2.31,3.03ZM24.136,15.055H23.051V18H21.966v-2.94H20.882V18H19.8v-2.94H18.712V18H17.627v-2.94H16.543v5.078H15.458V15.055H14.373V18H13.288v-2.94H12.2V18H11.119v-2.94H10.034V18H8.949v-2.94H7.865V18H6.78v-2.94H5.7v5.078H4.61V15.055H1.9a.27.27,0,0,0-.272.268v6.413A.269.269,0,0,0,1.9,22H30.1a.268.268,0,0,0,.271-.267V15.323a.269.269,0,0,0-.271-.268H27.39v5.078H26.305V15.055H25.221V18H24.136Zm5.966-1.6A1.884,1.884,0,0,1,32,15.323v6.413a1.885,1.885,0,0,1-1.9,1.871H1.9A1.885,1.885,0,0,1,0,21.736V15.323a1.885,1.885,0,0,1,1.9-1.871Z"/></g></svg>`,
|
||||
minDistance: `<svg viewBox="0 0 32 32" aria-hidden="true"><path fill="currentColor" d="M-5.839,24.8H-34.16A1.875,1.875,0,0,1-36,22.933V16.52a1.887,1.887,0,0,1,1.9-1.871H-5.9A1.887,1.887,0,0,1-4,16.52v6.412A1.875,1.875,0,0,1-5.839,24.8ZM-34.1,16.252a.27.27,0,0,0-.272.268v6.412a.27.27,0,0,0,.272.267H-5.9a.269.269,0,0,0,.271-.267V16.52a.27.27,0,0,0-.271-.268H-8.61V21.33H-9.695V16.252h-1.085v2.939h-1.085V16.252h-1.085v2.939h-1.085V16.252h-1.084v2.939H-16.2V16.252h-1.085v2.939h-1.085V16.252h-1.084V21.33h-1.084V16.252h-1.085v2.939h-1.085V16.252H-23.8v2.939h-1.085V16.252h-1.085v2.939h-1.085V16.252h-1.084v2.939H-29.22V16.252H-30.3V21.33H-31.39V16.252Z" transform="translate(36 2)"/><path fill="currentColor" d="M23.716,7.947V4.875c0-.8-.232-1.085-.765-1.085a1.573,1.573,0,0,0-1.133.585V7.947H20.4V2.75h1.163l.1.687H21.7a2.547,2.547,0,0,1,1.763-.817c1.172,0,1.676.78,1.676,2.089V7.947Zm-7.26,0V2.62h1.58V7.947Zm-3.8,0V4.875c0-.8-.243-1.085-.76-1.085a1.606,1.606,0,0,0-1.049.585V7.947H9.421V4.875c0-.8-.243-1.085-.758-1.085a1.608,1.608,0,0,0-1.05.585V7.947H6.194V2.75H7.36l.1.7H7.5A2.326,2.326,0,0,1,9.169,2.62a1.486,1.486,0,0,1,1.5.91A2.445,2.445,0,0,1,12.4,2.62c1.156,0,1.691.78,1.691,2.089V7.947Zm3.8-6.849a.79.79,0,0,1,1.58,0,.79.79,0,0,1-1.58,0Z" transform="translate(0.333 3.053)"/></svg>`,
|
||||
angle: `<svg viewBox="0 0 32 32" aria-hidden="true"><path fill="currentColor" d="M39.587,50.766h13.7a1,1,0,0,1,0,2H23.171a1,1,0,0,1,0-2h1.418l6.582-7.006v-.006a.517.517,0,0,1,.14-.357.456.456,0,0,1,.337-.144l12.1-12.876a.451.451,0,0,1,.665,0,.524.524,0,0,1,0,.708L32.883,43.355a8.3,8.3,0,0,1,6.7,7.411Zm-.949,0a7.254,7.254,0,0,0-6.611-6.5l-6.108,6.5Z" transform="translate(-22.229 -26.489)"/></svg>`,
|
||||
elevation: `<svg viewBox="0 0 32 32" aria-hidden="true"><path fill="currentColor" d="M84.131,193.119a1.056,1.056,0,0,1,1.116.982v7.857a1.056,1.056,0,0,1-1.116.982H54.367a1.056,1.056,0,0,1-1.116-.982V194.1a1.056,1.056,0,0,1,1.116-.982Zm-1.116,1.964H55.483v5.893H83.015Zm1.116-13.749a1.064,1.064,0,0,1,1.114.935,1.032,1.032,0,0,1-1.007,1.025l-.107,0H71.2l-7.858,6.914a1.227,1.227,0,0,1-1.578,0l-8.185-7.2-.018-.016-.032-.031.049.047a1.107,1.107,0,0,1-.092-.092l-.011-.014a.869.869,0,0,1-.182-.857l0-.008L53.31,182l.012-.029.02-.045.019-.035a1.1,1.1,0,0,1,.891-.552h.007q.053,0,.107,0ZM68.043,183.3H57.06l5.492,4.831Z" transform="translate(-53.247 -176.136)"/></svg>`,
|
||||
volume: `<svg viewBox="0 0 32 32" aria-hidden="true"><path fill="currentColor" d="M94.74,86.658V71.189a.371.371,0,0,1,.2-.329l13.869-7.22a.371.371,0,0,1,.344,0l13.053,6.891h0l.819.431a.371.371,0,0,1,.2.328v15.3a.371.371,0,0,1-.2.328l-13.872,7.255a.371.371,0,0,1-.342,0L94.94,86.987a.371.371,0,0,1-.2-.329Zm2.119-.837,11.2,5.8a.024.024,0,0,0,.035-.022V79.483a.371.371,0,0,0-.2-.328l-11.2-5.909a.024.024,0,0,0-.035.021V85.492A.371.371,0,0,0,96.859,85.821Zm13.151-6.459v12a.12.12,0,0,0,.176.106L114,89.474l3.334-1.745,3.771-1.978a.371.371,0,0,0,.2-.328V73.5a.193.193,0,0,0-.284-.171l-10.812,5.708A.371.371,0,0,0,110.01,79.362ZM97.925,71.725l10.839,5.72a.371.371,0,0,0,.346,0L119.8,71.808a.214.214,0,0,0,0-.378l-10.649-5.621a.371.371,0,0,0-.344,0L97.925,71.47A.144.144,0,0,0,97.925,71.725Z" transform="translate(-92.982 -62.907)"/></svg>`,
|
||||
laserDistance: `<svg viewBox="0 0 32 32" aria-hidden="true"><g transform="translate(0 -1.293)"><path fill="currentColor" d="M0,1.293v31.96H32V1.293ZM30.97,32.182H1.03V2.323H30.97Z"/><path fill="currentColor" d="M160.026,291.9l1.6,1.6,7.305-7.305-7.305-7.305-1.6,1.6,4.794,4.566h-6.392v2.283h6.392Zm-5.251,0-4.566-4.566h6.164v-2.283H150.21l4.566-4.566-1.37-1.6L146.1,286.19l7.305,7.305Z" transform="translate(-141.535 -268.917)"/></g></svg>`,
|
||||
slope: `<svg viewBox="0 0 32 32" aria-hidden="true"><path fill="currentColor" d="M202.1,188.337l2.629-2.191-8.447-3.106,1.533,8.871,2.629-2.194,9.341,11.209,1.656-1.379Zm-13.726-.435a1.075,1.075,0,0,0-1.07-.341,1.057,1.057,0,0,0-.5.277l-5.11,4.08a1.08,1.08,0,0,0-.406.84l-.007,17.386a1.079,1.079,0,0,0,1.077,1.077L205.7,211.2a1.078,1.078,0,0,0,.822-1.774Zm-4.934,21.164.007-15.788,3.968-3.171,15.974,18.941Z" transform="translate(-180.36 -181.131)"/></svg>`,
|
||||
spaceVolume: `<svg viewBox="0 0 32 32" aria-hidden="true"><g transform="translate(-106.35 -97.661)"><path fill="currentColor" d="M125.977,128.829l13.076-7.363v-13.6l-13.076,6.8Zm-3.126-15.655a.565.565,0,0,1-.258-.064L109.3,106.323a.567.567,0,0,1-.011-1L122.578,98a.567.567,0,0,1,.55,0l13.288,7.325a.567.567,0,0,1-.011,1l-13.292,6.79A.63.63,0,0,1,122.851,113.174ZM110.773,105.8l12.078,6.172,12.078-6.172-12.078-6.657Z" transform="translate(-1.922)"/><path fill="currentColor" d="M120.649,322.52a.58.58,0,0,1-.262-.064l-13.08-6.8a.573.573,0,0,1-.307-.5V301a.566.566,0,0,1,.273-.486.573.573,0,0,1,.558-.019l13.076,6.8a.573.573,0,0,1,.307.5v14.161a.57.57,0,0,1-.565.569Zm-12.511-7.708,11.942,6.206V308.136l-11.942-6.206Zm15.917,9.408a.585.585,0,0,1-.288-.076.567.567,0,0,1-.281-.489V309.49a.562.562,0,0,1,.307-.5l13.076-6.8a.573.573,0,0,1,.558.019.562.562,0,0,1,.273.486v13.6a.568.568,0,0,1-.288.493l-13.076,7.359A.557.557,0,0,1,124.055,324.22Zm.569-14.385V322.68l11.942-6.722V303.629Z" transform="translate(0 -194.822)"/></g></svg>`
|
||||
};
|
||||
|
||||
/**
|
||||
* 测量面板组件(只做 UI,不实现真实测量)
|
||||
*
|
||||
@@ -371,8 +390,9 @@ export class MeasurePanel implements IBimComponent {
|
||||
'spaceVolume'
|
||||
];
|
||||
|
||||
// 图标占位:统一用圆形(你要求的“圆形占位”)
|
||||
const circleIconSvg = `
|
||||
// 图标:优先使用你上传的 SVG 文件内容(已内联到 MEASURE_MODE_ICON_SVGS)
|
||||
// 兜底:如果某个 mode 没有配置图标,则使用圆形占位(防止页面空白)
|
||||
const fallbackCircleIconSvg = `
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="9"></circle>
|
||||
</svg>
|
||||
@@ -389,7 +409,7 @@ export class MeasurePanel implements IBimComponent {
|
||||
// icon
|
||||
const icon = document.createElement('span');
|
||||
icon.className = 'bim-measure-tool-icon';
|
||||
icon.innerHTML = circleIconSvg;
|
||||
icon.innerHTML = MEASURE_MODE_ICON_SVGS[mode] || fallbackCircleIconSvg;
|
||||
btn.appendChild(icon);
|
||||
|
||||
// 点击切换模式
|
||||
|
||||
92
src/components/section-axis-panel/index.css
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 轴向剖切面板样式
|
||||
*/
|
||||
|
||||
.section-axis-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 12px 12px 16px 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 第一行:隐藏、反向 */
|
||||
.section-axis-row-1 {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* 第二行:X、Y、Z */
|
||||
.section-axis-row-2 {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
/* 按钮基础样式 */
|
||||
.section-axis-btn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
padding: 8px;
|
||||
background: var(--bim-section-axis-btn-bg, rgba(255, 255, 255, 0.06));
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
outline: none;
|
||||
color: var(--bim-text-color, rgba(255, 255, 255, 0.90));
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
.section-axis-btn:hover {
|
||||
background: var(--bim-section-axis-btn-hover, rgba(255, 255, 255, 0.10));
|
||||
}
|
||||
|
||||
/* 激活状态 */
|
||||
.section-axis-btn.active {
|
||||
background: var(--bim-section-axis-btn-active, rgba(255, 255, 255, 0.14));
|
||||
border-color: var(--bim-text-active-color, #fff);
|
||||
color: var(--bim-text-active-color, #fff);
|
||||
}
|
||||
|
||||
/* 图标样式 */
|
||||
.section-axis-btn-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--bim-icon-color, #ccc);
|
||||
}
|
||||
|
||||
.section-axis-btn-icon svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 标签样式 */
|
||||
.section-axis-btn-label {
|
||||
font-size: 12px;
|
||||
color: inherit;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* 文字按钮样式(XYZ) */
|
||||
.section-axis-btn-text {
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.section-axis-btn-text .section-axis-btn-label {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
300
src/components/section-axis-panel/index.ts
Normal file
@@ -0,0 +1,300 @@
|
||||
import './index.css';
|
||||
import type { ThemeConfig } from '../../themes/types';
|
||||
import { IBimComponent } from '../../types/component';
|
||||
import { localeManager, t } from '../../services/locale';
|
||||
import { themeManager } from '../../services/theme';
|
||||
import type { SectionAxisPanelOptions, SectionAxis } from './types';
|
||||
|
||||
/**
|
||||
* 轴向剖切面板组件
|
||||
* 第一行:隐藏(toggle)、反向
|
||||
* 第二行:X、Y、Z(互斥按钮组)
|
||||
*/
|
||||
export class SectionAxisPanel implements IBimComponent {
|
||||
public element: HTMLElement;
|
||||
private options: SectionAxisPanelOptions;
|
||||
|
||||
// 状态
|
||||
private isHidden: boolean = false;
|
||||
private activeAxis: SectionAxis = 'x';
|
||||
|
||||
// DOM 引用 - 第一行
|
||||
private hideBtn!: HTMLButtonElement;
|
||||
private reverseBtn!: HTMLButtonElement;
|
||||
private hideLabelEl!: HTMLElement;
|
||||
private reverseLabelEl!: HTMLElement;
|
||||
|
||||
// DOM 引用 - 第二行
|
||||
private axisXBtn!: HTMLButtonElement;
|
||||
private axisYBtn!: HTMLButtonElement;
|
||||
private axisZBtn!: HTMLButtonElement;
|
||||
|
||||
// 订阅清理
|
||||
private unsubscribeLocale: (() => void) | null = null;
|
||||
private unsubscribeTheme: (() => void) | null = null;
|
||||
|
||||
constructor(options: SectionAxisPanelOptions = {}) {
|
||||
this.options = options;
|
||||
this.isHidden = options.defaultHidden ?? false;
|
||||
this.activeAxis = options.defaultAxis ?? 'x';
|
||||
this.element = this.createDom();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化组件
|
||||
*/
|
||||
public init(): void {
|
||||
// 订阅语言变更
|
||||
this.unsubscribeLocale = localeManager.subscribe(() => {
|
||||
this.setLocales();
|
||||
});
|
||||
|
||||
// 订阅主题变更
|
||||
this.unsubscribeTheme = themeManager.subscribe((theme) => {
|
||||
this.setTheme(theme);
|
||||
});
|
||||
|
||||
// 初始应用
|
||||
this.setLocales();
|
||||
this.setTheme(themeManager.getTheme());
|
||||
|
||||
// 初始化按钮状态
|
||||
this.updateHideButtonState();
|
||||
this.updateAxisButtonsState();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置主题
|
||||
*/
|
||||
public setTheme(theme: ThemeConfig): void {
|
||||
const style = this.element.style;
|
||||
style.setProperty('--bim-section-axis-btn-bg', theme.componentBackground ?? 'rgba(255, 255, 255, 0.06)');
|
||||
style.setProperty('--bim-section-axis-btn-hover', theme.componentHover ?? 'rgba(255, 255, 255, 0.10)');
|
||||
style.setProperty('--bim-section-axis-btn-active', theme.componentActive ?? 'rgba(255, 255, 255, 0.14)');
|
||||
style.setProperty('--bim-primary-color', theme.primary ?? '#1890ff');
|
||||
style.setProperty('--bim-icon-color', theme.icon ?? '#ccc');
|
||||
style.setProperty('--bim-text-color', theme.textSecondary ?? 'rgba(255, 255, 255, 0.90)');
|
||||
style.setProperty('--bim-text-active-color', theme.textPrimary ?? '#fff');
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置语言
|
||||
*/
|
||||
public setLocales(): void {
|
||||
this.hideLabelEl.textContent = t('sectionAxis.actions.hide');
|
||||
this.reverseLabelEl.textContent = t('sectionAxis.actions.reverse');
|
||||
// XYZ按钮的文字不需要国际化,保持为单个字母
|
||||
|
||||
this.hideBtn.title = t('sectionAxis.actions.hide');
|
||||
this.reverseBtn.title = t('sectionAxis.actions.reverse');
|
||||
this.axisXBtn.title = t('sectionAxis.actions.axisX');
|
||||
this.axisYBtn.title = t('sectionAxis.actions.axisY');
|
||||
this.axisZBtn.title = t('sectionAxis.actions.axisZ');
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置隐藏状态
|
||||
*/
|
||||
public setHiddenState(isHidden: boolean): void {
|
||||
this.isHidden = isHidden;
|
||||
this.updateHideButtonState();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取隐藏状态
|
||||
*/
|
||||
public getHiddenState(): boolean {
|
||||
return this.isHidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置激活的轴向
|
||||
*/
|
||||
public setActiveAxis(axis: SectionAxis): void {
|
||||
this.activeAxis = axis;
|
||||
this.updateAxisButtonsState();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取激活的轴向
|
||||
*/
|
||||
public getActiveAxis(): SectionAxis {
|
||||
return this.activeAxis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁组件
|
||||
*/
|
||||
public destroy(): void {
|
||||
if (this.unsubscribeLocale) {
|
||||
this.unsubscribeLocale();
|
||||
this.unsubscribeLocale = null;
|
||||
}
|
||||
if (this.unsubscribeTheme) {
|
||||
this.unsubscribeTheme();
|
||||
this.unsubscribeTheme = null;
|
||||
}
|
||||
this.element.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 DOM
|
||||
*/
|
||||
private createDom(): HTMLElement {
|
||||
const root = document.createElement('div');
|
||||
root.className = 'section-axis-panel';
|
||||
|
||||
// 第一行:隐藏、反向
|
||||
const row1 = document.createElement('div');
|
||||
row1.className = 'section-axis-row-1';
|
||||
|
||||
this.hideBtn = this.createButton(
|
||||
'hide',
|
||||
'<svg viewBox="0 0 24 24"><path fill="currentColor" d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46A11.804 11.804 0 0 0 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z"/></svg>',
|
||||
() => this.handleHideToggle()
|
||||
);
|
||||
|
||||
this.reverseBtn = this.createButton(
|
||||
'reverse',
|
||||
'<svg viewBox="0 0 24 24"><path fill="currentColor" d="M9 3L5 6.99h3V14h2V6.99h3L9 3zm7 14.01V10h-2v7.01h-3L15 21l4-3.99h-3z"/></svg>',
|
||||
() => this.handleReverse()
|
||||
);
|
||||
|
||||
row1.appendChild(this.hideBtn);
|
||||
row1.appendChild(this.reverseBtn);
|
||||
|
||||
// 第二行:X、Y、Z
|
||||
const row2 = document.createElement('div');
|
||||
row2.className = 'section-axis-row-2';
|
||||
|
||||
this.axisXBtn = this.createAxisButton('axisX', 'X', () => this.handleAxisChange('x'));
|
||||
this.axisYBtn = this.createAxisButton('axisY', 'Y', () => this.handleAxisChange('y'));
|
||||
this.axisZBtn = this.createAxisButton('axisZ', 'Z', () => this.handleAxisChange('z'));
|
||||
|
||||
row2.appendChild(this.axisXBtn);
|
||||
row2.appendChild(this.axisYBtn);
|
||||
row2.appendChild(this.axisZBtn);
|
||||
|
||||
root.appendChild(row1);
|
||||
root.appendChild(row2);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建按钮(带图标)
|
||||
*/
|
||||
private createButton(
|
||||
type: 'hide' | 'reverse',
|
||||
iconSvg: string,
|
||||
onClick: () => void
|
||||
): HTMLButtonElement {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'section-axis-btn';
|
||||
|
||||
// 图标
|
||||
const icon = document.createElement('div');
|
||||
icon.className = 'section-axis-btn-icon';
|
||||
icon.innerHTML = iconSvg;
|
||||
btn.appendChild(icon);
|
||||
|
||||
// 标签
|
||||
const label = document.createElement('div');
|
||||
label.className = 'section-axis-btn-label';
|
||||
btn.appendChild(label);
|
||||
|
||||
// 保存 label 引用
|
||||
if (type === 'hide') {
|
||||
this.hideLabelEl = label;
|
||||
} else if (type === 'reverse') {
|
||||
this.reverseLabelEl = label;
|
||||
}
|
||||
|
||||
// 点击事件
|
||||
btn.addEventListener('click', onClick);
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建轴向按钮(仅文字)
|
||||
*/
|
||||
private createAxisButton(
|
||||
_type: 'axisX' | 'axisY' | 'axisZ',
|
||||
text: string,
|
||||
onClick: () => void
|
||||
): HTMLButtonElement {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'section-axis-btn section-axis-btn-text';
|
||||
|
||||
// 文字标签(既是图标也是标签)
|
||||
const label = document.createElement('div');
|
||||
label.className = 'section-axis-btn-label';
|
||||
label.textContent = text;
|
||||
btn.appendChild(label);
|
||||
|
||||
// 点击事件
|
||||
btn.addEventListener('click', onClick);
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理隐藏按钮切换
|
||||
*/
|
||||
private handleHideToggle(): void {
|
||||
this.isHidden = !this.isHidden;
|
||||
this.updateHideButtonState();
|
||||
|
||||
if (this.options.onHideToggle) {
|
||||
this.options.onHideToggle(this.isHidden);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理反向按钮点击
|
||||
*/
|
||||
private handleReverse(): void {
|
||||
if (this.options.onReverse) {
|
||||
this.options.onReverse();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理轴向切换
|
||||
*/
|
||||
private handleAxisChange(axis: SectionAxis): void {
|
||||
if (this.activeAxis === axis) {
|
||||
return; // 已经是激活状态,不重复触发
|
||||
}
|
||||
|
||||
this.activeAxis = axis;
|
||||
this.updateAxisButtonsState();
|
||||
|
||||
if (this.options.onAxisChange) {
|
||||
this.options.onAxisChange(axis);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新隐藏按钮状态
|
||||
*/
|
||||
private updateHideButtonState(): void {
|
||||
if (this.isHidden) {
|
||||
this.hideBtn.classList.add('active');
|
||||
} else {
|
||||
this.hideBtn.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新轴向按钮状态
|
||||
*/
|
||||
private updateAxisButtonsState(): void {
|
||||
this.axisXBtn.classList.toggle('active', this.activeAxis === 'x');
|
||||
this.axisYBtn.classList.toggle('active', this.activeAxis === 'y');
|
||||
this.axisZBtn.classList.toggle('active', this.activeAxis === 'z');
|
||||
}
|
||||
}
|
||||
36
src/components/section-axis-panel/types.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 轴向类型
|
||||
*/
|
||||
export type SectionAxis = 'x' | 'y' | 'z';
|
||||
|
||||
/**
|
||||
* 轴向剖切面板配置选项
|
||||
*/
|
||||
export interface SectionAxisPanelOptions {
|
||||
/**
|
||||
* 隐藏按钮切换回调
|
||||
* @param isHidden 是否隐藏
|
||||
*/
|
||||
onHideToggle?: (isHidden: boolean) => void;
|
||||
|
||||
/**
|
||||
* 反向按钮回调
|
||||
*/
|
||||
onReverse?: () => void;
|
||||
|
||||
/**
|
||||
* 轴向切换回调
|
||||
* @param axis 当前激活的轴向
|
||||
*/
|
||||
onAxisChange?: (axis: SectionAxis) => void;
|
||||
|
||||
/**
|
||||
* 默认激活的轴向(默认 'x')
|
||||
*/
|
||||
defaultAxis?: SectionAxis;
|
||||
|
||||
/**
|
||||
* 初始隐藏状态(默认 false)
|
||||
*/
|
||||
defaultHidden?: boolean;
|
||||
}
|
||||
154
src/components/section-box-panel/index.css
Normal file
@@ -0,0 +1,154 @@
|
||||
.section-box-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.section-box-row-buttons {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.section-box-btn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
padding: 6px;
|
||||
background: var(--bim-section-box-btn-bg);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: var(--bim-text-color);
|
||||
min-height: 44px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.section-box-btn:hover {
|
||||
background: var(--bim-section-box-btn-hover);
|
||||
}
|
||||
|
||||
.section-box-btn.active {
|
||||
background: var(--bim-section-box-btn-active);
|
||||
border-color: var(--bim-text-active-color);
|
||||
color: var(--bim-text-active-color);
|
||||
}
|
||||
|
||||
.section-box-btn-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--bim-icon-color);
|
||||
}
|
||||
|
||||
.section-box-btn-icon svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.section-box-btn-label {
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 滑块区域 */
|
||||
.section-box-sliders {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
/* 增加行间距防止重叠 */
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.section-box-slider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 鼠标移入当前轴时提升层级 */
|
||||
.section-box-slider:hover {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.section-box-slider-label {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: var(--bim-text-color);
|
||||
min-width: 14px;
|
||||
}
|
||||
|
||||
.section-box-slider-track {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
height: 4px;
|
||||
/* 轨道变细 */
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.section-box-slider-range {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
background: var(--bim-primary-color);
|
||||
border-radius: 2px;
|
||||
pointer-events: none;
|
||||
/* 防止遮挡手柄点击 */
|
||||
}
|
||||
|
||||
.section-box-slider-handle {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 14px;
|
||||
/* 手柄变小 */
|
||||
height: 14px;
|
||||
background: #fff;
|
||||
border: 2px solid var(--bim-primary-color);
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
cursor: grab;
|
||||
z-index: 5;
|
||||
touch-action: none;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.section-box-slider-handle:hover {
|
||||
transform: translate(-50%, -50%) scale(1.2);
|
||||
box-shadow: 0 0 0 4px rgba(24, 144, 255, 0.2);
|
||||
}
|
||||
|
||||
.section-box-slider-handle.dragging {
|
||||
cursor: grabbing;
|
||||
transform: translate(-50%, -50%) scale(1.2);
|
||||
background: var(--bim-primary-color);
|
||||
}
|
||||
|
||||
/* 数值显示气泡 */
|
||||
.section-box-slider-handle::after {
|
||||
content: attr(data-value);
|
||||
position: absolute;
|
||||
top: -22px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: #fff;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font-size: 10px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.section-box-slider-handle:hover::after,
|
||||
.section-box-slider-handle.dragging::after {
|
||||
opacity: 1;
|
||||
}
|
||||
360
src/components/section-box-panel/index.ts
Normal file
@@ -0,0 +1,360 @@
|
||||
import './index.css';
|
||||
import type { ThemeConfig } from '../../themes/types';
|
||||
import { IBimComponent } from '../../types/component';
|
||||
import { localeManager, t } from '../../services/locale';
|
||||
import { themeManager } from '../../services/theme';
|
||||
import type { SectionBoxPanelOptions, SectionBoxRange } from './types';
|
||||
|
||||
const DEFAULT_RANGE: SectionBoxRange = {
|
||||
x: { min: 0, max: 100 },
|
||||
y: { min: 0, max: 100 },
|
||||
z: { min: 0, max: 100 }
|
||||
};
|
||||
|
||||
export class SectionBoxPanel implements IBimComponent {
|
||||
public element!: HTMLElement;
|
||||
private options: SectionBoxPanelOptions;
|
||||
|
||||
private isHidden: boolean = false;
|
||||
private isReversed: boolean = false;
|
||||
private range: SectionBoxRange;
|
||||
|
||||
private hideBtn!: HTMLButtonElement;
|
||||
private reverseBtn!: HTMLButtonElement;
|
||||
private fitBtn!: HTMLButtonElement;
|
||||
private resetBtn!: HTMLButtonElement;
|
||||
|
||||
private hideLabelEl!: HTMLElement;
|
||||
private reverseLabelEl!: HTMLElement;
|
||||
private fitLabelEl!: HTMLElement;
|
||||
private resetLabelEl!: HTMLElement;
|
||||
private xLabelEl!: HTMLElement;
|
||||
private yLabelEl!: HTMLElement;
|
||||
private zLabelEl!: HTMLElement;
|
||||
|
||||
private unsubscribeLocale: (() => void) | null = null;
|
||||
private unsubscribeTheme: (() => void) | null = null;
|
||||
|
||||
private xSlider!: HTMLElement;
|
||||
private ySlider!: HTMLElement;
|
||||
private zSlider!: HTMLElement;
|
||||
|
||||
private xMinHandle!: HTMLElement;
|
||||
private xMaxHandle!: HTMLElement;
|
||||
private yMinHandle!: HTMLElement;
|
||||
private yMaxHandle!: HTMLElement;
|
||||
private zMinHandle!: HTMLElement;
|
||||
private zMaxHandle!: HTMLElement;
|
||||
|
||||
private dragState: {
|
||||
isDragging: boolean;
|
||||
axis: 'x' | 'y' | 'z' | null;
|
||||
handleType: 'min' | 'max' | null;
|
||||
pointerId: number | null;
|
||||
} = {
|
||||
isDragging: false,
|
||||
axis: null,
|
||||
handleType: null,
|
||||
pointerId: null
|
||||
};
|
||||
|
||||
constructor(options: SectionBoxPanelOptions = {}) {
|
||||
this.options = options;
|
||||
this.isHidden = options.defaultHidden ?? false;
|
||||
this.isReversed = options.defaultReversed ?? false;
|
||||
this.range = JSON.parse(JSON.stringify(options.defaultRange ?? DEFAULT_RANGE));
|
||||
}
|
||||
|
||||
public init(): void {
|
||||
this.element = this.createPanel();
|
||||
this.unsubscribeLocale = localeManager.subscribe(() => this.setLocales());
|
||||
this.unsubscribeTheme = themeManager.subscribe((theme) => this.setTheme(theme));
|
||||
|
||||
this.setLocales();
|
||||
this.setTheme(themeManager.getTheme());
|
||||
this.updateButtonStates();
|
||||
this.updateAllSlidersUI();
|
||||
this.setupDragListeners();
|
||||
}
|
||||
|
||||
// --- Public APIs ---
|
||||
|
||||
public setHiddenState(isHidden: boolean): void {
|
||||
this.isHidden = isHidden;
|
||||
this.updateButtonStates();
|
||||
}
|
||||
|
||||
public getHiddenState(): boolean {
|
||||
return this.isHidden;
|
||||
}
|
||||
|
||||
public setReversedState(isReversed: boolean): void {
|
||||
this.isReversed = isReversed;
|
||||
this.updateButtonStates();
|
||||
}
|
||||
|
||||
public getReversedState(): boolean {
|
||||
return this.isReversed;
|
||||
}
|
||||
|
||||
public setRange(range: Partial<SectionBoxRange>): void {
|
||||
if (range.x) this.range.x = { ...this.range.x, ...range.x };
|
||||
if (range.y) this.range.y = { ...this.range.y, ...range.y };
|
||||
if (range.z) this.range.z = { ...this.range.z, ...range.z };
|
||||
this.updateAllSlidersUI();
|
||||
}
|
||||
|
||||
public getRange(): SectionBoxRange {
|
||||
return JSON.parse(JSON.stringify(this.range));
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this.isHidden = this.options.defaultHidden ?? false;
|
||||
this.isReversed = this.options.defaultReversed ?? false;
|
||||
this.range = JSON.parse(JSON.stringify(this.options.defaultRange ?? DEFAULT_RANGE));
|
||||
this.updateButtonStates();
|
||||
this.updateAllSlidersUI();
|
||||
this.options.onReset?.();
|
||||
this.options.onRangeChange?.(this.range);
|
||||
}
|
||||
|
||||
// --- Private Setup ---
|
||||
|
||||
private createPanel(): HTMLElement {
|
||||
const panel = document.createElement('div');
|
||||
panel.className = 'section-box-panel';
|
||||
|
||||
const buttonsContainer = document.createElement('div');
|
||||
buttonsContainer.className = 'section-box-row-buttons';
|
||||
|
||||
this.hideBtn = this.createButton('hide', t('sectionBox.actions.hide'), () => {
|
||||
this.isHidden = !this.isHidden;
|
||||
this.updateButtonStates();
|
||||
this.options.onHideToggle?.(this.isHidden);
|
||||
}, 'hide');
|
||||
|
||||
this.reverseBtn = this.createButton('reverse', t('sectionBox.actions.reverse'), () => {
|
||||
this.isReversed = !this.isReversed;
|
||||
this.updateButtonStates();
|
||||
this.options.onReverseToggle?.(this.isReversed);
|
||||
}, 'reverse');
|
||||
|
||||
this.fitBtn = this.createButton('fit', t('sectionBox.actions.fitToModel'), () => {
|
||||
this.options.onFitToModel?.();
|
||||
}, 'fit');
|
||||
|
||||
this.resetBtn = this.createButton('reset', t('sectionBox.actions.reset'), () => this.reset(), 'reset');
|
||||
|
||||
[this.hideBtn, this.reverseBtn, this.fitBtn, this.resetBtn].forEach(btn => buttonsContainer.appendChild(btn));
|
||||
|
||||
const slidersContainer = document.createElement('div');
|
||||
slidersContainer.className = 'section-box-sliders';
|
||||
slidersContainer.addEventListener('pointerdown', (e) => e.stopPropagation());
|
||||
|
||||
this.xSlider = this.createSlider('x', t('sectionBox.axes.x'));
|
||||
this.ySlider = this.createSlider('y', t('sectionBox.axes.y'));
|
||||
this.zSlider = this.createSlider('z', t('sectionBox.axes.z'));
|
||||
|
||||
[this.xSlider, this.ySlider, this.zSlider].forEach(s => slidersContainer.appendChild(s));
|
||||
|
||||
panel.appendChild(buttonsContainer);
|
||||
panel.appendChild(slidersContainer);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private createButton(type: string, label: string, onClick: () => void, ref?: string): HTMLButtonElement {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'section-box-btn';
|
||||
btn.title = label;
|
||||
|
||||
const icon = document.createElement('div');
|
||||
icon.className = 'section-box-btn-icon';
|
||||
icon.innerHTML = this.getIconSVG(type);
|
||||
|
||||
const labelEl = document.createElement('div');
|
||||
labelEl.className = 'section-box-btn-label';
|
||||
labelEl.textContent = label;
|
||||
|
||||
if (ref === 'hide') this.hideLabelEl = labelEl;
|
||||
else if (ref === 'reverse') this.reverseLabelEl = labelEl;
|
||||
else if (ref === 'fit') this.fitLabelEl = labelEl;
|
||||
else if (ref === 'reset') this.resetLabelEl = labelEl;
|
||||
|
||||
btn.appendChild(icon);
|
||||
btn.appendChild(labelEl);
|
||||
btn.addEventListener('click', onClick);
|
||||
return btn;
|
||||
}
|
||||
|
||||
private createSlider(axis: 'x' | 'y' | 'z', label: string): HTMLElement {
|
||||
const slider = document.createElement('div');
|
||||
slider.className = 'section-box-slider';
|
||||
|
||||
const labelEl = document.createElement('div');
|
||||
labelEl.className = 'section-box-slider-label';
|
||||
labelEl.textContent = label;
|
||||
if (axis === 'x') this.xLabelEl = labelEl;
|
||||
else if (axis === 'y') this.yLabelEl = labelEl;
|
||||
else this.zLabelEl = labelEl;
|
||||
|
||||
const track = document.createElement('div');
|
||||
track.className = 'section-box-slider-track';
|
||||
|
||||
const range = document.createElement('div');
|
||||
range.className = 'section-box-slider-range';
|
||||
|
||||
const minHandle = document.createElement('div');
|
||||
minHandle.className = 'section-box-slider-handle';
|
||||
minHandle.setAttribute('data-axis', axis);
|
||||
minHandle.setAttribute('data-handle', 'min');
|
||||
|
||||
const maxHandle = document.createElement('div');
|
||||
maxHandle.className = 'section-box-slider-handle';
|
||||
maxHandle.setAttribute('data-axis', axis);
|
||||
maxHandle.setAttribute('data-handle', 'max');
|
||||
|
||||
track.append(range, minHandle, maxHandle);
|
||||
slider.append(labelEl, track);
|
||||
|
||||
if (axis === 'x') { this.xMinHandle = minHandle; this.xMaxHandle = maxHandle; }
|
||||
else if (axis === 'y') { this.yMinHandle = minHandle; this.yMaxHandle = maxHandle; }
|
||||
else { this.zMinHandle = minHandle; this.zMaxHandle = maxHandle; }
|
||||
|
||||
return slider;
|
||||
}
|
||||
|
||||
private setupDragListeners(): void {
|
||||
const handles = [this.xMinHandle, this.xMaxHandle, this.yMinHandle, this.yMaxHandle, this.zMinHandle, this.zMaxHandle];
|
||||
|
||||
handles.forEach(handle => {
|
||||
handle.addEventListener('pointerdown', (e: PointerEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// 核心锁定:确保后续所有移动事件都只发给这个手柄
|
||||
handle.setPointerCapture(e.pointerId);
|
||||
|
||||
this.dragState = {
|
||||
isDragging: true,
|
||||
axis: handle.getAttribute('data-axis') as 'x' | 'y' | 'z',
|
||||
handleType: handle.getAttribute('data-handle') as 'min' | 'max',
|
||||
pointerId: e.pointerId
|
||||
};
|
||||
|
||||
handle.classList.add('dragging');
|
||||
(handle.closest('.section-box-slider') as HTMLElement).style.zIndex = '100';
|
||||
});
|
||||
|
||||
handle.addEventListener('pointermove', (e: PointerEvent) => {
|
||||
if (this.dragState.isDragging && this.dragState.pointerId === e.pointerId) {
|
||||
this.onDragging(e);
|
||||
}
|
||||
});
|
||||
|
||||
const stop = (e: PointerEvent) => {
|
||||
if (this.dragState.isDragging && this.dragState.pointerId === e.pointerId) {
|
||||
handle.releasePointerCapture(e.pointerId);
|
||||
(handle.closest('.section-box-slider') as HTMLElement).style.zIndex = '';
|
||||
handle.classList.remove('dragging');
|
||||
this.dragState.isDragging = false;
|
||||
this.dragState.pointerId = null;
|
||||
}
|
||||
};
|
||||
|
||||
handle.addEventListener('pointerup', stop);
|
||||
handle.addEventListener('pointercancel', stop);
|
||||
});
|
||||
}
|
||||
|
||||
private onDragging(e: PointerEvent): void {
|
||||
const { axis, handleType } = this.dragState;
|
||||
if (!axis || !handleType) return;
|
||||
|
||||
const sliderEl = axis === 'x' ? this.xSlider : (axis === 'y' ? this.ySlider : this.zSlider);
|
||||
const track = sliderEl.querySelector('.section-box-slider-track') as HTMLElement;
|
||||
const rect = track.getBoundingClientRect();
|
||||
|
||||
let percentage = ((e.clientX - rect.left) / rect.width) * 100;
|
||||
percentage = Math.max(0, Math.min(100, percentage));
|
||||
|
||||
const current = this.range[axis];
|
||||
if (handleType === 'min') {
|
||||
current.min = Math.min(percentage, current.max);
|
||||
} else {
|
||||
current.max = Math.max(percentage, current.min);
|
||||
}
|
||||
|
||||
this.updateSliderUI(axis);
|
||||
this.options.onRangeChange?.(this.range);
|
||||
}
|
||||
|
||||
private updateSliderUI(axis: 'x' | 'y' | 'z'): void {
|
||||
const range = this.range[axis];
|
||||
const minH = axis === 'x' ? this.xMinHandle : (axis === 'y' ? this.yMinHandle : this.zMinHandle);
|
||||
const maxH = axis === 'x' ? this.xMaxHandle : (axis === 'y' ? this.yMaxHandle : this.zMaxHandle);
|
||||
const slider = axis === 'x' ? this.xSlider : (axis === 'y' ? this.ySlider : this.zSlider);
|
||||
const rangeEl = slider.querySelector('.section-box-slider-range') as HTMLElement;
|
||||
|
||||
minH.style.left = `${range.min}%`;
|
||||
maxH.style.left = `${range.max}%`;
|
||||
rangeEl.style.left = `${range.min}%`;
|
||||
rangeEl.style.width = `${range.max - range.min}%`;
|
||||
|
||||
minH.setAttribute('data-value', Math.round(range.min).toString());
|
||||
maxH.setAttribute('data-value', Math.round(range.max).toString());
|
||||
}
|
||||
|
||||
private updateAllSlidersUI(): void {
|
||||
['x', 'y', 'z'].forEach((a: any) => this.updateSliderUI(a));
|
||||
}
|
||||
|
||||
private updateButtonStates(): void {
|
||||
if (this.hideBtn) this.hideBtn.classList.toggle('active', this.isHidden);
|
||||
if (this.reverseBtn) this.reverseBtn.classList.toggle('active', this.isReversed);
|
||||
}
|
||||
|
||||
private getIconSVG(type: string): string {
|
||||
const icons: Record<string, string> = {
|
||||
hide: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z"/></svg>',
|
||||
reverse: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M9 3L5 6.99h3V14h2V6.99h3L9 3zm7 14.01V10h-2v7.01h-3L15 21l4-3.99h-3z"/></svg>',
|
||||
fit: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M13 3h-2v10h2V3zm4 8h2v10h-2V11zm-8 4H7v6h2v-6zm-4 3H3v3h2v-3z"/></svg>',
|
||||
reset: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>'
|
||||
};
|
||||
return icons[type] || '';
|
||||
}
|
||||
|
||||
public setLocales(): void {
|
||||
if (!this.hideLabelEl) return;
|
||||
this.hideLabelEl.textContent = t('sectionBox.actions.hide');
|
||||
this.reverseLabelEl.textContent = t('sectionBox.actions.reverse');
|
||||
this.fitLabelEl.textContent = t('sectionBox.actions.fitToModel');
|
||||
this.resetLabelEl.textContent = t('sectionBox.actions.reset');
|
||||
this.xLabelEl.textContent = t('sectionBox.axes.x');
|
||||
this.yLabelEl.textContent = t('sectionBox.axes.y');
|
||||
this.zLabelEl.textContent = t('sectionBox.axes.z');
|
||||
this.hideBtn.title = t('sectionBox.actions.hide');
|
||||
this.reverseBtn.title = t('sectionBox.actions.reverse');
|
||||
this.fitBtn.title = t('sectionBox.actions.fitToModel');
|
||||
this.resetBtn.title = t('sectionBox.actions.reset');
|
||||
}
|
||||
|
||||
public setTheme(theme: ThemeConfig): void {
|
||||
if (!this.element) return;
|
||||
const style = this.element.style;
|
||||
style.setProperty('--bim-section-box-btn-bg', theme.componentBackground ?? 'rgba(255, 255, 255, 0.06)');
|
||||
style.setProperty('--bim-section-box-btn-hover', theme.componentHover ?? 'rgba(255, 255, 255, 0.10)');
|
||||
style.setProperty('--bim-section-box-btn-active', theme.componentActive ?? 'rgba(255, 255, 255, 0.14)');
|
||||
style.setProperty('--bim-primary-color', theme.primary ?? '#1890ff');
|
||||
style.setProperty('--bim-icon-color', theme.icon ?? '#ccc');
|
||||
style.setProperty('--bim-text-color', theme.textSecondary ?? 'rgba(255, 255, 255, 0.90)');
|
||||
style.setProperty('--bim-text-active-color', theme.textPrimary ?? '#fff');
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this.unsubscribeLocale?.();
|
||||
this.unsubscribeTheme?.();
|
||||
if (this.element && this.element.parentElement) {
|
||||
this.element.parentElement.removeChild(this.element);
|
||||
}
|
||||
}
|
||||
}
|
||||
69
src/components/section-box-panel/types.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 剖切盒轴向范围
|
||||
*/
|
||||
export interface SectionBoxAxisRange {
|
||||
/** 最小值(0-100的百分比) */
|
||||
min: number;
|
||||
/** 最大值(0-100的百分比) */
|
||||
max: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 剖切盒范围数据
|
||||
*/
|
||||
export interface SectionBoxRange {
|
||||
/** X轴范围 */
|
||||
x: SectionBoxAxisRange;
|
||||
/** Y轴范围 */
|
||||
y: SectionBoxAxisRange;
|
||||
/** Z轴范围 */
|
||||
z: SectionBoxAxisRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* 剖切盒面板配置选项
|
||||
*/
|
||||
export interface SectionBoxPanelOptions {
|
||||
/**
|
||||
* 隐藏按钮切换回调
|
||||
* @param isHidden 是否隐藏剖切盒
|
||||
*/
|
||||
onHideToggle?: (isHidden: boolean) => void;
|
||||
|
||||
/**
|
||||
* 反向按钮切换回调
|
||||
* @param isReversed 是否反向
|
||||
*/
|
||||
onReverseToggle?: (isReversed: boolean) => void;
|
||||
|
||||
/**
|
||||
* 适应到模型按钮回调
|
||||
*/
|
||||
onFitToModel?: () => void;
|
||||
|
||||
/**
|
||||
* 重置按钮回调
|
||||
*/
|
||||
onReset?: () => void;
|
||||
|
||||
/**
|
||||
* 范围变化回调
|
||||
* @param range 当前范围值
|
||||
*/
|
||||
onRangeChange?: (range: SectionBoxRange) => void;
|
||||
|
||||
/**
|
||||
* 默认隐藏状态(默认 false)
|
||||
*/
|
||||
defaultHidden?: boolean;
|
||||
|
||||
/**
|
||||
* 默认反向状态(默认 false)
|
||||
*/
|
||||
defaultReversed?: boolean;
|
||||
|
||||
/**
|
||||
* 默认范围值
|
||||
*/
|
||||
defaultRange?: SectionBoxRange;
|
||||
}
|
||||
60
src/components/section-plane-panel/index.css
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 拾取面剖切面板样式
|
||||
*/
|
||||
|
||||
.section-plane-panel {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 12px 12px 16px 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.section-plane-btn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
padding: 8px;
|
||||
border: 1px solid transparent;
|
||||
background: var(--bim-section-btn-bg, rgba(255, 255, 255, 0.06));
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
|
||||
min-width: 60px;
|
||||
outline: none;
|
||||
color: var(--bim-text-color, rgba(255, 255, 255, 0.90));
|
||||
}
|
||||
|
||||
.section-plane-btn:hover {
|
||||
background: var(--bim-section-btn-hover, rgba(255, 255, 255, 0.10));
|
||||
}
|
||||
|
||||
.section-plane-btn:active {
|
||||
background: var(--bim-section-btn-active, rgba(255, 255, 255, 0.14));
|
||||
border-color: var(--bim-text-active-color, #fff);
|
||||
color: var(--bim-text-active-color, #fff);
|
||||
}
|
||||
|
||||
.section-plane-btn-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--bim-icon-color, #ccc);
|
||||
}
|
||||
|
||||
.section-plane-btn-icon svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.section-plane-btn-label {
|
||||
font-size: 12px;
|
||||
color: inherit;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
}
|
||||
174
src/components/section-plane-panel/index.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import './index.css';
|
||||
import type { ThemeConfig } from '../../themes/types';
|
||||
import { IBimComponent } from '../../types/component';
|
||||
import { localeManager, t } from '../../services/locale';
|
||||
import { themeManager } from '../../services/theme';
|
||||
import type { SectionPlanePanelOptions } from './types';
|
||||
|
||||
/**
|
||||
* 拾取面剖切面板组件
|
||||
* 包含三个操作按钮:隐藏、反向、重置
|
||||
*/
|
||||
export class SectionPlanePanel implements IBimComponent {
|
||||
public element: HTMLElement;
|
||||
private options: SectionPlanePanelOptions;
|
||||
|
||||
// DOM 引用
|
||||
private hideBtn!: HTMLButtonElement;
|
||||
private reverseBtn!: HTMLButtonElement;
|
||||
private resetBtn!: HTMLButtonElement;
|
||||
private hideLabelEl!: HTMLElement;
|
||||
private reverseLabelEl!: HTMLElement;
|
||||
private resetLabelEl!: HTMLElement;
|
||||
|
||||
// 订阅清理
|
||||
private unsubscribeLocale: (() => void) | null = null;
|
||||
private unsubscribeTheme: (() => void) | null = null;
|
||||
|
||||
constructor(options: SectionPlanePanelOptions = {}) {
|
||||
this.options = options;
|
||||
this.element = this.createDom();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化组件
|
||||
*/
|
||||
public init(): void {
|
||||
// 订阅语言变更
|
||||
this.unsubscribeLocale = localeManager.subscribe(() => {
|
||||
this.setLocales();
|
||||
});
|
||||
|
||||
// 订阅主题变更
|
||||
this.unsubscribeTheme = themeManager.subscribe((theme) => {
|
||||
this.setTheme(theme);
|
||||
});
|
||||
|
||||
// 初始应用
|
||||
this.setLocales();
|
||||
this.setTheme(themeManager.getTheme());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置主题
|
||||
*/
|
||||
public setTheme(theme: ThemeConfig): void {
|
||||
const style = this.element.style;
|
||||
style.setProperty('--bim-section-btn-bg', theme.componentBackground ?? 'rgba(255, 255, 255, 0.06)');
|
||||
style.setProperty('--bim-section-btn-hover', theme.componentHover ?? 'rgba(255, 255, 255, 0.10)');
|
||||
style.setProperty('--bim-section-btn-active', theme.componentActive ?? 'rgba(255, 255, 255, 0.14)');
|
||||
style.setProperty('--bim-primary-color', theme.primary ?? '#1890ff');
|
||||
style.setProperty('--bim-icon-color', theme.icon ?? '#ccc');
|
||||
style.setProperty('--bim-text-color', theme.textSecondary ?? 'rgba(255, 255, 255, 0.90)');
|
||||
style.setProperty('--bim-text-active-color', theme.textPrimary ?? '#fff');
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置语言
|
||||
*/
|
||||
public setLocales(): void {
|
||||
this.hideLabelEl.textContent = t('sectionPlane.actions.hide');
|
||||
this.reverseLabelEl.textContent = t('sectionPlane.actions.reverse');
|
||||
this.resetLabelEl.textContent = t('sectionPlane.actions.reset');
|
||||
|
||||
this.hideBtn.title = t('sectionPlane.actions.hide');
|
||||
this.reverseBtn.title = t('sectionPlane.actions.reverse');
|
||||
this.resetBtn.title = t('sectionPlane.actions.reset');
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁组件
|
||||
*/
|
||||
public destroy(): void {
|
||||
if (this.unsubscribeLocale) {
|
||||
this.unsubscribeLocale();
|
||||
this.unsubscribeLocale = null;
|
||||
}
|
||||
if (this.unsubscribeTheme) {
|
||||
this.unsubscribeTheme();
|
||||
this.unsubscribeTheme = null;
|
||||
}
|
||||
this.element.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 DOM
|
||||
*/
|
||||
private createDom(): HTMLElement {
|
||||
const root = document.createElement('div');
|
||||
root.className = 'section-plane-panel';
|
||||
|
||||
// 隐藏按钮
|
||||
this.hideBtn = this.createButton(
|
||||
'hide',
|
||||
'<svg viewBox="0 0 24 24"><path fill="currentColor" d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46A11.804 11.804 0 0 0 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z"/></svg>',
|
||||
() => {
|
||||
if (this.options.onHide) {
|
||||
this.options.onHide();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 反向按钮
|
||||
this.reverseBtn = this.createButton(
|
||||
'reverse',
|
||||
'<svg viewBox="0 0 24 24"><path fill="currentColor" d="M9 3L5 6.99h3V14h2V6.99h3L9 3zm7 14.01V10h-2v7.01h-3L15 21l4-3.99h-3z"/></svg>',
|
||||
() => {
|
||||
if (this.options.onReverse) {
|
||||
this.options.onReverse();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 重置按钮
|
||||
this.resetBtn = this.createButton(
|
||||
'reset',
|
||||
'<svg viewBox="0 0 24 24"><path fill="currentColor" d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>',
|
||||
() => {
|
||||
if (this.options.onReset) {
|
||||
this.options.onReset();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
root.appendChild(this.hideBtn);
|
||||
root.appendChild(this.reverseBtn);
|
||||
root.appendChild(this.resetBtn);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建按钮
|
||||
*/
|
||||
private createButton(type: 'hide' | 'reverse' | 'reset', iconSvg: string, onClick: () => void): HTMLButtonElement {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'section-plane-btn';
|
||||
|
||||
// 图标
|
||||
const icon = document.createElement('div');
|
||||
icon.className = 'section-plane-btn-icon';
|
||||
icon.innerHTML = iconSvg;
|
||||
btn.appendChild(icon);
|
||||
|
||||
// 标签
|
||||
const label = document.createElement('div');
|
||||
label.className = 'section-plane-btn-label';
|
||||
btn.appendChild(label);
|
||||
|
||||
// 保存 label 引用
|
||||
if (type === 'hide') {
|
||||
this.hideLabelEl = label;
|
||||
} else if (type === 'reverse') {
|
||||
this.reverseLabelEl = label;
|
||||
} else {
|
||||
this.resetLabelEl = label;
|
||||
}
|
||||
|
||||
// 点击事件
|
||||
btn.addEventListener('click', onClick);
|
||||
|
||||
return btn;
|
||||
}
|
||||
}
|
||||
19
src/components/section-plane-panel/types.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 拾取面剖切面板配置选项
|
||||
*/
|
||||
export interface SectionPlanePanelOptions {
|
||||
/**
|
||||
* 隐藏按钮回调
|
||||
*/
|
||||
onHide?: () => void;
|
||||
|
||||
/**
|
||||
* 反向按钮回调
|
||||
*/
|
||||
onReverse?: () => void;
|
||||
|
||||
/**
|
||||
* 重置按钮回调
|
||||
*/
|
||||
onReset?: () => void;
|
||||
}
|
||||
@@ -10,6 +10,7 @@ export const enUS: TranslationDictionary = {
|
||||
toolbar: {
|
||||
home: 'Home',
|
||||
measure: 'Measure',
|
||||
zoomBox: 'Zoom Box',
|
||||
info: 'Info',
|
||||
location: 'Location',
|
||||
setting: 'Settings',
|
||||
@@ -18,6 +19,10 @@ export const enUS: TranslationDictionary = {
|
||||
walkBird: 'Bird Eye',
|
||||
walkMenu: 'Menu',
|
||||
tree: 'Tree',
|
||||
section: 'Section',
|
||||
sectionPlane: 'Plane Section',
|
||||
sectionAxis: 'Axis Section',
|
||||
sectionBox: 'Section Box'
|
||||
},
|
||||
dialog: {
|
||||
testTitle: 'Test Dialog',
|
||||
@@ -102,5 +107,37 @@ export const enUS: TranslationDictionary = {
|
||||
save: 'Save',
|
||||
cancel: 'Cancel',
|
||||
}
|
||||
},
|
||||
sectionPlane: {
|
||||
dialogTitle: 'Plane Section',
|
||||
actions: {
|
||||
hide: 'Hide',
|
||||
reverse: 'Reverse',
|
||||
reset: 'Reset'
|
||||
}
|
||||
},
|
||||
sectionAxis: {
|
||||
dialogTitle: 'Axis Section',
|
||||
actions: {
|
||||
hide: 'Hide',
|
||||
reverse: 'Reverse',
|
||||
axisX: 'X',
|
||||
axisY: 'Y',
|
||||
axisZ: 'Z'
|
||||
}
|
||||
},
|
||||
sectionBox: {
|
||||
dialogTitle: 'Section Box',
|
||||
actions: {
|
||||
hide: 'Hide',
|
||||
reverse: 'Reverse',
|
||||
fitToModel: 'Fit',
|
||||
reset: 'Reset'
|
||||
},
|
||||
axes: {
|
||||
x: 'X',
|
||||
y: 'Y',
|
||||
z: 'Z'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface TranslationDictionary {
|
||||
toolbar: {
|
||||
home: string;
|
||||
measure: string;
|
||||
zoomBox: string;
|
||||
info: string;
|
||||
location: string;
|
||||
setting: string;
|
||||
@@ -21,6 +22,10 @@ export interface TranslationDictionary {
|
||||
walkPerson: string;
|
||||
walkBird: string;
|
||||
tree: string;
|
||||
section: string;
|
||||
sectionPlane: string;
|
||||
sectionAxis: string;
|
||||
sectionBox: string;
|
||||
};
|
||||
panel: {
|
||||
property: {
|
||||
@@ -125,7 +130,39 @@ export interface TranslationDictionary {
|
||||
save: string;
|
||||
cancel: string;
|
||||
};
|
||||
}
|
||||
};
|
||||
sectionPlane: {
|
||||
dialogTitle: string;
|
||||
actions: {
|
||||
hide: string;
|
||||
reverse: string;
|
||||
reset: string;
|
||||
};
|
||||
};
|
||||
sectionAxis: {
|
||||
dialogTitle: string;
|
||||
actions: {
|
||||
hide: string;
|
||||
reverse: string;
|
||||
axisX: string;
|
||||
axisY: string;
|
||||
axisZ: string;
|
||||
};
|
||||
};
|
||||
sectionBox: {
|
||||
dialogTitle: string;
|
||||
actions: {
|
||||
hide: string;
|
||||
reverse: string;
|
||||
fitToModel: string;
|
||||
reset: string;
|
||||
};
|
||||
axes: {
|
||||
x: string;
|
||||
y: string;
|
||||
z: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,7 @@ export const zhCN: TranslationDictionary = {
|
||||
toolbar: {
|
||||
home: '首页',
|
||||
measure: '测量',
|
||||
zoomBox: '选框放大',
|
||||
info: '信息',
|
||||
location: '定位',
|
||||
setting: '设置',
|
||||
@@ -17,7 +18,11 @@ export const zhCN: TranslationDictionary = {
|
||||
walkMenu: '漫游菜单',
|
||||
walkPerson: '第一人称',
|
||||
walkBird: '第三人称',
|
||||
tree: '模型树'
|
||||
tree: '模型树',
|
||||
section: '剖切',
|
||||
sectionPlane: '拾取面剖切',
|
||||
sectionAxis: '轴向剖切',
|
||||
sectionBox: '剖切盒'
|
||||
},
|
||||
dialog: {
|
||||
testTitle: '测试弹窗',
|
||||
@@ -102,5 +107,37 @@ export const zhCN: TranslationDictionary = {
|
||||
save: '保存设置',
|
||||
cancel: '取消',
|
||||
}
|
||||
},
|
||||
sectionPlane: {
|
||||
dialogTitle: '拾取面剖切',
|
||||
actions: {
|
||||
hide: '隐藏',
|
||||
reverse: '反向',
|
||||
reset: '重置'
|
||||
}
|
||||
},
|
||||
sectionAxis: {
|
||||
dialogTitle: '轴向剖切',
|
||||
actions: {
|
||||
hide: '隐藏',
|
||||
reverse: '反向',
|
||||
axisX: 'X',
|
||||
axisY: 'Y',
|
||||
axisZ: 'Z'
|
||||
}
|
||||
},
|
||||
sectionBox: {
|
||||
dialogTitle: '剖切盒',
|
||||
actions: {
|
||||
hide: '隐藏',
|
||||
reverse: '反向',
|
||||
fitToModel: '适应',
|
||||
reset: '重置'
|
||||
},
|
||||
axes: {
|
||||
x: 'X',
|
||||
y: 'Y',
|
||||
z: 'Z'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
135
src/managers/section-axis-dialog-manager.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { BimComponent } from '../core/component';
|
||||
import { BimEngine } from '../bim-engine';
|
||||
import { BimDialog } from '../components/dialog';
|
||||
import { SectionAxisPanel } from '../components/section-axis-panel';
|
||||
import type { SectionAxis } from '../components/section-axis-panel/types';
|
||||
|
||||
/**
|
||||
* 轴向剖切弹窗管理器
|
||||
*/
|
||||
export class SectionAxisDialogManager extends BimComponent {
|
||||
private dialogId = 'section-axis-dialog';
|
||||
private dialog: BimDialog | null = null;
|
||||
private panel: SectionAxisPanel | null = null;
|
||||
|
||||
constructor(engine: BimEngine) {
|
||||
super(engine);
|
||||
}
|
||||
|
||||
public init(): void {
|
||||
// 可以在这里监听事件
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示弹窗
|
||||
*/
|
||||
public show(): void {
|
||||
if (!this.engine.dialog || !this.engine.container) {
|
||||
console.warn('Dialog manager or container is not initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果已打开,先销毁
|
||||
this.destroy();
|
||||
|
||||
// 创建面板
|
||||
this.panel = new SectionAxisPanel({
|
||||
defaultAxis: 'x',
|
||||
defaultHidden: false,
|
||||
onHideToggle: (isHidden) => {
|
||||
console.log('[SectionAxisDialogManager] 隐藏切换:', isHidden);
|
||||
// TODO: 实现隐藏/显示剖切面的逻辑
|
||||
},
|
||||
onReverse: () => {
|
||||
console.log('[SectionAxisDialogManager] 反向剖切');
|
||||
// TODO: 实现反向剖切的逻辑
|
||||
},
|
||||
onAxisChange: (axis) => {
|
||||
console.log('[SectionAxisDialogManager] 切换轴向:', axis);
|
||||
// TODO: 实现轴向切换的逻辑
|
||||
}
|
||||
});
|
||||
this.panel.init();
|
||||
|
||||
// 创建弹窗
|
||||
const dialogWidth = 240;
|
||||
const paddingRight = 20;
|
||||
const paddingBottom = 50;
|
||||
const container = this.engine.container;
|
||||
const containerWidth = container.clientWidth;
|
||||
const containerHeight = container.clientHeight;
|
||||
const x = containerWidth - dialogWidth - paddingRight;
|
||||
const y = containerHeight - paddingBottom - 200; // 临时y值,会被fitHeight调整
|
||||
|
||||
this.dialog = this.engine.dialog.create({
|
||||
id: this.dialogId,
|
||||
title: 'sectionAxis.dialogTitle',
|
||||
width: dialogWidth,
|
||||
height: 'auto', // 自动高度
|
||||
position: { x, y },
|
||||
draggable: true,
|
||||
resizable: false,
|
||||
content: this.panel.element,
|
||||
onClose: () => {
|
||||
this.engine.toolbar?.setBtnActive('section-axis', false);
|
||||
this.hide();
|
||||
}
|
||||
});
|
||||
this.dialog.init();
|
||||
|
||||
// 自适应高度
|
||||
this.dialog.fitHeight(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏弹窗
|
||||
*/
|
||||
public hide(): void {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取隐藏状态
|
||||
*/
|
||||
public getHiddenState(): boolean {
|
||||
return this.panel?.getHiddenState() ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置隐藏状态
|
||||
*/
|
||||
public setHiddenState(isHidden: boolean): void {
|
||||
this.panel?.setHiddenState(isHidden);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前激活的轴向
|
||||
*/
|
||||
public getActiveAxis(): SectionAxis {
|
||||
return this.panel?.getActiveAxis() ?? 'x';
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置激活的轴向
|
||||
*/
|
||||
public setActiveAxis(axis: SectionAxis): void {
|
||||
this.panel?.setActiveAxis(axis);
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁弹窗和面板
|
||||
*/
|
||||
public destroy(): void {
|
||||
// 关闭弹窗
|
||||
if (this.dialog) {
|
||||
this.dialog.destroy();
|
||||
this.dialog = null;
|
||||
}
|
||||
|
||||
// 销毁面板
|
||||
if (this.panel) {
|
||||
this.panel.destroy();
|
||||
this.panel = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
159
src/managers/section-box-dialog-manager.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import { BimComponent } from '../core/component';
|
||||
import { BimEngine } from '../bim-engine';
|
||||
import { BimDialog } from '../components/dialog';
|
||||
import { SectionBoxPanel } from '../components/section-box-panel';
|
||||
import type { SectionBoxRange } from '../components/section-box-panel/types';
|
||||
|
||||
/**
|
||||
* 剖切盒弹窗管理器
|
||||
*/
|
||||
export class SectionBoxDialogManager extends BimComponent {
|
||||
private dialogId = 'section-box-dialog';
|
||||
private dialog: BimDialog | null = null;
|
||||
private panel: SectionBoxPanel | null = null;
|
||||
|
||||
constructor(engine: BimEngine) {
|
||||
super(engine);
|
||||
}
|
||||
|
||||
public init(): void {
|
||||
// 可以在这里监听事件
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示弹窗
|
||||
*/
|
||||
public show(): void {
|
||||
if (!this.engine.dialog || !this.engine.container) {
|
||||
console.warn('Dialog manager or container is not initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果已打开,先销毁
|
||||
this.destroy();
|
||||
|
||||
// 创建面板
|
||||
this.panel = new SectionBoxPanel({
|
||||
defaultHidden: false,
|
||||
defaultReversed: false,
|
||||
onHideToggle: (isHidden) => {
|
||||
console.log('[SectionBoxDialogManager] 隐藏切换:', isHidden);
|
||||
// TODO: 实现隐藏/显示剖切盒的逻辑
|
||||
},
|
||||
onReverseToggle: (isReversed) => {
|
||||
console.log('[SectionBoxDialogManager] 反向切换:', isReversed);
|
||||
// TODO: 实现反向剖切的逻辑
|
||||
},
|
||||
onFitToModel: () => {
|
||||
console.log('[SectionBoxDialogManager] 适应到模型');
|
||||
// TODO: 实现自动适应模型的逻辑
|
||||
},
|
||||
onReset: () => {
|
||||
console.log('[SectionBoxDialogManager] 重置');
|
||||
// 注意:不要在这里调用 panel.reset(),会造成无限递归
|
||||
// panel 的 reset 按钮已经在内部处理了状态重置
|
||||
// TODO: 这里只需要通知 3D 引擎重置剖切盒即可
|
||||
},
|
||||
onRangeChange: (range) => {
|
||||
console.log('[SectionBoxDialogManager] 范围变化:', range);
|
||||
// TODO: 实现范围变化的逻辑
|
||||
}
|
||||
});
|
||||
this.panel.init();
|
||||
|
||||
// 创建弹窗
|
||||
const dialogWidth = 280;
|
||||
const paddingRight = 20;
|
||||
const paddingBottom = 50;
|
||||
const container = this.engine.container;
|
||||
const containerWidth = container.clientWidth;
|
||||
const containerHeight = container.clientHeight;
|
||||
const x = containerWidth - dialogWidth - paddingRight;
|
||||
const y = containerHeight - paddingBottom - 300; // 临时y值,会被fitHeight调整
|
||||
|
||||
this.dialog = this.engine.dialog.create({
|
||||
id: this.dialogId,
|
||||
title: 'sectionBox.dialogTitle',
|
||||
width: dialogWidth,
|
||||
height: 'auto',
|
||||
position: { x, y },
|
||||
draggable: true,
|
||||
resizable: false,
|
||||
content: this.panel.element,
|
||||
onClose: () => {
|
||||
this.engine.toolbar?.setBtnActive('section-box', false);
|
||||
this.hide();
|
||||
}
|
||||
});
|
||||
this.dialog.init();
|
||||
|
||||
// 自适应高度
|
||||
this.dialog.fitHeight(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏弹窗
|
||||
*/
|
||||
public hide(): void {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取隐藏状态
|
||||
*/
|
||||
public getHiddenState(): boolean {
|
||||
return this.panel?.getHiddenState() ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置隐藏状态
|
||||
*/
|
||||
public setHiddenState(isHidden: boolean): void {
|
||||
this.panel?.setHiddenState(isHidden);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取反向状态
|
||||
*/
|
||||
public getReversedState(): boolean {
|
||||
return this.panel?.getReversedState() ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置反向状态
|
||||
*/
|
||||
public setReversedState(isReversed: boolean): void {
|
||||
this.panel?.setReversedState(isReversed);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取范围值
|
||||
*/
|
||||
public getRange(): SectionBoxRange | null {
|
||||
return this.panel?.getRange() ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置范围值
|
||||
*/
|
||||
public setRange(range: Partial<SectionBoxRange>): void {
|
||||
this.panel?.setRange(range);
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁弹窗和面板
|
||||
*/
|
||||
public destroy(): void {
|
||||
// 关闭弹窗
|
||||
if (this.dialog) {
|
||||
this.dialog.destroy();
|
||||
this.dialog = null;
|
||||
}
|
||||
|
||||
// 销毁面板
|
||||
if (this.panel) {
|
||||
this.panel.destroy();
|
||||
this.panel = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
98
src/managers/section-plane-dialog-manager.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { BimComponent } from '../core/component';
|
||||
import { BimEngine } from '../bim-engine';
|
||||
import { BimDialog } from '../components/dialog';
|
||||
import { SectionPlanePanel } from '../components/section-plane-panel';
|
||||
|
||||
/**
|
||||
* 拾取面剖切弹窗管理器
|
||||
*/
|
||||
export class SectionPlaneDialogManager extends BimComponent {
|
||||
private dialogId = 'section-plane-dialog';
|
||||
private dialog: BimDialog | null = null;
|
||||
private panel: SectionPlanePanel | null = null;
|
||||
|
||||
constructor(engine: BimEngine) {
|
||||
super(engine);
|
||||
}
|
||||
|
||||
public init(): void {
|
||||
// 可以在这里监听事件
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示拾取面剖切弹窗
|
||||
*/
|
||||
public show(): void {
|
||||
if (!this.engine.dialog || !this.engine.container) {
|
||||
console.warn('Dialog manager or container is not initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果已打开过,先销毁旧实例
|
||||
this.destroy();
|
||||
|
||||
// 创建面板
|
||||
this.panel = new SectionPlanePanel({
|
||||
onHide: () => {
|
||||
console.log('[SectionPlaneDialogManager] 隐藏');
|
||||
// TODO: 调用引擎的隐藏功能
|
||||
},
|
||||
onReverse: () => {
|
||||
console.log('[SectionPlaneDialogManager] 反向');
|
||||
// TODO: 调用引擎的反向功能
|
||||
},
|
||||
onReset: () => {
|
||||
console.log('[SectionPlaneDialogManager] 重置');
|
||||
// TODO: 调用引擎的重置功能
|
||||
}
|
||||
});
|
||||
this.panel.init();
|
||||
|
||||
// 创建弹窗
|
||||
const dialogWidth = 240;
|
||||
const dialogHeight = 120;
|
||||
const paddingRight = 20;
|
||||
const paddingBottom = 50;
|
||||
const container = this.engine.container;
|
||||
const containerWidth = container.clientWidth;
|
||||
const containerHeight = container.clientHeight;
|
||||
const x = containerWidth - dialogWidth - paddingRight;
|
||||
const y = containerHeight - dialogHeight - paddingBottom;
|
||||
|
||||
this.dialog = this.engine.dialog.create({
|
||||
id: this.dialogId,
|
||||
title: 'sectionPlane.dialogTitle',
|
||||
width: dialogWidth,
|
||||
height: dialogHeight,
|
||||
position: { x, y },
|
||||
draggable: true,
|
||||
resizable: false,
|
||||
content: this.panel.element,
|
||||
onClose: () => {
|
||||
this.engine.toolbar?.setBtnActive('section-plane', false);
|
||||
this.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏弹窗
|
||||
*/
|
||||
public hide(): void {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁弹窗
|
||||
*/
|
||||
public destroy(): void {
|
||||
if (this.dialog) {
|
||||
this.dialog.destroy();
|
||||
this.dialog = null;
|
||||
}
|
||||
if (this.panel) {
|
||||
this.panel.destroy();
|
||||
this.panel = null;
|
||||
}
|
||||
}
|
||||
}
|
||||