From bd3051488bdbab156c2bc5ca0ad108989978fbb8 Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期四, 15 一月 2026 14:34:54 +0800
Subject: [PATCH] Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-management into dev_New

---
 src/views/salesManagement/indicatorStats/index.vue |  346 ++++++++++++++++++++++++++++++++++++++++++--------------
 1 files changed, 257 insertions(+), 89 deletions(-)

diff --git a/src/views/salesManagement/indicatorStats/index.vue b/src/views/salesManagement/indicatorStats/index.vue
index 7bd609e..65dae96 100644
--- a/src/views/salesManagement/indicatorStats/index.vue
+++ b/src/views/salesManagement/indicatorStats/index.vue
@@ -31,7 +31,7 @@
               <el-icon :size="30" color="#e6a23c"><Van /></el-icon>
             </div>
             <div class="stat-content">
-              <div class="stat-value">{{ indicatorKpis.shipmentRate }}%</div>
+              <div class="stat-value">{{ indicatorKpis.shipRate }}%</div>
               <div class="stat-label">鍙戣揣鐜�</div>
             </div>
           </div>
@@ -41,44 +41,27 @@
       <!-- 缁村害绛涢�� -->
       <el-row :gutter="20" class="search-row">
         <el-col :span="6">
-          <el-select v-model="indicatorFilter.product" placeholder="浜у搧" clearable>
-            <el-option label="鍏ㄩ儴浜у搧" value="" />
-            <el-option label="P.O 42.5鏅�氱閰哥洂姘存偿" value="P.O 42.5鏅�氱閰哥洂姘存偿" />
-            <el-option label="P.S 32.5鐭挎福纭呴吀鐩愭按娉�" value="P.S 32.5鐭挎福纭呴吀鐩愭按娉�" />
-            <el-option label="P.C 32.5澶嶅悎纭呴吀鐩愭按娉�" value="P.C 32.5澶嶅悎纭呴吀鐩愭按娉�" />
-          </el-select>
+          <el-tree-select v-model="indicatorFilter.productCategory" placeholder="浜у搧绫诲埆" clearable check-strictly
+            :data="productOptions" :render-after-expand="false" style="width: 100%" />
         </el-col>
         <el-col :span="6">
-          <el-select v-model="indicatorFilter.customer" placeholder="瀹㈡埛" clearable>
-            <el-option label="鍏ㄩ儴瀹㈡埛" value="" />
-            <el-option label="鍗庝笢寤烘潗闆嗗洟" value="鍗庝笢寤烘潗闆嗗洟" />
-            <el-option label="闀挎睙娣峰嚌鍦熷叕鍙�" value="闀挎睙娣峰嚌鍦熷叕鍙�" />
-            <el-option label="娴︽睙姘存偿鍒跺搧鍘�" value="娴︽睙姘存偿鍒跺搧鍘�" />
-          </el-select>
-        </el-col>
-        <el-col :span="6">
-          <el-select v-model="indicatorFilter.region" placeholder="鍖哄煙" clearable>
-            <el-option label="鍏ㄩ儴鍖哄煙" value="" />
-            <el-option label="鍗庝笢鍦板尯" value="鍗庝笢鍦板尯" />
-            <el-option label="鍗庡崡鍦板尯" value="鍗庡崡鍦板尯" />
-            <el-option label="鍗庡寳鍦板尯" value="鍗庡寳鍦板尯" />
+          <el-select v-model="indicatorFilter.customerName" placeholder="瀹㈡埛" clearable filterable>
+            <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.customerName" />
           </el-select>
         </el-col>
         <el-col :span="6">
           <el-date-picker v-model="indicatorFilter.dateRange" type="daterange" range-separator="鑷�"
                           start-placeholder="寮�濮嬫棩鏈�" end-placeholder="缁撴潫鏃ユ湡" value-format="YYYY-MM-DD" style="width: 100%" />
         </el-col>
-        <el-col :span="24" style="text-align: right; margin-top: 10px;">
+        <el-col :span="6" style="text-align: right;">
           <el-button type="primary" @click="applyIndicatorFilter">鏌ヨ</el-button>
           <el-button @click="resetIndicatorFilter">閲嶇疆</el-button>
