You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

471 lines
14 KiB

2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
5 days ago
5 days ago
2 weeks ago
5 days ago
5 days ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
5 days ago
5 days ago
2 weeks ago
5 days ago
2 weeks ago
5 days ago
  1. <script setup>
  2. import { ref, watch, nextTick, onMounted } from 'vue'
  3. import { useRoute } from 'vue-router'
  4. import { ElMessage } from 'element-plus'
  5. import API from '@/util/http.js'
  6. import moment from 'moment'
  7. import { useI18n } from 'vue-i18n'
  8. import { useAdminStore } from "@/store/index.js"
  9. import { storeToRefs } from "pinia"
  10. import { selectWalletRecords } from "@/api/cash/cash.js"
  11. const props = defineProps({
  12. type: {
  13. type: Number,
  14. required: true
  15. }
  16. })
  17. const { t } = useI18n()
  18. const route = useRoute()
  19. const adminStore = useAdminStore()
  20. const { flag } = storeToRefs(adminStore)
  21. const tableData = ref([])
  22. const total = ref(0)
  23. const loading = ref(false)
  24. const marketLists = ref([])
  25. const selectedMarketPath = ref([])
  26. const selectData = ref({
  27. jwcode: '',
  28. walletId: '',
  29. market: ''
  30. })
  31. const getObj = ref({
  32. pageNum: 1,
  33. pageSize: 20
  34. })
  35. const tableRef = ref(null)
  36. const scrollTableTop = () => {
  37. tableRef.value?.setScrollTop?.(0)
  38. }
  39. // 精网号去空格
  40. const trimJwCode = () => {
  41. if (selectData.value.jwcode) {
  42. selectData.value.jwcode = selectData.value.jwcode.replace(/\s/g, '');
  43. }
  44. }
  45. // 获取类型文本
  46. const getWalletRecordTypeText = (item) => {
  47. const type = Number(item.type)
  48. if (type === 0) {
  49. return t('clientCount.recharge')
  50. }
  51. if (type === 1) {
  52. return t('clientCount.consume')
  53. }
  54. if (type === 2) {
  55. return t('clientCount.refund')
  56. }
  57. return item.typeText || t('clientCount.other')
  58. }
  59. // 格式化金额
  60. const format3 = (num) => {
  61. return num.toLocaleString('en-US')
  62. }
  63. // 统一获取数据的方法
  64. const getWalletData = async () => {
  65. console.log('walletId:', selectData.value.walletId)
  66. if (!selectData.value.walletId) return;
  67. if (selectData.value.jwcode) {
  68. const numberRegex = /^\d{1,9}$/;
  69. if (!numberRegex.test(selectData.value.jwcode)) {
  70. ElMessage.error(t('elmessage.checkJwcodeFormat'))
  71. return
  72. }
  73. }
  74. loading.value = true
  75. try {
  76. const params = {
  77. pageNum: getObj.value.pageNum,
  78. pageSize: getObj.value.pageSize,
  79. userWalletRecord: {
  80. walletId: selectData.value.walletId,
  81. market: selectData.value.market,
  82. jwcode: selectData.value.jwcode
  83. }
  84. }
  85. const result = await selectWalletRecords(params)
  86. if (result.code === 200) {
  87. tableData.value = result.data.list.map(item => ({
  88. ...item,
  89. time: moment(item.createTime).format('YYYY-MM-DD HH:mm:ss'),
  90. typeText: getWalletRecordTypeText(item),
  91. amount: Number(item.amount) || 0,
  92. desc: item.description || '',
  93. orderNo: item.orderCode,
  94. status: item.status === 0 ? 1 : 2,
  95. userName: item.userName || '-',
  96. jwcode: item.jwcode || '-'
  97. }))
  98. total.value = result.data.total
  99. await nextTick()
  100. scrollTableTop()
  101. } else {
  102. ElMessage.error(result.msg || t('elmessage.getDataFailed'))
  103. }
  104. } catch (error) {
  105. console.error('获取钱包明细失败:', error)
  106. } finally {
  107. loading.value = false
  108. }
  109. }
  110. // 搜索
  111. const search = function () {
  112. trimJwCode()
  113. getObj.value.pageNum = 1
  114. getWalletData()
  115. }
  116. // 重置
  117. const reset = function () {
  118. selectData.value.jwcode = ''
  119. selectData.value.market = ''
  120. selectedMarketPath.value = []
  121. getObj.value.pageNum = 1
  122. getWalletData()
  123. }
  124. // 分页改变
  125. const handlePageSizeChange = function (val) {
  126. getObj.value.pageSize = val
  127. getWalletData()
  128. }
  129. const handleCurrentChange = function (val) {
  130. getObj.value.pageNum = val
  131. getWalletData()
  132. }
  133. // 监听全局flag变化重新请求数据
  134. watch(flag, (newFlag, oldFlag) => {
  135. if (newFlag !== oldFlag) {
  136. getWalletData()
  137. }
  138. })
  139. // 核心:监听父组件传入的 props 参数变化
  140. watch(
  141. () => props.type,
  142. (newType) => {
  143. if (newType) {
  144. selectData.value.walletId = newType
  145. getObj.value.pageNum = 1
  146. getWalletData()
  147. }
  148. },
  149. { immediate: true }
  150. )
  151. // ==================== 导出逻辑 ====================
  152. const exportListVisible = ref(false)
  153. const exportList = ref([])
  154. const exportListLoading = ref(false)
  155. const exportExcelOnlyOne = async function () {
  156. if (!selectData.value.walletId) {
  157. ElMessage.error(t('elmessage.selectCompanyWallet'))
  158. return
  159. }
  160. const params = {
  161. pageNum: 1, // 导出通常从第一页开始
  162. pageSize: 10000, // 导出大量数据
  163. userWalletRecord: {
  164. walletId: selectData.value.walletId,
  165. jwcode: selectData.value.jwcode ? Number(selectData.value.jwcode) : null
  166. }
  167. }
  168. try {
  169. const res = await API({
  170. url: '/export/exportUserWalletRecord',
  171. method: 'post',
  172. data: params
  173. })
  174. if (res.code === 200) {
  175. ElMessage.success(t('elmessage.exportSuccess'))
  176. } else {
  177. ElMessage.error(res.msg || t('elmessage.exportFailed'))
  178. }
  179. } catch (error) {
  180. console.error('导出失败:', error)
  181. ElMessage.error(t('elmessage.exportFailed'))
  182. }
  183. }
  184. // 打开导出列表弹窗
  185. const openExportList = () => {
  186. getExportList()
  187. exportListVisible.value = true
  188. }
  189. // 获取导出列表
  190. const getExportList = async () => {
  191. exportListLoading.value = true
  192. try {
  193. const result = await API({ url: '/export/export' })
  194. if (result.code === 200) {
  195. // 过滤只显示 type 为 16 和 17 的导出记录(保持和 WalletBalance 页面一致)
  196. const filteredData = result.data.filter(item => {
  197. return item.type === 16 || item.type === 17;
  198. });
  199. exportList.value = filteredData
  200. } else {
  201. ElMessage.error(result.msg || t('elmessage.getExportListError'))
  202. }
  203. } catch (error) {
  204. console.error('获取导出列表出错:', error)
  205. ElMessage.error(t('elmessage.getExportListError'))
  206. } finally {
  207. exportListLoading.value = false
  208. }
  209. }
  210. // 下载导出文件
  211. const downloadExportFile = (item) => {
  212. if (item.state === 2) {
  213. const link = document.createElement('a')
  214. link.href = item.url
  215. link.download = item.fileName
  216. link.click()
  217. } else {
  218. ElMessage.warning(t('elmessage.exportingInProgress'))
  219. }
  220. }
  221. //根据状态返回对应的标签类型
  222. const getTagType = (state) => {
  223. switch (state) {
  224. case 0: return 'info';
  225. case 1: return 'primary';
  226. case 2: return 'success';
  227. case 3: return 'danger';
  228. default: return 'info';
  229. }
  230. }
  231. //根据状态返回对应的标签文案
  232. const getTagText = (state) => {
  233. switch (state) {
  234. case 0: return t('elmessage.pendingExecution');
  235. case 1: return t('elmessage.executing');
  236. case 2: return t('elmessage.executed');
  237. case 3: return t('elmessage.errorExecution');
  238. default: return t('elmessage.unknownStatus');
  239. }
  240. }
  241. const transformTree = (nodes) => {
  242. // 直接处理第一级节点的子节点
  243. const allChildren = nodes.flatMap(node => node.children || []);
  244. return allChildren.map(child => {
  245. const grandchildren = child.children && child.children.length
  246. ? transformTree([child]) // 递归处理子节点
  247. : null;
  248. return {
  249. value: child.id,
  250. label: child.name,
  251. children: grandchildren
  252. };
  253. });
  254. };
  255. const selectMarket = async function () {
  256. try {
  257. const selectMarketResult = await API({ url: '/market/selectMarket' });
  258. marketLists.value = transformTree(selectMarketResult.data)
  259. console.log('转换后的地区树==============:', marketLists.value);
  260. } catch (error) {
  261. console.error('获取地区树失败:', error);
  262. return {};
  263. }
  264. };
  265. const handleMarketChange = (value) => {
  266. if (value && value.length > 0) {
  267. // 级联选择器绑定了 id,直接取数组最后一级(叶子节点)的 id 即可
  268. const lastValueId = value[value.length - 1];
  269. selectData.value.market = lastValueId;
  270. } else {
  271. selectData.value.market = '';
  272. }
  273. };
  274. onMounted(() => {
  275. selectData.value.walletId = props.type
  276. selectMarket()
  277. })
  278. </script>
  279. <template>
  280. <div style="display: flex; flex-direction: column; height: 95vh;">
  281. <el-card class="card1" style="margin-bottom: 1vh;">
  282. <div class="head-card">
  283. <div class="head-card-element">
  284. <el-text class="mx-1" size="large">{{ $t('common_list.jwcode') }}</el-text>
  285. <el-input v-model="selectData.jwcode" style="width: 12.5vw" placeholder="请输入精网号" clearable />
  286. </div>
  287. <div class="head-card-element">
  288. <el-text class="mx-1" size="large">{{ $t('common.market') }}</el-text>
  289. <el-cascader v-model="selectedMarketPath" :options="marketLists" :placeholder="$t('common.marketPlaceholder')"
  290. clearable style="width:180px" @change="handleMarketChange" />
  291. </div>
  292. <div class="head-card-btn">
  293. <el-button type="success" @click="reset">{{ $t('common.reset') }}</el-button>
  294. <el-button type="primary" @click="search">{{ $t('common.search') }}</el-button>
  295. <el-button type="primary" @click="exportExcelOnlyOne">{{ $t('common.exportExcel') }}</el-button>
  296. <el-button type="primary" @click="openExportList">{{ $t('common.viewExportList') }}</el-button>
  297. </div>
  298. </div>
  299. </el-card>
  300. <el-card class="card2">
  301. <div style="height: 85vh; overflow-y: auto">
  302. <el-table ref="tableRef" :data="tableData" v-loading="loading" style="width: 100%; height: 85vh"
  303. :row-style="{ height: '50px' }">
  304. <el-table-column type="index" :label="$t('common_list.id')" width="80px" fixed="left">
  305. <template #default="scope">
  306. <span>{{ scope.$index + 1 + (getObj.pageNum - 1) * getObj.pageSize }}</span>
  307. </template>
  308. </el-table-column>
  309. <el-table-column prop="jwcode" :label="$t('common_list.jwcode')" width="140" />
  310. <el-table-column prop="userName" :label="$t('common_list.name')" width="140" />
  311. <el-table-column prop="marketName" :label="$t('common_list.marketName')" width="140" />
  312. <el-table-column prop="typeText" :label="$t('clientCount.transactionType')" align="center" width="120" />
  313. <el-table-column prop="transactionCurrency" :label="$t('clientCount.transactionCurrency')" align="center"
  314. width="120" />
  315. <el-table-column prop="amount" :label="$t('common_list.money')" align="center" width="120">
  316. <template #default="scope">
  317. <span :style="{ color: scope.row.amount >= 0 ? '#67C23A' : '#F56C6C', fontWeight: 'bold' }">
  318. {{ scope.row.amount > 0 ? '+' + format3(scope.row.amount) : format3(scope.row.amount) }}
  319. </span>
  320. </template>
  321. </el-table-column>
  322. <el-table-column prop="orderNo" :label="$t('clientCount.transactionOrderNo')" align="center" width="220" />
  323. <el-table-column prop="desc" :label="$t('clientCount.transactionDesc')" align="center" min-width="150" />
  324. <el-table-column prop="status" :label="$t('clientCount.transactionStatus')" align="center" width="150">
  325. <template #default="scope">
  326. <el-tag :type="scope.row.status === 1 ? 'success' : scope.row.status === 2 ? 'danger' : 'info'"
  327. :effect="scope.row.status === 1 ? 'light' : 'plain'">
  328. {{ scope.row.status === 1 ? t('common_list.normal') : scope.row.status === 2 ? t('common_list.refunded')
  329. : t('clientCount.exceptionData') }}
  330. </el-tag>
  331. </template>
  332. </el-table-column>
  333. <el-table-column prop="time" :label="$t('clientCount.time')" align="center" width="180">
  334. <template #default="scope">{{ scope.row.time }}</template>
  335. </el-table-column>
  336. </el-table>
  337. </div>
  338. <!-- 分页 -->
  339. <div class="pagination" style="margin-top: 20px">
  340. <el-pagination background :current-page="getObj.pageNum" :page-size="getObj.pageSize"
  341. :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper" :total="total"
  342. @size-change="handlePageSizeChange" @current-change="handleCurrentChange"></el-pagination>
  343. </div>
  344. </el-card>
  345. <!-- 导出列表弹窗 -->
  346. <el-dialog v-model="exportListVisible" :title="$t('common_export.exportList')" width="80%">
  347. <el-table :data="exportList" style="width: 100% ;height: 60vh;" :loading="exportListLoading">
  348. <el-table-column prop="fileName" :label="$t('common_export.fileName')" />
  349. <el-table-column prop="state" :label="$t('common_export.status')">
  350. <template #default="scope">
  351. <el-tag :type="getTagType(scope.row.state)" :effect="scope.row.state === 3 ? 'light' : 'plain'">
  352. {{ getTagText(scope.row.state) }}
  353. </el-tag>
  354. </template>
  355. </el-table-column>
  356. <el-table-column prop="createTime" :label="$t('common_export.createTime')">
  357. <template #default="scope">
  358. {{ moment(scope.row.createTime).format('YYYY-MM-DD HH:mm:ss') }}
  359. </template>
  360. </el-table-column>
  361. <el-table-column :label="$t('common_export.operation')">
  362. <template #default="scope">
  363. <el-button type="primary" size="small" @click="downloadExportFile(scope.row)"
  364. :disabled="scope.row.state !== 2">
  365. {{ $t('common_export.download') }}
  366. </el-button>
  367. </template>
  368. </el-table-column>
  369. </el-table>
  370. <template #footer>
  371. <div class="dialog-footer">
  372. <el-button text @click="exportListVisible = false">{{ $t('common_export.close') }}</el-button>
  373. </div>
  374. </template>
  375. </el-dialog>
  376. </div>
  377. </template>
  378. <style scoped lang="scss">
  379. .card1 {
  380. background: #F3FAFE;
  381. }
  382. .card2 {
  383. background: #E7F4FD;
  384. flex: 1;
  385. display: flex;
  386. flex-direction: column;
  387. :deep(.el-card__body) {
  388. padding: 20px;
  389. flex: 1;
  390. display: flex;
  391. flex-direction: column;
  392. overflow: hidden;
  393. }
  394. }
  395. :deep(.el-table__header-wrapper),
  396. :deep(.el-table__body-wrapper),
  397. :deep(.el-table__cell),
  398. :deep(.el-table__body td) {
  399. background-color: #F3FAFE !important;
  400. }
  401. :deep(.el-table__header th) {
  402. background-color: #F3FAFE !important;
  403. }
  404. :deep(.el-table__row:hover > .el-table__cell) {
  405. background-color: #E5EBFE !important;
  406. }
  407. .pagination {
  408. display: flex;
  409. }
  410. .head-card {
  411. display: flex;
  412. }
  413. .head-card-element {
  414. margin-right: 20px;
  415. white-space: nowrap;
  416. .mx-1 {
  417. font-size: 16px;
  418. margin: 0 10px;
  419. }
  420. }
  421. .head-card-btn {
  422. margin-left: auto;
  423. }
  424. </style>