dashboard.vue 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117
  1. <template>
  2. <cwg-page-wrapper class="create-page" :isHeaderFixed="true">
  3. <view class="header">
  4. <view class="title">
  5. <view class="mb-3 h3">{{ t('Home.msg.Custom') }}</view>
  6. <view class="">{{ dateWeek }}</view>
  7. </view>
  8. <button type="button" class="btn btn-danger btn-shadow waves-effect"
  9. @click="createAccount">
  10. <view class="d-flex align-items-center">
  11. <cwg-icon name="crm-plus" :size="14" color="#fff" />
  12. <text v-t="'Custom.Index.AddAccount'" />
  13. </view>
  14. </button>
  15. </view>
  16. <uni-loading v-if="loading" />
  17. <uni-row v-else class="demo-uni-row uni-row1" :gutter="20">
  18. <uni-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
  19. <uni-row :gutter="10">
  20. <uni-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
  21. <view class="card position-relative">
  22. <view class="card-body d-flex align-items-center gap-3">
  23. <view class="icon-placeholder">
  24. <cwg-icon name="crm-wallet" :size="28" color="red" />
  25. </view>
  26. <view class="flex-1">
  27. <view class="d-flex justify-between items-start mb-1">
  28. <text class="text-muted text-sm">{{t('vu.custom.t1')}}</text>
  29. <text
  30. :class="['growth-rate', compareData.totalBalanceGrowthRate||0 >= 0 ? 'rate-up' : 'rate-down']">
  31. {{ compareData.totalBalanceGrowthRate || 0 >= 0 ? '+' : ''
  32. }}{{ compareData.totalBalanceGrowthRate || 0 }}%
  33. </text>
  34. </view>
  35. <view class="fw-bold text-lg">${{ numberFormat(compareData.totalBalance || '0') }}</view>
  36. </view>
  37. </view>
  38. </view>
  39. </uni-col>
  40. <uni-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
  41. <view class="card position-relative">
  42. <view class="card-body d-flex align-items-center gap-3">
  43. <view class="icon-placeholder">
  44. <cwg-icon name="crm-money-up" :size="14" color="red" />
  45. </view>
  46. <view class="flex-1">
  47. <view class="d-flex justify-between items-start mb-1">
  48. <text class="text-muted text-sm">{{t('vu.custom.t2')}}</text>
  49. <text :class="['growth-rate', compareData.totalEquityGrowthRate ||0>= 0 ? 'rate-up' : 'rate-down']">
  50. {{ compareData.totalEquityGrowthRate || 0 >= 0 ? '+' : ''
  51. }}{{ compareData.totalEquityGrowthRate || 0 }}%
  52. </text>
  53. </view>
  54. <view class="fw-bold text-lg">${{ numberFormat(compareData.totalEquity || '0') }}</view>
  55. </view>
  56. </view>
  57. </view>
  58. </uni-col>
  59. <uni-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
  60. <view class="card position-relative">
  61. <view class="card-body d-flex align-items-center gap-3">
  62. <view class="icon-placeholder">
  63. <cwg-icon name="icon_withdraw" :size="28" color="red" />
  64. </view>
  65. <view class="flex-1">
  66. <view class="d-flex justify-between items-start mb-1">
  67. <text class="text-muted text-sm">{{t('vu.custom.t3')}}</text>
  68. <text
  69. :class="['growth-rate', compareData.totalDepositAmountGrowthRate ||0 >= 0 ? 'rate-up' : 'rate-down']">
  70. {{ compareData.totalDepositAmountGrowthRate || 0 >= 0 ? '+' : ''
  71. }}{{ compareData.totalDepositAmountGrowthRate || 0 }}%
  72. </text>
  73. </view>
  74. <view class="fw-bold text-lg">${{ numberFormat(compareData.totalDepositAmount || '0') }}</view>
  75. </view>
  76. </view>
  77. </view>
  78. </uni-col>
  79. <uni-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
  80. <view class="card position-relative">
  81. <view class="card-body d-flex align-items-center gap-3">
  82. <view class="icon-placeholder">
  83. <cwg-icon name="crm-money-bill-transfer" :size="28" color="red" />
  84. </view>
  85. <view class="flex-1">
  86. <view class="d-flex justify-between items-start mb-1">
  87. <text class="text-muted text-sm">{{t('vu.custom.t4')}}</text>
  88. <text
  89. :class="['growth-rate', compareData.totalWithdrawalAmountGrowthRate||0 >= 0 ? 'rate-up' : 'rate-down']">
  90. {{ compareData.totalWithdrawalAmountGrowthRate || 0 >= 0 ? '+' : ''
  91. }}{{ compareData.totalWithdrawalAmountGrowthRate || 0 }}%
  92. </text>
  93. </view>
  94. <view class="fw-bold text-lg">${{ numberFormat(compareData.totalWithdrawalAmount || '0') }}</view>
  95. </view>
  96. </view>
  97. </view>
  98. </uni-col>
  99. </uni-row>
  100. </uni-col>
  101. <uni-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
  102. <uni-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" class="mobilH mb-3">
  103. <view class="chart-box crm-border-radius">
  104. <view class="chart-title">
  105. <uni-row>
  106. <uni-row>
  107. <view class="opt-title mb-2">
  108. <view class="bigtitle">
  109. <text v-if="chartForm.chartType == 4">{{ t('Custom.Index.TradingVolumeStatistics') }}</text>
  110. <text v-if="chartForm.chartType == 2">{{ t('Custom.Index.DepositStatistical') }}</text>
  111. <text v-if="chartForm.chartType == 3">{{ t('Custom.Index.WithdrawalsStatistical') }}</text>
  112. <text v-if="chartForm.chartType == 6">{{ t('Custom.Index.ProfitLoss') }}</text>
  113. </view>
  114. <view class="time" @click="toReload">
  115. <cwg-icon name="crm-refresh" :size="16" color="#666" />
  116. <text class="crm-cursor"> GMT+3 {{ time }}</text>
  117. </view>
  118. </view>
  119. </uni-row>
  120. <uni-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
  121. <view class="btn-opt">
  122. <view class="title">
  123. <view class="account" v-if="chartForm.login">
  124. <text>{{ t('Custom.Index.TradingAccount') }}</text>
  125. <text> —</text>
  126. <text>{{ chartForm.login }}</text>
  127. </view>
  128. <view class="date">
  129. <uni-datetime-picker
  130. v-model="isDate"
  131. type="daterange"
  132. :placeholder="t('placeholder.Start') + ' - ' + t('placeholder.End')"
  133. @change="getSearchDate"
  134. />
  135. </view>
  136. </view>
  137. <button class="operation" @click="isChart = true">
  138. <text style="font-size: 14px">{{ t('Custom.Index.Parameter') }}</text>
  139. </button>
  140. </view>
  141. </uni-col>
  142. </uni-row>
  143. </view>
  144. <view class="chart-container">
  145. <cwg-match-media :max-width="991">
  146. <cwg-charts type="line" :chartData="chartData" :opts="chartOpts" tooltipFormat="tooltipCustom" ontouch />
  147. </cwg-match-media>
  148. <cwg-match-media :min-width="991">
  149. <cwg-charts type="line" :chartData="chartData" :opts="chartOpts" tooltipFormat="tooltipCustom" />
  150. </cwg-match-media>
  151. </view>
  152. </view>
  153. </uni-col>
  154. <uni-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12" class="mobilH">
  155. <view class="chart-box mh crm-border-radius">
  156. <view class="bigtitle mb-3">
  157. {{t('Custom.Index.MyAccount')}}
  158. </view>
  159. <view class="mb-2">
  160. <cwg-combox :clearable="false" v-model:value="loginValue"
  161. :options="loginComboxOptions" :placeholder="t('placeholder.choose')" @change="setCurData"/>
  162. </view>
  163. <view>
  164. <AccountCard :key="curData?.accountNumber"
  165. :account="curData" noCtrl />
  166. </view>
  167. </view>
  168. </uni-col>
  169. </uni-col>
  170. </uni-row>
  171. <cwg-improve-popup v-model:visible="dialogCheck" @confirm="confirm" />
  172. <cwg-popup v-model:visible="isChart" type="center" :mask-click="false" :show-footers="true"
  173. :title="t('Custom.Index.ChartSet')">
  174. <view class="dia-content">
  175. <uni-forms ref="chartFormRef" labelWidth="160">
  176. <uni-forms-item :label="t('Label.ChartType') + ':'" prop="chartType">
  177. <cwg-combox
  178. v-model:value="chartForm.chartType"
  179. :options="chartTypeOptions"
  180. :placeholder="t('placeholder.choose')"
  181. />
  182. </uni-forms-item>
  183. <uni-forms-item :label="t('Label.PlatformType') + ':'" prop="platform">
  184. <cwg-combox
  185. v-model:value="chartForm.platform"
  186. :options="platformOptions"
  187. :placeholder="t('placeholder.choose')"
  188. @change="onPlatformChange"
  189. />
  190. </uni-forms-item>
  191. <uni-forms-item :label="t('Label.TradingAccount') + ':'" prop="login">
  192. <cwg-combox
  193. v-model:value="chartForm.login"
  194. :options="loginOptions"
  195. :placeholder="t('placeholder.choose')"
  196. />
  197. </uni-forms-item>
  198. </uni-forms>
  199. </view>
  200. <template #footer>
  201. <button @click="isChart = false">{{ t('Btn.Cancel') }}</button>
  202. <button type="primary" @click="handleChartConfirm">{{ t('Btn.Confirm') }}</button>
  203. </template>
  204. </cwg-popup>
  205. </cwg-page-wrapper>
  206. </template>
  207. <script setup>
  208. import { ref, computed, watch, onMounted, nextTick,onUnmounted } from 'vue'
  209. import { useI18n } from 'vue-i18n'
  210. import dayjs from 'dayjs'
  211. import useRouter from '@/hooks/useRouter'
  212. import config from '@/config/index'
  213. import useUserStore from '@/stores/use-user-store'
  214. import { useFilters } from '@/composables/useFilters'
  215. import { documentaryApi } from '@/service/documentary'
  216. import { customApi } from '@/service/custom'
  217. import { userApi } from '@/api/user'
  218. import AccountCard from '@/pages/customer/components/AccountCard.vue'
  219. import { useWindowWidth } from '@/composables/useWindowWidth'
  220. const windowWidth = useWindowWidth(300)
  221. const { t, locale } = useI18n()
  222. const loading = ref(false)
  223. const router = useRouter()
  224. const { Code } = config
  225. const userStore = useUserStore()
  226. const { numberFormat } = useFilters()
  227. const loginOptions = ref([])
  228. const ChartSetDate = ref({})
  229. const isDealLogin = ref(false)
  230. const dealDate = ref({})
  231. const SubscribeProfitDate = ref([])
  232. const compareData = ref({})
  233. const loginValue = ref('')
  234. const isChart = ref(false)
  235. const isDate = ref(['', ''])
  236. const time = ref('')
  237. const chartData = ref({ categories: [], series: [] })
  238. const chartOpts = computed(() => ({
  239. color: ['#1890FF', '#91CB74', '#FAC858', '#EE6666', '#73C0DE', '#3CA272', '#FC8452', '#9A60B4', '#ea7ccc'],
  240. padding: [15, 10, 0, 15],
  241. enableScroll: true,
  242. legend: {
  243. show: false,
  244. },
  245. xAxis: {
  246. // disableGrid: true,
  247. scrollShow: true,
  248. itemCount: isMobile.value?3:4,
  249. },
  250. tooltipFormat: 'tooltipCustom',
  251. extra: {
  252. line: {
  253. type: 'straight',
  254. width: 2,
  255. activeType: 'hollow',
  256. },
  257. tooltip: {
  258. legendShow: false,
  259. // showCategory: true,
  260. formatter: 'tooltipCustom',
  261. },
  262. },
  263. }))
  264. const dropDown = ref([])
  265. const chartFormRef = ref(null)
  266. const chartForm = ref({
  267. chartType: 4,
  268. platform: '',
  269. login: '',
  270. })
  271. const chartTypeOptions = computed(() => [
  272. { text: t('Custom.Index.TradingVolumeStatistics'), value: 4 },
  273. { text: t('Custom.Index.DepositStatistical'), value: 2 },
  274. { text: t('Custom.Index.WithdrawalsStatistical'), value: 3 },
  275. { text: t('Custom.Index.ProfitLoss'), value: 6 },
  276. ])
  277. const platformOptions = [
  278. { text: 'MT4', value: 'MT4' },
  279. { text: 'MT5', value: 'MT5' },
  280. ]
  281. const loginDataOptions = ref([])
  282. const loginComboxOptions = computed(() => {
  283. return loginDataOptions.value.map((item) => ({
  284. text: item.label,
  285. value: item.login
  286. }))
  287. })
  288. const isAfterJuly28 = () => {
  289. const now = new Date();
  290. const july28 = new Date(2025, 6, 28, 0, 0, 0); // 月份从0开始,所以7月是6
  291. return now >= july28;
  292. }
  293. const getLocalTime = () => {
  294. let timezone = 2 //目标时区时间,东2区 东时区正数 西市区负数
  295. let offset_GMT = new Date().getTimezoneOffset() // 本地时间和格林威治的时间差,单位为分钟
  296. let nowDate = new Date().getTime() // 本地时间距 1970 年 1 月 1 日午夜(GMT 时间)之间的毫秒数
  297. let now = new Date(
  298. nowDate + offset_GMT * 60 * 1000 + timezone * 60 * 60 * 1000,
  299. )
  300. //当前时间
  301. let month = now.getMonth() + 1 //月
  302. let day = now.getDate() //日
  303. let hh = now.getHours() //时
  304. let mm = now.getMinutes() //分
  305. let clock
  306. if (month < 10) {
  307. month = '0' + month
  308. }
  309. if (day < 10) {
  310. day = '0' + day
  311. }
  312. if (hh < 10) {
  313. hh = '0' + hh
  314. }
  315. if (mm < 10) {
  316. mm = '0' + mm
  317. }
  318. clock = ' ' + hh + ':' + mm + ' ' + month + '/' + day
  319. time.value = clock
  320. }
  321. const toReload = () => {
  322. goTime()
  323. }
  324. // 获取账户信息
  325. const getDateList = async () => {
  326. let res = await customApi.CustomDropdown({ platform: "" })
  327. if (res.code == Code.StatusOK) {
  328. loginDataOptions.value = res.data.map(item => ({
  329. ...item,
  330. label: `${item.login}`,
  331. disable: item.closeFunctions.includes('1')
  332. }))
  333. const found = loginDataOptions.value?.[0]
  334. loginValue.value = found?.login||null
  335. } else {
  336. uni.showToast({ title: res.msg, icon: 'none' })
  337. }
  338. }
  339. const getSearchDate = (val) => {
  340. if (Array.isArray(val)) {
  341. isDate.value = val
  342. }
  343. fetchChartData()
  344. }
  345. const onPlatformChange = (val) => {
  346. console.log('Platform changed:', val)
  347. }
  348. const handleChartConfirm = async () => {
  349. try {
  350. const params = {
  351. type: chartForm.value.chartType || 4,
  352. platform: chartForm.value.platform,
  353. login: chartForm.value.login,
  354. startDate: isDate.value[0],
  355. endDate: isDate.value[1],
  356. }
  357. const res = await customApi.getChartInfo(params)
  358. if (res.code === Code.StatusOK) {
  359. if (res.data) {
  360. let categories = []
  361. let seriesData = []
  362. res.data.forEach((item) => {
  363. categories.push(item.date.split(' ')[0])
  364. seriesData.push(item.amount)
  365. })
  366. chartData.value = { categories, series: [{ name: '', data: seriesData }] }
  367. } else {
  368. chartData.value = { categories: [], series: [] }
  369. }
  370. }
  371. isChart.value = false
  372. } catch (error) {
  373. console.error('获取图表数据失败', error)
  374. isChart.value = false
  375. }
  376. }
  377. const fetchChartData = async () => {
  378. try {
  379. const params = {
  380. type: chartForm.value.chartType || 4,
  381. platform: chartForm.value.platform,
  382. login: chartForm.value.login,
  383. startDate: isDate.value[0],
  384. endDate: isDate.value[1],
  385. }
  386. const res = await customApi.getChartInfo(params)
  387. if (res.code === Code.StatusOK) {
  388. if (res.data) {
  389. let categories = []
  390. let seriesData = []
  391. res.data.forEach((item) => {
  392. categories.push(item.date.split(' ')[0])
  393. seriesData.push(item.amount)
  394. })
  395. chartData.value = { categories, series: [{ name: '', data: seriesData }] }
  396. } else {
  397. chartData.value = { categories: [], series: [] }
  398. }
  399. }
  400. } catch (error) {
  401. console.error('获取图表数据失败', error)
  402. }
  403. }
  404. const getDropDown = async () => {
  405. try {
  406. const res = await customApi.CustomDropdown()
  407. if (res.code === Code.StatusOK) {
  408. dropDown.value = res.data || []
  409. loginOptions.value = (res.data || []).map(item => ({
  410. text: item.login,
  411. value: item.login,
  412. }))
  413. }
  414. } catch (error) {
  415. console.error('获取账户列表失败', error)
  416. }
  417. }
  418. // 今日时间
  419. const dateWeek = computed(() => {
  420. const now = dayjs()
  421. const isChinese = locale.value === 'cn' || locale.value === 'zhHant'
  422. if (isChinese) {
  423. const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
  424. const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
  425. return `${weekDays[now.day()]}, ${months[now.month()]}${now.date()}日, ${now.year()}`
  426. } else {
  427. return now.format('ddd, MMMD, YYYY')
  428. }
  429. })
  430. const createAccount = () => {
  431. getCustomLoginInfo()
  432. }
  433. // 获取客户登录信息
  434. const dialogCheck = ref(false)
  435. async function getCustomLoginInfo() {
  436. try {
  437. const res = await userApi.getUserInfo()
  438. userStore.saveUserInfo(res.data)
  439. if (res.code === 200) {
  440. if (
  441. res.data.customInfo.status == 2 &&
  442. res.data.customInfo.applyRealStatus == 2
  443. ) {
  444. router.push(`/pages/customer/account-select?server=${'real'}`)
  445. } else {
  446. dialogCheck.value = true
  447. }
  448. }
  449. } catch (error) {
  450. // console.log(error, 111);
  451. }
  452. }
  453. const confirm = () => {
  454. dialogCheck.value = false
  455. router.push(`/pages/mine/improveImmediately`)
  456. }
  457. const getDailyCompare = async (login) => {
  458. const res = await documentaryApi.followDailyCompare({ login })
  459. if (res.code === Code.StatusOK) {
  460. ChartSetDate.value = res.data ?? {}
  461. } else {
  462. uni.showToast({ title: res.msg, icon: 'none' })
  463. }
  464. }
  465. const getDailyDeal = async (login) => {
  466. const res = await documentaryApi.followDailyDeal({ login })
  467. if (res.code === Code.StatusOK) {
  468. dealDate.value = res.data ?? {}
  469. } else {
  470. uni.showToast({ title: res.msg, icon: 'none' })
  471. }
  472. }
  473. const getDailySubscribeProfit = async (login) => {
  474. const res = await documentaryApi.followDailySubscribeProfit({ login })
  475. if (res.code === Code.StatusOK) {
  476. SubscribeProfitDate.value = res.data?.data ?? []
  477. } else {
  478. uni.showToast({ title: res.msg, icon: 'none' })
  479. }
  480. }
  481. watch(() => chartForm.value.login, async (login) => {
  482. await getDailyCompare(login)
  483. if (isDealLogin.value) {
  484. await getDailyDeal(login)
  485. } else {
  486. await getDailySubscribeProfit(login)
  487. }
  488. })
  489. const getData = async () => {
  490. try {
  491. const res = await customApi.customDailyCompare()
  492. if (res.code === Code.StatusOK) {
  493. compareData.value = res.data
  494. }
  495. } catch (e) {
  496. console.log(e)
  497. }
  498. }
  499. const goTime = () => {
  500. getLocalTime()
  501. let timezone = 2 //目标时区时间,东2区 东时区正数 西市区负数
  502. let offset_GMT = new Date().getTimezoneOffset() // 本地时间和格林威治的时间差,单位为分钟
  503. let nowDate = new Date().getTime() // 本地时间距 1970 年 1 月 1 日午夜(GMT 时间)之间的毫秒数
  504. let data = new Date(
  505. nowDate + offset_GMT * 60 * 1000 + timezone * 60 * 60 * 1000,
  506. )
  507. //当月第一天和最后一天
  508. let mon1 = data.setDate(1) //当月第一天
  509. let mon2 = data.setMonth(data.getMonth() + 1) //下月
  510. let y1 = new Date(mon1).getFullYear()
  511. let m1 = new Date(mon1).getMonth() + 1
  512. let d1 = new Date(mon1).getDate()
  513. let y2 = new Date(mon2).getFullYear()
  514. let m2 = new Date(mon2).getMonth() + 1
  515. let d2 = 1
  516. isDate.value = [
  517. y1 + '-' + (m1 < 10 ? '0' + m1 : m1) + '-' + (d1 < 10 ? '0' + d1 : d1),
  518. y2 + '-' + (m2 < 10 ? '0' + m2 : m2) + '-' + (d2 < 10 ? '0' + d2 : d2),
  519. ]
  520. fetchChartData()
  521. }
  522. const isMobile = ref(false)
  523. onUnmounted(() => {
  524. // #ifdef H5
  525. window.removeEventListener('resize', handleResize)
  526. // #endif
  527. })
  528. const chartShow = ref(true)
  529. // 监听窗口大小变化(仅 H5)
  530. // #ifdef H5
  531. const handleResize = () => {
  532. checkIsMobile()
  533. }
  534. // #endif
  535. const checkIsMobile = () => {
  536. chartShow.value = false
  537. isMobile.value = windowWidth.value < 991
  538. chartShow.value = true
  539. }
  540. const AccountList = ref([])
  541. const getAccountList = async () => {
  542. AccountList.value = []
  543. const res = await customApi.AccountAllList({
  544. page: {
  545. current: 1,
  546. size: 100
  547. }
  548. })
  549. if (res.code === 200) {
  550. AccountList.value = res.data
  551. }
  552. }
  553. const typeMap = computed(() => ({
  554. 1: t('AccountType.ClassicAccount'),
  555. 2: t('AccountType.SeniorAccount'),
  556. 3: !isAfterJuly28() ? t('AccountType.AgencyAccount') : '',
  557. 5: t('AccountType.SpeedAccount'),
  558. 6: t('AccountType.SpeedAccount'),
  559. 7: t('AccountType.StandardAccount'),
  560. 8: t('AccountType.CentAccount')
  561. }));
  562. // 格式化数值函数
  563. function formatMoney(value) {
  564. if (value === null || value === undefined) value = 0;
  565. const sign = value >= 0 ? '' : '-';
  566. const absoluteValue = Math.abs(value);
  567. return '$' + sign + absoluteValue.toFixed(2);
  568. }
  569. // 转换数组
  570. const accounts = computed(() => {
  571. if (!AccountList.value || AccountList.value.length == 0) return []
  572. let filteredAccounts = AccountList.value
  573. return filteredAccounts.map((acc, index) => {
  574. const currency = acc.currency || 'USD';
  575. const floating = acc.floating ?? 0;
  576. let labels = [t('vu.item1'), 'MT4', 'Standard'];
  577. labels[0] = t('vu.item1');
  578. labels[1] = acc.platform || 'MT4';
  579. labels[2] = typeMap.value[acc.type];
  580. let nickname = typeMap.value[acc.type];
  581. let fwq
  582. fwq = acc.platform == 'MT4' ? 'CWGMarketsLtd-Live' : 'CWGMarketsSVG-Live';
  583. const balance = acc.balance
  584. return {
  585. ...acc,
  586. labels,
  587. isExpanded: index == 0,
  588. balance,
  589. accountNumber: acc.login.toString(),
  590. nickname,
  591. fwq,
  592. balanceWithSymbol: acc.balanceWithSymbol ?? '$0',
  593. creditWithSymbol: acc.creditWithSymbol ?? '$0',
  594. equityWithSymbol: acc.equityWithSymbol ?? '$0',
  595. currency,
  596. actualLeverage: '1:' + (acc.leverage ?? 0),
  597. floatingPL: formatMoney(floating),
  598. platform: acc.platform || 'MT4',
  599. server: acc.groupCode || '',
  600. login: acc.login.toString(),
  601. listType: 'real'
  602. };
  603. })
  604. })
  605. const curData = ref()
  606. const setCurData = (val)=>{
  607. console.log(accounts.value, val)
  608. curData.value = accounts.value.find(item=>item.login == val)
  609. }
  610. watch(() => accounts.value, (val)=>{
  611. if (val.length){
  612. curData.value = accounts.value.find(item=>item.login == loginValue.value)
  613. }
  614. })
  615. onMounted(async () => {
  616. loading.value = true
  617. goTime()
  618. await getData()
  619. await getDropDown()
  620. await getDateList()
  621. await getAccountList()
  622. // #ifdef H5
  623. window.addEventListener('resize', handleResize)
  624. // #endif
  625. checkIsMobile()
  626. // await fetchChartData()
  627. loading.value = false
  628. })
  629. </script>
  630. <style lang="scss" scoped>
  631. @import "@/uni.scss";
  632. .mobilH{
  633. height: 100%;
  634. }
  635. @media screen and (max-width: 991px){
  636. .mobilH{
  637. height: auto;
  638. }
  639. }
  640. .header {
  641. display: flex;
  642. align-items: center;
  643. justify-content: space-between;
  644. margin-bottom: px2rpx(20);
  645. .title {
  646. font-size: px2rpx(24);
  647. font-weight: bold;
  648. }
  649. }
  650. .demo-uni-row {
  651. display: flex;
  652. flex-wrap: wrap;
  653. align-items: stretch;
  654. margin: 0 auto !important;
  655. }
  656. .uni-col-left {
  657. //display: flex;
  658. //flex-direction: column;
  659. }
  660. .uni-col-right {
  661. display: flex;
  662. }
  663. .dashboard-container {
  664. min-height: 10vh;
  665. box-sizing: border-box;
  666. display: flex;
  667. flex-direction: column;
  668. height: 100%;
  669. }
  670. /* 卡片通用样式 */
  671. .card {
  672. background: var(--color-white);
  673. color: var(--bs-heading-color);
  674. border-radius: 4px;
  675. margin-bottom: px2rpx(20);
  676. box-shadow: 0 px2rpx(4) px2rpx(12) rgba(0, 0, 0, 0.2);
  677. }
  678. .card-body {
  679. padding: px2rpx(16);
  680. }
  681. .icon-placeholder {
  682. width: px2rpx(48);
  683. height: px2rpx(48);
  684. border-radius: px2rpx(24);
  685. background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%);
  686. display: flex;
  687. align-items: center;
  688. justify-content: center;
  689. flex-shrink: 0;
  690. }
  691. .icon-text {
  692. font-size: px2rpx(20);
  693. font-weight: bold;
  694. color: #6c757d;
  695. }
  696. .growth-rate {
  697. font-size: px2rpx(12);
  698. font-weight: 500;
  699. padding: px2rpx(2) px2rpx(8);
  700. border-radius: px2rpx(2);
  701. }
  702. .growth-rate.rate-up {
  703. color: #10b981;
  704. background: rgba(16, 185, 129, 0.1);
  705. }
  706. .growth-rate.rate-down {
  707. color: #ef4444;
  708. background: rgba(239, 68, 68, 0.1);
  709. }
  710. .custom-number,
  711. .custom-money {
  712. background: var(--bs-body-bg);
  713. border: 1px solid var(--bs-border-color);
  714. padding: 15px;
  715. border-radius: 4px;
  716. margin-bottom: 20px;
  717. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  718. min-height: px2rpx(125);
  719. }
  720. .custom-number .title,
  721. .custom-money .left .tit,
  722. .custom-money .right .tit {
  723. font-size: 14px;
  724. color: var(--bs-heading-color);
  725. margin-bottom: 10px;
  726. }
  727. .custom-number .title {
  728. display: flex;
  729. justify-content: space-between;
  730. align-items: center;
  731. }
  732. .custom-money {
  733. display: flex;
  734. flex-direction: column;
  735. }
  736. .custom-money .left,
  737. .custom-money .right {
  738. flex: 1;
  739. }
  740. .custom-money .num {
  741. font-size: 20px;
  742. font-weight: bold;
  743. color: var(--bs-heading-color);
  744. }
  745. .custom-money .num.red {
  746. color: #eb3f57;
  747. }
  748. .el-dropdown-link {
  749. display: flex;
  750. align-items: center;
  751. gap: 4px;
  752. color: #6c8595;
  753. font-size: 12px;
  754. }
  755. .account-info1 {
  756. margin-bottom: 20px;
  757. }
  758. .custom-money-left .header,
  759. .custom-money-right .header {
  760. display: flex;
  761. align-items: center;
  762. justify-content: space-between;
  763. margin-bottom: px2rpx(15);
  764. }
  765. .custom-money-left .tab,
  766. .custom-money-right .tab {
  767. font-size: px2rpx(16);
  768. font-weight: 600;
  769. color: var(--bs-heading-color);
  770. }
  771. .bottomCol {
  772. font-size: 16px;
  773. color: #868686;
  774. .bo-left1 {
  775. margin: 20px 0;
  776. padding: 20px 0;
  777. border-right: 1px dashed #989898;
  778. text-align: center;
  779. font-size: 16px;
  780. }
  781. .bo-right1 {
  782. margin: 20px 0px;
  783. padding: 20px 0;
  784. text-align: center;
  785. font-size: 16px;
  786. }
  787. }
  788. .blue-font {
  789. margin-top: px2rpx(15);
  790. color: #007aff;
  791. font-weight: 600;
  792. font-size: px2rpx(24);
  793. }
  794. .subscribe-table {
  795. width: 100%;
  796. border: 1px solid #ebeef5;
  797. border-radius: 6px;
  798. overflow: hidden;
  799. }
  800. .subscribe-row {
  801. display: grid;
  802. grid-template-columns: 1fr 1fr 1fr 1fr;
  803. border-top: 1px solid #ebeef5;
  804. }
  805. .subscribe-head {
  806. background: #f5f7fa;
  807. border-top: none;
  808. }
  809. .subscribe-cell {
  810. padding: 10px 8px;
  811. font-size: 12px;
  812. color: var(--bs-heading-color);
  813. text-align: center;
  814. overflow: hidden;
  815. text-overflow: ellipsis;
  816. white-space: nowrap;
  817. }
  818. .custom-dialog-content {
  819. padding: px2rpx(20);
  820. .info-text {
  821. color: var(--bs-heading-color);
  822. font-size: px2rpx(14);
  823. line-height: px2rpx(36);
  824. }
  825. }
  826. .card-header {
  827. display: flex;
  828. justify-content: space-between;
  829. align-items: center;
  830. margin-bottom: px2rpx(12);
  831. }
  832. .header-left {
  833. display: flex;
  834. align-items: center;
  835. gap: 12rpx;
  836. }
  837. .header-title {
  838. font-size: px2rpx(14);
  839. font-weight: 600;
  840. }
  841. .header-right {
  842. display: flex;
  843. align-items: center;
  844. }
  845. .action-btn {
  846. background: #ffde02;
  847. border: none;
  848. border-radius: 50%;
  849. width: px2rpx(32);
  850. height: px2rpx(32);
  851. display: flex;
  852. align-items: center;
  853. justify-content: center;
  854. padding: 0;
  855. margin: 0;
  856. &:after {
  857. border: none;
  858. }
  859. }
  860. .dia-content {
  861. padding: 20rpx;
  862. }
  863. .content {
  864. display: flex;
  865. flex-direction: column;
  866. gap: 20rpx;
  867. }
  868. .label {
  869. font-weight: 500;
  870. margin-bottom: 8rpx;
  871. }
  872. .btn {
  873. text-align: center;
  874. //background-color: rgb(var(--bs-danger-rgb));
  875. //font-size: px2rpx(16);
  876. color: #fff;
  877. //padding: px2rpx(10) px2rpx(20);
  878. border-radius: px2rpx(8)!important;
  879. margin: 0;
  880. }
  881. .crm-cursor {
  882. cursor: pointer;
  883. }
  884. .link {
  885. display: flex;
  886. margin-top: 20rpx;
  887. .btn {
  888. display: flex;
  889. align-items: center;
  890. justify-content: center;
  891. height: px2rpx(35);
  892. margin: 0 px2rpx(10);
  893. }
  894. }
  895. .chart-box {
  896. background: var(--color-white);
  897. border-radius: 8px;
  898. padding: px2rpx(16);
  899. height: 100%;
  900. box-shadow: 0 px2rpx(4) px2rpx(12) rgba(0, 0, 0, 0.2);
  901. }
  902. .mh{
  903. height: 100%;
  904. }
  905. .chart-title {
  906. width: 100%;
  907. //display: flex;
  908. //justify-content: space-between;
  909. //align-items: flex-start;
  910. //margin-bottom: px2rpx(16);
  911. //padding-bottom: px2rpx(16);
  912. border-bottom: 1px solid #f0f0f0;
  913. }
  914. .bigtitle{
  915. font-size: px2rpx(18);
  916. }
  917. .opt-title{
  918. display: flex;
  919. align-items: center;
  920. justify-content: space-between;
  921. }
  922. .btn-opt{
  923. display: flex;
  924. height: 100%;
  925. align-items: flex-end;
  926. justify-content: flex-end;
  927. }
  928. .chart-title .time {
  929. display: flex;
  930. align-items: center;
  931. gap: px2rpx(8);
  932. font-size: px2rpx(14);
  933. margin-bottom: px2rpx(10);
  934. color: #999;
  935. }
  936. .chart-title .title {
  937. flex: 1;
  938. margin: 0;
  939. margin-bottom: px2rpx(10);
  940. }
  941. .chart-title .title .name {
  942. font-size: px2rpx(18);
  943. font-weight: 600;
  944. color: var(--bs-heading-color);
  945. text-align: center;
  946. margin-bottom: px2rpx(8);
  947. }
  948. .chart-title .title .account {
  949. font-size: px2rpx(14);
  950. color: #999;
  951. text-align: center;
  952. margin-bottom: px2rpx(8);
  953. }
  954. .chart-title .title .date {
  955. display: flex;
  956. }
  957. .chart-title .date-picker {
  958. font-size: px2rpx(12);
  959. color: #666;
  960. padding: px2rpx(6) px2rpx(12);
  961. background: #f5f5f5;
  962. border-radius: 4px;
  963. }
  964. .chart-title .operation {
  965. //gap: px2rpx(4);
  966. margin-left: px2rpx(10);
  967. color: #666;
  968. margin-bottom: px2rpx(10);
  969. height: px2rpx(48);
  970. width: px2rpx(100);
  971. font-size: px2rpx(16);
  972. line-height: px2rpx(48)!important;
  973. }
  974. .chart-container {
  975. margin-top: px2rpx(20);
  976. height: px2rpx(350);
  977. }
  978. .popup-content .form {
  979. padding: px2rpx(16);
  980. }
  981. .popup-content .form-item {
  982. margin-bottom: px2rpx(20);
  983. }
  984. .popup-content .form-label {
  985. display: block;
  986. font-size: px2rpx(14);
  987. color: #666;
  988. margin-bottom: px2rpx(8);
  989. }
  990. .popup-content .picker-value {
  991. display: flex;
  992. justify-content: space-between;
  993. align-items: center;
  994. padding: px2rpx(12);
  995. background: #f5f5f5;
  996. border-radius: 4px;
  997. font-size: px2rpx(14);
  998. color: #333;
  999. }
  1000. .crm-cursor {
  1001. cursor: pointer;
  1002. }
  1003. .crm-border-radius {
  1004. border-radius: 8px;
  1005. }
  1006. </style>