-          <el-button @click="exportIndicatorTable">瀵煎嚭鎶ヨ〃</el-button>
-          <el-button @click="exportIndicatorChart">瀵煎嚭鍥捐〃</el-button>
         </el-col>
       </el-row>
 
       <!-- 鍥捐〃鍖� -->
       <div class="chart-container">
-        <div ref="indicatorChartRef" style="width: 100%; height: 360px;"></div>
+        <div ref="indicatorChartRef" class="chart-wrapper"></div>
       </div>
 
       <!-- 涓氱哗缁熻锛堝洟闃熺淮搴︼紝鏃犱釜浜哄鍚嶏級 -->
@@ -88,8 +71,8 @@
         <el-table-column prop="salesAmount" label="閿�鍞">
           <template #default="scope">楼{{ scope.row.salesAmount.toLocaleString() }}</template>
         </el-table-column>
-        <el-table-column prop="shipmentRate" label="鍙戣揣鐜�">
-          <template #default="scope">{{ scope.row.shipmentRate }}%</template>
+        <el-table-column prop="shipRate" label="鍙戣揣鐜�">
+          <template #default="scope">{{ scope.row.shipRate }}</template>
         </el-table-column>
         <el-table-column prop="attainment" label="鐩爣杈炬垚鐜�">
           <template #default="scope">
@@ -104,35 +87,209 @@
 </template>
 
 <script setup>
-import { ref, reactive, onMounted, nextTick } from 'vue'
+import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'
 import { Document, Van, Tickets } from '@element-plus/icons-vue'
 import * as echarts from 'echarts'
+import { getTotalStatistics, getStatisticsTable } from '@/api/salesManagement/indicatorStats'
+import { productTreeList } from '@/api/basicData/product.js'
+import { customerList } from '@/api/salesManagement/salesLedger.js'
+import { ElMessage } from 'element-plus'
 
 const indicatorKpis = reactive({
-  orderCount: 1280,
-  salesAmount: 9650000,
-  shipmentRate: 89.2
+  orderCount: 0,
+  salesAmount: 0,
+  shipRate: 0
 })
 
 // 鏄惁灞曠ず閿�鍞洟闃熸槑缁嗚〃锛屾寜闇�寮�鍚�
 const showTeamPerformance = ref(false)
+const loading = ref(false)
 
 const indicatorFilter = reactive({
-  product: '',
-  customer: '',
-  region: '',
+  productCategory: '',
+  customerName: '',
   dateRange: []
 })
 
 const indicatorChartRef = ref(null)
 let indicatorChart = null
 
+const productOptions = ref([])
+const customerOption = ref([])
+
 const teamPerformanceList = ref([
-  { team: '鍗庝笢澶у尯', orderCount: 320, salesAmount: 2850000, shipmentRate: 90, attainment: 105 },
-  { team: '鍗庡寳澶у尯', orderCount: 280, salesAmount: 2150000, shipmentRate: 86, attainment: 92 },
-  { team: '鍗庡崡澶у尯', orderCount: 210, salesAmount: 1850000, shipmentRate: 88, attainment: 78 },
-  { team: '瑗垮崡澶у尯', orderCount: 180, salesAmount: 1500000, shipmentRate: 83, attainment: 74 }
+  { team: '鍗庝笢澶у尯', orderCount: 320, salesAmount: 2850000, shipRate: 90, attainment: 105 },
+  { team: '鍗庡寳澶у尯', orderCount: 280, salesAmount: 2150000, shipRate: 86, attainment: 92 },
+  { team: '鍗庡崡澶у尯', orderCount: 210, salesAmount: 1850000, shipRate: 88, attainment: 78 },
+  { team: '瑗垮崡澶у尯', orderCount: 180, salesAmount: 1500000, shipRate: 83, attainment: 74 }
 ])
