+ {/* ===== 顶部标题 + 时间范围 ===== */}
+
+
+ {/* ===== 统计卡 ===== */}
+
+ {statCards.map((item) => (
+
+
{item.label}
+
+ ¥{formatMoney(item.value)}
+
+
+ {item.desc}
+
+
+ ))}
+
+
+ {/* ===== 柱状图卡(echarts) ===== */}
+
+
+ 📊
+ 月度收支趋势(近 6 个月)
+
+
{
+ if (el) chartRef.value = el as HTMLElement;
+ }}
+ class={styles.chartEcharts}
+ />
+
+
+ {/* ===== 月度收支明细 ===== */}
+
+
+ );
+ };
},
});
diff --git a/src/pages/finance/reports/model/useReportsModel.ts b/src/pages/finance/reports/model/useReportsModel.ts
new file mode 100644
index 0000000..a3a232a
--- /dev/null
+++ b/src/pages/finance/reports/model/useReportsModel.ts
@@ -0,0 +1,163 @@
+import { ref } from 'vue';
+import { useState } from '@/hooks';
+
+// ============================================================
+// 常量
+// ============================================================
+
+/** 时间范围选项 */
+export const RANGE_OPTIONS = [
+ { value: 'month', label: '本月' },
+ { value: '3m', label: '近 3 个月' },
+ { value: '6m', label: '近 6 个月' },
+ { value: 'year', label: '本年' },
+] as const;
+
+// ============================================================
+// 假数据(数值自洽:总收入 - 总提现 = 平台余额)
+// ============================================================
+
+/** 6 个月柱状图数据(月份标签 + 订单金额 + 净收入) */
+const CHART_DATA = [
+ { month: '01月', order: 22500, netIncome: 19000, withdraw: 300 },
+ { month: '02月', order: 24500, netIncome: 21500, withdraw: 1850 },
+ { month: '03月', order: 24800, netIncome: 23200, withdraw: 1400 },
+ { month: '04月', order: 26100, netIncome: 24900, withdraw: 1500 },
+ { month: '05月', order: 28900, netIncome: 27400, withdraw: 1800 },
+ { month: '06月', order: 32500, netIncome: 31860, withdraw: 2100 },
+];
+
+/** 月度收支明细(最近 4 个月) */
+const DETAIL_DATA = [
+ {
+ key: '2026-06',
+ month: '2026-06',
+ order: 32500,
+ refund: 640,
+ netIncome: 31860,
+ withdraw: 2100,
+ },
+ {
+ key: '2026-05',
+ month: '2026-05',
+ order: 28900,
+ refund: 1500,
+ netIncome: 27400,
+ withdraw: 1800,
+ },
+ {
+ key: '2026-04',
+ month: '2026-04',
+ order: 26100,
+ refund: 1200,
+ netIncome: 24900,
+ withdraw: 1500,
+ },
+ {
+ key: '2026-03',
+ month: '2026-03',
+ order: 24800,
+ refund: 1600,
+ netIncome: 23200,
+ withdraw: 1400,
+ },
+];
+
+/** 顶部 3 张统计卡数据 */
+const SUMMARY = {
+ totalIncome: 147860, // 总收入(净收入合计)
+ totalWithdraw: 8950, // 总提现
+ totalBalance: 138910, // 平台余额 = 总收入 - 总提现
+};
+
+// ============================================================
+// 工具
+// ============================================================
+
+/** 千分位 + 保留 2 位小数 */
+function formatMoney(n: number): string {
+ return (n || 0).toLocaleString('zh-CN', {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ });
+}
+
+// ============================================================
+// Model
+// ============================================================
+
+/**
+ * 账务报表数据模型
+ */
+export function useReportsModel() {
+ // ===== 时间范围 =====
+ const [range, setRange] = useState
('month');
+
+ /** 切换时间范围(演示用:打日志,不改数据) */
+ const handleRangeChange = (val: string) => {
+ setRange(val);
+ // TODO: 接入 API 时按 range 重新拉取汇总 / 图表 / 明细
+ };
+
+ // ===== 汇总 =====
+ const summary = ref({ ...SUMMARY });
+
+ // ===== 图表 =====
+ const chartData = ref([...CHART_DATA]);
+
+ // ===== 明细表 =====
+ const detailColumns = [
+ {
+ title: '月份',
+ dataIndex: 'month',
+ key: 'month',
+ width: 140,
+ align: 'left' as const,
+ },
+ {
+ title: '订单金额',
+ dataIndex: 'order',
+ key: 'order',
+ align: 'right' as const,
+ customRender: ({ text }: { text: number }) => `¥${formatMoney(text)}`,
+ },
+ {
+ title: '退款金额',
+ dataIndex: 'refund',
+ key: 'refund',
+ align: 'right' as const,
+ customRender: ({ text }: { text: number }) => `¥${formatMoney(text)}`,
+ customCell: (_record: any) => ({ style: { color: '#ff4d4f' } }),
+ },
+ {
+ title: '净收入',
+ dataIndex: 'netIncome',
+ key: 'netIncome',
+ align: 'right' as const,
+ customRender: ({ text }: { text: number }) => `¥${formatMoney(text)}`,
+ customCell: (_record: any) => ({
+ style: { color: '#52c41a', fontWeight: 600 },
+ }),
+ },
+ {
+ title: '提现金额',
+ dataIndex: 'withdraw',
+ key: 'withdraw',
+ align: 'right' as const,
+ customRender: ({ text }: { text: number }) => `¥${formatMoney(text)}`,
+ customCell: (_record: any) => ({ style: { color: '#fa8c16' } }),
+ },
+ ];
+ const detailData = ref([...DETAIL_DATA]);
+
+ return {
+ range,
+ handleRangeChange,
+ summary,
+ chartData,
+ detailColumns,
+ detailData,
+ formatMoney,
+ RANGE_OPTIONS: RANGE_OPTIONS as any,
+ };
+}
diff --git a/src/pages/finance/wallet/components/WalletDetailModal.module.less b/src/pages/finance/wallet/components/WalletDetailModal.module.less
new file mode 100644
index 0000000..2abdd8f
--- /dev/null
+++ b/src/pages/finance/wallet/components/WalletDetailModal.module.less
@@ -0,0 +1,62 @@
+// ===== 自定义标题栏 =====
+.modalHeader {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0 0 16px;
+ margin-bottom: 16px;
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.modalTitle {
+ font-size: 16px;
+ font-weight: 600;
+ color: rgba(0, 0, 0, 0.85);
+}
+
+.closeBtn {
+ width: 28px;
+ height: 28px;
+ border: none;
+ background: transparent;
+ font-size: 22px;
+ line-height: 1;
+ color: rgba(0, 0, 0, 0.45);
+ cursor: pointer;
+ border-radius: 4px;
+ transition: all 0.2s;
+
+ &:hover {
+ background: rgba(0, 0, 0, 0.04);
+ color: rgba(0, 0, 0, 0.85);
+ }
+}
+
+// ===== 内容区 =====
+.modalBody {
+ padding-top: 0;
+}
+
+// ===== 筛选区 =====
+.filterBar {
+ flex-shrink: 0;
+ padding: 16px 20px;
+ margin-bottom: 16px;
+ background: #fafafa;
+ border: 1px solid #f0f0f0;
+ border-radius: 6px;
+}
+
+// ===== 表格区 =====
+.tableWrap {
+ flex: 1;
+ min-height: 0;
+}
+
+// ===== Modal 外层包裹:去除 antd 默认内边距 =====
+/* .walletModalWrap {
+ :global(.ant-modal-body) {
+ padding-top: 16px;
+ padding-bottom: 20px;
+ }
+} */
diff --git a/src/pages/finance/wallet/components/WalletDetailModal.tsx b/src/pages/finance/wallet/components/WalletDetailModal.tsx
new file mode 100644
index 0000000..de45682
--- /dev/null
+++ b/src/pages/finance/wallet/components/WalletDetailModal.tsx
@@ -0,0 +1,276 @@
+import { defineComponent, ref, reactive, computed, watch } from 'vue';
+import { Modal, Table, Button, Select, Form, Space, DatePicker } from 'ant-design-vue';
+import {
+ useWalletModel,
+ TRANSACTION_TYPE_OPTIONS,
+ TRANSACTION_STATUS_OPTIONS,
+} from '../model/useWalletModel';
+import styles from './WalletDetailModal.module.less';
+
+const { RangePicker } = DatePicker;
+
+interface WalletDetailModalProps {
+ visible: boolean;
+ record: any;
+ onClose: () => void;
+}
+
+/**
+ * 钱包交易明细弹窗
+ */
+export default defineComponent({
+ name: 'WalletDetailModal',
+ props: {
+ visible: { type: Boolean, default: false },
+ record: { type: Object, default: () => ({}) },
+ onClose: { type: Function, required: true },
+ },
+ setup(props: WalletDetailModalProps) {
+ const { loadTransactions, renderTransactionStatus } = useWalletModel();
+
+ // ===== 弹窗内筛选条件 =====
+ const detailFilter = reactive({
+ timeRange: null as [string, string] | null,
+ type: '',
+ status: '',
+ });
+
+ // ===== 加载明细 =====
+ const loading = ref(false);
+ const allTransactions = ref([]);
+
+ const fetchTransactions = async () => {
+ loading.value = true;
+ try {
+ allTransactions.value = await loadTransactions(props.record);
+ } finally {
+ loading.value = false;
+ }
+ };
+
+ /**
+ * 弹窗内筛选后的数据(用 computed 自动响应全量数据和筛选条件的变化)
+ * 真实场景应传给后端做查询;此处做前端过滤演示
+ */
+ const filteredTransactions = computed(() => {
+ return allTransactions.value.filter((item) => {
+ if (detailFilter.type && item.type !== detailFilter.type) return false;
+ if (detailFilter.status && item.status !== detailFilter.status) return false;
+ if (detailFilter.timeRange && detailFilter.timeRange.length === 2) {
+ const [start, end] = detailFilter.timeRange;
+ if (item.time < start || item.time > end) return false;
+ }
+ return true;
+ });
+ });
+
+ const handleSearch = () => {
+ // computed 会自动响应;这里保留方法以便将来扩展(如后端查询)
+ };
+
+ const handleReset = () => {
+ detailFilter.timeRange = null;
+ detailFilter.type = '';
+ detailFilter.status = '';
+ };
+
+ // ===== 分页 =====
+ const pagination = reactive({
+ current: 1,
+ pageSize: 10,
+ total: 0,
+ showSizeChanger: true,
+ showTotal: (total: number) => `共 ${total} 条`,
+ });
+
+ /** 筛选条件变化时重置到第 1 页,并同步总数 */
+ watch(filteredTransactions, (list) => {
+ pagination.total = list.length;
+ pagination.current = 1;
+ });
+
+ const handleTableChange = (pag: any) => {
+ pagination.current = pag.current;
+ pagination.pageSize = pag.pageSize;
+ };
+
+ /** 监听 visible 变化:打开时加载明细 */
+ watch(
+ () => props.visible,
+ (val) => {
+ if (val) {
+ detailFilter.timeRange = null;
+ detailFilter.type = '';
+ detailFilter.status = '';
+ allTransactions.value = [];
+ fetchTransactions();
+ }
+ },
+ );
+
+ // ===== 表格列配置 =====
+ const detailColumns = [
+ {
+ title: '类型',
+ dataIndex: 'type',
+ key: 'type',
+ width: 120,
+ },
+ {
+ title: '金额',
+ dataIndex: 'amount',
+ key: 'amount',
+ width: 110,
+ align: 'center' as const,
+ customRender: ({ text }: { text: number }) => {
+ const num = text || 0;
+ const isPlus = num > 0;
+ const isMinus = num < 0;
+ const color = isPlus ? '#52c41a' : isMinus ? '#ff4d4f' : 'rgba(0,0,0,0.85)';
+ const sign = isPlus ? '+' : '';
+ return (
+
+ {sign}
+ {num.toFixed(2)}
+
+ );
+ },
+ },
+ {
+ title: '余额',
+ dataIndex: 'balance',
+ key: 'balance',
+ width: 110,
+ align: 'center' as const,
+ customRender: ({ text }: { text: number }) => `¥${(text || 0).toFixed(2)}`,
+ },
+ {
+ title: '可提现金额',
+ dataIndex: 'withdrawable',
+ key: 'withdrawable',
+ width: 130,
+ align: 'center' as const,
+ customRender: ({ text }: { text: number }) => `¥${(text || 0).toFixed(2)}`,
+ },
+ {
+ title: '冻结金额',
+ dataIndex: 'frozen',
+ key: 'frozen',
+ width: 110,
+ align: 'center' as const,
+ customRender: ({ text }: { text: number }) => `¥${(text || 0).toFixed(2)}`,
+ },
+ {
+ title: '状态',
+ dataIndex: 'status',
+ key: 'status',
+ width: 90,
+ align: 'center' as const,
+ customRender: ({ text }: { text: string }) => renderTransactionStatus(text),
+ },
+ {
+ title: '冻结时间',
+ dataIndex: 'frozenTime',
+ key: 'frozenTime',
+ width: 130,
+ align: 'center' as const,
+ },
+ {
+ title: '解冻时间',
+ dataIndex: 'unfreezeTime',
+ key: 'unfreezeTime',
+ width: 130,
+ align: 'center' as const,
+ },
+ {
+ title: '关联单号',
+ dataIndex: 'relatedNo',
+ key: 'relatedNo',
+ width: 130,
+ align: 'center' as const,
+ },
+ {
+ title: '时间',
+ dataIndex: 'time',
+ key: 'time',
+ width: 130,
+ align: 'center' as const,
+ },
+ ];
+
+ return () => (
+
+
+ {/* ===== 弹窗内筛选区 ===== */}
+
+
+ (detailFilter.timeRange = val)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* ===== 表格区 ===== */}
+
+
+
+ );
+ },
+});
diff --git a/src/pages/finance/wallet/index.module.less b/src/pages/finance/wallet/index.module.less
new file mode 100644
index 0000000..3dae585
--- /dev/null
+++ b/src/pages/finance/wallet/index.module.less
@@ -0,0 +1,107 @@
+// ===== 表格 body 容器:占满 flex 剩余空间,并确保 antd 嵌套 div 逐层传递 height: 100% =====
+.tableBody {
+ flex: 1;
+ min-height: 0;
+ overflow: hidden;
+
+ :global {
+ .ant-spin-nested-loading,
+ .ant-spin-container,
+ .ant-table,
+ .ant-table-container {
+ height: 100%;
+ }
+ }
+}
+
+// ===== 顶部 3 张统计卡 =====
+.statCards {
+ display: flex;
+ gap: 16px;
+ flex-shrink: 0;
+ margin-bottom: 16px;
+}
+
+.statCard {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding: 22px 24px;
+ background: #fff;
+ border: 1px solid #f0f0f0;
+ border-radius: 10px;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
+ transition: all 0.25s ease;
+ cursor: default;
+
+ &:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
+ }
+}
+
+.statCardLeft {
+ flex-shrink: 0;
+}
+
+.statIconWrap {
+ width: 52px;
+ height: 52px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 12px;
+ font-size: 26px;
+ transition: transform 0.25s ease;
+
+ .statCard:hover & {
+ transform: scale(1.05);
+ }
+}
+
+.statCardRight {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+
+.statLabel {
+ font-size: 13px;
+ color: rgba(0, 0, 0, 0.55);
+ letter-spacing: 0.3px;
+}
+
+.statValue {
+ font-size: 26px;
+ font-weight: 600;
+ line-height: 1.2;
+ font-family:
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+ letter-spacing: 0.3px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+// ===== 下方白卡片:包裹筛选区 + 表格区 =====
+.tableSection {
+ flex: 1;
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+ padding: 16px 20px;
+ background: #fff;
+ border: 1px solid #f0f0f0;
+ border-radius: 10px;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02);
+ overflow: hidden;
+
+ // 表格区紧贴筛选区下方,控制间距
+ :global(.page-table) {
+ margin-top: 16px;
+ }
+}
diff --git a/src/pages/finance/wallet/index.tsx b/src/pages/finance/wallet/index.tsx
index 7f2a94e..a2437fa 100644
--- a/src/pages/finance/wallet/index.tsx
+++ b/src/pages/finance/wallet/index.tsx
@@ -1,4 +1,43 @@
import { defineComponent } from 'vue';
+import { Button, Input, Table, Form, Space, Pagination } from 'ant-design-vue';
+import { WalletOutlined, LockOutlined, RiseOutlined } from '@ant-design/icons-vue';
+import { useWalletModel } from './model/useWalletModel';
+import { useContainerSize } from '@/hooks';
+import WalletDetailModal from './components/WalletDetailModal';
+import styles from './index.module.less';
+
+interface StatCardItem {
+ key: string;
+ label: string;
+ value: number;
+ icon: any;
+ color: string;
+ bgColor: string;
+}
+
+/**
+ * bodyCell 渲染函数
+ */
+function renderBodyCell({
+ column,
+ record,
+ onViewDetail,
+}: {
+ column: any;
+ text: any;
+ record: any;
+ onViewDetail: (record: any) => void;
+}) {
+ // 操作列
+ if (column.key === 'action') {
+ return (
+
+ );
+ }
+ return;
+}
/**
* 用户钱包
@@ -6,10 +45,198 @@ import { defineComponent } from 'vue';
export default defineComponent({
name: 'FinanceWallet',
setup() {
- return () => (
-
- );
+ const {
+ filterForm,
+ loading,
+ dataSource,
+ columns,
+ summary,
+ pagination,
+ detailVisible,
+ currentWallet,
+ handleSearch,
+ handleReset,
+ handlePageChange,
+ handleViewDetail,
+ handleCloseDetail,
+ } = useWalletModel();
+
+ const { containerRef, height } = useContainerSize({ headerOffset: 55 });
+
+ /** 最终表格列:模型列 + 操作 */
+ const tableColumns = [
+ ...columns,
+ {
+ title: '操作',
+ key: 'action',
+ width: 100,
+ fixed: 'right' as const,
+ align: 'center' as const,
+ },
+ ];
+
+ return () => {
+ /** 顶部统计卡配置 */
+ const statCards: StatCardItem[] = [
+ {
+ key: 'totalBalance',
+ label: '总余额',
+ value: summary.value.totalBalance,
+ icon: WalletOutlined,
+ color: '#1677ff',
+ bgColor: 'rgba(22, 119, 255, 0.08)',
+ },
+ {
+ key: 'frozenAmount',
+ label: '冻结金额',
+ value: summary.value.frozenAmount,
+ icon: LockOutlined,
+ color: '#fa8c16',
+ bgColor: 'rgba(250, 140, 22, 0.08)',
+ },
+ {
+ key: 'totalWithdraw',
+ label: '累计提现',
+ value: summary.value.totalWithdraw,
+ icon: RiseOutlined,
+ color: '#52c41a',
+ bgColor: 'rgba(82, 196, 26, 0.08)',
+ },
+ ];
+
+ return (
+
+ {/* ===== 顶部统计卡 ===== */}
+
+ {statCards.map((item) => (
+
+
+
+
{item.label}
+
+ ¥
+ {item.value.toLocaleString('zh-CN', {
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ })}
+
+
+
+ ))}
+
+
+ {/* ===== 筛选区 ===== */}
+
+
+
+
+
+
+
+
+
+ (filterForm.minBalance = val ?? '')}
+ onPressEnter={handleSearch}
+ allowClear
+ />
+
+ (filterForm.maxBalance = val ?? '')}
+ onPressEnter={handleSearch}
+ allowClear
+ />
+
+
+
+
+
+
+
+
+
+
+
+ {/* ===== 下方区域:筛选 + 表格(独立白卡片) ===== */}
+
+ {/* ===== 表格区 ===== */}
+
+
+
+ {{
+ bodyCell: (args: any) =>
+ renderBodyCell({
+ ...args,
+ onViewDetail: handleViewDetail,
+ }),
+ }}
+
+
+
+ {/* 独立分页,右下方 */}
+
+
+
+ {/* ===== 交易明细弹窗 ===== */}
+
+
+ );
+ };
},
});
diff --git a/src/pages/finance/wallet/model/useWalletModel.ts b/src/pages/finance/wallet/model/useWalletModel.ts
new file mode 100644
index 0000000..1da1173
--- /dev/null
+++ b/src/pages/finance/wallet/model/useWalletModel.ts
@@ -0,0 +1,349 @@
+import { computed, reactive, toRef, Ref, h } from 'vue';
+import { message, Tag } from 'ant-design-vue';
+import { useState, useDebounce, useThrottleFn } from '@/hooks';
+
+// ============================================================
+// 常量
+// ============================================================
+
+/** 明细类型选项(弹窗内筛选) */
+export const TRANSACTION_TYPE_OPTIONS = [
+ { value: '', label: '全部' },
+ { value: '报名收入', label: '报名收入' },
+ { value: '提现', label: '提现' },
+ { value: '取消报名退款', label: '取消报名退款' },
+] as const;
+
+/** 明细状态选项(弹窗内筛选) */
+export const TRANSACTION_STATUS_OPTIONS = [
+ { value: '', label: '全部' },
+ { value: '正常', label: '正常' },
+ { value: '冻结中', label: '冻结中' },
+ { value: '已解冻', label: '已解冻' },
+] as const;
+
+/** 明细状态映射(用于表格 Tag 渲染) */
+const TRANSACTION_STATUS_MAP: Record = {
+ 正常: { label: '正常', color: 'blue' },
+ 冻结中: { label: '冻结中', color: 'orange' },
+ 已解冻: { label: '已解冻', color: 'green' },
+};
+
+// ============================================================
+// 顶部统计卡假数据
+// ============================================================
+
+const MOCK_SUMMARY = {
+ totalBalance: 86520.0,
+ frozenAmount: 3200.0,
+ totalWithdraw: 63080.0,
+};
+
+// ============================================================
+// 假数据(钱包列表)
+// ============================================================
+
+const MOCK_DATA = [
+ {
+ key: '1',
+ userId: 'U20260701001',
+ nickName: '张三',
+ phone: '12345678997',
+ balance: 0,
+ frozen: 0,
+ totalWithdraw: 0,
+ },
+ {
+ key: '2',
+ userId: '',
+ nickName: '李四',
+ phone: '12345678998',
+ balance: 350,
+ frozen: 200,
+ totalWithdraw: 1450,
+ },
+ {
+ key: '3',
+ userId: '',
+ nickName: '王五',
+ phone: '12345678999',
+ balance: 1280,
+ frozen: 0,
+ totalWithdraw: 800,
+ },
+ {
+ key: '4',
+ userId: 'U20260701004',
+ nickName: '赵六',
+ phone: '13800138000',
+ balance: 560,
+ frozen: 100,
+ totalWithdraw: 200,
+ },
+ {
+ key: '5',
+ userId: '',
+ nickName: '钱七',
+ phone: '13900139001',
+ balance: 4200,
+ frozen: 1500,
+ totalWithdraw: 5600,
+ },
+ {
+ key: '6',
+ userId: '',
+ nickName: '孙八',
+ phone: '12345678997',
+ balance: 880,
+ frozen: 0,
+ totalWithdraw: 300,
+ },
+];
+
+// ============================================================
+// 交易明细假数据(所有用户共用,演示用)
+// ============================================================
+
+const MOCK_TRANSACTIONS: any[] = [
+ {
+ key: 't-1',
+ type: '报名收入',
+ amount: 100,
+ balance: 600,
+ withdrawable: 500,
+ frozen: 100,
+ status: '已解冻',
+ frozenTime: '2026-06-10 08:00',
+ unfreezeTime: '2026-06-12 08:00',
+ relatedNo: 'xxxxxxxx',
+ time: '2026-06-10 08:00',
+ },
+ {
+ key: 't-2',
+ type: '提现',
+ amount: -500,
+ balance: 1100,
+ withdrawable: 500,
+ frozen: 600,
+ status: '冻结中',
+ frozenTime: '2026-06-10 06:00',
+ unfreezeTime: '-',
+ relatedNo: 'xxxxxxxx',
+ time: '2026-06-10 06:00',
+ },
+ {
+ key: 't-3',
+ type: '取消报名退款',
+ amount: -100,
+ balance: 500,
+ withdrawable: 500,
+ frozen: 0,
+ status: '正常',
+ frozenTime: '-',
+ unfreezeTime: '-',
+ relatedNo: 'xxxxxxxx',
+ time: '2026-06-10 05:00',
+ },
+ {
+ key: 't-4',
+ type: '报名收入',
+ amount: 350,
+ balance: 850,
+ withdrawable: 750,
+ frozen: 100,
+ status: '正常',
+ frozenTime: '-',
+ unfreezeTime: '-',
+ relatedNo: 'xxxxxxxx',
+ time: '2026-06-09 17:30',
+ },
+ {
+ key: 't-5',
+ type: '提现',
+ amount: -250,
+ balance: 600,
+ withdrawable: 500,
+ frozen: 100,
+ status: '已解冻',
+ frozenTime: '2026-06-08 10:00',
+ unfreezeTime: '2026-06-09 08:00',
+ relatedNo: 'xxxxxxxx',
+ time: '2026-06-08 10:00',
+ },
+];
+
+// ============================================================
+// Model
+// ============================================================
+
+/**
+ * 用户钱包页数据模型
+ */
+export function useWalletModel() {
+ // ===== 筛选条件 =====
+ const filterForm = reactive({
+ searchUserName: '',
+ searchPhone: '',
+ minBalance: '' as string | number,
+ maxBalance: '' as string | number,
+ });
+
+ // 可搜索字段防抖
+ const { debouncedValue: debouncedUserName } = useDebounce(
+ toRef(filterForm, 'searchUserName') as Ref,
+ { delay: 300 },
+ );
+ const { debouncedValue: debouncedPhone } = useDebounce(
+ toRef(filterForm, 'searchPhone') as Ref,
+ { delay: 300 },
+ );
+
+ // ===== 顶部统计 =====
+ const [summary, setSummary] = useState(MOCK_SUMMARY);
+
+ // ===== 表格状态 =====
+ const [loading, setLoading] = useState(false);
+ const [dataSource, setDataSource] = useState(MOCK_DATA);
+ const [pagination, setPagination] = useState({
+ current: 1,
+ pageSize: 10,
+ total: MOCK_DATA.length,
+ });
+
+ // ===== 详情弹窗状态 =====
+ const [detailVisible, setDetailVisible] = useState(false);
+ const [currentWallet, setCurrentWallet] = useState({});
+ const [detailLoading, setDetailLoading] = useState(false);
+
+ // ===== 表格列配置 =====
+ const columns = [
+ { title: '用户ID', dataIndex: 'userId', key: 'userId', width: 160 },
+ { title: '用户昵称', dataIndex: 'nickName', key: 'nickName', width: 120 },
+ { title: '手机号', dataIndex: 'phone', key: 'phone', width: 140 },
+ {
+ title: '余额',
+ dataIndex: 'balance',
+ key: 'balance',
+ width: 120,
+ customRender: ({ text }: { text: number }) => `${(text || 0).toFixed(2)}`,
+ },
+ {
+ title: '冻结金额',
+ dataIndex: 'frozen',
+ key: 'frozen',
+ width: 120,
+ customRender: ({ text }: { text: number }) => `${(text || 0).toFixed(2)}`,
+ },
+ {
+ title: '累计提现',
+ dataIndex: 'totalWithdraw',
+ key: 'totalWithdraw',
+ width: 120,
+ customRender: ({ text }: { text: number }) => `${(text || 0).toFixed(2)}`,
+ },
+ ];
+
+ // ===== 计算属性 =====
+ const hasFilter = computed(() => {
+ return (
+ debouncedUserName.value.trim() !== '' ||
+ debouncedPhone.value.trim() !== '' ||
+ filterForm.minBalance !== '' ||
+ filterForm.maxBalance !== ''
+ );
+ });
+
+ // ===== 方法 =====
+
+ /** 查询(节流 500ms) */
+ const handleSearch = useThrottleFn(async () => {
+ setLoading(true);
+ try {
+ console.log('搜索条件:', {
+ nickName: debouncedUserName.value,
+ phone: debouncedPhone.value,
+ minBalance: filterForm.minBalance,
+ maxBalance: filterForm.maxBalance,
+ });
+ // TODO: 替换为真实 API 调用
+ setDataSource(MOCK_DATA);
+ setPagination({ ...pagination.value, total: MOCK_DATA.length });
+ setSummary(MOCK_SUMMARY);
+ message.success('查询成功');
+ } catch (error: any) {
+ message.error(error.msg || '查询失败');
+ } finally {
+ setLoading(false);
+ }
+ }, 500);
+
+ /** 重置(节流 500ms) */
+ const handleReset = useThrottleFn(() => {
+ filterForm.searchUserName = '';
+ filterForm.searchPhone = '';
+ filterForm.minBalance = '';
+ filterForm.maxBalance = '';
+ setPagination({ current: 1, pageSize: 10, total: MOCK_DATA.length });
+ setDataSource(MOCK_DATA);
+ setSummary(MOCK_SUMMARY);
+ }, 500);
+
+ const handlePageChange = (page: number, pageSize: number) => {
+ setPagination({ ...pagination.value, current: page, pageSize });
+ handleSearch();
+ };
+
+ /** 打开明细弹窗 */
+ const handleViewDetail = (record: any) => {
+ setCurrentWallet(record);
+ setDetailVisible(true);
+ };
+
+ /** 关闭明细弹窗 */
+ const handleCloseDetail = () => {
+ setDetailVisible(false);
+ };
+
+ /**
+ * 加载某用户的交易明细
+ * 真实场景应调用 API;演示阶段所有用户共用同一份假数据
+ */
+ const loadTransactions = async (_record: any): Promise => {
+ setDetailLoading(true);
+ try {
+ // 模拟接口延迟
+ await new Promise((resolve) => setTimeout(resolve, 200));
+ return [...MOCK_TRANSACTIONS];
+ } finally {
+ setDetailLoading(false);
+ }
+ };
+
+ /** 渲染交易状态 Tag */
+ const renderTransactionStatus = (status: string) => {
+ const info = TRANSACTION_STATUS_MAP[status] || { label: status || '-', color: 'default' };
+ return h(Tag, { color: info.color }, () => info.label);
+ };
+
+ return {
+ filterForm,
+ loading,
+ dataSource,
+ columns,
+ summary,
+ pagination,
+ hasFilter,
+ detailVisible,
+ currentWallet,
+ detailLoading,
+ handleSearch,
+ handleReset,
+ handlePageChange,
+ handleViewDetail,
+ handleCloseDetail,
+ loadTransactions,
+ renderTransactionStatus,
+ TRANSACTION_TYPE_OPTIONS: TRANSACTION_TYPE_OPTIONS as any,
+ TRANSACTION_STATUS_OPTIONS: TRANSACTION_STATUS_OPTIONS as any,
+ };
+}
diff --git a/src/pages/finance/withdraw/components/WithdrawAuditModal.module.less b/src/pages/finance/withdraw/components/WithdrawAuditModal.module.less
new file mode 100644
index 0000000..584a28e
--- /dev/null
+++ b/src/pages/finance/withdraw/components/WithdrawAuditModal.module.less
@@ -0,0 +1,195 @@
+// ===== Modal 外层包裹:去掉 antd 默认内边距 =====
+.modalWrap {
+ :global(.ant-modal-header) {
+ display: none;
+ }
+}
+
+// ===== 自定义标题栏 =====
+.modalHeader {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-bottom: 14px;
+ margin-bottom: 8px;
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.modalTitle {
+ font-size: 16px;
+ font-weight: 600;
+ color: rgba(0, 0, 0, 0.85);
+}
+
+.closeBtn {
+ width: 28px;
+ height: 28px;
+ border: none;
+ background: transparent;
+ font-size: 22px;
+ line-height: 1;
+ color: rgba(0, 0, 0, 0.45);
+ cursor: pointer;
+ border-radius: 4px;
+ transition: all 0.2s;
+
+ &:hover {
+ background: rgba(0, 0, 0, 0.04);
+ color: rgba(0, 0, 0, 0.85);
+ }
+}
+
+// ===== 通用区块 =====
+.section {
+ padding: 14px 0;
+ border-bottom: 1px dashed #f0f0f0;
+
+ &:last-child {
+ border-bottom: none;
+ padding-bottom: 0;
+ }
+}
+
+.sectionTitle {
+ font-size: 14px;
+ font-weight: 600;
+ color: rgba(0, 0, 0, 0.85);
+ margin-bottom: 14px;
+ position: relative;
+ padding-left: 10px;
+
+ &::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 3px;
+ height: 14px;
+ background: #1677ff;
+ border-radius: 2px;
+ }
+}
+
+// ===== 描述项栅格(3 列) =====
+.descGrid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ row-gap: 14px;
+ column-gap: 24px;
+}
+
+.descItem {
+ display: flex;
+ align-items: baseline;
+ min-width: 0;
+}
+
+.descLabel {
+ flex-shrink: 0;
+ color: rgba(0, 0, 0, 0.55);
+ font-size: 13px;
+ white-space: nowrap;
+}
+
+.descValue {
+ flex: 1;
+ min-width: 0;
+ color: rgba(0, 0, 0, 0.85);
+ font-size: 13px;
+ word-break: break-all;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.descValueBold {
+ font-weight: 600;
+ color: rgba(0, 0, 0, 0.85);
+}
+
+// ===== 审核表单 =====
+.formRow {
+ display: flex;
+ align-items: flex-start;
+ margin-bottom: 18px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+}
+
+.formLabel {
+ width: 88px;
+ flex-shrink: 0;
+ color: rgba(0, 0, 0, 0.55);
+ font-size: 13px;
+ padding-top: 6px;
+}
+
+.formLabelTop {
+ padding-top: 6px;
+}
+
+.footer {
+ display: flex;
+ justify-content: flex-end;
+ margin-top: 24px;
+}
+
+// ===== 上传 =====
+.uploader {
+ flex: 1;
+
+ :global {
+ .ant-upload-list-picture-card-container {
+ width: 96px;
+ height: 96px;
+ }
+ .ant-upload-select {
+ width: 96px;
+ height: 96px;
+ margin: 0;
+ }
+ }
+}
+
+.uploadTrigger {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ color: rgba(0, 0, 0, 0.45);
+ font-size: 22px;
+ background: #fafafa;
+ border-radius: 4px;
+ transition: all 0.2s;
+
+ &:hover {
+ color: #1677ff;
+ background: #f0f7ff;
+ }
+}
+
+.uploadText {
+ font-size: 12px;
+ margin-top: 4px;
+ color: rgba(0, 0, 0, 0.45);
+}
+
+// ===== 审核信息(只读,逐行展示) =====
+.infoColumn {
+ display: flex;
+ flex-direction: column;
+ row-gap: 14px;
+}
+
+.auditImg {
+ border-radius: 4px;
+ object-fit: cover;
+ margin-right: 8px;
+}
+
+.noImage {
+ color: rgba(0, 0, 0, 0.45);
+}
diff --git a/src/pages/finance/withdraw/components/WithdrawAuditModal.tsx b/src/pages/finance/withdraw/components/WithdrawAuditModal.tsx
new file mode 100644
index 0000000..812f405
--- /dev/null
+++ b/src/pages/finance/withdraw/components/WithdrawAuditModal.tsx
@@ -0,0 +1,245 @@
+import { defineComponent, reactive, ref, watch } from 'vue';
+import { Modal, Radio, Input, Button, Upload, Image, message } from 'ant-design-vue';
+import { PlusOutlined } from '@ant-design/icons-vue';
+import { useWithdrawModel } from '../model/useWithdrawModel';
+import styles from './WithdrawAuditModal.module.less';
+
+const { TextArea } = Input;
+
+interface WithdrawAuditModalProps {
+ visible: boolean;
+ record: any;
+ onClose: () => void;
+}
+
+/**
+ * 提现申请详情 / 审核弹窗
+ *
+ * 结构:
+ * - 基本信息
+ * - 银行卡信息
+ * - 支付信息
+ * - 审核操作
+ */
+export default defineComponent({
+ name: 'WithdrawAuditModal',
+ props: {
+ visible: { type: Boolean, default: false },
+ record: { type: Object, default: () => ({}) },
+ onClose: { type: Function, required: true },
+ },
+ setup(props: WithdrawAuditModalProps) {
+ const { submitAudit } = useWithdrawModel();
+
+ // ===== 表单状态 =====
+ const auditForm = reactive({
+ pass: true,
+ remark: '',
+ });
+ const imageList = ref([]);
+ const submitting = ref(false);
+
+ /** 监听 visible:每次打开都重置表单 */
+ watch(
+ () => props.visible,
+ (val) => {
+ if (val) {
+ auditForm.pass = true;
+ auditForm.remark = '';
+ imageList.value = [];
+ submitting.value = false;
+ }
+ },
+ );
+
+ /** 上传前校验(演示用:限制 5 张 & 5MB) */
+ const beforeUpload = (file: any) => {
+ const isLt5M = file.size / 1024 / 1024 < 5;
+ if (!isLt5M) {
+ message.error('图片大小不能超过 5MB');
+ return false;
+ }
+ imageList.value = [...imageList.value, file];
+ return false; // 阻止自动上传,由"提交审核"按钮统一处理
+ };
+
+ const handleRemove = (file: any) => {
+ imageList.value = imageList.value.filter((f) => f.uid !== file.uid);
+ };
+
+ const handleSubmit = async () => {
+ if (!auditForm.remark.trim()) {
+ message.warning('请输入审核内容');
+ return;
+ }
+ submitting.value = true;
+ try {
+ // 真实场景应上传图片并调用审核 API
+ const urls = imageList.value.map((f) => f.name || '');
+ submitAudit(auditForm.pass, auditForm.remark.trim(), urls);
+ } finally {
+ submitting.value = false;
+ }
+ };
+
+ /**
+ * 通用描述项渲染
+ */
+ const renderItem = (label: string, value: any, isBold = false) => (
+
+ {label}
+ {value || '-'}
+
+ );
+
+ return () => {
+ const record = props.record || {};
+ const bank = record.bank || {};
+ const pay = record.pay || {};
+ const isPending = record.auditStatus === '待审核';
+
+ return (
+
+ {/* 自定义标题栏 */}
+
+
+ {/* ===== 基本信息 ===== */}
+
+
基本信息
+
+ {renderItem('申请人昵称:', record.nickName)}
+ {renderItem('申请人手机号:', record.phone)}
+ {renderItem('真实姓名:', record.realName)}
+ {renderItem('申请时间:', record.applyTime)}
+ {renderItem('打款类型:', record.withdrawType)}
+
+ {renderItem('提现金额:', `¥${(record.withdrawAmount || 0).toFixed(2)}`, true)}
+ {renderItem('费率:', `${((record.feeRate || 0) * 100).toFixed(2)}%`)}
+ {renderItem('手续费:', `¥${(record.feeAmount || 0).toFixed(2)}`)}
+ {renderItem('到账金额:', `¥${(record.transferAmount || 0).toFixed(2)}`, true)}
+ {renderItem('审核状态:', record.auditStatus, true)}
+ {renderItem('列账状态:', record.transferStatus || '-')}
+
+
+
+ {/* ===== 银行卡信息 ===== */}
+
+
银行卡信息
+
+ {renderItem('银行卡类型:', bank.cardType)}
+ {renderItem('持卡人:', bank.holder)}
+ {renderItem('银行卡号:', bank.cardNo)}
+ {renderItem('开户行:', bank.bankName)}
+ {renderItem('开户支行:', bank.branchName)}
+
+
+
+ {/* ===== 支付信息 ===== */}
+
+
支付信息
+
+ {renderItem('到账支付时间:', pay.transferTime || '-')}
+ {renderItem('商户订单号:', pay.merchantNo)}
+
+
+
+ {/* ===== 审核操作 / 审核信息 ===== */}
+
+ {isPending ? (
+ <>
+
审核操作
+
+ 审核状态
+ (auditForm.pass = val)}
+ >
+ 通过
+ 未通过
+
+
+
+
+ 审核内容
+
+
+
+
+
+
+ >
+ ) : (
+ <>
+
审核信息
+
+ {renderItem('审核时间:', record.auditTime)}
+ {renderItem('审核人:', record.auditor)}
+
+ 审核图片:
+ {record.auditImages && record.auditImages.length ? (
+
+ {record.auditImages.map((src: string, idx: number) => (
+
+ ))}
+
+ ) : (
+ 暂无
+ )}
+
+
+ >
+ )}
+
+
+ );
+ };
+ },
+});
diff --git a/src/pages/finance/withdraw/index.module.less b/src/pages/finance/withdraw/index.module.less
new file mode 100644
index 0000000..295db04
--- /dev/null
+++ b/src/pages/finance/withdraw/index.module.less
@@ -0,0 +1,15 @@
+// ===== 表格 body 容器:占满 flex 剩余空间,并确保 antd 嵌套 div 逐层传递 height: 100% =====
+.tableBody {
+ flex: 1;
+ min-height: 0;
+ overflow: hidden;
+
+ :global {
+ .ant-spin-nested-loading,
+ .ant-spin-container,
+ .ant-table,
+ .ant-table-container {
+ height: 100%;
+ }
+ }
+}
diff --git a/src/pages/finance/withdraw/index.tsx b/src/pages/finance/withdraw/index.tsx
index 3fa6d1b..54f0be3 100644
--- a/src/pages/finance/withdraw/index.tsx
+++ b/src/pages/finance/withdraw/index.tsx
@@ -1,4 +1,43 @@
import { defineComponent } from 'vue';
+import { Button, Input, Table, Form, Space, Pagination, Select } from 'ant-design-vue';
+import { useWithdrawModel } from './model/useWithdrawModel';
+import { useContainerSize } from '@/hooks';
+import WithdrawAuditModal from './components/WithdrawAuditModal';
+import styles from './index.module.less';
+
+/**
+ * bodyCell 渲染函数(操作列:根据审核状态切换"审核 / 查看"按钮)
+ */
+function renderBodyCell({
+ column,
+ record,
+ onAudit,
+ onView,
+}: {
+ column: any;
+ text: any;
+ record: any;
+ onAudit: (record: any) => void;
+ onView: (record: any) => void;
+}) {
+ if (column.key === 'action') {
+ // 待审核:显示「审核」
+ if (record.auditStatus === '待审核') {
+ return (
+
+ );
+ }
+ // 其它状态:仅显示「查看」
+ return (
+
+ );
+ }
+ return;
+}
/**
* 提现申请
@@ -6,9 +45,137 @@ import { defineComponent } from 'vue';
export default defineComponent({
name: 'FinanceWithdraw',
setup() {
+ const {
+ filterForm,
+ loading,
+ dataSource,
+ columns,
+ pagination,
+ auditVisible,
+ currentRecord,
+ handleSearch,
+ handleReset,
+ handlePageChange,
+ handleAudit,
+ handleCloseAudit,
+ handleView,
+ AUDIT_STATUS_OPTIONS,
+ TRANSFER_STATUS_OPTIONS,
+ } = useWithdrawModel();
+
+ const { containerRef, height } = useContainerSize({ headerOffset: 55 });
+
+ /** 最终表格列:模型列 + 操作 */
+ const tableColumns = [
+ ...columns,
+ {
+ title: '操作',
+ key: 'action',
+ width: 100,
+ fixed: 'right' as const,
+ align: 'center' as const,
+ },
+ ];
+
return () => (
-
提现申请 - 开发中
+ {/* ===== 筛选区 ===== */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* ===== 表格区 ===== */}
+
+
+
+ {{
+ bodyCell: (args: any) =>
+ renderBodyCell({
+ ...args,
+ onAudit: handleAudit,
+ onView: handleView,
+ }),
+ }}
+
+
+
+ {/* 独立分页,右下方 */}
+
+
+
+ {/* ===== 审核弹窗 ===== */}
+
);
},
diff --git a/src/pages/finance/withdraw/model/useWithdrawModel.ts b/src/pages/finance/withdraw/model/useWithdrawModel.ts
new file mode 100644
index 0000000..84a00e5
--- /dev/null
+++ b/src/pages/finance/withdraw/model/useWithdrawModel.ts
@@ -0,0 +1,342 @@
+import { reactive, toRef, Ref } from 'vue';
+import { message } from 'ant-design-vue';
+import { useState, useDebounce, useThrottleFn } from '@/hooks';
+
+// ============================================================
+// 常量
+// ============================================================
+
+/** 审核状态选项(筛选) */
+export const AUDIT_STATUS_OPTIONS = [
+ { value: '', label: '全部' },
+ { value: '待审核', label: '待审核' },
+ { value: '审核通过', label: '审核通过' },
+ { value: '审核不通过', label: '审核不通过' },
+] as const;
+
+/** 到账状态选项(筛选) */
+export const TRANSFER_STATUS_OPTIONS = [
+ { value: '', label: '全部' },
+ { value: '到账成功', label: '到账成功' },
+ { value: '到账失败', label: '到账失败' },
+ { value: '-', label: '-' },
+] as const;
+
+/** 提现类型选项(筛选) */
+export const WITHDRAW_TYPE_OPTIONS = [
+ { value: '', label: '全部' },
+ { value: '银行卡', label: '银行卡' },
+ { value: '支付宝', label: '支付宝' },
+ { value: '微信', label: '微信' },
+] as const;
+
+// ============================================================
+// 提现假数据(4 条,覆盖各审核/到账状态组合)
+// ============================================================
+
+const MOCK_DATA = [
+ {
+ key: '1',
+ nickName: '可乐',
+ phone: '17762466262',
+ realName: '可乐',
+ withdrawType: '银行卡',
+ withdrawAmount: 1.0,
+ feeRate: 0.006,
+ feeAmount: 0.01,
+ transferAmount: 0.99,
+ auditStatus: '待审核',
+ transferStatus: '',
+ thirdNo: '',
+ applyTime: '2026-04-11 09:53',
+ // 银行卡信息
+ bank: {
+ cardType: '个人',
+ holder: '龚',
+ cardNo: '6215581807006475126',
+ bankName: '交通银行',
+ branchName: '交通银行宜昌西坝支行',
+ },
+ // 支付信息
+ pay: {
+ transferTime: '',
+ merchantNo: 'WD04111775872421935100',
+ },
+ },
+ {
+ key: '2',
+ nickName: '李四',
+ phone: '12345678998',
+ realName: '李四',
+ withdrawType: '支付宝',
+ withdrawAmount: 200.0,
+ feeRate: 0.006,
+ feeAmount: 1.2,
+ transferAmount: 198.8,
+ auditStatus: '审核通过',
+ transferStatus: '到账成功',
+ thirdNo: 'yyyyyyyyyy',
+ applyTime: '2026-05-25 14:30:00',
+ bank: {
+ cardType: '个人',
+ holder: '李四',
+ cardNo: '6222021234567890123',
+ bankName: '工商银行',
+ branchName: '工商银行北京中关村支行',
+ },
+ pay: {
+ transferTime: '2026-05-25 14:35:00',
+ merchantNo: 'WD0000111122223333',
+ },
+ // 审核信息(已审核后展示)
+ auditTime: '2026-05-25 14:32:00',
+ auditor: '管理员A',
+ auditImages: [],
+ },
+ {
+ key: '3',
+ nickName: '王五',
+ phone: '13800138000',
+ realName: '王五',
+ withdrawType: '微信',
+ withdrawAmount: 500.0,
+ feeRate: 0.006,
+ feeAmount: 3.0,
+ transferAmount: 497.0,
+ auditStatus: '审核通过',
+ transferStatus: '到账失败',
+ thirdNo: 'zzzzzzzzzz',
+ applyTime: '2026-05-24 11:15:00',
+ bank: {
+ cardType: '个人',
+ holder: '王五',
+ cardNo: '微信钱包',
+ bankName: '微信支付',
+ branchName: '-',
+ },
+ pay: {
+ transferTime: '',
+ merchantNo: 'WD9999888877776666',
+ },
+ // 审核信息(已审核后展示)
+ auditTime: '2026-05-24 11:20:00',
+ auditor: '管理员B',
+ auditImages: [],
+ },
+ {
+ key: '4',
+ nickName: '赵六',
+ phone: '13900139001',
+ realName: '赵六',
+ withdrawType: '银行卡',
+ withdrawAmount: 80.0,
+ feeRate: 0.006,
+ feeAmount: 0.48,
+ transferAmount: 79.52,
+ auditStatus: '审核不通过',
+ transferStatus: '',
+ thirdNo: '',
+ applyTime: '2026-05-23 09:45:00',
+ bank: {
+ cardType: '个人',
+ holder: '赵六',
+ cardNo: '6217858000123456789',
+ bankName: '建设银行',
+ branchName: '建设银行上海陆家嘴支行',
+ },
+ pay: {
+ transferTime: '',
+ merchantNo: '',
+ },
+ // 审核信息(已审核后展示)
+ auditTime: '2026-05-23 10:00:00',
+ auditor: '管理员A',
+ auditImages: [],
+ },
+];
+
+// ============================================================
+// Model
+// ============================================================
+
+/**
+ * 提现申请页数据模型
+ */
+export function useWithdrawModel() {
+ // ===== 筛选条件 =====
+ const filterForm = reactive({
+ searchUserName: '',
+ searchPhone: '',
+ realName: '',
+ auditStatus: '',
+ transferStatus: '',
+ });
+
+ // 可搜索字段防抖
+ const { debouncedValue: debouncedUserName } = useDebounce(
+ toRef(filterForm, 'searchUserName') as Ref,
+ { delay: 300 },
+ );
+ const { debouncedValue: debouncedPhone } = useDebounce(
+ toRef(filterForm, 'searchPhone') as Ref,
+ { delay: 300 },
+ );
+ const { debouncedValue: debouncedRealName } = useDebounce(
+ toRef(filterForm, 'realName') as Ref,
+ { delay: 300 },
+ );
+
+ // ===== 表格状态 =====
+ const [loading, setLoading] = useState(false);
+ const [dataSource, setDataSource] = useState(MOCK_DATA);
+ const [pagination, setPagination] = useState({
+ current: 1,
+ pageSize: 10,
+ total: MOCK_DATA.length,
+ });
+
+ // ===== 审核弹窗状态 =====
+ const [auditVisible, setAuditVisible] = useState(false);
+ const [currentRecord, setCurrentRecord] = useState({});
+
+ // ===== 表格列配置 =====
+ const columns = [
+ { title: '申请人昵称', dataIndex: 'nickName', key: 'nickName', width: 120 },
+ { title: '申请人手机号', dataIndex: 'phone', key: 'phone', width: 140 },
+ { title: '真实姓名', dataIndex: 'realName', key: 'realName', width: 120 },
+ {
+ title: '提现类型',
+ dataIndex: 'withdrawType',
+ key: 'withdrawType',
+ width: 110,
+ },
+ {
+ title: '提现金额',
+ dataIndex: 'withdrawAmount',
+ key: 'withdrawAmount',
+ width: 120,
+ customRender: ({ text }: { text: number }) => `${(text || 0).toFixed(2)}`,
+ },
+ {
+ title: '到账金额',
+ dataIndex: 'transferAmount',
+ key: 'transferAmount',
+ width: 120,
+ customRender: ({ text }: { text: number }) => `${(text || 0).toFixed(2)}`,
+ },
+ {
+ title: '审核状态',
+ dataIndex: 'auditStatus',
+ key: 'auditStatus',
+ width: 110,
+ },
+ {
+ title: '到账状态',
+ dataIndex: 'transferStatus',
+ key: 'transferStatus',
+ width: 110,
+ },
+ { title: '第三方单号', dataIndex: 'thirdNo', key: 'thirdNo', width: 160 },
+ {
+ title: '申请时间',
+ dataIndex: 'applyTime',
+ key: 'applyTime',
+ width: 170,
+ },
+ ];
+
+ // ===== 方法 =====
+
+ /** 查询(节流 500ms) */
+ const handleSearch = useThrottleFn(async () => {
+ setLoading(true);
+ try {
+ console.log('搜索条件:', {
+ nickName: debouncedUserName.value,
+ phone: debouncedPhone.value,
+ realName: debouncedRealName.value,
+ auditStatus: filterForm.auditStatus,
+ transferStatus: filterForm.transferStatus,
+ });
+ // TODO: 替换为真实 API 调用
+ setDataSource(MOCK_DATA);
+ setPagination({ ...pagination.value, total: MOCK_DATA.length });
+ message.success('查询成功');
+ } catch (error: any) {
+ message.error(error.msg || '查询失败');
+ } finally {
+ setLoading(false);
+ }
+ }, 500);
+
+ /** 重置(节流 500ms) */
+ const handleReset = useThrottleFn(() => {
+ filterForm.searchUserName = '';
+ filterForm.searchPhone = '';
+ filterForm.realName = '';
+ filterForm.auditStatus = '';
+ filterForm.transferStatus = '';
+ setPagination({ current: 1, pageSize: 10, total: MOCK_DATA.length });
+ setDataSource(MOCK_DATA);
+ }, 500);
+
+ const handlePageChange = (page: number, pageSize: number) => {
+ setPagination({ ...pagination.value, current: page, pageSize });
+ handleSearch();
+ };
+
+ /**
+ * 打开审核弹窗
+ * 真实场景应调用 API;此处直接用列表中的 record
+ */
+ const handleAudit = (record: any) => {
+ setCurrentRecord(record);
+ setAuditVisible(true);
+ };
+
+ /** 关闭审核弹窗 */
+ const handleCloseAudit = () => {
+ setAuditVisible(false);
+ };
+
+ /**
+ * 提交审核结果(在弹窗中调用)
+ * pass = true 通过 / false 不通过
+ */
+ const submitAudit = (pass: boolean, _remark: string, _images: string[]) => {
+ setDataSource(
+ dataSource.value.map((item) =>
+ item.key === currentRecord.value.key
+ ? { ...item, auditStatus: pass ? '审核通过' : '审核不通过' }
+ : item,
+ ),
+ );
+ message.success(pass ? '已审核通过' : '已审核不通过');
+ handleCloseAudit();
+ };
+
+ /** 查看:打开同一个详情弹窗(只读展示审核信息) */
+ const handleView = (record: any) => {
+ setCurrentRecord(record);
+ setAuditVisible(true);
+ };
+
+ return {
+ filterForm,
+ loading,
+ dataSource,
+ columns,
+ pagination,
+ auditVisible,
+ currentRecord,
+ handleSearch,
+ handleReset,
+ handlePageChange,
+ handleAudit,
+ handleCloseAudit,
+ submitAudit,
+ handleView,
+ AUDIT_STATUS_OPTIONS: AUDIT_STATUS_OPTIONS as any,
+ TRANSFER_STATUS_OPTIONS: TRANSFER_STATUS_OPTIONS as any,
+ };
+}