From a5378ba9d7f0aac37092c43eecdf54782d714bc5 Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期四, 29 一月 2026 17:58:25 +0800
Subject: [PATCH] fix: 财务大屏接口联调90%

---
 /dev/null                                                                   |  306 --------------------
 src/views/reportAnalysis/financialAnalysis/components/left-bottom.vue       |   65 ++-
 src/views/reportAnalysis/financialAnalysis/components/center-top.vue        |  140 +++++++-
 src/api/viewIndex.js                                                        |  236 ++++++++------
 src/views/reportAnalysis/financialAnalysis/components/ProductTypeSwitch.vue |   23 +
 src/views/reportAnalysis/financialAnalysis/components/center-center.vue     |   22 +
 src/views/reportAnalysis/financialAnalysis/components/center-bottom.vue     |   19 +
 src/views/reportAnalysis/financialAnalysis/components/left-top.vue          |   51 ++
 8 files changed, 385 insertions(+), 477 deletions(-)

diff --git a/src/api/viewIndex.js b/src/api/viewIndex.js
index b003dbf..8bb9c83 100644
--- a/src/api/viewIndex.js
+++ b/src/api/viewIndex.js
@@ -1,157 +1,189 @@
 // 棣栭〉鎺ュ彛
-import request from '@/utils/request'
+import request from "@/utils/request";
 
 // 閿�鍞�-閲囪喘-搴撳瓨鏁版嵁
 export const getBusiness = () => {
-    return request({
-        url: '/home/business',
-        method: 'get'
-    })
-}
+  return request({
+    url: "/home/business",
+    method: "get",
+  });
+};
 // 瀹㈡埛鍚堝悓閲戦鍒嗘瀽
 export const analysisCustomerContractAmounts = () => {
-    return request({
-        url: '/home/analysisCustomerContractAmounts',
-        method: 'get'
-    })
-}
+  return request({
+    url: "/home/analysisCustomerContractAmounts",
+    method: "get",
+  });
+};
 // 璐ㄦ鍒嗘瀽
 export const qualityStatistics = () => {
-    return request({
-        url: '/home/qualityStatistics',
-        method: 'get'
-    })
-}
+  return request({
+    url: "/home/qualityStatistics",
+    method: "get",
+  });
+};
 // 搴旀敹搴斾粯缁熻
 export const statisticsReceivablePayable = (query) => {
-    return request({
-        url: '/home/statisticsReceivablePayable',
-        method: 'get',
-        params: query
-    })
-}
+  return request({
+    url: "/home/statisticsReceivablePayable",
+    method: "get",
+    params: query,
+  });
+};
 // 寰呭姙浜嬮」
 export const homeTodos = () => {
-    return request({
-        url: '/home/todos',
-        method: 'get'
-    })
-}
+  return request({
+    url: "/home/todos",
+    method: "get",
+  });
+};
 
 // 绾垮舰鍥�
 export const getAmountHalfYear = () => {
-    return request({
-        url: '/sales/ledger/getAmountHalfYear',
-        method: 'get'
-    })
-}
+  return request({
+    url: "/sales/ledger/getAmountHalfYear",
+    method: "get",
+  });
+};
 
 // 鍚勭敓浜ц鍗曠殑瀹屾垚杩涘害缁熻
 // /home/progressStatistics
