xsh_1997 hai 1 semana
pai
achega
b798f43010

+ 206 - 0
doc/平台后台/财务管理/提现审核/提现审核前端技术方案.md

@@ -0,0 +1,206 @@
1
+# 提现审核 — 前端技术方案
2
+
3
+> **依据:** 《提现审核功能需求.md》v1.0、《提现审核技术方案.md》v1.0  
4
+> **前端规范:** `doc/前端设计/前端设计.md`  
5
+> **范围:** 仅 **ruoyi-ui 平台端** **全平台提现申请列表**、**高级检索**、**审核通过/驳回**;**不含** 商家提交、账户维护、自动打款、批量审核。  
6
+> **实现状态:** `index.vue`、`api/agri/finance/withdrawAudit.js` **已按 v1.0 落地**;待菜单配置及 `/agri/finance/withdrawAudit` 联调。
7
+
8
+---
9
+
10
+## 1. 技术栈与写法约定
11
+
12
+| 项 | 说明 |
13
+|----|------|
14
+| 框架 | Vue 2 + Element UI |
15
+| 请求 | `@/utils/request`(**无** `X-Shop-Id`;全平台) |
16
+| 参考页面 | `agri/goods/audit/index.vue`(Tab+待审角标)、`agri/seller/finance/withdraw/index.vue`(列表字段) |
17
+| 布局 | 检索 `el-card` + `<br/>` + 列表 `el-card` + `border` 表格 |
18
+| 审核 | `el-dialog` width=560px;通过/驳回 + 处理说明 |
19
+| 默认 Tab | **待审核**(与商品审核工作台一致) |
20
+
21
+---
22
+
23
+## 2. 业务要点(前端需体现)
24
+
25
+| 项 | 说明 |
26
+|----|------|
27
+| 唯一审核入口 | 平台 **PUT approve/reject** |
28
+| 可审状态 | **仅** `withdrawStatus=1`(待审核)显示「审核」 |
29
+| 通过 | **直接** 置「提现完成」;无中间态 |
30
+| 处理说明 | 通过/驳回 **均必填**;≤200 字 |
31
+| 终态 | 审核不通过 / 提现完成 **不可再审** |
32
+| 账户展示 | `accountSummary`(申请时快照) |
33
+| 待审角标 | `GET /pendingCount` |
34
+
35
+---
36
+
37
+## 3. 文件清单
38
+
39
+| 类型 | 路径 | 说明 |
40
+|------|------|------|
41
+| 页面 | `ruoyi-ui/src/views/agri/finance/withdrawAudit/index.vue` | 列表 + 检索 + 审核弹窗 |
42
+| API | `ruoyi-ui/src/api/agri/finance/withdrawAudit.js` | list、pendingCount、approve、reject |
43
+
44
+**组件 name(keep-alive):** `AgriFinanceWithdrawAudit`
45
+
46
+**不提供:** 批量审核、独立详情页、导出、编号/店名关键词检索。
47
+
48
+---
49
+
50
+## 4. 菜单与路由
51
+
52
+| 菜单名称 | 组件路径 | 路由 path(建议) | 权限标识 |
53
+|----------|----------|-------------------|----------|
54
+| 提现审核 | `agri/finance/withdrawAudit/index` | `finance/withdrawAudit` | 见下表 |
55
+
56
+**上级菜单:** 平台管理端 → **财务管理**
57
+
58
+| 按钮权限 | 标识 | 页面落点 |
59
+|----------|------|----------|
60
+| 列表 + 待审数 | `agri:finance:withdrawAudit:list` | 进入页面、Tab 角标 |
61
+| 审核 | `agri:finance:withdrawAudit:audit` | 「审核」、PUT 提交 |
62
+
63
+---
64
+
65
+## 5. 页面结构(与代码一致)
66
+
67
+```text
68
+提现审核 index.vue
69
+├── 检索区 search-card
70
+│   ├── 申请时间 daterange → beginApplyTime / endApplyTime
71
+│   ├── 提现状态 withdrawStatus
72
+│   └── 搜索 / 重置
73
+├── <br/>
74
+├── 列表区 table-card
75
+│   ├── el-tabs:全部 / 待审核(角标) / 审核不通过 / 提现完成
76
+│   ├── el-table border
77
+│   │   ├── 提现编号、申请时间、店铺名称
78
+│   │   ├── 提现账号 accountSummary
79
+│   │   ├── 提现金额、备注
80
+│   │   ├── 提现状态、提现处理说明
81
+│   │   └── 操作:审核(仅待审核)
82
+│   └── pagination
83
+└── 审核弹窗 el-dialog
84
+    ├── 单据摘要 descriptions
85
+    ├── 审核结果 approve / reject
86
+    └── 提现处理说明 processRemark(必填)
87
+```
88
+
89
+---
90
+
91
+## 6. 列表与检索
92
+
93
+**GET** `/list`
94
+
95
+| Query | 说明 |
96
+|-------|------|
97
+| pageNum / pageSize | 分页 |
98
+| beginApplyTime / endApplyTime | `yyyy-MM-dd` |
99
+| withdrawStatus | 空=全部;`1`/`2`/`3` |
100
+
101
+**GET** `/pendingCount` → `{ pendingCount }`
102
+
103
+Tab 与 `withdrawStatus` 联动;**默认 Tab=待审核**。
104
+
105
+---
106
+
107
+## 7. 审核提交
108
+
109
+### 7.1 审核通过
110
+
111
+**PUT** `/approve/{withdrawId}`
112
+
113
+```json
114
+{ "processRemark": "已向商家打款" }
115
+```
116
+
117
+### 7.2 审核驳回
118
+
119
+**PUT** `/reject`
120
+
121
+```json
122
+{
123
+  "withdrawId": 1,
124
+  "processRemark": "账户信息有误"
125
+}
126
+```
127
+
128
+### 7.3 前端校验
129
+
130
+| 规则 | 说明 |
131
+|------|------|
132
+| 未选结果 | 「请选择审核结果」 |
133
+| 说明为空 | 「请填写提现处理说明」 |
134
+| 说明超长 | maxlength 200 |
135
+| 成功 | 「操作成功」→ 刷新列表与待审数 |
136
+
137
+后端另校验:非待审、并发状态变更、说明必填等。
138
+
139
+---
140
+
141
+## 8. 状态展示
142
+
143
+| withdrawStatus | 文案 | Tag | 操作 |
144
+|----------------|------|-----|------|
145
+| `1` | 待审核 | warning | 审核 |
146
+| `2` | 审核不通过 | danger | — |
147
+| `3` | 提现完成 | success | — |
148
+
149
+处理说明:待审核展示 **—**。
150
+
151
+---
152
+
153
+## 9. API 封装
154
+
155
+**模块:** `@/api/agri/finance/withdrawAudit.js`
156
+
157
+| 方法 | HTTP | 路径 | 权限 |
158
+|------|------|------|------|
159
+| `listPlatformWithdrawAudits` | GET | `/list` | withdrawAudit:list |
160
+| `getPlatformWithdrawAuditPendingCount` | GET | `/pendingCount` | withdrawAudit:list |
161
+| `approvePlatformWithdrawAudit` | PUT | `/approve/{withdrawId}` | withdrawAudit:audit |
162
+| `rejectPlatformWithdrawAudit` | PUT | `/reject` | withdrawAudit:audit |
163
+
164
+---
165
+
166
+## 10. 空状态与错误提示
167
+
168
+| 场景 | 文案 |
169
+|------|------|
170
+| 列表无数据 | 「暂无提现申请」 |
171
+| 检索无结果 | 「未找到符合条件的提现申请」 |
172
+| 后端状态已变 | 「提现状态已变更,请刷新后重试」 |
173
+
174
+---
175
+
176
+## 11. 与兄弟模块边界(前端)
177
+
178
+| 模块 | 关系 |
179
+|------|------|
180
+| **商家 · 提现管理** | 商家提交;本页审核回写状态与处理说明 |
181
+| **商家/平台 · 资金概览** | 审核写流水;本页不展示流水 |
182
+| **账户管理** | 列表只读账户快照 |
183
+
184
+---
185
+
186
+## 12. 联调检查清单
187
+
188
+- [ ] 菜单挂载 `agri/finance/withdrawAudit/index`
189
+- [ ] 默认进入待审核 Tab
190
+- [ ] 待审角标与 pendingCount 一致
191
+- [ ] 仅待审核行显示「审核」
192
+- [ ] 通过/驳回均须填处理说明
193
+- [ ] 审核成功后列表与角标刷新
194
+- [ ] 商家端提现列表同步终态
195
+
196
+---
197
+
198
+## 13. 版本记录
199
+
200
+| 版本 | 说明 |
201
+|------|------|
202
+| **v1.0** | 首版:列表/检索/审核弹窗/API 封装;对齐需求 v1.0 |
203
+
204
+---
205
+
206
+*文档版本:v1.0 · 依据《提现审核功能需求.md》v1.0、《提现审核技术方案.md》v1.0*

+ 180 - 0
doc/平台后台/财务管理/提现汇总/提现汇总前端技术方案.md

