外包风险评估系统:领域引擎+前端+服务端持久化与生产部署
- 确定性领域引擎(分类/评分/分级/红线/费用/裁决)+LLM(通义千问)语言理解 - 6步评估向导、服务端草稿持久化(跨设备/编辑草稿保护) - 工作流(草稿→风控→管理层)、RBAC、报告导出、校准、客户/费率/红线/最低工资管理 - 专业图标体系替换全部emoji、看板美化 - 生产化:API_BASE可配置(同源反代)、auth密钥惰性读取修复RBAC - 444单测+204前端测试+51 e2e
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* 登录页面 — 3 个测试角色账号。
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
colorVar,
|
||||
FONT_FAMILY,
|
||||
RADIUS,
|
||||
SHADOW,
|
||||
space,
|
||||
typographyStyle,
|
||||
} from '../design-system/components/styles.js';
|
||||
import { useAuthStore, TEST_ACCOUNTS } from '../stores/authStore.js';
|
||||
|
||||
export function Login(): JSX.Element {
|
||||
const navigate = useNavigate();
|
||||
const { login, error, clearError } = useAuthStore();
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
|
||||
const handleSubmit = (e: React.FormEvent): void => {
|
||||
e.preventDefault();
|
||||
clearError();
|
||||
const ok = login(username, password);
|
||||
if (ok) {
|
||||
navigate('/');
|
||||
}
|
||||
};
|
||||
|
||||
const pageStyle: React.CSSProperties = {
|
||||
minHeight: '100vh',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: `${space(5)}px`,
|
||||
padding: `${space(6)}px ${space(4)}px`,
|
||||
backgroundColor: colorVar('color.bg.canvas'),
|
||||
fontFamily: FONT_FAMILY,
|
||||
};
|
||||
|
||||
const cardStyle: React.CSSProperties = {
|
||||
fontFamily: FONT_FAMILY,
|
||||
width: '100%',
|
||||
maxWidth: 400,
|
||||
padding: `${space(7)}px ${space(6)}px`,
|
||||
backgroundColor: colorVar('color.bg.elevated'),
|
||||
borderRadius: `${RADIUS.lg}px`,
|
||||
border: `1px solid ${colorVar('color.border.default')}`,
|
||||
boxShadow: SHADOW.md,
|
||||
};
|
||||
|
||||
const inputStyle: React.CSSProperties = {
|
||||
width: '100%',
|
||||
padding: `${space(2)}px ${space(3)}px`,
|
||||
border: `1px solid ${colorVar('color.border.default')}`,
|
||||
borderRadius: `${RADIUS.md}px`,
|
||||
fontFamily: FONT_FAMILY,
|
||||
...typographyStyle('body'),
|
||||
backgroundColor: colorVar('color.bg.canvas'),
|
||||
color: colorVar('color.text.primary'),
|
||||
boxSizing: 'border-box',
|
||||
};
|
||||
|
||||
const buttonStyle: React.CSSProperties = {
|
||||
width: '100%',
|
||||
padding: `${space(3)}px`,
|
||||
backgroundColor: colorVar('color.brand.primary'),
|
||||
color: colorVar('color.text.onAccent'),
|
||||
border: 'none',
|
||||
borderRadius: `${RADIUS.md}px`,
|
||||
cursor: 'pointer',
|
||||
...typographyStyle('body'),
|
||||
fontWeight: 600,
|
||||
letterSpacing: '-0.01em',
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={pageStyle}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: `${space(3)}px` }}>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
width: 52,
|
||||
height: 52,
|
||||
borderRadius: `${RADIUS.lg}px`,
|
||||
background: `linear-gradient(135deg, ${colorVar('color.brand.primary')}, #7C83F0)`,
|
||||
color: '#fff',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 700,
|
||||
fontSize: '24px',
|
||||
boxShadow: SHADOW.sm,
|
||||
}}
|
||||
>
|
||||
风
|
||||
</div>
|
||||
<div style={{ textAlign: 'center', display: 'flex', flexDirection: 'column', gap: `${space(1)}px` }}>
|
||||
<h1 style={{ ...typographyStyle('heading'), margin: 0, letterSpacing: '-0.02em', color: colorVar('color.text.primary') }}>
|
||||
外包项目风险评估
|
||||
</h1>
|
||||
<span style={{ ...typographyStyle('caption'), color: colorVar('color.text.secondary') }}>
|
||||
智能风险评估平台
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={cardStyle}>
|
||||
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: `${space(3)}px` }}>
|
||||
<div>
|
||||
<label style={{ display: 'block', marginBottom: `${space(1)}px`, color: colorVar('color.text.secondary'), ...typographyStyle('caption') }}>
|
||||
用户名
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
placeholder="请输入用户名"
|
||||
style={inputStyle}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label style={{ display: 'block', marginBottom: `${space(1)}px`, color: colorVar('color.text.secondary'), ...typographyStyle('caption') }}>
|
||||
密码
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="请输入密码"
|
||||
style={inputStyle}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error !== null && (
|
||||
<div style={{ color: colorVar('color.risk.critical'), ...typographyStyle('caption'), textAlign: 'center' }}>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button type="submit" style={buttonStyle}>
|
||||
登录
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div style={{ marginTop: `${space(5)}px`, paddingTop: `${space(4)}px`, borderTop: `1px solid ${colorVar('color.border.default')}` }}>
|
||||
<p style={{ margin: `0 0 ${space(3)}px`, color: colorVar('color.text.secondary'), ...typographyStyle('caption'), textAlign: 'center' }}>
|
||||
点击角色快速登录
|
||||
</p>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: `${space(2)}px` }}>
|
||||
{TEST_ACCOUNTS.map((a) => (
|
||||
<button
|
||||
key={a.username}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setUsername(a.username);
|
||||
setPassword(a.password);
|
||||
}}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: `${space(2)}px ${space(3)}px`,
|
||||
backgroundColor: colorVar('color.bg.surface'),
|
||||
border: `1px solid ${colorVar('color.border.default')}`,
|
||||
borderRadius: `${RADIUS.md}px`,
|
||||
cursor: 'pointer',
|
||||
fontFamily: FONT_FAMILY,
|
||||
textAlign: 'left',
|
||||
width: '100%',
|
||||
color: colorVar('color.text.primary'),
|
||||
}}
|
||||
>
|
||||
<span style={{ fontWeight: 600 }}>{a.role}</span>
|
||||
<span style={{ color: colorVar('color.text.secondary'), ...typographyStyle('caption') }}>
|
||||
{a.username} / {a.password}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user