feat: 迭代
This commit is contained in:
@@ -29,6 +29,7 @@ import PlatformWithdrawals from '@/pages/finance/PlatformWithdrawals';
|
||||
import BankCards from '@/pages/finance/BankCards';
|
||||
import InviteManage from '@/pages/InviteManage';
|
||||
import ReviewManage from '@/pages/ReviewManage';
|
||||
import AdminManage from '@/pages/AdminManage';
|
||||
|
||||
const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const token = localStorage.getItem('admin_token');
|
||||
@@ -71,6 +72,7 @@ const App: React.FC = () => (
|
||||
<Route path="coupons/create" element={<CouponForm />} />
|
||||
<Route path="coupons/edit/:id" element={<CouponForm />} />
|
||||
<Route path="promotions" element={<Promotion />} />
|
||||
<Route path="admins" element={<AdminManage />} />
|
||||
<Route path="settings" element={<SystemSettings />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
|
||||
@@ -67,3 +67,72 @@ export function getPlatformStatistics() {
|
||||
export function getOrderTrend(params: { startDate: string; endDate: string }) {
|
||||
return request.get('/api/admin/finance/reports/trend', { params });
|
||||
}
|
||||
|
||||
// 管理员管理
|
||||
export interface Admin {
|
||||
id: number;
|
||||
username: string;
|
||||
name: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
role: 'super_admin' | 'admin' | 'operator';
|
||||
status: 'active' | 'frozen';
|
||||
lastLoginAt?: string;
|
||||
lastLoginIp?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface CreateAdminParams {
|
||||
username: string;
|
||||
password: string;
|
||||
name: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
role: 'super_admin' | 'admin' | 'operator';
|
||||
}
|
||||
|
||||
export interface UpdateAdminParams {
|
||||
name?: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
role?: 'super_admin' | 'admin' | 'operator';
|
||||
status?: 'active' | 'frozen';
|
||||
}
|
||||
|
||||
export interface QueryAdminParams {
|
||||
username?: string;
|
||||
name?: string;
|
||||
role?: 'super_admin' | 'admin' | 'operator';
|
||||
status?: 'active' | 'frozen';
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export function getAdminList(params: QueryAdminParams) {
|
||||
return request.get('/api/admin/admins', { params });
|
||||
}
|
||||
|
||||
export function getAdminById(id: number) {
|
||||
return request.get(`/api/admin/admins/${id}`);
|
||||
}
|
||||
|
||||
export function createAdmin(data: CreateAdminParams) {
|
||||
return request.post('/api/admin/admins', data);
|
||||
}
|
||||
|
||||
export function updateAdmin(id: number, data: UpdateAdminParams) {
|
||||
return request.put(`/api/admin/admins/${id}`, data);
|
||||
}
|
||||
|
||||
export function updateAdminPassword(id: number, password: string) {
|
||||
return request.put(`/api/admin/admins/${id}/password`, { password });
|
||||
}
|
||||
|
||||
export function toggleAdminStatus(id: number) {
|
||||
return request.put(`/api/admin/admins/${id}/toggle-status`);
|
||||
}
|
||||
|
||||
export function deleteAdmin(id: number) {
|
||||
return request.delete(`/api/admin/admins/${id}`);
|
||||
}
|
||||
|
||||
@@ -6,4 +6,10 @@ export const updateServiceFeeConfig = (rate: number) => request.put('/api/admin/
|
||||
|
||||
export const getStorageConfig = () => request.get('/api/admin/config/storage');
|
||||
|
||||
export const updateStorageConfig = (data: Record<string, string>) => request.put('/api/admin/config/storage', data);
|
||||
export const updateStorageConfig = (data: Record<string, string>) => request.put('/api/admin/config/storage', data);
|
||||
|
||||
export const getWithdrawConfig = () => request.get('/api/admin/config/withdraw');
|
||||
|
||||
export const updateMerchantMinWithdrawAmount = (amount: number) => request.put('/api/admin/config/withdraw/merchant-min', { amount });
|
||||
|
||||
export const updatePlatformMinWithdrawAmount = (amount: number) => request.put('/api/admin/config/withdraw/platform-min', { amount });
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
BarChartOutlined,
|
||||
TagOutlined,
|
||||
CreditCardOutlined,
|
||||
SafetyOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useAuthStore } from '@/store/auth';
|
||||
|
||||
@@ -53,6 +54,7 @@ const menuItems = [
|
||||
{ key: '/invite', icon: <TrophyOutlined />, label: '邀请返现' },
|
||||
{ key: '/coupons', icon: <TagOutlined />, label: '优惠券管理' },
|
||||
{ key: '/promotions', icon: <GiftOutlined />, label: '推广管理' },
|
||||
{ key: '/admins', icon: <SafetyOutlined />, label: '管理员管理' },
|
||||
{ key: '/settings', icon: <SettingOutlined />, label: '系统设置' },
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,416 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Card, Table, Button, Space, Tag, Modal, Form, Input, Select, message, Popconfirm } from 'antd';
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined, LockOutlined, StopOutlined, CheckCircleOutlined } from '@ant-design/icons';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { formatDateTime } from '@rent/shared-utils';
|
||||
import {
|
||||
getAdminList,
|
||||
createAdmin,
|
||||
updateAdmin,
|
||||
updateAdminPassword,
|
||||
toggleAdminStatus,
|
||||
deleteAdmin,
|
||||
type Admin,
|
||||
type CreateAdminParams,
|
||||
type UpdateAdminParams,
|
||||
type QueryAdminParams
|
||||
} from '@/api/admin';
|
||||
|
||||
const ROLE_MAP = {
|
||||
super_admin: { label: '超级管理员', color: 'red' },
|
||||
admin: { label: '管理员', color: 'blue' },
|
||||
operator: { label: '运营人员', color: 'green' },
|
||||
};
|
||||
|
||||
const STATUS_MAP = {
|
||||
active: { label: '正常', color: 'success' },
|
||||
frozen: { label: '冻结', color: 'error' },
|
||||
};
|
||||
|
||||
const AdminManage: React.FC = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [dataSource, setDataSource] = useState<Admin[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(20);
|
||||
const [filters, setFilters] = useState<QueryAdminParams>({});
|
||||
|
||||
const [createModalVisible, setCreateModalVisible] = useState(false);
|
||||
const [editModalVisible, setEditModalVisible] = useState(false);
|
||||
const [passwordModalVisible, setPasswordModalVisible] = useState(false);
|
||||
const [currentAdmin, setCurrentAdmin] = useState<Admin | null>(null);
|
||||
|
||||
const [createForm] = Form.useForm();
|
||||
const [editForm] = Form.useForm();
|
||||
const [passwordForm] = Form.useForm();
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchData();
|
||||
}, [page, pageSize, filters]);
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res: any = await getAdminList({ ...filters, page, pageSize });
|
||||
setDataSource(res.data.items || []);
|
||||
setTotal(res.data.total || 0);
|
||||
} catch (error: any) {
|
||||
message.error(error.response?.data?.message || '获取管理员列表失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreate = () => {
|
||||
createForm.resetFields();
|
||||
setCreateModalVisible(true);
|
||||
};
|
||||
|
||||
const handleCreateSubmit = async () => {
|
||||
try {
|
||||
const values = await createForm.validateFields();
|
||||
await createAdmin(values);
|
||||
message.success('创建成功');
|
||||
setCreateModalVisible(false);
|
||||
fetchData();
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
message.error(error.response?.data?.message || '创建失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleEdit = (record: Admin) => {
|
||||
setCurrentAdmin(record);
|
||||
editForm.setFieldsValue({
|
||||
name: record.name,
|
||||
phone: record.phone,
|
||||
email: record.email,
|
||||
role: record.role,
|
||||
status: record.status,
|
||||
});
|
||||
setEditModalVisible(true);
|
||||
};
|
||||
|
||||
const handleEditSubmit = async () => {
|
||||
if (!currentAdmin) return;
|
||||
try {
|
||||
const values = await editForm.validateFields();
|
||||
await updateAdmin(currentAdmin.id, values);
|
||||
message.success('更新成功');
|
||||
setEditModalVisible(false);
|
||||
fetchData();
|
||||
} catch (error: any) {
|
||||
message.error(error.response?.data?.message || '更新失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetPassword = (record: Admin) => {
|
||||
setCurrentAdmin(record);
|
||||
passwordForm.resetFields();
|
||||
setPasswordModalVisible(true);
|
||||
};
|
||||
|
||||
const handlePasswordSubmit = async () => {
|
||||
if (!currentAdmin) return;
|
||||
try {
|
||||
const values = await passwordForm.validateFields();
|
||||
await updateAdminPassword(currentAdmin.id, values.password);
|
||||
message.success('密码重置成功');
|
||||
setPasswordModalVisible(false);
|
||||
} catch (error: any) {
|
||||
message.error(error.response?.data?.message || '密码重置失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleStatus = async (record: Admin) => {
|
||||
try {
|
||||
await toggleAdminStatus(record.id);
|
||||
message.success('状态切换成功');
|
||||
fetchData();
|
||||
} catch (error: any) {
|
||||
message.error(error.response?.data?.message || '状态切换失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (record: Admin) => {
|
||||
try {
|
||||
await deleteAdmin(record.id);
|
||||
message.success('删除成功');
|
||||
fetchData();
|
||||
} catch (error: any) {
|
||||
message.error(error.response?.data?.message || '删除失败');
|
||||
}
|
||||
};
|
||||
|
||||
const columns: ColumnsType<Admin> = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'username',
|
||||
key: 'username',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
dataIndex: 'phone',
|
||||
key: 'phone',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
dataIndex: 'email',
|
||||
key: 'email',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
dataIndex: 'role',
|
||||
key: 'role',
|
||||
width: 120,
|
||||
render: (role: string) => {
|
||||
const roleInfo = ROLE_MAP[role as keyof typeof ROLE_MAP];
|
||||
return <Tag color={roleInfo?.color}>{roleInfo?.label || role}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
render: (status: string) => {
|
||||
const statusInfo = STATUS_MAP[status as keyof typeof STATUS_MAP];
|
||||
return <Tag color={statusInfo?.color}>{statusInfo?.label || status}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '最后登录时间',
|
||||
dataIndex: 'lastLoginAt',
|
||||
key: 'lastLoginAt',
|
||||
width: 180,
|
||||
render: (date: string) => date ? formatDateTime(date) : '-',
|
||||
},
|
||||
{
|
||||
title: '最后登录IP',
|
||||
dataIndex: 'lastLoginIp',
|
||||
key: 'lastLoginIp',
|
||||
width: 140,
|
||||
render: (ip: string) => ip || '-',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createdAt',
|
||||
key: 'createdAt',
|
||||
width: 180,
|
||||
render: (date: string) => formatDateTime(date),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 280,
|
||||
fixed: 'right',
|
||||
render: (_, record) => (
|
||||
<Space size="small">
|
||||
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => handleEdit(record)}>
|
||||
编辑
|
||||
</Button>
|
||||
<Button type="link" size="small" icon={<LockOutlined />} onClick={() => handleResetPassword(record)}>
|
||||
重置密码
|
||||
</Button>
|
||||
{record.role !== 'super_admin' && (
|
||||
<>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
icon={record.status === 'active' ? <StopOutlined /> : <CheckCircleOutlined />}
|
||||
onClick={() => handleToggleStatus(record)}
|
||||
>
|
||||
{record.status === 'active' ? '冻结' : '解冻'}
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title="确定要删除该管理员吗?"
|
||||
onConfirm={() => handleDelete(record)}
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Button type="link" size="small" danger icon={<DeleteOutlined />}>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 style={{ marginBottom: 24 }}>管理员管理</h2>
|
||||
|
||||
<Card>
|
||||
<Space style={{ marginBottom: 16 }}>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>
|
||||
新增管理员
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={dataSource}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
scroll={{ x: 1600 }}
|
||||
pagination={{
|
||||
current: page,
|
||||
pageSize,
|
||||
total,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `共 ${total} 条`,
|
||||
onChange: (page, pageSize) => {
|
||||
setPage(page);
|
||||
setPageSize(pageSize);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* 创建管理员 */}
|
||||
<Modal
|
||||
title="新增管理员"
|
||||
open={createModalVisible}
|
||||
onOk={handleCreateSubmit}
|
||||
onCancel={() => setCreateModalVisible(false)}
|
||||
width={600}
|
||||
>
|
||||
<Form form={createForm} layout="vertical">
|
||||
<Form.Item
|
||||
name="username"
|
||||
label="用户名"
|
||||
rules={[
|
||||
{ required: true, message: '请输入用户名' },
|
||||
{ min: 3, max: 50, message: '用户名长度为3-50个字符' },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入用户名" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="password"
|
||||
label="密码"
|
||||
rules={[
|
||||
{ required: true, message: '请输入密码' },
|
||||
{ min: 6, max: 50, message: '密码长度为6-50个字符' },
|
||||
]}
|
||||
>
|
||||
<Input.Password placeholder="请输入密码" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="姓名"
|
||||
rules={[{ required: true, message: '请输入姓名' }]}
|
||||
>
|
||||
<Input placeholder="请输入姓名" />
|
||||
</Form.Item>
|
||||
<Form.Item name="phone" label="手机号">
|
||||
<Input placeholder="请输入手机号" />
|
||||
</Form.Item>
|
||||
<Form.Item name="email" label="邮箱">
|
||||
<Input placeholder="请输入邮箱" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="role"
|
||||
label="角色"
|
||||
rules={[{ required: true, message: '请选择角色' }]}
|
||||
>
|
||||
<Select placeholder="请选择角色">
|
||||
<Select.Option value="admin">管理员</Select.Option>
|
||||
<Select.Option value="operator">运营人员</Select.Option>
|
||||
<Select.Option value="super_admin">超级管理员</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* 编辑管理员 */}
|
||||
<Modal
|
||||
title="编辑管理员"
|
||||
open={editModalVisible}
|
||||
onOk={handleEditSubmit}
|
||||
onCancel={() => setEditModalVisible(false)}
|
||||
width={600}
|
||||
>
|
||||
<Form form={editForm} layout="vertical">
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="姓名"
|
||||
rules={[{ required: true, message: '请输入姓名' }]}
|
||||
>
|
||||
<Input placeholder="请输入姓名" />
|
||||
</Form.Item>
|
||||
<Form.Item name="phone" label="手机号">
|
||||
<Input placeholder="请输入手机号" />
|
||||
</Form.Item>
|
||||
<Form.Item name="email" label="邮箱">
|
||||
<Input placeholder="请输入邮箱" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="role"
|
||||
label="角色"
|
||||
rules={[{ required: true, message: '请选择角色' }]}
|
||||
>
|
||||
<Select placeholder="请选择角色">
|
||||
<Select.Option value="admin">管理员</Select.Option>
|
||||
<Select.Option value="operator">运营人员</Select.Option>
|
||||
<Select.Option value="super_admin">超级管理员</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="status"
|
||||
label="状态"
|
||||
rules={[{ required: true, message: '请选择状态' }]}
|
||||
>
|
||||
<Select placeholder="请选择状态">
|
||||
<Select.Option value="active">正常</Select.Option>
|
||||
<Select.Option value="frozen">冻结</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* 重置密码 */}
|
||||
<Modal
|
||||
title="重置密码"
|
||||
open={passwordModalVisible}
|
||||
onOk={handlePasswordSubmit}
|
||||
onCancel={() => setPasswordModalVisible(false)}
|
||||
width={500}
|
||||
>
|
||||
<Form form={passwordForm} layout="vertical">
|
||||
<Form.Item
|
||||
name="password"
|
||||
label="新密码"
|
||||
rules={[
|
||||
{ required: true, message: '请输入新密码' },
|
||||
{ min: 6, max: 50, message: '密码长度为6-50个字符' },
|
||||
]}
|
||||
>
|
||||
<Input.Password placeholder="请输入新密码" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminManage;
|
||||
@@ -1,20 +1,33 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Card, Form, InputNumber, Button, message, Spin, Divider } from 'antd';
|
||||
import { getServiceFeeConfig, updateServiceFeeConfig } from '@/api/config';
|
||||
import { getServiceFeeConfig, updateServiceFeeConfig, getWithdrawConfig, updateMerchantMinWithdrawAmount, updatePlatformMinWithdrawAmount } from '@/api/config';
|
||||
import StorageSettings from './StorageSettings';
|
||||
|
||||
const SystemSettings: React.FC = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [savingWithdraw, setSavingWithdraw] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [withdrawForm] = Form.useForm();
|
||||
|
||||
const fetchConfig = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res: any = await getServiceFeeConfig();
|
||||
form.setFieldsValue({ serviceFeeRate: res.data?.rate || 0.05 });
|
||||
const [feeRes, withdrawRes]: any = await Promise.all([
|
||||
getServiceFeeConfig(),
|
||||
getWithdrawConfig(),
|
||||
]);
|
||||
form.setFieldsValue({ serviceFeeRate: feeRes.data?.rate || 0.05 });
|
||||
withdrawForm.setFieldsValue({
|
||||
merchantMinWithdrawAmount: withdrawRes.data?.merchantMinWithdrawAmount || 100,
|
||||
platformMinWithdrawAmount: withdrawRes.data?.platformMinWithdrawAmount || 10,
|
||||
});
|
||||
} catch (e) {
|
||||
form.setFieldsValue({ serviceFeeRate: 0.05 });
|
||||
withdrawForm.setFieldsValue({
|
||||
merchantMinWithdrawAmount: 100,
|
||||
platformMinWithdrawAmount: 10,
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -36,6 +49,23 @@ const SystemSettings: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveWithdraw = async () => {
|
||||
try {
|
||||
const values = await withdrawForm.validateFields();
|
||||
setSavingWithdraw(true);
|
||||
await Promise.all([
|
||||
updateMerchantMinWithdrawAmount(values.merchantMinWithdrawAmount),
|
||||
updatePlatformMinWithdrawAmount(values.platformMinWithdrawAmount),
|
||||
]);
|
||||
message.success('提现配置已保存');
|
||||
fetchConfig();
|
||||
} catch (e: any) {
|
||||
if (e?.message) message.error(e.message);
|
||||
} finally {
|
||||
setSavingWithdraw(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) return <Spin size="large" style={{ display: 'block', marginTop: 100 }} />;
|
||||
|
||||
return (
|
||||
@@ -83,6 +113,59 @@ const SystemSettings: React.FC = () => {
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card title="提现配置" style={{ maxWidth: 600, marginBottom: 24 }}>
|
||||
<Form form={withdrawForm} layout="vertical">
|
||||
<Form.Item
|
||||
label="商家提现最低金额"
|
||||
name="merchantMinWithdrawAmount"
|
||||
rules={[
|
||||
{ required: true, message: '请输入商家提现最低金额' },
|
||||
{ type: 'number', min: 0, message: '金额不能为负数' },
|
||||
]}
|
||||
extra="商家申请提现时的最低金额限制"
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
step={10}
|
||||
precision={2}
|
||||
style={{ width: '100%' }}
|
||||
addonAfter="元"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="平台提现最低金额"
|
||||
name="platformMinWithdrawAmount"
|
||||
rules={[
|
||||
{ required: true, message: '请输入平台提现最低金额' },
|
||||
{ type: 'number', min: 0, message: '金额不能为负数' },
|
||||
]}
|
||||
extra="平台账户申请提现时的最低金额限制"
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
step={10}
|
||||
precision={2}
|
||||
style={{ width: '100%' }}
|
||||
addonAfter="元"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button type="primary" loading={savingWithdraw} onClick={handleSaveWithdraw}>
|
||||
保存配置
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
<Divider />
|
||||
|
||||
<div style={{ color: '#666', fontSize: 14 }}>
|
||||
<p><strong>说明:</strong></p>
|
||||
<p>• 商家提现:商家在商家后台申请提现时,提现金额必须大于等于设置的最低金额</p>
|
||||
<p>• 平台提现:平台管理员在平台后台申请提现时,提现金额必须大于等于设置的最低金额</p>
|
||||
<p>• 用户提现:用户提现最低金额由邀请活动配置中的"提现门槛"控制</p>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<StorageSettings />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Card, Row, Col, Statistic, Descriptions, Button, Space, Modal, Form, In
|
||||
import { WalletOutlined, ArrowUpOutlined, ArrowDownOutlined, DollarOutlined, ExportOutlined, HistoryOutlined } from '@ant-design/icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { getPlatformAccounts, createPlatformWithdrawal, getBankCards } from '@/api/finance';
|
||||
import { getWithdrawConfig } from '@/api/config';
|
||||
import { formatMoney, formatDateTime } from '@rent/shared-utils';
|
||||
|
||||
interface PlatformAccount {
|
||||
@@ -34,11 +35,13 @@ const PlatformWallet: React.FC = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [withdrawModalVisible, setWithdrawModalVisible] = useState(false);
|
||||
const [bankCards, setBankCards] = useState<BankCard[]>([]);
|
||||
const [minWithdrawAmount, setMinWithdrawAmount] = useState(10);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
useEffect(() => {
|
||||
fetchAccount();
|
||||
fetchBankCards();
|
||||
fetchWithdrawConfig();
|
||||
}, []);
|
||||
|
||||
const fetchAccount = async () => {
|
||||
@@ -67,6 +70,17 @@ const PlatformWallet: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchWithdrawConfig = async () => {
|
||||
try {
|
||||
const res: any = await getWithdrawConfig();
|
||||
if (res.data?.platformMinWithdrawAmount) {
|
||||
setMinWithdrawAmount(res.data.platformMinWithdrawAmount);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取提现配置失败', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleWithdraw = () => {
|
||||
if (bankCards.length === 0) {
|
||||
message.warning('请先添加银行卡');
|
||||
@@ -328,6 +342,9 @@ const PlatformWallet: React.FC = () => {
|
||||
{ required: true, message: '请输入提现金额' },
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (value && value < minWithdrawAmount) {
|
||||
return Promise.reject(`提现金额不能小于最低提现金额(${minWithdrawAmount}元)`);
|
||||
}
|
||||
if (value && value > withdrawableAmount) {
|
||||
return Promise.reject(`提现金额不能大于可提现金额(${withdrawableAmount.toFixed(2)}元)`);
|
||||
}
|
||||
@@ -338,11 +355,12 @@ const PlatformWallet: React.FC = () => {
|
||||
},
|
||||
},
|
||||
]}
|
||||
extra={`最低提现金额:${minWithdrawAmount}元`}
|
||||
>
|
||||
<InputNumber
|
||||
style={{ width: '100%' }}
|
||||
placeholder="请输入提现金额"
|
||||
min={0}
|
||||
min={minWithdrawAmount}
|
||||
max={withdrawableAmount}
|
||||
precision={2}
|
||||
addonAfter="元"
|
||||
|
||||
Reference in New Issue
Block a user