;
+ }
+}
\ No newline at end of file
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/index.ts b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/index.ts
new file mode 100644
index 00000000..6dbd4cf4
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/index.ts
@@ -0,0 +1,7 @@
+import CwdTreeSelect from './index.vue';
+
+export {
+ CwdTreeSelect,
+};
+
+export default CwdTreeSelect;
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/index.vue b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/index.vue
new file mode 100644
index 00000000..4bd8aee6
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/index.vue
@@ -0,0 +1,1989 @@
+
+
+
+
+
+
+
+
+
{{ placeholder }}
+
+
+
+
+ {{ getItemText(item) }}
+ ×
+
+
+
+ +{{ selectedItems.length - maxTagCount }}
+
+
+
+ {{ selectedItems.length > 0 ? getItemText(selectedItems[0]) : '' }}
+
+
+
+
+
+
+
+ ×
+
+
+ ▼
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 📄
+ {{ searchQuery ? '未找到匹配项' : '暂无数据' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/any-field-test.stories.js b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/any-field-test.stories.js
new file mode 100644
index 00000000..0ea0f7eb
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/any-field-test.stories.js
@@ -0,0 +1,448 @@
+import Component from '../index';
+
+export default {
+ id: 'cwd-tree-select-any-field-test',
+ title: '组件列表/CwdTreeSelect/任意字段测试',
+ component: Component,
+ parameters: {
+ layout: 'padded',
+ },
+};
+
+export const AnyFieldTest = {
+ name: '任意字段作为父节点字段',
+ render: (args) => ({
+ template: `
+
+
任意字段作为父节点字段测试
+
+ ✅ 已彻底修复:现在完全支持任意字段名作为父节点字段,不再依赖预设列表
+
+
+
+
🎯 修复后的工作原理
+
+ ✅ 简化逻辑:
+ • 配置了父节点字段选择器 → 完全使用该字段构建树结构
+ • 未配置父节点字段选择器 → 所有数据显示为平铺列表
+ • 完全移除预设字段和自动检测机制
+
+
+
+
+
+
+
✅ 用户的aapid字段
+
+
+
数据结构:
+
{{ JSON.stringify(aapidData, null, 2) }}
+
+
+
+
字段配置:
+
• 值字段: id
+
• 文本字段: name
+
• 父节点字段: aapid(通过属性选择器选择)
+
+
+
+
+
+ 选中值: {{ aapidValue || '无' }}
+
+
+
+ ✅ 修复后效果:
+ • "测试4" 作为根节点(aapid=0,数据中没有id=0的项)
+ • "测试5" 作为 "测试4" 的子节点(aapid=123)
+
+
+
+
+
+
🚀 自定义字段名
+
+
+
数据结构:
+
{{ JSON.stringify(customData, null, 2) }}
+
+
+
+
字段配置:
+
• 值字段: uuid
+
• 文本字段: title
+
• 父节点字段: superior_node_id(通过属性选择器选择)
+
+
+
+
+
+ 选中值: {{ customValue || '无' }}
+
+
+
+ ✅ 预期结果:
+ • "根部门" 作为根节点(superior_node_id=null)
+ • "研发部" 作为 "根部门" 的子节点
+
+
+
+
+
+
+
📋 对比:未配置时的平铺显示
+
+ 验证未配置父节点字段时,数据显示为平铺列表
+
+
+
+
+
aapid数据 - 无配置
+
+ 不配置父节点字段,应该显示为平铺列表
+
+
+
+
+
+ 选中: {{ aapidFlatValue || '无' }}
+
+
+
+ 📋 预期:所有数据显示为平铺列表
+
+
+
+
+
自定义数据 - 无配置
+
+ 不配置父节点字段,应该显示为平铺列表
+
+
+
+
+
+ 选中: {{ customFlatValue || '无' }}
+
+
+
+ 📋 预期:所有数据显示为平铺列表
+
+
+
+
+
+
+
+
🔢 数字0的特殊处理
+
+ 验证数字0被正确处理为有效的父节点ID
+
+
+
+
+
包含0作为有效父ID
+
+
{{ JSON.stringify(zeroData, null, 2) }}
+
+
+
+
+
+ 选中: {{ zeroValue || '无' }}
+
+
+
+ ✅ 预期:"CEO"是根节点,"总监"是"CEO"的子节点
+
+
+
+
+
null/undefined作为根节点标识
+
+
{{ JSON.stringify(nullData, null, 2) }}
+
+
+
+
+
+ 选中: {{ nullValue || '无' }}
+
+
+
+ ✅ 预期:"董事长"是根节点,"经理"是"董事长"的子节点
+
+
+
+
+
+
+
+
🐛 调试方法:
+
+ - 打开浏览器开发者工具Console面板
+ - 查看🔧标记的字段映射日志
+ - 查看🌳标记的树构建日志
+ - 确认日志中有"ONLY PropertySelectSetter"字样
+ - 确认不再有任何自动检测相关的日志
+
+
+
+ 🎉 修复确认:现在任何字段名都应该能正确工作,完全不再依赖预设字段列表!
+
+
+
+ `,
+ data() {
+ return {
+ // 用户实际数据:aapid字段
+ aapidData: [
+ { id: 123, name: "测试4", aapid: 0 },
+ { id: 456, name: "测试5", aapid: 123 }
+ ],
+
+ // 自定义字段名数据
+ customData: [
+ { uuid: 'DEPT001', title: '根部门', superior_node_id: null },
+ { uuid: 'DEPT002', title: '研发部', superior_node_id: 'DEPT001' },
+ { uuid: 'DEPT003', title: '前端组', superior_node_id: 'DEPT002' }
+ ],
+
+ // 数字0测试数据
+ zeroData: [
+ { id: 0, name: 'CEO', manager_id: null },
+ { id: 1, name: '总监', manager_id: 0 }
+ ],
+
+ // null测试数据
+ nullData: [
+ { id: 1, name: '董事长', manager_id: null },
+ { id: 2, name: '经理', manager_id: 1 }
+ ],
+
+ // 各种值
+ aapidValue: null,
+ customValue: null,
+ aapidFlatValue: null,
+ customFlatValue: null,
+ zeroValue: null,
+ nullValue: null
+ };
+ },
+ methods: {
+ // 基础字段映射
+ getIdField(item) {
+ return item.id;
+ },
+ getNameField(item) {
+ return item.name;
+ },
+
+ // aapid字段映射
+ getAapidField(item) {
+ console.log('🔧 getAapidField called with:', item, '-> returning:', item.aapid);
+ return item.aapid;
+ },
+
+ // 自定义字段映射
+ getUuidField(item) {
+ return item.uuid;
+ },
+ getTitleField(item) {
+ return item.title;
+ },
+ getSuperiorField(item) {
+ console.log('🔧 getSuperiorField called with:', item, '-> returning:', item.superior_node_id);
+ return item.superior_node_id;
+ },
+
+ // manager_id字段映射
+ getManagerIdField(item) {
+ console.log('🔧 getManagerIdField called with:', item, '-> returning:', item.manager_id);
+ return item.manager_id;
+ }
+ }
+ })
+};
+
+export const ComparisonTest = {
+ name: '配置vs未配置对比',
+ render: (args) => ({
+ template: `
+
+
配置vs未配置对比测试
+
+ 对比配置父节点字段和未配置父节点字段的不同效果
+
+
+
+
测试数据:
+
{{ JSON.stringify(testData, null, 2) }}
+
+
+
+
+
+
📋 未配置父节点字段
+
+ 配置:不指定父节点字段
+
+
+
+
+
+ 选中值: {{ noConfigValue || '无' }}
+
+
+
+ 📋 效果:所有数据显示为平铺列表,无树形结构
+
+
+
+
+
+
🌳 配置了父节点字段
+
+ 配置:通过属性选择器指定custom_parent字段
+
+
+
+
+
+ 选中值: {{ configuredValue || '无' }}
+
+
+
+ 🌳 效果:根据custom_parent字段构建树形结构
+
+
+
+
+
+
+
🔍 结果分析:
+
+
+
未配置模式:
+
+ • 所有数据都作为根节点显示
+ • 显示为平铺列表,没有任何层级关系
+ • 不会进行任何字段的自动检测
+
+
+
+
+
配置模式:
+
+ • 完全使用PropertySelectSetter配置的字段
+ • 根据字段值构建正确的树形结构
+ • 不会回退到任何自动检测逻辑
+
+
+
+
+ 💡 核心改进:逻辑更加简单清晰,用户选择什么字段,就用什么字段,不再有任何预设和自动检测!
+
+
+
+
+ `,
+ data() {
+ return {
+ testData: [
+ {
+ id: 'A',
+ name: '节点A',
+ custom_parent: null // 自定义字段:A是根节点
+ },
+ {
+ id: 'B',
+ name: '节点B',
+ custom_parent: 'A' // 自定义字段:B的父节点是A
+ },
+ {
+ id: 'C',
+ name: '节点C',
+ custom_parent: 'A' // 自定义字段:C的父节点是A
+ }
+ ],
+ noConfigValue: null,
+ configuredValue: null
+ };
+ },
+ methods: {
+ getIdField(item) {
+ return item.id;
+ },
+ getNameField(item) {
+ return item.name;
+ },
+ getCustomParentField(item) {
+ console.log('🔧 getCustomParentField called with:', item, '-> returning:', item.custom_parent);
+ return item.custom_parent;
+ }
+ }
+ })
+};
\ No newline at end of file
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/block.stories.js b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/block.stories.js
new file mode 100644
index 00000000..97293777
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/block.stories.js
@@ -0,0 +1,133 @@
+import Component from '../index';
+
+export default {
+ id: 'cwd-tree-select-blocks',
+ title: '组件列表/CwdTreeSelect/内置区块',
+ component: Component,
+ parameters: {
+ layout: 'centered',
+ },
+};
+
+export const Default = {
+ name: '基本用法',
+ render: () => ({
+ template: `
+
+
+
⚠️ 重要配置说明
+
+ 要显示树形结构,必须在IDE中配置以下字段:
+ • 值字段:选择 deptId 字段
+ • 文本字段:选择 name 字段
+ • 父节点字段:选择 parentDeptId 字段(关键!)
+ 如果不配置父节点字段,只会显示平铺列表!
+
+
+
+
+ `,
+ data() {
+ return {
+ treeData: [
+ // 🚀 用户实际数据格式测试
+ {
+ "lCAPDepartment": {
+ "id": 3214327916045824,
+ "createdTime": "2025-09-13T05:35:09.000Z",
+ "updatedTime": "2025-09-13T05:35:09.000Z",
+ "createdBy": null,
+ "updatedBy": null,
+ "name": "中铁工业",
+ "deptId": "13000000",
+ "parentDeptId": "10000000", // 父节点不存在,但不是空值,应该被识别为孤儿节点并提升为根节点
+ "orgType": 1,
+ "mdOrgAssociationID": 33471,
+ "departFullName": "//中国中铁股份有限公司/中铁高新工业股份有限公司",
+ "orgLevel": 2,
+ "realType": null,
+ "preUnit": null,
+ "nextUnit": null
+ }
+ },
+ {
+ "lCAPDepartment": {
+ "id": 3214327916045825,
+ "createdTime": "2025-09-13T05:35:09.000Z",
+ "updatedTime": "2025-09-13T05:35:09.000Z",
+ "createdBy": null,
+ "updatedBy": null,
+ "name": "中铁工业本部",
+ "deptId": "13000001",
+ "parentDeptId": "13000000", // 父节点存在
+ "orgType": 1,
+ "mdOrgAssociationID": 33472,
+ "departFullName": "//中国中铁股份有限公司/中铁高新工业股份有限公司/中铁高新工业股份有限公司本部",
+ "orgLevel": 3,
+ "realType": null,
+ "preUnit": null,
+ "nextUnit": null
+ }
+ },
+ {
+ "lCAPDepartment": {
+ "id": 3214327916045826,
+ "createdTime": "2025-09-13T05:35:09.000Z",
+ "updatedTime": "2025-09-13T05:35:09.000Z",
+ "createdBy": null,
+ "updatedBy": null,
+ "name": "中铁山桥",
+ "deptId": "13000002",
+ "parentDeptId": "13000000", // 父节点存在
+ "orgType": 1,
+ "mdOrgAssociationID": 33473,
+ "departFullName": "//中国中铁股份有限公司/中铁高新工业股份有限公司/中铁山桥集团有限公司",
+ "orgLevel": 3,
+ "realType": null,
+ "preUnit": null,
+ "nextUnit": null
+ }
+ },
+ {
+ "lCAPDepartment": {
+ "id": 3214327916045827,
+ "createdTime": "2025-09-13T05:35:09.000Z",
+ "updatedTime": "2025-09-13T05:35:09.000Z",
+ "createdBy": null,
+ "updatedBy": null,
+ "name": "公司领导及高管",
+ "deptId": "13000026",
+ "parentDeptId": "13000001", // 父节点存在
+ "orgType": 3,
+ "mdOrgAssociationID": 33474,
+ "departFullName": "//中国中铁股份有限公司/中铁高新工业股份有限公司/中铁高新工业股份有限公司本部/公司领导及高管",
+ "orgLevel": 4,
+ "realType": null,
+ "preUnit": null,
+ "nextUnit": null
+ }
+ },
+ {
+ "lCAPDepartment": {
+ "id": 3214327916045841,
+ "createdTime": "2025-09-13T05:35:09.000Z",
+ "updatedTime": "2025-09-13T05:35:09.000Z",
+ "createdBy": null,
+ "updatedBy": null,
+ "name": "技术专家",
+ "deptId": "13000046",
+ "parentDeptId": "13000001", // 父节点存在
+ "orgType": 3,
+ "mdOrgAssociationID": 33488,
+ "departFullName": "//中国中铁股份有限公司/中铁高新工业股份有限公司/中铁高新工业股份有限公司本部/技术专家",
+ "orgLevel": 4,
+ "realType": null,
+ "preUnit": null,
+ "nextUnit": null
+ }
+ }
+ ]
+ };
+ }
+ }),
+};
\ No newline at end of file
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/component-height.stories.js b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/component-height.stories.js
new file mode 100644
index 00000000..69ee2e0d
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/component-height.stories.js
@@ -0,0 +1,284 @@
+import Component from '../index';
+
+export default {
+ id: 'cwd-tree-select-component-height',
+ title: '组件列表/CwdTreeSelect/组件高度',
+ component: Component,
+ parameters: {
+ layout: 'padded',
+ },
+ argTypes: {
+ height: {
+ control: 'number',
+ },
+ dropdownHeight: {
+ control: 'number',
+ },
+ multiple: {
+ control: 'boolean',
+ },
+ searchable: {
+ control: 'boolean',
+ },
+ },
+};
+
+const treeData = [
+ // 根节点
+ { value: '1', text: '前端开发', parentId: null },
+ { value: '2', text: '后端开发', parentId: null },
+ { value: '3', text: '移动开发', parentId: null },
+
+ // 前端开发的子节点
+ { value: '1-1', text: 'Vue.js', parentId: '1' },
+ { value: '1-2', text: 'React', parentId: '1' },
+ { value: '1-3', text: 'Angular', parentId: '1' },
+
+ // Vue.js 的子节点
+ { value: '1-1-1', text: 'Vue 2', parentId: '1-1' },
+ { value: '1-1-2', text: 'Vue 3', parentId: '1-1' },
+
+ // React 的子节点
+ { value: '1-2-1', text: 'React 16', parentId: '1-2' },
+ { value: '1-2-2', text: 'React 18', parentId: '1-2' },
+
+ // 后端开发的子节点
+ { value: '2-1', text: 'Node.js', parentId: '2' },
+ { value: '2-2', text: 'Java', parentId: '2' },
+ { value: '2-3', text: 'Python', parentId: '2' },
+
+ // 移动开发的子节点
+ { value: '3-1', text: 'iOS', parentId: '3' },
+ { value: '3-2', text: 'Android', parentId: '3' },
+ { value: '3-3', text: 'Flutter', parentId: '3' }
+];
+
+export const CustomComponentHeight = {
+ name: '自定义组件高度',
+ render: (args) => ({
+ props: Object.keys(args),
+ template: `
+
+
自定义组件输入框高度
+
+ 通过 height 属性控制组件输入框的高度
+
+
+
+
+ 当前组件高度: {{ height }}px,下拉面板高度: {{ dropdownHeight }}px
+
+
+
+ `,
+ data() {
+ return {
+ treeData
+ };
+ },
+ methods: {
+ onChange(event) {
+ console.log('选择改变:', event);
+ }
+ }
+ }),
+ args: {
+ placeholder: '请选择技术栈',
+ multiple: true,
+ searchable: true,
+ clearable: true,
+ height: 60,
+ dropdownHeight: 200,
+ },
+};
+
+export const HeightComparison = {
+ name: '不同高度对比',
+ render: (args) => ({
+ template: `
+
+
+
小高度 (32px)
+
+
+
+
+
默认高度 (44px)
+
+
+
+
+
大高度 (64px)
+
+
+
+ `,
+ data() {
+ return {
+ treeData
+ };
+ }
+ }),
+ args: {},
+};
+
+export const ResponsiveHeight = {
+ name: '响应式高度',
+ render: (args) => ({
+ template: `
+
+
响应式高度演示
+
+ 动态调整组件高度,观察组件的适应效果
+
+
+
+
+
+ {{ componentHeight }}px
+
+
+
+
+
+ {{ panelHeight }}px
+
+
+
+
+
+
+
+
当前配置:
+
组件高度: {{ componentHeight }}px
+
下拉面板高度: {{ panelHeight }}px
+
+
+ `,
+ data() {
+ return {
+ treeData,
+ componentHeight: 44,
+ panelHeight: 200
+ };
+ },
+ methods: {
+ onChange(event) {
+ console.log('选择改变:', event);
+ }
+ }
+ }),
+ args: {},
+};
+
+export const ExtremeCases = {
+ name: '极端情况测试',
+ render: (args) => ({
+ template: `
+
+
极端情况测试
+
+ 测试组件在极端高度下的表现
+
+
+
+
+
超小高度 (24px)
+
+
+
+
+
超大高度 (100px)
+
+
+
+
+
+
多选模式 - 不同高度
+
+
+
紧凑模式 (32px)
+
+
+
+
+
宽松模式 (56px)
+
+
+
+
+
+ `,
+ data() {
+ return {
+ treeData
+ };
+ }
+ }),
+ args: {},
+};
\ No newline at end of file
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/configuration-guide.stories.js b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/configuration-guide.stories.js
new file mode 100644
index 00000000..ea44a669
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/configuration-guide.stories.js
@@ -0,0 +1,275 @@
+import Component from '../index';
+
+export default {
+ id: 'cwd-tree-select-configuration-guide',
+ title: '组件列表/CwdTreeSelect/配置指南',
+ component: Component,
+ parameters: {
+ layout: 'padded',
+ },
+};
+
+// 用户实际数据格式
+const userRealData = [
+ {
+ "lCAPDepartment": {
+ "id": 3214327916045824,
+ "name": "中铁工业",
+ "deptId": "13000000",
+ "parentDeptId": "10000000" // 父节点不存在,会成为根节点
+ }
+ },
+ {
+ "lCAPDepartment": {
+ "id": 3214327916045825,
+ "name": "中铁工业本部",
+ "deptId": "13000001",
+ "parentDeptId": "13000000" // 指向中铁工业
+ }
+ },
+ {
+ "lCAPDepartment": {
+ "id": 3214327916045826,
+ "name": "中铁山桥",
+ "deptId": "13000002",
+ "parentDeptId": "13000000" // 指向中铁工业
+ }
+ },
+ {
+ "lCAPDepartment": {
+ "id": 3214327916045827,
+ "name": "公司领导及高管",
+ "deptId": "13000026",
+ "parentDeptId": "13000001" // 指向中铁工业本部
+ }
+ }
+];
+
+export const ConfigurationGuide = {
+ name: '📋 配置指南',
+ render: (args) => ({
+ template: `
+
+
+
🎯 CwdTreeSelect 树形结构配置指南
+
+ 问题症状:组件只显示平铺列表,没有树形层级结构
+ 解决方案:在IDE的属性面板中正确配置字段映射
+
+
+
+
+
+
+
⚙️ IDE配置步骤
+
+
+
+
+
1️⃣ 值字段配置
+
+ 在属性面板中找到"值字段"
+ 点击选择器,选择:
+ deptId
+
+
+
+
+
2️⃣ 文本字段配置
+
+ 在属性面板中找到"文本字段"
+ 点击选择器,选择:
+ name
+
+
+
+
+
3️⃣ 父节点字段配置(关键!)
+
+ 在属性面板中找到"父节点字段"
+ 点击选择器,选择:
+ parentDeptId
+
+
+
+
+
+
✅ 配置完成后的效果:
+
+ 组件会自动根据 parentDeptId 字段构建树形结构,显示正确的父子关系和展开/收起功能
+
+
+
+
+
+
+
+
+
+
+
❌ 错误配置(未配置父节点字段)
+
+
+
+ 配置:只配置了值字段和文本字段,未配置父节点字段
+
+
+
+
+
+ 选中值: {{ wrongValue || '无' }}
+
+
+
+ 结果:显示为平铺列表,没有树形结构
+
+
+
+
+
+
+
+
✅ 正确配置(包含父节点字段)
+
+
+
+ 配置:值字段=deptId, 文本字段=name, 父节点字段=parentDeptId
+
+
+
+
+
+ 选中值: {{ correctValue || '无' }}
+
+
+
+ 结果:显示为树形结构,支持展开/收起
+
+
+
+
+
+
+
+
📋 数据结构分析
+
{{ JSON.stringify(userData, null, 2) }}
+
+
+ 预期树结构:
+ 📁 中铁工业 (deptId: 13000000, parentDeptId: 10000000 - 父节点不存在,成为根节点)
+ ├── 📁 中铁工业本部 (parentDeptId: 13000000)
+ │ └── 📄 公司领导及高管 (parentDeptId: 13000001)
+ └── 📄 中铁山桥 (parentDeptId: 13000000)
+
+
+
+
+
+
🤔 常见问题排查
+
+ Q: 为什么配置了字段还是显示平铺?
+ A: 请检查控制台日志,确保字段选择器函数被正确调用
+
+ Q: 如何确认字段配置是否生效?
+ A: 打开控制台,应该看到类似 "🔧 [Field Mapping] getParentDeptIdField called" 的日志
+
+ Q: 数据是嵌套格式怎么办?
+ A: 组件会自动提取嵌套对象(如 lCAPDepartment),无需特殊处理
+
+
+
+ `,
+ data() {
+ return {
+ userData: userRealData,
+ wrongValue: null,
+ correctValue: null
+ };
+ },
+ methods: {
+ getDeptIdField(item) {
+ console.log('🔧 [Configuration Guide] getDeptIdField called:', item);
+ return item.deptId;
+ },
+ getNameField(item) {
+ console.log('🔧 [Configuration Guide] getNameField called:', item);
+ return item.name;
+ },
+ getParentDeptIdField(item) {
+ console.log('🔧 [Configuration Guide] getParentDeptIdField called:', item, '-> returning:', item.parentDeptId);
+ return item.parentDeptId;
+ }
+ }
+ })
+};
+
+export const QuickTest = {
+ name: '🚀 快速测试',
+ render: (args) => ({
+ template: `
+
+
快速测试您的配置
+
+ 使用以下测试组件验证您的字段配置是否正确
+
+
+
+
+
+
测试结果:
+
+
选中值: {{ testValue || '无' }}
+
+
+
+ ✅ 成功标志:
+ • 下拉框中显示 "中铁工业" 作为根节点
+ • "中铁工业" 前面有展开箭头 ▶
+ • 点击箭头可以展开显示子节点
+ • 控制台有 "🔧 [Field Mapping]" 相关日志
+
+
+
+ `,
+ data() {
+ return {
+ testData: userRealData.slice(0, 4), // 取前4条数据用于测试
+ testValue: null
+ };
+ },
+ methods: {
+ getDeptIdField(item) {
+ return item.deptId;
+ },
+ getNameField(item) {
+ return item.name;
+ },
+ getParentDeptIdField(item) {
+ return item.parentDeptId;
+ }
+ }
+ })
+};
\ No newline at end of file
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/debug-detailed.stories.js b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/debug-detailed.stories.js
new file mode 100644
index 00000000..94dd96ef
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/debug-detailed.stories.js
@@ -0,0 +1,327 @@
+import Component from '../index';
+
+export default {
+ id: 'cwd-tree-select-debug-detailed',
+ title: '组件列表/CwdTreeSelect/详细调试',
+ component: Component,
+ parameters: {
+ layout: 'padded',
+ },
+};
+
+// 简单的两级数据,用于问题排查
+const simpleTestData = [
+ { id: 123, name: "测试4", aapid: 0 },
+ { id: 456, name: "测试5", aapid: 123 }
+];
+
+export const DetailedDebug = {
+ name: '详细调试分析',
+ render: (args) => ({
+ template: `
+
+
CwdTreeSelect 问题详细调试
+
+ 用户问题:无论选择什么字段,都是平铺显示,没有树形结构
+
+
+
+
+
🔍 问题分析步骤
+
+ - 检查数据源:确认原始数据结构是否正确
+ - 检查字段映射:确认PropertySelectSetter是否被正确调用
+ - 检查树构建:确认buildTreeFromFlatData是否正确执行
+ - 检查渲染:确认TreeNode组件是否正确显示children
+
+
+
+
+
+
📋 测试数据
+
{{ JSON.stringify(testData, null, 2) }}
+
+ 预期结构:测试4(id=123) 作为根节点,测试5(id=456) 作为测试4的子节点
+
+
+
+
+
+
✅ 配置aapid字段测试
+
+ 配置:值字段=id, 文本字段=name, 父节点字段=aapid
+
+
+
+
+
+ 选中值: {{ selectedValue || '无' }}
+
+
+
+ ✅ 预期效果:
+ • 测试4 显示为根节点(因为aapid=0,数据中没有id=0的项目)
+ • 测试5 显示为 测试4 的子节点(因为aapid=123,指向测试4的id)
+ • 应该能看到展开/收起的箭头图标
+
+
+
+
+
+
📋 未配置字段对比
+
+ 配置:值字段=id, 文本字段=name, 父节点字段=未配置
+
+
+
+
+
+ 选中值: {{ flatValue || '无' }}
+
+
+
+ 📋 预期效果:
+ • 所有数据显示为平铺列表,没有层级关系
+ • 不应该有展开/收起图标
+
+
+
+
+
+
🐛 调试方法
+
+
+
1. 打开浏览器Console面板
+
+ F12 → Console,清空控制台后重新选择下拉框
+
+
+
+
+
2. 查看关键日志
+
+ • 🔧 标记:字段映射函数调用
+ • 🌳 标记:树构建过程
+ • 🌱 标记:根节点识别
+ • 🌿 标记:子节点添加
+ • ❌ 标记:错误或异常情况
+
+
+
+
+
3. 关键检查点
+
+ • getAapidField 是否被调用并返回正确值
+ • buildTreeFromFlatData 是否构建出正确的树结构
+ • TreeNode 是否正确获取和显示 children
+ • 是否有任何错误导致回退到平铺显示
+
+
+
+
+ ⚠️ 注意:如果仍然显示为平铺结构,请将完整的Console日志截图或复制给开发者
+
+
+
+
+ `,
+ data() {
+ return {
+ testData: simpleTestData,
+ selectedValue: null,
+ flatValue: null
+ };
+ },
+ methods: {
+ getIdField(item) {
+ console.log('🔧 [Field Mapping] getIdField called with:', item, '-> returning:', item.id);
+ return item.id;
+ },
+ getNameField(item) {
+ console.log('🔧 [Field Mapping] getNameField called with:', item, '-> returning:', item.name);
+ return item.name;
+ },
+ getAapidField(item) {
+ console.log('🔧 [Field Mapping] getAapidField called with:', item, '-> returning:', item.aapid);
+ console.log('🔧 [Field Mapping] aapid field details - value:', item.aapid, 'type:', typeof item.aapid, 'isNull:', item.aapid === null, 'isUndefined:', item.aapid === undefined);
+ return item.aapid;
+ }
+ }
+ })
+};
+
+export const StepByStepDebug = {
+ name: '分步调试验证',
+ render: (args) => ({
+ template: `
+
+
分步调试验证
+
+ 逐步验证每个环节,找出问题所在
+
+
+
+
+
步骤1:数据源验证
+
+
原始数据:
+
{{ JSON.stringify(step1Data, null, 2) }}
+
+
+
验证要点:
+
+ - id=123 的项,aapid=0
+ - id=456 的项,aapid=123(指向id=123)
+ - 预期:123为根节点,456为123的子节点
+
+
+
+
+
+
+
步骤2:字段映射验证
+
+ 手动测试字段映射函数:
+
+
+
+ 项目1:
+ • getIdField: {{ getIdField(step1Data[0]) }}
+ • getNameField: {{ getNameField(step1Data[0]) }}
+ • getAapidField: {{ getAapidField(step1Data[0]) }}
+
+
+ 项目2:
+ • getIdField: {{ getIdField(step1Data[1]) }}
+ • getNameField: {{ getNameField(step1Data[1]) }}
+ • getAapidField: {{ getAapidField(step1Data[1]) }}
+
+
+
+ 预期结果:所有字段映射函数都应该返回正确的值
+
+
+
+
+
+
步骤3:组件渲染验证
+
+
+
+
+ 选中值: {{ step3Value || '无' }}
+
+
+
+
检查要点:
+
+ - 下拉框中是否显示为树形结构
+ - 是否有展开/收起箭头
+ - Console中是否有正确的调试日志
+
+
+
+
+
+
+
步骤4:不同字段名测试
+
+
+
测试数据(使用parent123字段):
+
{{ JSON.stringify(parent123Data, null, 2) }}
+
+
+
+
+
+ 选中值: {{ step4Value || '无' }}
+
+
+
+
+
+
🚨 问题总结
+
+ 如果以上所有测试都显示为平铺结构,说明问题可能在:
+
+ - PropertySelectSetter没有被正确传递
+ - 树构建逻辑有根本性错误
+ - TreeNode组件渲染逻辑有问题
+
+
+
+ 请务必检查Console中的完整日志,并将问题相关的错误信息提供给开发者。
+
+
+
+
+ `,
+ data() {
+ return {
+ step1Data: [
+ { id: 123, name: "测试4", aapid: 0 },
+ { id: 456, name: "测试5", aapid: 123 }
+ ],
+ parent123Data: [
+ { id: 1, name: "根节点", parent123: null },
+ { id: 2, name: "子节点", parent123: 1 }
+ ],
+ step3Value: null,
+ step4Value: null
+ };
+ },
+ methods: {
+ getIdField(item) {
+ const result = item.id;
+ console.log('🔧 [Manual Test] getIdField:', item, '->', result);
+ return result;
+ },
+ getNameField(item) {
+ const result = item.name;
+ console.log('🔧 [Manual Test] getNameField:', item, '->', result);
+ return result;
+ },
+ getAapidField(item) {
+ const result = item.aapid;
+ console.log('🔧 [Manual Test] getAapidField:', item, '->', result);
+ return result;
+ },
+ getParent123Field(item) {
+ const result = item.parent123;
+ console.log('🔧 [Manual Test] getParent123Field:', item, '->', result);
+ return result;
+ }
+ }
+ })
+};
\ No newline at end of file
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/debug-tree-building.stories.js b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/debug-tree-building.stories.js
new file mode 100644
index 00000000..1142fd69
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/debug-tree-building.stories.js
@@ -0,0 +1,289 @@
+import Component from '../index';
+
+export default {
+ id: 'cwd-tree-select-debug-tree-building',
+ title: '组件列表/CwdTreeSelect/树构建调试',
+ component: Component,
+ parameters: {
+ layout: 'padded',
+ },
+};
+
+// 用户实际问题的测试数据
+const problemData1 = [
+ { id: 123, name: "测试", fid: 0 },
+ { id: 456, name: "测试1", fid: 123 }
+];
+
+const problemData2 = [
+ { id: 1, name: "根节点1", fid: null },
+ { id: 2, name: "子节点1", fid: 1 },
+ { id: 3, name: "子节点2", fid: 1 },
+ { id: 4, name: "孙节点1", fid: 2 }
+];
+
+export const DebugTreeBuilding = {
+ name: '树构建调试',
+ render: (args) => ({
+ template: `
+
+
树构建调试工具
+
+ 调试为什么选择了父节点字段后没有形成树层级结构
+
+
+
+
+
+
测试1:用户实际数据 (fid字段)
+
+
数据结构:
+
{{ JSON.stringify(data1, null, 2) }}
+
+
+
+ 字段配置: 值字段=getIdField, 文本字段=getNameField, 父节点字段=getFidField
+
+
+
+
+
+ 选中值: {{ value1 || '无' }}
+
+
+
+ 预期结果: "测试"(id:123,fid:0) 作为根节点,"测试1"(id:456,fid:123) 作为其子节点
+
+
+
+
+
+
测试2:标准数据格式 (fid字段)
+
+
数据结构:
+
{{ JSON.stringify(data2, null, 2) }}
+
+
+
+ 字段配置: 值字段=getIdField, 文本字段=getNameField, 父节点字段=getFidField
+
+
+
+
+
+ 选中值: {{ value2 || '无' }}
+
+
+
+ 预期结果: 应该显示正确的多层树结构
+
+
+
+
+
+
+
对比:自动检测 vs 属性选择器
+
+ 对比同一数据源使用自动检测和属性选择器的效果
+
+
+
+
+
自动检测父节点字段
+
+ 配置:未指定父节点字段,自动检测
+
+
+
+
+
+ 选中: {{ autoValue || '无' }}
+
+
+
+
+
属性选择器配置
+
+ 配置:通过PropertySelectSetter选择fid字段
+
+
+
+
+
+ 选中: {{ selectorValue || '无' }}
+
+
+
+
+
+
+
🔍 调试步骤:
+
+ - 打开浏览器开发者工具的Console面板
+ - 观察组件日志输出,检查字段选择器是否正确工作
+ - 检查树构建过程中的节点关系映射
+ - 验证父子节点ID的匹配逻辑
+ - 确认数字0是否被正确处理为有效的父节点ID
+
+
+
+ `,
+ data() {
+ return {
+ data1: problemData1,
+ data2: problemData2,
+ value1: null,
+ value2: null,
+ autoValue: null,
+ selectorValue: null
+ };
+ },
+ methods: {
+ getIdField(item) {
+ console.log('getIdField called with:', item, 'returning:', item.id);
+ return item.id;
+ },
+ getNameField(item) {
+ console.log('getNameField called with:', item, 'returning:', item.name);
+ return item.name;
+ },
+ getFidField(item) {
+ console.log('getFidField called with:', item, 'returning:', item.fid);
+ return item.fid;
+ },
+ onChange1(event) {
+ console.log('测试1 - 选择改变:', event);
+ },
+ onChange2(event) {
+ console.log('测试2 - 选择改变:', event);
+ }
+ }
+ })
+};
+
+export const StepByStepDebug = {
+ name: '分步调试流程',
+ render: (args) => ({
+ template: `
+
+
分步调试流程
+
+ 手动验证每个步骤是否正常工作
+
+
+
+
+
步骤1:数据源检查
+
+
原始数据:
+
{{ JSON.stringify(testData, null, 2) }}
+
+
+
+
+
+
步骤2:字段选择器测试
+
+
+ 数据项 {{ index + 1 }}:
+ 值字段={{ getIdField(item) }},
+ 文本字段={{ getNameField(item) }},
+ 父节点字段={{ getFidField(item) }}
+
+
+
+
+
+
+
步骤3:组件渲染测试
+
+
+
+
+ 当前选中值: {{ debugValue || '无' }}
+
+
+
+
+
+
步骤4:预期结果验证
+
+
应该看到:
+
+ - 下拉面板中显示树形结构
+ - "测试" 作为根节点(因为 fid=0 且数据中没有 id=0 的节点)
+ - "测试1" 作为 "测试" 的子节点(因为 fid=123 对应父节点id=123)
+ - 可以展开/收起节点
+ - Console中有详细的调试日志
+
+
+
+
+ `,
+ data() {
+ return {
+ testData: [
+ { id: 123, name: "测试", fid: 0 },
+ { id: 456, name: "测试1", fid: 123 }
+ ],
+ debugValue: null
+ };
+ },
+ methods: {
+ getIdField(item) {
+ return item.id;
+ },
+ getNameField(item) {
+ return item.name;
+ },
+ getFidField(item) {
+ return item.fid;
+ },
+ onDebugChange(event) {
+ console.log('分步调试 - 选择改变:', event);
+ }
+ }
+ })
+};
\ No newline at end of file
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/field-mapping-debug.stories.js b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/field-mapping-debug.stories.js
new file mode 100644
index 00000000..5fffa291
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/field-mapping-debug.stories.js
@@ -0,0 +1,359 @@
+import Component from '../index';
+
+export default {
+ id: 'cwd-tree-select-field-mapping-debug',
+ title: '组件列表/CwdTreeSelect/字段映射调试',
+ component: Component,
+ parameters: {
+ layout: 'padded',
+ },
+};
+
+// 用户实际遇到的问题数据
+const userProblemData = [
+ { id: 123, name: "测试4", aapid: 0 },
+ { id: 456, name: "测试5", aapid: 123 }
+];
+
+// 其他字段名测试
+const differentFieldData = [
+ { uuid: 'A001', title: '根节点A', upper_id: null },
+ { uuid: 'A002', title: '子节点A1', upper_id: 'A001' },
+ { uuid: 'A003', title: '子节点A2', upper_id: 'A001' },
+ { uuid: 'B001', title: '根节点B', upper_id: '' },
+ { uuid: 'B002', title: '子节点B1', upper_id: 'B001' }
+];
+
+export const FieldMappingDebug = {
+ name: '字段映射调试工具',
+ render: (args) => ({
+ template: `
+
+
字段映射问题调试工具
+
+ ✅ 已彻底修复:完全移除自动检测机制,现在选择什么字段名,该字段就作为父节点字段
+
+
+
+
🎯 新工作原理
+
+ ✅ 只有一种模式:PropertySelectSetter配置优先
+ • 如果配置了父节点字段选择器,完全使用该字段
+ • 如果未配置父节点字段选择器,所有数据都显示为平铺结构(无树形层级)
+ • 完全移除预设字段列表和自动检测机制
+
+
+
+
+
+
+
✅ 用户问题:aapid 字段
+
+
+
数据结构:
+
{{ JSON.stringify(userData, null, 2) }}
+
+
+
+
配置说明:
+
+ • 值字段: getIdField (选择 id)
+ • 文本字段: getNameField (选择 name)
+ • 父节点字段: getAapidField (选择 aapid)
+
+
+
+
+
+
+ 选中值: {{ userValue || '无' }}
+
+
+
+ ✅ 修复后效果:
+ "测试4" 作为根节点(aapid=0)
+ "测试5" 作为 "测试4" 的子节点(aapid=123)
+
+
+
+
+
+
📋 对比:无配置平铺显示
+
+
+
测试数据:
+
{{ JSON.stringify(userData, null, 2) }}
+
+
+
+
配置说明:
+
+ • 值字段: getIdField (选择 id)
+ • 文本字段: getNameField (选择 name)
+ • 父节点字段: 未配置
+
+
+
+
+
+
+ 选中值: {{ flatValue || '无' }}
+
+
+
+ 📋 预期效果:
+ 所有数据显示为平铺列表,无树形层级结构
+
+
+
+
+
+
+
🚀 任意字段名测试
+
+ 测试各种奇怪字段名的支持情况
+
+
+
+
测试数据:
+
{{ JSON.stringify(otherData, null, 2) }}
+
+
+
+ 字段映射: uuid → value, title → text, upper_id → parentId
+
+
+
+
+
+ 选中值: {{ otherValue || '无' }}
+
+
+
+ ✅ 预期效果:
+ 根据upper_id字段构建树结构,支持任意字段名
+
+
+
+
+
+
🐛 调试信息查看:
+
+ - 打开浏览器开发者工具 Console 面板
+ - 查看带有🔧标记的日志,确认字段映射过程
+ - 查看带有🌳标记的日志,了解树构建过程
+ - 确认是否有"ONLY PropertySelectSetter"的日志
+ - 验证不再有自动检测的相关日志
+
+
+
+ 🎉 修复确认: 现在任意字段名都应该能正确工作,不再依赖预设的字段列表!
+
+
+
+ `,
+ data() {
+ return {
+ userData: userProblemData,
+ otherData: differentFieldData,
+ userValue: null,
+ flatValue: null,
+ otherValue: null
+ };
+ },
+ methods: {
+ // 用户数据的字段映射
+ getIdField(item) {
+ console.log('🔧 getIdField called with:', item, '-> returning:', item.id);
+ return item.id;
+ },
+ getNameField(item) {
+ console.log('🔧 getNameField called with:', item, '-> returning:', item.name);
+ return item.name;
+ },
+ getAapidField(item) {
+ console.log('🔧 getAapidField called with:', item, '-> returning:', item.aapid);
+ return item.aapid;
+ },
+
+ // 其他数据的字段映射
+ getUuidField(item) {
+ console.log('🔧 getUuidField called with:', item, '-> returning:', item.uuid);
+ return item.uuid;
+ },
+ getTitleField(item) {
+ console.log('🔧 getTitleField called with:', item, '-> returning:', item.title);
+ return item.title;
+ },
+ getUpperIdField(item) {
+ console.log('🔧 getUpperIdField called with:', item, '-> returning:', item.upper_id);
+ return item.upper_id;
+ },
+
+ // 事件处理
+ onUserChange(event) {
+ console.log('👤 aapid字段测试 - 选择改变:', event);
+ },
+ onFlatChange(event) {
+ console.log('📋 平铺显示测试 - 选择改变:', event);
+ }
+ }
+ })
+};
+
+export const AnyFieldNameTest = {
+ name: '任意字段名批量测试',
+ render: (args) => ({
+ template: `
+
+
任意字段名批量测试
+
+ ✅ 验证修复效果:测试各种奇怪字段名是否都能正确工作
+
+
+
+
{{ testCase.title }}
+
+
+ 父节点字段: {{ testCase.fieldName }}
+
+
+
+
+ 查看测试数据
+ {{ JSON.stringify(testCase.data, null, 2) }}
+
+
+
+
+
+
+ 选中: {{ testCase.value || '无' }}
+
+
+
+ ✅ 预期: {{ testCase.expectation }}
+
+
+
+
+
🎯 测试要点
+
+ - parent123: 用户反映的问题字段,现在应该正常工作
+ - weird_field_name: 包含下划线的奇怪字段名
+ - f123abc: 数字+字母混合的字段名
+ - xyz789: 完全自定义的字段名
+
+
+
+ ✅ 成功标准:所有字段名都应该能形成正确的树结构,不再有任何限制!
+
+
+
+ `,
+ data() {
+ return {
+ testCases: [
+ {
+ title: '测试1:parent123字段(用户问题)',
+ fieldName: 'parent123',
+ data: [
+ { id: 1, name: '根1', parent123: null },
+ { id: 2, name: '子1', parent123: 1 }
+ ],
+ value: null,
+ parentIdField: (item) => {
+ console.log('🔧 Testing parent123 field:', item, '-> result:', item.parent123);
+ return item.parent123;
+ },
+ expectation: '应该形成正确的树结构,根1→子1'
+ },
+ {
+ title: '测试2:weird_field_name字段',
+ fieldName: 'weird_field_name',
+ data: [
+ { id: 'A', name: '节点A', weird_field_name: null },
+ { id: 'B', name: '节点B', weird_field_name: 'A' }
+ ],
+ value: null,
+ parentIdField: (item) => {
+ console.log('🔧 Testing weird_field_name field:', item, '-> result:', item.weird_field_name);
+ return item.weird_field_name;
+ },
+ expectation: '应该形成正确的树结构,节点A→节点B'
+ },
+ {
+ title: '测试3:f123abc字段',
+ fieldName: 'f123abc',
+ data: [
+ { id: 10, name: '根节点', f123abc: 0 },
+ { id: 20, name: '叶节点', f123abc: 10 }
+ ],
+ value: null,
+ parentIdField: (item) => {
+ console.log('🔧 Testing f123abc field:', item, '-> result:', item.f123abc);
+ return item.f123abc;
+ },
+ expectation: '数字0应该被正确处理为有效父ID,根节点→叶节点'
+ },
+ {
+ title: '测试4:xyz789字段',
+ fieldName: 'xyz789',
+ data: [
+ { id: 'ROOT', name: '总根节点', xyz789: null },
+ { id: 'CHILD1', name: '第一子节点', xyz789: 'ROOT' },
+ { id: 'CHILD2', name: '第二子节点', xyz789: 'ROOT' }
+ ],
+ value: null,
+ parentIdField: (item) => {
+ console.log('🔧 Testing xyz789 field:', item, '-> result:', item.xyz789);
+ return item.xyz789;
+ },
+ expectation: '完全自定义字段名应该正常工作,1个根节点+2个子节点'
+ }
+ ]
+ };
+ },
+ methods: {
+ getIdField(item) {
+ return item.id;
+ },
+ getNameField(item) {
+ return item.name;
+ }
+ }
+ })
+};
\ No newline at end of file
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/field-selector-validator.stories.js b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/field-selector-validator.stories.js
new file mode 100644
index 00000000..95ff59a4
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/field-selector-validator.stories.js
@@ -0,0 +1,339 @@
+import Component from '../index';
+
+export default {
+ id: 'cwd-tree-select-field-selector-validator',
+ title: '组件列表/CwdTreeSelect/字段选择器验证工具',
+ component: Component,
+ parameters: {
+ layout: 'padded',
+ },
+};
+
+// 用户实际数据
+const actualUserData = [
+ {
+ "lCAPDepartment": {
+ "id": 3214327916045824,
+ "name": "中铁工业",
+ "deptId": "13000000",
+ "parentDeptId": "10000000"
+ }
+ },
+ {
+ "lCAPDepartment": {
+ "id": 3214327916045825,
+ "name": "中铁工业本部",
+ "deptId": "13000001",
+ "parentDeptId": "13000000"
+ }
+ },
+ {
+ "lCAPDepartment": {
+ "id": 3214327916045826,
+ "name": "中铁山桥",
+ "deptId": "13000002",
+ "parentDeptId": "13000000"
+ }
+ }
+];
+
+export const FieldSelectorValidator = {
+ name: '🔧 字段选择器验证工具',
+ render: (args) => ({
+ template: `
+
+
🔧 PropertySelectSetter 配置验证工具
+
+ 问题诊断:字段选择器返回undefined,需要检查IDE中的字段配置
+
+
+
+
+
🚨 问题确认
+
+ 从您的日志可以看出:
+
+ - hasParentIdField: true - 说明配置了父节点字段
+ - parentId: undefined - 但字段选择器返回undefined
+ - 所有节点都成为根节点 - 因为undefined被判断为根节点标识符
+
+
+
+ 结论:IDE中的父节点字段选择器没有正确工作,需要重新配置。
+
+
+
+
+
+
+
📋 手动验证正确的字段选择器
+
+ 以下是正确配置的字段选择器,可以作为参考
+
+
+
+
+
+
✅ 正确配置示例
+
+ 字段映射:
+ • 值字段: 返回 item.deptId
+ • 文本字段: 返回 item.name
+ • 父节点字段: 返回 item.parentDeptId
+
+
+
+
+
+ 选中值: {{ correctValue || '无' }}
+
+
+
+
+
+
❌ 错误配置模拟
+
+ 字段映射:
+ • 值字段: 返回 item.deptId
+ • 文本字段: 返回 item.name
+ • 父节点字段: 返回 undefined(错误!)
+
+
+
+
+
+ 选中值: {{ brokenValue || '无' }}
+
+
+
+
+
+
+
+
🔍 数据结构验证
+
+ 原始数据结构(经过嵌套提取后):
+
+
+
+
{{ JSON.stringify(flattenedTestData, null, 2) }}
+
+
+
+
字段验证:
+
+ -
+ 第{{index+1}}项:
+
deptId="{{ item.deptId }}",
+ name="{{ item.name }}",
+ parentDeptId="{{ item.parentDeptId }}"
+
+
+
+
+
+
+
+
💡 解决方案
+
+
请按以下步骤重新配置IDE中的字段选择器:
+
+ -
+ 打开IDE页面编辑器,选中您的树选择组件
+
+ -
+ 在右侧属性面板中找到"父节点字段"配置项
+
+ -
+ 点击字段选择器下拉框,确保选择了
parentDeptId 字段
+
+ -
+ 保存配置并刷新页面,观察控制台日志是否有改善
+
+ -
+ 验证:应该看到
"🔗 PropertySelectSetter最终结论-子节点" 的日志
+
+
+
+
+ ✅ 成功标志:控制台中应该出现详细的字段访问日志,且parentId不再是undefined
+
+
+
+
+
+
+
🐛 如果问题仍然存在
+
+ 请检查浏览器控制台中
"🔧 [PropertySelectSetter 详细调试]" 组合日志:
+
+ - parentIdField类型: 应该是 "function"
+ - 函数执行结果: 不应该是 undefined
+ - 数据项字段列表: 应该包含 parentDeptId
+
+
+
+
+ `,
+ data() {
+ return {
+ testData: actualUserData,
+ correctValue: null,
+ brokenValue: null
+ };
+ },
+ computed: {
+ // 将嵌套数据扁平化,用于展示
+ flattenedTestData() {
+ return this.testData.map(item => {
+ if (item.lCAPDepartment) {
+ return item.lCAPDepartment;
+ }
+ return item;
+ });
+ }
+ },
+ methods: {
+ // 正确的字段选择器
+ getCorrectDeptIdField(item) {
+ console.log('✅ [正确配置] getDeptIdField called with:', item, '-> returning:', item.deptId);
+ return item.deptId;
+ },
+ getCorrectNameField(item) {
+ console.log('✅ [正确配置] getNameField called with:', item, '-> returning:', item.name);
+ return item.name;
+ },
+ getCorrectParentDeptIdField(item) {
+ console.log('✅ [正确配置] getParentDeptIdField called with:', item, '-> returning:', item.parentDeptId);
+ return item.parentDeptId;
+ },
+
+ // 错误的字段选择器(模拟用户的问题)
+ getBrokenParentField(item) {
+ console.log('❌ [错误配置] getBrokenParentField called with:', item, '-> returning: undefined');
+ return undefined; // 模拟返回undefined的情况
+ }
+ }
+ })
+};
+
+export const DebugHelper = {
+ name: '🔍 调试助手',
+ render: (args) => ({
+ template: `
+
+
🔍 实时调试助手
+
+ 实时监控PropertySelectSetter的工作情况
+
+
+
+
📊 实时统计
+
+
+ 调用次数: {{ debugStats.callCount }}
+
+
+ 返回undefined次数: {{ debugStats.undefinedCount }}
+
+
+ 返回有效值次数: {{ debugStats.validCount }}
+
+
+
+
+
+
+
+ 选中值: {{ debugValue || '无' }}
+ 调试提示: 请查看控制台中带有 "🔧 [PropertySelectSetter 详细调试]" 的日志组
+
+
+
+
+ `,
+ data() {
+ return {
+ debugData: actualUserData.slice(0, 3), // 使用少量数据进行调试
+ debugValue: null,
+ debugStats: {
+ callCount: 0,
+ undefinedCount: 0,
+ validCount: 0
+ }
+ };
+ },
+ methods: {
+ debugGetDeptIdField(item) {
+ return item.deptId;
+ },
+ debugGetNameField(item) {
+ return item.name;
+ },
+ debugGetParentDeptIdField(item) {
+ this.debugStats.callCount++;
+
+ const result = item.parentDeptId;
+ if (result === undefined || result === null) {
+ this.debugStats.undefinedCount++;
+ } else {
+ this.debugStats.validCount++;
+ }
+
+ console.log('🔧 [调试助手] parentDeptId访问:', {
+ item: item.name,
+ 原始数据: item,
+ parentDeptId字段值: result,
+ 字段类型: typeof result,
+ 调用统计: { ...this.debugStats }
+ });
+
+ return result;
+ }
+ }
+ })
+};
\ No newline at end of file
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/lowcode.stories.js b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/lowcode.stories.js
new file mode 100644
index 00000000..73a12f7e
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/lowcode.stories.js
@@ -0,0 +1,423 @@
+import Component from '../index';
+
+export default {
+ id: 'cwd-tree-select-lowcode',
+ title: '组件列表/CwdTreeSelect/低代码演示',
+ component: Component,
+ parameters: {
+ layout: 'padded',
+ },
+ argTypes: {
+ multiple: {
+ control: 'boolean',
+ },
+ searchable: {
+ control: 'boolean',
+ },
+ clearable: {
+ control: 'boolean',
+ },
+ },
+};
+
+const treeData = [
+ // 根节点
+ { value: '1', text: '前端开发', parentId: null },
+ { value: '2', text: '后端开发', parentId: null },
+ { value: '3', text: '移动开发', parentId: null },
+
+ // 前端开发的子节点
+ { value: '1-1', text: 'Vue.js', parentId: '1' },
+ { value: '1-2', text: 'React', parentId: '1' },
+ { value: '1-3', text: 'Angular', parentId: '1' },
+
+ // Vue.js 的子节点
+ { value: '1-1-1', text: 'Vue 2', parentId: '1-1' },
+ { value: '1-1-2', text: 'Vue 3', parentId: '1-1' },
+
+ // React 的子节点
+ { value: '1-2-1', text: 'React 16', parentId: '1-2' },
+ { value: '1-2-2', text: 'React 18', parentId: '1-2' },
+
+ // 后端开发的子节点
+ { value: '2-1', text: 'Node.js', parentId: '2' },
+ { value: '2-2', text: 'Java', parentId: '2' },
+ { value: '2-3', text: 'Python', parentId: '2' },
+
+ // 移动开发的子节点
+ { value: '3-1', text: 'iOS', parentId: '3' },
+ { value: '3-2', text: 'Android', parentId: '3' },
+ { value: '3-3', text: 'Flutter', parentId: '3' }
+];
+
+export const LowCodeBasic = {
+ name: '低代码基础用法',
+ render: (args) => ({
+ props: Object.keys(args),
+ template: `
+
+
低代码开发平台使用示例
+
+ 在低代码平台中,你可以通过拖拽组件到插槽中来自定义下拉面板内容
+
+
+
基础树选择器(默认模式)
+
+
+
带自定义头部的树选择器
+
+
+
+
+
+ 🏷️ 技术栈选择
+
+
+ 请选择您熟悉的技术栈,可以多选
+
+
+
+
+
+
带自定义底部的树选择器
+
+
+
+
+
+ 已选择 {{ selectedCount }} 项
+
+
+
+
+
+
+
+
+
+
+ 💡 低代码开发提示:
+ 1. 在低代码平台中,可以直接拖拽文本、按钮、布局等组件到插槽中
+ 2. 支持配置组件的样式、事件和数据绑定
+ 3. 实时预览效果,所见即所得
+
+
+ `,
+ data() {
+ return {
+ treeData,
+ selectedCount: 0
+ };
+ },
+ methods: {
+ onChange(event) {
+ this.selectedCount = event.values ? event.values.length : (event.value ? 1 : 0);
+ console.log('选择改变:', event);
+ },
+ selectAll() {
+ console.log('全选操作');
+ },
+ clearAll() {
+ console.log('清空操作');
+ }
+ }
+ }),
+ args: {
+ placeholder: '请选择技术栈',
+ multiple: true,
+ searchable: true,
+ clearable: true,
+ },
+};
+
+export const LowCodeEmptyStates = {
+ name: '低代码空状态自定义',
+ render: (args) => ({
+ template: `
+
+
+
自定义空状态(低代码配置)
+
+
+
+
+
📋
+
+ 暂无选项数据
+
+
+ 请联系管理员配置选项或稍后重试
+
+
+
+
+
+
+
+
+
自定义加载状态(低代码配置)
+
+
+
+
+
🚀
+
+ 数据加载中...
+
+
+ 正在从服务器获取最新数据
+
+
+
+
+
+
+
+
+ `,
+ data() {
+ return {
+ emptyData: []
+ };
+ },
+ computed: {
+ loadingDataSource() {
+ return () => {
+ return new Promise(() => {
+ // 永不resolve,保持加载状态用于演示
+ });
+ };
+ }
+ },
+ methods: {
+ loadSampleData() {
+ this.emptyData = [...treeData];
+ console.log('加载示例数据');
+ }
+ }
+ }),
+ args: {},
+};
+
+export const LowCodeCompleteExample = {
+ name: '低代码完整示例',
+ render: (args) => ({
+ template: `
+
+
完整的低代码自定义示例
+
+ 这个示例展示了如何在低代码平台中使用所有插槽来构建一个完整的选择器界面
+
+
+
+
+
+
+
+ 🎯 技能评估
+
+
+ 请选择您掌握的技术技能,支持多选
+
+
+
+
+
+
+
+
+
+
+ 已选择 {{ selectedCount }} 项技能
+
+
+ {{ getSkillLevel() }}
+
+
+
+
+
+
+
+
+
+
+
+
+
快捷选择:
+
+
+ {{ tag }}
+
+
+
+
+
+
+
+
+
+
+ 🎉 选择结果:
+
+
+ 还没有选择任何技能
+
+
+
+ {{ skill }}
+
+
+
+ 技能等级: {{ getSkillLevel() }} | 完成度: {{ getCompletionRate() }}%
+
+
+
+
+
+
+ 💡 低代码开发说明
+
+
+ • 头部插槽:拖入文本、图片等组件来设计标题区域
+ • 底部插槽:拖入按钮、统计组件来添加操作功能
+ • 支持数据绑定、事件配置和样式自定义
+ • 实时预览,快速构建专业的选择器界面
+
+
+
+ `,
+ data() {
+ return {
+ selectedValues: [],
+ quickTags: ['Vue.js', 'React', 'Node.js', 'Python', 'JavaScript'],
+ allSkills: [
+ 'Vue.js', 'React', 'Angular', 'Vue 2', 'Vue 3',
+ 'React 16', 'React 18', 'Node.js', 'Java', 'Python',
+ 'iOS', 'Android', 'Flutter'
+ ]
+ };
+ },
+ computed: {
+ selectedCount() {
+ return this.selectedValues.length;
+ },
+ selectedSkillNames() {
+ return this.selectedValues;
+ }
+ },
+ methods: {
+ onChange(event) {
+ this.selectedValues = event.values || [];
+ console.log('技能选择变化:', event);
+ },
+ selectAll() {
+ this.selectedValues = [...this.allSkills];
+ },
+ clearAll() {
+ this.selectedValues = [];
+ },
+ selectRecommended() {
+ this.selectedValues = ['Vue.js', 'JavaScript', 'Node.js', 'Python'];
+ },
+ toggleQuickTag(tag) {
+ const index = this.selectedValues.indexOf(tag);
+ if (index > -1) {
+ this.selectedValues.splice(index, 1);
+ } else {
+ this.selectedValues.push(tag);
+ }
+ },
+ getSkillLevel() {
+ const count = this.selectedCount;
+ if (count === 0) return '未评估';
+ if (count <= 3) return '初级开发者';
+ if (count <= 6) return '中级开发者';
+ if (count <= 9) return '高级开发者';
+ return '全栈大师';
+ },
+ getCompletionRate() {
+ return Math.round((this.selectedCount / this.allSkills.length) * 100);
+ }
+ }
+ }),
+ args: {},
+};
\ No newline at end of file
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/positioning-debug.stories.js b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/positioning-debug.stories.js
new file mode 100644
index 00000000..2f7dbaa9
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/positioning-debug.stories.js
@@ -0,0 +1,411 @@
+import Component from '../index';
+
+export default {
+ id: 'cwd-tree-select-positioning-debug',
+ title: '组件列表/CwdTreeSelect/位置调试',
+ component: Component,
+ parameters: {
+ layout: 'padded',
+ },
+};
+
+const testData = [
+ { id: 1, name: '根节点1', parentId: null },
+ { id: 11, name: '子节点1-1', parentId: 1 },
+ { id: 12, name: '子节点1-2', parentId: 1 },
+ { id: 111, name: '孙节点1-1-1', parentId: 11 },
+ { id: 2, name: '根节点2', parentId: null },
+ { id: 21, name: '子节点2-1', parentId: 2 },
+];
+
+export const PositioningDebug = {
+ name: '位置调试工具',
+ render: (args) => ({
+ template: `
+
+
弹出层位置调试工具
+
+ 详细调试自动位置计算逻辑,查看Console输出
+
+
+
+
+
🔝 页面顶部区域测试
+
+ 预期:下方空间充足,应该向下弹出
+
+
+
+
+
相对定位 + 自动方向
+
+
+ 选中: {{ topValue1 || '无' }}
+
+
+
+
+
Body挂载 + 自动方向
+
+
+ 选中: {{ topValue2 || '无' }}
+
+
+
+
+
+ ✅ 预期结果:两个选择器都应该向下弹出,Console中显示 "bottom" 方向
+
+
+
+
+
+
+
📏 中间填充区域
+
滚动到底部测试向上弹出
+
+ 当前视口高度: {{ viewportHeight }}px
+ 滚动位置: {{ scrollTop }}px
+
+
+
+
+
+
+
🔻 页面底部区域测试
+
+ 预期:下方空间不足,应该向上弹出
+
+
+
+
+
相对定位 + 自动方向
+
+
+ 选中: {{ bottomValue1 || '无' }}
+
+
+
+
+
Body挂载 + 自动方向
+
+
+ 选中: {{ bottomValue2 || '无' }}
+
+
+
+
+
+ ✅ 预期结果:两个选择器都应该向上弹出,Console中显示 "top" 方向
+
+
+
+
+
+
🎯 强制方向测试
+
+ 测试强制指定弹出方向是否生效
+
+
+
+
+
强制向上弹出 (placement="top")
+
+
+ 选中: {{ forceTopValue || '无' }}
+
+
+
+
+
强制向下弹出 (placement="bottom")
+
+
+ 选中: {{ forceBottomValue || '无' }}
+
+
+
+
+
+ ✅ 预期结果:左边向上弹,右边向下弹,无论位置如何
+
+
+
+
+
+
🐛 实时调试信息
+
视口高度: {{ viewportHeight }}px
+
滚动位置: {{ scrollTop }}px
+
滚动百分比: {{ scrollPercentage }}%
+
+ 打开浏览器Console查看详细位置计算日志
+
+
+
+ `,
+ data() {
+ return {
+ testData,
+ topValue1: null,
+ topValue2: null,
+ bottomValue1: null,
+ bottomValue2: null,
+ forceTopValue: null,
+ forceBottomValue: null,
+ viewportHeight: 0,
+ scrollTop: 0
+ };
+ },
+ computed: {
+ scrollPercentage() {
+ const maxScroll = document.documentElement.scrollHeight - this.viewportHeight;
+ return maxScroll > 0 ? Math.round((this.scrollTop / maxScroll) * 100) : 0;
+ }
+ },
+ mounted() {
+ this.updateViewportInfo();
+ window.addEventListener('scroll', this.updateViewportInfo);
+ window.addEventListener('resize', this.updateViewportInfo);
+ },
+ beforeDestroy() {
+ window.removeEventListener('scroll', this.updateViewportInfo);
+ window.removeEventListener('resize', this.updateViewportInfo);
+ },
+ methods: {
+ getIdField(item) {
+ return item.id;
+ },
+ getNameField(item) {
+ return item.name;
+ },
+ getParentIdField(item) {
+ return item.parentId;
+ },
+ updateViewportInfo() {
+ this.viewportHeight = window.innerHeight;
+ this.scrollTop = window.pageYOffset || document.documentElement.scrollTop;
+ },
+ onTopChange1(event) {
+ console.log('🔝 顶部区域-相对定位 选择改变:', event);
+ },
+ onTopChange2(event) {
+ console.log('🔝 顶部区域-Body挂载 选择改变:', event);
+ },
+ onBottomChange1(event) {
+ console.log('🔻 底部区域-相对定位 选择改变:', event);
+ },
+ onBottomChange2(event) {
+ console.log('🔻 底部区域-Body挂载 选择改变:', event);
+ }
+ }
+ })
+};
+
+export const EdgeCaseTest = {
+ name: '边界情况测试',
+ render: (args) => ({
+ template: `
+
+
边界情况测试
+
+ 测试各种边界情况下的弹出层位置
+
+
+
+
+
小空间测试
+
+
+
+
顶部位置(空间很小)
+
+
+
+
+
+
中部位置
+
+
+
+
+
+
底部位置(空间很小)
+
+
+
+
+
+
+
+
+
+
+
大数据量测试
+
+
+
+
+
🧪 测试要点:
+
+ - 小空间适应:在空间受限时能否正确调整方向和高度
+ - 边界检测:是否能防止弹出层超出视口
+ - 自适应高度:是否能根据可用空间调整最大高度
+ - 性能表现:大数据量时位置计算是否流畅
+
+
+
+ `,
+ data() {
+ return {
+ testData,
+ smallSpaceTop: null,
+ smallSpaceMiddle: null,
+ smallSpaceBottom: null,
+ narrowWidth: null,
+ largeDataValue: null,
+ largeData: this.generateLargeData()
+ };
+ },
+ methods: {
+ getIdField(item) {
+ return item.id;
+ },
+ getNameField(item) {
+ return item.name;
+ },
+ getParentIdField(item) {
+ return item.parentId;
+ },
+ generateLargeData() {
+ const data = [];
+ for (let i = 1; i <= 100; i++) {
+ data.push({
+ id: i,
+ name: `节点 ${i}`,
+ parentId: i <= 10 ? null : Math.floor((i - 1) / 10) + 1
+ });
+ }
+ return data;
+ }
+ }
+ })
+};
\ No newline at end of file
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/positioning-test.stories.js b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/positioning-test.stories.js
new file mode 100644
index 00000000..c7d2c050
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/positioning-test.stories.js
@@ -0,0 +1,396 @@
+import Component from '../index';
+
+export default {
+ id: 'cwd-tree-select-positioning-test',
+ title: '组件列表/CwdTreeSelect/位置测试',
+ component: Component,
+ parameters: {
+ layout: 'padded',
+ },
+};
+
+const testData = [
+ { id: 1, name: '根节点1', parentId: null },
+ { id: 11, name: '子节点1-1', parentId: 1 },
+ { id: 12, name: '子节点1-2', parentId: 1 },
+ { id: 111, name: '孙节点1-1-1', parentId: 11 },
+ { id: 2, name: '根节点2', parentId: null },
+ { id: 21, name: '子节点2-1', parentId: 2 },
+ { id: 22, name: '子节点2-2', parentId: 2 },
+ { id: 211, name: '孙节点2-1-1', parentId: 21 },
+];
+
+export const PositioningTest = {
+ name: '弹出层位置测试',
+ render: (args) => ({
+ template: `
+
+
弹出层位置智能调整测试
+
+ 测试在不同位置时弹出层的智能定位功能
+
+
+
+
+
页面顶部区域 - 向下弹出
+
+ 在页面顶部时,下方有充足空间,弹出层应该向下展开
+
+
+
+
+
相对定位 + 自动方向
+
+
+
+
+
挂载到Body + 自动方向
+
+
+
+
+
强制向上弹出
+
+
+
+
+
+
+
+
中间填充区域 - 滚动到底部测试
+
+
+
+
+
页面底部区域 - 向上弹出
+
+ 在页面底部时,下方空间不足,弹出层应该向上展开
+
+
+
+
+
相对定位 + 自动方向
+
+
+ 选中: {{ value4 || '无' }}
+
+
+
+
+
挂载到Body + 自动方向
+
+
+ 选中: {{ value5 || '无' }}
+
+
+
+
+
强制向下弹出
+
+
+ 选中: {{ value6 || '无' }}
+
+
+
+
+
+
+
+
📋 配置说明:
+
+ 🎯 appendToBody: 是否将弹出层挂载到body元素下
+ • true: 解决容器遮挡问题,适用于复杂布局
+ • false: 相对定位,适用于简单场景
+
+ 🎯 placement: 弹出方向控制
+ • auto: 智能判断,优先向下,空间不足时向上
+ • bottom: 强制向下弹出
+ • top: 强制向上弹出
+
+ 🎯 智能定位逻辑:
+ 1. 计算上下可用空间
+ 2. 优先使用下方空间
+ 3. 下方空间不足且上方空间更大时,向上弹出
+ 4. 动态调整最大高度以适应可用空间
+
+
+
+
+
+
🧪 测试方法:
+
+ - 在页面顶部打开下拉框,观察是否向下弹出
+ - 滚动到页面底部,打开下拉框,观察是否向上弹出
+ - 对比相对定位和body挂载的区别
+ - 测试强制方向设置是否生效
+ - 滚动页面时,body挂载的弹出层是否跟随移动
+
+
+
+ `,
+ data() {
+ return {
+ testData,
+ value1: null,
+ value2: null,
+ value3: null,
+ value4: null,
+ value5: null,
+ value6: null
+ };
+ },
+ methods: {
+ getIdField(item) {
+ return item.id;
+ },
+ getNameField(item) {
+ return item.name;
+ },
+ getParentIdField(item) {
+ return item.parentId;
+ }
+ }
+ })
+};
+
+export const ContainerTest = {
+ name: '容器遮挡测试',
+ render: (args) => ({
+ template: `
+
+
容器遮挡问题测试
+
+ 测试在有遮挡的容器中使用appendToBody解决显示问题
+
+
+
+
+
正常容器(无遮挡)
+
+
+
+
相对定位
+
+
+
+
+
挂载到Body
+
+
+
+
+
+
+
+
+
有遮挡的容器(overflow: hidden + 固定高度)
+
+
+
+
相对定位 - 会被遮挡
+
+
+ ⚠ 弹出层会被容器遮挡
+
+
+
+
+
挂载到Body - 不被遮挡
+
+
+ ✅ 弹出层正常显示
+
+
+
+
+
+
+
+
+
滚动容器测试
+
+
+
+
容器顶部
+
+
+
+
+
容器中部
+
+
+
+
+
容器底部
+
+
+
+
+
+
+
+
💡 使用建议:
+
+ 🎯 何时使用 appendToBody="true":
+ 1. 父容器设置了 overflow: hidden
+ 2. 在弹框、抽屉等浮层组件内部
+ 3. 复杂的嵌套布局中
+ 4. 需要确保弹出层不被遮挡
+
+ 🎯 何时使用 appendToBody="false":
+ 1. 简单的页面布局
+ 2. 没有容器遮挡问题
+ 3. 性能要求较高的场景
+ 4. 希望弹出层跟随页面滚动
+
+
+
+ `,
+ data() {
+ return {
+ testData,
+ normalValue1: null,
+ normalValue2: null,
+ hiddenValue1: null,
+ hiddenValue2: null,
+ scrollValue1: null,
+ scrollValue2: null,
+ scrollValue3: null
+ };
+ },
+ methods: {
+ getIdField(item) {
+ return item.id;
+ },
+ getNameField(item) {
+ return item.name;
+ },
+ getParentIdField(item) {
+ return item.parentId;
+ }
+ }
+ })
+};
\ No newline at end of file
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/property-selector.stories.js b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/property-selector.stories.js
new file mode 100644
index 00000000..a8b7d46f
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/property-selector.stories.js
@@ -0,0 +1,355 @@
+import Component from '../index';
+
+export default {
+ id: 'cwd-tree-select-property-selector',
+ title: '组件列表/CwdTreeSelect/属性选择器',
+ component: Component,
+ parameters: {
+ layout: 'padded',
+ },
+};
+
+// 不同的测试数据格式
+const departmentData = [
+ { code: 'A001', title: '技术部', parentCode: null },
+ { code: 'A001-01', title: '前端组', parentCode: 'A001' },
+ { code: 'A001-02', title: '后端组', parentCode: 'A001' },
+ { code: 'A001-01-01', title: 'Vue小组', parentCode: 'A001-01' },
+ { code: 'B001', title: '产品部', parentCode: null },
+ { code: 'B001-01', title: 'UI设计组', parentCode: 'B001' },
+];
+
+const organizationData = [
+ { orgId: 1001, orgName: '总公司', superiorId: 0 },
+ { orgId: 1002, orgName: '分公司A', superiorId: 1001 },
+ { orgId: 1003, orgName: '分公司B', superiorId: 1001 },
+ { orgId: 1004, orgName: '部门A1', superiorId: 1002 },
+ { orgId: 1005, orgName: '部门A2', superiorId: 1002 },
+ { orgId: 1006, orgName: '部门B1', superiorId: 1003 },
+];
+
+const mixedFieldData = [
+ { id: 1, name: '根节点1', parent_id: null, fid: null },
+ { id: 11, name: '子节点1-1', parent_id: 1, fid: 1 },
+ { id: 12, name: '子节点1-2', parent_id: 1, fid: 1 },
+ { id: 111, name: '孙节点1-1-1', parent_id: 11, fid: 11 },
+ { id: 2, name: '根节点2', parent_id: null, fid: null },
+];
+
+export const PropertySelector = {
+ name: '属性选择器配置',
+ render: (args) => ({
+ template: `
+
+
属性选择器配置父节点字段
+
+ 在属性面板中通过下拉选择器选择数据字段作为父节点字段,无需手动输入字段名
+
+
+
+
+
+
示例1:选择 parentCode 字段
+
+
字段配置:
+
+ 值字段: getCodeField | 文本字段: getTitleField | 父节点字段: getParentCodeField
+
+
+
+
+
+
+ 选中值: {{ value1 || '无' }}
+
+
+
+ 查看数据结构
+ {{ JSON.stringify(deptData, null, 2) }}
+
+
+
+
+
+
示例2:选择 superiorId 字段
+
+
字段配置:
+
+ 值字段: getOrgIdField | 文本字段: getOrgNameField | 父节点字段: getSuperiorIdField
+
+
+
+
+
+
+ 选中值: {{ value2 || '无' }}
+
+
+
+ 查看数据结构
+ {{ JSON.stringify(orgData, null, 2) }}
+
+
+
+
+
+
+
示例3:对比不同父节点字段的效果
+
+ 同一数据源,选择不同的父节点字段,构建不同的树结构
+
+
+
+
+
选择 parent_id 字段
+
+ 父节点字段: getParentIdField
+
+
+
+
+
+ 选中值: {{ value3a || '无' }}
+
+
+
+
+
选择 fid 字段
+
+ 父节点字段: getFidField
+
+
+
+
+
+ 选中值: {{ value3b || '无' }}
+
+
+
+
+
+ 查看测试数据结构
+ {{ JSON.stringify(mixedData, null, 2) }}
+
+
+
+
+
✅ 属性选择器的优势:
+
+ - 🎯 智能识别:自动识别数据源中的可用字段
+ - ⚡ 方便快捷:通过下拉选择器快速选择字段
+ - 🔧 类型安全:确保选择的字段在数据中真实存在
+ - 🎨 用户友好:无需记住字段名,可视化选择
+ - 📝 减少错误:避免手动输入字段名时的拼写错误
+ - 🔄 动态更新:数据源字段变化时自动更新可选项
+
+
+
+ `,
+ data() {
+ return {
+ deptData: departmentData,
+ orgData: organizationData,
+ mixedData: mixedFieldData,
+ value1: null,
+ value2: null,
+ value3a: null,
+ value3b: null
+ };
+ },
+ methods: {
+ // 部门数据字段选择器
+ getCodeField(item) {
+ return item.code;
+ },
+ getTitleField(item) {
+ return item.title;
+ },
+ getParentCodeField(item) {
+ return item.parentCode;
+ },
+
+ // 组织数据字段选择器
+ getOrgIdField(item) {
+ return item.orgId;
+ },
+ getOrgNameField(item) {
+ return item.orgName;
+ },
+ getSuperiorIdField(item) {
+ return item.superiorId;
+ },
+
+ // 混合数据字段选择器
+ getIdField(item) {
+ return item.id;
+ },
+ getNameField(item) {
+ return item.name;
+ },
+ getParentIdField(item) {
+ return item.parent_id;
+ },
+ getFidField(item) {
+ return item.fid;
+ }
+ }
+ })
+};
+
+export const SelectorVsAutoDetection = {
+ name: '选择器 vs 自动检测',
+ render: (args) => ({
+ template: `
+
+
属性选择器 vs 自动检测对比
+
+ 对比属性选择器配置和自动检测的效果差异
+
+
+
+
+
+
属性选择器配置
+
+ 配置:通过属性面板选择 parent_id 字段
+
+
+
+
+
+ 选中: {{ selectorValue || '无' }}
+
+
+
+ 选择器优势:
+ 精确控制,可视化选择,避免字段名错误
+
+
+
+
+
+
自动检测
+
+ 配置:未指定父节点字段,自动检测
+
+
+
+
+
+ 选中: {{ autoValue || '无' }}
+
+
+
+ 检测规则:
+ 按优先级查找: parentId > parent_id > pid > parentid > parent > pId > fid
+
+
+
+
+
+
测试数据:
+
{{ JSON.stringify(testData, null, 2) }}
+
+
+
+
📋 配置方式对比:
+
+
+
+ | 配置方式 |
+ 优势 |
+ 适用场景 |
+
+
+
+
+ | 属性选择器 |
+ 精确控制、可视化操作、类型安全 |
+ 自定义字段名、复杂数据结构 |
+
+
+ | 自动检测 |
+ 零配置、快速上手、适应标准字段 |
+ 标准字段命名、快速原型 |
+
+
+
+
+
+ `,
+ data() {
+ return {
+ testData: [
+ { id: 1, name: '根节点1', parent_id: null, parentId: null },
+ { id: 11, name: '子节点1-1', parent_id: 1, parentId: 1 },
+ { id: 12, name: '子节点1-2', parent_id: 1, parentId: 1 },
+ { id: 111, name: '孙节点1-1-1', parent_id: 11, parentId: 11 },
+ { id: 2, name: '根节点2', parent_id: null, parentId: null }
+ ],
+ selectorValue: null,
+ autoValue: null
+ };
+ },
+ methods: {
+ getIdField(item) {
+ return item.id;
+ },
+ getNameField(item) {
+ return item.name;
+ },
+ getParentIdField(item) {
+ return item.parent_id;
+ }
+ }
+ })
+};
\ No newline at end of file
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/random-field-test.stories.js b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/random-field-test.stories.js
new file mode 100644
index 00000000..fb7c6753
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/random-field-test.stories.js
@@ -0,0 +1,289 @@
+import Component from '../index';
+
+export default {
+ id: 'cwd-tree-select-random-field-test',
+ title: '组件列表/CwdTreeSelect/随机字段测试',
+ component: Component,
+ parameters: {
+ layout: 'padded',
+ },
+};
+
+// 用户提供的实际数据 - aapid字段
+const userActualData = [
+ { id: 123, name: "测试4", aapid: 0 },
+ { id: 456, name: "测试5", aapid: 123 }
+];
+
+// 其他随机字段名测试数据
+const randomFieldData1 = [
+ { uniqueId: "A001", displayName: "根节点", xyz999abc: null },
+ { uniqueId: "A002", displayName: "子节点1", xyz999abc: "A001" },
+ { uniqueId: "A003", displayName: "子节点2", xyz999abc: "A001" },
+ { uniqueId: "A004", displayName: "孙节点", xyz999abc: "A002" }
+];
+
+const randomFieldData2 = [
+ { recordId: 1001, caption: "总部", superiorCode: 0 },
+ { recordId: 1002, caption: "分部A", superiorCode: 1001 },
+ { recordId: 1003, caption: "分部B", superiorCode: 1001 },
+ { recordId: 1004, caption: "小组A1", superiorCode: 1002 }
+];
+
+const randomFieldData3 = [
+ { pk: "X100", title: "主分类", parentKey: "" },
+ { pk: "X101", title: "子分类A", parentKey: "X100" },
+ { pk: "X102", title: "子分类B", parentKey: "X100" },
+ { pk: "X103", title: "子分类A1", parentKey: "X101" }
+];
+
+export const UserActualFieldTest = {
+ name: '用户实际字段测试 (aapid)',
+ render: (args) => ({
+ template: `
+
+
用户实际数据字段测试
+
+ 测试用户提供的实际数据:id、name、aapid 字段
+
+
+
+
原始数据:
+
{{ JSON.stringify(testData, null, 2) }}
+
+
+
+
属性选择器配置:
+
+ 值字段: getIdField (选择id字段)
+ 文本字段: getNameField (选择name字段)
+ 父节点字段: getAapidField (选择aapid字段)
+
+
+
+
+
+ 选中值: {{ selectedValue || '无' }}
+
+
+
+
+
✅ 预期结果:
+
+ - "测试4" (id:123, aapid:0) - 作为根节点显示
+ - "测试5" (id:456, aapid:123) - 作为"测试4"的子节点显示
+ - 能够正确展开/收起树节点
+ - 选择功能正常工作
+
+
+
+
+
🔧 关键修复:
+
+ 问题:数字0被错误地当作"根节点标识"处理
+ 解决:只有null、undefined、空字符串才视为根节点,数字0是有效的父节点ID
+
+
+
+ `,
+ data() {
+ return {
+ testData: userActualData,
+ selectedValue: null
+ };
+ },
+ methods: {
+ getIdField(item) {
+ console.log('getIdField called with:', item, 'returning:', item.id);
+ return item.id;
+ },
+ getNameField(item) {
+ console.log('getNameField called with:', item, 'returning:', item.name);
+ return item.name;
+ },
+ getAapidField(item) {
+ console.log('getAapidField called with:', item, 'returning:', item.aapid);
+ return item.aapid;
+ },
+ onChange(event) {
+ console.log('aapid字段测试 - 选择改变:', event);
+ }
+ }
+ })
+};
+
+export const RandomFieldNames = {
+ name: '随机字段名测试',
+ render: (args) => ({
+ template: `
+
+
随机字段名全面测试
+
+ 测试各种随机字段名,验证属性选择器的通用性
+
+
+
+
+
+
测试1:xyz999abc 字段
+
+ 父节点字段: getXyz999abcField
+
+
+
+
+
+ 选中: {{ value1 || '无' }}
+
+
+
+ 查看数据
+ {{ JSON.stringify(data1, null, 2) }}
+
+
+
+
+
+
测试2:superiorCode 字段
+
+ 父节点字段: getSuperiorCodeField
+
+
+
+
+
+ 选中: {{ value2 || '无' }}
+
+
+
+ 查看数据
+ {{ JSON.stringify(data2, null, 2) }}
+
+
+
+
+
+
测试3:parentKey 字段
+
+ 父节点字段: getParentKeyField
+
+
+
+
+
+ 选中: {{ value3 || '无' }}
+
+
+
+ 查看数据
+ {{ JSON.stringify(data3, null, 2) }}
+
+
+
+
+
+
对比:用户的 aapid 字段
+
+ 父节点字段: getAapidField
+
+
+
+
+
+ 选中: {{ userValue || '无' }}
+
+
+
+ 查看数据
+ {{ JSON.stringify(userData, null, 2) }}
+
+
+
+
+
+
🎯 测试结论:
+
+ ✅ 任意字段名支持:PropertySelectSetter 能够识别和选择任何字段名
+ ✅ 数据类型兼容:支持数字、字符串等不同类型的ID值
+ ✅ 空值处理:正确处理 null、0、空字符串等各种空值情况
+ ✅ 树构建:能够基于任意字段名构建正确的树形结构
+
+
+
+ `,
+ data() {
+ return {
+ data1: randomFieldData1,
+ data2: randomFieldData2,
+ data3: randomFieldData3,
+ userData: userActualData,
+ value1: null,
+ value2: null,
+ value3: null,
+ userValue: null
+ };
+ },
+ methods: {
+ // 数据1的字段选择器
+ getUniqueIdField(item) { return item.uniqueId; },
+ getDisplayNameField(item) { return item.displayName; },
+ getXyz999abcField(item) { return item.xyz999abc; },
+
+ // 数据2的字段选择器
+ getRecordIdField(item) { return item.recordId; },
+ getCaptionField(item) { return item.caption; },
+ getSuperiorCodeField(item) { return item.superiorCode; },
+
+ // 数据3的字段选择器
+ getPkField(item) { return item.pk; },
+ getTitleField(item) { return item.title; },
+ getParentKeyField(item) { return item.parentKey; },
+
+ // 用户数据的字段选择器
+ getUserIdField(item) { return item.id; },
+ getUserNameField(item) { return item.name; },
+ getAapidField(item) { return item.aapid; }
+ }
+ })
+};
\ No newline at end of file
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/slots.stories.js b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/slots.stories.js
new file mode 100644
index 00000000..927d16db
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/slots.stories.js
@@ -0,0 +1,483 @@
+import Component from '../index';
+
+export default {
+ id: 'cwd-tree-select-slots',
+ title: '组件列表/CwdTreeSelect/插槽演示',
+ component: Component,
+ parameters: {
+ layout: 'padded',
+ },
+ argTypes: {
+ multiple: {
+ control: 'boolean',
+ },
+ searchable: {
+ control: 'boolean',
+ },
+ clearable: {
+ control: 'boolean',
+ },
+ },
+};
+
+const treeData = [
+ // 根节点
+ { value: '1', text: '前端开发', parentId: null },
+ { value: '2', text: '后端开发', parentId: null },
+ { value: '3', text: '移动开发', parentId: null },
+
+ // 前端开发的子节点
+ { value: '1-1', text: 'Vue.js', parentId: '1' },
+ { value: '1-2', text: 'React', parentId: '1' },
+ { value: '1-3', text: 'Angular', parentId: '1' },
+
+ // Vue.js 的子节点
+ { value: '1-1-1', text: 'Vue 2', parentId: '1-1' },
+ { value: '1-1-2', text: 'Vue 3', parentId: '1-1' },
+
+ // React 的子节点
+ { value: '1-2-1', text: 'React 16', parentId: '1-2' },
+ { value: '1-2-2', text: 'React 18', parentId: '1-2' },
+
+ // 后端开发的子节点
+ { value: '2-1', text: 'Node.js', parentId: '2' },
+ { value: '2-2', text: 'Java', parentId: '2' },
+ { value: '2-3', text: 'Python', parentId: '2' },
+
+ // 移动开发的子节点
+ { value: '3-1', text: 'iOS', parentId: '3' },
+ { value: '3-2', text: 'Android', parentId: '3' },
+ { value: '3-3', text: 'Flutter', parentId: '3' }
+];
+
+export const HeaderFooterSlots = {
+ name: '头部和底部插槽',
+ render: (args) => ({
+ props: Object.keys(args),
+ template: `
+
+
带头部和底部插槽的树选择器
+
+ 使用 header 和 footer 插槽自定义下拉面板的头部和底部内容
+
+
+
+
+
+ 🌟 技术栈分类
+
+
+ 请选择您熟悉的技术栈,支持多选
+
+
+
+
+
+
+
+
+
+
+
+
+ 选中项数: {{ selectedCount }}
+
+
+ `,
+ data() {
+ return {
+ treeData,
+ selectedCount: 0
+ };
+ },
+ methods: {
+ onChange(event) {
+ this.selectedCount = event.values ? event.values.length : (event.value ? 1 : 0);
+ console.log('选择改变:', event);
+ },
+ selectAll() {
+ // 这里可以实现全选逻辑
+ console.log('全选');
+ },
+ clearAll() {
+ // 这里可以实现清空逻辑
+ console.log('清空');
+ }
+ }
+ }),
+ args: {
+ placeholder: '请选择技术栈',
+ multiple: true,
+ searchable: true,
+ clearable: true,
+ },
+};
+
+export const CustomEmptyLoading = {
+ name: '自定义空状态和加载状态',
+ render: (args) => ({
+ props: Object.keys(args),
+ template: `
+
+
+
自定义空状态
+
+
+
+
🔍
+
+ 暂无可选项
+
+
+ 请联系管理员添加选项数据
+
+
+
+
+
+
+
+
+
+ `,
+ data() {
+ return {
+ emptyData: []
+ };
+ },
+ computed: {
+ loadingDataSource() {
+ return () => {
+ return new Promise(() => {
+ // 永不resolve,保持加载状态
+ });
+ };
+ }
+ },
+ methods: {
+ refreshData() {
+ console.log('刷新数据');
+ this.emptyData = [...treeData];
+ }
+ }
+ }),
+ args: {},
+};
+
+export const FullCustomPanel = {
+ name: '完全自定义面板',
+ render: (args) => ({
+ props: Object.keys(args),
+ template: `
+
+
完全自定义下拉面板
+
+ 使用默认插槽完全替换下拉面板内容,实现自定义的选择界面
+
+
+
+
🎨 选择你喜欢的颜色
+
+
+
+
+ {{ color.name }}
+
+
+
+
+
+
+ 当前选择:
+
+ {{ selectedColorName || '未选择' }}
+
+
+
+
+
+
+
+
+
+
+
+ 当前值: {{ selectedValue || '未选择' }}
+ 颜色名称: {{ selectedColorName || '无' }}
+
+
+ `,
+ data() {
+ return {
+ selectedValue: null,
+ colors: [
+ { name: '红色', value: '#FF6B6B' },
+ { name: '橙色', value: '#FF8E53' },
+ { name: '黄色', value: '#FFD93D' },
+ { name: '绿色', value: '#6BCF7F' },
+ { name: '青色', value: '#4ECDC4' },
+ { name: '蓝色', value: '#45B7D1' },
+ { name: '紫色', value: '#96CEB4' },
+ { name: '粉色', value: '#FECA57' },
+ { name: '灰色', value: '#95A5A6' },
+ { name: '黑色', value: '#2C3E50' },
+ { name: '白色', value: '#ECF0F1' },
+ { name: '棕色', value: '#D2B48C' }
+ ]
+ };
+ },
+ computed: {
+ selectedColorName() {
+ const color = this.colors.find(c => c.value === this.selectedValue);
+ return color ? color.name : null;
+ }
+ },
+ methods: {
+ selectColor(color) {
+ this.selectedValue = color.value;
+ console.log('选择颜色:', color);
+ },
+ randomSelect() {
+ const randomIndex = Math.floor(Math.random() * this.colors.length);
+ this.selectedValue = this.colors[randomIndex].value;
+ },
+ clearSelection() {
+ this.selectedValue = null;
+ }
+ }
+ }),
+ args: {},
+};
+
+export const TabsStylePanel = {
+ name: '选项卡风格面板',
+ render: (args) => ({
+ template: `
+
+
选项卡风格的选择面板
+
+ 使用插槽实现分类选项卡的选择界面
+
+
+
+
+
+
+ {{ category.name }}
+
+
+
+
+
+
+
+
+
+ 已选择 {{ selectedValues.length }} 个技能
+
+
+
+
+
+
+
+
+
+
+
选中的技能:
+
+
+ {{ skill }}
+
+
+
+
+ `,
+ data() {
+ return {
+ selectedValues: [],
+ activeTab: 'frontend',
+ categories: [
+ {
+ id: 'frontend',
+ name: '前端',
+ skills: ['HTML', 'CSS', 'JavaScript', 'Vue.js', 'React', 'Angular', 'TypeScript', 'Webpack']
+ },
+ {
+ id: 'backend',
+ name: '后端',
+ skills: ['Node.js', 'Java', 'Python', 'Go', 'PHP', 'C#', 'Ruby', 'Spring Boot']
+ },
+ {
+ id: 'mobile',
+ name: '移动端',
+ skills: ['Flutter', 'React Native', 'iOS', 'Android', 'Xamarin', 'Ionic', 'Cordova', 'Swift']
+ },
+ {
+ id: 'other',
+ name: '其他',
+ skills: ['Docker', 'Kubernetes', 'AWS', 'MySQL', 'MongoDB', 'Redis', 'Git', 'Linux']
+ }
+ ]
+ };
+ },
+ methods: {
+ toggleSkill(skill) {
+ const index = this.selectedValues.indexOf(skill);
+ if (index > -1) {
+ this.selectedValues.splice(index, 1);
+ } else {
+ this.selectedValues.push(skill);
+ }
+ },
+ selectAllInCategory() {
+ const currentCategory = this.categories.find(cat => cat.id === this.activeTab);
+ if (currentCategory) {
+ currentCategory.skills.forEach(skill => {
+ if (!this.selectedValues.includes(skill)) {
+ this.selectedValues.push(skill);
+ }
+ });
+ }
+ },
+ clearAllSelections() {
+ this.selectedValues = [];
+ }
+ }
+ }),
+ args: {},
+};
\ No newline at end of file
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/tree-structure-debug.stories.js b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/tree-structure-debug.stories.js
new file mode 100644
index 00000000..32f0df3b
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/tree-structure-debug.stories.js
@@ -0,0 +1,440 @@
+import Component from '../index';
+
+export default {
+ id: 'cwd-tree-select-tree-debug',
+ title: '组件列表/CwdTreeSelect/树结构调试',
+ component: Component,
+ parameters: {
+ layout: 'padded',
+ },
+};
+
+// 测试用的各种数据格式
+const standardTreeData = [
+ { value: '1', text: '根节点1', parentId: null },
+ { value: '1-1', text: '子节点1-1', parentId: '1' },
+ { value: '1-2', text: '子节点1-2', parentId: '1' },
+ { value: '1-1-1', text: '孙节点1-1-1', parentId: '1-1' },
+ { value: '2', text: '根节点2', parentId: null },
+ { value: '2-1', text: '子节点2-1', parentId: '2' },
+];
+
+const numberIdTreeData = [
+ { id: 1, name: '部门1', pid: null },
+ { id: 11, name: '部门1-1', pid: 1 },
+ { id: 12, name: '部门1-2', pid: 1 },
+ { id: 111, name: '部门1-1-1', pid: 11 },
+ { id: 2, name: '部门2', pid: null },
+ { id: 21, name: '部门2-1', pid: 2 },
+];
+
+const mixedTypeTreeData = [
+ { value: 1, text: '类型1', parentId: null },
+ { value: '1-1', text: '子类型1-1', parentId: 1 },
+ { value: '1-2', text: '子类型1-2', parentId: '1' }, // 故意混合字符串和数字
+ { value: 2, text: '类型2', parentId: null },
+];
+
+const problemTreeData = [
+ { value: '1', text: '节点1', parentId: '' }, // 空字符串父ID
+ { value: '2', text: '节点2', parentId: null },
+ { value: '3', text: '节点3', parentId: '999' }, // 不存在的父节点
+ { value: '4', text: '节点4', parentId: 0 }, // 数字0作为父ID
+];
+
+// 用户提供的实际数据格式
+const userActualTreeData = [
+ { id: 123, name: "测试", fid: 0 },
+ { id: 456, name: "测试1", fid: 123 }
+];
+
+export const StandardTreeStructure = {
+ name: '标准树结构测试',
+ render: (args) => ({
+ template: `
+
+
标准树结构测试
+
+ 测试标准的 value/text/parentId 字段格式
+
+
+
+
+
+
数据结构分析:
+
+
原始数据: {{ treeData.length }} 条
+
选中值: {{ selectedValue || '无' }}
+
+
+
原始数据:
+
{{ JSON.stringify(treeData, null, 2) }}
+
+
+
+ ✅ 预期结果: 应该显示为两级树结构,根节点1和根节点2各有子节点
+
+
+ `,
+ data() {
+ return {
+ treeData: standardTreeData,
+ selectedValue: null
+ };
+ },
+ methods: {
+ onChange(event) {
+ console.log('标准树结构 - 选择改变:', event);
+ }
+ }
+ })
+};
+
+export const NumberIdTreeStructure = {
+ name: '数字ID树结构测试',
+ render: (args) => ({
+ template: `
+
+
数字ID树结构测试
+
+ 测试 id/name/pid 字段格式,ID为数字类型
+
+
+
+
+
+
数据结构分析:
+
+
原始数据: {{ treeData.length }} 条
+
选中值: {{ selectedValue || '无' }}
+
字段映射: id→value, name→text, pid→parentId
+
+
+
原始数据:
+
{{ JSON.stringify(treeData, null, 2) }}
+
+
+
+ ✅ 预期结果: 应该正确显示部门层级结构,即使ID是数字类型
+
+
+ `,
+ data() {
+ return {
+ treeData: numberIdTreeData,
+ selectedValue: null
+ };
+ },
+ methods: {
+ getIdField(item) {
+ return item.id;
+ },
+ getNameField(item) {
+ return item.name;
+ },
+ getPidField(item) {
+ return item.pid;
+ },
+ onChange(event) {
+ console.log('数字ID树结构 - 选择改变:', event);
+ }
+ }
+ })
+};
+
+export const MixedTypeTreeStructure = {
+ name: '混合类型树结构测试',
+ render: (args) => ({
+ template: `
+
+
混合类型树结构测试
+
+ 测试ID和父ID类型不一致的情况(数字vs字符串)
+
+
+
+
+
+
数据结构分析:
+
+
原始数据: {{ treeData.length }} 条
+
选中值: {{ selectedValue || '无' }}
+
注意: 存在类型混合问题
+
+
+
原始数据:
+
{{ JSON.stringify(treeData, null, 2) }}
+
+
+
+ ⚠️ 预期结果: 组件应该处理类型不一致问题,统一转换为字符串进行比较
+
+
+ `,
+ data() {
+ return {
+ treeData: mixedTypeTreeData,
+ selectedValue: null
+ };
+ },
+ methods: {
+ onChange(event) {
+ console.log('混合类型树结构 - 选择改变:', event);
+ }
+ }
+ })
+};
+
+export const ProblemTreeStructure = {
+ name: '问题数据树结构测试',
+ render: (args) => ({
+ template: `
+
+
问题数据树结构测试
+
+ 测试各种异常的父ID值(空字符串、不存在的父节点等)
+
+
+
+
+
+
数据结构分析:
+
+
原始数据: {{ treeData.length }} 条
+
选中值: {{ selectedValue || '无' }}
+
包含异常父ID值
+
+
+
原始数据:
+
{{ JSON.stringify(treeData, null, 2) }}
+
+
+
+ 🔧 预期结果: 组件应该容错处理,将无效父ID的节点都显示为根节点
+
+
+ `,
+ data() {
+ return {
+ treeData: problemTreeData,
+ selectedValue: null
+ };
+ },
+ methods: {
+ onChange(event) {
+ console.log('问题数据树结构 - 选择改变:', event);
+ }
+ }
+ })
+};
+
+export const UserActualDataTest = {
+ name: '用户实际数据测试',
+ render: (args) => ({
+ template: `
+
+
用户实际数据测试
+
+ 测试用户提供的实际数据格式: id/name/fid (fid=0为根节点)
+
+
+
+
+
+
数据结构分析:
+
+
原始数据: {{ userData.length }} 条
+
选中值: {{ selectedValue || '无' }}
+
字段映射: id→value, name→text, fid→parentId
+
注意: fid=0 视为根节点
+
+
+
原始数据:
+
{{ JSON.stringify(userData, null, 2) }}
+
+
+
+ ✅ 预期结果: "测试"作为根节点,"测试1"作为其子节点
+
+
+ `,
+ data() {
+ return {
+ userData: userActualTreeData,
+ selectedValue: null
+ };
+ },
+ methods: {
+ getIdField(item) {
+ return item.id;
+ },
+ getNameField(item) {
+ return item.name;
+ },
+ getFidField(item) {
+ return item.fid;
+ },
+ onChange(event) {
+ console.log('用户实际数据 - 选择改变:', event);
+ }
+ }
+ })
+};
+
+export const TreeStructureComparison = {
+ name: '树结构对比测试',
+ render: (args) => ({
+ template: `
+
+
树结构构建对比测试
+
+ 对比不同数据格式的树结构构建效果
+
+
+
+
+
+
标准格式 (value/text/parentId)
+
+
+ 选中: {{ standardValue || '无' }}
+
+
+
+
+
+
数字ID格式 (id/name/pid)
+
+
+ 选中: {{ numberValue || '无' }}
+
+
+
+
+
+
用户格式 (id/name/fid)
+
+
+ 选中: {{ userValue || '无' }}
+
+
+
+
+
+
构建结果分析:
+
+
+
标准格式数据:
+
{{ JSON.stringify(standardData, null, 2) }}
+
+
+
数字ID格式数据:
+
{{ JSON.stringify(numberData, null, 2) }}
+
+
+
用户格式数据:
+
{{ JSON.stringify(userData, null, 2) }}
+
+
+
+
+
+ 📊 对比目标: 三种格式都应该构建出正确的树结构,验证字段映射和类型转换的正确性
+
+
+ `,
+ data() {
+ return {
+ standardData: standardTreeData,
+ numberData: numberIdTreeData,
+ userData: userActualTreeData,
+ standardValue: null,
+ numberValue: null,
+ userValue: null
+ };
+ },
+ methods: {
+ getIdField(item) {
+ return item.id;
+ },
+ getNameField(item) {
+ return item.name;
+ },
+ getPidField(item) {
+ return item.pid;
+ },
+ getUserIdField(item) {
+ return item.id;
+ },
+ getUserNameField(item) {
+ return item.name;
+ },
+ getFidField(item) {
+ return item.fid;
+ },
+ getPidField(item) {
+ return item.pid;
+ },
+ getUserFidField(item) {
+ return item.fid;
+ }
+ }
+ })
+};
\ No newline at end of file
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/vue2-teleport-fix.stories.js b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/vue2-teleport-fix.stories.js
new file mode 100644
index 00000000..85f6cb68
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/cwd-tree-select/stories/vue2-teleport-fix.stories.js
@@ -0,0 +1,217 @@
+import Component from '../index';
+
+export default {
+ id: 'cwd-tree-select-vue2-teleport-fix',
+ title: '组件列表/CwdTreeSelect/Vue2兼容性修复',
+ component: Component,
+ parameters: {
+ layout: 'padded',
+ },
+};
+
+const testData = [
+ { id: 1, name: '根节点1', parentId: null },
+ { id: 11, name: '子节点1-1', parentId: 1 },
+ { id: 12, name: '子节点1-2', parentId: 1 },
+ { id: 111, name: '孙节点1-1-1', parentId: 11 },
+ { id: 2, name: '根节点2', parentId: null },
+ { id: 21, name: '子节点2-1', parentId: 2 },
+ { id: 22, name: '子节点2-2', parentId: 2 },
+];
+
+export const Vue2TeleportFix = {
+ name: 'Vue2 Teleport兼容性修复',
+ render: (args) => ({
+ template: `
+
+
Vue2 Teleport兼容性修复测试
+
+ 修复Vue2中不支持teleport导致的弹出层不显示问题
+
+
+
+
+
页面顶部 - 向下弹出测试
+
+
+
+
相对定位(不挂载到body)
+
+
+ 选中: {{ value1 || '无' }}
+
+
+
+
+
挂载到body(修复后)
+
+
+ 选中: {{ value2 || '无' }}
+
+
+
+
+
+ ✅ 预期结果:两个选择器都应该能正常弹出向下的下拉面板
+
+
+
+
+
+
滚动到底部进行向上弹出测试
+
+
+
+
+
页面底部 - 向上弹出测试
+
+
+
+
相对定位 + 向上弹出
+
+
+ 选中: {{ value3 || '无' }}
+
+
+
+
+
Body挂载 + 向上弹出
+
+
+ 选中: {{ value4 || '无' }}
+
+
+
+
+
+
强制向上弹出测试
+
+
+
相对定位 + 强制向上
+
+
+
+
+
Body挂载 + 强制向上
+
+
+
+
+
+
+ ✅ 预期结果:所有选择器都应该能正常弹出向上的下拉面板
+
+
+
+
+
+
🔧 修复说明:
+
+
问题:Vue2不支持teleport组件,导致弹出层无法正确渲染
+
解决方案:
+
+ - 移除teleport组件,使用v-if条件渲染
+ - appendToBody=false时,使用相对定位
+ - appendToBody=true时,使用固定定位挂载到body
+ - 保持原有的智能位置计算和样式控制
+
+
+
+
+
+
+
🧪 测试要点:
+
+ - 弹出层显示:所有配置下弹出层都应该正常显示
+ - 位置计算:向上/向下弹出方向应该正确
+ - 样式渲染:弹出层样式应该正确应用
+ - 交互功能:选择、展开/收起功能正常
+ - 遮挡处理:appendToBody应该能解决遮挡问题
+
+
+
+ `,
+ data() {
+ return {
+ testData,
+ value1: null,
+ value2: null,
+ value3: null,
+ value4: null,
+ value5: null,
+ value6: null
+ };
+ },
+ methods: {
+ getIdField(item) {
+ return item.id;
+ },
+ getNameField(item) {
+ return item.name;
+ },
+ getParentIdField(item) {
+ return item.parentId;
+ }
+ }
+ })
+};
\ No newline at end of file
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/index.ts b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/index.ts
new file mode 100644
index 00000000..70446c8a
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/components/index.ts
@@ -0,0 +1,5 @@
+// COMPONENT IMPORTS
+export {
+ // COMPONENT EXPORTS
+};
+export { default as CwdTreeSelect } from './cwd-tree-select';
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/index.ts b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/index.ts
new file mode 100644
index 00000000..66ee91c3
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/index.ts
@@ -0,0 +1,29 @@
+import * as Components from './components';
+import * as logics from './logics';
+
+const LIBRARY_NAME = '{{LIBRARY_NAME}}';
+const UtilsLogics = {
+ install: (Vue) => {
+ Vue.prototype.$library = Vue.prototype.$library || {};
+ Vue.prototype.$library[LIBRARY_NAME] = {
+ ...logics,
+ };
+ },
+};
+
+const UseComponents = {
+ install: (Vue) => {
+ Object.keys(Components).forEach((name) => {
+ if (Components[name].Component && Components[name].install) {
+ Vue.component(name, Components[name].Component);
+ return;
+ }
+ Vue.component(name, Components[name])
+ })
+ }
+};
+
+export {
+ UseComponents,
+ UtilsLogics,
+};
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/logics/index.ts b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/logics/index.ts
new file mode 100644
index 00000000..e7cc1e5c
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/logics/index.ts
@@ -0,0 +1,3 @@
+import '@nasl/types';
+
+export {};
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/typings.d.ts b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/typings.d.ts
new file mode 100644
index 00000000..0f57c23e
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/src/typings.d.ts
@@ -0,0 +1,8 @@
+declare module '*.module.css' {
+ const classes: { [key: string]: string };
+ export default classes;
+}
+declare module '*.module.less' {
+ const classes: { [key: string]: string };
+ export default classes;
+}
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/tsconfig.api.json b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/tsconfig.api.json
new file mode 100644
index 00000000..83249c56
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/tsconfig.api.json
@@ -0,0 +1,33 @@
+{
+ "compilerOptions": {
+ "target": "esnext",
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "noImplicitAny": true,
+ "noImplicitThis": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowJs": false,
+ "resolveJsonModule": true,
+ "allowSyntheticDefaultImports": false,
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "sourceMap": false,
+ "pretty": true,
+ "baseUrl": ".",
+ "declaration": true,
+ "noEmit": false,
+ "emitDeclarationOnly": true,
+ "outFile": "./nasl.extension.ts",
+ "lib": ["esnext", "dom"],
+ "paths": {
+ "@/*": ["*"]
+ }
+ },
+ "include": [
+ "./node_modules/@lcap/builder/typings.d.ts",
+ "./src/components/*/api.ts",
+ "./src/logics/api.ts",
+ "./src/typings.d.ts"
+ ]
+}
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/tsconfig.json b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/tsconfig.json
new file mode 100644
index 00000000..cf7d8dcc
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/tsconfig.json
@@ -0,0 +1,41 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "alwaysStrict": false,
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": false,
+ "allowJs": true,
+ "noEmit": true,
+ "jsx": "preserve",
+ "jsxFactory": "h",
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "experimentalDecorators": true,
+ "strictPropertyInitialization": false,
+ "noFallthroughCasesInSwitch": true,
+ "emitDecoratorMetadata": true,
+ "noImplicitAny": false,
+ "paths": {
+ "@/*": ["./src/*"],
+ "@components/*": ["./src/components/*"],
+ "@lcap-ui/*": ["./.lcap/lcap-ui/package/*"]
+ }
+ },
+ "include": [
+ "./node_modules/@lcap/builder/typings.d.ts",
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "ide/**/*.ts"
+ ],
+ "references": [
+ ]
+}
diff --git a/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/vite.config.js b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/vite.config.js
new file mode 100644
index 00000000..ede14583
--- /dev/null
+++ b/workspaces/ts-vue2/packages/cust/cust_h5_tree_library/vite.config.js
@@ -0,0 +1,69 @@
+import { defineConfig } from 'vite';
+import path from 'path';
+import { createVuePlugin as vue2 } from '@lcap/vite-plugin-vue2';
+import { createGenScopedName, lcapPlugin } from '@lcap/builder';
+
+// 设置测试运行的时区
+process.env.TZ = 'Asia/Shanghai';
+const kb2Camcel = (name) => name.replace(/(?:^|-)([a-zA-Z0-9])/g, (m, $1) => $1.toUpperCase());
+
+// https://vitejs.dev/config/
+export default defineConfig(({ command }) => {
+ const pkgInfo = require(`${process.cwd()}/package.json`);
+ return {
+ plugins: [
+ vue2({
+ jsx: true,
+ jsxInclude: [
+ /.(jsx|tsx)$/,
+ /\.lcap\/.*(js|ts)$/,
+ ],
+ jsxOptions: {
+ vModel: true,
+ functional: false,
+ injectH: true,
+ vOn: true,
+ compositionAPI: false,
+ },
+ }),
+ lcapPlugin({
+ type: 'extension',
+ framework: 'vue2',
+ }),
+ ],
+ resolve: {
+ extensions: ['.js', '.ts', '.tsx', '.jsx', '.vue', '.mjs', '.cjs', '.json'],
+ alias: {
+ '@': path.resolve(__dirname, './src'),
+ '@lcap-ui': path.resolve(__dirname, './.lcap/lcap-ui/package'),
+ 'cloud-ui.vusion/src': path.resolve(__dirname, './.lcap/lcap-ui/package/cloudui'),
+ 'swiper/swiper-bundle.esm.js': path.resolve(__dirname, './node_modules/swiper/swiper-bundle.esm.js'),
+ '@joskii/jflow-core': path.resolve(__dirname, './node_modules/@joskii/jflow-core/dist/jflow.es.min.js'),
+ '@joskii/jflow-vue2-plugin': path.resolve(__dirname, './node_modules/@joskii/jflow-vue2-plugin/dist/jflow-vue2-plugin.es.min.js'),
+ },
+ },
+ define: {
+ 'process.env': {
+ VUE_APP_DESIGNER: false,
+ NODE_ENV: command === 'build' ? 'production' : 'development',
+ },
+ },
+ css: {
+ modules: {
+ generateScopedName: createGenScopedName(pkgInfo.name, './src'),
+ },
+ },
+ build: {
+ cssCodeSplit: false,
+ target: ['es2020', 'edge88', 'firefox78', 'chrome56', 'safari14'],
+ lib: {
+ entry: 'src/index',
+ name: kb2Camcel(pkgInfo.name),
+ },
+ sourcemap: true,
+ },
+ test: {
+ environment: 'jsdom',
+ },
+ };
+});