feat: improve user interface layout for better accessibility

This commit is contained in:
2026-02-06 15:40:26 +08:00
parent 9961b9f87a
commit a7dd3e49a9
5 changed files with 119 additions and 211 deletions

View File

@@ -6,7 +6,6 @@ import PublicQueryPage from './pages/PublicQuery';
import DashboardPage from './pages/Dashboard';
import GeneratePage from './pages/Generate';
import ManagePage from './pages/Manage';
import AdminQueryPage from './pages/AdminQuery';
import ProfilePage from './pages/Profile';
const PrivateRoute = () => {
@@ -39,7 +38,6 @@ function App() {
<Route path="/admin/dashboard" element={<DashboardPage />} />
<Route path="/admin/generate" element={<GeneratePage />} />
<Route path="/admin/manage" element={<ManagePage />} />
<Route path="/admin/query" element={<AdminQueryPage />} />
<Route path="/admin/profile" element={<ProfilePage />} />
</Route>
</Route>

View File

@@ -4,7 +4,6 @@ import {
DashboardOutlined,
QrcodeOutlined,
TeamOutlined,
SearchOutlined,
UserOutlined,
LogoutOutlined,
LockOutlined,
@@ -40,12 +39,6 @@ function AdminLayout() {
label: '企业管理',
onClick: () => navigate('/admin/manage'),
},
{
key: 'query',
icon: <SearchOutlined />,
label: '序列号查询',
onClick: () => navigate('/admin/query'),
},
];
const handleLogout = () => {
@@ -99,7 +92,6 @@ function AdminLayout() {
if (path.includes('/dashboard')) return 'dashboard';
if (path.includes('/generate')) return 'generate';
if (path.includes('/manage')) return 'manage';
if (path.includes('/query')) return 'query';
if (path.includes('/profile')) return 'profile';
return 'dashboard';
};

View File

@@ -1,100 +0,0 @@
import { useState } from 'react';
import { Form, Input, Button, Card, message, Result, Alert } from 'antd';
import { SearchOutlined } from '@ant-design/icons';
import { serialApi } from '@/services/api';
import type { Serial } from '@/types';
function AdminQueryPage() {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [result, setResult] = useState<Serial | null>(null);
const [error, setError] = useState<string | null>(null);
const handleQuery = async (values: { serialNumber: string }) => {
setLoading(true);
setError(null);
setResult(null);
try {
const data = await serialApi.query(values.serialNumber.trim());
setResult(data);
message.success('查询成功!');
} catch (err: any) {
setError(err.message || '查询失败');
} finally {
setLoading(false);
}
};
return (
<div style={{ maxWidth: '600px', margin: '0 auto' }}>
<Card title="序列号查询" bordered={false}>
<Form
form={form}
onFinish={handleQuery}
layout="vertical"
>
<Form.Item
label="授权序列号"
name="serialNumber"
rules={[{ required: true, message: '请输入授权序列号' }]}
>
<Input
placeholder="请输入授权序列号BF20260001"
size="large"
/>
</Form.Item>
<Form.Item>
<Button
type="primary"
htmlType="submit"
loading={loading}
size="large"
block
icon={<SearchOutlined />}
>
</Button>
</Form.Item>
</Form>
</Card>
{result && (
<Card style={{ marginTop: '24px' }}>
<Result
status="success"
title="授权有效"
subTitle="您的序列号已验证通过"
/>
<Alert
message={
<div>
<p><strong>:</strong> {result.serialNumber}</p>
<p><strong>:</strong> {result.companyName}</p>
<p><strong>:</strong> <span style={{ color: '#52c41a', fontWeight: 'bold' }}></span></p>
<p><strong>:</strong> {new Date(result.validUntil).toLocaleString('zh-CN')}</p>
<p><strong>:</strong> {new Date(result.createdAt).toLocaleString('zh-CN')}</p>
</div>
}
type="success"
showIcon
/>
</Card>
)}
{error && (
<Card style={{ marginTop: '24px' }}>
<Result
status="error"
title="无效序列号"
subTitle={error}
/>
</Card>
)}
</div>
);
}
export default AdminQueryPage;

View File

@@ -1,9 +1,10 @@
import { useState } from 'react';
import { Form, Input, Button, Card, Radio, InputNumber, DatePicker, message, Modal, Space, ColorPicker, Divider } from 'antd';
import { Form, Input, Button, Card, Radio, InputNumber, DatePicker, message, Modal, Space, ColorPicker, Divider, Row, Col } from 'antd';
import { QrcodeOutlined } from '@ant-design/icons';
import QRCode from 'qrcode';
import type { Color } from 'antd/es/color-picker';
import { useNavigate } from 'react-router-dom';
import './styles/Generate.css';
function GeneratePage() {
const [form] = Form.useForm();
@@ -93,8 +94,16 @@ function GeneratePage() {
};
return (
<div style={{ maxWidth: '800px', margin: '0 auto' }}>
<Card title="生成二维码" bordered={false}>
<div>
<Card
title={
<Space>
<QrcodeOutlined />
<span></span>
</Space>
}
bordered={false}
>
<Form
form={form}
onFinish={handleGenerate}
@@ -106,112 +115,118 @@ function GeneratePage() {
validDays: 365,
}}
>
<Form.Item
label="企业名称"
name="companyName"
rules={[{ required: true, message: '请输入企业名称' }]}
>
<Input placeholder="输入企业名称(如:浙江贝凡)" />
</Form.Item>
<Row gutter={24}>
<Col span={12}>
<Form.Item
label="企业名称"
name="companyName"
rules={[{ required: true, message: '请输入企业名称' }]}
>
<Input placeholder="输入企业名称(如:浙江贝凡)" />
</Form.Item>
<Form.Item label="序列号设置" name="serialOption">
<Radio.Group>
<Radio value="auto"></Radio>
<Radio value="custom"></Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="序列号设置" name="serialOption">
<Radio.Group>
<Radio value="auto"></Radio>
<Radio value="custom"></Radio>
</Radio.Group>
</Form.Item>
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) => prevValues.serialOption !== currentValues.serialOption}
>
{({ getFieldValue }) =>
getFieldValue('serialOption') === 'custom' ? (
<Form.Item
label="自定义前缀"
name="serialPrefix"
rules={[{ max: 10, message: '前缀不能超过10个字符' }]}
>
<Input placeholder="输入自定义前缀MYCOMPANY" maxLength={10} />
</Form.Item>
) : null
}
</Form.Item>
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) => prevValues.serialOption !== currentValues.serialOption}
>
{({ getFieldValue }) =>
getFieldValue('serialOption') === 'custom' ? (
<Form.Item
label="自定义前缀"
name="serialPrefix"
rules={[{ max: 10, message: '前缀不能超过10个字符' }]}
>
<Input placeholder="输入自定义前缀MYCOMPANY" maxLength={10} />
</Form.Item>
) : null
}
</Form.Item>
<Form.Item
label="序列号数量"
name="quantity"
rules={[{ required: true, message: '请输入序列号数量' }]}
>
<InputNumber min={1} max={100} style={{ width: '100%' }} />
</Form.Item>
<Form.Item
label="序列号数量"
name="quantity"
rules={[{ required: true, message: '请输入序列号数量' }]}
>
<InputNumber min={1} max={100} style={{ width: '100%' }} />
</Form.Item>
</Col>
<Form.Item label="有效期设置" name="validOption">
<Radio.Group>
<Radio value="days"></Radio>
<Radio value="date"></Radio>
</Radio.Group>
</Form.Item>
<Col span={12}>
<Form.Item label="有效期设置" name="validOption">
<Radio.Group>
<Radio value="days"></Radio>
<Radio value="date"></Radio>
</Radio.Group>
</Form.Item>
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) => prevValues.validOption !== currentValues.validOption}
>
{({ getFieldValue }) =>
getFieldValue('validOption') === 'days' ? (
<Form.Item
label="有效天数"
name="validDays"
rules={[{ required: true, message: '请输入有效天数' }]}
>
<InputNumber min={1} max={3650} style={{ width: '100%' }} />
</Form.Item>
) : (
<Form.Item
label="有效期至"
name="validUntil"
rules={[{ required: true, message: '请选择有效期' }]}
>
<DatePicker showTime style={{ width: '100%' }} />
</Form.Item>
)
}
</Form.Item>
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) => prevValues.validOption !== currentValues.validOption}
>
{({ getFieldValue }) =>
getFieldValue('validOption') === 'days' ? (
<Form.Item
label="有效天数"
name="validDays"
rules={[{ required: true, message: '请输入有效天数' }]}
>
<InputNumber min={1} max={3650} style={{ width: '100%' }} />
</Form.Item>
) : (
<Form.Item
label="有效期至"
name="validUntil"
rules={[{ required: true, message: '请选择有效期' }]}
>
<DatePicker showTime style={{ width: '100%' }} />
</Form.Item>
)
}
</Form.Item>
<Form.Item
label="二维码颜色"
name="qrColor"
initialValue="#000000"
>
<div>
<div style={{ marginBottom: '12px', display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
{colorPresets.map((color) => (
<div
key={color}
onClick={() => setQrColor(color)}
style={{
width: '32px',
height: '32px',
backgroundColor: color,
border: qrColor === color ? '2px solid #165DFF' : '2px solid #d9d9d9',
borderRadius: '4px',
cursor: 'pointer',
transition: 'all 0.2s',
<Form.Item
label="二维码颜色"
name="qrColor"
initialValue="#000000"
>
<div>
<div style={{ marginBottom: '12px', display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
{colorPresets.map((color) => (
<div
key={color}
onClick={() => setQrColor(color)}
style={{
width: '32px',
height: '32px',
backgroundColor: color,
border: qrColor === color ? '2px solid #165DFF' : '2px solid #d9d9d9',
borderRadius: '4px',
cursor: 'pointer',
transition: 'all 0.2s',
}}
/>
))}
</div>
<Divider style={{ margin: '12px 0' }} />
<ColorPicker
value={qrColor}
onChange={(color: Color) => {
const hexColor = color.toHexString();
setQrColor(hexColor);
}}
showText
/>
))}
</div>
<Divider style={{ margin: '12px 0' }} />
<ColorPicker
value={qrColor}
onChange={(color: Color) => {
const hexColor = color.toHexString();
setQrColor(hexColor);
}}
showText
/>
</div>
</Form.Item>
</div>
</Form.Item>
</Col>
</Row>
<Form.Item>
<Button

View File

@@ -0,0 +1,3 @@
.generate-page {
padding: 24px;
}