@yuku123/z-frontend-common 0.2.9 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +25 -0
- package/README.md +55 -42
- package/dist/index.js +1 -0
- package/package.json +49 -44
- package/src/components/ErrorBoundary/index.jsx +24 -77
- package/src/components/Layout/index.jsx +141 -38
- package/src/components/LoginPage/index.jsx +47 -21
- package/src/components/StatusTag/index.jsx +14 -12
- package/src/index.js +32 -38
- package/src/services/request.js +74 -4
- package/dist/z-frontend-common.css +0 -1
- package/dist/z-frontend-common.es.js +0 -51275
- package/dist/z-frontend-common.umd.js +0 -459
- package/src/components/Ctc/Layout.jsx +0 -330
- package/src/components/Ctc/Layout.module.css +0 -145
- package/src/components/Ctc/agentTeam/index.tsx +0 -308
- package/src/components/Ctc/login/AuthPage.module.css +0 -26
- package/src/components/Ctc/login/AuthPage.tsx +0 -235
- package/src/components/Ctc/login/index.less +0 -49
- package/src/components/Ctc/login/index.tsx +0 -142
- package/src/components/Ctc/uc/UCLogin.tsx +0 -31
- package/src/components/Ctc/uc/UCPhoneLogin.tsx +0 -62
- package/src/components/Ctc/uc/UCRegister.tsx +0 -37
- package/src/components/Ctc/uc/UCResetPassword.tsx +0 -69
- package/src/components/Ctc/uc/index.tsx +0 -15
- package/src/components/Ctc/userPanel/index.tsx +0 -999
- package/src/components/Ctc/webide/index.tsx +0 -272
- package/src/components/LowCode/LowCodeModel.jsx +0 -962
- package/src/components/LowCode/LowCodePage.jsx +0 -31
- package/src/components/LowCode/LowCodeRuntime.jsx +0 -335
- package/src/components/LowCode/MaterializePage.jsx +0 -235
- package/src/components/LowCode/index.js +0 -1
- package/src/components/MockPlatform/CurlImportModal.jsx +0 -362
- package/src/components/MockPlatform/EndpointsTab.jsx +0 -509
- package/src/components/MockPlatform/EnvironmentsTab.jsx +0 -212
- package/src/components/MockPlatform/MockTemplateHelper.jsx +0 -200
- package/src/components/MockPlatform/OpenApiImportModal.jsx +0 -305
- package/src/components/MockPlatform/RecordingsTab.jsx +0 -397
- package/src/components/MockPlatform/RequestLogsTab.jsx +0 -239
- package/src/components/MockPlatform/ScenariosTab.jsx +0 -236
- package/src/components/MockPlatform/TestCasesTab.jsx +0 -462
- package/src/components/MockPlatform/index.jsx +0 -127
- package/src/components/Overview.jsx +0 -74
- package/src/dev.jsx +0 -28
- package/src/services/agentTeam.js +0 -7
- package/src/services/api.js +0 -84
- package/src/services/ctcAc.js +0 -7
- package/src/services/ctcAcDomain.js +0 -7
- package/src/services/ctcAuthorization.js +0 -7
- package/src/services/ctcSurl.js +0 -7
- package/src/services/ctcUser.js +0 -7
- package/src/services/job.js +0 -7
- package/src/services/metaApp.js +0 -7
- package/src/services/privateConfig.js +0 -7
- package/src/services/webide.js +0 -6
- package/src/services/workspace.js +0 -21
- package/src/utils/request.ts +0 -102
|
@@ -1,462 +0,0 @@
|
|
|
1
|
-
import {useEffect, useState} from 'react'
|
|
2
|
-
import {
|
|
3
|
-
Alert,
|
|
4
|
-
Button,
|
|
5
|
-
Card,
|
|
6
|
-
Col,
|
|
7
|
-
Descriptions,
|
|
8
|
-
Drawer,
|
|
9
|
-
Form,
|
|
10
|
-
Input,
|
|
11
|
-
message,
|
|
12
|
-
Modal,
|
|
13
|
-
Popconfirm,
|
|
14
|
-
Row,
|
|
15
|
-
Select,
|
|
16
|
-
Space,
|
|
17
|
-
Statistic,
|
|
18
|
-
Table,
|
|
19
|
-
Tag,
|
|
20
|
-
Typography
|
|
21
|
-
} from 'antd'
|
|
22
|
-
import {
|
|
23
|
-
CodeOutlined,
|
|
24
|
-
DeleteOutlined,
|
|
25
|
-
EditOutlined,
|
|
26
|
-
FileSearchOutlined,
|
|
27
|
-
PlayCircleOutlined,
|
|
28
|
-
PlusOutlined,
|
|
29
|
-
ReloadOutlined
|
|
30
|
-
} from '@ant-design/icons'
|
|
31
|
-
import {mockPlatformApi} from '@/services/api'
|
|
32
|
-
import CurlImportModal from './CurlImportModal'
|
|
33
|
-
|
|
34
|
-
const {Text} = Typography
|
|
35
|
-
|
|
36
|
-
const PRIORITY_OPTIONS = [
|
|
37
|
-
{value: 'P0', label: 'P0 (最高)'},
|
|
38
|
-
{value: 'P1', label: 'P1 (高)'},
|
|
39
|
-
{value: 'P2', label: 'P2 (中)'},
|
|
40
|
-
{value: 'P3', label: 'P3 (低)'},
|
|
41
|
-
]
|
|
42
|
-
|
|
43
|
-
const PRIORITY_COLORS = {P0: 'red', P1: 'orange', P2: 'blue', P3: 'default'}
|
|
44
|
-
|
|
45
|
-
const STATUS_COLORS = {PASS: 'green', FAIL: 'red', SKIP: 'default'}
|
|
46
|
-
|
|
47
|
-
const BODY_MATCH_OPTIONS = [
|
|
48
|
-
{value: 'EXACT', label: '完全匹配'},
|
|
49
|
-
{value: 'CONTAINS', label: '包含'},
|
|
50
|
-
{value: 'REGEX', label: '正则'},
|
|
51
|
-
{value: 'JSONPATH', label: 'JSONPath'},
|
|
52
|
-
]
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* 测试用例 Tab - 用例库管理 + 单条/批量执行 + 执行结果查看
|
|
56
|
-
*/
|
|
57
|
-
export default function TestCasesTab() {
|
|
58
|
-
const [cases, setCases] = useState([])
|
|
59
|
-
const [loading, setLoading] = useState(false)
|
|
60
|
-
const [selectedRowKeys, setSelectedRowKeys] = useState([])
|
|
61
|
-
const [modalOpen, setModalOpen] = useState(false)
|
|
62
|
-
const [editing, setEditing] = useState(null)
|
|
63
|
-
const [resultOpen, setResultOpen] = useState(false)
|
|
64
|
-
const [result, setResult] = useState(null)
|
|
65
|
-
const [batchResult, setBatchResult] = useState(null)
|
|
66
|
-
const [batchOpen, setBatchOpen] = useState(false)
|
|
67
|
-
const [curlImportOpen, setCurlImportOpen] = useState(false)
|
|
68
|
-
const [form] = Form.useForm()
|
|
69
|
-
|
|
70
|
-
useEffect(() => {
|
|
71
|
-
loadCases()
|
|
72
|
-
}, [])
|
|
73
|
-
|
|
74
|
-
const loadCases = async () => {
|
|
75
|
-
setLoading(true)
|
|
76
|
-
try {
|
|
77
|
-
const res = await mockPlatformApi.cases.list()
|
|
78
|
-
setCases(Array.isArray(res) ? res : [])
|
|
79
|
-
} catch (e) {
|
|
80
|
-
message.warning('加载用例列表失败')
|
|
81
|
-
} finally {
|
|
82
|
-
setLoading(false)
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const handleCreate = () => {
|
|
87
|
-
setEditing(null)
|
|
88
|
-
form.resetFields()
|
|
89
|
-
form.setFieldsValue({
|
|
90
|
-
requestMethod: 'GET', expectedStatus: 200,
|
|
91
|
-
priority: 'P1', status: 1, expectedBodyMatchType: 'EXACT',
|
|
92
|
-
})
|
|
93
|
-
setModalOpen(true)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const handleEdit = (record) => {
|
|
97
|
-
setEditing(record)
|
|
98
|
-
form.setFieldsValue(record)
|
|
99
|
-
setModalOpen(true)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const handleSave = async () => {
|
|
103
|
-
try {
|
|
104
|
-
const values = await form.validateFields()
|
|
105
|
-
let assertions
|
|
106
|
-
try {
|
|
107
|
-
assertions = values.assertionsJson ? JSON.parse(values.assertionsJson) : null
|
|
108
|
-
} catch {
|
|
109
|
-
message.error('断言 JSON 格式错误');
|
|
110
|
-
return
|
|
111
|
-
}
|
|
112
|
-
const payload = {...values, assertions}
|
|
113
|
-
delete payload.assertionsJson
|
|
114
|
-
if (editing) {
|
|
115
|
-
await mockPlatformApi.cases.update(editing.caseCode, payload)
|
|
116
|
-
message.success('更新成功')
|
|
117
|
-
} else {
|
|
118
|
-
await mockPlatformApi.cases.create(payload)
|
|
119
|
-
message.success('创建成功')
|
|
120
|
-
}
|
|
121
|
-
setModalOpen(false)
|
|
122
|
-
loadCases()
|
|
123
|
-
} catch (e) {
|
|
124
|
-
if (!e?.errorFields) message.error('保存失败')
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const handleDelete = async (code) => {
|
|
129
|
-
try {
|
|
130
|
-
await mockPlatformApi.cases.delete(code)
|
|
131
|
-
message.success('删除成功')
|
|
132
|
-
loadCases()
|
|
133
|
-
} catch (e) {
|
|
134
|
-
message.error('删除失败')
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const handleRun = async (record) => {
|
|
139
|
-
try {
|
|
140
|
-
const res = await mockPlatformApi.cases.run(record.caseCode)
|
|
141
|
-
setResult(res)
|
|
142
|
-
setResultOpen(true)
|
|
143
|
-
if (res?.status === 'PASS') message.success('用例通过')
|
|
144
|
-
else message.error('用例失败: ' + (res?.error || res?.actualStatus))
|
|
145
|
-
loadCases()
|
|
146
|
-
} catch (e) {
|
|
147
|
-
message.error('执行失败: ' + e?.message)
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const handleBatchRun = async () => {
|
|
152
|
-
if (selectedRowKeys.length === 0) {
|
|
153
|
-
message.warning('请先选择用例')
|
|
154
|
-
return
|
|
155
|
-
}
|
|
156
|
-
try {
|
|
157
|
-
const res = await mockPlatformApi.cases.runBatch(selectedRowKeys)
|
|
158
|
-
setBatchResult(res)
|
|
159
|
-
setBatchOpen(true)
|
|
160
|
-
message.success(`批量执行完成: ${res?.pass} 通过, ${res?.fail} 失败`)
|
|
161
|
-
loadCases()
|
|
162
|
-
} catch (e) {
|
|
163
|
-
message.error('批量执行失败')
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const columns = [
|
|
168
|
-
{
|
|
169
|
-
title: '用例编码', dataIndex: 'caseCode', key: 'caseCode', width: 180,
|
|
170
|
-
render: (v) => <Text code>{v}</Text>
|
|
171
|
-
},
|
|
172
|
-
{title: '名称', dataIndex: 'caseName', key: 'caseName', width: 220},
|
|
173
|
-
{
|
|
174
|
-
title: '分组', dataIndex: 'caseGroup', key: 'caseGroup', width: 130,
|
|
175
|
-
render: (v) => v ? <Tag>{v}</Tag> : '-'
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
title: '方法', dataIndex: 'requestMethod', key: 'requestMethod', width: 80,
|
|
179
|
-
render: (v) => <Tag color="blue">{v}</Tag>
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
title: '路径', dataIndex: 'requestUrl', key: 'requestUrl',
|
|
183
|
-
ellipsis: true,
|
|
184
|
-
render: (v) => <Text code>{v}</Text>
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
title: '优先级', dataIndex: 'priority', key: 'priority', width: 80, align: 'center',
|
|
188
|
-
render: (v) => <Tag color={PRIORITY_COLORS[v]}>{v || 'P1'}</Tag>
|
|
189
|
-
},
|
|
190
|
-
{
|
|
191
|
-
title: '上次结果', dataIndex: 'lastRunResult', key: 'lastRunResult', width: 100, align: 'center',
|
|
192
|
-
render: (v) => v ? <Tag color={STATUS_COLORS[v]}>{v}</Tag> : '-'
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
title: '统计', key: 'stats', width: 130, align: 'center',
|
|
196
|
-
render: (_, r) => (
|
|
197
|
-
<Space size="small">
|
|
198
|
-
<Text type="success">{r.passCount || 0}</Text>/<Text type="danger">{r.failCount || 0}</Text>
|
|
199
|
-
<Text type="secondary">({r.runCount || 0})</Text>
|
|
200
|
-
</Space>
|
|
201
|
-
)
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
title: '操作', key: 'action', width: 220, fixed: 'right',
|
|
205
|
-
render: (_, record) => (
|
|
206
|
-
<Space size="small">
|
|
207
|
-
<Button size="small" type="link" icon={<PlayCircleOutlined/>}
|
|
208
|
-
onClick={() => handleRun(record)}>运行</Button>
|
|
209
|
-
<Button size="small" type="link" icon={<EditOutlined/>}
|
|
210
|
-
onClick={() => handleEdit(record)}/>
|
|
211
|
-
<Popconfirm title={`删除用例 ${record.caseCode}?`}
|
|
212
|
-
onConfirm={() => handleDelete(record.caseCode)}>
|
|
213
|
-
<Button size="small" type="link" danger icon={<DeleteOutlined/>}/>
|
|
214
|
-
</Popconfirm>
|
|
215
|
-
</Space>
|
|
216
|
-
)
|
|
217
|
-
},
|
|
218
|
-
]
|
|
219
|
-
|
|
220
|
-
return (
|
|
221
|
-
<div>
|
|
222
|
-
<Alert type="info" showIcon style={{marginBottom: 16}}
|
|
223
|
-
title="测试用例库"
|
|
224
|
-
description="对 Mock 接口进行断言验证。支持 7 种断言类型:STATUS / HEADER / BODY / JSON_PATH / REGEX / SCHEMA / RESPONSE_TIME。支持单条运行与批量运行,自动统计通过/失败次数。"
|
|
225
|
-
/>
|
|
226
|
-
|
|
227
|
-
<Space style={{marginBottom: 16}}>
|
|
228
|
-
<Button icon={<ReloadOutlined/>} onClick={loadCases}>刷新</Button>
|
|
229
|
-
<Button type="primary" icon={<PlusOutlined/>} onClick={handleCreate}>新建用例</Button>
|
|
230
|
-
<Button icon={<CodeOutlined/>} onClick={() => setCurlImportOpen(true)}>
|
|
231
|
-
粘贴 curl 导入
|
|
232
|
-
</Button>
|
|
233
|
-
<Button type="primary" icon={<PlayCircleOutlined/>}
|
|
234
|
-
onClick={handleBatchRun} disabled={selectedRowKeys.length === 0}>
|
|
235
|
-
批量运行 ({selectedRowKeys.length})
|
|
236
|
-
</Button>
|
|
237
|
-
</Space>
|
|
238
|
-
|
|
239
|
-
<Table
|
|
240
|
-
dataSource={cases}
|
|
241
|
-
columns={columns}
|
|
242
|
-
rowKey="id"
|
|
243
|
-
loading={loading}
|
|
244
|
-
rowSelection={{selectedRowKeys, onChange: setSelectedRowKeys}}
|
|
245
|
-
pagination={{pageSize: 10}}
|
|
246
|
-
scroll={{x: 1300}}
|
|
247
|
-
size="small"
|
|
248
|
-
/>
|
|
249
|
-
|
|
250
|
-
<Modal
|
|
251
|
-
title={editing ? `编辑用例: ${editing.caseCode}` : '新建测试用例'}
|
|
252
|
-
open={modalOpen}
|
|
253
|
-
onCancel={() => setModalOpen(false)}
|
|
254
|
-
onOk={handleSave}
|
|
255
|
-
width={750}
|
|
256
|
-
>
|
|
257
|
-
<Form form={form} layout="vertical" style={{marginTop: 16}}>
|
|
258
|
-
<Row gutter={16}>
|
|
259
|
-
<Col span={12}>
|
|
260
|
-
<Form.Item name="caseCode" label="用例编码" rules={[{required: true}]}>
|
|
261
|
-
<Input placeholder="case-user-login" disabled={!!editing}/>
|
|
262
|
-
</Form.Item>
|
|
263
|
-
</Col>
|
|
264
|
-
<Col span={12}>
|
|
265
|
-
<Form.Item name="caseName" label="用例名称">
|
|
266
|
-
<Input placeholder="用户登录成功用例"/>
|
|
267
|
-
</Form.Item>
|
|
268
|
-
</Col>
|
|
269
|
-
</Row>
|
|
270
|
-
<Row gutter={16}>
|
|
271
|
-
<Col span={8}>
|
|
272
|
-
<Form.Item name="caseGroup" label="用例分组">
|
|
273
|
-
<Input placeholder="login"/>
|
|
274
|
-
</Form.Item>
|
|
275
|
-
</Col>
|
|
276
|
-
<Col span={8}>
|
|
277
|
-
<Form.Item name="priority" label="优先级">
|
|
278
|
-
<Select options={PRIORITY_OPTIONS}/>
|
|
279
|
-
</Form.Item>
|
|
280
|
-
</Col>
|
|
281
|
-
<Col span={8}>
|
|
282
|
-
<Form.Item name="envCode" label="环境">
|
|
283
|
-
<Input placeholder="default"/>
|
|
284
|
-
</Form.Item>
|
|
285
|
-
</Col>
|
|
286
|
-
</Row>
|
|
287
|
-
<Row gutter={16}>
|
|
288
|
-
<Col span={6}>
|
|
289
|
-
<Form.Item name="requestMethod" label="方法">
|
|
290
|
-
<Select options={[
|
|
291
|
-
{value: 'GET', label: 'GET'},
|
|
292
|
-
{value: 'POST', label: 'POST'},
|
|
293
|
-
{value: 'PUT', label: 'PUT'},
|
|
294
|
-
{value: 'DELETE', label: 'DELETE'},
|
|
295
|
-
]}/>
|
|
296
|
-
</Form.Item>
|
|
297
|
-
</Col>
|
|
298
|
-
<Col span={18}>
|
|
299
|
-
<Form.Item name="requestUrl" label="请求 URL" rules={[{required: true}]}>
|
|
300
|
-
<Input placeholder="/api/mock/default/user/login"/>
|
|
301
|
-
</Form.Item>
|
|
302
|
-
</Col>
|
|
303
|
-
</Row>
|
|
304
|
-
<Row gutter={16}>
|
|
305
|
-
<Col span={12}>
|
|
306
|
-
<Form.Item name="requestQuery" label="查询参数">
|
|
307
|
-
<Input placeholder="debug=true"/>
|
|
308
|
-
</Form.Item>
|
|
309
|
-
</Col>
|
|
310
|
-
<Col span={12}>
|
|
311
|
-
<Form.Item name="expectedStatus" label="期望状态码">
|
|
312
|
-
<Select options={[
|
|
313
|
-
{value: 200, label: '200 OK'},
|
|
314
|
-
{value: 201, label: '201 Created'},
|
|
315
|
-
{value: 400, label: '400 Bad Request'},
|
|
316
|
-
{value: 401, label: '401 Unauthorized'},
|
|
317
|
-
{value: 404, label: '404 Not Found'},
|
|
318
|
-
{value: 500, label: '500 Server Error'},
|
|
319
|
-
]}/>
|
|
320
|
-
</Form.Item>
|
|
321
|
-
</Col>
|
|
322
|
-
</Row>
|
|
323
|
-
<Form.Item name="requestHeaders" label="请求头 (JSON)"
|
|
324
|
-
extra='例: {"Authorization": "Bearer xxx"}'>
|
|
325
|
-
<Input.TextArea rows={2} placeholder='{"X-Trace-Id": "abc"}'/>
|
|
326
|
-
</Form.Item>
|
|
327
|
-
<Form.Item name="requestBody" label="请求体">
|
|
328
|
-
<Input.TextArea rows={3} placeholder='{"username":"test","password":"xxx"}'/>
|
|
329
|
-
</Form.Item>
|
|
330
|
-
<Form.Item name="expectedBodyMatchType" label="Body 匹配方式">
|
|
331
|
-
<Select options={BODY_MATCH_OPTIONS}/>
|
|
332
|
-
</Form.Item>
|
|
333
|
-
<Form.Item name="expectedBody" label="期望 Body">
|
|
334
|
-
<Input.TextArea rows={3} placeholder='{"code": 200, "success": true}'/>
|
|
335
|
-
</Form.Item>
|
|
336
|
-
<Form.Item name="assertionsJson" label="断言列表 (JSON 数组)"
|
|
337
|
-
extra='例: [{"assertType":"STATUS","expected":200,"comparator":"EQUALS"}]'>
|
|
338
|
-
<Input.TextArea rows={4} placeholder='[{"assertType":"STATUS","expected":200}]'/>
|
|
339
|
-
</Form.Item>
|
|
340
|
-
<Form.Item name="description" label="描述">
|
|
341
|
-
<Input.TextArea rows={2}/>
|
|
342
|
-
</Form.Item>
|
|
343
|
-
</Form>
|
|
344
|
-
</Modal>
|
|
345
|
-
|
|
346
|
-
<Drawer
|
|
347
|
-
title={<Space><FileSearchOutlined/>用例执行结果</Space>}
|
|
348
|
-
open={resultOpen}
|
|
349
|
-
onClose={() => setResultOpen(false)}
|
|
350
|
-
width={650}
|
|
351
|
-
>
|
|
352
|
-
{result && (
|
|
353
|
-
<div>
|
|
354
|
-
<Row gutter={16} style={{marginBottom: 16}}>
|
|
355
|
-
<Col span={8}>
|
|
356
|
-
<Card>
|
|
357
|
-
<Statistic
|
|
358
|
-
title="结果"
|
|
359
|
-
value={result.status}
|
|
360
|
-
styles={{content: {color: STATUS_COLORS[result.status] === 'green' ? '#52c41a' : '#ff4d4f'}}}
|
|
361
|
-
/>
|
|
362
|
-
</Card>
|
|
363
|
-
</Col>
|
|
364
|
-
<Col span={8}>
|
|
365
|
-
<Card>
|
|
366
|
-
<Statistic title="实际状态码" value={result.actualStatus || '-'}/>
|
|
367
|
-
</Card>
|
|
368
|
-
</Col>
|
|
369
|
-
<Col span={8}>
|
|
370
|
-
<Card>
|
|
371
|
-
<Statistic title="耗时(ms)" value={result.durationMs || 0}/>
|
|
372
|
-
</Card>
|
|
373
|
-
</Col>
|
|
374
|
-
</Row>
|
|
375
|
-
|
|
376
|
-
<Descriptions column={1} size="small" bordered style={{marginBottom: 16}}>
|
|
377
|
-
<Descriptions.Item label="用例编码">{result.caseCode}</Descriptions.Item>
|
|
378
|
-
<Descriptions.Item label="用例名称">{result.caseName}</Descriptions.Item>
|
|
379
|
-
<Descriptions.Item label="错误信息">{result.error || '-'}</Descriptions.Item>
|
|
380
|
-
</Descriptions>
|
|
381
|
-
|
|
382
|
-
{result.assertionResults && (
|
|
383
|
-
<>
|
|
384
|
-
<Text strong>断言详情:</Text>
|
|
385
|
-
<Table
|
|
386
|
-
style={{marginTop: 8}}
|
|
387
|
-
dataSource={result.assertionResults}
|
|
388
|
-
columns={[
|
|
389
|
-
{title: '断言类型', dataIndex: 'assertType', key: 'assertType', width: 110},
|
|
390
|
-
{title: '表达式', dataIndex: 'expression', key: 'expression'},
|
|
391
|
-
{title: '期望值', dataIndex: 'expected', key: 'expected'},
|
|
392
|
-
{title: '实际值', dataIndex: 'actual', key: 'actual'},
|
|
393
|
-
{
|
|
394
|
-
title: '结果', dataIndex: 'passed', key: 'passed', width: 80,
|
|
395
|
-
render: (v) => v ? <Tag color="green">PASS</Tag> :
|
|
396
|
-
<Tag color="red">FAIL</Tag>
|
|
397
|
-
},
|
|
398
|
-
]}
|
|
399
|
-
pagination={false} size="small"
|
|
400
|
-
/>
|
|
401
|
-
</>
|
|
402
|
-
)}
|
|
403
|
-
|
|
404
|
-
<Text strong style={{marginTop: 16, display: 'block'}}>响应体:</Text>
|
|
405
|
-
<pre style={{
|
|
406
|
-
background: '#f5f5f5', padding: 12, borderRadius: 4,
|
|
407
|
-
maxHeight: 250, overflow: 'auto', fontSize: 12, fontFamily: 'monospace',
|
|
408
|
-
whiteSpace: 'pre-wrap', wordBreak: 'break-all',
|
|
409
|
-
}}>
|
|
410
|
-
{result.responseBody || '(empty)'}
|
|
411
|
-
</pre>
|
|
412
|
-
</div>
|
|
413
|
-
)}
|
|
414
|
-
</Drawer>
|
|
415
|
-
|
|
416
|
-
<Modal
|
|
417
|
-
title={`批量执行结果 (${batchResult?.total || 0} 条)`}
|
|
418
|
-
open={batchOpen}
|
|
419
|
-
onCancel={() => setBatchOpen(false)}
|
|
420
|
-
footer={<Button onClick={() => setBatchOpen(false)}>关闭</Button>}
|
|
421
|
-
width={800}
|
|
422
|
-
>
|
|
423
|
-
{batchResult && (
|
|
424
|
-
<>
|
|
425
|
-
<Row gutter={16} style={{marginBottom: 16}}>
|
|
426
|
-
<Col span={6}><Card><Statistic title="总数" value={batchResult.total}/></Card></Col>
|
|
427
|
-
<Col span={6}><Card><Statistic title="通过" value={batchResult.pass}
|
|
428
|
-
styles={{content: {color: '#52c41a'}}}/></Card></Col>
|
|
429
|
-
<Col span={6}><Card><Statistic title="失败" value={batchResult.fail}
|
|
430
|
-
styles={{content: {color: '#ff4d4f'}}}/></Card></Col>
|
|
431
|
-
<Col span={6}><Card><Statistic title="通过率"
|
|
432
|
-
value={`${batchResult.total > 0 ? Math.round((batchResult.pass / batchResult.total) * 100) : 0}%`}/></Card></Col>
|
|
433
|
-
</Row>
|
|
434
|
-
<Table
|
|
435
|
-
dataSource={batchResult.results || []}
|
|
436
|
-
columns={[
|
|
437
|
-
{title: '编码', dataIndex: 'caseCode', key: 'caseCode', width: 180},
|
|
438
|
-
{title: '名称', dataIndex: 'caseName', key: 'caseName'},
|
|
439
|
-
{
|
|
440
|
-
title: '结果', dataIndex: 'status', key: 'status', width: 80,
|
|
441
|
-
render: (v) => <Tag color={STATUS_COLORS[v]}>{v}</Tag>
|
|
442
|
-
},
|
|
443
|
-
{title: '耗时', dataIndex: 'durationMs', key: 'durationMs', width: 80, align: 'center'},
|
|
444
|
-
{title: '错误', dataIndex: 'error', key: 'error', ellipsis: true},
|
|
445
|
-
]}
|
|
446
|
-
pagination={false} size="small" rowKey="caseCode"
|
|
447
|
-
/>
|
|
448
|
-
</>
|
|
449
|
-
)}
|
|
450
|
-
</Modal>
|
|
451
|
-
|
|
452
|
-
<CurlImportModal
|
|
453
|
-
open={curlImportOpen}
|
|
454
|
-
onClose={() => setCurlImportOpen(false)}
|
|
455
|
-
onSuccess={(type, res) => {
|
|
456
|
-
message.success(type === 'case' ? '测试用例已导入,正在刷新列表' : 'Mock 端点已导入')
|
|
457
|
-
loadCases()
|
|
458
|
-
}}
|
|
459
|
-
/>
|
|
460
|
-
</div>
|
|
461
|
-
)
|
|
462
|
-
}
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import {useEffect, useState} from 'react'
|
|
2
|
-
import {Button, Card, message, Select, Space, Tabs, Tag, Typography} from 'antd'
|
|
3
|
-
import {DashboardOutlined, GlobalOutlined, ReloadOutlined} from '@ant-design/icons'
|
|
4
|
-
import EndpointsTab from './EndpointsTab'
|
|
5
|
-
import EnvironmentsTab from './EnvironmentsTab'
|
|
6
|
-
import ScenariosTab from './ScenariosTab'
|
|
7
|
-
import TestCasesTab from './TestCasesTab'
|
|
8
|
-
import RecordingsTab from './RecordingsTab'
|
|
9
|
-
import RequestLogsTab from './RequestLogsTab'
|
|
10
|
-
import {mockPlatformApi} from '@/services/api'
|
|
11
|
-
|
|
12
|
-
const {Text} = Typography
|
|
13
|
-
|
|
14
|
-
const TAB_ITEMS = [
|
|
15
|
-
{key: 'endpoints', label: 'Mock 端点', component: EndpointsTab},
|
|
16
|
-
{key: 'environments', label: '环境 (MOCK/REAL/MIXED)', component: EnvironmentsTab},
|
|
17
|
-
{key: 'scenarios', label: '场景状态机', component: ScenariosTab},
|
|
18
|
-
{key: 'cases', label: '测试用例', component: TestCasesTab},
|
|
19
|
-
{key: 'recordings', label: '录制回放', component: RecordingsTab},
|
|
20
|
-
{key: 'request-logs', label: '请求日志', component: RequestLogsTab},
|
|
21
|
-
]
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Mock 平台主页 - 全功能 Mock 平台
|
|
25
|
-
*
|
|
26
|
-
* 能力对标 WireMock / Apifox / MockServer / ApiPost:
|
|
27
|
-
* 1. Mock 端点管理 - URL/Header/Query/Body 多维匹配、优先级、模板渲染
|
|
28
|
-
* 2. 环境隔离 - MOCK | REAL | MIXED 三种模式,无缝切换真实/Mock
|
|
29
|
-
* 3. 场景状态机 - 业务流驱动的 Mock 集合(登录、下单、支付)
|
|
30
|
-
* 4. 测试用例库 - 7 种断言,单条/批量运行,自动统计
|
|
31
|
-
* 5. 录制回放 - 录制真实流量,一键转换为 Mock 集合
|
|
32
|
-
* 6. 请求日志 - 排查"为什么没命中",性能分析
|
|
33
|
-
*/
|
|
34
|
-
export default function MockPlatformPage() {
|
|
35
|
-
const [activeTab, setActiveTab] = useState('endpoints')
|
|
36
|
-
const [envs, setEnvs] = useState([])
|
|
37
|
-
const [activeEnv, setActiveEnv] = useState('default')
|
|
38
|
-
const [platformStats, setPlatformStats] = useState(null)
|
|
39
|
-
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
loadEnvs()
|
|
42
|
-
loadPlatformStats()
|
|
43
|
-
}, [])
|
|
44
|
-
|
|
45
|
-
const loadEnvs = async () => {
|
|
46
|
-
try {
|
|
47
|
-
const res = await mockPlatformApi.environments.list()
|
|
48
|
-
const list = Array.isArray(res) ? res : []
|
|
49
|
-
setEnvs(list)
|
|
50
|
-
// Set default env if current is not in list
|
|
51
|
-
if (list.length > 0 && !list.find(e => e.envCode === activeEnv)) {
|
|
52
|
-
const def = list.find(e => e.isDefault === 1) || list[0]
|
|
53
|
-
setActiveEnv(def.envCode)
|
|
54
|
-
}
|
|
55
|
-
} catch (e) {
|
|
56
|
-
// backend not ready - keep defaults
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const loadPlatformStats = async () => {
|
|
61
|
-
try {
|
|
62
|
-
const stats = await mockPlatformApi.stats()
|
|
63
|
-
setPlatformStats(stats)
|
|
64
|
-
} catch (e) { /* ignore */
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const ActiveComponent = TAB_ITEMS.find(t => t.key === activeTab)?.component
|
|
69
|
-
|
|
70
|
-
return (
|
|
71
|
-
<div>
|
|
72
|
-
<Card style={{marginBottom: 16}} bodyStyle={{padding: 16}}>
|
|
73
|
-
<Space size="large" wrap>
|
|
74
|
-
<Space>
|
|
75
|
-
<DashboardOutlined style={{fontSize: 18, color: '#1890ff'}}/>
|
|
76
|
-
<Text strong style={{fontSize: 16}}>Mock 平台</Text>
|
|
77
|
-
<Tag color="blue">WireMock 级</Tag>
|
|
78
|
-
</Space>
|
|
79
|
-
<Space>
|
|
80
|
-
<GlobalOutlined/>
|
|
81
|
-
<Text>当前环境:</Text>
|
|
82
|
-
<Select
|
|
83
|
-
value={activeEnv}
|
|
84
|
-
onChange={setActiveEnv}
|
|
85
|
-
style={{width: 200}}
|
|
86
|
-
options={envs.map(e => ({
|
|
87
|
-
value: e.envCode,
|
|
88
|
-
label: `${e.envName || e.envCode} (${e.envType})`,
|
|
89
|
-
}))}
|
|
90
|
-
/>
|
|
91
|
-
</Space>
|
|
92
|
-
{platformStats && (
|
|
93
|
-
<Space size="large">
|
|
94
|
-
<Text type="secondary">端点: <Text strong>{platformStats.totalEndpoints}</Text></Text>
|
|
95
|
-
<Text type="secondary">启用: <Text strong
|
|
96
|
-
style={{color: '#52c41a'}}>{platformStats.activeEndpoints}</Text></Text>
|
|
97
|
-
<Text type="secondary">场景: <Text strong>{platformStats.totalScenarios}</Text></Text>
|
|
98
|
-
<Text type="secondary">用例: <Text strong>{platformStats.totalCases}</Text></Text>
|
|
99
|
-
</Space>
|
|
100
|
-
)}
|
|
101
|
-
<Button icon={<ReloadOutlined/>} onClick={() => {
|
|
102
|
-
loadEnvs();
|
|
103
|
-
loadPlatformStats()
|
|
104
|
-
}}>刷新</Button>
|
|
105
|
-
</Space>
|
|
106
|
-
</Card>
|
|
107
|
-
|
|
108
|
-
<Tabs
|
|
109
|
-
activeKey={activeTab}
|
|
110
|
-
onChange={setActiveTab}
|
|
111
|
-
type="card"
|
|
112
|
-
items={TAB_ITEMS.map(tab => ({
|
|
113
|
-
key: tab.key,
|
|
114
|
-
label: tab.label,
|
|
115
|
-
children: tab.component === EndpointsTab
|
|
116
|
-
? <EndpointsTab activeEnvCode={activeEnv}/>
|
|
117
|
-
: tab.component === EnvironmentsTab
|
|
118
|
-
? <EnvironmentsTab onEnvChange={(code) => {
|
|
119
|
-
setActiveEnv(code);
|
|
120
|
-
message.success(`已切换到环境: ${code}`)
|
|
121
|
-
}}/>
|
|
122
|
-
: <tab.component/>,
|
|
123
|
-
}))}
|
|
124
|
-
/>
|
|
125
|
-
</div>
|
|
126
|
-
)
|
|
127
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import {Card, Col, Row, Statistic, Typography} from 'antd'
|
|
2
|
-
import {AuditOutlined, FileTextOutlined, SettingOutlined, UserOutlined,} from '@ant-design/icons'
|
|
3
|
-
|
|
4
|
-
const {Title, Text, Paragraph} = Typography
|
|
5
|
-
|
|
6
|
-
const Overview = () => {
|
|
7
|
-
return (
|
|
8
|
-
<div style={{padding: '0'}}>
|
|
9
|
-
<div style={{marginBottom: 24}}>
|
|
10
|
-
<Title level={2} style={{margin: 0}}>Z-One Company 统一管理平台</Title>
|
|
11
|
-
<Text type="secondary">一站式企业级应用管理平台</Text>
|
|
12
|
-
</div>
|
|
13
|
-
|
|
14
|
-
<Row gutter={16} style={{marginBottom: 24}}>
|
|
15
|
-
<Col span={6}>
|
|
16
|
-
<Card>
|
|
17
|
-
<Statistic
|
|
18
|
-
title="配置中心"
|
|
19
|
-
value={0}
|
|
20
|
-
prefix={<SettingOutlined/>}
|
|
21
|
-
/>
|
|
22
|
-
</Card>
|
|
23
|
-
</Col>
|
|
24
|
-
<Col span={6}>
|
|
25
|
-
<Card>
|
|
26
|
-
<Statistic
|
|
27
|
-
title="任务中心"
|
|
28
|
-
value={0}
|
|
29
|
-
prefix={<FileTextOutlined/>}
|
|
30
|
-
/>
|
|
31
|
-
</Card>
|
|
32
|
-
</Col>
|
|
33
|
-
<Col span={6}>
|
|
34
|
-
<Card>
|
|
35
|
-
<Statistic
|
|
36
|
-
title="工作流"
|
|
37
|
-
value={0}
|
|
38
|
-
prefix={<AuditOutlined/>}
|
|
39
|
-
/>
|
|
40
|
-
</Card>
|
|
41
|
-
</Col>
|
|
42
|
-
<Col span={6}>
|
|
43
|
-
<Card>
|
|
44
|
-
<Statistic
|
|
45
|
-
title="用户中心"
|
|
46
|
-
value={0}
|
|
47
|
-
prefix={<UserOutlined/>}
|
|
48
|
-
/>
|
|
49
|
-
</Card>
|
|
50
|
-
</Col>
|
|
51
|
-
</Row>
|
|
52
|
-
|
|
53
|
-
<Card title="关于 Z-One Company">
|
|
54
|
-
<Paragraph>
|
|
55
|
-
Z-One Company 是一站式企业级应用管理平台,整合了配置中心、任务中心、工作流、用户中心等多个模块。
|
|
56
|
-
</Paragraph>
|
|
57
|
-
<Paragraph>
|
|
58
|
-
<strong>核心模块:</strong>
|
|
59
|
-
</Paragraph>
|
|
60
|
-
<ul>
|
|
61
|
-
<li>配置中心 - 集中管理应用配置</li>
|
|
62
|
-
<li>任务中心 - 任务调度与执行</li>
|
|
63
|
-
<li>工作流 - 流程审批与管理</li>
|
|
64
|
-
<li>用户中心 - 统一身份认证与权限管理</li>
|
|
65
|
-
<li>调度中心 - 定时任务管理</li>
|
|
66
|
-
<li>Mist - 密钥管理</li>
|
|
67
|
-
<li>元数据 - API与应用管理</li>
|
|
68
|
-
</ul>
|
|
69
|
-
</Card>
|
|
70
|
-
</div>
|
|
71
|
-
)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export default Overview
|