AccountList.vue 14 KB

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