wallet-flow.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. <style scoped lang="scss">
  2. .system-container {
  3. :deep(.el-card__body) {
  4. display: flex;
  5. flex-direction: column;
  6. justify-content: space-between;
  7. flex: 1;
  8. overflow: auto;
  9. .el-table {
  10. flex: 1;
  11. }
  12. }
  13. }
  14. .page-content {
  15. margin-bottom: 20px;
  16. }
  17. .page-pager {
  18. background-color: var(--el-color-white);
  19. height: 24px;
  20. }
  21. .wd120 {
  22. width: 120px;
  23. }
  24. .wd150 {
  25. width: 150px;
  26. }
  27. </style>
  28. <template>
  29. <div class="system-container layout-padding">
  30. <el-card shadow="hover" class="layout-padding-auto">
  31. <el-form
  32. :model="state.formQuery"
  33. ref="queryRef"
  34. size="default" label-width="0px" class="mt5 mb5">
  35. <el-input
  36. v-model="state.formQuery.mobilePhone"
  37. placeholder="用户手机号"
  38. clearable
  39. @blur="loadData(true)"
  40. class="wd150 mr10">
  41. </el-input>
  42. <el-select
  43. v-model="state.formQuery.type"
  44. placeholder="交易类型"
  45. clearable
  46. class="wd120 mr10"
  47. @change="loadData(true)">
  48. <el-option label="全部" :value="undefined" />
  49. <el-option label="充值" :value="1" />
  50. <el-option label="退款" :value="2" />
  51. <el-option label="消费" :value="3" />
  52. </el-select>
  53. <el-select
  54. v-model="state.formQuery.status"
  55. placeholder="状态"
  56. clearable
  57. class="wd120 mr10"
  58. @change="loadData(true)">
  59. <el-option label="全部" :value="undefined" />
  60. <el-option label="待确认" :value="0" />
  61. <el-option label="已确认" :value="1" />
  62. <el-option label="已取消" :value="2" />
  63. </el-select>
  64. <el-date-picker
  65. v-model="state.formQuery.startDate"
  66. type="date"
  67. placeholder="开始日期"
  68. value-format="YYYY-MM-DD"
  69. class="wd150 mr10"
  70. @change="loadData(true)">
  71. </el-date-picker>
  72. <el-date-picker
  73. v-model="state.formQuery.endDate"
  74. type="date"
  75. placeholder="结束日期"
  76. value-format="YYYY-MM-DD"
  77. class="wd150 mr10"
  78. @change="loadData(true)">
  79. </el-date-picker>
  80. <el-button class="ml10" plain size="default" type="success" @click="loadData(true)">
  81. <SvgIcon name="ele-Search"/>
  82. 查询
  83. </el-button>
  84. </el-form>
  85. <el-table
  86. border
  87. stripe="stripe"
  88. :height="state.tableData.height"
  89. highlight-current-row
  90. current-row-key="id"
  91. row-key="id"
  92. :data="state.tableData.data"
  93. v-loading="state.tableData.loading"
  94. @selection-change="handleTableSelectionChange"
  95. @sort-change="handleTableSortChange">
  96. <template #empty>
  97. <el-empty description="暂无资金流水记录"></el-empty>
  98. </template>
  99. <el-table-column
  100. v-for="field in state.tableData.columns"
  101. :key="field.prop"
  102. :label="field.label"
  103. :column-key="field.prop"
  104. :width="field.width"
  105. :min-width="field.minWidth"
  106. :fixed="field.fixed"
  107. :sortable="field.sortable"
  108. :show-overflow-tooltip="!field.fixed&&field.width>150"
  109. >
  110. <template #default="{row}">
  111. <template v-if="field.prop==='type'">
  112. <el-tag :type="getTypeTag(row.type).type" size="small" effect="plain">
  113. {{ getTypeTag(row.type).text }}
  114. </el-tag>
  115. </template>
  116. <template v-else-if="field.prop==='status'">
  117. <el-tag :type="getStatusTag(row.status).type" size="small" effect="plain">
  118. {{ getStatusTag(row.status).text }}
  119. </el-tag>
  120. </template>
  121. <template v-else-if="['amount','grantsAmount','commission','beforeBalance','afterBalance','beforeGrantsBalance','afterGrantsBalance'].includes(field.prop)">
  122. {{ u.fmt.fmtMoney(row[field.prop]) }}
  123. </template>
  124. <template v-else-if="field.prop==='orderNo'">
  125. <el-button v-if="row.orderNo" link type="primary" @click="handleOrderNoClick(row)">
  126. {{ row.orderNo }}
  127. </el-button>
  128. <span v-else>-</span>
  129. </template>
  130. <template v-else>
  131. <div>{{ row[field.prop] }}</div>
  132. </template>
  133. </template>
  134. </el-table-column>
  135. </el-table>
  136. <ext-page class="page-pager" v-model:value="state.pageQuery" @change="loadData(false)"/>
  137. </el-card>
  138. <!-- 详情弹窗 (按类型展示) -->
  139. <el-dialog
  140. v-model="orderDialog.visible"
  141. :title="orderDialog.title"
  142. width="680px"
  143. destroy-on-close
  144. :close-on-click-modal="false"
  145. align-center>
  146. <!-- 消费订单详情 -->
  147. <el-descriptions v-if="orderDialog.type === 3" v-loading="orderDialog.loading" :column="2" border size="default">
  148. <el-descriptions-item label="订单号" :span="2">{{ orderDialog.data.orderId }}</el-descriptions-item>
  149. <el-descriptions-item label="设备编号">{{ orderDialog.data.shortId }}</el-descriptions-item>
  150. <el-descriptions-item label="消费站点">{{ orderDialog.data.stationName }}</el-descriptions-item>
  151. <el-descriptions-item label="消费金额">{{ u.fmt.fmtMoney(orderDialog.data.amount) }}</el-descriptions-item>
  152. <el-descriptions-item label="实收金额">{{ u.fmt.fmtMoney(orderDialog.data.amountReceived) }}</el-descriptions-item>
  153. <el-descriptions-item label="优惠金额">{{ u.fmt.fmtMoney(orderDialog.data.discountAmount) }}</el-descriptions-item>
  154. <el-descriptions-item label="优惠方式">
  155. <ext-d-label v-if="orderDialog.data.discountType" type="Activity.discountType" :model-value="orderDialog.data.discountType" />
  156. </el-descriptions-item>
  157. <el-descriptions-item label="充值款支付">{{ u.fmt.fmtMoney(orderDialog.data.rechargePayment) }}</el-descriptions-item>
  158. <el-descriptions-item label="赠款支付">{{ u.fmt.fmtMoney(orderDialog.data.grantsPayment) }}</el-descriptions-item>
  159. <el-descriptions-item label="订单状态">
  160. <ext-d-label v-if="orderDialog.data.orderStatus != null" type="Order.status" :model-value="orderDialog.data.orderStatus" />
  161. </el-descriptions-item>
  162. <el-descriptions-item label="支付状态">
  163. <ext-d-label v-if="orderDialog.data.payStatus != null" type="Order.pay" :model-value="orderDialog.data.payStatus" />
  164. </el-descriptions-item>
  165. <el-descriptions-item label="开始时间">{{ orderDialog.data.startTime }}</el-descriptions-item>
  166. <el-descriptions-item label="结束时间">{{ orderDialog.data.endTime }}</el-descriptions-item>
  167. <el-descriptions-item label="关机方式" :span="2">
  168. <ext-d-label v-if="orderDialog.data.closeType" type="Order.closeType" :model-value="orderDialog.data.closeType" />
  169. </el-descriptions-item>
  170. <el-descriptions-item label="停止原因" :span="2">{{ orderDialog.data.stopReason }}</el-descriptions-item>
  171. </el-descriptions>
  172. <!-- 充值记录详情 -->
  173. <el-descriptions v-else-if="orderDialog.type === 1" v-loading="orderDialog.loading" :column="2" border size="default">
  174. <el-descriptions-item label="商户订单号" :span="2">{{ orderDialog.data.outTradeNo }}</el-descriptions-item>
  175. <el-descriptions-item label="微信交易号" :span="2">{{ orderDialog.data.transactionId }}</el-descriptions-item>
  176. <el-descriptions-item label="支付金额">{{ u.fmt.fmtMoney(orderDialog.data.total) }}</el-descriptions-item>
  177. <el-descriptions-item label="用户实付">{{ u.fmt.fmtMoney(orderDialog.data.payerTotal) }}</el-descriptions-item>
  178. <el-descriptions-item label="交易类型">{{ orderDialog.data.tradeType }}</el-descriptions-item>
  179. <el-descriptions-item label="交易状态">{{ orderDialog.data.tradeState }}</el-descriptions-item>
  180. <el-descriptions-item label="银行类型">{{ orderDialog.data.bankType }}</el-descriptions-item>
  181. <el-descriptions-item label="支付完成时间">{{ orderDialog.data.successTime }}</el-descriptions-item>
  182. <el-descriptions-item label="创建时间">{{ orderDialog.data.createTime }}</el-descriptions-item>
  183. </el-descriptions>
  184. <!-- 退款详情 -->
  185. <el-descriptions v-else-if="orderDialog.type === 2" v-loading="orderDialog.loading" :column="2" border size="default">
  186. <el-descriptions-item label="商户退款单号" :span="2">{{ orderDialog.data.outRefundNo }}</el-descriptions-item>
  187. <el-descriptions-item label="商户支付单号" :span="2">{{ orderDialog.data.outTradeNo }}</el-descriptions-item>
  188. <el-descriptions-item label="微信退款单号" :span="2">{{ orderDialog.data.refundId }}</el-descriptions-item>
  189. <el-descriptions-item label="原充值金额">{{ u.fmt.fmtMoney(orderDialog.data.total) }}</el-descriptions-item>
  190. <el-descriptions-item label="退款金额">{{ u.fmt.fmtMoney(orderDialog.data.refund) }}</el-descriptions-item>
  191. <el-descriptions-item label="不可退优惠">{{ u.fmt.fmtMoney(orderDialog.data.discountAmount) }}</el-descriptions-item>
  192. <el-descriptions-item label="退款渠道">{{ orderDialog.data.channel }}</el-descriptions-item>
  193. <el-descriptions-item label="退款状态">
  194. <ext-d-label v-if="orderDialog.data.status" type="RefundLog.status" :model-value="orderDialog.data.status" />
  195. </el-descriptions-item>
  196. <el-descriptions-item label="退款入账账户">{{ orderDialog.data.userReceivedAccount }}</el-descriptions-item>
  197. <el-descriptions-item label="退款原因" :span="2">{{ orderDialog.data.reason }}</el-descriptions-item>
  198. <el-descriptions-item label="退款人">{{ orderDialog.data.adminUsername }}</el-descriptions-item>
  199. <el-descriptions-item label="退款申请时间">{{ orderDialog.data.createTime }}</el-descriptions-item>
  200. <el-descriptions-item label="退款成功时间">{{ orderDialog.data.successTime }}</el-descriptions-item>
  201. </el-descriptions>
  202. <template #footer>
  203. <el-button @click="orderDialog.visible = false">关 闭</el-button>
  204. </template>
  205. </el-dialog>
  206. </div>
  207. </template>
  208. <script setup lang="ts" name="adminFinanceWalletFlow">
  209. import {nextTick, onMounted, reactive, ref} from 'vue';
  210. import {$get} from "/@/utils/request";
  211. import u from "/@/utils/u"
  212. import ExtPage from '/@/components/form/ExtPage.vue'
  213. import ExtDLabel from "/@/components/form/ExtDLabel.vue";
  214. const typeTagMap: Record<number, { text: string; type: string }> = {
  215. 1: { text: "充值", type: "success" },
  216. 2: { text: "退款", type: "warning" },
  217. 3: { text: "消费", type: "danger" }
  218. };
  219. const statusTagMap: Record<number, { text: string; type: string }> = {
  220. 0: { text: "待确认", type: "info" },
  221. 1: { text: "已确认", type: "success" },
  222. 2: { text: "已取消", type: "danger" }
  223. };
  224. const getTypeTag = (type: number) => {
  225. return typeTagMap[type] || { text: "未知", type: "info" };
  226. };
  227. const getStatusTag = (status: number) => {
  228. return statusTagMap[status] || { text: "未知", type: "info" };
  229. };
  230. //定义引用
  231. const queryRef = ref();
  232. //定义变量
  233. const state = reactive({
  234. formQuery: {
  235. mobilePhone: "",
  236. type: undefined as number | undefined,
  237. status: undefined as number | undefined,
  238. startDate: "",
  239. endDate: ""
  240. },
  241. pageQuery: {
  242. pageNum: 1,
  243. pageSize: 10,
  244. total: 0
  245. },
  246. tableData: {
  247. height: 500,
  248. data: [] as Array<any>,
  249. loading: false,
  250. columns: [
  251. {label: '用户ID', prop: 'userId', width: 120, resizable: true, fixed: 'left'},
  252. {label: '用户手机号', prop: 'mobilePhone', width: 140, resizable: true, fixed: 'left'},
  253. {label: '交易类型', prop: 'type', width: 100, resizable: true},
  254. {label: '订单号', prop: 'orderNo', width: 200, resizable: true},
  255. {label: '金额', prop: 'amount', width: 110, resizable: true},
  256. {label: '赠款金额', prop: 'grantsAmount', width: 110, resizable: true},
  257. {label: '手续费', prop: 'commission', width: 100, resizable: true},
  258. {label: '交易前余额', prop: 'beforeBalance', width: 120, resizable: true},
  259. {label: '交易后余额', prop: 'afterBalance', width: 120, resizable: true},
  260. {label: '交易前赠款', prop: 'beforeGrantsBalance', width: 120, resizable: true},
  261. {label: '交易后赠款', prop: 'afterGrantsBalance', width: 120, resizable: true},
  262. {label: '交易时间', prop: 'transactionTime', width: 170, resizable: true, fixed: 'right'},
  263. {label: '状态', prop: 'status', width: 100, resizable: true},
  264. {label: '备注', prop: 'remark', minWidth: 150, resizable: true},
  265. ],
  266. },
  267. })
  268. onMounted(() => {
  269. loadData();
  270. nextTick(() => {
  271. let bodyHeight = document.body.clientHeight;
  272. let queryHeight = queryRef.value.$el.clientHeight;
  273. state.tableData.height = bodyHeight - queryHeight - 320
  274. })
  275. });
  276. const buildParams = () => {
  277. const params: any = { ...state.formQuery, ...state.pageQuery };
  278. if (params.type === undefined || params.type === null) delete params.type;
  279. if (params.status === undefined || params.status === null) delete params.status;
  280. if (!params.startDate) delete params.startDate;
  281. if (!params.endDate) delete params.endDate;
  282. return params;
  283. };
  284. const typeTitles: Record<number, string> = { 1: '充值详情', 2: '退款详情', 3: '订单详情' };
  285. const orderDialog = reactive({
  286. visible: false,
  287. loading: false,
  288. type: 0,
  289. title: '',
  290. data: {} as any,
  291. });
  292. const handleOrderNoClick = (row: any) => {
  293. const type = row.type as number;
  294. orderDialog.type = type;
  295. orderDialog.title = typeTitles[type] || '交易详情';
  296. orderDialog.visible = true;
  297. orderDialog.loading = true;
  298. orderDialog.data = {};
  299. let url = '';
  300. if (type === 1) {
  301. // 充值:orderNo 对应 pay_log 的 outTradeNo
  302. url = `/custom/payLog/byOutTradeNo/${row.orderNo}`;
  303. } else if (type === 2) {
  304. // 退款:orderNo 对应 refund_log 的 outRefundNo
  305. url = `/finance/refundLog/detail/${row.orderNo}`;
  306. } else {
  307. // 消费:orderNo 是 wash_order 的 orderId
  308. url = `/washOrder/detail/${row.orderNo}`;
  309. }
  310. $get(url).then((res: any) => {
  311. orderDialog.data = res;
  312. }).catch(() => {
  313. orderDialog.data = {};
  314. }).finally(() => {
  315. orderDialog.loading = false;
  316. });
  317. };
  318. // 初始化表格数据
  319. const loadData = (refresh: boolean = false) => {
  320. if (refresh) {
  321. state.pageQuery.pageNum = 1;
  322. }
  323. state.tableData.loading = true;
  324. $get(`/finance/walletDetails`, buildParams()).then((res: any) => {
  325. let {list, total} = res;
  326. state.tableData.data = list;
  327. state.pageQuery.total = total;
  328. state.tableData.loading = false;
  329. }).catch(e => {
  330. state.tableData.loading = false;
  331. })
  332. };
  333. const handleTableSelectionChange = (selection: any) => {
  334. }
  335. const handleTableSortChange = (column: any, prop: string, order: string) => {
  336. }
  337. </script>