feat: enhance description component, update property panel with tabs, and refine docs
This commit is contained in:
62
.idea/workspace.xml
generated
62
.idea/workspace.xml
generated
@@ -4,7 +4,20 @@
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="728b1ce9-7308-4507-bebd-62399c54bf21" name="更改" comment="添加测试信息" />
|
||||
<list default="true" id="728b1ce9-7308-4507-bebd-62399c54bf21" name="更改" comment="添加折叠面板">
|
||||
<change beforePath="$PROJECT_DIR$/dist/bim-engine-sdk.es.js" beforeDir="false" afterPath="$PROJECT_DIR$/dist/bim-engine-sdk.es.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/dist/bim-engine-sdk.es.js.map" beforeDir="false" afterPath="$PROJECT_DIR$/dist/bim-engine-sdk.es.js.map" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/dist/bim-engine-sdk.umd.js" beforeDir="false" afterPath="$PROJECT_DIR$/dist/bim-engine-sdk.umd.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/dist/bim-engine-sdk.umd.js.map" beforeDir="false" afterPath="$PROJECT_DIR$/dist/bim-engine-sdk.umd.js.map" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/dist/index.d.ts" beforeDir="false" afterPath="$PROJECT_DIR$/dist/index.d.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/collapse/index.css" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/collapse/index.css" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/components/tab/index.css" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/tab/index.css" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/index.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/locales/en-US.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/locales/en-US.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/locales/types.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/locales/types.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/locales/zh-CN.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/locales/zh-CN.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/managers/property-panel-manager.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/managers/property-panel-manager.ts" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
@@ -31,7 +44,7 @@
|
||||
"git-widget-placeholder": "main",
|
||||
"go.import.settings.migrated": "true",
|
||||
"kotlin-language-version-configured": "true",
|
||||
"last_opened_file_path": "/Users/yuding/WORK/LYZ/project/bimEngine/engine",
|
||||
"last_opened_file_path": "/Users/yuding/WORK/LYZ/project/bimEngine/engine/src/managers",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
@@ -42,6 +55,11 @@
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$/src/managers" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="SharedIndexes">
|
||||
<attachedChunks>
|
||||
<set>
|
||||
@@ -63,6 +81,16 @@
|
||||
<workItem from="1764923867307" duration="53000" />
|
||||
<workItem from="1764923944573" duration="598000" />
|
||||
<workItem from="1765159215556" duration="215000" />
|
||||
<workItem from="1765276444696" duration="1275000" />
|
||||
<workItem from="1765332689442" duration="125000" />
|
||||
<workItem from="1765785106464" duration="10802000" />
|
||||
<workItem from="1765857284634" duration="196000" />
|
||||
<workItem from="1765880383085" duration="690000" />
|
||||
<workItem from="1765936484546" duration="357000" />
|
||||
<workItem from="1765943119364" duration="2464000" />
|
||||
<workItem from="1766108012524" duration="1315000" />
|
||||
<workItem from="1766371049964" duration="671000" />
|
||||
<workItem from="1766385054791" duration="7532000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="添加测试信息">
|
||||
<option name="closed" value="true" />
|
||||
@@ -88,7 +116,31 @@
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1765159346641</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="4" />
|
||||
<task id="LOCAL-00004" summary="添加测试信息">
|
||||
<option name="closed" value="true" />
|
||||
<created>1765857466168</created>
|
||||
<option name="number" value="00004" />
|
||||
<option name="presentableId" value="LOCAL-00004" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1765857466168</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00005" summary="修改">
|
||||
<option name="closed" value="true" />
|
||||
<created>1766385084570</created>
|
||||
<option name="number" value="00005" />
|
||||
<option name="presentableId" value="LOCAL-00005" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1766385084570</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00006" summary="添加折叠面板">
|
||||
<option name="closed" value="true" />
|
||||
<created>1766389199604</created>
|
||||
<option name="number" value="00006" />
|
||||
<option name="presentableId" value="LOCAL-00006" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1766389199604</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="7" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
@@ -96,7 +148,9 @@
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<MESSAGE value="添加测试信息" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="添加测试信息" />
|
||||
<MESSAGE value="修改" />
|
||||
<MESSAGE value="添加折叠面板" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="添加折叠面板" />
|
||||
</component>
|
||||
<component name="VgoProject">
|
||||
<settings-migrated>true</settings-migrated>
|
||||
|
||||
@@ -527,6 +527,7 @@ const dialog = engine.dialog.create({
|
||||
| `BimTree` | `src/components/tree/index.ts` | 通用树形组件 | `IBimComponent` |
|
||||
| `BimTab` | `src/components/tab/index.ts` | 固定标签页组件 | `IBimComponent` |
|
||||
| `BimCollapse` | `src/components/collapse/index.ts` | 折叠面板组件 | `IBimComponent` |
|
||||
| `BimDescription` | `src/components/description/index.ts` | 描述列表组件 (Key-Value) | `IBimComponent` |
|
||||
|
||||
### 4.3 服务类清单
|
||||
|
||||
|
||||
3255
dist/bim-engine-sdk.es.js
vendored
3255
dist/bim-engine-sdk.es.js
vendored
File diff suppressed because one or more lines are too long
2
dist/bim-engine-sdk.es.js.map
vendored
2
dist/bim-engine-sdk.es.js.map
vendored
File diff suppressed because one or more lines are too long
363
dist/bim-engine-sdk.umd.js
vendored
363
dist/bim-engine-sdk.umd.js
vendored
File diff suppressed because one or more lines are too long
2
dist/bim-engine-sdk.umd.js.map
vendored
2
dist/bim-engine-sdk.umd.js.map
vendored
File diff suppressed because one or more lines are too long
64
dist/index.d.ts
vendored
64
dist/index.d.ts
vendored
@@ -361,6 +361,51 @@ declare class ConstructTreeManagerBtn extends BimComponent {
|
||||
setColors(colors: ButtonGroupColors): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 描述列表项配置
|
||||
*/
|
||||
export declare interface DescriptionItem {
|
||||
/** 标签文本 (直接显示,组件内部不翻译) */
|
||||
label: string;
|
||||
/** 内容文本或元素 */
|
||||
value: string | HTMLElement;
|
||||
/** 行级自定义标签颜色 */
|
||||
labelColor?: string;
|
||||
/** 行级自定义内容颜色 */
|
||||
valueColor?: string;
|
||||
/** 自定义类名 */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 描述列表组件配置
|
||||
*/
|
||||
export declare 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹窗颜色配置
|
||||
*/
|
||||
@@ -608,7 +653,7 @@ export declare interface IBimComponent {
|
||||
declare type Listener<T = any> = (payload: T) => void;
|
||||
|
||||
/**
|
||||
* 语言代码类型
|
||||
* 语言<EFBFBD><EFBFBD>码类型
|
||||
*/
|
||||
declare type LocaleType = 'zh-CN' | 'en-US';
|
||||
|
||||
@@ -651,11 +696,28 @@ declare interface OptButton extends ButtonConfig {
|
||||
children?: OptButton[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 属性面板管理器
|
||||
* 负责展示和管理属性面板弹窗 (演示 Tab + Collapse + Description 组件)
|
||||
*/
|
||||
declare class PropertyPanelManager extends BimComponent {
|
||||
private dialogId;
|
||||
constructor(engine: BimEngine);
|
||||
init(): void;
|
||||
/**
|
||||
* 显示属性面板
|
||||
*/
|
||||
show(): void;
|
||||
/**
|
||||
* 创建"属性"标签页的内容 (包含 Collapse)
|
||||
*/
|
||||
private createPropsTabContent;
|
||||
/**
|
||||
* 创建"材质"标签页的内容 (包含 Collapse)
|
||||
*/
|
||||
private createMaterialTabContent;
|
||||
private createBaseInfoContent;
|
||||
private createAdvancedInfoContent;
|
||||
private createMaterialContent;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
@@ -1,69 +1,97 @@
|
||||
# Collapse 组件文档
|
||||
|
||||
> **注意**: 本组件为 UI 组件,必须通过 Manager(如 `PropertyPanelManager`)封装后使用,不可直接在业务代码中实例化。
|
||||
|
||||
## 1. 组件概述
|
||||
# Collapse 折叠面板组件
|
||||
|
||||
`BimCollapse` 是一个通用的折叠面板组件,支持手风琴模式、自定义内容和标题。常用于属性面板、设置菜单等场景。
|
||||
|
||||
## 2. API 参考
|
||||
## 1. 组件概述
|
||||
|
||||
### 2.1 配置项 `CollapseOptions`
|
||||
- **类名**: `BimCollapse`
|
||||
- **文件路径**: `src/components/collapse/index.ts`
|
||||
- **样式文件**: `src/components/collapse/index.css`
|
||||
- **类型定义**: `src/components/collapse/types.ts`
|
||||
|
||||
该组件实现了 `IBimComponent` 接口,具备完整的生命周期管理、主题响应和国际化支持能力。
|
||||
|
||||
## 2. API 文档
|
||||
|
||||
### 2.1 构造函数
|
||||
|
||||
```typescript
|
||||
interface CollapseOptions {
|
||||
container: HTMLElement | string; // 挂载容器
|
||||
items: CollapseItemConfig[]; // 面板项列表
|
||||
accordion?: boolean; // 是否开启手风琴模式 (默认 false)
|
||||
activeIds?: string[]; // 初始展开的 ID 列表
|
||||
bordered?: boolean; // 是否显示边框 (默认 true)
|
||||
ghost?: boolean; // 是否幽灵模式 (默认 false)
|
||||
className?: string; // 自定义类名
|
||||
onChange?: (activeIds: string[]) => void; // 切换回调
|
||||
}
|
||||
const collapse = new BimCollapse(options: CollapseOptions);
|
||||
```
|
||||
|
||||
### 2.2 面板项配置 `CollapseItemConfig`
|
||||
### 2.2 CollapseOptions 配置项
|
||||
|
||||
```typescript
|
||||
interface CollapseItemConfig {
|
||||
id: string; // 唯一标识
|
||||
title: string; // 标题翻译键 (例如 'panel.base')
|
||||
content: string | HTMLElement; // 内容
|
||||
icon?: string; // 标题图标 (SVG)
|
||||
extra?: string | HTMLElement; // 标题右侧额外内容
|
||||
disabled?: boolean; // 是否禁用
|
||||
className?: string; // 自定义类名
|
||||
}
|
||||
```
|
||||
| 属性 | 类型 | 默认值 | 说明 |
|
||||
|------|------|-------|------|
|
||||
| `container` | `HTMLElement \| string` | - | **(必填)** 挂载容器或其 ID |
|
||||
| `items` | `CollapseItemConfig[]` | - | **(必填)** 面板项列表 |
|
||||
| `accordion` | `boolean` | `false` | 是否开启手风琴模式(一次只能展开一项) |
|
||||
| `activeIds` | `string[]` | `[]` | 初始展开的面板 ID 列表 |
|
||||
| `bordered` | `boolean` | `true` | 是否显示外边框 |
|
||||
| `ghost` | `boolean` | `false` | 是否开启幽灵模式(无背景、无边框) |
|
||||
| `className` | `string` | - | 自定义类名 |
|
||||
| `onChange` | `(activeIds: string[]) => void` | - | 切换面板时的回调函数 |
|
||||
|
||||
### 2.3 方法
|
||||
### 2.3 CollapseItemConfig 面板项配置
|
||||
|
||||
* `toggleItem(id: string)`: 切换指定面板的展开/折叠状态。
|
||||
* `setLocales()`: 更新组件文本(通常自动调用)。
|
||||
* `destroy()`: 销毁组件。
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | `string` | **(必填)** 唯一标识符 |
|
||||
| `title` | `string` | **(必填)** 标题文本的**翻译键** (例如 `'panel.property.base'`) |
|
||||
| `content` | `string \| HTMLElement` | **(必填)** 面板内容 |
|
||||
| `icon` | `string` | 标题左侧图标 (SVG 字符串) |
|
||||
| `extra` | `string \| HTMLElement` | 标题栏右侧额外内容 |
|
||||
| `disabled` | `boolean` | 是否禁用该面板 |
|
||||
| `className` | `string` | 自定义面板类名 |
|
||||
|
||||
## 3. 使用示例 (在 Manager 中)
|
||||
### 2.4 实例方法
|
||||
|
||||
- **`toggleItem(id: string)`**: 切换指定面板的展开/折叠状态。
|
||||
- **`setTheme(theme: ThemeConfig)`**: 设置组件主题 (CSS 变量映射)。
|
||||
- **`setLocales()`**: 更新组件文本 (标题翻译)。
|
||||
- **`destroy()`**: 销毁组件,清理资源。
|
||||
|
||||
## 3. 使用示例
|
||||
|
||||
```typescript
|
||||
import { BimCollapse } from '../components/collapse/index';
|
||||
|
||||
// 在 Manager 的方法中
|
||||
const collapse = new BimCollapse({
|
||||
container: this.containerElement,
|
||||
container: document.getElementById('panel'),
|
||||
accordion: true,
|
||||
activeIds: ['base'],
|
||||
items: [
|
||||
{
|
||||
id: 'item1',
|
||||
title: 'my.title.key', // 翻译键
|
||||
content: '<div>Content Here</div>'
|
||||
id: 'base',
|
||||
title: 'panel.property.base', // 必须是翻译键
|
||||
content: document.createElement('div'), // 或 HTML 字符串
|
||||
icon: '<svg>...</svg>'
|
||||
},
|
||||
{
|
||||
id: 'advanced',
|
||||
title: 'panel.property.advanced',
|
||||
content: 'Advanced Content',
|
||||
disabled: true
|
||||
}
|
||||
],
|
||||
onChange: (ids) => {
|
||||
console.log('Current active panels:', ids);
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
## 4. 国际化
|
||||
## 4. 主题支持
|
||||
|
||||
组件会自动订阅 `localeManager`。
|
||||
* **标题**: `config.title` 必须是翻译键。
|
||||
* **内容**: 如果内容包含文本,请确保内容生成时已翻译,或内容本身具有响应国际化的能力。
|
||||
组件自动订阅主题变更,并通过 CSS 变量控制样式:
|
||||
|
||||
- **面板背景**: `theme.panelBackground`
|
||||
- **边框颜色**: `theme.border`
|
||||
- **文本颜色**: `theme.textPrimary`
|
||||
- **标题栏背景**: `theme.componentHover` (默认) / `theme.componentBackground`
|
||||
- **禁用态颜色**: `theme.textSecondary`
|
||||
|
||||
## 5. 国际化支持
|
||||
|
||||
组件自动订阅语言变更:
|
||||
- **标题 (`title`)**: 会自动使用 `t(config.title)` 进行翻译。确保传入的是有效的翻译键。
|
||||
- **内容 (`content`)**: 如果内容包含文本,请确保内容生成时已翻译,或内容本身具有响应国际化的能力(如使用 `BimDescription`)。
|
||||
127
docs/components/description.md
Normal file
127
docs/components/description.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Description 描述列表组件
|
||||
|
||||
`BimDescription` 组件用于展示一组键值对(Key-Value)数据,常用于详情页、属性面板等场景。
|
||||
|
||||
## 1. 组件概述
|
||||
|
||||
- **类名**: `BimDescription`
|
||||
- **文件路径**: `src/components/description/index.ts`
|
||||
- **样式文件**: `src/components/description/index.css`
|
||||
- **类型定义**: `src/components/description/types.ts`
|
||||
|
||||
该组件是一个**纯展示组件**,其特点是:
|
||||
- **不内置国际化**: 为了最大灵活性,Label 和 Value 均直接显示传入的字符串/元素,不调用翻译函数。调用者应负责传入已翻译的文本。
|
||||
- **高度定制化**: 支持全局或行级的颜色、Padding、字体大小定制。
|
||||
- **布局灵活**: 支持普通列表模式和带边框的表格模式(Key-Value 间有纵向分割线)。
|
||||
|
||||
## 2. API 文档
|
||||
|
||||
### 2.1 构造函数
|
||||
|
||||
```typescript
|
||||
const description = new BimDescription(options: DescriptionOptions);
|
||||
```
|
||||
|
||||
### 2.2 DescriptionOptions 配置项
|
||||
|
||||
| 属性 | 类型 | 默认值 | 说明 |
|
||||
|------|------|-------|------|
|
||||
| `container` | `HTMLElement \| string` | - | **(必填)** 挂载容器或其 ID |
|
||||
| `items` | `DescriptionItem[]` | - | **(必填)** 数据项列表 |
|
||||
| `bordered` | `boolean` | `false` | 是否显示边框。开启后会显示行间分割线以及 Key-Value 之间的纵向分割线 |
|
||||
| `labelWidth` | `string` | - | 标签列的固定宽度 (如 `'80px'`),不设置则自适应 |
|
||||
| `fontSize` | `string` | `'14px'` | 全局字体大小 (如 `'12px'`) |
|
||||
| `labelColor` | `string` | `theme.textSecondary` | 全局标签颜色 |
|
||||
| `valueColor` | `string` | `theme.textPrimary` | 全局内容颜色 |
|
||||
| `labelPadding` | `string` | `'0 4px'` | 标签单元格内边距 (CSS padding 语法) |
|
||||
| `valuePadding` | `string` | `'0 4px'` | 内容单元格内边距 (CSS padding 语法) |
|
||||
| `className` | `string` | - | 自定义类名 |
|
||||
|
||||
### 2.3 DescriptionItem 数据项
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `label` | `string` | 标签文本 (直接显示,在非 bordered 模式下会自动添加冒号) |
|
||||
| `value` | `string \| HTMLElement` | 内容文本或 DOM 元素 |
|
||||
| `labelColor` | `string` | 行级自定义标签颜色 (优先级高于全局) |
|
||||
| `valueColor` | `string` | 行级自定义内容颜色 (优先级高于全局) |
|
||||
| `className` | `string` | 自定义行类名 |
|
||||
|
||||
### 2.4 实例方法
|
||||
|
||||
- **`setItems(items: DescriptionItem[])`**: 动态更新数据列表。
|
||||
- **`setTheme(theme: ThemeConfig)`**: <20><>置组件主题 (通常自动调用)。
|
||||
- **`destroy()`**: 销毁组件,清理 DOM 和事件订阅。
|
||||
|
||||
## 3. 使用示例
|
||||
|
||||
### 3.1 基础使用
|
||||
|
||||
```typescript
|
||||
new BimDescription({
|
||||
container: document.getElementById('container'),
|
||||
labelWidth: '80px',
|
||||
items: [
|
||||
{ label: 'Name', value: 'Wall-01' },
|
||||
{ label: 'ID', value: 'E-1001' },
|
||||
{ label: 'Level', value: 'Level 1' }
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### 3.2 带边框的表格模式 (Bordered)
|
||||
|
||||
```typescript
|
||||
new BimDescription({
|
||||
container: document.getElementById('container'),
|
||||
bordered: true,
|
||||
fontSize: '12px',
|
||||
labelPadding: '4px 8px',
|
||||
valuePadding: '4px 8px',
|
||||
items: [
|
||||
{ label: 'Material', value: 'Concrete' },
|
||||
{ label: 'Density', value: '2400 kg/m³' }
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### 3.3 自定义样式
|
||||
|
||||
```typescript
|
||||
new BimDescription({
|
||||
container: 'container',
|
||||
labelColor: '#999',
|
||||
valueColor: '#333',
|
||||
items: [
|
||||
{
|
||||
label: 'Status',
|
||||
value: '<span style="color: green">Active</span>',
|
||||
labelColor: 'blue' // 单独覆盖此行的标签颜色
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
## 4. 主题支持
|
||||
|
||||
组件会自动响应系统主题变更。默认映射关系如下:
|
||||
|
||||
- **文本颜色**: `theme.textPrimary`
|
||||
- **标签颜色**: `theme.textSecondary`
|
||||
- **边框颜色**: `theme.border`
|
||||
- **标签背景 (仅 Bordered 模式)**: `theme.background` (用于轻微区分 Key 和 Value)
|
||||
|
||||
## 5. 国际化支持
|
||||
|
||||
**组件内部不进行翻译**。调用者应在传入 `label` 之前使用 `t()` 函数进行翻译。
|
||||
|
||||
```typescript
|
||||
import { t } from 'bim-engine-sdk/services/locale';
|
||||
|
||||
new BimDescription({
|
||||
// ...
|
||||
items: [
|
||||
{ label: t('panel.property.id'), value: '123' }
|
||||
]
|
||||
});
|
||||
```
|
||||
@@ -117,5 +117,4 @@
|
||||
}
|
||||
|
||||
.bim-collapse-content-box {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
53
src/components/description/index.css
Normal file
53
src/components/description/index.css
Normal 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;
|
||||
}
|
||||
160
src/components/description/index.ts
Normal file
160
src/components/description/index.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
58
src/components/description/types.ts
Normal file
58
src/components/description/types.ts
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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';
|
||||
@@ -39,10 +39,14 @@ export const zhCN: TranslationDictionary = {
|
||||
},
|
||||
panel: {
|
||||
property: {
|
||||
title: '属性面板',
|
||||
title: '构件详情',
|
||||
base: '基本属性',
|
||||
material: '材质信息',
|
||||
advanced: '高级设置'
|
||||
advanced: '高级设置',
|
||||
tab: {
|
||||
props: '属性',
|
||||
material: '材质'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
// 如果有需要清理的资源
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user