feat: 添加前端页面和业务说明书
refactor(server): 重构服务层代码结构 feat(server): 添加基础设施、跨境电商、AI决策等核心服务 docs: 完善前端业务说明书和开发进度文档 style: 格式化代码和文档
This commit is contained in:
@@ -222,7 +222,410 @@ import { ArrowUpOutlined } from '@ant-design/icons';
|
||||
|
||||
## 3. 业务组件
|
||||
|
||||
### 3.1 状态徽章 (StatusBadge)
|
||||
### 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
|
||||
@@ -263,7 +666,7 @@ export const StatusBadge: React.FC<StatusBadgeProps> = ({ status, type }) => {
|
||||
};
|
||||
```
|
||||
|
||||
### 3.2 筛选面板 (FilterPanel)
|
||||
### 3.9 筛选面板组件 (FilterPanel)
|
||||
|
||||
**组件定义**
|
||||
```tsx
|
||||
@@ -306,7 +709,7 @@ export const FilterPanel: React.FC<FilterPanelProps> = ({ onFilter, onReset }) =
|
||||
};
|
||||
```
|
||||
|
||||
### 3.3 数据表格 (DataTable)
|
||||
### 3.10 数据表格组件 (DataTable)
|
||||
|
||||
**组件定义**
|
||||
```tsx
|
||||
|
||||
Reference in New Issue
Block a user