CRM_26-08-31#story#9355,服务商KPI
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
Reference in New Issue
Block a user