You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

763 lines
24 KiB
TypeScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import React, { useEffect, useState } from 'react';
import {
Modal,
Form,
Input,
Grid,
Space,
Divider,
Button,
Table,
TableColumnProps,
Select,
Message,
Upload,
Progress
} from '@arco-design/web-react';
import { IconPlus, IconEdit } from '@arco-design/web-react/icon';
import styles from './style/addComponentModal.module.less';
import EditorSection from '@/components/EditorSection';
import AddApiModal from '@/pages/componentDevelopment/componentList/addApiModal';
import CompReview from '@/pages/componentDevelopment/componentList/compReview';
import { getComponentClassify } from '@/api/componentClassify';
import {
compProjectValidate,
compSubmit,
getTagList,
copyAll,
getMyComponentList,
getCooperationComponentList
} from '@/api/componentBase';
import { copyDesign } from '@/api/componentMarket';
import { codeInit, getComponentDesign, updateComponentDesign } from '@/api/componentDevelopProcess';
const FormItem = Form.Item;
const Option = Select.Option;
// 定义组件模式类型
type ComponentMode = 'create' | 'edit' | 'copy';
const AddComponentModal = ({ visible, baseInfo, setVisible, onReFresh, mode = 'create' }) => {
const [selectedImage, setSelectedImage] = useState('');
const [description, setDescription] = useState('');
const [classifyList, setClassifyList] = useState([]);
const [showSaveBtn, setShowSaveBtn] = useState(false);
const [tagsList, setTagsList] = useState([]);
const [componentInfo, setComponentInfo] = useState(null); // 合并后的组件信息状态
const [componentDesignData, setComponentDesignData] = useState([]); // 新增状态用于存储接口设计数据
const [selectedApiData, setSelectedApiData] = useState(null); // 新增状态用于存储选中的API数据
const [showApiModal, setShowApiModal] = useState(false);
const [created, setCreated] = useState(false); // 是否提交了组件信息
const [codeInitLoading, setCodeInitLoading] = useState(false); // 代码初始化按钮 loading
const [file, setFile] = useState(null);
const [form] = Form.useForm();
// 组件语言选项
const codeLanguageOptions = [
{ label: 'Java:8', value: 'Java' },
{ label: 'Python:3.10.12', value: 'Python' }
];
// 组件类型选项
const componentTypeOptions = [
{ label: '普通组件', value: 'normal' },
{ label: '监听组件', value: 'loop' }
];
const cs = `arco-upload-list-item${file && file.status === 'error' ? ' is-error' : ''}`;
// 判断是否为复制模式
const isCopyMode = mode === 'copy';
// 判断是否为编辑模式
const isEditMode = mode === 'edit';
// 判断是否为创建模式
const isCreateMode = mode === 'create';
const columns: TableColumnProps[] = [
{
title: '接口名称',
dataIndex: 'ident'
},
{
title: '输入参数',
dataIndex: 'parameters',
render: (_, record) => {
if (!record.parameters || record.parameters.length === 0) {
return '-';
}
return record.parameters.map(param => param.ident).join(', ');
}
},
{
title: '输出参数',
dataIndex: 'responses',
render: (_, record) => {
if (!record.responses || record.responses.length === 0) {
return '-';
}
return record.responses.map(response => response.ident).join(', ');
}
},
{
title: '操作',
dataIndex: 'op',
width: 240,
render: (_, record) => (
<>
<Button
type="text"
onClick={() => {
// 设置选中的API数据用于编辑时回显
setSelectedApiData(record);
setShowApiModal(true);
}}
disabled={isCopyMode || (componentInfo && !['DEFAULT', 'DESIGN'].includes(componentInfo.componentStatus))}
>
</Button>
<Button
type="text"
status="danger"
disabled={isCopyMode || (componentInfo && !['DEFAULT', 'DESIGN'].includes(componentInfo.componentStatus))}
onClick={async () => {
// 显示删除确认框
Modal.confirm({
title: '确认删除',
content: `确定要删除接口 "${record.ident}" 吗?此操作不可恢复。`,
okButtonProps: { status: 'danger' },
onOk: async () => {
// 从 componentDesignData 中过滤掉要删除的记录
const updatedData = componentDesignData.filter(item => item.ident !== record.ident);
// 构造要提交的数据
const params = {
baseInfo: componentInfo,
operates: updatedData // 保持数组格式
};
// 调用接口更新数据
const res: any = await updateComponentDesign(params);
try {
if (res.code === 200) {
Message.success('删除成功');
// 更新本地状态
setComponentDesignData(updatedData);
}
else {
Message.error(res.message || '删除失败');
// 删除失败时恢复数据
setComponentDesignData(componentDesignData);
}
} catch (error) {
console.error('删除失败:', error);
Message.error('删除失败');
// 删除失败时恢复数据
setComponentDesignData(componentDesignData);
}
}
});
}}
>
</Button>
</>
)
}
];
const getComponentClassifyList = async () => {
const res: any = await getComponentClassify('component');
if (res.code === 200) {
const data = [];
res.data.forEach((item) => {
data.push({
label: item.classifyName,
value: item.id
});
});
setClassifyList(data);
}
};
const getTageList = async () => {
const res: any = await getTagList();
if (res.code === 200) setTagsList(res.data);
};
const validateProjectId = async (projectId: string) => {
if (!projectId) return;
try {
const res = await compProjectValidate(projectId);
if (res.data === false) {
// 项目标识已存在,设置表单字段错误
form.setFields({
projectId: {
value: projectId,
error: {
message: '项目标识已存在'
}
}
});
}
else {
// 项目标识可用,清除错误
form.setFields({
projectId: {
value: projectId,
error: null
}
});
}
} catch (error) {
// API调用出错
form.setFields({
projectId: {
value: projectId,
error: {
message: '验证项目标识时发生错误'
}
}
});
}
};
const onSubmit = async (showMessage = true) => {
try {
await form.validate();
const formData = form.getFields();
const params: any = {
...componentInfo,
name: formData.name,
projectId: formData.projectId,
logoUrl: selectedImage,
desc: description,
componentClassify: formData.componentClassify,
codeLanguage: formData.codeLanguage, // 直接使用value值
type: formData.type, // 直接使用value值
tags: formData.tags
};
// 如果是编辑模式componentInfo存在则传递id
if (componentInfo && componentInfo.id) {
params.id = componentInfo.id;
}
const res: any = await compSubmit(params);
if (res.code === 200) {
setComponentInfo(res.data);
setShowSaveBtn(true);
showMessage && Message.success(isEditMode ? '组件信息更新成功' : isCopyMode ? '组件复制成功' : '组件信息提交完成,可继续组件接口设计');
onReFresh();
// 最终保存成功后关闭弹窗
if (showSaveBtn && showMessage) {
setVisible(false);
return;
}
// 提交成功后获取组件设计数据
if (res.data && res.data.id) {
setCreated(true);
getComponentDesignData(res.data.id);
}
}
else {
showMessage && Message.error(isEditMode ? '组件信息更新失败' : isCopyMode ? '组件复制失败' : '组件信息提交失败');
}
} catch (error) {
console.error('提交/更新失败:', error);
}
};
const fetchComponentData = async (extraParams: any = {}) => {
try {
const params = {
currPage: 1,
pageSize: 99,
...extraParams
};
const res: any = await getMyComponentList(params);
if (res.code === 200 && res.data.list.length > 0) {
setComponentInfo(res.data.list[0]);
getComponentDesignData(res.data.list[0].id);
}
} catch (error) {
console.error('获取组件列表失败:', error);
Message.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('获取组件设计数据失败');
}
};
// 仅复制设计
const handleCopyDesign = async () => {
try {
await form.validate();
const formData = form.getFields();
// 构造要提交的数据
const params = {
...componentInfo,
name: formData.name,
projectId: formData.projectId,
logoUrl: selectedImage,
desc: description,
componentClassify: formData.componentClassify,
codeLanguage: formData.codeLanguage, // 直接使用value值
type: formData.type, // 直例使用value值
tags: formData.tags
};
const res: any = await copyDesign(params);
if (res.code === 200) {
Message.success('仅复制设计成功');
onReFresh && onReFresh();
setVisible(false);
}
else {
Message.error(res.message || '复制失败');
}
} catch (error) {
console.error('复制失败:', error);
}
};
// 复制设计和代码
const handleCopyAll = async () => {
try {
await form.validate();
const formData = form.getFields();
// 构造要提交的数据
const params = {
...componentInfo,
name: formData.name,
projectId: formData.projectId,
logoUrl: selectedImage,
desc: description,
componentClassify: formData.componentClassify,
codeLanguage: formData.codeLanguage, // 直接使用value值
type: formData.type, // 直接使用value값
tags: formData.tags
};
const res: any = await copyAll(params);
if (res.code === 200) {
Message.success('复制设计和代码成功');
onReFresh && onReFresh();
setVisible(false);
}
else {
Message.error(res.message || '复制失败');
}
} catch (error) {
console.error('复制失败:', error);
}
};
const handleCodeInit = async () => {
setCodeInitLoading(true);
Message.warning('代码初始化中');
const res: any = await codeInit(componentInfo?.id);
setCodeInitLoading(false);
if (res.code === 200) {
Message.success('代码初始化完成');
fetchComponentData({ id: componentInfo?.id });
}
else Message.error(res.message || '代码初始化失败');
};
useEffect(() => {
getComponentClassifyList();
getTageList();
}, []);
// 当baseInfo或visible变化时设置表单初始值
useEffect(() => {
if (visible && (isEditMode || isCopyMode)) {
// 设置表单字段值
form.setFieldsValue({
name: baseInfo.name || '',
projectId: (isCopyMode ? '' : baseInfo.projectId) || '',
componentClassify: baseInfo.componentClassify || '',
codeLanguage: baseInfo.codeLanguage || '',
tags: baseInfo.tags || [],
type: baseInfo.type || ''
});
// 设置其他状态
setSelectedImage(baseInfo.logoUrl || '');
setDescription(baseInfo.desc || '');
setShowSaveBtn(true);
setComponentInfo(baseInfo);
// 获取组件设计数据
if (isEditMode || isCopyMode) {
getComponentDesignData(baseInfo.id);
}
// else {
// // 复制模式下清空接口设计数据
// setComponentDesignData([]);
// }
}
else if (visible && isCreateMode) {
// 重置表单
form.resetFields();
setSelectedImage('');
setDescription('');
setShowSaveBtn(false);
setComponentDesignData([]); // 清空接口设计数据
setComponentInfo(null);
}
}, [baseInfo, visible, form, isEditMode, isCopyMode, isCreateMode]);
const modalFooter = () => {
return (
<Space size={30}>
<Button size="small" type="outline" style={{ borderRadius: 5 }} onClick={() => setVisible(false)}></Button>
{isCopyMode ? (
<>
<Button
size="small"
type="primary"
style={{ borderRadius: 5 }}
onClick={handleCopyDesign}
>
</Button>
<Button
size="small"
type="primary"
style={{ borderRadius: 5 }}
onClick={handleCopyAll}
>
</Button>
</>
) : showSaveBtn ? (
<Button size="small" type="primary" style={{ borderRadius: 5 }} onClick={() => onSubmit()}>
{isEditMode ? '更新' : '保存'}
</Button>
) : (
<Button size="small" type="primary" style={{ borderRadius: 5 }} onClick={() => onSubmit()}>
{isEditMode ? '更新组件信息' : '提交组件信息'}
</Button>
)}
</Space>
);
};
const UploadImage = () => {
// 如果是编辑模式且有componentInfo.logoUrl则使用componentInfo中的logoUrl
const initialImageUrl = componentInfo && componentInfo.logoUrl ? componentInfo.logoUrl : null;
return (
<Upload
action="/api/v1/bpms-workbench/fileSystem/fileUpload"
fileList={file ? [file] : initialImageUrl ? [{ url: initialImageUrl, status: 'done' }] : []}
showUploadList={false}
onChange={(_, currentFile: any) => {
setFile({
...currentFile,
url: URL.createObjectURL(currentFile.originFile)
});
if (currentFile.status === 'done') setSelectedImage(currentFile.response.data.link);
}}
onProgress={(currentFile) => {
setFile(currentFile);
}}
>
<div className={cs}>
{(file && file.url) || initialImageUrl ? (
<div className="arco-upload-list-item-picture custom-upload-avatar">
<img src={file?.url || initialImageUrl} />
<div className="arco-upload-list-item-picture-mask">
<IconEdit />
</div>
{file && file.status === 'uploading' && file.percent < 100 && (
<Progress
percent={file.percent}
type="circle"
size="mini"
style={{
position: 'absolute',
left: '50%',
top: '50%',
transform: 'translateX(-50%) translateY(-50%)'
}}
/>
)}
</div>
) : (
<div className="arco-upload-trigger-picture">
<div className="arco-upload-trigger-picture-text">
<IconPlus />
</div>
</div>
)}
</div>
</Upload>
);
};
return (
<Modal
className={styles['add-component-modal']}
title={isEditMode ? '编辑组件' : isCopyMode ? '复制组件' : '新增组件'}
visible={visible}
autoFocus={false}
focusLock={true}
style={{ width: '75%' }}
footer={modalFooter}
onCancel={() => setVisible(false)}
afterClose={() => {
// 关闭模态框后重置表单
form.resetFields();
setSelectedImage('');
setDescription('');
setShowSaveBtn(false);
setFile(null);
setCreated(false);
}}
>
<Form form={form} className={styles['add-component-container']}>
<div className={styles['first-half']}>
<div className={styles['component-preview']}>
<FormItem label="图标:" field="logoUrl">
<UploadImage />
</FormItem>
<FormItem label="组件预览:">
<div
style={{
width: '80%',
height: 200
}}
>
<CompReview componentDesignData={componentDesignData} />
</div>
</FormItem>
</div>
<div className={styles['component-info']}>
<Grid.Row gutter={8}>
<Grid.Col span={10}>
<FormItem label="名称:" field="name" required rules={[
{
validator(value, cb) {
if (!value) {
return cb('请输入名称');
}
return cb();
}
}
]}>
<Input style={{ width: '90%' }} allowClear placeholder="请输入名称" />
</FormItem>
</Grid.Col>
<Grid.Col span={14}>
<FormItem label="代码标识:" field="projectId" required rules={[
{
validator(value, cb) {
if (!value) {
return cb('请输入代码标识');
}
return cb();
}
}
]}>
<Input
style={{ width: '90%' }}
allowClear
placeholder="请输入代码标识"
disabled={created || isEditMode}
onChange={(e) => validateProjectId(e)}
/>
</FormItem>
</Grid.Col>
</Grid.Row>
<Grid.Row gutter={8}>
<Grid.Col span={10}>
<FormItem label="分类:" field="componentClassify" required rules={[
{
validator(value, cb) {
if (!value) {
return cb('请选择分类');
}
return cb();
}
}
]}>
<Select
placeholder="请选择分类"
style={{ width: '90%' }}
>
{classifyList.map((option, index) => (
<Option key={option.id} value={option.label}>
{option.label}
</Option>
))}
</Select>
</FormItem>
</Grid.Col>
<Grid.Col span={14}>
<FormItem label="组件语言:" field="codeLanguage" required rules={[
{
validator(value, cb) {
if (!value) {
return cb('请选择组件语言');
}
return cb();
}
}
]}>
<Select
placeholder="请选择组件语言"
style={{ width: '90%' }}
disabled={created || isEditMode || isCopyMode}
>
{codeLanguageOptions.map((option) => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
</FormItem>
</Grid.Col>
</Grid.Row>
<Grid.Row gutter={8}>
<Grid.Col span={10}>
<FormItem label="标签:" field="tags">
<Select
placeholder="请选择标签"
allowCreate
mode="multiple"
style={{ width: '90%' }}
>
{tagsList.map((option, index) => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
</FormItem>
</Grid.Col>
<Grid.Col span={14}>
<FormItem label="组件类型:" field="type" required rules={[
{
validator(value, cb) {
if (!value) {
return cb('请选择组件类型');
}
return cb();
}
}
]}>
<Select
placeholder="请选择组件类型"
style={{ width: '90%' }}
disabled={created || isEditMode}
>
{componentTypeOptions.map((option) => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
</FormItem>
</Grid.Col>
</Grid.Row>
<div className={styles['markdown-editor']}>
<div className={styles['markdown-label']}></div>
<EditorSection
visible={visible}
initialContent={description}
/>
</div>
</div>
</div>
{showSaveBtn && !isCopyMode && <div className={styles['last-half']}>
<div className={styles['last-half-header']}>
<p> </p>
<Space split={<Divider type="vertical" />}>
<Button
size="small"
type="secondary"
style={{ borderRadius: 5 }}
loading={codeInitLoading}
disabled={(!componentDesignData || componentDesignData.length === 0) || componentInfo && !['DEFAULT', 'DESIGN'].includes(componentInfo.componentStatus)}
onClick={() => handleCodeInit()}
>
</Button>
<Button
size="mini"
type="primary"
style={{ borderRadius: 5 }}
onClick={() => {
setSelectedApiData(null); // 确保新增时清空选中的API数据
setShowApiModal(true);
}}
disabled={componentInfo && !['DEFAULT', 'DESIGN'].includes(componentInfo.componentStatus)}
>
+
</Button>
</Space>
</div>
<Table columns={columns} data={componentDesignData} pagination={false} />
<AddApiModal
visible={showApiModal}
baseInfo={componentInfo}
componentDesignProgress={selectedApiData} //
componentDesignData={componentDesignData}
onUpdateComponentDesign={(params) => {
// 更新成功后重新获取组件设计数据
if (componentInfo && componentInfo.id) {
getComponentDesignData(componentInfo.id);
}
}}
onCancel={() => {
setShowApiModal(false);
setSelectedApiData(null); // 重置选中的API数据
}}
onOk={() => {
setShowApiModal(false);
setSelectedApiData(null); // 重置选中的API数据
}}
/>
</div>}
</Form>
</Modal>
);
};
export default AddComponentModal;