UNPKG

whistle.mock-plugins

Version:

Whistle 插件,用于快速创建 API 模拟数据

166 lines (155 loc) 5.38 kB
import React, { useState } from 'react'; import { Button, Input, message, Space, Tabs, Tag } from 'antd'; import { DownloadOutlined, ThunderboltOutlined, CopyOutlined } from '@ant-design/icons'; import axios from 'axios'; const { TextArea } = Input; /** * JSON 转 Mock.js 模板 * 简单转换:字符串 -> '@string', 数字 -> '@integer(1,100)', 布尔 -> '@boolean' * 保留对象结构 */ const jsonToMockTemplate = (obj, depth = 0) => { if (obj === null) return null; if (typeof obj === 'string') { // 保留特定格式字符串的提示 if (/^\d{4}-\d{2}-\d{2}/.test(obj)) return '@date'; if (/^\d{4}-\d{2}-\d{2}T/.test(obj)) return '@datetime'; if (/^https?:\/\//.test(obj)) return '@url'; if (/^\w+@\w+\.\w+/.test(obj)) return '@email'; if (/^1[3-9]\d{9}$/.test(obj)) return '@string("number", 11)'; return '@csentence(3, 10)'; } if (typeof obj === 'number') { if (Number.isInteger(obj)) return '@integer(1, 999)'; return '@float(1, 100, 2, 2)'; } if (typeof obj === 'boolean') return '@boolean'; if (Array.isArray(obj)) { if (obj.length === 0) return []; const template = jsonToMockTemplate(obj[0], depth + 1); // 用 |1-5 表示数组长度随机 return [template]; } if (typeof obj === 'object') { const result = {}; for (const [key, value] of Object.entries(obj)) { result[key] = jsonToMockTemplate(value, depth + 1); } return result; } return obj; }; const RealResponseRecorder = ({ urlPattern, onApply }) => { const [fetchUrl, setFetchUrl] = useState(''); const [loading, setLoading] = useState(false); const [response, setResponse] = useState(null); const [activeTab, setActiveTab] = useState('preview'); const handleFetch = async () => { const targetUrl = fetchUrl.trim() || urlPattern; if (!targetUrl) { message.warning('请输入目标 URL'); return; } setLoading(true); try { const res = await axios.post('/cgi-bin/proxy-fetch', { url: targetUrl, method: 'GET', }); if (res.data.code === 0) { setResponse(res.data.data); setActiveTab('preview'); message.success('抓取成功'); } else { throw new Error(res.data.message); } } catch (err) { message.error('抓取失败: ' + (err.response?.data?.message || err.message)); } finally { setLoading(false); } }; const handleGenerateMock = () => { if (!response?.body) return; try { const json = JSON.parse(response.body); const template = jsonToMockTemplate(json); const mockTemplate = JSON.stringify(template, null, 2); onApply?.(mockTemplate); message.success('Mock 模板已生成'); } catch (e) { message.error('响应不是 JSON,无法生成模板: ' + e.message); } }; const handleCopy = () => { if (response?.body) { navigator.clipboard.writeText(response.body); message.success('已复制'); } }; return ( <div style={{ padding: '16px 0' }}> <Space direction="vertical" style={{ width: '100%' }}> <Space style={{ width: '100%' }}> <Input placeholder={urlPattern || '输入真实接口 URL'} value={fetchUrl} onChange={(e) => setFetchUrl(e.target.value)} style={{ width: 400 }} onPressEnter={handleFetch} /> <Button type="primary" icon={<ThunderboltOutlined />} loading={loading} onClick={handleFetch} > 抓取真实响应 </Button> </Space> {response && ( <> <Space> <Tag color={response.status >= 200 && response.status < 300 ? 'success' : 'error'}> {response.status} {response.statusText} </Tag> <Button size="small" icon={<CopyOutlined />} onClick={handleCopy}> 复制原文 </Button> <Button size="small" icon={<DownloadOutlined />} onClick={handleGenerateMock} type="primary"> 生成 Mock 模板 </Button> </Space> <Tabs activeKey={activeTab} onChange={setActiveTab}> <Tabs.TabPane tab="响应预览" key="preview"> <TextArea value={response.body} readOnly rows={12} style={{ fontFamily: 'monospace', fontSize: 12 }} /> </Tabs.TabPane> <Tabs.TabPane tab="生成的 Mock 模板" key="mock"> <TextArea value={(() => { try { const json = JSON.parse(response.body); const template = jsonToMockTemplate(json); return JSON.stringify(template, null, 2); } catch (e) { return '// 响应不是 JSON,无法生成模板\n' + response.body; } })()} readOnly rows={12} style={{ fontFamily: 'monospace', fontSize: 12 }} /> </Tabs.TabPane> </Tabs> </> )} </Space> </div> ); }; export default RealResponseRecorder;