You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

355 lines
9.0 KiB

4 months ago
  1. <template>
  2. <div class="cash-management">
  3. <div class="cash-title">
  4. <div class="text1">
  5. {{ t('workbench.cashManagement') }}
  6. <!-- <span class="text1-update-time">-->
  7. <!-- 最后更新时间{{ workDataUpdateTime || '该地区暂无数据' }}-->
  8. <!-- </span>-->
  9. <el-popover
  10. placement="top-start"
  11. :title="t('workbench.dataExplanationTitle')"
  12. :width="240"
  13. trigger="hover"
  14. :content="t('workbench.dataExplanationContent')"
  15. >
  16. <template #reference>
  17. <el-icon
  18. class="service-icon"
  19. style="
  20. margin-left: 5px;
  21. cursor: pointer;
  22. font-size: 16px;
  23. transition: all 0.3s ease;">
  24. <Warning/>
  25. </el-icon>
  26. </template>
  27. </el-popover>
  28. </div>
  29. </div>
  30. <div class=" text2
  31. ">
  32. <span class="text2-income">{{ t('workbench.totalRevenue') }}{{ totalIncome.toFixed(2) }} {{ t('workbench.SGD') }}</span>
  33. </div>
  34. <div class="chart-container">
  35. <!-- 左侧数据列表 -->
  36. <div class="market-data">
  37. <div v-if="marksFlag" v-for="market in cashData.markets" :key="market.name" class="market-item">
  38. <span class="market-name">{{ market.name }}</span>
  39. <span class="market-value">{{ market.value.toLocaleString() }} {{ t('workbench.SGD') }}</span>
  40. </div>
  41. <div v-else v-for="item in cashData.markets" :key="item.name" class="market-item">
  42. <span class="market-name">{{ item.name }}{{ t('workbench.collect') }}</span>
  43. <span class="market-value">{{ item.value.toLocaleString() }} {{ item.currencyLabel }}</span>
  44. </div>
  45. </div>
  46. <!-- 图表 -->
  47. <div ref="chartRef" class="chart"></div>
  48. </div>
  49. </div>
  50. </template>
  51. <script setup>
  52. import * as echarts from 'echarts'
  53. import {onMounted, ref} from 'vue'
  54. import request from "@/util/http.js";
  55. import API from "@/util/http.js";
  56. import {Warning, Service} from "@element-plus/icons-vue";
  57. import { useI18n } from 'vue-i18n'
  58. const { t } = useI18n()
  59. const chartRef = ref(null)
  60. let chartInstance = null
  61. // 响应式数据
  62. const cashData = ref({
  63. markets: []
  64. })
  65. const markets = ref()
  66. // 定义默认市场
  67. const defaultMarkets = [
  68. {name: t('workbench.Singapore'), value: 0},
  69. {name: t('workbench.Malaysia'), value: 0},
  70. {name: t('workbench.HongKong'), value: 0},
  71. {name: t('workbench.Thailand'), value: 0},
  72. {name: t('workbench.VietnamHCM'), value: 0},
  73. {name: t('workbench.Canada'), value: 0},
  74. // {name: '未知', value: 0},
  75. // { name: '其他', value: 0 },
  76. // {name: '市场部', value: 0},
  77. // { name: '深圳运营', value: 0 },
  78. // { name: '研发部', value: 0 },
  79. ]
  80. const workDataUpdateTime = ref('')
  81. const totalIncome = ref(0)
  82. // 获取当前年份
  83. const currentYear = new Date().getFullYear();
  84. // 本年第一天 00:00:00
  85. const startDate = `${currentYear}-01-01 00:00:00`;
  86. // 本年最后一天 23:59:59
  87. const endDate = `${currentYear}-12-31 23:59:59`;
  88. // 获取接口数据
  89. const fetchCashData = async () => {
  90. try {
  91. const res = await request({
  92. url: '/workbench/getTotalRevenue',
  93. method: 'POST',
  94. data: {
  95. startDate: startDate,
  96. endDate: endDate
  97. }
  98. })
  99. console.log("jjjjjjj", res.market)
  100. // const data = res
  101. console.log("jjjjjjj", res)
  102. // 总新币
  103. console.log("totalIncome", res)
  104. totalIncome.value = res.reduce((sum, item) => {
  105. return sum + (item.totalSGD || 0);
  106. }, 0);
  107. // 格式化数据
  108. if (marksFlag.value) {
  109. // 生成接口数据映射表
  110. const resMap = new Map(
  111. res.map(item => [item.market, Number(item.totalSGD) || 0])
  112. )
  113. // 合并:优先用接口数据,否则默认 0
  114. markets.value = defaultMarkets.map(m => ({
  115. name: m.name,
  116. value: resMap.get(m.name) ?? 0
  117. }))
  118. } else if (marksFlag.value=== false) {
  119. const currencyCollectMarketMap = {
  120. sgd: t('workbench.Singapore'),
  121. myr: t('workbench.Malaysia'),
  122. hkd: t('workbench.HongKong'),
  123. cad: t('workbench.Canada'),
  124. thb: t('workbench.Thailand'),
  125. vdn: t('workbench.VietnamHCM'),
  126. };
  127. const currencyMap = {
  128. sgd: t('workbench.SGD'),
  129. myr: t('workbench.MYR'),
  130. hkd: t('workbench.HKD'),
  131. cad: t('workbench.CAD'),
  132. thb: t('workbench.THB'),
  133. vdn: t('workbench.VND'),
  134. };
  135. // 2. 取出所有币种字段(排除 market 与 totalSGD),只保留 currencyMap 中定义的币种
  136. const currencyKeys =
  137. res.length > 0
  138. ? Object.keys(res[0]).filter(
  139. key =>
  140. key !== 'market' &&
  141. key !== 'totalSGD' &&
  142. Object.keys(currencyMap).includes(key.toLowerCase())
  143. )
  144. : Object.keys(currencyMap);
  145. // 3. 累加每个币种的总额并替换中文名
  146. markets.value = currencyKeys.map(currency => {
  147. const lowerCurrency = currency.toLowerCase();
  148. const total =
  149. res.length > 0
  150. ? res.reduce((sum, item) => sum + (Number(item[currency]) || 0), 0)
  151. : 0;
  152. return {
  153. name: currencyCollectMarketMap[lowerCurrency],
  154. value: total,
  155. currencyLabel: currencyMap[lowerCurrency],
  156. };
  157. });
  158. }
  159. // 更新数据
  160. cashData.value.markets = markets.value
  161. console.log("cashData", cashData.value.markets)
  162. // // 使用reduce方法遍历markets数组,将所有市场的value值累加,赋值给totalIncome响应式变量
  163. // totalIncome.value = markets.value.reduce((sum, cur) => sum + cur.value, 0)
  164. // workDataUpdateTime.value = new Date().toLocaleString('zh-CN', { hour12: false })
  165. renderChart()
  166. } catch (err) {
  167. console.error('获取数据失败:', err)
  168. }
  169. }
  170. // 标记地区 为 研发部、总部时值为
  171. const marksFlag = ref();
  172. const loading = ref(true); // 新增加载状态
  173. const getAdminData = async function () {
  174. try {
  175. loading.value = true; // 开始加载
  176. const result = await API({url: '/admin/userinfo', data: {}});
  177. marksFlag.value = result.markets === '总部' || result.markets === '研发部'
  178. || result.markets === 'Headquarters' || result.markets === 'R&D Department';
  179. console.log("marksFlag", marksFlag.value);
  180. // alert(marksFlag.value)
  181. } catch (error) {
  182. console.log('请求失败', error);
  183. } finally {
  184. loading.value = false; // 无论成功失败都结束加载
  185. }
  186. };
  187. // 渲染饼图
  188. const renderChart = () => {
  189. if (!chartRef.value) return
  190. if (!chartInstance) chartInstance = echarts.init(chartRef.value)
  191. const option = {
  192. tooltip: {trigger: 'item'},
  193. legend: {
  194. bottom: 5,
  195. icon: 'circle',
  196. left: 'center'
  197. },
  198. series: [
  199. {
  200. label: {show: false},
  201. type: 'pie',
  202. radius: ['40%', '70%'],
  203. data: cashData.value.markets,
  204. center: ['60%', '45%']
  205. }
  206. ]
  207. }
  208. chartInstance.setOption(option)
  209. }
  210. onMounted( async() => {
  211. await getAdminData()
  212. await fetchCashData()
  213. })
  214. </script>
  215. <style scoped>
  216. /* 保留你原来的样式 */
  217. .cash-management {
  218. margin: 10px 5px;
  219. width: 100%;
  220. height: 550px;
  221. border-radius: 8px;
  222. background: #E7F4FD;
  223. box-shadow: 0 2px 2px 0 #00000040;
  224. display: flex;
  225. flex-direction: column;
  226. align-items: center;
  227. }
  228. .cash-title {
  229. width: 100%;
  230. height: 5vh;
  231. flex-shrink: 0;
  232. border-radius: 8px;
  233. background: linear-gradient(90deg, #E4F0FC 0%, #C6ADFF 50%, #E4F0FC 100%);
  234. box-shadow: 0 2px 2px 0 #00152940;
  235. display: flex;
  236. align-items: center;
  237. justify-content: center;
  238. }
  239. .text1 {
  240. color: #040a2d;
  241. font-family: "PingFang SC";
  242. font-size: 28px;
  243. font-weight: 900;
  244. }
  245. .text1-update-time {
  246. margin-left: 10px;
  247. color: #040a2d;
  248. font-size: 20px;
  249. font-weight: 700;
  250. }
  251. .text2 {
  252. margin: 13px;
  253. width: 95%;
  254. height: 10vh;
  255. border-radius: 8px;
  256. background: linear-gradient(90deg, #E4F0FC 0%, #C1DCF8 50%, #E4F0FC 100%);
  257. box-shadow: 0 2px 2px 0 #00152940;
  258. display: flex;
  259. align-items: center;
  260. justify-content: center;
  261. }
  262. .text2-income {
  263. color: #040a2d;
  264. font-size: 40px;
  265. font-weight: 900;
  266. }
  267. .chart-container {
  268. display: flex;
  269. align-items: center;
  270. width: 100%;
  271. height: 100%;
  272. padding: 10px;
  273. }
  274. .market-data {
  275. display: flex;
  276. width: 265px;
  277. flex-direction: column;
  278. align-items: flex-start;
  279. gap: 20px;
  280. padding: 10px;
  281. margin-left: 20px;
  282. }
  283. .market-item {
  284. display: flex;
  285. justify-content: space-between;
  286. width: 100%;
  287. font-family: "PingFang SC";
  288. font-size: 16px;
  289. color: #040a2d;
  290. white-space: nowrap; /* 禁止换行 */
  291. overflow: hidden; /* 隐藏溢出内容 */
  292. text-overflow: ellipsis; /* 溢出显示省略号 */
  293. margin-bottom: 8px; /* 增加项间距,提升可读性 */
  294. }
  295. .market-name {
  296. flex: 0 0 auto; /* 名称部分自适应宽度 */
  297. margin-right: 16px; /* 与金额保持距离 */
  298. }
  299. .market-value {
  300. flex: 1; /* 金额部分占剩余宽度 */
  301. text-align: right;
  302. }
  303. .chart {
  304. flex: 1;
  305. height: 300px;
  306. width: auto;
  307. margin-top: 10px;
  308. }
  309. </style>