AccountList.vue 14 KB

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