From 7c2cdcbc7f5585b96fba76a07b0e4417a09c4d7e Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 02 六月 2026 11:06:10 +0800
Subject: [PATCH] 新疆马铃薯 1.过程检验下载添加3个模版
---
src/views/salesManagement/indicatorStats/index.vue | 715 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 715 insertions(+), 0 deletions(-)
diff --git a/src/views/salesManagement/indicatorStats/index.vue b/src/views/salesManagement/indicatorStats/index.vue
new file mode 100644
index 0000000..8ae15ed
--- /dev/null
+++ b/src/views/salesManagement/indicatorStats/index.vue
@@ -0,0 +1,715 @@
+<template>
+ <div class="app-container indicator-stats">
+ <!-- KPI 姹囨�� -->
+ <el-row :gutter="20" class="stats-row">
+ <el-col :xs="24" :sm="12" :md="8">
+ <div class="stat-card stat-card-blue">
+ <div class="stat-icon-wrapper">
+ <div class="stat-icon">
+ <el-icon :size="32"><Document /></el-icon>
+ </div>
+ </div>
+ <div class="stat-content">
+ <div class="stat-value">{{ indicatorKpis.orderCount.toLocaleString() }}</div>
+ <div class="stat-label">璁㈠崟鏁伴噺</div>
+ </div>
+ <div class="stat-bg-decoration"></div>
+ </div>
+ </el-col>
+ <el-col :xs="24" :sm="12" :md="8">
+ <div class="stat-card stat-card-green">
+ <div class="stat-icon-wrapper">
+ <div class="stat-icon">
+ <el-icon :size="32"><Tickets /></el-icon>
+ </div>
+ </div>
+ <div class="stat-content">
+ <div class="stat-value">楼{{ indicatorKpis.salesAmount.toLocaleString() }}</div>
+ <div class="stat-label">閿�鍞</div>
+ </div>
+ <div class="stat-bg-decoration"></div>
+ </div>
+ </el-col>
+ <el-col :xs="24" :sm="12" :md="8">
+ <div class="stat-card stat-card-orange">
+ <div class="stat-icon-wrapper">
+ <div class="stat-icon">
+ <el-icon :size="32"><Van /></el-icon>
+ </div>
+ </div>
+ <div class="stat-content">
+ <div class="stat-value">{{ indicatorKpis.shipRate }}</div>
+ <div class="stat-label">鍙戣揣鐜�</div>
+ </div>
+ <div class="stat-bg-decoration"></div>
+ </div>
+ </el-col>
+ </el-row>
+
+ <!-- 鍥捐〃鍖猴紙鍖呭惈绛涢�夋潯浠讹級 -->
+ <el-card class="chart-card" shadow="hover">
+ <template #header>
+ <div class="card-header">
+ <div class="header-left">
+ <span class="card-title">閿�鍞秼鍔垮垎鏋�</span>
+ <span class="card-subtitle">绛涢�夋潯浠朵粎褰卞搷涓嬫柟鍥捐〃鏁版嵁</span>
+ </div>
+ </div>
+ </template>
+
+ <!-- 鍥捐〃绛涢�夋潯浠� -->
+ <div class="chart-filter-section">
+ <el-row :gutter="16" class="search-row">
+ <el-col :xs="24" :sm="12" :md="6">
+ <div class="filter-item">
+ <label class="filter-label">浜у搧绫诲埆</label>
+ <el-tree-select
+ v-model="indicatorFilter.productCategory"
+ placeholder="璇烽�夋嫨浜у搧绫诲埆"
+ clearable
+ check-strictly
+ :data="productOptions"
+ :render-after-expand="false"
+ style="width: 100%"
+ />
+ </div>
+ </el-col>
+ <el-col :xs="24" :sm="12" :md="6">
+ <div class="filter-item">
+ <label class="filter-label">瀹㈡埛</label>
+ <el-select
+ v-model="indicatorFilter.customerName"
+ placeholder="璇烽�夋嫨瀹㈡埛"
+ clearable
+ filterable
+ style="width: 100%"
+ >
+ <el-option
+ v-for="item in customerOption"
+ :key="item.id"
+ :label="item.customerName"
+ :value="item.customerName"
+ />
+ </el-select>
+ </div>
+ </el-col>
+ <el-col :xs="24" :sm="12" :md="6">
+ <div class="filter-item">
+ <label class="filter-label">鏃ユ湡鑼冨洿</label>
+ <el-date-picker
+ v-model="indicatorFilter.dateRange"
+ type="monthrange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ value-format="YYYY-MM"
+ style="width: 100%"
+ />
+ </div>
+ </el-col>
+ <el-col :xs="24" :sm="12" :md="6">
+ <div class="filter-item filter-buttons">
+ <el-button type="primary" :loading="loading" @click="applyIndicatorFilter">
+ <el-icon><Search /></el-icon>
+ 鏌ヨ鍥捐〃
+ </el-button>
+ <el-button @click="resetIndicatorFilter">
+ <el-icon><Refresh /></el-icon>
+ 閲嶇疆
+ </el-button>
+ </div>
+ </el-col>
+ </el-row>
+ </div>
+
+ <!-- 鍥捐〃灞曠ず鍖� -->
+ <div class="chart-container" v-loading="loading">
+ <div ref="indicatorChartRef" class="chart-wrapper"></div>
+ </div>
+ </el-card>
+ </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'
+import { Document, Van, Tickets, Search, Refresh } 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: 0,
+ salesAmount: 0,
+ shipRate: 0
+})
+
+// 鏄惁灞曠ず閿�鍞洟闃熸槑缁嗚〃锛屾寜闇�寮�鍚�
+const showTeamPerformance = ref(false)
+const loading = ref(false)
+
+const indicatorFilter = reactive({
+ productCategory: '',
+ customerName: '',
+ dateRange: []
+})
+
+const indicatorChartRef = ref(null)
+let indicatorChart = null
+
+const productOptions = ref([])
+const customerOption = ref([])
+
+// 杞崲浜у搧鏍戞暟鎹紝灏� 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
+ if (indicatorChart) indicatorChart.dispose()
+ indicatorChart = echarts.init(indicatorChartRef.value)
+ 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: [] },
+ 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: [], itemStyle: { color: '#409eff' } },
+ { name: '閿�鍞', type: 'bar', yAxisIndex: 0, data: [], itemStyle: { color: '#67c23a' } }
+ ]
+ }
+ indicatorChart.setOption(option)
+}
+
+const applyIndicatorFilter = async () => {
+ // 绛涢�夋潯浠跺彧褰卞搷鍥捐〃鏁版嵁锛屼笉褰卞搷KPI姹囨��
+ await fetchStatisticsTable()
+}
+
+const resetIndicatorFilter = () => {
+ indicatorFilter.productCategory = ''
+ indicatorFilter.customerName = ''
+ indicatorFilter.dateRange = []
+ applyIndicatorFilter()
+}
+
+// 绐楀彛澶у皬鍙樺寲鏃惰皟鏁村浘琛ㄥぇ灏�
+const handleResize = () => {
+ if (indicatorChart) {
+ indicatorChart.resize()
+ }
+}
+
+onMounted(() => {
+ nextTick(() => {
+ initIndicatorChart()
+ getProductOptions()
+ getCustomerList()
+ fetchTotalStatistics()
+ fetchStatisticsTable()
+ })
+ // 鐩戝惉绐楀彛澶у皬鍙樺寲
+ window.addEventListener('resize', handleResize)
+})
+
+onUnmounted(() => {
+ // 绉婚櫎绐楀彛澶у皬鍙樺寲鐩戝惉鍣�
+ window.removeEventListener('resize', handleResize)
+ // 閿�姣佸浘琛ㄥ疄渚�
+ if (indicatorChart) {
+ indicatorChart.dispose()
+ indicatorChart = null
+ }
+})
+</script>
+
+<style scoped lang="scss">
+.indicator-stats {
+ padding: 20px;
+ min-height: calc(100vh - 84px);
+}
+
+.page-header {
+ margin-bottom: 24px;
+ padding: 20px 0;
+
+ .page-title {
+ font-size: 24px;
+ font-weight: 600;
+ color: #303133;
+ margin: 0 0 8px 0;
+ }
+
+ .page-desc {
+ font-size: 14px;
+ color: #909399;
+ margin: 0;
+ }
+}
+
+.stats-row {
+ margin-bottom: 24px;
+}
+
+.stat-card {
+ position: relative;
+ display: flex;
+ align-items: center;
+ padding: 24px;
+ background: #fff;
+ border-radius: 12px;
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08);
+ transition: all 0.3s ease;
+ overflow: hidden;
+
+ &:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 8px 24px 0 rgba(0, 0, 0, 0.12);
+ }
+
+ .stat-icon-wrapper {
+ margin-right: 20px;
+
+ .stat-icon {
+ width: 64px;
+ height: 64px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 12px;
+ transition: all 0.3s ease;
+ }
+ }
+
+ .stat-content {
+ flex: 1;
+ z-index: 1;
+
+ .stat-value {
+ font-size: 32px;
+ font-weight: 700;
+ color: #303133;
+ margin-bottom: 8px;
+ line-height: 1.2;
+ }
+
+ .stat-label {
+ font-size: 14px;
+ color: #909399;
+ font-weight: 500;
+ }
+ }
+
+ .stat-bg-decoration {
+ position: absolute;
+ right: -20px;
+ top: -20px;
+ width: 120px;
+ height: 120px;
+ border-radius: 50%;
+ opacity: 0.1;
+ z-index: 0;
+ }
+
+ &.stat-card-blue {
+ .stat-icon {
+ background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
+ color: #fff;
+ }
+
+ .stat-bg-decoration {
+ background: #409eff;
+ }
+ }
+
+ &.stat-card-green {
+ .stat-icon {
+ background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%);
+ color: #fff;
+ }
+
+ .stat-bg-decoration {
+ background: #67c23a;
+ }
+ }
+
+ &.stat-card-orange {
+ .stat-icon {
+ background: linear-gradient(135deg, #e6a23c 0%, #ebb563 100%);
+ color: #fff;
+ }
+
+ .stat-bg-decoration {
+ background: #e6a23c;
+ }
+ }
+}
+
+.chart-card,
+.table-card {
+ margin-bottom: 20px;
+ border-radius: 12px;
+ border: none;
+
+ :deep(.el-card__header) {
+ padding: 18px 20px;
+ border-bottom: 1px solid #ebeef5;
+ background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%);
+ }
+
+ :deep(.el-card__body) {
+ padding: 0;
+ }
+}
+
+.card-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+
+ .header-left {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ }
+
+ .card-title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #303133;
+ }
+
+ .card-subtitle {
+ font-size: 12px;
+ color: #909399;
+ font-weight: normal;
+ }
+}
+
+.chart-filter-section {
+ padding: 20px;
+ background: #fafbfc;
+ border-bottom: 1px solid #ebeef5;
+ margin-bottom: 0;
+}
+
+.search-row {
+ .filter-item {
+ margin-bottom: 0;
+
+ .filter-label {
+ display: block;
+ font-size: 13px;
+ color: #606266;
+ margin-bottom: 8px;
+ font-weight: 500;
+ }
+
+ &.filter-buttons {
+ display: flex;
+ align-items: flex-end;
+ gap: 10px;
+ padding-top: 28px;
+
+ .el-button {
+ flex: 1;
+ font-size: 14px;
+ }
+ }
+ }
+}
+
+.chart-container {
+ width: 100%;
+ overflow: hidden;
+ position: relative;
+ padding: 20px;
+ background: #fff;
+
+ .chart-wrapper {
+ width: 100%;
+ height: 420px;
+ min-width: 0;
+ }
+}
+
+.table-card {
+ :deep(.el-table) {
+ border-radius: 8px;
+ overflow: hidden;
+ }
+
+ :deep(.el-table__header-wrapper) {
+ .el-table__header {
+ th {
+ background: #f5f7fa;
+ color: #606266;
+ font-weight: 600;
+ }
+ }
+ }
+
+ :deep(.el-table__body-wrapper) {
+ .el-table__body {
+ tr:hover {
+ background-color: #f5f7fa;
+ }
+ }
+ }
+}
+
+// 鍝嶅簲寮忚璁�
+@media (max-width: 768px) {
+ .indicator-stats {
+ padding: 12px;
+ }
+
+ .stat-card {
+ padding: 20px;
+
+ .stat-content .stat-value {
+ font-size: 24px;
+ }
+
+ .stat-icon-wrapper .stat-icon {
+ width: 56px;
+ height: 56px;
+ }
+ }
+
+ .chart-filter-section {
+ padding: 16px;
+ }
+
+ .search-row {
+ .filter-item.filter-buttons {
+ padding-top: 0;
+ margin-top: 12px;
+ }
+ }
+
+ .chart-container {
+ padding: 16px;
+
+ .chart-wrapper {
+ height: 320px;
+ }
+ }
+
+ .card-header {
+ .header-left {
+ .card-title {
+ font-size: 15px;
+ }
+
+ .card-subtitle {
+ font-size: 11px;
+ }
+ }
+ }
+}
+
+@media (max-width: 576px) {
+ .page-header {
+ .page-title {
+ font-size: 20px;
+ }
+
+ .page-desc {
+ font-size: 12px;
+ }
+ }
+
+ .stat-card {
+ flex-direction: column;
+ text-align: center;
+
+ .stat-icon-wrapper {
+ margin-right: 0;
+ margin-bottom: 12px;
+ }
+ }
+}
+</style>
+
+
--
Gitblit v1.9.3