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.

464 lines
13 KiB

2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
  1. <script setup>
  2. import { ref, reactive, onMounted, toRefs } from 'vue'
  3. import { ElMessage, ElMessageBox } from 'element-plus'
  4. import request from '@/util/http.js'
  5. import { storeToRefs } from 'pinia'
  6. import dayjs from 'dayjs'
  7. import { useI18n } from 'vue-i18n'
  8. import { refundOnline,performanceSelect,exportPerformance } from '@/api/cash/financialAccount.js'
  9. import {useAdminStore} from '@/store/index.js'
  10. const adminStore = useAdminStore()
  11. const {adminData} = storeToRefs(adminStore)
  12. const { t } = useI18n()
  13. const paytypeList = [
  14. t('cash.payMethods.stripe'),
  15. t('cash.payMethods.paymentAsia'),
  16. t('cash.payMethods.ipay88'),
  17. t('cash.payMethods.bankTransfer'),
  18. t('cash.payMethods.card'),
  19. t('cash.payMethods.cash'),
  20. t('cash.payMethods.check'),
  21. t('cash.payMethods.grabpay'),
  22. t('cash.payMethods.nets'),
  23. t('cash.payMethods.transfer'),
  24. t('cash.payMethods.paypal'),
  25. ]
  26. const payPlatformOptions = ref([...paytypeList])
  27. const statusOptions = [
  28. { label: '已到账', value: 4 },
  29. { label: '已退款', value: 6 }
  30. ]
  31. // 地区树
  32. const marketOptions = ref([])
  33. // 查询参数
  34. const queryParams = reactive({
  35. jwcode: '',
  36. adminMarket: [], // 下拉多选
  37. timeRange: [], // [startTime, endTime]
  38. customerMarket: [], // 客户地区
  39. pageNum: 1,
  40. pageSize: 20
  41. })
  42. const total = ref(0)
  43. const tableData = ref([])
  44. const loading = ref(false)
  45. // 转换树形结构(参考 coinConsumeDetail.vue)
  46. const transformTree = (nodes) => {
  47. const allChildren = nodes.flatMap(node => node.children || []);
  48. return allChildren.map(child => {
  49. const grandchildren = child.children && child.children.length
  50. ? transformTree([child])
  51. : null;
  52. return {
  53. value: child.name,
  54. label: child.name,
  55. children: grandchildren
  56. };
  57. });
  58. };
  59. // 获取地区数据
  60. const getMarket = async () => {
  61. try {
  62. const result = await request({ url: '/market/selectMarket' });
  63. if (result && result.data) {
  64. marketOptions.value = transformTree(result.data)
  65. }
  66. } catch (error) {
  67. console.error('获取地区失败', error)
  68. }
  69. }
  70. // 查询列表
  71. const fetchData = async () => {
  72. loading.value = true
  73. try {
  74. // 构建请求参数
  75. const params = {
  76. pageNum: queryParams.pageNum,
  77. pageSize: queryParams.pageSize,
  78. performanceDTO:{
  79. jwcode: queryParams.jwcode,
  80. adminMarket: adminData.value.markets.split(',').filter(item => item.trim() !== '') || [],
  81. customerMarket: queryParams.customerMarket,
  82. startTime: queryParams.timeRange?.[0] ? dayjs(queryParams.timeRange[0]).format('YYYY-MM-DD HH:mm:ss') : '',
  83. endTime: queryParams.timeRange?.[1] ? dayjs(queryParams.timeRange[1]).format('YYYY-MM-DD HH:mm:ss') : '',
  84. }
  85. }
  86. console.log('查询参数:', params)
  87. const res = await performanceSelect(params)
  88. if (res.code == 200) {
  89. tableData.value = res.data.list || []
  90. total.value = res.data.total || 0
  91. loading.value = false
  92. } else {
  93. ElMessage.error(res.msg || '获取数据失败')
  94. loading.value = false
  95. }
  96. } catch (error) {
  97. console.error(error)
  98. loading.value = false
  99. ElMessage.error('获取数据失败')
  100. }
  101. }
  102. const handleSearch = () => {
  103. queryParams.pageNum = 1
  104. fetchData()
  105. }
  106. const handleReset = () => {
  107. queryParams.jwcode = ''
  108. queryParams.adminMarket = []
  109. queryParams.timeRange = null
  110. queryParams.customerMarket = []
  111. handleSearch()
  112. }
  113. const handlePageSizeChange = (val) => {
  114. queryParams.pageSize = val
  115. fetchData()
  116. }
  117. const handleCurrentChange = (val) => {
  118. queryParams.pageNum = val
  119. fetchData()
  120. }
  121. // 退款操作
  122. const handleRefund = (row) => {
  123. ElMessageBox.confirm(`确定要对订单 ${row.systemTradeNo} 进行退款吗?`, '退款确认', {
  124. confirmButtonText: '确定',
  125. cancelButtonText: '取消',
  126. type: 'warning'
  127. }).then(() => {
  128. ElMessage.success('退款申请已提交')
  129. // 刷新列表
  130. fetchData()
  131. }).catch(() => {})
  132. }
  133. // ==================== 导出相关逻辑 ====================
  134. const exportListVisible = ref(false)
  135. const exportList = ref([])
  136. const exportListLoading = ref(false)
  137. // 导出Excel
  138. const handleExport = async () => {
  139. try {
  140. const params = {
  141. pageNum: queryParams.pageNum,
  142. pageSize: queryParams.pageSize,
  143. performanceDTO:{
  144. jwcode: queryParams.jwcode,
  145. adminMarket: queryParams.adminMarket,
  146. startTime: queryParams.timeRange?.[0] ? dayjs(queryParams.timeRange[0]).format('YYYY-MM-DD HH:mm:ss') : '',
  147. endTime: queryParams.timeRange?.[1] ? dayjs(queryParams.timeRange[1]).format('YYYY-MM-DD HH:mm:ss') : '',
  148. customerMarket: queryParams.customerMarket,
  149. }
  150. }
  151. // TODO: 确认导出接口 URL
  152. const res = await exportPerformance(params)
  153. if(res.code == 200){
  154. console.log('导出参数', params)
  155. ElMessage.success(t('elmessage.exportSuccess'))
  156. }
  157. } catch (error) {
  158. console.error(error)
  159. ElMessage.error('导出失败')
  160. }
  161. }
  162. // 打开导出列表弹窗
  163. const openExportList = () => {
  164. getExportList()
  165. exportListVisible.value = true
  166. }
  167. // 获取导出列表
  168. const getExportList = async () => {
  169. exportListLoading.value = true
  170. try {
  171. const result = await request({ url: '/export/export' })
  172. if (result.code === 200) {
  173. const filteredData = result.data.filter(item => item.type == 13);
  174. exportList.value = filteredData || []
  175. } else {
  176. ElMessage.error(result.msg || t('elmessage.getExportListError'))
  177. }
  178. } catch (error) {
  179. console.error('获取导出列表出错:', error)
  180. ElMessage.error(t('elmessage.getExportListError'))
  181. } finally {
  182. exportListLoading.value = false
  183. }
  184. }
  185. // 下载导出文件
  186. const downloadExportFile = (item) => {
  187. if (item.state === 2) {
  188. const link = document.createElement('a')
  189. link.href = item.url
  190. link.download = item.fileName
  191. link.click()
  192. } else {
  193. ElMessage.warning(t('elmessage.exportingInProgress'))
  194. }
  195. }
  196. // 根据状态返回对应的标签类型
  197. const getTagType = (state) => {
  198. switch (state) {
  199. case 0: return 'info';
  200. case 1: return 'primary';
  201. case 2: return 'success';
  202. case 3: return 'danger';
  203. default: return 'info';
  204. }
  205. }
  206. // 根据状态返回对应的标签文案
  207. const getTagText = (state) => {
  208. switch (state) {
  209. case 0: return t('elmessage.pendingExecution');
  210. case 1: return t('elmessage.executing');
  211. case 2: return t('elmessage.executed');
  212. case 3: return t('elmessage.errorExecution');
  213. default: return t('elmessage.unknownStatus');
  214. }
  215. }
  216. onMounted(() => {
  217. getMarket()
  218. fetchData()
  219. })
  220. </script>
  221. <template>
  222. <div class="cash-flow-container">
  223. <!-- 搜索区域 -->
  224. <el-card class="search-card">
  225. <div class="search-bar">
  226. <!-- 第一行 -->
  227. <div class="search-row">
  228. <div class="search-item">
  229. <span class="label">精网号</span>
  230. <el-input v-model="queryParams.jwcode" placeholder="请输入精网号" clearable />
  231. </div>
  232. <div class="search-item">
  233. <span class="label">所属地区</span>
  234. <!-- 下拉多选使用 el-cascader 匹配地区树结构 -->
  235. <el-cascader
  236. v-model="queryParams.customerMarket"
  237. :options="marketOptions"
  238. :props="{ multiple: true, emitPath: false }"
  239. collapse-tags
  240. collapse-tags-tooltip
  241. placeholder="请选择地区"
  242. clearable
  243. style="width: 220px;"
  244. />
  245. </div>
  246. <div class="search-item" style="width: auto;">
  247. <span class="label">付款时间</span>
  248. <el-date-picker
  249. v-model="queryParams.timeRange"
  250. type="datetimerange"
  251. range-separator="至"
  252. start-placeholder="开始时间"
  253. end-placeholder="结束时间"
  254. :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
  255. style="width: 350px;"
  256. />
  257. </div>
  258. <div class="search-btn-group">
  259. <el-button type="primary" @click="handleSearch">{{ t('common.search') }}</el-button>
  260. <el-button type="primary" @click="handleExport">{{ t('common.exportExcel') }}</el-button>
  261. <el-button type="primary" @click="openExportList">{{ t('common.viewExportList') }}</el-button>
  262. <el-button type="success" @click="handleReset">{{ t('common.reset') }}</el-button>
  263. </div>
  264. </div>
  265. </div>
  266. </el-card>
  267. <!-- 表格区域 -->
  268. <el-card class="table-card">
  269. <el-table :data="tableData" v-loading="loading" style="width: 100%; flex: 1;" :header-cell-style="{ background: '#F3FAFE', color: '#333' }">
  270. <el-table-column type="index" label="序号" width="60" align="center" fixed="left" />
  271. <el-table-column prop="jwcode" label="精网号" width="120" fixed="left" />
  272. <el-table-column prop="name" label="姓名" width="120" show-overflow-tooltip />
  273. <el-table-column prop="market" label="所属地区" width="120" show-overflow-tooltip />
  274. <el-table-column prop="orderCode" label="系统交易号" width="180" show-overflow-tooltip />
  275. <el-table-column prop="paymentAmount" label="付款金额" width="150" align="right">
  276. <!-- <template #default="{ row }">
  277. {{ row.paymentAmount }} {{ row.paymentCurrency }}
  278. </template> -->
  279. </el-table-column>
  280. <el-table-column prop="paymentCurrency" label="付款币种" width="180" show-overflow-tooltip />
  281. <el-table-column prop="receivedAmount" label="到账金额" width="150" align="right">
  282. <!-- <template #default="{ row }">
  283. {{ row.receivedAmount }} {{ row.receivedCurrency }}
  284. </template> -->
  285. </el-table-column>
  286. <el-table-column prop="receivedCurrency" label="到账币种" width="180" show-overflow-tooltip />
  287. <el-table-column prop="handlingCharge" label="手续费" width="100" align="right" />
  288. </el-table>
  289. <!-- 分页 -->
  290. <div class="pagination-container">
  291. <el-pagination
  292. background
  293. layout="total, sizes, prev, pager, next, jumper"
  294. :total="total"
  295. :current-page="queryParams.pageNum"
  296. :page-size="queryParams.pageSize"
  297. :page-sizes="[10, 20, 50, 100]"
  298. @size-change="handlePageSizeChange"
  299. @current-change="handleCurrentChange"
  300. />
  301. </div>
  302. </el-card>
  303. <!-- 导出列表弹窗 -->
  304. <el-dialog v-model="exportListVisible" :title="t('common_export.exportList')" width="80%">
  305. <el-table :data="exportList" style="width: 100% ;height: 60vh;" :loading="exportListLoading">
  306. <el-table-column prop="fileName" :label="t('common_export.fileName')" />
  307. <el-table-column prop="state" :label="t('common_export.status')">
  308. <template #default="scope">
  309. <el-tag :type="getTagType(scope.row.state)" :effect="scope.row.state === 3 ? 'light' : 'plain'">
  310. {{ getTagText(scope.row.state) }}
  311. </el-tag>
  312. </template>
  313. </el-table-column>
  314. <el-table-column prop="createTime" :label="t('common_export.createTime')">
  315. <template #default="scope">
  316. {{ dayjs(scope.row.createTime).format('YYYY-MM-DD HH:mm:ss') }}
  317. </template>
  318. </el-table-column>
  319. <el-table-column :label="t('common_export.operation')">
  320. <template #default="scope">
  321. <el-button type="primary" size="small" @click="downloadExportFile(scope.row)"
  322. :disabled="scope.row.state !== 2">
  323. {{ t('common_export.download') }}
  324. </el-button>
  325. </template>
  326. </el-table-column>
  327. </el-table>
  328. <template #footer>
  329. <div class="dialog-footer">
  330. <el-button text @click="exportListVisible = false">{{ t('common_export.close') }}</el-button>
  331. </div>
  332. </template>
  333. </el-dialog>
  334. </div>
  335. </template>
  336. <style scoped lang="scss">
  337. .cash-flow-container {
  338. display: flex;
  339. flex-direction: column;
  340. height: 100%;
  341. }
  342. .search-card {
  343. margin-bottom: 10px;
  344. background: #F3FAFE; // 浅蓝背景
  345. border: none;
  346. :deep(.el-card__body) {
  347. padding: 15px;
  348. }
  349. }
  350. .search-bar {
  351. display: flex;
  352. flex-direction: column;
  353. gap: 15px;
  354. }
  355. .search-row {
  356. display: flex;
  357. flex-wrap: wrap;
  358. gap: 20px;
  359. align-items: center;
  360. }
  361. .search-item {
  362. display: flex;
  363. align-items: center;
  364. .label {
  365. font-size: 15px; // 参考 coinConsumeDetail 的 .text size="large"
  366. color: #000; // 或 #606266
  367. white-space: nowrap;
  368. margin-right: 8px;
  369. min-width: 60px;
  370. text-align: right;
  371. }
  372. .el-input, .el-select {
  373. width: 200px;
  374. }
  375. }
  376. .search-btn-group {
  377. margin-left: 2vw;
  378. display: flex;
  379. gap: 10px;
  380. }
  381. .table-card {
  382. background: #E7F4FD;
  383. flex: 1;
  384. border: none;
  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. .pagination-container {
  396. margin-top: 15px;
  397. display: flex;
  398. justify-content: flex-start;
  399. }
  400. // 表格样式覆盖 (参考 coinConsumeDetail)
  401. :deep(.el-table__header-wrapper),
  402. :deep(.el-table__body-wrapper),
  403. :deep(.el-table__cell),
  404. :deep(.el-table__body td) {
  405. background-color: #F3FAFE !important; // 如果想完全一致可以加这个,但有时候会影响阅读,暂保留头部颜色
  406. }
  407. :deep(.el-table__row:hover > .el-table__cell) {
  408. background-color: #E5EBFE !important;
  409. }
  410. </style>