表单设计器

This commit is contained in:
dengqichen 2025-10-23 23:26:33 +08:00
parent 00e5bf6315
commit 34a5eb0ddd
5 changed files with 157 additions and 16 deletions

View File

@ -7,6 +7,7 @@ import {
environmentProjectsConfig, environmentProjectsConfig,
jenkinsServerViewsJobsConfig, jenkinsServerViewsJobsConfig,
departmentUsersConfig, departmentUsersConfig,
departmentTreeConfig,
projectGroupAppsConfig projectGroupAppsConfig
} from './presets/cascade'; } from './presets/cascade';
@ -17,6 +18,7 @@ export const CASCADE_DATA_SOURCE_REGISTRY: CascadeDataSourceRegistry = {
[CascadeDataSourceType.ENVIRONMENT_PROJECTS]: environmentProjectsConfig, [CascadeDataSourceType.ENVIRONMENT_PROJECTS]: environmentProjectsConfig,
[CascadeDataSourceType.JENKINS_SERVER_VIEWS_JOBS]: jenkinsServerViewsJobsConfig, [CascadeDataSourceType.JENKINS_SERVER_VIEWS_JOBS]: jenkinsServerViewsJobsConfig,
[CascadeDataSourceType.DEPARTMENT_USERS]: departmentUsersConfig, [CascadeDataSourceType.DEPARTMENT_USERS]: departmentUsersConfig,
[CascadeDataSourceType.DEPARTMENT_TREE]: departmentTreeConfig,
[CascadeDataSourceType.PROJECT_GROUP_APPS]: projectGroupAppsConfig [CascadeDataSourceType.PROJECT_GROUP_APPS]: projectGroupAppsConfig
}; };

View File

@ -10,6 +10,13 @@ import { getCascadeDataSourceConfig } from './CascadeDataSourceRegistry';
* *
*/ */
class CascadeDataSourceService { class CascadeDataSourceService {
/**
*
*/
private isRecursiveMode(config: any): boolean {
return !!config.recursive;
}
/** /**
* *
* @param type * @param type
@ -18,8 +25,19 @@ class CascadeDataSourceService {
async loadFirstLevel(type: CascadeDataSourceType): Promise<CascadeOption[]> { async loadFirstLevel(type: CascadeDataSourceType): Promise<CascadeOption[]> {
const config = getCascadeDataSourceConfig(type); const config = getCascadeDataSourceConfig(type);
if (!config || config.levels.length === 0) { if (!config) {
console.error(`❌ 级联数据源类型 ${type} 未配置或配置为空`); 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 []; return [];
} }
@ -43,6 +61,18 @@ class CascadeDataSourceService {
return []; 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; const levelIndex = selectedOptions.length;
if (levelIndex >= config.levels.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<CascadeOption[]> {
try {
// 构建请求参数
const params: Record<string, any> = { ...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 levelConfig
* @param parentValue * @param parentValue
* @param hasNextLevel * @param hasNextLevel
@ -108,11 +191,18 @@ class CascadeDataSourceService {
/** /**
* *
* @param type * @param type
* @returns * @returns -1
*/ */
getLevelCount(type: CascadeDataSourceType): number { getLevelCount(type: CascadeDataSourceType): number {
const config = getCascadeDataSourceConfig(type); 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;
} }
/** /**

View File

@ -53,7 +53,7 @@ export const jenkinsServerViewsJobsConfig: CascadeDataSourceConfig = {
}; };
/** /**
* *
*/ */
export const departmentUsersConfig: CascadeDataSourceConfig = { export const departmentUsersConfig: CascadeDataSourceConfig = {
description: '部门 → 用户', 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 字段
}
};
/** /**
* *
*/ */

View File

@ -21,10 +21,11 @@ export enum DataSourceType {
* *
*/ */
export enum CascadeDataSourceType { export enum CascadeDataSourceType {
ENVIRONMENT_PROJECTS = 'ENVIRONMENT_PROJECTS', // 环境 → 项目 ENVIRONMENT_PROJECTS = 'ENVIRONMENT_PROJECTS', // 环境 → 项目固定2级
JENKINS_SERVER_VIEWS_JOBS = 'JENKINS_SERVER_VIEWS_JOBS', // Jenkins → View → Job JENKINS_SERVER_VIEWS_JOBS = 'JENKINS_SERVER_VIEWS_JOBS', // Jenkins → View → Job固定3级
DEPARTMENT_USERS = 'DEPARTMENT_USERS', // 部门 → 用户 DEPARTMENT_USERS = 'DEPARTMENT_USERS', // 部门 → 用户固定2级
PROJECT_GROUP_APPS = 'PROJECT_GROUP_APPS' // 项目组 → 应用 DEPARTMENT_TREE = 'DEPARTMENT_TREE', // 部门树(递归无限层级)⭐
PROJECT_GROUP_APPS = 'PROJECT_GROUP_APPS' // 项目组 → 应用固定2级
} }
/** /**
@ -66,11 +67,27 @@ export interface CascadeLevelConfig {
isLeaf?: (item: any) => boolean; // 判断是否为叶子节点 isLeaf?: (item: any) => boolean; // 判断是否为叶子节点
} }
/**
*
*/
export interface RecursiveCascadeConfig {
url: string; // 接口地址
labelField: string; // 标签字段
valueField: string; // 值字段
parentParam: string; // 父级参数名(如 'parentId'
params?: Record<string, any>; // 额外参数
hasChildren?: (item: any) => boolean; // 判断是否有子节点(可选,默认根据后端 isLeaf 字段)
}
/** /**
* *
*
* 1. - 使 levels
* 2. - 使 recursive
*/ */
export interface CascadeDataSourceConfig { export interface CascadeDataSourceConfig {
levels: CascadeLevelConfig[]; // 级联层级配置 levels?: CascadeLevelConfig[]; // 固定层级配置
recursive?: RecursiveCascadeConfig; // 递归层级配置(无限层级)
description?: string; // 描述 description?: string; // 描述
} }

View File

@ -85,20 +85,35 @@ export const useCascaderLoadData = (field: FieldConfig) => {
// 提取已选择的值 // 提取已选择的值
const selectedValues = selectedOptions.map(opt => opt.value); const selectedValues = selectedOptions.map(opt => opt.value);
// 获取目标选项(最后一个被选择的选项)
const targetOption = selectedOptions[selectedOptions.length - 1];
try { try {
// 加载子级数据 // 加载子级数据
const childrenData = await cascadeDataSourceService.loadChildren(sourceType, selectedValues); const childrenData = await cascadeDataSourceService.loadChildren(sourceType, selectedValues);
console.log(`✅ 懒加载成功,子级数据:`, childrenData); console.log(`✅ 懒加载成功,子级数据:`, childrenData);
// 转换为级联选项格式
const children = convertToCascadeFieldOption(childrenData);
// 更新最后一个选项的 children // 更新最后一个选项的 children
const targetOption = selectedOptions[selectedOptions.length - 1]; // 重要:需要设置 loading: false 和 children
targetOption.loading = false; 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) { } catch (error) {
console.error(`❌ 懒加载失败:`, error); console.error(`❌ 懒加载失败:`, error);
const targetOption = selectedOptions[selectedOptions.length - 1];
// 出错时标记为叶子节点(不再展开)
targetOption.loading = false; targetOption.loading = false;
targetOption.children = []; targetOption.children = undefined;
targetOption.isLeaf = true;
} }
}, },
[sourceType] [sourceType]