CRM_26-08-31#story#9355,服务商KPI

This commit is contained in:
zlf
2026-08-28 14:15:51 +08:00
parent 04befddf8a
commit 7604c8c224
2 changed files with 345 additions and 2 deletions
+10 -2
View File
@@ -1,12 +1,12 @@
const kpiRouter = [ const kpiRouter = [
{ /* {
path: '/kpiIndex', path: '/kpiIndex',
name: 'kpiIndex', name: 'kpiIndex',
component: () => import('@/views/kpi/kpiIndex'), component: () => import('@/views/kpi/kpiIndex'),
meta:{ meta:{
title: 'kpi首页', title: 'kpi首页',
} }
}, },*/
{ {
path: '/kpiCaseNew', path: '/kpiCaseNew',
name: 'kpiCaseNew', name: 'kpiCaseNew',
@@ -15,5 +15,13 @@ const kpiRouter = [
title: 'kpi服务商案件&车辆情况', title: 'kpi服务商案件&车辆情况',
} }
}, },
{
path: '/kpiIndex',
name: 'kpiAssess',
component: () => import('@/views/kpi/kpiAssess'),
meta:{
title: '考核指标明细',
}
},
] ]
export default kpiRouter export default kpiRouter
+335
View File
@@ -0,0 +1,335 @@
<template>
<div class="wrap">
<div v-loading="loadingData" class="contentWrap">
<!-- 统计年月 -->
<div class="monthBar">
<span class="monthTag">统计年月<b>{{ currentMonth }}</b></span>
</div>
<!-- 总览卡片 -->
<div class="cardRow">
<div class="card">
<div class="iconWrap trophy"><i class="el-icon-trophy"></i></div>
<div class="cardInfo">
<div class="num">{{ monthData && monthData.score != null ? Number(monthData.score).toFixed(1) : '-' }}</div>
<div class="label">本服务商总分</div>
</div>
</div>
<div class="card">
<div class="iconWrap truck"><i class="el-icon-document-copy"></i></div>
<div class="cardInfo">
<div class="num">{{ monthData && monthData.receiveOrderCount != null ? monthData.receiveOrderCount : '-' }}</div>
<div class="label">承接案件量</div>
<div class="subLabel">{{ currentMonth }}</div>
</div>
</div>
</div>
<!-- 考核指标明细 -->
<div class="tableCard">
<div class="tableTitle"><i class="el-icon-tickets"></i> 考核指标明细</div>
<div class="tableBody">
<div class="tr th">
<div class="td name">考核指标</div>
<div class="td value">指标值</div>
<div class="td score">得分</div>
</div>
<div class="tr" v-for="(item, index) in indicatorList" :key="index">
<div class="td name">{{ item.name }}</div>
<div class="td value">{{ item.value }}</div>
<div class="td score">{{ item.score }}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import dayjs from "dayjs";
import { getStatisticsKpi } from "@/api/kpi.js"
export default {
name: "kpiAssess",
data() {
return {
supplierId: '',
currentMonth: '',
startTime: '',
endTime: '',
loadingData: false,
monthData: null,
// 考核指标配置:name 指标名称,valueKey 指标值字段,scoreKey 得分字段,type 格式化类型
indicatorConfig: [
{ name: '拒单超时率', valueKey: 'refuseOrderRate', scoreKey: 'refuseOrderScore', type: 'percent' },
{ name: '平均接单时效', valueKey: 'receiving', scoreKey: 'orderAgeingScore', type: 'minute' },
{ name: '平均到达时效', valueKey: 'arriving', scoreKey: 'arriveAgeingScore', type: 'minute' },
{ name: '上游聚合成功率', valueKey: 'wholeJuheSuccessRate', scoreKey: 'polymerizationSuccessScore', type: 'percent' },
{ name: 'APP正确使用率', valueKey: 'appRate', scoreKey: 'appUseScore', type: 'percent' },
{ name: '催促率', valueKey: 'urgeRate', scoreKey: 'urgeScore', type: 'percent' },
{ name: '投诉率', valueKey: 'complainOrderRate', scoreKey: 'complainScore', type: 'percent' },
{ name: '日间在线率', valueKey: 'daytimeOnlineRate', scoreKey: 'daytimeOnlineScore', type: 'percent' },
{ name: '自解紧急率', valueKey: 'zijieRate', scoreKey: 'zijieScore', type: 'percent' },
],
}
},
computed: {
indicatorList() {
return this.indicatorConfig.map(cfg => {
const data = this.monthData || {};
return {
name: cfg.name,
value: this.formatValue(data[cfg.valueKey], cfg.type),
score: this.formatScore(data[cfg.scoreKey]),
}
})
},
},
created() {
const urlParams = new URLSearchParams(window.location.search);
this.supplierId = urlParams?.get('supplierId') || ''
},
async mounted() {
await this.initDate();
await this.getData();
},
methods: {
// 初始化获取当月日期(与 kpiIndex 保持一致:1号默认取上月)
initDate() {
const today = dayjs();
let targetMonth = today;
if (today.date() === 1) {
targetMonth = today.subtract(1, 'month');
}
this.currentMonth = targetMonth.format('YYYY-MM');
this.startTime = targetMonth.startOf('month').format('YYYY-MM-DD HH:mm:ss');
let endTime;
if (targetMonth.isSame(today, 'month')) {
endTime = today.subtract(1, 'day').endOf('day');
} else {
endTime = targetMonth.endOf('month');
}
this.endTime = endTime.format('YYYY-MM-DD HH:mm:ss');
},
async getData() {
try {
this.loadingData = true
this.monthData = null
let res = await getStatisticsKpi({
startTime: this.startTime,
endTime: this.endTime,
statisticsType: 1,
supplierId: this.supplierId,
})
let list = res.data || []
// 取统计年月对应的那一条月统计数据
this.monthData = list.find(item => dayjs(item.statisticsDate).format('YYYY-MM') === this.currentMonth) || list[0] || null
} finally {
this.loadingData = false
}
},
formatValue(value, type) {
if (value == null || value === '') return '-';
if (type === 'percent') {
return (Number(value) * 100).toFixed(2) + '%';
}
if (type === 'minute') {
return Number(value).toFixed(1) + ' 分钟';
}
return value;
},
formatScore(value) {
if (value == null || value === '') return '-';
return Number(value).toFixed(2);
},
},
}
</script>
<style scoped lang="scss">
@import "@/styles/mixin.scss";
@import "@/styles/common.scss";
.wrap {
@include wh(100%, 100%);
}
.contentWrap {
@include wh(100%, 100%);
overflow-y: auto;
background: #EFF2F8;
box-sizing: border-box;
padding: 12px;
}
/* 统计年月 */
.monthBar {
display: flex;
align-items: center;
margin-bottom: 12px;
.monthTag {
display: inline-block;
background: #FFFFFF;
border-radius: 6px;
padding: 8px 14px;
font-size: 14px;
color: #4E5969;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
b {
color: #1D2129;
}
}
}
/* 总览卡片 */
.cardRow {
display: flex;
gap: 12px;
margin-bottom: 12px;
.card {
flex: 1;
background: #FFFFFF;
border-radius: 8px;
padding: 20px;
display: flex;
align-items: center;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
.iconWrap {
@include wh(48px, 48px);
border-radius: 8px;
@include flexCenter;
font-size: 24px;
margin-right: 14px;
flex-shrink: 0;
&.trophy {
background: #E8F3FF;
color: #165DFF;
}
&.truck {
background: #E8FFFB;
color: #0FC6C2;
}
}
.cardInfo {
.num {
font-size: 26px;
font-weight: bold;
color: #1D2129;
line-height: 1.2;
}
.label {
font-size: 13px;
color: #86909C;
margin-top: 4px;
}
.subLabel {
font-size: 12px;
color: #C9CDD4;
margin-top: 2px;
}
}
}
}
/* 明细表格 */
.tableCard {
background: #FFFFFF;
border-radius: 8px;
padding: 16px 20px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
.tableTitle {
font-size: 15px;
font-weight: bold;
color: #1D2129;
margin-bottom: 12px;
i {
color: #165DFF;
margin-right: 4px;
}
}
.tableBody {
.tr {
display: flex;
align-items: center;
padding: 12px 8px;
border-bottom: 1px solid #F2F3F5;
font-size: 13px;
&:last-child {
border-bottom: none;
}
&.th {
color: #86909C;
font-weight: 500;
padding: 8px;
}
.td {
&.name {
flex: 1;
color: #1D2129;
font-weight: 500;
}
&.value {
width: 30%;
text-align: right;
color: #4E5969;
}
&.score {
width: 100px;
text-align: right;
color: #165DFF;
font-weight: bold;
}
}
&.th .td {
&.name, &.value, &.score {
color: #86909C;
font-weight: 500;
}
}
}
}
}
/* 移动端适配 */
@media screen and (max-width: 768px) {
.contentWrap {
padding: 10px;
}
.cardRow {
flex-direction: column;
}
.tableCard {
padding: 12px;
.tableBody .tr .td {
&.value {
width: 28%;
}
&.score {
width: 70px;
}
}
}
}
</style>