whistle.mock-plugins
Version:
Whistle 插件,用于快速创建 API 模拟数据
472 lines (420 loc) • 15.3 kB
JavaScript
import React, { useState, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import AppLayout from '../components/AppLayout';
import { Card, Row, Col, Button, Modal, Form, Input, message, Switch, Empty, Spin, Typography, Tooltip, Badge } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined, ApiOutlined, ExportOutlined, InfoCircleOutlined, CalendarOutlined } from '@ant-design/icons';
import '../styles/mock-data.css';
const { Text, Title, Paragraph } = Typography;
const { TextArea } = Input;
const MockData = () => {
const history = useHistory();
const [mockFeatures, setMockFeatures] = useState([]);
const [showModal, setShowModal] = useState(false);
const [currentFeature, setCurrentFeature] = useState(null);
const [formData, setFormData] = useState({
name: '',
description: '',
active: true
});
const [loading, setLoading] = useState(true);
const [form] = Form.useForm();
// 加载功能列表
useEffect(() => {
fetchFeatures();
}, []);
// 获取所有功能模块
const fetchFeatures = async () => {
try {
setLoading(true);
const response = await fetch('/cgi-bin/features');
const result = await response.json();
if (result.code === 0) {
setMockFeatures(result.data || []);
} else {
console.error('获取功能模块失败:', result.message);
message.error('获取功能模块失败: ' + result.message);
}
} catch (error) {
console.error('获取功能模块错误:', error);
message.error('获取功能模块失败, 请检查网络连接');
} finally {
setLoading(false);
}
};
const openModal = (feature = null) => {
if (feature) {
setCurrentFeature(feature);
form.setFieldsValue({
name: feature.name,
description: feature.description,
active: feature.active !== false
});
} else {
setCurrentFeature(null);
form.resetFields();
form.setFieldsValue({
active: true
});
}
setShowModal(true);
};
const closeModal = () => {
setShowModal(false);
};
const handleSubmit = async (values) => {
try {
const featureData = {
...values
};
// 如果是编辑已有功能,添加ID
if (currentFeature) {
featureData.id = currentFeature.id;
}
setLoading(true);
const response = await fetch('/cgi-bin/features', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(featureData)
});
const result = await response.json();
if (result.code === 0) {
// 刷新功能列表
message.success(currentFeature ? '功能模块更新成功' : '功能模块创建成功');
fetchFeatures();
closeModal();
} else {
message.error('操作失败: ' + result.message);
}
} catch (error) {
console.error('保存功能模块错误:', error);
message.error('保存失败: ' + error.message);
} finally {
setLoading(false);
}
};
const handleToggleActive = async (id, currentActive) => {
try {
const response = await fetch(`/cgi-bin/features?id=${id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
active: !currentActive
})
});
const result = await response.json();
if (result.code === 0) {
message.success(`${currentActive ? '禁用' : '启用'}功能模块成功`);
// 更新本地状态
setMockFeatures(mockFeatures.map(feature =>
feature.id === id ? { ...feature, active: !currentActive } : feature
));
} else {
message.error(`${currentActive ? '禁用' : '启用'}功能模块失败: ${result.message}`);
}
} catch (error) {
console.error(`${currentActive ? '禁用' : '启用'}功能模块错误:`, error);
message.error(`操作失败: ${error.message}`);
}
};
const deleteFeature = async (id) => {
Modal.confirm({
title: '确定要删除此功能吗?',
content: '这将删除所有相关的接口和模拟数据,此操作无法恢复。',
okText: '删除',
okType: 'danger',
cancelText: '取消',
onOk: async () => {
try {
setLoading(true);
const response = await fetch(`/cgi-bin/features?id=${id}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.code === 0) {
message.success('功能模块已成功删除');
// 更新本地状态
setMockFeatures(mockFeatures.filter(f => f.id !== id));
} else {
message.error('删除失败: ' + result.message);
}
} catch (error) {
console.error('删除功能错误:', error);
message.error('操作失败: ' + error.message);
} finally {
setLoading(false);
}
}
});
};
const viewInterfaces = (feature) => {
// 导航到该功能的接口列表页面
history.push(`/interface/${feature.id}`);
};
const exportFeatureConfig = async (feature) => {
try {
// 获取该功能的所有接口
const response = await fetch(`/cgi-bin/interfaces?featureId=${feature.id}`);
const result = await response.json();
// 创建完整配置
const config = {
...feature,
interfaces: result.code === 0 ? result.data : []
};
const dataStr = JSON.stringify(config, null, 2);
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
const exportFileDefaultName = `whistle-mock-feature-${feature.id}.json`;
const linkElement = document.createElement('a');
linkElement.setAttribute('href', dataUri);
linkElement.setAttribute('download', exportFileDefaultName);
linkElement.click();
message.success('配置导出成功');
} catch (error) {
console.error('导出配置错误:', error);
message.error('导出失败: ' + error.message);
}
};
const importFeatureConfig = () => {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.onchange = async (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async (event) => {
try {
const config = JSON.parse(event.target.result);
// 验证导入的配置
if (!config.name) {
message.error('无效的配置文件: 缺少功能名称');
return;
}
// 创建新功能
const featureData = {
name: config.name,
description: config.description || '',
active: config.active !== false
};
// 保存功能
const featureResponse = await fetch('/cgi-bin/features', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(featureData)
});
const featureResult = await featureResponse.json();
if (featureResult.code === 0) {
const newFeature = featureResult.data;
// 导入接口配置
if (Array.isArray(config.interfaces) && config.interfaces.length > 0) {
message.loading({ content: '正在导入接口配置...', key: 'importInfo' });
let successCount = 0;
let errorCount = 0;
for (const interfaceItem of config.interfaces) {
try {
// 创建接口,使用新功能ID
const interfaceData = {
...interfaceItem,
featureId: newFeature.id,
id: undefined // 不使用原接口ID
};
await fetch('/cgi-bin/interfaces', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(interfaceData)
});
successCount++;
} catch (error) {
console.error('导入接口配置失败:', error);
errorCount++;
}
}
if (errorCount > 0) {
message.info({ content: `导入完成,成功 ${successCount} 个接口,失败 ${errorCount} 个接口`, key: 'importInfo', duration: 3 });
} else {
message.success({ content: `成功导入 ${successCount} 个接口`, key: 'importInfo', duration: 2 });
}
} else {
message.success('功能导入成功(无接口配置)');
}
// 刷新功能列表
fetchFeatures();
} else {
message.error('导入功能失败: ' + featureResult.message);
}
} catch (error) {
console.error('导入配置错误:', error);
message.error('导入失败: ' + error.message);
}
};
reader.readAsText(file);
};
input.click();
};
// 渲染功能模块卡片
const renderFeatureCard = (feature) => {
const formattedDate = feature.createdAt
? new Date(feature.createdAt).toLocaleDateString()
: '未知日期';
return (
<Col xs={24} sm={12} md={8} lg={6} key={feature.id} style={{ marginBottom: 16 }}>
<Badge.Ribbon
text={feature.active ? '已启用' : '已禁用'}
color={feature.active ? '#52c41a' : '#f5222d'}
style={{ display: 'block' }}
>
<Card
hoverable
className={`feature-card ${!feature.active ? 'inactive-feature' : ''}`}
actions={[
<Tooltip title="管理接口">
<ApiOutlined key="interfaces" onClick={() => viewInterfaces(feature)} />
</Tooltip>,
<Tooltip title="编辑功能">
<EditOutlined key="edit" onClick={() => openModal(feature)} />
</Tooltip>,
<Tooltip title="删除功能">
<DeleteOutlined key="delete" onClick={() => deleteFeature(feature.id)} />
</Tooltip>,
<Tooltip title="导出配置">
<ExportOutlined key="export" onClick={() => exportFeatureConfig(feature)} />
</Tooltip>
]}
>
<div className="feature-card-content">
<div className="feature-name">
<Title level={4} ellipsis={{ tooltip: feature.name }}>
{feature.name}
</Title>
<Switch
checked={feature.active}
onChange={() => handleToggleActive(feature.id, feature.active)}
size="small"
/>
</div>
<Paragraph className="feature-description" ellipsis={{ rows: 2, expandable: false, tooltip: feature.description }}>
{feature.description || '无描述'}
</Paragraph>
<div className="feature-stat">
<span>
<InfoCircleOutlined /> {feature.interfaceCount || 0} 个接口
</span>
<span className="feature-date">
<CalendarOutlined /> {formattedDate}
</span>
</div>
</div>
</Card>
</Badge.Ribbon>
</Col>
);
};
return (
<AppLayout>
<div className="page-container">
<div className="page-title-bar">
<div>
<h1 className="page-title">功能模块管理</h1>
<div className="page-description">
创建和管理功能模块,为每个功能配置独立的接口
</div>
</div>
<div className="page-actions">
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => openModal()}
>
新建功能
</Button>
<Button
type="primary"
icon={<ExportOutlined />}
onClick={importFeatureConfig}
style={{ backgroundColor: '#52c41a', borderColor: '#52c41a' }}
>
导入功能
</Button>
</div>
</div>
<div className="feature-list-container">
{loading ? (
<div className="loading">
<div className="loading-spinner"></div>
<div>正在加载功能模块...</div>
</div>
) : mockFeatures.length > 0 ? (
<Row gutter={[16, 16]}>
{mockFeatures.map(feature => renderFeatureCard(feature))}
</Row>
) : (
<div className="empty-data">
<div className="empty-icon">📂</div>
<div className="empty-text">暂无功能,请点击"新建功能"按钮创建</div>
<div className="empty-actions">
<button className="create-button" onClick={() => openModal()}>
创建新功能
</button>
<button className="import-button-large" onClick={importFeatureConfig}>
导入已有功能
</button>
</div>
</div>
)}
</div>
</div>
<Modal
title={currentFeature ? '编辑功能' : '新建功能'}
open={showModal}
onCancel={closeModal}
footer={null}
destroyOnClose
width={500}
>
<Form
form={form}
layout="vertical"
onFinish={handleSubmit}
initialValues={{
name: '',
description: '',
active: true
}}
>
<Form.Item
name="name"
label="功能名称"
rules={[{ required: true, message: '请输入功能名称' }]}
>
<Input placeholder="请输入功能名称" />
</Form.Item>
<Form.Item
name="description"
label="功能描述"
>
<TextArea
placeholder="请输入功能描述(可选)"
autoSize={{ minRows: 3, maxRows: 6 }}
/>
</Form.Item>
<div className="form-actions">
<Button onClick={closeModal}>
取消
</Button>
<Button type="primary" htmlType="submit" loading={loading}>
确定
</Button>
</div>
</Form>
</Modal>
</AppLayout>
);
};
export default MockData;