From 9bb75a9bd734c24d1913b37ab6ce7693359b139c Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 15 一月 2026 13:26:15 +0800
Subject: [PATCH] 进销存-升级 1.供应商往来展示联调修改 2.指标统计页面展示联调修改 3.用户管理新增修改时字段修改 4.不需要多用户登录
---
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