添加测试信息

This commit is contained in:
yuding
2025-12-16 11:57:44 +08:00
parent 2a2258cb9c
commit 9d1ebfd817
30 changed files with 6196 additions and 5211 deletions

View File

@@ -94,6 +94,29 @@ export class BimButtonGroup implements IBimComponent {
}
this.updatePosition();
// 添加事件拦截,防止点击穿透到 3D 引擎
this.setupEventInterception(this.container);
}
/**
* 设置事件拦截防止事件<E4BA8B><E4BBB6>泡到下层元素如 3D 引擎)
*/
private setupEventInterception(el: HTMLElement): void {
const stopPropagation = (e: Event) => {
e.stopPropagation();
};
const events = [
'click', 'dblclick', 'contextmenu', 'wheel',
'mousedown', 'mouseup', 'mousemove',
'touchstart', 'touchend', 'touchmove',
'pointerdown', 'pointerup', 'pointermove', 'pointerenter', 'pointerleave', 'pointerover', 'pointerout'
];
events.forEach(eventType => {
el.addEventListener(eventType, stopPropagation, { passive: false });
});
}
private updatePosition() {
@@ -405,6 +428,9 @@ export class BimButtonGroup implements IBimComponent {
// 先添加到 DOM 以便计算尺寸
document.body.appendChild(dropdown);
// 添加事件拦截
this.setupEventInterception(dropdown);
// 添加菜单项
button.children.forEach(subBtn => {

View File

@@ -129,6 +129,8 @@ export class BimDialog implements IBimComponent {
// 设置初始尺寸
this.setSize(el, this.options.width, this.options.height);
// 确保最小尺寸生效
if (this.options.minWidth) el.style.minWidth = `${this.options.minWidth}px`;
// 创建标题栏 (Header)
const header = document.createElement('div');
@@ -197,10 +199,32 @@ export class BimDialog implements IBimComponent {
*/
private setSize(el: HTMLElement, width?: number | string, height?: number | string) {
if (width !== undefined) {
el.style.width = typeof width === 'number' ? `${width}px` : width;
if (width === 'auto' || width === 'fit-content') {
el.style.width = width;
} else {
el.style.width = typeof width === 'number' ? `${width}px` : width;
}
}
if (height !== undefined) {
el.style.height = typeof height === 'number' ? `${height}px` : height;
if (height === 'auto' || height === 'fit-content') {
el.style.height = height;
} else {
el.style.height = typeof height === 'number' ? `${height}px` : height;
}
}
}
/**
* 根据内容自动调整弹窗宽度
* @param recenter 是否重新计算定位(例如保持居中),默认 true
*/
public fitWidth(recenter: boolean = false) {
// 1. 设置为 fit-content 以获取自然宽度,高度保持不变
this.element.style.width = 'fit-content';
// 2. 如果需要重新定位
if (recenter) {
this.initPosition();
}
}
@@ -427,4 +451,4 @@ export class BimDialog implements IBimComponent {
public destroy() {
this.close();
}
}
}

View File

@@ -1,10 +1,122 @@
/* 树容器 */
.bim-tree {
width: 100%;
overflow: auto;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden; /* 内部滚动 */
font-size: 14px;
color: var(--bim-ui_text_primary, #333);
user-select: none; /* 防止双击选中文字 */
position: relative; /* 为下拉框定位 */
}
/* 搜索区域 */
.bim-tree-search {
padding: 8px;
/* border-bottom: 1px solid var(--bim-ui_border_color, #d9d9d9); */
/* 背景透明,使用父布局背景 */
background-color: transparent;
flex-shrink: 0;
position: relative;
}
.bim-tree-search-wrapper {
position: relative;
width: 100%;
display: flex;
align-items: center;
}
.bim-tree-search-icon {
position: absolute;
left: 8px;
width: 16px;
height: 16px;
color: var(--bim-ui_text_secondary, #999);
pointer-events: none; /* 让点击穿透到 input */
display: flex;
align-items: center;
justify-content: center;
}
.bim-tree-search-icon svg {
width: 100%;
height: 100%;
}
.bim-tree-search-input {
width: 100%;
height: 32px;
/* 左侧留出图标位置: 8px left + 16px icon + 4px gap = 28px */
padding: 4px 8px 4px 30px;
border: 1px solid var(--bim-ui_border_color, #d9d9d9);
border-radius: 4px;
outline: none;
font-size: 13px;
color: inherit;
/* 输入框背景半透明或白色,看设计,通常输入框本身还是需要背景的,否则文字看不清 */
background-color: var(--bim-ui_bg_color, #fff);
transition: all 0.2s;
box-sizing: border-box;
}
.bim-tree-search-input:focus {
border-color: var(--bim-primary_color, #1890ff);
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
/* 搜索结果下拉框 */
.bim-tree-search-results {
position: absolute;
top: 100%;
left: 8px;
right: 8px;
background-color: var(--bim-ui_bg_color, #fff);
border: 1px solid var(--bim-ui_border_color, #eee);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
border-radius: 4px;
max-height: 200px;
overflow-y: auto;
z-index: 10;
display: none;
}
.bim-tree-search-results.is-visible {
display: block;
}
.bim-tree-search-item {
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid rgba(0,0,0,0.03);
}
.bim-tree-search-item:last-child {
border-bottom: none;
}
.bim-tree-search-item:hover {
background-color: var(--bim-ui_bg_hover, #f5f5f5);
}
.bim-tree-search-item-title {
font-weight: 500;
display: block;
}
.bim-tree-search-item-path {
font-size: 12px;
color: var(--bim-ui_text_secondary, #999);
margin-top: 2px;
display: block;
}
/* 树内容区域 */
.bim-tree-content {
flex: 1;
overflow-y: auto;
padding: 4px 0;
}
/* 节点行容器 */
@@ -146,16 +258,66 @@
.bim-tree-title {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
/* 移除省略号样式,许文本撑开 */
/* overflow: hidden; */
/* text-overflow: ellipsis; */
}
/* 子节点容器 */
.bim-tree-children {
display: none;
overflow: hidden;
overflow: visible; /* 允许子节点撑开宽度 */
}
.bim-tree-children.is-visible {
display: block;
}
/* 选中高亮状态 */
.bim-tree-node-content.is-selected {
background-color: var(--bim-ui_bg_selected, rgba(24, 144, 255, 0.2));
color: var(--bim-primary_color, #1890ff);
}
/* 节点操作栏 */
.bim-tree-node-actions {
display: none; /* 默认隐藏 */
align-items: center;
margin-left: 8px; /* 与文字的间距 */
flex-shrink: 0;
}
.bim-tree-node-content.is-selected .bim-tree-node-actions {
display: flex; /* 选中时显示 */
}
/* 树内容区域 */
.bim-tree-content {
flex: 1;
overflow: auto; /* 支持横向和纵向滚动 */
padding: 4px 0;
}
/* 节点行容器 */
.bim-tree-node {
display: flex;
flex-direction: column;
width: fit-content; /* 关键:让节点容器跟随内容宽度 */
min-width: 100%;
}
/* 节点内容行 (Flex 布局) */
.bim-tree-node-content {
display: flex;
align-items: center;
height: 32px; /* 标准行高 */
cursor: pointer;
transition: background-color 0.2s;
border-radius: 4px;
padding-right: 8px;
/* 关键:允许宽度根据内容撑开,且至少占满容器 */
width: fit-content;
min-width: 100%;
box-sizing: border-box;
}

View File

@@ -1,6 +1,6 @@
import { IBimComponent } from '../../types/component';
import { ThemeConfig } from '../../themes/types';
import { localeManager } from '../../services/locale';
import { localeManager, t } from '../../services/locale';
import { themeManager } from '../../services/theme';
import { TreeOptions, TreeNodeConfig, TreeNodeCheckState } from './types';
import { BimTreeNode } from './tree-node';
@@ -14,15 +14,21 @@ import './index.css';
export class BimTree implements IBimComponent {
public element: HTMLElement;
private contentElement: HTMLElement; // 树内容容器
private searchInput: HTMLInputElement | null = null;
private searchResults: HTMLElement | null = null;
private options: TreeOptions;
private nodeMap: Map<string, BimTreeNode> = new Map();
private rootNodes: BimTreeNode[] = [];
private selectedNode: BimTreeNode | null = null; // 当前选中的高亮节点
// 订阅清理函数
private unsubscribeLocale: (() => void) | null = null;
private unsubscribeTheme: (() => void) | null = null;
// 事<><E4BA8B><EFBFBD>回调 (由 Manager 注入)
private clickOutsideHandler: ((e: MouseEvent) => void) | null = null;
// 事件回调 (由 Manager 注入)
public onNodeCheck?: (node: BimTreeNode) => void;
public onNodeSelect?: (node: BimTreeNode) => void;
public onNodeExpand?: (node: BimTreeNode) => void;
@@ -32,11 +38,75 @@ export class BimTree implements IBimComponent {
checkable: true,
checkStrictly: true,
indent: 24,
defaultExpandAll: false,
defaultExpandAll: true,
enableSearch: false,
searchPlaceholder: 'tree.searchPlaceholder',
...options
};
// 主容器
this.element = document.createElement('div');
this.element.className = 'bim-tree';
// 搜索区域
if (this.options.enableSearch) {
this.createSearchDOM();
}
// 内容容器
this.contentElement = document.createElement('div');
this.contentElement.className = 'bim-tree-content';
this.element.appendChild(this.contentElement);
// 初始化回调
if (options.onNodeCheck) this.onNodeCheck = options.onNodeCheck;
if (options.onNodeSelect) this.onNodeSelect = options.onNodeSelect;
if (options.onNodeExpand) this.onNodeExpand = options.onNodeExpand;
}
private createSearchDOM() {
const searchContainer = document.createElement('div');
searchContainer.className = 'bim-tree-search';
const wrapper = document.createElement('div');
wrapper.className = 'bim-tree-search-wrapper';
// 图标
const icon = document.createElement('span');
icon.className = 'bim-tree-search-icon';
icon.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m21 21l-4.343-4.343m0 0A8 8 0 1 0 5.343 5.343a8 8 0 0 0 11.314 11.314"/></svg>`;
wrapper.appendChild(icon);
this.searchInput = document.createElement('input');
this.searchInput.className = 'bim-tree-search-input';
this.searchInput.type = 'text';
this.searchInput.placeholder = t(this.options.searchPlaceholder || '搜索...');
// 绑定输入事件
this.searchInput.addEventListener('input', (e) => {
const query = (e.target as HTMLInputElement).value;
this.handleSearch(query);
});
wrapper.appendChild(this.searchInput);
searchContainer.appendChild(wrapper);
// 搜索结果容器
this.searchResults = document.createElement('div');
this.searchResults.className = 'bim-tree-search-results';
searchContainer.appendChild(this.searchResults);
this.element.appendChild(searchContainer);
// 点击外部关闭搜索结果
this.clickOutsideHandler = (e: MouseEvent) => {
if (this.searchResults &&
!this.searchResults.contains(e.target as Node) &&
!this.searchInput?.contains(e.target as Node)) {
this.searchResults.classList.remove('is-visible');
}
};
document.addEventListener('click', this.clickOutsideHandler);
}
public init(): void {
@@ -45,6 +115,119 @@ export class BimTree implements IBimComponent {
// 订阅系统事件
this.unsubscribeLocale = localeManager.subscribe(() => this.setLocales());
this.unsubscribeTheme = themeManager.subscribe((theme) => this.setTheme(theme));
// 初始化主题
this.setTheme(themeManager.getTheme());
}
/**
* 处理搜索逻辑
*/
private handleSearch(query: string) {
if (!this.searchResults) return;
if (!query.trim()) {
this.searchResults.classList.remove('is-visible');
this.searchResults.innerHTML = '';
return;
}
const results: { node: BimTreeNode, label: string, path: string }[] = [];
const lowerQuery = query.toLowerCase();
this.nodeMap.forEach(node => {
// 直接使用 config.label不进行翻译
const label = node.config.label;
if (label.toLowerCase().includes(lowerQuery)) {
results.push({
node,
label,
path: this.getNodePath(node)
});
}
});
this.renderSearchResults(results);
}
/**
* 获取节点路径(面包屑)
*/
private getNodePath(node: BimTreeNode): string {
const path: string[] = [];
let current: BimTreeNode | null = node.parent; // 从父级开始
while (current) {
// 直接使用 label
path.unshift(current.config.label);
current = current.parent;
}
return path.join(' > ');
}
/**
* 渲染搜索结果列表
*/
private renderSearchResults(results: { node: BimTreeNode, label: string, path: string }[]) {
if (!this.searchResults) return;
this.searchResults.innerHTML = '';
if (results.length === 0) {
const noData = document.createElement('div');
noData.className = 'bim-tree-search-item';
noData.style.cursor = 'default';
noData.style.color = '#999';
noData.textContent = 'No results';
this.searchResults.appendChild(noData);
} else {
results.forEach(res => {
const item = document.createElement('div');
item.className = 'bim-tree-search-item';
const title = document.createElement('span');
title.className = 'bim-tree-search-item-title';
title.textContent = res.label;
const path = document.createElement('span');
path.className = 'bim-tree-search-item-path';
path.textContent = res.path;
item.appendChild(title);
if (res.path) item.appendChild(path);
item.addEventListener('click', () => {
this.revealNode(res.node);
});
this.searchResults!.appendChild(item);
});
}
this.searchResults.classList.add('is-visible');
}
/**
* 定位到指定节点
*/
public revealNode(node: BimTreeNode) {
// 1. 关闭搜索下拉
if (this.searchResults) {
this.searchResults.classList.remove('is-visible');
if (this.searchInput) this.searchInput.value = ''; // 可选:清空搜索框
}
// 2. 递归展开父节点
let current = node.parent;
while (current) {
current.toggleExpand(true); // 强制展开
current = current.parent;
}
// 3. 选中节点 (Select)
this.handleNodeSelect(node);
// 4. 滚动到可视区域
setTimeout(() => {
node.element.scrollIntoView({ behavior: 'smooth', block: 'center' });
}, 100);
}
/**
@@ -66,6 +249,9 @@ export class BimTree implements IBimComponent {
*/
public setLocales(): void {
this.nodeMap.forEach(node => node.updateLabel());
if (this.searchInput) {
this.searchInput.placeholder = t(this.options.searchPlaceholder || 'tree.searchPlaceholder');
}
}
public destroy(): void {
@@ -77,38 +263,45 @@ export class BimTree implements IBimComponent {
this.unsubscribeTheme();
this.unsubscribeTheme = null;
}
if (this.clickOutsideHandler) {
document.removeEventListener('click', this.clickOutsideHandler);
this.clickOutsideHandler = null;
}
this.rootNodes.forEach(node => node.destroy());
this.rootNodes = [];
this.nodeMap.clear();
this.element.remove();
this.selectedNode = null;
}
/**
* 核心渲染逻辑
*/
private render(): void {
this.element.innerHTML = '';
this.contentElement.innerHTML = ''; // 清空内容区,而不是整个 element
this.nodeMap.clear();
this.rootNodes = [];
this.options.data.forEach(config => {
this.createNodeRecursively(config, null);
});
// 如果设置了 defaultExpandAll展开所有
if (this.options.defaultExpandAll) {
this.expandAll(true);
}
// 移除旧的 expandAll 调用,因为 expanded 状态已在 createNodeRecursively 中处理
}
/**
* 递归创建节点
*/
private createNodeRecursively(config: TreeNodeConfig, parent: BimTreeNode | null) {
// 处理展开状态优先级: config.expanded > defaultExpandAll
if (config.expanded === undefined) {
config.expanded = this.options.defaultExpandAll;
}
const node = new BimTreeNode(config, this.options, {
onExpand: (n) => { if (this.onNodeExpand) this.onNodeExpand(n); },
onCheck: (n) => this.handleNodeCheck(n),
onClick: (n) => { if (this.onNodeSelect) this.onNodeSelect(n); }
onClick: (n) => this.handleNodeSelect(n)
});
this.nodeMap.set(config.id, node);
@@ -117,7 +310,7 @@ export class BimTree implements IBimComponent {
parent.appendChild(node);
} else {
this.rootNodes.push(node);
this.element.appendChild(node.element);
this.contentElement.appendChild(node.element); // 挂载到 contentElement
}
if (config.children && config.children.length > 0) {
@@ -125,10 +318,27 @@ export class BimTree implements IBimComponent {
this.createNodeRecursively(childConfig, node);
});
}
// 如果是初始化渲染,需要处理 checkStrictly 的向上联动(因为数据里可能只给了子节点 checked父节点没给
// 这里做一个简单的后处理:如果 checkStrictly 开启,且当前节点 checked则触发一次联动
// 注意:这可能会导致性能问题,化做法是在所有节点创建完后统一计算一次状态
// 注意:这可能会导致性能问题,<EFBFBD><EFBFBD><EFBFBD>化做法是在所有节点创建完后统一计算一次状态
}
/**
* 处理节点选择 (高亮)
*/
private handleNodeSelect(node: BimTreeNode) {
// 如果之前有选中的,先取消选中
if (this.selectedNode && this.selectedNode !== node) {
this.selectedNode.setSelected(false);
}
// 设置当前为选中
node.setSelected(true);
this.selectedNode = node;
// 触发外部回调
if (this.onNodeSelect) this.onNodeSelect(node);
}
/**
@@ -152,7 +362,7 @@ export class BimTree implements IBimComponent {
updateChildren(child, state);
});
};
// 当前节点是 Checked 或 Unchecked子节点跟随
if (isChecked) {
updateChildren(node, TreeNodeCheckState.Checked);
@@ -179,7 +389,7 @@ export class BimTree implements IBimComponent {
} else {
current.setChecked(TreeNodeCheckState.Indeterminate, false);
}
current = current.parent;
}
}

View File

@@ -1,6 +1,4 @@
import { TreeNodeConfig, TreeNodeCheckState, TreeOptions } from './types';
import { t } from '../../services/locale';
/**
* 树节点类
* 负责渲染单个节点、处理交互和递归
@@ -11,39 +9,40 @@ export class BimTreeNode {
public children: BimTreeNode[] = [];
public parent: BimTreeNode | null = null;
public checkState: TreeNodeCheckState = TreeNodeCheckState.Unchecked;
// UI Elements
private contentEl!: HTMLElement;
private switcherEl!: HTMLElement;
private checkboxEl: HTMLElement | null = null;
private titleEl!: HTMLElement;
private childrenContainer!: HTMLElement;
// 外部回调
private onExpandChange: (node: BimTreeNode) => void;
private onCheckChange: (node: BimTreeNode) => void;
private onNodeClick: (node: BimTreeNode) => void;
constructor(
config: TreeNodeConfig,
options: TreeOptions,
callbacks: {
onExpand: (n: BimTreeNode) => void,
onCheck: (n: BimTreeNode) => void,
onClick: (n: BimTreeNode) => void
// UI Elements
private contentEl!: HTMLElement;
private switcherEl!: HTMLElement;
private checkboxEl: HTMLElement | null = null;
private titleEl!: HTMLElement;
private actionsEl!: HTMLElement; // 操作栏容器
private childrenContainer!: HTMLElement;
// 外部回调
private onExpandChange: (node: BimTreeNode) => void;
private onCheckChange: (node: BimTreeNode) => void;
private onNodeClick: (node: BimTreeNode) => void;
private renderActions?: (node: TreeNodeConfig) => HTMLElement | string;
constructor(
config: TreeNodeConfig,
options: TreeOptions,
callbacks: {
onExpand: (n: BimTreeNode) => void,
onCheck: (n: BimTreeNode) => void,
onClick: (n: BimTreeNode) => void
}
) {
this.config = config;
this.onExpandChange = callbacks.onExpand;
this.onCheckChange = callbacks.onCheck;
this.onNodeClick = callbacks.onClick;
this.renderActions = options.renderActions;
// 初始化状态
this.checkState = config.checked ? TreeNodeCheckState.Checked : TreeNodeCheckState.Unchecked;
this.element = this.createDom(options);
}
) {
this.config = config;
this.onExpandChange = callbacks.onExpand;
this.onCheckChange = callbacks.onCheck;
this.onNodeClick = callbacks.onClick;
// 初始化状态
this.checkState = config.checked ? TreeNodeCheckState.Checked : TreeNodeCheckState.Unchecked;
this.element = this.createDom(options);
}
/**
* 创建节点 DOM
*/
@@ -55,33 +54,25 @@ export class BimTreeNode {
// 1. 内容行
this.contentEl = document.createElement('div');
this.contentEl.className = 'bim-tree-node-content';
// 计算缩进 (由父级传入或在 Tree 中统一处理样式,这里使用 padding-left 动态计算较复杂,
// 我们改用 Flex + 占位符 或者直接在 Tree 递归时处理。
// 实际上,递归渲染时只需把 children 放在一个有 padding-left 的容器里即可CSS 中已经处理吗?
// 检查 CSS: .bim-tree-children 没有 padding-left。
// 我们采用在 CSS 中给 children 加 padding-left 的方式最简单,或者动态设置 style.
// 为了灵活性,这里采用动态设置 contentEl 的 paddingLeft
// 但这需要知道层级 depth。为了简单我们在 renderChildren 时设置容器样式。
// 1.1 展开/折叠箭头
this.switcherEl = document.createElement('span');
this.switcherEl.className = 'bim-tree-switcher';
// 默认右箭头 SVG
this.switcherEl.innerHTML = `<svg viewBox="0 0 1024 1024"><path d="M288 192l448 320-448 320z"></path></svg>`;
const hasChildren = this.config.children && this.config.children.length > 0;
if (!hasChildren) {
this.switcherEl.classList.add('is-hidden');
} else if (this.config.expanded) {
this.switcherEl.classList.add('is-expanded');
}
this.switcherEl.addEventListener('click', (e) => {
e.stopPropagation();
this.toggleExpand();
});
this.contentEl.appendChild(this.switcherEl);
// 1.2 复选框 (可选)
@@ -89,7 +80,7 @@ export class BimTreeNode {
this.checkboxEl = document.createElement('span');
this.checkboxEl.className = 'bim-tree-checkbox';
this.updateCheckboxUI();
this.checkboxEl.addEventListener('click', (e) => {
e.stopPropagation();
if (this.config.disabled) return;
@@ -102,8 +93,8 @@ export class BimTreeNode {
if (this.config.icon) {
const iconEl = document.createElement('span');
iconEl.className = 'bim-tree-icon';
iconEl.innerHTML = this.config.icon.includes('<svg')
? this.config.icon
iconEl.innerHTML = this.config.icon.includes('<svg')
? this.config.icon
: `<img src="${this.config.icon}" />`;
this.contentEl.appendChild(iconEl);
}
@@ -114,11 +105,26 @@ export class BimTreeNode {
this.updateLabel(); // 设置文本
this.contentEl.appendChild(this.titleEl);
// 1.5 操作栏 (Actions)
this.actionsEl = document.createElement('div');
this.actionsEl.className = 'bim-tree-node-actions';
this.actionsEl.addEventListener('click', (e) => {
e.stopPropagation(); // 防止点击操作栏触发选中/展开
});
this.contentEl.appendChild(this.actionsEl);
// 绑定整行点击
this.contentEl.addEventListener('click', (e) => {
e.stopPropagation();
if (this.config.disabled) return;
this.onNodeClick(this);
const action = this.config.clickAction || 'select';
if (action === 'expand') {
this.toggleExpand();
} else {
this.onNodeClick(this);
}
});
nodeEl.appendChild(this.contentEl);
@@ -128,8 +134,8 @@ export class BimTreeNode {
this.childrenContainer.className = 'bim-tree-children';
// 设置缩进
const indent = options.indent || 24;
this.childrenContainer.style.paddingLeft = `${indent}px`; // 每一级子<EFBFBD><EFBFBD><EFBFBD>器左移
this.childrenContainer.style.paddingLeft = `${indent}px`; // 每一级子器左移
if (this.config.expanded && hasChildren) {
this.childrenContainer.classList.add('is-visible');
}
@@ -139,11 +145,33 @@ export class BimTreeNode {
}
/**
* 更新显示文本 (国际化支持)
* 设置高亮选中状态 (Select 模式下)
*/
public setSelected(selected: boolean) {
if (selected) {
this.contentEl.classList.add('is-selected');
// 渲染自定义操作栏
if (this.renderActions) {
const content = this.renderActions(this.config);
this.actionsEl.innerHTML = '';
if (typeof content === 'string') {
this.actionsEl.innerHTML = content;
} else if (content instanceof HTMLElement) {
this.actionsEl.appendChild(content);
}
}
} else {
this.contentEl.classList.remove('is-selected');
this.actionsEl.innerHTML = ''; // 清空内容
}
}
/**
* 更新显示文本 (国际化支持) -> 移除国际化,直接显示
*/
public updateLabel() {
if (this.titleEl) {
this.titleEl.textContent = t(this.config.label);
this.titleEl.textContent = this.config.label;
}
}
@@ -151,7 +179,7 @@ export class BimTreeNode {
* 切换展开状态
*/
public toggleExpand(force?: boolean) {
if (this.config.children?.length === 0) return;
if (!this.config.children || this.config.children.length === 0) return;
const newState = force !== undefined ? force : !this.config.expanded;
this.config.expanded = newState;
@@ -174,7 +202,7 @@ export class BimTreeNode {
* 切换选中状态 (用户点击)
*/
public toggleCheck() {
// 如果当前是半选,点击变全选;如果全选,点击<EFBFBD><EFBFBD>未选;如果未选,点击变全选
// 如果当前是半选,点击变全选;如果全选,点击未选;如果未选,点击变全选
// 简化逻辑:只要不是 Checked点击都变 Checked如果是 Checked变 Unchecked
const newChecked = this.checkState !== TreeNodeCheckState.Checked;
this.setChecked(newChecked ? TreeNodeCheckState.Checked : TreeNodeCheckState.Unchecked, true);
@@ -203,9 +231,9 @@ export class BimTreeNode {
*/
public updateCheckboxUI() {
if (!this.checkboxEl) return;
this.checkboxEl.classList.remove('is-checked', 'is-indeterminate');
if (this.checkState === TreeNodeCheckState.Checked) {
this.checkboxEl.classList.add('is-checked');
} else if (this.checkState === TreeNodeCheckState.Indeterminate) {
@@ -220,13 +248,13 @@ export class BimTreeNode {
childNode.parent = this;
this.children.push(childNode);
this.childrenContainer.appendChild(childNode.element);
// 如果之前是隐藏的箭头,现在有了子节点,需要显示出来
if (this.children.length === 1) {
this.switcherEl.classList.remove('is-hidden');
}
}
/**
* 销毁
*/

View File

@@ -1,3 +1,5 @@
import type { BimTreeNode } from './tree-node';
/**
* 节点勾选状态枚举
*/
@@ -7,6 +9,11 @@ export enum TreeNodeCheckState {
Indeterminate = 2 // 半选
}
/**
* 节点点击行为类型
*/
export type NodeClickAction = 'select' | 'expand';
/**
* 树节点配置接口
*/
@@ -35,8 +42,11 @@ export interface TreeNodeConfig {
/** 自定义业务数据 */
data?: any;
/** 是否是叶子节点 (用于异步加载场景,暂<EFBFBD><EFBFBD><EFBFBD>接口) */
/** 是否是叶子节点 (用于异步加载场景,暂接口) */
isLeaf?: boolean;
/** 点击整行的行为 (默认 'select') */
clickAction?: NodeClickAction;
}
/**
@@ -61,4 +71,25 @@ export interface TreeOptions {
/** 缩进宽度 (像素,默认 24) */
indent?: number;
}
/** 是否启用搜索功能 (默认 false) */
enableSearch?: boolean;
/** 搜索框占位符 */
searchPlaceholder?: string;
/** 节点勾选回调 */
onNodeCheck?: (node: BimTreeNode) => void;
/** 节点选择回调 */
onNodeSelect?: (node: BimTreeNode) => void;
/** 节点展开/折叠回调 */
onNodeExpand?: (node: BimTreeNode) => void;
/**
* 选中时显示的自定义操作栏渲染函数
* 返回 HTML 字符串或 HTMLElement
*/
renderActions?: (node: TreeNodeConfig) => HTMLElement | string;
}