payment-history.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. <template>
  2. <cwg-page-wrapper class="create-page" :isHeaderFixed="true">
  3. <cwg-header :title="t('Home.page_customer.item4')" />
  4. <view class="info-card">
  5. <cwg-complex-search :fields="filterFields" v-model="searchParams" @search="handleSearch"
  6. @reset="handleReset" />
  7. <cwg-tabel ref="tableRef" :columns="columns" :immediate="false" :mobilePrimaryFields="mobilePrimaryFields"
  8. :queryParams="search" :api="listApi" :show-operation="false">
  9. <template #avatar="{ row }">
  10. <image :src="row.avatar" class="avatar" mode="widthFix" />
  11. <cwg-file :path="row.path" />
  12. </template>
  13. <template #type="{ row }">
  14. {{typeMap.find(item => item.value === row.type)?.text}}
  15. </template>
  16. <template #status="{ row }">
  17. <view class="status-box">
  18. <template v-if="row.type == 1">
  19. <text v-if="
  20. row.status == 1 &&
  21. row.callbackStatus == 0 &&
  22. (row.expireTime == null || time < row.expireTime)
  23. " class="status-tag status-pending" v-t="'State.ToBeProcessed'"></text>
  24. <text v-if="
  25. row.status == 2 &&
  26. row.executionStatus == 2 &&
  27. row.callbackStatus == 1
  28. " class="status-tag status-completed" v-t="'State.Completed'"></text>
  29. <text v-if="
  30. row.status == 2 &&
  31. (row.executionStatus == 1 || row.executionStatus == 0) &&
  32. row.callbackStatus != 2 &&
  33. (row.expireTime == null || time < row.expireTime)
  34. " class="status-tag status-processing" v-t="'State.InTheProcessing'"></text>
  35. <text v-if="
  36. row.callbackStatus == 2 ||
  37. row.status == 3 ||
  38. row.executionStatus == 3
  39. " class="status-tag status-failed" v-t="'State.Refused'"></text>
  40. <text v-if="
  41. !(
  42. row.status == 2 &&
  43. row.executionStatus == 2 &&
  44. row.callbackStatus == 1
  45. ) &&
  46. !(
  47. row.callbackStatus == 2 ||
  48. row.status == 3 ||
  49. row.executionStatus == 3
  50. ) &&
  51. row.expireTime != null &&
  52. time > row.expireTime
  53. " class="status-tag status-expired" v-t="'State.expireTime'"></text>
  54. </template>
  55. <template v-if="row.type == 2">
  56. <text v-if="
  57. row.status == 1 &&
  58. row.callbackStatus == 0 &&
  59. (row.expireTime == null || time < row.expireTime)
  60. " class="status-tag status-pending" v-t="'State.ToBeProcessed'"></text>
  61. <text v-if="
  62. row.status == 2 &&
  63. row.executionStatus == 2 &&
  64. row.submitStatus == 2
  65. " class="status-tag status-completed" v-t="'State.Completed'"></text>
  66. <text v-if="
  67. (row.status == 2 &&
  68. (row.executionStatus == 1 || row.executionStatus == 0) &&
  69. row.callbackStatus != 2) ||
  70. (row.status == 2 &&
  71. row.executionStatus == 2 &&
  72. row.callbackStatus == 0 &&
  73. row.type == 2 &&
  74. row.submitStatus != 2)
  75. " class="status-tag status-processing" v-t="'State.InTheProcessing'"></text>
  76. <text v-if="
  77. row.callbackStatus == 2 ||
  78. row.status == 3 ||
  79. row.executionStatus == 3
  80. " class="status-tag status-failed" v-t="'State.Refused'"></text>
  81. </template>
  82. <text v-if="row.status == 5" class="status-tag status-cancelled" v-t="'State.Cancelled'"></text>
  83. <!-- 取消按钮(保留原有功能,不添加状态类名) -->
  84. <view v-if="
  85. row.status == 1 &&
  86. row.callbackStatus == 0 &&
  87. (row.expireTime == null || time < row.expireTime) &&
  88. row.type == 2
  89. ">
  90. <text class="status-tag btn crm-cursor status-cancelled" v-t="'Btn.Cancel'"
  91. @click.stop="cancleConfirm(row.id, 1)"></text>
  92. </view>
  93. <view v-if="
  94. row.status == 2 &&
  95. row.executionStatus == 2 &&
  96. row.backstageStatus == 1 &&
  97. row.type == 2
  98. ">
  99. <text class="status-tag btn crm-cursor status-cancelled" v-t="'Btn.Cancel'"
  100. @click.stop="cancleConfirm(row.id, 2)"></text>
  101. </view>
  102. </view>
  103. </template>
  104. <template #btn="{ row }">
  105. <text :class="['operation-btn', row.status !== 4 ? 'disabled' : '']" @click="openAddFile(row)">
  106. <cwg-icon name="crm-image" :size="16" color="#1d293d" />
  107. <text v-t="'State.Again'" />
  108. </text>
  109. </template>
  110. </cwg-tabel>
  111. </view>
  112. <cwg-confirm-popup />
  113. </cwg-page-wrapper>
  114. </template>
  115. <script setup lang="ts">
  116. import { computed, ref, nextTick, reactive, watch } from 'vue';
  117. import { useI18n } from 'vue-i18n';
  118. import { onLoad } from '@dcloudio/uni-app'
  119. const { t, locale } = useI18n();
  120. import { financialApi } from '@/service/financial';
  121. import { useConfirm } from '@/hooks/useConfirm'
  122. import { useAccountOptions } from '@/composables/useAccountOptions'
  123. const { loginOptions, isLoaded, isSuccess } = useAccountOptions()
  124. const search = reactive({
  125. login: null,
  126. type: null,
  127. orderStatus: null,
  128. date: null
  129. })
  130. const typeMap = computed(() => ([
  131. { value: null, text: t('Custom.PaymentHistory.All') },
  132. { value: 1, text: t('Custom.PaymentHistory.Deposit') },
  133. { value: 2, text: t('Custom.PaymentHistory.Withdrawals') }
  134. ]));
  135. const orderStatusMap = computed(() => ([
  136. { value: null, text: t('Custom.PaymentHistory.All') },
  137. { value: 1, text: t('State.ToBeProcessed') },
  138. { value: 2, text: t('State.Completed') },
  139. { value: 3, text: t('State.InTheProcessing') },
  140. { value: 4, text: t('State.Refused') },
  141. { value: 5, text: t('State.expireTime') },
  142. { value: 6, text: t('State.Cancelled') },
  143. ]));
  144. const isZh = computed(() => ['cn', 'zh', 'zhHant'].includes(locale.value));
  145. const time = computed(() => {
  146. let timezone = 2; //目标时区时间,东3区 东时区正数 西市区负数
  147. let offset_GMT = new Date().getTimezoneOffset(); // 本地时间和格林威治的时间差,单位为分钟
  148. let nowDate = new Date().getTime(); // 本地时间距 1970 年 1 月 1 日午夜(GMT 时间)之间的毫秒数
  149. let now = new Date(
  150. nowDate + offset_GMT * 60 * 1000 + timezone * 60 * 60 * 1000
  151. );
  152. //当前时间
  153. let hh = now.getHours(); //时
  154. let mm = now.getMinutes(); //分
  155. let ss = now.getSeconds(); //分
  156. let clock;
  157. let year = now.getFullYear(); //年
  158. let month = now.getMonth() + 1; //月
  159. let day = now.getDate(); //日
  160. if (month < 10) {
  161. month = "0" + month;
  162. }
  163. if (day < 10) {
  164. day = "0" + day;
  165. }
  166. if (hh < 10) {
  167. hh = "0" + hh;
  168. }
  169. if (mm < 10) {
  170. mm = "0" + mm;
  171. }
  172. if (ss < 10) {
  173. ss = "0" + ss;
  174. }
  175. clock = year + "-" + month + "-" + day + " " + hh + ":" + mm + ":" + ss;
  176. return clock;
  177. })
  178. // 表格列配置
  179. const columns = computed(() => [
  180. {
  181. prop: 'serial',
  182. label: t('Custom.PaymentHistory.Serial'),
  183. align: 'left'
  184. },
  185. {
  186. prop: 'login',
  187. label: t('Custom.PaymentHistory.TradingAccount'),
  188. align: 'left'
  189. },
  190. {
  191. prop: 'type',
  192. label: t('Custom.PaymentHistory.payType'),
  193. align: 'left',
  194. slot: 'type'
  195. },
  196. {
  197. prop: 'channelName',
  198. label: t('Custom.PaymentHistory.PaymentMethod'),
  199. formatter: ({ row }) => isZh.value ? row.channelName : row.channelEnName,
  200. align: 'left'
  201. },
  202. {
  203. prop: 'amount',
  204. label: t('Custom.PaymentHistory.Amount'),
  205. formatter: ({ row }) => row.amount + ' ' + row.currency,
  206. align: 'left'
  207. },
  208. {
  209. prop: 'addTime',
  210. label: t('Custom.PaymentHistory.ApplicationDate'),
  211. type: 'date',
  212. dateFormat: 'YYYY-MM-DD HH:mm',
  213. align: 'left'
  214. },
  215. {
  216. prop: 'status',
  217. label: t('Custom.PaymentHistory.Status'),
  218. slot: 'status',
  219. align: 'left'
  220. },
  221. {
  222. prop: 'note',
  223. label: t('Custom.Recording.Note'),
  224. type: 'note',
  225. align: 'right'
  226. },
  227. {
  228. prop: 'more',
  229. type: 'more',
  230. width: 20,
  231. align: 'right'
  232. },
  233. ])
  234. // 动态传入筛选字段配置
  235. const filterFields = computed(() => [
  236. { key: 'type', type: 'select', label: t('Custom.PaymentHistory.payType'), placeholder: t('placeholder.choose'), options: typeMap.value, defaultValue: null },
  237. {
  238. key: 'orderStatus', type: 'select', label: t('Custom.PaymentHistory.Status'), placeholder: t('placeholder.choose'), options: orderStatusMap.value, defaultValue: null
  239. },
  240. isLoaded.value && isSuccess.value && { key: 'login', type: 'select', label: t('Custom.PaymentHistory.TradingAccount'), placeholder: t('placeholder.login'), options: loginOptions || [], defaultValue: undefined, clearable: true },
  241. { key: 'date', label: t('placeholder.Start') + ' - ' + t('placeholder.End'), type: 'daterange' }
  242. ])
  243. const searchParams = ref({})
  244. const handleSearch = (params, type) => {
  245. Object.assign(search, params)
  246. search.platform = loginOptions?.find(item => item.value === params.login)?.platform || ''
  247. search.login = params.login && Number(params.login)
  248. setTimeout(() => {
  249. console.log(search, params, 10000, type);
  250. tableRef.value.refreshTable()
  251. }, 10)
  252. }
  253. const handleReset = (params) => {
  254. Object.assign(search, params)
  255. search.platform = loginOptions?.find(item => item.value === params.login)?.platform || ''
  256. search.login = params.login && Number(params.login)
  257. nextTick(() => {
  258. tableRef.value.refreshTable()
  259. })
  260. }
  261. const mobilePrimaryFields = computed(() => [
  262. {
  263. prop: 'serial',
  264. label: t('Custom.PaymentHistory.Serial'),
  265. align: 'left'
  266. },
  267. {
  268. prop: 'status',
  269. label: t('Custom.PaymentHistory.Status'),
  270. slot: 'status',
  271. align: 'left'
  272. },
  273. {
  274. prop: 'amount',
  275. label: t('Custom.PaymentHistory.Amount'),
  276. formatter: ({ row }) => row.amount + ' ' + row.currency,
  277. align: 'right'
  278. },
  279. {
  280. prop: 'more',
  281. type: 'more',
  282. width: 20,
  283. align: 'right'
  284. },
  285. ])
  286. const listApi = ref(null)
  287. listApi.value = financialApi.BalanceList
  288. const confirm = useConfirm()
  289. const cancleConfirm = async (id, type) => {
  290. tableRef.value.setDetailVisible(false)
  291. try {
  292. await confirm({
  293. title: t('Msg.SystemPrompt'),
  294. content: t('Msg.Cancle'),
  295. confirmText: t('Btn.item6'),
  296. cancelText: t('Btn.item7')
  297. })
  298. cancle(id, type)
  299. } catch (error) {
  300. }
  301. }
  302. const tableRef = ref(null)
  303. const cancle = async (id, type) => {
  304. try {
  305. let api
  306. if (type == 1) {
  307. api = financialApi.withdrawCancel
  308. } else {
  309. api = financialApi.withdrawCancelBackstage
  310. }
  311. let res = await api({ id: id });
  312. if (res.code == 200) {
  313. uni.showToast({
  314. title: t("Msg.Success"),
  315. icon: 'success'
  316. });
  317. tableRef.value.refreshTable()
  318. } else {
  319. uni.showToast({
  320. title: res.msg,
  321. icon: 'error'
  322. });
  323. }
  324. } catch (error) {
  325. console.log('取消删除')
  326. }
  327. }
  328. onLoad(async (e) => {
  329. let targetLogin: number | null = null
  330. if (e.login) {
  331. targetLogin = Number(e.login)
  332. }
  333. await new Promise<void>(resolve => {
  334. const stopWatch = watch([isLoaded, isSuccess], ([loaded, success]) => {
  335. if (loaded && success) {
  336. stopWatch()
  337. resolve()
  338. }
  339. })
  340. })
  341. searchParams.value.login = targetLogin
  342. search.platform = loginOptions?.find(item => item.value === searchParams.value.login)?.platform || ''
  343. search.login = searchParams.value.login && Number(searchParams.value.login)
  344. })
  345. </script>
  346. <style scoped lang="scss">
  347. @import "@/uni.scss";
  348. .avatar {
  349. width: px2rpx(60);
  350. height: px2rpx(60);
  351. border-radius: 4px;
  352. }
  353. .content-title {
  354. display: flex;
  355. justify-content: space-between;
  356. align-items: center;
  357. font-size: px2rpx(20);
  358. font-weight: 500;
  359. .content-title-btns {
  360. margin: px2rpx(8) 0;
  361. display: flex;
  362. align-items: center;
  363. justify-content: center;
  364. gap: px2rpx(12);
  365. .btn-primary {
  366. min-width: px2rpx(120);
  367. background-color: var(--color-error);
  368. color: white;
  369. padding: 0 px2rpx(12);
  370. border: none;
  371. font-size: px2rpx(14);
  372. text-align: center;
  373. cursor: pointer;
  374. display: flex;
  375. align-items: center;
  376. justify-content: center;
  377. gap: px2rpx(8);
  378. }
  379. .btn-primary:active {
  380. background-color: #cf1322;
  381. ;
  382. }
  383. }
  384. }
  385. .operation-btn {
  386. :deep(text) {
  387. display: flex;
  388. align-items: center;
  389. justify-content: center;
  390. gap: px2rpx(4);
  391. cursor: pointer;
  392. background-color: var(--color-slate-150);
  393. padding: px2rpx(8) 0;
  394. }
  395. }
  396. .operation-btn.disabled {
  397. cursor: not-allowed;
  398. opacity: 0.5;
  399. }
  400. </style>