From fc089395c93f86078f4137085ee758402b4c32b8 Mon Sep 17 00:00:00 2001 From: zqm Date: Mon, 2 Feb 2026 13:46:36 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=A0=91=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Web/Vue/CubeLib/CHANGELOG.md | 40 ++ Web/Vue/CubeLib/README.md | 190 +++++++++ Web/Vue/CubeLib/index.js | 9 +- Web/Vue/CubeLib/package.json | 6 +- .../CubeLib/src/components/CubeTreeItem.vue | 264 +++++++++++++ .../CubeLib/src/components/CubeTreeView.vue | 369 ++++++++++++++++++ .../CubeLib/src/utils/treeDataConverter.js | 103 +++++ 7 files changed, 976 insertions(+), 5 deletions(-) create mode 100644 Web/Vue/CubeLib/src/components/CubeTreeItem.vue create mode 100644 Web/Vue/CubeLib/src/components/CubeTreeView.vue create mode 100644 Web/Vue/CubeLib/src/utils/treeDataConverter.js diff --git a/Web/Vue/CubeLib/CHANGELOG.md b/Web/Vue/CubeLib/CHANGELOG.md index 54388b6..b6f9fd0 100644 --- a/Web/Vue/CubeLib/CHANGELOG.md +++ b/Web/Vue/CubeLib/CHANGELOG.md @@ -2,6 +2,46 @@ 所有重要的项目更改都将记录在此文件中。 +## [1.2.2] - 2026-01-30 + +### 新增 + +- 新增 `CubeTreeView` 组件文档 + - 添加了完整的 Props 说明 + - 添加了 Config 配置对象说明 + - 添加了 Events、Methods、Slots 说明 + - 添加了使用示例 + - 添加了键盘导航说明 + - 添加了 CSS 变量说明 + +### 变更 + +- 更新 `package.json` 版本号至 1.2.2 +- 更新 `README.md`,添加 `CubeTreeView` 组件文档 + +## [1.2.1] - 2026-01-30 + +### 移除 + +- 移除 `CubeButton` 组件 + - 删除了按钮组件的实现 + - 从组件列表中移除注册和导出 + +### 变更 + +- 更新 `package.json` 版本号至 1.2.1 +- 更新 `index.ts`,只保留 `CubeSplitter` 组件 +- 更新 `README.md`,移除 `CubeButton` 组件文档 +- 修复 `vite.config.js`,添加 `exports: 'named'` 配置 +- 修复构建警告,解决默认导出和命名导出混合的问题 +- 更新 `package.json`,移除不存在的 `vue-demi-vite` 依赖 +- 更新 `package.json`,将构建命令从 `vue-demi-vite build` 改为 `vite build` + +### 优化 + +- 简化组件库结构,专注于 `CubeSplitter` 组件 +- 优化构建配置,提高构建稳定性 + ## [1.1.0] - 2026-01-30 ### 新增 diff --git a/Web/Vue/CubeLib/README.md b/Web/Vue/CubeLib/README.md index 684c897..74e55a3 100644 --- a/Web/Vue/CubeLib/README.md +++ b/Web/Vue/CubeLib/README.md @@ -305,6 +305,196 @@ const handleStatusChanged = (newStatus) => { - [自动重连示例](examples/CubeWebSocket/AutoReconnectExample.vue) - [消息队列示例](examples/CubeWebSocket/MessageQueueExample.vue) +### CubeTreeView + +树形视图组件,用于展示层级数据结构,支持展开/折叠、键盘导航、自定义样式等功能。 + +#### Props + +| 参数 | 说明 | 类型 | 默认值 | +|------|------|------|---------| +| data | 树形数据数组 | Array | [] | +| selectedId | 当前选中的节点ID | String | '' | +| config | 组件配置对象 | Object | 见下方配置说明 | +| iconMap | 自定义图标映射 | Object | - | + +#### Config 配置对象 + +| 配置项 | 说明 | 类型 | 默认值 | +|--------|------|------|---------| +| defaultExpandAll | 是否默认展开所有节点 | Boolean | false | +| showExpandIcon | 是否显示展开/折叠图标 | Boolean | true | +| indentSize | 缩进大小(像素) | Number | 16 | +| expandable | 是否允许展开/折叠 | Boolean | true | +| iconType | 图标类型:'custom'、'file'、'heading' | String | 'custom' | +| showLevel | 是否显示层级前缀 | Boolean | false | +| levelPrefix | 层级前缀文本 | String | 'H' | +| levelKey | 层级字段名 | String | 'level' | +| autoExpand | 选中节点时是否自动展开路径 | Boolean | false | +| highlightPath | 是否高亮选中节点的路径 | Boolean | false | + +#### Events + +| 事件名 | 说明 | 参数 | +|--------|------|------| +| node-click | 节点被点击 | item | +| toggle-expand | 节点展开/折叠状态变化 | itemId | + +#### Methods + +| 方法名 | 说明 | 参数 | 返回值 | +|--------|------|------|--------| +| expandAll | 展开所有节点 | - | void | +| collapseAll | 折叠所有节点 | - | void | +| expandNode | 展开指定节点 | id | void | +| collapseNode | 折叠指定节点 | id | void | +| toggleNode | 切换节点展开/折叠状态 | id | void | +| expandPath | 展开到指定节点的路径 | id | void | +| collapsePath | 折叠到指定节点的路径 | id | void | +| getNodeById | 根据ID获取节点 | id | Object | +| getSelectedNode | 获取当前选中的节点 | - | Object | +| selectNode | 选中指定节点 | id | void | + +#### Slots + +| 插槽名 | 说明 | 作用域参数 | +|--------|------|-----------| +| icon | 自定义节点图标 | { item } | +| title | 自定义节点标题 | { item } | +| prefix | 自定义节点前缀 | { item } | +| suffix | 自定义节点后缀 | { item } | + +#### 使用示例 + +```vue + + + + + +``` + +#### 键盘导航 + +CubeTreeView 支持以下键盘操作: + +- `↑` - 向上移动选择 +- `↓` - 向下移动选择 +- `←` - 折叠当前节点或选择父节点 +- `→` - 展开当前节点或选择第一个子节点 +- `Enter` / `Space` - 触发节点点击事件 + +#### CSS 变量 + +CubeTreeView 组件支持通过 CSS 变量进行定制: + +```css +:root { + --cube-tree-item-hover-bg: rgba(0, 0, 0, 0.05); + --cube-tree-item-selected-bg: #1890ff; + --cube-tree-item-selected-color: #ffffff; + --cube-tree-item-path-bg: rgba(24, 144, 255, 0.1); + --cube-tree-item-icon-color: rgba(0, 0, 0, 0.45); + --cube-tree-item-level-color: rgba(0, 0, 0, 0.45); + --cube-tree-item-title-color: inherit; +} +``` + ## 常见问题解答 ### Q: 如何保存分隔条的位置? diff --git a/Web/Vue/CubeLib/index.js b/Web/Vue/CubeLib/index.js index 0489c2d..277849f 100644 --- a/Web/Vue/CubeLib/index.js +++ b/Web/Vue/CubeLib/index.js @@ -1,10 +1,15 @@ import { createApp } from 'vue' import CubeSplitter from './src/components/CubeSplitter.vue' import CubeWebSocket from './src/components/CubeWebSocket.vue' +import CubeTreeView from './src/components/CubeTreeView.vue' +import CubeTreeItem from './src/components/CubeTreeItem.vue' +import { TreeDataConverter } from './src/utils/treeDataConverter.js' const components = { CubeSplitter, - CubeWebSocket + CubeWebSocket, + CubeTreeView, + CubeTreeItem } const CubeLib = { @@ -16,4 +21,4 @@ const CubeLib = { } export default CubeLib -export { CubeSplitter, CubeWebSocket } \ No newline at end of file +export { CubeSplitter, CubeWebSocket, CubeTreeView, CubeTreeItem, TreeDataConverter } \ No newline at end of file diff --git a/Web/Vue/CubeLib/package.json b/Web/Vue/CubeLib/package.json index bc09473..c22fa6f 100644 --- a/Web/Vue/CubeLib/package.json +++ b/Web/Vue/CubeLib/package.json @@ -1,7 +1,7 @@ { "name": "joyd.web.vue.cubelib", - "version": "1.1.1", - "description": "Vue3 CubeLib 组件库", + "version": "1.2.2", + "description": "Vue3 CubeLib 组件库 - 包含WebSocket、Splitter、TreeView等组件", "type": "module", "main": "index.js", "files": [ @@ -15,7 +15,7 @@ "peerDependencies": { "vue": ">=3.3.0" }, - "keywords": ["vue3", "components", "cubelib", "joyd"], + "keywords": ["vue3", "components", "cubelib", "joyd", "websocket", "splitter", "treeview"], "author": "JoyD", "license": "MIT", "engines": { diff --git a/Web/Vue/CubeLib/src/components/CubeTreeItem.vue b/Web/Vue/CubeLib/src/components/CubeTreeItem.vue new file mode 100644 index 0000000..9332706 --- /dev/null +++ b/Web/Vue/CubeLib/src/components/CubeTreeItem.vue @@ -0,0 +1,264 @@ + + + + + diff --git a/Web/Vue/CubeLib/src/components/CubeTreeView.vue b/Web/Vue/CubeLib/src/components/CubeTreeView.vue new file mode 100644 index 0000000..0987652 --- /dev/null +++ b/Web/Vue/CubeLib/src/components/CubeTreeView.vue @@ -0,0 +1,369 @@ + + + + + diff --git a/Web/Vue/CubeLib/src/utils/treeDataConverter.js b/Web/Vue/CubeLib/src/utils/treeDataConverter.js new file mode 100644 index 0000000..0047d33 --- /dev/null +++ b/Web/Vue/CubeLib/src/utils/treeDataConverter.js @@ -0,0 +1,103 @@ +export class TreeDataConverter { + static fromBackend(backendData, options = {}) { + const { + parentPath = '', + removeExtension = true, + defaultExpanded = false + } = options + + return backendData.map(item => { + const fullPath = parentPath ? `${parentPath}/${item.name}` : item.name + const title = removeExtension && item.name.endsWith('.md') + ? item.name.slice(0, -3) + : item.name + + return { + id: fullPath, + title: title, + expanded: defaultExpanded || item.is_directory, + children: item.children + ? this.fromBackend(item.children, { + parentPath: fullPath, + removeExtension, + defaultExpanded + }) + : undefined + } + }) + } + + static fromFlatArray(flatData, options = {}) { + const { + levelKey = 'level', + defaultExpanded = true + } = options + + if (!Array.isArray(flatData) || flatData.length === 0) { + return [] + } + + const result = [] + const path = [] + + flatData.forEach(item => { + const currentLevel = item[levelKey] + + while (path.length > 0 && path[path.length - 1].level >= currentLevel) { + path.pop() + } + + const treeNode = { + ...item, + expanded: defaultExpanded, + children: [], + level: currentLevel + } + + if (path.length === 0) { + result.push(treeNode) + } else { + path[path.length - 1].children.push(treeNode) + } + + path.push(treeNode) + }) + + return result + } + + static validate(data) { + const errors = [] + + if (!Array.isArray(data)) { + errors.push('数据必须是数组') + return { valid: false, errors } + } + + const validateNode = (node, path = '') => { + if (!node.id) { + errors.push(`节点缺少id字段: ${path}`) + } + if (!node.title) { + errors.push(`节点缺少title字段: ${path}`) + } + if (node.children && !Array.isArray(node.children)) { + errors.push(`节点的children必须是数组: ${path}`) + } + if (node.children) { + node.children.forEach((child, index) => { + validateNode(child, `${path}/${node.title}[${index}]`) + }) + } + } + + data.forEach((node, index) => { + validateNode(node, `[${index}]`) + }) + + return { + valid: errors.length === 0, + errors + } + } +}