feat: enhance description component, update property panel with tabs, and refine docs

This commit is contained in:
yuding
2025-12-22 16:41:24 +08:00
parent ed0414c75b
commit e1bb5558ff
19 changed files with 2640 additions and 1856 deletions

View File

@@ -117,5 +117,4 @@
}
.bim-collapse-content-box {
padding: 16px;
}

View File

@@ -0,0 +1,53 @@
.bim-description {
display: flex;
flex-direction: column;
width: 100%;
/* 默认字体大小和颜色 */
font-size: var(--bim-desc-font-size, 14px);
color: var(--bim-text-color, #333);
/* 严格移除容器本身的 padding */
padding: 0;
}
.bim-description-item {
display: flex;
align-items: stretch;
/* 严格移除 item 的 padding完全由 label/value padding 控制 */
padding: 0;
line-height: 1.5;
}
/* 边框模式 */
.bim-description.is-bordered {
border-bottom: none; /* 最后一项会补齐 */
}
.bim-description.is-bordered .bim-description-item {
border-bottom: 1px solid var(--bim-border-color, #eee);
}
/* 标签样式 */
.bim-description-label {
color: var(--bim-desc-label-color, var(--bim-label-color, #666));
flex-shrink: 0;
/* 默认 padding: 0 4px */
padding: var(--bim-desc-label-padding, 4px 4px);
display: flex;
align-items: center; /* 垂直居中 */
}
/* 边框模式下的标签样式 */
.bim-description.is-bordered .bim-description-label {
border-right: 1px solid var(--bim-border-color, #eee);
}
/* 内容样式 */
.bim-description-value {
color: var(--bim-desc-value-color, var(--bim-value-color, #333));
flex: 1;
word-break: break-all;
/* 默认 padding: 0 4px */
padding: var(--bim-desc-value-padding, 4px 4px);
display: flex;
align-items: center;
}

View File

@@ -0,0 +1,160 @@
import './index.css';
import { DescriptionOptions, DescriptionItem } from './types';
import { IBimComponent } from '../../types/component';
import { themeManager } from '../../services/theme';
import type { ThemeConfig } from '../../themes/types';
/**
* 描述列表组件
* 用于展示一组 Key-Value 数据
* 注意:本组件为纯展示组件,不处理国际化,请在外部传入处理好的文本。
*/
export class BimDescription implements IBimComponent {
private element: HTMLElement;
private options: DescriptionOptions;
private unsubscribeTheme: (() => void) | null = null;
constructor(options: DescriptionOptions) {
this.options = {
bordered: false,
...options
};
this.element = this.createDom();
const container = typeof this.options.container === 'string'
? document.getElementById(this.options.container)
: this.options.container;
if (container) {
container.appendChild(this.element);
}
this.init();
}
public init(): void {
this.applyCustomStyles();
this.renderItems();
// 订阅主题变更
this.unsubscribeTheme = themeManager.subscribe((theme) => {
this.setTheme(theme);
});
// 初始应用主题
this.setTheme(themeManager.getTheme());
}
private createDom(): HTMLElement {
const el = document.createElement('div');
el.className = `bim-description ${this.options.className || ''}`;
if (this.options.bordered) el.classList.add('is-bordered');
return el;
}
private applyCustomStyles() {
const style = this.element.style;
// 应用全局字体大小
if (this.options.fontSize) {
style.setProperty('--bim-desc-font-size', this.options.fontSize);
}
// 应用全局 Label 颜色
if (this.options.labelColor) {
style.setProperty('--bim-desc-label-color', this.options.labelColor);
}
// 应用全局 Value 颜色
if (this.options.valueColor) {
style.setProperty('--bim-desc-value-color', this.options.valueColor);
}
// 应用 Padding 配置
if (this.options.labelPadding) {
style.setProperty('--bim-desc-label-padding', this.options.labelPadding);
}
if (this.options.valuePadding) {
style.setProperty('--bim-desc-value-padding', this.options.valuePadding);
}
}
private renderItems() {
this.element.innerHTML = ''; // 清空现有内容
this.options.items.forEach(item => {
const itemEl = document.createElement('div');
itemEl.className = `bim-description-item ${item.className || ''}`;
// 1. Label
const labelEl = document.createElement('div');
labelEl.className = 'bim-description-label';
// 行级颜色覆盖全局颜色
if (item.labelColor) {
labelEl.style.color = item.labelColor;
}
// 设置固定宽度
if (this.options.labelWidth) {
labelEl.style.width = this.options.labelWidth;
}
// 直接显示文本
// bordered 模式移除冒号,普通模式保留
labelEl.textContent = this.options.bordered ? item.label : (item.label + ':');
// 2. Value
const valueEl = document.createElement('div');
valueEl.className = 'bim-description-value';
// 行级颜色覆盖全局颜色
if (item.valueColor) {
valueEl.style.color = item.valueColor;
}
if (typeof item.value === 'string') {
valueEl.innerHTML = item.value;
} else {
valueEl.appendChild(item.value);
}
itemEl.appendChild(labelEl);
itemEl.appendChild(valueEl);
this.element.appendChild(itemEl);
});
}
/**
* 动态更新数据
*/
public setItems(items: DescriptionItem[]) {
this.options.items = items;
this.renderItems();
}
public setTheme(theme: ThemeConfig): void {
const style = this.element.style;
// 设置基础主题变量 (作为 fallback 或默认值)
style.setProperty('--bim-text-color', theme.textPrimary);
style.setProperty('--bim-label-color', theme.textSecondary);
style.setProperty('--bim-value-color', theme.textPrimary);
style.setProperty('--bim-border-color', theme.border);
}
public setLocales(): void {
// 本组件不处理国际化
}
public destroy(): void {
if (this.unsubscribeTheme) {
this.unsubscribeTheme();
this.unsubscribeTheme = null;
}
this.element.remove();
}
}

View File

@@ -0,0 +1,58 @@
/**
* 描述列表项配置
*/
export interface DescriptionItem {
/** 标签文本 (直接显示,组件内部不翻译) */
label: string;
/** 内容文本或元素 */
value: string | HTMLElement;
/** 行级自定义标签颜色 */
labelColor?: string;
/** 行级自定义内容颜色 */
valueColor?: string;
/** 自定义类名 */
className?: string;
}
/**
* 描述列表组件配置
*/
export interface DescriptionOptions {
/** 挂载容器 */
container: HTMLElement | string;
/** 数据项列表 */
items: DescriptionItem[];
/**
* 是否显示边框 (默认 false)
* 开启后,将显示行间分割线以及 Key-Value 之间的纵向分割线
*/
bordered?: boolean;
/** 标签固定宽度 (例如 '80px'),若不设置则自适应 */
labelWidth?: string;
/** 全局标签颜色 */
labelColor?: string;
/** 全局内容颜色 */
valueColor?: string;
/** 全局字体大小 */
fontSize?: string;
/** 标签内边距 (默认 '0 4px') */
labelPadding?: string;
/** 内容内边距 (默认 '0 4px') */
valuePadding?: string;
/** 自定义类名 */
className?: string;
}

View File

@@ -11,8 +11,6 @@
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
padding: 4px 0;
background: transparent;
}
@@ -21,22 +19,25 @@
align-items: center;
justify-content: center;
gap: 6px;
/* 恢复原样式上下4px左右0 */
padding: 4px 0;
border: none;
border-radius: 0;
border-radius: 0; /* 恢复直角 */
background: transparent;
color: var(--bim-tab-text, #e6e6e6);
cursor: pointer;
transition: color 0.2s ease, border-color 0.2s ease;
transition: all 0.2s ease;
font-size: 14px;
border-bottom: 2px solid transparent;
border-bottom: 4px solid transparent;
}
.bim-tab__item:hover:not(.is-disabled):not(.is-active) {
.bim-tab__item:hover {
color: var(--bim-tab-text, #e6e6e6);
border-bottom-color: var(--bim-tab-border, rgba(255, 255, 255, 0.15));
background-color: var(--bim-tab-hover-bg, rgba(255, 255, 255, 0.05));
border-bottom-color: var(--bim-tab-hover-bg, rgba(255, 255, 255, 0.15));
}
/* Active 状态 */
.bim-tab__item.is-active {
color: var(--bim-tab-text-active, #4da3ff);
border-bottom-color: var(--bim-tab-text-active, #4da3ff);
@@ -105,4 +106,3 @@
.construct-tab__panel-content .bim-tree {
flex: 1;
}

View File

@@ -12,5 +12,6 @@ export type { DialogOptions, DialogPosition } from './components/dialog/index.ty
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';
export type { DescriptionOptions, DescriptionItem } from './components/description/types';
// Note: Component classes are intentionally NOT exported to enforce Manager pattern usage.

View File

@@ -39,11 +39,14 @@ export const enUS: TranslationDictionary = {
},
panel: {
property: {
title: 'Property Panel',
title: 'Component Details',
base: 'Basic Info',
material: 'Material',
advanced: 'Advanced'
advanced: 'Advanced',
tab: {
props: 'Properties',
material: 'Material'
}
}
}
};
};

View File

@@ -26,6 +26,10 @@ export interface TranslationDictionary {
base: string;
material: string;
advanced: string;
tab: {
props: string;
material: string;
}
}
};
dialog: {
@@ -50,6 +54,6 @@ export interface TranslationDictionary {
}
/**
* 语言码类型
* 语言<EFBFBD><EFBFBD>码类型
*/
export type LocaleType = 'zh-CN' | 'en-US';
export type LocaleType = 'zh-CN' | 'en-US';

View File

@@ -39,10 +39,14 @@ export const zhCN: TranslationDictionary = {
},
panel: {
property: {
title: '属性面板',
title: '构件详情',
base: '基本属性',
material: '材质信息',
advanced: '高级设置'
advanced: '高级设置',
tab: {
props: '属性',
material: '材质'
}
}
}
};
};

View File

@@ -1,92 +1,200 @@
import { BimComponent } from '../core/component';
import { BimEngine } from '../bim-engine';
import { BimCollapse } from '../components/collapse/index';
import { BimDescription } from '../components/description/index';
import { BimTab } from '../components/tab/index';
/**
* 属性面板管理器
* 负责展示和管理属性面板弹窗 (演示 Tab + Collapse + Description 组件)
*/
export class PropertyPanelManager extends BimComponent {
private dialogId = 'property-panel-dialog';
constructor(engine: BimEngine) {
super(engine);
}
public init(): void {
// 监听来自 Demo 的打开属性面板事件
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
// 1. 创建弹窗
const width = 360; // 稍微加宽一点以容纳 Tab
const x = document.body.clientWidth - width - 40;
console.log('x', x)
const dialog = this.engine.dialog.create({
id: this.dialogId,
title: 'panel.property.title', // '构件详情'
content: '',
width: `${width}px`,
height: '500px',
position: { x, y: 20 },
showMask: false,
resizable: true
} as any);
// 2. 创建内容容器
const contentContainer = document.createElement('div');
contentContainer.style.height = '100%';
contentContainer.style.overflowY = 'auto';
contentContainer.style.display = 'flex';
contentContainer.style.flexDirection = 'column';
// Use public API to set content
dialog.setContent(contentContainer);
// 3. Create Collapse inside the Dialog
new BimCollapse({
// 3. 创建标签页组件
const tab = new BimTab({
container: contentContainer,
tabs: [
{
id: 'props',
title: 'panel.property.tab.props', // '属性'
content: this.createPropsTabContent()
},
{
id: 'material',
title: 'panel.property.tab.material', // '材质'
content: this.createMaterialTabContent()
}
]
});
tab.init();
}
/**
* 创建"属性"标签页的内容 (包含 Collapse)
*/
private createPropsTabContent(): HTMLElement {
const container = document.createElement('div');
container.style.height = '100%';
container.style.overflowY = 'auto'; // 内容区域滚动
new BimCollapse({
container: container,
accordion: true,
activeIds: ['base'],
activeIds: ['base', 'location'],
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
content: this.createAdvancedInfoContent(), // 新增一个内容
disabled: false
}
]
});
return container;
}
/**
* 创建"材质"标签页的内容 (包含 Collapse)
*/
private createMaterialTabContent(): HTMLElement {
const container = document.createElement('div');
container.style.height = '100%';
container.style.overflowY = 'auto';
new BimCollapse({
container: container,
accordion: true,
activeIds: ['material'],
items: [
{
id: 'material',
title: 'panel.property.material', // '材质信息'
content: this.createMaterialContent(),
}
]
});
return container;
}
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;
const container = document.createElement('div');
new BimDescription({
container: container,
labelWidth: '80px',
bordered: true,
items: [
{ label: 'Guid', value: '<span style="color:#666">1f8d-4a2e-9c</span>' },
{ label: 'Name', value: '<b>Basic Wall: Generic - 200mm</b>' },
{ label: 'Type', value: 'Basic Wall' },
{ label: 'Level', value: 'Trane - Centrifugal Water Chiller - CVHF 2 Stage direct drive TAG(BP-RHS-1100RT) 0202104531 1' }
]
});
return container;
}
private createAdvancedInfoContent(): HTMLElement {
const container = document.createElement('div');
new BimDescription({
container: container,
labelWidth: '100px',
bordered: true,
items: [
{ label: 'Area', value: '32.5 m²' },
{ label: 'Volume', value: '6.5 m³' },
{ label: 'Length', value: '5000 mm' },
{ label: 'Phase', value: 'New Construction' }
]
});
return container;
}
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>
const container = document.createElement('div');
// 材质预览块
const preview = document.createElement('div');
preview.style.display = 'flex';
preview.style.alignItems = 'center';
preview.style.marginBottom = '4px';
preview.innerHTML = `
<div style="width:24px;height:24px;background:#9ca3af;margin-right:8px;border:1px solid #6b7280;border-radius:2px;"></div>
<span>Concrete - Cast-in-Place Gray</span>
`;
return div;
const descContainer = document.createElement('div');
new BimDescription({
container: descContainer,
items: [
{ label: 'Preview', value: preview },
{ label: 'Class', value: 'Concrete' },
{ label: 'Density', value: '2400 kg/m³' },
{ label: 'Thermal', value: '0.6 W/(m·K)' }
]
});
container.appendChild(descContainer);
return container;
}
public destroy(): void {
// Cleanup if needed
// 如果有需要清理的资源
}
}