diff --git a/frontend/src/domain/dataSource/CascadeDataSourceRegistry.ts b/frontend/src/domain/dataSource/CascadeDataSourceRegistry.ts index bfa1cf64..f2bf7f0f 100644 --- a/frontend/src/domain/dataSource/CascadeDataSourceRegistry.ts +++ b/frontend/src/domain/dataSource/CascadeDataSourceRegistry.ts @@ -7,6 +7,7 @@ import { environmentProjectsConfig, jenkinsServerViewsJobsConfig, departmentUsersConfig, + departmentTreeConfig, projectGroupAppsConfig } from './presets/cascade'; @@ -17,6 +18,7 @@ export const CASCADE_DATA_SOURCE_REGISTRY: CascadeDataSourceRegistry = { [CascadeDataSourceType.ENVIRONMENT_PROJECTS]: environmentProjectsConfig, [CascadeDataSourceType.JENKINS_SERVER_VIEWS_JOBS]: jenkinsServerViewsJobsConfig, [CascadeDataSourceType.DEPARTMENT_USERS]: departmentUsersConfig, + [CascadeDataSourceType.DEPARTMENT_TREE]: departmentTreeConfig, [CascadeDataSourceType.PROJECT_GROUP_APPS]: projectGroupAppsConfig }; diff --git a/frontend/src/domain/dataSource/CascadeDataSourceService.ts b/frontend/src/domain/dataSource/CascadeDataSourceService.ts index 456484a2..c4b20982 100644 --- a/frontend/src/domain/dataSource/CascadeDataSourceService.ts +++ b/frontend/src/domain/dataSource/CascadeDataSourceService.ts @@ -10,6 +10,13 @@ import { getCascadeDataSourceConfig } from './CascadeDataSourceRegistry'; * 级联数据源服务类 */ class CascadeDataSourceService { + /** + * 判断配置是否为递归模式 + */ + private isRecursiveMode(config: any): boolean { + return !!config.recursive; + } + /** * 加载第一级数据 * @param type 级联数据源类型 @@ -18,8 +25,19 @@ class CascadeDataSourceService { async loadFirstLevel(type: CascadeDataSourceType): Promise { const config = getCascadeDataSourceConfig(type); - if (!config || config.levels.length === 0) { - console.error(`❌ 级联数据源类型 ${type} 未配置或配置为空`); + if (!config) { + console.error(`❌ 级联数据源类型 ${type} 未配置`); + return []; + } + + // 递归模式 + if (this.isRecursiveMode(config)) { + return this.loadRecursiveLevel(config.recursive!, null); + } + + // 固定层级模式 + if (!config.levels || config.levels.length === 0) { + console.error(`❌ 级联数据源类型 ${type} 未配置层级`); return []; } @@ -43,6 +61,18 @@ class CascadeDataSourceService { return []; } + // 递归模式:使用同一配置加载所有层级 + if (this.isRecursiveMode(config)) { + const parentValue = selectedOptions[selectedOptions.length - 1]; + return this.loadRecursiveLevel(config.recursive!, parentValue); + } + + // 固定层级模式 + if (!config.levels) { + console.error(`❌ 级联数据源类型 ${type} 未配置层级`); + return []; + } + const levelIndex = selectedOptions.length; if (levelIndex >= config.levels.length) { @@ -58,7 +88,60 @@ class CascadeDataSourceService { } /** - * 加载单个层级的数据 + * 加载递归层级数据(无限层级) + * @param recursiveConfig 递归配置 + * @param parentValue 父级值(null 表示加载根节点) + * @returns 选项列表 + */ + private async loadRecursiveLevel( + recursiveConfig: any, + parentValue: any + ): Promise { + try { + // 构建请求参数 + const params: Record = { ...recursiveConfig.params }; + + // 添加父级参数 + if (parentValue !== null) { + params[recursiveConfig.parentParam] = parentValue; + } + + // 发起请求 + const response = await request.get(recursiveConfig.url, { params }); + const data = response || []; + + // 转换为级联选项格式 + return data.map((item: any) => { + const option: CascadeOption = { + label: item[recursiveConfig.labelField], + value: item[recursiveConfig.valueField] + }; + + // 判断是否为叶子节点 + if (recursiveConfig.hasChildren) { + // 使用自定义判断函数 + option.isLeaf = !recursiveConfig.hasChildren(item); + } else if ('isLeaf' in item) { + // 使用后端返回的 isLeaf 字段 + option.isLeaf = item.isLeaf; + } else if ('hasChildren' in item) { + // 使用后端返回的 hasChildren 字段 + option.isLeaf = !item.hasChildren; + } else { + // 默认不是叶子节点(允许继续展开) + option.isLeaf = false; + } + + return option; + }); + } catch (error) { + console.error(`❌ 加载递归级联数据失败:`, error); + return []; + } + } + + /** + * 加载单个层级的数据(固定层级模式) * @param levelConfig 层级配置 * @param parentValue 父级值 * @param hasNextLevel 是否有下一级 @@ -108,11 +191,18 @@ class CascadeDataSourceService { /** * 获取级联配置的层级数 * @param type 级联数据源类型 - * @returns 层级数 + * @returns 层级数(递归模式返回 -1 表示无限层级) */ getLevelCount(type: CascadeDataSourceType): number { const config = getCascadeDataSourceConfig(type); - return config?.levels.length || 0; + if (!config) return 0; + + // 递归模式返回 -1 表示无限层级 + if (this.isRecursiveMode(config)) { + return -1; + } + + return config.levels?.length || 0; } /** diff --git a/frontend/src/domain/dataSource/presets/cascade.ts b/frontend/src/domain/dataSource/presets/cascade.ts index 7a5d21fc..483d8cb6 100644 --- a/frontend/src/domain/dataSource/presets/cascade.ts +++ b/frontend/src/domain/dataSource/presets/cascade.ts @@ -53,7 +53,7 @@ export const jenkinsServerViewsJobsConfig: CascadeDataSourceConfig = { }; /** - * 部门 → 用户 + * 部门 → 用户(固定两级) */ export const departmentUsersConfig: CascadeDataSourceConfig = { description: '部门 → 用户', @@ -73,6 +73,23 @@ export const departmentUsersConfig: CascadeDataSourceConfig = { ] }; +/** + * 部门树(递归无限层级)⭐ 新增 + * 适用于组织架构等需要无限层级的场景 + */ +export const departmentTreeConfig: CascadeDataSourceConfig = { + description: '部门树(无限层级)', + recursive: { + url: '/api/v1/department/tree', + parentParam: 'parentId', + labelField: 'name', + valueField: 'id', + // 可选:自定义判断是否有子节点 + // hasChildren: (item) => item.hasChildren + // 如果不提供,会自动使用后端返回的 isLeaf 或 hasChildren 字段 + } +}; + /** * 项目组 → 应用 */ diff --git a/frontend/src/domain/dataSource/types.ts b/frontend/src/domain/dataSource/types.ts index d247235f..e01af7fe 100644 --- a/frontend/src/domain/dataSource/types.ts +++ b/frontend/src/domain/dataSource/types.ts @@ -21,10 +21,11 @@ export enum DataSourceType { * 级联数据源类型枚举 */ export enum CascadeDataSourceType { - ENVIRONMENT_PROJECTS = 'ENVIRONMENT_PROJECTS', // 环境 → 项目 - JENKINS_SERVER_VIEWS_JOBS = 'JENKINS_SERVER_VIEWS_JOBS', // Jenkins → View → Job - DEPARTMENT_USERS = 'DEPARTMENT_USERS', // 部门 → 用户 - PROJECT_GROUP_APPS = 'PROJECT_GROUP_APPS' // 项目组 → 应用 + ENVIRONMENT_PROJECTS = 'ENVIRONMENT_PROJECTS', // 环境 → 项目(固定2级) + JENKINS_SERVER_VIEWS_JOBS = 'JENKINS_SERVER_VIEWS_JOBS', // Jenkins → View → Job(固定3级) + DEPARTMENT_USERS = 'DEPARTMENT_USERS', // 部门 → 用户(固定2级) + DEPARTMENT_TREE = 'DEPARTMENT_TREE', // 部门树(递归无限层级)⭐ + PROJECT_GROUP_APPS = 'PROJECT_GROUP_APPS' // 项目组 → 应用(固定2级) } /** @@ -66,12 +67,28 @@ export interface CascadeLevelConfig { isLeaf?: (item: any) => boolean; // 判断是否为叶子节点 } +/** + * 递归级联配置(用于无限层级场景,如组织架构) + */ +export interface RecursiveCascadeConfig { + url: string; // 接口地址 + labelField: string; // 标签字段 + valueField: string; // 值字段 + parentParam: string; // 父级参数名(如 'parentId') + params?: Record; // 额外参数 + hasChildren?: (item: any) => boolean; // 判断是否有子节点(可选,默认根据后端 isLeaf 字段) +} + /** * 级联数据源配置接口 + * 支持两种模式: + * 1. 固定层级模式 - 使用 levels 配置 + * 2. 递归层级模式 - 使用 recursive 配置(无限层级) */ export interface CascadeDataSourceConfig { - levels: CascadeLevelConfig[]; // 级联层级配置 - description?: string; // 描述 + levels?: CascadeLevelConfig[]; // 固定层级配置 + recursive?: RecursiveCascadeConfig; // 递归层级配置(无限层级) + description?: string; // 描述 } /** diff --git a/frontend/src/pages/FormDesigner/hooks/useCascaderOptions.ts b/frontend/src/pages/FormDesigner/hooks/useCascaderOptions.ts index 37bbafaf..4e80a6f6 100644 --- a/frontend/src/pages/FormDesigner/hooks/useCascaderOptions.ts +++ b/frontend/src/pages/FormDesigner/hooks/useCascaderOptions.ts @@ -85,20 +85,35 @@ export const useCascaderLoadData = (field: FieldConfig) => { // 提取已选择的值 const selectedValues = selectedOptions.map(opt => opt.value); + // 获取目标选项(最后一个被选择的选项) + const targetOption = selectedOptions[selectedOptions.length - 1]; + try { // 加载子级数据 const childrenData = await cascadeDataSourceService.loadChildren(sourceType, selectedValues); console.log(`✅ 懒加载成功,子级数据:`, childrenData); + // 转换为级联选项格式 + const children = convertToCascadeFieldOption(childrenData); + // 更新最后一个选项的 children - const targetOption = selectedOptions[selectedOptions.length - 1]; + // 重要:需要设置 loading: false 和 children targetOption.loading = false; - targetOption.children = convertToCascadeFieldOption(childrenData); + + // 如果有子数据,设置 children;如果没有,设置为 undefined 并标记为叶子节点 + if (children && children.length > 0) { + targetOption.children = children; + } else { + targetOption.children = undefined; + targetOption.isLeaf = true; + } } catch (error) { console.error(`❌ 懒加载失败:`, error); - const targetOption = selectedOptions[selectedOptions.length - 1]; + + // 出错时标记为叶子节点(不再展开) targetOption.loading = false; - targetOption.children = []; + targetOption.children = undefined; + targetOption.isLeaf = true; } }, [sourceType]