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.

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