@@ -0,0 +1,180 @@
1
+# 提现汇总 — 前端技术方案
2
+
3
+> **依据:** 《提现汇总功能需求.md》v1.0、《提现汇总技术方案.md》v1.0  
4
+> **前端规范:** `doc/前端设计/前端设计.md`  
5
+> **范围:** 仅 **ruoyi-ui 平台端** **按店提现汇总列表**、**单店提现明细**(只读);**不含** 提现审核、商家提交、账户维护。  
6
+> **实现状态:** `index.vue`、`api/agri/finance/withdrawSummary.js` **已按 v1.0 落地**;待菜单配置及 `/agri/finance/withdrawSummary` 联调。
7
+
8
+---
9
+
10
+## 1. 技术栈与写法约定
11
+
12
+| 项 | 说明 |
13
+|----|------|
14
+| 框架 | Vue 2 + Element UI |
15
+| 请求 | `@/utils/request`(**无** `X-Shop-Id`;全平台) |
16
+| 参考页面 | `agri/finance/fundOverview/index.vue`(汇总+抽屉)、`agri/finance/withdrawAudit/index.vue`(明细字段) |
17
+| 布局 | 检索 `el-card` + `<br/>` + 汇总 `el-card` + `border` 表格 |
18
+| 明细 | `el-drawer` width=72%;内嵌检索 + 表格 + 分页 |
19
+| 图片 | 全局 `image-preview` |
20
+
21
+---
22
+
23
+## 2. 业务要点(前端需体现)
24
+
25
+| 项 | 说明 |
26
+|----|------|
27
+| 只读 | **无** 审核、改状态/金额 |
28
+| 已提现金额 | **提现完成** 单据金额合计 |
29
+| 待审核金额 | **待审核** 单据金额合计 |
30
+| 审核不通过 | **不计入** 汇总两列;**明细可见** |
31
+| 无提现店 | 汇总 **仍展示**;金额为 **¥0.00** |
32
+| 汇总检索 | **店铺名称** 模糊 |
33
+| 明细检索 | **申请时间** + **提现状态**;固定 **shopId** |
34
+| 明细字段 | 与 **提现审核 / 商家提现管理** 对齐(无提现编号列,非本期) |
35
+
36
+---
37
+
38
+## 3. 文件清单
39
+
40
+| 类型 | 路径 | 说明 |
41
+|------|------|------|
42
+| 页面 | `ruoyi-ui/src/views/agri/finance/withdrawSummary/index.vue` | 汇总列表 + 明细抽屉 |
43
+| API | `ruoyi-ui/src/api/agri/finance/withdrawSummary.js` | shops、details |
44
+
45
+**组件 name(keep-alive):** `AgriFinanceWithdrawSummary`
46
+
47
+**不提供:** 汇总列排序、跳转提现审核、导出、提现编号列。
48
+
49
+---
50
+
51
+## 4. 菜单与路由
52
+
53
+| 菜单名称 | 组件路径 | 路由 path(建议) | 权限标识 |
54
+|----------|----------|-------------------|----------|
55
+| 提现汇总 | `agri/finance/withdrawSummary/index` | `finance/withdrawSummary` | 见下表 |
56
+
57
+**上级菜单:** 平台管理端 → **财务管理**
58
+
59
+| 能力 | 权限 | 页面落点 |
60
+|------|------|----------|
61
+| 店铺汇总 | `agri:finance:withdrawSummary:summary` | 汇总列表 |
62
+| 提现明细 | `agri:finance:withdrawSummary:detail` | 「查看提现明细」、抽屉内列表 |
63
+
64
+---
65
+
66
+## 5. 页面结构(与代码一致)
67
+
68
+```text
69
+提现汇总 index.vue
70
+├── 检索区 search-card
71
+│   ├── 店铺名称 shopName(模糊)
72
+│   └── 搜索 / 重置
73
+├── <br/>
74
+├── 汇总列表 table-card
75
+│   ├── el-table border
76
+│   │   ├── 店铺名称(LOGO + 名称)
77
+│   │   ├── 店铺状态
78
+│   │   ├── 已提现金额 withdrawnAmount
79
+│   │   ├── 待审核金额 pendingAuditAmount(>0 可高亮)
80
+│   │   └── 操作:查看提现明细
81
+│   └── pagination
82
+└── 提现明细 el-drawer 72%
83
+    ├── 申请时间 daterange
84
+    ├── 提现状态 withdrawStatus
85
+    ├── el-table:时间、店名、账号、金额、备注、状态、处理说明
86
+    └── pagination
87
+```
88
+
89
+---
90
+
91
+## 6. 店铺提现汇总
92
+
93
+**GET** `/agri/finance/withdrawSummary/shops`
94
+
95
+| Query | 说明 |
96
+|-------|------|
97
+| pageNum / pageSize | 分页 |
98
+| shopName | 店铺名称模糊 |
99
+
100
+**默认排序(后端):** 待审核金额降序。
101
+
102
+| 字段 | 说明 |
103
+|------|------|
104
+| shopId | 下钻明细 |
105
+| shopName / shopAvatar | 店铺信息 |
106
+| shopStatus / shopStatusText / deleted | 状态 |
107
+| withdrawnAmount | 已提现金额 |
108
+| pendingAuditAmount | 待审核金额 |
109
+
110
+---
111
+
112
+## 7. 提现明细
113
+
114
+**GET** `/agri/finance/withdrawSummary/details`
115
+
116
+| Query | 说明 |
117
+|-------|------|
118
+| shopId | **必填**(汇总行传入) |
119
+| withdrawStatus | 空=全部;`1`/`2`/`3` |
120
+| beginApplyTime / endApplyTime | `yyyy-MM-dd` |
121
+| pageNum / pageSize | 分页 |
122
+
123
+**重置:** 恢复该店全部明细(清空时间与状态筛选)。
124
+
125
+---
126
+
127
+## 8. API 封装
128
+
129
+**模块:** `@/api/agri/finance/withdrawSummary.js`
130
+
131
+| 方法 | HTTP | 路径 | 权限 |
132
+|------|------|------|------|
133
+| `listPlatformWithdrawSummaryShops` | GET | `/shops` | withdrawSummary:summary |
134
+| `listPlatformWithdrawSummaryDetails` | GET | `/details` | withdrawSummary:detail |
135
+
136
+---
137
+
138
+## 9. 空状态与展示
139
+
140
+| 场景 | 文案 |
141
+|------|------|
142
+| 汇总无店 | 「暂无店铺数据」 |
143
+| 店名检索无结果 | 「未找到符合条件的店铺」 |
144
+| 明细无记录 | 「暂无提现记录」 |
145
+| 明细检索无结果 | 「未找到符合条件的提现记录」 |
146
+| 金额为 0 | 「¥0.00」 |
147
+| 待审核处理说明 | 「—」 |
148
+
149
+---
150
+
151
+## 10. 与兄弟模块边界(前端)
152
+
153
+| 模块 | 关系 |
154
+|------|------|
155
+| **提现审核** | 审核写状态;本页只读;待审金额应与审核列表 SUM 一致 |
156
+| **商家 · 提现管理** | 同 shopId 明细字段一致 |
157
+| **资金概览** | 互补(余额/冻结 vs 已提现/待审) |
158
+
159
+---
160
+
161
+## 11. 联调检查清单
162
+
163
+- [ ] 菜单挂载 `agri/finance/withdrawSummary/index`
164
+- [ ] 从未提现店铺展示 ¥0.00
165
+- [ ] 提交/审核后汇总列实时变化
166
+- [ ] 驳回单不计入汇总两列、明细可见
167
+- [ ] 明细与商家提现记录对账一致
168
+- [ ] 权限 summary/detail 分权生效
169
+
170
+---
171
+
172
+## 12. 版本记录
173
+
174
+| 版本 | 说明 |
175
+|------|------|
176
+| **v1.0** | 首版:按店汇总 + 单店明细抽屉 + API 封装 |
177
+
178
+---
179
+
180
+*文档版本:v1.0 · 依据《提现汇总功能需求.md》v1.0、《提现汇总技术方案.md》v1.0*

+ 202 - 0
doc/平台后台/财务管理/资金概览/资金概览前端技术方案.md