+
+// 杞崲浜у搧鏍戞暟鎹紝灏� id 鏀逛负 value
+function convertIdToValue(data) {
+  return data.map((item) => {
+    const { id, children, ...rest } = item
+    const newItem = {
+      ...rest,
+      value: id, // 灏� id 鏀逛负 value
+    }
+    if (children && children.length > 0) {
+      newItem.children = convertIdToValue(children)
+    }
+    return newItem
+  })
+}
+
+// 鑾峰彇浜у搧鏍戞暟鎹�
+const getProductOptions = () => {
+  return productTreeList().then((res) => {
+    productOptions.value = convertIdToValue(res)
+  }).catch((error) => {
+    console.error('鑾峰彇浜у搧鏍戝け璐�:', error)
+    ElMessage.error('鑾峰彇浜у搧绫诲埆澶辫触')
+  })
+}
+
+// 鑾峰彇瀹㈡埛鍒楄〃
+const getCustomerList = () => {
+  return customerList().then((res) => {
+    customerOption.value = res || []
+  }).catch((error) => {
+    console.error('鑾峰彇瀹㈡埛鍒楄〃澶辫触:', error)
+    ElMessage.error('鑾峰彇瀹㈡埛鍒楄〃澶辫触')
+  })
+}
+
+// 鏍规嵁 id 鏌ユ壘浜у搧绫诲埆鍚嶇О
+const findNodeLabelById = (nodes, id) => {
+  if (!id) return null
+  for (let i = 0; i < nodes.length; i++) {
+    if (nodes[i].value === id) {
+      return nodes[i].label
+    }
+    if (nodes[i].children && nodes[i].children.length > 0) {
+      const found = findNodeLabelById(nodes[i].children, id)
+      if (found) return found
+    }
+  }
+  return null
+}
+
+// 鑾峰彇澶撮儴缁熻鏁版嵁
+const fetchTotalStatistics = async () => {
+  try {
+    loading.value = true
+    const params = {}
+    if (indicatorFilter.customerName) {
+      params.customerName = indicatorFilter.customerName
+    }
+    if (indicatorFilter.productCategory) {
+      // 鏍规嵁 id 鏌ユ壘浜у搧绫诲埆鍚嶇О
+      const categoryName = findNodeLabelById(productOptions.value, indicatorFilter.productCategory)
+      if (categoryName) {
+        params.productCategory = categoryName
+      }
+    }
+    if (indicatorFilter.dateRange && indicatorFilter.dateRange.length === 2) {
+      params.entryDateStart = indicatorFilter.dateRange[0]
+      params.entryDateEnd = indicatorFilter.dateRange[1]
+    }
+    const res = await getTotalStatistics(params)
+    if (res && res.data) {
+      indicatorKpis.orderCount = res.data.total || 0
+      indicatorKpis.salesAmount = res.data.contractAmountTotal || 0
+      // 鍙戣揣鐜囧鏋滄帴鍙f病鏈夎繑鍥烇紝淇濇寔鍘熷�兼垨璁句负0
+      // indicatorKpis.shipRate = res.data.shipRate || 0
+    }
+  } catch (error) {
+    console.error('鑾峰彇澶撮儴缁熻澶辫触:', error)
+    ElMessage.error('鑾峰彇缁熻鏁版嵁澶辫触')
+  } finally {
+    loading.value = false
+  }
+}
+
+// 鑾峰彇鏌辩姸鍥炬暟鎹�
+const fetchStatisticsTable = async () => {
+  try {
+    loading.value = true
+    const params = {}
+    if (indicatorFilter.customerName) {
+      params.customerName = indicatorFilter.customerName
+    }
+    if (indicatorFilter.productCategory) {
+      // 鏍规嵁 id 鏌ユ壘浜у搧绫诲埆鍚嶇О
+      const categoryName = findNodeLabelById(productOptions.value, indicatorFilter.productCategory)
+      if (categoryName) {
+        params.productCategory = categoryName
+      }
+    }
+    if (indicatorFilter.dateRange && indicatorFilter.dateRange.length === 2) {
+      params.entryDateStart = indicatorFilter.dateRange[0]
+      params.entryDateEnd = indicatorFilter.dateRange[1]
+    }
+    const res = await getStatisticsTable(params)
+    if (res && res.data) {
+      updateChart(res.data)
+    }
+  } catch (error) {
+    console.error('鑾峰彇鍥捐〃鏁版嵁澶辫触:', error)
+    ElMessage.error('鑾峰彇鍥捐〃鏁版嵁澶辫触')
+  } finally {
+    loading.value = false
+  }
+}
+
+// 鏇存柊鍥捐〃
+const updateChart = (chartData) => {
+  if (!indicatorChartRef.value) return
+  if (indicatorChart) indicatorChart.dispose()
+  indicatorChart = echarts.init(indicatorChartRef.value)
+  
+  // 鏍规嵁鎺ュ彛杩斿洖鐨勬暟鎹粨鏋勬洿鏂板浘琛�
+  // 鎺ュ彛杩斿洖: dateList, orderCountList, salesAmountList
+  const option = {
+    title: { text: '澶氱淮搴﹂攢鍞寚鏍囪秼鍔�', left: 'center' },
+    tooltip: { trigger: 'axis' },
+    legend: { data: ['璁㈠崟鏁�', '閿�鍞'], top: 30 },
+    grid: { left: '3%', right: '8%', bottom: '3%', containLabel: true },
+    xAxis: { 
+      type: 'category', 
+      data: chartData.dateList || [] 
+    },
+    yAxis: [
+      { type: 'value', name: '閲戦', position: 'left', axisLabel: { formatter: '{value}' } },
+      { 
+        type: 'value', 
+        name: '鏁伴噺', 
+        position: 'right', 
+        minInterval: 1,
+        axisLabel: { 
+          formatter: (value) => {
+            const intValue = Math.round(value)
+            return intValue.toString()
+          }
+        }
+      }
+    ],
+    series: [
+      { 
+        name: '璁㈠崟鏁�', 
+        type: 'line', 
+        yAxisIndex: 1,
+        data: chartData.orderCountList || [], 
+        itemStyle: { color: '#409eff' } 
+      },
+      { 
+        name: '閿�鍞', 
+        type: 'bar', 
+        yAxisIndex: 0,
+        data: chartData.salesAmountList || [], 
+        itemStyle: { color: '#67c23a' } 
+      }
+    ]
+  }
+  indicatorChart.setOption(option)
+}
 
 const initIndicatorChart = () => {
   if (!indicatorChartRef.value) return
@@ -141,75 +298,73 @@
   const option = {
     title: { text: '澶氱淮搴﹂攢鍞寚鏍囪秼鍔�', left: 'center' },
     tooltip: { trigger: 'axis' },
-    legend: { data: ['璁㈠崟鏁�', '閿�鍞', '鍙戣揣鐜�'], top: 30 },
-    grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
-    xAxis: { type: 'category', data: ['2024-12', '2025-01', '2025-02', '2025-03', '2025-04', '2025-05'] },
+    legend: { data: ['璁㈠崟鏁�', '閿�鍞'], top: 30 },
+    grid: { left: '3%', right: '8%', bottom: '3%', containLabel: true },
+    xAxis: { type: 'category', data: [] },
     yAxis: [
-      { type: 'value', name: '鏁伴噺/閲戦', axisLabel: { formatter: '{value}' } },
-      { type: 'value', name: '姣斾緥(%)', min: 0, max: 100, axisLabel: { formatter: '{value}%' } }
+      { type: 'value', name: '閲戦', position: 'left', axisLabel: { formatter: '{value}' } },
+      { 
+        type: 'value', 
+        name: '鏁伴噺', 
+        position: 'right', 
+        minInterval: 1,
+        axisLabel: { 
+          formatter: (value) => {
+            const intValue = Math.round(value)
+            return intValue.toString()
+          }
+        }
+      }
     ],
     series: [
-      { name: '璁㈠崟鏁�', type: 'bar', data: [180, 220, 210, 260, 205, 225], itemStyle: { color: '#409eff' } },
-      { name: '閿�鍞', type: 'bar', data: [820, 950, 910, 1080, 980, 1020], itemStyle: { color: '#67c23a' } },
-      { name: '鍙戣揣鐜�', type: 'line', yAxisIndex: 1, data: [86, 89, 88, 91, 87, 90], itemStyle: { color: '#e6a23c' } }
+      { name: '璁㈠崟鏁�', type: 'line', yAxisIndex: 1, data: [], itemStyle: { color: '#409eff' } },
+      { name: '閿�鍞', type: 'bar', yAxisIndex: 0, data: [], itemStyle: { color: '#67c23a' } }
     ]
   }
   indicatorChart.setOption(option)
 }
 
