Commit 4a9044d7eda186b25b03743dad481e5384be46c5
1 parent
24664be0
feat(usr): rewrite LoginPage visual to match prototype REQ-USR-004
Showing
1 changed file
with
55 additions
and
30 deletions
frontend/src/pages/usr/LoginPage.tsx
| 1 | 1 | import { useEffect, useState } from 'react' |
| 2 | 2 | import { useNavigate } from 'react-router-dom' |
| 3 | -import { Button, Form, Input, message, Select } from 'antd' | |
| 4 | -import { getBrands, login, BrandVO } from '../../api/auth' | |
| 3 | +import { Button, Form, Input, Select, message } from 'antd' | |
| 4 | +import { getBrands, login, type BrandVO } from '../../api/auth' | |
| 5 | 5 | import { setCredentials } from '../../store/slices/authSlice' |
| 6 | +import { activateTab } from '../../store/slices/tabsSlice' | |
| 6 | 7 | import { useAppDispatch } from '../../store/hooks' |
| 7 | 8 | |
| 9 | +const ANTLER_PATHS = [ | |
| 10 | + 'M14 10c2 4 1 8-1 11 3-1 7 0 10 3 1-4 4-7 8-7-3 3-4 7-3 11l4 1c-1 3 0 6 3 8-3 0-6 1-8 4-1-3-4-5-8-5 2-3 2-7 0-10-3 1-7 0-10-3 3 0 5-2 6-5l-1-8z', | |
| 11 | + 'M48 14c-2 3-2 6-1 9-2-2-5-2-8-1 1 3 1 6-1 9 3 0 5 2 6 5 1-3 4-5 7-5-2-3-2-6 0-9 2 1 5 1 7-1-2 0-4-1-5-3-1-2-3-4-5-4z', | |
| 12 | + 'M28 38c2 3 5 5 9 5 1 4 4 7 8 8-3 2-5 5-5 9-3-2-7-3-11-2 1-3 1-7-1-10-3 0-6-1-8-4 3-1 6-3 8-6z', | |
| 13 | +] | |
| 14 | + | |
| 8 | 15 | export default function LoginPage() { |
| 9 | 16 | const [brandOptions, setBrandOptions] = useState<BrandVO[]>([]) |
| 10 | 17 | const [loading, setLoading] = useState(false) |
| ... | ... | @@ -19,17 +26,14 @@ export default function LoginPage() { |
| 19 | 26 | const defaultNo = std ? std.sNo : brands[0]?.sNo |
| 20 | 27 | if (defaultNo) form.setFieldValue('brandNo', defaultNo) |
| 21 | 28 | }).catch(() => {}) |
| 22 | - }, []) | |
| 29 | + }, [form]) | |
| 23 | 30 | |
| 24 | 31 | const onFinish = async (values: { brandNo: string; username: string; password: string }) => { |
| 25 | 32 | setLoading(true) |
| 26 | 33 | try { |
| 27 | 34 | const result = await login(values) |
| 28 | - dispatch(setCredentials({ | |
| 29 | - accessToken: result.accessToken, | |
| 30 | - refreshToken: result.refreshToken, | |
| 31 | - userInfo: result.userInfo | |
| 32 | - })) | |
| 35 | + dispatch(setCredentials({ accessToken: result.accessToken, refreshToken: result.refreshToken, userInfo: result.userInfo })) | |
| 36 | + dispatch(activateTab('main')) | |
| 33 | 37 | navigate('/') |
| 34 | 38 | } catch (e: unknown) { |
| 35 | 39 | message.error(e instanceof Error ? e.message : '登录失败') |
| ... | ... | @@ -39,28 +43,49 @@ export default function LoginPage() { |
| 39 | 43 | } |
| 40 | 44 | |
| 41 | 45 | return ( |
| 42 | - <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '100vh' }}> | |
| 43 | - <div style={{ width: 360 }}> | |
| 44 | - <h2 style={{ textAlign: 'center', marginBottom: 24 }}>小羚羊 ERP</h2> | |
| 45 | - <Form form={form} layout="vertical" onFinish={onFinish}> | |
| 46 | - <Form.Item label="公司/版本" name="brandNo" rules={[{ required: true, message: '请选择公司/版本' }]}> | |
| 47 | - <Select | |
| 48 | - options={brandOptions.map(b => ({ value: b.sNo, label: b.sName }))} | |
| 49 | - placeholder="请选择公司/版本" | |
| 50 | - /> | |
| 51 | - </Form.Item> | |
| 52 | - <Form.Item label="用户名" name="username" rules={[{ required: true, message: '请输入用户名' }]}> | |
| 53 | - <Input placeholder="请输入用户名" /> | |
| 54 | - </Form.Item> | |
| 55 | - <Form.Item label="密码" name="password" rules={[{ required: true, message: '请输入密码' }]}> | |
| 56 | - <Input.Password placeholder="请输入密码" /> | |
| 57 | - </Form.Item> | |
| 58 | - <Form.Item> | |
| 59 | - <Button type="primary" htmlType="submit" loading={loading} block> | |
| 60 | - 登录 | |
| 61 | - </Button> | |
| 62 | - </Form.Item> | |
| 63 | - </Form> | |
| 46 | + <div style={{ position: 'absolute', inset: 0, background: '#eaedf2', display: 'flex', flexDirection: 'column' }}> | |
| 47 | + {/* Header */} | |
| 48 | + <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '18px 36px' }}> | |
| 49 | + <div style={{ width: 42, height: 42, display: 'flex', alignItems: 'center', justifyContent: 'center' }}> | |
| 50 | + <svg viewBox="0 0 64 64" width={42} height={42} fill="#0e1216"> | |
| 51 | + {ANTLER_PATHS.map((d, i) => <path key={i} d={d} />)} | |
| 52 | + </svg> | |
| 53 | + </div> | |
| 54 | + <span style={{ fontSize: 24, fontWeight: 700, color: '#e0a020', letterSpacing: 2 }}>Antler ERP</span> | |
| 55 | + <span style={{ color: '#444', fontSize: 14, marginLeft: 6 }}>欢迎登录EBC平台</span> | |
| 56 | + </div> | |
| 57 | + {/* Hero */} | |
| 58 | + <div style={{ flex: 1, position: 'relative', background: 'radial-gradient(ellipse at center, #1a4ea0 0%, #0a1d44 60%, #050d20 100%)', overflow: 'hidden' }}> | |
| 59 | + {/* Brand text */} | |
| 60 | + <div style={{ position: 'absolute', left: '8%', top: '35%', color: '#fff', zIndex: 2 }}> | |
| 61 | + <div style={{ fontSize: 30, fontWeight: 300, color: '#cfe1ff', marginBottom: 6 }}>Enterprise Business Capability</div> | |
| 62 | + <div style={{ fontSize: 54, fontWeight: 700, letterSpacing: 4, marginBottom: 4 }}>企业业务能力平台</div> | |
| 63 | + <div style={{ fontSize: 90, fontWeight: 800, letterSpacing: 8, lineHeight: 0.9 }}>ERP</div> | |
| 64 | + </div> | |
| 65 | + {/* Login card */} | |
| 66 | + <div style={{ position: 'absolute', right: '8%', top: '50%', transform: 'translateY(-50%)', background: '#fff', width: 380, padding: '36px 32px', borderRadius: 2, boxShadow: '0 12px 40px rgba(0,0,0,.3)', zIndex: 3 }}> | |
| 67 | + <h3 style={{ margin: '0 0 22px', fontSize: 18, color: '#333', fontWeight: 500 }}>用户登录</h3> | |
| 68 | + <Form form={form} layout="vertical" onFinish={onFinish}> | |
| 69 | + <Form.Item label="公司/版本" name="brandNo" rules={[{ required: true, message: '请选择公司/版本' }]}> | |
| 70 | + <Select options={brandOptions.map(b => ({ value: b.sNo, label: b.sName }))} placeholder="请选择公司/版本" /> | |
| 71 | + </Form.Item> | |
| 72 | + <Form.Item label="用户名" name="username" rules={[{ required: true, message: '请输入用户名' }]}> | |
| 73 | + <Input placeholder="请输入用户名" /> | |
| 74 | + </Form.Item> | |
| 75 | + <Form.Item label="密码" name="password" rules={[{ required: true, message: '请输入密码' }]}> | |
| 76 | + <Input.Password placeholder="请输入密码" /> | |
| 77 | + </Form.Item> | |
| 78 | + <Form.Item> | |
| 79 | + <Button type="primary" htmlType="submit" loading={loading} block style={{ height: 42, fontSize: 15, letterSpacing: 8, borderRadius: 2 }}> | |
| 80 | + 登 录 | |
| 81 | + </Button> | |
| 82 | + </Form.Item> | |
| 83 | + </Form> | |
| 84 | + </div> | |
| 85 | + </div> | |
| 86 | + {/* Footer */} | |
| 87 | + <div style={{ background: '#eaedf2', textAlign: 'center', padding: '14px 8px', color: '#666', fontSize: 12, borderTop: '1px solid #d8dce2' }}> | |
| 88 | + 🛠 ©Copyright Antler Software | 印刷ERP | 400-880-6237 | |
| 64 | 89 | </div> |
| 65 | 90 | </div> |
| 66 | 91 | ) | ... | ... |