@@ -0,0 +1,202 @@
1
+# 资金概览 — 前端技术方案
2
+
3
+> **依据:** 《资金概览功能需求.md》v1.0、《资金概览技术方案.md》v1.0  
4
+> **前端规范:** `doc/前端设计/前端设计.md`  
5
+> **范围:** 仅 **ruoyi-ui 平台端** **全平台总览**、**店铺余额排行**、**资金变动明细**(只读);**不含** 提现审核、手工调账、商家账户维护。  
6
+> **实现状态:** `index.vue`、`api/agri/finance/fundOverview.js` **已按 v1.0 落地**;待菜单配置及 `/agri/finance/fundOverview` 联调。
7
+
8
+---
9
+
10
+## 1. 技术栈与写法约定
11
+
12
+| 项 | 说明 |
13
+|----|------|
14
+| 框架 | Vue 2 + Element UI |
15
+| 请求 | `@/utils/request`(**无** `X-Shop-Id`;平台全平台可见) |
16
+| 参考页面 | `agri/seller/finance/overview/index.vue`(总览/流水格式)、`agri/org/shop/index.vue`(平台列表) |
17
+| 布局 | 总览 `el-card` + `<br/>` + 排行 `el-card` + `border` 表格 |
18
+| 资金日志 | `el-drawer` width=72%;内嵌检索 + 表格 + 分页 |
19
+| 图片 | 全局 `image-preview` |
20
+
21
+---
22
+
23
+## 2. 业务要点(前端需体现)
24
+
25
+| 项 | 说明 |
26
+|----|------|
27
+| 只读 | **无** 改余额/冻结、审核提现 |
28
+| 总览 | 总余额 / 待结算总额 / 冻结总额(全平台 Σ) |
29
+| 排行 | 店名+LOGO、状态(开业/停业/已删除)、三金额列、开店时间 |
30
+| 列排序 | 店铺余额 / 待结算 / 冻结 **可 sortable**;默认余额 **降序** |
31
+| 资金日志 | 排行行「查看资金日志」;默认 **仅该 shopId** |
32
+| 日志检索 | 店铺名称模糊、变动时间区间;**店名有值时忽略 shopId** |
33
+| 重置日志 | 恢复 **入口店铺** 默认范围 |
34
+| 变动展示 | 与商家端一致「±变更值(余额/冻结:变更后)」 |
35
+
36
+---
37
+
38
+## 3. 文件清单
39
+
40
+| 类型 | 路径 | 说明 |
41
+|------|------|------|
42
+| 页面 | `ruoyi-ui/src/views/agri/finance/fundOverview/index.vue` | 总览 + 排行 + 日志抽屉 |
43
+| API | `ruoyi-ui/src/api/agri/finance/fundOverview.js` | summary、shops、logs |
44
+
45
+**组件 name(keep-alive):** `AgriFinanceFundOverview`
46
+
47
+**不提供:** 排行店名检索、流水原因/编号筛选、导出、跳转提现审核。
48
+
49
+---
50
+
51
+## 4. 菜单与路由
52
+
53
+| 菜单名称 | 组件路径 | 路由 path(建议) | 权限标识 |
54
+|----------|----------|-------------------|----------|
55
+| 资金概览 | `agri/finance/fundOverview/index` | `finance/fundOverview` | 见下表 |
56
+
57
+**上级菜单:** 平台管理端 → **财务管理**
58
+
59
+| 能力 | 权限 | 页面落点 |
60
+|------|------|----------|
61
+| 全平台总览 | `agri:finance:fundOverview:summary` | 顶部三块指标 |
62
+| 店铺排行 | `agri:finance:fundOverview:ranking` | 排行表格 |
63
+| 资金日志 | `agri:finance:fundOverview:log` | 「查看资金日志」、抽屉内列表 |
64
+
65
+---
66
+
67
+## 5. 页面结构(与代码一致)
68
+
69
+```text
70
+平台资金概览 index.vue
71
+├── 全平台资金总览 overview-card
72
+│   ├── 总余额 totalBalance
73
+│   ├── 待结算总额 pendingSettleTotal
74
+│   └── 冻结总额 frozenTotal
75
+├── <br/>
76
+├── 店铺余额排行 table-card
77
+│   ├── el-table border(sortable 三列)
78
+│   │   ├── 店铺名称(LOGO + 名称)
79
+│   │   ├── 店铺状态
80
+│   │   ├── 店铺余额 availableBalance
81
+│   │   ├── 待结算金额 pendingSettleAmount
82
+│   │   ├── 冻结金额 frozenAmount
83
+│   │   ├── 开店时间 openTime
84
+│   │   └── 操作:查看资金日志
85
+│   └── pagination
86
+└── 资金日志 el-drawer 72%
87
+    ├── 检索:店铺名称 shopName、变动时间 daterange
88
+    ├── el-table border(六列,同商家收支概况)
89
+    └── pagination
90
+```
91
+
92
+---
93
+
94
+## 6. 全平台总览
95
+
96
+**GET** `/agri/finance/fundOverview/summary`
97
+
98
+| 字段 | 说明 |
99
+|------|------|
100
+| totalBalance | 总余额 |
101
+| pendingSettleTotal | 待结算总额 |
102
+| frozenTotal | 冻结总额 |
103
+
104
+展示:`formatMoney`(¥ + 两位小数 + 千分位)。
105
+
106
+---
107
+
108
+## 7. 店铺余额排行
109
+
110
+**GET** `/agri/finance/fundOverview/shops`
111
+
112
+| Query | 说明 |
113
+|-------|------|
114
+| pageNum / pageSize | 分页 |
115
+| orderByColumn | `availableBalance` / `pendingSettleAmount` / `frozenAmount` |
116
+| isAsc | `asc` / `desc`(前端由 `sort-change` 转换) |
117
+
118
+**默认:** `orderByColumn=availableBalance`,`isAsc=desc`。
119
+
120
+**店铺状态:**
121
+
122
+| 条件 | 展示 | Tag |
123
+|------|------|-----|
124
+| deleted=true | 已删除 | info |
125
+| shopStatus=0 | 开业 | success |
126
+| shopStatus=1 | 停业 | info |
127
+
128
+---
129
+
130
+## 8. 资金变动明细
131
+
132
+**GET** `/agri/finance/fundOverview/logs`
133
+
134
+| Query | 说明 |
135
+|-------|------|
136
+| shopId | 排行入口传入;店名检索时 **不传** |
137
+| shopName | 模糊;**有值覆盖 shopId 范围** |
138
+| beginChangeTime / endChangeTime | `yyyy-MM-dd` |
139
+| pageNum / pageSize | 分页 |
140
+
141
+**入口逻辑:**
142
+
143
+- 从排行进入:固定 `defaultLogShopId`;重置恢复该 shopId;
144
+- 填写店名搜索:清除 shopId,可跨店对账。
145
+
146
+**行字段:** 复用 `FundLogListRowVO`(shopName、changeTime、bizNo、balanceChange/After、frozenChange/After、changeReasonText)。
147
+
148
+---
149
+
150
+## 9. API 封装
151
+
152
+**模块:** `@/api/agri/finance/fundOverview.js`
153
+
154
+| 方法 | HTTP | 路径 | 权限 |
155
+|------|------|------|------|
156
+| `getPlatformFundOverviewSummary` | GET | `/summary` | summary |
157
+| `listPlatformFundOverviewShops` | GET | `/shops` | ranking |
158
+| `listPlatformFundOverviewLogs` | GET | `/logs` | log |
159
+
160
+---
161
+
162
+## 10. 空状态与错误提示
163
+
164
+| 场景 | 文案 |
165
+|------|------|
166
+| 排行无数据 | 「暂无店铺资金数据」 |
167
+| 日志无数据 | 「暂无资金变动记录」 |
168
+| 日志检索无结果 | 「未找到符合条件的资金变动记录」 |
169
+
170
+---
171
+
172
+## 11. 与兄弟模块边界(前端)
173
+
174
+| 模块 | 关系 |
175
+|------|------|
176
+| **商家 · 资金概览** | 同 shopId 指标/流水 **须一致** |
177
+| **提现审核** | 审核写流水;本页 **只读** |
178
+| **店铺管理** | 排行展示店名/LOGO/状态/开店时间 |
179
+
180
+---
181
+
182
+## 12. 联调检查清单
183
+
184
+- [ ] 菜单挂载 `agri/finance/fundOverview/index`
185
+- [ ] 总览三块与 Σ 排行 **口径一致**
186
+- [ ] 排行默认余额降序;三列排序切换
187
+- [ ] 从某店打开日志默认仅该店
188
+- [ ] 店名检索可跨店;重置恢复单店
189
+- [ ] 变动列格式与商家端一致
190
+- [ ] 权限:summary/ranking/log 分权生效
191
+
192
+---
193
+
194
+## 13. 版本记录
195
+
196
+| 版本 | 说明 |
197
+|------|------|
198
+| **v1.0** | 首版:全平台总览 + 店铺排行 + 资金日志抽屉 + API 封装 |
199
+
200
+---
201
+
202
+*文档版本:v1.0 · 依据《资金概览功能需求.md》v1.0、《资金概览技术方案.md》v1.0*

+ 27 - 0
ruoyi-ui/src/api/agri/finance/fundOverview.js

@@ -0,0 +1,27 @@
1
+import request from '@/utils/request'
2
+
3
+// 全平台资金总览
4
+export function getPlatformFundOverviewSummary() {
5
+  return request({
6
+    url: '/agri/finance/fundOverview/summary',
7
+    method: 'get'
8
+  })
9
+}
10
+
11
+// 店铺余额排行
12
+export function listPlatformFundOverviewShops(query) {
13
+  return request({
14
+    url: '/agri/finance/fundOverview/shops',
15
+    method: 'get',
16
+    params: query
17
+  })
18
+}
19
+
20
+// 资金变动明细
21
+export function listPlatformFundOverviewLogs(query) {
22
+  return request({
23
+    url: '/agri/finance/fundOverview/logs',
24
+    method: 'get',
25
+    params: query
26
+  })
27
+}

+ 36 - 0
ruoyi-ui/src/api/agri/finance/withdrawAudit.js

@@ -0,0 +1,36 @@
1
+import request from '@/utils/request'
2
+
3
+// 提现申请列表
4
+export function listPlatformWithdrawAudits(query) {
5
+  return request({
6
+    url: '/agri/finance/withdrawAudit/list',
7
+    method: 'get',
8
+    params: query
9
+  })
10
+}
11
+
12
+// 待审核数量
13
+export function getPlatformWithdrawAuditPendingCount() {
14
+  return request({
15
+    url: '/agri/finance/withdrawAudit/pendingCount',
16
+    method: 'get'
17
+  })
18
+}
19
+
20
+// 审核通过
21
+export function approvePlatformWithdrawAudit(withdrawId, data) {
22
+  return request({
23
+    url: '/agri/finance/withdrawAudit/approve/' + withdrawId,
24
+    method: 'put',
25
+    data: data
26
+  })
27
+}
28
+
29
+// 审核驳回
30
+export function rejectPlatformWithdrawAudit(data) {
31
+  return request({
32
+    url: '/agri/finance/withdrawAudit/reject',
33
+    method: 'put',
34
+    data: data
35
+  })
36
+}

+ 19 - 0
ruoyi-ui/src/api/agri/finance/withdrawSummary.js

@@ -0,0 +1,19 @@
1
+import request from '@/utils/request'
2
+
3
+// 店铺提现汇总列表
4
+export function listPlatformWithdrawSummaryShops(query) {
5
+  return request({
6
+    url: '/agri/finance/withdrawSummary/shops',
7
+    method: 'get',
8
+    params: query
9
+  })
10
+}
11
+
12
+// 单店提现明细
13
+export function listPlatformWithdrawSummaryDetails(query) {
14
+  return request({
15
+    url: '/agri/finance/withdrawSummary/details',
16
+    method: 'get',
17
+    params: query
18
+  })
19
+}

+ 499 - 0
ruoyi-ui/src/views/agri/finance/fundOverview/index.vue

