dashboard.vue 29 KB

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