-const applyIndicatorFilter = () => {
-  const random = (base, delta) => {
-    const v = base + Math.round((Math.random() - 0.5) * delta)
-    return v < 0 ? 0 : v
-  }
-  indicatorKpis.orderCount = random(1280, 120)
-  indicatorKpis.salesAmount = random(9650000, 350000)
-  indicatorKpis.shipmentRate = (85 + Math.random() * 10).toFixed(1) * 1
-  setTimeout(() => initIndicatorChart(), 200)
+const applyIndicatorFilter = async () => {
+  await Promise.all([
+    fetchTotalStatistics(),
+    fetchStatisticsTable()
+  ])
 }
 
 const resetIndicatorFilter = () => {
-  indicatorFilter.product = ''
-  indicatorFilter.customer = ''
-  indicatorFilter.region = ''
+  indicatorFilter.productCategory = ''
+  indicatorFilter.customerName = ''
   indicatorFilter.dateRange = []
   applyIndicatorFilter()
 }
 
-const exportIndicatorTable = () => {
-  const header = ['閿�鍞洟闃�', '璁㈠崟鏁�', '閿�鍞', '鍙戣揣鐜�(%)', '鐩爣杈炬垚鐜�(%)']
-  const rows = teamPerformanceList.value.map(r => [
-    r.team,
-    r.orderCount,
-    r.salesAmount,
-    r.shipmentRate,
-    r.attainment
-  ])
-  const csv = [header, ...rows].map(r => r.join(',')).join('\n')
-  const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
-  const url = URL.createObjectURL(blob)
-  const link = document.createElement('a')
-  link.href = url
-  link.download = '鎸囨爣缁熻-鍥㈤槦涓氱哗.csv'
-  document.body.appendChild(link)
-  link.click()
-  document.body.removeChild(link)
-  URL.revokeObjectURL(url)
-}
-
-const exportIndicatorChart = () => {
-  if (!indicatorChart) return
-  const url = indicatorChart.getDataURL({ type: 'png', pixelRatio: 2, backgroundColor: '#fff' })
-  const link = document.createElement('a')
-  link.href = url
-  link.download = '鎸囨爣缁熻-鍥捐〃.png'
-  document.body.appendChild(link)
-  link.click()
-  document.body.removeChild(link)
+// 绐楀彛澶у皬鍙樺寲鏃惰皟鏁村浘琛ㄥぇ灏�
+const handleResize = () => {
+  if (indicatorChart) {
+    indicatorChart.resize()
+  }
 }
 
 onMounted(() => {
-  nextTick(() => initIndicatorChart())
+  nextTick(() => {
+    initIndicatorChart()
+    getProductOptions()
+    getCustomerList()
+    fetchTotalStatistics()
+    fetchStatisticsTable()
+  })
+  // 鐩戝惉绐楀彛澶у皬鍙樺寲
+  window.addEventListener('resize', handleResize)
+})
+
+onUnmounted(() => {
+  // 绉婚櫎绐楀彛澶у皬鍙樺寲鐩戝惉鍣�
+  window.removeEventListener('resize', handleResize)
+  // 閿�姣佸浘琛ㄥ疄渚�
+  if (indicatorChart) {
+    indicatorChart.dispose()
+    indicatorChart = null
+  }
 })
 </script>
 
@@ -225,7 +380,20 @@
 .stat-content { flex: 1; }
 .stat-value { font-size: 28px; font-weight: bold; color: #303133; margin-bottom: 4px; }
 .stat-label { font-size: 14px; color: #909399; }
-.chart-container { margin: 20px 0; padding: 20px; background: #fff; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); }
+.chart-container { 
+  margin: 20px 0; 
+  padding: 20px; 
+  background: #fff; 
+  border-radius: 8px; 
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  width: 100%;
+  overflow: hidden;
+}
+.chart-wrapper {
+  width: 100%;
+  height: 360px;
+  min-width: 0;
+}
 </style>
 
 

--
Gitblit v1.9.3