| | |
| | | /> |
| | | </el-card> |
| | | </el-tab-pane> |
| | | |
| | | <!-- 指标统计(多维度销售分析) --> |
| | | <el-tab-pane label="指标统计" name="indicatorStats"> |
| | | <el-card class="box-card"> |
| | | <!-- KPI 汇总 --> |
| | | <el-row :gutter="20" class="stats-row"> |
| | | <el-col :span="6"> |
| | | <div class="stat-card"> |
| | | <div class="stat-icon" style="background: #ecf5ff;"> |
| | | <el-icon :size="30" color="#409eff"><Document /></el-icon> |
| | | </div> |
| | | <div class="stat-content"> |
| | | <div class="stat-value">{{ indicatorKpis.orderCount.toLocaleString() }}</div> |
| | | <div class="stat-label">订单数量</div> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <div class="stat-card"> |
| | | <div class="stat-icon" style="background: #f0f9ff;"> |
| | | <el-icon :size="30" color="#67c23a"><Tickets /></el-icon> |
| | | </div> |
| | | <div class="stat-content"> |
| | | <div class="stat-value">¥{{ indicatorKpis.salesAmount.toLocaleString() }}</div> |
| | | <div class="stat-label">销售额</div> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <div class="stat-card"> |
| | | <div class="stat-icon" style="background: #fef0f0;"> |
| | | <el-icon :size="30" color="#e6a23c"><Van /></el-icon> |
| | | </div> |
| | | <div class="stat-content"> |
| | | <div class="stat-value">{{ indicatorKpis.shipmentRate }}%</div> |
| | | <div class="stat-label">发货率</div> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <div class="stat-card"> |
| | | <div class="stat-icon" style="background: #f4f4f5;"> |
| | | <el-icon :size="30" color="#f56c6c"><Wallet /></el-icon> |
| | | </div> |
| | | <div class="stat-content"> |
| | | <div class="stat-value">{{ indicatorKpis.collectionRate }}%</div> |
| | | <div class="stat-label">回款率</div> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <!-- 维度筛选 --> |
| | | <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-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> |
| | | </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-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> |
| | | |
| | | <!-- 业绩统计(团队维度,无个人姓名) --> |
| | | <el-table :data="teamPerformanceList" border stripe style="margin-top: 20px;"> |
| | | <el-table-column prop="team" label="销售团队" width="140" /> |
| | | <el-table-column prop="orderCount" label="订单数" width="100" /> |
| | | <el-table-column prop="salesAmount" label="销售额" width="140"> |
| | | <template #default="scope">¥{{ scope.row.salesAmount.toLocaleString() }}</template> |
| | | </el-table-column> |
| | | <el-table-column prop="shipmentRate" label="发货率" width="100"> |
| | | <template #default="scope">{{ scope.row.shipmentRate }}%</template> |
| | | </el-table-column> |
| | | <el-table-column prop="collectionRate" label="回款率" width="100"> |
| | | <template #default="scope">{{ scope.row.collectionRate }}%</template> |
| | | </el-table-column> |
| | | <el-table-column prop="attainment" label="目标达成率" width="120"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.attainment >= 100 ? 'success' : scope.row.attainment >= 80 ? 'warning' : 'danger'"> |
| | | {{ scope.row.attainment }}% |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | |
| | | <!-- 价格策略对话框 --> |
| | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="策略类型" prop="strategyType"> |
| | | <el-select v-model="priceStrategyForm.strategyType" placeholder="请选择策略类型" style="width: 100%;"> |
| | | <el-select v-model="priceStrategyForm.strategyType" placeholder="请选择策略类型" style="width: 100%;" :disabled="priceStrategyDialogMode === 'view'"> |
| | | <el-option label="专属价格" value="专属价格"></el-option> |
| | | <el-option label="阶梯报价" value="阶梯报价"></el-option> |
| | | <el-option label="促销折扣" value="促销折扣"></el-option> |
| | |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客户名称" prop="customerName"> |
| | | <el-select v-model="priceStrategyForm.customerName" placeholder="请选择客户" style="width: 100%;"> |
| | | <el-select v-model="priceStrategyForm.customerName" placeholder="请选择客户" style="width: 100%;" :disabled="priceStrategyDialogMode === 'view'"> |
| | | <el-option label="华东建材集团" value="华东建材集团"></el-option> |
| | | <el-option label="长江混凝土公司" value="长江混凝土公司"></el-option> |
| | | <el-option label="浦江水泥制品厂" value="浦江水泥制品厂"></el-option> |
| | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="产品名称" prop="productName"> |
| | | <el-select v-model="priceStrategyForm.productName" placeholder="请选择产品" style="width: 100%;"> |
| | | <el-select v-model="priceStrategyForm.productName" placeholder="请选择产品" style="width: 100%;" :disabled="priceStrategyDialogMode === 'view'"> |
| | | <el-option label="P.O 42.5普通硅酸盐水泥" value="P.O 42.5普通硅酸盐水泥"></el-option> |
| | | <el-option label="P.S 32.5矿渣硅酸盐水泥" value="P.S 32.5矿渣硅酸盐水泥"></el-option> |
| | | <el-option label="P.C 32.5复合硅酸盐水泥" value="P.C 32.5复合硅酸盐水泥"></el-option> |
| | |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="规格型号" prop="specification"> |
| | | <el-input v-model="priceStrategyForm.specification" placeholder="请输入规格型号" /> |
| | | <el-input v-model="priceStrategyForm.specification" placeholder="请输入规格型号" :disabled="priceStrategyDialogMode === 'view'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="基础价格(元/吨)" prop="basePrice"> |
| | | <el-input-number v-model="priceStrategyForm.basePrice" :min="0" :precision="2" style="width: 100%;" /> |
| | | <el-input-number v-model="priceStrategyForm.basePrice" :min="0" :precision="2" style="width: 100%;" :disabled="priceStrategyDialogMode === 'view'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="策略价格" prop="strategyPrice"> |
| | | <el-input v-model="priceStrategyForm.strategyPrice" placeholder="如: ¥350/吨 或 9折" /> |
| | | <el-input v-model="priceStrategyForm.strategyPrice" placeholder="如: ¥350/吨 或 9折" :disabled="priceStrategyDialogMode === 'view'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | placeholder="选择生效日期" |
| | | style="width: 100%" |
| | | value-format="YYYY-MM-DD" |
| | | :disabled="priceStrategyDialogMode === 'view'" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | placeholder="选择失效日期" |
| | | style="width: 100%" |
| | | value-format="YYYY-MM-DD" |
| | | :disabled="priceStrategyDialogMode === 'view'" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="策略说明" prop="description"> |
| | | <el-input type="textarea" v-model="priceStrategyForm.description" :rows="3" placeholder="请输入策略说明" /> |
| | | <el-input type="textarea" v-model="priceStrategyForm.description" :rows="3" placeholder="请输入策略说明" :disabled="priceStrategyDialogMode === 'view'" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button @click="priceStrategyDialogVisible = false">取消</el-button> |
| | | <el-button type="primary" @click="handleSavePriceStrategy">保存</el-button> |
| | | <el-button @click="priceStrategyDialogVisible = false">{{ priceStrategyDialogMode === 'view' ? '关闭' : '取消' }}</el-button> |
| | | <el-button v-if="priceStrategyDialogMode !== 'view'" type="primary" @click="handleSavePriceStrategy">保存</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | |
| | | |
| | | const priceStrategyDialogVisible = ref(false) |
| | | const priceStrategyDialogTitle = ref('新增价格策略') |
| | | const priceStrategyDialogMode = ref('add') // add | edit | view |
| | | const priceStrategyForm = reactive({ |
| | | strategyType: '', |
| | | customerName: '', |
| | |
| | | const handleAddPriceStrategy = () => { |
| | | priceStrategyDialogTitle.value = '新增价格策略' |
| | | resetPriceStrategyForm() |
| | | priceStrategyDialogMode.value = 'add' |
| | | priceStrategyDialogVisible.value = true |
| | | } |
| | | |
| | | const handleViewPriceStrategy = (row) => { |
| | | ElMessage.info('查看策略详情: ' + row.strategyNo) |
| | | priceStrategyDialogTitle.value = '查看价格策略' |
| | | Object.assign(priceStrategyForm, row) |
| | | priceStrategyDialogMode.value = 'view' |
| | | priceStrategyDialogVisible.value = true |
| | | } |
| | | |
| | | const handleEditPriceStrategy = (row) => { |
| | | priceStrategyDialogTitle.value = '编辑价格策略' |
| | | Object.assign(priceStrategyForm, row) |
| | | priceStrategyDialogMode.value = 'edit' |
| | | priceStrategyDialogVisible.value = true |
| | | } |
| | | |
| | |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | // 本地假数据删除:从列表中移除并更新总数 |
| | | const index = priceStrategyList.value.findIndex(item => item.id === row.id) |
| | | if (index > -1) { |
| | | priceStrategyList.value.splice(index, 1) |
| | | if (pricePagination.total > 0) pricePagination.total -= 1 |
| | | } |
| | | ElMessage.success('删除成功') |
| | | }) |
| | | } |
| | |
| | | profitPagination.pageSize = val.limit |
| | | } |
| | | |
| | | // ========== 指标统计(多维度分析) ========== |
| | | const indicatorKpis = reactive({ |
| | | orderCount: 1280, |
| | | salesAmount: 9650000, |
| | | shipmentRate: 89.2, |
| | | collectionRate: 76.4 |
| | | }) |
| | | |
| | | const indicatorFilter = reactive({ |
| | | product: '', |
| | | customer: '', |
| | | region: '', |
| | | dateRange: [] |
| | | }) |
| | | |
| | | const indicatorChartRef = ref(null) |
| | | let indicatorChart = null |
| | | |
| | | const teamPerformanceList = ref([ |
| | | { team: '华东团队A', orderCount: 320, salesAmount: 2850000, shipmentRate: 90, collectionRate: 80, attainment: 105 }, |
| | | { team: '华北团队B', orderCount: 280, salesAmount: 2150000, shipmentRate: 86, collectionRate: 73, attainment: 92 }, |
| | | { team: '华南团队C', orderCount: 210, salesAmount: 1850000, shipmentRate: 88, collectionRate: 70, attainment: 78 }, |
| | | { team: '西南团队D', orderCount: 180, salesAmount: 1500000, shipmentRate: 83, collectionRate: 68, attainment: 74 } |
| | | ]) |
| | | |
| | | 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: '4%', bottom: '3%', containLabel: true }, |
| | | xAxis: { type: 'category', data: ['2024-12', '2025-01', '2025-02', '2025-03', '2025-04', '2025-05'] }, |
| | | yAxis: [ |
| | | { type: 'value', name: '数量/金额', axisLabel: { formatter: '{value}' } }, |
| | | { type: 'value', name: '比例(%)', min: 0, max: 100, axisLabel: { formatter: '{value}%' } } |
| | | ], |
| | | 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: [72, 76, 74, 79, 75, 78], itemStyle: { color: '#f56c6c' } } |
| | | ] |
| | | } |
| | | indicatorChart.setOption(option) |
| | | } |
| | | |
| | | const applyIndicatorFilter = () => { |
| | | // 使用假数据模拟查询,刷新KPI和图表 |
| | | // 仅演示:随机微调以体现筛选效果 |
| | | 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 |
| | | indicatorKpis.collectionRate = (70 + Math.random() * 12).toFixed(1) * 1 |
| | | |
| | | setTimeout(() => initIndicatorChart(), 200) |
| | | } |
| | | |
| | | const resetIndicatorFilter = () => { |
| | | indicatorFilter.product = '' |
| | | indicatorFilter.customer = '' |
| | | indicatorFilter.region = '' |
| | | indicatorFilter.dateRange = [] |
| | | applyIndicatorFilter() |
| | | } |
| | | |
| | | const exportIndicatorTable = () => { |
| | | // 导出团队业绩为CSV(假导出) |
| | | const header = ['销售团队', '订单数', '销售额', '发货率(%)', '回款率(%)', '目标达成率(%)'] |
| | | const rows = teamPerformanceList.value.map(r => [ |
| | | r.team, |
| | | r.orderCount, |
| | | r.salesAmount, |
| | | r.shipmentRate, |
| | | r.collectionRate, |
| | | 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) |
| | | } |
| | | |
| | | // 生命周期 |
| | | onMounted(() => { |
| | | // 组件挂载后不立即初始化图表,等待用户切换到对应标签页 |
| | |
| | | initPriceChart() |
| | | } else if (newVal === 'profitAnalysis') { |
| | | initProfitChart() |
| | | } else if (newVal === 'indicatorStats') { |
| | | initIndicatorChart() |
| | | } |
| | | }) |
| | | }) |