dashboard.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998
  1. <template>
  2. <cwg-page-wrapper class="create-page" :isHeaderFixed="true">
  3. <view class="header">
  4. <view class="title">
  5. <view class="mb-3">{{ t('Home.msg.Custom') }}</view>
  6. <view class="">{{ dateWeek }}</view>
  7. </view>
  8. <button type="button" class="btn btn-secondary 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">Total Balance</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="28" 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">Equity</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">Total deposit</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">Total withdrawal</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">
  103. <view class="chart-box crm-border-radius">
  104. <view class="chart-title">
  105. <uni-row>
  106. <uni-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
  107. <view class="time" @click="toReload">
  108. <cwg-icon name="crm-refresh" :size="16" color="#666" />
  109. <text class="crm-cursor"> GMT+3 {{ time }}</text>
  110. </view>
  111. </uni-col>
  112. <uni-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
  113. <view class="title">
  114. <view class="name">
  115. <text v-if="chartForm.chartType == 4">{{ t('Custom.Index.TradingVolumeStatistics') }}</text>
  116. <text v-if="chartForm.chartType == 2">{{ t('Custom.Index.DepositStatistical') }}</text>
  117. <text v-if="chartForm.chartType == 3">{{ t('Custom.Index.WithdrawalsStatistical') }}</text>
  118. <text v-if="chartForm.chartType == 6">{{ t('Custom.Index.ProfitLoss') }}</text>
  119. </view>
  120. <view class="account" v-if="chartForm.login">
  121. <text>{{ t('Custom.Index.TradingAccount') }}</text>
  122. <text> —</text>
  123. <text>{{ chartForm.login }}</text>
  124. </view>
  125. <view class="date">
  126. <uni-datetime-picker
  127. v-model="isDate"
  128. type="daterange"
  129. :placeholder="t('placeholder.Start') + ' - ' + t('placeholder.End')"
  130. @change="getSearchDate"
  131. />
  132. </view>
  133. </view>
  134. </uni-col>
  135. <uni-col :xs="24" :sm="24" :md="8" :lg="8" :xl="8">
  136. <view class="operation" @click="isChart = true">
  137. <text style="font-size: 14px">{{ t('Custom.Index.Parameter') }}</text>
  138. <cwg-icon name="iconcanshu" :size="16" color="#666" />
  139. </view>
  140. </uni-col>
  141. </uni-row>
  142. </view>
  143. {{isMobile}}
  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>
  155. </uni-row>
  156. <cwg-improve-popup v-model:visible="dialogCheck" @confirm="confirm" />
  157. <cwg-popup v-model:visible="isChart" type="center" :mask-click="false" :show-footers="true"
  158. :title="t('Custom.Index.ChartSet')">
  159. <view class="dia-content">
  160. <uni-forms ref="chartFormRef" labelWidth="160">
  161. <uni-forms-item :label="t('Label.ChartType') + ':'" prop="chartType">
  162. <cwg-combox
  163. v-model:value="chartForm.chartType"
  164. :options="chartTypeOptions"
  165. :placeholder="t('placeholder.choose')"
  166. filterable
  167. />
  168. </uni-forms-item>
  169. <uni-forms-item :label="t('Label.PlatformType') + ':'" prop="platform">
  170. <cwg-combox
  171. v-model:value="chartForm.platform"
  172. :options="platformOptions"
  173. :placeholder="t('placeholder.choose')"
  174. @change="onPlatformChange"
  175. />
  176. </uni-forms-item>
  177. <uni-forms-item :label="t('Label.TradingAccount') + ':'" prop="login">
  178. <cwg-combox
  179. v-model:value="chartForm.login"
  180. :options="loginOptions"
  181. :placeholder="t('placeholder.choose')"
  182. filterable
  183. />
  184. </uni-forms-item>
  185. </uni-forms>
  186. </view>
  187. <template #footer>
  188. <button @click="isChart = false">{{ t('Btn.Cancel') }}</button>
  189. <button 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 { useStorage } from '@/hooks/useStorage'
  202. import { useFilters } from '@/composables/useFilters'
  203. import { documentaryApi } from '@/service/documentary'
  204. import { customApi } from '@/service/custom'
  205. import { userApi } from '@/api/user'
  206. const { t, locale } = useI18n()
  207. const loading = ref(false)
  208. const router = useRouter()
  209. const { Code } = config
  210. const userStore = useUserStore()
  211. const { numberFormat } = useFilters()
  212. const listApi = ref(documentaryApi.followDailySubscribeProfit)
  213. const loginOptions = ref([])
  214. const ChartSetDate = ref({})
  215. const isDealLogin = ref(false)
  216. const dealDate = ref({})
  217. const SubscribeProfitDate = ref([])
  218. const compareData = ref({})
  219. const isChart = ref(false)
  220. const isDate = ref(['', ''])
  221. const time = ref('')
  222. const chartData = ref({ categories: [], series: [] })
  223. const chartOpts = ref({
  224. color: ['#1890FF', '#91CB74', '#FAC858', '#EE6666', '#73C0DE', '#3CA272', '#FC8452', '#9A60B4', '#ea7ccc'],
  225. padding: [15, 10, 0, 15],
  226. enableScroll: true,
  227. legend: {
  228. show: false,
  229. },
  230. xAxis: {
  231. // disableGrid: true,
  232. scrollShow: true,
  233. itemCount: 4,
  234. },
  235. tooltipFormat: 'tooltipCustom',
  236. extra: {
  237. line: {
  238. type: 'straight',
  239. width: 2,
  240. activeType: 'hollow',
  241. },
  242. tooltip: {
  243. legendShow: false,
  244. // showCategory: true,
  245. formatter: 'tooltipCustom',
  246. },
  247. },
  248. })
  249. const dropDown = ref([])
  250. const chartFormRef = ref(null)
  251. const chartForm = ref({
  252. chartType: 4,
  253. platform: '',
  254. login: '',
  255. })
  256. const chartTypeOptions = ref([
  257. { text: 'Trading Volume Statistics', value: 4 },
  258. { text: 'Deposit Statistical', value: 2 },
  259. { text: 'Withdrawals Statistical', value: 3 },
  260. { text: 'Profit Loss', value: 6 },
  261. ])
  262. const platformOptions = [
  263. { text: 'MT4', value: 'MT4' },
  264. { text: 'MT5', value: 'MT5' },
  265. ]
  266. const getLocalTime = () => {
  267. let timezone = 2 //目标时区时间,东2区 东时区正数 西市区负数
  268. let offset_GMT = new Date().getTimezoneOffset() // 本地时间和格林威治的时间差,单位为分钟
  269. let nowDate = new Date().getTime() // 本地时间距 1970 年 1 月 1 日午夜(GMT 时间)之间的毫秒数
  270. let now = new Date(
  271. nowDate + offset_GMT * 60 * 1000 + timezone * 60 * 60 * 1000,
  272. )
  273. //当前时间
  274. let month = now.getMonth() + 1 //月
  275. let day = now.getDate() //日
  276. let hh = now.getHours() //时
  277. let mm = now.getMinutes() //分
  278. let clock
  279. if (month < 10) {
  280. month = '0' + month
  281. }
  282. if (day < 10) {
  283. day = '0' + day
  284. }
  285. if (hh < 10) {
  286. hh = '0' + hh
  287. }
  288. if (mm < 10) {
  289. mm = '0' + mm
  290. }
  291. clock = ' ' + hh + ':' + mm + ' ' + month + '/' + day
  292. time.value = clock
  293. }
  294. const toReload = () => {
  295. goTime()
  296. }
  297. const getSearchDate = (val) => {
  298. if (Array.isArray(val)) {
  299. isDate.value = val
  300. }
  301. fetchChartData()
  302. }
  303. const onPlatformChange = (val) => {
  304. console.log('Platform changed:', val)
  305. }
  306. const handleChartConfirm = async () => {
  307. try {
  308. const params = {
  309. type: chartForm.value.chartType || 4,
  310. platform: chartForm.value.platform,
  311. login: chartForm.value.login,
  312. startDate: isDate.value[0],
  313. endDate: isDate.value[1],
  314. }
  315. const res = await customApi.getChartInfo(params)
  316. if (res.code === Code.StatusOK) {
  317. if (res.data) {
  318. let categories = []
  319. let seriesData = []
  320. res.data.forEach((item) => {
  321. categories.push(item.date.split(' ')[0])
  322. seriesData.push(item.amount)
  323. })
  324. chartData.value = { categories, series: [{ name: '', data: seriesData }] }
  325. } else {
  326. chartData.value = { categories: [], series: [] }
  327. }
  328. }
  329. isChart.value = false
  330. } catch (error) {
  331. console.error('获取图表数据失败', error)
  332. isChart.value = false
  333. }
  334. }
  335. const fetchChartData = async () => {
  336. try {
  337. const params = {
  338. type: chartForm.value.chartType || 4,
  339. platform: chartForm.value.platform,
  340. login: chartForm.value.login,
  341. startDate: isDate.value[0],
  342. endDate: isDate.value[1],
  343. }
  344. const res = await customApi.getChartInfo(params)
  345. if (res.code === Code.StatusOK) {
  346. if (res.data) {
  347. let categories = []
  348. let seriesData = []
  349. res.data.forEach((item) => {
  350. categories.push(item.date.split(' ')[0])
  351. seriesData.push(item.amount)
  352. })
  353. chartData.value = { categories, series: [{ name: '', data: seriesData }] }
  354. } else {
  355. chartData.value = { categories: [], series: [] }
  356. }
  357. }
  358. } catch (error) {
  359. console.error('获取图表数据失败', error)
  360. }
  361. }
  362. const getDropDown = async () => {
  363. try {
  364. const res = await customApi.CustomDropdown()
  365. if (res.code === Code.StatusOK) {
  366. dropDown.value = res.data || []
  367. loginOptions.value = (res.data || []).map(item => ({
  368. text: item.login,
  369. value: item.login,
  370. }))
  371. }
  372. } catch (error) {
  373. console.error('获取账户列表失败', error)
  374. }
  375. }
  376. const groupTypeName = (type) => {
  377. if (!type) return '--'
  378. if (type == 1) return t('AccountType.ClassicAccount')
  379. if (type == 2) return t('AccountType.SeniorAccount')
  380. if (type == 5) return t('AccountType.SpeedAccount')
  381. if (type == 6) return t('AccountType.SpeedAccount')
  382. if (type == 7) return t('AccountType.StandardAccount')
  383. if (type == 8) return t('AccountType.CentAccount')
  384. return type
  385. }
  386. const groupCurrency = (type) => {
  387. console.log(type, 'usd')
  388. if (type == 'GBP') {
  389. return ': £'
  390. } else if (type == 'USD') {
  391. return ': $'
  392. } else if (type == 'EUR') {
  393. return ': €'
  394. } else if (type == 'USC') {
  395. return ': ¢'
  396. } else {
  397. return ': $'
  398. }
  399. }
  400. const groupCurrency1 = (type) => {
  401. if (type == 'GBP') {
  402. return '£'
  403. } else if (type == 'USD') {
  404. return '$'
  405. } else if (type == 'EUR') {
  406. return '€'
  407. } else if (type == 'USC') {
  408. return '¢'
  409. } else {
  410. return '$'
  411. }
  412. }
  413. // 今日时间
  414. const dateWeek = computed(() => {
  415. const now = dayjs()
  416. const isChinese = locale.value === 'cn' || locale.value === 'zhHant'
  417. if (isChinese) {
  418. const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
  419. const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
  420. return `${weekDays[now.day()]}, ${months[now.month()]}${now.date()}日, ${now.year()}`
  421. } else {
  422. return now.format('ddd, MMMD, YYYY')
  423. }
  424. })
  425. const createAccount = () => {
  426. getCustomLoginInfo()
  427. }
  428. // 获取客户登录信息
  429. const dialogCheck = ref(false)
  430. async function getCustomLoginInfo() {
  431. try {
  432. const res = await userApi.getUserInfo()
  433. userStore.saveUserInfo(res.data)
  434. if (res.code === 200) {
  435. if (
  436. res.data.customInfo.status == 2 &&
  437. res.data.customInfo.applyRealStatus == 2
  438. ) {
  439. router.push(`/pages/customer/account-select?server=${cativeIndex.value}`)
  440. } else {
  441. dialogCheck.value = true
  442. }
  443. }
  444. } catch (error) {
  445. // console.log(error, 111);
  446. }
  447. }
  448. const confirm = () => {
  449. dialogCheck.value = false
  450. router.push(`/pages/mine/improveImmediately`)
  451. }
  452. const getDailyCompare = async (login) => {
  453. const res = await documentaryApi.followDailyCompare({ login })
  454. if (res.code === Code.StatusOK) {
  455. ChartSetDate.value = res.data ?? {}
  456. } else {
  457. uni.showToast({ title: res.msg, icon: 'none' })
  458. }
  459. }
  460. const getDailyDeal = async (login) => {
  461. const res = await documentaryApi.followDailyDeal({ login })
  462. if (res.code === Code.StatusOK) {
  463. dealDate.value = res.data ?? {}
  464. } else {
  465. uni.showToast({ title: res.msg, icon: 'none' })
  466. }
  467. }
  468. const getDailySubscribeProfit = async (login) => {
  469. const res = await documentaryApi.followDailySubscribeProfit({ login })
  470. if (res.code === Code.StatusOK) {
  471. SubscribeProfitDate.value = res.data?.data ?? []
  472. } else {
  473. uni.showToast({ title: res.msg, icon: 'none' })
  474. }
  475. }
  476. watch(() => chartForm.value.login, async (login) => {
  477. await getDailyCompare(login)
  478. if (isDealLogin.value) {
  479. await getDailyDeal(login)
  480. } else {
  481. await getDailySubscribeProfit(login)
  482. }
  483. })
  484. const getData = async () => {
  485. try {
  486. const res = await customApi.customDailyCompare()
  487. if (res.code === Code.StatusOK) {
  488. compareData.value = res.data
  489. }
  490. } catch (e) {
  491. console.log(e)
  492. }
  493. }
  494. const goTime = () => {
  495. getLocalTime()
  496. let timezone = 2 //目标时区时间,东2区 东时区正数 西市区负数
  497. let offset_GMT = new Date().getTimezoneOffset() // 本地时间和格林威治的时间差,单位为分钟
  498. let nowDate = new Date().getTime() // 本地时间距 1970 年 1 月 1 日午夜(GMT 时间)之间的毫秒数
  499. let data = new Date(
  500. nowDate + offset_GMT * 60 * 1000 + timezone * 60 * 60 * 1000,
  501. )
  502. //当月第一天和最后一天
  503. let mon1 = data.setDate(1) //当月第一天
  504. let mon2 = data.setMonth(data.getMonth() + 1) //下月
  505. let y1 = new Date(mon1).getFullYear()
  506. let m1 = new Date(mon1).getMonth() + 1
  507. let d1 = new Date(mon1).getDate()
  508. let y2 = new Date(mon2).getFullYear()
  509. let m2 = new Date(mon2).getMonth() + 1
  510. let d2 = 1
  511. isDate.value = [
  512. y1 + '-' + (m1 < 10 ? '0' + m1 : m1) + '-' + (d1 < 10 ? '0' + d1 : d1),
  513. y2 + '-' + (m2 < 10 ? '0' + m2 : m2) + '-' + (d2 < 10 ? '0' + d2 : d2),
  514. ]
  515. fetchChartData()
  516. }
  517. const isMobile = ref(false)
  518. onUnmounted(() => {
  519. // #ifdef H5
  520. window.removeEventListener('resize', handleResize)
  521. // #endif
  522. })
  523. const chartShow = ref(true)
  524. // 监听窗口大小变化(仅 H5)
  525. // #ifdef H5
  526. const handleResize = () => {
  527. checkIsMobile()
  528. }
  529. // #endif
  530. const checkIsMobile = () => {
  531. // 适配 uni-app 环境
  532. chartShow.value = false
  533. const width = window.innerWidth
  534. console.log(width)
  535. isMobile.value = width < 991
  536. chartShow.value = true
  537. // #ifndef H5
  538. chartShow.value = false
  539. const systemInfo = uni.getSystemInfoSync()
  540. isMobile.value = systemInfo.windowWidth < 991
  541. chartShow.value = true
  542. // #endif
  543. }
  544. onMounted(async () => {
  545. loading.value = true
  546. goTime()
  547. await getData()
  548. await getDropDown()
  549. // await fetchChartData()
  550. loading.value = false
  551. })
  552. </script>
  553. <style lang="scss" scoped>
  554. @import "@/uni.scss";
  555. .header {
  556. display: flex;
  557. align-items: center;
  558. justify-content: space-between;
  559. margin-bottom: px2rpx(20);
  560. .title {
  561. font-size: px2rpx(24);
  562. font-weight: bold;
  563. }
  564. }
  565. .demo-uni-row {
  566. display: flex;
  567. flex-wrap: wrap;
  568. align-items: stretch;
  569. margin: 0 auto !important;
  570. }
  571. .uni-col-left {
  572. //display: flex;
  573. //flex-direction: column;
  574. }
  575. .uni-col-right {
  576. display: flex;
  577. }
  578. .dashboard-container {
  579. min-height: 10vh;
  580. box-sizing: border-box;
  581. display: flex;
  582. flex-direction: column;
  583. height: 100%;
  584. }
  585. /* 卡片通用样式 */
  586. .card {
  587. background: var(--color-white);
  588. color: var(--bs-heading-color);
  589. border-radius: 4px;
  590. margin-bottom: px2rpx(20);
  591. box-shadow: 0 px2rpx(4) px2rpx(12) rgba(0, 0, 0, 0.2);
  592. }
  593. .card-body {
  594. padding: px2rpx(16);
  595. }
  596. .icon-placeholder {
  597. width: px2rpx(48);
  598. height: px2rpx(48);
  599. border-radius: px2rpx(24);
  600. background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ec 100%);
  601. display: flex;
  602. align-items: center;
  603. justify-content: center;
  604. flex-shrink: 0;
  605. }
  606. .icon-text {
  607. font-size: px2rpx(20);
  608. font-weight: bold;
  609. color: #6c757d;
  610. }
  611. .growth-rate {
  612. font-size: px2rpx(12);
  613. font-weight: 500;
  614. padding: px2rpx(2) px2rpx(8);
  615. border-radius: px2rpx(2);
  616. }
  617. .growth-rate.rate-up {
  618. color: #10b981;
  619. background: rgba(16, 185, 129, 0.1);
  620. }
  621. .growth-rate.rate-down {
  622. color: #ef4444;
  623. background: rgba(239, 68, 68, 0.1);
  624. }
  625. .custom-number,
  626. .custom-money {
  627. background: var(--bs-body-bg);
  628. border: 1px solid var(--bs-border-color);
  629. padding: 15px;
  630. border-radius: 4px;
  631. margin-bottom: 20px;
  632. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  633. min-height: px2rpx(125);
  634. }
  635. .custom-number .title,
  636. .custom-money .left .tit,
  637. .custom-money .right .tit {
  638. font-size: 14px;
  639. color: var(--bs-heading-color);
  640. margin-bottom: 10px;
  641. }
  642. .custom-number .title {
  643. display: flex;
  644. justify-content: space-between;
  645. align-items: center;
  646. }
  647. .custom-money {
  648. display: flex;
  649. flex-direction: column;
  650. }
  651. .custom-money .left,
  652. .custom-money .right {
  653. flex: 1;
  654. }
  655. .custom-money .num {
  656. font-size: 20px;
  657. font-weight: bold;
  658. color: var(--bs-heading-color);
  659. }
  660. .custom-money .num.red {
  661. color: #eb3f57;
  662. }
  663. .el-dropdown-link {
  664. display: flex;
  665. align-items: center;
  666. gap: 4px;
  667. color: #6c8595;
  668. font-size: 12px;
  669. }
  670. .account-info1 {
  671. margin-bottom: 20px;
  672. }
  673. .custom-money-left .header,
  674. .custom-money-right .header {
  675. display: flex;
  676. align-items: center;
  677. justify-content: space-between;
  678. margin-bottom: px2rpx(15);
  679. }
  680. .custom-money-left .tab,
  681. .custom-money-right .tab {
  682. font-size: px2rpx(16);
  683. font-weight: 600;
  684. color: var(--bs-heading-color);
  685. }
  686. .bottomCol {
  687. font-size: 16px;
  688. color: #868686;
  689. .bo-left1 {
  690. margin: 20px 0;
  691. padding: 20px 0;
  692. border-right: 1px dashed #989898;
  693. text-align: center;
  694. font-size: 16px;
  695. }
  696. .bo-right1 {
  697. margin: 20px 0px;
  698. padding: 20px 0;
  699. text-align: center;
  700. font-size: 16px;
  701. }
  702. }
  703. .blue-font {
  704. margin-top: px2rpx(15);
  705. color: #007aff;
  706. font-weight: 600;
  707. font-size: px2rpx(24);
  708. }
  709. .subscribe-table {
  710. width: 100%;
  711. border: 1px solid #ebeef5;
  712. border-radius: 6px;
  713. overflow: hidden;
  714. }
  715. .subscribe-row {
  716. display: grid;
  717. grid-template-columns: 1fr 1fr 1fr 1fr;
  718. border-top: 1px solid #ebeef5;
  719. }
  720. .subscribe-head {
  721. background: #f5f7fa;
  722. border-top: none;
  723. }
  724. .subscribe-cell {
  725. padding: 10px 8px;
  726. font-size: 12px;
  727. color: var(--bs-heading-color);
  728. text-align: center;
  729. overflow: hidden;
  730. text-overflow: ellipsis;
  731. white-space: nowrap;
  732. }
  733. .custom-dialog-content {
  734. padding: px2rpx(20);
  735. .info-text {
  736. color: var(--bs-heading-color);
  737. font-size: px2rpx(14);
  738. line-height: px2rpx(36);
  739. }
  740. }
  741. .card-header {
  742. display: flex;
  743. justify-content: space-between;
  744. align-items: center;
  745. margin-bottom: px2rpx(12);
  746. }
  747. .header-left {
  748. display: flex;
  749. align-items: center;
  750. gap: 12rpx;
  751. }
  752. .header-title {
  753. font-size: px2rpx(14);
  754. font-weight: 600;
  755. }
  756. .header-right {
  757. display: flex;
  758. align-items: center;
  759. }
  760. .action-btn {
  761. background: #ffde02;
  762. border: none;
  763. border-radius: 50%;
  764. width: px2rpx(32);
  765. height: px2rpx(32);
  766. display: flex;
  767. align-items: center;
  768. justify-content: center;
  769. padding: 0;
  770. margin: 0;
  771. &:after {
  772. border: none;
  773. }
  774. }
  775. .dia-content {
  776. padding: 20rpx;
  777. }
  778. .content {
  779. display: flex;
  780. flex-direction: column;
  781. gap: 20rpx;
  782. }
  783. .label {
  784. font-weight: 500;
  785. margin-bottom: 8rpx;
  786. }
  787. .btn {
  788. text-align: center;
  789. background-color: rgb(var(--bs-danger-rgb));
  790. font-size: px2rpx(16);
  791. color: #fff;
  792. padding: px2rpx(10) px2rpx(20);
  793. border-radius: px2rpx(24);
  794. margin: 0;
  795. }
  796. .crm-cursor {
  797. cursor: pointer;
  798. }
  799. .link {
  800. display: flex;
  801. margin-top: 20rpx;
  802. .btn {
  803. display: flex;
  804. align-items: center;
  805. justify-content: center;
  806. height: px2rpx(35);
  807. margin: 0 px2rpx(10);
  808. }
  809. }
  810. .chart-box {
  811. background: var(--color-white);
  812. border-radius: 8px;
  813. padding: px2rpx(16);
  814. box-shadow: 0 px2rpx(4) px2rpx(12) rgba(0, 0, 0, 0.2);
  815. }
  816. .chart-title {
  817. display: flex;
  818. justify-content: space-between;
  819. align-items: flex-start;
  820. margin-bottom: px2rpx(16);
  821. padding-bottom: px2rpx(16);
  822. border-bottom: 1px solid #f0f0f0;
  823. }
  824. .chart-title .time {
  825. display: flex;
  826. align-items: center;
  827. gap: px2rpx(8);
  828. font-size: px2rpx(20);
  829. margin-bottom: px2rpx(10);
  830. color: #999;
  831. }
  832. .chart-title .title {
  833. flex: 1;
  834. margin: 0;
  835. margin-bottom: px2rpx(10);
  836. }
  837. .chart-title .title .name {
  838. font-size: px2rpx(20);
  839. font-weight: 600;
  840. color: var(--bs-heading-color);
  841. margin-bottom: px2rpx(8);
  842. }
  843. .chart-title .title .account {
  844. font-size: px2rpx(20);
  845. color: #999;
  846. margin-bottom: px2rpx(8);
  847. }
  848. .chart-title .title .date {
  849. display: flex;
  850. }
  851. .chart-title .date-picker {
  852. font-size: px2rpx(12);
  853. color: #666;
  854. padding: px2rpx(6) px2rpx(12);
  855. background: #f5f5f5;
  856. border-radius: 4px;
  857. }
  858. .chart-title .operation {
  859. display: flex;
  860. align-items: center;
  861. gap: px2rpx(4);
  862. color: #666;
  863. margin-bottom: px2rpx(10);
  864. cursor: pointer;
  865. }
  866. .chart-container {
  867. height: px2rpx(300);
  868. }
  869. .popup-content .form {
  870. padding: px2rpx(16);
  871. }
  872. .popup-content .form-item {
  873. margin-bottom: px2rpx(20);
  874. }
  875. .popup-content .form-label {
  876. display: block;
  877. font-size: px2rpx(14);
  878. color: #666;
  879. margin-bottom: px2rpx(8);
  880. }
  881. .popup-content .picker-value {
  882. display: flex;
  883. justify-content: space-between;
  884. align-items: center;
  885. padding: px2rpx(12);
  886. background: #f5f5f5;
  887. border-radius: 4px;
  888. font-size: px2rpx(14);
  889. color: #333;
  890. }
  891. .crm-cursor {
  892. cursor: pointer;
  893. }
  894. .crm-border-radius {
  895. border-radius: 8px;
  896. }
  897. </style>