@@ -3,7 +3,7 @@ import type { ThemeConfig } from '../../themes/types';
import { IBimComponent } from '../../types/component' ;
import { localeManager , t } from '../../services/locale' ;
import { themeManager } from '../../services/theme' ;
import type { MeasureMode , MeasurePanelOptions , MeasureResul t } from './types' ;
import type { MeasureConfig , MeasureMode, MeasurePanelOptions , MeasurePrecision , MeasureResult , MeasureUni t } from './types' ;
/**
* 测量面板组件(只做 UI, 不实现真实测量)
@@ -26,19 +26,53 @@ export class MeasurePanel implements IBimComponent {
private isExpanded : boolean ;
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)
private toolButtons : Map < MeasureMode , HTMLButtonElement > = new Map ( ) ;
private toggleBtn ! : HTMLButtonElement ;
private toggleTextEl ! : HTMLElement ;
private currentModeValueEl ! : HTMLElement ;
private mainValueValueEl ! : HTMLElement ;
private mainValueLabelEl ! : HTMLElement ;
private mainNumberEl ! : HTMLElement ;
private mainUnitEl ! : HTMLElement ;
private xyzBoxEl ! : HTMLElement ;
private xyzXEl ! : HTMLElement ;
private xyzYEl ! : HTMLElement ;
private xyzZEl ! : HTMLElement ;
private clearBtn ! : 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 unsubscribeTheme : ( ( ) = > void ) | null = null ;
@@ -52,6 +86,9 @@ export class MeasurePanel implements IBimComponent {
this . activeMode = options . defaultMode ? ? 'distance' ;
this . isExpanded = options . defaultExpanded ? ? false ;
// 读取配置:优先缓存,否则默认
this . config = this . loadConfigFromCache ( ) ? ? { . . . MeasurePanel . DEFAULT_CONFIG } ;
this . element = this . createDom ( ) ;
}
@@ -76,6 +113,7 @@ export class MeasurePanel implements IBimComponent {
// 初始渲染状态(按钮显隐、选中态、结果区)
this . applyExpandedState ( ) ;
this . applyActiveModeState ( ) ;
this . applyViewState ( ) ;
this . renderResult ( ) ;
}
@@ -96,6 +134,9 @@ export class MeasurePanel implements IBimComponent {
// “删除全部”颜色:截图中偏绿色,这里用 primary 做一个合理映射
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-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)' ) ;
@@ -125,10 +166,7 @@ export class MeasurePanel implements IBimComponent {
this . settingsBtn . title = t ( 'measure.actions.settings' ) ;
this . settingsBtn . setAttribute ( 'aria-label' , this . settingsBtn . title ) ;
// 4) 更新“当前方式”显示( value )
this . currentModeValueEl . textContent = t ( this . getModeI18nKey ( this . activeMode ) ) ;
// 5) 主值 label( 随模式变化)
// 4) 主值 label( 随模式变化 )
this . mainValueLabelEl . textContent = t ( this . getModeValueLabelI18nKey ( this . activeMode ) ) ;
// 6) XYZ label( 使用 key)
@@ -139,6 +177,10 @@ export class MeasurePanel implements IBimComponent {
const key = node . dataset . i18nKey ;
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 也需要更新
this . mainValueLabelEl . textContent = t ( this . getModeValueLabelI18nKey ( this . activeMode ) ) ;
this . currentModeValueEl . textContent = t ( this . getModeI18nKey ( this . activeMode ) ) ;
// 通知外部(如果需要)
if ( this . options . onModeChange ) {
@@ -201,6 +242,12 @@ export class MeasurePanel implements IBimComponent {
// 模式切换后,结果展示也应刷新(例如某些字段显示为 --)
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 {
// 进入设置面板(组件内部逻辑)
this . enterSettingsView ( ) ;
// 仍然保留回调(如果外部想监听)
if ( 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' ) ;
root . className = 'bim-measure-panel' ;
// 主视图容器(默认显示)
this . mainViewEl = document . createElement ( 'div' ) ;
this . mainViewEl . className = 'bim-measure-main' ;
// 顶部:工具按钮区
const toolsBox = document . createElement ( 'div' ) ;
toolsBox . className = 'bim-measure-tools' ;
@@ -355,25 +437,12 @@ export class MeasurePanel implements IBimComponent {
toggleBox . appendChild ( this . toggleBtn ) ;
toolsBox . appendChild ( toggleBox ) ;
root . appendChild ( toolsBox ) ;
this . mainViewEl . appendChild ( toolsBox ) ;
// 中部:结果区
const resultBox = document . createElement ( 'div' ) ;
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' ) ;
mainValueRow . className = 'bim-measure-row' ;
@@ -383,6 +452,18 @@ export class MeasurePanel implements IBimComponent {
const mainValueValue = document . createElement ( 'span' ) ;
mainValueValue . className = 'value' ;
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 ( mainValueValue ) ;
resultBox . appendChild ( mainValueRow ) ;
@@ -390,27 +471,28 @@ export class MeasurePanel implements IBimComponent {
// XYZ
const xyzBox = document . createElement ( 'div' ) ;
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' ) ;
row . className = 'bim-measure-row' ;
const label = document . createElement ( 'span' ) ;
label . className = 'label' ;
label . dataset . i18nKey = labelKey ;
const value = document . createElement ( 'span' ) ;
value . className = ' value' ;
value . className = ` value ${ valueClassName } ` ;
valueElSetter ( value ) ;
row . appendChild ( label ) ;
row . appendChild ( value ) ;
return row ;
} ;
xyzBox . appendChild ( makeXyzRow ( 'measure.labels.x' , ( el ) = > ( this . xyzXEl = el ) ) ) ;
xyzBox . appendChild ( makeXyzRow ( 'measure.labels.y' , ( el ) = > ( this . xyzYEl = el ) ) ) ;
xyzBox . appendChild ( makeXyzRow ( 'measure.labels.z' , ( el ) = > ( this . xyzZEl = el ) ) ) ;
xyzBox . appendChild ( makeXyzRow ( 'measure.labels.x' , 'bim-measure-xyz-x' , ( el ) = > ( this . xyzXEl = el ) ) ) ;
xyzBox . appendChild ( makeXyzRow ( 'measure.labels.y' , 'bim-measure-xyz-y' , ( el ) = > ( this . xyzYEl = el ) ) ) ;
xyzBox . appendChild ( makeXyzRow ( 'measure.labels.z' , 'bim-measure-xyz-z' , ( el ) = > ( this . xyzZEl = el ) ) ) ;
resultBox . appendChild ( xyzBox ) ;
root . appendChild ( resultBox ) ;
this . mainViewEl . appendChild ( resultBox ) ;
// 底部:删除全部 + 设置
const footer = document . createElement ( 'div' ) ;
@@ -437,11 +519,230 @@ export class MeasurePanel implements IBimComponent {
footer . appendChild ( this . clearBtn ) ;
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 ;
}
/**
* 创建“设置面板”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 个按钮
*/
@@ -482,23 +783,53 @@ export class MeasurePanel implements IBimComponent {
* 渲染结果区(根据 activeMode 从 result 里取对应字段)
*/
private renderResult ( ) : void {
// 1) 主值
const mainText = this . formatMainValue ( this . activeMode , this . result ) ;
this . mainValueValueEl . textContent = mainText ;
// 1) 根据模式决定结果区显示规则
// 你给的规则:
// - 距离:显示数值 + xyz
// - 最小距离:只显示数值
// - 角度:--°
// - 标高:--m( 固定 m)
// - 体积:--mm³( 单位随设置变动, 即 unit³)
// - 激光测距:不显示任何数值/xyz, 只显示“激光测距”文字
// - 坡度:--%
// - 空间体积:--mm³( 单位随设置变动, 即 unit³)
// 2) XYZ
const xyz = this . result ? . xyz ;
if ( ! xyz ) {
this . xyzXEl . textContent = '-- ' ;
this . xyzY El. textContent = '--' ;
this . xyzZ El. textContent = '-- ' ;
// 1.1) 主行:默认显示 label + value( 数值/单位拆分)
// 激光测距:只显示文字,因此隐藏 label/单位
if ( this . activeMode === 'laserDistance' ) {
this . mainValueLabelEl . style . display = 'none ' ;
this . mainNumber El. textContent = t ( this . getModeI18nKey ( 'laserDistance' ) ) ;
this . mainUnit El. 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 ;
}
// 为了可读性:这里不做 fancy formatter, 只做基础展示
this . xyzX El . textContent = this . formatNumber ( xyz . x ) ;
this . xyzYEl . textContent = this . formatNumber ( xyz . y ) ;
this . xyzZEl . textContent = this . formatNumber ( xyz . z ) ;
// 非 distance: 隐藏 xyz
this . xyzBox El . style . display = 'none' ;
}
/**
@@ -515,53 +846,144 @@ export class MeasurePanel implements IBimComponent {
return ` measure.labels.value. ${ mode } ` ;
}
/**
* 将“当前模式”的主值格式化为文本
* @param mode 当前模式
* @param result 当前结果
*/
private formatMainValue ( mode : MeasureMode , result : MeasureResult | null ) : string {
if ( ! result ) return '--' ;
// 注意:旧的 formatMainValue/formatWithFixedUnit 已被 formatMainValueParts 替代,
// 以支持“数值与单位分色显示”和“无数据时仍展示单位”。
// 根据不同 mode 读取对应字段并格式化单位
// 单位文本也走国际化(可替换为英文/中文 )
switch ( mode ) {
case 'distance' :
return this . formatWithUnit ( result . distanceMm , 'measure.units.mm' ) ;
case 'minDistance' :
return this . formatWithUnit ( result . minDistanceMm , 'measure.units.mm' ) ;
case 'angle' :
return this . formatWithUnit ( result . angleDeg , 'measure.units.deg' ) ;
case 'elevation' :
return this . formatWithUnit ( result . elevationMm , 'm easure.u nits.mm' ) ;
case 'volume' :
return this . formatWithUnit ( result . volumeM3 , 'measure.units.m3' ) ;
case 'laserDistance' :
return this . formatWithUnit ( result . laserDistanceMm , 'measure.units.mm' ) ;
case 'slope' :
return this . formatWithUnit ( result . slopePercent , 'measure.units.percent' ) ;
case 'spaceVolume' :
return this . formatWithUnit ( result . spaceVolumeM3 , 'measure.units.m3' ) ;
/**
* 基础数字格式化(按精度显示 )
*/
private formatNumberWithPrecision ( value : number , precision : MeasurePrecision ) : string {
// 你要求精度可选: 0 / 0.0 / 0.00 / 0.000,因此这里不做 trim, 严格按 toFixed 输出
return value . toFixed ( precision ) ;
}
// 注意:旧的 formatLengthWithConfig 已被 formatLengthParts 替代。
private convertMmToUnit ( mm : number , unit : M easureU nit) : number {
switch ( unit ) {
case 'mm' :
return mm ;
case 'cm' :
return mm / 10 ;
case 'm' :
return mm / 1000 ;
case 'km' :
return mm / 1 _000_000 ;
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 {
if ( valu e === null || value === undefined || Number . isNaN ( value ) ) return '--' ;
return ` ${ this . formatNumber ( value ) } ${ t ( unitKey ) } ` ;
private formatMainValueParts ( mode : MeasureMode , result : MeasureResult | null ) : { numberText : string ; unitText : string } {
if ( mod e === 'laserDistance' ) return { numberText : t ( this . getModeI18nKey ( 'laserDistance' ) ) , unitText : '' } ;
// 没有数据:显示 `-- 单位`
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 formatNumber ( value : number ) : string {
// 保留 3 位小数以内(简单策略:整数不带小数,非整数保留到 3 位)
if ( Number . isInteger ( value ) ) return String ( value ) ;
return value . toFixed ( 3 ) . replace ( /0+$/g , '' ) . replace ( /\.$/g , '' ) ;
private getEmptyValuePartsByMode ( mode : MeasureMode ) : { numberText : string ; unitText : string } {
switch ( mode ) {
case 'distance' :
case 'minDistance' :
return { numberText : '--' , unitText : t ( this . getUnitI18nKey ( this . config . unit ) ) } ;
case 'angle' :
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 } ;
}
}