@@ -0,0 +1,499 @@
1
+<template>
2
+  <div class="app-container">
3
+    <!-- 全平台资金总览 -->
4
+    <el-card shadow="never" class="overview-card" v-loading="summaryLoading">
5
+      <div slot="header" class="card-header">
6
+        <span class="card-title">全平台资金总览</span>
7
+      </div>
8
+      <el-row :gutter="24" class="stats-row">
9
+        <el-col :xs="24" :sm="8">
10
+          <div class="stat-block stat-block-primary">
11
+            <div class="stat-value">{{ formatMoney(summary.totalBalance) }}</div>
12
+            <div class="stat-label">总余额</div>
13
+            <div class="stat-tip">全平台可用店铺余额合计</div>
14
+          </div>
15
+        </el-col>
16
+        <el-col :xs="24" :sm="8">
17
+          <div class="stat-block">
18
+            <div class="stat-value">{{ formatMoney(summary.pendingSettleTotal) }}</div>
19
+            <div class="stat-label">待结算总额</div>
20
+            <div class="stat-tip">已支付未完成订单实付合计</div>
21
+          </div>
22
+        </el-col>
23
+        <el-col :xs="24" :sm="8">
24
+          <div class="stat-block">
25
+            <div class="stat-value">{{ formatMoney(summary.frozenTotal) }}</div>
26
+            <div class="stat-label">冻结总额</div>
27
+            <div class="stat-tip">提现审核中临时冻结金额合计</div>
28
+          </div>
29
+        </el-col>
30
+      </el-row>
31
+    </el-card>
32
+
33
+    <br/>
34
+
35
+    <!-- 店铺余额排行 -->
36
+    <el-card shadow="never" class="table-card">
37
+      <div slot="header" class="card-header">
38
+        <span class="card-title">店铺余额排行</span>
39
+      </div>
40
+
41
+      <el-row :gutter="10" class="mb8">
42
+        <right-toolbar :showSearch="false" @queryTable="refreshRank"></right-toolbar>
43
+      </el-row>
44
+
45
+      <el-table
46
+        ref="rankTable"
47
+        border
48
+        v-loading="rankLoading"
49
+        :data="shopList"
50
+        :empty-text="rankEmptyText"
51
+        :default-sort="defaultSort"
52
+        @sort-change="handleSortChange"
53
+      >
54
+        <el-table-column label="店铺名称" align="left" min-width="160" :show-overflow-tooltip="true">
55
+          <template slot-scope="scope">
56
+            <div class="shop-cell">
57
+              <image-preview v-if="scope.row.shopAvatar" :src="scope.row.shopAvatar" :width="44" :height="44" />
58
+              <span class="shop-name">{{ scope.row.shopName || '—' }}</span>
59
+            </div>
60
+          </template>
61
+        </el-table-column>
62
+        <el-table-column label="店铺状态" align="center" width="100">
63
+          <template slot-scope="scope">
64
+            <el-tag size="small" :type="shopStatusTag(scope.row)">
65
+              {{ shopStatusText(scope.row) }}
66
+            </el-tag>
67
+          </template>
68
+        </el-table-column>
69
+        <el-table-column label="店铺余额" align="center" prop="availableBalance" width="130" sortable="custom">
70
+          <template slot-scope="scope">
71
+            <span>{{ formatMoney(scope.row.availableBalance) }}</span>
72
+          </template>
73
+        </el-table-column>
74
+        <el-table-column label="待结算金额" align="center" prop="pendingSettleAmount" width="130" sortable="custom">
75
+          <template slot-scope="scope">
76
+            <span>{{ formatMoney(scope.row.pendingSettleAmount) }}</span>
77
+          </template>
78
+        </el-table-column>
79
+        <el-table-column label="冻结金额" align="center" prop="frozenAmount" width="120" sortable="custom">
80
+          <template slot-scope="scope">
81
+            <span>{{ formatMoney(scope.row.frozenAmount) }}</span>
82
+          </template>
83
+        </el-table-column>
84
+        <el-table-column label="开店时间" align="center" width="160">
85
+          <template slot-scope="scope">
86
+            <span>{{ parseTime(scope.row.openTime) || '—' }}</span>
87
+          </template>
88
+        </el-table-column>
89
+        <el-table-column label="操作" align="center" width="120" fixed="right">
90
+          <template slot-scope="scope">
91
+            <el-button size="mini" type="text" @click="openLogs(scope.row)" v-hasPermi="['agri:finance:fundOverview:log']">查看资金日志</el-button>
92
+          </template>
93
+        </el-table-column>
94
+      </el-table>
95
+
96
+      <pagination v-show="rankTotal > 0" :total="rankTotal" :page.sync="rankQuery.pageNum" :limit.sync="rankQuery.pageSize" @pagination="getShopList" />
97
+    </el-card>
98
+
99
+    <!-- 资金日志抽屉 -->
100
+    <el-drawer
101
+      :title="logsDrawerTitle"
102
+      :visible.sync="logsOpen"
103
+      size="72%"
104
+      append-to-body
105
+      @close="resetLogs"
106
+    >
107
+      <div class="logs-panel">
108
+        <el-form :model="logQuery" ref="logQueryForm" size="small" :inline="true" label-width="80px" class="logs-search">
109
+          <el-form-item label="店铺名称" prop="shopName">
110
+            <el-input
111
+              v-model="logQuery.shopName"
112
+              placeholder="模糊检索"
113
+              clearable
114
+              style="width: 180px"
115
+              @keyup.enter.native="handleLogQuery"
116
+            />
117
+          </el-form-item>
118
+          <el-form-item label="变动时间">
119
+            <el-date-picker
120
+              v-model="logDateRange"
121
+              type="daterange"
122
+              value-format="yyyy-MM-dd"
123
+              range-separator="至"
124
+              start-placeholder="开始日期"
125
+              end-placeholder="结束日期"
126
+              style="width: 240px"
127
+            />
128
+          </el-form-item>
129
+          <el-form-item>
130
+            <el-button type="primary" icon="el-icon-search" size="mini" @click="handleLogQuery">搜索</el-button>
131
+            <el-button icon="el-icon-refresh" size="mini" @click="resetLogQuery">重置</el-button>
132
+          </el-form-item>
133
+        </el-form>
134
+
135
+        <el-table border v-loading="logsLoading" :data="logList" :empty-text="logsEmptyText">
136
+          <el-table-column label="店铺名称" align="center" prop="shopName" min-width="120" :show-overflow-tooltip="true">
137
+            <template slot-scope="scope">
138
+              <span>{{ scope.row.shopName || '—' }}</span>
139
+            </template>
140
+          </el-table-column>
141
+          <el-table-column label="变动时间" align="center" width="160">
142
+            <template slot-scope="scope">
143
+              <span>{{ parseTime(scope.row.changeTime) || '—' }}</span>
144
+            </template>
145
+          </el-table-column>
146
+          <el-table-column label="业务编号" align="center" prop="bizNo" min-width="160" :show-overflow-tooltip="true">
147
+            <template slot-scope="scope">
148
+              <span>{{ scope.row.bizNo || '—' }}</span>
149
+            </template>
150
+          </el-table-column>
151
+          <el-table-column label="变动余额资金" align="center" min-width="180">
152
+            <template slot-scope="scope">
153
+              <span :class="changeClass(scope.row.balanceChange)">
154
+                {{ formatFundChange(scope.row.balanceChange, scope.row.balanceAfter, '余额') }}
155
+              </span>
156
+            </template>
157
+          </el-table-column>
158
+          <el-table-column label="变动冻结资金" align="center" min-width="180">
159
+            <template slot-scope="scope">
160
+              <span :class="changeClass(scope.row.frozenChange)">
161
+                {{ formatFundChange(scope.row.frozenChange, scope.row.frozenAfter, '冻结') }}
162
+              </span>
163
+            </template>
164
+          </el-table-column>
165
+          <el-table-column label="变动原因" align="center" width="110">
166
+            <template slot-scope="scope">
167
+              <span>{{ scope.row.changeReasonText || changeReasonLabel(scope.row.changeReason) }}</span>
168
+            </template>
169
+          </el-table-column>
170
+        </el-table>
171
+
172
+        <pagination v-show="logTotal > 0" :total="logTotal" :page.sync="logQuery.pageNum" :limit.sync="logQuery.pageSize" @pagination="getLogList" />
173
+      </div>
174
+    </el-drawer>
175
+  </div>
176
+</template>
177
+
178
+<script>
179
+import {
180
+  getPlatformFundOverviewSummary,
181
+  listPlatformFundOverviewShops,
182
+  listPlatformFundOverviewLogs
183
+} from "@/api/agri/finance/fundOverview"
184
+
185
+const CHANGE_REASON_MAP = {
186
+  "1": "订单收支",
187
+  "2": "余额提现",
188
+  "3": "提现驳回",
189
+  "4": "提现完成"
190
+}
191
+
192
+export default {
193
+  name: "AgriFinanceFundOverview",
194
+  data() {
195
+    return {
196
+      summaryLoading: false,
197
+      rankLoading: false,
198
+      logsLoading: false,
199
+      summary: {},
200
+      shopList: [],
201
+      logList: [],
202
+      rankTotal: 0,
203
+      logTotal: 0,
204
+      rankQuery: {
205
+        pageNum: 1,
206
+        pageSize: 10,
207
+        orderByColumn: "availableBalance",
208
+        isAsc: "desc"
209
+      },
210
+      defaultSort: {
211
+        prop: "availableBalance",
212
+        order: "descending"
213
+      },
214
+      logsOpen: false,
215
+      currentShop: null,
216
+      logQuery: {
217
+        pageNum: 1,
218
+        pageSize: 10,
219
+        shopId: undefined,
220
+        shopName: undefined,
221
+        beginChangeTime: undefined,
222
+        endChangeTime: undefined
223
+      },
224
+      logDateRange: [],
225
+      defaultLogShopId: undefined
226
+    }
227
+  },
228
+  computed: {
229
+    rankEmptyText() {
230
+      return "暂无店铺资金数据"
231
+    },
232
+    logsDrawerTitle() {
233
+      if (this.currentShop && this.currentShop.shopName) {
234
+        return `资金日志 · ${this.currentShop.shopName}`
235
+      }
236
+      return "资金日志"
237
+    },
238
+    logsEmptyText() {
239
+      const hasFilter = !!(this.logQuery.shopName || (this.logDateRange && this.logDateRange.length))
240
+      return hasFilter ? "未找到符合条件的资金变动记录" : "暂无资金变动记录"
241
+    }
242
+  },
243
+  created() {
244
+    this.loadSummary()
245
+    this.getShopList()
246
+  },
247
+  methods: {
248
+    /** 刷新总览与排行 */
249
+    refreshRank() {
250
+      this.loadSummary()
251
+      this.getShopList()
252
+    },
253
+    /** 查询全平台总览 */
254
+    loadSummary() {
255
+      this.summaryLoading = true
256
+      getPlatformFundOverviewSummary().then(response => {
257
+        this.summary = response.data || {}
258
+        this.summaryLoading = false
259
+      }).catch(() => {
260
+        this.summaryLoading = false
261
+      })
262
+    },
263
+    /** 查询店铺排行 */
264
+    getShopList() {
265
+      this.rankLoading = true
266
+      listPlatformFundOverviewShops(this.rankQuery).then(response => {
267
+        this.shopList = response.rows || []
268
+        this.rankTotal = response.total || 0
269
+        this.rankLoading = false
270
+      }).catch(() => {
271
+        this.rankLoading = false
272
+      })
273
+    },
274
+    /** 排行列排序 */
275
+    handleSortChange(column) {
276
+      if (!column.prop || !column.order) {
277
+        this.rankQuery.orderByColumn = "availableBalance"
278
+        this.rankQuery.isAsc = "desc"
279
+      } else {
280
+        this.rankQuery.orderByColumn = column.prop
281
+        this.rankQuery.isAsc = column.order === "ascending" ? "asc" : "desc"
282
+      }
283
+      this.rankQuery.pageNum = 1
284
+      this.getShopList()
285
+    },
286
+    /** 打开资金日志抽屉 */
287
+    openLogs(row) {
288
+      this.currentShop = row
289
+      this.defaultLogShopId = row.shopId
290
+      this.logQuery = {
291
+        pageNum: 1,
292
+        pageSize: 10,
293
+        shopId: row.shopId,
294
+        shopName: undefined,
295
+        beginChangeTime: undefined,
296
+        endChangeTime: undefined
297
+      }
298
+      this.logDateRange = []
299
+      this.logsOpen = true
300
+      this.getLogList()
301
+    },
302
+    /** 构建日志查询参数 */
303
+    buildLogQueryParams() {
304
+      const params = { ...this.logQuery }
305
+      if (this.logDateRange && this.logDateRange.length === 2) {
306
+        params.beginChangeTime = this.logDateRange[0]
307
+        params.endChangeTime = this.logDateRange[1]
308
+      } else {
309
+        params.beginChangeTime = undefined
310
+        params.endChangeTime = undefined
311
+      }
312
+      if (params.shopName && String(params.shopName).trim()) {
313
+        params.shopId = undefined
314
+      } else if (this.defaultLogShopId != null && !params.shopName) {
315
+        params.shopId = this.defaultLogShopId
316
+      }
317
+      return params
318
+    },
319
+    /** 查询资金日志 */
320
+    getLogList() {
321
+      this.logsLoading = true
322
+      listPlatformFundOverviewLogs(this.buildLogQueryParams()).then(response => {
323
+        this.logList = response.rows || []
324
+        this.logTotal = response.total || 0
325
+        this.logsLoading = false
326
+      }).catch(() => {
327
+        this.logsLoading = false
328
+      })
329
+    },
330
+    handleLogQuery() {
331
+      this.logQuery.pageNum = 1
332
+      this.getLogList()
333
+    },
334
+    /** 重置日志检索:恢复默认店铺范围 */
335
+    resetLogQuery() {
336
+      this.logDateRange = []
337
+      this.logQuery.shopName = undefined
338
+      this.logQuery.shopId = this.defaultLogShopId
339
+      this.logQuery.pageNum = 1
340
+      this.logQuery.beginChangeTime = undefined
341
+      this.logQuery.endChangeTime = undefined
342
+      if (this.$refs.logQueryForm) {
343
+        this.resetForm("logQueryForm")
344
+      }
345
+      this.getLogList()
346
+    },
347
+    resetLogs() {
348
+      this.currentShop = null
349
+      this.defaultLogShopId = undefined
350
+      this.logList = []
351
+      this.logTotal = 0
352
+      this.logDateRange = []
353
+      this.logQuery = {
354
+        pageNum: 1,
355
+        pageSize: 10,
356
+        shopId: undefined,
357
+        shopName: undefined,
358
+        beginChangeTime: undefined,
359
+        endChangeTime: undefined
360
+      }
361
+      this.logsLoading = false
362
+    },
363
+    /** 店铺状态文案 */
364
+    shopStatusText(row) {
365
+      if (row.deleted) {
366
+        return "已删除"
367
+      }
368
+      return row.shopStatusText || (row.shopStatus === "0" ? "开业" : row.shopStatus === "1" ? "停业" : "—")
369
+    },
370
+    shopStatusTag(row) {
371
+      if (row.deleted) {
372
+        return "info"
373
+      }
374
+      return row.shopStatus === "0" ? "success" : "info"
375
+    },
376
+    /** 金额格式化 */
377
+    formatMoney(val) {
378
+      if (val == null || val === "") {
379
+        return "—"
380
+      }
381
+      const num = Number(val)
382
+      if (isNaN(num)) {
383
+        return "—"
384
+      }
385
+      const fixed = num.toFixed(2)
386
+      const parts = fixed.split(".")
387
+      parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",")
388
+      return "¥" + parts.join(".")
389
+    },
390
+    formatAfterAmount(val) {
391
+      if (val == null || val === "") {
392
+        return "—"
393
+      }
394
+      const num = Number(val)
395
+      if (isNaN(num)) {
396
+        return "—"
397
+      }
398
+      const fixed = num.toFixed(2)
399
+      const parts = fixed.split(".")
400
+      parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",")
401
+      return parts.join(".")
402
+    },
403
+    formatChangeValue(change) {
404
+      if (change == null || change === "") {
405
+        return "0"
406
+      }
407
+      const num = Number(change)
408
+      if (isNaN(num) || num === 0) {
409
+        return "0"
410
+      }
411
+      const sign = num > 0 ? "+" : ""
412
+      return sign + num.toFixed(2)
413
+    },
414
+    formatFundChange(change, after, label) {
415
+      const changeStr = this.formatChangeValue(change)
416
+      const afterStr = this.formatAfterAmount(after)
417
+      return `${changeStr}(${label}:${afterStr})`
418
+    },
419
+    changeClass(change) {
420
+      if (change == null || change === "" || Number(change) === 0) {
421
+        return ""
422
+      }
423
+      return Number(change) > 0 ? "change-in" : "change-out"
424
+    },
425
+    changeReasonLabel(reason) {
426
+      return CHANGE_REASON_MAP[reason] || "—"
427
+    }
428
+  }
429
+}
430
+</script>
431
+
432
+<style scoped lang="scss">
433
+.card-header {
434
+  display: flex;
435
+  align-items: center;
436
+  justify-content: space-between;
437
+}
438
+.card-title {
439
+  font-weight: 600;
440
+  font-size: 15px;
441
+}
442
+.stats-row {
443
+  margin-bottom: 4px;
444
+}
445
+.stat-block {
446
+  padding: 16px;
447
+  border: 1px solid #ebeef5;
448
+  border-radius: 4px;
449
+  min-height: 120px;
450
+  background: #fafafa;
451
+  margin-bottom: 12px;
452
+}
453
+.stat-block-primary {
454
+  background: #f0f9eb;
455
+  border-color: #e1f3d8;
456
+}
457
+.stat-value {
458
+  font-size: 28px;
459
+  font-weight: 600;
460
+  color: #303133;
461
+  line-height: 1.2;
462
+  margin-bottom: 8px;
463
+}
464
+.stat-label {
465
+  font-size: 14px;
466
+  color: #606266;
467
+  margin-bottom: 4px;
468
+}
469
+.stat-tip {
470
+  font-size: 12px;
471
+  color: #909399;
472
+  line-height: 1.5;
473
+}
474
+.mb8 {
475
+  margin-bottom: 8px;
476
+}
477
+.shop-cell {
478
+  display: flex;
479
+  align-items: center;
480
+  gap: 8px;
481
+}
482
+.shop-name {
483
+  flex: 1;
484
+  min-width: 0;
485
+  line-height: 1.4;
486
+}
487
+.logs-panel {
488
+  padding: 0 8px 16px;
489
+}
490
+.logs-search {
491
+  margin-bottom: 12px;
492
+}
493
+.change-in {
494
+  color: #67c23a;
495
+}
496
+.change-out {
497
+  color: #f56c6c;
498
+}
499
+</style>

