diff --git a/apps/merchant-admin/src/pages/finance/Wallet.tsx b/apps/merchant-admin/src/pages/finance/Wallet.tsx index 5640afb..4a09f96 100644 --- a/apps/merchant-admin/src/pages/finance/Wallet.tsx +++ b/apps/merchant-admin/src/pages/finance/Wallet.tsx @@ -138,7 +138,7 @@ const Wallet: React.FC = () => {
- 手续费率 0.6%,实际到账 = 提现金额 - 手续费 + 无手续费,实际到账 = 提现金额
diff --git a/apps/merchant-admin/src/pages/finance/Withdrawals.tsx b/apps/merchant-admin/src/pages/finance/Withdrawals.tsx index b19b9cb..eadb617 100644 --- a/apps/merchant-admin/src/pages/finance/Withdrawals.tsx +++ b/apps/merchant-admin/src/pages/finance/Withdrawals.tsx @@ -34,7 +34,6 @@ const Withdrawals: React.FC = () => { const columns: ColumnsType = [ { title: '提现金额', dataIndex: 'amount', width: 120, render: (v) => `¥${Number(v).toFixed(2)}` }, - { title: '手续费', dataIndex: 'fee', width: 100, render: (v) => `¥${Number(v).toFixed(2)}` }, { title: '实际到账', dataIndex: 'actualAmount', width: 120, render: (v) => ¥{Number(v).toFixed(2)} }, { title: '开户银行', dataIndex: 'bankName', width: 120 }, { title: '银行账号', dataIndex: 'bankAccount', width: 160 }, diff --git a/apps/miniapp/src/pages/seller/order-detail.vue b/apps/miniapp/src/pages/seller/order-detail.vue index 181e3d5..991713d 100644 --- a/apps/miniapp/src/pages/seller/order-detail.vue +++ b/apps/miniapp/src/pages/seller/order-detail.vue @@ -70,6 +70,14 @@ 优惠券 -¥{{ order.couponDiscount }} + + 软件服务费 + -¥{{ order.serviceFee || 0 }} + + + 预计收入 + ¥{{ order.merchantIncome || order.payAmount }} + 实付金额 ¥{{ order.payAmount }} @@ -340,6 +348,10 @@ async function submitReject() { .discount { color: #52c41a; } +.service-fee { color: #faad14; } + +.merchant-income { color: #1890ff; font-weight: 600; } + .total-row { padding-top: 20rpx; border-top: 2rpx solid #eee; diff --git a/apps/platform-admin/src/App.tsx b/apps/platform-admin/src/App.tsx index dc89fab..e1b8667 100644 --- a/apps/platform-admin/src/App.tsx +++ b/apps/platform-admin/src/App.tsx @@ -15,8 +15,7 @@ import SystemSettings from '@/pages/SystemSettings'; import FinanceSettlements from '@/pages/finance/Settlements'; import FinanceWithdrawals from '@/pages/finance/Withdrawals'; import FinanceEarnings from '@/pages/finance/Earnings'; -import ActivityList from '@/pages/activity/ActivityList'; -import ActivityDetail from '@/pages/activity/ActivityDetail'; +import InviteManage from '@/pages/InviteManage'; const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => { const token = localStorage.getItem('admin_token'); @@ -43,11 +42,7 @@ const App: React.FC = () => ( } /> } /> - - } /> - } /> - } /> - + } /> } /> } /> diff --git a/apps/platform-admin/src/api/config.ts b/apps/platform-admin/src/api/config.ts new file mode 100644 index 0000000..2447b4b --- /dev/null +++ b/apps/platform-admin/src/api/config.ts @@ -0,0 +1,5 @@ +import request from '@/utils/request'; + +export const getServiceFeeConfig = () => request.get('/admin/config/service-fee'); + +export const updateServiceFeeConfig = (rate: number) => request.put('/admin/config/service-fee', { rate }); \ No newline at end of file diff --git a/apps/platform-admin/src/api/activity.ts b/apps/platform-admin/src/api/invite.ts similarity index 63% rename from apps/platform-admin/src/api/activity.ts rename to apps/platform-admin/src/api/invite.ts index d5e787e..0e889e7 100644 --- a/apps/platform-admin/src/api/activity.ts +++ b/apps/platform-admin/src/api/invite.ts @@ -1,25 +1,5 @@ import request from '@/utils/request'; -// 活动列表 -export const getActivities = (params?: { page?: number; pageSize?: number; type?: string; enabled?: boolean }) => - request.get('/admin/activity/list', { params }); - -// 活动详情 -export const getActivityById = (id: number) => - request.get(`/admin/activity/${id}`); - -// 创建活动 -export const createActivity = (data: any) => - request.post('/admin/activity/create', data); - -// 编辑活动 -export const updateActivity = (id: number, data: any) => - request.put(`/admin/activity/${id}/update`, data); - -// 启用/停用活动 -export const toggleActivity = (id: number) => - request.put(`/admin/activity/${id}/toggle`); - // 邀请数据总览 export const getInviteStats = () => request.get('/admin/activity/invite/stats'); @@ -47,3 +27,18 @@ export const rejectInviteWithdrawal = (id: number, reason: string) => // 确认打款 export const payInviteWithdrawal = (id: number) => request.put(`/admin/activity/invite/withdrawals/${id}/pay`); + +// 获取邀请活动配置 +export const getInviteConfig = () => + request.get('/admin/activity/invite/config'); + +// 更新邀请活动配置 +export const updateInviteConfig = (data: { + firstOrderRate?: number; + secondOrderRate?: number; + minCashback?: number; + maxCashback?: number; + withdrawThreshold?: number; + enabled?: boolean; +}) => + request.put('/admin/activity/invite/config', data); \ No newline at end of file diff --git a/apps/platform-admin/src/layouts/MainLayout.tsx b/apps/platform-admin/src/layouts/MainLayout.tsx index d86f3c8..b8793bf 100644 --- a/apps/platform-admin/src/layouts/MainLayout.tsx +++ b/apps/platform-admin/src/layouts/MainLayout.tsx @@ -15,8 +15,6 @@ import { AuditOutlined, PayCircleOutlined, FundOutlined, - RocketOutlined, - OrderedListOutlined, TrophyOutlined, } from '@ant-design/icons'; import { useAuthStore } from '@/store/auth'; @@ -39,14 +37,7 @@ const menuItems = [ { key: '/finance/earnings', icon: , label: '平台收益' }, ], }, - { - key: '/activity', - icon: , - label: '活动管理', - children: [ - { key: '/activity/list', icon: , label: '活动列表' }, - ], - }, + { key: '/invite', icon: , label: '邀请返现' }, { key: '/promotions', icon: , label: '推广管理' }, { key: '/settings', icon: , label: '系统设置' }, ]; @@ -59,7 +50,6 @@ const MainLayout: React.FC = () => { const openKeys = useMemo(() => { const path = location.pathname; if (path.startsWith('/finance')) return ['/finance']; - if (path.startsWith('/activity')) return ['/activity']; return []; }, [location.pathname]); diff --git a/apps/platform-admin/src/pages/activity/ActivityDetail.tsx b/apps/platform-admin/src/pages/InviteManage.tsx similarity index 60% rename from apps/platform-admin/src/pages/activity/ActivityDetail.tsx rename to apps/platform-admin/src/pages/InviteManage.tsx index 2ac286e..c781f61 100644 --- a/apps/platform-admin/src/pages/activity/ActivityDetail.tsx +++ b/apps/platform-admin/src/pages/InviteManage.tsx @@ -1,22 +1,13 @@ import React, { useEffect, useState } from 'react'; -import { useParams, useNavigate } from 'react-router-dom'; -import { Card, Descriptions, Tag, Button, Tabs, Table, Modal, Input, Space, message, Statistic, Row, Col } from 'antd'; +import { Card, Tabs, Table, Tag, Button, Modal, Input, Space, message, Statistic, Row, Col, Switch, Form, InputNumber } from 'antd'; import { - getActivityById, createActivity, updateActivity, getInviteStats, getInviteRecords, getCashbackRecords, getInviteWithdrawals, approveInviteWithdrawal, rejectInviteWithdrawal, payInviteWithdrawal, -} from '@/api/activity'; + getInviteConfig, updateInviteConfig, +} from '@/api/invite'; import dayjs from 'dayjs'; -const ActivityDetail: React.FC = () => { - const { id } = useParams<{ id: string }>(); - const navigate = useNavigate(); - const isCreate = id === 'create'; - - const [activity, setActivity] = useState(null); - const [loading, setLoading] = useState(!isCreate); - - // 邀请返现数据 +const InviteManage: React.FC = () => { const [stats, setStats] = useState({}); const [invitations, setInvitations] = useState([]); const [invTotal, setInvTotal] = useState(0); @@ -37,59 +28,70 @@ const ActivityDetail: React.FC = () => { const [currentWdId, setCurrentWdId] = useState(null); const [rejectReason, setRejectReason] = useState(''); - const fetchActivity = async () => { - if (isCreate) return; - setLoading(true); + const [config, setConfig] = useState(null); + const [configLoading, setConfigLoading] = useState(false); + const [form] = Form.useForm(); + + const fetchStats = async () => { try { - const res = await getActivityById(Number(id)); - setActivity(res.data); + const res = await getInviteStats(); + setStats(res.data || {}); + } catch {} + }; + + const fetchInvitations = async () => { + setInvLoading(true); + try { + const res = await getInviteRecords({ page: invPage, pageSize: 10 }); + setInvitations(res.data?.list || []); + setInvTotal(res.data?.total || 0); } finally { - setLoading(false); + setInvLoading(false); } }; - const fetchInviteData = async () => { + const fetchCashbacks = async () => { + setCbLoading(true); try { - const [statsRes, invRes, cbRes, wdRes] = await Promise.all([ - getInviteStats(), - getInviteRecords({ page: invPage, pageSize: 10 }), - getCashbackRecords({ page: cbPage, pageSize: 10 }), - getInviteWithdrawals({ page: wdPage, pageSize: 10 }), - ]); - setStats(statsRes.data || {}); - setInvitations(invRes.data?.list || []); - setInvTotal(invRes.data?.total || 0); - setCashbacks(cbRes.data?.list || []); - setCbTotal(cbRes.data?.total || 0); - setWithdrawals(wdRes.data?.list || []); - setWdTotal(wdRes.data?.total || 0); - } catch {} + const res = await getCashbackRecords({ page: cbPage, pageSize: 10 }); + setCashbacks(res.data?.list || []); + setCbTotal(res.data?.total || 0); + } finally { + setCbLoading(false); + } }; - useEffect(() => { fetchActivity(); }, [id]); - useEffect(() => { - if (!isCreate && activity?.type === 'invite_cashback') fetchInviteData(); - }, [activity, invPage, cbPage, wdPage]); - - // 创建固定活动 - const handleCreate = async (type: string) => { - const typeMap: Record = { - invite_cashback: { - name: '邀请返现', - config: { firstOrderRate: 0.05, secondOrderRate: 0.005, minCashback: 0.01, maxCashback: 50, withdrawThreshold: 10 }, - }, - }; - const preset = typeMap[type]; - if (!preset) return; + const fetchWithdrawals = async () => { + setWdLoading(true); try { - await createActivity({ name: preset.name, type, enabled: true, config: preset.config }); - message.success('创建成功'); - navigate('/activity/list'); - } catch {} + const res = await getInviteWithdrawals({ page: wdPage, pageSize: 10 }); + setWithdrawals(res.data?.list || []); + setWdTotal(res.data?.total || 0); + } finally { + setWdLoading(false); + } }; + const fetchConfig = async () => { + setConfigLoading(true); + try { + const res = await getInviteConfig(); + setConfig(res.data); + if (res.data) { + form.setFieldsValue(res.data); + } + } finally { + setConfigLoading(false); + } + }; + + useEffect(() => { fetchStats(); fetchConfig(); }, []); + useEffect(() => { fetchInvitations(); }, [invPage]); + useEffect(() => { fetchCashbacks(); }, [cbPage]); + useEffect(() => { fetchWithdrawals(); }, [wdPage]); + const handleApprove = async (wid: number) => { - try { await approveInviteWithdrawal(wid); message.success('审核通过'); fetchInviteData(); } catch {} + try { await approveInviteWithdrawal(wid); message.success('审核通过'); fetchWithdrawals(); fetchStats(); } catch {} }; const handleReject = async () => { @@ -99,31 +101,31 @@ const ActivityDetail: React.FC = () => { message.success('已拒绝'); setRejectModalOpen(false); setRejectReason(''); - fetchInviteData(); + fetchWithdrawals(); + fetchStats(); } catch {} }; const handlePay = async (wid: number) => { - try { await payInviteWithdrawal(wid); message.success('已确认打款'); fetchInviteData(); } catch {} + try { await payInviteWithdrawal(wid); message.success('已确认打款'); fetchWithdrawals(); fetchStats(); } catch {} }; - if (isCreate) { - return ( - -
- handleCreate('invite_cashback')}> - - -
-
- -
-
- ); - } + const handleToggle = async (enabled: boolean) => { + try { + await updateInviteConfig({ enabled }); + setConfig({ ...config, enabled }); + message.success(enabled ? '已启用' : '已停用'); + } catch {} + }; - if (loading) return ; - if (!activity) return 活动不存在; + const handleSaveConfig = async () => { + try { + const values = await form.validateFields(); + await updateInviteConfig(values); + message.success('配置已保存'); + setConfig({ ...config, ...values }); + } catch {} + }; const invColumns = [ { title: 'ID', dataIndex: 'id', width: 60 }, @@ -181,34 +183,7 @@ const ActivityDetail: React.FC = () => { const tabItems = [ { - key: 'info', - label: '活动信息', - children: ( - - {activity.name} - 邀请返现 - - {activity.enabled ? '启用' : '停用'} - - - {activity.startTime - ? `${dayjs(activity.startTime).format('YYYY-MM-DD')} ~ ${dayjs(activity.endTime).format('YYYY-MM-DD')}` - : '不限'} - - {activity.config && ( - <> - {(activity.config.firstOrderRate * 100).toFixed(1)}% - {(activity.config.secondOrderRate * 100).toFixed(1)}% - ¥{activity.config.minCashback} - ¥{activity.config.maxCashback} - ¥{activity.config.withdrawThreshold} - - )} - - ), - }, - { - key: 'stats', + key: 'overview', label: '数据概览', children: (
@@ -238,13 +213,45 @@ const ActivityDetail: React.FC = () => { pagination={{ current: wdPage, total: wdTotal, pageSize: 10, onChange: setWdPage }} /> ), }, + { + key: 'config', + label: '活动配置', + children: ( + +
+ 邀请返现活动 + +
+
+ + `${(Number(v) * 100).toFixed(1)}`} parser={v => Number(v?.replace('%', '')) / 100} /> + + + `${(Number(v) * 100).toFixed(1)}`} parser={v => Number(v?.replace('%', '')) / 100} /> + + + + + + + + + + + + + +
+
+ ), + }, ]; return (
-
- -
+

邀请返现管理

@@ -255,4 +262,4 @@ const ActivityDetail: React.FC = () => { ); }; -export default ActivityDetail; +export default InviteManage; \ No newline at end of file diff --git a/apps/platform-admin/src/pages/SystemSettings.tsx b/apps/platform-admin/src/pages/SystemSettings.tsx index 72ad8de..036575c 100644 --- a/apps/platform-admin/src/pages/SystemSettings.tsx +++ b/apps/platform-admin/src/pages/SystemSettings.tsx @@ -1,11 +1,88 @@ -import React from 'react'; -import { Empty } from 'antd'; +import React, { useEffect, useState } from 'react'; +import { Card, Form, InputNumber, Button, message, Spin, Divider } from 'antd'; +import { getServiceFeeConfig, updateServiceFeeConfig } from '@/api/config'; -const SystemSettings: React.FC = () => ( -
-

系统设置

- -
-); +const SystemSettings: React.FC = () => { + const [loading, setLoading] = useState(false); + const [saving, setSaving] = useState(false); + const [form] = Form.useForm(); -export default SystemSettings; + const fetchConfig = async () => { + setLoading(true); + try { + const res: any = await getServiceFeeConfig(); + form.setFieldsValue({ serviceFeeRate: res.data?.rate || 0.05 }); + } catch (e) { + form.setFieldsValue({ serviceFeeRate: 0.05 }); + } finally { + setLoading(false); + } + }; + + useEffect(() => { fetchConfig(); }, []); + + const handleSave = async () => { + try { + const values = await form.validateFields(); + setSaving(true); + await updateServiceFeeConfig(values.serviceFeeRate); + message.success('服务费配置已保存'); + fetchConfig(); + } catch (e: any) { + if (e?.message) message.error(e.message); + } finally { + setSaving(false); + } + }; + + if (loading) return ; + + return ( +
+

系统设置

+ + +
+ + `${(Number(v) * 100).toFixed(2)}%`} + parser={(v) => Number(v?.replace('%', '')) / 100} + /> + + + + +
+ + + +
+

计算公式:

+

软件服务费 = 实付金额 × 服务费比例

+

商家预计收入 = 实付金额 - 软件服务费

+

+ 示例:实付金额 ¥100.00,服务费比例 5% +

+

软件服务费 = ¥5.00,商家预计收入 = ¥95.00

+
+
+
+ ); +}; + +export default SystemSettings; \ No newline at end of file diff --git a/apps/platform-admin/src/pages/activity/ActivityList.tsx b/apps/platform-admin/src/pages/activity/ActivityList.tsx deleted file mode 100644 index 27ba543..0000000 --- a/apps/platform-admin/src/pages/activity/ActivityList.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Card, Table, Button, Tag, Space, message } from 'antd'; -import { useNavigate } from 'react-router-dom'; -import { getActivities, toggleActivity } from '@/api/activity'; -import dayjs from 'dayjs'; - -const ACTIVITY_TYPES: Record = { - invite_cashback: '邀请返现', -}; - -const ActivityList: React.FC = () => { - const navigate = useNavigate(); - const [list, setList] = useState([]); - const [total, setTotal] = useState(0); - const [page, setPage] = useState(1); - const [loading, setLoading] = useState(false); - - const fetchList = async () => { - setLoading(true); - try { - const res = await getActivities({ page, pageSize: 10 }); - setList(res.data?.list || []); - setTotal(res.data?.total || 0); - } finally { - setLoading(false); - } - }; - - useEffect(() => { fetchList(); }, [page]); - - const handleToggle = async (record: any) => { - try { - await toggleActivity(record.id); - message.success(record.enabled ? '已停用' : '已启用'); - fetchList(); - } catch {} - }; - - const columns = [ - { title: 'ID', dataIndex: 'id', width: 60 }, - { title: '活动名称', dataIndex: 'name' }, - { title: '活动类型', dataIndex: 'type', render: (v: string) => ACTIVITY_TYPES[v] || v }, - { title: '状态', dataIndex: 'enabled', render: (v: boolean) => {v ? '启用' : '停用'} }, - { - title: '活动时间', - render: (_: any, r: any) => { - if (!r.startTime) return '不限'; - return `${dayjs(r.startTime).format('YYYY-MM-DD')} ~ ${dayjs(r.endTime).format('YYYY-MM-DD')}`; - }, - }, - { title: '创建时间', dataIndex: 'createdAt', render: (v: string) => v ? dayjs(v).format('YYYY-MM-DD HH:mm') : '-' }, - { - title: '操作', - render: (_: any, r: any) => ( - - - - - ), - }, - ]; - - return ( - navigate('/activity/detail/create')}>创建活动}> - - - ); -}; - -export default ActivityList; diff --git a/apps/platform-admin/src/pages/finance/Earnings.tsx b/apps/platform-admin/src/pages/finance/Earnings.tsx index a33303f..46c3035 100644 --- a/apps/platform-admin/src/pages/finance/Earnings.tsx +++ b/apps/platform-admin/src/pages/finance/Earnings.tsx @@ -100,19 +100,8 @@ const Earnings: React.FC = () => { - - - - - { const columns: ColumnsType = [ { title: '商家', dataIndex: ['merchant', 'shopName'], width: 120, ellipsis: true }, { title: '提现金额', dataIndex: 'amount', width: 120, render: (v) => `¥${Number(v).toFixed(2)}` }, - { title: '手续费', dataIndex: 'fee', width: 90, render: (v) => `¥${Number(v).toFixed(2)}` }, - { title: '佣金', dataIndex: 'commissionAmount', width: 90, render: (v) => `¥${Number(v).toFixed(2)}` }, { title: '实际到账', dataIndex: 'actualAmount', width: 120, render: (v) => ¥{Number(v).toFixed(2)} }, { title: '开户银行', dataIndex: 'bankName', width: 120 }, { title: '银行账号', dataIndex: 'bankAccount', width: 150 }, diff --git a/apps/server/src/app.module.ts b/apps/server/src/app.module.ts index 5a4992f..3ec8f7d 100644 --- a/apps/server/src/app.module.ts +++ b/apps/server/src/app.module.ts @@ -16,6 +16,7 @@ import { AdminAuthModule } from './modules/admin-auth/admin-auth.module'; import { ReviewModule } from './modules/review/review.module'; import { FinanceModule } from './modules/finance/finance.module'; import { ActivityModule } from './modules/activity/activity.module'; +import { PlatformConfigModule } from './modules/config/config.module'; import { ScheduleModule as TaskScheduleModule } from './schedule/schedule.module'; @Module({ @@ -53,6 +54,7 @@ import { ScheduleModule as TaskScheduleModule } from './schedule/schedule.module RoomCalendarModule, OrderModule, AdminAuthModule, + PlatformConfigModule, ], }) export class AppModule {} diff --git a/apps/server/src/entities/order.entity.ts b/apps/server/src/entities/order.entity.ts index e474419..92463f1 100644 --- a/apps/server/src/entities/order.entity.ts +++ b/apps/server/src/entities/order.entity.ts @@ -44,6 +44,12 @@ export class Order { @Column({ name: 'room_amount', type: 'decimal', precision: 10, scale: 2, unsigned: true, comment: '房费总额' }) roomAmount: number; + @Column({ name: 'service_fee', type: 'decimal', precision: 10, scale: 2, unsigned: true, default: 0, comment: '软件服务费' }) + serviceFee: number; + + @Column({ name: 'merchant_income', type: 'decimal', precision: 10, scale: 2, unsigned: true, default: 0, comment: '商家预计收入' }) + merchantIncome: number; + @Column({ name: 'coupon_discount', type: 'decimal', precision: 10, scale: 2, unsigned: true, default: 0, comment: '优惠券抵扣' }) couponDiscount: number; diff --git a/apps/server/src/entities/platform-config.entity.ts b/apps/server/src/entities/platform-config.entity.ts new file mode 100644 index 0000000..41d08bd --- /dev/null +++ b/apps/server/src/entities/platform-config.entity.ts @@ -0,0 +1,22 @@ +import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm'; + +@Entity('platform_configs') +export class PlatformConfig { + @PrimaryGeneratedColumn({ type: 'bigint', unsigned: true }) + id: number; + + @Column({ name: 'config_key', length: 50, unique: true, comment: '配置键' }) + configKey: string; + + @Column({ name: 'config_value', length: 500, comment: '配置值' }) + configValue: string; + + @Column({ length: 200, nullable: true, comment: '配置说明' }) + description: string; + + @CreateDateColumn({ comment: '创建时间' }) + createdAt: Date; + + @UpdateDateColumn({ comment: '更新时间' }) + updatedAt: Date; +} \ No newline at end of file diff --git a/apps/server/src/modules/activity/activity.controller.ts b/apps/server/src/modules/activity/activity.controller.ts index 296d7b6..52a8b51 100644 --- a/apps/server/src/modules/activity/activity.controller.ts +++ b/apps/server/src/modules/activity/activity.controller.ts @@ -124,6 +124,18 @@ export class AdminActivityController { return this.activityService.getAdminStats(); } + @Get('invite/config') + @ApiOperation({ summary: '获取邀请活动配置' }) + async getInviteConfig() { + return this.activityService.getInviteCashbackConfig(); + } + + @Put('invite/config') + @ApiOperation({ summary: '更新邀请活动配置' }) + async updateInviteConfig(@Body() dto: UpdateActivityDto) { + return this.activityService.updateInviteCashbackConfig(dto); + } + @Get('invite/records') @ApiOperation({ summary: '邀请记录列表' }) async getRecords(@Query() dto: QueryInviteRecordsDto) { diff --git a/apps/server/src/modules/activity/activity.service.ts b/apps/server/src/modules/activity/activity.service.ts index baff6c6..f535b1a 100644 --- a/apps/server/src/modules/activity/activity.service.ts +++ b/apps/server/src/modules/activity/activity.service.ts @@ -435,6 +435,36 @@ export class ActivityService { // ===== 管理端功能 ===== + async getInviteCashbackConfig() { + const activity = await this.activityRepo.findOne({ + where: { type: 'invite_cashback' }, + }); + if (!activity) { + // 创建默认配置 + activity = this.activityRepo.create({ + name: '邀请返现', + type: 'invite_cashback', + enabled: true, + config: { + firstOrderRate: 0.05, + secondOrderRate: 0.005, + minCashback: 0.01, + maxCashback: 50, + withdrawThreshold: 10, + }, + }); + await this.activityRepo.save(activity); + } + return activity; + } + + async updateInviteCashbackConfig(dto: UpdateActivityDto) { + const activity = await this.getInviteCashbackConfig(); + if (dto.enabled !== undefined) activity.enabled = dto.enabled; + if (dto.config) activity.config = { ...activity.config, ...dto.config }; + return this.activityRepo.save(activity); + } + async getAdminStats(activityId?: number) { const where: any = {}; if (activityId) where.activityId = activityId; diff --git a/apps/server/src/modules/config/config.controller.ts b/apps/server/src/modules/config/config.controller.ts new file mode 100644 index 0000000..c369fa9 --- /dev/null +++ b/apps/server/src/modules/config/config.controller.ts @@ -0,0 +1,26 @@ +import { Controller, Get, Put, Body, UseGuards } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; +import { ConfigService } from './config.service'; +import { JwtAuthGuard, RolesGuard, Roles } from '@/common'; + +@ApiTags('管理端-系统配置') +@ApiBearerAuth() +@UseGuards(JwtAuthGuard, RolesGuard) +@Roles('admin') +@Controller('admin/config') +export class ConfigController { + constructor(private readonly configService: ConfigService) {} + + @Get('service-fee') + @ApiOperation({ summary: '获取服务费配置' }) + async getServiceFeeConfig() { + return this.configService.getServiceFeeConfig(); + } + + @Put('service-fee') + @ApiOperation({ summary: '更新服务费配置' }) + async updateServiceFeeConfig(@Body() body: { rate: number }) { + await this.configService.updateServiceFeeRate(body.rate); + return this.configService.getServiceFeeConfig(); + } +} \ No newline at end of file diff --git a/apps/server/src/modules/config/config.module.ts b/apps/server/src/modules/config/config.module.ts new file mode 100644 index 0000000..54c90a9 --- /dev/null +++ b/apps/server/src/modules/config/config.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { PlatformConfig } from '@/entities/platform-config.entity'; +import { ConfigService } from './config.service'; +import { ConfigController } from './config.controller'; + +@Module({ + imports: [TypeOrmModule.forFeature([PlatformConfig])], + controllers: [ConfigController], + providers: [ConfigService], + exports: [ConfigService], +}) +export class ConfigModule {} \ No newline at end of file diff --git a/apps/server/src/modules/config/config.service.ts b/apps/server/src/modules/config/config.service.ts new file mode 100644 index 0000000..0f7d9cb --- /dev/null +++ b/apps/server/src/modules/config/config.service.ts @@ -0,0 +1,57 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { PlatformConfig } from '@/entities/platform-config.entity'; + +@Injectable() +export class ConfigService { + constructor( + @InjectRepository(PlatformConfig) + private configRepo: Repository, + ) {} + + async getConfig(key: string): Promise { + const config = await this.configRepo.findOne({ where: { configKey: key } }); + return config?.configValue || null; + } + + async setConfig(key: string, value: string, description?: string): Promise { + let config = await this.configRepo.findOne({ where: { configKey: key } }); + if (config) { + config.configValue = value; + if (description) config.description = description; + } else { + config = this.configRepo.create({ configKey: key, configValue: value, description }); + } + await this.configRepo.save(config); + } + + async getServiceFeeRate(): Promise { + const value = await this.getConfig('service_fee_rate'); + const rate = parseFloat(value || '0.05'); + return isNaN(rate) ? 0.05 : rate; + } + + async getCommissionRate(): Promise { + const value = await this.getConfig('commission_rate'); + const rate = parseFloat(value || '0.10'); + return isNaN(rate) ? 0.10 : rate; + } + + async getServiceFeeConfig(): Promise<{ rate: number; description: string }> { + const value = await this.getConfig('service_fee_rate'); + const config = await this.configRepo.findOne({ where: { configKey: 'service_fee_rate' } }); + const rate = parseFloat(value || '0.05'); + return { + rate: isNaN(rate) ? 0.05 : rate, + description: config?.description || '软件服务费比例', + }; + } + + async updateServiceFeeRate(rate: number): Promise { + if (rate < 0 || rate > 1) { + throw new Error('服务费比例必须在0-1之间'); + } + await this.setConfig('service_fee_rate', rate.toString(), '软件服务费比例'); + } +} \ No newline at end of file diff --git a/apps/server/src/modules/finance/finance.service.ts b/apps/server/src/modules/finance/finance.service.ts index 99dc31e..c0ccb14 100644 --- a/apps/server/src/modules/finance/finance.service.ts +++ b/apps/server/src/modules/finance/finance.service.ts @@ -119,13 +119,6 @@ export class FinanceService { order: { createdAt: 'ASC' }, }); - // 计算手续费率 - const feeRate = 0.006; - const fee = Math.round(dto.amount * feeRate * 100) / 100; - const commissionRate = Number(merchant.walletBalance) > 0 ? 0.10 : 0; - const commissionAmount = Math.round(dto.amount * commissionRate * 100) / 100; - const actualAmount = Math.round((dto.amount - fee - commissionAmount) * 100) / 100; - const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); @@ -138,9 +131,9 @@ export class FinanceService { merchantId, settlementIds: approvedSettlements.map(s => s.id), amount: dto.amount, - fee, - commissionAmount, - actualAmount, + fee: 0, + commissionAmount: 0, + actualAmount: dto.amount, bankName: dto.bankName, bankAccount: dto.bankAccount, accountName: dto.accountName, @@ -361,8 +354,13 @@ export class FinanceService { const withdrawalStats = await wQb .select('COUNT(*)', 'totalWithdrawals') .addSelect('COALESCE(SUM(CASE WHEN w.status = \'paid\' THEN w.amount ELSE 0 END), 0)', 'paidAmount') - .addSelect('COALESCE(SUM(w.fee), 0)', 'totalFee') - .addSelect('COALESCE(SUM(w.commission_amount), 0)', 'totalWithdrawCommission') + .getRawOne(); + + // 服务费统计 + const serviceFeeStats = await this.orderRepo + .createQueryBuilder('o') + .where('o.status IN (:...statuses)', { statuses: ['completed', 'checked_in'] }) + .select('COALESCE(SUM(o.service_fee), 0)', 'totalServiceFee') .getRawOne(); return { @@ -371,9 +369,8 @@ export class FinanceService { settlementAmount: Number(settlementStats?.totalSettlementAmount || 0), totalSettlements: Number(settlementStats?.totalSettlements || 0), paidAmount: Number(withdrawalStats?.paidAmount || 0), - totalFee: Number(withdrawalStats?.totalFee || 0), - totalWithdrawCommission: Number(withdrawalStats?.totalWithdrawCommission || 0), totalWithdrawals: Number(withdrawalStats?.totalWithdrawals || 0), + totalServiceFee: Number(serviceFeeStats?.totalServiceFee || 0), }; } diff --git a/apps/server/src/modules/order/order.module.ts b/apps/server/src/modules/order/order.module.ts index 4b3b535..9ae4789 100644 --- a/apps/server/src/modules/order/order.module.ts +++ b/apps/server/src/modules/order/order.module.ts @@ -7,12 +7,14 @@ import { OrderService } from './order.service'; import { UserOrderController, MerchantOrderController, AdminOrderController } from './order.controller'; import { MerchantModule } from '../merchant/merchant.module'; import { ActivityModule } from '../activity/activity.module'; +import { PlatformConfigModule } from '../config/config.module'; @Module({ imports: [ TypeOrmModule.forFeature([Order, Room, RoomCalendar]), MerchantModule, forwardRef(() => ActivityModule), + PlatformConfigModule, ], controllers: [UserOrderController, MerchantOrderController, AdminOrderController], providers: [OrderService], diff --git a/apps/server/src/modules/order/order.service.ts b/apps/server/src/modules/order/order.service.ts index c79c335..ad8ace5 100644 --- a/apps/server/src/modules/order/order.service.ts +++ b/apps/server/src/modules/order/order.service.ts @@ -6,6 +6,7 @@ import { Room } from '@/entities/room.entity'; import { RoomCalendar } from '@/entities/room-calendar.entity'; import { CreateOrderDto, QueryOrderDto, ConfirmOrderDto } from './dto/order.dto'; import { ActivityService } from '@/modules/activity/activity.service'; +import { ConfigService } from '@/modules/config/config.service'; @Injectable() export class OrderService { @@ -17,6 +18,7 @@ export class OrderService { @InjectRepository(RoomCalendar) private calendarRepo: Repository, private readonly activityService: ActivityService, + private readonly configService: ConfigService, ) {} async create(userId: number, dto: CreateOrderDto) { @@ -36,6 +38,11 @@ export class OrderService { const totalAmount = roomAmount; const payAmount = totalAmount - couponDiscount; + // 计算软件服务费和商家收入 + const serviceFeeRate = await this.configService.getServiceFeeRate(); + const serviceFee = Math.round(payAmount * serviceFeeRate * 100) / 100; + const merchantIncome = Math.round((payAmount - serviceFee) * 100) / 100; + const now = new Date(); const orderNo = [ now.getFullYear(), @@ -59,6 +66,8 @@ export class OrderService { guestCount: dto.guestCount || 1, roomPrice: room.price, roomAmount, + serviceFee, + merchantIncome, couponDiscount, totalAmount, payAmount, diff --git a/database/migrations/001_init_schema.sql b/database/migrations/001_init_schema.sql index 7ef101d..1596ed7 100644 --- a/database/migrations/001_init_schema.sql +++ b/database/migrations/001_init_schema.sql @@ -209,7 +209,8 @@ CREATE TABLE `orders` ( `room_count` TINYINT UNSIGNED DEFAULT 1 COMMENT '房间数', `room_price` DECIMAL(10,2) UNSIGNED NOT NULL COMMENT '房费单价', `room_amount` DECIMAL(10,2) UNSIGNED NOT NULL COMMENT '房费总额', - `service_fee` DECIMAL(10,2) UNSIGNED DEFAULT 0.00 COMMENT '服务费', + `service_fee` DECIMAL(10,2) UNSIGNED DEFAULT 0.00 COMMENT '软件服务费', + `merchant_income` DECIMAL(10,2) UNSIGNED DEFAULT 0.00 COMMENT '商家预计收入', `coupon_discount` DECIMAL(10,2) UNSIGNED DEFAULT 0.00 COMMENT '优惠券抵扣', `total_amount` DECIMAL(10,2) UNSIGNED NOT NULL COMMENT '订单总金额', `pay_amount` DECIMAL(10,2) UNSIGNED NOT NULL COMMENT '实付金额', diff --git a/database/modules.md b/database/modules.md index acc2994..1eab957 100644 --- a/database/modules.md +++ b/database/modules.md @@ -136,12 +136,32 @@ ``` 待支付 ──支付成功──> 待确认 ──商家确认──> 待入住 ──入住──> 已入住 ──离店──> 已完成 - │ │ - └──超时/取消──> 已取消 └──商家拒绝──> 已取消 + │ │ │ + └──超时/取消──> 已取消 └──商家拒绝──> 已取消 └──用户退款──> 退款中 + │ + ├──商家通过──> 已退款(恢复房量) + └──商家拒绝──> 已取消 -已完成 ──申请退款──> 退款中 ──平台处理──> 已退款 +待确认 ──用户退款──> 退款中 ──商家通过──> 已退款(恢复房量) ``` +### 用户订单操作 + +| 操作 | 接口 | 适用状态 | 说明 | +| -------- | ------------------------- | ---------------------------- | ------------------------ | +| 取消订单 | PUT /orders/:id/cancel | pending_pay, pending_confirm | 取消订单,已支付则退款 | +| 申请退款 | PUT /orders/:id/refund | pending_confirm, pending_checkin | 提交退款申请 | + +### 商家订单操作 + +| 操作 | 接口 | 适用状态 | 说明 | +| ---------- | ----------------------------------------- | ----------------- | ----------------------------- | +| 确认订单 | PUT /seller/orders/:id/confirm | pending_confirm | 确认后等待用户入住 | +| 拒绝订单 | PUT /seller/orders/:id/reject | pending_confirm | 拒绝并退款 | +| 办理入住 | PUT /seller/orders/:id/checkin | pending_checkin | 标记已入住 | +| 通过退款 | PUT /seller/orders/:id/approve-refund | refunding | 同意退款,恢复房量日历库存 | +| 拒绝退款 | PUT /seller/orders/:id/reject-refund | refunding | 拒绝退款,订单变为已取消 | + ### 自动化规则 - 待支付订单: 30分钟未支付自动取消 @@ -152,11 +172,17 @@ ``` 房费总额 = 房价/晚 × 入住晚数 × 房间数 -服务费 = 房费总额 × 服务费比例(默认5%) -订单总额 = 房费总额 + 服务费 +订单总额 = 房费总额 实付金额 = 订单总额 - 优惠券抵扣 +软件服务费 = 实付金额 × 服务费比例(默认5%,平台后台可配置) +商家预计收入 = 实付金额 - 软件服务费 ``` +**说明**: +- 软件服务费归平台所有,在订单创建时计算 +- 商家预计收入 = 实付金额 - 软件服务费 +- 费用明细仅在商家订单详情中展示,用户端不显示 + ### 佣金计算 ``` @@ -242,9 +268,9 @@ ### 提现规则 - 最低提现金额: 100元 -- 提现手续费: 默认0.6% +- 提现手续费: 无 - 到账时间: T+1工作日 -- 需绑定银行卡且实名认证 +- 需绑定银行卡 ### 对账维度 @@ -330,6 +356,10 @@ ## 11. 邀请返现活动模块 +### 模块概述 + +邀请返现是平台级营销活动,独立于其他促销活动管理。平台管理后台有专门的「邀请返现」菜单入口,不与其他活动混在一起。 + ### 活动概述 平台级营销活动,用户(邀请人)通过分享邀请链接/海报邀请新用户注册并完成订单,邀请人获得返现奖励。被邀请人无额外收益。 @@ -517,19 +547,19 @@ **平台管理端(需管理员Token):** -| 方法 | 路径 | 说明 | -| ---- | ---------------------------------------------- | ------------- | -| GET | /admin/activity/list | 活动列表 | -| POST | /admin/activity/create | 创建活动 | -| PUT | /admin/activity/:id/update | 编辑活动 | -| PUT | /admin/activity/:id/toggle | 启用/停用活动 | -| GET | /admin/activity/invite/stats | 邀请数据总览 | -| GET | /admin/activity/invite/records | 邀请记录列表 | -| GET | /admin/activity/invite/cashbacks | 返现记录列表 | -| GET | /admin/activity/invite/withdrawals | 提现审核列表 | -| PUT | /admin/activity/invite/withdrawals/:id/approve | 审核通过 | -| PUT | /admin/activity/invite/withdrawals/:id/reject | 审核拒绝 | -| PUT | /admin/activity/invite/withdrawals/:id/pay | 确认打款 | +| 方法 | 路径 | 说明 | +| ---- | ---------------------------------------------- | --------------- | +| GET | /admin/activity/invite/stats | 邀请数据总览 | +| GET | /admin/activity/invite/config | 获取活动配置 | +| PUT | /admin/activity/invite/config | 更新活动配置 | +| GET | /admin/activity/invite/records | 邀请记录列表 | +| GET | /admin/activity/invite/cashbacks | 返现记录列表 | +| GET | /admin/activity/invite/withdrawals | 提现审核列表 | +| PUT | /admin/activity/invite/withdrawals/:id/approve | 审核通过 | +| PUT | /admin/activity/invite/withdrawals/:id/reject | 审核拒绝 | +| PUT | /admin/activity/invite/withdrawals/:id/pay | 确认打款 | + +**注意**: 平台管理后台不再提供通用的活动列表CRUD,邀请返现活动有独立的配置管理页面。 ### 小程序页面路由 @@ -538,4 +568,17 @@ | 邀请首页 | /pages/invite/index | 邀请统计、海报生成、分享 | | 邀请记录 | /pages/invite/records | 邀请人数、被邀请人列表 | | 返现记录 | /pages/invite/cashbacks | 返现明细列表 | -| 提现页面 | /pages/invite/withdraw | 余额展示、提现操作、提现记录 | +| 提现页面 | /pages/invite/withdraw | 余额展示、提现操作 | +| 提现记录 | /pages/invite/withdrawals | 提现历史列表 | +| 邀请海报 | /pages/invite/poster | 生成分享海报 | + +### 平台管理后台页面 + +| 页面 | 路径 | 说明 | +| ------------ | -------- | ---------------------------------------------- | +| 邀请返现管理 | /invite | 数据概览、提现审核、活动配置三个Tab | + +**页面结构**: +- 数据概览Tab: 统计卡片 + 邀请记录列表 + 返现记录列表 +- 提现审核Tab: 提现申请列表,支持审核通过/拒绝/确认打款 +- 活动配置Tab: 启用/停用开关 + 返现比例/金额限制/提现门槛配置表单 diff --git a/database/seeds/001_init_data.sql b/database/seeds/001_init_data.sql index 14fe333..76af248 100644 --- a/database/seeds/001_init_data.sql +++ b/database/seeds/001_init_data.sql @@ -28,7 +28,7 @@ INSERT INTO `member_levels` (`name`, `level`, `min_points`, `discount`, `benefit -- 平台配置 INSERT INTO `platform_configs` (`config_key`, `config_value`, `description`) VALUES ('commission_rate', '0.10', '默认平台佣金比例'), -('withdraw_fee_rate', '0.006', '提现手续费率'), +('service_fee_rate', '0.05', '软件服务费比例'), ('min_deposit', '5000', '最低保证金金额'), ('auto_cancel_minutes', '30', '未支付订单自动取消时间(分钟)'), ('auto_complete_hours', '24', '入住后自动完成订单时间(小时)'), diff --git a/database/skills.md b/database/skills.md index 3dcbe9a..9da9a8d 100644 --- a/database/skills.md +++ b/database/skills.md @@ -19,7 +19,7 @@ | rooms | 房源表 | merchant_id, name, type, price, status, audit_status | | room_calendar | 房量房价日历 | room_id, date, price, stock, status | | room_calendar_logs | 房态变更日志 | room_id, operator_id, change_type | -| orders | 订单表 | order_no, user_id, merchant_id, room_id, status | +| orders | 订单表 | order_no, user_id, merchant_id, room_id, pay_amount, service_fee, merchant_income, status | | reviews | 评价表 | order_id, user_id, rating, content | | favorites | 收藏表 | user_id, room_id | | coupons | 优惠券模板 | name, type, value, scope | @@ -100,6 +100,29 @@ orders (1) ──── (1) settlements - `refunding` - 退款中 - `refunded` - 已退款 +### 订单状态流转 + +``` +待支付 ──支付成功──> 待确认 ──商家确认──> 待入住 ──入住──> 已入住 ──离店──> 已完成 + │ │ │ + └──超时/取消──> 已取消 └──商家拒绝──> 已取消 └──用户退款──> 退款中 + │ + ├──商家通过──> 已退款(恢复房量) + └──商家拒绝──> 已取消 + +待确认 ──用户退款──> 退款中 ──商家通过──> 已退款(恢复房量) +``` + +### 订单取消/退款规则 + +| 操作 | 适用状态 | 说明 | +| ------------ | ---------------------------- | --------------------------------------- | +| 用户取消 | pending_pay | 直接取消,无退款 | +| 用户取消 | pending_confirm | 取消并退款,恢复房量 | +| 用户退款申请 | pending_confirm, pending_checkin | 状态变为 refunding,等待商家审核 | +| 商家通过退款 | refunding | 状态变为 refunded,恢复房量日历库存 | +| 商家拒绝退款 | refunding | 状态变为 cancelled | + ### 房源状态 (rooms.status) - `on_sale` - 在售 @@ -210,6 +233,26 @@ mysql -u root -p rent_platform < database/seeds/001_init_data.sql --- +## 平台配置表 (platform_configs) + +| 配置键 | 默认值 | 说明 | +| ----------------- | ------ | ------------------------ | +| commission_rate | 0.10 | 默认平台佣金比例 | +| service_fee_rate | 0.05 | 软件服务费比例(可配置) | +| min_deposit | 5000 | 最低保证金金额 | +| auto_cancel_minutes | 30 | 未支付订单自动取消时间 | +| auto_complete_hours | 24 | 入住后自动完成订单时间 | +| sms_enabled | true | 是否启用短信通知 | +| max_images_per_room | 20 | 每个房源最大图片数 | +| max_images_per_review | 9 | 每条评价最大图片数 | + +**服务费计算**: +- 软件服务费 = 实付金额 × service_fee_rate +- 商家预计收入 = 实付金额 - 软件服务费 +- 配置可通过平台管理后台「系统设置」页面调整 + +--- + ## 商家入驻流程 ### 流程概述 @@ -295,7 +338,9 @@ interface SellerLoginResult { | ------------- | ---------------------------- | ---------------------------- | | 个人中心 | /pages/mine/index | 入口:商家中心、商家入驻按钮 | | 商家注册/登录 | /pages/seller-register/index | 商家账号注册登录 | -| 商家中心 | /pages/merchant/home | 商家管理入口 | +| 商家中心 | /pages/seller/home | 商家管理入口 | +| 商家订单列表 | /pages/seller/orders | 商家订单管理 | +| 商家订单详情 | /pages/seller/order-detail | 商家订单详情 | | 创建店铺 | /pages/shop-create/index | 填写店铺信息提交审核 | | 修改店铺 | /pages/shop-edit/index | 修改店铺信息重新审核 | @@ -437,39 +482,39 @@ async update(id, dto) { 2. **商家实名认证**: 身份证上传、银行卡绑定 3. **图片上传接口**: 营业执照真实上传功能 -- 显示状态标签"已冻结" -- 显示提示"店铺已被冻结,请联系平台客服" -- 不显示修改按钮 -- 不显示数据概览和功能菜单 +--- -### 修改店铺重新审核逻辑 +## 商家订单管理 -**后端逻辑** (`merchant.service.ts`): +### 商家订单接口 -```typescript -async update(id, dto) { - const merchant = await this.findById(id); - // 审核通过或拒绝后修改,重置为pending - if (merchant.status === 'approved' || merchant.status === 'rejected') { - await this.merchantRepo.update(id, { ...dto, status: 'pending', rejectReason: null }); - } else { - await this.merchantRepo.update(id, dto); - } - return this.findById(id); -} -``` +| 接口 | 路径 | 说明 | +| -------------- | --------------------------------- | ---------------------------- | +| 订单列表 | GET /api/seller/orders | 支持状态筛选、订单号搜索 | +| 订单详情 | GET /api/seller/orders/:id | 获取订单详情 | +| 确认订单 | PUT /api/seller/orders/:id/confirm| pending_confirm → pending_checkin | +| 拒绝订单 | PUT /api/seller/orders/:id/reject | pending_confirm → cancelled | +| 办理入住 | PUT /api/seller/orders/:id/checkin| pending_checkin → checked_in | +| 通过退款 | PUT /api/seller/orders/:id/approve-refund | refunding → refunded,恢复房量 | +| 拒绝退款 | PUT /api/seller/orders/:id/reject-refund | refunding → cancelled | -**修改流程**: +### 商家订单状态Tab -``` -点击"修改店铺信息" → 填写表单 → 提交修改 - → 调用 PUT /merchant/update - → 后端自动将 status 重置为 pending,清空 rejectReason - → 跳转商家中心显示"审核中"状态 -``` +| Tab名称 | 对应状态 | +| --------- | --------------------------------------------- | +| 全部 | 所有状态 | +| 待确认 | pending_confirm | +| 待入住 | pending_checkin | +| 已入住 | checked_in | +| 已完成 | completed | +| 已取消 | cancelled | +| 已退款 | refunded, refunding | -### 后续迭代事项 +### 商家订单操作权限 -1. **商家管理后台集成**: 商家后台跳转入口 -2. **商家实名认证**: 身份证上传、银行卡绑定 -3. **图片上传接口**: 营业执照真实上传功能 \ No newline at end of file +| 状态 | 可操作 | +| ---------------- | -------------------------- | +| pending_confirm | 确认、拒绝 | +| pending_checkin | 办理入住 | +| refunding | 通过退款、拒绝退款 | +| 其他状态 | 无操作 | \ No newline at end of file