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.

343 lines
8.3 KiB

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