增加测量窗口
This commit is contained in:
5326
dist/bim-engine-sdk.es.js
vendored
5326
dist/bim-engine-sdk.es.js
vendored
File diff suppressed because one or more lines are too long
2
dist/bim-engine-sdk.es.js.map
vendored
2
dist/bim-engine-sdk.es.js.map
vendored
File diff suppressed because one or more lines are too long
426
dist/bim-engine-sdk.umd.js
vendored
426
dist/bim-engine-sdk.umd.js
vendored
File diff suppressed because one or more lines are too long
2
dist/bim-engine-sdk.umd.js.map
vendored
2
dist/bim-engine-sdk.umd.js.map
vendored
File diff suppressed because one or more lines are too long
39
dist/index.d.ts
vendored
39
dist/index.d.ts
vendored
@@ -680,6 +680,14 @@ declare type Listener<T = any> = (payload: T) => void;
|
|||||||
*/
|
*/
|
||||||
declare type LocaleType = 'zh-CN' | 'en-US';
|
declare type LocaleType = 'zh-CN' | 'en-US';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测量配置项(由组件内部维护默认值,并读取/写入缓存)
|
||||||
|
*/
|
||||||
|
declare interface MeasureConfig {
|
||||||
|
unit: MeasureUnit;
|
||||||
|
precision: MeasurePrecision;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 测量弹窗管理器
|
* 测量弹窗管理器
|
||||||
*/
|
*/
|
||||||
@@ -687,6 +695,11 @@ declare class MeasureDialogManager extends BimComponent {
|
|||||||
private dialogId;
|
private dialogId;
|
||||||
private dialog;
|
private dialog;
|
||||||
private panel;
|
private panel;
|
||||||
|
/**
|
||||||
|
* 测量配置项(单位/精度)
|
||||||
|
* 说明:MeasurePanel 会自行从缓存加载默认配置,Manager 这里只做“对外读取/设置”的镜像。
|
||||||
|
*/
|
||||||
|
private config;
|
||||||
constructor(engine: BimEngine);
|
constructor(engine: BimEngine);
|
||||||
init(): void;
|
init(): void;
|
||||||
/**
|
/**
|
||||||
@@ -708,6 +721,18 @@ declare class MeasureDialogManager extends BimComponent {
|
|||||||
* @param result 测量结果;传 null 表示清空
|
* @param result 测量结果;传 null 表示清空
|
||||||
*/
|
*/
|
||||||
setResult(result: MeasureResult | null): void;
|
setResult(result: MeasureResult | null): void;
|
||||||
|
/**
|
||||||
|
* 获取测量配置(单位/精度)
|
||||||
|
* - 如果面板存在:返回面板当前配置
|
||||||
|
* - 否则:返回 Manager 缓存的最后一次配置(可能为 null)
|
||||||
|
*/
|
||||||
|
getConfig(): MeasureConfig | null;
|
||||||
|
/**
|
||||||
|
* 设置测量配置(单位/精度)
|
||||||
|
* @param partial 部分更新
|
||||||
|
* @param persist 是否写入缓存(默认 true)
|
||||||
|
*/
|
||||||
|
setConfig(partial: Partial<MeasureConfig>, persist?: boolean): void;
|
||||||
/**
|
/**
|
||||||
* 删除全部(仅清空 UI;真实测量清理逻辑后续再接)
|
* 删除全部(仅清空 UI;真实测量清理逻辑后续再接)
|
||||||
*/
|
*/
|
||||||
@@ -735,6 +760,15 @@ declare class MeasureDialogManager extends BimComponent {
|
|||||||
*/
|
*/
|
||||||
declare type MeasureMode = 'distance' | 'minDistance' | 'angle' | 'elevation' | 'volume' | 'laserDistance' | 'slope' | 'spaceVolume';
|
declare type MeasureMode = 'distance' | 'minDistance' | 'angle' | 'elevation' | 'volume' | 'laserDistance' | 'slope' | 'spaceVolume';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 精度(小数位数)
|
||||||
|
* - 0 -> 0
|
||||||
|
* - 1 -> 0.0
|
||||||
|
* - 2 -> 0.00
|
||||||
|
* - 3 -> 0.000
|
||||||
|
*/
|
||||||
|
declare type MeasurePrecision = 0 | 1 | 2 | 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 测量结果数据
|
* 测量结果数据
|
||||||
*
|
*
|
||||||
@@ -763,6 +797,11 @@ declare interface MeasureResult {
|
|||||||
xyz?: MeasureXYZ;
|
xyz?: MeasureXYZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 距离/标高等“长度类”单位
|
||||||
|
*/
|
||||||
|
declare type MeasureUnit = 'm' | 'cm' | 'mm' | 'km';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 3D 坐标(可选展示)
|
* 3D 坐标(可选展示)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1192,3 +1192,5 @@ type ExpandDirection = 'up' | 'down' | 'left' | 'right';
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -610,3 +610,5 @@ interface ModelLoadOptions {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,17 @@
|
|||||||
- 由 `MeasureDialogManager` 创建并挂载到 `BimDialog` 中
|
- 由 `MeasureDialogManager` 创建并挂载到 `BimDialog` 中
|
||||||
- 外部业务(SDK 使用者)不直接 import 组件类,统一通过 `engine.measure`(Manager)调用
|
- 外部业务(SDK 使用者)不直接 import 组件类,统一通过 `engine.measure`(Manager)调用
|
||||||
|
|
||||||
|
### 1.3 配置项(单位/精度)与缓存策略(新增)
|
||||||
|
- **创建 `MeasurePanel` 不传入单位/精度**
|
||||||
|
- 默认配置由组件内部维护:
|
||||||
|
- `unit`: `'mm'`
|
||||||
|
- `precision`: `2`(即 `0.00`)
|
||||||
|
- 组件初始化时会读取缓存(`localStorage`):
|
||||||
|
- key:`bim-engine:measure:config`
|
||||||
|
- 若缓存存在且合法,则使用缓存值覆盖默认配置
|
||||||
|
- 若缓存不存在/解析失败,则使用默认配置
|
||||||
|
- 用户在设置面板点击“保存设置”后,组件会写入缓存
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 组件类 API 文档
|
## 2. 组件类 API 文档
|
||||||
@@ -76,7 +87,16 @@ constructor(options?: MeasurePanelOptions)
|
|||||||
- 清空结果展示并触发 `onClearAll`(如果提供)。
|
- 清空结果展示并触发 `onClearAll`(如果提供)。
|
||||||
|
|
||||||
#### `openSettings(): void`
|
#### `openSettings(): void`
|
||||||
- 触发 `onSettings`(如果提供),否则输出中文警告日志(仅预留接口)。
|
- 进入组件内部“设置面板”(单位/精度选择)。
|
||||||
|
- 同时触发 `onSettings`(如果提供,作为外部监听)。
|
||||||
|
|
||||||
|
#### `getConfig(): MeasureConfig`
|
||||||
|
- 获取当前测量配置(单位/精度)。
|
||||||
|
|
||||||
|
#### `setConfig(partial: Partial<MeasureConfig>, persist = false): void`
|
||||||
|
- 更新配置:
|
||||||
|
- `persist=false`:仅更新内存,不写缓存
|
||||||
|
- `persist=true`:更新并写入 `localStorage`
|
||||||
|
|
||||||
#### `setExpanded(expanded: boolean): void`
|
#### `setExpanded(expanded: boolean): void`
|
||||||
- 展开/收起按钮区(收起时只显示前 4 个)。
|
- 展开/收起按钮区(收起时只显示前 4 个)。
|
||||||
@@ -112,6 +132,8 @@ constructor(options?: MeasurePanelOptions)
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
<div class="bim-measure-panel">
|
<div class="bim-measure-panel">
|
||||||
|
<!-- 主视图 -->
|
||||||
|
<div class="bim-measure-main">
|
||||||
<div class="bim-measure-tools">
|
<div class="bim-measure-tools">
|
||||||
<div class="bim-measure-tool-grid">
|
<div class="bim-measure-tool-grid">
|
||||||
<!-- 8 个按钮:收起时隐藏后 4 个 -->
|
<!-- 8 个按钮:收起时隐藏后 4 个 -->
|
||||||
@@ -150,6 +172,37 @@ constructor(options?: MeasurePanelOptions)
|
|||||||
<button class="bim-measure-clear-btn">删除全部</button>
|
<button class="bim-measure-clear-btn">删除全部</button>
|
||||||
<button class="bim-measure-settings-btn">(齿轮 svg)</button>
|
<button class="bim-measure-settings-btn">(齿轮 svg)</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 设置视图(点击设置按钮进入) -->
|
||||||
|
<div class="bim-measure-settings">
|
||||||
|
<div class="bim-measure-settings-title">设置</div>
|
||||||
|
|
||||||
|
<div class="bim-measure-settings-row">
|
||||||
|
<div class="label">单位:</div>
|
||||||
|
<select class="bim-measure-settings-select">
|
||||||
|
<option value="m">m</option>
|
||||||
|
<option value="cm">cm</option>
|
||||||
|
<option value="mm">mm</option>
|
||||||
|
<option value="km">km</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bim-measure-settings-row">
|
||||||
|
<div class="label">精度:</div>
|
||||||
|
<select class="bim-measure-settings-select">
|
||||||
|
<option value="0">0</option>
|
||||||
|
<option value="1">0.0</option>
|
||||||
|
<option value="2">0.00</option>
|
||||||
|
<option value="3">0.000</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bim-measure-settings-actions">
|
||||||
|
<button class="bim-measure-settings-save">保存设置</button>
|
||||||
|
<button class="bim-measure-settings-cancel">取消</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,100 @@
|
|||||||
color: var(--bim-dialog-text-color, #ccc);
|
color: var(--bim-dialog-text-color, #ccc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bim-measure-settings {
|
||||||
|
display: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: var(--bim-dialog-text-color, #ccc);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-measure-settings-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-measure-settings-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-measure-settings-row .label {
|
||||||
|
color: var(--bim-measure-label-color, rgba(255, 255, 255, 0.70));
|
||||||
|
font-size: 13px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-measure-settings-select {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 120px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--bim-measure-border, rgba(255, 255, 255, 0.12));
|
||||||
|
background: rgba(0, 0, 0, 0.12);
|
||||||
|
color: var(--bim-dialog-text-color, #ccc);
|
||||||
|
padding: 0 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-measure-settings-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: var(--bim-measure-label-color, rgba(255, 255, 255, 0.70));
|
||||||
|
margin-top: -4px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-measure-settings-actions {
|
||||||
|
margin-top: 14px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 注意:demo 里有全局 button 样式,这里用 class 强制覆盖,避免被污染 */
|
||||||
|
.bim-measure-settings-save,
|
||||||
|
.bim-measure-settings-cancel {
|
||||||
|
flex: 0 0 auto !important;
|
||||||
|
width: auto;
|
||||||
|
min-width: 0;
|
||||||
|
height: 30px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-measure-settings-save {
|
||||||
|
border: none;
|
||||||
|
background: var(--bim-measure-primary, #0078d4);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-measure-settings-cancel {
|
||||||
|
border: 1px solid var(--bim-measure-border, rgba(255, 255, 255, 0.12));
|
||||||
|
background: transparent;
|
||||||
|
color: var(--bim-dialog-text-color, #ccc);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-measure-settings-save:hover,
|
||||||
|
.bim-measure-settings-save:active,
|
||||||
|
.bim-measure-settings-save:focus,
|
||||||
|
.bim-measure-settings-cancel:hover,
|
||||||
|
.bim-measure-settings-cancel:active,
|
||||||
|
.bim-measure-settings-cancel:focus {
|
||||||
|
background: inherit;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 保存按钮 hover 用主题 hover 色(轻微反馈,不改变布局) */
|
||||||
|
.bim-measure-settings-save:hover {
|
||||||
|
background: var(--bim-measure-primary-hover, #0063b1);
|
||||||
|
}
|
||||||
|
|
||||||
/* 顶部:测量方式按钮区 */
|
/* 顶部:测量方式按钮区 */
|
||||||
.bim-measure-tools {
|
.bim-measure-tools {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -139,6 +233,20 @@
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 主数据:仅数值黄色,单位使用默认颜色 */
|
||||||
|
.bim-measure-main-number {
|
||||||
|
color: #ffd24a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-measure-main-number.is-laser-text {
|
||||||
|
/* 激光测距:不使用黄色,回到默认文字颜色 */
|
||||||
|
color: var(--bim-measure-value-color, rgba(255, 255, 255, 0.90));
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-measure-main-unit {
|
||||||
|
color: var(--bim-measure-value-color, rgba(255, 255, 255, 0.90));
|
||||||
|
}
|
||||||
|
|
||||||
.bim-measure-xyz {
|
.bim-measure-xyz {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -149,6 +257,19 @@
|
|||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* XYZ:红/绿/蓝展示 */
|
||||||
|
.bim-measure-xyz-x {
|
||||||
|
color: #ff4d4f !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-measure-xyz-y {
|
||||||
|
color: #52c41a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bim-measure-xyz-z {
|
||||||
|
color: #1677ff !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* 底部:操作区(删除全部 / 设置) */
|
/* 底部:操作区(删除全部 / 设置) */
|
||||||
.bim-measure-footer {
|
.bim-measure-footer {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { ThemeConfig } from '../../themes/types';
|
|||||||
import { IBimComponent } from '../../types/component';
|
import { IBimComponent } from '../../types/component';
|
||||||
import { localeManager, t } from '../../services/locale';
|
import { localeManager, t } from '../../services/locale';
|
||||||
import { themeManager } from '../../services/theme';
|
import { themeManager } from '../../services/theme';
|
||||||
import type { MeasureMode, MeasurePanelOptions, MeasureResult } from './types';
|
import type { MeasureConfig, MeasureMode, MeasurePanelOptions, MeasurePrecision, MeasureResult, MeasureUnit } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 测量面板组件(只做 UI,不实现真实测量)
|
* 测量面板组件(只做 UI,不实现真实测量)
|
||||||
@@ -26,19 +26,53 @@ export class MeasurePanel implements IBimComponent {
|
|||||||
private isExpanded: boolean;
|
private isExpanded: boolean;
|
||||||
private result: MeasureResult | null = null;
|
private result: MeasureResult | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测量配置(单位/精度)
|
||||||
|
* 说明:
|
||||||
|
* - 你要求:创建 MeasurePanel 不传入单位和精度
|
||||||
|
* - 默认值维护在组件内部
|
||||||
|
* - 初始化时优先读取缓存(localStorage),否则使用默认值
|
||||||
|
*/
|
||||||
|
private config: MeasureConfig;
|
||||||
|
|
||||||
|
/** 设置面板的临时配置(用于“取消”回滚) */
|
||||||
|
private draftConfig: MeasureConfig | null = null;
|
||||||
|
|
||||||
|
/** 当前视图:主面板 / 设置面板 */
|
||||||
|
private view: 'main' | 'settings' = 'main';
|
||||||
|
|
||||||
|
/** 缓存 key(默认全局) */
|
||||||
|
private static readonly CONFIG_CACHE_KEY = 'bim-engine:measure:config';
|
||||||
|
|
||||||
|
/** 默认配置(由组件内部维护) */
|
||||||
|
private static readonly DEFAULT_CONFIG: MeasureConfig = {
|
||||||
|
unit: 'mm',
|
||||||
|
precision: 2
|
||||||
|
};
|
||||||
|
|
||||||
// DOM 引用(便于局部更新,减少频繁 querySelector)
|
// DOM 引用(便于局部更新,减少频繁 querySelector)
|
||||||
private toolButtons: Map<MeasureMode, HTMLButtonElement> = new Map();
|
private toolButtons: Map<MeasureMode, HTMLButtonElement> = new Map();
|
||||||
private toggleBtn!: HTMLButtonElement;
|
private toggleBtn!: HTMLButtonElement;
|
||||||
private toggleTextEl!: HTMLElement;
|
private toggleTextEl!: HTMLElement;
|
||||||
private currentModeValueEl!: HTMLElement;
|
|
||||||
private mainValueValueEl!: HTMLElement;
|
private mainValueValueEl!: HTMLElement;
|
||||||
private mainValueLabelEl!: HTMLElement;
|
private mainValueLabelEl!: HTMLElement;
|
||||||
|
private mainNumberEl!: HTMLElement;
|
||||||
|
private mainUnitEl!: HTMLElement;
|
||||||
|
private xyzBoxEl!: HTMLElement;
|
||||||
private xyzXEl!: HTMLElement;
|
private xyzXEl!: HTMLElement;
|
||||||
private xyzYEl!: HTMLElement;
|
private xyzYEl!: HTMLElement;
|
||||||
private xyzZEl!: HTMLElement;
|
private xyzZEl!: HTMLElement;
|
||||||
private clearBtn!: HTMLButtonElement;
|
private clearBtn!: HTMLButtonElement;
|
||||||
private settingsBtn!: HTMLButtonElement;
|
private settingsBtn!: HTMLButtonElement;
|
||||||
|
|
||||||
|
// Settings DOM
|
||||||
|
private mainViewEl!: HTMLElement;
|
||||||
|
private settingsViewEl!: HTMLElement;
|
||||||
|
private unitSelectEl!: HTMLSelectElement;
|
||||||
|
private precisionSelectEl!: HTMLSelectElement;
|
||||||
|
private saveSettingsBtn!: HTMLButtonElement;
|
||||||
|
private cancelSettingsBtn!: HTMLButtonElement;
|
||||||
|
|
||||||
// 订阅清理
|
// 订阅清理
|
||||||
private unsubscribeLocale: (() => void) | null = null;
|
private unsubscribeLocale: (() => void) | null = null;
|
||||||
private unsubscribeTheme: (() => void) | null = null;
|
private unsubscribeTheme: (() => void) | null = null;
|
||||||
@@ -52,6 +86,9 @@ export class MeasurePanel implements IBimComponent {
|
|||||||
this.activeMode = options.defaultMode ?? 'distance';
|
this.activeMode = options.defaultMode ?? 'distance';
|
||||||
this.isExpanded = options.defaultExpanded ?? false;
|
this.isExpanded = options.defaultExpanded ?? false;
|
||||||
|
|
||||||
|
// 读取配置:优先缓存,否则默认
|
||||||
|
this.config = this.loadConfigFromCache() ?? { ...MeasurePanel.DEFAULT_CONFIG };
|
||||||
|
|
||||||
this.element = this.createDom();
|
this.element = this.createDom();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +113,7 @@ export class MeasurePanel implements IBimComponent {
|
|||||||
// 初始渲染状态(按钮显隐、选中态、结果区)
|
// 初始渲染状态(按钮显隐、选中态、结果区)
|
||||||
this.applyExpandedState();
|
this.applyExpandedState();
|
||||||
this.applyActiveModeState();
|
this.applyActiveModeState();
|
||||||
|
this.applyViewState();
|
||||||
this.renderResult();
|
this.renderResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,6 +134,9 @@ export class MeasurePanel implements IBimComponent {
|
|||||||
|
|
||||||
// “删除全部”颜色:截图中偏绿色,这里用 primary 做一个合理映射
|
// “删除全部”颜色:截图中偏绿色,这里用 primary 做一个合理映射
|
||||||
style.setProperty('--bim-measure-danger', theme.primary ?? '#46d369');
|
style.setProperty('--bim-measure-danger', theme.primary ?? '#46d369');
|
||||||
|
// 设置面板“保存设置”按钮用主题色
|
||||||
|
style.setProperty('--bim-measure-primary', theme.primary ?? '#0078d4');
|
||||||
|
style.setProperty('--bim-measure-primary-hover', theme.primaryHover ?? '#0063b1');
|
||||||
style.setProperty('--bim-measure-btn-bg', theme.componentBackground ?? 'rgba(255, 255, 255, 0.06)');
|
style.setProperty('--bim-measure-btn-bg', theme.componentBackground ?? 'rgba(255, 255, 255, 0.06)');
|
||||||
style.setProperty('--bim-measure-btn-hover-bg', theme.componentHover ?? 'rgba(255, 255, 255, 0.10)');
|
style.setProperty('--bim-measure-btn-hover-bg', theme.componentHover ?? 'rgba(255, 255, 255, 0.10)');
|
||||||
style.setProperty('--bim-measure-btn-active-bg', theme.componentActive ?? 'rgba(255, 255, 255, 0.14)');
|
style.setProperty('--bim-measure-btn-active-bg', theme.componentActive ?? 'rgba(255, 255, 255, 0.14)');
|
||||||
@@ -125,10 +166,7 @@ export class MeasurePanel implements IBimComponent {
|
|||||||
this.settingsBtn.title = t('measure.actions.settings');
|
this.settingsBtn.title = t('measure.actions.settings');
|
||||||
this.settingsBtn.setAttribute('aria-label', this.settingsBtn.title);
|
this.settingsBtn.setAttribute('aria-label', this.settingsBtn.title);
|
||||||
|
|
||||||
// 4) 更新“当前方式”显示(value)
|
// 4) 主值 label(随模式变化)
|
||||||
this.currentModeValueEl.textContent = t(this.getModeI18nKey(this.activeMode));
|
|
||||||
|
|
||||||
// 5) 主值 label(随模式变化)
|
|
||||||
this.mainValueLabelEl.textContent = t(this.getModeValueLabelI18nKey(this.activeMode));
|
this.mainValueLabelEl.textContent = t(this.getModeValueLabelI18nKey(this.activeMode));
|
||||||
|
|
||||||
// 6) XYZ label(使用 key)
|
// 6) XYZ label(使用 key)
|
||||||
@@ -139,6 +177,10 @@ export class MeasurePanel implements IBimComponent {
|
|||||||
const key = node.dataset.i18nKey;
|
const key = node.dataset.i18nKey;
|
||||||
if (key) node.textContent = t(key);
|
if (key) node.textContent = t(key);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 7) 设置面板文本
|
||||||
|
this.saveSettingsBtn.textContent = t('measure.settings.save');
|
||||||
|
this.cancelSettingsBtn.textContent = t('measure.settings.cancel');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -192,7 +234,6 @@ export class MeasurePanel implements IBimComponent {
|
|||||||
|
|
||||||
// 切换方式后,主值 label 也需要更新
|
// 切换方式后,主值 label 也需要更新
|
||||||
this.mainValueLabelEl.textContent = t(this.getModeValueLabelI18nKey(this.activeMode));
|
this.mainValueLabelEl.textContent = t(this.getModeValueLabelI18nKey(this.activeMode));
|
||||||
this.currentModeValueEl.textContent = t(this.getModeI18nKey(this.activeMode));
|
|
||||||
|
|
||||||
// 通知外部(如果需要)
|
// 通知外部(如果需要)
|
||||||
if (this.options.onModeChange) {
|
if (this.options.onModeChange) {
|
||||||
@@ -201,6 +242,12 @@ export class MeasurePanel implements IBimComponent {
|
|||||||
|
|
||||||
// 模式切换后,结果展示也应刷新(例如某些字段显示为 --)
|
// 模式切换后,结果展示也应刷新(例如某些字段显示为 --)
|
||||||
this.renderResult();
|
this.renderResult();
|
||||||
|
|
||||||
|
// 切换模式会影响结果区高度(例如 distance 显示 xyz,其它不显示)
|
||||||
|
// 复用 onExpandedChange 来通知外部重新计算 Dialog 高度(不额外扩展回调,保持接口简单)
|
||||||
|
if (this.options.onExpandedChange) {
|
||||||
|
this.options.onExpandedChange(this.isExpanded);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -230,13 +277,44 @@ export class MeasurePanel implements IBimComponent {
|
|||||||
* 打开设置(本次只预留方法/回调)
|
* 打开设置(本次只预留方法/回调)
|
||||||
*/
|
*/
|
||||||
public openSettings(): void {
|
public openSettings(): void {
|
||||||
|
// 进入设置面板(组件内部逻辑)
|
||||||
|
this.enterSettingsView();
|
||||||
|
|
||||||
|
// 仍然保留回调(如果外部想监听)
|
||||||
if (this.options.onSettings) {
|
if (this.options.onSettings) {
|
||||||
this.options.onSettings();
|
this.options.onSettings();
|
||||||
return;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前测量配置
|
||||||
|
*/
|
||||||
|
public getConfig(): MeasureConfig {
|
||||||
|
return { ...this.config };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置测量配置(可选对外调用)
|
||||||
|
* @param partial 部分更新
|
||||||
|
* @param persist 是否写入缓存(默认 false)
|
||||||
|
*/
|
||||||
|
public setConfig(partial: Partial<MeasureConfig>, persist: boolean = false): void {
|
||||||
|
const next: MeasureConfig = {
|
||||||
|
unit: partial.unit ?? this.config.unit,
|
||||||
|
precision: partial.precision ?? this.config.precision
|
||||||
|
};
|
||||||
|
this.config = next;
|
||||||
|
if (persist) {
|
||||||
|
this.saveConfigToCache(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 兜底:避免无声失败,打印中文日志(符合项目规范)
|
// 配置变化会影响数值显示(单位/精度)
|
||||||
console.warn('[MeasurePanel] 未提供设置回调 onSettings,当前仅预留接口。');
|
this.renderResult();
|
||||||
|
|
||||||
|
// 如果当前在设置面板,表单也需要同步
|
||||||
|
if (this.view === 'settings') {
|
||||||
|
this.syncSettingsFormFromConfig(next);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -270,6 +348,10 @@ export class MeasurePanel implements IBimComponent {
|
|||||||
const root = document.createElement('div');
|
const root = document.createElement('div');
|
||||||
root.className = 'bim-measure-panel';
|
root.className = 'bim-measure-panel';
|
||||||
|
|
||||||
|
// 主视图容器(默认显示)
|
||||||
|
this.mainViewEl = document.createElement('div');
|
||||||
|
this.mainViewEl.className = 'bim-measure-main';
|
||||||
|
|
||||||
// 顶部:工具按钮区
|
// 顶部:工具按钮区
|
||||||
const toolsBox = document.createElement('div');
|
const toolsBox = document.createElement('div');
|
||||||
toolsBox.className = 'bim-measure-tools';
|
toolsBox.className = 'bim-measure-tools';
|
||||||
@@ -355,25 +437,12 @@ export class MeasurePanel implements IBimComponent {
|
|||||||
|
|
||||||
toggleBox.appendChild(this.toggleBtn);
|
toggleBox.appendChild(this.toggleBtn);
|
||||||
toolsBox.appendChild(toggleBox);
|
toolsBox.appendChild(toggleBox);
|
||||||
root.appendChild(toolsBox);
|
this.mainViewEl.appendChild(toolsBox);
|
||||||
|
|
||||||
// 中部:结果区
|
// 中部:结果区
|
||||||
const resultBox = document.createElement('div');
|
const resultBox = document.createElement('div');
|
||||||
resultBox.className = 'bim-measure-result';
|
resultBox.className = 'bim-measure-result';
|
||||||
|
|
||||||
// 当前方式
|
|
||||||
const currentModeRow = document.createElement('div');
|
|
||||||
currentModeRow.className = 'bim-measure-row';
|
|
||||||
const currentModeLabel = document.createElement('span');
|
|
||||||
currentModeLabel.className = 'label';
|
|
||||||
currentModeLabel.dataset.i18nKey = 'measure.labels.currentMode';
|
|
||||||
const currentModeValue = document.createElement('span');
|
|
||||||
currentModeValue.className = 'value';
|
|
||||||
this.currentModeValueEl = currentModeValue;
|
|
||||||
currentModeRow.appendChild(currentModeLabel);
|
|
||||||
currentModeRow.appendChild(currentModeValue);
|
|
||||||
resultBox.appendChild(currentModeRow);
|
|
||||||
|
|
||||||
// 主结果值(随模式变化)
|
// 主结果值(随模式变化)
|
||||||
const mainValueRow = document.createElement('div');
|
const mainValueRow = document.createElement('div');
|
||||||
mainValueRow.className = 'bim-measure-row';
|
mainValueRow.className = 'bim-measure-row';
|
||||||
@@ -383,6 +452,18 @@ export class MeasurePanel implements IBimComponent {
|
|||||||
const mainValueValue = document.createElement('span');
|
const mainValueValue = document.createElement('span');
|
||||||
mainValueValue.className = 'value';
|
mainValueValue.className = 'value';
|
||||||
this.mainValueValueEl = mainValueValue;
|
this.mainValueValueEl = mainValueValue;
|
||||||
|
|
||||||
|
// 主值拆分:数值(黄色)+ 单位(普通色)
|
||||||
|
// 这样可以满足:
|
||||||
|
// 1) 只让“数据”变黄,单位不变色
|
||||||
|
// 2) 没有数据时展示 `-- 单位`
|
||||||
|
this.mainNumberEl = document.createElement('span');
|
||||||
|
this.mainNumberEl.className = 'bim-measure-main-number';
|
||||||
|
this.mainUnitEl = document.createElement('span');
|
||||||
|
this.mainUnitEl.className = 'bim-measure-main-unit';
|
||||||
|
this.mainValueValueEl.appendChild(this.mainNumberEl);
|
||||||
|
this.mainValueValueEl.appendChild(document.createTextNode(' '));
|
||||||
|
this.mainValueValueEl.appendChild(this.mainUnitEl);
|
||||||
mainValueRow.appendChild(mainValueLabel);
|
mainValueRow.appendChild(mainValueLabel);
|
||||||
mainValueRow.appendChild(mainValueValue);
|
mainValueRow.appendChild(mainValueValue);
|
||||||
resultBox.appendChild(mainValueRow);
|
resultBox.appendChild(mainValueRow);
|
||||||
@@ -390,27 +471,28 @@ export class MeasurePanel implements IBimComponent {
|
|||||||
// XYZ
|
// XYZ
|
||||||
const xyzBox = document.createElement('div');
|
const xyzBox = document.createElement('div');
|
||||||
xyzBox.className = 'bim-measure-xyz';
|
xyzBox.className = 'bim-measure-xyz';
|
||||||
|
this.xyzBoxEl = xyzBox;
|
||||||
|
|
||||||
const makeXyzRow = (labelKey: string, valueElSetter: (el: HTMLElement) => void) => {
|
const makeXyzRow = (labelKey: string, valueClassName: string, valueElSetter: (el: HTMLElement) => void) => {
|
||||||
const row = document.createElement('div');
|
const row = document.createElement('div');
|
||||||
row.className = 'bim-measure-row';
|
row.className = 'bim-measure-row';
|
||||||
const label = document.createElement('span');
|
const label = document.createElement('span');
|
||||||
label.className = 'label';
|
label.className = 'label';
|
||||||
label.dataset.i18nKey = labelKey;
|
label.dataset.i18nKey = labelKey;
|
||||||
const value = document.createElement('span');
|
const value = document.createElement('span');
|
||||||
value.className = 'value';
|
value.className = `value ${valueClassName}`;
|
||||||
valueElSetter(value);
|
valueElSetter(value);
|
||||||
row.appendChild(label);
|
row.appendChild(label);
|
||||||
row.appendChild(value);
|
row.appendChild(value);
|
||||||
return row;
|
return row;
|
||||||
};
|
};
|
||||||
|
|
||||||
xyzBox.appendChild(makeXyzRow('measure.labels.x', (el) => (this.xyzXEl = el)));
|
xyzBox.appendChild(makeXyzRow('measure.labels.x', 'bim-measure-xyz-x', (el) => (this.xyzXEl = el)));
|
||||||
xyzBox.appendChild(makeXyzRow('measure.labels.y', (el) => (this.xyzYEl = el)));
|
xyzBox.appendChild(makeXyzRow('measure.labels.y', 'bim-measure-xyz-y', (el) => (this.xyzYEl = el)));
|
||||||
xyzBox.appendChild(makeXyzRow('measure.labels.z', (el) => (this.xyzZEl = el)));
|
xyzBox.appendChild(makeXyzRow('measure.labels.z', 'bim-measure-xyz-z', (el) => (this.xyzZEl = el)));
|
||||||
resultBox.appendChild(xyzBox);
|
resultBox.appendChild(xyzBox);
|
||||||
|
|
||||||
root.appendChild(resultBox);
|
this.mainViewEl.appendChild(resultBox);
|
||||||
|
|
||||||
// 底部:删除全部 + 设置
|
// 底部:删除全部 + 设置
|
||||||
const footer = document.createElement('div');
|
const footer = document.createElement('div');
|
||||||
@@ -437,11 +519,230 @@ export class MeasurePanel implements IBimComponent {
|
|||||||
|
|
||||||
footer.appendChild(this.clearBtn);
|
footer.appendChild(this.clearBtn);
|
||||||
footer.appendChild(this.settingsBtn);
|
footer.appendChild(this.settingsBtn);
|
||||||
root.appendChild(footer);
|
this.mainViewEl.appendChild(footer);
|
||||||
|
|
||||||
|
// 设置视图容器(默认隐藏)
|
||||||
|
this.settingsViewEl = this.createSettingsDom();
|
||||||
|
|
||||||
|
root.appendChild(this.mainViewEl);
|
||||||
|
root.appendChild(this.settingsViewEl);
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建“设置面板”DOM
|
||||||
|
*/
|
||||||
|
private createSettingsDom(): HTMLElement {
|
||||||
|
const box = document.createElement('div');
|
||||||
|
box.className = 'bim-measure-settings';
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
const title = document.createElement('div');
|
||||||
|
title.className = 'bim-measure-settings-title';
|
||||||
|
title.dataset.i18nKey = 'measure.settings.title';
|
||||||
|
box.appendChild(title);
|
||||||
|
|
||||||
|
// 单位
|
||||||
|
const unitRow = document.createElement('div');
|
||||||
|
unitRow.className = 'bim-measure-settings-row';
|
||||||
|
const unitLabel = document.createElement('div');
|
||||||
|
unitLabel.className = 'label';
|
||||||
|
unitLabel.dataset.i18nKey = 'measure.settings.unit';
|
||||||
|
this.unitSelectEl = document.createElement('select');
|
||||||
|
this.unitSelectEl.className = 'bim-measure-settings-select';
|
||||||
|
this.unitSelectEl.appendChild(this.makeOption('m'));
|
||||||
|
this.unitSelectEl.appendChild(this.makeOption('cm'));
|
||||||
|
this.unitSelectEl.appendChild(this.makeOption('mm'));
|
||||||
|
this.unitSelectEl.appendChild(this.makeOption('km'));
|
||||||
|
unitRow.appendChild(unitLabel);
|
||||||
|
unitRow.appendChild(this.unitSelectEl);
|
||||||
|
box.appendChild(unitRow);
|
||||||
|
|
||||||
|
// 提示文本:你要求放在“单位”下面
|
||||||
|
const hint = document.createElement('div');
|
||||||
|
hint.className = 'bim-measure-settings-hint';
|
||||||
|
hint.dataset.i18nKey = 'measure.settings.hint';
|
||||||
|
box.appendChild(hint);
|
||||||
|
|
||||||
|
// 精度
|
||||||
|
const precisionRow = document.createElement('div');
|
||||||
|
precisionRow.className = 'bim-measure-settings-row';
|
||||||
|
const precisionLabel = document.createElement('div');
|
||||||
|
precisionLabel.className = 'label';
|
||||||
|
precisionLabel.dataset.i18nKey = 'measure.settings.precision';
|
||||||
|
this.precisionSelectEl = document.createElement('select');
|
||||||
|
this.precisionSelectEl.className = 'bim-measure-settings-select';
|
||||||
|
this.precisionSelectEl.appendChild(this.makePrecisionOption(0));
|
||||||
|
this.precisionSelectEl.appendChild(this.makePrecisionOption(1));
|
||||||
|
this.precisionSelectEl.appendChild(this.makePrecisionOption(2));
|
||||||
|
this.precisionSelectEl.appendChild(this.makePrecisionOption(3));
|
||||||
|
precisionRow.appendChild(precisionLabel);
|
||||||
|
precisionRow.appendChild(this.precisionSelectEl);
|
||||||
|
box.appendChild(precisionRow);
|
||||||
|
|
||||||
|
// 底部按钮
|
||||||
|
const actions = document.createElement('div');
|
||||||
|
actions.className = 'bim-measure-settings-actions';
|
||||||
|
|
||||||
|
this.saveSettingsBtn = document.createElement('button');
|
||||||
|
this.saveSettingsBtn.type = 'button';
|
||||||
|
this.saveSettingsBtn.className = 'bim-measure-settings-save';
|
||||||
|
this.saveSettingsBtn.addEventListener('click', () => {
|
||||||
|
this.saveSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.cancelSettingsBtn = document.createElement('button');
|
||||||
|
this.cancelSettingsBtn.type = 'button';
|
||||||
|
this.cancelSettingsBtn.className = 'bim-measure-settings-cancel';
|
||||||
|
this.cancelSettingsBtn.addEventListener('click', () => {
|
||||||
|
this.cancelSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
actions.appendChild(this.saveSettingsBtn);
|
||||||
|
actions.appendChild(this.cancelSettingsBtn);
|
||||||
|
box.appendChild(actions);
|
||||||
|
|
||||||
|
// 初次同步表单值
|
||||||
|
this.syncSettingsFormFromConfig(this.config);
|
||||||
|
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
private makeOption(unit: MeasureUnit): HTMLOptionElement {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = unit;
|
||||||
|
// 选项显示内容:直接显示单位字符串
|
||||||
|
opt.textContent = unit;
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private makePrecisionOption(precision: MeasurePrecision): HTMLOptionElement {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = String(precision);
|
||||||
|
// 显示:0 / 0.0 / 0.00 / 0.000
|
||||||
|
opt.textContent = precision === 0 ? '0' : `0.${'0'.repeat(precision)}`;
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 进入设置视图:保存一份当前配置作为草稿基线
|
||||||
|
*/
|
||||||
|
private enterSettingsView(): void {
|
||||||
|
this.draftConfig = { ...this.config };
|
||||||
|
this.view = 'settings';
|
||||||
|
this.syncSettingsFormFromConfig(this.config);
|
||||||
|
this.applyViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存设置:写入 config + 写缓存 + 返回主视图
|
||||||
|
*/
|
||||||
|
private saveSettings(): void {
|
||||||
|
const unit = (this.unitSelectEl.value as MeasureUnit) || this.config.unit;
|
||||||
|
const precision = (Number(this.precisionSelectEl.value) as MeasurePrecision);
|
||||||
|
const next: MeasureConfig = {
|
||||||
|
unit,
|
||||||
|
precision: this.isValidPrecision(precision) ? precision : this.config.precision
|
||||||
|
};
|
||||||
|
|
||||||
|
this.config = next;
|
||||||
|
this.saveConfigToCache(next);
|
||||||
|
this.draftConfig = null;
|
||||||
|
this.view = 'main';
|
||||||
|
this.applyViewState();
|
||||||
|
|
||||||
|
// 配置变化会影响显示
|
||||||
|
this.renderResult();
|
||||||
|
|
||||||
|
// 高度变化(设置面板 -> 主面板)也需要通知外部
|
||||||
|
if (this.options.onExpandedChange) {
|
||||||
|
this.options.onExpandedChange(this.isExpanded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消设置:回滚到进入设置前的配置,并返回主视图
|
||||||
|
*/
|
||||||
|
private cancelSettings(): void {
|
||||||
|
if (this.draftConfig) {
|
||||||
|
this.config = { ...this.draftConfig };
|
||||||
|
}
|
||||||
|
this.draftConfig = null;
|
||||||
|
this.view = 'main';
|
||||||
|
this.applyViewState();
|
||||||
|
this.renderResult();
|
||||||
|
|
||||||
|
// 高度变化(设置面板 -> 主面板)也需要通知外部
|
||||||
|
if (this.options.onExpandedChange) {
|
||||||
|
this.options.onExpandedChange(this.isExpanded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private syncSettingsFormFromConfig(config: MeasureConfig): void {
|
||||||
|
this.unitSelectEl.value = config.unit;
|
||||||
|
this.precisionSelectEl.value = String(config.precision);
|
||||||
|
}
|
||||||
|
|
||||||
|
private applyViewState(): void {
|
||||||
|
if (this.view === 'settings') {
|
||||||
|
this.mainViewEl.style.display = 'none';
|
||||||
|
// 注意:CSS 里 `.bim-measure-settings { display: none; }` 是默认隐藏
|
||||||
|
// 因此这里必须显式设置为可见(否则会出现“进入设置页后什么都不显示”的问题)
|
||||||
|
this.settingsViewEl.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
// 显式恢复主视图显示(避免外部样式干扰)
|
||||||
|
this.mainViewEl.style.display = 'block';
|
||||||
|
this.settingsViewEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从缓存读取配置
|
||||||
|
* - 有缓存:返回解析后的配置
|
||||||
|
* - 无缓存/解析失败:返回 null
|
||||||
|
*/
|
||||||
|
private loadConfigFromCache(): MeasureConfig | null {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(MeasurePanel.CONFIG_CACHE_KEY);
|
||||||
|
if (!raw) return null;
|
||||||
|
const parsed = JSON.parse(raw) as Partial<MeasureConfig>;
|
||||||
|
if (!parsed || typeof parsed !== 'object') return null;
|
||||||
|
|
||||||
|
const unit = parsed.unit;
|
||||||
|
const precision = parsed.precision;
|
||||||
|
|
||||||
|
if (!this.isValidUnit(unit) || !this.isValidPrecision(precision as number)) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
unit,
|
||||||
|
precision: precision as MeasurePrecision
|
||||||
|
};
|
||||||
|
} catch (_e) {
|
||||||
|
// localStorage 可能被禁用或 JSON 格式不正确,直接忽略
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写入缓存(localStorage)
|
||||||
|
*/
|
||||||
|
private saveConfigToCache(config: MeasureConfig): void {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(MeasurePanel.CONFIG_CACHE_KEY, JSON.stringify(config));
|
||||||
|
} catch (_e) {
|
||||||
|
// localStorage 可能被禁用:忽略即可,不影响功能
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private isValidUnit(unit: any): unit is MeasureUnit {
|
||||||
|
return unit === 'm' || unit === 'cm' || unit === 'mm' || unit === 'km';
|
||||||
|
}
|
||||||
|
|
||||||
|
private isValidPrecision(precision: any): precision is MeasurePrecision {
|
||||||
|
return precision === 0 || precision === 1 || precision === 2 || precision === 3;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用“展开/收起”状态:默认只显示前 4 个按钮
|
* 应用“展开/收起”状态:默认只显示前 4 个按钮
|
||||||
*/
|
*/
|
||||||
@@ -482,23 +783,53 @@ export class MeasurePanel implements IBimComponent {
|
|||||||
* 渲染结果区(根据 activeMode 从 result 里取对应字段)
|
* 渲染结果区(根据 activeMode 从 result 里取对应字段)
|
||||||
*/
|
*/
|
||||||
private renderResult(): void {
|
private renderResult(): void {
|
||||||
// 1) 主值
|
// 1) 根据模式决定结果区显示规则
|
||||||
const mainText = this.formatMainValue(this.activeMode, this.result);
|
// 你给的规则:
|
||||||
this.mainValueValueEl.textContent = mainText;
|
// - 距离:显示数值 + xyz
|
||||||
|
// - 最小距离:只显示数值
|
||||||
|
// - 角度:--°
|
||||||
|
// - 标高:--m(固定 m)
|
||||||
|
// - 体积:--mm³(单位随设置变动,即 unit³)
|
||||||
|
// - 激光测距:不显示任何数值/xyz,只显示“激光测距”文字
|
||||||
|
// - 坡度:--%
|
||||||
|
// - 空间体积:--mm³(单位随设置变动,即 unit³)
|
||||||
|
|
||||||
// 2) XYZ
|
// 1.1) 主行:默认显示 label + value(数值/单位拆分)
|
||||||
const xyz = this.result?.xyz;
|
// 激光测距:只显示文字,因此隐藏 label/单位
|
||||||
if (!xyz) {
|
if (this.activeMode === 'laserDistance') {
|
||||||
this.xyzXEl.textContent = '--';
|
this.mainValueLabelEl.style.display = 'none';
|
||||||
this.xyzYEl.textContent = '--';
|
this.mainNumberEl.textContent = t(this.getModeI18nKey('laserDistance'));
|
||||||
this.xyzZEl.textContent = '--';
|
this.mainUnitEl.textContent = '';
|
||||||
|
// 激光测距:你要求不使用黄色主数据
|
||||||
|
this.mainNumberEl.classList.add('is-laser-text');
|
||||||
|
} else {
|
||||||
|
this.mainValueLabelEl.style.display = '';
|
||||||
|
this.mainValueLabelEl.textContent = t(this.getModeValueLabelI18nKey(this.activeMode));
|
||||||
|
const parts = this.formatMainValueParts(this.activeMode, this.result);
|
||||||
|
this.mainNumberEl.textContent = parts.numberText;
|
||||||
|
this.mainUnitEl.textContent = parts.unitText;
|
||||||
|
// 其它模式:恢复黄色主数据
|
||||||
|
this.mainNumberEl.classList.remove('is-laser-text');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1.2) XYZ:只有“距离”需要展示
|
||||||
|
if (this.activeMode === 'distance') {
|
||||||
|
this.xyzBoxEl.style.display = '';
|
||||||
|
const xyz = this.result?.xyz;
|
||||||
|
if (!xyz) {
|
||||||
|
this.xyzXEl.textContent = '--';
|
||||||
|
this.xyzYEl.textContent = '--';
|
||||||
|
this.xyzZEl.textContent = '--';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.xyzXEl.textContent = this.formatNumberWithPrecision(xyz.x, this.config.precision);
|
||||||
|
this.xyzYEl.textContent = this.formatNumberWithPrecision(xyz.y, this.config.precision);
|
||||||
|
this.xyzZEl.textContent = this.formatNumberWithPrecision(xyz.z, this.config.precision);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为了可读性:这里不做 fancy formatter,只做基础展示
|
// 非 distance:隐藏 xyz
|
||||||
this.xyzXEl.textContent = this.formatNumber(xyz.x);
|
this.xyzBoxEl.style.display = 'none';
|
||||||
this.xyzYEl.textContent = this.formatNumber(xyz.y);
|
|
||||||
this.xyzZEl.textContent = this.formatNumber(xyz.z);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -515,53 +846,144 @@ export class MeasurePanel implements IBimComponent {
|
|||||||
return `measure.labels.value.${mode}`;
|
return `measure.labels.value.${mode}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 注意:旧的 formatMainValue/formatWithFixedUnit 已被 formatMainValueParts 替代,
|
||||||
* 将“当前模式”的主值格式化为文本
|
// 以支持“数值与单位分色显示”和“无数据时仍展示单位”。
|
||||||
* @param mode 当前模式
|
|
||||||
* @param result 当前结果
|
|
||||||
*/
|
|
||||||
private formatMainValue(mode: MeasureMode, result: MeasureResult | null): string {
|
|
||||||
if (!result) return '--';
|
|
||||||
|
|
||||||
// 根据不同 mode 读取对应字段并格式化单位
|
/**
|
||||||
// 单位文本也走国际化(可替换为英文/中文)
|
* 基础数字格式化(按精度显示)
|
||||||
switch (mode) {
|
*/
|
||||||
case 'distance':
|
private formatNumberWithPrecision(value: number, precision: MeasurePrecision): string {
|
||||||
return this.formatWithUnit(result.distanceMm, 'measure.units.mm');
|
// 你要求精度可选:0 / 0.0 / 0.00 / 0.000,因此这里不做 trim,严格按 toFixed 输出
|
||||||
case 'minDistance':
|
return value.toFixed(precision);
|
||||||
return this.formatWithUnit(result.minDistanceMm, 'measure.units.mm');
|
}
|
||||||
case 'angle':
|
|
||||||
return this.formatWithUnit(result.angleDeg, 'measure.units.deg');
|
// 注意:旧的 formatLengthWithConfig 已被 formatLengthParts 替代。
|
||||||
case 'elevation':
|
|
||||||
return this.formatWithUnit(result.elevationMm, 'measure.units.mm');
|
private convertMmToUnit(mm: number, unit: MeasureUnit): number {
|
||||||
case 'volume':
|
switch (unit) {
|
||||||
return this.formatWithUnit(result.volumeM3, 'measure.units.m3');
|
case 'mm':
|
||||||
case 'laserDistance':
|
return mm;
|
||||||
return this.formatWithUnit(result.laserDistanceMm, 'measure.units.mm');
|
case 'cm':
|
||||||
case 'slope':
|
return mm / 10;
|
||||||
return this.formatWithUnit(result.slopePercent, 'measure.units.percent');
|
case 'm':
|
||||||
case 'spaceVolume':
|
return mm / 1000;
|
||||||
return this.formatWithUnit(result.spaceVolumeM3, 'measure.units.m3');
|
case 'km':
|
||||||
|
return mm / 1_000_000;
|
||||||
default:
|
default:
|
||||||
return '--';
|
return mm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getUnitI18nKey(unit: MeasureUnit): string {
|
||||||
|
return `measure.units.${unit}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注意:旧的 formatElevationFixedMeters / formatVolumeWithConfig 已被 formatMainValueParts 替代。
|
||||||
|
|
||||||
|
private convertMm3ToUnit3(mm3: number, unit: MeasureUnit): number {
|
||||||
|
// 先把 mm³ -> 对应 unit³
|
||||||
|
// mm -> cm: /10,因此 mm³ -> cm³: /1000
|
||||||
|
// mm -> m : /1000,因此 mm³ -> m³ : /1e9
|
||||||
|
// mm -> km: /1e6,因此 mm³ -> km³: /1e18
|
||||||
|
switch (unit) {
|
||||||
|
case 'mm':
|
||||||
|
return mm3;
|
||||||
|
case 'cm':
|
||||||
|
return mm3 / 1000;
|
||||||
|
case 'm':
|
||||||
|
return mm3 / 1_000_000_000;
|
||||||
|
case 'km':
|
||||||
|
return mm3 / 1_000_000_000_000_000_000;
|
||||||
|
default:
|
||||||
|
return mm3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 格式化数值 + 单位(单位走国际化)
|
* 主数据拆分:返回 { 数值文本, 单位文本 }
|
||||||
|
* 规则:
|
||||||
|
* - 没数据时:必须展示 `-- 单位`(而不是只展示 `--`)
|
||||||
|
* - 单位随模式变化:
|
||||||
|
* - 距离/最小距离:单位随设置变动
|
||||||
|
* - 角度:°
|
||||||
|
* - 标高:固定 m
|
||||||
|
* - 体积/空间体积:单位³(随设置变动)
|
||||||
|
* - 坡度:%
|
||||||
*/
|
*/
|
||||||
private formatWithUnit(value: number | undefined, unitKey: string): string {
|
private formatMainValueParts(mode: MeasureMode, result: MeasureResult | null): { numberText: string; unitText: string } {
|
||||||
if (value === null || value === undefined || Number.isNaN(value)) return '--';
|
if (mode === 'laserDistance') return { numberText: t(this.getModeI18nKey('laserDistance')), unitText: '' };
|
||||||
return `${this.formatNumber(value)} ${t(unitKey)}`;
|
|
||||||
|
// 没有数据:显示 `-- 单位`
|
||||||
|
if (!result) {
|
||||||
|
return this.getEmptyValuePartsByMode(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case 'distance':
|
||||||
|
return this.formatLengthParts(result.distanceMm);
|
||||||
|
case 'minDistance':
|
||||||
|
return this.formatLengthParts(result.minDistanceMm);
|
||||||
|
case 'angle':
|
||||||
|
return this.formatFixedUnitParts(result.angleDeg, t('measure.units.deg'));
|
||||||
|
case 'elevation':
|
||||||
|
// 标高固定 m(外部注入值约定为 mm)
|
||||||
|
return this.formatFixedUnitParts(
|
||||||
|
result.elevationMm === undefined ? undefined : result.elevationMm / 1000,
|
||||||
|
t('measure.units.m')
|
||||||
|
);
|
||||||
|
case 'volume':
|
||||||
|
return this.formatVolumeParts(result.volumeM3);
|
||||||
|
case 'slope':
|
||||||
|
return this.formatFixedUnitParts(result.slopePercent, t('measure.units.percent'));
|
||||||
|
case 'spaceVolume':
|
||||||
|
return this.formatVolumeParts(result.spaceVolumeM3);
|
||||||
|
default:
|
||||||
|
return { numberText: '--', unitText: '' };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private getEmptyValuePartsByMode(mode: MeasureMode): { numberText: string; unitText: string } {
|
||||||
* 基础数字格式化(可读性优先)
|
switch (mode) {
|
||||||
*/
|
case 'distance':
|
||||||
private formatNumber(value: number): string {
|
case 'minDistance':
|
||||||
// 保留 3 位小数以内(简单策略:整数不带小数,非整数保留到 3 位)
|
return { numberText: '--', unitText: t(this.getUnitI18nKey(this.config.unit)) };
|
||||||
if (Number.isInteger(value)) return String(value);
|
case 'angle':
|
||||||
return value.toFixed(3).replace(/0+$/g, '').replace(/\.$/g, '');
|
return { numberText: '--', unitText: t('measure.units.deg') };
|
||||||
|
case 'elevation':
|
||||||
|
return { numberText: '--', unitText: t('measure.units.m') };
|
||||||
|
case 'volume':
|
||||||
|
case 'spaceVolume':
|
||||||
|
return { numberText: '--', unitText: `${this.config.unit}³` };
|
||||||
|
case 'slope':
|
||||||
|
return { numberText: '--', unitText: t('measure.units.percent') };
|
||||||
|
default:
|
||||||
|
return { numberText: '--', unitText: '' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatFixedUnitParts(value: number | undefined, unitText: string): { numberText: string; unitText: string } {
|
||||||
|
if (value === null || value === undefined || Number.isNaN(value)) {
|
||||||
|
return { numberText: '--', unitText };
|
||||||
|
}
|
||||||
|
return { numberText: this.formatNumberWithPrecision(value, this.config.precision), unitText };
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatLengthParts(valueMm: number | undefined): { numberText: string; unitText: string } {
|
||||||
|
const unitText = t(this.getUnitI18nKey(this.config.unit));
|
||||||
|
if (valueMm === null || valueMm === undefined || Number.isNaN(valueMm)) {
|
||||||
|
return { numberText: '--', unitText };
|
||||||
|
}
|
||||||
|
const converted = this.convertMmToUnit(valueMm, this.config.unit);
|
||||||
|
return { numberText: this.formatNumberWithPrecision(converted, this.config.precision), unitText };
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatVolumeParts(valueMm3: number | undefined): { numberText: string; unitText: string } {
|
||||||
|
const unitText = `${this.config.unit}³`;
|
||||||
|
if (valueMm3 === null || valueMm3 === undefined || Number.isNaN(valueMm3)) {
|
||||||
|
return { numberText: '--', unitText };
|
||||||
|
}
|
||||||
|
const converted = this.convertMm3ToUnit3(valueMm3, this.config.unit);
|
||||||
|
return { numberText: this.formatNumberWithPrecision(converted, this.config.precision), unitText };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,28 @@ export type MeasureMode =
|
|||||||
| 'slope' // 坡度
|
| 'slope' // 坡度
|
||||||
| 'spaceVolume'; // 空间体积
|
| 'spaceVolume'; // 空间体积
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 距离/标高等“长度类”单位
|
||||||
|
*/
|
||||||
|
export type MeasureUnit = 'm' | 'cm' | 'mm' | 'km';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 精度(小数位数)
|
||||||
|
* - 0 -> 0
|
||||||
|
* - 1 -> 0.0
|
||||||
|
* - 2 -> 0.00
|
||||||
|
* - 3 -> 0.000
|
||||||
|
*/
|
||||||
|
export type MeasurePrecision = 0 | 1 | 2 | 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测量配置项(由组件内部维护默认值,并读取/写入缓存)
|
||||||
|
*/
|
||||||
|
export interface MeasureConfig {
|
||||||
|
unit: MeasureUnit;
|
||||||
|
precision: MeasurePrecision;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 3D 坐标(可选展示)
|
* 3D 坐标(可选展示)
|
||||||
*/
|
*/
|
||||||
@@ -83,6 +105,7 @@ export interface MeasurePanelOptions {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* “设置”回调
|
* “设置”回调
|
||||||
|
* 说明:设置按钮点击时会先在组件内部进入设置面板,再触发该回调(如果存在)
|
||||||
*/
|
*/
|
||||||
onSettings?: () => void;
|
onSettings?: () => void;
|
||||||
|
|
||||||
|
|||||||
@@ -87,9 +87,20 @@ export const enUS: TranslationDictionary = {
|
|||||||
},
|
},
|
||||||
units: {
|
units: {
|
||||||
mm: 'mm',
|
mm: 'mm',
|
||||||
|
cm: 'cm',
|
||||||
|
m: 'm',
|
||||||
|
km: 'km',
|
||||||
deg: '°',
|
deg: '°',
|
||||||
m3: 'm³',
|
m3: 'm³',
|
||||||
percent: '%',
|
percent: '%',
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
title: 'Settings',
|
||||||
|
unit: 'Unit:',
|
||||||
|
precision: 'Precision:',
|
||||||
|
hint: 'Distance, min distance and elevation use this unit by default; angle and volume use their own units.',
|
||||||
|
save: 'Save',
|
||||||
|
cancel: 'Cancel',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -106,10 +106,25 @@ export interface TranslationDictionary {
|
|||||||
*/
|
*/
|
||||||
units: {
|
units: {
|
||||||
mm: string;
|
mm: string;
|
||||||
|
cm: string;
|
||||||
|
m: string;
|
||||||
|
km: string;
|
||||||
deg: string;
|
deg: string;
|
||||||
m3: string;
|
m3: string;
|
||||||
percent: string;
|
percent: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置面板(单位/精度)
|
||||||
|
*/
|
||||||
|
settings: {
|
||||||
|
title: string;
|
||||||
|
unit: string;
|
||||||
|
precision: string;
|
||||||
|
hint: string;
|
||||||
|
save: string;
|
||||||
|
cancel: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,9 +87,20 @@ export const zhCN: TranslationDictionary = {
|
|||||||
},
|
},
|
||||||
units: {
|
units: {
|
||||||
mm: 'mm',
|
mm: 'mm',
|
||||||
|
cm: 'cm',
|
||||||
|
m: 'm',
|
||||||
|
km: 'km',
|
||||||
deg: '°',
|
deg: '°',
|
||||||
m3: 'm³',
|
m3: 'm³',
|
||||||
percent: '%',
|
percent: '%',
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
title: '设置',
|
||||||
|
unit: '单位:',
|
||||||
|
precision: '精度:',
|
||||||
|
hint: '距离、最小距离和标高默认使用该单位;角度和体积有各自默认单位。',
|
||||||
|
save: '保存设置',
|
||||||
|
cancel: '取消',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {BimComponent} from '../core/component';
|
|||||||
import {BimEngine} from '../bim-engine';
|
import {BimEngine} from '../bim-engine';
|
||||||
import {BimDialog} from "../components/dialog";
|
import {BimDialog} from "../components/dialog";
|
||||||
import { MeasurePanel } from '../components/measure-panel';
|
import { MeasurePanel } from '../components/measure-panel';
|
||||||
import type { MeasureMode, MeasureResult } from '../components/measure-panel/types';
|
import type { MeasureConfig, MeasureMode, MeasureResult } from '../components/measure-panel/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 测量弹窗管理器
|
* 测量弹窗管理器
|
||||||
@@ -11,6 +11,11 @@ export class MeasureDialogManager extends BimComponent {
|
|||||||
private dialogId = 'measure-dialog';
|
private dialogId = 'measure-dialog';
|
||||||
private dialog: BimDialog | null = null;
|
private dialog: BimDialog | null = null;
|
||||||
private panel: MeasurePanel | null = null;
|
private panel: MeasurePanel | null = null;
|
||||||
|
/**
|
||||||
|
* 测量配置项(单位/精度)
|
||||||
|
* 说明:MeasurePanel 会自行从缓存加载默认配置,Manager 这里只做“对外读取/设置”的镜像。
|
||||||
|
*/
|
||||||
|
private config: MeasureConfig | null = null;
|
||||||
|
|
||||||
constructor(engine: BimEngine) {
|
constructor(engine: BimEngine) {
|
||||||
super(engine);
|
super(engine);
|
||||||
@@ -63,6 +68,8 @@ export class MeasureDialogManager extends BimComponent {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.panel.init();
|
this.panel.init();
|
||||||
|
// 同步一次当前配置(由组件从缓存/默认加载)
|
||||||
|
this.config = this.panel.getConfig();
|
||||||
|
|
||||||
// 注意:你要求“组件本身不加边距”,因此在 Manager 这里用 wrapper 增加左右内边距
|
// 注意:你要求“组件本身不加边距”,因此在 Manager 这里用 wrapper 增加左右内边距
|
||||||
// 这样 MeasurePanel 可以保持通用性,避免在不同场景复用时产生多余 padding。
|
// 这样 MeasurePanel 可以保持通用性,避免在不同场景复用时产生多余 padding。
|
||||||
@@ -109,14 +116,56 @@ export class MeasureDialogManager extends BimComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置测量结果(由外部注入,仅用于显示)
|
* 设置测量结果(推荐使用的新方法名)
|
||||||
|
* 说明:内部直接调用 MeasurePanel.setResult()
|
||||||
* @param result 测量结果;传 null 表示清空
|
* @param result 测量结果;传 null 表示清空
|
||||||
*/
|
*/
|
||||||
public setResult(result: MeasureResult | null): void {
|
public setMeasureResult(result: MeasureResult | null): void {
|
||||||
if (!this.panel) return;
|
// 按你的要求:仅当 panel 存在时才调用,不做缓存
|
||||||
|
if (!this.panel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.panel.setResult(result);
|
this.panel.setResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取测量配置(单位/精度)
|
||||||
|
* - 如果面板存在:返回面板当前配置
|
||||||
|
* - 否则:返回 Manager 缓存的最后一次配置(可能为 null)
|
||||||
|
*/
|
||||||
|
public getConfig(): MeasureConfig | null {
|
||||||
|
if (this.panel) {
|
||||||
|
this.config = this.panel.getConfig();
|
||||||
|
}
|
||||||
|
return this.config ? { ...this.config } : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置测量配置(单位/精度)
|
||||||
|
* @param partial 部分更新
|
||||||
|
* @param persist 是否写入缓存(默认 true)
|
||||||
|
*/
|
||||||
|
public setConfig(partial: Partial<MeasureConfig>, persist: boolean = true): void {
|
||||||
|
// 面板存在则直接设置面板;否则仅更新 Manager 缓存
|
||||||
|
if (this.panel) {
|
||||||
|
this.panel.setConfig(partial, persist);
|
||||||
|
this.config = this.panel.getConfig();
|
||||||
|
// 配置变化可能影响高度(比如设置面板显示/隐藏),安全起见做一次 fit
|
||||||
|
this.dialog?.fitHeight(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 面板未创建:只更新本地缓存
|
||||||
|
const prev = this.config;
|
||||||
|
const next: MeasureConfig = {
|
||||||
|
unit: partial.unit ?? prev?.unit ?? 'mm',
|
||||||
|
precision: partial.precision ?? prev?.precision ?? 2
|
||||||
|
};
|
||||||
|
this.config = next;
|
||||||
|
// 注意:缓存写入由 MeasurePanel 负责(你要求默认维护在组件里)
|
||||||
|
// 这里不写 localStorage,避免重复逻辑。
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除全部(仅清空 UI;真实测量清理逻辑后续再接)
|
* 删除全部(仅清空 UI;真实测量清理逻辑后续再接)
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user