添加折叠面板

This commit is contained in:
yuding
2025-12-22 15:39:58 +08:00
parent 005535a26d
commit ed0414c75b
29 changed files with 2759 additions and 1416 deletions

View File

@@ -5,6 +5,7 @@ import { DialogManager } from './managers/dialog-manager';
import { EngineManager } from './managers/engine-manager';
import { RightKeyManager } from './managers/right-key-manager';
import { ConstructTreeManagerBtn } from './managers/construct-tree-manager-btn';
import { PropertyPanelManager } from './managers/property-panel-manager';
import type { EngineOptions, ModelLoadOptions } from './components/engine';
import { localeManager } from './services/locale';
import { themeManager } from './services/theme';
@@ -25,6 +26,7 @@ export class BimEngine extends EventEmitter {
public dialog: DialogManager | null = null;
public engine: EngineManager | null = null; // 3D 引擎管理器
public rightKey: RightKeyManager | null = null; // 右键菜单管理器
public propertyPanel: PropertyPanelManager | null = null; // 属性面板 (演示 Collapse)
constructor(
@@ -78,6 +80,7 @@ export class BimEngine extends EventEmitter {
this.buttonGroup = new ButtonGroupManager(this, this.wrapper);
this.rightKey = new RightKeyManager(this, this.wrapper);
this.constructTreeBtn = new ConstructTreeManagerBtn(this, this.wrapper);
this.propertyPanel = new PropertyPanelManager(this);
// 初始主题
this.updateTheme(themeManager.getTheme());
@@ -101,6 +104,7 @@ export class BimEngine extends EventEmitter {
this.engine?.destroy();
this.dialog?.destroy();
this.rightKey?.destroy();
this.propertyPanel?.destroy();
this.container.innerHTML = '';
this.clear();
}

View File

@@ -0,0 +1 @@
export const infoIcon = '<svg viewBox="0 0 1024 1024"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z" fill="currentColor"/><path d="M464 336a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm72 112h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V456c0-4.4-3.6-8-8-8z" fill="currentColor"/></svg>';

View File

@@ -1,16 +1,13 @@
import type { ButtonConfig } from '../../../index.type';
import { ButtonConfig } from '../../../index.type';
import { infoIcon } from './icon';
/**
* 定位按钮配置
*/
export const infoButton: ButtonConfig = {
id: 'info',
groupId: 'group-2',
id: 'toolbar-info',
type: 'button',
label: 'toolbar.info',
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M12 7q.425 0 .713-.288T13 6t-.288-.712T12 5t-.712.288T11 6t.288.713T12 7m0 8q.425 0 .713-.288T13 14v-4q0-.425-.288-.712T12 9t-.712.288T11 10v4q0 .425.288.713T12 15m-6 3l-2.3 2.3q-.475.475-1.088.213T2 19.575V4q0-.825.588-1.412T4 2h16q.825 0 1.413.588T22 4v12q0 .825-.587 1.413T20 18z"/></svg>',
keepActive: false,
onClick: (button) => {
console.log('信息按钮被点击:', button.id);
icon: infoIcon,
onClick: () => {
// WORKAROUND: Dispatch a standard custom event on document
document.dispatchEvent(new CustomEvent('bim-demo:open-property-panel'));
}
};

View File

@@ -0,0 +1,121 @@
/* Root Container */
.bim-collapse {
background-color: var(--bim-bg-color, #ffffff);
border: 1px solid var(--bim-border-color, #d9d9d9);
border-radius: 4px;
font-size: 14px;
color: var(--bim-text-color, rgba(0, 0, 0, 0.88));
}
.bim-collapse.is-ghost {
background-color: transparent;
border: none;
}
.bim-collapse.is-ghost .bim-collapse-item {
border-bottom: none;
}
.bim-collapse.is-ghost .bim-collapse-header {
background-color: transparent;
padding-left: 0;
padding-right: 0;
}
.bim-collapse.is-ghost .bim-collapse-content {
background-color: transparent;
border-top: none;
}
/* Item */
.bim-collapse-item {
border-bottom: 1px solid var(--bim-border-color, #d9d9d9);
}
.bim-collapse-item:last-child {
border-bottom: none;
}
.bim-collapse-item.is-disabled .bim-collapse-header {
color: var(--bim-disabled-color, rgba(0, 0, 0, 0.25));
cursor: not-allowed;
}
/* Header */
.bim-collapse-header {
display: flex;
align-items: center;
padding: 12px 16px;
background-color: var(--bim-header-bg-color, rgba(0, 0, 0, 0.02));
cursor: pointer;
transition: all 0.3s;
position: relative;
}
.bim-collapse-header:hover {
background-color: var(--bim-header-hover-bg-color, rgba(0, 0, 0, 0.05));
}
/* Arrow Icon */
.bim-collapse-arrow {
margin-right: 12px;
font-size: 12px;
width: 12px;
height: 12px;
transition: transform 0.24s;
display: inline-flex;
align-items: center;
justify-content: center;
}
.bim-collapse-arrow svg {
width: 100%;
height: 100%;
fill: currentColor;
}
.bim-collapse-item.is-active .bim-collapse-arrow {
transform: rotate(90deg);
}
/* Icon (User provided) */
.bim-collapse-icon {
margin-right: 8px;
display: inline-flex;
align-items: center;
}
.bim-collapse-icon svg {
width: 16px;
height: 16px;
fill: currentColor;
}
/* Title */
.bim-collapse-title {
flex: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/* Extra */
.bim-collapse-extra {
margin-left: auto;
}
/* Content */
.bim-collapse-content {
overflow: hidden;
background-color: var(--bim-content-bg-color, #ffffff);
border-top: 1px solid var(--bim-border-color, #d9d9d9);
transition: height 0.2s ease-in-out, opacity 0.2s ease-in-out;
}
.bim-collapse-content.is-hidden {
display: none;
}
.bim-collapse-content-box {
padding: 16px;
}

View File

@@ -0,0 +1,244 @@
import './index.css';
import { CollapseOptions, CollapseItemConfig } from './types';
import { IBimComponent } from '../../types/component';
import { t, localeManager } from '../../services/locale';
import { themeManager } from '../../services/theme';
import type { ThemeConfig } from '../../themes/types';
/**
* 单个折叠面板项
*/
class BimCollapseItem {
public element: HTMLElement;
public headerEl!: HTMLElement;
public contentEl!: HTMLElement;
public contentBoxEl!: HTMLElement;
public arrowEl!: HTMLElement;
public titleEl!: HTMLElement;
private config: CollapseItemConfig;
private parent: BimCollapse;
constructor(config: CollapseItemConfig, parent: BimCollapse) {
this.config = config;
this.parent = parent;
this.element = this.createDom();
}
private createDom(): HTMLElement {
const itemEl = document.createElement('div');
itemEl.className = `bim-collapse-item ${this.config.className || ''}`;
if (this.config.disabled) itemEl.classList.add('is-disabled');
itemEl.dataset.id = this.config.id;
// <20><>部区域
this.headerEl = document.createElement('div');
this.headerEl.className = 'bim-collapse-header';
// 箭头图标
this.arrowEl = document.createElement('span');
this.arrowEl.className = 'bim-collapse-arrow';
this.arrowEl.innerHTML = `<svg viewBox="0 0 1024 1024"><path d="M288 192l448 320-448 320z"></path></svg>`;
this.headerEl.appendChild(this.arrowEl);
// 自定义图标 (可选)
if (this.config.icon) {
const iconEl = document.createElement('span');
iconEl.className = 'bim-collapse-icon';
iconEl.innerHTML = this.config.icon;
this.headerEl.appendChild(iconEl);
}
// 标题文本
this.titleEl = document.createElement('span');
this.titleEl.className = 'bim-collapse-title';
this.titleEl.textContent = t(this.config.title); // 初始翻译
this.headerEl.appendChild(this.titleEl);
// 额外内容 (可选,如右侧标签)
if (this.config.extra) {
const extraEl = document.createElement('div');
extraEl.className = 'bim-collapse-extra';
if (typeof this.config.extra === 'string') {
extraEl.innerHTML = this.config.extra;
} else {
extraEl.appendChild(this.config.extra);
}
this.headerEl.appendChild(extraEl);
}
// 点击事件
this.headerEl.addEventListener('click', () => {
if (this.config.disabled) return;
this.parent.toggleItem(this.config.id);
});
itemEl.appendChild(this.headerEl);
// 内容区域
this.contentEl = document.createElement('div');
this.contentEl.className = 'bim-collapse-content is-hidden';
this.contentBoxEl = document.createElement('div');
this.contentBoxEl.className = 'bim-collapse-content-box';
if (typeof this.config.content === 'string') {
this.contentBoxEl.innerHTML = this.config.content;
} else {
this.contentBoxEl.appendChild(this.config.content);
}
this.contentEl.appendChild(this.contentBoxEl);
itemEl.appendChild(this.contentEl);
return itemEl;
}
public updateLocale() {
if (this.titleEl) {
this.titleEl.textContent = t(this.config.title);
}
}
public setActive(isActive: boolean) {
if (isActive) {
this.element.classList.add('is-active');
this.contentEl.classList.remove('is-hidden');
// 简单的动画处理:设置 height
// 实际生产中可能需要更复杂的 JS 动画库或 transitionend 事件处理
// 这里依赖 CSS transition
} else {
this.element.classList.remove('is-active');
this.contentEl.classList.add('is-hidden');
}
}
}
/**
* 折叠面板组件
*/
export class BimCollapse implements IBimComponent {
private element: HTMLElement;
private options: CollapseOptions;
private items: Map<string, BimCollapseItem> = new Map();
private activeIds: Set<string> = new Set();
private unsubscribeLocale: (() => void) | null = null;
private unsubscribeTheme: (() => void) | null = null;
constructor(options: CollapseOptions) {
this.options = {
bordered: true,
accordion: false,
...options
};
this.element = document.createElement('div');
this.element.className = `bim-collapse ${this.options.className || ''}`;
if (!this.options.bordered) this.element.style.border = 'none';
if (this.options.ghost) this.element.classList.add('is-ghost');
const container = typeof this.options.container === 'string'
? document.getElementById(this.options.container)
: this.options.container;
if (container) {
container.appendChild(this.element);
}
// 初始化激活的 ID
if (this.options.activeIds) {
this.options.activeIds.forEach(id => this.activeIds.add(id));
}
this.init();
}
public init() {
// 创建子项
this.options.items.forEach(itemConfig => {
const item = new BimCollapseItem(itemConfig, this);
this.items.set(itemConfig.id, item);
this.element.appendChild(item.element);
// 设置初始状态
if (this.activeIds.has(itemConfig.id)) {
item.setActive(true);
}
});
// 订阅语言变更
this.unsubscribeLocale = localeManager.subscribe(() => {
this.setLocales();
});
// 订阅主题变更
this.unsubscribeTheme = themeManager.subscribe((theme) => {
this.setTheme(theme);
});
// 初始应用主题
this.setTheme(themeManager.getTheme());
}
public toggleItem(id: string) {
const isActive = this.activeIds.has(id);
if (this.options.accordion) {
// 手风琴模式:关闭其他所有,只展开目标
this.activeIds.clear();
if (!isActive) {
this.activeIds.add(id);
}
} else {
// 普通模式:切换目标状态
if (isActive) {
this.activeIds.delete(id);
} else {
this.activeIds.add(id);
}
}
this.refreshState();
if (this.options.onChange) {
this.options.onChange(Array.from(this.activeIds));
}
}
private refreshState() {
this.items.forEach((item, id) => {
item.setActive(this.activeIds.has(id));
});
}
public setTheme(theme: ThemeConfig): void {
const style = this.element.style;
style.setProperty('--bim-bg-color', theme.panelBackground);
style.setProperty('--bim-border-color', theme.border);
style.setProperty('--bim-text-color', theme.textPrimary);
// 头部默认背景色使用 componentBackground
style.setProperty('--bim-header-bg-color', theme.componentHover);
style.setProperty('--bim-header-hover-bg-color', theme.componentHover);
style.setProperty('--bim-content-bg-color', theme.panelBackground);
style.setProperty('--bim-disabled-color', theme.textSecondary);
}
public setLocales(): void {
this.items.forEach(item => item.updateLocale());
}
public destroy(): void {
if (this.unsubscribeLocale) {
this.unsubscribeLocale();
this.unsubscribeLocale = null;
}
if (this.unsubscribeTheme) {
this.unsubscribeTheme();
this.unsubscribeTheme = null;
}
this.element.remove();
this.items.clear();
}
}

View File

@@ -0,0 +1,49 @@
export interface CollapseItemConfig {
/** 唯一标识符 */
id: string;
/** 标题文本的翻译键 (例如 'panel.attributes') */
title: string;
/** 内容: HTML字符串 或 HTMLElement */
content: string | HTMLElement;
/** 标题栏左侧图标 (SVG 字符串, 可选) */
icon?: string;
/** 标题栏右侧额外内容 (可选) */
extra?: string | HTMLElement;
/** 是否禁用 */
disabled?: boolean;
/** 自定义类名 */
className?: string;
}
export interface CollapseOptions {
/** 挂载容器 */
container: HTMLElement | string;
/** 面板项列表 */
items: CollapseItemConfig[];
/** 是否开启手风琴模式 (默认 false) */
accordion?: boolean;
/** 初始展开的面板 ID 列表 */
activeIds?: string[];
/** 是否显示边框 (默认 true) */
bordered?: boolean;
/** 是否幽灵模式 (默认 false) */
ghost?: boolean;
/** 自定义类名 */
className?: string;
/** 切换面板时的回调 */
onChange?: (activeIds: string[]) => void;
}

View File

@@ -62,7 +62,6 @@
.bim-dialog-content {
flex: 1;
padding: 10px;
overflow: auto;
font-size: 14px;
color: var(--bim-dialog-text-color);
@@ -93,4 +92,4 @@
.bim-dialog-resize-handle:hover::after {
border-color: #fff;
}
}

View File

@@ -8,6 +8,14 @@ import { createEngine as createEngineSDK } from '../../bim-engine-sdk.es.js';
// 重新导出类型,方便外部引用
export type { EngineOptions, ModelLoadOptions };
/**
* 创建 Engine 实例的工厂函数
* 兼容旧代码直接 import { createEngine } 的方式
*/
export const createEngine = (options: EngineOptions) => {
return new Engine(options);
};
/**
* 3D 引擎组件
* 负责创建和管理第三方 3D 引擎实例

View File

@@ -0,0 +1,108 @@
.bim-tab {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
background: transparent;
color: var(--bim-tab-text, #e6e6e6);
}
.bim-tab__nav {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
padding: 4px 0;
background: transparent;
}
.bim-tab__item {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 4px 0;
border: none;
border-radius: 0;
background: transparent;
color: var(--bim-tab-text, #e6e6e6);
cursor: pointer;
transition: color 0.2s ease, border-color 0.2s ease;
font-size: 14px;
border-bottom: 2px solid transparent;
}
.bim-tab__item:hover:not(.is-disabled):not(.is-active) {
color: var(--bim-tab-text, #e6e6e6);
border-bottom-color: var(--bim-tab-border, rgba(255, 255, 255, 0.15));
}
.bim-tab__item.is-active {
color: var(--bim-tab-text-active, #4da3ff);
border-bottom-color: var(--bim-tab-text-active, #4da3ff);
}
.bim-tab__item.is-disabled {
opacity: 0.5;
cursor: not-allowed;
}
.bim-tab__icon {
width: 16px;
height: 16px;
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--bim-tab-icon, currentColor);
}
.bim-tab__icon svg {
width: 100%;
height: 100%;
fill: currentColor;
}
.bim-tab__title {
white-space: nowrap;
}
.bim-tab__content {
flex: 1;
display: flex;
position: relative;
min-height: 0; /* 防止撑开导致外层滚动 */
overflow: hidden; /* 限制滚动只在内部面板 */
}
.bim-tab__panel {
display: none;
width: 100%;
height: 100%;
flex: 1;
}
.bim-tab__panel.is-active {
display: flex;
flex-direction: column;
height: 100%;
}
/* 构件树弹窗内的内容布局tab+搜索固定,树区域滚动 */
.construct-tab__container {
height: 100%;
display: flex;
flex-direction: column;
}
.construct-tab__panel-content {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0; /* 允许内部滚动 */
overflow: hidden;
}
.construct-tab__panel-content .bim-tree {
flex: 1;
}

254
src/components/tab/index.ts Normal file
View File

@@ -0,0 +1,254 @@
import { IBimComponent } from '../../types/component';
import { localeManager, t } from '../../services/locale';
import { themeManager } from '../../services/theme';
import type { ThemeConfig } from '../../themes/types';
import type { TabItem, TabOptions } from './index.type';
import './index.css';
/**
* 简单标签页组件(固定标签,不支持运行时增删)
* - 仅处理标签头部与内容切换
* - 主题从 ThemeManager 获取,不在配置中传入
* - 文案通过 t() 翻译,支持传原文直接展示
*/
export class BimTab implements IBimComponent {
/** 组件根节点 */
public element: HTMLElement;
/** 头部容器 */
private navElement: HTMLElement;
/** 内容容器 */
private contentElement: HTMLElement;
/** 业务配置 */
private options: TabOptions;
/** 当前激活的标签 id */
private activeId: string | null;
/** id -> TabItem */
private tabMap: Map<string, TabItem> = new Map();
/** id -> 内容容器 */
private panelMap: Map<string, HTMLElement> = new Map();
/** 主题/语言订阅解除函数 */
private unsubscribeLocale: (() => void) | null = null;
private unsubscribeTheme: (() => void) | null = null;
/** 头部点击事件处理引用(便于销毁时解绑) */
private navClickHandler: ((e: MouseEvent) => void) | null = null;
constructor(options: TabOptions) {
this.options = options;
this.activeId = options.activeId || (options.tabs[0]?.id ?? null);
// 预置 tabMap方便后续查找
options.tabs.forEach((tab) => this.tabMap.set(tab.id, tab));
// 构建基础 DOM 结构
this.element = document.createElement('div');
this.element.className = 'bim-tab';
this.navElement = document.createElement('div');
this.navElement.className = 'bim-tab__nav';
this.navElement.setAttribute('role', 'tablist');
this.element.appendChild(this.navElement);
this.contentElement = document.createElement('div');
this.contentElement.className = 'bim-tab__content';
this.element.appendChild(this.contentElement);
// 挂载到容器
this.options.container.appendChild(this.element);
}
/**
* 初始化组件
*/
public init(): void {
this.renderNav();
this.renderPanels();
// 初始化文案与主题
this.setLocales();
this.setTheme(themeManager.getTheme());
// 订阅语言、主题变化
this.unsubscribeLocale = localeManager.subscribe(() => this.setLocales());
this.unsubscribeTheme = themeManager.subscribe((theme) => this.setTheme(theme));
}
/**
* 渲染头部标签
*/
private renderNav(): void {
this.navElement.innerHTML = '';
this.navClickHandler = (event: MouseEvent) => {
const target = (event.target as HTMLElement).closest<HTMLButtonElement>('.bim-tab__item');
if (!target) return;
const tabId = target.dataset.id;
if (!tabId) return;
const tab = this.tabMap.get(tabId);
if (tab?.disabled) return;
this.activateTab(tabId);
};
this.navElement.addEventListener('click', this.navClickHandler);
this.options.tabs.forEach((tab) => {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'bim-tab__item';
btn.dataset.id = tab.id;
btn.setAttribute('role', 'tab');
btn.id = `tab-${tab.id}`;
btn.setAttribute('aria-selected', `${tab.id === this.activeId}`);
if (tab.disabled) {
btn.disabled = true;
btn.setAttribute('aria-disabled', 'true');
btn.classList.add('is-disabled');
}
// 图标
if (tab.icon) {
const iconEl = document.createElement('span');
iconEl.className = 'bim-tab__icon';
iconEl.innerHTML = tab.icon;
btn.appendChild(iconEl);
}
const titleEl = document.createElement('span');
titleEl.className = 'bim-tab__title';
titleEl.textContent = this.resolveTitle(tab.title);
btn.appendChild(titleEl);
if (tab.id === this.activeId) {
btn.classList.add('is-active');
}
this.navElement.appendChild(btn);
});
}
/**
* 渲染内容面板
*/
private renderPanels(): void {
this.contentElement.innerHTML = '';
this.panelMap.clear();
this.options.tabs.forEach((tab) => {
const panel = document.createElement('div');
panel.className = 'bim-tab__panel';
panel.dataset.id = tab.id;
panel.setAttribute('role', 'tabpanel');
panel.setAttribute('aria-labelledby', `tab-${tab.id}`);
if (tab.content instanceof HTMLElement) {
panel.appendChild(tab.content);
} else if (typeof tab.content === 'string') {
panel.innerHTML = tab.content;
}
if (tab.id === this.activeId) {
panel.classList.add('is-active');
} else {
panel.style.display = 'none';
}
this.panelMap.set(tab.id, panel);
this.contentElement.appendChild(panel);
});
}
/**
* 激活指定标签
* @param tabId 目标标签 id
*/
public activateTab(tabId: string): void {
if (this.activeId === tabId) return;
const targetTab = this.tabMap.get(tabId);
if (!targetTab || targetTab.disabled) return;
this.activeId = tabId;
// 更新头部状态
const buttons = this.navElement.querySelectorAll<HTMLButtonElement>('.bim-tab__item');
buttons.forEach((btn) => {
const isActive = btn.dataset.id === tabId;
btn.classList.toggle('is-active', isActive);
btn.setAttribute('aria-selected', `${isActive}`);
});
// 更新面板显示
this.panelMap.forEach((panel, id) => {
const isActive = id === tabId;
panel.classList.toggle('is-active', isActive);
panel.style.display = isActive ? 'block' : 'none';
});
if (this.options.onChange) {
this.options.onChange(tabId, targetTab);
}
}
/**
* 应用主题
*/
public setTheme(theme: ThemeConfig): void {
const style = this.element.style;
style.setProperty('--bim-tab-bg', theme.panelBackground);
style.setProperty('--bim-tab-nav-bg', theme.panelBackground);
style.setProperty('--bim-tab-text', theme.textPrimary);
style.setProperty('--bim-tab-text-secondary', theme.textSecondary);
style.setProperty('--bim-tab-text-active', theme.primary);
style.setProperty('--bim-tab-border', theme.border);
style.setProperty('--bim-tab-hover-bg', theme.componentHover);
style.setProperty('--bim-tab-active-bg', theme.componentActive);
style.setProperty('--bim-tab-icon', theme.icon);
}
/**
* 应用当前语言文案
*/
public setLocales(): void {
const buttons = this.navElement.querySelectorAll<HTMLButtonElement>('.bim-tab__item');
buttons.forEach((btn) => {
const id = btn.dataset.id;
if (!id) return;
const tab = this.tabMap.get(id);
if (!tab) return;
const titleEl = btn.querySelector<HTMLElement>('.bim-tab__title');
if (titleEl) {
titleEl.textContent = this.resolveTitle(tab.title);
}
});
}
/**
* 清理资源
*/
public destroy(): void {
if (this.navClickHandler) {
this.navElement.removeEventListener('click', this.navClickHandler);
this.navClickHandler = null;
}
if (this.unsubscribeLocale) {
this.unsubscribeLocale();
this.unsubscribeLocale = null;
}
if (this.unsubscribeTheme) {
this.unsubscribeTheme();
this.unsubscribeTheme = null;
}
this.panelMap.clear();
this.tabMap.clear();
this.element.remove();
}
/**
* 工具:解析标题(优先翻译,不存在则回退原值)
*/
private resolveTitle(title: string): string {
try {
const translated = t(title);
return translated || title;
} catch (err) {
// 翻译失败时使用原值
return title;
}
}
}

View File

@@ -0,0 +1,37 @@
/**
* 标签项定义TabItem
* 用于描述单个标签页的基础信息和内容。
*/
export interface TabItem {
/** 唯一标识 */
id: string;
/** 标题文案或翻译键,渲染时统一走 t() */
title: string;
/** 是否禁用 */
disabled?: boolean;
/** 可选图标,支持内联 SVG / HTML 字符串 */
icon?: string;
/** 可选的内容区域,支持 HTMLElement 或 HTML 字符串 */
content?: HTMLElement | string;
/** 业务侧自定义附加数据 */
meta?: Record<string, unknown>;
}
/**
* Tab 组件初始化参数
*/
export interface TabOptions {
/** 挂载容器 */
container: HTMLElement;
/** 预设的固定标签列表(不支持运行期增删) */
tabs: TabItem[];
/** 初始激活的标签 id默认使用首个标签 */
activeId?: string;
/**
* 切换回调
* @param tabId 当前激活的标签 id
* @param tab 当前激活的标签对象
*/
onChange?: (tabId: string, tab: TabItem | undefined) => void;
}

View File

@@ -1,21 +1,16 @@
import { BimEngine } from './bim-engine';
// Main Entry
export * from './bim-engine';
// 导出通用组件
export { BimButtonGroup } from './components/button-group';
export { Toolbar } from './components/button-group/toolbar';
// Types - Core
export * from './types/component';
export * from './types/events';
export type { ThemeConfig, ThemeType } from './themes/types';
// 导出相关类型定义
export type { OptButton, ButtonGroup, ButtonGroupOptions, ClickPayload } from './components/button-group/index.type';
// Types - Components
export type { EngineOptions, ModelLoadOptions } from './components/engine/types';
export type { DialogOptions, DialogPosition } from './components/dialog/index.type';
export type { ButtonConfig, ButtonGroupOptions } from './components/button-group/index.type';
export type { TreeOptions, TreeNodeConfig, TreeNodeCheckState, NodeClickAction } from './components/tree/types';
export type { CollapseOptions, CollapseItemConfig } from './components/collapse/types';
// 导出 RightKey/Menu 组件
export type { MenuItemConfig } from './components/menu/item';
export { BimMenu } from './components/menu';
export { BimRightKey } from './components/right-key';
// 导出主引擎类
export { BimEngine };
// 导出 3D 引擎相关类型
export type { EngineOptions, ModelLoadOptions } from './components/engine';
export { createEngine } from './bim-engine-sdk.es.js';
// Note: Component classes are intentionally NOT exported to enforce Manager pattern usage.

View File

@@ -16,6 +16,7 @@ export const enUS: TranslationDictionary = {
walkPerson: 'Person',
walkBird: 'Bird Eye',
walkMenu: 'Menu',
tree: 'Tree',
},
dialog: {
testTitle: 'Test Dialog',
@@ -35,5 +36,14 @@ export const enUS: TranslationDictionary = {
component: 'Component',
system: 'System',
space: 'Space',
}
};
},
panel: {
property: {
title: 'Property Panel',
base: 'Basic Info',
material: 'Material',
advanced: 'Advanced'
}
}
};

View File

@@ -9,35 +9,44 @@ export interface TranslationDictionary {
openTestDialog: string;
openInfoDialog: string;
};
toolbar: {
home: string;
info: string;
location: string;
setting: string;
walk: string;
walkPerson: string;
walkBird: string;
walkMenu: string;
};
dialog: {
testTitle: string;
testContent: string;
};
menu: {
info: string;
home: string;
};
toolbar: {
home: string;
info: string;
location: string;
setting: string;
walk: string;
walkMenu: string;
walkPerson: string;
walkBird: string;
tree: string;
};
panel: {
property: {
title: string;
base: string;
material: string;
advanced: string;
}
};
dialog: {
testTitle: string;
testContent: string;
};
menu: {
info: string;
home: string;
};
tree: {
searchPlaceholder: string;
};
constructTree: {
title: string;
}
};
tab: {
component: string;
system: string;
space: string;
}
};
}
/**

View File

@@ -13,9 +13,10 @@ export const zhCN: TranslationDictionary = {
location: '定位',
setting: '设置',
walk: '漫游',
walkPerson: '人视',
walkBird: '鸟瞰',
walkMenu: '菜单',
walkMenu: '漫游菜单',
walkPerson: '第一人称',
walkBird: '第三人称',
tree: '模型树'
},
dialog: {
testTitle: '测试弹窗',
@@ -35,5 +36,13 @@ export const zhCN: TranslationDictionary = {
component: '构件',
system: '系统',
space: '空间',
},
panel: {
property: {
title: '属性面板',
base: '基本属性',
material: '材质信息',
advanced: '高级设置'
}
}
};

View File

@@ -0,0 +1,92 @@
import { BimComponent } from '../core/component';
import { BimEngine } from '../bim-engine';
import { BimCollapse } from '../components/collapse/index';
export class PropertyPanelManager extends BimComponent {
constructor(engine: BimEngine) {
super(engine);
}
public init(): void {
document.addEventListener('bim-demo:open-property-panel', () => {
this.show();
});
}
public show() {
if (!this.engine.dialog) {
console.warn('Dialog manager is not initialized');
return;
}
const dialog = this.engine.dialog.create({
title: 'panel.property.title', // '属性面板'
minWidth: 320,
height: 420,
position: 'top-right',
resizable: false
});
// 2. Create Content Container
const contentContainer = document.createElement('div');
contentContainer.style.height = '100%';
contentContainer.style.overflowY = 'auto';
// Use public API to set content
dialog.setContent(contentContainer);
// 3. Create Collapse inside the Dialog
new BimCollapse({
container: contentContainer,
accordion: true,
activeIds: ['base'],
items: [
{
id: 'base',
title: 'panel.property.base', // '基本属性'
content: this.createBaseInfoContent(),
icon: '<svg viewBox="0 0 1024 1024"><path d="M512 64q190.4 0 326.4 136T974.4 526.4 838.4 852.8 512 988.8 185.6 852.8 49.6 526.4 185.6 200 512 64m0-64C229.6 0 0 229.6 0 512s229.6 512 512 512 512-229.6 512-512S794.4 0 512 0z" fill="currentColor"/></svg>'
},
{
id: 'material',
title: 'panel.property.material', // '材质信息'
content: this.createMaterialContent(),
icon: '<svg viewBox="0 0 1024 1024"><path d="M128 128h768v768H128z" fill="none" stroke="currentColor" stroke-width="64"/></svg>'
},
{
id: 'advanced',
title: 'panel.property.advanced', // '高级设置'
content: '<div>Loading...</div>', // Placeholder
disabled: true
}
]
});
}
private createBaseInfoContent(): HTMLElement {
const div = document.createElement('div');
div.style.padding = '8px 0';
div.innerHTML = `
<div style="margin-bottom:8px"><b>ID:</b> <span style="color:#666">E-2023001</span></div>
<div style="margin-bottom:8px"><b>Name:</b> <span style="color:#666">Wall-01</span></div>
<div style="margin-bottom:8px"><b>Level:</b> <span style="color:#666">F1</span></div>
`;
return div;
}
private createMaterialContent(): HTMLElement {
const div = document.createElement('div');
div.innerHTML = `
<div style="display:flex;align-items:center;margin-bottom:8px">
<div style="width:20px;height:20px;background:#ccc;margin-right:8px;border:1px solid #999"></div>
<span>Concrete</span>
</div>
<div>Density: 2400 kg/m³</div>
`;
return div;
}
public destroy(): void {
// Cleanup if needed
}
}

View File

@@ -7,12 +7,28 @@ export interface EngineEvents {
'engine:model-loaded': { url: string };
'engine:object-clicked': { objectId: string; position: { x: number, y: number, z: number } };
// 树组件事件
'ui:tree-node-check': { id: string; checked: boolean; node: any };
'ui:tree-node-select': { id: string; selected: boolean; node: any };
'ui:tree-node-expand': { id: string; expanded: boolean };
// 系统事件
'sys:theme-changed': { theme: string };
'sys:locale-changed': { locale: string };
}
// 树组件事件
'ui:tree-node-check': { id: string; checked: boolean; node: any };
'ui:tree-node-select': { id: string; selected: boolean; node: any };
'ui:tree-node-expand': { id: string; expanded: boolean };
// 折叠面板事件
'ui:collapse-change': { activeIds: string[] };
// 系统事件
'sys:theme-changed': { theme: string };
'sys:locale-changed': { locale: string };
}