| | |
| | | /> |
| | | </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() |
| | | } |
| | | }) |
| | | }) |