Files
zpark-ems/frontend/src/pages/Login/index.tsx
Du Wenbo 1274e77cb4 feat: Z-Park branding, data collection fix, buyoff pass (v1.3.0)
Branding:
- Replace all Tianpu text/colors with Z-Park (green #52c41a)
- Update login, sidebar, BigScreen, localStorage keys

Data collection:
- Populate ps_id for all 10 inverters (Phase1: 2226182, Phase2: 2226188)
- Fix docker-compose volume mount for customer config.yaml

Buyoff warning fixes:
- Installed capacity: 2200 kW / 10 Sungrow inverters (was wrong Huawei data)
- Feature flags: hide charging menu when features.charging=false
- Device total count: compute client-side from stats
- Device groups: enrich group names from metadata

Buyoff result: CONDITIONAL PASS (21/21 critical, 54/63 total)
Data accuracy: <3% deviation from iSolarCloud reference

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 22:05:06 +08:00

89 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from 'react';
import { Form, Input, Button, Card, message, Typography } from 'antd';
import { UserOutlined, LockOutlined, ThunderboltOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import { login } from '../../services/api';
import { setToken, setUser } from '../../utils/auth';
const { Title, Text } = Typography;
export default function LoginPage() {
const [loading, setLoading] = useState(false);
const [guestLoading, setGuestLoading] = useState(false);
const navigate = useNavigate();
const doLogin = async (username: string, password: string) => {
const res: any = await login(username, password);
setToken(res.access_token);
setUser(res.user);
return res;
};
const onFinish = async (values: { username: string; password: string }) => {
setLoading(true);
try {
await doLogin(values.username, values.password);
message.success('登录成功');
navigate('/');
} catch {
message.error('用户名或密码错误');
} finally {
setLoading(false);
}
};
const onGuestLogin = async () => {
setGuestLoading(true);
try {
await doLogin('visitor', 'visitor123');
message.success('访客登录成功');
navigate('/');
} catch {
message.error('访客登录失败,请联系管理员');
} finally {
setGuestLoading(false);
}
};
return (
<div style={{
minHeight: '100vh', display: 'flex', justifyContent: 'center', alignItems: 'center',
background: 'linear-gradient(135deg, #0a1628 0%, #1a3a5c 50%, #0d2137 100%)',
}}>
<Card style={{ width: 400, borderRadius: 12, boxShadow: '0 8px 32px rgba(0,0,0,0.3)' }}>
<div style={{ textAlign: 'center', marginBottom: 32 }}>
<ThunderboltOutlined style={{ fontSize: 48, color: '#52c41a' }} />
<Title level={3} style={{ marginTop: 12, marginBottom: 4 }}>
</Title>
<Text type="secondary"> · </Text>
</div>
<Form onFinish={onFinish} size="large">
<Form.Item name="username" rules={[{ required: true, message: '请输入用户名' }]}>
<Input prefix={<UserOutlined />} placeholder="用户名" />
</Form.Item>
<Form.Item name="password" rules={[{ required: true, message: '请输入密码' }]}>
<Input.Password prefix={<LockOutlined />} placeholder="密码" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" loading={loading} block>
</Button>
</Form.Item>
<Form.Item style={{ marginBottom: 8 }}>
<Button block loading={guestLoading} onClick={onGuestLogin}
style={{ borderColor: '#52c41a', color: '#52c41a' }}>
访
</Button>
</Form.Item>
<div style={{ textAlign: 'center' }}>
<Text type="secondary" style={{ fontSize: 12 }}>
访
</Text>
</div>
</Form>
</Card>
</div>
);
}