payment-history.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  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. <view 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. </view>
  55. <view 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. </view>
  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 } 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: search.login || undefined, clearable: true },
  241. { key: 'date', label: t('placeholder.Start') + ' - ' + t('placeholder.End'), type: 'daterange' }
  242. ])
  243. const searchParams = ref({})
  244. const handleSearch = (params) => {
  245. Object.assign(search, params)
  246. search.platform = loginOptions.find(item => item.value === params.login)?.platform || ''
  247. nextTick(() => {
  248. tableRef.value.refreshTable()
  249. })
  250. }
  251. const handleReset = (params) => {
  252. Object.assign(search, params)
  253. search.platform = loginOptions.find(item => item.value === params.login)?.platform || ''
  254. nextTick(() => {
  255. tableRef.value.refreshTable()
  256. })
  257. }
  258. const mobilePrimaryFields = computed(() => [
  259. {
  260. prop: 'serial',
  261. label: t('Custom.PaymentHistory.Serial'),
  262. align: 'left'
  263. },
  264. {
  265. prop: 'status',
  266. label: t('Custom.PaymentHistory.Status'),
  267. slot: 'status',
  268. align: 'left'
  269. },
  270. {
  271. prop: 'amount',
  272. label: t('Custom.PaymentHistory.Amount'),
  273. formatter: ({ row }) => row.amount + ' ' + row.currency,
  274. align: 'right'
  275. },
  276. {
  277. prop: 'more',
  278. type: 'more',
  279. width: 20,
  280. align: 'right'
  281. },
  282. ])
  283. const listApi = ref(null)
  284. listApi.value = financialApi.BalanceList
  285. const confirm = useConfirm()
  286. const cancleConfirm = async (id, type) => {
  287. tableRef.value.setDetailVisible(false)
  288. try {
  289. await confirm({
  290. title: t('Msg.SystemPrompt'),
  291. content: t('Msg.Cancle'),
  292. confirmText: t('Btn.item6'),
  293. cancelText: t('Btn.item7')
  294. })
  295. cancle(id, type)
  296. } catch (error) {
  297. }
  298. }
  299. const tableRef = ref(null)
  300. const cancle = async (id, type) => {
  301. try {
  302. let api
  303. if (type == 1) {
  304. api = financialApi.withdrawCancel
  305. } else {
  306. api = financialApi.withdrawCancelBackstage
  307. }
  308. let res = await api({ id: id });
  309. if (res.code == 200) {
  310. uni.showToast({
  311. title: t("Msg.Success"),
  312. icon: 'success'
  313. });
  314. tableRef.value.refreshTable()
  315. } else {
  316. uni.showToast({
  317. title: res.msg,
  318. icon: 'error'
  319. });
  320. }
  321. } catch (error) {
  322. console.log('取消删除')
  323. }
  324. }
  325. onLoad((e) => {
  326. if (e.login) {
  327. search.login = Number(e.login)
  328. }
  329. })
  330. </script>
  331. <style scoped lang="scss">
  332. @import "@/uni.scss";
  333. .avatar {
  334. width: px2rpx(60);
  335. height: px2rpx(60);
  336. border-radius: 4px;
  337. }
  338. .content-title {
  339. display: flex;
  340. justify-content: space-between;
  341. align-items: center;
  342. font-size: px2rpx(20);
  343. font-weight: 500;
  344. .content-title-btns {
  345. margin: px2rpx(8) 0;
  346. display: flex;
  347. align-items: center;
  348. justify-content: center;
  349. gap: px2rpx(12);
  350. .btn-primary {
  351. min-width: px2rpx(120);
  352. background-color: var(--color-error);
  353. color: white;
  354. padding: 0 px2rpx(12);
  355. border: none;
  356. font-size: px2rpx(14);
  357. text-align: center;
  358. cursor: pointer;
  359. display: flex;
  360. align-items: center;
  361. justify-content: center;
  362. gap: px2rpx(8);
  363. }
  364. .btn-primary:active {
  365. background-color: #cf1322;
  366. ;
  367. }
  368. }
  369. }
  370. .operation-btn {
  371. :deep(text) {
  372. display: flex;
  373. align-items: center;
  374. justify-content: center;
  375. gap: px2rpx(4);
  376. cursor: pointer;
  377. background-color: var(--color-slate-150);
  378. padding: px2rpx(8) 0;
  379. }
  380. }
  381. .operation-btn.disabled {
  382. cursor: not-allowed;
  383. opacity: 0.5;
  384. }
  385. </style>