From 2e7f9a1cebe83d74f06b6458250d915d2824af00 Mon Sep 17 00:00:00 2001 From: cst Date: Mon, 27 Jul 2026 14:57:48 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=B4=A2=E5=8A=A1=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E7=BC=96=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + src/pages/finance/reports/index.module.less | 127 +++++++ src/pages/finance/reports/index.tsx | 219 ++++++++++- .../finance/reports/model/useReportsModel.ts | 163 ++++++++ .../components/WalletDetailModal.module.less | 62 ++++ .../wallet/components/WalletDetailModal.tsx | 276 ++++++++++++++ src/pages/finance/wallet/index.module.less | 107 ++++++ src/pages/finance/wallet/index.tsx | 237 +++++++++++- .../finance/wallet/model/useWalletModel.ts | 349 ++++++++++++++++++ .../components/WithdrawAuditModal.module.less | 195 ++++++++++ .../components/WithdrawAuditModal.tsx | 245 ++++++++++++ src/pages/finance/withdraw/index.module.less | 15 + src/pages/finance/withdraw/index.tsx | 169 ++++++++- .../withdraw/model/useWithdrawModel.ts | 342 +++++++++++++++++ 14 files changed, 2495 insertions(+), 12 deletions(-) create mode 100644 src/pages/finance/reports/index.module.less create mode 100644 src/pages/finance/reports/model/useReportsModel.ts create mode 100644 src/pages/finance/wallet/components/WalletDetailModal.module.less create mode 100644 src/pages/finance/wallet/components/WalletDetailModal.tsx create mode 100644 src/pages/finance/wallet/index.module.less create mode 100644 src/pages/finance/wallet/model/useWalletModel.ts create mode 100644 src/pages/finance/withdraw/components/WithdrawAuditModal.module.less create mode 100644 src/pages/finance/withdraw/components/WithdrawAuditModal.tsx create mode 100644 src/pages/finance/withdraw/index.module.less create mode 100644 src/pages/finance/withdraw/model/useWithdrawModel.ts diff --git a/package.json b/package.json index b30813c..96538f3 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "ant-design-vue": "^4.0.0", "china-area-data": "^5.0.1", "dayjs": "^1.11.21", + "echarts": "^5.6.0", "express": "^5.2.1", "html2canvas": "^1.4.1", "qs": "^6.15.0", diff --git a/src/pages/finance/reports/index.module.less b/src/pages/finance/reports/index.module.less new file mode 100644 index 0000000..2b05832 --- /dev/null +++ b/src/pages/finance/reports/index.module.less @@ -0,0 +1,127 @@ +// ===== 整页可滚动:内容超出视口时出滚动条 ===== +.pageWrap { + height: auto !important; + min-height: 100%; + overflow-y: auto; + overflow-x: hidden; + padding-bottom: 16px; +} + +// ===== 顶部标题区 ===== +.reportHeader { + display: flex; + justify-content: space-between; + align-items: flex-end; + padding: 4px 4px 0; + flex-shrink: 0; +} + +.headerLeft { + display: flex; + flex-direction: column; + gap: 6px; +} + +.reportTitle { + font-size: 20px; + font-weight: 600; + color: rgba(0, 0, 0, 0.88); + letter-spacing: 0.5px; +} + +.reportSub { + font-size: 12px; + color: rgba(0, 0, 0, 0.45); + letter-spacing: 0.3px; +} + +// ===== 顶部 3 张统计卡 ===== +.statCards { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; + flex-shrink: 0; + margin: 16px 0; +} + +.statCard { + padding: 20px 24px; + border-radius: 10px; + display: flex; + flex-direction: column; + gap: 8px; + transition: all 0.25s ease; + cursor: default; + + &:hover { + transform: translateY(-2px); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.06); + } +} + +.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; +} + +.statDesc { + font-size: 12px; + letter-spacing: 0.3px; +} + +// ===== 图表卡 ===== +.chartCard { + flex-shrink: 0; + background: #fff; + border-radius: 10px; + padding: 18px 20px 14px; + margin-bottom: 16px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02); +} + +.cardTitle { + font-size: 14px; + font-weight: 600; + color: rgba(0, 0, 0, 0.85); + margin-bottom: 14px; + display: flex; + align-items: center; + gap: 6px; +} + +.cardTitleIcon { + font-size: 14px; +} + +// ===== 柱状图(echarts) ===== +.chartEcharts { + width: 100%; + height: 300px; +} + +// ===== 明细表卡(按内容自适应高度,不被压缩) ===== +.detailCard { + flex: none; + display: flex; + flex-direction: column; + background: #fff; + border-radius: 10px; + padding: 18px 20px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.02); + + :global(.ant-table-thead > tr > th) { + background: #fafafa; + font-weight: 600; + color: rgba(0, 0, 0, 0.85); + } +} diff --git a/src/pages/finance/reports/index.tsx b/src/pages/finance/reports/index.tsx index 0f9481a..2c4db89 100644 --- a/src/pages/finance/reports/index.tsx +++ b/src/pages/finance/reports/index.tsx @@ -1,15 +1,222 @@ -import { defineComponent } from 'vue'; +import { defineComponent, ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'; +import { Select, Table } from 'ant-design-vue'; +import * as echarts from 'echarts'; +import { useReportsModel } from './model/useReportsModel'; +import styles from './index.module.less'; /** * 账务报表 + * + * 页面结构: + * 1. 顶部标题 + 时间范围选择 + * 2. 三张统计卡(总收入 / 总提现 / 平台余额) + * 3. 月度收支趋势(近 6 个月,echarts 柱状图) + * 4. 月度收支明细(表格) */ export default defineComponent({ name: 'FinanceReports', setup() { - return () => ( -
-
账务报表 - 开发中
-
- ); + const { + range, + handleRangeChange, + summary, + chartData, + detailColumns, + detailData, + formatMoney, + RANGE_OPTIONS, + } = useReportsModel(); + + // ===== 柱状图(echarts) ===== + const chartRef = ref(); + let chartInstance: any = null; + + const getChartOption = () => { + const months = chartData.value.map((d) => d.month); + const order = chartData.value.map((d) => d.order); + const netIncome = chartData.value.map((d) => d.netIncome); + return { + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + valueFormatter: (v: number) => `¥${formatMoney(v)}`, + }, + legend: { + data: ['订单金额', '净收入'], + bottom: 8, + icon: 'roundRect', + itemWidth: 14, + itemHeight: 10, + textStyle: { color: 'rgba(0, 0, 0, 0.65)' }, + }, + grid: { left: 8, right: 16, top: 24, bottom: 48, containLabel: true }, + xAxis: { + type: 'category', + data: months, + axisTick: { show: false }, + axisLine: { lineStyle: { color: '#e8e8e8' } }, + axisLabel: { color: 'rgba(0, 0, 0, 0.55)' }, + }, + yAxis: { + type: 'value', + axisLabel: { + color: 'rgba(0, 0, 0, 0.45)', + formatter: (v: number) => (v >= 1000 ? `${v / 1000}k` : `${v}`), + }, + splitLine: { lineStyle: { color: '#f5f5f5' } }, + }, + series: [ + { + name: '订单金额', + type: 'bar', + data: order, + barWidth: 30, + itemStyle: { color: '#1677ff', borderRadius: [3, 3, 0, 0] }, + }, + { + name: '净收入', + type: 'bar', + data: netIncome, + barWidth: 30, + itemStyle: { color: '#52c41a', borderRadius: [3, 3, 0, 0] }, + }, + ], + }; + }; + + const renderChart = () => { + if (!chartRef.value) return; + if (!chartInstance) { + chartInstance = echarts.init(chartRef.value); + } + chartInstance.setOption(getChartOption()); + }; + + const handleResize = () => { + chartInstance?.resize(); + }; + + onMounted(() => { + nextTick(() => { + renderChart(); + }); + window.addEventListener('resize', handleResize); + }); + + onBeforeUnmount(() => { + window.removeEventListener('resize', handleResize); + chartInstance?.dispose(); + chartInstance = null; + }); + + // 时间范围切换时刷新图表(接入 API 后生效) + watch(range, () => { + renderChart(); + }); + + return () => { + /** 根据增长率构建箭头 + 颜色(正值 ↑ 绿,负值 ↓ 红,null 时固定文本) */ + const changeDesc = (rate: number | null) => { + if (rate == null) return { desc: '= 总收入 - 提现金额', descColor: 'rgba(0, 0, 0, 0.55)' }; + const arrow = rate > 0 ? '↑' : '↓'; + const sign = rate > 0 ? '+' : ''; + return { + desc: `${arrow} 较上月 ${sign}${rate}%`, + descColor: rate > 0 ? '#52c41a' : '#ff4d4f', + }; + }; + + /** 顶部 3 张统计卡配置 */ + const statCards = [ + { + key: 'totalIncome', + label: '总收入(扣除退款后)', + value: summary.value.totalIncome, + bgColor: '#f6ffed', + accentColor: '#52c41a', + ...changeDesc(12.5), + }, + { + key: 'totalWithdraw', + label: '总提现金额', + value: summary.value.totalWithdraw, + bgColor: '#fffbe6', + accentColor: '#faad14', + ...changeDesc(32), + }, + { + key: 'totalBalance', + label: '平台余额(所有用户余额总和)', + value: summary.value.totalBalance, + bgColor: '#e6f4ff', + accentColor: '#1677ff', + ...changeDesc(null), + }, + ]; + + return ( +
+ {/* ===== 顶部标题 + 时间范围 ===== */} +
+
+
财务报表
+
+ 平台资金流水概览,包括收入、退款、提现、用户余额等核心财务指标 +
+
+ (detailFilter.type = val || '')} + /> + + + + + + + + + + (filterForm.minBalance = val ?? '')} + onPressEnter={handleSearch} + allowClear + /> + + (filterForm.maxBalance = val ?? '')} + onPressEnter={handleSearch} + allowClear + /> + + + + + + + + + +
+ + {/* ===== 下方区域:筛选 + 表格(独立白卡片) ===== */} + + {/* ===== 表格区 ===== */} +
+
+ + {{ + bodyCell: (args: any) => + renderBodyCell({ + ...args, + onViewDetail: handleViewDetail, + }), + }} +
+
+ + {/* 独立分页,右下方 */} +
+ `共 ${total} 条`} + onChange={handlePageChange} + onShowSizeChange={handlePageChange} + /> +
+
+ + {/* ===== 交易明细弹窗 ===== */} + +
+ ); + }; }, }); 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)} + > + 通过 + 未通过 + +
+ +
+ 审核内容 +