2026-01-28 11:58:16 +08:00
|
|
|
import { BaseManager } from '../core/base-manager';
|
2026-02-28 10:08:36 +08:00
|
|
|
import { ManagerRegistry } from '../core/manager-registry';
|
2026-01-28 11:58:16 +08:00
|
|
|
import { BimCollapse } from '../components/collapse/index';
|
2026-01-28 17:19:36 +08:00
|
|
|
import { BimTab } from '../components/tab';
|
|
|
|
|
import { t } from '../services/locale';
|
2026-01-28 11:58:16 +08:00
|
|
|
|
|
|
|
|
export class ComponentDetailManager extends BaseManager {
|
|
|
|
|
private dialogId = 'component-detail-dialog';
|
|
|
|
|
private dialog: any = null;
|
2026-01-28 17:19:36 +08:00
|
|
|
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;
|
2026-01-28 11:58:16 +08:00
|
|
|
|
2026-02-28 10:08:36 +08:00
|
|
|
constructor(registry: ManagerRegistry) {
|
|
|
|
|
super(registry);
|
2026-01-28 11:58:16 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-28 17:19:36 +08:00
|
|
|
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 {
|
2026-01-28 11:58:16 +08:00
|
|
|
if (!this.registry.dialog) {
|
|
|
|
|
console.warn('[ComponentDetailManager] Dialog manager not initialized');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 17:19:36 +08:00
|
|
|
if (!this.isOpen()) {
|
|
|
|
|
this.createDialog();
|
2026-01-28 11:58:16 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-28 17:19:36 +08:00
|
|
|
if (this.currentSelection) {
|
|
|
|
|
this.loadAndRenderContent();
|
|
|
|
|
} else {
|
|
|
|
|
this.renderNoSelection();
|
|
|
|
|
}
|
2026-01-28 11:58:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private createDialog(): void {
|
2026-01-28 17:19:36 +08:00
|
|
|
const width = 300;
|
2026-01-28 11:58:16 +08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 17:19:36 +08:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 11:58:16 +08:00
|
|
|
private showLoading(): void {
|
|
|
|
|
const container = document.createElement('div');
|
2026-01-28 17:19:36 +08:00
|
|
|
container.style.cssText = 'height:100%;display:flex;align-items:center;justify-content:center;';
|
2026-01-28 11:58:16 +08:00
|
|
|
container.textContent = '加载中...';
|
|
|
|
|
this.dialog?.setContent(container);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 17:19:36 +08:00
|
|
|
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 {
|
2026-01-28 11:58:16 +08:00
|
|
|
if (!this.dialog) return;
|
|
|
|
|
|
2026-01-28 17:19:36 +08:00
|
|
|
if (this.tabInstance) {
|
|
|
|
|
this.tabInstance.destroy();
|
|
|
|
|
this.tabInstance = null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 11:58:16 +08:00
|
|
|
const container = document.createElement('div');
|
2026-01-28 17:19:36 +08:00
|
|
|
container.style.cssText = 'height:100%;display:flex;flex-direction:column;';
|
|
|
|
|
|
|
|
|
|
const propertiesPanel = this.createPropertiesPanel();
|
|
|
|
|
const materialsPanel = this.createMaterialsPanel();
|
2026-01-28 11:58:16 +08:00
|
|
|
|
2026-01-28 17:19:36 +08:00
|
|
|
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 || [];
|
2026-01-28 11:58:16 +08:00
|
|
|
|
|
|
|
|
if (properties.length === 0) {
|
2026-01-28 17:19:36 +08:00
|
|
|
container.innerHTML = '<div style="padding:20px;text-align:center;color:var(--bim-text-secondary,#999);">无属性数据</div>';
|
|
|
|
|
return container;
|
2026-01-28 11:58:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
2026-01-28 17:19:36 +08:00
|
|
|
ghost: true,
|
2026-01-28 11:58:16 +08:00
|
|
|
activeIds: collapseItems.length > 0 ? [collapseItems[0].id] : [],
|
|
|
|
|
items: collapseItems
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-28 17:19:36 +08:00
|
|
|
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;
|
2026-01-28 11:58:16 +08:00
|
|
|
}
|
|
|
|
|
|
2026-01-28 17:19:36 +08:00
|
|
|
private createMaterialsPanel(): HTMLElement {
|
2026-01-28 11:58:16 +08:00
|
|
|
const container = document.createElement('div');
|
2026-01-28 17:19:36 +08:00
|
|
|
container.style.cssText = 'height:100%;overflow-y:auto;';
|
2026-01-28 11:58:16 +08:00
|
|
|
|
2026-01-28 17:19:36 +08:00
|
|
|
const materials = this.propertiesData?.materials || [];
|
|
|
|
|
|
|
|
|
|
if (materials.length === 0) {
|
|
|
|
|
container.innerHTML = '<div style="padding:20px;text-align:center;color:var(--bim-text-secondary,#999);">无材质数据</div>';
|
|
|
|
|
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 || [])
|
2026-01-28 11:58:16 +08:00
|
|
|
}));
|
|
|
|
|
|
2026-01-28 17:19:36 +08:00
|
|
|
new BimCollapse({
|
2026-01-28 11:58:16 +08:00
|
|
|
container,
|
2026-01-28 17:19:36 +08:00
|
|
|
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);
|
2026-01-28 11:58:16 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return container;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public isOpen(): boolean {
|
|
|
|
|
return this.dialog !== null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public hide(): void {
|
2026-01-28 17:19:36 +08:00
|
|
|
if (this.tabInstance) {
|
|
|
|
|
this.tabInstance.destroy();
|
|
|
|
|
this.tabInstance = null;
|
|
|
|
|
}
|
2026-01-28 11:58:16 +08:00
|
|
|
if (this.dialog) {
|
|
|
|
|
this.dialog.destroy();
|
|
|
|
|
this.dialog = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public destroy(): void {
|
2026-01-28 17:19:36 +08:00
|
|
|
if (this.unsubscribeSelected) {
|
|
|
|
|
this.unsubscribeSelected();
|
|
|
|
|
this.unsubscribeSelected = null;
|
|
|
|
|
}
|
|
|
|
|
if (this.unsubscribeDeselected) {
|
|
|
|
|
this.unsubscribeDeselected();
|
|
|
|
|
this.unsubscribeDeselected = null;
|
|
|
|
|
}
|
2026-01-28 11:58:16 +08:00
|
|
|
this.hide();
|
|
|
|
|
super.destroy();
|
|
|
|
|
}
|
|
|
|
|
}
|