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