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.

342 lines
8.3 KiB

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