whistle.mock-plugins 1.2.2-beta.23 → 1.2.2-beta.25

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whistle.mock-plugins",
3
- "version": "1.2.2-beta.23",
3
+ "version": "1.2.2-beta.25",
4
4
  "description": "Whistle 插件,用于快速创建 API 模拟数据",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
package/ui/src/App.css CHANGED
@@ -170,4 +170,4 @@
170
170
  width: 100%;
171
171
  justify-content: flex-start;
172
172
  }
173
- }
173
+ }
@@ -1,5 +1,5 @@
1
- import React, { useState } from 'react';
2
- import { Layout, Menu, Button, Breadcrumb, Typography, Space, theme, Divider, Badge } from 'antd';
1
+ import React, { useState, useMemo, useEffect } from 'react';
2
+ import { Layout, Menu, Button, Breadcrumb, Space, ConfigProvider, theme } from 'antd';
3
3
  import {
4
4
  AppstoreOutlined,
5
5
  ApiOutlined,
@@ -14,14 +14,67 @@ import VersionModal from './VersionModal';
14
14
  import '../styles/app-layout.css';
15
15
 
16
16
  const { Header, Content, Sider } = Layout;
17
- const { Title, Text } = Typography;
18
17
 
19
18
  const AppLayout = ({ children }) => {
20
19
  const location = useLocation();
21
20
  const currentPath = location.pathname;
22
21
  const [collapsed, setCollapsed] = useState(false);
23
22
  const [versionModalVisible, setVersionModalVisible] = useState(false);
24
- const { token } = theme.useToken();
23
+
24
+ // 赛博朋克主题开关
25
+ const [enableCyber, setEnableCyber] = useState(() => {
26
+ return localStorage.getItem('cyberTheme') !== 'false';
27
+ });
28
+
29
+ useEffect(() => {
30
+ document.body.classList.toggle('cyber-theme', enableCyber);
31
+ }, [enableCyber]);
32
+
33
+ useEffect(() => {
34
+ const handleStorage = () => {
35
+ setEnableCyber(localStorage.getItem('cyberTheme') !== 'false');
36
+ };
37
+ window.addEventListener('storage', handleStorage);
38
+ return () => window.removeEventListener('storage', handleStorage);
39
+ }, []);
40
+
41
+ const cyberTheme = useMemo(() => ({
42
+ token: {
43
+ colorPrimary: '#00f0ff',
44
+ colorPrimaryHover: '#33f3ff',
45
+ colorPrimaryActive: '#00c2cc',
46
+ colorSuccess: '#00ff41',
47
+ colorWarning: '#f0e800',
48
+ colorError: '#ff4d4f',
49
+ colorInfo: '#00f0ff',
50
+ colorText: '#e0e0e0',
51
+ colorTextSecondary: '#a0a0a0',
52
+ colorTextTertiary: '#6b7280',
53
+ colorBorder: '#1a1a2e',
54
+ colorBorderSecondary: '#1a1a2e',
55
+ colorBgBase: '#050508',
56
+ colorBgContainer: '#0f0f1a',
57
+ colorBgElevated: '#141420',
58
+ colorBgLayout: '#0a0a12',
59
+ colorBgSpotlight: '#141420',
60
+ borderRadius: 6,
61
+ borderRadiusSM: 4,
62
+ borderRadiusLG: 8,
63
+ wireframe: false,
64
+ fontFamily: "'Noto Sans SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
65
+ fontFamilyCode: "'Share Tech Mono', 'SFMono-Regular', Consolas, monospace",
66
+ },
67
+ algorithm: theme.darkAlgorithm,
68
+ }), []);
69
+
70
+ const lightTheme = useMemo(() => ({
71
+ token: {
72
+ borderRadius: 6,
73
+ borderRadiusSM: 4,
74
+ borderRadiusLG: 8,
75
+ },
76
+ algorithm: theme.defaultAlgorithm,
77
+ }), []);
25
78
 
26
79
  // 版本弹窗控制方法
27
80
  const showVersionModal = () => setVersionModalVisible(true);
@@ -83,88 +136,130 @@ const AppLayout = ({ children }) => {
83
136
  ];
84
137
 
85
138
  const breadcrumbs = getBreadcrumbs();
86
- const colorBgContainer = token.colorBgContainer;
87
- const colorBgLayout = token.colorBgLayout;
88
- const borderRadius = token.borderRadius;
89
139
 
90
140
  return (
91
- <Layout className="app-layout">
92
- {/* 顶部导航栏 */}
93
- <Header className="app-header" style={{ background: colorBgContainer }}>
94
- <div className="header-left">
95
- <div className="logo-container">
96
- <div className="logo-icon-group">
97
- <ExperimentOutlined className="logo-icon logo-icon-secondary" />
141
+ <ConfigProvider theme={enableCyber ? cyberTheme : lightTheme}>
142
+ <Layout className="app-layout">
143
+ {/* 顶部导航栏 */}
144
+ <Header className="app-header" style={{ background: enableCyber ? '#0f0f1a' : '#ffffff', borderBottom: enableCyber ? '1px solid #1a1a2e' : '1px solid #f0f0f0' }}>
145
+ <div className="header-left">
146
+ <div className="logo-container">
147
+ <div className="logo-icon-group">
148
+ <ExperimentOutlined className="logo-icon logo-icon-secondary" style={{ color: enableCyber ? '#00ff41' : '#52c41a' }} />
149
+ </div>
150
+ <div
151
+ className="logo-text"
152
+ style={enableCyber ? {
153
+ background: 'linear-gradient(135deg, #00f0ff, #00ff41)',
154
+ WebkitBackgroundClip: 'text',
155
+ WebkitTextFillColor: 'transparent',
156
+ } : {
157
+ background: 'linear-gradient(135deg, #1890ff, #52c41a)',
158
+ WebkitBackgroundClip: 'text',
159
+ WebkitTextFillColor: 'transparent',
160
+ }}
161
+ >
162
+ MockMaster
163
+ </div>
98
164
  </div>
99
- <div className="logo-text">MockMaster</div>
165
+ <Button
166
+ type="text"
167
+ icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
168
+ onClick={() => setCollapsed(!collapsed)}
169
+ className="collapse-button"
170
+ />
100
171
  </div>
101
- <Button
102
- type="text"
103
- icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
104
- onClick={() => setCollapsed(!collapsed)}
105
- className="collapse-button"
106
- />
107
- </div>
108
- <div className="header-right">
109
- <Space>
110
- <Button
111
- type="text"
112
- size="small"
113
- className="version-button"
114
- onClick={showVersionModal}
115
- >
116
- <Text type="secondary">v{__APP_VERSION__}</Text>
117
- </Button>
118
- </Space>
119
- </div>
120
- </Header>
121
-
122
- <Layout>
123
- {/* 侧边导航 */}
124
- <Sider
125
- width={220}
126
- className="app-sider"
127
- collapsed={collapsed}
128
- collapsible
129
- trigger={null}
130
- style={{ background: colorBgContainer }}
131
- >
132
- <Menu
133
- mode="inline"
134
- selectedKeys={getSelectedKeys()}
135
- items={menuItems}
136
- style={{
137
- height: '100%',
138
- borderRight: 0,
139
- padding: '8px',
140
- }}
141
- />
142
- </Sider>
172
+ <div className="header-right">
173
+ <Space>
174
+ <Button
175
+ type="text"
176
+ size="small"
177
+ className="version-button"
178
+ onClick={showVersionModal}
179
+ >
180
+ <span style={{ color: enableCyber ? '#6b7280' : '#000000d9', fontFamily: "'Share Tech Mono', monospace" }}>
181
+ v{__APP_VERSION__}
182
+ </span>
183
+ </Button>
184
+ </Space>
185
+ </div>
186
+ </Header>
143
187
 
144
- {/* 主内容区 */}
145
- <Layout className="app-content-wrapper" style={{ padding: 0, background: colorBgLayout }}>
146
- {/* 内容区 */}
147
- <Content
148
- className="app-content"
149
- style={{
150
- background: colorBgContainer,
151
- padding: 24,
152
- borderRadius: borderRadius,
153
- minHeight: 280,
154
- }}
188
+ <Layout>
189
+ {/* 侧边导航 */}
190
+ <Sider
191
+ width={220}
192
+ className="app-sider"
193
+ collapsed={collapsed}
194
+ collapsible
195
+ trigger={null}
196
+ style={{ background: enableCyber ? '#0f0f1a' : '#ffffff' }}
155
197
  >
156
- {children}
157
- </Content>
198
+ <Menu
199
+ mode="inline"
200
+ selectedKeys={getSelectedKeys()}
201
+ items={menuItems}
202
+ style={{
203
+ height: '100%',
204
+ borderRight: 0,
205
+ padding: '8px',
206
+ background: enableCyber ? '#0f0f1a' : '#ffffff',
207
+ }}
208
+ />
209
+ </Sider>
210
+
211
+ {/* 主内容区 */}
212
+ <Layout className="app-content-wrapper" style={{ padding: 0, background: enableCyber ? '#0a0a12' : '#f5f5f5' }}>
213
+ {/* 面包屑和标题 */}
214
+ <div style={{ padding: '16px 24px 0' }}>
215
+ <Breadcrumb className="app-breadcrumb" style={{ marginBottom: 16 }}>
216
+ {breadcrumbs.map((crumb, index) => (
217
+ <Breadcrumb.Item key={index}>
218
+ {index < breadcrumbs.length - 1 ? (
219
+ <Link to={crumb.path} style={{ color: enableCyber ? '#a0a0a0' : '#000000d9' }}>{crumb.title}</Link>
220
+ ) : (
221
+ <span style={{ color: enableCyber ? '#e0e0e0' : '#000000d9', fontWeight: 500 }}>{crumb.title}</span>
222
+ )}
223
+ </Breadcrumb.Item>
224
+ ))}
225
+ </Breadcrumb>
226
+ <h1 style={{
227
+ margin: 0,
228
+ fontSize: 22,
229
+ fontWeight: 700,
230
+ color: enableCyber ? '#e0e0e0' : '#000000d9',
231
+ fontFamily: enableCyber ? "'Orbitron', sans-serif" : undefined,
232
+ letterSpacing: enableCyber ? '0.5px' : undefined,
233
+ }}>
234
+ {getPageTitle()}
235
+ </h1>
236
+ </div>
237
+
238
+ {/* 内容区 */}
239
+ <Content
240
+ className="app-content"
241
+ style={{
242
+ background: enableCyber ? '#0f0f1a' : '#ffffff',
243
+ padding: 24,
244
+ borderRadius: 6,
245
+ minHeight: 280,
246
+ margin: '16px 24px 24px',
247
+ border: enableCyber ? '1px solid #1a1a2e' : '1px solid #f0f0f0',
248
+ }}
249
+ >
250
+ {children}
251
+ </Content>
252
+ </Layout>
158
253
  </Layout>
254
+
255
+ {/* 版本信息弹窗 */}
256
+ <VersionModal
257
+ visible={versionModalVisible}
258
+ onCancel={hideVersionModal}
259
+ />
159
260
  </Layout>
160
-
161
- {/* 版本信息弹窗 */}
162
- <VersionModal
163
- visible={versionModalVisible}
164
- onCancel={hideVersionModal}
165
- />
166
- </Layout>
261
+ </ConfigProvider>
167
262
  );
168
263
  };
169
264
 
170
- export default AppLayout;
265
+ export default AppLayout;
@@ -6,7 +6,7 @@ const FeatureCard = ({ feature }) => {
6
6
 
7
7
  // 生成随机的主题色(实际项目中可能会根据某种规则来确定)
8
8
  const getRandomColor = () => {
9
- const colors = ['#1890ff', '#13c2c2', '#52c41a', '#faad14', '#722ed1'];
9
+ const colors = ['#00f0ff', '#00ff41', '#f0e800', '#ff00a0', '#bd00ff'];
10
10
  return colors[Math.floor(Math.random() * colors.length)];
11
11
  };
12
12
 
@@ -252,7 +252,7 @@ const InterfaceTestModal = ({
252
252
  <span>
253
253
  <span style={{ color: '#d9d9d9' }}>{value}</span>
254
254
  {' '} → {' '}
255
- <span style={{ color: '#52c41a', fontWeight: 'bold' }}>{generatedValue}</span>
255
+ <span style={{ color: '#00ff41', fontWeight: 'bold' }}>{generatedValue}</span>
256
256
  <span style={{ color: '#8c8c8c', fontSize: '12px' }}> (随机生成)</span>
257
257
  </span>
258
258
  ) : generatedValue}
@@ -22,22 +22,20 @@ const PreviewModal = ({
22
22
  <div style={{
23
23
  marginBottom: '16px',
24
24
  padding: '8px',
25
- background: '#f9f9f9',
26
25
  borderRadius: '4px',
27
- borderLeft: '4px solid #1890ff'
26
+ borderLeft: '4px solid #00f0ff'
28
27
  }}>
29
28
  <div style={{ fontWeight: 'bold', marginBottom: '4px' }}>描述:</div>
30
- <div>{previewContent.description}</div>
29
+ <div style={{ opacity: 0.7 }}>{previewContent.description}</div>
31
30
  </div>
32
31
  )}
33
32
 
34
33
  <div style={{
35
34
  maxHeight: '60vh',
36
35
  overflow: 'auto',
37
- backgroundColor: '#f5f5f5',
38
36
  padding: '16px',
39
37
  borderRadius: '4px',
40
- fontFamily: 'monospace'
38
+ fontFamily: "'Share Tech Mono', monospace",
41
39
  }}>
42
40
  <pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordWrap: 'break-word' }}>
