feat: 集成基本功能

This commit is contained in:
micah 2026-03-14 15:10:11 +08:00
parent a746c26f94
commit ba94d299e3
10 changed files with 1352 additions and 434 deletions

967
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,8 @@
"preview": "vite preview"
},
"dependencies": {
"@ant-design/icons": "^6.1.0",
"antd": "^6.3.2",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router-dom": "^7.13.1"

View File

@ -8,6 +8,13 @@ const USE_MOCK = true // 启用 Mock 数据
const BASE_URL = '/api'
// 全局 Message API 实例
let messageApi = null
export const setMessageApi = (api) => {
messageApi = api
}
class ApiService {
constructor(baseUrl = BASE_URL) {
this.baseUrl = baseUrl
@ -27,6 +34,15 @@ class ApiService {
return this.token || localStorage.getItem('token')
}
// 显示错误消息
showError(message) {
if (messageApi) {
messageApi.error(message)
} else {
console.error('Message API not initialized:', message)
}
}
async request(endpoint, options = {}) {
if (USE_MOCK) {
return this.mockRequest(endpoint, options)
@ -51,13 +67,16 @@ class ApiService {
if (!response.ok) {
const error = await response.json().catch(() => ({}))
throw new Error(error.message || `HTTP ${response.status}`)
const errMsg = error.message || `HTTP ${response.status}`
this.showError(errMsg)
return { code: -1, message: errMsg }
}
return await response.json()
} catch (error) {
console.error('API Error:', error)
throw error
this.showError(error.message || '网络错误')
return { code: -1, message: error.message || '网络错误' }
}
}
@ -90,7 +109,7 @@ class ApiService {
}
mockRequest(endpoint, options) {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
setTimeout(() => {
const { method = 'GET', body } = options
const data = body ? JSON.parse(body) : {}
@ -107,7 +126,8 @@ class ApiService {
message: '登录成功'
})
} else {
reject(new Error('用户名或密码错误'))
this.showError('用户名或密码错误')
resolve({ code: -1, message: '用户名或密码错误' })
}
return
}
@ -163,7 +183,9 @@ class ApiService {
return
}
reject(new Error(`Mock: 未知接口 ${endpoint}`))
const errMsg = `Mock: 未知接口 ${endpoint}`
this.showError(errMsg)
resolve({ code: -1, message: errMsg })
}, 300)
})
}

View File