-export const getProgressStatistics = ()=>{
-    return request({
-        url: '/home/progressStatistics',
-        method: 'get'
-    })
-}
+export const getProgressStatistics = () => {
+  return request({
+    url: "/home/progressStatistics",
+    method: "get",
+  });
+};
 
 //鍦ㄥ埗鍝佸懆杞儏鍐�
 //home/workInProcessTurnover
 export const getWorkInProcessTurnover = () => {
-    return request({
-        url: '/home/workInProcessTurnover',
-        method: 'get'
-    })
-}
+  return request({
+    url: "/home/workInProcessTurnover",
+    method: "get",
+  });
+};
 
 // 瀹㈡埛钀ユ敹璐$尞鏁板�煎垎鏋�
 export const customerRevenueAnalysis = (params) => {
-    return request({
-        url: '/home/customerRevenueAnalysis',
-        method: 'get',
-        params
-    })
-}
+  return request({
+    url: "/home/customerRevenueAnalysis",
+    method: "get",
+    params,
+  });
+};
 
 // 鍛樺伐-瀹㈡埛-渚涘簲鍟嗘�绘暟
 export const summaryStatistics = () => {
-    return request({
-        url: '/home/summaryStatistics',
-        method: 'get'
-    })
-}
+  return request({
+    url: "/home/summaryStatistics",
+    method: "get",
+  });
+};
 
 // 鍚勯儴闂ㄤ汉鍛樺垎甯�
 export const deptStaffDistribution = () => {
-    return request({
-        url: '/home/deptStaffDistribution',
-        method: 'get'
-    })
-}
+  return request({
+    url: "/home/deptStaffDistribution",
+    method: "get",
+  });
+};
 
 // 渚涘簲鍟嗛噰璐帓鍚�
 export const supplierPurchaseRanking = (query) => {
-    return request({
-        url: '/home/supplierPurchaseRanking',
-        method: 'get',
-        params: query
-    })
-}
+  return request({
+    url: "/home/supplierPurchaseRanking",
+    method: "get",
+    params: query,
+  });
+};
 
 // 瀹㈡埛閲戦璐$尞鎺掑悕
 export const customerContributionRanking = (query) => {
-    return request({
-        url: '/home/customerContributionRanking',
-        method: 'get',
-        params: query
-    })
-}
+  return request({
+    url: "/home/customerContributionRanking",
+    method: "get",
+    params: query,
+  });
+};
 
 // 鍚勪骇鍝佸ぇ绫诲垎甯�
 export const productCategoryDistribution = () => {
-    return request({
-        url: '/home/productCategoryDistribution',
-        method: 'get'
-    })
-}
+  return request({
+    url: "/home/productCategoryDistribution",
+    method: "get",
+  });
+};
 
 // 浜у搧閿�鍞噾棰濆垎鏋�
 export const productSalesAnalysis = () => {
-    return request({
-        url: '/home/productSalesAnalysis',
-        method: 'get'
-    })
-}
+  return request({
+    url: "/home/productSalesAnalysis",
+    method: "get",
+  });
+};
 
 // 鍘熸潗鏂欓噰璐噾棰濆崰姣�
 export const rawMaterialPurchaseAmountRatio = () => {
-    return request({
-        url: '/home/rawMaterialPurchaseAmountRatio',
-        method: 'get'
-    })
-}
+  return request({
+    url: "/home/rawMaterialPurchaseAmountRatio",
+    method: "get",
+  });
+};
 
 // 閿�鍞�/閲囪喘/鍌ㄥ瓨浜у搧鏁�
 export const salesPurchaseStorageProductCount = () => {
-    return request({
-        url: '/home/salesPurchaseStorageProductCount',
-        method: 'get'
-    })
-}
+  return request({
+    url: "/home/salesPurchaseStorageProductCount",
+    method: "get",
+  });
+};
 
 // 浜у搧鍑哄叆搴撳垎鏋愶紙鍙紶 productType: 1 鍘熸潗鏂� 2 鍗婃垚鍝� 3 鎴愬搧锛�
 export const productInOutAnalysis = (params) => {
-    return request({
-        url: '/home/productInOutAnalysis',
-        method: 'get',
-        params
-    })
-}
+  return request({
+    url: "/home/productInOutAnalysis",
+    method: "get",
+    params,
+  });
+};
 
 // 浜у搧鍛ㄨ浆澶╂暟
 export const productTurnoverDays = () => {
-    return request({
-        url: '/home/productTurnoverDays',
-        method: 'get'
-    })
-}
+  return request({
+    url: "/home/productTurnoverDays",
+    method: "get",
+  });
+};
+
+// 鏀舵敮瀵规瘮鍒嗘瀽
+export const incomeExpenseAnalysis = () => {
+  return request({
+    url: "/home/incomeExpenseAnalysis",
+    method: "get",
+  });
+};
+
+// 鍒╂鼎瓒嬪娍鍒嗘瀽
+export const profitTrendAnalysis = () => {
+  return request({
+    url: "/home/profitTrendAnalysis",
+    method: "get",
+  });
+};
+
+// 鏈堝害鏀跺叆
+export const getMonthlyIncome = () => {
+  return request({
+    url: "/home/monthlyIncome",
+    method: "get",
+  });
+};
+
+// 鏈堝害鏀嚭
+export const getMonthlyExpenditure = () => {
+  return request({
+    url: "/home/monthlyExpenditure",
+    method: "get",
+  });
+};
diff --git a/src/views/reportAnalysis/financialAnalysis/components/CarouselCards.vue b/src/views/reportAnalysis/financialAnalysis/components/CarouselCards.vue
deleted file mode 100644
index 0498824..0000000
--- a/src/views/reportAnalysis/financialAnalysis/components/CarouselCards.vue
+++ /dev/null
@@ -1,306 +0,0 @@
-<template>
-  <div class="carousel-cards">
-    <button 
-      v-if="canScrollLeft"
-      class="nav-button nav-button-left"
-      @click="scrollLeftFn"
-    >
-      <img src="@/assets/BI/jiantou.png" alt="宸︾澶�" />
-    </button>
-    <div 
-      class="cards-container" 
-      :style="{ '--visible-count': visibleCount }"
-      ref="cardsContainerRef"
-    >
-      <div
-        v-for="(item, index) in items"
-        :key="index"
-        class="card-item"
-      >
-        <div v-if="item.icon" class="card-icon" :style="{ backgroundImage: `url(${item.icon})` }"></div>
-        <div class="card-title">
-          <div class="card-label">{{ item.label }}</div>
-          <div class="card-value">
-            <span class="value-number">{{ item.value }}</span>
-            <span class="value-unit">{{ item.unit }}</span>
-          </div>
-          <div v-if="item.rate ?? item.ratio ?? item.percent" class="card-rate">
-            <span class="rate-value">{{ item.rate ?? item.ratio ?? item.percent }}%</span>
-          </div>
-        </div>
-      </div>
-    </div>
-    <button 
-      v-if="canScrollRight"
-      class="nav-button nav-button-right"
-      @click="scrollRightFn"
-    >
-      <img src="@/assets/BI/jiantou.png" alt="鍙崇澶�" />
-    </button>
-  </div>
-</template>
-
-<script setup>
-import { ref, onMounted, onBeforeUnmount, nextTick, watch, computed } from 'vue'
-
-const props = defineProps({
-  items: {
-    type: Array,
-    default: () => [],
-    validator: (value) => {
-      return value.every(item => 
-        item && typeof item.label !== 'undefined' && 
-        typeof item.value !== 'undefined' && 
-        typeof item.unit !== 'undefined'
-      )
-    }
-  },
-  visibleCount: {
-    type: Number,
-    default: 3
-  }
-})
-
-const cardsContainerRef = ref(null)
-const currentScrollLeft = ref(0)
-const maxScrollLeft = ref(0)
-
-// 妫�鏌ユ槸鍚﹀彲浠ュ悜宸︽粴鍔�
-const canScrollLeft = computed(() => {
-  return currentScrollLeft.value > 0
-})
-
-// 妫�鏌ユ槸鍚﹀彲浠ュ悜鍙虫粴鍔�
-const canScrollRight = computed(() => {
-  return currentScrollLeft.value < maxScrollLeft.value
-})
-
-// 鏇存柊婊氬姩鐘舵��
-const updateScrollState = () => {
-  const container = cardsContainerRef.value
-  if (!container) return
-  
-  currentScrollLeft.value = container.scrollLeft
-  maxScrollLeft.value = container.scrollWidth - container.clientWidth
-}
-
-// 鍚戝乏婊氬姩
-const scrollLeftFn = () => {
-  const container = cardsContainerRef.value
-  if (!container) return
-  
-  const scrollItems = Array.from(container.querySelectorAll('.card-item'))
-  if (scrollItems.length === 0) return
-  
-  const itemWidth = scrollItems[0]?.offsetWidth || 0
-  const gap = 12
-  const scrollDistance = itemWidth + gap
-  
-  container.scrollBy({
-    left: -scrollDistance,
-    behavior: 'smooth'
-  })
-  
-  // 寤惰繜鏇存柊鐘舵�侊紝绛夊緟婊氬姩鍔ㄧ敾瀹屾垚
-  setTimeout(() => {
-    updateScrollState()
-  }, 300)
-}
-
-// 鍚戝彸婊氬姩
-const scrollRightFn = () => {
-  const container = cardsContainerRef.value
-  if (!container) return
-  
-  const scrollItems = Array.from(container.querySelectorAll('.card-item'))
-  if (scrollItems.length === 0) return
-  
-  const itemWidth = scrollItems[0]?.offsetWidth || 0
-  const gap = 12
-  const scrollDistance = itemWidth + gap
-  
-  container.scrollBy({
-    left: scrollDistance,
-    behavior: 'smooth'
-  })
-  
-  // 寤惰繜鏇存柊鐘舵�侊紝绛夊緟婊氬姩鍔ㄧ敾瀹屾垚
-  setTimeout(() => {
-    updateScrollState()
-  }, 300)
-}
-
-// 鐩戝惉 items 鍙樺寲锛屾洿鏂版粴鍔ㄧ姸鎬�
-watch(() => props.items, () => {
-  nextTick(() => {
-    updateScrollState()
-  })
-}, { deep: true })
-
-onMounted(() => {
-  nextTick(() => {
-    updateScrollState()
-    // 鐩戝惉婊氬姩浜嬩欢
-    const container = cardsContainerRef.value
-    if (container) {
-      container.addEventListener('scroll', updateScrollState)
-    }
-  })
-})
-
-onBeforeUnmount(() => {
-  // 娓呯悊婊氬姩浜嬩欢鐩戝惉鍣�
-  const container = cardsContainerRef.value
-  if (container) {
-    container.removeEventListener('scroll', updateScrollState)
-  }
-})
-</script>
-
-<style scoped>
-.carousel-cards {
-  width: 100%;
-  overflow: hidden;
-  position: relative;
-  display: flex;
-  align-items: center;
-}
-
-.cards-container {
-  display: flex;
-  gap: 12px;
-  width: 100%;
-  overflow-x: auto;
-  overflow-y: hidden;
-  scrollbar-width: none; /* Firefox */
-  -ms-overflow-style: none; /* IE and Edge */
-  padding-bottom: 4px;
-  scroll-behavior: smooth;
-}
-
-.cards-container::-webkit-scrollbar {
-  display: none; /* Chrome, Safari, Opera */
-}
-
-.nav-button {
-  position: absolute;
-  top: 50%;
-  transform: translateY(-50%);
-  width: 32px;
-  height: 32px;
-  background: rgba(26, 88, 176, 0.6);
-  border: 1px solid rgba(26, 88, 176, 0.8);
-  border-radius: 50%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  cursor: pointer;
-  z-index: 10;
-  transition: all 0.3s ease;
-  padding: 0;
-}
-
-.nav-button:hover {
-  background: rgba(26, 88, 176, 0.8);
-  transform: translateY(-50%) scale(1.1);
-}
-
-.nav-button-left {
-  left: -16px;
-}
-
-.nav-button-left img {
-  width: 16px;
-  height: 16px;
-  transform: rotate(180deg);
-}
-
-.nav-button-right {
-  right: -16px;
-}
-
-.nav-button-right img {
-  width: 16px;
-  height: 16px;
-}
-
-.card-item {
-  flex: 0 0 calc((100% - (var(--visible-count) - 1) * 12px) / var(--visible-count));
-  min-width: calc((100% - (var(--visible-count) - 1) * 12px) / var(--visible-count));
-  display: flex;
-  align-items: center;
-  background: linear-gradient(269deg, rgba(27,57,126,0.13) 0%, rgba(33,137,206,0.33) 98.13%, #24AFF4 100%);
-  border-radius: 8px 8px 8px 8px;
-  padding: 12px 16px;
-  transition: all 0.3s ease;
-}
-
-.card-item:hover {
-  transform: translateY(-2px);
-}
-
-.card-icon {
-  width: 80px;
-  height: 60px;
-  background-size: cover;
-  background-position: center;
-  background-repeat: no-repeat;
-  flex-shrink: 0;
-  margin-right: 12px;
-}
-
-.card-title {
-  display: flex;
-  align-items: flex-start;
-  flex-direction: column;
-  flex: 1;
-}
-
-.card-label {
-  font-weight: 400;
-  font-size: 14px;
-  color: #FFFFFF;
-  margin-bottom: 4px;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  width: 100%;
-}
-
-.card-value {
-  display: flex;
-  align-items: baseline;
-  gap: 4px;
-}
-
-.card-rate {
-  margin-top: 4px;
-  display: flex;
-  align-items: center;
-  gap: 6px;
-  font-weight: 400;
-  font-size: 12px;
-  color: rgba(255, 255, 255, 0.85);
-}
-
-.rate-label {
-  opacity: 0.85;
-}
-
-.rate-value {
-  font-weight: 500;
-}
-
-.value-number {
-  font-weight: 400;
-  font-size: 14px;
-  color: #FFFFFF;
-  line-height: 1;
-}
-
-.value-unit {
-  font-size: 14px;
-  color: #FFFFFF;
-  font-weight: 400;
-}
-</style>
diff --git a/src/views/reportAnalysis/financialAnalysis/components/ProductTypeSwitch.vue b/src/views/reportAnalysis/financialAnalysis/components/ProductTypeSwitch.vue
index 87cde44..98d07eb 100644
--- a/src/views/reportAnalysis/financialAnalysis/components/ProductTypeSwitch.vue
+++ b/src/views/reportAnalysis/financialAnalysis/components/ProductTypeSwitch.vue
@@ -4,9 +4,13 @@
     class="product-type-switch"
     @change="handleChange"
   >
-    <el-radio-button :label="1">鍘熸潗鏂�</el-radio-button>
-    <el-radio-button :label="3">鍗婃垚鍝�</el-radio-button>
-    <el-radio-button :label="2">鎴愬搧</el-radio-button>
+    <el-radio-button
+      v-for="opt in options"
+      :key="opt.label"
+      :label="opt.label"
+    >
+      {{ opt.text }}
+    </el-radio-button>
   </el-radio-group>
 </template>
 
@@ -15,8 +19,17 @@
 
 const props = defineProps({
   modelValue: {
-    type: Number,
-    default: 1, // 榛樿閫変腑"鍘熸潗鏂�"
+    type: [Number, String],
+    default: 1, // 榛樿閫変腑绗竴涓�
+  },
+  // 鍙厤缃�夐」锛岄粯璁ゆ槸鍘熺粍浠剁殑銆屽師鏉愭枡 / 鍗婃垚鍝� / 鎴愬搧銆�
+  options: {
+    type: Array,
+    default: () => [
+      { label: 1, text: '鍘熸潗鏂�' },
+      { label: 3, text: '鍗婃垚鍝�' },
+      { label: 2, text: '鎴愬搧' },
+    ],
   },
 })
 
diff --git a/src/views/reportAnalysis/financialAnalysis/components/center-bottom.vue b/src/views/reportAnalysis/financialAnalysis/components/center-bottom.vue
index 1d15a39..c8eeeb9 100644
--- a/src/views/reportAnalysis/financialAnalysis/components/center-bottom.vue
+++ b/src/views/reportAnalysis/financialAnalysis/components/center-bottom.vue
@@ -22,6 +22,7 @@
 import { ref, onMounted } from 'vue'
 import Echarts from '@/components/Echarts/echarts.vue'
 import PanelHeader from './PanelHeader.vue'
+import { profitTrendAnalysis } from '@/api/viewIndex.js'
 
 const chartStyle = { width: '100%', height: '150%' }
 const grid = { left: '3%', right: '4%', bottom: '3%', top: '4%', containLabel: true }
@@ -70,10 +71,22 @@
 
 const yAxis1 = [{ type: 'value', axisLabel: { color: '#B8C8E0' } }]
 
+const fetchData = () => {
+  profitTrendAnalysis()
+    .then((res) => {
+      if (res.code === 200 && Array.isArray(res.data)) {
+        const list = res.data
+        xAxis1.value[0].data = list.map((d) => d.name)
+        barSeries1.value[0].data = list.map((d) => parseFloat(d.value) || 0)
+      }
+    })
+    .catch((err) => {
+      console.error('鑾峰彇鍒╂鼎瓒嬪娍鍒嗘瀽澶辫触:', err)
+    })
+}
+
 onMounted(() => {
-  // 鍏堢敤鏈湴鍋囨暟鎹紙鍚庣画濡傛湁鎺ュ彛鍙浛鎹級
-  xAxis1.value[0].data = ['1鏈�', '2鏈�', '3鏈�', '4鏈�', '5鏈�', '6鏈�']
-  barSeries1.value[0].data = [12000, 18000, 9000, 16000, 14000, 20000]
+  fetchData()
 })
 </script>
 
diff --git a/src/views/reportAnalysis/financialAnalysis/components/center-center.vue b/src/views/reportAnalysis/financialAnalysis/components/center-center.vue
index 7778752..7d32ebd 100644
--- a/src/views/reportAnalysis/financialAnalysis/components/center-center.vue
+++ b/src/views/reportAnalysis/financialAnalysis/components/center-center.vue
@@ -30,6 +30,7 @@
 import { ref, onMounted } from 'vue'
 import * as echarts from 'echarts'
 import Echarts from '@/components/Echarts/echarts.vue'
+import { incomeExpenseAnalysis } from '@/api/viewIndex.js'
 
 const chartStyle = { width: '100%', height: '100%' }
 const grid = {
@@ -128,16 +129,23 @@
   },
 }
 
