wallet-flow.vue 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. <script setup lang="ts">
  2. import { reactive, onMounted, ref, nextTick } from "vue";
  3. import { getWalletDetailList } from "@/api/finance";
  4. import { useRenderIcon } from "@/components/ReIcon/src/hooks";
  5. import { ElMessage } from "element-plus";
  6. defineOptions({
  7. name: "AdminFinanceWalletFlow"
  8. });
  9. const queryRef = ref();
  10. const tableRef = ref();
  11. const typeOptions = [
  12. { value: undefined, label: "全部" },
  13. { value: 1, label: "充值" },
  14. { value: 2, label: "提现" },
  15. { value: 3, label: "消费" }
  16. ];
  17. const statusOptions = [
  18. { value: undefined, label: "全部" },
  19. { value: 0, label: "待确认" },
  20. { value: 1, label: "已确认" },
  21. { value: 2, label: "已取消" }
  22. ];
  23. const typeTagMap: Record<number, { text: string; type: string }> = {
  24. 1: { text: "充值", type: "success" },
  25. 2: { text: "提现", type: "warning" },
  26. 3: { text: "消费", type: "danger" }
  27. };
  28. const statusTagMap: Record<number, { text: string; type: string }> = {
  29. 0: { text: "待确认", type: "info" },
  30. 1: { text: "已确认", type: "success" },
  31. 2: { text: "已取消", type: "danger" }
  32. };
  33. const state = reactive({
  34. formQuery: {
  35. mobilePhone: "",
  36. type: undefined as number | undefined,
  37. status: undefined as number | undefined,
  38. startDate: "",
  39. endDate: ""
  40. },
  41. pageQuery: {
  42. pageNum: 1,
  43. pageSize: 10,
  44. total: 0
  45. },
  46. tableData: {
  47. height: 500,
  48. data: [] as Array<any>,
  49. loading: false,
  50. columns: [
  51. { label: "用户ID", prop: "userId", width: 120 },
  52. { label: "用户手机号", prop: "mobilePhone", width: 140 },
  53. { label: "交易类型", prop: "type", width: 100 },
  54. { label: "订单号", prop: "orderNo", width: 200 },
  55. { label: "金额", prop: "amount", width: 110 },
  56. { label: "赠款金额", prop: "grantsAmount", width: 110 },
  57. { label: "手续费", prop: "commission", width: 100 },
  58. { label: "交易前余额", prop: "beforeBalance", width: 120 },
  59. { label: "交易后余额", prop: "afterBalance", width: 120 },
  60. { label: "交易前赠款", prop: "beforeGrantsBalance", width: 120 },
  61. { label: "交易后赠款", prop: "afterGrantsBalance", width: 120 },
  62. { label: "状态", prop: "status", width: 100 },
  63. { label: "交易时间", prop: "transactionTime", width: 170 },
  64. { label: "备注", prop: "remark", minWidth: 150 }
  65. ]
  66. }
  67. });
  68. onMounted(() => {
  69. loadData();
  70. nextTick(() => {
  71. const bodyHeight = document.body.clientHeight;
  72. const queryHeight = queryRef.value?.$el?.clientHeight || 0;
  73. state.tableData.height = bodyHeight - queryHeight - 230;
  74. });
  75. });
  76. const buildParams = () => {
  77. const params: any = { ...state.formQuery, ...state.pageQuery };
  78. if (!params.type) delete params.type;
  79. if (params.status === undefined || params.status === null) delete params.status;
  80. if (!params.startDate) delete params.startDate;
  81. if (!params.endDate) delete params.endDate;
  82. return params;
  83. };
  84. const loadData = (refresh: boolean = false) => {
  85. if (refresh) {
  86. state.pageQuery.pageNum = 1;
  87. }
  88. state.tableData.loading = true;
  89. getWalletDetailList(buildParams())
  90. .then((res: any) => {
  91. const { list, total } = res || {};
  92. state.tableData.data = list || [];
  93. state.pageQuery.total = total || 0;
  94. })
  95. .catch(() => {
  96. state.tableData.data = [];
  97. ElMessage.error("加载资金流水记录失败");
  98. })
  99. .finally(() => {
  100. state.tableData.loading = false;
  101. });
  102. };
  103. const handleSizeChange = (size: number) => {
  104. state.pageQuery.pageSize = size;
  105. loadData(true);
  106. };
  107. const handleCurrentChange = (page: number) => {
  108. state.pageQuery.pageNum = page;
  109. loadData();
  110. };
  111. const handleSearch = () => {
  112. loadData(true);
  113. };
  114. const handleReset = () => {
  115. state.formQuery = {
  116. mobilePhone: "",
  117. type: undefined,
  118. status: undefined,
  119. startDate: "",
  120. endDate: ""
  121. };
  122. loadData(true);
  123. };
  124. const formatMoney = (value: number) => {
  125. if (value === null || value === undefined) return "-";
  126. return `¥${(value / 100).toFixed(2)}`;
  127. };
  128. const getTypeTag = (type: number) => {
  129. return typeTagMap[type] || { text: "未知", type: "info" };
  130. };
  131. const getStatusTag = (status: number) => {
  132. return statusTagMap[status] || { text: "未知", type: "info" };
  133. };
  134. </script>
  135. <template>
  136. <div class="page-container">
  137. <el-card shadow="hover">
  138. <template #header>
  139. <span class="card-header">用户资金流</span>
  140. </template>
  141. <el-form
  142. ref="queryRef"
  143. :model="state.formQuery"
  144. inline
  145. class="search-form"
  146. >
  147. <el-form-item label="用户手机号">
  148. <el-input
  149. v-model="state.formQuery.mobilePhone"
  150. placeholder="请输入手机号"
  151. clearable
  152. style="width: 160px"
  153. @keyup.enter="handleSearch"
  154. />
  155. </el-form-item>
  156. <el-form-item label="交易类型">
  157. <el-select
  158. v-model="state.formQuery.type"
  159. placeholder="全部"
  160. style="width: 120px"
  161. clearable
  162. >
  163. <el-option
  164. v-for="opt in typeOptions"
  165. :key="opt.value"
  166. :label="opt.label"
  167. :value="opt.value"
  168. />
  169. </el-select>
  170. </el-form-item>
  171. <el-form-item label="状态">
  172. <el-select
  173. v-model="state.formQuery.status"
  174. placeholder="全部"
  175. style="width: 120px"
  176. clearable
  177. >
  178. <el-option
  179. v-for="opt in statusOptions"
  180. :key="opt.value"
  181. :label="opt.label"
  182. :value="opt.value"
  183. />
  184. </el-select>
  185. </el-form-item>
  186. <el-form-item label="交易时间">
  187. <el-date-picker
  188. v-model="state.formQuery.startDate"
  189. type="date"
  190. placeholder="开始日期"
  191. value-format="YYYY-MM-DD"
  192. style="width: 150px"
  193. />
  194. <span style="margin: 0 8px; color: #999">至</span>
  195. <el-date-picker
  196. v-model="state.formQuery.endDate"
  197. type="date"
  198. placeholder="结束日期"
  199. value-format="YYYY-MM-DD"
  200. style="width: 150px"
  201. />
  202. </el-form-item>
  203. <el-form-item>
  204. <el-button
  205. type="primary"
  206. :icon="useRenderIcon('ri/search-line')"
  207. @click="handleSearch"
  208. >
  209. 查询
  210. </el-button>
  211. <el-button
  212. :icon="useRenderIcon('ri/refresh-line')"
  213. @click="handleReset"
  214. >
  215. 重置
  216. </el-button>
  217. </el-form-item>
  218. </el-form>
  219. <el-table
  220. ref="tableRef"
  221. v-loading="state.tableData.loading"
  222. :data="state.tableData.data"
  223. :height="state.tableData.height"
  224. border
  225. stripe
  226. >
  227. <template #empty>
  228. <el-empty description="暂无资金流水记录" />
  229. </template>
  230. <el-table-column
  231. v-for="col in state.tableData.columns"
  232. :key="col.prop"
  233. :prop="col.prop"
  234. :label="col.label"
  235. :width="col.width"
  236. :min-width="col.minWidth"
  237. show-overflow-tooltip
  238. >
  239. <template #default="{ row }">
  240. <template v-if="col.prop === 'type'">
  241. <el-tag :type="getTypeTag(row.type).type" size="small" effect="plain">
  242. {{ getTypeTag(row.type).text }}
  243. </el-tag>
  244. </template>
  245. <template v-else-if="col.prop === 'status'">
  246. <el-tag :type="getStatusTag(row.status).type" size="small" effect="plain">
  247. {{ getStatusTag(row.status).text }}
  248. </el-tag>
  249. </template>
  250. <template v-else-if="['amount', 'grantsAmount', 'commission', 'beforeBalance', 'afterBalance', 'beforeGrantsBalance', 'afterGrantsBalance'].includes(col.prop)">
  251. {{ formatMoney(row[col.prop]) }}
  252. </template>
  253. <template v-else>
  254. {{ row[col.prop] }}
  255. </template>
  256. </template>
  257. </el-table-column>
  258. </el-table>
  259. <div class="pagination-container">
  260. <el-pagination
  261. v-model:current-page="state.pageQuery.pageNum"
  262. v-model:page-size="state.pageQuery.pageSize"
  263. :total="state.pageQuery.total"
  264. :page-sizes="[10, 20, 50, 100]"
  265. layout="total, sizes, prev, pager, next, jumper"
  266. @size-change="handleSizeChange"
  267. @current-change="handleCurrentChange"
  268. />
  269. </div>
  270. </el-card>
  271. </div>
  272. </template>
  273. <style scoped lang="scss">
  274. .page-container {
  275. padding: 20px;
  276. }
  277. .card-header {
  278. font-size: 16px;
  279. font-weight: 600;
  280. }
  281. .search-form {
  282. margin-bottom: 16px;
  283. }
  284. .pagination-container {
  285. display: flex;
  286. justify-content: flex-end;
  287. margin-top: 20px;
  288. }
  289. </style>