@ -3,7 +3,7 @@
*/
import api from '../index'
export const authApi = {
const authApi = {
/**
* 登录
* @param {string} username 用户名

View File

@ -1,10 +1,31 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { ConfigProvider, App as AntApp } from 'antd'
import zhCN from 'antd/locale/zh_CN'
import 'antd/dist/reset.css'
import './index.css'
import App from './App.jsx'
import { setMessageApi } from './api'
// App message API
// eslint-disable-next-line react-refresh/only-export-components
function AppWrapper() {
const { message } = AntApp.useApp()
// message API
if (message) {
setMessageApi(message)
}
return <App />
}
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
<ConfigProvider locale={zhCN}>
<AntApp>
<AppWrapper />
</AntApp>
</ConfigProvider>
</StrictMode>,
)
)

View File

@ -1,5 +1,7 @@
import { useState, useEffect } from 'react'
import { statsApi } from '../api/modules/stats'
import { Card, Row, Col, Statistic, Spin } from 'antd'
import { UserOutlined, ShoppingOutlined, DollarOutlined } from '@ant-design/icons'
import statsApi from '../api/modules/stats'
function Home() {
const [stats, setStats] = useState({
@ -28,50 +30,60 @@ function Home() {
return (
<div>
<h1>欢迎来到管理后台</h1>
<h2 style={styles.title}>欢迎来到 Atlas Console</h2>
{loading ? (
<p>加载中...</p>
) : (
<div style={styles.cards}>
<div style={styles.card}>
<h3>📊 数据统计</h3>
<p style={styles.number}>{stats.totalUsers.toLocaleString()}</p>
<p>总用户数</p>
</div>
<div style={styles.card}>
<h3>📦 订单</h3>
<p style={styles.number}>{stats.todayOrders.toLocaleString()}</p>
<p>今日订单</p>
</div>
<div style={styles.card}>
<h3>💰 收入</h3>
<p style={styles.number}>¥{stats.todayRevenue.toLocaleString()}</p>
<p>今日收入</p>
</div>
<div style={styles.loading}>
<Spin size="large" />
</div>
) : (
<Row gutter={[16, 16]}>
<Col xs={24} sm={8}>
<Card>
<Statistic
title="总用户数"
value={stats.totalUsers}
prefix={<UserOutlined />}
styles={{ value: { color: '#3f8600' } }}
/>
</Card>
</Col>
<Col xs={24} sm={8}>
<Card>
<Statistic
title="今日订单"
value={stats.todayOrders}
prefix={<ShoppingOutlined />}
styles={{ value: { color: '#1890ff' } }}
/>
</Card>
</Col>
<Col xs={24} sm={8}>
<Card>
<Statistic
title="今日收入"
value={stats.todayRevenue}
prefix={<DollarOutlined />}
precision={2}
suffix="元"
/>
</Card>
</Col>
</Row>
)}
</div>
)
}
const styles = {
cards: {
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
gap: '24px',
marginTop: '24px',
title: {
marginBottom: '24px',
},
card: {
backgroundColor: '#fff',
padding: '24px',
borderRadius: '8px',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
},
number: {
fontSize: '32px',
fontWeight: 'bold',
color: '#1890ff',
margin: '16px 0',
loading: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
minHeight: '200px',
},
}

View File

@ -1,11 +1,25 @@
import { useState } from 'react'
import { Outlet, NavLink, useNavigate } from 'react-router-dom'
import { authApi } from '../api/modules/auth'
import { Layout as AntLayout, Menu, Button, theme } from 'antd'
import {
HomeOutlined,
AppstoreOutlined,
SettingOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
LogoutOutlined,
} from '@ant-design/icons'
import authApi from '../api/modules/auth'
import api from '../api'
const { Header, Sider, Content } = AntLayout
function Layout({ onLogout }) {
const [collapsed, setCollapsed] = useState(false)
const navigate = useNavigate()
const {
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken()
const handleLogout = async () => {
try {
@ -19,131 +33,114 @@ function Layout({ onLogout }) {
}
}
const menuItems = [
{
key: '/',
icon: <HomeOutlined />,
label: <NavLink to="/">首页</NavLink>,
},
{
key: '/menu1',
icon: <AppstoreOutlined />,
label: <NavLink to="/menu1">菜单1</NavLink>,
},
{
key: '/menu2',
icon: <SettingOutlined />,
label: <NavLink to="/menu2">菜单2</NavLink>,
},
]
return (
<div style={styles.container}>
{/* 侧边栏 */}
<div style={{ ...styles.sidebar, width: collapsed ? '80px' : '200px' }}>
<AntLayout style={{ minHeight: '100vh' }}>
<Sider
trigger={null}
collapsible
collapsed={collapsed}
theme="dark"
width={200}
collapsedWidth={80}
>
<div style={styles.logo}>
{collapsed ? 'A' : 'Atlas'}
{collapsed ? 'A' : 'Atlas Console'}
</div>
<nav style={styles.nav}>
<NavLink to="/" style={({ isActive }) => isActive ? { ...styles.navItem, ...styles.active } : styles.navItem}>
<span style={styles.icon}>🏠</span>
{!collapsed && <span>首页</span>}
</NavLink>
<NavLink to="/menu1" style={({ isActive }) => isActive ? { ...styles.navItem, ...styles.active } : styles.navItem}>
<span style={styles.icon}>📦</span>
{!collapsed && <span>菜单1</span>}
</NavLink>
<NavLink to="/menu2" style={({ isActive }) => isActive ? { ...styles.navItem, ...styles.active } : styles.navItem}>
<span style={styles.icon}></span>
{!collapsed && <span>菜单2</span>}
</NavLink>
</nav>
<button onClick={() => setCollapsed(!collapsed)} style={styles.collapseBtn}>
{collapsed ? '→' : '←'}
</button>
</div>
{/* 主内容区 */}
<div style={styles.main}>
{/* 顶部栏 */}
<div style={styles.header}>
<span style={styles.headerTitle}>Atlas Console</span>
<button onClick={handleLogout} style={styles.logoutBtn}>退出登录</button>
</div>
{/* 内容区 */}
<div style={styles.content}>
<Menu
theme="dark"
mode="inline"
defaultSelectedKeys={['/']}
items={menuItems}
style={styles.menu}
/>
</Sider>
<AntLayout>
<Header style={styles.header(colorBgContainer)}>
<Button
type="text"
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
onClick={() => setCollapsed(!collapsed)}
style={styles.trigger}
/>
<div style={styles.headerRight}>
<span style={styles.userInfo}>管理员</span>
<Button
type="text"
danger
icon={<LogoutOutlined />}
onClick={handleLogout}
>
退出登录
</Button>
</div>
</Header>
<Content style={styles.content(colorBgContainer, borderRadiusLG)}>
<Outlet />
</div>
</div>
</div>
</Content>
</AntLayout>
</AntLayout>
)
}
const styles = {
container: {
display: 'flex',
height: '100vh',
},
sidebar: {
backgroundColor: '#001529',
color: '#fff',
display: 'flex',
flexDirection: 'column',
transition: 'width 0.3s',
},
logo: {
height: '64px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '20px',
fontWeight: 'bold',
borderBottom: '1px solid #ffffff20',
},
nav: {
flex: 1,
padding: '16px 0',
},
navItem: {
display: 'flex',
alignItems: 'center',
gap: '12px',
padding: '12px 24px',
color: '#ffffff80',
textDecoration: 'none',
transition: 'all 0.2s',
},
active: {
backgroundColor: '#1890ff',
color: '#fff',
},
icon: {
fontSize: '18px',
},
collapseBtn: {
padding: '12px',
backgroundColor: '#ffffff10',
border: 'none',
fontWeight: 'bold',
color: '#fff',
cursor: 'pointer',
borderBottom: '1px solid rgba(255, 255, 255, 0.1)',
},
main: {
flex: 1,
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
menu: {
borderRight: 'none',
},
header: {
height: '64px',
backgroundColor: '#fff',
header: (colorBgContainer) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 24px',
boxShadow: '0 1px 4px rgba(0,0,0,0.1)',
},
headerTitle: {
padding: '0 16px',
background: colorBgContainer,
borderBottom: '1px solid #f0f0f0',
}),
trigger: {
fontSize: '18px',
fontWeight: 'bold',
color: '#333',
padding: '0 12px',
},
logoutBtn: {
padding: '8px 16px',
backgroundColor: '#ff4d4f',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
headerRight: {
display: 'flex',
alignItems: 'center',
gap: '16px',
},
content: {
flex: 1,
userInfo: {
color: '#666',
},
content: (colorBgContainer, borderRadiusLG) => ({
margin: '24px',
padding: '24px',
overflow: 'auto',
backgroundColor: '#f5f5f5',
},
minHeight: '280px',
background: colorBgContainer,
borderRadius: borderRadiusLG,
}),
}
export default Layout

View File

@ -1,36 +1,30 @@
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { authApi } from '../api/modules/auth'
import { Form, Input, Button, message } from 'antd'
import { UserOutlined, LockOutlined } from '@ant-design/icons'
import authApi from '../api/modules/auth'
import api from '../api'
function Login({ onLogin }) {
const navigate = useNavigate()
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
const handleSubmit = async (e) => {
e.preventDefault()
console.log('Login clicked', { username, password })
setError('')
const handleSubmit = async (values) => {
setLoading(true)
try {
// API使 Mock
const response = await authApi.login(username, password)
console.log('Login response:', response)
const response = await authApi.login(values.username, values.password)
if (response.code === 0) {
api.setToken(response.data.token)
message.success('登录成功')
onLogin()
navigate('/')
} else {
setError(response.message || '登录失败')
message.error(response.message || '登录失败')
}
} catch (err) {
console.error('Login error:', err)
setError(err.message || '用户名或密码错误')
message.error(err.message || '用户名或密码错误')
} finally {
setLoading(false)
}
@ -39,29 +33,47 @@ function Login({ onLogin }) {
return (
<div style={styles.container}>
<div style={styles.card}>
<h2 style={styles.title}>登录</h2>
<form onSubmit={handleSubmit} style={styles.form}>
<input
type="text"
placeholder="用户名"
value={username}
onChange={(e) => setUsername(e.target.value)}
style={styles.input}
disabled={loading}
/>
<input
type="password"
placeholder="密码"
value={password}
onChange={(e) => setPassword(e.target.value)}
style={styles.input}
disabled={loading}
/>
{error && <p style={styles.error}>{error}</p>}
<button type="submit" style={styles.button} disabled={loading}>
{loading ? '登录中...' : '登录'}
</button>
</form>
<h2 style={styles.title}>Atlas Console</h2>
<p style={styles.subtitle}>登录您的账户</p>
<Form
name="login"
onFinish={handleSubmit}
autoComplete="off"
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>
<p style={styles.hint}>测试账号: admin / admin123</p>
</div>
</div>
@ -73,45 +85,26 @@ const styles = {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh',
backgroundColor: '#f5f5f5',
minHeight: '100vh',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
},
card: {
backgroundColor: '#fff',
padding: '40px',
borderRadius: '8px',
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
width: '320px',
boxShadow: '0 4px 20px rgba(0,0,0,0.15)',
width: '360px',
},
title: {
textAlign: 'center',
marginBottom: '24px',
marginBottom: '8px',
color: '#333',
},
form: {
display: 'flex',
flexDirection: 'column',
gap: '16px',
},
input: {
padding: '12px',
border: '1px solid #ddd',
borderRadius: '4px',
fontSize: '14px',
},
button: {
padding: '12px',
backgroundColor: '#1890ff',
color: '#fff',
border: 'none',
borderRadius: '4px',
fontSize: '16px',
cursor: 'pointer',
},
error: {
color: '#ff4d4f',
fontSize: '14px',
subtitle: {
textAlign: 'center',
marginBottom: '24px',
color: '#666',
fontSize: '14px',
},
hint: {
marginTop: '16px',

View File

@ -1,5 +1,6 @@
import { useState, useEffect } from 'react'
import { projectApi } from '../api/modules/project'
import { Table, Tag, Progress, Card } from 'antd'
import projectApi from '../api/modules/project'
function Menu1() {
const [projects, setProjects] = useState([])
@ -24,98 +25,61 @@ function Menu1() {
const getStatusColor = (status) => {
switch (status) {
case '已完成': return '#52c41a'
case '进行中': return '#1890ff'
default: return '#d9d9d9'
case '已完成': return 'success'
case '进行中': return 'processing'
default: return 'default'
}
}
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 80,
},
{
title: '项目名称',
dataIndex: 'name',
key: 'name',
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status) => (
<Tag color={getStatusColor(status)}>{status}</Tag>
),
},
{
title: '进度',
dataIndex: 'progress',
key: 'progress',
render: (progress) => (
<Progress percent={progress} size="small" />
),
},
]
return (
<div>
<h1>菜单1 - 项目管理</h1>
{loading ? (
<p>加载中...</p>
) : (
<div style={styles.table}>
<div style={styles.header}>
<div style={{...styles.cell, flex: 1}}>ID</div>
<div style={{...styles.cell, flex: 2}}>项目名称</div>
<div style={{...styles.cell, flex: 1}}>状态</div>
<div style={{...styles.cell, flex: 2}}>进度</div>
</div>
{projects.map(item => (
<div key={item.id} style={styles.row}>
<div style={{...styles.cell, flex: 1}}>{item.id}</div>
<div style={{...styles.cell, flex: 2}}>{item.name}</div>
<div style={{...styles.cell, flex: 1}}>
<span style={{
...styles.badge,
backgroundColor: getStatusColor(item.status)
}}>
{item.status}
</span>
</div>
<div style={{...styles.cell, flex: 2}}>
<div style={styles.progressBg}>
<div style={{...styles.progressBar, width: `${item.progress}%`}}></div>
</div>
<span style={styles.progressText}>{item.progress}%</span>
</div>
</div>
))}
</div>
)}
<h2 style={styles.title}>项目管理</h2>
<Card>
<Table
columns={columns}
dataSource={projects}
rowKey="id"
loading={loading}
pagination={false}
/>
</Card>
</div>
)
}
const styles = {
table: {
marginTop: '24px',
backgroundColor: '#fff',
borderRadius: '8px',
overflow: 'hidden',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
},
header: {
display: 'flex',
backgroundColor: '#fafafa',
padding: '16px',
fontWeight: 'bold',
borderBottom: '1px solid #f0f0f0',
},
row: {
display: 'flex',
padding: '16px',
borderBottom: '1px solid #f0f0f0',
},
cell: {
display: 'flex',
alignItems: 'center',
},
badge: {
padding: '4px 12px',
borderRadius: '4px',
color: '#fff',
fontSize: '12px',
},
progressBg: {
flex: 1,
height: '8px',
backgroundColor: '#f0f0f0',
borderRadius: '4px',
marginRight: '8px',
},
progressBar: {
height: '100%',
backgroundColor: '#1890ff',
borderRadius: '4px',
transition: 'width 0.3s',
},
progressText: {
fontSize: '12px',
color: '#666',
width: '40px',
title: {
marginBottom: '24px',
},
}

View File

@ -1,196 +1,138 @@
import { useState, useEffect } from 'react'
import { settingsApi } from '../api/modules/settings'
import { useState, useEffect, useCallback, useRef } from 'react'
import { Card, Form, Input, Switch, Button, message } from 'antd'
import settingsApi from '../api/modules/settings'
function Menu2() {
const [settings, setSettings] = useState([])
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
const [message, setMessage] = useState('')
const [form] = Form.useForm()
const formRef = useRef(form)
useEffect(() => {
fetchSettings()
}, [])
const fetchSettings = async () => {
const fetchSettings = useCallback(async () => {
try {
const response = await settingsApi.getList()
if (response.code === 0) {
setSettings(response.data)
//
const formData = {}
response.data.forEach(item => {
formData[item.key] = item.value
})
formRef.current.setFieldsValue(formData)
}
} catch (error) {
console.error('Failed to fetch settings:', error)
} finally {
setLoading(false)
}
}
}, [])
useEffect(() => {
fetchSettings()
}, [fetchSettings])
const handleSave = async () => {
setSaving(true)
setMessage('')
try {
const response = await settingsApi.save(settings)
const values = form.getFieldsValue()
//
const updatedSettings = settings.map(item => ({
...item,
value: values[item.key]
}))
setSaving(true)
const response = await settingsApi.save(updatedSettings)
if (response.code === 0) {
setMessage('保存成功')
setTimeout(() => setMessage(''), 3000)
message.success('保存成功')
}
} catch (error) {
setMessage('保存失败: ' + error.message)
message.error('保存失败: ' + error.message)
} finally {
setSaving(false)
}
}
const updateSetting = (key, value) => {
setSettings(settings.map(s =>
s.key === key ? { ...s, value } : s
))
const renderField = (item) => {
if (item.type === 'switch') {
return (
<Form.Item
key={item.key}
name={item.key}
valuePropName="checked"
style={styles.formItem}
>
<Switch />
</Form.Item>
)
}
return (
<Form.Item
key={item.key}
name={item.key}
style={styles.formItem}
>
<Input style={{ maxWidth: 300 }} />
</Form.Item>
)
}
return (
<div>
<h1>菜单2 - 系统设置</h1>
{loading ? (
<p>加载中...</p>
) : (
<div style={styles.card}>
<h2 style={styles.title}>系统设置</h2>
<Card loading={loading}>
<Form
form={form}
layout="vertical"
style={styles.form}
>
{settings.map(item => (
<div key={item.key} style={styles.row}>
<div style={styles.label}>{item.label}</div>
<div style={styles.value}>
{item.type === 'switch' ? (
<label style={styles.switch}>
<input
type="checkbox"
checked={item.value === true}
onChange={(e) => updateSetting(item.key, e.target.checked)}
style={styles.switchInput}
/>
<span style={{
...styles.slider,
backgroundColor: item.value === true ? '#52c41a' : '#ccc',
}}>
<span style={{
...styles.sliderBefore,
transform: item.value === true ? 'translateX(22px)' : 'translateX(0)',
}}></span>
</span>
</label>
) : (
<input
type="text"
value={item.value}
onChange={(e) => updateSetting(item.key, e.target.value)}
style={styles.input}
/>
)}
</div>
<span style={styles.label}>{item.label}</span>
{renderField(item)}
</div>
))}
<div style={styles.actions}>
<button
style={styles.saveBtn}
onClick={handleSave}
disabled={saving}
>
{saving ? '保存中...' : '保存设置'}
</button>
{message && (
<span style={message.includes('成功') ? styles.successMsg : styles.errorMsg}>
{message}
</span>
)}
</div>
</Form>
<div style={styles.actions}>
<Button
type="primary"
onClick={handleSave}
loading={saving}
>
保存设置
</Button>
</div>
)}
</Card>
</div>
)
}
const styles = {
card: {
marginTop: '24px',
backgroundColor: '#fff',
padding: '24px',
borderRadius: '8px',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
title: {
marginBottom: '24px',
},
form: {
maxWidth: 600,
},
row: {
display: 'flex',
alignItems: 'center',
padding: '16px 0',
borderBottom: '1px solid #f0f0f0',
marginBottom: '8px',
},
label: {
width: '150px',
fontWeight: 'bold',
fontWeight: '500',
color: '#333',
},
value: {
formItem: {
marginBottom: '0',
flex: 1,
},
input: {
width: '100%',
maxWidth: '400px',
padding: '8px 12px',
border: '1px solid #d9d9d9',
borderRadius: '4px',
fontSize: '14px',
},
switch: {
position: 'relative',
display: 'inline-block',
width: '44px',
height: '22px',
},
switchInput: {
opacity: 0,
width: 0,
height: 0,
},
slider: {
position: 'absolute',
cursor: 'pointer',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: '#ccc',
transition: '0.3s',
borderRadius: '22px',
},
sliderBefore: {
position: 'absolute',
content: '""',
height: '18px',
width: '18px',
left: '2px',
bottom: '2px',
backgroundColor: 'white',
transition: '0.3s',
borderRadius: '50%',
},
actions: {
marginTop: '24px',
display: 'flex',
alignItems: 'center',
gap: '16px',
},
saveBtn: {
padding: '10px 24px',
backgroundColor: '#1890ff',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '14px',
},
successMsg: {
color: '#52c41a',
fontSize: '14px',
},
errorMsg: {
color: '#ff4d4f',
fontSize: '14px',
paddingTop: '24px',
borderTop: '1px solid #f0f0f0',
},
}