feat: add settings dialog with render mode selection

- Add SettingDialogManager with radio-style render mode picker (simple/balance/advanced)
- Add getRenderMode/setRenderMode API to Engine and EngineManager layers
- Wire setting toolbar button to toggle the settings dialog
- Add i18n keys for settings dialog (zh-CN/en-US)
- Add version display at bottom-right of engine wrapper
- Bump version to 1.1.7, rebuild and sync demo libs
This commit is contained in:
yuding
2026-02-28 11:26:59 +08:00
parent a9c8317b10
commit 837177f3f2
15 changed files with 875 additions and 525 deletions

View File

@@ -22,3 +22,17 @@
left: 20px !important;
z-index: 100;
}
.bim-engine-version {
position: absolute;
bottom: 6px;
right: 10px;
font-size: 11px;
color: rgba(0, 0, 0, 0.25);
pointer-events: none;
user-select: none;
z-index: 1;
font-family: system-ui, -apple-system, sans-serif;
letter-spacing: 0.3px;
}

View File

@@ -1,3 +1,4 @@
declare const __APP_VERSION__: string;
import './bim-engine.css';
import { ToolbarManager } from './managers/toolbar-manager';
import { ButtonGroupManager } from './managers/button-group-manager';
@@ -12,6 +13,7 @@ import { SectionAxisDialogManager } from './managers/section-axis-dialog-manager
import { SectionBoxDialogManager } from './managers/section-box-dialog-manager';
import { WalkControlManager } from './managers/walk-control-manager';
import { EngineInfoDialogManager } from './managers/engine-info-dialog-manager';
import { SettingDialogManager } from './managers/setting-dialog-manager';
import { ComponentDetailManager } from './managers/component-detail-manager';
import { AiChatManager } from './managers/ai-chat-manager';
import type { EngineOptions, ModelLoadOptions } from './components/engine';
@@ -44,6 +46,7 @@ export class BimEngine {
public engineInfo: EngineInfoDialogManager | null = null;
public componentDetail: ComponentDetailManager | null = null;
public aiChat: AiChatManager | null = null;
public setting: SettingDialogManager | null = null;
constructor(
container: HTMLElement | string,
@@ -100,6 +103,11 @@ export class BimEngine {
this.wrapper.className = 'bim-engine-wrapper';
this.container.appendChild(this.wrapper);
const versionEl = document.createElement('div');
versionEl.className = 'bim-engine-version';
versionEl.textContent = `v${__APP_VERSION__}`;
this.wrapper.appendChild(versionEl);
this.registry.container = this.container;
this.registry.wrapper = this.wrapper;
@@ -141,6 +149,10 @@ export class BimEngine {
this.registry.aiChat = this.aiChat;
this.aiChat.init();
this.setting = new SettingDialogManager(this.registry);
this.registry.setting = this.setting;
this.setting.init();
this.updateTheme(themeManager.getTheme());
themeManager.subscribe((theme) => {
this.updateTheme(theme);
@@ -166,6 +178,7 @@ export class BimEngine {
this.sectionBox?.destroy();
this.walkControl?.destroy();
this.aiChat?.destroy();
this.setting?.destroy();
this.container.innerHTML = '';
this.registry.reset();
}

View File

@@ -2,7 +2,7 @@ import type { ButtonConfig } from '../../../index.type';
import { getIcon } from '../../../../../utils/icon-manager';
import type { ManagerRegistry } from '../../../../../core/manager-registry';
export const createSettingButton = (_registry?: ManagerRegistry): ButtonConfig => {
export const createSettingButton = (registry?: ManagerRegistry): ButtonConfig => {
return {
id: 'setting',
groupId: 'group-2',
@@ -10,8 +10,8 @@ export const createSettingButton = (_registry?: ManagerRegistry): ButtonConfig =
label: 'toolbar.setting',
icon: getIcon('设置'),
keepActive: false,
onClick: (button) => {
console.log('设置按钮被点击:', button.id);
onClick: (_button) => {
registry?.setting?.toggle();
}
};
};

View File

@@ -557,6 +557,33 @@ export class Engine implements IBimComponent {
// ==================== 结束:剖切功能 ====================
// ==================== 渲染模式 ====================
/**
* 获取当前渲染模式
* @returns 'simple' | 'balance' | 'advanced'
*/
public getRenderMode(): string {
if (!this._isInitialized || !this.engine?.engineModelModule) {
return 'balance';
}
return this.engine.engineModelModule.getCurrentMode();
}
/**
* 设置渲染模式
* @param mode 'simple'(性能模式) | 'balance'(平衡模式) | 'advanced'(效果模式)
*/
public setRenderMode(mode: 'simple' | 'balance' | 'advanced'): void {
if (!this._isInitialized || !this.engine?.engineModelModule) {
console.warn('[Engine] Cannot set render mode: engine not initialized.');
return;
}
this.engine.engineModelModule.setMode(mode);
}
// ==================== 结束:渲染模式 ====================
// ==================== 漫游功能 ====================
/** 漫游模式是否激活 */

View File

@@ -20,6 +20,7 @@ import type { SectionBoxDialogManager } from '../managers/section-box-dialog-man
import type { WalkPathDialogManager } from '../managers/walk-path-dialog-manager';
import type { WalkPlanViewDialogManager } from '../managers/walk-plan-view-dialog-manager';
import type { EngineInfoDialogManager } from '../managers/engine-info-dialog-manager';
import type { SettingDialogManager } from '../managers/setting-dialog-manager';
import type { ComponentDetailManager } from '../managers/component-detail-manager';
import type { AiChatManager } from '../managers/ai-chat-manager';
@@ -69,6 +70,8 @@ export class ManagerRegistry {
public componentDetail: ComponentDetailManager | null = null;
/** AI 聊天管理器 */
public aiChat: AiChatManager | null = null;
/** 设置对话框管理器 */
public setting: SettingDialogManager | null = null;
constructor() {}
@@ -93,6 +96,7 @@ export class ManagerRegistry {
this.engineInfo = null;
this.componentDetail = null;
this.aiChat = null;
this.setting = null;
}
/**

View File

@@ -209,5 +209,14 @@ export const enUS: TranslationDictionary = {
other: 'Other',
otherPlaceholder: 'Enter custom answer',
submit: 'Submit'
},
setting: {
dialogTitle: 'Settings',
renderMode: 'Render Mode',
modes: {
simple: 'Performance',
balance: 'Balanced',
advanced: 'Quality',
}
}
};

View File

@@ -231,6 +231,15 @@ export interface TranslationDictionary {
otherPlaceholder: string;
submit: string;
};
setting: {
dialogTitle: string;
renderMode: string;
modes: {
simple: string;
balance: string;
advanced: string;
};
};
}
/**

View File

@@ -209,5 +209,14 @@ export const zhCN: TranslationDictionary = {
other: '其他',
otherPlaceholder: '请输入自定义答案',
submit: '提交'
},
setting: {
dialogTitle: '设置',
renderMode: '渲染模式',
modes: {
simple: '性能模式',
balance: '平衡模式',
advanced: '效果模式',
}
}
};

View File

@@ -685,6 +685,26 @@ export class EngineManager extends BaseManager {
// ==================== 结束:构件操作 ====================
// ==================== 渲染模式 ====================
/**
* 获取当前渲染模式
* @returns 'simple' | 'balance' | 'advanced'
*/
public getRenderMode(): string {
return this.engineInstance?.getRenderMode() ?? 'balance';
}
/**
* 设置渲染模式
* @param mode 'simple' | 'balance' | 'advanced'
*/
public setRenderMode(mode: 'simple' | 'balance' | 'advanced'): void {
this.engineInstance?.setRenderMode(mode);
}
// ==================== 结束:渲染模式 ====================
/** 销毁引擎管理器 */
public destroy(): void {
if (this.engineInstance) {

View File

@@ -0,0 +1,107 @@
import { BaseDialogManager } from '../core/base-dialog-manager';
import { ManagerRegistry } from '../core/manager-registry';
import { t } from '../services/locale';
type RenderMode = 'simple' | 'balance' | 'advanced';
export class SettingDialogManager extends BaseDialogManager {
protected get dialogId() { return 'setting-dialog'; }
protected get dialogTitle() { return 'setting.dialogTitle'; }
protected get dialogWidth() { return 280; }
constructor(registry: ManagerRegistry) {
super(registry);
}
public init(): void {}
protected getDialogPosition() {
const container = this.registry.container;
if (!container) return { x: 100, y: 100 };
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight;
return {
x: (containerWidth - this.dialogWidth) / 2,
y: (containerHeight - 200) / 2
};
}
protected createContent(): HTMLElement {
const currentMode = this.registry.engine3d?.getRenderMode() ?? 'balance';
const content = document.createElement('div');
content.style.cssText = 'padding: 16px;';
// 渲染模式标题
const label = document.createElement('div');
label.style.cssText = 'font-size: 13px; color: var(--bim-text-secondary, #94a3b8); margin-bottom: 12px;';
label.textContent = t('setting.renderMode');
content.appendChild(label);
// 三个模式选项
const modes: { key: RenderMode; labelKey: string }[] = [
{ key: 'simple', labelKey: 'setting.modes.simple' },
{ key: 'balance', labelKey: 'setting.modes.balance' },
{ key: 'advanced', labelKey: 'setting.modes.advanced' },
];
const group = document.createElement('div');
group.style.cssText = 'display: flex; flex-direction: column; gap: 4px;';
for (const mode of modes) {
const item = document.createElement('div');
item.style.cssText = `
display: flex; align-items: center; gap: 10px;
padding: 10px 12px; border-radius: 6px; cursor: pointer;
transition: background 0.15s;
background: ${mode.key === currentMode ? 'var(--bim-primary, #3b82f6)' : 'transparent'};
color: ${mode.key === currentMode ? 'var(--bim-text-inverse, #fff)' : 'var(--bim-text-primary, #fff)'};
`;
// 圆点指示器
const dot = document.createElement('div');
dot.style.cssText = `
width: 16px; height: 16px; border-radius: 50%; flex-shrink: 0;
border: 2px solid ${mode.key === currentMode ? 'var(--bim-text-inverse, #fff)' : 'var(--bim-text-tertiary, #64748b)'};
display: flex; align-items: center; justify-content: center;
`;
if (mode.key === currentMode) {
const inner = document.createElement('div');
inner.style.cssText = 'width: 8px; height: 8px; border-radius: 50%; background: var(--bim-text-inverse, #fff);';
dot.appendChild(inner);
}
const text = document.createElement('span');
text.style.cssText = 'font-size: 14px; font-weight: 500;';
text.textContent = t(mode.labelKey);
item.appendChild(dot);
item.appendChild(text);
item.addEventListener('mouseenter', () => {
if (mode.key !== currentMode) {
item.style.background = 'var(--bim-floating-btn-bg, rgba(255,255,255,0.08))';
}
});
item.addEventListener('mouseleave', () => {
if (mode.key !== currentMode) {
item.style.background = 'transparent';
}
});
item.addEventListener('click', () => {
this.registry.engine3d?.setRenderMode(mode.key);
// 重新渲染弹窗内容
this.hide();
this.show();
});
group.appendChild(item);
}
content.appendChild(group);
return content;
}
}