表单设计器
This commit is contained in:
parent
da38d2386b
commit
00e5bf6315
48
frontend/src/domain/dataSource/CascadeDataSourceRegistry.ts
Normal file
48
frontend/src/domain/dataSource/CascadeDataSourceRegistry.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* 级联数据源注册表
|
||||||
|
* 集中管理所有预设级联数据源配置
|
||||||
|
*/
|
||||||
|
import { CascadeDataSourceType, type CascadeDataSourceRegistry } from './types';
|
||||||
|
import {
|
||||||
|
environmentProjectsConfig,
|
||||||
|
jenkinsServerViewsJobsConfig,
|
||||||
|
departmentUsersConfig,
|
||||||
|
projectGroupAppsConfig
|
||||||
|
} from './presets/cascade';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 级联数据源配置注册表
|
||||||
|
*/
|
||||||
|
export const CASCADE_DATA_SOURCE_REGISTRY: CascadeDataSourceRegistry = {
|
||||||
|
[CascadeDataSourceType.ENVIRONMENT_PROJECTS]: environmentProjectsConfig,
|
||||||
|
[CascadeDataSourceType.JENKINS_SERVER_VIEWS_JOBS]: jenkinsServerViewsJobsConfig,
|
||||||
|
[CascadeDataSourceType.DEPARTMENT_USERS]: departmentUsersConfig,
|
||||||
|
[CascadeDataSourceType.PROJECT_GROUP_APPS]: projectGroupAppsConfig
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取级联数据源配置
|
||||||
|
* @param type 级联数据源类型
|
||||||
|
* @returns 级联数据源配置,如果不存在则返回 undefined
|
||||||
|
*/
|
||||||
|
export const getCascadeDataSourceConfig = (type: CascadeDataSourceType) => {
|
||||||
|
return CASCADE_DATA_SOURCE_REGISTRY[type];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查级联数据源类型是否已注册
|
||||||
|
* @param type 级联数据源类型
|
||||||
|
* @returns 是否已注册
|
||||||
|
*/
|
||||||
|
export const hasCascadeDataSource = (type: CascadeDataSourceType): boolean => {
|
||||||
|
return type in CASCADE_DATA_SOURCE_REGISTRY;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有已注册的级联数据源类型
|
||||||
|
* @returns 级联数据源类型列表
|
||||||
|
*/
|
||||||
|
export const getAllCascadeDataSourceTypes = (): CascadeDataSourceType[] => {
|
||||||
|
return Object.keys(CASCADE_DATA_SOURCE_REGISTRY) as CascadeDataSourceType[];
|
||||||
|
};
|
||||||
|
|
||||||
138
frontend/src/domain/dataSource/CascadeDataSourceService.ts
Normal file
138
frontend/src/domain/dataSource/CascadeDataSourceService.ts
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
/**
|
||||||
|
* 级联数据源服务
|
||||||
|
* 提供懒加载级联数据功能
|
||||||
|
*/
|
||||||
|
import request from '@/utils/request';
|
||||||
|
import { CascadeDataSourceType, type CascadeOption, type CascadeLevelConfig } from './types';
|
||||||
|
import { getCascadeDataSourceConfig } from './CascadeDataSourceRegistry';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 级联数据源服务类
|
||||||
|
*/
|
||||||
|
class CascadeDataSourceService {
|
||||||
|
/**
|
||||||
|
* 加载第一级数据
|
||||||
|
* @param type 级联数据源类型
|
||||||
|
* @returns 第一级选项列表
|
||||||
|
*/
|
||||||
|
async loadFirstLevel(type: CascadeDataSourceType): Promise<CascadeOption[]> {
|
||||||
|
const config = getCascadeDataSourceConfig(type);
|
||||||
|
|
||||||
|
if (!config || config.levels.length === 0) {
|
||||||
|
console.error(`❌ 级联数据源类型 ${type} 未配置或配置为空`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.loadLevel(config.levels[0], null, config.levels.length > 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载子级数据
|
||||||
|
* @param type 级联数据源类型
|
||||||
|
* @param selectedOptions 已选择的选项路径(如 [env1, project1])
|
||||||
|
* @returns 下一级选项列表
|
||||||
|
*/
|
||||||
|
async loadChildren(
|
||||||
|
type: CascadeDataSourceType,
|
||||||
|
selectedOptions: any[]
|
||||||
|
): Promise<CascadeOption[]> {
|
||||||
|
const config = getCascadeDataSourceConfig(type);
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
console.error(`❌ 级联数据源类型 ${type} 未配置`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const levelIndex = selectedOptions.length;
|
||||||
|
|
||||||
|
if (levelIndex >= config.levels.length) {
|
||||||
|
console.warn(`⚠️ 级联层级 ${levelIndex} 超出配置范围`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const levelConfig = config.levels[levelIndex];
|
||||||
|
const parentValue = selectedOptions[levelIndex - 1];
|
||||||
|
const hasNextLevel = levelIndex + 1 < config.levels.length;
|
||||||
|
|
||||||
|
return this.loadLevel(levelConfig, parentValue, hasNextLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载单个层级的数据
|
||||||
|
* @param levelConfig 层级配置
|
||||||
|
* @param parentValue 父级值
|
||||||
|
* @param hasNextLevel 是否有下一级
|
||||||
|
* @returns 选项列表
|
||||||
|
*/
|
||||||
|
private async loadLevel(
|
||||||
|
levelConfig: CascadeLevelConfig,
|
||||||
|
parentValue: any,
|
||||||
|
hasNextLevel: boolean
|
||||||
|
): Promise<CascadeOption[]> {
|
||||||
|
try {
|
||||||
|
// 构建请求参数
|
||||||
|
const params: Record<string, any> = { ...levelConfig.params };
|
||||||
|
|
||||||
|
// 如果有父级值且配置了父级参数名,添加到请求参数中
|
||||||
|
if (parentValue !== null && levelConfig.parentParam) {
|
||||||
|
params[levelConfig.parentParam] = parentValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发起请求
|
||||||
|
const response = await request.get(levelConfig.url, { params });
|
||||||
|
const data = response || [];
|
||||||
|
|
||||||
|
// 转换为级联选项格式
|
||||||
|
return data.map((item: any) => {
|
||||||
|
const option: CascadeOption = {
|
||||||
|
label: item[levelConfig.labelField],
|
||||||
|
value: item[levelConfig.valueField]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 判断是否为叶子节点
|
||||||
|
if (levelConfig.isLeaf) {
|
||||||
|
option.isLeaf = levelConfig.isLeaf(item);
|
||||||
|
} else {
|
||||||
|
// 如果没有下一级配置,则为叶子节点
|
||||||
|
option.isLeaf = !hasNextLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return option;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ 加载级联数据失败:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取级联配置的层级数
|
||||||
|
* @param type 级联数据源类型
|
||||||
|
* @returns 层级数
|
||||||
|
*/
|
||||||
|
getLevelCount(type: CascadeDataSourceType): number {
|
||||||
|
const config = getCascadeDataSourceConfig(type);
|
||||||
|
return config?.levels.length || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取级联配置的描述
|
||||||
|
* @param type 级联数据源类型
|
||||||
|
* @returns 描述文本
|
||||||
|
*/
|
||||||
|
getDescription(type: CascadeDataSourceType): string {
|
||||||
|
const config = getCascadeDataSourceConfig(type);
|
||||||
|
return config?.description || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出单例
|
||||||
|
export const cascadeDataSourceService = new CascadeDataSourceService();
|
||||||
|
|
||||||
|
// 向后兼容:导出函数式API
|
||||||
|
export const loadCascadeFirstLevel = (type: CascadeDataSourceType) =>
|
||||||
|
cascadeDataSourceService.loadFirstLevel(type);
|
||||||
|
|
||||||
|
export const loadCascadeChildren = (type: CascadeDataSourceType, selectedOptions: any[]) =>
|
||||||
|
cascadeDataSourceService.loadChildren(type, selectedOptions);
|
||||||
|
|
||||||
@ -4,12 +4,22 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// 类型定义
|
// 类型定义
|
||||||
export { DataSourceType } from './types';
|
export { DataSourceType, CascadeDataSourceType } from './types';
|
||||||
export type { DataSourceOption, DataSourceConfig, DataSourceRegistry } from './types';
|
export type {
|
||||||
|
DataSourceOption,
|
||||||
|
DataSourceConfig,
|
||||||
|
DataSourceRegistry,
|
||||||
|
CascadeOption,
|
||||||
|
CascadeDataSourceConfig,
|
||||||
|
CascadeDataSourceRegistry,
|
||||||
|
CascadeLevelConfig
|
||||||
|
} from './types';
|
||||||
|
|
||||||
// 注册表
|
// 注册表
|
||||||
export { DATA_SOURCE_REGISTRY, getDataSourceConfig, hasDataSource, getAllDataSourceTypes } from './DataSourceRegistry';
|
export { DATA_SOURCE_REGISTRY, getDataSourceConfig, hasDataSource, getAllDataSourceTypes } from './DataSourceRegistry';
|
||||||
|
export { CASCADE_DATA_SOURCE_REGISTRY, getCascadeDataSourceConfig, hasCascadeDataSource, getAllCascadeDataSourceTypes } from './CascadeDataSourceRegistry';
|
||||||
|
|
||||||
// 服务
|
// 服务
|
||||||
export { dataSourceService, loadDataSource, loadMultipleDataSources } from './DataSourceService';
|
export { dataSourceService, loadDataSource, loadMultipleDataSources } from './DataSourceService';
|
||||||
|
export { cascadeDataSourceService, loadCascadeFirstLevel, loadCascadeChildren } from './CascadeDataSourceService';
|
||||||
|
|
||||||
|
|||||||
96
frontend/src/domain/dataSource/presets/cascade.ts
Normal file
96
frontend/src/domain/dataSource/presets/cascade.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
/**
|
||||||
|
* 级联数据源预设配置
|
||||||
|
*/
|
||||||
|
import type { CascadeDataSourceConfig } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 环境 → 项目
|
||||||
|
*/
|
||||||
|
export const environmentProjectsConfig: CascadeDataSourceConfig = {
|
||||||
|
description: '环境 → 项目',
|
||||||
|
levels: [
|
||||||
|
{
|
||||||
|
url: '/api/v1/environment/list',
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'id'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/api/v1/deployment-config/list',
|
||||||
|
parentParam: 'environmentId',
|
||||||
|
labelField: 'applicationName',
|
||||||
|
valueField: 'id',
|
||||||
|
isLeaf: () => true // 第二级是叶子节点
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jenkins 服务器 → View → Job
|
||||||
|
*/
|
||||||
|
export const jenkinsServerViewsJobsConfig: CascadeDataSourceConfig = {
|
||||||
|
description: 'Jenkins 服务器 → View → Job',
|
||||||
|
levels: [
|
||||||
|
{
|
||||||
|
url: '/api/v1/external-system/list',
|
||||||
|
params: { type: 'JENKINS' },
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'id'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/api/v1/jenkins/views',
|
||||||
|
parentParam: 'externalSystemId',
|
||||||
|
labelField: 'viewName',
|
||||||
|
valueField: 'id'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/api/v1/jenkins/jobs',
|
||||||
|
parentParam: 'viewId',
|
||||||
|
labelField: 'jobName',
|
||||||
|
valueField: 'id',
|
||||||
|
isLeaf: () => true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门 → 用户
|
||||||
|
*/
|
||||||
|
export const departmentUsersConfig: CascadeDataSourceConfig = {
|
||||||
|
description: '部门 → 用户',
|
||||||
|
levels: [
|
||||||
|
{
|
||||||
|
url: '/api/v1/department/list',
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'code'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/api/v1/user/list',
|
||||||
|
parentParam: 'departmentCode',
|
||||||
|
labelField: 'nickname',
|
||||||
|
valueField: 'username',
|
||||||
|
isLeaf: () => true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目组 → 应用
|
||||||
|
*/
|
||||||
|
export const projectGroupAppsConfig: CascadeDataSourceConfig = {
|
||||||
|
description: '项目组 → 应用',
|
||||||
|
levels: [
|
||||||
|
{
|
||||||
|
url: '/api/v1/project-group/list',
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'id'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/api/v1/application/list',
|
||||||
|
parentParam: 'projectGroupId',
|
||||||
|
labelField: 'appName',
|
||||||
|
valueField: 'id',
|
||||||
|
isLeaf: () => true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
@ -17,6 +17,16 @@ export enum DataSourceType {
|
|||||||
DEPARTMENTS = 'DEPARTMENTS'
|
DEPARTMENTS = 'DEPARTMENTS'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 级联数据源类型枚举
|
||||||
|
*/
|
||||||
|
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' // 项目组 → 应用
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据源选项接口
|
* 数据源选项接口
|
||||||
*/
|
*/
|
||||||
@ -26,6 +36,15 @@ export interface DataSourceOption {
|
|||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 级联选项接口(支持 children)
|
||||||
|
*/
|
||||||
|
export interface CascadeOption extends DataSourceOption {
|
||||||
|
children?: CascadeOption[];
|
||||||
|
isLeaf?: boolean; // 是否为叶子节点
|
||||||
|
loading?: boolean; // 加载状态
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据源配置接口
|
* 数据源配置接口
|
||||||
*/
|
*/
|
||||||
@ -35,8 +54,33 @@ export interface DataSourceConfig {
|
|||||||
transform: (data: any) => DataSourceOption[];
|
transform: (data: any) => DataSourceOption[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 级联层级配置
|
||||||
|
*/
|
||||||
|
export interface CascadeLevelConfig {
|
||||||
|
url: string; // 接口地址
|
||||||
|
labelField: string; // 标签字段
|
||||||
|
valueField: string; // 值字段
|
||||||
|
parentParam?: string; // 父级参数名(如 'environmentId')
|
||||||
|
params?: Record<string, any>; // 额外参数
|
||||||
|
isLeaf?: (item: any) => boolean; // 判断是否为叶子节点
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 级联数据源配置接口
|
||||||
|
*/
|
||||||
|
export interface CascadeDataSourceConfig {
|
||||||
|
levels: CascadeLevelConfig[]; // 级联层级配置
|
||||||
|
description?: string; // 描述
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据源注册表类型
|
* 数据源注册表类型
|
||||||
*/
|
*/
|
||||||
export type DataSourceRegistry = Record<DataSourceType, DataSourceConfig>;
|
export type DataSourceRegistry = Record<DataSourceType, DataSourceConfig>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 级联数据源注册表类型
|
||||||
|
*/
|
||||||
|
export type CascadeDataSourceRegistry = Record<CascadeDataSourceType, CascadeDataSourceConfig>;
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,262 @@
|
|||||||
|
/**
|
||||||
|
* 级联选项可视化编辑器
|
||||||
|
* 提供树形界面来编辑级联数据
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Button, Input, Space, Popconfirm, Typography, Tooltip } from 'antd';
|
||||||
|
import { PlusOutlined, DeleteOutlined, EditOutlined, PlusCircleOutlined } from '@ant-design/icons';
|
||||||
|
import type { CascadeFieldOption } from '../types';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
interface CascadeOptionEditorProps {
|
||||||
|
value?: CascadeFieldOption[];
|
||||||
|
onChange?: (value: CascadeFieldOption[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OptionItemProps {
|
||||||
|
option: CascadeFieldOption;
|
||||||
|
level: number;
|
||||||
|
onUpdate: (option: CascadeFieldOption) => void;
|
||||||
|
onDelete: () => void;
|
||||||
|
onAddChild: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单个选项编辑项
|
||||||
|
*/
|
||||||
|
const OptionItem: React.FC<OptionItemProps> = ({ option, level, onUpdate, onDelete, onAddChild }) => {
|
||||||
|
const [editing, setEditing] = useState(false);
|
||||||
|
const [label, setLabel] = useState(option.label);
|
||||||
|
const [value, setValue] = useState(option.value);
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
onUpdate({ ...option, label, value: value || label });
|
||||||
|
setEditing(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setLabel(option.label);
|
||||||
|
setValue(option.value);
|
||||||
|
setEditing(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const paddingLeft = level * 24;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ marginBottom: 8 }}>
|
||||||
|
{/* 当前选项 */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '8px 12px',
|
||||||
|
backgroundColor: level % 2 === 0 ? '#fafafa' : '#f5f5f5',
|
||||||
|
borderRadius: 4,
|
||||||
|
paddingLeft: paddingLeft + 12,
|
||||||
|
border: '1px solid #e8e8e8',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{editing ? (
|
||||||
|
<>
|
||||||
|
<Input
|
||||||
|
size="small"
|
||||||
|
placeholder="标签"
|
||||||
|
value={label}
|
||||||
|
onChange={(e) => setLabel(e.target.value)}
|
||||||
|
style={{ width: 120, marginRight: 8 }}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
size="small"
|
||||||
|
placeholder="值(可选)"
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => setValue(e.target.value)}
|
||||||
|
style={{ width: 120, marginRight: 8 }}
|
||||||
|
/>
|
||||||
|
<Button size="small" type="primary" onClick={handleSave} style={{ marginRight: 4 }}>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
<Button size="small" onClick={handleCancel}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div style={{ flex: 1, display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
|
<Text strong>{option.label}</Text>
|
||||||
|
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||||
|
({option.value})
|
||||||
|
</Text>
|
||||||
|
{option.children && option.children.length > 0 && (
|
||||||
|
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||||
|
[{option.children.length} 个子项]
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Space size="small">
|
||||||
|
<Tooltip title="添加子选项">
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="text"
|
||||||
|
icon={<PlusCircleOutlined />}
|
||||||
|
onClick={onAddChild}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="编辑">
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="text"
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
onClick={() => setEditing(true)}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Popconfirm
|
||||||
|
title="确定删除此选项吗?"
|
||||||
|
description={option.children?.length ? '将同时删除所有子选项' : undefined}
|
||||||
|
onConfirm={onDelete}
|
||||||
|
okText="确定"
|
||||||
|
cancelText="取消"
|
||||||
|
>
|
||||||
|
<Tooltip title="删除">
|
||||||
|
<Button size="small" type="text" danger icon={<DeleteOutlined />} />
|
||||||
|
</Tooltip>
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 子选项 */}
|
||||||
|
{option.children && option.children.length > 0 && (
|
||||||
|
<div style={{ marginTop: 4 }}>
|
||||||
|
{option.children.map((child, index) => (
|
||||||
|
<OptionItem
|
||||||
|
key={index}
|
||||||
|
option={child}
|
||||||
|
level={level + 1}
|
||||||
|
onUpdate={(updatedChild) => {
|
||||||
|
const newChildren = [...(option.children || [])];
|
||||||
|
newChildren[index] = updatedChild;
|
||||||
|
onUpdate({ ...option, children: newChildren });
|
||||||
|
}}
|
||||||
|
onDelete={() => {
|
||||||
|
const newChildren = option.children?.filter((_, i) => i !== index) || [];
|
||||||
|
onUpdate({ ...option, children: newChildren.length > 0 ? newChildren : undefined });
|
||||||
|
}}
|
||||||
|
onAddChild={() => {
|
||||||
|
const newChild: CascadeFieldOption = {
|
||||||
|
label: '新选项',
|
||||||
|
value: `option_${Date.now()}`,
|
||||||
|
};
|
||||||
|
const newChildren = [...(child.children || []), newChild];
|
||||||
|
const updatedChild = { ...child, children: newChildren };
|
||||||
|
const newChildren2 = [...(option.children || [])];
|
||||||
|
newChildren2[index] = updatedChild;
|
||||||
|
onUpdate({ ...option, children: newChildren2 });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 级联选项编辑器主组件
|
||||||
|
*/
|
||||||
|
const CascadeOptionEditor: React.FC<CascadeOptionEditorProps> = ({ value = [], onChange }) => {
|
||||||
|
const handleAddOption = () => {
|
||||||
|
const newOption: CascadeFieldOption = {
|
||||||
|
label: '新选项',
|
||||||
|
value: `option_${Date.now()}`,
|
||||||
|
};
|
||||||
|
onChange?.([...value, newOption]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdateOption = (index: number, updatedOption: CascadeFieldOption) => {
|
||||||
|
const newOptions = [...value];
|
||||||
|
newOptions[index] = updatedOption;
|
||||||
|
onChange?.(newOptions);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteOption = (index: number) => {
|
||||||
|
const newOptions = value.filter((_, i) => i !== index);
|
||||||
|
onChange?.(newOptions);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddChild = (index: number) => {
|
||||||
|
const newChild: CascadeFieldOption = {
|
||||||
|
label: '新子选项',
|
||||||
|
value: `child_${Date.now()}`,
|
||||||
|
};
|
||||||
|
const newOptions = [...value];
|
||||||
|
const currentOption = newOptions[index];
|
||||||
|
newOptions[index] = {
|
||||||
|
...currentOption,
|
||||||
|
children: [...(currentOption.children || []), newChild],
|
||||||
|
};
|
||||||
|
onChange?.(newOptions);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<Button type="dashed" icon={<PlusOutlined />} onClick={handleAddOption} block>
|
||||||
|
添加顶级选项
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{value.length === 0 ? (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: 24,
|
||||||
|
textAlign: 'center',
|
||||||
|
color: '#8c8c8c',
|
||||||
|
backgroundColor: '#fafafa',
|
||||||
|
borderRadius: 4,
|
||||||
|
border: '1px dashed #d9d9d9',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
暂无选项,点击上方按钮添加
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
{value.map((option, index) => (
|
||||||
|
<OptionItem
|
||||||
|
key={index}
|
||||||
|
option={option}
|
||||||
|
level={0}
|
||||||
|
onUpdate={(updatedOption) => handleUpdateOption(index, updatedOption)}
|
||||||
|
onDelete={() => handleDeleteOption(index)}
|
||||||
|
onAddChild={() => handleAddChild(index)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: 12,
|
||||||
|
padding: 12,
|
||||||
|
background: '#f5f5f5',
|
||||||
|
borderRadius: 4,
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#666',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<strong>💡 使用提示:</strong>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: 4 }}>1. 点击"添加顶级选项"创建第一级选项</div>
|
||||||
|
<div>2. 点击 <PlusCircleOutlined /> 图标为选项添加子级</div>
|
||||||
|
<div>3. 支持多级嵌套,构建完整的级联结构</div>
|
||||||
|
<div>4. 值(value)留空时将自动使用标签(label)作为值</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CascadeOptionEditor;
|
||||||
|
|
||||||
@ -26,6 +26,7 @@ import { UploadOutlined } from '@ant-design/icons';
|
|||||||
import type { FieldConfig } from '../types';
|
import type { FieldConfig } from '../types';
|
||||||
import GridField from './GridField';
|
import GridField from './GridField';
|
||||||
import { useFieldOptions } from '../hooks/useFieldOptions';
|
import { useFieldOptions } from '../hooks/useFieldOptions';
|
||||||
|
import { useCascaderOptions, useCascaderLoadData } from '../hooks/useCascaderOptions';
|
||||||
import '../styles.css';
|
import '../styles.css';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
@ -56,6 +57,10 @@ const FieldRenderer: React.FC<FieldRendererProps> = ({
|
|||||||
// 获取字段选项(支持静态和动态数据源)
|
// 获取字段选项(支持静态和动态数据源)
|
||||||
const options = useFieldOptions(field);
|
const options = useFieldOptions(field);
|
||||||
|
|
||||||
|
// 获取级联选择器选项和懒加载函数
|
||||||
|
const cascadeOptions = useCascaderOptions(field);
|
||||||
|
const loadData = useCascaderLoadData(field);
|
||||||
|
|
||||||
// 布局组件特殊处理
|
// 布局组件特殊处理
|
||||||
if (field.type === 'divider') {
|
if (field.type === 'divider') {
|
||||||
return <Divider>{field.label}</Divider>;
|
return <Divider>{field.label}</Divider>;
|
||||||
@ -229,9 +234,11 @@ const FieldRenderer: React.FC<FieldRendererProps> = ({
|
|||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
placeholder={field.placeholder}
|
placeholder={field.placeholder}
|
||||||
disabled={field.disabled}
|
disabled={field.disabled}
|
||||||
options={options as any}
|
options={cascadeOptions as any}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
loadData={loadData as any}
|
||||||
|
changeOnSelect={field.dataSourceType === 'predefined'}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,8 @@ import {
|
|||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons';
|
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||||
import type { FieldConfig, FormConfig } from '../types';
|
import type { FieldConfig, FormConfig } from '../types';
|
||||||
import { DataSourceType } from '@/domain/dataSource';
|
import { DataSourceType, CascadeDataSourceType } from '@/domain/dataSource';
|
||||||
|
import CascadeOptionEditor from './CascadeOptionEditor';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
const { TabPane } = Tabs;
|
const { TabPane } = Tabs;
|
||||||
@ -42,7 +43,7 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (selectedField) {
|
if (selectedField) {
|
||||||
// 确保嵌套对象被正确设置到表单
|
// 确保嵌套对象被正确设置到表单
|
||||||
const formValues = {
|
const formValues: any = {
|
||||||
...selectedField,
|
...selectedField,
|
||||||
apiDataSource: selectedField.apiDataSource || {
|
apiDataSource: selectedField.apiDataSource || {
|
||||||
url: '',
|
url: '',
|
||||||
@ -54,7 +55,11 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
|||||||
predefinedDataSource: selectedField.predefinedDataSource || {
|
predefinedDataSource: selectedField.predefinedDataSource || {
|
||||||
sourceType: '',
|
sourceType: '',
|
||||||
},
|
},
|
||||||
|
predefinedCascadeDataSource: selectedField.predefinedCascadeDataSource || {
|
||||||
|
sourceType: '',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
form.setFieldsValue(formValues);
|
form.setFieldsValue(formValues);
|
||||||
}
|
}
|
||||||
}, [selectedField, form]);
|
}, [selectedField, form]);
|
||||||
@ -107,9 +112,21 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
|||||||
console.log('💾 预定义数据源配置已更新:', updatedField.predefinedDataSource);
|
console.log('💾 预定义数据源配置已更新:', updatedField.predefinedDataSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 合并其他字段
|
// 如果改变的是 predefinedCascadeDataSource 的子字段
|
||||||
|
if ('predefinedCascadeDataSource' in changedValues) {
|
||||||
|
updatedField.predefinedCascadeDataSource = {
|
||||||
|
...updatedField.predefinedCascadeDataSource,
|
||||||
|
...changedValues.predefinedCascadeDataSource,
|
||||||
|
};
|
||||||
|
console.log('💾 预定义级联数据源配置已更新:', updatedField.predefinedCascadeDataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并其他字段(cascadeOptions 现在直接作为对象处理,不需要特殊处理)
|
||||||
Object.keys(changedValues).forEach(key => {
|
Object.keys(changedValues).forEach(key => {
|
||||||
if (key !== 'dataSourceType' && key !== 'apiDataSource' && key !== 'predefinedDataSource') {
|
if (key !== 'dataSourceType' &&
|
||||||
|
key !== 'apiDataSource' &&
|
||||||
|
key !== 'predefinedDataSource' &&
|
||||||
|
key !== 'predefinedCascadeDataSource') {
|
||||||
(updatedField as any)[key] = changedValues[key];
|
(updatedField as any)[key] = changedValues[key];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -155,7 +172,8 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasOptions = ['select', 'radio', 'checkbox', 'cascader'].includes(selectedField.type);
|
const hasOptions = ['select', 'radio', 'checkbox'].includes(selectedField.type);
|
||||||
|
const isCascader = selectedField.type === 'cascader';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
@ -466,6 +484,73 @@ const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 级联选择器专用配置 */}
|
||||||
|
{isCascader && (
|
||||||
|
<div>
|
||||||
|
<Divider style={{ margin: '16px 0' }} />
|
||||||
|
|
||||||
|
{/* 数据源类型选择(级联选择器只支持静态和预定义) */}
|
||||||
|
<Form.Item label="数据源类型" name="dataSourceType">
|
||||||
|
<Radio.Group buttonStyle="solid" style={{ display: 'flex', flexWrap: 'nowrap' }}>
|
||||||
|
<Radio.Button value="static" style={{ flex: 1, textAlign: 'center' }}>静态数据</Radio.Button>
|
||||||
|
<Radio.Button value="predefined" style={{ flex: 1, textAlign: 'center' }}>预定义</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
{/* 静态级联数据配置 */}
|
||||||
|
{selectedField.dataSourceType === 'static' && (
|
||||||
|
<div>
|
||||||
|
<Text strong>级联选项配置</Text>
|
||||||
|
<div style={{ marginTop: 12 }}>
|
||||||
|
<Form.Item
|
||||||
|
name="cascadeOptions"
|
||||||
|
tooltip="可视化配置树形结构的级联选项"
|
||||||
|
>
|
||||||
|
<CascadeOptionEditor />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 预定义级联数据源配置 */}
|
||||||
|
{selectedField.dataSourceType === 'predefined' && (
|
||||||
|
<div>
|
||||||
|
<Text strong>预定义级联数据源</Text>
|
||||||
|
<div style={{ marginTop: 12 }}>
|
||||||
|
<Form.Item
|
||||||
|
label="数据源"
|
||||||
|
name={['predefinedCascadeDataSource', 'sourceType']}
|
||||||
|
rules={[{ required: true, message: '请选择级联数据源' }]}
|
||||||
|
>
|
||||||
|
<Select placeholder="选择预定义级联数据源">
|
||||||
|
{Object.keys(CascadeDataSourceType).map((key) => {
|
||||||
|
const sourceType = CascadeDataSourceType[key as keyof typeof CascadeDataSourceType];
|
||||||
|
return (
|
||||||
|
<Select.Option key={sourceType} value={sourceType}>
|
||||||
|
{key.replace(/_/g, ' → ')}
|
||||||
|
</Select.Option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
padding: 12,
|
||||||
|
background: '#f5f5f5',
|
||||||
|
borderRadius: 4,
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#666'
|
||||||
|
}}>
|
||||||
|
<div><strong>💡 提示:</strong></div>
|
||||||
|
<div style={{ marginTop: 4 }}>使用系统预定义的级联数据源,支持动态懒加载</div>
|
||||||
|
<div style={{ marginTop: 4 }}>例如:环境 → 项目、部门 → 用户等</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -205,12 +205,24 @@ export const COMPONENT_LIST: ComponentMeta[] = [
|
|||||||
defaultConfig: {
|
defaultConfig: {
|
||||||
placeholder: '请选择',
|
placeholder: '请选择',
|
||||||
dataSourceType: 'static',
|
dataSourceType: 'static',
|
||||||
options: [
|
cascadeOptions: [
|
||||||
{
|
{
|
||||||
label: '浙江',
|
label: '浙江',
|
||||||
value: 'zhejiang',
|
value: 'zhejiang',
|
||||||
|
children: [
|
||||||
|
{ label: '杭州', value: 'hangzhou' },
|
||||||
|
{ label: '宁波', value: 'ningbo' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
] as any,
|
{
|
||||||
|
label: '江苏',
|
||||||
|
value: 'jiangsu',
|
||||||
|
children: [
|
||||||
|
{ label: '南京', value: 'nanjing' },
|
||||||
|
{ label: '苏州', value: 'suzhou' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
110
frontend/src/pages/FormDesigner/hooks/useCascaderOptions.ts
Normal file
110
frontend/src/pages/FormDesigner/hooks/useCascaderOptions.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
* 级联选择器选项 Hook
|
||||||
|
* 支持静态数据和预定义级联数据源
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
|
import type { FieldConfig, CascadeFieldOption } from '../types';
|
||||||
|
import {
|
||||||
|
cascadeDataSourceService,
|
||||||
|
CascadeDataSourceType,
|
||||||
|
type CascadeOption
|
||||||
|
} from '@/domain/dataSource';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换 CascadeOption 为 CascadeFieldOption
|
||||||
|
*/
|
||||||
|
const convertToCascadeFieldOption = (options: CascadeOption[]): CascadeFieldOption[] => {
|
||||||
|
return options.map(opt => ({
|
||||||
|
label: opt.label,
|
||||||
|
value: opt.value,
|
||||||
|
isLeaf: opt.isLeaf,
|
||||||
|
children: opt.children ? convertToCascadeFieldOption(opt.children) : undefined
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用级联选择器选项
|
||||||
|
* @param field 字段配置
|
||||||
|
* @returns 级联选项列表
|
||||||
|
*/
|
||||||
|
export const useCascaderOptions = (field: FieldConfig): CascadeFieldOption[] => {
|
||||||
|
const [options, setOptions] = useState<CascadeFieldOption[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 如果是静态数据源,直接使用配置的选项
|
||||||
|
if (field.dataSourceType === 'static' && field.cascadeOptions) {
|
||||||
|
console.log('📊 使用静态级联数据:', field.cascadeOptions);
|
||||||
|
setOptions(field.cascadeOptions);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是预定义级联数据源
|
||||||
|
if (field.dataSourceType === 'predefined' && field.predefinedCascadeDataSource?.sourceType) {
|
||||||
|
const sourceType = field.predefinedCascadeDataSource.sourceType as CascadeDataSourceType;
|
||||||
|
|
||||||
|
console.log(`📡 加载预定义级联数据源: ${sourceType}`);
|
||||||
|
|
||||||
|
// 加载第一级数据
|
||||||
|
cascadeDataSourceService
|
||||||
|
.loadFirstLevel(sourceType)
|
||||||
|
.then(data => {
|
||||||
|
console.log(`✅ 预定义级联数据源 ${sourceType} 第一级加载成功:`, data);
|
||||||
|
setOptions(convertToCascadeFieldOption(data));
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(`❌ 加载预定义级联数据源 ${sourceType} 失败:`, error);
|
||||||
|
setOptions([]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [field.dataSourceType, field.cascadeOptions, field.predefinedCascadeDataSource]);
|
||||||
|
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 懒加载级联子选项
|
||||||
|
* 用于动态加载级联选择器的子级数据
|
||||||
|
*/
|
||||||
|
export const useCascaderLoadData = (field: FieldConfig) => {
|
||||||
|
// 获取数据源类型(如果不是预定义类型,则为 null)
|
||||||
|
const sourceType = field.dataSourceType === 'predefined' && field.predefinedCascadeDataSource?.sourceType
|
||||||
|
? (field.predefinedCascadeDataSource.sourceType as CascadeDataSourceType)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// 始终调用 useCallback,但在内部处理条件逻辑
|
||||||
|
const loadData = useCallback(
|
||||||
|
async (selectedOptions: any[]) => {
|
||||||
|
// 如果不是预定义数据源,直接返回
|
||||||
|
if (!sourceType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`🔄 懒加载级联数据: ${sourceType}, 已选择:`, selectedOptions);
|
||||||
|
|
||||||
|
// 提取已选择的值
|
||||||
|
const selectedValues = selectedOptions.map(opt => opt.value);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 加载子级数据
|
||||||
|
const childrenData = await cascadeDataSourceService.loadChildren(sourceType, selectedValues);
|
||||||
|
console.log(`✅ 懒加载成功,子级数据:`, childrenData);
|
||||||
|
|
||||||
|
// 更新最后一个选项的 children
|
||||||
|
const targetOption = selectedOptions[selectedOptions.length - 1];
|
||||||
|
targetOption.loading = false;
|
||||||
|
targetOption.children = convertToCascadeFieldOption(childrenData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ 懒加载失败:`, error);
|
||||||
|
const targetOption = selectedOptions[selectedOptions.length - 1];
|
||||||
|
targetOption.loading = false;
|
||||||
|
targetOption.children = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[sourceType]
|
||||||
|
);
|
||||||
|
|
||||||
|
// 只有预定义级联数据源才返回懒加载函数,否则返回 undefined
|
||||||
|
return sourceType ? loadData : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
@ -28,6 +28,14 @@ export interface FieldOption {
|
|||||||
value: string | number;
|
value: string | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 级联选项类型(用于 cascader,支持 children)
|
||||||
|
export interface CascadeFieldOption {
|
||||||
|
label: string;
|
||||||
|
value: string | number;
|
||||||
|
children?: CascadeFieldOption[];
|
||||||
|
isLeaf?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
// 数据源类型
|
// 数据源类型
|
||||||
export type DataSourceType = 'static' | 'api' | 'predefined';
|
export type DataSourceType = 'static' | 'api' | 'predefined';
|
||||||
|
|
||||||
@ -46,6 +54,11 @@ export interface PredefinedDataSource {
|
|||||||
sourceType: string; // 数据源类型,对应 DataSourceType 枚举(如 'JENKINS_SERVERS')
|
sourceType: string; // 数据源类型,对应 DataSourceType 枚举(如 'JENKINS_SERVERS')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 预定义级联数据源配置
|
||||||
|
export interface PredefinedCascadeDataSource {
|
||||||
|
sourceType: string; // 级联数据源类型,对应 CascadeDataSourceType 枚举(如 'ENVIRONMENT_PROJECTS')
|
||||||
|
}
|
||||||
|
|
||||||
// 字段配置
|
// 字段配置
|
||||||
export interface FieldConfig {
|
export interface FieldConfig {
|
||||||
id: string;
|
id: string;
|
||||||
@ -56,10 +69,12 @@ export interface FieldConfig {
|
|||||||
required?: boolean;
|
required?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
defaultValue?: any;
|
defaultValue?: any;
|
||||||
options?: FieldOption[]; // 静态选项数据
|
options?: FieldOption[]; // 静态选项数据(select, radio, checkbox 使用)
|
||||||
|
cascadeOptions?: CascadeFieldOption[]; // 静态级联选项数据(cascader 静态模式使用)
|
||||||
dataSourceType?: DataSourceType; // 数据源类型:static(静态)、api(接口)或 predefined(预定义)
|
dataSourceType?: DataSourceType; // 数据源类型:static(静态)、api(接口)或 predefined(预定义)
|
||||||
apiDataSource?: ApiDataSource; // API 数据源配置(当 dataSourceType 为 'api' 时使用)
|
apiDataSource?: ApiDataSource; // API 数据源配置(当 dataSourceType 为 'api' 时使用)
|
||||||
predefinedDataSource?: PredefinedDataSource; // 预定义数据源配置(当 dataSourceType 为 'predefined' 时使用)
|
predefinedDataSource?: PredefinedDataSource; // 预定义数据源配置(当 dataSourceType 为 'predefined' 时使用)
|
||||||
|
predefinedCascadeDataSource?: PredefinedCascadeDataSource; // 预定义级联数据源配置(cascader 使用)
|
||||||
min?: number;
|
min?: number;
|
||||||
max?: number;
|
max?: number;
|
||||||
rows?: number;
|
rows?: number;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user