record.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. <template>
  2. <cwg-page-wrapper class="create-page" :isHeaderFixed="true">
  3. <cwg-header :title="t('Documentary.TundManagement.item10')" />
  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="currentColumns" :immediate="false" :queryParams="search" :api="listApi"
  8. :show-operation="false">
  9. <template #status="{ row }">
  10. <view v-if="getStatusText(row)" class="status-tag" :class="getStatusClass(row)">
  11. {{ getStatusText(row) }}
  12. </view>
  13. <view v-else></view>
  14. </template>
  15. <template #accountType="{ row }">
  16. {{ getAccountTypeText(row.dealLoginType || row.followAccountType) }}
  17. </template>
  18. </cwg-tabel>
  19. </view>
  20. </cwg-page-wrapper>
  21. </template>
  22. <script setup lang="ts">
  23. import { computed, ref, reactive, nextTick, watch } from 'vue';
  24. import { useI18n } from 'vue-i18n';
  25. const { t, locale } = useI18n();
  26. import { documentaryApi } from '@/service/documentary';
  27. import useUserStore from "@/stores/use-user-store";
  28. const userStore = useUserStore();
  29. const userInfo = computed(() => userStore.userInfo);
  30. import { useAccountOptions } from '@/composables/useAccountOptions'
  31. const { loginOptions, isLoaded, isSuccess } = useAccountOptions()
  32. import { useFilters } from '@/composables/useFilters'
  33. import {useFollowEnum} from '@/pages/follow/const/enum'
  34. const {optObj} = useFollowEnum()
  35. const { numberFormat, numberDesensitization, numberDecimal } = useFilters()
  36. const search = reactive({
  37. type: 1,
  38. followLogin: null,
  39. status: null
  40. })
  41. const typeMap = computed(() => ([
  42. { value: 1, text: t('Documentary.TundManagement.item58') },
  43. { value: 2, text: t('Documentary.TundManagement.item47') },
  44. ]));
  45. const isZh = computed(() => ['cn', 'zh', 'zhHant'].includes(locale.value));
  46. // 账户类型映射
  47. const accountTypeMap = {
  48. 1: 'AccountType.ClassicAccount',
  49. 2: 'AccountType.SeniorAccount',
  50. 5: 'AccountType.SpeedAccount',
  51. 6: 'AccountType.SpeedAccount',
  52. 7: 'AccountType.StandardAccount',
  53. 8: 'AccountType.CentAccount'
  54. }
  55. // 拒绝原因映射(示例)
  56. const reasons = ref({})
  57. // 根据类型获取列配置
  58. const getColumnsByType = (type: number) => {
  59. switch (type) {
  60. case 1: //信号源申请
  61. return [
  62. {
  63. prop: 'dealLogin',
  64. label: t('Documentary.TundManagement.item25'),
  65. align: 'center',
  66. headerAlign: 'center',
  67. formatter: ({ row }) => numberDesensitization(row.dealLogin) || '--'
  68. },
  69. {
  70. prop: 'dealPlatform',
  71. label: t('Label.Platform'),
  72. align: 'center',
  73. headerAlign: 'center',
  74. formatter: ({ row }) => row.dealPlatform || '--'
  75. },
  76. {
  77. prop: 'dealLoginType',
  78. label: t('Label.AccountType'),
  79. align: 'center',
  80. slot: 'accountType' // 多语言枚举
  81. },
  82. {
  83. prop: 'dealLeverage',
  84. label: t('Label.Leverage'),
  85. align: 'center',
  86. formatter: ({ row }) => row.dealLeverage ? `1:${row.dealLeverage}` : '--'
  87. },
  88. {
  89. prop: 'distributionType',
  90. label: t('Documentary.TundManagement.item38'),
  91. align: 'center',
  92. headerAlign: 'center',
  93. formatter: ({ row }) => row.distributionType === 1 ? t('Documentary.TundManagement.item59') : '--'
  94. },
  95. {
  96. prop: 'distributionRatio',
  97. label: t('Documentary.TundManagement.item39'),
  98. align: 'center',
  99. headerAlign: 'center',
  100. formatter: ({ row }) => (row.distributionRatio || '0') + '%'
  101. },
  102. {
  103. prop: 'settlementCycle',
  104. label: t('Documentary.TundManagement.item55'),
  105. align: 'center',
  106. headerAlign: 'center',
  107. formatter: ({ row }) => {
  108. return optObj.value[row.settlementCycle] || '--'
  109. }
  110. },
  111. {
  112. prop: 'approveTime',
  113. label: t('Documentary.TundManagement.item56'),
  114. align: 'center',
  115. formatter: ({ row }) => row.approveTime || '--'
  116. },
  117. {
  118. prop: 'status',
  119. label: t('Documentary.TundManagement.item57'),
  120. align: 'center',
  121. slot: 'status' // 状态
  122. },
  123. {
  124. prop: 'approveDesc',
  125. label: t('Label.Note'),
  126. align: 'center',
  127. type: 'note' // 备注多语言
  128. }
  129. ]
  130. case 2: // 取消记录
  131. return [
  132. {
  133. prop: 'dealNickname',
  134. label: t('Documentary.tradingCenter.item1'),
  135. align: 'center',
  136. headerAlign: 'center',
  137. formatter: ({ row }) => row.dealNickname || '--'
  138. },
  139. {
  140. prop: 'dealLogin',
  141. label: t('Documentary.tradingCenter.item18'),
  142. align: 'center',
  143. headerAlign: 'center',
  144. formatter: ({ row }) => numberDesensitization(row.dealLogin) || '--'
  145. },
  146. {
  147. prop: 'followLogin',
  148. label: t('Documentary.console.item28'),
  149. align: 'center',
  150. headerAlign: 'center',
  151. formatter: ({ row }) => row.followLogin || '--'
  152. },
  153. {
  154. prop: 'followPlatform',
  155. label: t('Label.Platform'),
  156. align: 'center',
  157. formatter: ({ row }) => row.followPlatform || '--'
  158. },
  159. {
  160. prop: 'followLoginType',
  161. label: t('Label.AccountType'),
  162. align: 'center',
  163. slot: 'followAccountType'
  164. },
  165. {
  166. prop: 'followLeverage',
  167. label: t('Label.Leverage'),
  168. align: 'center',
  169. formatter: ({ row }) => row.followLeverage ? `1:${row.followLeverage}` : '--'
  170. },
  171. {
  172. prop: 'distributionType',
  173. label: t('Documentary.TundManagement.item38'),
  174. align: 'center',
  175. headerAlign: 'center',
  176. formatter: () => t('Documentary.TundManagement.item59')
  177. },
  178. {
  179. prop: 'distributionRatio',
  180. label: t('Documentary.TundManagement.item39'),
  181. align: 'center',
  182. headerAlign: 'center',
  183. formatter: ({ row }) => (row.distributionRatio || '0') + '%'
  184. },
  185. {
  186. prop: 'settlementCycle',
  187. label: t('Documentary.TundManagement.item55'),
  188. align: 'center',
  189. headerAlign: 'center',
  190. formatter: ({ row }) => {
  191. return optObj.value[row.settlementCycle] || '--'
  192. }
  193. },
  194. {
  195. prop: 'timeRange',
  196. label: t('Documentary.TundManagement.item51') + ' / ' + t('Documentary.TundManagement.item52'),
  197. align: 'center',
  198. slot: 'timeRange' // 开始-结束时间
  199. },
  200. {
  201. prop: 'endTime',
  202. label: t('Documentary.TundManagement.item56'),
  203. align: 'center',
  204. formatter: ({ row }) => row.endTime || '--'
  205. }
  206. ]
  207. }
  208. }
  209. const setSearchValue = (key, value) => {
  210. search[key] = value
  211. }
  212. const filterFields = computed(() => {
  213. return [
  214. { key: 'type', type: 'select', label: t('Custom.PaymentHistory.payType'), placeholder: t('placeholder.choose'), options: typeMap.value },
  215. isLoaded.value && isSuccess.value && { key: search.type == 2 ? 'followLogin':'login', type: 'select', label: t('Custom.PaymentHistory.TradingAccount'), placeholder: t('placeholder.login'), options: loginOptions || [], defaultValue: search.login || undefined, clearable: true },
  216. { key: 'date', label: t('placeholder.Start') + ' - ' + t('placeholder.End'), type: 'daterange' }
  217. ]
  218. })
  219. const searchParams = ref({
  220. type: 1,
  221. })
  222. const tableRef = ref(null)
  223. const handleSearch = (params) => {
  224. searchParams.value = {...params}
  225. Object.assign(search, params)
  226. nextTick(() => {
  227. tableRef.value?.refreshTable()
  228. })
  229. }
  230. const handleReset = (params) => {
  231. Object.assign(search, params)
  232. searchParams.value = {
  233. ...params,
  234. type: 1
  235. }
  236. nextTick(() => {
  237. tableRef.value?.refreshTable()
  238. })
  239. }
  240. const currentColumns = computed(() => getColumnsByType(search.type))
  241. const getStatusText = (row: any) => {
  242. const status = row.status
  243. if (search.type === 1) {
  244. if (status === 0) return t('State.ToBeProcessed')
  245. if (status === 2) return t('State.Completed')
  246. if (status === 1) return t('State.InTheProcessing')
  247. if (status === 3) return t('State.Refused')
  248. } else if (search.type === 2) {
  249. if (status === 1) return t('State.ToBeProcessed')
  250. if (status === 2 && row.leverageStatus === 2) return t('State.Completed')
  251. if (status === 2 && row.leverageStatus === 1) return t('State.InTheProcessing')
  252. if (status === 3) return t('State.Refused')
  253. } else if (search.type === 3 || search.type === 5) {
  254. if (status === 1) return t('State.ToBeProcessed')
  255. if (status === 2 && row.withdrawStatus === 2 && row.depositStatus === 2) return t('State.Completed')
  256. if (status === 2 && (row.withdrawStatus === 1 || row.depositStatus === 1)) return t('State.InTheProcessing')
  257. if (status === 3 || row.withdrawStatus === 3 || row.depositStatus === 3) return t('State.Refused')
  258. } else {
  259. // 活动申请等
  260. if (status === 1) return t('State.ToBeProcessed')
  261. if (status === 2) return t('State.Completed')
  262. if (status === 3) return t('State.Refused')
  263. }
  264. return ''
  265. }
  266. // 获取状态样式类
  267. const getStatusClass = (row: any) => {
  268. const text = getStatusText(row)
  269. if (text === t('State.Refused')) return 'status-failed'
  270. if (text === t('State.ToBeProcessed')) return 'status-pending'
  271. if (text === t('State.Completed')) return 'status-success'
  272. if (text === t('State.InTheProcessing')) return 'status-processing'
  273. return ''
  274. }
  275. // 获取账户类型文本
  276. const getAccountTypeText = (type: number) => {
  277. const key = accountTypeMap[type as keyof typeof accountTypeMap]
  278. return key ? t(key) : '--'
  279. }
  280. // 格式化数字
  281. const formatNumber = (value: string | number) => {
  282. if (!value) return '--'
  283. const num = Number(value)
  284. return isNaN(num) ? '--' : num.toFixed(2)
  285. }
  286. // 格式化备注
  287. const formatNote = (approveDesc: string) => {
  288. if (!approveDesc) return '--'
  289. const reason = reasons.value[approveDesc as keyof typeof reasons.value]
  290. if (reason) {
  291. return isZh.value ? reason.content : reason.enContent
  292. }
  293. return approveDesc
  294. }
  295. const listApi = ref(null)
  296. watch(() => search.type, (newVal) => {
  297. switch (newVal) {
  298. case 1:
  299. setSearchValue('status', null)
  300. setSearchValue('followLogin', null)
  301. listApi.value = documentaryApi.followDealList
  302. break
  303. case 2:
  304. setSearchValue('status', null)
  305. setSearchValue('followLogin', null)
  306. listApi.value = documentaryApi.followDealSubscribeList
  307. setSearchValue('status', 2)
  308. break
  309. }
  310. }, { immediate: true })
  311. </script>
  312. <style scoped lang="scss">
  313. @import "@/uni.scss";
  314. .avatar {
  315. width: px2rpx(60);
  316. height: px2rpx(60);
  317. border-radius: 4px;
  318. }
  319. .content-title {
  320. display: flex;
  321. justify-content: space-between;
  322. align-items: center;
  323. font-size: px2rpx(20);
  324. font-weight: 500;
  325. .content-title-btns {
  326. margin: px2rpx(8) 0;
  327. display: flex;
  328. align-items: center;
  329. justify-content: center;
  330. gap: px2rpx(12);
  331. .btn-primary {
  332. min-width: px2rpx(120);
  333. background-color: var(--color-error);
  334. color: white;
  335. padding: 0 px2rpx(12);
  336. border: none;
  337. font-size: px2rpx(14);
  338. text-align: center;
  339. cursor: pointer;
  340. display: flex;
  341. align-items: center;
  342. justify-content: center;
  343. gap: px2rpx(8);
  344. }
  345. .btn-primary:active {
  346. background-color: #cf1322;
  347. ;
  348. }
  349. }
  350. }
  351. .operation-btn {
  352. :deep(span) {
  353. display: flex;
  354. align-items: center;
  355. justify-content: center;
  356. gap: px2rpx(4);
  357. cursor: pointer;
  358. background-color: var(--color-slate-150);
  359. padding: px2rpx(8) 0;
  360. }
  361. }
  362. .operation-btn.disabled {
  363. cursor: not-allowed;
  364. opacity: 0.5;
  365. }
  366. .search-bar {
  367. display: flex;
  368. align-items: center;
  369. justify-content: flex-start;
  370. flex-wrap: wrap;
  371. gap: px2rpx(16);
  372. margin: px2rpx(16) 0;
  373. .cwg-combox,
  374. .uni-easyinput,
  375. .uni-date {
  376. width: px2rpx(240) !important;
  377. flex: none;
  378. }
  379. }
  380. </style>