Files
makemd/docs/03_Frontend/05_UI_Components.md
wurenzhi eafa1bbe94 feat: 添加货币和汇率管理功能
refactor: 重构前端路由和登录逻辑

docs: 更新业务闭环、任务和架构文档

style: 调整代码格式和文件结构

chore: 更新依赖项和配置文件
2026-03-19 19:08:15 +08:00

897 lines
21 KiB
Markdown
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.
# UI Components (Crawlful Hub)
> **定位**Crawlful Hub 前端 UI 组件规范 - 基于 Ant Design 5.x 的组件库使用指南。
> **更新日期**: 2026-03-18
---
## 1. 设计系统
### 1.1 色彩规范
```typescript
// 主色调
const colors = {
primary: '#1890ff', // 主色
success: '#52c41a', // 成功
warning: '#faad14', // 警告
error: '#f5222d', // 错误
info: '#1890ff', // 信息
};
// 中性色
const neutral = {
textPrimary: 'rgba(0, 0, 0, 0.85)',
textSecondary: 'rgba(0, 0, 0, 0.65)',
textDisabled: 'rgba(0, 0, 0, 0.25)',
border: '#d9d9d9',
background: '#f5f5f5',
};
```
### 1.2 字体规范
```typescript
const typography = {
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial',
fontSize: {
small: '12px',
base: '14px',
medium: '16px',
large: '20px',
xlarge: '24px',
},
};
```
---
## 2. 基础组件
### 2.1 按钮 (Button)
**使用场景**
- 主操作:蓝色主按钮
- 次操作:默认按钮
- 危险操作:红色按钮
- 文字链接:链接按钮
**示例**
```tsx
import { Button, Space } from 'antd';
// 主操作
<Button type="primary"></Button>
// 次操作
<Button></Button>
// 危险操作
<Button type="primary" danger></Button>
// 图标按钮
<Button icon={<PlusOutlined />}></Button>
// 加载状态
<Button loading></Button>
```
### 2.2 表单 (Form)
**使用场景**
- 商品创建/编辑
- 订单审核
- 用户设置
**示例**
```tsx
import { Form, Input, Select, Button } from 'antd';
const ProductForm: React.FC = () => {
const [form] = Form.useForm();
const onFinish = (values: any) => {
console.log(values);
};
return (
<Form form={form} onFinish={onFinish} layout="vertical">
<Form.Item
name="title"
label="商品名称"
rules={[{ required: true }]}
>
<Input placeholder="请输入商品名称" />
</Form.Item>
<Form.Item name="platform" label="平台">
<Select>
<Select.Option value="AMAZON">Amazon</Select.Option>
<Select.Option value="EBAY">eBay</Select.Option>
<Select.Option value="SHOPIFY">Shopify</Select.Option>
</Select>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit"></Button>
</Form.Item>
</Form>
);
};
```
### 2.3 表格 (Table)
**使用场景**
- 商品列表
- 订单列表
- 交易流水
**示例**
```tsx
import { Table, Tag } from 'antd';
const ProductTable: React.FC = () => {
const columns = [
{
title: '商品名称',
dataIndex: 'title',
key: 'title',
},
{
title: '平台',
dataIndex: 'platform',
key: 'platform',
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: string) => {
const colorMap: Record<string, string> = {
DRAFTED: 'default',
PENDING_REVIEW: 'processing',
APPROVED: 'success',
REJECTED: 'error',
};
return <Tag color={colorMap[status]}>{status}</Tag>;
},
},
{
title: '售价',
dataIndex: 'sellingPrice',
key: 'sellingPrice',
render: (price: number) => `$${price.toFixed(2)}`,
},
{
title: '操作',
key: 'action',
render: (_, record) => (
<Space>
<Button type="link"></Button>
<Button type="link"></Button>
</Space>
),
},
];
return (
<Table
columns={columns}
dataSource={products}
rowKey="id"
pagination={{ pageSize: 20 }}
/>
);
};
```
### 2.4 卡片 (Card)
**使用场景**
- 仪表盘统计卡片
- 商品信息展示
- 订单概要
**示例**
```tsx
import { Card, Statistic } from 'antd';
import { ArrowUpOutlined } from '@ant-design/icons';
// 统计卡片
<Card>
<Statistic
title="本月营收"
value={112893}
precision={2}
valueStyle={{ color: '#3f8600' }}
prefix="$"
suffix={<ArrowUpOutlined />}
/>
</Card>
// 带标题的卡片
<Card title="商品信息" extra={<a href="#"></a>}>
<p>商品名称: Product Name</p>
<p>平台: Amazon</p>
<p>状态: 已上架</p>
</Card>
```
---
## 3. 业务组件
### 3.1 自定义按钮组件 (CustomButton)
**组件定义**
```tsx
import React from 'react';
import { Button } from 'antd';
export const CustomButton: React.FC<{
type?: 'primary' | 'default' | 'dashed' | 'link' | 'text';
size?: 'small' | 'middle' | 'large';
onClick?: () => void;
children: React.ReactNode;
}> = ({ type = 'default', size = 'middle', onClick, children }) => {
return (
<Button type={type} size={size} onClick={onClick}>
{children}
</Button>
);
};
```
### 3.2 自定义卡片组件 (CustomCard)
**组件定义**
```tsx
import React from 'react';
import { Card } from 'antd';
export const CustomCard: React.FC<{
title?: string;
children: React.ReactNode;
extra?: React.ReactNode;
bordered?: boolean;
hoverable?: boolean;
}> = ({ title, children, extra, bordered = true, hoverable = true }) => {
return (
<Card
title={title}
extra={extra}
bordered={bordered}
hoverable={hoverable}
>
{children}
</Card>
);
};
```
### 3.3 自定义输入框组件 (CustomInput)
**组件定义**
```tsx
import React from 'react';
import { Input } from 'antd';
export const CustomInput: React.FC<{
placeholder?: string;
value?: string;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
disabled?: boolean;
size?: 'small' | 'middle' | 'large';
maxLength?: number;
showCount?: boolean;
}> = ({ placeholder, value, onChange, disabled = false, size = 'middle', maxLength, showCount }) => {
return (
<Input
placeholder={placeholder}
value={value}
onChange={onChange}
disabled={disabled}
size={size}
maxLength={maxLength}
showCount={showCount}
/>
);
};
```
### 3.4 响应式布局组件 (ResponsiveLayout)
**组件定义**
```tsx
import React from 'react';
import { Layout, Row, Col, Menu, ConfigProvider } from 'antd';
import { useMediaQuery } from 'react-responsive';
const { Header, Sider, Content, Footer } = Layout;
export const ResponsiveLayout: React.FC<{
children: React.ReactNode;
menuItems: { key: string; label: string }[];
selectedKey: string;
onMenuSelect: (key: string) => void;
logo?: React.ReactNode;
headerContent?: React.ReactNode;
footerContent?: React.ReactNode;
}> = ({
children,
menuItems,
selectedKey,
onMenuSelect,
logo,
headerContent,
footerContent,
}) => {
const isMobile = useMediaQuery({ maxWidth: 768 });
const isTablet = useMediaQuery({ minWidth: 769, maxWidth: 1024 });
const isDesktop = useMediaQuery({ minWidth: 1025 });
const [collapsed, setCollapsed] = React.useState(false);
const toggleCollapse = () => {
setCollapsed(!collapsed);
};
return (
<ConfigProvider
theme={{
token: {
colorPrimary: '#1890ff',
},
}}
>
<Layout style={{ minHeight: '100vh' }}>
{!isMobile && (
<Sider
collapsible
collapsed={collapsed}
onCollapse={setCollapsed}
style={{
background: '#fff',
}}
>
<div className="logo" style={{ padding: '16px', textAlign: 'center' }}>
{logo || <div style={{ fontSize: '18px', fontWeight: 'bold' }}>Crawlful Hub</div>}
</div>
<Menu
mode="inline"
selectedKeys={[selectedKey]}
onSelect={({ key }) => onMenuSelect(key)}
items={menuItems}
style={{
height: '100%',
borderRight: 0,
}}
/>
</Sider>
)}
<Layout style={{ flex: 1 }}>
<Header
style={{
background: '#fff',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 24px',
}}
>
{isMobile && (
<div style={{ display: 'flex', alignItems: 'center' }}>
<button
onClick={toggleCollapse}
style={{
background: 'none',
border: 'none',
fontSize: '18px',
cursor: 'pointer',
}}
>
</button>
<div style={{ marginLeft: '16px', fontSize: '16px', fontWeight: 'bold' }}>
Crawlful Hub
</div>
</div>
)}
{headerContent || (
<div style={{ fontSize: '14px' }}>Welcome to Crawlful Hub</div>
)}
</Header>
<Content
style={{
margin: '24px',
padding: '24px',
background: '#fff',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
borderRadius: '4px',
minHeight: 280,
}}
>
{children}
</Content>
<Footer
style={{
textAlign: 'center',
background: '#fff',
borderTop: '1px solid #f0f0f0',
}}
>
{footerContent || 'Crawlful Hub ©2026 Created by AI'}
</Footer>
</Layout>
</Layout>
</ConfigProvider>
);
};
```
### 3.5 响应式网格组件 (ResponsiveGrid)
**组件定义**
```tsx
import React from 'react';
import { Row, Col } from 'antd';
export const ResponsiveGrid: React.FC<{
children: React.ReactNode;
gutter?: number;
xs?: number;
sm?: number;
md?: number;
lg?: number;
xl?: number;
}> = ({
children,
gutter = 16,
xs = 24,
sm = 12,
md = 8,
lg = 6,
xl = 4,
}) => {
return (
<Row gutter={gutter}>
<Col xs={xs} sm={sm} md={md} lg={lg} xl={xl}>
{children}
</Col>
</Row>
);
};
```
### 3.6 响应式卡片组件 (ResponsiveCard)
**组件定义**
```tsx
import React from 'react';
import { useMediaQuery } from 'react-responsive';
export const ResponsiveCard: React.FC<{
title?: string;
children: React.ReactNode;
extra?: React.ReactNode;
className?: string;
}> = ({ title, children, extra, className }) => {
const isMobile = useMediaQuery({ maxWidth: 768 });
return (
<div
className={className}
style={{
background: '#fff',
borderRadius: '8px',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
padding: isMobile ? '16px' : '24px',
marginBottom: '16px',
}}
>
{title && (
<div
style={{
fontSize: '16px',
fontWeight: 'bold',
marginBottom: '16px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
{title}
{extra}
</div>
)}
{children}
</div>
);
};
```
### 3.7 响应式导航栏组件 (ResponsiveNavbar)
**组件定义**
```tsx
import React from 'react';
import { useMediaQuery } from 'react-responsive';
export const ResponsiveNavbar: React.FC<{
brand: React.ReactNode;
items: { label: string; href: string; active?: boolean }[];
onItemClick: (href: string) => void;
}> = ({ brand, items, onItemClick }) => {
const isMobile = useMediaQuery({ maxWidth: 768 });
const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false);
return (
<nav
style={{
background: '#fff',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
padding: '0 24px',
}}
>
<div
style={{
maxWidth: '1200px',
margin: '0 auto',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
height: '64px',
}}
>
<div style={{ display: 'flex', alignItems: 'center' }}>{brand}</div>
{!isMobile ? (
<div style={{ display: 'flex', gap: '24px' }}>
{items.map((item, index) => (
<a
key={index}
href={item.href}
onClick={(e) => {
e.preventDefault();
onItemClick(item.href);
}}
style={{
color: item.active ? '#1890ff' : '#333',
textDecoration: 'none',
fontSize: '14px',
fontWeight: item.active ? 'bold' : 'normal',
}}
>
{item.label}
</a>
))}
</div>
) : (
<div>
<button
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
style={{
background: 'none',
border: 'none',
fontSize: '18px',
cursor: 'pointer',
}}
>
</button>
{mobileMenuOpen && (
<div
style={{
position: 'absolute',
top: '64px',
right: '0',
left: '0',
background: '#fff',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
padding: '16px',
zIndex: 1000,
}}
>
{items.map((item, index) => (
<a
key={index}
href={item.href}
onClick={(e) => {
e.preventDefault();
onItemClick(item.href);
setMobileMenuOpen(false);
}}
style={{
display: 'block',
padding: '8px 0',
color: item.active ? '#1890ff' : '#333',
textDecoration: 'none',
fontSize: '14px',
fontWeight: item.active ? 'bold' : 'normal',
}}
>
{item.label}
</a>
))}
</div>
)}
</div>
)}
</div>
</nav>
);
};
```
### 3.8 状态徽章组件 (StatusBadge)
**组件定义**
```tsx
import { Badge } from 'antd';
interface StatusBadgeProps {
status: string;
type: 'product' | 'order' | 'payment';
}
const statusMap: Record<string, Record<string, { color: string; text: string }>> = {
product: {
DRAFTED: { color: 'default', text: '草稿' },
PENDING_REVIEW: { color: 'processing', text: '待审核' },
APPROVED: { color: 'success', text: '已通过' },
REJECTED: { color: 'error', text: '已拒绝' },
LISTED: { color: 'success', text: '已上架' },
DELISTED: { color: 'default', text: '已下架' },
},
order: {
PULLED: { color: 'default', text: '已拉取' },
PENDING_REVIEW: { color: 'processing', text: '待审核' },
CONFIRMED: { color: 'success', text: '已确认' },
SHIPPED: { color: 'blue', text: '已发货' },
DELIVERED: { color: 'success', text: '已送达' },
},
payment: {
PENDING: { color: 'warning', text: '待支付' },
COMPLETED: { color: 'success', text: '已完成' },
FAILED: { color: 'error', text: '失败' },
REFUNDED: { color: 'default', text: '已退款' },
},
};
export const StatusBadge: React.FC<StatusBadgeProps> = ({ status, type }) => {
const config = statusMap[type]?.[status] || { color: 'default', text: status };
return <Badge status={config.color as any} text={config.text} />;
};
```
### 3.9 筛选面板组件 (FilterPanel)
**组件定义**
```tsx
import { Form, Input, Select, DatePicker, Button, Space } from 'antd';
interface FilterPanelProps {
onFilter: (values: any) => void;
onReset: () => void;
}
export const FilterPanel: React.FC<FilterPanelProps> = ({ onFilter, onReset }) => {
const [form] = Form.useForm();
return (
<Form form={form} layout="inline" onFinish={onFilter}>
<Form.Item name="keyword" label="关键词">
<Input placeholder="搜索..." allowClear />
</Form.Item>
<Form.Item name="platform" label="平台">
<Select allowClear style={{ width: 120 }}>
<Select.Option value="AMAZON">Amazon</Select.Option>
<Select.Option value="EBAY">eBay</Select.Option>
<Select.Option value="SHOPIFY">Shopify</Select.Option>
</Select>
</Form.Item>
<Form.Item name="dateRange" label="日期">
<DatePicker.RangePicker />
</Form.Item>
<Form.Item>
<Space>
<Button type="primary" htmlType="submit"></Button>
<Button onClick={() => { form.resetFields(); onReset(); }}></Button>
</Space>
</Form.Item>
</Form>
);
};
```
### 3.10 数据表格组件 (DataTable)
**组件定义**
```tsx
import { Table, TableProps } from 'antd';
interface DataTableProps<T> extends TableProps<T> {
loading?: boolean;
pagination?: {
current: number;
pageSize: number;
total: number;
};
onPageChange?: (page: number, pageSize: number) => void;
}
export function DataTable<T extends object>({
loading,
pagination,
onPageChange,
...tableProps
}: DataTableProps<T>) {
return (
<Table<T>
{...tableProps}
loading={loading}
pagination={pagination ? {
...pagination,
showSizeChanger: true,
showTotal: (total) => `${total}`,
onChange: onPageChange,
} : false}
scroll={{ x: 'max-content' }}
/>
);
}
```
---
## 4. 图表组件
### 4.1 利润趋势图
**使用 Ant Design Charts**
```tsx
import { Line } from '@ant-design/charts';
const ProfitChart: React.FC = () => {
const data = [
{ date: '2026-03-01', profit: 1000 },
{ date: '2026-03-02', profit: 1200 },
{ date: '2026-03-03', profit: 900 },
// ...
];
const config = {
data,
xField: 'date',
yField: 'profit',
smooth: true,
point: {
size: 5,
shape: 'diamond',
},
label: {
style: {
fill: '#aaa',
},
},
};
return <Line {...config} />;
};
```
### 4.2 订单分布图
```tsx
import { Pie } from '@ant-design/charts';
const OrderDistributionChart: React.FC = () => {
const data = [
{ type: 'Amazon', value: 400 },
{ type: 'eBay', value: 300 },
{ type: 'Shopify', value: 300 },
];
const config = {
data,
angleField: 'value',
colorField: 'type',
radius: 0.8,
label: {
type: 'outer',
},
};
return <Pie {...config} />;
};
```
---
## 5. 布局组件
### 5.1 主布局 (MainLayout)
```tsx
import { Layout, Menu } from 'antd';
import { Outlet, useNavigate } from 'react-router-dom';
const { Header, Sider, Content } = Layout;
const MainLayout: React.FC = () => {
const navigate = useNavigate();
const menuItems = [
{ key: '/', label: '仪表盘', icon: <DashboardOutlined /> },
{ key: '/products', label: '商品管理', icon: <ShoppingOutlined /> },
{ key: '/orders', label: '订单管理', icon: <FileTextOutlined /> },
{ key: '/finance', label: '财务管理', icon: <DollarOutlined /> },
{ key: '/inventory', label: '库存管理', icon: <InboxOutlined /> },
{ key: '/marketing', label: '营销广告', icon: <BarChartOutlined /> },
{ key: '/suppliers', label: '供应商', icon: <TeamOutlined /> },
{ key: '/reports', label: '报表分析', icon: <PieChartOutlined /> },
{ key: '/settings', label: '系统设置', icon: <SettingOutlined /> },
];
return (
<Layout style={{ minHeight: '100vh' }}>
<Sider theme="light">
<div style={{ height: 64, padding: 16 }}>Crawlful Hub</div>
<Menu
mode="inline"
items={menuItems}
onClick={({ key }) => navigate(key)}
/>
</Sider>
<Layout>
<Header style={{ background: '#fff', padding: '0 24px' }}>
{/* Header content */}
</Header>
<Content style={{ margin: 24, padding: 24, background: '#fff' }}>
<Outlet />
</Content>
</Layout>
</Layout>
);
};
```
---
## 6. 表单校验规则
```typescript
// utils/validators.ts
export const validators = {
required: (message: string) => ({ required: true, message }),
email: { type: 'email', message: '请输入有效的邮箱地址' },
price: (min: number = 0) => ({
validator: (_: any, value: number) => {
if (value >= min) return Promise.resolve();
return Promise.reject(new Error(`价格不能低于 ${min}`));
},
}),
url: { type: 'url', message: '请输入有效的URL' },
};
```
---
## 7. 相关文档
- [Frontend Design](./Frontend_Design.md)
- [Pages Flow](./Pages_Flow.md)
- [Ant Design 官方文档](https://ant.design/)
---
*本文档基于 Ant Design 5.x最后更新: 2026-03-18*