feat: 迭代

This commit is contained in:
2026-05-14 19:12:26 +08:00
parent da1e8b3c7d
commit 6a7ec5fca7
229 changed files with 7669 additions and 3236 deletions
+16 -16
View File
@@ -1,69 +1,69 @@
import request from '@/utils/request';
export function loginByPassword(username: string, password: string) {
return request.post('/admin/auth/login', { username, password });
return request.post('/api/admin/auth/login', { username, password });
}
export function getAdminProfile() {
return request.get('/admin/auth/profile');
return request.get('/api/admin/auth/profile');
}
export function changeAdminPassword(data: { oldPassword: string; newPassword: string }) {
return request.put('/admin/auth/password', data);
return request.put('/api/admin/auth/password', data);
}
// 商家管理
export function getMerchantList(params: any) {
return request.get('/admin/merchants', { params });
return request.get('/api/admin/merchants', { params });
}
export function getMerchantDetail(id: number) {
return request.get(`/admin/merchants/${id}`);
return request.get(`/api/admin/merchants/${id}`);
}
export function approveMerchant(id: number) {
return request.put(`/admin/merchants/${id}/approve`);
return request.put(`/api/admin/merchants/${id}/approve`);
}
export function rejectMerchant(id: number, reason: string) {
return request.put(`/admin/merchants/${id}/reject`, { reason });
return request.put(`/api/admin/merchants/${id}/reject`, { reason });
}
export function freezeMerchant(id: number) {
return request.put(`/admin/merchants/${id}/freeze`);
return request.put(`/api/admin/merchants/${id}/freeze`);
}
export function unfreezeMerchant(id: number) {
return request.put(`/admin/merchants/${id}/unfreeze`);
return request.put(`/api/admin/merchants/${id}/unfreeze`);
}
// 用户管理
export function getUserList(params: any) {
return request.get('/admin/users', { params });
return request.get('/api/admin/users', { params });
}
export function freezeUser(id: number) {
return request.put(`/admin/users/${id}/freeze`);
return request.put(`/api/admin/users/${id}/freeze`);
}
export function unfreezeUser(id: number) {
return request.put(`/admin/users/${id}/unfreeze`);
return request.put(`/api/admin/users/${id}/unfreeze`);
}
// 订单管理
export function getOrderList(params: any) {
return request.get('/admin/orders', { params });
return request.get('/api/admin/orders', { params });
}
export function getOrderDetail(id: number) {
return request.get(`/admin/orders/${id}`);
return request.get(`/api/admin/orders/${id}`);
}
// 统计数据
export function getPlatformStatistics() {
return request.get('/admin/finance/reports/overview');
return request.get('/api/admin/finance/reports/overview');
}
export function getOrderTrend(params: { startDate: string; endDate: string }) {
return request.get('/admin/finance/reports/trend', { params });
return request.get('/api/admin/finance/reports/trend', { params });
}
+4 -4
View File
@@ -1,9 +1,9 @@
import request from '@/utils/request';
export const getServiceFeeConfig = () => request.get('/admin/config/service-fee');
export const getServiceFeeConfig = () => request.get('/api/admin/config/service-fee');
export const updateServiceFeeConfig = (rate: number) => request.put('/admin/config/service-fee', { rate });
export const updateServiceFeeConfig = (rate: number) => request.put('/api/admin/config/service-fee', { rate });
export const getStorageConfig = () => request.get('/admin/config/storage');
export const getStorageConfig = () => request.get('/api/admin/config/storage');
export const updateStorageConfig = (data: Record<string, string>) => request.put('/admin/config/storage', data);
export const updateStorageConfig = (data: Record<string, string>) => request.put('/api/admin/config/storage', data);
+5 -5
View File
@@ -1,21 +1,21 @@
import request from '@/utils/request';
export function getCouponList(params: any) {
return request({ url: '/admin/coupons', method: 'GET', params });
return request({ url: '/api/admin/coupons', method: 'GET', params });
}
export function getCouponDetail(id: number) {
return request({ url: `/admin/coupons/${id}`, method: 'GET' });
return request({ url: `/api/admin/coupons/${id}`, method: 'GET' });
}
export function createCoupon(data: any) {
return request({ url: '/admin/coupons', method: 'POST', data });
return request({ url: '/api/admin/coupons', method: 'POST', data });
}
export function updateCoupon(id: number, data: any) {
return request({ url: `/admin/coupons/${id}`, method: 'PUT', data });
return request({ url: `/api/admin/coupons/${id}`, method: 'PUT', data });
}
export function deleteCoupon(id: number) {
return request({ url: `/admin/coupons/${id}`, method: 'DELETE' });
return request({ url: `/api/admin/coupons/${id}`, method: 'DELETE' });
}
+51 -35
View File
@@ -2,126 +2,142 @@ import request from '@/utils/request';
// 账户管理
export function getPlatformAccounts(params: any) {
return request.get('/admin/finance/accounts/platform', { params });
return request.get('/api/admin/finance/accounts/platform', { params });
}
export function getUserAccounts(params: any) {
return request.get('/admin/finance/accounts/users', { params });
return request.get('/api/admin/finance/accounts/users', { params });
}
export function getMerchantAccounts(params: any) {
return request.get('/admin/finance/accounts/merchants', { params });
return request.get('/api/admin/finance/accounts/merchants', { params });
}
export function getAccountDetail(type: string, id: number) {
return request.get(`/admin/finance/accounts/${type}/${id}`);
return request.get(`/api/admin/finance/accounts/${type}/${id}`);
}
export function getAccountsSummary() {
return request.get('/admin/finance/accounts/summary');
return request.get('/api/admin/finance/accounts/summary');
}
// 交易流水
export function getPlatformTransactions(params: any) {
return request.get('/admin/finance/transactions/platform', { params });
return request.get('/api/admin/finance/transactions/platform', { params });
}
export function getUserTransactions(params: any) {
return request.get('/admin/finance/transactions/users', { params });
return request.get('/api/admin/finance/transactions/users', { params });
}
export function getMerchantTransactions(params: any) {
return request.get('/admin/finance/transactions/merchants', { params });
return request.get('/api/admin/finance/transactions/merchants', { params });
}
export function getTransactionDetail(id: number) {
return request.get(`/admin/finance/transactions/${id}`);
return request.get(`/api/admin/finance/transactions/${id}`);
}
// 提现管理
export function getWithdrawals(params: any) {
return request.get('/admin/finance/withdrawals', { params });
export function getUserWithdrawals(params: any) {
return request.get('/api/admin/finance/withdrawals/users', { params });
}
export function getWithdrawalDetail(id: number) {
return request.get(`/admin/finance/withdrawals/${id}`);
export function getMerchantWithdrawals(params: any) {
return request.get('/api/admin/finance/withdrawals/merchants', { params });
}
export function approveWithdrawal(id: number) {
return request.put(`/admin/finance/withdrawals/${id}/approve`);
export function getPlatformWithdrawals(params: any) {
return request.get('/api/admin/finance/withdrawals/platform', { params });
}
export function rejectWithdrawal(id: number, reason: string) {
return request.put(`/admin/finance/withdrawals/${id}/reject`, { reason });
export function approveUserWithdrawal(id: number) {
return request.put(`/api/admin/finance/withdrawals/users/${id}/approve`);
}
export function confirmWithdrawal(id: number, data: { transactionNo: string; paidAt: string }) {
return request.put(`/admin/finance/withdrawals/${id}/confirm`, data);
export function rejectUserWithdrawal(id: number, reason: string) {
return request.put(`/api/admin/finance/withdrawals/users/${id}/reject`, { rejectReason: reason });
}
export function confirmUserWithdrawal(id: number, transactionNo: string) {
return request.put(`/api/admin/finance/withdrawals/users/${id}/confirm`, { paymentNo: transactionNo });
}
export function approveMerchantWithdrawal(id: number) {
return request.put(`/api/admin/finance/withdrawals/merchants/${id}/approve`);
}
export function rejectMerchantWithdrawal(id: number, reason: string) {
return request.put(`/api/admin/finance/withdrawals/merchants/${id}/reject`, { rejectReason: reason });
}
export function confirmMerchantWithdrawal(id: number, transactionNo: string) {
return request.put(`/api/admin/finance/withdrawals/merchants/${id}/confirm`, { paymentNo: transactionNo });
}
// 结算管理
export function getSettlements(params: any) {
return request.get('/admin/finance/settlements', { params });
return request.get('/api/admin/finance/settlements', { params });
}
export function getSettlementDetail(id: number) {
return request.get(`/admin/finance/settlements/${id}`);
return request.get(`/api/admin/finance/settlements/${id}`);
}
export function getSettlementItems(id: number, params: any) {
return request.get(`/admin/finance/settlements/${id}/items`, { params });
return request.get(`/api/admin/finance/settlements/${id}/items`, { params });
}
export function generateSettlement(merchantId: number, data: { startDate: string; endDate: string }) {
return request.post(`/admin/finance/settlements/generate/${merchantId}`, data);
return request.post(`/api/admin/finance/settlements/generate/${merchantId}`, data);
}
// 对账管理
export function getReconciliations(params: any) {
return request.get('/admin/finance/reconciliations', { params });
return request.get('/api/admin/finance/reconciliations', { params });
}
export function getReconciliationDetail(id: number) {
return request.get(`/admin/finance/reconciliations/${id}`);
return request.get(`/api/admin/finance/reconciliations/${id}`);
}
export function triggerReconciliation(date: string) {
return request.post('/admin/finance/reconciliations/trigger', { date });
return request.post('/api/admin/finance/reconciliations/trigger', { date });
}
export function getTransactionStats(params: any) {
return request.get('/admin/finance/reconciliations/stats', { params });
return request.get('/api/admin/finance/reconciliations/stats', { params });
}
export function checkBalanceConsistency() {
return request.get('/admin/finance/reconciliations/check-balance');
return request.get('/api/admin/finance/reconciliations/check-balance');
}
// 财务报表
export function getFinancialOverview(params: any) {
return request.get('/admin/finance/reports/overview', { params });
return request.get('/api/admin/finance/reports/overview', { params });
}
export function getFinancialTrend(params: any) {
return request.get('/admin/finance/reports/trend', { params });
return request.get('/api/admin/finance/reports/trend', { params });
}
export function getDailyReport(params: any) {
return request.get('/admin/finance/reports/daily', { params });
return request.get('/api/admin/finance/reports/daily', { params });
}
export function getWeeklyReport(params: any) {
return request.get('/admin/finance/reports/weekly', { params });
return request.get('/api/admin/finance/reports/weekly', { params });
}
export function getMonthlyReport(params: any) {
return request.get('/admin/finance/reports/monthly', { params });
return request.get('/api/admin/finance/reports/monthly', { params });
}
export function getMerchantReport(merchantId: number, params: any) {
return request.get(`/admin/finance/reports/merchant/${merchantId}`, { params });
return request.get(`/api/admin/finance/reports/merchant/${merchantId}`, { params });
}
export function exportReport(type: string, params: any) {
return request.get(`/admin/finance/reports/export/${type}`, { params, responseType: 'blob' });
return request.get(`/api/admin/finance/reports/export/${type}`, { params, responseType: 'blob' });
}
+9 -9
View File
@@ -2,35 +2,35 @@ import request from '@/utils/request';
// 邀请数据总览
export const getInviteStats = () =>
request.get('/admin/activity/invite/stats');
request.get('/api/admin/activity/invite/stats');
// 邀请记录列表
export const getInviteRecords = (params?: { page?: number; pageSize?: number }) =>
request.get('/admin/activity/invite/records', { params });
request.get('/api/admin/activity/invite/records', { params });
// 返现记录列表
export const getCashbackRecords = (params?: { page?: number; pageSize?: number }) =>
request.get('/admin/activity/invite/cashbacks', { params });
request.get('/api/admin/activity/invite/cashbacks', { params });
// 邀请提现列表
export const getInviteWithdrawals = (params?: { page?: number; pageSize?: number; status?: string }) =>
request.get('/admin/activity/invite/withdrawals', { params });
request.get('/api/admin/activity/invite/withdrawals', { params });
// 审核通过提现
export const approveInviteWithdrawal = (id: number) =>
request.put(`/admin/activity/invite/withdrawals/${id}/approve`);
request.put(`/api/admin/activity/invite/withdrawals/${id}/approve`);
// 审核拒绝提现
export const rejectInviteWithdrawal = (id: number, reason: string) =>
request.put(`/admin/activity/invite/withdrawals/${id}/reject`, { reason });
request.put(`/api/admin/activity/invite/withdrawals/${id}/reject`, { reason });
// 确认打款
export const payInviteWithdrawal = (id: number) =>
request.put(`/admin/activity/invite/withdrawals/${id}/pay`);
request.put(`/api/admin/activity/invite/withdrawals/${id}/pay`);
// 获取邀请活动配置
export const getInviteConfig = () =>
request.get('/admin/activity/invite/config');
request.get('/api/admin/activity/invite/config');
// 更新邀请活动配置
export const updateInviteConfig = (data: {
@@ -43,4 +43,4 @@ export const updateInviteConfig = (data: {
};
enabled?: boolean;
}) =>
request.put('/admin/activity/invite/config', data);
request.put('/api/admin/activity/invite/config', data);
+1 -1
View File
@@ -1,5 +1,5 @@
import request from '@/utils/request';
export function getServiceFees(params: any) {
return request.get('/admin/orders/service-fees/list', { params });
return request.get('/api/admin/orders/service-fees/list', { params });
}
+3 -3
View File
@@ -1,13 +1,13 @@
import request from '@/utils/request';
export function getReviews(params: any) {
return request.get('/admin/reviews', { params });
return request.get('/api/admin/reviews', { params });
}
export function approveReview(id: number) {
return request.put(`/admin/reviews/${id}/approve`);
return request.put(`/api/admin/reviews/${id}/approve`);
}
export function rejectReview(id: number, reason: string) {
return request.put(`/admin/reviews/${id}/reject`, { reason });
return request.put(`/api/admin/reviews/${id}/reject`, { reason });
}
+3 -3
View File
@@ -2,13 +2,13 @@ import request from '@/utils/request';
// 房源审核管理
export function getAdminRoomList(params: any) {
return request.get('/admin/rooms', { params });
return request.get('/api/admin/rooms', { params });
}
export function approveRoom(id: number) {
return request.put(`/admin/rooms/${id}/approve`);
return request.put(`/api/admin/rooms/${id}/approve`);
}
export function rejectRoom(id: number, reason: string) {
return request.put(`/admin/rooms/${id}/reject`, { reason });
return request.put(`/api/admin/rooms/${id}/reject`, { reason });
}
@@ -52,7 +52,8 @@ export function useTableData<T = any>(
} finally {
setLoading(false);
}
}, [fetchFn, params]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [params]);
useEffect(() => {
if (autoLoad) {
@@ -225,11 +225,11 @@ const InviteManage: React.FC = () => {
<Form form={form} layout="vertical" style={{ maxWidth: 500 }}>
<Form.Item label="首单返现比例" name="firstOrderRate" rules={[{ required: true }]} extra="好友首次下单,邀请人获得的返现比例">
<InputNumber min={0.01} max={0.20} step={0.01} addonAfter="%" style={{ width: '100%' }}
formatter={v => `${(Number(v) * 100).toFixed(1)}`} parser={v => Number(v?.replace('%', '')) / 100} />
formatter={v => `${(Number(v) * 100).toFixed(1)}`} parser={v => Number(v?.replace('%', '')) / 100 as any} />
</Form.Item>
<Form.Item label="二单返现比例" name="secondOrderRate" rules={[{ required: true }]} extra="好友再次下单,邀请人获得的返现比例">
<InputNumber min={0.001} max={0.05} step={0.001} addonAfter="%" style={{ width: '100%' }}
formatter={v => `${(Number(v) * 100).toFixed(2)}`} parser={v => Number(v?.replace('%', '')) / 100} />
formatter={v => `${(Number(v) * 100).toFixed(2)}`} parser={v => Number(v?.replace('%', '')) / 100 as any} />
</Form.Item>
<Form.Item label="最多返现订单数" name="maxOrderIndex" rules={[{ required: true }]} extra="好友最多前几单可以获得返现">
<InputNumber min={1} max={10} step={1} addonAfter="单" style={{ width: '100%' }} />
@@ -60,7 +60,7 @@ const SystemSettings: React.FC = () => {
precision={4}
style={{ width: '100%' }}
formatter={(v) => `${(Number(v) * 100).toFixed(2)}%`}
parser={(v) => Number(v?.replace('%', '')) / 100}
parser={(v) => Number(v?.replace('%', '')) / 100 as any}
/>
</Form.Item>
<Form.Item>
@@ -32,7 +32,7 @@ const Accounts: React.FC = () => {
fetchAccounts();
}, [activeTab]);
const fetchAccounts = async (params = {}) => {
const fetchAccounts = async (params: any = {}) => {
setLoading(true);
try {
const values = form.getFieldsValue();
@@ -0,0 +1,325 @@
import React, { useState, useEffect } from 'react';
import { Card, Table, DatePicker, Space, Statistic, Row, Col, Tag, message } from 'antd';
import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table';
import { formatMoney, formatDateTime } from '@rent/shared-utils';
import { getFinancialOverview, getWeeklyReport } from '@/api/finance';
import type { Dayjs } from 'dayjs';
const { RangePicker } = DatePicker;
interface EarningRecord {
id: number;
date: string;
orderCount: number;
totalAmount: number;
platformCommission: number;
withdrawalFee: number;
totalIncome: number;
totalExpense: number;
profit: number;
}
const Earnings: React.FC = () => {
const [loading, setLoading] = useState(false);
const [dataSource, setDataSource] = useState<EarningRecord[]>([]);
const [dateRange, setDateRange] = useState<[Dayjs | null, Dayjs | null] | null>(null);
const [overview, setOverview] = useState({
todayIncome: 0,
monthIncome: 0,
totalIncome: 0,
monthProfit: 0,
platformCommission: 0,
withdrawalFee: 0,
pendingSettlement: 0,
balance: 0,
growthRate: 0,
});
useEffect(() => {
fetchOverview();
fetchData();
}, []);
const fetchOverview = async () => {
try {
const res = await getFinancialOverview({});
if (res?.data) {
setOverview({
todayIncome: res.data?.todayIncome || 0,
monthIncome: res.data?.monthIncome || 0,
totalIncome: res.data?.totalIncome || 0,
monthProfit: res.data?.monthProfit || 0,
platformCommission: res.data?.platformCommission || 0,
withdrawalFee: res.data?.withdrawalFee || 0,
pendingSettlement: res.data?.pendingSettlement || 0,
balance: res.data?.balance || 0,
growthRate: res.data?.growthRate || 0,
});
}
} catch (error) {
console.error('获取财务概览失败:', error);
}
};
const fetchData = async () => {
setLoading(true);
try {
const params: any = {};
if (dateRange && dateRange[0] && dateRange[1]) {
params.startDate = dateRange[0].format('YYYY-MM-DD');
params.endDate = dateRange[1].format('YYYY-MM-DD');
}
// 使用周报表接口获取数据
const res = await getWeeklyReport(params);
if (res?.data) {
// 将单条数据转换为数组格式
const data = res.data;
if (data) {
setDataSource([{
id: 1,
date: data.startDate,
orderCount: data.orderCount || 0,
totalAmount: data.orderAmount || 0,
platformCommission: data.serviceFee || 0,
withdrawalFee: 0,
totalIncome: data.totalIncome || 0,
totalExpense: data.totalExpense || 0,
profit: data.netAmount || 0,
}]);
} else {
setDataSource([]);
}
}
} catch (error) {
message.error('获取数据失败');
} finally {
setLoading(false);
}
};
const handleDateChange = (dates: [Dayjs | null, Dayjs | null] | null) => {
setDateRange(dates);
if (dates) {
fetchData();
}
};
const columns: ColumnsType<EarningRecord> = [
{
title: '日期',
dataIndex: 'date',
key: 'date',
render: (date: string) => formatDateTime(date).split(' ')[0],
},
{
title: '订单数',
dataIndex: 'orderCount',
key: 'orderCount',
},
{
title: '订单总额',
dataIndex: 'totalAmount',
key: 'totalAmount',
render: (amount: number) => `¥${formatMoney(amount)}`,
},
{
title: '平台佣金',
dataIndex: 'platformCommission',
key: 'platformCommission',
render: (commission: number) => (
<span style={{ color: '#52c41a', fontWeight: 600 }}>
¥{formatMoney(commission)}
</span>
),
},
{
title: '提现手续费',
dataIndex: 'withdrawalFee',
key: 'withdrawalFee',
render: (fee: number) => (
<span style={{ color: '#52c41a' }}>
¥{formatMoney(fee)}
</span>
),
},
{
title: '总收入',
dataIndex: 'totalIncome',
key: 'totalIncome',
render: (income: number) => (
<span style={{ color: '#52c41a', fontWeight: 600 }}>
¥{formatMoney(income)}
</span>
),
},
{
title: '总支出',
dataIndex: 'totalExpense',
key: 'totalExpense',
render: (expense: number) => (
<span style={{ color: '#ff4d4f' }}>
¥{formatMoney(expense)}
</span>
),
},
{
title: '净利润',
dataIndex: 'profit',
key: 'profit',
render: (profit: number) => (
<span style={{ color: profit >= 0 ? '#52c41a' : '#ff4d4f', fontWeight: 700 }}>
{profit >= 0 ? '+' : ''}¥{formatMoney(profit)}
</span>
),
},
];
return (
<div>
<Card style={{ marginBottom: 16 }} title="平台财务概览">
<Row gutter={16}>
<Col span={6}>
<Statistic
title="今日收益"
value={overview.todayIncome}
prefix="¥"
precision={2}
valueStyle={{ color: '#52c41a' }}
suffix={
<span style={{ fontSize: 14, color: '#999' }}>
{overview.growthRate >= 0 ? (
<><ArrowUpOutlined style={{ color: '#52c41a' }} /> {overview.growthRate.toFixed(1)}%</>
) : (
<><ArrowDownOutlined style={{ color: '#ff4d4f' }} /> {Math.abs(overview.growthRate).toFixed(1)}%</>
)}
</span>
}
/>
</Col>
<Col span={6}>
<Statistic
title="本月收益"
value={overview.monthIncome}
prefix="¥"
precision={2}
valueStyle={{ color: '#52c41a' }}
/>
</Col>
<Col span={6}>
<Statistic
title="累计收益"
value={overview.totalIncome}
prefix="¥"
precision={2}
/>
</Col>
<Col span={6}>
<Statistic
title="本月利润"
value={overview.monthProfit}
prefix="¥"
precision={2}
valueStyle={{ color: '#1890ff' }}
/>
</Col>
</Row>
<Row gutter={16} style={{ marginTop: 24 }}>
<Col span={6}>
<Statistic
title="平台佣金收入"
value={overview.platformCommission}
prefix="¥"
precision={2}
/>
</Col>
<Col span={6}>
<Statistic
title="提现手续费收入"
value={overview.withdrawalFee}
prefix="¥"
precision={2}
/>
</Col>
<Col span={6}>
<Statistic
title="待结算金额"
value={overview.pendingSettlement}
prefix="¥"
precision={2}
valueStyle={{ color: '#faad14' }}
/>
</Col>
<Col span={6}>
<Statistic
title="资金池余额"
value={overview.balance}
prefix="¥"
precision={2}
valueStyle={{ color: '#1890ff' }}
/>
</Col>
</Row>
</Card>
<Card
title="收益明细"
extra={
<Space>
<RangePicker value={dateRange} onChange={handleDateChange} />
</Space>
}
>
<Table
columns={columns}
dataSource={dataSource}
rowKey="id"
loading={loading}
pagination={false}
summary={(pageData) => {
if (pageData.length === 0) return null;
const totalOrders = pageData.reduce((sum, record) => sum + record.orderCount, 0);
const totalAmount = pageData.reduce((sum, record) => sum + record.totalAmount, 0);
const totalCommission = pageData.reduce((sum, record) => sum + record.platformCommission, 0);
const totalFee = pageData.reduce((sum, record) => sum + record.withdrawalFee, 0);
const totalIncome = pageData.reduce((sum, record) => sum + record.totalIncome, 0);
const totalExpense = pageData.reduce((sum, record) => sum + record.totalExpense, 0);
const totalProfit = pageData.reduce((sum, record) => sum + record.profit, 0);
return (
<Table.Summary fixed>
<Table.Summary.Row style={{ fontWeight: 600 }}>
<Table.Summary.Cell index={0}></Table.Summary.Cell>
<Table.Summary.Cell index={1}>{totalOrders}</Table.Summary.Cell>
<Table.Summary.Cell index={2}>¥{formatMoney(totalAmount)}</Table.Summary.Cell>
<Table.Summary.Cell index={3}>
<span style={{ color: '#52c41a' }}>¥{formatMoney(totalCommission)}</span>
</Table.Summary.Cell>
<Table.Summary.Cell index={4}>
<span style={{ color: '#52c41a' }}>¥{formatMoney(totalFee)}</span>
</Table.Summary.Cell>
<Table.Summary.Cell index={5}>
<span style={{ color: '#52c41a' }}>¥{formatMoney(totalIncome)}</span>
</Table.Summary.Cell>
<Table.Summary.Cell index={6}>
<span style={{ color: '#ff4d4f' }}>¥{formatMoney(totalExpense)}</span>
</Table.Summary.Cell>
<Table.Summary.Cell index={7}>
<span style={{ color: totalProfit >= 0 ? '#52c41a' : '#ff4d4f' }}>
{totalProfit >= 0 ? '+' : ''}¥{formatMoney(totalProfit)}
</span>
</Table.Summary.Cell>
</Table.Summary.Row>
</Table.Summary>
);
}}
/>
</Card>
</div>
);
};
export default Earnings;
@@ -0,0 +1,272 @@
import React, { useState, useEffect } from 'react';
import { Card, Table, DatePicker, Space, Tag, Button, Statistic, Row, Col, message } from 'antd';
import { DownloadOutlined } from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table';
import { formatMoney, formatDateTime } from '@rent/shared-utils';
import { getFinancialOverview, getMerchantTransactions } from '@/api/finance';
import type { Dayjs } from 'dayjs';
const { RangePicker } = DatePicker;
interface ServiceFeeRecord {
id: number;
orderId: number;
orderNo: string;
merchantName: string;
userName: string;
orderAmount: number;
commissionRate: number;
commissionAmount: number;
status: string;
settledAt: string;
createdAt: string;
}
const ServiceFees: React.FC = () => {
const [loading, setLoading] = useState(false);
const [dataSource, setDataSource] = useState<ServiceFeeRecord[]>([]);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 20,
total: 0,
});
const [dateRange, setDateRange] = useState<[Dayjs | null, Dayjs | null] | null>(null);
const [statistics, setStatistics] = useState({
todayCommission: 0,
monthCommission: 0,
totalCommission: 0,
pendingCommission: 0,
});
useEffect(() => {
fetchStatistics();
fetchData();
}, []);
const fetchStatistics = async () => {
try {
const res = await getFinancialOverview({});
if (res?.data) {
setStatistics({
todayCommission: res.data?.todayCommission || 0,
monthCommission: res.data?.monthCommission || 0,
totalCommission: res.data?.totalCommission || 0,
pendingCommission: res.data?.pendingCommission || 0,
});
}
} catch (error) {
console.error('获取统计数据失败:', error);
}
};
const fetchData = async (page = 1, pageSize = 20) => {
setLoading(true);
try {
const params: any = {
page,
pageSize,
};
if (dateRange && dateRange[0] && dateRange[1]) {
params.startDate = dateRange[0].format('YYYY-MM-DD');
params.endDate = dateRange[1].format('YYYY-MM-DD');
}
const res = await getMerchantTransactions(params);
if (res?.data) {
setDataSource(res.data?.list || []);
setPagination({
current: page,
pageSize,
total: res.data?.total || 0,
});
}
} catch (error) {
message.error('获取数据失败');
} finally {
setLoading(false);
}
};
const handleTableChange = (newPagination: any) => {
fetchData(newPagination.current, newPagination.pageSize);
};
const handleDateChange = (dates: [Dayjs | null, Dayjs | null] | null) => {
setDateRange(dates);
};
const handleSearch = () => {
fetchData(1, pagination.pageSize);
};
const handleExport = () => {
message.info('导出功能开发中');
};
const columns: ColumnsType<ServiceFeeRecord> = [
{
title: '订单号',
dataIndex: 'orderNo',
key: 'orderNo',
width: 180,
},
{
title: '商家名称',
dataIndex: 'merchantName',
key: 'merchantName',
width: 150,
},
{
title: '用户',
dataIndex: 'userName',
key: 'userName',
width: 120,
},
{
title: '订单金额',
dataIndex: 'orderAmount',
key: 'orderAmount',
width: 120,
render: (amount: number) => `¥${formatMoney(amount)}`,
},
{
title: '佣金比例',
dataIndex: 'commissionRate',
key: 'commissionRate',
width: 100,
render: (rate: number) => `${(rate * 100).toFixed(1)}%`,
},
{
title: '佣金金额',
dataIndex: 'commissionAmount',
key: 'commissionAmount',
width: 120,
render: (amount: number) => (
<span style={{ color: '#52c41a', fontWeight: 600 }}>
¥{formatMoney(amount)}
</span>
),
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 100,
render: (status: string) => {
const statusMap: Record<string, { text: string; color: string }> = {
pending: { text: '待结算', color: 'orange' },
settled: { text: '已结算', color: 'green' },
frozen: { text: '已冻结', color: 'red' },
};
const config = statusMap[status] || { text: status, color: 'default' };
return <Tag color={config.color}>{config.text}</Tag>;
},
},
{
title: '结算时间',
dataIndex: 'settledAt',
key: 'settledAt',
width: 160,
render: (time: string) => time ? formatDateTime(time) : '-',
},
{
title: '创建时间',
dataIndex: 'createdAt',
key: 'createdAt',
width: 160,
render: (time: string) => formatDateTime(time),
},
];
return (
<div>
<Card style={{ marginBottom: 16 }} title="佣金统计">
<Row gutter={16}>
<Col span={6}>
<Statistic
title="今日佣金"
value={statistics.todayCommission}
prefix="¥"
precision={2}
valueStyle={{ color: '#52c41a' }}
/>
</Col>
<Col span={6}>
<Statistic
title="本月佣金"
value={statistics.monthCommission}
prefix="¥"
precision={2}
valueStyle={{ color: '#52c41a' }}
/>
</Col>
<Col span={6}>
<Statistic
title="累计佣金"
value={statistics.totalCommission}
prefix="¥"
precision={2}
/>
</Col>
<Col span={6}>
<Statistic
title="待结算佣金"
value={statistics.pendingCommission}
prefix="¥"
precision={2}
valueStyle={{ color: '#faad14' }}
/>
</Col>
</Row>
</Card>
<Card
title="佣金明细"
extra={
<Space>
<RangePicker value={dateRange} onChange={handleDateChange} />
<Button onClick={handleSearch}></Button>
<Button icon={<DownloadOutlined />} onClick={handleExport}></Button>
</Space>
}
>
<Table
columns={columns}
dataSource={dataSource}
rowKey="id"
loading={loading}
scroll={{ x: 1400 }}
pagination={{
...pagination,
showSizeChanger: true,
showTotal: (total) => `${total}`,
}}
onChange={handleTableChange}
summary={(pageData) => {
if (pageData.length === 0) return null;
const totalOrderAmount = pageData.reduce((sum, record) => sum + record.orderAmount, 0);
const totalCommission = pageData.reduce((sum, record) => sum + record.commissionAmount, 0);
return (
<Table.Summary fixed>
<Table.Summary.Row style={{ fontWeight: 600 }}>
<Table.Summary.Cell index={0} colSpan={3}></Table.Summary.Cell>
<Table.Summary.Cell index={3}>¥{formatMoney(totalOrderAmount)}</Table.Summary.Cell>
<Table.Summary.Cell index={4}>-</Table.Summary.Cell>
<Table.Summary.Cell index={5}>
<span style={{ color: '#52c41a' }}>¥{formatMoney(totalCommission)}</span>
</Table.Summary.Cell>
<Table.Summary.Cell index={6} colSpan={3}>-</Table.Summary.Cell>
</Table.Summary.Row>
</Table.Summary>
);
}}
/>
</Card>
</div>
);
};
export default ServiceFees;
@@ -1,7 +1,17 @@
import React, { useEffect, useState } from 'react';
import { Card, Table, Tag, Button, Space, Modal, Form, Input, message, Descriptions, DatePicker } from 'antd';
import { Card, Table, Tag, Button, Space, Modal, Form, Input, message, Descriptions, DatePicker, Tabs } from 'antd';
import { CheckOutlined, CloseOutlined, EyeOutlined } from '@ant-design/icons';
import { getWithdrawals, getWithdrawalDetail, approveWithdrawal, rejectWithdrawal, confirmWithdrawal } from '@/api/finance';
import {
getUserWithdrawals,
getMerchantWithdrawals,
getPlatformWithdrawals,
approveUserWithdrawal,
rejectUserWithdrawal,
confirmUserWithdrawal,
approveMerchantWithdrawal,
rejectMerchantWithdrawal,
confirmMerchantWithdrawal
} from '@/api/finance';
import type { ColumnsType } from 'antd/es/table';
import dayjs from 'dayjs';
@@ -30,6 +40,7 @@ const Withdrawals: React.FC = () => {
const [form] = Form.useForm();
const [rejectForm] = Form.useForm();
const [confirmForm] = Form.useForm();
const [withdrawalType, setWithdrawalType] = useState<'user' | 'merchant' | 'platform'>('user');
const [withdrawals, setWithdrawals] = useState<Withdrawal[]>([]);
const [loading, setLoading] = useState(false);
const [pagination, setPagination] = useState({ current: 1, pageSize: 20, total: 0 });
@@ -42,9 +53,9 @@ const Withdrawals: React.FC = () => {
useEffect(() => {
fetchWithdrawals();
}, []);
}, [withdrawalType]);
const fetchWithdrawals = async (params = {}) => {
const fetchWithdrawals = async (params: any = {}) => {
setLoading(true);
try {
const queryParams = {
@@ -53,9 +64,17 @@ const Withdrawals: React.FC = () => {
pageSize: params.pageSize || pagination.pageSize,
};
const res = await getWithdrawals(queryParams);
setWithdrawals(res.data.items);
setPagination({ ...pagination, total: res.data.total, ...params });
let res;
if (withdrawalType === 'user') {
res = await getUserWithdrawals(queryParams);
} else if (withdrawalType === 'merchant') {
res = await getMerchantWithdrawals(queryParams);
} else {
res = await getPlatformWithdrawals(queryParams);
}
setWithdrawals(res.data.items || []);
setPagination({ ...pagination, total: res.data.total || 0, ...params });
} catch (error) {
message.error('获取提现记录失败');
} finally {
@@ -65,9 +84,11 @@ const Withdrawals: React.FC = () => {
const handleViewDetail = async (id: number) => {
try {
const res = await getWithdrawalDetail(id);
setCurrentDetail(res.data);
setDetailVisible(true);
// TODO: 实现提现详情接口
message.info('提现详情功能待实现');
// const res = await getWithdrawalDetail(id);
// setCurrentDetail(res.data);
// setDetailVisible(true);
} catch (error) {
message.error('获取提现详情失败');
}
@@ -79,7 +100,11 @@ const Withdrawals: React.FC = () => {
content: '确定要通过这笔提现申请吗?',
onOk: async () => {
try {
await approveWithdrawal(id);
if (withdrawalType === 'user') {
await approveUserWithdrawal(id);
} else if (withdrawalType === 'merchant') {
await approveMerchantWithdrawal(id);
}
message.success('审核通过');
fetchWithdrawals();
} catch (error: any) {
@@ -93,7 +118,11 @@ const Withdrawals: React.FC = () => {
if (!currentId) return;
setSubmitting(true);
try {
await rejectWithdrawal(currentId, values.reason);
if (withdrawalType === 'user') {
await rejectUserWithdrawal(currentId, values.reason);
} else if (withdrawalType === 'merchant') {
await rejectMerchantWithdrawal(currentId, values.reason);
}
message.success('已拒绝');
setRejectVisible(false);
rejectForm.resetFields();
@@ -109,10 +138,11 @@ const Withdrawals: React.FC = () => {
if (!currentId) return;
setSubmitting(true);
try {
await confirmWithdrawal(currentId, {
transactionNo: values.transactionNo,
paidAt: values.paidAt.format('YYYY-MM-DD HH:mm:ss'),
});
if (withdrawalType === 'user') {
await confirmUserWithdrawal(currentId, values.transactionNo);
} else if (withdrawalType === 'merchant') {
await confirmMerchantWithdrawal(currentId, values.transactionNo);
}
message.success('打款确认成功');
setConfirmVisible(false);
confirmForm.resetFields();
@@ -131,17 +161,6 @@ const Withdrawals: React.FC = () => {
key: 'id',
width: 80,
},
{
title: '类型',
dataIndex: 'type',
key: 'type',
width: 100,
render: (type: string) => (
<Tag color={type === 'user' ? 'blue' : 'orange'}>
{type === 'user' ? '用户' : '商家'}
</Tag>
),
},
{
title: '申请人',
key: 'applicant',
@@ -268,12 +287,25 @@ const Withdrawals: React.FC = () => {
<h2 style={{ marginBottom: 24 }}></h2>
<Card>
<Tabs
activeKey={withdrawalType}
onChange={(key) => {
setWithdrawalType(key as 'user' | 'merchant' | 'platform');
setPagination({ current: 1, pageSize: 20, total: 0 });
}}
items={[
{ key: 'user', label: '用户提现' },
{ key: 'merchant', label: '商家提现' },
{ key: 'platform', label: '平台提现' },
]}
style={{ marginBottom: 16 }}
/>
<Table
columns={columns}
dataSource={withdrawals}
rowKey="id"
loading={loading}
scroll={{ x: 1400 }}
scroll={{ x: 1300 }}
pagination={{
...pagination,
showSizeChanger: true,
@@ -294,11 +326,6 @@ const Withdrawals: React.FC = () => {
{currentDetail && (
<Descriptions column={1} bordered>
<Descriptions.Item label="提现ID">{currentDetail.id}</Descriptions.Item>
<Descriptions.Item label="类型">
<Tag color={currentDetail.type === 'user' ? 'blue' : 'orange'}>
{currentDetail.type === 'user' ? '用户' : '商家'}
</Tag>
</Descriptions.Item>
<Descriptions.Item label="申请人">
{currentDetail.userName || currentDetail.merchantName} (ID: {currentDetail.userId || currentDetail.merchantId})
</Descriptions.Item>
+1 -1
View File
@@ -1,7 +1,7 @@
import axios from 'axios';
const request = axios.create({
baseURL: '/api',
baseURL: '',
timeout: 15000,
});
+1 -1
View File
@@ -1 +1 @@
{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/admin.ts","./src/api/config.ts","./src/api/coupon.ts","./src/api/finance.ts","./src/api/invite.ts","./src/api/order.ts","./src/api/review.ts","./src/api/room.ts","./src/components/settlementstatustag.tsx","./src/components/transactionamount.tsx","./src/components/withdrawalstatustag.tsx","./src/hooks/useapproval.ts","./src/hooks/usemodal.ts","./src/hooks/usetabledata.ts","./src/layouts/mainlayout.tsx","./src/pages/dashboard.tsx","./src/pages/invitemanage.tsx","./src/pages/login.tsx","./src/pages/merchantdetail.tsx","./src/pages/merchantlist.tsx","./src/pages/orderdetail.tsx","./src/pages/orderlist.tsx","./src/pages/orderstatistics.tsx","./src/pages/promotion.tsx","./src/pages/reviewmanage.tsx","./src/pages/roomaudit.tsx","./src/pages/storagesettings.tsx","./src/pages/systemsettings.tsx","./src/pages/userlist.tsx","./src/pages/coupon/couponform.tsx","./src/pages/coupon/couponlist.tsx","./src/pages/finance/accounts.tsx","./src/pages/finance/dashboard.tsx","./src/pages/finance/settlements.tsx","./src/pages/finance/withdrawals.tsx","./src/store/auth.ts","./src/utils/request.ts"],"errors":true,"version":"5.9.3"}
{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/admin.ts","./src/api/config.ts","./src/api/coupon.ts","./src/api/finance.ts","./src/api/invite.ts","./src/api/order.ts","./src/api/review.ts","./src/api/room.ts","./src/components/settlementstatustag.tsx","./src/components/transactionamount.tsx","./src/components/withdrawalstatustag.tsx","./src/hooks/useapproval.ts","./src/hooks/usemodal.ts","./src/hooks/usetabledata.ts","./src/layouts/mainlayout.tsx","./src/pages/dashboard.tsx","./src/pages/invitemanage.tsx","./src/pages/login.tsx","./src/pages/merchantdetail.tsx","./src/pages/merchantlist.tsx","./src/pages/orderdetail.tsx","./src/pages/orderlist.tsx","./src/pages/orderstatistics.tsx","./src/pages/promotion.tsx","./src/pages/reviewmanage.tsx","./src/pages/roomaudit.tsx","./src/pages/storagesettings.tsx","./src/pages/systemsettings.tsx","./src/pages/userlist.tsx","./src/pages/coupon/couponform.tsx","./src/pages/coupon/couponlist.tsx","./src/pages/finance/accounts.tsx","./src/pages/finance/dashboard.tsx","./src/pages/finance/earnings.tsx","./src/pages/finance/servicefees.tsx","./src/pages/finance/settlements.tsx","./src/pages/finance/withdrawals.tsx","./src/store/auth.ts","./src/utils/request.ts"],"version":"5.9.3"}