+ 377 - 0
ruoyi-ui/src/views/agri/finance/withdrawAudit/index.vue

@@ -0,0 +1,377 @@
1
+<template>
2
+  <div class="app-container">
3
+    <!-- 检索区 -->
4
+    <el-card shadow="never" class="search-card">
5
+      <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="90px">
6
+        <el-form-item label="申请时间">
7
+          <el-date-picker
8
+            v-model="dateRange"
9
+            type="daterange"
10
+            value-format="yyyy-MM-dd"
11
+            range-separator="至"
12
+            start-placeholder="开始日期"
13
+            end-placeholder="结束日期"
14
+            style="width: 240px"
15
+          />
16
+        </el-form-item>
17
+        <el-form-item label="提现状态" prop="withdrawStatus">
18
+          <el-select v-model="queryParams.withdrawStatus" placeholder="全部" clearable style="width: 140px">
19
+            <el-option v-for="item in withdrawStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
20
+          </el-select>
21
+        </el-form-item>
22
+        <el-form-item>
23
+          <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
24
+          <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
25
+        </el-form-item>
26
+      </el-form>
27
+    </el-card>
28
+
29
+    <br/>
30
+
31
+    <!-- 列表区 -->
32
+    <el-card shadow="never" class="table-card">
33
+      <el-tabs v-model="statusTab" @tab-click="handleTabClick">
34
+        <el-tab-pane label="全部" name="all" />
35
+        <el-tab-pane name="1">
36
+          <span slot="label">待审核<el-badge v-if="pendingCount > 0" :value="pendingCount" class="tab-badge" /></span>
37
+        </el-tab-pane>
38
+        <el-tab-pane label="审核不通过" name="2" />
39
+        <el-tab-pane label="提现完成" name="3" />
40
+      </el-tabs>
41
+
42
+      <el-row :gutter="10" class="mb8">
43
+        <right-toolbar :showSearch.sync="showSearch" @queryTable="refreshList"></right-toolbar>
44
+      </el-row>
45
+
46
+      <el-table border v-loading="loading" :data="withdrawList" :empty-text="emptyTableText">
47
+        <el-table-column label="提现编号" align="center" prop="withdrawNo" min-width="170" :show-overflow-tooltip="true">
48
+          <template slot-scope="scope">
49
+            <span>{{ scope.row.withdrawNo || '—' }}</span>
50
+          </template>
51
+        </el-table-column>
52
+        <el-table-column label="申请时间" align="center" width="160">
53
+          <template slot-scope="scope">
54
+            <span>{{ parseTime(scope.row.applyTime) || '—' }}</span>
55
+          </template>
56
+        </el-table-column>
57
+        <el-table-column label="店铺名称" align="center" prop="shopName" min-width="120" :show-overflow-tooltip="true">
58
+          <template slot-scope="scope">
59
+            <span>{{ scope.row.shopName || '—' }}</span>
60
+          </template>
61
+        </el-table-column>
62
+        <el-table-column label="提现账号" align="center" min-width="180" :show-overflow-tooltip="true">
63
+          <template slot-scope="scope">
64
+            <span>{{ accountSummaryText(scope.row) }}</span>
65
+          </template>
66
+        </el-table-column>
67
+        <el-table-column label="提现金额" align="center" width="110">
68
+          <template slot-scope="scope">
69
+            <span>{{ formatMoney(scope.row.withdrawAmount) }}</span>
70
+          </template>
71
+        </el-table-column>
72
+        <el-table-column label="备注" align="center" min-width="100" :show-overflow-tooltip="true">
73
+          <template slot-scope="scope">
74
+            <span>{{ scope.row.remark || '—' }}</span>
75
+          </template>
76
+        </el-table-column>
77
+        <el-table-column label="提现状态" align="center" width="110">
78
+          <template slot-scope="scope">
79
+            <el-tag size="small" :type="withdrawStatusTag(scope.row.withdrawStatus)">
80
+              {{ scope.row.withdrawStatusText || withdrawStatusLabel(scope.row.withdrawStatus) }}
81
+            </el-tag>
82
+          </template>
83
+        </el-table-column>
84
+        <el-table-column label="提现处理说明" align="center" min-width="140" :show-overflow-tooltip="true">
85
+          <template slot-scope="scope">
86
+            <span>{{ processRemarkText(scope.row) }}</span>
87
+          </template>
88
+        </el-table-column>
89
+        <el-table-column label="操作" align="center" width="90" fixed="right">
90
+          <template slot-scope="scope">
91
+            <el-button
92
+              v-if="scope.row.withdrawStatus === '1'"
93
+              size="mini"
94
+              type="text"
95
+              icon="el-icon-edit-outline"
96
+              @click="openAudit(scope.row)"
97
+              v-hasPermi="['agri:finance:withdrawAudit:audit']"
98
+            >审核</el-button>
99
+            <span v-else>—</span>
100
+          </template>
101
+        </el-table-column>
102
+      </el-table>
103
+
104
+      <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
105
+    </el-card>
106
+
107
+    <!-- 审核弹窗 -->
108
+    <el-dialog title="提现审核" :visible.sync="auditOpen" width="560px" append-to-body @close="cancelAudit">
109
+      <div v-if="currentRow" class="audit-brief">
110
+        <el-descriptions :column="1" border size="small">
111
+          <el-descriptions-item label="提现编号">{{ currentRow.withdrawNo || '—' }}</el-descriptions-item>
112
+          <el-descriptions-item label="店铺名称">{{ currentRow.shopName || '—' }}</el-descriptions-item>
113
+          <el-descriptions-item label="提现账号">{{ accountSummaryText(currentRow) }}</el-descriptions-item>
114
+          <el-descriptions-item label="提现金额">{{ formatMoney(currentRow.withdrawAmount) }}</el-descriptions-item>
115
+          <el-descriptions-item label="商家备注">{{ currentRow.remark || '—' }}</el-descriptions-item>
116
+        </el-descriptions>
117
+      </div>
118
+      <el-form ref="auditForm" :model="auditForm" :rules="auditRules" label-width="110px" size="small" class="audit-form">
119
+        <el-form-item label="审核结果" prop="auditResult">
120
+          <el-radio-group v-model="auditForm.auditResult">
121
+            <el-radio label="approve">审核通过(提现完成)</el-radio>
122
+            <el-radio label="reject">审核驳回</el-radio>
123
+          </el-radio-group>
124
+        </el-form-item>
125
+        <el-form-item label="提现处理说明" prop="processRemark">
126
+          <el-input
127
+            v-model="auditForm.processRemark"
128
+            type="textarea"
129
+            :rows="4"
130
+            placeholder="请输入处理说明"
131
+            maxlength="200"
132
+            show-word-limit
133
+          />
134
+        </el-form-item>
135
+      </el-form>
136
+      <div slot="footer" class="dialog-footer">
137
+        <el-button type="primary" :loading="auditSubmitting" @click="submitAudit">确 定</el-button>
138
+        <el-button @click="cancelAudit">取 消</el-button>
139
+      </div>
140
+    </el-dialog>
141
+  </div>
142
+</template>
143
+
144
+<script>
145
+import {
146
+  listPlatformWithdrawAudits,
147
+  getPlatformWithdrawAuditPendingCount,
148
+  approvePlatformWithdrawAudit,
149
+  rejectPlatformWithdrawAudit
150
+} from "@/api/agri/finance/withdrawAudit"
151
+
152
+const WITHDRAW_STATUS_MAP = {
153
+  "1": "待审核",
154
+  "2": "审核不通过",
155
+  "3": "提现完成"
156
+}
157
+
158
+const ACCOUNT_TYPE_MAP = {
159
+  "1": "银行卡",
160
+  "2": "支付宝",
161
+  "3": "微信"
162
+}
163
+
164
+export default {
165
+  name: "AgriFinanceWithdrawAudit",
166
+  data() {
167
+    return {
168
+      loading: false,
169
+      showSearch: true,
170
+      total: 0,
171
+      pendingCount: 0,
172
+      withdrawList: [],
173
+      dateRange: [],
174
+      statusTab: "1",
175
+      queryParams: {
176
+        pageNum: 1,
177
+        pageSize: 10,
178
+        withdrawStatus: "1",
179
+        beginApplyTime: undefined,
180
+        endApplyTime: undefined
181
+      },
182
+      withdrawStatusOptions: [
183
+        { value: "1", label: "待审核" },
184
+        { value: "2", label: "审核不通过" },
185
+        { value: "3", label: "提现完成" }
186
+      ],
187
+      auditOpen: false,
188
+      auditSubmitting: false,
189
+      currentRow: null,
190
+      auditForm: {
191
+        auditResult: "approve",
192
+        processRemark: undefined
193
+      },
194
+      auditRules: {
195
+        auditResult: [{ required: true, message: "请选择审核结果", trigger: "change" }],
196
+        processRemark: [{ required: true, message: "请填写提现处理说明", trigger: "blur" }]
197
+      }
198
+    }
199
+  },
200
+  computed: {
201
+    hasSearchFilter() {
202
+      return !!(this.queryParams.withdrawStatus || (this.dateRange && this.dateRange.length))
203
+    },
204
+    emptyTableText() {
205
+      return this.hasSearchFilter ? "未找到符合条件的提现申请" : "暂无提现申请"
206
+    }
207
+  },
208
+  created() {
209
+    this.loadPendingCount()
210
+    this.getList()
211
+  },
212
+  methods: {
213
+    /** 刷新列表与待审数 */
214
+    refreshList() {
215
+      this.loadPendingCount()
216
+      this.getList()
217
+    },
218
+    /** 待审核数量 */
219
+    loadPendingCount() {
220
+      getPlatformWithdrawAuditPendingCount().then(response => {
221
+        this.pendingCount = (response.data && response.data.pendingCount) || 0
222
+      }).catch(() => {})
223
+    },
224
+    /** 构建查询参数 */
225
+    buildQueryParams() {
226
+      const params = { ...this.queryParams }
227
+      if (this.dateRange && this.dateRange.length === 2) {
228
+        params.beginApplyTime = this.dateRange[0]
229
+        params.endApplyTime = this.dateRange[1]
230
+      } else {
231
+        params.beginApplyTime = undefined
232
+        params.endApplyTime = undefined
233
+      }
234
+      return params
235
+    },
236
+    /** 查询列表 */
237
+    getList() {
238
+      this.loading = true
239
+      listPlatformWithdrawAudits(this.buildQueryParams()).then(response => {
240
+        this.withdrawList = response.rows || []
241
+        this.total = response.total || 0
242
+        this.loading = false
243
+      }).catch(() => {
244
+        this.loading = false
245
+      })
246
+    },
247
+    handleTabClick() {
248
+      this.queryParams.withdrawStatus = this.statusTab === "all" ? undefined : this.statusTab
249
+      this.queryParams.pageNum = 1
250
+      this.getList()
251
+    },
252
+    handleQuery() {
253
+      this.queryParams.pageNum = 1
254
+      if (this.queryParams.withdrawStatus) {
255
+        this.statusTab = this.queryParams.withdrawStatus
256
+      } else {
257
+        this.statusTab = "all"
258
+      }
259
+      this.getList()
260
+    },
261
+    resetQuery() {
262
+      this.dateRange = []
263
+      this.statusTab = "1"
264
+      this.resetForm("queryForm")
265
+      this.queryParams.pageNum = 1
266
+      this.queryParams.withdrawStatus = "1"
267
+      this.queryParams.beginApplyTime = undefined
268
+      this.queryParams.endApplyTime = undefined
269
+      this.getList()
270
+    },
271
+    /** 打开审核弹窗 */
272
+    openAudit(row) {
273
+      this.currentRow = row
274
+      this.auditForm = {
275
+        auditResult: "approve",
276
+        processRemark: undefined
277
+      }
278
+      this.auditOpen = true
279
+      this.$nextTick(() => {
280
+        if (this.$refs.auditForm) {
281
+          this.$refs.auditForm.clearValidate()
282
+        }
283
+      })
284
+    },
285
+    /** 提交审核 */
286
+    submitAudit() {
287
+      if (!this.currentRow || !this.currentRow.withdrawId) {
288
+        return
289
+      }
290
+      this.$refs.auditForm.validate(valid => {
291
+        if (!valid) {
292
+          return
293
+        }
294
+        const remark = String(this.auditForm.processRemark).trim()
295
+        this.auditSubmitting = true
296
+        const request = this.auditForm.auditResult === "approve"
297
+          ? approvePlatformWithdrawAudit(this.currentRow.withdrawId, { processRemark: remark })
298
+          : rejectPlatformWithdrawAudit({
299
+            withdrawId: this.currentRow.withdrawId,
300
+            processRemark: remark
301
+          })
302
+        request.then(() => {
303
+          this.$modal.msgSuccess("操作成功")
304
+          this.auditOpen = false
305
+          this.auditSubmitting = false
306
+          this.refreshList()
307
+        }).catch(() => {
308
+          this.auditSubmitting = false
309
+        })
310
+      })
311
+    },
312
+    cancelAudit() {
313
+      this.auditOpen = false
314
+      this.currentRow = null
315
+      this.auditSubmitting = false
316
+      this.auditForm = {
317
+        auditResult: "approve",
318
+        processRemark: undefined
319
+      }
320
+      if (this.$refs.auditForm) {
321
+        this.resetForm("auditForm")
322
+      }
323
+    },
324
+    formatMoney(val) {
325
+      if (val == null || val === "") {
326
+        return "—"
327
+      }
328
+      const num = Number(val)
329
+      if (isNaN(num)) {
330
+        return "—"
331
+      }
332
+      const fixed = num.toFixed(2)
333
+      const parts = fixed.split(".")
334
+      parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",")
335
+      return "¥" + parts.join(".")
336
+    },
337
+    accountSummaryText(row) {
338
+      if (row.accountSummary) {
339
+        return row.accountSummary
340
+      }
341
+      const type = row.accountTypeText || ACCOUNT_TYPE_MAP[row.accountType] || ""
342
+      const mask = row.accountNoMask || ""
343
+      const name = row.accountRealName || ""
344
+      const text = [type, mask, name].filter(Boolean).join(" ")
345
+      return text || "—"
346
+    },
347
+    processRemarkText(row) {
348
+      if (row.withdrawStatus === "1") {
349
+        return "—"
350
+      }
351
+      return row.processRemark || "—"
352
+    },
353
+    withdrawStatusLabel(status) {
354
+      return WITHDRAW_STATUS_MAP[status] || "—"
355
+    },
356
+    withdrawStatusTag(status) {
357
+      const map = { "1": "warning", "2": "danger", "3": "success" }
358
+      return map[status] || "info"
359
+    }
360
+  }
361
+}
362
+</script>
363
+
364
+<style scoped lang="scss">
365
+.mb8 {
366
+  margin-bottom: 8px;
367
+}
368
+.tab-badge {
369
+  margin-left: 6px;
370
+}
371
+.audit-brief {
372
+  margin-bottom: 16px;
373
+}
374
+.audit-form {
375
+  margin-top: 8px;
376
+}
377
+</style>

