AccountList.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. <template>
  2. <view class="account-card">
  3. <view class="content-title">
  4. <view v-t="'Custom.Index.AccountList'"></view>
  5. <view class="content-title-btns">
  6. <view class="btn-primary btn-primary1" @click="createAccount()">
  7. <cwg-icon icon="crm-plus" :size="16" color="#fff" />
  8. <text v-t="'Custom.Index.AddAccount'" />
  9. </view>
  10. </view>
  11. </view>
  12. <view class="tabs-container">
  13. <view class="tabs-class">
  14. <cwg-tabs v-model:cativeIndex="cativeIndex" :tabs="tabs" />
  15. </view>
  16. <view class="btn-class">
  17. <view class="btn-primary" :class="{ 'btn-primary2': isGridLayout }"
  18. @click="isGridLayout || toggleLayout()">
  19. <cwg-icon icon="crm-card" :size="16" color="#141d2299" />
  20. </view>
  21. <view class="btn-primary" :class="{ 'btn-primary2': !isGridLayout }"
  22. @click="!isGridLayout || toggleLayout()">
  23. <cwg-icon icon="crm-list" :size="16" color="#141d2299" />
  24. </view>
  25. </view>
  26. </view>
  27. <view v-if="accounts.length" :class="{ 'grid-layout': isGridLayout }">
  28. <AccountCard v-for="acc in accounts" :zhtype="cativeIndex" :key="acc.accountNumber" :account="acc"
  29. :is-grid-layout="isGridLayout" @action="handleAction" @copy="handleCopy"
  30. @change-password="handleChangePassword" />
  31. </view>
  32. <view class="table-loading-mask">
  33. <uni-loading v-if="loading" />
  34. </view>
  35. <cwg-empty-state v-if="!loading && accounts.length == 0" />
  36. <DeleteAccountDialogs ref="deleteAccountDialogRef" v-model:visible="deleteAccountDialogVisible" />
  37. </view>
  38. </template>
  39. <script setup lang="ts">
  40. import { computed, ref, onMounted, watch } from 'vue';
  41. import { useI18n } from 'vue-i18n';
  42. const { t, locale } = useI18n();
  43. import useRouter from "@/hooks/useRouter";
  44. const router = useRouter();
  45. import { customApi } from '@/service/custom';
  46. import { userApi } from '@/api/user';
  47. import useUserStore from "@/stores/use-user-store";
  48. const userStore = useUserStore();
  49. import AccountCard from './AccountCard.vue'
  50. import DeleteAccountDialogs from './DeleteAccountDialogs.vue'
  51. import { useFilters } from '@/composables/useFilters'
  52. const { numberFormat, numberDecimal } = useFilters()
  53. const search = ref({ platform: 'MT4' })
  54. const isAfterJuly28 = () => {
  55. const now = new Date();
  56. const july28 = new Date(2025, 6, 28, 0, 0, 0); // 月份从0开始,所以7月是6
  57. return now >= july28;
  58. }
  59. const handleAction = (type) => { /* 处理交易/入金等 */ };
  60. const handleCopy = (text) => { uni.setClipboardData({ data: text }); };
  61. const handleChangePassword = () => { /* 跳转修改密码 */ };
  62. const typeMap = computed(() => ({
  63. 1: t('AccountType.ClassicAccount'),
  64. 2: t('AccountType.SeniorAccount'),
  65. 3: !isAfterJuly28() ? t('AccountType.AgencyAccount') : '',
  66. 5: t('AccountType.SpeedAccount'),
  67. 6: t('AccountType.SpeedAccount'),
  68. 7: t('AccountType.StandardAccount'),
  69. 8: t('AccountType.CentAccount')
  70. }));
  71. const cativeIndex = ref(0)
  72. const isGridLayout = ref(true)
  73. const tabs = computed(() => ([
  74. { id: 'real', name: t('vu.item1') },
  75. { id: 'demo', name: t('vu.item2') }
  76. ]))
  77. const toggleLayout = () => {
  78. isGridLayout.value = !isGridLayout.value
  79. }
  80. const tableRef = ref(null)
  81. const expanded = ref(null)
  82. const toggleRowExpand = (row) => {
  83. expanded.value = row.expanded ? row.rowIndex : null
  84. }
  85. const toggleExpand = (index) => {
  86. tableRef?.value.toggleRowExpand(index)
  87. }
  88. const createActionButtons = (row) => {
  89. console.log(row, 'row');
  90. return computed(() => [
  91. {
  92. icon: 'crm-circle-dollar-to-slot',
  93. text: t('Custom.Index.Deposit'),
  94. action: 'deposit',
  95. disabled: row.closeFunctions?.indexOf('1') !== -1,
  96. show: true,
  97. class: 'deposit-btn'
  98. },
  99. {
  100. icon: 'crm-credit-card',
  101. text: t('Custom.Index.Withdrawals'),
  102. action: 'withdraw',
  103. disabled: row.closeFunctions?.indexOf('2') !== -1,
  104. show: true,
  105. class: 'withdraw-btn'
  106. },
  107. {
  108. icon: 'crm-money-bill-transfer',
  109. text: t('Custom.Index.Transfer'),
  110. action: 'transfer',
  111. disabled: (
  112. row.closeFunctions?.indexOf('5') !== -1 ||
  113. row.closeFunctions?.indexOf('6') !== -1 ||
  114. row.closeFunctions?.indexOf('3') !== -1
  115. ),
  116. show: true,
  117. class: 'transfer-btn'
  118. },
  119. {
  120. icon: 'crm-gear',
  121. text: t('Custom.Index.Settings'),
  122. action: 'settings',
  123. disabled: row.closeFunctions?.indexOf('4') !== -1,
  124. show: true,
  125. class: 'settings-btn'
  126. },
  127. {
  128. icon: 'crm-award',
  129. text: t('standardRebate.item1'),
  130. action: 'standardRebate',
  131. disabled: false,
  132. show: row.type === 7,
  133. class: 'rebate-btn'
  134. }
  135. ])
  136. }
  137. // 处理按钮点击
  138. const handleActionBtn = (action, row) => {
  139. switch (action) {
  140. case 'deposit':
  141. toDeposit(row)
  142. break
  143. case 'withdraw':
  144. toWithdraw(row)
  145. break
  146. case 'transfer':
  147. toTransfer(row)
  148. break
  149. case 'settings':
  150. toSettings(row)
  151. break
  152. case 'standardRebate':
  153. toStandardRebate(row)
  154. break
  155. }
  156. }
  157. const deleteAccountDialogVisible = ref(false)
  158. const openDeleteAccountDialogs = () => {
  159. deleteAccountDialogVisible.value = true
  160. }
  161. const createAccount = () => {
  162. getCustomLoginInfo()
  163. }
  164. const isZh = computed(() => ['cn', 'zh', 'zhHant'].includes(locale.value));
  165. watch(cativeIndex, (newVal) => {
  166. // search.value.platform = tabs.value[newVal].id
  167. getAccountList()
  168. })
  169. const loading = ref(false)
  170. // 获取客户登录信息
  171. async function getCustomLoginInfo() {
  172. try {
  173. const res = await userApi.getUserInfo();
  174. userStore.saveUserInfo(res.data);
  175. if (res.code === 200) {
  176. if (
  177. res.data.customInfo.status == 2 &&
  178. res.data.customInfo.applyRealStatus == 2
  179. ) {
  180. router.push(`/pages/customer/account-select?server=${cativeIndex.value == 1 ? 'demo' : 'real'}`)
  181. } else {
  182. // this.dialogCheck = true;
  183. }
  184. }
  185. } catch (error) {
  186. // console.log(error, 111);
  187. }
  188. }
  189. const AccountList = ref([])
  190. const getAccountList = async () => {
  191. AccountList.value = []
  192. loading.value = true
  193. const api = cativeIndex.value == 1 ? customApi.demoList : customApi.AccountAllList
  194. const res = await api({
  195. page: {
  196. current: 1,
  197. size: 100
  198. }
  199. })
  200. if (res.code === 200) {
  201. AccountList.value = res.data
  202. }
  203. loading.value = false
  204. }
  205. // 格式化数值函数
  206. function formatMoney(value) {
  207. if (value === null || value === undefined) value = 0;
  208. const sign = value >= 0 ? '' : '-';
  209. const absoluteValue = Math.abs(value);
  210. return sign + '$' + absoluteValue.toFixed(2);
  211. }
  212. // 转换数组
  213. const accounts = computed(() =>
  214. AccountList.value && AccountList.value.length != 0 ? AccountList.value.map((acc, index) => {
  215. const currency = acc.currency || 'USD';
  216. const floating = acc.floating ?? 0;
  217. let labels = [t('vu.item1'), 'MT4', 'Standard'];
  218. labels[0] = cativeIndex.value == 1 ? t('vu.item2') : t('vu.item1');
  219. labels[1] = acc.platform || 'MT4';
  220. labels[2] = typeMap.value[acc.type];
  221. let nickname = typeMap.value[acc.type];
  222. let fwq = acc.platform == 'MT4' ? 'MT4 CWGMarketsLtd-Live' : 'MT5 CWGMarketsSVG-Live';
  223. const balance = acc.balance
  224. return {
  225. ...acc,
  226. labels,
  227. isExpanded: index == 0,
  228. balance,
  229. accountNumber: acc.login.toString(),
  230. nickname,
  231. fwq,
  232. balanceWithSymbol: acc.balanceWithSymbol ?? '$0', // 余额
  233. creditWithSymbol: acc.creditWithSymbol ?? '$0',//信用
  234. equityWithSymbol: acc.equityWithSymbol ?? '$0',//净值
  235. currency,
  236. actualLeverage: '1:' + (acc.leverage ?? 0), // 实际杠杆
  237. floatingPL: formatMoney(floating), // 盈亏
  238. platform: acc.platform || 'MT4',
  239. server: acc.groupCode || '',
  240. login: acc.login.toString(),
  241. listType: cativeIndex.value == 1 ? 'demo' : 'real'
  242. };
  243. }) : []
  244. )
  245. onMounted(async () => {
  246. await getAccountList()
  247. })
  248. </script>
  249. <style scoped lang="scss">
  250. @import "@/uni.scss";
  251. .account-card {
  252. width: 100%;
  253. background-color: var(--color-white);
  254. box-sizing: border-box;
  255. }
  256. .tabs-container {
  257. display: flex;
  258. justify-content: space-between;
  259. align-items: center;
  260. gap: px2rpx(12);
  261. .tabs-class {
  262. width: px2rpx(200);
  263. margin: px2rpx(20) 0;
  264. }
  265. .btn-primary {
  266. width: px2rpx(26);
  267. height: px2rpx(26);
  268. border-radius: px2rpx(2);
  269. border: none;
  270. font-size: px2rpx(14);
  271. text-align: center;
  272. cursor: pointer;
  273. display: flex;
  274. align-items: center;
  275. justify-content: center;
  276. gap: px2rpx(8);
  277. }
  278. .btn-primary2 {
  279. background-color: rgba(108, 133, 149, 0.08);
  280. }
  281. .btn-class {
  282. display: flex;
  283. justify-content: center;
  284. align-items: center;
  285. border: 1px solid #f3f4f6;
  286. font-size: px2rpx(14);
  287. height: px2rpx(34);
  288. text-align: center;
  289. cursor: pointer;
  290. display: flex;
  291. align-items: center;
  292. justify-content: center;
  293. gap: px2rpx(8);
  294. padding: px2rpx(4) px2rpx(8);
  295. box-sizing: border-box;
  296. }
  297. .btn-primary:active {}
  298. }
  299. .content-title {
  300. display: flex;
  301. justify-content: space-between;
  302. align-items: center;
  303. font-size: px2rpx(22);
  304. font-weight: 500;
  305. color: var(--color-slate-800);
  306. background-color: rgba(255, 255, 255, 0);
  307. .content-title-btns {
  308. display: flex;
  309. align-items: center;
  310. justify-content: center;
  311. gap: px2rpx(12);
  312. .btn-primary {
  313. min-width: px2rpx(120);
  314. background-color: var(--color-error);
  315. color: var(--color-slate-150);
  316. padding: 0 px2rpx(12);
  317. border: none;
  318. font-size: px2rpx(14);
  319. text-align: center;
  320. cursor: pointer;
  321. display: flex;
  322. align-items: center;
  323. justify-content: center;
  324. gap: px2rpx(8);
  325. }
  326. .btn-primary1 {
  327. background-color: var(--color-navy-700);
  328. }
  329. .btn-primary2 {
  330. background-color: var(--color-secondary-focus);
  331. }
  332. }
  333. }
  334. /* 网格布局样式 */
  335. .grid-layout {
  336. display: grid;
  337. grid-template-columns: repeat(auto-fill, minmax(25%, 1fr));
  338. gap: px2rpx(20);
  339. @media (max-width: 1200px) {
  340. grid-template-columns: repeat(auto-fill, minmax(33.33%, 1fr));
  341. }
  342. @media (max-width: 768px) {
  343. grid-template-columns: repeat(auto-fill, minmax(100%, 1fr));
  344. }
  345. .account-card {
  346. margin-bottom: 0;
  347. height: 100%;
  348. display: flex;
  349. flex-direction: column;
  350. }
  351. .account-card .main-content {
  352. flex: 1;
  353. }
  354. }
  355. .operation-btn {
  356. :deep(span) {
  357. display: flex;
  358. align-items: center;
  359. justify-content: center;
  360. gap: px2rpx(4);
  361. cursor: pointer;
  362. background-color: var(--color-slate-150);
  363. padding: px2rpx(8) 0;
  364. }
  365. }
  366. .operation-btn.disabled {
  367. cursor: not-allowed;
  368. opacity: 0.5;
  369. }
  370. .search-bar {
  371. display: flex;
  372. align-items: center;
  373. justify-content: flex-start;
  374. flex-wrap: wrap;
  375. gap: px2rpx(16);
  376. margin: px2rpx(16) 0;
  377. .cwg-combox,
  378. .uni-easyinput,
  379. .uni-date {
  380. width: px2rpx(240) !important;
  381. flex: none;
  382. }
  383. }
  384. .expand-btn {
  385. display: flex;
  386. align-items: center;
  387. justify-self: center;
  388. gap: 4px;
  389. cursor: pointer;
  390. width: px2rpx(32);
  391. height: px2rpx(32);
  392. justify-content: center;
  393. .icon {
  394. transition: transform 0.3s ease;
  395. }
  396. &:hover {
  397. background-color:
  398. color-mix(in oklab, var(--color-slate-300) 20%, transparent);
  399. }
  400. &.expanded {
  401. background-color:
  402. color-mix(in oklab, var(--color-slate-300) 20%, transparent);
  403. }
  404. &.expanded .icon {
  405. transform: rotate(180deg);
  406. }
  407. }
  408. .action-buttons {
  409. display: flex;
  410. flex-wrap: wrap;
  411. gap: px2rpx(8);
  412. justify-content: flex-end;
  413. padding: px2rpx(16) px2rpx(20);
  414. background-color: var(--color-slate-100);
  415. .action-btn {
  416. display: inline-flex;
  417. align-items: center;
  418. height: px2rpx(24);
  419. padding: 0 px2rpx(16);
  420. border: none;
  421. background-color: var(--color-secondary-focus);
  422. color: var(--color-white);
  423. font-size: px2rpx(13);
  424. font-weight: 500;
  425. cursor: pointer;
  426. transition: all 0.2s ease;
  427. .icon {
  428. margin-right: px2rpx(4);
  429. }
  430. }
  431. .action-btn.disabled {
  432. cursor: not-allowed;
  433. opacity: 0.5;
  434. }
  435. }
  436. </style>