whistle.mock-plugins 1.1.2-dev.2 → 1.1.3-beta.1

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.
@@ -0,0 +1,702 @@
1
+ /**
2
+ * MockDataV2.js - 功能模块管理页面(重构后版本)
3
+ * 这是重构后的新版本界面,提供更好的用户体验和功能优化
4
+ * 当前内容与V1版本相同,等待后续重构
5
+ */
6
+
7
+ import React, { useState, useEffect } from 'react';
8
+ import { useHistory } from 'react-router-dom';
9
+ import AppLayout from '../components/AppLayout';
10
+ import {
11
+ Form, Input, Modal, Button, message, Card, Switch,
12
+ Row, Col, Badge, Popconfirm, Alert, Space, Select,
13
+ Tooltip, Menu, Dropdown, Pagination, AutoComplete
14
+ } from 'antd';
15
+ import {
16
+ PlusOutlined, DeleteOutlined, EditOutlined, SettingOutlined,
17
+ ExportOutlined, ImportOutlined, SearchOutlined, FilterOutlined,
18
+ SortAscendingOutlined, ReloadOutlined, ClearOutlined, DownloadOutlined
19
+ } from '@ant-design/icons';
20
+ import '../styles/mock-data.css';
21
+
22
+ const { Option } = Select;
23
+ const { TextArea } = Input;
24
+
25
+ // 刷新缓存工具函数
26
+ const flushCache = async () => {
27
+ try {
28
+ await fetch('/cgi-bin/flushCache', { method: 'POST' });
29
+ } catch (error) {
30
+ console.error('刷新缓存失败:', error);
31
+ throw error;
32
+ }
33
+ };
34
+
35
+
36
+ const MockData = () => {
37
+ const history = useHistory();
38
+ const [mockFeatures, setMockFeatures] = useState([]);
39
+ const [showModal, setShowModal] = useState(false);
40
+ const [currentFeature, setCurrentFeature] = useState(null);
41
+ const [formData, setFormData] = useState({
42
+ name: '',
43
+ description: '',
44
+ active: true
45
+ });
46
+ const [loading, setLoading] = useState(true);
47
+ const [form] = Form.useForm();
48
+
49
+ // 排序和分页配置(支持缓存)
50
+ const [listConfig, setListConfig] = useState(() => {
51
+ const cached = localStorage.getItem('feature-list-config');
52
+ return cached ? JSON.parse(cached) : {
53
+ sortBy: 'name', // name, createdAt, interfaceCount, active
54
+ sortOrder: 'ascend', // ascend, descend
55
+ pageSize: 12,
56
+ current: 1
57
+ };
58
+ });
59
+
60
+ // 保存列表配置到localStorage
61
+ const saveListConfig = (config) => {
62
+ const newConfig = { ...listConfig, ...config };
63
+ setListConfig(newConfig);
64
+ localStorage.setItem('feature-list-config', JSON.stringify(newConfig));
65
+ };
66
+
67
+ // 加载功能列表
68
+ useEffect(() => {
69
+ fetchFeatures();
70
+ }, []);
71
+
72
+ // 获取所有功能模块
73
+ const fetchFeatures = async () => {
74
+ try {
75
+ setLoading(true);
76
+ const response = await fetch('/cgi-bin/features');
77
+ const result = await response.json();
78
+
79
+ if (result.code === 0) {
80
+ setMockFeatures(result.data || []);
81
+ } else {
82
+ console.error('获取功能模块失败:', result.message);
83
+ message.error('获取功能模块失败: ' + result.message);
84
+ }
85
+ } catch (error) {
86
+ console.error('获取功能模块错误:', error);
87
+ message.error('获取功能模块失败, 请检查网络连接');
88
+ } finally {
89
+ setLoading(false);
90
+ }
91
+ };
92
+
93
+ const openModal = (feature = null) => {
94
+ setCurrentFeature(feature);
95
+ if (feature) {
96
+ setFormData({
97
+ name: feature.name,
98
+ description: feature.description || '',
99
+ active: feature.active !== false
100
+ });
101
+ form.setFieldsValue({
102
+ name: feature.name,
103
+ description: feature.description || '',
104
+ active: feature.active !== false
105
+ });
106
+ } else {
107
+ setFormData({
108
+ name: '',
109
+ description: '',
110
+ active: true
111
+ });
112
+ form.resetFields();
113
+ }
114
+ setShowModal(true);
115
+ };
116
+
117
+ const handleSubmit = async (values) => {
118
+ console.log('表单提交数据:', values);
119
+ try {
120
+ const submitData = {
121
+ name: values.name.trim(),
122
+ description: values.description?.trim() || '',
123
+ active: values.active !== false
124
+ };
125
+
126
+ console.log('处理后的提交数据:', submitData);
127
+
128
+ if (currentFeature) {
129
+ // 更新功能模块
130
+ const response = await fetch(`/cgi-bin/features?id=${currentFeature.id}`, {
131
+ method: 'PUT',
132
+ headers: {
133
+ 'Content-Type': 'application/json',
134
+ },
135
+ body: JSON.stringify(submitData),
136
+ });
137
+
138
+ const result = await response.json();
139
+ console.log('更新响应:', result);
140
+
141
+ if (result.code === 0) {
142
+ message.success('功能模块更新成功');
143
+ setShowModal(false);
144
+ fetchFeatures();
145
+
146
+ // 刷新缓存
147
+ await flushCache();
148
+ } else {
149
+ message.error('功能模块更新失败: ' + (result.message || '未知错误'));
150
+ }
151
+ } else {
152
+ // 创建新功能模块
153
+ const response = await fetch('/cgi-bin/features', {
154
+ method: 'POST',
155
+ headers: {
156
+ 'Content-Type': 'application/json',
157
+ },
158
+ body: JSON.stringify(submitData),
159
+ });
160
+
161
+ const result = await response.json();
162
+ console.log('创建响应:', result);
163
+
164
+ if (result.code === 0) {
165
+ message.success('功能模块创建成功');
166
+ setShowModal(false);
167
+ fetchFeatures();
168
+
169
+ // 刷新缓存
170
+ await flushCache();
171
+ } else {
172
+ message.error('功能模块创建失败: ' + (result.message || '未知错误'));
173
+ }
174
+ }
175
+ } catch (error) {
176
+ console.error('操作失败:', error);
177
+ message.error('操作失败: ' + error.message);
178
+ }
179
+ };
180
+
181
+ const handleDelete = async (id) => {
182
+ try {
183
+ const response = await fetch(`/cgi-bin/features?id=${id}`, {
184
+ method: 'DELETE',
185
+ });
186
+
187
+ const result = await response.json();
188
+ if (result.code === 0) {
189
+ message.success('功能模块删除成功');
190
+ fetchFeatures();
191
+
192
+ // 刷新缓存
193
+ await flushCache();
194
+ } else {
195
+ message.error('功能模块删除失败: ' + (result.message || '未知错误'));
196
+ }
197
+ } catch (error) {
198
+ console.error('删除失败:', error);
199
+ message.error('删除失败: ' + error.message);
200
+ }
201
+ };
202
+
203
+ const handleToggleActive = async (id, currentActive) => {
204
+ try {
205
+ const response = await fetch(`/cgi-bin/features?id=${id}`, {
206
+ method: 'PUT',
207
+ headers: {
208
+ 'Content-Type': 'application/json',
209
+ },
210
+ body: JSON.stringify({
211
+ active: !currentActive
212
+ }),
213
+ });
214
+
215
+ const result = await response.json();
216
+ if (result.code === 0) {
217
+ message.success(`功能模块${!currentActive ? '启用' : '禁用'}成功`);
218
+ fetchFeatures();
219
+
220
+ // 刷新缓存
221
+ await flushCache();
222
+ } else {
223
+ message.error(`功能模块${!currentActive ? '启用' : '禁用'}失败: ` + (result.message || '未知错误'));
224
+ }
225
+ } catch (error) {
226
+ console.error(`${!currentActive ? '启用' : '禁用'}失败:`, error);
227
+ message.error(`${!currentActive ? '启用' : '禁用'}失败: ` + error.message);
228
+ }
229
+ };
230
+
231
+ const gotoInterfaceManagement = (featureId) => {
232
+ history.push(`/interface/${featureId}`);
233
+ };
234
+
235
+ // 导出功能配置
236
+ const exportFeatureConfig = async () => {
237
+ try {
238
+ const response = await fetch('/cgi-bin/export-features');
239
+ const result = await response.json();
240
+
241
+ if (result.code === 0) {
242
+ // 创建下载链接
243
+ const dataStr = JSON.stringify(result.data, null, 2);
244
+ const dataBlob = new Blob([dataStr], { type: 'application/json' });
245
+ const url = URL.createObjectURL(dataBlob);
246
+ const link = document.createElement('a');
247
+ link.href = url;
248
+ link.download = `whistle-mock-features-${new Date().getTime()}.json`;
249
+ document.body.appendChild(link);
250
+ link.click();
251
+ document.body.removeChild(link);
252
+ URL.revokeObjectURL(url);
253
+
254
+ message.success('功能配置导出成功');
255
+ } else {
256
+ message.error('导出失败: ' + (result.message || '未知错误'));
257
+ }
258
+ } catch (error) {
259
+ console.error('导出失败:', error);
260
+ message.error('导出失败: ' + error.message);
261
+ }
262
+ };
263
+
264
+ // 导入功能配置
265
+ const importFeatureConfig = () => {
266
+ const input = document.createElement('input');
267
+ input.type = 'file';
268
+ input.accept = '.json';
269
+ input.onchange = async (e) => {
270
+ const file = e.target.files[0];
271
+ if (!file) return;
272
+
273
+ try {
274
+ const text = await file.text();
275
+ const data = JSON.parse(text);
276
+
277
+ const response = await fetch('/cgi-bin/import-features', {
278
+ method: 'POST',
279
+ headers: {
280
+ 'Content-Type': 'application/json',
281
+ },
282
+ body: JSON.stringify(data),
283
+ });
284
+
285
+ const result = await response.json();
286
+ if (result.code === 0) {
287
+ message.success('功能配置导入成功');
288
+ fetchFeatures();
289
+
290
+ // 刷新缓存
291
+ await flushCache();
292
+ } else {
293
+ message.error('导入失败: ' + (result.message || '未知错误'));
294
+ }
295
+ } catch (error) {
296
+ console.error('导入失败:', error);
297
+ message.error('导入失败: ' + error.message);
298
+ }
299
+ };
300
+ input.click();
301
+ };
302
+
303
+ // 搜索和筛选状态
304
+ const [searchText, setSearchText] = useState('');
305
+ const [filterStatus, setFilterStatus] = useState('all'); // all, active, inactive
306
+
307
+ // 筛选和排序数据
308
+ const getFilteredAndSortedData = () => {
309
+ let filtered = [...mockFeatures];
310
+
311
+ // 文本搜索
312
+ if (searchText) {
313
+ const searchLower = searchText.toLowerCase();
314
+ filtered = filtered.filter(item =>
315
+ item.name.toLowerCase().includes(searchLower) ||
316
+ (item.description && item.description.toLowerCase().includes(searchLower))
317
+ );
318
+ }
319
+
320
+ // 状态筛选
321
+ if (filterStatus === 'active') {
322
+ filtered = filtered.filter(item => item.active !== false);
323
+ } else if (filterStatus === 'inactive') {
324
+ filtered = filtered.filter(item => item.active === false);
325
+ }
326
+
327
+ // 排序
328
+ filtered.sort((a, b) => {
329
+ let aValue, bValue;
330
+
331
+ switch (listConfig.sortBy) {
332
+ case 'name':
333
+ aValue = a.name || '';
334
+ bValue = b.name || '';
335
+ break;
336
+ case 'createdAt':
337
+ aValue = new Date(a.createdAt || 0);
338
+ bValue = new Date(b.createdAt || 0);
339
+ break;
340
+ case 'interfaceCount':
341
+ aValue = a.interfaceCount || 0;
342
+ bValue = b.interfaceCount || 0;
343
+ break;
344
+ case 'active':
345
+ aValue = a.active === false ? 0 : 1;
346
+ bValue = b.active === false ? 0 : 1;
347
+ break;
348
+ default:
349
+ aValue = a.name || '';
350
+ bValue = b.name || '';
351
+ }
352
+
353
+ if (listConfig.sortOrder === 'descend') {
354
+ return aValue < bValue ? 1 : aValue > bValue ? -1 : 0;
355
+ } else {
356
+ return aValue > bValue ? 1 : aValue < bValue ? -1 : 0;
357
+ }
358
+ });
359
+
360
+ return filtered;
361
+ };
362
+
363
+ const filteredData = getFilteredAndSortedData();
364
+
365
+ // 分页计算
366
+ const startIndex = (listConfig.current - 1) * listConfig.pageSize;
367
+ const endIndex = startIndex + listConfig.pageSize;
368
+ const paginatedData = filteredData.slice(startIndex, endIndex);
369
+
370
+ // 排序菜单
371
+ const sortMenu = (
372
+ <Menu onClick={({ key }) => {
373
+ const [sortBy, sortOrder] = key.split('-');
374
+ saveListConfig({ sortBy, sortOrder, current: 1 });
375
+ }}>
376
+ <Menu.Item key="name-ascend">名称 (A-Z)</Menu.Item>
377
+ <Menu.Item key="name-descend">名称 (Z-A)</Menu.Item>
378
+ <Menu.Item key="createdAt-descend">创建时间 (新-旧)</Menu.Item>
379
+ <Menu.Item key="createdAt-ascend">创建时间 (旧-新)</Menu.Item>
380
+ <Menu.Item key="interfaceCount-descend">接口数量 (多-少)</Menu.Item>
381
+ <Menu.Item key="interfaceCount-ascend">接口数量 (少-多)</Menu.Item>
382
+ <Menu.Item key="active-descend">状态 (启用优先)</Menu.Item>
383
+ <Menu.Item key="active-ascend">状态 (禁用优先)</Menu.Item>
384
+ </Menu>
385
+ );
386
+
387
+ const renderFeatureCard = (feature) => {
388
+ const isActive = feature.active !== false;
389
+
390
+ return (
391
+ <Col xs={24} sm={12} md={8} lg={6} key={feature.id}>
392
+ <Badge.Ribbon
393
+ text={isActive ? "启用" : "禁用"}
394
+ color={isActive ? "green" : "volcano"}
395
+ >
396
+ <Card
397
+ className={`feature-card ${!isActive ? 'inactive' : ''}`}
398
+ hoverable
399
+ actions={[
400
+ <Tooltip title="编辑功能">
401
+ <EditOutlined key="edit" onClick={() => openModal(feature)} />
402
+ </Tooltip>,
403
+ <Tooltip title="管理接口">
404
+ <SettingOutlined key="setting" onClick={() => gotoInterfaceManagement(feature.id)} />
405
+ </Tooltip>,
406
+ <Popconfirm
407
+ title="确定删除此功能模块吗?"
408
+ description="删除后相关的所有接口配置也会被删除,且无法恢复。"
409
+ onConfirm={() => handleDelete(feature.id)}
410
+ okText="确定"
411
+ cancelText="取消"
412
+ >
413
+ <Tooltip title="删除功能">
414
+ <DeleteOutlined key="delete" style={{ color: '#ff4d4f' }} />
415
+ </Tooltip>
416
+ </Popconfirm>,
417
+ ]}
418
+ >
419
+ <div className="feature-card-header">
420
+ <div className="feature-name">
421
+ {feature.name}
422
+ </div>
423
+ <Switch
424
+ checked={isActive}
425
+ onChange={() => handleToggleActive(feature.id, isActive)}
426
+ size="small"
427
+ />
428
+ </div>
429
+ <div className="feature-description">
430
+ {feature.description || '暂无描述'}
431
+ </div>
432
+ <div className="feature-stats">
433
+ <div className="stat-item">
434
+ <span className="stat-label">接口数量:</span>
435
+ <span className="stat-value">{feature.interfaceCount || 0}</span>
436
+ </div>
437
+ <div className="stat-item">
438
+ <span className="stat-label">创建时间:</span>
439
+ <span className="stat-value">
440
+ {feature.createdAt ? new Date(feature.createdAt).toLocaleDateString() : '未知'}
441
+ </span>
442
+ </div>
443
+ </div>
444
+ </Card>
445
+ </Badge.Ribbon>
446
+ </Col>
447
+ );
448
+ };
449
+
450
+ return (
451
+ <AppLayout>
452
+ <div className="page-container">
453
+ {/* V2版本标识 */}
454
+ <div style={{
455
+ position: 'absolute',
456
+ top: '10px',
457
+ right: '10px',
458
+ backgroundColor: '#52c41a',
459
+ color: 'white',
460
+ padding: '4px 8px',
461
+ borderRadius: '4px',
462
+ fontSize: '12px',
463
+ zIndex: 1000
464
+ }}>
465
+ 重构后版本 V2
466
+ </div>
467
+
468
+ <div className="page-title-bar">
469
+ <div>
470
+ <h1 className="page-title">功能模块管理</h1>
471
+ <div className="page-description">
472
+ 创建和管理功能模块,为每个功能配置独立的接口
473
+ </div>
474
+ </div>
475
+ <div className="page-actions">
476
+ <Button
477
+ type="primary"
478
+ icon={<PlusOutlined />}
479
+ onClick={() => openModal()}
480
+ >
481
+ 新建功能
482
+ </Button>
483
+ <Button
484
+ type="primary"
485
+ icon={<ExportOutlined />}
486
+ onClick={importFeatureConfig}
487
+ style={{ backgroundColor: '#52c41a', borderColor: '#52c41a' }}
488
+ >
489
+ 导入功能
490
+ </Button>
491
+ </div>
492
+ </div>
493
+
494
+ {/* 排序和分页控制栏 */}
495
+ {mockFeatures.length > 0 && !loading && (
496
+ <Card className="filter-controls" bordered={false}>
497
+ <div className="control-row">
498
+ <div className="search-controls">
499
+ <Input
500
+ placeholder="搜索功能名称或描述"
501
+ prefix={<SearchOutlined />}
502
+ value={searchText}
503
+ onChange={(e) => setSearchText(e.target.value)}
504
+ style={{ width: 240 }}
505
+ allowClear
506
+ />
507
+ <Select
508
+ value={filterStatus}
509
+ onChange={setFilterStatus}
510
+ style={{ width: 120 }}
511
+ >
512
+ <Option value="all">全部状态</Option>
513
+ <Option value="active">已启用</Option>
514
+ <Option value="inactive">已禁用</Option>
515
+ </Select>
516
+ </div>
517
+
518
+ <div className="action-controls">
519
+ <Space>
520
+ <Dropdown overlay={sortMenu} trigger={['click']}>
521
+ <Button icon={<SortAscendingOutlined />}>
522
+ 排序
523
+ </Button>
524
+ </Dropdown>
525
+ <Button
526
+ icon={<DownloadOutlined />}
527
+ onClick={exportFeatureConfig}
528
+ >
529
+ 导出配置
530
+ </Button>
531
+ <Button
532
+ icon={<ReloadOutlined />}
533
+ onClick={fetchFeatures}
534
+ loading={loading}
535
+ >
536
+ 刷新
537
+ </Button>
538
+ {(searchText || filterStatus !== 'all') && (
539
+ <Button
540
+ icon={<ClearOutlined />}
541
+ onClick={() => {
542
+ setSearchText('');
543
+ setFilterStatus('all');
544
+ }}
545
+ >
546
+ 清除筛选
547
+ </Button>
548
+ )}
549
+ </Space>
550
+ </div>
551
+ </div>
552
+
553
+ <div className="result-info">
554
+ <Space>
555
+ <span>
556
+ 共 {mockFeatures.length} 个功能模块
557
+ {filteredData.length !== mockFeatures.length && (
558
+ <span>,筛选后 {filteredData.length} 个</span>
559
+ )}
560
+ </span>
561
+ <Badge
562
+ count={mockFeatures.filter(f => f.active !== false).length}
563
+ style={{ backgroundColor: '#52c41a' }}
564
+ title="启用的功能数量"
565
+ />
566
+ <Badge
567
+ count={mockFeatures.filter(f => f.active === false).length}
568
+ style={{ backgroundColor: '#ff4d4f' }}
569
+ title="禁用的功能数量"
570
+ />
571
+ </Space>
572
+ </div>
573
+ </Card>
574
+ )}
575
+
576
+ {/* 功能模块列表 */}
577
+ <div className="feature-list-container">
578
+ {loading ? (
579
+ <div className="loading-container">
580
+ <Row gutter={[16, 16]}>
581
+ {[1, 2, 3, 4].map(i => (
582
+ <Col xs={24} sm={12} md={8} lg={6} key={i}>
583
+ <Card loading style={{ height: 200 }} />
584
+ </Col>
585
+ ))}
586
+ </Row>
587
+ </div>
588
+ ) : mockFeatures.length === 0 ? (
589
+ <Alert
590
+ message="暂无功能模块"
591
+ description="点击 '新建功能' 按钮创建第一个功能模块"
592
+ type="info"
593
+ showIcon
594
+ style={{ textAlign: 'center', padding: '60px 20px' }}
595
+ />
596
+ ) : filteredData.length === 0 ? (
597
+ <Alert
598
+ message="无匹配结果"
599
+ description="请调整搜索条件或筛选设置"
600
+ type="warning"
601
+ showIcon
602
+ style={{ textAlign: 'center', padding: '60px 20px' }}
603
+ />
604
+ ) : (
605
+ <>
606
+ <Row gutter={[16, 16]} className="feature-grid">
607
+ {paginatedData.map(renderFeatureCard)}
608
+ </Row>
609
+
610
+ {/* 分页控件 */}
611
+ {filteredData.length > listConfig.pageSize && (
612
+ <div className="pagination-container">
613
+ <Pagination
614
+ current={listConfig.current}
615
+ pageSize={listConfig.pageSize}
616
+ total={filteredData.length}
617
+ showSizeChanger
618
+ showQuickJumper
619
+ showTotal={(total, range) =>
620
+ `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
621
+ }
622
+ pageSizeOptions={['8', '12', '16', '24']}
623
+ onChange={(current, pageSize) => {
624
+ saveListConfig({ current, pageSize });
625
+ }}
626
+ />
627
+ </div>
628
+ )}
629
+ </>
630
+ )}
631
+ </div>
632
+
633
+ {/* 新建/编辑功能模块弹窗 */}
634
+ <Modal
635
+ title={currentFeature ? '编辑功能模块' : '新建功能模块'}
636
+ open={showModal}
637
+ onCancel={() => setShowModal(false)}
638
+ footer={null}
639
+ destroyOnClose
640
+ >
641
+ <Form
642
+ form={form}
643
+ layout="vertical"
644
+ onFinish={handleSubmit}
645
+ initialValues={{
646
+ name: '',
647
+ description: '',
648
+ active: true
649
+ }}
650
+ >
651
+ <Form.Item
652
+ name="name"
653
+ label="功能名称"
654
+ rules={[
655
+ { required: true, message: '请输入功能名称' },
656
+ { max: 50, message: '功能名称不能超过50个字符' }
657
+ ]}
658
+ >
659
+ <Input placeholder="请输入功能名称" />
660
+ </Form.Item>
661
+
662
+ <Form.Item
663
+ name="description"
664
+ label="功能描述"
665
+ rules={[
666
+ { max: 200, message: '功能描述不能超过200个字符' }
667
+ ]}
668
+ >
669
+ <TextArea
670
+ placeholder="请输入功能描述(可选)"
671
+ rows={3}
672
+ showCount
673
+ maxLength={200}
674
+ />
675
+ </Form.Item>
676
+
677
+ <Form.Item
678
+ name="active"
679
+ label="启用状态"
680
+ valuePropName="checked"
681
+ >
682
+ <Switch checkedChildren="启用" unCheckedChildren="禁用" />
683
+ </Form.Item>
684
+
685
+ <Form.Item className="form-actions">
686
+ <Space>
687
+ <Button onClick={() => setShowModal(false)}>
688
+ 取消
689
+ </Button>
690
+ <Button type="primary" htmlType="submit">
691
+ {currentFeature ? '保存' : '创建'}
692
+ </Button>
693
+ </Space>
694
+ </Form.Item>
695
+ </Form>
696
+ </Modal>
697
+ </div>
698
+ </AppLayout>
699
+ );
700
+ };
701
+
702
+ export default MockData;