import { BaseManager } from '../core/base-manager'; import { BimCollapse } from '../components/collapse/index'; import { BimTab } from '../components/tab'; import { t } from '../services/locale'; export class ComponentDetailManager extends BaseManager { private dialogId = 'component-detail-dialog'; private dialog: any = null; private currentSelection: { url: string; id: string } | null = null; private unsubscribeSelected: (() => void) | null = null; private unsubscribeDeselected: (() => void) | null = null; private tabInstance: BimTab | null = null; private propertiesData: any = null; constructor() { super(); } public init(): void { this.unsubscribeSelected = this.registry.on('component:selected', (payload) => { this.currentSelection = payload; if (this.isOpen()) { this.loadAndRenderContent(); } }); this.unsubscribeDeselected = this.registry.on('component:deselected', () => { this.currentSelection = null; if (this.isOpen()) { this.renderNoSelection(); } }); } public show(): void { if (!this.registry.dialog) { console.warn('[ComponentDetailManager] Dialog manager not initialized'); return; } if (!this.isOpen()) { this.createDialog(); } if (this.currentSelection) { this.loadAndRenderContent(); } else { this.renderNoSelection(); } } private createDialog(): void { const width = 300; const x = document.body.clientWidth - width - 40; this.dialog = this.registry.dialog?.create({ id: this.dialogId, title: 'panel.componentDetail.title', content: '', width: `${width}px`, height: '500px', position: { x, y: 20 }, resizable: true, onClose: () => this.hide() } as any); } private loadAndRenderContent(): void { if (!this.dialog || !this.currentSelection) return; this.showLoading(); this.registry.engine3d?.getComponentProperties( this.currentSelection.url, this.currentSelection.id, (data) => { this.propertiesData = data; this.renderTabbedContent(); } ); } private showLoading(): void { const container = document.createElement('div'); container.style.cssText = 'height:100%;display:flex;align-items:center;justify-content:center;'; container.textContent = '加载中...'; this.dialog?.setContent(container); } private renderNoSelection(): void { if (!this.dialog) return; const container = document.createElement('div'); container.style.cssText = 'height:100%;display:flex;align-items:center;justify-content:center;color:var(--bim-text-secondary,#999);'; container.textContent = t('panel.componentDetail.noSelection') || '请先选中构件'; this.dialog.setContent(container); } private renderTabbedContent(): void { if (!this.dialog) return; if (this.tabInstance) { this.tabInstance.destroy(); this.tabInstance = null; } const container = document.createElement('div'); container.style.cssText = 'height:100%;display:flex;flex-direction:column;'; const propertiesPanel = this.createPropertiesPanel(); const materialsPanel = this.createMaterialsPanel(); this.tabInstance = new BimTab({ container, activeId: 'properties', tabs: [ { id: 'properties', title: 'panel.property.tab.props', content: propertiesPanel }, { id: 'materials', title: 'panel.property.tab.material', content: materialsPanel } ] }); this.tabInstance.init(); this.dialog.setContent(container); } private createPropertiesPanel(): HTMLElement { const container = document.createElement('div'); container.style.cssText = 'height:100%;overflow-y:auto;'; const properties = this.propertiesData?.properties || []; if (properties.length === 0) { container.innerHTML = '
无属性数据
'; return container; } const collapseItems = properties.map((category: any, index: number) => ({ id: `category-${index}`, title: category.name || `分类 ${index + 1}`, content: this.createCategoryContent(category.children || []) })); new BimCollapse({ container, accordion: false, ghost: true, activeIds: collapseItems.length > 0 ? [collapseItems[0].id] : [], items: collapseItems }); const style = document.createElement('style'); style.textContent = ` #${this.dialogId} .bim-collapse-header { background-color: var(--bim-component-bg-hover) !important; } #${this.dialogId} .bim-collapse-header:hover { background-color: var(--bim-component-bg-active) !important; } `; container.appendChild(style); return container; } private createMaterialsPanel(): HTMLElement { const container = document.createElement('div'); container.style.cssText = 'height:100%;overflow-y:auto;'; const materials = this.propertiesData?.materials || []; if (materials.length === 0) { container.innerHTML = '
无材质数据
'; return container; } const collapseItems = materials.map((material: any, index: number) => ({ id: `material-${index}`, title: material.name || `材质 ${index + 1}`, content: this.createCategoryContent(material.children || material.properties || []) })); new BimCollapse({ container, accordion: false, ghost: true, activeIds: collapseItems.length > 0 ? [collapseItems[0].id] : [], items: collapseItems }); const style = document.createElement('style'); style.textContent = ` #${this.dialogId} .bim-collapse-header { background-color: var(--bim-component-bg-hover) !important; } #${this.dialogId} .bim-collapse-header:hover { background-color: var(--bim-component-bg-active) !important; } `; container.appendChild(style); return container; } private createCategoryContent(items: any[]): HTMLElement { const container = document.createElement('div'); items.forEach((item: any, index: number) => { const row = document.createElement('div'); row.style.cssText = ` display: flex; border-bottom: 1px solid var(--bim-border-default, rgba(255,255,255,0.15)); `; if (index === items.length - 1) { row.style.borderBottom = 'none'; } const label = document.createElement('div'); label.style.cssText = ` width: 120px; flex-shrink: 0; color: var(--bim-text-secondary, #999); font-size: 13px; padding: 8px 12px; border-right: 1px solid var(--bim-border-default, rgba(255,255,255,0.15)); `; label.textContent = item.name || '-'; const value = document.createElement('div'); value.style.cssText = ` flex: 1; color: var(--bim-text-primary, #fff); font-size: 13px; padding: 8px 12px; word-break: break-all; `; value.textContent = String(item.value ?? '-'); row.appendChild(label); row.appendChild(value); container.appendChild(row); }); return container; } public isOpen(): boolean { return this.dialog !== null; } public hide(): void { if (this.tabInstance) { this.tabInstance.destroy(); this.tabInstance = null; } if (this.dialog) { this.dialog.destroy(); this.dialog = null; } } public destroy(): void { if (this.unsubscribeSelected) { this.unsubscribeSelected(); this.unsubscribeSelected = null; } if (this.unsubscribeDeselected) { this.unsubscribeDeselected(); this.unsubscribeDeselected = null; } this.hide(); super.destroy(); } }