|
|
import React, { useState, useRef, useEffect } from 'react';
|
|
|
import { Modal, Button, Message, Tabs } from '@arco-design/web-react';
|
|
|
import { IconUpload, IconDelete, IconFile } from '@arco-design/web-react/icon';
|
|
|
import styles from './style/importComponentModal.module.less';
|
|
|
|
|
|
interface ParsedComponentInfo {
|
|
|
baseInfo?: any;
|
|
|
operates?: any[];
|
|
|
}
|
|
|
|
|
|
interface ImportComponentModalProps {
|
|
|
visible: boolean;
|
|
|
onCancel: () => void;
|
|
|
onOk: (files: FileItem[]) => void;
|
|
|
onFileSelect: (file: File) => Promise<ParsedComponentInfo[] | ParsedComponentInfo | null>;
|
|
|
loading: boolean;
|
|
|
}
|
|
|
|
|
|
interface FileItem {
|
|
|
file: File;
|
|
|
componentInfo: ParsedComponentInfo[]; // 该文件包含的组件列表
|
|
|
}
|
|
|
|
|
|
const ImportComponentModal: React.FC<ImportComponentModalProps> = ({
|
|
|
visible,
|
|
|
onCancel,
|
|
|
onOk,
|
|
|
onFileSelect,
|
|
|
loading
|
|
|
}) => {
|
|
|
const [fileItems, setFileItems] = useState<FileItem[]>([]);
|
|
|
const [activeTabKey, setActiveTabKey] = useState<string>('0');
|
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
|
|
|
|
// 当弹窗关闭时清空所有状态
|
|
|
useEffect(() => {
|
|
|
if (!visible) {
|
|
|
setFileItems([]);
|
|
|
setActiveTabKey('0');
|
|
|
}
|
|
|
}, [visible]);
|
|
|
|
|
|
const updateFileItem = (file: File, componentInfo: ParsedComponentInfo[] | ParsedComponentInfo) => {
|
|
|
const componentList = Array.isArray(componentInfo) ? componentInfo : [componentInfo];
|
|
|
let nextActiveKey = '0';
|
|
|
|
|
|
setFileItems((prev) => {
|
|
|
const existingIndex = prev.findIndex((item) => item.file.name === file.name);
|
|
|
|
|
|
if (existingIndex >= 0) {
|
|
|
nextActiveKey = String(existingIndex);
|
|
|
const nextItems = [...prev];
|
|
|
nextItems[existingIndex] = {
|
|
|
file,
|
|
|
componentInfo: componentList
|
|
|
};
|
|
|
return nextItems;
|
|
|
}
|
|
|
|
|
|
nextActiveKey = String(prev.length);
|
|
|
return [
|
|
|
...prev,
|
|
|
{
|
|
|
file,
|
|
|
componentInfo: componentList
|
|
|
}
|
|
|
];
|
|
|
});
|
|
|
|
|
|
setActiveTabKey(nextActiveKey);
|
|
|
};
|
|
|
|
|
|
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
const selectedFiles = Array.from(event.target.files ?? []);
|
|
|
if (selectedFiles.length === 0) return;
|
|
|
|
|
|
const validFiles = selectedFiles.filter((file) => file.name.match(/\.zip$/i));
|
|
|
const invalidFileCount = selectedFiles.length - validFiles.length;
|
|
|
|
|
|
if (invalidFileCount > 0) {
|
|
|
Message.error('请选择 ZIP 压缩包文件');
|
|
|
}
|
|
|
|
|
|
for (const file of validFiles) {
|
|
|
const parsedComponentInfo = await onFileSelect(file);
|
|
|
|
|
|
if (!parsedComponentInfo) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
updateFileItem(file, parsedComponentInfo);
|
|
|
}
|
|
|
|
|
|
event.target.value = '';
|
|
|
};
|
|
|
|
|
|
const handleSelectFile = () => {
|
|
|
fileInputRef.current?.click();
|
|
|
};
|
|
|
|
|
|
// 删除文件
|
|
|
const handleDeleteFile = (index: number) => {
|
|
|
const newFileItems = fileItems.filter((_, i) => i !== index);
|
|
|
setFileItems(newFileItems);
|
|
|
|
|
|
// 如果删除的是当前激活的标签页,切换到第一个
|
|
|
if (String(index) === activeTabKey && newFileItems.length > 0) {
|
|
|
setActiveTabKey('0');
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const handleUpload = () => {
|
|
|
|
|
|
if (fileItems.length === 0) {
|
|
|
Message.warning('请至少选择一个组件');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
onOk(fileItems);
|
|
|
};
|
|
|
|
|
|
const handleClose = () => {
|
|
|
setFileItems([]);
|
|
|
setActiveTabKey('0');
|
|
|
onCancel();
|
|
|
};
|
|
|
|
|
|
return (
|
|
|
<Modal
|
|
|
title="导入组件"
|
|
|
visible={visible}
|
|
|
onCancel={handleClose}
|
|
|
style={{ width: 900 }}
|
|
|
footer={
|
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
|
<div>
|
|
|
<Button onClick={handleSelectFile} type="outline" icon={<IconUpload />}>
|
|
|
添加文件
|
|
|
</Button>
|
|
|
</div>
|
|
|
<div>
|
|
|
<Button onClick={handleClose} style={{ marginRight: 12 }}>
|
|
|
取消
|
|
|
</Button>
|
|
|
<Button
|
|
|
type="primary"
|
|
|
onClick={handleUpload}
|
|
|
disabled={fileItems.length === 0}
|
|
|
loading={loading}
|
|
|
>
|
|
|
确认导入
|
|
|
</Button>
|
|
|
</div>
|
|
|
</div>
|
|
|
}
|
|
|
>
|
|
|
<input
|
|
|
ref={fileInputRef}
|
|
|
multiple
|
|
|
type="file"
|
|
|
accept=".zip"
|
|
|
style={{ display: 'none' }}
|
|
|
onChange={handleFileChange}
|
|
|
/>
|
|
|
|
|
|
<div className={styles['import-modal-content']}>
|
|
|
{fileItems.length === 0 && (
|
|
|
<div className={styles['empty-state']}>
|
|
|
<p className={styles['empty-text']}>请点击下方"添加文件"按钮选择组件压缩包</p>
|
|
|
<p className={styles['empty-hint']}>支持 .zip 格式文件,可添加多个文件</p>
|
|
|
</div>
|
|
|
)}
|
|
|
|
|
|
{fileItems.length > 0 && (
|
|
|
<Tabs
|
|
|
activeTab={activeTabKey}
|
|
|
onChange={setActiveTabKey}
|
|
|
type="card"
|
|
|
>
|
|
|
{fileItems.map((fileItem, fileIndex) => {
|
|
|
const currentComponent = fileItem.componentInfo[0];
|
|
|
const baseInfo = currentComponent?.baseInfo || {};
|
|
|
const operates = currentComponent?.operates || [];
|
|
|
return (
|
|
|
<Tabs.TabPane
|
|
|
key={String(fileIndex)}
|
|
|
title={
|
|
|
<div className={styles['tab-title']}>
|
|
|
<IconFile style={{ marginRight: 4 }} />
|
|
|
<span>{fileItem.file.name}</span>
|
|
|
<Button
|
|
|
type="text"
|
|
|
size="mini"
|
|
|
icon={<IconDelete />}
|
|
|
status="danger"
|
|
|
onClick={(e) => {
|
|
|
e.stopPropagation();
|
|
|
handleDeleteFile(fileIndex);
|
|
|
}}
|
|
|
style={{ marginLeft: 8 }}
|
|
|
/>
|
|
|
</div>
|
|
|
}
|
|
|
>
|
|
|
<div className={styles['file-content']}>
|
|
|
<div className={styles['component-list']}>
|
|
|
<div
|
|
|
className={styles['component-card']}
|
|
|
>
|
|
|
<div className={styles['card-header']}>
|
|
|
<span className={styles['component-name']} style={{ fontWeight: 600, fontSize: 16 }}>
|
|
|
{baseInfo.name || baseInfo.identifier || baseInfo.projectId || '组件信息'}
|
|
|
</span>
|
|
|
</div>
|
|
|
{/* 组件基本信息 */}
|
|
|
<div className={styles['component-info']}>
|
|
|
{baseInfo.projectId && (
|
|
|
<div className={styles['info-item']}>
|
|
|
<span className={styles['info-label']}>组件标识:</span>
|
|
|
<span className={styles['info-value']}>{baseInfo.projectId}</span>
|
|
|
</div>
|
|
|
)}
|
|
|
{baseInfo.codeLanguage && (
|
|
|
<div className={styles['info-item']}>
|
|
|
<span className={styles['info-label']}>代码语言:</span>
|
|
|
<span className={styles['info-value']}>{baseInfo.codeLanguage}</span>
|
|
|
</div>
|
|
|
)}
|
|
|
{baseInfo.componentClassify && (
|
|
|
<div className={styles['info-item']}>
|
|
|
<span className={styles['info-label']}>组件分类:</span>
|
|
|
<span className={styles['info-value']}>{baseInfo.componentClassify}</span>
|
|
|
</div>
|
|
|
)}
|
|
|
{baseInfo.desc && (
|
|
|
<div className={styles['component-desc']}>
|
|
|
{baseInfo.desc}
|
|
|
</div>
|
|
|
)}
|
|
|
</div>
|
|
|
|
|
|
{/* 接口列表 */}
|
|
|
{operates.length > 0 && (
|
|
|
<div className={styles['operates-section']}>
|
|
|
<div className={styles['section-title']}>
|
|
|
接口列表 ({operates.length})
|
|
|
</div>
|
|
|
<div className={styles['operates-list']}>
|
|
|
{operates.map((operate: any, opIndex: number) => (
|
|
|
<div key={opIndex} className={styles['operate-item']}>
|
|
|
<div className={styles['operate-header']}>
|
|
|
<span className={styles['operate-ident']}>
|
|
|
{operate.ident}
|
|
|
</span>
|
|
|
<span className={styles['operate-type']}>
|
|
|
{operate.type}
|
|
|
</span>
|
|
|
</div>
|
|
|
{operate.name && (
|
|
|
<div className={styles['operate-name']}>{operate.name}</div>
|
|
|
)}
|
|
|
<div className={styles['operate-params']}>
|
|
|
{operate.parameters && operate.parameters.length > 0 && (
|
|
|
<span className={styles['param-info']}>
|
|
|
输入: {operate.parameters.length} 个参数
|
|
|
</span>
|
|
|
)}
|
|
|
{operate.responses && operate.responses.length > 0 && (
|
|
|
<span className={styles['param-info']}>
|
|
|
输出: {operate.responses.length} 个参数
|
|
|
</span>
|
|
|
)}
|
|
|
</div>
|
|
|
</div>
|
|
|
))}
|
|
|
</div>
|
|
|
</div>
|
|
|
)}
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</Tabs.TabPane>
|
|
|
);
|
|
|
})}
|
|
|
</Tabs>
|
|
|
)}
|
|
|
</div>
|
|
|
</Modal>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
export default ImportComponentModal;
|