43
41
  {previewContent?.content}
@@ -47,4 +45,4 @@ const PreviewModal = ({
47
45
  );
48
46
  };
49
47
 
50
- export default PreviewModal;
48
+ export default PreviewModal;
@@ -274,7 +274,7 @@ const ResponseContentEditor = ({
274
274
  onClick={toggleTextareaFullscreen}
275
275
  size="small"
276
276
  type="text"
277
- style={{ color: '#ffffff' }}
277
+ style={{ color: '#595959' }}
278
278
  >
279
279
  退出全屏 (ESC)
280
280
  </Button>
@@ -301,7 +301,7 @@ const ResponseContentEditor = ({
301
301
  <div style={{ marginBottom: '12px' }}>
302
302
  <Divider orientation="left" orientationMargin={0} style={{ margin: '0 0 10px 0', fontSize: '12px' }}>
303
303
  <Space size={6}>
304
- <FilterOutlined style={{ color: '#1890ff' }} />
304
+ <FilterOutlined style={{ color: '#1677ff' }} />
305
305
  <span style={{ fontWeight: 'bold', color: '#333' }}>入参匹配规则</span>
306
306
  {matchers.length > 0 && (
307
307
  <Tag color="blue" style={{ fontSize: '11px', padding: '0 4px' }}>{matchers.length}条</Tag>
@@ -382,11 +382,10 @@ const ResponseContentEditor = ({
382
382
  <div style={{
383
383
  textAlign: 'center',
384
384
  padding: '40px 20px',
385
- background: '#fafafa',
386
385
  borderRadius: '6px',
387
386
  border: '1px dashed #d9d9d9'
388
387
  }}>
389
- <FileTextOutlined style={{ fontSize: '32px', color: '#d9d9d9', marginBottom: '12px' }} />
388
+ <FileTextOutlined style={{ fontSize: '32px', color: '#bfbfbf', marginBottom: '12px' }} />
390
389
  <div style={{ color: '#999', marginBottom: '16px' }}>暂无响应数据</div>
391
390
  <Button
392
391
  type="primary"
@@ -408,7 +407,7 @@ const ResponseContentEditor = ({
408
407
  <span>响应内容编辑</span>
409
408
  <Badge
410
409
  count={responses.length}
411
- style={{ backgroundColor: '#52c41a' }}
410
+ style={{ backgroundColor: '#00ff41' }}
412
411
  />
413
412
  </div>
414
413
  }
@@ -433,9 +432,7 @@ const ResponseContentEditor = ({
433
432
  style={{
434
433
  display: 'flex',
435
434
  alignItems: 'center',
436
- gap: '4px',
437
- borderColor: '#1890ff',
438
- color: '#1890ff'
435
+ gap: '4px'
439
436
  }}
440
437
  >
441
438
  添加响应
@@ -449,8 +446,8 @@ const ResponseContentEditor = ({
449
446
  display: 'flex',
450
447
  alignItems: 'center',
451
448
  gap: '4px',
452
- borderColor: '#52c41a',
453
- color: '#52c41a'
449
+ borderColor: '#00ff41',
450
+ color: '#00ff41'
454
451
  }}
455
452
  >
456
453
  预览
package/ui/src/index.html CHANGED
@@ -3,9 +3,12 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Whistle Mock 数据工厂</title>
6
+ <title>Whistle Mock Plugin</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700;900&family=Orbitron:wght@400;500;600;700;800;900&family=Share+Tech+Mono&display=swap" rel="stylesheet">
7
10
  </head>
8
11
  <body>
9
12
  <div id="root"></div>
10
13
  </body>
11
- </html>
14
+ </html>
package/ui/src/index.js CHANGED
@@ -3,10 +3,13 @@ import ReactDOM from 'react-dom';
3
3
  import { HashRouter as Router, Switch, Route, Redirect } from 'react-router-dom';
4
4
 
5
5
  // 导入样式
6
- import 'antd/dist/reset.css'; // 导入antd样式,使用适合的版本
6
+ import 'antd/dist/reset.css';
7
7
  import './styles/global.css';
8
8
  import './App.css';
9
9
 
10
+ // 赛博朋克主题 CSS(规则均带 body.cyber-theme 前缀,默认不生效)
11
+ require('./styles/cyber-theme.css');
12
+
10
13
  // 导入页面组件
11
14
  import MockData from './pages/MockDataV2';
12
15
  import InterfaceManagement from './pages/InterfaceManagementV2';
@@ -37,4 +40,4 @@ if (ReactDOM.createRoot) {
37
40
  } else {
38
41
  // 旧版本React
39
42
  ReactDOM.render(<App />, root);
40
- }
43
+ }
@@ -1006,13 +1006,13 @@ const InterfaceManagement = () => {
1006
1006
  <div key={key}>
1007
1007
  {key}: {value}
1008
1008
  {value && value.startsWith('@') && (
1009
- <span style={{ color: '#52c41a' }}> (随机)</span>
1009
+ <span style={{ color: '#00ff41' }}> (随机)</span>
1010
1010
  )}
1011
1011
  </div>
1012
1012
  ))}
1013
1013
  </div>
1014
1014
  }>
1015
- <span style={{ color: '#1890ff' }}>
1015
+ <span style={{ color: '#1677ff' }}>
1016
1016
  {count}个
1017
1017
  {hasRandomValue && <span style={{ marginLeft: 4 }}>🎲</span>}
1018
1018
  </span>
@@ -1058,7 +1058,7 @@ const InterfaceManagement = () => {
1058
1058
  {interfaceMatchers.filter(m => m.paramPath).map((m, i) => (
1059
1059
  <div key={i} style={{ fontSize: '12px' }}>
1060
1060
  {m.paramPath}: {m.paramValue}
1061
- <span style={{ color: '#52c41a', marginLeft: 4 }}>
1061
+ <span style={{ color: '#00ff41', marginLeft: 4 }}>
1062
1062
  ({m.matchType === 'exact' ? '精确' : m.matchType === 'contains' ? '包含' : '正则'})
1063
1063
  </span>
1064
1064
  </div>
@@ -1067,14 +1067,14 @@ const InterfaceManagement = () => {
1067
1067
  )}
1068
1068
  {responsesWithMatchers.length > 0 && (
1069
1069
  <div>
1070
- <div style={{ fontWeight: 'bold', marginBottom: 4, color: '#1890ff' }}>响应级规则(共{responseMatcherTotal}条)</div>
1070
+ <div style={{ fontWeight: 'bold', marginBottom: 4, color: '#1677ff' }}>响应级规则(共{responseMatcherTotal}条)</div>
1071
1071
  {responsesWithMatchers.map((resp, ri) => (
1072
1072
  <div key={ri} style={{ marginBottom: 4 }}>
1073
1073
  <div style={{ fontSize: '12px', color: '#aaa', marginBottom: 2 }}>{resp.name}:</div>
1074
1074
  {resp.paramMatchers.filter(m => m.paramPath).map((m, mi) => (
1075
1075
  <div key={mi} style={{ fontSize: '12px', paddingLeft: 8 }}>
1076
1076
  {m.paramPath}: {m.paramValue}
1077
- <span style={{ color: '#52c41a', marginLeft: 4 }}>
1077
+ <span style={{ color: '#00ff41', marginLeft: 4 }}>
1078
1078
  ({m.matchType === 'exact' ? '精确' : m.matchType === 'contains' ? '包含' : '正则'})
1079
1079
  </span>
1080
1080
  </div>
@@ -1312,7 +1312,7 @@ const InterfaceManagement = () => {
1312
1312
  ) : (
1313
1313
  <Badge
1314
1314
  count={`共 ${filteredInterfaces.length} 个接口`}
1315
- style={{ backgroundColor: '#52c41a' }}
1315
+ style={{ backgroundColor: '#00ff41' }}
1316
1316
  />
1317
1317
  )}
1318
1318
  </span>
@@ -1500,7 +1500,6 @@ const InterfaceManagement = () => {
1500
1500
  destroyOnClose
1501
1501
  bodyStyle={{
1502
1502
  padding: '0',
1503
- background: '#fafafa',
1504
1503
  overflowY: 'auto'
1505
1504
  }}
1506
1505
  footer={
@@ -1744,8 +1743,7 @@ const InterfaceManagement = () => {
1744
1743
  bodyStyle={{
1745
1744
  maxHeight: '75vh',
1746
1745
  overflow: 'auto',
1747
- padding: '0',
1748
- background: '#fafafa'
1746
+ padding: '0'
1749
1747
  }}
1750
1748
  className="interface-modal"
1751
1749
  >
@@ -454,7 +454,7 @@ const MockData = () => {
454
454
  <Col xs={24} sm={12} md={8} lg={6} key={feature.id} style={{ marginBottom: 16 }}>
455
455
  <Badge.Ribbon
456
456
  text={feature.active ? '已启用' : '已禁用'}
457
- color={feature.active ? '#52c41a' : '#f5222d'}
457
+ color={feature.active ? '#00ff41' : '#f5222d'}
458
458
  style={{ display: 'block' }}
459
459
  >
460
460
  <Card
@@ -566,7 +566,7 @@ const MockData = () => {
566
566
  width: 100,
567
567
  align: 'center',
568
568
  render: (count) => (
569
- <Badge count={count || 0} showZero style={{ backgroundColor: '#1890ff' }} />
569
+ <Badge count={count || 0} showZero style={{ backgroundColor: '#1677ff' }} />
570
570
  ),
571
571
  },
572
572
  {
@@ -664,7 +664,7 @@ const MockData = () => {
664
664
  <Badge
665
665
  count="V2"
666
666
  style={{
667
- backgroundColor: '#52c41a',
667
+ backgroundColor: '#00ff41',
668
668
  fontSize: '12px',
669
669
  height: '20px',
670
670
  lineHeight: '20px',
@@ -689,7 +689,7 @@ const MockData = () => {
689
689
  type="primary"
690
690
  icon={<ExportOutlined />}
691
691
  onClick={importFeatureConfig}
692
- style={{ backgroundColor: '#52c41a', borderColor: '#52c41a' }}
692
+ style={{ backgroundColor: '#00ff41', borderColor: '#00ff41' }}
693
693
  >
694
694
  导入功能
695
695
  </Button>
@@ -1,6 +1,6 @@
1
1
  import React, { useState, useEffect } from 'react';
2
2
  import AppLayout from '../components/AppLayout';
3
- import { Card, Typography, Switch, Space, Modal, Button } from 'antd';
3
+ import { Card, Typography, Switch, Space, Modal, Button, message } from 'antd';
4
4
  import { QuestionCircleOutlined } from '@ant-design/icons';
5
5
  import axios from 'axios';
6
6
  import '../styles/settings.css';
@@ -16,6 +16,7 @@ const Settings = () => {
16
16
  const [interfaceFormDisplayMode, setInterfaceFormDisplayMode] = useState('modal');
17
17
  const [interfaceParamMatcherEnabled, setInterfaceParamMatcherEnabled] = useState(true);
18
18
  const [responseParamMatcherEnabled, setResponseParamMatcherEnabled] = useState(true);
19
+ const [cyberThemeEnabled, setCyberThemeEnabled] = useState(true);
19
20
 
20
21
  useEffect(() => {
21
22
  const savedMode = localStorage.getItem('dataManagementDisplayMode');
@@ -39,6 +40,10 @@ const Settings = () => {
39
40
  const savedResponseParamMatcher = localStorage.getItem('responseParamMatcherEnabled');
40
41
  if (savedResponseParamMatcher !== null) setResponseParamMatcherEnabled(savedResponseParamMatcher !== 'false');
41
42
  });
43
+
44
+ // 读取赛博朋克主题开关(默认开启)
45
+ const savedCyber = localStorage.getItem('cyberTheme');
46
+ setCyberThemeEnabled(savedCyber !== 'false');
42
47
  }, []);
43
48
 
44
49
  const handleDisplayModeChange = (checked) => {
@@ -69,6 +74,17 @@ const Settings = () => {
69
74
  window.dispatchEvent(new Event('storage'));
70
75
  };
71
76
 
77
+ const handleCyberThemeChange = (checked) => {
78
+ setCyberThemeEnabled(checked);
79
+ localStorage.setItem('cyberTheme', String(checked));
80
+ window.dispatchEvent(new Event('storage'));
81
+ message.success(checked ? '赛博朋克主题已开启,刷新页面后生效' : '经典主题已开启,刷新页面后生效');
82
+ // 1.5秒后自动刷新
83
+ setTimeout(() => {
84
+ window.location.reload();
85
+ }, 1500);
86
+ };
87
+
72
88
  const showMatcherRuleModal = (type) => {
73
89
  if (type === 'interface') {
74
90
  Modal.info({
@@ -229,6 +245,28 @@ const Settings = () => {
229
245
  </div>
230
246
  </div>
231
247
  </Card>
248
+
249
+ <Card title="主题设置" className="settings-card" style={{ marginTop: 24 }}>
250
+ <div className="settings-item">
251
+ <div className="settings-item-header">
252
+ <div>
253
+ <div className="settings-item-title">赛博朋克风格</div>
254
+ <div className="settings-item-description">
255
+ 启用深色赛博朋克霓虹主题,关闭则恢复经典亮色主题
256
+ </div>
257
+ </div>
258
+ <Space>
259
+ <Text type="secondary">
260
+ {cyberThemeEnabled ? '已开启' : '已关闭'}
261
+ </Text>
262
+ <Switch
263
+ checked={cyberThemeEnabled}
264
+ onChange={handleCyberThemeChange}
265
+ />
266
+ </Space>
267
+ </div>
268
+ </div>
269
+ </Card>
232
270
  </div>
233
271
  </AppLayout>
234
272
  );