feat(component): 实现组件开发流程的新建组件,接口设计,组件编辑

- 新增组件接口设计功能,支持新增和编辑接口
- 实现组件基本信息的增删改查功能
- 添加组件状态、发布状态、公开状态的管理
- 支持组件删除功能,增加确认提示
- 实现组件编辑时的数据回显和表单重置
- 新增API接口设计弹窗,支持参数配置
- 添加数据类型字典和组件类型映射
- 实现组件列表的分页和筛选功能
- 增加工具函数:对象类型判断和深拷贝方法
master
钟良源 3 months ago
parent c986114e75
commit 2d3bb0f7f3

@ -19,3 +19,7 @@ export function compProjectValidate(projectId) {
export function compSubmit(params) { export function compSubmit(params) {
return axios.post(`${urlPrefix}/componentBase/submit`, params); return axios.post(`${urlPrefix}/componentBase/submit`, params);
} }
export const remove = (ids) => {
return axios.post(`${urlPrefix}/componentBase/remove?ids=${ids}`);
};

@ -0,0 +1,14 @@
import axios from 'axios';
// 公共路径
const urlPrefix = '/api/v1/bpms-workbench';
// 获取组件设计
export function getComponentDesign(componentBaseId) {
return axios.get(`${urlPrefix}/componentDevelopProcess/design?componentBaseId=${componentBaseId}`);
}
// 更新组件设计
export function updateComponentDesign(params) {
return axios.post(`${urlPrefix}/componentDevelopProcess/design`, params);
}

@ -0,0 +1,13 @@
export const globalOption = {
border: false,
align: 'left',
menuHeaderAlign: 'left',
size: 'mini',
refreshBtn: false,
filterBtn: false,
columnBtn: false,
searchShowBtn: false,
// labelPosition: "left"
};
export const TrimReg = /^[^\s]+$/;
export const patternTrim = { pattern: TrimReg, message: '不能输入空格' };

@ -0,0 +1,541 @@
import { globalOption, patternTrim } from '../globalOption';
// 组件公开状态码
export const publishStatusConstant = {
// 未公开
UNPUBLISHED: -1,
// 已公开
PUBLISHED: 1,
// 已撤销
REVOKED: 0,
// 审批中
APPROVAL: 2
};
export const publishStatusDict = [
{
label: '未公开',
value: publishStatusConstant.UNPUBLISHED
},
{
label: '未公开',
value: publishStatusConstant.REVOKED
},
{
label: '已公开',
value: publishStatusConstant.PUBLISHED
},
{
label: '审批中',
value: publishStatusConstant.APPROVAL
}
];
// 组价发布的状态码
export const publicStatus = {
// 未发布
UNPUBLISHED: -1,
UNPUBLISHED1: 0,
// 已发布
PUBLISHED: 1,
// 审核中
REVIEW: 2,
// 审核被拒
REJECTED: 3
};
export const publicStatusDict = [
{
label: '未发布',
value: publicStatus.UNPUBLISHED
},
{
label: '未发布',
value: publicStatus.UNPUBLISHED1
},
{
label: '已发布',
value: publicStatus.PUBLISHED
},
{
label: '审核中',
value: publicStatus.REVIEW
},
{
label: '审核被拒',
value: publicStatus.REJECTED
}
];
// 组件基本信息状态字段
export const componentStatusConstant = {
// 未设计
DEFAULT: 'default',
// 设计中
DESIGN: 'design',
// 编码中
CODING: 'coding',
// 已部署
DEPLOYED: 'deployed',
// 已公开
PUBLISHED: 'published'
};
// 组件所处阶段状态
export const componentStatusDict = [
{
label: '未设计',
value: componentStatusConstant.DEFAULT
},
{
label: '设计中',
value: componentStatusConstant.DESIGN
},
{
label: '编码中',
value: componentStatusConstant.CODING
},
{
label: '已部署',
value: componentStatusConstant.DEPLOYED
},
{
label: '已公开',
value: componentStatusConstant.PUBLISHED
}
];
// 组件所处阶段状态
export const componentStatusMap = componentStatusDict.reduce((obj, item) => {
obj[item.value] = item.label;
return obj;
}, {});
// 组件类型字典映射
export const componentTypeDict = [
{
dictKey: '普通组件',
dictValue: 'normal'
},
{
dictKey: '监听组件',
dictValue: 'loop'
}
];
// 表格参数
export const option = {
...globalOption,
height: 'auto',
calcHeight: 30,
tip: false,
searchShow: true,
searchMenuSpan: 6,
viewBtn: true,
delBtn: false,
selection: false,
dialogClickModal: false,
labelWidth: '100',
menuWidth: 500,
menuHeaderAlign: 'center',
header: true,
addBtn: true,
editBtn: false,
updateBtnText: '保 存',
addBtnText: '新增组件',
addBtnIcon: 'none',
column: [
{
label: '组件名称',
prop: 'name',
type: 'input',
width: 150,
placeholder: '请输入组件名称',
rules: [
{
required: true,
message: '组件名称不能为空',
trigger: 'blur'
},
{
pattern: /^[\u4e00-\u9fa5a-zA-Z0-9_-]+$/,
message: '组件名称只能为中文、字母、数字、"-"、"_"组成'
},
patternTrim,
{ max: 50, message: '组件名称最长为50字符' }
]
},
{
label: '组件标识',
prop: 'projectId',
type: 'input',
width: 200,
overHidden: true,
placeholder: '请输入组件标识',
editDisabled: true,
rules: [
{ required: true, message: '组件标识不能为空' },
{
pattern: /^([a-z][a-z0-9]*[_-]?)+$/,
message: '组件标识由小写字母、数字、"-"、"_"组成,以小写字母开头,且“-”、"_"后第一个字符需为小写字母'
},
{ max: 30, message: '组件标识最长为30个字符' },
patternTrim
]
},
{
label: '分类',
prop: 'componentClassify',
type: 'select',
placeholder: '请选择分类',
width: 200,
dicUrl: '/api/blade-system/dict-biz/dictionary-tree?code=component_classify',
props: {
label: 'dictValue',
value: 'dictKey'
},
rules: [{ required: true, message: '分类不能为空' }]
},
{
label: '创建人',
prop: 'createUserName',
type: 'input',
display: false,
overHidden: true,
editDisabled: false,
width: 180,
hide: false
},
{
label: '代码语言',
prop: 'codeLanguage',
type: 'select',
dicUrl: '/api/blade-system/dict/dictionary?code=language_type',
value: 'Java',
editDisabled: true,
width: 150,
placeholder: '请选择代码语言',
props: {
label: 'dictValue',
value: 'dictKey'
},
rules: [{ required: true, message: '代码语言不能为空' }]
},
{
label: '标签',
prop: 'tags',
type: 'select',
remote: true,
multiple: true,
allowCreate: true,
filterable: true,
placeholder: '请选择标签',
width: 200,
dicUrl: '/api/blade-isdp/componentBase/tags',
hide: true
},
{
label: '组件类型',
prop: 'type',
type: 'select',
dicData: componentTypeDict,
value: 'normal',
editDisabled: true,
width: 150,
placeholder: '请选择组件类型',
props: {
label: 'dictKey',
value: 'dictValue'
},
rules: [{ required: true, message: '组件类型不能为空' }]
},
{
label: '图标',
prop: 'logoUrl',
type: 'upload',
hide: true,
listType: 'picture-card',
action: '/api/blade-isdp/fileSystem/fileUpload',
propsHttp: {
res: 'data',
url: 'link'
},
fileType: 'img',
limit: 1,
fileSize: 1024,
accept: 'image/png, image/jpeg, image/jpg',
tip: '图片尺寸不超过300*300像素大小限制为1mb以内'
},
{
label: '描述',
prop: 'desc',
type: 'textarea',
overHidden: true,
placeholder: '请输入描述',
formSlot: true,
hide: true,
minRows: 2,
maxLen: 100,
span: 24
},
{
label: '组件状态',
prop: 'componentStatus',
type: 'input',
display: false,
width: 100,
dicData: componentStatusDict
},
{
label: '创建部门',
prop: 'createDept',
type: 'input',
display: false,
hide: true
},
{
label: '创建时间',
prop: 'createTime',
type: 'input',
hide: true,
display: false
},
{
label: '发布状态',
prop: 'publicStatus',
type: 'input',
width: 90,
dicData: [
{
label: '未发布',
value: publicStatus.UNPUBLISHED
},
{
label: '未发布',
value: publicStatus.UNPUBLISHED1
},
{
label: '已发布',
value: publicStatus.PUBLISHED
},
{
label: '审核中',
value: publicStatus.REVIEW
},
{
label: '审核被拒',
value: publicStatus.REJECTED
}
],
display: false
},
{
label: '公开状态',
prop: 'publishStatus',
type: 'input',
width: 90,
dicData: [
{
label: '未公开',
value: publishStatusConstant.UNPUBLISHED
},
{
label: '未公开',
value: publishStatusConstant.REVOKED
},
{
label: '已公开',
value: publishStatusConstant.PUBLISHED
},
{
label: '审批中',
value: publishStatusConstant.APPROVAL
}
],
display: false
},
{
label: '修改人',
prop: 'updateUser',
type: 'input',
display: false,
hide: true
},
{
label: '修改时间',
prop: 'updateTime',
type: 'input',
width: 130,
display: false
},
{
label: '是否已删除',
prop: 'isDeleted',
type: 'input',
display: false,
hide: true
}
]
};
export const operateOption = {
...globalOption,
height: '400',
calcHeight: 30,
tip: false,
searchShow: false,
selection: false,
dialogClickModal: false,
header: false,
delBtn: true,
viewBtn: false,
dialogWidth: '55%',
menuWidth: 100,
saveBtn: false,
cancelBtn: false,
editBtn: true,
editBtnIcon: 'none',
delBtnIcon: 'none',
column: [
{
label: '接口名称',
prop: 'ident',
type: 'input',
placeholder: '请输入名称',
rules: [
{ required: true, message: '名称不能为空' },
{ pattern: /^[a-z][a-zA-Z0-9]*$/, message: '名称符需要为首字母小写的驼峰命名法' },
{ max: 20, message: '名称最长为20个字符' },
patternTrim
]
},
{
label: '描述',
prop: 'desc',
type: 'textarea',
placeholder: '请输入描述',
hide: true,
overHidden: true,
minRows: 2,
rules: [{ max: 200, message: '最长为200个字符' }, patternTrim],
span: 24
},
{
label: '输入参数',
prop: 'parameters',
type: 'input',
span: 24,
formSlot: true,
overHidden: true,
value: [],
slot: true
},
{
label: '输出参数',
prop: 'responses',
type: 'input',
span: 24,
formSlot: true,
overHidden: true,
value: [],
slot: true
}
]
};
export const baseConfigOption = {
...globalOption,
addBtn: false,
addRowBtn: true,
menu: true,
cellBtn: false,
editBtn: false,
delBtn: false,
dialogWidth: '40%',
menuWidth: '80',
column: [
{
label: '启动项名称',
prop: 'name',
placeholder: '启动项名称',
width: '150px',
rules: [{ required: true, message: '启动项名称不能为空' }, {
max: 50,
message: '启动项名称最长为50字符'
}, patternTrim],
cell: true,
span: 24
},
{
label: '参数名key',
prop: 'key',
input: 'input',
width: '200px',
rules: [{ required: true, message: '请输入参数名key' }, {
max: 50,
message: '参数名key最长为50字符'
}, patternTrim],
cell: true,
span: 24
},
{
label: '默认值',
prop: 'value',
width: '200px',
span: 24,
cell: true,
rules: [{ max: 200, message: '默认值最长为200字符' }, patternTrim]
},
{
label: '描述说明',
prop: 'desc',
overHidden: true,
span: 24,
cell: true,
rules: [{ max: 200, message: '描述说明最长为200字符' }, patternTrim]
}
]
};
export const baseDisplayToEntity = (row) => {
row.desc = row.desc || '';
row.tags = row.tags || [];
row.logoUrl = row.logoUrl || '';
return row;
};
export const baseEntityToDisplay = (row) => {
row.desc = row.desc || '';
row.logoUrl = row.logoUrl || '';
row.tags = row.tags || [];
return row;
};
export const toolbars = {
bold: true, // 粗体
italic: true, // 斜体
header: true, // 标题
underline: true, // 下划线
strikethrough: true, // 中划线
mark: true, // 标记
superscript: true, // 上角标
subscript: true, // 下角标
quote: true, // 引用
ol: true, // 有序列表
ul: true, // 无序列表
link: true, // 链接
imagelink: true, // 图片链接
code: true, // code
table: true, // 表格
fullscreen: true, // 全屏编辑
readmodel: true, // 沉浸式阅读
htmlcode: true, // 展示html源码
help: false, // 帮助
/* 1.3.5 */
undo: true, // 上一步
redo: true, // 下一步
trash: true, // 清空
save: false, // 保存触发events中的save事件
/* 1.4.2 */
navigation: true, // 导航目录
/* 2.1.8 */
alignleft: true, // 左对齐
aligncenter: true, // 居中
alignright: true, // 右对齐
/* 2.2.1 */
subfield: true, // 单双栏模式
preview: true // 预览
};

@ -0,0 +1,562 @@
import { globalOption, patternTrim } from '../globalOption';
/**
*
*/
export const runStatusConstant = {
HEALTHY: 'healthy',
UNKNOWN: 'unknown',
RUN: 'run',
ERROR: 'error',
STOP: 'stop',
};
export const runStatusDic = [
{
label: '可用',
value: runStatusConstant.HEALTHY,
},
{
label: '未知',
value: runStatusConstant.UNKNOWN,
},
{
// 部署 可停止
label: '运行',
value: runStatusConstant.RUN,
},
{
label: '异常',
value: runStatusConstant.ERROR,
},
{
// 部署 可启用
label: '停止',
value: runStatusConstant.STOP,
},
];
export const runStatusMap = runStatusDic.reduce((obj, item) => {
obj[item.value] = item.label;
return obj;
}, {});
/**
*
*/
export const startStatusConstant = {
RUN: 'run',
STOP: 'stop',
};
export const startStatusDic = [
{
label: '启用中',
value: startStatusConstant.RUN,
},
{
label: '已下架',
value: startStatusConstant.STOP,
},
];
export const startStatusMap = startStatusDic.reduce((obj, item) => {
obj[item.value] = item.label;
return obj;
}, {});
/**
*
* BUILT_DOING, BUILT_IGNORE,BUILT_DOCKER,BUILT_FAIL
*/
export const compileStatusConstant = {
BUILT_DOING: 'built-doing',
BUILT_IGNORE: 'built-ignore',
BUILT_DOCKER: 'built-docker',
BUILT_FAIL: 'built-fail',
// 前端单独使用
NOT_COMPILED: 'not-compiled',
get(type) {
return compileStatusConstant[type] || compileStatusConstant.NOT_COMPILED;
},
};
export const compileStatusDic = [
{
label: '编译中',
value: compileStatusConstant.BUILT_DOING,
},
{
label: '免编译',
value: compileStatusConstant.BUILT_IGNORE,
},
{
label: '已编译',
value: compileStatusConstant.BUILT_DOCKER,
},
{
label: '编译失败',
value: compileStatusConstant.BUILT_FAIL,
},
];
export const compileStatusMap = startStatusDic.reduce((obj, item) => {
obj[item.value] = item.label;
return obj;
}, {});
/**
*
*/
export const runTypeConstant = {
LOCAL: 0,
ONLINE: 1,
};
export const runTypeDic = [
{
label: '本地运行',
value: runTypeConstant.LOCAL,
},
{
label: '线上运行',
value: runTypeConstant.ONLINE,
},
];
export const option = {
...globalOption,
height: '300',
calcHeight: 30,
tip: false,
searchShow: false,
header: false,
viewBtn: false,
delBtn: false,
index: true,
menuWidth: 300,
addBtn: false,
editBtn: false,
submitText: '修改',
emptyText: '暂无数据',
span: 24,
column: [
{
label: 'id',
prop: 'id',
hide: true,
display: false,
},
{
label: '标识',
prop: 'identifier',
type: 'input',
overHidden: true,
display: false,
},
{
label: '实例名称',
prop: 'name',
type: 'input',
overHidden: true,
rules: [
{ required: true, message: '必须填写实例名称' },
{ max: 50, message: '实例名称不能超过50字符' },
],
},
{
label: '运行类型',
prop: 'runType',
type: 'input',
width: 80,
display: false,
dicData: runTypeDic,
},
{
label: '运行状态',
prop: 'runStatus',
type: 'input',
width: 80,
dicData: runStatusDic,
display: false,
slot: true,
},
{
label: '创建时间',
prop: 'createTime',
type: 'input',
// width: 160,
display: false,
},
{
label: '实例描述',
prop: 'description',
type: 'textarea',
hide: true,
minRows: 3,
span: 24,
},
],
};
export const instanceOption = {
...globalOption,
calcHeight: 'auto',
// height: '300',
// calcHeight: 30,
tip: false,
searchShow: false,
header: false,
viewBtn: false,
delBtn: false,
index: true,
menuWidth: 350,
addBtn: false,
editBtn: false,
submitText: '修改',
emptyText: '取消',
span: 24,
column: [
...option.column,
{
label: '图标',
prop: 'logoUrl',
type: 'upload',
hide: true,
listType: 'picture-img',
action: '/api/blade-isdp/fileSystem/fileUpload',
propsHttp: {
res: 'data',
url: 'link',
},
fileType: 'img',
limit: 1,
fileSize: 1024 * 10,
accept: 'image/png, image/jpeg, image/jpg',
// tip: '图片尺寸不超过300*300像素大小限制为1mb以内',
// rules: [{
// required: true,
// message: "请输入图片地址",
// trigger: "blur"
// }]
},
],
};
export const envConfigOption = {
...globalOption,
height: 300,
calcHeight: 30,
tip: false,
searchShow: false,
addBtn: false,
addRowBtn: false,
cellBtn: false,
index: true,
menuWidth: 80,
editBtn: false,
delBtn: false,
column: [
{
label: '启动项名称',
prop: 'name',
placeholder: '启动项名称',
formSlot: true,
rules: [
{ required: true, message: '启动项名称不能为空' },
{ max: 50, message: '启动项名称最长为50字符' },
],
width: 120,
span: 24,
cell: true,
},
{
label: '参数名key',
prop: 'key',
formSlot: true,
rules: [
{ required: true, message: '请输入参数名key' },
{ max: 50, message: '参数名key最长为50字符' },
],
span: 24,
cell: true,
},
{
label: '配置值',
prop: 'value',
span: 24,
formSlot: true,
rules: [{ max: 200, message: '配置值最长为200字符' }],
cell: true,
},
{
label: '描述说明',
prop: 'desc',
type: 'input',
formSlot: true,
span: 24,
rules: [{ max: 200, message: '描述说明最长为200字符' }],
cell: true,
},
// {
// label: "配置类型",
// prop: "unmodifiable",
// value: false,
// width: 80,
// dicData: [
// {
// label: "自定义配置",
// value: false
// },
// {
// label: "必须配置",
// value: true
// }
// ]
// },
],
};
export const addInstanceOption = {
...globalOption,
submitBtn: false,
emptyBtn: false,
column: [
{
label: '部署环境',
prop: 'environment',
type: 'select',
value: 'docker-env-prod',
// disabled: true,
dicData: [],
rules: [{ required: true, message: '请选择环境变量' }],
overHidden: true,
},
{
label: '主机',
prop: 'deployEnvId',
type: 'select',
value: '',
props: {
label: 'name',
value: 'id',
desc: 'ip',
},
rules: [{ required: true, message: '请输入主机' }],
overHidden: true,
},
{
label: 'CPU核数',
prop: 'cpuCount',
type: 'slider',
step: 1,
min: 1,
max: 12,
showStops: true,
marks: [1, 4, 8].reduce((obj, pre) => {
obj[pre] = pre + '核';
return obj;
}, {}),
},
// {
// label: "容器名称",
// prop: "containerName",
// tip: "为空时自动生成",
// rules: [
// { min: 1, max: 63, message: "容器名称长度为1-63个字符" },
// patternTrim,
// ],
// },
{
label: '是否使用GPU',
prop: 'useGpu',
type: 'switch',
labelWidth: 100,
value: false,
dicData: [
{ label: '否', value: false },
{ label: '是', value: true },
],
},
{
label: '最大内存',
prop: 'memory',
type: 'slider',
step: 64,
min: 64,
max: 8192,
formSlot: true,
span: 24,
marks: {
64: '64MB',
1024: '1GB',
2048: '2GB',
4096: '4GB',
6144: '6GB',
},
showStops: true,
},
// 网络模式 选择框
{
label: '网络模式',
prop: 'networkType',
type: 'select',
span: 6,
value: 'bridge',
dicData: [
{
label: '桥接模式',
value: 'bridge',
},
{
label: '主机模式',
value: 'host',
},
{
label: '禁用网络',
value: 'null',
},
],
rules: [{ required: true, message: '请选择网络模式' }],
overHidden: true,
},
// 网卡
{
label: '网卡',
prop: 'networkMode',
type: 'select',
value: 'bridge',
span: 9,
overHidden: true,
rules: [{ required: true, message: '请选择网卡' }],
},
// ip
{
label: 'IP',
prop: 'ipv4Address',
type: 'input',
formSlot: true,
value: '',
overHidden: true,
span: 9,
},
{
label: '端口映射',
prop: 'ports',
type: 'dynamic',
span: 24,
children: {
height: 120,
column: [
{
label: '主机端口',
prop: 'hostPort',
type: 'number',
value: '80',
max: 65535,
rules: [{ required: true, message: '请输入主机端口' }],
},
{
label: '容器端口',
prop: 'containerPort',
type: 'number',
max: 65535,
rules: [{ required: true, message: '请输入容器端口' }],
},
{
label: '备注',
prop: 'remake',
type: 'input',
value: '',
},
],
},
},
{
label: '目录挂载',
prop: 'volumes',
type: 'dynamic',
span: 24,
children: {
height: 170,
column: [
{
label: '主机路径',
prop: 'hostPath',
type: 'input',
value: '',
rules: [{ required: true, message: '请输入主机路径' }, patternTrim],
},
{
label: '容器路径',
prop: 'containerPath',
type: 'input',
value: '',
rules: [{ required: true, message: '请输入容器路径' }, patternTrim],
},
{
label: '备注',
prop: 'remake',
type: 'input',
},
],
},
},
{
// 设备挂载
label: '设备挂载',
prop: 'devices',
type: 'dynamic',
span: 24,
children: {
height: 120,
column: [
{
label: '主机路径',
prop: 'hostPath',
type: 'input',
value: '',
rules: [{ required: true, message: '请输入主机路径' }, patternTrim],
},
{
label: '容器路径',
prop: 'containerPath',
type: 'input',
value: '',
rules: [{ required: true, message: '请输入容器路径' }, patternTrim],
},
{
label: '权限',
prop: 'permissions',
type: 'select',
value: 'rwm',
dicData: [
{ value: 'rwm', label: 'rwm' },
{ value: 'rw', label: 'rw' },
{ value: 'r', label: 'r' },
],
rules: [{ required: true, message: '请输入权限' }],
},
{
label: '备注',
prop: 'remake',
type: 'input',
value: '',
},
],
},
},
// 重启策略
{
label: '重启策略',
prop: 'restartPolicy',
type: 'select',
span: 6,
display: false,
hide: true,
value: 'always',
dicData: [
{
label: '始终',
value: 'always',
},
{
label: '失败',
value: 'on-failure',
},
{
label: '无',
value: 'no',
},
],
overHidden: true,
},
],
};

@ -0,0 +1,179 @@
import { globalOption, patternTrim } from '../globalOption';
import { componentBaseTagsUrl } from '@/api/isdp/componentBase';
export const releaseMaping = {
0: {
label: '未公开',
type: 'info',
},
'-1': {
label: '未公开',
type: 'info',
},
1: {
label: '已公开',
type: 'success',
},
};
export const option = {
...globalOption,
height: 'auto',
calcHeight: 30,
tip: false,
searchShow: true,
searchSpan: 5,
searchMenuSpan: 4,
menuWidth: 120,
viewBtn: true,
delBtn: false,
selection: false,
header: false,
editBtn: false,
column: [
{
label: '组件名称',
prop: 'name',
type: 'input',
overHidden: true,
search: true,
placeholder: '请输入组件名称',
rules: [
{
required: true,
message: '组件名称不能为空',
trigger: 'blur',
},
patternTrim,
{ max: 20, message: '组件名称最长为20字符' },
],
},
{
label: '项目名',
prop: 'identifier',
type: 'input',
overHidden: true,
search: true,
placeholder: '请输入项目名',
rules: [
{ required: true, message: '项目名不能为空' },
{
pattern: /^([a-z][a-z0-9]*[_-]?)+$/,
message: '项目名由小写字母、数字、"-"、"_"组成,以小写字母开头,且“-”、"_"后第一个字符需为小写字母',
},
{ max: 30, message: '项目名最长为30个字符' },
patternTrim,
],
},
{
label: '版本号',
prop: 'version',
type: 'input',
overHidden: true,
disabled: true,
value: 'v1',
width: 60,
span: 6,
rules: [
{
required: true,
message: '请输入组件版本号',
trigger: 'blur',
},
// {
// pattern: /^\d+(\.\d+){0,3}$/,
// message: "版本只能是由数字组成的x.x或x.x.x格式",
// trigger: "blur"
// },
{ max: 20, message: '版本号最长为20字符' },
],
},
{
label: '分类',
prop: 'componentClassify',
type: 'select',
placeholder: '请选择分类',
search: true,
dicUrl: '/api/blade-system/dict-biz/dictionary-tree?code=component_classify',
props: {
label: 'dictValue',
value: 'dictKey',
},
span: 9,
rules: [{ required: true, message: '分类不能为空' }],
// search: true,
},
{
label: '代码语言',
prop: 'codeLanguage',
type: 'select',
search: true,
dicUrl: '/api/blade-system/dict/dictionary?code=language_type',
value: 'Java',
width: 70,
span: 9,
placeholder: '请选择代码语言',
props: {
label: 'dictValue',
value: 'dictKey',
},
rules: [{ required: true, message: '代码语言不能为空' }],
},
{
label: '标签',
prop: 'tags',
type: 'select',
remote: true,
multiple: true,
allowCreate: true,
filterable: true,
placeholder: '请选择标签',
span: 24,
dicUrl: componentBaseTagsUrl,
hide: true,
},
{
label: '描述',
prop: 'desc',
type: 'textarea',
overHidden: true,
placeholder: '请输入描述',
minRows: 2,
maxLen: 100,
rules: [{ max: 200, message: '最长为200个字符' }],
span: 24,
},
{
label: '图标',
prop: 'logoUrl',
type: 'upload',
hide: true,
listType: 'picture-img',
action: '/api/blade-isdp/fileSystem/fileUpload',
propsHttp: {
res: 'data',
url: 'link',
},
fileType: 'img',
limit: 1,
fileSize: 1024,
accept: 'image/png, image/jpeg, image/jpg',
tip: '图片尺寸不超过300*300像素大小限制为1mb以内',
},
{
label: '公开状态',
prop: 'publishStatus',
type: 'input',
slot: true,
display: false,
width: 90,
},
{
label: '修改时间',
prop: 'updateTime',
type: 'input',
width: 130,
display: false,
},
],
};

@ -0,0 +1,176 @@
import { globalOption, patternTrim } from '@/const/globalOption';
import { runTypeDic, runStatusDic, runStatusConstant } from '@/const/isdp/componentDeploy';
export const runStatusConstant2 = runStatusConstant;
export const componentTestOption = {
...globalOption,
height: '400',
calcHeight: 30,
tip: false,
searchShow: false,
selection: false,
dialogClickModal: false,
header: false,
delBtn: true,
viewBtn: true,
editBtn: false,
dialogWidth: '55%',
menuWidth: 230,
saveBtn: false,
cancelBtn: false,
menu: false,
emptyBtn: false,
submitBtn: false,
menuPosition: 'right',
column: [
{
label: '用例名称',
prop: 'testCaseName',
type: 'select',
allowCreate: true,
filterable: true,
placeholder: '请输入或选择测试用例',
rules: [{ required: true, message: '用例名称不能为空' }, { max: 20, message: '用例名称最长为20个字符' }, patternTrim],
dicData: [
{
label: '通过用例',
value: '通过用例',
},
{
label: '失败用例',
value: '失败用例',
},
{
label: '参数有误',
value: '参数有误',
},
{
label: '数据为空',
value: '数据为空',
},
{
label: '缺少参数',
value: '缺少参数',
},
{
label: '记录不存在',
value: '记录不存在',
},
],
span: 12,
},
{
type: 'none',
display: 'none',
},
{
label: '输入参数',
prop: 'expectDataIns',
type: 'dynamic',
span: 24,
children: {
height: 250,
addBtn: false,
delBtn: false,
column: [
{
label: '名称',
prop: 'id',
type: 'input',
width: 100,
},
{
label: '参数类型',
prop: 'type',
type: 'input',
width: 80,
},
{
label: '数组类型',
placeholder: '请选择数组类型',
prop: 'generic',
type: 'input',
width: 80,
},
{
label: '参数值',
prop: 'value',
type: 'input',
cell: true,
},
],
},
},
{
label: '输出参数',
prop: 'expectDataOuts',
type: 'dynamic',
span: 24,
children: {
height: 250,
addBtn: false,
delBtn: false,
column: [
{
label: '名称',
prop: 'id',
type: 'input',
width: 100,
},
{
label: '参数类型',
prop: 'type',
type: 'input',
width: 80,
},
{
label: '数组类型',
prop: 'generic',
type: 'input',
width: 80,
},
{
label: '参数值',
prop: 'value',
type: 'input',
cell: true,
},
],
},
},
],
};
export const option = {
...globalOption,
header: false,
editBtn: false,
delBtn: false,
menuWidth: 100,
column: [
{
label: '实例标识',
prop: 'identifier',
},
{
label: '实例名',
prop: 'name',
},
{
label: '运行类型',
prop: 'runType',
width: 80,
dicData: runTypeDic,
},
{
label: '实例状态',
prop: 'runStatus',
width: 80,
dicData: runStatusDic,
},
{
label: '实例测试时间',
width: 130,
prop: 'lastTestTime',
slot: true,
},
],
};

@ -0,0 +1,153 @@
import { globalOption, patternTrim } from '../globalOption';
export const archDict = [
{
label: 'x86_64',
value: 'x86_64',
},
{
label: 'aarch64',
value: 'aarch64',
},
];
export const option = {
...globalOption,
height: 'auto',
calcHeight: 30,
searchShow: true,
searchMenuSpan: 6,
viewBtn: false,
delBtn: false,
selection: false,
header: true,
addBtn: false,
editBtn: false,
updateBtnText: '保 存',
menuHeaderAlign: 'center',
column: [
{
label: '环境IP',
prop: 'ip',
type: 'input',
overHidden: true,
span: 12,
rules: [
{
required: true,
message: '环境IP不能为空',
trigger: 'blur',
},
{
pattern: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
message: '请输入正确的IP地址范围在0.0.0.0-255.255.255.255之间',
trigger: 'blur',
},
],
},
{
label: 'docker端口',
prop: 'dockerTcpPort',
type: 'number',
labelWidth: 100,
overHidden: true,
value: 2375,
span: 12,
rules: [
{
required: true,
message: 'docker端口不能为空',
trigger: 'blur',
},
{
pattern: /^(?:[1-9]\d{0,3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/,
message: 'docker端口只能是1-65535之间',
trigger: 'blur',
},
],
},
{
label: '环境类型',
prop: 'type',
type: 'select',
overHidden: true,
search: true,
span: 12,
dicUrl: '/api/blade-system/dict/dictionary-tree?code=docker-env',
props: {
label: 'dictValue',
value: 'dictKey',
},
rules: [
{
required: true,
message: '环境类型不能为空',
trigger: 'blur',
},
],
},
{
label: '架构类型',
prop: 'arch',
type: 'select',
overHidden: true,
search: true,
span: 12,
dicData: archDict,
formSlot: true,
props: {
label: 'label',
value: 'value',
},
rules: [
{
required: true,
message: '架构类型不能为空',
trigger: 'blur',
},
],
},
{
label: '环境别名',
prop: 'name',
type: 'input',
search: true,
span: 24,
rules: [
{
required: true,
message: '环境IP不能为空',
trigger: 'blur',
},
patternTrim,
// 最大50
{
max: 50,
message: '环境别名最大长度不能超过50',
trigger: 'blur',
},
],
},
{
label: '备注',
prop: 'description',
type: 'textarea',
span: 24,
rules: [
{
max: 200,
message: '长度不超过200字符',
trigger: 'blur',
},
],
overHidden: true,
},
{
label: '实例数量',
labelWidth: 20,
span: 24,
prop: 'instanceCount',
display: false,
},
],
};

@ -0,0 +1,113 @@
import { globalOption } from '@/const/globalOption';
/**
* @
*/
// 数据显示的映射
export const typeMap = {
INTEGER: 'INT',
DOUBLE: 'DOU',
STRING: 'STR',
LONG: 'LONG',
FLOAT: 'FLOAT',
DATE: 'DATE',
TIMESTAMP: 'TS',
DATETIME: 'DT',
BOOLEAN: 'BOOL',
ARRAY: 'ARR',
OBJECT: 'OBJ',
};
export const dataTypeDict = [
{
label: '整数',
value: 'INTEGER',
},
{
label: '浮点数',
value: 'DOUBLE',
},
{
label: '字符串',
value: 'STRING',
},
{
label: '布尔值',
value: 'BOOLEAN',
},
{
label: '数组',
value: 'ARRAY',
},
{
label: '对象',
value: 'OBJECT',
},
{
label: '日期',
value: 'DATE',
},
{
label: '日期时间',
value: 'DATETIME',
},
{
label: '时间戳',
value: 'TIMESTAMP',
},
];
// 显示查看表单
export const showOption = {
...globalOption,
minHeight: '100',
calcHeight: 30,
tip: false,
searchShow: false,
searchMenuSpan: 24,
addBtn: false,
viewBtn: false,
selection: false,
editBtn: false,
delBtn: false,
dialogClickModal: false,
header: false,
dialogWidth: '50%',
menuWidth: 60,
// dialogFullscreen: true,
column: [
{
label: '名称',
prop: 'ident',
span: 12,
trigger: 'blur',
placeholder: '请输入名称',
rules: [
{ required: true, message: '名称不能为空' },
{ pattern: /^[a-z][a-zA-Z0-9]*$/, message: '名称符需要为首字母小写的驼峰命名法' },
{ max: 50, message: '名称最长为50个字符' },
],
},
{
label: '数据类型',
prop: 'type',
type: 'select',
placeholder: '请选择数据类型',
dicData: dataTypeDict,
trigger: 'blur',
rules: [{ required: true, message: '数据类型不能为空' }],
},
// 需要加上change
{
label: '数组类型',
prop: 'generic',
slot: true,
formSlot: true,
placeholder: '请选择数组类型',
dicData: dataTypeDict.filter((item) => item.value !== 'ARRAY'),
rules: [{ required: true, message: '数组类型不能为空' }],
},
{
label: '参数描述',
prop: 'desc',
},
],
};

@ -0,0 +1,72 @@
import { option as baseOption } from '../componentBase';
import { deepClone } from '@/utils/common';
export const permissionConstant = {
ADMIN: 'admin',
READ: 'read',
WRITE: 'write',
};
// 系统模块
export const permissionDic = [
{
label: '管理',
value: 'admin',
},
{
label: '读',
value: 'read',
},
{
label: '写',
value: 'write',
},
];
export const option = deepClone(baseOption);
option.header = false;
option.addBtn = false;
option.menuWidth = 200;
const hideArr = ['name', 'componentClassify', 'publicStatus'];
const columnArr = [];
option.column.forEach((element) => {
if (hideArr.includes(element.prop)) {
element.align = 'center';
columnArr.push(element);
}
});
option.column = [
...columnArr,
{
label: '组件标识',
prop: 'identifier',
type: 'input',
width: 150,
},
{
label: '组件版本',
prop: 'componentVersion',
type: 'input',
width: 120,
},
{
label: '组件描述',
prop: 'recommend',
type: 'input',
width: 150,
},
{
label: '组件评分',
prop: 'stars',
type: 'input',
width: 100,
},
{
label: '审核结果',
prop: 'reviewOpinion',
type: 'input',
width: 'auto',
},
];

@ -0,0 +1,59 @@
import { option as baseOption } from '../componentBase';
import { deepClone } from '@/util/util';
import { globalOption } from '@/const/globalOption';
export const permissionConstant = {
ADMIN: 'admin',
READ: 'read',
WRITE: 'write',
};
// 系统模块
export const permissionDic = [
{
label: '管理',
value: 'admin',
},
{
label: '读',
value: 'read',
},
{
label: '写',
value: 'write',
},
];
export const userOption = {
...globalOption,
addBtn: false,
editBtn: false,
viewBtn: false,
header: false,
delBtn: false,
height: 200,
menuWidth: 100,
column: [
{
label: '用户账号',
prop: 'collaboratorAccount',
},
{
label: '操作权限',
prop: 'permission',
slot: true,
width: '120px',
},
],
};
export const option = deepClone(baseOption);
option.header = false;
option.addBtn = false;
option.column.push({
label: '权限',
prop: 'permission',
type: 'select',
dicData: permissionDic,
display: false,
});

@ -0,0 +1,53 @@
import { globalOption } from '../globalOption';
export const option = {
...globalOption,
height: 'auto',
calcHeight: 30,
tip: false,
dialogClickModal: false,
header: false,
menu: true,
editBtn: false,
delBtn: false,
menuWidth: '100',
column: [
{
label: '#',
prop: 'index',
fixed: true,
width: 40,
},
{
label: '名称',
prop: 'name',
overHidden: true,
width: 200,
},
{
label: '类型',
prop: 'type',
overHidden: true,
width: 100,
},
{
label: '格式',
prop: 'format',
overHidden: true,
width: 100,
},
{
label: '状态',
prop: 'status',
overHidden: true,
slot: true,
width: 150,
},
{
label: 'URL',
prop: 'url',
slot: true,
overHidden: true,
},
],
};

@ -0,0 +1,115 @@
import React, { useState, useEffect } from 'react';
import { Modal, Form, Input, Message } from '@arco-design/web-react';
import EditableTable from '@/pages/componentDevelopment/componentList/editableTable';
import { updateComponentDesign } from '@/api/componentDevelopProcess';
const FormItem = Form.Item;
const TextArea = Input.TextArea;
const AddApiModal = ({ visible, baseInfo, componentDesignProgress, onCancel, onOk }) => {
const [form] = Form.useForm();
const [parametersData, setParametersData] = useState([]);
const [responsesData, setResponsesData] = useState([]);
// 当 visible 或 componentDesignProgress 变化时,设置表单初始值
useEffect(() => {
if (visible && componentDesignProgress) {
// 设置表单字段值
form.setFieldsValue({
ident: componentDesignProgress.ident || '',
desc: componentDesignProgress.desc || ''
});
// 设置参数表格数据
if (componentDesignProgress.parameters && Array.isArray(componentDesignProgress.parameters)) {
setParametersData(componentDesignProgress.parameters.map((param, index) => ({
key: param.id || index,
...param
})));
}
// 设置响应表格数据
if (componentDesignProgress.responses && Array.isArray(componentDesignProgress.responses)) {
setResponsesData(componentDesignProgress.responses.map((response, index) => ({
key: response.id || index,
...response
})));
}
}
else if (visible) {
// 重置表单和表格数据
form.resetFields();
setParametersData([]);
setResponsesData([]);
}
}, [visible, componentDesignProgress, form]);
const submit = async () => {
try {
await form.validate();
const formData = form.getFields();
const params = {
baseInfo
};
params['operates'] = {
...formData,
type: 'EVENT',
parameters: parametersData,
responses: responsesData
};
const res: any = await updateComponentDesign(params);
if (res.code === 200) {
Message.success('新增成功');
}
else {
Message.error(res.message);
}
// 调用父组件传递的 onOk 回调,并传递数据
onOk && onOk(params);
} catch (error) {
console.error('表单验证失败:', error);
Message.error('请检查表单填写是否正确');
}
};
return (
<Modal
title={componentDesignProgress ? '编辑接口' : '新增接口'}
visible={visible}
onCancel={onCancel}
onOk={submit}
style={{ width: '50%' }}
okButtonProps={{ disabled: !visible }}
>
<Form form={form} layout="vertical">
<FormItem
label="接口名称"
field="ident"
required
rules={[
{
required: true,
message: '请输入接口名称'
}
]}
>
<Input placeholder="请输入接口名称" />
</FormItem>
<FormItem label="描述" field="desc">
<TextArea placeholder="请输入描述" />
</FormItem>
<FormItem label="输入参数" field="parameters">
<EditableTable onDataUpdate={setParametersData} />
</FormItem>
<FormItem label="输出参数" field="responses">
<EditableTable onDataUpdate={setResponsesData} />
</FormItem>
</Form>
</Modal>
);
};
export default AddApiModal;

@ -16,55 +16,70 @@ import {
} from '@arco-design/web-react'; } from '@arco-design/web-react';
import { IconPlus, IconEdit } from '@arco-design/web-react/icon'; import { IconPlus, IconEdit } from '@arco-design/web-react/icon';
import styles from './style/addComponentModal.module.less'; import styles from './style/addComponentModal.module.less';
import Cover from '@/pages/scene/cover';
import EditorSection from '@/components/EditorSection'; import EditorSection from '@/components/EditorSection';
import AddApiModal from '@/pages/componentDevelopment/componentList/addApiModal';
import { getComponentClassify } from '@/api/componentClassify'; import { getComponentClassify } from '@/api/componentClassify';
import { compProjectValidate, compSubmit, getTagList } from '@/api/componentBase'; import { compProjectValidate, compSubmit, getTagList } from '@/api/componentBase';
import { getComponentDesign } from '@/api/componentDevelopProcess';
const FormItem = Form.Item; const FormItem = Form.Item;
const Option = Select.Option; const Option = Select.Option;
const columns: TableColumnProps[] = [ const AddComponentModal = ({ visible, baseInfo, setVisible, onReFresh }) => {
const [selectedImage, setSelectedImage] = useState('');
const [description, setDescription] = useState('');
const [classifyList, setClassifyList] = useState([]);
const [showSaveBtn, setShowSaveBtn] = useState(false);
const [tagsList, setTagsList] = useState([]);
const [componentData, setComponentData] = useState(null);
const [componentDesignData, setComponentDesignData] = useState([]); // 新增状态用于存储接口设计数据
const [selectedApiData, setSelectedApiData] = useState(null); // 新增状态用于存储选中的API数据
const [showApiModal, setShowApiModal] = useState(false);
const [file, setFile] = useState(null);
const [form] = Form.useForm();
const cs = `arco-upload-list-item${file && file.status === 'error' ? ' is-error' : ''}`;
const columns: TableColumnProps[] = [
{ {
title: '接口名称', title: '接口名称',
dataIndex: 'name' dataIndex: 'ident'
}, },
{ {
title: '输入参数', title: '输入参数',
dataIndex: 'salary' dataIndex: 'parameters',
render: (_, record) => {
if (!record.parameters || record.parameters.length === 0) {
return '-';
}
return record.parameters.map(param => param.ident).join(', ');
}
}, },
{ {
title: '输出参数', title: '输出参数',
dataIndex: 'address' dataIndex: 'responses',
render: (_, record) => {
if (!record.responses || record.responses.length === 0) {
return '-';
}
return record.responses.map(response => response.ident).join(', ');
}
}, },
{ {
title: '操作', title: '操作',
dataIndex: 'email' dataIndex: 'op',
render: (_, record) => (
<Button type="text" onClick={() => {
// 设置选中的API数据用于编辑时回显
setSelectedApiData(record);
setShowApiModal(true);
}}>
</Button>
)
} }
]; ];
const data = [
{
key: '1',
name: 'Jane Doe',
salary: 23000,
address: '32 Park Road, London',
email: 'jane.doe@example.com'
}
];
const AddComponentModal = ({ visible, setVisible, onReFresh }) => {
const [selectedImage, setSelectedImage] = useState('');
const [description, setDescription] = useState('');
const [classifyList, setClassifyList] = useState([]);
const [showSaveBtn, setShowSaveBtn] = useState(false);
const [tagsList, setTagsList] = useState([]);
const [componentData, setComponentData] = useState({});
const [file, setFile] = useState(null);
const [form] = Form.useForm();
const cs = `arco-upload-list-item${file && file.status === 'error' ? ' is-error' : ''}`;
const getComponentClassifyList = async () => { const getComponentClassifyList = async () => {
@ -128,7 +143,7 @@ const AddComponentModal = ({ visible, setVisible, onReFresh }) => {
try { try {
await form.validate(); await form.validate();
const formData = form.getFields(); const formData = form.getFields();
const params = { const params: any = {
name: formData.name, name: formData.name,
projectId: formData.projectId, projectId: formData.projectId,
logoUrl: selectedImage, logoUrl: selectedImage,
@ -138,18 +153,47 @@ const AddComponentModal = ({ visible, setVisible, onReFresh }) => {
type: formData.type === '普通组件' ? 'normal' : 'loop', type: formData.type === '普通组件' ? 'normal' : 'loop',
tags: formData.tags tags: formData.tags
}; };
// 如果是编辑模式baseInfo存在则传递id
if (baseInfo && baseInfo.id) {
params.id = baseInfo.id;
}
const res: any = await compSubmit(params); const res: any = await compSubmit(params);
if (res.code === 200) { if (res.code === 200) {
setComponentData(res.data); setComponentData(res.data);
setShowSaveBtn(true); setShowSaveBtn(true);
Message.success('组件信息提交完成,可继续组件接口设计'); Message.success(baseInfo ? '组件信息更新成功' : '组件信息提交完成,可继续组件接口设计');
onReFresh(); onReFresh();
// 提交成功后获取组件设计数据
if (res.data && res.data.id) {
getComponentDesignData(res.data.id);
}
} }
else { else {
Message.error('组件信息提交失败'); Message.error(baseInfo ? '组件信息更新失败' : '组件信息提交失败');
} }
} catch (error) { } catch (error) {
console.error('提交/更新失败:', error);
}
};
const getComponentDesignData = async (componentBaseId) => {
try {
const res = await getComponentDesign(componentBaseId);
if (res.data) {
// 处理返回的数据,使其符合表格显示格式
const processedData = res.data.operates.map(item => ({
...item,
key: item.ident,
name: item.ident // 使用ident作为名称显示
}));
setComponentDesignData(processedData);
}
} catch (error) {
console.error('获取组件设计数据失败:', error);
Message.error('获取组件设计数据失败');
} }
}; };
@ -158,23 +202,64 @@ const AddComponentModal = ({ visible, setVisible, onReFresh }) => {
getTageList(); getTageList();
}, []); }, []);
// 当baseInfo或visible变化时设置表单初始值
useEffect(() => {
if (visible && baseInfo) {
// 设置表单字段值
form.setFieldsValue({
name: baseInfo.name || '',
projectId: baseInfo.projectId || '',
componentClassify: baseInfo.componentClassify || '',
codeLanguage: baseInfo.codeLanguage || '',
tags: baseInfo.tags || [],
type: baseInfo.type || ''
});
// 设置其他状态
setSelectedImage(baseInfo.logoUrl || '');
setDescription(baseInfo.desc || '');
setShowSaveBtn(true);
// 获取组件设计数据
getComponentDesignData(baseInfo.id);
}
else if (visible) {
// 重置表单
form.resetFields();
setSelectedImage('');
setDescription('');
setShowSaveBtn(false);
setComponentDesignData([]); // 清空接口设计数据
}
}, [baseInfo, visible, form]);
const modalFooter = () => { const modalFooter = () => {
const isEditMode = !!baseInfo; // 判断是否为编辑模式
return ( return (
<Space size={30}> <Space size={30}>
<Button size="small" type="outline" style={{ borderRadius: 5 }} onClick={() => setVisible(false)}></Button> <Button size="small" type="outline" style={{ borderRadius: 5 }} onClick={() => setVisible(false)}></Button>
{showSaveBtn && {showSaveBtn ? (
<Button size="small" type="primary" style={{ borderRadius: 5 }} onClick={() => onSubmit()}></Button>} <Button size="small" type="primary" style={{ borderRadius: 5 }} onClick={() => onSubmit()}>
{!showSaveBtn && <Button size="small" type="primary" style={{ borderRadius: 5 }} {isEditMode ? '更新' : '保存'}
onClick={() => onSubmit()}></Button>} </Button>
) : (
<Button size="small" type="primary" style={{ borderRadius: 5 }} onClick={() => onSubmit()}>
{isEditMode ? '更新组件信息' : '提交组件信息'}
</Button>
)}
</Space> </Space>
); );
}; };
const UploadImage = () => { const UploadImage = () => {
// 如果是编辑模式且有baseInfo.logoUrl则使用baseInfo中的logoUrl
const initialImageUrl = baseInfo && baseInfo.logoUrl ? baseInfo.logoUrl : null;
return ( return (
<Upload <Upload
action="/api/v1/bpms-workbench/fileSystem/fileUpload" action="/api/v1/bpms-workbench/fileSystem/fileUpload"
fileList={file ? [file] : []} fileList={file ? [file] : initialImageUrl ? [{ url: initialImageUrl, status: 'done' }] : []}
showUploadList={false} showUploadList={false}
onChange={(_, currentFile: any) => { onChange={(_, currentFile: any) => {
setFile({ setFile({
@ -188,13 +273,13 @@ const AddComponentModal = ({ visible, setVisible, onReFresh }) => {
}} }}
> >
<div className={cs}> <div className={cs}>
{file && file.url ? ( {(file && file.url) || initialImageUrl ? (
<div className="arco-upload-list-item-picture custom-upload-avatar"> <div className="arco-upload-list-item-picture custom-upload-avatar">
<img src={file.url} /> <img src={file?.url || initialImageUrl} />
<div className="arco-upload-list-item-picture-mask"> <div className="arco-upload-list-item-picture-mask">
<IconEdit /> <IconEdit />
</div> </div>
{file.status === 'uploading' && file.percent < 100 && ( {file && file.status === 'uploading' && file.percent < 100 && (
<Progress <Progress
percent={file.percent} percent={file.percent}
type="circle" type="circle"
@ -223,12 +308,21 @@ const AddComponentModal = ({ visible, setVisible, onReFresh }) => {
return ( return (
<Modal <Modal
className={styles['add-component-modal']} className={styles['add-component-modal']}
title="新增组件" title={baseInfo ? '编辑组件' : '新增组件'}
visible={visible} visible={visible}
autoFocus={false} autoFocus={false}
focusLock={true} focusLock={true}
style={{ width: '60%' }} style={{ width: '60%' }}
footer={modalFooter} footer={modalFooter}
onCancel={() => setVisible(false)}
afterClose={() => {
// 关闭模态框后重置表单
form.resetFields();
setSelectedImage('');
setDescription('');
setShowSaveBtn(false);
setFile(null);
}}
> >
<Form form={form} className={styles['add-component-container']}> <Form form={form} className={styles['add-component-container']}>
<div className={styles['first-half']}> <div className={styles['first-half']}>
@ -390,12 +484,40 @@ const AddComponentModal = ({ visible, setVisible, onReFresh }) => {
<div className={styles['last-half-header']}> <div className={styles['last-half-header']}>
<p> </p> <p> </p>
<Space split={<Divider type="vertical" />}> <Space split={<Divider type="vertical" />}>
<Button size="small" type="secondary" style={{ borderRadius: 5 }}></Button> <Button size="small" type="secondary" style={{ borderRadius: 5 }} disabled={true}></Button>
<Button size="mini" type="primary" style={{ borderRadius: 5 }}>+ </Button> <Button
size="mini"
type="primary"
style={{ borderRadius: 5 }}
onClick={() => {
setSelectedApiData(null); // 确保新增时清空选中的API数据
setShowApiModal(true);
}}
>
+
</Button>
</Space> </Space>
</div> </div>
<Table columns={columns} data={data} pagination={false} /> <Table columns={columns} data={componentDesignData} pagination={false} />
<AddApiModal
visible={showApiModal}
baseInfo={componentData || baseInfo}
componentDesignProgress={selectedApiData} // 添加这一行
onCancel={() => {
setShowApiModal(false);
setSelectedApiData(null); // 重置选中的API数据
}}
onOk={() => {
setShowApiModal(false);
setSelectedApiData(null); // 重置选中的API数据
// 重新获取组件设计数据以更新表格
if ((componentData && componentData.id) || (baseInfo && baseInfo.id)) {
getComponentDesignData(componentData?.id || baseInfo.id);
}
}}
/>
</div>} </div>}
</Form> </Form>
</Modal> </Modal>

@ -0,0 +1,197 @@
import React, { useState } from 'react';
import { Button, Table, Input, Select, Modal, Message } from '@arco-design/web-react';
// 定义数据类型选项
const dataTypeOptions = [
{ label: 'STRING', value: 'STRING' },
{ label: 'INTEGER', value: 'INTEGER' },
{ label: 'BOOLEAN', value: 'BOOLEAN' },
{ label: 'ARRAY', value: 'ARRAY' },
{ label: 'OBJECT', value: 'OBJECT' },
{ label: 'JSON', value: 'JSON' },
{ label: 'DOUBLE', value: 'DOUBLE' }
];
// 定义数组类型选项
const arrayTypeOptions = [
{ label: 'STRING', value: 'STRING' },
{ label: 'INTEGER', value: 'INTEGER' },
{ label: 'DOUBLE', value: 'DOUBLE' },
{ label: 'BOOLEAN', value: 'BOOLEAN' },
{ label: 'JSON', value: 'JSON' },
{ label: 'OBJECT', value: 'OBJECT' }
];
function EditableCell({ value, onChange, columnType, record, dataIndex }) {
// 对于数组类型字段的特殊处理
if (dataIndex === 'generic') {
// 仅当数据类型为 ARRAY 时才可编辑
if (record.type === 'ARRAY') {
return (
<Select
value={value}
onChange={onChange}
options={arrayTypeOptions}
placeholder="请选择数组类型"
/>
);
}
else {
// 非 ARRAY 类型显示横杠
return <span>-</span>;
}
}
if (columnType === 'input') {
return (
<Input
value={value}
onChange={onChange}
placeholder="请输入"
/>
);
}
if (columnType === 'select') {
return (
<Select
value={value}
onChange={onChange}
options={dataTypeOptions}
placeholder="请选择数据类型"
/>
);
}
return <span>{value}</span>;
}
function EditableTable({ onDataUpdate }) {
const [data, setData] = useState([]);
const handleValueChange = (key, dataIndex, value) => {
const newData = data.map(item => {
if (item.key === key) {
const updatedItem = { ...item, [dataIndex]: value };
// 如果更改的是 type 字段且不是 ARRAY则清空 generic 字段
if (dataIndex === 'type' && value !== 'ARRAY') {
updatedItem.generic = '';
}
return updatedItem;
}
return item;
});
setData(newData);
onDataUpdate && onDataUpdate(newData);
};
const columns = [
{
title: '名称',
dataIndex: 'ident',
render: (_, record) => (
<EditableCell
value={record.ident}
onChange={(value) => handleValueChange(record.key, 'ident', value)}
columnType="input"
record={record}
dataIndex="ident"
/>
)
},
{
title: '数据类型',
dataIndex: 'type',
render: (_, record) => (
<EditableCell
value={record.type}
onChange={(value) => handleValueChange(record.key, 'type', value)}
columnType="select"
record={record}
dataIndex="type"
/>
)
},
{
title: '数组类型',
dataIndex: 'generic',
render: (_, record) => (
<EditableCell
value={record.generic}
onChange={(value) => handleValueChange(record.key, 'generic', value)}
columnType="select"
record={record}
dataIndex="generic"
/>
)
},
{
title: '参数描述',
dataIndex: 'desc',
render: (_, record) => (
<EditableCell
value={record.desc}
onChange={(value) => handleValueChange(record.key, 'desc', value)}
columnType="input"
record={record}
dataIndex="desc"
/>
)
},
{
title: '操作',
dataIndex: 'op',
render: (_, record) => (
<Button
onClick={() => removeRow(record.key)}
type="text"
status="danger"
>
</Button>
)
}
];
const removeRow = (key) => {
const newData = data.filter(item => item.key !== key);
setData(newData);
onDataUpdate && onDataUpdate(newData);
};
const addRow = () => {
const newRow = {
key: `${Date.now()}`,
ident: '',
type: '',
generic: '',
desc: ''
};
const newData = [...data, newRow];
setData(newData);
onDataUpdate && onDataUpdate(newData);
};
return (
<>
<Button
style={{ marginBottom: 10 }}
type="primary"
onClick={addRow}
>
</Button>
<Table
data={data}
pagination={false}
columns={columns}
className="table-demo-editable-cell"
/>
</>
);
}
export default EditableTable;

@ -0,0 +1,184 @@
import React from 'react';
import { Button, Dropdown, Menu, Message } from '@arco-design/web-react';
import { ComponentItem } from '@/api/interface';
import {
componentStatusConstant,
publicStatus,
publishStatusConstant
} from '@/const/isdp/componentBase';
interface HandleButtonGroupProps {
row: ComponentItem;
index: number;
onHandlePublishComponent: (row: ComponentItem) => void;
onGoToReview: (row: ComponentItem) => void;
onPublishOrRevokeComponent: (action: 'publish' | 'revoke', identifier: string, version: string) => void;
onNavTo: (id: number, type: string) => void;
onSourceCodeView: (row: ComponentItem) => void;
onShowEdit: (row: ComponentItem, index: number) => void;
onCopyHandler: (row: ComponentItem) => void;
onShareCollaboration: (row: ComponentItem) => void;
onExportComponent: (id: number) => void;
onStopComponentShow: (row: ComponentItem) => void;
onRowDel: (row: ComponentItem) => void;
}
const HandleButtonGroup: React.FC<HandleButtonGroupProps> = ({
row,
index,
onHandlePublishComponent,
onGoToReview,
onPublishOrRevokeComponent,
onNavTo,
onSourceCodeView,
onShowEdit,
onCopyHandler,
onShareCollaboration,
onExportComponent,
onStopComponentShow,
onRowDel
}) => {
// 检查组件状态是否符合条件
const isEligible = (eligibleStatuses: string[], currentStatus: string) => {
return eligibleStatuses.includes(currentStatus);
};
// 构建更多操作菜单
const renderDropdownMenu = () => {
const items = [];
// 组件复制
if (!isEligible([componentStatusConstant.DEFAULT, componentStatusConstant.DESIGN], row.componentStatus)) {
items.push(
<Menu.Item key="copy">
<Button type="text" onClick={() => onCopyHandler(row)}></Button>
</Menu.Item>
);
}
// 分享协作
if (!isEligible([componentStatusConstant.DESIGN, componentStatusConstant.DEFAULT], row.componentStatus)) {
items.push(
<Menu.Item key="share">
<Button type="text" onClick={() => onShareCollaboration(row)}></Button>
</Menu.Item>
);
}
// 导出组件
items.push(
<Menu.Item key="export">
<Button type="text" onClick={() => onExportComponent(row.id)}></Button>
</Menu.Item>
);
// 下架组件
if (isEligible([componentStatusConstant.DEPLOYED], row.componentStatus)) {
items.push(
<Menu.Item key="stop">
<Button type="text" onClick={() => onStopComponentShow(row)}></Button>
</Menu.Item>
);
}
// 删除组件
if (!isEligible([componentStatusConstant.DEPLOYED, componentStatusConstant.PUBLISHED], row.componentStatus)) {
items.push(
<Menu.Item key="delete">
<Button type="text" onClick={() => onRowDel(row)}></Button>
</Menu.Item>
);
}
return <Menu>{items}</Menu>;
};
return (
<>
{/* 发布组件/更新版本/查看审核 */}
{row.publicStatus !== publicStatus.REVIEW ? (
<Button
type="text"
onClick={() => onHandlePublishComponent(row)}
>
{row.publicStatus === publicStatus.PUBLISHED ? '更新版本' : '发布组件'}
</Button>
) : (
<Button
type="text"
onClick={() => onGoToReview(row)}
>
</Button>
)}
{/* 公开组件/取消公开 */}
{row.publishStatus !== publishStatusConstant.PUBLISHED ? (
<Button
type="text"
onClick={() => onPublishOrRevokeComponent('publish', row.identifier, row.version)}
>
</Button>
) : (
<Button
type="text"
onClick={() => onPublishOrRevokeComponent('revoke', row.identifier, row.version)}
>
</Button>
)}
{/* 接口设计/查看接口 */}
{isEligible([componentStatusConstant.DESIGN, componentStatusConstant.DEFAULT], row.componentStatus) ? (
<Button
type="text"
onClick={() => onNavTo(row.id, '接口设计')}
>
</Button>
) : null}
{/* 组件编码 */}
{isEligible([componentStatusConstant.CODING], row.componentStatus) ? (
<Button
type="text"
onClick={() => onNavTo(row.id, '逻辑编码')}
>
</Button>
) : null}
{/* 查看源码 */}
{!isEligible(
[componentStatusConstant.CODING, componentStatusConstant.DESIGN, componentStatusConstant.DEFAULT],
row.componentStatus
) ? (
<Button
type="text"
onClick={() => onSourceCodeView(row)}
>
</Button>
) : null}
{/* 编辑组件 */}
<Button
type="text"
onClick={() => onShowEdit(row, index)}
>
</Button>
{/* 更多操作 */}
<Dropdown droplist={renderDropdownMenu()} position="bl">
<Button type="text">
</Button>
</Dropdown>
</>
);
};
export default HandleButtonGroup;

@ -1,14 +1,35 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import styles from './style/index.module.less'; import styles from './style/index.module.less';
import { Button, Divider, Input, Space, Table, Radio, Pagination } from '@arco-design/web-react'; import { Button, Divider, Input, Space, Table, Radio, Pagination, Modal, Message } from '@arco-design/web-react';
import { IconSearch } from '@arco-design/web-react/icon'; import { IconSearch } from '@arco-design/web-react/icon';
import { getMyComponentList } from '@/api/componentBase'; import { getMyComponentList, remove } from '@/api/componentBase';
import { ComponentItem } from '@/api/interface'; import { ComponentItem } from '@/api/interface';
import AddComponentModal from '@/pages/componentDevelopment/componentList/addComponentModal'; import AddComponentModal from '@/pages/componentDevelopment/componentList/addComponentModal';
import HandleButtonGroup from '@/pages/componentDevelopment/componentList/handleButtonGroup';
import {
componentStatusConstant,
componentStatusDict,
publicStatus,
publicStatusDict, publishStatusConstant, publishStatusDict
} from '@/const/isdp/componentBase';
import dayjs from 'dayjs';
const Group = Radio.Group; const Group = Radio.Group;
const menuItems = [ const GlobalVarContainer = () => {
const [selectedItem, setSelectedItem] = useState('我的组件');
const [selectComponent, setSelectComponent] = useState(null);
const [componentData, setComponentData] = useState<ComponentItem[]>([]);
const [pagination, setPagination] = useState({
totalCount: 0,
pageSize: 10,
totalPage: 0,
currPage: 1
});
const [loading, setLoading] = useState(false);
const [visible, setVisible] = useState(false);
const menuItems = [
{ {
key: '1', key: '1',
label: '我的组件', label: '我的组件',
@ -27,57 +48,150 @@ const menuItems = [
icon: '/ideContainer/icon/compAudit.png', icon: '/ideContainer/icon/compAudit.png',
activeIcon: '/ideContainer/icon/compAudit_active.png' activeIcon: '/ideContainer/icon/compAudit_active.png'
} }
]; ];
const columns = [ const columns = [
{ {
title: '组件名称', title: '组件名称',
dataIndex: 'name' dataIndex: 'name'
}, },
{ {
title: '标识', title: '组件标识',
dataIndex: 'identifier' dataIndex: 'projectId'
}, },
{ {
title: '分类', title: '分类',
dataIndex: 'componentClassify' dataIndex: 'componentClassify'
}, },
{ {
title: '语言', title: '创建人',
dataIndex: 'createUserName'
},
{
title: '代码语言',
dataIndex: 'codeLanguage' dataIndex: 'codeLanguage'
}, },
{ {
title: '描述', title: '组件类型',
dataIndex: 'desc' dataIndex: 'type',
render: (_, record) => {
return (
<span>{record.type === 'normal' ? '普通组件' : '监听组件'}</span>
);
}
},
{
title: '组件状态',
dataIndex: 'componentStatus',
render: (_, record) => {
const formattedData = componentStatusDict.find(item => item.value === componentStatusConstant[record.componentStatus]) || { label: '' };
return (
<span>{formattedData.label}</span>
);
}
}, },
{ {
title: '状态', title: '发布状态',
dataIndex: 'componentStatus' dataIndex: 'publicStatus',
render: (_, record) => {
const formattedData = publicStatusDict.find(item => item.value === record.publicStatus) || { label: '' };
return (
<span>{formattedData.label}</span>
);
}
}, },
{ {
title: '更新时间', title: '公开状态',
dataIndex: 'updateTime' dataIndex: 'publishStatus',
render: (_, record) => {
const formattedData = publishStatusDict.find(item => item.value === record.publishStatus) || { label: '' };
return (
<span>{formattedData.label}</span>
);
}
},
{
title: '修改时间',
dataIndex: 'updateTime',
render: (_, record) => {
return (
<span>{dayjs(record.updateTime).format('YYYY-MM-DD HH:mm:ss')}</span>
);
}
}, },
{ {
title: '操作', title: '操作',
dataIndex: 'operations', dataIndex: 'operations',
render: () => ( render: (_, record, index) => (
<Button type="text"></Button> <HandleButtonGroup
) row={record}
index={index}
onHandlePublishComponent={(row) => {
// TODO: 实现发布组件逻辑
console.log('Handle publish component', row);
}}
onGoToReview={(row) => {
// TODO: 实现查看审核逻辑
console.log('Go to review', row);
}}
onPublishOrRevokeComponent={(action, identifier, version) => {
// TODO: 实现公开/取消公开组件逻辑
console.log('Publish or revoke component', action, identifier, version);
}}
onNavTo={(id, type) => {
// TODO: 实现导航逻辑
console.log('Nav to', id, type);
}}
onSourceCodeView={(row) => {
// TODO: 实现查看源码逻辑
console.log('Source code view', row);
}}
onShowEdit={(row, index) => {
// TODO: 实现编辑组件逻辑
console.log('Show edit', row, index);
setSelectComponent(row);
setVisible(true);
}}
onCopyHandler={(row) => {
// TODO: 实现组件复制逻辑
console.log('Copy handler', row);
}}
onShareCollaboration={(row) => {
// TODO: 实现分享协作逻辑
console.log('Share collaboration', row);
}}
onExportComponent={(id) => {
// TODO: 实现导出组件逻辑
console.log('Export component', id);
}}
onStopComponentShow={(row) => {
// TODO: 实现下架组件逻辑
console.log('Stop component show', row);
}}
onRowDel={(row) => {
// 显示确认框
Modal.confirm({
title: '确认删除',
content: `确定要删除组件 "${row.name}" 吗?此操作不可恢复。`,
okButtonProps: { status: 'danger' },
onOk: async () => {
try {
const res = await remove(row.id);
console.log('res:', res);
Message.success('组件删除成功');
// 重新获取数据
fetchComponentData();
} catch (error) {
console.error('删除组件失败:', error);
Message.error('组件删除失败');
}
} }
];
const GlobalVarContainer = () => {
const [selectedItem, setSelectedItem] = useState('我的组件');
const [componentData, setComponentData] = useState<ComponentItem[]>([]);
const [pagination, setPagination] = useState({
totalCount: 0,
pageSize: 10,
totalPage: 0,
currPage: 1
}); });
const [loading, setLoading] = useState(false); }}
const [visible, setVisible] = useState(false); />
)
}
];
useEffect(() => { useEffect(() => {
if (selectedItem === '我的组件') { if (selectedItem === '我的组件') {
@ -170,7 +284,10 @@ const GlobalVarContainer = () => {
<Space split={<Divider type="vertical" />}> <Space split={<Divider type="vertical" />}>
<Button type="secondary" style={{ borderRadius: 4 }}></Button> <Button type="secondary" style={{ borderRadius: 4 }}></Button>
<Button type="outline" style={{ borderRadius: 4 }}></Button> <Button type="outline" style={{ borderRadius: 4 }}></Button>
<Button type="primary" style={{ borderRadius: 4 }} onClick={() => setVisible(true)}>+ <Button type="primary" style={{ borderRadius: 4 }} onClick={() => {
setSelectComponent(null);
setVisible(true);
}}>+
</Button> </Button>
</Space> </Space>
</div> </div>
@ -189,7 +306,6 @@ const GlobalVarContainer = () => {
current={pagination.currPage} current={pagination.currPage}
pageSize={pagination.pageSize} pageSize={pagination.pageSize}
total={pagination.totalCount} total={pagination.totalCount}
// showTotal={(total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`}
onChange={handlePageChange} onChange={handlePageChange}
/> />
</div> </div>
@ -200,6 +316,7 @@ const GlobalVarContainer = () => {
{/*新增弹窗*/} {/*新增弹窗*/}
<AddComponentModal <AddComponentModal
visible={visible} visible={visible}
baseInfo={selectComponent}
setVisible={setVisible} setVisible={setVisible}
onReFresh={fetchComponentData} onReFresh={fetchComponentData}
/> />

@ -8,6 +8,25 @@ export interface OpenWindowOptions {
[key: string]: any; [key: string]: any;
} }
export const getObjType = (obj) => {
const toString = Object.prototype.toString;
const map = {
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regExp',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Object]': 'object',
};
if (obj instanceof Element) {
return 'element';
}
return map[toString.call(obj)];
};
// 新窗口打开处理函数 // 新窗口打开处理函数
export function openWindow(url: string, opts?: OpenWindowOptions) { export function openWindow(url: string, opts?: OpenWindowOptions) {
const { target = '_blank', ...others } = opts || {}; const { target = '_blank', ...others } = opts || {};
@ -231,6 +250,32 @@ export const deserializeValue = (value: any): any => {
} }
}; };
/**
*
*/
export const deepClone = (data) => {
const type = getObjType(data);
let obj;
if (type === 'array') {
obj = [];
} else if (type === 'object') {
obj = {};
} else {
//不再具有下一层次
return data;
}
if (type === 'array') {
for (let i = 0, len = data.length; i < len; i++) {
obj.push(deepClone(data[i]));
}
} else if (type === 'object') {
for (const key in data) {
obj[key] = deepClone(data[key]);
}
}
return obj;
};
export const sleep = (ms: number): Promise<void> => { export const sleep = (ms: number): Promise<void> => {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise(resolve => setTimeout(resolve, ms));
}; };

Loading…
Cancel
Save