@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,308 +0,0 @@
|
|
|
1
|
-
import {useEffect, useRef, useState} from 'react'
|
|
2
|
-
import {PageContainer, ProCard} from '@ant-design/pro-components'
|
|
3
|
-
import {Alert, Button, Empty, Input, List, message, Space, Tag, Tooltip} from 'antd'
|
|
4
|
-
import {
|
|
5
|
-
CheckCircleOutlined,
|
|
6
|
-
CloseCircleOutlined,
|
|
7
|
-
PoweroffOutlined,
|
|
8
|
-
SendOutlined,
|
|
9
|
-
TeamOutlined,
|
|
10
|
-
UserAddOutlined
|
|
11
|
-
} from '@ant-design/icons'
|
|
12
|
-
import {agentTeamApi, type AgentTeam, type AgentTeamMember} from '@/services/agentTeam'
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* z-agent-team 即时通讯 (IM) 集成页
|
|
16
|
-
*
|
|
17
|
-
* 后端 (z-agent-team-web):
|
|
18
|
-
* - GET /api/agent/team/list
|
|
19
|
-
* - POST /api/agent/team/create
|
|
20
|
-
* - GET /api/agent/team/{teamCode}/members
|
|
21
|
-
* - POST /api/agent/team/{teamCode}/members/add
|
|
22
|
-
* - POST /api/agent/team/{teamCode}/members/remove?appCode=
|
|
23
|
-
* - WS /api/agent/team/ws (STOMP)
|
|
24
|
-
*
|
|
25
|
-
* 注意:完整的 IM 客户端 (含 STOMP 订阅) 需要 sockjs-client / @stomp/stompjs,
|
|
26
|
-
* 完整实现见 z-agent-team-frontend (z-agent/z-agent-team/z-agent-team-frontend)。
|
|
27
|
-
* 这里展示:群组管理 + 消息发送 (REST) + WS URL 配置。
|
|
28
|
-
*/
|
|
29
|
-
const ROLE_COLOR: Record<string, string> = {
|
|
30
|
-
LEADER: 'red',
|
|
31
|
-
WORKER: 'blue',
|
|
32
|
-
OBSERVER: 'default',
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export default function AgentTeamIM() {
|
|
36
|
-
const [teams, setTeams] = useState<AgentTeam[]>([])
|
|
37
|
-
const [selectedTeam, setSelectedTeam] = useState<AgentTeam | null>(null)
|
|
38
|
-
const [members, setMembers] = useState<AgentTeamMember[]>([])
|
|
39
|
-
const [loadingTeams, setLoadingTeams] = useState(false)
|
|
40
|
-
const [loadingMembers, setLoadingMembers] = useState(false)
|
|
41
|
-
const [wsState, setWsState] = useState<'disconnected' | 'connecting' | 'connected' | 'error'>('disconnected')
|
|
42
|
-
const [wsLog, setWsLog] = useState<string[]>([])
|
|
43
|
-
const [messageInput, setMessageInput] = useState('')
|
|
44
|
-
const [messages, setMessages] = useState<{from: string; text: string; time: string}[]>([])
|
|
45
|
-
const wsRef = useRef<WebSocket | null>(null)
|
|
46
|
-
|
|
47
|
-
// 加载团队列表
|
|
48
|
-
const loadTeams = async () => {
|
|
49
|
-
setLoadingTeams(true)
|
|
50
|
-
try {
|
|
51
|
-
const list = await agentTeamApi.listTeams()
|
|
52
|
-
setTeams(list || [])
|
|
53
|
-
} catch (e: any) {
|
|
54
|
-
message.warning('加载团队失败:' + (e?.message || '未知错误'))
|
|
55
|
-
setTeams([])
|
|
56
|
-
} finally {
|
|
57
|
-
setLoadingTeams(false)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// 加载成员
|
|
62
|
-
const loadMembers = async (teamCode: string) => {
|
|
63
|
-
setLoadingMembers(true)
|
|
64
|
-
try {
|
|
65
|
-
const list = await agentTeamApi.listMembers(teamCode)
|
|
66
|
-
setMembers(list || [])
|
|
67
|
-
} catch (e: any) {
|
|
68
|
-
message.warning('加载成员失败:' + (e?.message || '未知错误'))
|
|
69
|
-
setMembers([])
|
|
70
|
-
} finally {
|
|
71
|
-
setLoadingMembers(false)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// WebSocket 连接 (原生 WS,无 STOMP 简化版)
|
|
76
|
-
const connectWs = () => {
|
|
77
|
-
if (wsRef.current) {
|
|
78
|
-
message.warning('已连接')
|
|
79
|
-
return
|
|
80
|
-
}
|
|
81
|
-
const url = agentTeamApi.buildWsUrl()
|
|
82
|
-
setWsState('connecting')
|
|
83
|
-
setWsLog(prev => [...prev, `[${new Date().toLocaleTimeString()}] 连接到 ${url}...`])
|
|
84
|
-
try {
|
|
85
|
-
const ws = new WebSocket(url)
|
|
86
|
-
ws.onopen = () => {
|
|
87
|
-
setWsState('connected')
|
|
88
|
-
setWsLog(prev => [...prev, `[${new Date().toLocaleTimeString()}] ✅ 连接已建立`])
|
|
89
|
-
}
|
|
90
|
-
ws.onmessage = (ev) => {
|
|
91
|
-
const text = typeof ev.data === 'string' ? ev.data : '[binary]'
|
|
92
|
-
setWsLog(prev => [...prev, `[${new Date().toLocaleTimeString()}] ← ${text}`])
|
|
93
|
-
// 简单消息格式: "from|text"
|
|
94
|
-
const m = text.match(/^([^|]+)\|(.+)$/)
|
|
95
|
-
if (m) {
|
|
96
|
-
setMessages(prev => [...prev, {from: m[1], text: m[2], time: new Date().toLocaleTimeString()}])
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
ws.onerror = () => {
|
|
100
|
-
setWsState('error')
|
|
101
|
-
setWsLog(prev => [...prev, `[${new Date().toLocaleTimeString()}] ❌ 连接错误`])
|
|
102
|
-
}
|
|
103
|
-
ws.onclose = () => {
|
|
104
|
-
setWsState('disconnected')
|
|
105
|
-
setWsLog(prev => [...prev, `[${new Date().toLocaleTimeString()}] 🔌 连接已关闭`])
|
|
106
|
-
wsRef.current = null
|
|
107
|
-
}
|
|
108
|
-
wsRef.current = ws
|
|
109
|
-
} catch (e: any) {
|
|
110
|
-
setWsState('error')
|
|
111
|
-
message.error('WS 连接失败:' + (e?.message || '未知错误'))
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const disconnectWs = () => {
|
|
116
|
-
if (wsRef.current) {
|
|
117
|
-
wsRef.current.close()
|
|
118
|
-
wsRef.current = null
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const sendMessage = () => {
|
|
123
|
-
if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
|
|
124
|
-
message.error('WebSocket 未连接')
|
|
125
|
-
return
|
|
126
|
-
}
|
|
127
|
-
if (!messageInput.trim()) return
|
|
128
|
-
const text = `${localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')!).userName : 'me'}|${messageInput}`
|
|
129
|
-
wsRef.current.send(text)
|
|
130
|
-
setWsLog(prev => [...prev, `[${new Date().toLocaleTimeString()}] → ${messageInput}`])
|
|
131
|
-
setMessages(prev => [...prev, {from: 'me', text: messageInput, time: new Date().toLocaleTimeString()}])
|
|
132
|
-
setMessageInput('')
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
useEffect(() => {
|
|
136
|
-
loadTeams()
|
|
137
|
-
return () => {
|
|
138
|
-
disconnectWs()
|
|
139
|
-
}
|
|
140
|
-
}, [])
|
|
141
|
-
|
|
142
|
-
useEffect(() => {
|
|
143
|
-
if (selectedTeam) {
|
|
144
|
-
loadMembers(selectedTeam.teamCode)
|
|
145
|
-
}
|
|
146
|
-
}, [selectedTeam])
|
|
147
|
-
|
|
148
|
-
const stateColor = {
|
|
149
|
-
disconnected: 'default',
|
|
150
|
-
connecting: 'processing',
|
|
151
|
-
connected: 'success',
|
|
152
|
-
error: 'error',
|
|
153
|
-
} as const
|
|
154
|
-
const stateText = {
|
|
155
|
-
disconnected: '未连接',
|
|
156
|
-
connecting: '连接中',
|
|
157
|
-
connected: '已连接',
|
|
158
|
-
error: '错误',
|
|
159
|
-
} as const
|
|
160
|
-
|
|
161
|
-
return (
|
|
162
|
-
<PageContainer
|
|
163
|
-
header={{
|
|
164
|
-
title: 'Agent 群组 IM (z-agent-team)',
|
|
165
|
-
subTitle: '团队协作 + 实时通讯',
|
|
166
|
-
breadcrumb: {},
|
|
167
|
-
}}
|
|
168
|
-
>
|
|
169
|
-
<div style={{display: 'grid', gridTemplateColumns: '300px 1fr', gap: 16}}>
|
|
170
|
-
{/* 团队列表面板 */}
|
|
171
|
-
<ProCard title={<Space><TeamOutlined/> 团队列表</Space>} loading={loadingTeams} headerBordered>
|
|
172
|
-
{teams.length === 0 ? (
|
|
173
|
-
<Empty
|
|
174
|
-
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
|
175
|
-
description="暂无团队"
|
|
176
|
-
>
|
|
177
|
-
<Button type="primary" size="small" onClick={loadTeams}>
|
|
178
|
-
刷新
|
|
179
|
-
</Button>
|
|
180
|
-
</Empty>
|
|
181
|
-
) : (
|
|
182
|
-
<List
|
|
183
|
-
dataSource={teams}
|
|
184
|
-
renderItem={(t) => (
|
|
185
|
-
<List.Item
|
|
186
|
-
onClick={() => setSelectedTeam(t)}
|
|
187
|
-
style={{
|
|
188
|
-
cursor: 'pointer',
|
|
189
|
-
padding: '8px 12px',
|
|
190
|
-
background: selectedTeam?.teamCode === t.teamCode ? '#e6f7ff' : undefined,
|
|
191
|
-
borderRadius: 4,
|
|
192
|
-
}}
|
|
193
|
-
>
|
|
194
|
-
<List.Item.Meta
|
|
195
|
-
title={t.teamName}
|
|
196
|
-
description={
|
|
197
|
-
<Space size={4}>
|
|
198
|
-
<Tag>{t.teamCode}</Tag>
|
|
199
|
-
{t.status === 'ENABLE' ? <Tag color="green">启用</Tag> : <Tag>停用</Tag>}
|
|
200
|
-
</Space>
|
|
201
|
-
}
|
|
202
|
-
/>
|
|
203
|
-
</List.Item>
|
|
204
|
-
)}
|
|
205
|
-
/>
|
|
206
|
-
)}
|
|
207
|
-
</ProCard>
|
|
208
|
-
|
|
209
|
-
{/* 详情面板 */}
|
|
210
|
-
<ProCard
|
|
211
|
-
title={
|
|
212
|
-
selectedTeam
|
|
213
|
-
? <Space><TeamOutlined/> {selectedTeam.teamName} ({selectedTeam.teamCode})</Space>
|
|
214
|
-
: '请选择团队'
|
|
215
|
-
}
|
|
216
|
-
headerBordered
|
|
217
|
-
extra={
|
|
218
|
-
<Space>
|
|
219
|
-
{wsState === 'connected' ? (
|
|
220
|
-
<Button icon={<PoweroffOutlined/>} onClick={disconnectWs}>断开</Button>
|
|
221
|
-
) : (
|
|
222
|
-
<Button type="primary" icon={<CheckCircleOutlined/>} onClick={connectWs}>连接</Button>
|
|
223
|
-
)}
|
|
224
|
-
<Tag color={stateColor[wsState]}>{stateText[wsState]}</Tag>
|
|
225
|
-
</Space>
|
|
226
|
-
}
|
|
227
|
-
>
|
|
228
|
-
{selectedTeam ? (
|
|
229
|
-
<div style={{display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16}}>
|
|
230
|
-
{/* 成员列表 */}
|
|
231
|
-
<ProCard title="成员" loading={loadingMembers} size="small">
|
|
232
|
-
<List
|
|
233
|
-
dataSource={members}
|
|
234
|
-
renderItem={(m) => (
|
|
235
|
-
<List.Item>
|
|
236
|
-
<List.Item.Meta
|
|
237
|
-
title={<Space>{m.appCode}<Tag color={ROLE_COLOR[m.role || 'WORKER']}>{m.role}</Tag></Space>}
|
|
238
|
-
description={`优先级: ${m.priority ?? 0} · ${m.enabled === 1 ? '✅ 启用' : '❌ 停用'}`}
|
|
239
|
-
/>
|
|
240
|
-
</List.Item>
|
|
241
|
-
)}
|
|
242
|
-
/>
|
|
243
|
-
</ProCard>
|
|
244
|
-
|
|
245
|
-
{/* 消息面板 */}
|
|
246
|
-
<ProCard title="实时消息" size="small" styles={{body: {padding: 0, display: 'flex', flexDirection: 'column', height: 400}}}>
|
|
247
|
-
<div style={{flex: 1, overflow: 'auto', padding: 12}}>
|
|
248
|
-
{messages.length === 0 ? (
|
|
249
|
-
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无消息" />
|
|
250
|
-
) : (
|
|
251
|
-
messages.map((m, i) => (
|
|
252
|
-
<div key={i} style={{marginBottom: 8}}>
|
|
253
|
-
<Tag color={m.from === 'me' ? 'blue' : 'green'}>{m.from}</Tag>
|
|
254
|
-
<span style={{marginLeft: 8}}>{m.text}</span>
|
|
255
|
-
<span style={{marginLeft: 8, color: '#999', fontSize: 11}}>{m.time}</span>
|
|
256
|
-
</div>
|
|
257
|
-
))
|
|
258
|
-
)}
|
|
259
|
-
</div>
|
|
260
|
-
<div style={{padding: 8, borderTop: '1px solid #f0f0f0', display: 'flex', gap: 8}}>
|
|
261
|
-
<Input
|
|
262
|
-
value={messageInput}
|
|
263
|
-
onChange={e => setMessageInput(e.target.value)}
|
|
264
|
-
onPressEnter={sendMessage}
|
|
265
|
-
placeholder={wsState === 'connected' ? '输入消息...' : '请先连接 WebSocket'}
|
|
266
|
-
disabled={wsState !== 'connected'}
|
|
267
|
-
/>
|
|
268
|
-
<Button type="primary" icon={<SendOutlined/>} onClick={sendMessage}
|
|
269
|
-
disabled={wsState !== 'connected'}>
|
|
270
|
-
发送
|
|
271
|
-
</Button>
|
|
272
|
-
</div>
|
|
273
|
-
</ProCard>
|
|
274
|
-
</div>
|
|
275
|
-
) : (
|
|
276
|
-
<Empty description="请从左侧选择一个团队"/>
|
|
277
|
-
)}
|
|
278
|
-
|
|
279
|
-
{/* WS 日志 */}
|
|
280
|
-
{wsLog.length > 0 && (
|
|
281
|
-
<ProCard title="WebSocket 日志" size="small" style={{marginTop: 16}}>
|
|
282
|
-
<pre style={{
|
|
283
|
-
background: '#1e1e1e', color: '#d4d4d4', padding: 8, borderRadius: 4,
|
|
284
|
-
fontFamily: 'Menlo, monospace', fontSize: 11, maxHeight: 180, overflow: 'auto',
|
|
285
|
-
margin: 0,
|
|
286
|
-
}}>
|
|
287
|
-
{wsLog.join('\n')}
|
|
288
|
-
</pre>
|
|
289
|
-
</ProCard>
|
|
290
|
-
)}
|
|
291
|
-
</ProCard>
|
|
292
|
-
</div>
|
|
293
|
-
|
|
294
|
-
<Alert
|
|
295
|
-
style={{marginTop: 16}}
|
|
296
|
-
type="info"
|
|
297
|
-
showIcon
|
|
298
|
-
title="说明"
|
|
299
|
-
description={
|
|
300
|
-
<span>
|
|
301
|
-
完整 STOMP 客户端 (含订阅 Topic、ACK、消息历史) 见 <code>z-agent/z-agent-team/z-agent-team-frontend</code>。
|
|
302
|
-
本页提供轻量级 WS 连接 + 群组管理 REST 端点示例。
|
|
303
|
-
</span>
|
|
304
|
-
}
|
|
305
|
-
/>
|
|
306
|
-
</PageContainer>
|
|
307
|
-
)
|
|
308
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
.container {
|
|
2
|
-
min-height: 100vh;
|
|
3
|
-
display: flex;
|
|
4
|
-
align-items: center;
|
|
5
|
-
justify-content: center;
|
|
6
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
.content {
|
|
10
|
-
width: 400px;
|
|
11
|
-
padding: 20px;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
.card {
|
|
15
|
-
border-radius: 8px;
|
|
16
|
-
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.header {
|
|
20
|
-
text-align: center;
|
|
21
|
-
margin-bottom: 24px;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.header h3 {
|
|
25
|
-
margin-bottom: 8px;
|
|
26
|
-
}
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import React, {useState} from 'react'
|
|
2
|
-
import {useNavigate} from 'react-router-dom'
|
|
3
|
-
import {Button, Card, Checkbox, Form, Input, message, Tabs, Typography,} from 'antd'
|
|
4
|
-
import {LockOutlined, MailOutlined, MobileOutlined, UserOutlined} from '@ant-design/icons'
|
|
5
|
-
import {authRequest} from '../../../services/request'
|
|
6
|
-
import styles from './AuthPage.module.css'
|
|
7
|
-
|
|
8
|
-
const {Title, Text} = Typography
|
|
9
|
-
|
|
10
|
-
type LoginType = 'account' | 'phone' | 'email'
|
|
11
|
-
|
|
12
|
-
// 登录请求 — 后端 AuthController @ /api/ctc/auth/login, body: {identifier, password}
|
|
13
|
-
const loginByUsername = (data: { identifier: string; password: string }) =>
|
|
14
|
-
authRequest.post('/ctc/auth/login', data)
|
|
15
|
-
|
|
16
|
-
// 注册 / 手机登录 / 验证码 — 后端暂未实现,占位返回空响应避免 UI 报错
|
|
17
|
-
const stubNotImplemented = () =>
|
|
18
|
-
Promise.resolve({code: 0, message: '功能暂未开放'})
|
|
19
|
-
|
|
20
|
-
const AuthPage: React.FC = () => {
|
|
21
|
-
const navigate = useNavigate()
|
|
22
|
-
const [action, setAction] = useState<'login' | 'register'>('login')
|
|
23
|
-
const [type, setType] = useState<LoginType>('account')
|
|
24
|
-
const [sendingCode, setSendingCode] = useState(false)
|
|
25
|
-
const [countdown, setCountdown] = useState(0)
|
|
26
|
-
const [form] = Form.useForm()
|
|
27
|
-
|
|
28
|
-
// 发送验证码 (后端暂未实现)
|
|
29
|
-
const handleSendCode = async () => {
|
|
30
|
-
message.info('验证码功能暂未开放')
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// 处理登录/注册提交
|
|
34
|
-
const handleSubmit = async (values: any) => {
|
|
35
|
-
try {
|
|
36
|
-
let response: any
|
|
37
|
-
|
|
38
|
-
if (action === 'login' && type === 'account') {
|
|
39
|
-
// 后端 LoginRequest: {identifier, password, tenantCode?}
|
|
40
|
-
response = await loginByUsername({identifier: values.username, password: values.password})
|
|
41
|
-
// 后端返回: {token, account: {id, username, nickname, tenantCode, ...}}
|
|
42
|
-
if (response && response.token) {
|
|
43
|
-
message.success('登录成功!')
|
|
44
|
-
localStorage.setItem('token', response.token)
|
|
45
|
-
const acct = response.account || {}
|
|
46
|
-
localStorage.setItem('userInfo', JSON.stringify({
|
|
47
|
-
userId: acct.id,
|
|
48
|
-
userName: acct.username,
|
|
49
|
-
nickname: acct.nickname,
|
|
50
|
-
tenantCode: acct.tenantCode,
|
|
51
|
-
}))
|
|
52
|
-
navigate('/ctc')
|
|
53
|
-
return
|
|
54
|
-
}
|
|
55
|
-
message.error(response?.message || '登录失败')
|
|
56
|
-
} else {
|
|
57
|
-
// 注册 / 手机登录 — 后端暂未实现
|
|
58
|
-
await stubNotImplemented()
|
|
59
|
-
message.info('该功能暂未开放')
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
} catch (error: any) {
|
|
63
|
-
message.error(error.message || '操作失败,请重试')
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Tab配置
|
|
68
|
-
const getTabItems = () => {
|
|
69
|
-
const items = [
|
|
70
|
-
{key: 'account', label: '账户密码'},
|
|
71
|
-
]
|
|
72
|
-
|
|
73
|
-
if (action === 'register') {
|
|
74
|
-
items.push({key: 'phone', label: '手机注册'})
|
|
75
|
-
items.push({key: 'email', label: '邮箱注册'})
|
|
76
|
-
} else {
|
|
77
|
-
items.push({key: 'phone', label: '手机验证码'})
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return items
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// 获取表单项
|
|
84
|
-
const getFormItems = () => {
|
|
85
|
-
const isLogin = action === 'login'
|
|
86
|
-
|
|
87
|
-
return (
|
|
88
|
-
<>
|
|
89
|
-
{type === 'account' && (
|
|
90
|
-
<>
|
|
91
|
-
<Form.Item name="username" rules={[{required: true, message: '请输入用户名!'}]}>
|
|
92
|
-
<Input
|
|
93
|
-
size="large"
|
|
94
|
-
prefix={<UserOutlined/>}
|
|
95
|
-
placeholder="用户名"
|
|
96
|
-
/>
|
|
97
|
-
</Form.Item>
|
|
98
|
-
<Form.Item name="password" rules={[{required: true, message: '请输入密码!'}]}>
|
|
99
|
-
<Input.Password
|
|
100
|
-
size="large"
|
|
101
|
-
prefix={<LockOutlined/>}
|
|
102
|
-
placeholder="密码"
|
|
103
|
-
/>
|
|
104
|
-
</Form.Item>
|
|
105
|
-
</>
|
|
106
|
-
)}
|
|
107
|
-
|
|
108
|
-
{(type === 'phone' || type === 'email') && (
|
|
109
|
-
<>
|
|
110
|
-
<Form.Item
|
|
111
|
-
name="receiver"
|
|
112
|
-
rules={[
|
|
113
|
-
{required: true, message: type === 'phone' ? '请输入手机号!' : '请输入邮箱!'}
|
|
114
|
-
]}
|
|
115
|
-
>
|
|
116
|
-
<Input
|
|
117
|
-
size="large"
|
|
118
|
-
prefix={type === 'phone' ? <MobileOutlined/> : <MailOutlined/>}
|
|
119
|
-
placeholder={type === 'phone' ? '手机号' : '邮箱'}
|
|
120
|
-
disabled={countdown > 0}
|
|
121
|
-
/>
|
|
122
|
-
</Form.Item>
|
|
123
|
-
<Form.Item>
|
|
124
|
-
<div style={{display: 'flex', gap: 8}}>
|
|
125
|
-
<Input
|
|
126
|
-
size="large"
|
|
127
|
-
prefix={<LockOutlined/>}
|
|
128
|
-
placeholder="验证码"
|
|
129
|
-
style={{flex: 1}}
|
|
130
|
-
/>
|
|
131
|
-
<Button
|
|
132
|
-
type="primary"
|
|
133
|
-
onClick={handleSendCode}
|
|
134
|
-
loading={sendingCode}
|
|
135
|
-
disabled={countdown > 0}
|
|
136
|
-
style={{minWidth: 100}}
|
|
137
|
-
>
|
|
138
|
-
{countdown > 0 ? `${countdown}秒` : '获取验证码'}
|
|
139
|
-
</Button>
|
|
140
|
-
</div>
|
|
141
|
-
</Form.Item>
|
|
142
|
-
{!isLogin && (
|
|
143
|
-
<Form.Item name="password" rules={[{required: true, message: '请输入密码!'}]}>
|
|
144
|
-
<Input.Password
|
|
145
|
-
size="large"
|
|
146
|
-
prefix={<LockOutlined/>}
|
|
147
|
-
placeholder="设置密码"
|
|
148
|
-
/>
|
|
149
|
-
</Form.Item>
|
|
150
|
-
)}
|
|
151
|
-
</>
|
|
152
|
-
)}
|
|
153
|
-
</>
|
|
154
|
-
)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return (
|
|
158
|
-
<div className={styles.container}>
|
|
159
|
-
<div className={styles.content}>
|
|
160
|
-
<Card className={styles.card}>
|
|
161
|
-
{/* 品牌标识 — 内联 SVG(不再显示"CTC 组织管理中心 / 4A + SSO 统一身份认证平台"那种奇怪描述) */}
|
|
162
|
-
<div className={styles.header} style={{textAlign: 'center'}}>
|
|
163
|
-
<svg width="48" height="48" viewBox="0 0 56 56" fill="none" aria-label="logo"
|
|
164
|
-
style={{marginBottom: 8}}>
|
|
165
|
-
<defs>
|
|
166
|
-
<linearGradient id="brandGradAuth" x1="0" y1="0" x2="56" y2="56"
|
|
167
|
-
gradientUnits="userSpaceOnUse">
|
|
168
|
-
<stop offset="0%" stopColor="#1677ff"/>
|
|
169
|
-
<stop offset="100%" stopColor="#0958d9"/>
|
|
170
|
-
</linearGradient>
|
|
171
|
-
</defs>
|
|
172
|
-
<rect x="2" y="2" width="52" height="52" rx="14" fill="url(#brandGradAuth)"/>
|
|
173
|
-
<path
|
|
174
|
-
d="M18 18 H40 L18 38 H40"
|
|
175
|
-
stroke="#ffffff"
|
|
176
|
-
strokeWidth="3.5"
|
|
177
|
-
strokeLinecap="round"
|
|
178
|
-
strokeLinejoin="round"
|
|
179
|
-
fill="none"
|
|
180
|
-
/>
|
|
181
|
-
</svg>
|
|
182
|
-
<Title level={4} style={{margin: 0}}>
|
|
183
|
-
{action === 'register' ? '用户注册' : '欢迎登录'}
|
|
184
|
-
</Title>
|
|
185
|
-
</div>
|
|
186
|
-
|
|
187
|
-
<Form
|
|
188
|
-
form={form}
|
|
189
|
-
layout="vertical"
|
|
190
|
-
onFinish={handleSubmit}
|
|
191
|
-
initialValues={{autoLogin: true}}
|
|
192
|
-
>
|
|
193
|
-
{action !== 'forgot' && (
|
|
194
|
-
<Tabs
|
|
195
|
-
activeKey={type}
|
|
196
|
-
onChange={(key) => setType(key as LoginType)}
|
|
197
|
-
centered
|
|
198
|
-
items={getTabItems()}
|
|
199
|
-
/>
|
|
200
|
-
)}
|
|
201
|
-
|
|
202
|
-
{getFormItems()}
|
|
203
|
-
|
|
204
|
-
<Form.Item>
|
|
205
|
-
<div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
|
|
206
|
-
{action === 'login' && (
|
|
207
|
-
<Form.Item name="autoLogin" valuePropName="checked" noStyle>
|
|
208
|
-
<Checkbox>自动登录</Checkbox>
|
|
209
|
-
</Form.Item>
|
|
210
|
-
)}
|
|
211
|
-
<a
|
|
212
|
-
onClick={() => {
|
|
213
|
-
setAction(action === 'login' ? 'register' : 'login')
|
|
214
|
-
}}
|
|
215
|
-
>
|
|
216
|
-
{action === 'login'
|
|
217
|
-
? '没有账号?立即注册'
|
|
218
|
-
: '已有账号?立即登录'}
|
|
219
|
-
</a>
|
|
220
|
-
</div>
|
|
221
|
-
</Form.Item>
|
|
222
|
-
|
|
223
|
-
<Form.Item>
|
|
224
|
-
<Button type="primary" htmlType="submit" block size="large">
|
|
225
|
-
{action === 'login' ? '登录' : '注册'}
|
|
226
|
-
</Button>
|
|
227
|
-
</Form.Item>
|
|
228
|
-
</Form>
|
|
229
|
-
</Card>
|
|
230
|
-
</div>
|
|
231
|
-
</div>
|
|
232
|
-
)
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
export default AuthPage
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
@import 'antd/dist/reset.css';
|
|
2
|
-
|
|
3
|
-
.container {
|
|
4
|
-
display: flex;
|
|
5
|
-
flex-direction: column;
|
|
6
|
-
height: 100vh;
|
|
7
|
-
overflow: auto;
|
|
8
|
-
background: #f0f2f5;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
.lang {
|
|
12
|
-
width: 100%;
|
|
13
|
-
height: 40px;
|
|
14
|
-
line-height: 44px;
|
|
15
|
-
text-align: right;
|
|
16
|
-
|
|
17
|
-
:global(.ant-dropdown-trigger) {
|
|
18
|
-
margin-right: 24px;
|
|
19
|
-
padding: 12px;
|
|
20
|
-
cursor: pointer;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.content {
|
|
25
|
-
flex: 1;
|
|
26
|
-
padding: 32px 0;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
@media (min-width: 768px) {
|
|
30
|
-
.container {
|
|
31
|
-
background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
|
|
32
|
-
background-repeat: no-repeat;
|
|
33
|
-
background-position: center 110px;
|
|
34
|
-
background-size: 100%;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.content {
|
|
38
|
-
padding: 32px 0 24px;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.icon {
|
|
43
|
-
margin-left: 8px;
|
|
44
|
-
color: rgba(0, 0, 0, 0.2);
|
|
45
|
-
font-size: 24px;
|
|
46
|
-
vertical-align: middle;
|
|
47
|
-
cursor: pointer;
|
|
48
|
-
transition: color 0.3s;
|
|
49
|
-
}
|