-// 鍏堢敤鏈湴鍋囨暟鎹紙鍚庣画濡傛湁鎺ュ彛鍙洿鎺ユ浛鎹㈣繖閲岋級
-const setMockData = () => {
-  const dates = ['1/22', '1/23', '1/24', '1/25', '1/26', '1/27', '1/28']
-  xAxis1.value[0].data = dates
-  lineSeries.value[0].data = [1200, 1800, 900, 1600, 1400, 2000, 1700] // 鏀跺叆
-  lineSeries.value[1].data = [800, 1100, 700, 1200, 1000, 1500, 1300] // 鏀嚭
+const fetchData = () => {
+  incomeExpenseAnalysis()
+    .then((res) => {
+      if (res.code === 200 && Array.isArray(res.data)) {
+        const list = res.data
+        xAxis1.value[0].data = list.map((d) => d.date)
+        lineSeries.value[0].data = list.map((d) => Number(d.income) || 0)
+        lineSeries.value[1].data = list.map((d) => Number(d.expense) || 0)
+      }
+    })
+    .catch((err) => {
+      console.error('鑾峰彇鏀舵敮瀵规瘮鍒嗘瀽澶辫触:', err)
+    })
 }
 
 onMounted(() => {
-  setMockData()
+  fetchData()
 })
 </script>
 
diff --git a/src/views/reportAnalysis/financialAnalysis/components/center-top.vue b/src/views/reportAnalysis/financialAnalysis/components/center-top.vue
index d46a0ac..85f4928 100644
--- a/src/views/reportAnalysis/financialAnalysis/components/center-top.vue
+++ b/src/views/reportAnalysis/financialAnalysis/components/center-top.vue
@@ -10,7 +10,10 @@
         <div class="card-body">
           <div class="card-left">
             <div class="card-title">鏈堝害鏀跺叆</div>
-            <div class="card-amount">{{ income.amount }}</div>
+            <div class="card-amount">
+              <span>{{ formatAmountWanNumber(income.amount) }}</span>
+              <span v-if="isWanAmount(income.amount)" class="card-amount-unit">涓�</span>
+            </div>
           </div>
           <div class="card-right">
             <div class="metric-row">
@@ -22,7 +25,15 @@
             </div>
             <div class="metric-row">
               <span class="metric-label">閫炬湡鏁�</span>
-              <span class="metric-value metric-up">{{ income.overdueCount }}</span>
+              <span class="metric-value metric-up">
+                {{ formatAmountWanNumber(income.overdueCount) }}
+                <span
+                  v-if="isWanAmount(income.overdueCount)"
+                  class="metric-unit"
+                >
+                  涓�
+                </span>
+              </span>
             </div>
             <div class="metric-row">
               <span class="metric-label">閫炬湡鐜�</span>
@@ -43,16 +54,30 @@
         <div class="card-body">
           <div class="card-left">
             <div class="card-title">鏈堝害鏀嚭</div>
-            <div class="card-amount">{{ expense.amount }}</div>
+            <div class="card-amount">
+              <span>{{ formatAmountWanNumber(expense.amount) }}</span>
+              <span v-if="isWanAmount(expense.amount)" class="card-amount-unit">涓�</span>
+            </div>
           </div>
           <div class="card-right">
             <div class="metric-row">
               <span class="metric-label">浠樻鐜�</span>
-              <span class="metric-value metric-down">{{ expense.netProfit }}</span>
+              <span class="metric-value" :class="metricClass(expense.netProfit)">
+                {{ formatPercent(expense.netProfit.value) }}
+                <span class="arrow">{{ metricArrow(expense.netProfit) }}</span>
+              </span>
             </div>
             <div class="metric-row">
               <span class="metric-label">姣涘埄娑�</span>
-              <span class="metric-value metric-down">{{ expense.grossProfit }}</span>
+              <span class="metric-value metric-down">
+                {{ formatAmountWanNumber(expense.grossProfit) }}
+                <span
+                  v-if="isWanAmount(expense.grossProfit)"
+                  class="metric-unit"
+                >
+                  涓�
+                </span>
+              </span>
             </div>
             <div class="metric-row">
               <span class="metric-label">鍒╂鼎鐜�</span>
@@ -70,33 +95,93 @@
 </template>
 
 <script setup>
-import { ref } from 'vue'
+import { onMounted, ref } from 'vue'
+import { getMonthlyIncome, getMonthlyExpenditure } from '@/api/viewIndex'
 
-// 鏆傛椂浣跨敤鏈湴绀轰緥鏁版嵁锛屽悗缁彲鎺ョ湡瀹炴帴鍙h鐩�
 const income = ref({
-  amount: 102,
-  repayRate: { value: 52, trend: 1 }, // 姝e悜 鈫�
-  overdueCount: 10092,
-  overdueRate: { value: 12, trend: 1 },
+  amount: 0,
+  repayRate: { value: 0, trend: 0 },
+  overdueCount: 0,
+  overdueRate: { value: 0, trend: 0 },
 })
 
 const expense = ref({
-  amount: 102,
-  netProfit: 291013,
-  grossProfit: 10092,
-  profitRate: { value: 12, trend: -1 }, // 璐熷悜 鈫�
+  amount: 0,
+  netProfit: { value: 0, trend: 0 },
+  grossProfit: 0,
+  profitRate: { value: 0, trend: 0 },
 })
+
+const fetchMonthlyIncome = async () => {
+  const res = await getMonthlyIncome()
+  const data = res?.data || {}
+
+  income.value.amount = data.monthlyIncome ?? 0
+  const collectionRate = Number(data.collectionRate ?? 0)
+  const overdueRate = Number(data.overdueRate ?? 0)
+  income.value.repayRate = {
+    value: collectionRate,
+    trend: collectionRate >= 0 ? 1 : -1,
+  }
+  income.value.overdueCount = data.overdueNum ?? 0
+  income.value.overdueRate = {
+    value: overdueRate,
+    trend: overdueRate >= 0 ? 1 : -1,
+  }
+}
+
+const fetchMonthlyExpenditure = async () => {
+  const res = await getMonthlyExpenditure()
+  const data = res?.data || {}
+
+  expense.value.amount = data.monthlyExpenditure ?? 0
+  const paymentRate = Number(data.paymentRate ?? 0)
+  expense.value.netProfit = {
+    value: paymentRate,
+    trend: paymentRate >= 0 ? 1 : -1,
+  }
+  expense.value.grossProfit = data.grossProfit ?? 0
+
+  const profitMarginRate = Number(data.profitMarginRate ?? 0)
+  expense.value.profitRate = {
+    value: profitMarginRate,
+    trend: profitMarginRate >= 0 ? 1 : -1,
+  }
+}
+
+const isWanAmount = (val) => {
+  const num = Number(val) || 0
+  return Math.abs(num) >= 10000
+}
+
+const formatAmountWanNumber = (val) => {
+  const num = Number(val) || 0
+  if (Math.abs(num) >= 10000) {
+    return (num / 10000).toFixed(2)
+  }
+  return num.toFixed(2)
+}
 
 const formatPercent = (val) => {
   const num = Number(val) || 0
-  return `${num.toFixed(2)}%`
+  // 鐧惧垎姣斿睍绀哄缁堢敤缁濆鍊硷紝灏忔暟淇濈暀涓や綅
+  return `${Math.abs(num).toFixed(2)}%`
 }
 
-const metricClass = (metric) =>
-  Number(metric.trend) >= 0 ? 'metric-up' : 'metric-down'
+const metricClass = (metric) => {
+  if (metric?.trend === undefined || metric?.trend === null) return 'metric-up'
+  return Number(metric.trend) >= 0 ? 'metric-up' : 'metric-down'
+}
 
-const metricArrow = (metric) =>
-  Number(metric.trend) >= 0 ? '鈫�' : '鈫�'
+const metricArrow = (metric) => {
+  if (metric?.trend === undefined || metric?.trend === null) return ''
+  return Number(metric.trend) >= 0 ? '鈫�' : '鈫�'
+}
+
+onMounted(() => {
+  fetchMonthlyIncome()
+  fetchMonthlyExpenditure()
+})
 </script>
 
 <style scoped>
@@ -110,7 +195,7 @@
   flex: 1;
   display: flex;
   align-items: center;
-  padding: 18px 24px;
+  padding: 18px 10px;
   background-image: url('@/assets/BI/border@2x.png');
   background-size: 100% 100%;
   background-position: center;
@@ -156,10 +241,18 @@
   font-size: 36px;
   line-height: 1.1;
   margin-top: 8px;
+  display: inline-flex;
+  align-items: baseline;
+  white-space: nowrap;
   background: linear-gradient(360deg, #008bfd 0%, #ffffff 100%);
   -webkit-background-clip: text;
   -webkit-text-fill-color: transparent;
   background-clip: text;
+}
+
+.card-amount-unit {
+  font-size: 20px;
+  margin-left: 4px;
 }
 
 .card-right {
@@ -193,6 +286,11 @@
   align-items: center;
 }
 
+.metric-unit {
+  font-size: 12px;
+  margin-left: 2px;
+}
+
 .metric-value .arrow {
   font-size: 13px;
   margin-left: 4px;
diff --git a/src/views/reportAnalysis/financialAnalysis/components/left-bottom.vue b/src/views/reportAnalysis/financialAnalysis/components/left-bottom.vue
index 076dcbe..7aa8c4e 100644
--- a/src/views/reportAnalysis/financialAnalysis/components/left-bottom.vue
+++ b/src/views/reportAnalysis/financialAnalysis/components/left-bottom.vue
@@ -1,7 +1,14 @@
 <template>
   <div>
-    <PanelHeader title="鏀嚭鏋勬垚鍒嗘瀽" />
+    <PanelHeader title="鏋勬垚鍒嗘瀽" />
     <div class="main-panel panel-item-customers">
+      <div class="filters-row">
+        <ProductTypeSwitch
+          v-model="amountType"
+          :options="amountTypeOptions"
+          @change="handleTypeChange"
+        />
+      </div>
       <!-- <CarouselCards :items="cardItems" :visible-count="3" /> -->
       <div class="pie-chart-wrapper">
         <div class="pie-background"></div>
@@ -25,7 +32,7 @@
 import { ref, onMounted, computed } from 'vue'
 import Echarts from '@/components/Echarts/echarts.vue'
 import PanelHeader from './PanelHeader.vue'
-import CarouselCards from './CarouselCards.vue'
+import ProductTypeSwitch from './ProductTypeSwitch.vue'
 import { rawMaterialPurchaseAmountRatio } from '@/api/viewIndex.js'
 
 /**
@@ -41,6 +48,14 @@
   }
   return resObj
 }
+
+// 褰撳墠绫诲瀷锛�1=鏀嚭 2=鏀跺叆
+const amountType = ref(1)
+
+const amountTypeOptions = [
+  { label: 1, text: '鏀嚭' },
+  { label: 2, text: '鏀跺叆' },
+]
 
 // 鏁版嵁鍒楄〃锛堟潵鑷帴鍙o級
 const dataList = ref([])
@@ -69,7 +84,7 @@
     top: 'center',
     left: '52%',
     itemGap: 30,
-    show: false,
+    show: true,
     data: data,
     formatter: function (name) {
       const item = landObjData.value[name]
@@ -112,12 +127,13 @@
 }
 
 // 鍙屽眰鐜舰楗煎浘
+// 鍙屽眰鐜舰楗煎浘
 const landSeries = ref([
   {
-    name: '浜у搧閲囪喘閲戦鍒嗘瀽',
+    name: '鏋勬垚鍒嗘瀽',
     type: 'pie',
-    radius: ['50%', '75%'],
-    center: ['50%', '60%'],
+    radius: ['40%', '60%'],
+    center: ['25%', '50%'],
     itemStyle: {
       borderColor: '#0a1c3a',
       borderWidth: 2,
@@ -126,16 +142,7 @@
       },
     },
     label: {
-      show: true,
-      position: 'outside',
-      color: '#fff',
-      fontSize: 12,
-      lineHeight: 18,
-      // rich: {
-      //   ...dotRich,
-      //   parent: { fontSize: 14, fontWeight: 600, color: '#fff', lineHeight: 20, overflow: 'break' },
-      //   child: { fontSize: 12, color: '#fff', lineHeight: 18 },
-      // },
+      show: false
     },
     minAngle: 15,
     data: dataList.value,
@@ -148,8 +155,8 @@
   {
     // 鍐呭湀
     type: 'pie',
-    radius: ['50%', '60%'],
-    center: ['50%', '60%'],
+    radius: ['40%', '45%'],
+    center: ['25%', '50%'],
     silent: true,
     label: {
       show: false,
@@ -175,6 +182,8 @@
 }
 
 const fetchData = () => {
+  // 鐩墠鎺ュ彛鍙湁鏀嚭鏋勬垚鍗犳瘮锛屽厛蹇界暐绫诲瀷鍙傛暟
+  // 棰勭暀鎵╁睍锛氬悗缁彲鏍规嵁 amountType 鍒囦笉鍚屾帴鍙�
   rawMaterialPurchaseAmountRatio()
     .then((res) => {
       if (res.code === 200 && Array.isArray(res.data)) {
@@ -199,6 +208,10 @@
     })
 }
 
+const handleTypeChange = () => {
+  fetchData()
+}
+
 onMounted(() => {
   fetchData()
 })
@@ -209,6 +222,14 @@
   display: flex;
   flex-direction: column;
   gap: 20px;
+}
+
+.filters-row {
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 10px;
 }
 
 .panel-item-customers {
@@ -228,11 +249,11 @@
 
 .pie-background {
   position: absolute;
-  left: 50%;
-  top: 60%;
+  left: 25%;
+  top: 50%;
   transform: translate(-51.5%, -50%);
-  width: 380px;
-  height: 380px;
+  width: 310px;
+  height: 310px;
   background-image: url('@/assets/BI/鐜懓鍥捐竟妗�.png');
   background-size: contain;
   background-position: center;
diff --git a/src/views/reportAnalysis/financialAnalysis/components/left-top.vue b/src/views/reportAnalysis/financialAnalysis/components/left-top.vue
index 1c9f085..c735dba 100644
--- a/src/views/reportAnalysis/financialAnalysis/components/left-top.vue
+++ b/src/views/reportAnalysis/financialAnalysis/components/left-top.vue
@@ -20,7 +20,7 @@
 
 <script setup>
 import { ref, onMounted } from 'vue'
-import { productSalesAnalysis } from '@/api/viewIndex.js'
+import { getAmountHalfYear } from '@/api/viewIndex.js'
 import PanelHeader from './PanelHeader.vue'
 import Echarts from '@/components/Echarts/echarts.vue'
 
@@ -30,13 +30,16 @@
 }
 
 const grid = { left: '3%', right: '4%', bottom: '3%', top: '10%', containLabel: true }
-const barLegend = { show: true, textStyle: { color: '#B8C8E0' }, data: ['閲戦'] }
+const barLegend = {
+  show: true,
+  textStyle: { color: '#B8C8E0' },
+  data: ['寮�绁ㄩ噾棰�', '鍥炴閲戦'],
+}
 const barSeries1 = ref([
   {
-    name: '閲戦',
+    name: '寮�绁ㄩ噾棰�',
     type: 'bar',
-    barGap: 0,
-    barWidth: 30,
+    barWidth: 20,
     emphasis: { focus: 'series' },
     itemStyle: {
       color: {
@@ -46,8 +49,29 @@
         x2: 0,
         y2: 1,
         colorStops: [
-          { offset: 1, color: '#00A4ED' },
-          { offset: 0, color: '#4EE4FF' },
+          { offset: 1, color: 'rgba(0, 164, 237, 0)' },
+          { offset: 0, color: 'rgba(78, 228, 255, 1)' },
+        ],
+      },
+    },
+    data: [],
+  },
+  {
+    name: '鍥炴閲戦',
+    type: 'bar',
+    barGap: '40%',
+    barWidth: 20,
+    emphasis: { focus: 'series' },
+    itemStyle: {
+      color: {
+        type: 'linear',
+        x: 0,
+        y: 0,
+        x2: 0,
+        y2: 1,
+        colorStops: [
+          { offset: 1, color: 'rgba(83, 126, 245, 0.19)' },
+          { offset: 0, color: 'rgba(144, 97, 248, 1)' },
         ],
       },
     },
@@ -71,16 +95,21 @@
 const yAxis1 = [{ type: 'value', axisLabel: { color: '#B8C8E0' } }]
 
 const fetchData = () => {
-  productSalesAnalysis()
+  getAmountHalfYear()
     .then((res) => {
       if (res.code === 200 && Array.isArray(res.data)) {
         const items = res.data
-      xAxis1.value[0].data = items.map((item) => item.name)
-      barSeries1.value[0].data = items.map((item) => parseFloat(item.value) || 0)
+        xAxis1.value[0].data = items.map((item) => item.month)
+        barSeries1.value[0].data = items.map(
+          (item) => parseFloat(item.invoiceAmount) || 0
+        )
+        barSeries1.value[1].data = items.map(
+          (item) => parseFloat(item.receiptAmount) || 0
+        )
       }
     })
     .catch((err) => {
-      console.error('鑾峰彇浜у搧閿�鍞噾棰濆垎鏋愬け璐�:', err)
+      console.error('鑾峰彇杩戝崐骞村洖娆句笌寮�绁ㄦ暟鎹け璐�:', err)
     })
 }
 

--
Gitblit v1.9.3