+ 399 - 0
ruoyi-ui/src/views/agri/finance/withdrawSummary/index.vue

@@ -0,0 +1,399 @@
1
+<template>
2
+  <div class="app-container">
3
+    <!-- 检索区 -->
4
+    <el-card shadow="never" class="search-card">
5
+      <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
6
+        <el-form-item label="店铺名称" prop="shopName">
7
+          <el-input
8
+            v-model="queryParams.shopName"
9
+            placeholder="请输入店铺名称"
10
+            clearable
11
+            style="width: 220px"
12
+            @keyup.enter.native="handleQuery"
13
+          />
14
+        </el-form-item>
15
+        <el-form-item>
16
+          <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
17
+          <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
18
+        </el-form-item>
19
+      </el-form>
20
+    </el-card>
21
+
22
+    <br/>
23
+
24
+    <!-- 汇总列表 -->
25
+    <el-card shadow="never" class="table-card">
26
+      <div slot="header" class="card-header">
27
+        <span class="card-title">店铺提现汇总</span>
28
+      </div>
29
+
30
+      <el-row :gutter="10" class="mb8">
31
+        <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
32
+      </el-row>
33
+
34
+      <el-table border v-loading="loading" :data="shopList" :empty-text="emptyTableText">
35
+        <el-table-column label="店铺名称" align="left" min-width="180" :show-overflow-tooltip="true">
36
+          <template slot-scope="scope">
37
+            <div class="shop-cell">
38
+              <image-preview v-if="scope.row.shopAvatar" :src="scope.row.shopAvatar" :width="44" :height="44" />
39
+              <span class="shop-name">{{ scope.row.shopName || '—' }}</span>
40
+            </div>
41
+          </template>
42
+        </el-table-column>
43
+        <el-table-column label="店铺状态" align="center" width="100">
44
+          <template slot-scope="scope">
45
+            <el-tag size="small" :type="shopStatusTag(scope.row)">
46
+              {{ shopStatusText(scope.row) }}
47
+            </el-tag>
48
+          </template>
49
+        </el-table-column>
50
+        <el-table-column label="已提现金额" align="center" width="130">
51
+          <template slot-scope="scope">
52
+            <span>{{ formatMoney(scope.row.withdrawnAmount) }}</span>
53
+          </template>
54
+        </el-table-column>
55
+        <el-table-column label="待审核金额" align="center" width="130">
56
+          <template slot-scope="scope">
57
+            <span :class="{ 'amount-pending': Number(scope.row.pendingAuditAmount) > 0 }">
58
+              {{ formatMoney(scope.row.pendingAuditAmount) }}
59
+            </span>
60
+          </template>
61
+        </el-table-column>
62
+        <el-table-column label="操作" align="center" width="120" fixed="right">
63
+          <template slot-scope="scope">
64
+            <el-button size="mini" type="text" @click="openDetails(scope.row)" v-hasPermi="['agri:finance:withdrawSummary:detail']">查看提现明细</el-button>
65
+          </template>
66
+        </el-table-column>
67
+      </el-table>
68
+
69
+      <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
70
+    </el-card>
71
+
72
+    <!-- 提现明细抽屉 -->
73
+    <el-drawer
74
+      :title="detailDrawerTitle"
75
+      :visible.sync="detailOpen"
76
+      size="72%"
77
+      append-to-body
78
+      @close="resetDetails"
79
+    >
80
+      <div class="detail-panel">
81
+        <el-form :model="detailQuery" ref="detailQueryForm" size="small" :inline="true" label-width="80px" class="detail-search">
82
+          <el-form-item label="申请时间">
83
+            <el-date-picker
84
+              v-model="detailDateRange"
85
+              type="daterange"
86
+              value-format="yyyy-MM-dd"
87
+              range-separator="至"
88
+              start-placeholder="开始日期"
89
+              end-placeholder="结束日期"
90
+              style="width: 240px"
91
+            />
92
+          </el-form-item>
93
+          <el-form-item label="提现状态" prop="withdrawStatus">
94
+            <el-select v-model="detailQuery.withdrawStatus" placeholder="全部" clearable style="width: 140px">
95
+              <el-option v-for="item in withdrawStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
96
+            </el-select>
97
+          </el-form-item>
98
+          <el-form-item>
99
+            <el-button type="primary" icon="el-icon-search" size="mini" @click="handleDetailQuery">搜索</el-button>
100
+            <el-button icon="el-icon-refresh" size="mini" @click="resetDetailQuery">重置</el-button>
101
+          </el-form-item>
102
+        </el-form>
103
+
104
+        <el-table border v-loading="detailLoading" :data="detailList" :empty-text="detailEmptyText">
105
+          <el-table-column label="申请时间" align="center" width="160">
106
+            <template slot-scope="scope">
107
+              <span>{{ parseTime(scope.row.applyTime) || '—' }}</span>
108
+            </template>
109
+          </el-table-column>
110
+          <el-table-column label="店铺名称" align="center" prop="shopName" min-width="120" :show-overflow-tooltip="true">
111
+            <template slot-scope="scope">
112
+              <span>{{ scope.row.shopName || '—' }}</span>
113
+            </template>
114
+          </el-table-column>
115
+          <el-table-column label="提现账号" align="center" min-width="180" :show-overflow-tooltip="true">
116
+            <template slot-scope="scope">
117
+              <span>{{ accountSummaryText(scope.row) }}</span>
118
+            </template>
119
+          </el-table-column>
120
+          <el-table-column label="提现金额" align="center" width="110">
121
+            <template slot-scope="scope">
122
+              <span>{{ formatMoney(scope.row.withdrawAmount) }}</span>
123
+            </template>
124
+          </el-table-column>
125
+          <el-table-column label="备注" align="center" min-width="100" :show-overflow-tooltip="true">
126
+            <template slot-scope="scope">
127
+              <span>{{ scope.row.remark || '—' }}</span>
128
+            </template>
129
+          </el-table-column>
130
+          <el-table-column label="提现状态" align="center" width="110">
131
+            <template slot-scope="scope">
132
+              <el-tag size="small" :type="withdrawStatusTag(scope.row.withdrawStatus)">
133
+                {{ scope.row.withdrawStatusText || withdrawStatusLabel(scope.row.withdrawStatus) }}
134
+              </el-tag>
135
+            </template>
136
+          </el-table-column>
137
+          <el-table-column label="提现处理说明" align="center" min-width="140" :show-overflow-tooltip="true">
138
+            <template slot-scope="scope">
139
+              <span>{{ processRemarkText(scope.row) }}</span>
140
+            </template>
141
+          </el-table-column>
142
+        </el-table>
143
+
144
+        <pagination v-show="detailTotal > 0" :total="detailTotal" :page.sync="detailQuery.pageNum" :limit.sync="detailQuery.pageSize" @pagination="getDetailList" />
145
+      </div>
146
+    </el-drawer>
147
+  </div>
148
+</template>
149
+
150
+<script>
151
+import {
152
+  listPlatformWithdrawSummaryShops,
153
+  listPlatformWithdrawSummaryDetails
154
+} from "@/api/agri/finance/withdrawSummary"
155
+
156
+const WITHDRAW_STATUS_MAP = {
157
+  "1": "待审核",
158
+  "2": "审核不通过",
159
+  "3": "提现完成"
160
+}
161
+
162
+const ACCOUNT_TYPE_MAP = {
163
+  "1": "银行卡",
164
+  "2": "支付宝",
165
+  "3": "微信"
166
+}
167
+
168
+export default {
169
+  name: "AgriFinanceWithdrawSummary",
170
+  data() {
171
+    return {
172
+      loading: false,
173
+      detailLoading: false,
174
+      showSearch: true,
175
+      total: 0,
176
+      detailTotal: 0,
177
+      shopList: [],
178
+      detailList: [],
179
+      queryParams: {
180
+        pageNum: 1,
181
+        pageSize: 10,
182
+        shopName: undefined
183
+      },
184
+      detailOpen: false,
185
+      currentShop: null,
186
+      detailQuery: {
187
+        pageNum: 1,
188
+        pageSize: 10,
189
+        shopId: undefined,
190
+        withdrawStatus: undefined,
191
+        beginApplyTime: undefined,
192
+        endApplyTime: undefined
193
+      },
194
+      detailDateRange: [],
195
+      withdrawStatusOptions: [
196
+        { value: "1", label: "待审核" },
197
+        { value: "2", label: "审核不通过" },
198
+        { value: "3", label: "提现完成" }
199
+      ]
200
+    }
201
+  },
202
+  computed: {
203
+    emptyTableText() {
204
+      return this.queryParams.shopName ? "未找到符合条件的店铺" : "暂无店铺数据"
205
+    },
206
+    detailDrawerTitle() {
207
+      if (this.currentShop && this.currentShop.shopName) {
208
+        return `提现明细 · ${this.currentShop.shopName}`
209
+      }
210
+      return "提现明细"
211
+    },
212
+    detailEmptyText() {
213
+      const hasFilter = !!(this.detailQuery.withdrawStatus || (this.detailDateRange && this.detailDateRange.length))
214
+      return hasFilter ? "未找到符合条件的提现记录" : "暂无提现记录"
215
+    }
216
+  },
217
+  created() {
218
+    this.getList()
219
+  },
220
+  methods: {
221
+    /** 查询汇总列表 */
222
+    getList() {
223
+      this.loading = true
224
+      listPlatformWithdrawSummaryShops(this.queryParams).then(response => {
225
+        this.shopList = response.rows || []
226
+        this.total = response.total || 0
227
+        this.loading = false
228
+      }).catch(() => {
229
+        this.loading = false
230
+      })
231
+    },
232
+    handleQuery() {
233
+      this.queryParams.pageNum = 1
234
+      this.getList()
235
+    },
236
+    resetQuery() {
237
+      this.resetForm("queryForm")
238
+      this.queryParams.pageNum = 1
239
+      this.queryParams.shopName = undefined
240
+      this.getList()
241
+    },
242
+    /** 打开提现明细抽屉 */
243
+    openDetails(row) {
244
+      this.currentShop = row
245
+      this.detailQuery = {
246
+        pageNum: 1,
247
+        pageSize: 10,
248
+        shopId: row.shopId,
249
+        withdrawStatus: undefined,
250
+        beginApplyTime: undefined,
251
+        endApplyTime: undefined
252
+      }
253
+      this.detailDateRange = []
254
+      this.detailOpen = true
255
+      this.getDetailList()
256
+    },
257
+    /** 构建明细查询参数 */
258
+    buildDetailQueryParams() {
259
+      const params = { ...this.detailQuery }
260
+      if (this.detailDateRange && this.detailDateRange.length === 2) {
261
+        params.beginApplyTime = this.detailDateRange[0]
262
+        params.endApplyTime = this.detailDateRange[1]
263
+      } else {
264
+        params.beginApplyTime = undefined
265
+        params.endApplyTime = undefined
266
+      }
267
+      return params
268
+    },
269
+    /** 查询提现明细 */
270
+    getDetailList() {
271
+      if (!this.detailQuery.shopId) {
272
+        return
273
+      }
274
+      this.detailLoading = true
275
+      listPlatformWithdrawSummaryDetails(this.buildDetailQueryParams()).then(response => {
276
+        this.detailList = response.rows || []
277
+        this.detailTotal = response.total || 0
278
+        this.detailLoading = false
279
+      }).catch(() => {
280
+        this.detailLoading = false
281
+      })
282
+    },
283
+    handleDetailQuery() {
284
+      this.detailQuery.pageNum = 1
285
+      this.getDetailList()
286
+    },
287
+    /** 重置明细检索 */
288
+    resetDetailQuery() {
289
+      this.detailDateRange = []
290
+      this.detailQuery.withdrawStatus = undefined
291
+      this.detailQuery.pageNum = 1
292
+      this.detailQuery.beginApplyTime = undefined
293
+      this.detailQuery.endApplyTime = undefined
294
+      if (this.$refs.detailQueryForm) {
295
+        this.resetForm("detailQueryForm")
296
+      }
297
+      this.getDetailList()
298
+    },
299
+    resetDetails() {
300
+      this.currentShop = null
301
+      this.detailList = []
302
+      this.detailTotal = 0
303
+      this.detailDateRange = []
304
+      this.detailQuery = {
305
+        pageNum: 1,
306
+        pageSize: 10,
307
+        shopId: undefined,
308
+        withdrawStatus: undefined,
309
+        beginApplyTime: undefined,
310
+        endApplyTime: undefined
311
+      }
312
+      this.detailLoading = false
313
+    },
314
+    shopStatusText(row) {
315
+      if (row.deleted) {
316
+        return "已删除"
317
+      }
318
+      return row.shopStatusText || (row.shopStatus === "0" ? "开业" : row.shopStatus === "1" ? "停业" : "—")
319
+    },
320
+    shopStatusTag(row) {
321
+      if (row.deleted) {
322
+        return "info"
323
+      }
324
+      return row.shopStatus === "0" ? "success" : "info"
325
+    },
326
+    formatMoney(val) {
327
+      if (val == null || val === "") {
328
+        return "¥0.00"
329
+      }
330
+      const num = Number(val)
331
+      if (isNaN(num)) {
332
+        return "—"
333
+      }
334
+      const fixed = num.toFixed(2)
335
+      const parts = fixed.split(".")
336
+      parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",")
337
+      return "¥" + parts.join(".")
338
+    },
339
+    accountSummaryText(row) {
340
+      if (row.accountSummary) {
341
+        return row.accountSummary
342
+      }
343
+      const type = row.accountTypeText || ACCOUNT_TYPE_MAP[row.accountType] || ""
344
+      const mask = row.accountNoMask || ""
345
+      const name = row.accountRealName || ""
346
+      const text = [type, mask, name].filter(Boolean).join(" ")
347
+      return text || "—"
348
+    },
349
+    processRemarkText(row) {
350
+      if (row.withdrawStatus === "1") {
351
+        return "—"
352
+      }
353
+      return row.processRemark || "—"
354
+    },
355
+    withdrawStatusLabel(status) {
356
+      return WITHDRAW_STATUS_MAP[status] || "—"
357
+    },
358
+    withdrawStatusTag(status) {
359
+      const map = { "1": "warning", "2": "danger", "3": "success" }
360
+      return map[status] || "info"
361
+    }
362
+  }
363
+}
364
+</script>
365
+
366
+<style scoped lang="scss">
367
+.card-header {
368
+  display: flex;
369
+  align-items: center;
370
+  justify-content: space-between;
371
+}
372
+.card-title {
373
+  font-weight: 600;
374
+  font-size: 15px;
375
+}
376
+.mb8 {
377
+  margin-bottom: 8px;
378
+}
379
+.shop-cell {
380
+  display: flex;
381
+  align-items: center;
382
+  gap: 8px;
383
+}
384
+.shop-name {
385
+  flex: 1;
386
+  min-width: 0;
387
+  line-height: 1.4;
388
+}
389
+.amount-pending {
390
+  color: #e6a23c;
391
+  font-weight: 500;
392
+}
393
+.detail-panel {
394
+  padding: 0 8px 16px;
395
+}
396
+.detail-search {
397
+  margin-bottom: 12px;
398
+}
399
+</style>