Files
supplier-dispatch-h5/src/views/kpi/kpiCaseNew.vue
2025-12-01 13:06:10 +08:00

1204 lines
38 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="wrap" :class="{ 'flexCls': active == 1 }">
<div v-if="isMobile" class="headWrap">
<div class="title">服务商案件&车辆情况</div>
<div class="titleName">{{ current }}-{{supplierName}}</div>
</div>
<div v-else class="webHeadWrap" :style="'justify-content:'+(isZd==1 ? 'space-between':'center')">
<div class="empty" v-if="isZd==1"></div>
<div class="title">服务商案件&车辆情况<span v-if="isZd==1">--中道救援</span><span v-if="isZd!=1">--{{ current }}</span></div>
<!-- 只有中道账号才显示搜索框 -->
<div class="searchWrap" v-if="isZd==1">
<el-date-picker
v-model="current"
type="month"
:clearable="false"
class="month custom-date-picker"
@change="monthChangeHandle"
placeholder="选择月">
</el-date-picker>
<el-select
v-model="supplierId"
filterable
remote
reserve-keyword
clearable
placeholder="请输入后选择"
:remote-method="remoteMethod"
@change="selectSupplierNameHandle"
:loading="selectLoading">
<el-option
v-for="item in selectOption"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</div>
</div>
<van-tabs v-model="active" sticky @click="tabClickHandle">
<van-tab v-for="(item,index) in tabArr" :key="index" :title="item.name"></van-tab>
<div v-if="isMobile && !([0,1,2].includes(active))" class="tipArrow left">{{ leftArr }}</div>
<div v-if="isMobile && !([6,7, 8].includes(active))" class="tipArrow right">>>></div>
</van-tabs>
<!--总览 页面 s -->
<div v-loading="loadingData" class="contentWrap" :class="{'webcontentWrap':!isMobile}" v-if="active===0">
<div class="reciceOrder" :class="{'webCom':!isMobile, 'ownerHeight': isMobile}" style="margin-bottom: 10px;">
<div class="title" :class="{'webTitle':!isMobile}">业务情况</div>
<div><span>总案件量: {{totalCount}}</span></div>
<div id="pieChart" style="width: 100%;height:120px"></div>
</div>
<div :class="{'reciceOrder':true,'webCom':!isMobile}">
<div :class="{'title':true,'webTitle':!isMobile}">在线车辆</div>
<div class="reciceOrderIWrap">
<div class="left common">
<div class="num">{{ formatCurrency(indexData?.onlineVehicleCount) }}</div>
<div class="itemTitle">日均在线车辆数</div>
</div>
<div class="center common">
<div class="num" :class="{'numGreen': Number(indexData?.onlineRate) >= 60, 'numRed': Number(indexData?.onlineRate) < 60 }">{{ indexData && indexData.onlineRate }}%</div>
<div class="itemTitle" style="cursor: pointer">在线率</div>
</div>
<div class="right common">
<div class="num" :class="{'numGreen': Number(indexData?.averageOnlineDuration) >= 480, 'numRed': Number(indexData?.averageOnlineDuration) < 480 }">{{ toOnlineHours(indexData?.averageOnlineDuration) }}</div>
<div class="itemTitle" style="cursor: pointer">日均在线时长</div>
</div>
</div>
</div>
<div :class="{'reciceOrder':true,'webCom':!isMobile}">
<div :class="{'title':true,'webTitle':!isMobile}">案件承接指标</div>
<div class="reciceOrderIWrap">
<div class="left common">
<div class="num" :class="{'numRed': Number(indexData?.refusalRate) >= 5, 'numGreen': Number(indexData?.refusalRate) < 5 }">{{ indexData && indexData.refusalRate }}%</div>
<div class="itemTitle">拒单率</div>
</div>
<div class="center common">
<div class="num" :class="{'numRed': Number(indexData?.timeoutRate) >= 5, 'numGreen': Number(indexData?.timeoutRate) < 5 }">{{ indexData && indexData.timeoutRate }}%</div>
<div class="itemTitle" style="cursor: pointer">超时率</div>
</div>
<div class="right common">
<div class="num">{{ indexData && indexData.cancelRate }}%</div>
<div class="itemTitle" style="cursor: pointer">取消率</div>
</div>
</div>
</div>
<div v-if="isMobile" style="padding: 20px"></div>
</div>
<!--总览 页面 e -->
<!--月总 页面 s -->
<div v-loading="loadingData" class="contentWrap monthTotal" style="display: flex;flex-direction: column;flex: 1" v-if="active===1">
<div :class="{'chartWrapWeb':!isMobile,'comChart':true}">
<div style="width: 100%">
<div id="chartToMonth" style="width: 100%;height:300px"></div>
</div>
</div>
<div class="webTab">
<noFit-table v-if="isMobile" :active='active' :is-mobile='isMobile' :table-data="etlDetailList" :label-list="etlLabelList"></noFit-table>
<noFit-table v-else :active='active' :is-mobile='isMobile' :table-data="detailList" :label-list="labelList"></noFit-table>
</div>
</div>
<!--月总 页面 e -->
<!--日总 s -->
<div v-loading="loadingData" class="contentWrap monthTotal" v-if="active == 2 || active == 3">
<div class="comTab">
<noFit-table :active='active' :is-mobile='isMobile' :table-data="detailList" :label-list="labelList"></noFit-table>
</div>
</div>
<!--日总 e -->
<div v-loading="loadingData" class="contentWrap monthTotal" v-if="!([0,1,2, 3].includes(active))">
<div class="searchDriverName" v-if="active == 5">
<el-select
v-model="vehicleId"
filterable
remote
clearable
reserve-keyword
placeholder="请输入后选择"
:remote-method="driverremoteMethod"
@change="selectSupplierNameHandle"
:loading="driverselectLoading"
:style="isMobile ? 'width:100%' : 'width:50%'"
>
<el-option
v-for="item in driverselectOption"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</div>
<div class="comTab">
<noFit-table :active='active' :is-mobile='isMobile' :table-data="detailList" :label-list="labelList"></noFit-table>
</div>
<el-pagination
small
:page-sizes="[20, 50, 100]"
:current-page.sync="pageNum"
:page-size.sync="pageSize"
@current-change="selectSupplierNameHandle"
@size-change="selectSupplierNameHandle"
layout="prev, pager, next,sizes"
:total="total">
</el-pagination>
</div>
<!--<div v-loading="loadingData" class="contentWrap monthTotal" v-if="!([0, 1].includes(active))">
<div class="comTab" :class="{'detailTable':isMobile}">
<noFit-table :active='active' :is-mobile='isMobile' :table-data="detailList" :label-list="labelList"
></noFit-table>
</div>
<el-pagination
v-if="active !== 10"
small
:page-sizes="[20, 50, 100]"
:current-page.sync="pageNum"
:page-size.sync="pageSize"
@current-change="selectSupplierNameHandle"
@size-change="selectSupplierNameHandle"
layout="prev, pager, next,sizes"
:total="total">
</el-pagination>
</div>-->
</div>
</template>
<script>
import * as echarts from 'echarts';
import dayjs from "dayjs";
import {
getSupplierId,
vehicleTotalInfo,
vehicleInfoBySupplier,
vehicleInfoByVehicle,
getKpiDetailsData,
getVehicleName
} from "@/api/kpi.js"
import {kpiMixins} from "@/utils/kpiMixins"
import {myMixins} from "@/utils/myMixins";
import NoFitTable from "@/views/kpi/components/fit-table.vue";
export default {
name: "kpiCase",
components: {NoFitTable},
computed: {
totalCount() {
return (Number(this.indexData?.pinganOrderCount || 0) + Number(this.indexData?.zhonghuaOrderCount || 0) + Number(this.indexData?.zdJuheOrderCount || 0) + Number(this.indexData?.traditionOrderCount || 0))
}
},
mixins: [myMixins, kpiMixins],
data() {
return {
active:0,
tabArr: [{name: '总览',value:0}, {name: '月/总'}, {name: '日/总'},{name: '各时段在线车辆'}, {name: '车辆/月'}, {name: '车辆/日'}, {name: '拒单明细'}, {name: '超时明细'},{name: '取消明细'}],
indexData: {}, // 总览数据
detailList: [],
labelList: [],
etlDetailList: [],
etlLabelList: [],
loading: false,
loadingData: false,
v1: [],
v2: [],
v3: [],
v4: [],
v5: [],
v6: [],
v7: [],
v8: [],
xAxisArr: [],
pageNum: 1,
pageSize: 20,
total: 0,
selectLoading: false,
selectOption: [],
driverId:'',//68517
driverName:'',
vehicleId: '',
driverselectLoading: false,
driverselectOption: [],
leftArr:'<<<',
showScoreChart:true,
continueMonthKpi:[],
isBtn:false,//是否有信息变更申请按钮权限
}
},
created() {
this.checkMobile();
const urlParams = new URLSearchParams(window.location.search);
this.isZd = urlParams?.get('isZd') || ''
this.supplierId = urlParams?.get('supplierId') || ''
this.isBtn= Number(urlParams?.get('isBtn'))
},
async mounted() {
await this.checkMobile();
await this.initDate();
await this.selectSupplierNameHandle();
},
methods: {
tabClickHandle() {
// 根据 active不同请求不同的接口渲染不同的页面
this.selectSupplierNameHandle();
},
async getVehicleTotalInfo() {
let res = await vehicleTotalInfo({
startTime: this.startTime,
endTime: this.endTime,
supplierId: this.supplierId,
});
this.indexData = res?.data;
this.pieCharts();
},
async getVehicleInfoBySupplier(type, time) {
let res = await vehicleInfoBySupplier({
startTime: time ? time : this.startTime,
endTime: this.endTime,
statisticsType: type,
supplierId: this.supplierId,
});
this.detailList = res.data?.map(item => {
item.cancelRate=this.formatPercentage(item.cancelRate) || 0;
item.refusalRate=this.formatPercentage(item.refusalRate) || 0;
item.timeoutRate=this.formatPercentage(item.timeoutRate) || 0;
item.orderCount= Number(item?.zdJuheOrderCount || 0) + Number(item?.zhonghuaOrderCount || 0) + Number(item?.pinganOrderCount || 0) + Number(item?.traditionOrderCount || 0)
item.upWayOrderCount = Number(item?.zhonghuaOrderCount || 0) + Number(item?.pinganOrderCount || 0)
item.averageOnlineDuration = this.toOnlineHours(item?.averageOnlineDuration)
let formatVal = dayjs(new Date(item.statisticsTime)).format('DD');
let formatVal1 = dayjs(new Date(item.statisticsTime)).format('MM');
return {...item, date: formatVal, month: formatVal1 + '月'};
});
this.loading = false
if( this.active == 1 ) { // 月总的 label和月总所需要的图表的数据
this.etlDetailList=[{ 'month': '案件量' }, { 'month': '完成量' }, { 'month': '取消量' }, { 'month': '取消率' }, { 'month': '上游聚合量' },
{ 'month': '中道聚合量' }, { 'month': '传统派工量' }, { 'month': '日均在线车辆数' }, { 'month': '日均在线时长(小时)' },{ 'month': '拒单量' },
{ 'month': '拒单率' }, { 'month': '超时量' },{ 'month': '超时率' }]
let props = 'prop'
const columnObj = {} //创建标题数组中第一个对象
columnObj.label = 'KPI' //第一个标题名称
columnObj.prop = 'month' //第一个标题名称对应的字段
this.etlLabelList.push(columnObj)
this.detailList?.map((item, index) => {
this.xAxisArr.push(item.month)
this.xAxisArr = [...new Set(this.xAxisArr)]; // 去重
this.v1.push(item.finishOrderCount)
this.v2.push(item.cancelOrderCount)
this.v3.push(item.onlineRate?.replace('%', '') || 0)
// 转置
const columnObj = {}
columnObj.label = item.month // 每一列的标题的名称
columnObj.prop = props + index //自定义每一列标题字段名称
this.etlLabelList.push(columnObj)
let mappings = [ 'orderCount', 'finishOrderCount', 'cancelOrderCount','cancelRate','upWayOrderCount','zdJuheOrderCount',
'traditionOrderCount', 'vehicleCount', 'averageOnlineDuration', 'refusalOrderCount', 'refusalRate', 'timeoutOrdercount', 'timeoutRate' ];
for (let i = 0; i < mappings.length; i++) {
this.$set(this.etlDetailList[i], columnObj.prop, item[mappings[i]]);
}
});
this.labelList = [
{label: '月', prop: 'month'},
{label: '案件量', prop: 'orderCount'},
{label: '完成量', prop: 'finishOrderCount'},
{label: '取消量', prop: 'cancelOrderCount'},
{label: '取消率', prop: 'cancelRate'},
{label: '上游聚合量', prop: 'upWayOrderCount'},
{label: '中道聚合量', prop: 'zdJuheOrderCount'},
{label: '传统派工量', prop: 'traditionOrderCount'},
{label: '日均在线车辆数', prop: 'onlineVehicleCount'},
{label: '日均在线时长(小时)', prop: 'averageOnlineDuration'},
{label: '拒单量', prop: 'refusalOrderCount'},
{label: '拒单率', prop: 'refusalRate'},
{label: '超时量', prop: 'timeoutOrdercount'},
{label: '超时率', prop: 'timeoutRate'},
]
} else if(this.active == 2) {
this.labelList = [
{label: '日', prop: 'date'},
{label: '案件量', prop: 'orderCount'},
{label: '完成量', prop: 'finishOrderCount'},
{label: '取消量', prop: 'cancelOrderCount'},
{label: '取消率', prop: 'cancelRate'},
{label: '上游聚合量', prop: 'upWayOrderCount'},
{label: '中道聚合量', prop: 'zdJuheOrderCount'},
{label: '传统派工量', prop: 'traditionOrderCount'},
{label: '在线车辆数', prop: 'vehicleCount'},
{label: '车辆平均在线时长', prop: 'averageOnlineDuration'},
{label: '拒单量', prop: 'refusalOrderCount'},
{label: '拒单率', prop: 'refusalRate'},
{label: '超时量', prop: 'timeoutOrdercount'},
{label: '超时率', prop: 'timeoutRate'},
]
}
},
async getVehicleInfoByVehicle(type, vehicleId) {
let res = await vehicleInfoByVehicle({
startTime: this.startTime,
endTime: this.endTime,
statisticsType: type,
supplierId: this.supplierId,
pageNum: this.pageNum,
pageSize: this.pageSize,
vehicleId
});
this.detailList = res.data?.map(item => {
let formatVal =item.createTime ? dayjs(item.createTime).format('DD') : '';
let upMixinCount = Number(item?.pinganOrderCount || 0) + Number(item?.zhonghuaOrderCount || 0)
item.cancelRate = this.formatPercentage(item.cancelRate) || 0;
item.averageOnlineDuration = this.toOnlineHours(item?.averageOnlineDuration)
item.onlineDuration = this.toOnlineHours(item?.onlineDuration)
return {...item, date: formatVal, upMixinCount};
});
if( this.active == 4 ) {
this.labelList = [
{label: '车辆名称', prop: 'vehicleName'},
{label: '案件量', prop: 'orderCount'},
{label: '完成量', prop: 'finishOrderCount'},
{label: '取消量', prop: 'cancelOrderCount'},
{label: '取消率', prop: 'cancelRate'},
{label: '上游聚合量', prop: 'upMixinCount'},
{label: '中道聚合量', prop: 'zdJuheOrderCount'},
{label: '传统派工量', prop: 'traditionOrderCount'},
{label: '在线天数', prop: 'onlineDay'},
{label: '日均在线时长', prop: 'averageOnlineDuration'},
]
} else if ( this.active == 5 ) {
this.labelList = [
{label: '车辆名称', prop: 'vehicleName'},
{label: '日期', prop: 'statisticsTime'},
{label: '案件量', prop: 'orderCount'},
{label: '完成量', prop: 'finishOrderCount'},
{label: '取消量', prop: 'cancelOrderCount'},
{label: '取消率', prop: 'cancelRate'},
{label: '上游聚合量', prop: 'upMixinCount'},
{label: '中道聚合量', prop: 'zdJuheOrderCount'},
{label: '传统派工量', prop: 'traditionOrderCount'},
{label: '在线时长', prop: 'onlineDuration'},
]
}
},
async getSupplierKpiByOrigin(type) {
let result = await getKpiDetailsData({
startTime: this.startTime,
endTime: this.endTime,
searchType: type,
pageNum: this.pageNum,
pageSize: this.pageSize,
supplierId:this.supplierId
});
this.total = result.total
this.detailList = result.data?.map(item => {
let formatVal =item.createTime ? dayjs(item.createTime).format('DD') : '';
return {...item, date: formatVal};
});
if( this.active == 3 ) { // 各时段在线车辆处理,未完成
this.detailList = result.data?.map(item => {
item.zeroClockVehicleCount= item?.actualStatDays ? (parseInt(item.zeroClockVehicleCount / item.actualStatDays) || 0) : 0;
item.eightClockVehicleCount= item?.actualStatDays ? (parseInt(item.eightClockVehicleCount / item.actualStatDays) || 0) : 0;
item.twelveClockVehicleCount= item?.actualStatDays ? (parseInt(item.twelveClockVehicleCount / item.actualStatDays) || 0) : 0;
item.sixteenClockVehicleCount= item?.actualStatDays ? (parseInt(item.sixteenClockVehicleCount / item.actualStatDays) || 0) : 0;
item.twentyClockVehicleCount= item?.actualStatDays ? (parseInt(item.twentyClockVehicleCount / item.actualStatDays) || 0) : 0;
item.twentyTwoClockVehicleCount= item?.actualStatDays ? (parseInt(item.twentyTwoClockVehicleCount / item.actualStatDays) || 0) : 0;
let formatVal1 = dayjs(new Date(item.createTime)).format('MM');
return {...item, month: formatVal1 + '月'};
});
this.labelList = [
{label: '月', prop: 'month'},
{label: '00:00~08:00', prop: 'zeroClockVehicleCount'},
{label: '08:00~12:00', prop: 'eightClockVehicleCount'},
{label: '12:00~16:00', prop: 'twelveClockVehicleCount'},
{label: '16:00~20:00', prop: 'sixteenClockVehicleCount'},
{label: '20:00~22:00', prop: 'twentyClockVehicleCount'},
{label: '22:00~00:00', prop: 'twentyTwoClockVehicleCount'},
]
} else if(this.active == 6) { //拒单明细
this.labelList = [
{label: '案件编号', prop: 'orderCode'},
{label: '服务内容', prop: 'serviceName'},
{label: '拒单供应商', prop: 'supplierName'},
{label: '拒单车辆名称', prop: 'vehicleName'},
{label: '拒单师傅', prop: 'driverName'},
{label: '拒绝时间', prop: 'time'},
{label: '拒单原因', prop: 'reason'},
]
} else if(this.active == 7) { // 超时明细
this.labelList = [
{label: '案件编号', prop: 'orderCode'},
{label: '服务内容', prop: 'serviceName'},
{label: '超时供应商', prop: 'supplierName'},
{label: '超时车辆名称', prop: 'vehicleName'},
{label: '超时师傅', prop: 'driverName'},
{label: '超时时间', prop: 'time'},
{label: '超时原因', prop: 'reason'},
]
} else if(this.active == 8) { // 取消明细
this.labelList = [
{label: '案件编号', prop: 'orderCode'},
{label: '创建时间', prop: 'orderCreateTime'},
{label: '车辆名称', prop: 'vehicleName'},
{label: '取消原因', prop: 'giveUpReason'},
{label: '取消时间', prop: 'giveUpTime'},
]
}
},
monthChangeHandle(value){
if (value) {
this.current = dayjs(new Date(value)).format('YYYY-MM')
const _tempDate = dayjs(new Date(value)).format('YYYY-MM-DD')
const lastDay = dayjs(_tempDate).endOf('month');
this.startTime = `${this.current}-01 00:00:00`;
this.endTime = `${this.current}-${this.padZero(lastDay.date())} 23:59:59`;
this.startMonthTime=this.getStartTimeFromEndTime(this.endTime)
}
},
async remoteMethod(query) {
if (query !== '') {
this.selectLoading = true;
try {
const result = await getSupplierId(query);
this.selectOption = result.data || []
} catch (error) {
console.error('Error fetching data:', error);
this.selectOption = [];
} finally {
this.selectLoading = false;
}
} else {
this.selectOption = [];
}
},
async driverremoteMethod(query){
if (query !== '') {
this.driverselectLoading = true;
try {
const result = await getVehicleName(query);
this.driverselectOption = result.data || []
} catch (error) {
this.driverselectOption = [];
} finally {
this.driverselectLoading = false;
}
} else {
this.driverselectOption = [];
}
},
async selectSupplierNameHandle() { // 下拉切换服务商时调用的接口
if( this.active == 0 ) {
await this.getVehicleTotalInfo();
} else if( this.active == 1 ) {
await this.getVehicleInfoBySupplier(1, this.startMonthTime);
await this.mixinCharts();
} else if(this.active == 2) {
await this.getVehicleInfoBySupplier(2);
} else if(this.active == 3) {
await this.getSupplierKpiByOrigin(8);
} else if(this.active == 4){
await this.getVehicleInfoByVehicle(1);
} else if(this.active == 5) {
if( this.vehicleId ) {
await this.getVehicleInfoByVehicle(2, this.vehicleId);
} else {
this.labelList = [
{label: '车辆名称', prop: 'vehicleName'},
{label: '日期', prop: 'statisticsTime'},
{label: '案件量', prop: 'orderCount'},
{label: '完成量', prop: 'finishOrderCount'},
{label: '取消量', prop: 'cancelOrderCount'},
{label: '取消率', prop: 'cancelRate'},
{label: '上游聚合量', prop: 'upMixinCount'},
{label: '中道聚合量', prop: 'zdJuheOrderCount'},
{label: '传统派工量', prop: 'traditionOrderCount'},
{label: '在线时长', prop: 'onlineDuration'},
];
this.detailList = [];
}
} else if(this.active == 6) {
await this.getSupplierKpiByOrigin(1);
} else if(this.active == 7) {
await this.getSupplierKpiByOrigin(2);
} else if(this.active == 8) {
await this.getSupplierKpiByOrigin(9);
}
},
mixinCharts() {
const myChart = echarts.init(document.getElementById('chartToMonth'));
// 模拟数据(可替换为接口返回数据)
const xData = this.xAxisArr; // X轴月份
const seriesData = {
'完成量': this.v1, // 堆积柱状图-完成量
'取消量': this.v2, // 堆积柱状图-取消量
'在线率': this.v3 // 折线图-在线率
};
// 图表配置项
const option = {
// 图例(自动识别系列名称)
legend: {
top: '10%',
textStyle: { fontSize: 12, color: '#666' },
itemGap: 20 // 图例项间距
},
// 提示框(联动显示所有系列数据)
tooltip: {
trigger: 'axis', // 按坐标轴触发
axisPointer: { type: 'shadow' }, // 鼠标悬浮时显示阴影指示器
formatter: function(params) {
// 自定义 tooltip 格式(更清晰的展示)
let res = `<div style="font-weight: bold;">${params[0].axisValue}</div>`;
params.forEach(item => {
res += `<div>${item.seriesName}: ${item.value}</div>`;
});
return res;
}
},
// 网格(控制图表位置,避免与坐标轴标签重叠)
grid: {
left: '2%',
right: '5%', // 预留右轴空间
bottom: '10%',
top: '10%',
containLabel: true // 包含坐标轴标签
},
// X轴共用一个X轴
xAxis: {
type: 'category',
data: xData,
axisLabel: { fontSize: 12, color: '#666' },
axisLine: { lineStyle: { color: '#eee' } }
},
// 双 Y 轴(左轴控制柱状图,右轴控制折线图)
yAxis: [
{
type: 'value',
name: '订单量',
nameTextStyle: { color: '#4895ef' },
axisLabel: { color: '#666', formatter: '{value}' },
axisLine: { lineStyle: { color: '#4895ef' } },
splitLine: { lineStyle: { color: '#f5f5f5' } } // 网格线
},
{
type: 'value',
name: '在线率',
nameTextStyle: { color: '#48bb78' },
axisLabel: { color: '#666', formatter: '{value}%' },
axisLine: { lineStyle: { color: '#48bb78' } },
splitLine: { show: false } // 隐藏右轴网格线(避免重复)
}
],
// 系列数据(核心:柱状图+折线图组合)
series: [
// 堆积柱状图-系列1线上订单
{
name: '完成量',
type: 'bar',
yAxisIndex: 0, // 绑定左Y轴
stack: 'order', // 堆积分组(同组内数据堆积)
data: seriesData['完成量'],
itemStyle: { color: '#4299e1' }, // 柱状图颜色
label: {
show: true, // 显示数值标签
position: 'insideTop', // 标签位置:柱顶内部
color: '#fff',
fontSize: 10
},
barWidth: 30 // 柱子宽度
},
// 堆积柱状图-系列2线下订单
{
name: '取消量',
type: 'bar',
yAxisIndex: 0, // 绑定左Y轴
stack: 'order', // 与「线上订单」同堆积组
data: seriesData['取消量'],
itemStyle: { color: '#ed8936' }, // 柱状图颜色
label: {
show: true,
position: 'insideTop',
color: '#fff',
fontSize: 10
}
},
// 折线图:目标值
{
name: '在线率',
type: 'line',
yAxisIndex: 1, // 绑定右Y轴
data: seriesData['在线率'],
symbol: 'circle', // 标记点形状:圆形
symbolSize: 6, // 标记点大小
lineStyle: { color: '#48bb78', width: 2 }, // 线条样式
itemStyle: { color: '#48bb78' }, // 标记点颜色
label: {
show: true, // 显示数值标签
position: 'top', // 标签位置:点上方
color: '#48bb78',
fontSize: 10
}
}
]
};
// 渲染图表
myChart.setOption(option);
// 响应式适配:窗口 resize 时自动调整图表大小
window.addEventListener('resize', () => {
myChart.resize();
});
},
// 画饼图 案件渠道
pieCharts() {
let myChart = echarts.init(document.getElementById('pieChart'))
let _data = (Number(this.indexData?.pinganOrderCount || 0) + Number(this.indexData?.zhonghuaOrderCount || 0)) || 0
const charData = [
{value: this.indexData.zdJuheOrderCount, name: '中道聚合案件'},
{value: _data, name: '上游聚合案件'},
{value: this.indexData.traditionOrderCount, name: '传统派工案件'},
]
let option = {
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical', // 垂直排列
left: '1%', // 距离左侧5%
top: 'center', // 垂直居中
textStyle: {
color: '#333', // 图例文字颜色
fontSize: 14
},
itemWidth: 16, // 图例标记宽度
itemHeight: 8, // 图例标记高度
itemGap: 10, // 图例间距
formatter: function(name) {
// 在图例中显示名称和数值
const item = charData.find(item => item.name === name);
return `${name}: ${item?.value || 0}`;
}
},
series: [
{
name: '访问来源',
type: 'pie',
radius: ['75%'], // 增大饼图半径范围
data: charData,
color: [
'#1890FF', // 中道聚合案件 - 蓝色ECharts默认主色
'#FAAD14', // 上游聚合案件 - 黄色ECharts默认辅助色
'#52C41A' // 传统派工案件 - 绿色ECharts默认成功色
],
label: {
show: false // 隐藏文本标签[citation:1]
},
labelLine: {
show: false // 隐藏引导线[citation:1]
},
center: ['70%', '50%'], // 饼图向右移动,垂直居中
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
myChart.setOption(option);
},
/* async getKpiData() {
try {
this.loading = true
if (this.active === 0) {//总览
} else if (this.active == 1) { //月总
} else if (this.active == 3 ) {
this.labelList = [
{label: '案件编号', prop: 'orderCode'},
{label: '服务内容', prop: 'serviceName'},
{label: '超时供应商', prop: 'supplierName'},
{label: '超时车辆名称', prop: 'vehicleName'},
{label: '超时师傅', prop: 'driverName'},
{label: '超时时间', prop: 'time'},
{label: '超时原因', prop: 'reason'},
]
}
} finally {
this.loading = false
}
},*/
async changeTab(index) {
try {
this.loadingData = true
this.activeIndex = index
this.labelList = []
this.v1 = []
this.v2 = []
this.v3 = []
this.v4 = []
this.v5 = []
this.v6 = []
this.v7 = []
this.v8 = []
this.loadingData = false
if (this.active === 1) {
await this.drawLine()
}
} finally {
this.loadingData = false
}
},
}
,
}
</script>
<style scoped lang="scss">
@import "@/styles/mixin.scss";
@import "@/styles/common.scss";
@import "@/styles/mixin.scss";
.wrap {
@include wh(100%, 100%);
}
.headWrap {
@include wh(100%, 56px);
background: #3E62B9;
@include flexTwoCenter;
flex-direction: column;
.title {
font-size: 20px;
color: #FFFFFF;
}
.titleName {
font-family: PingFangSC, PingFang SC;
@include fontWeightSize(14px, 500);
color: #BDDAFF;
}
}
.webHeadWrap {
@include flexCenter;
@include wh(100%, 50px);
background: #3E62B9;
box-sizing: border-box;
padding: 0 20px;
.empty {
@include wh(250px, 100%);
}
.title {
font-size: 20px;
color: #FFFFFF;
}
.searchWrap {
display: flex;
align-items: center;
position: relative;
.el-icon-search {
font-size: 15px;
color: #4C81F5;
position: absolute;
right: 15px;
}
::v-deep .el-input--suffix .el-input__inner {
height: 32px;
background: #EFF2F8;
font-size: 12px;
}
::v-deep .el-input__inner {
border-radius: 0px 20px 20px 0px;
}
::v-deep .el-input__suffix {
top: 4px; /* 给清除按钮预留空间 */
}
}
}
::v-deep .van-tabs {
background: white;
}
::v-deep .van-tabs__nav--line.van-tabs__nav--complete {
background: #5D7FD7;
padding: 0;
height: 36px;
}
::v-deep .van-tab--active {
background: #FFFFFF;
border-radius: 7px 7px 0px 0px;
@include sizeWeightCol(13px, 500, #5D7FD7 !important);
border-bottom: none;
}
::v-deep .van-tabs__line {
display: none;
}
::v-deep .van-tab {
border-right: 1px solid #6B97D0;
@include sizeWeightCol(13px, 500, #ACC1DB);
}
::v-deep .van-col {
padding: 6px 10px;
background: #E5E6EB;
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;;
}
::v-deep .el-pagination .el-select .el-input{
width: 80px;
margin:0
}
.contentWrap {
@include wh(100%, calc(100% - 120px));
overflow-y: auto;
background: #EFF2F8;
.reciceOrder {
@include whBg(375px, 146px, #FFFFFF);
@include sizing4Padding(19px, 25px, 38px, 19px);
margin-bottom: 10px;
.scoreTitle{
@include flexBetCen;
i{
cursor: pointer;
font-size: 14px;
}
}
.storeWrap {
width: 100%;
text-align: center;
.detailScore{
display: flex;
justify-content:center;
}
.left{
text-align: right;
padding-right: 5px;
}
.right{
display: flex;
justify-content: space-between;
flex-direction: column;
text-align: left;
}
}
.title {
@include sizeWeightCol(14px, bold, #040415);
margin-bottom: 14px;
}
.reciceOrderIWrap {
@include flexColAround;
.common {
@include flexColCen;
}
}
.num {
@include sizeWeightCol(24px, bold, #040415);
margin-bottom: 10px;
}
.itemTitle {
@include flexCenter;
font-size: 12px;
color: rgba(4, 4, 21, 0.45);
}
.img {
@include whMarLe(8px, 10px, 4px);
}
.line {
border-right: 2px solid #EAE9EC;
}
}
.ownerHeight {
height: 180px;
}
.evaluate, .appUse, .store {
height: 179px;
.title {
margin-bottom: 0 !important;
}
}
.allDataChartWrap{
/* width: 375px;
height: 220px;*/
@include whBg(375px, 200px, #FFFFFF);
}
.allDataChart{
width: 100%;
height: 100%;
}
.webComAllData{
width: 420px;
height: 200px;
/*width: 375px;
height: 146px;*/
float: left;
margin: 10px;
}
.webCom {
@include whBg(420px, 200px, #FFFFFF);
float: left;
margin: 10px;
.webTitle {
margin-bottom: 40px !important;
}
}
}
.webcontentWrap {
@include wh(100%, calc(100% - 86px));
}
.tipArrow{
position: absolute;
top: 10px;
color: #FFFFFF;
font-size: 12px;
}
.left{
left: 0;
}
.right{
right: 0px;
}
.tabWrap {
@include wh(100%, 22px);
@include flexColAround;
margin: 10px 0;
div {
cursor: pointer;
padding: 2px 8px;
border-radius: 7px;
font-size: 12px;
@include bgFontColor(#3E3C3C, #F4F4F4);
}
.active {
position: relative;
opacity: 1;
@include bgFontColor(#ffffff, #F1B289);
}
.active:after {
content: '';
display: block;
@include wh(18px, 2px);
background: #FFFFFF;
position: absolute;
margin-top: 3px;
left: 50%;
opacity: 1;
transform: translateX(-50%);
width: 0;
height: 0;
border: 4px solid;
border-color: #F1B289 transparent transparent transparent;
}
}
.webTabWrap {
width: 40% !important;
.active:after {
margin-top: 4px;
}
}
.monthTotal {
background: #FFFFFF;
padding-left: 6px;
.leftMonth {
.leftItem {
font-weight: bold;
border-right: 1px solid #cccccc;
border-left: 1px solid #cccccc;
text-align: center;
padding: 8px 10px;
}
.leftItem:nth-child(odd) {
border-top: 1px solid #cccccc;
border-bottom: 1px solid #cccccc;
background-color: #f2f2f2; /* 奇数*/
}
}
.content {
flex-grow: 1; /* 占据剩余空间 */
overflow-x: auto; /* 当内容超出宽度时启用水平滚动 */
}
}
.searchDriverName{
width: 97%;
display: flex;
align-items: center;
span{
width: 20%;
}
::v-deep .el-input{
font-size: 12px;
}
::v-deep .el-input__inner{
height: 30px;
}
::v-deep .el-input__suffix {
top: 4px; /* 给清除按钮预留空间 */
}
}
.comTab {
width: 100%;
height: 95%;
}
::v-deep .el-table {
height: 100%;
}
::v-deep .el-table--scrollable-x .el-table__body-wrapper {
height: 95% !important;
}
.comTabActive3 ::v-deep .el-table--scrollable-x .el-table__body-wrapper{
width: 100%;
//height: 90%;
//height: 84% !important;
}
.chartWrapWeb, .webTab {
height: calc(100% - 200px);
width: 100%;
box-sizing: border-box;
padding: 0 10px;
}
.comChart {
position: relative;
.selectWrap {
position: absolute;
top: 0;
z-index: 1111;
left: 38px;
}
::v-deep .el-input__inner {
padding: 2px;
width: 98px;
height: 24px;
font-size: 12px;
}
::v-deep .el-input__inner {
right: 2px;
}
::v-deep .el-input__icon {
line-height: 0;
}
}
.el-pagination {
text-align: center;
}
.webSwitch {
width: 47%;
position: relative;
.swithContainer {
position: absolute;
z-index: 111;
left: 60px;
}
}
::v-deep .el-switch__core {
font-size: 12px;
width: 30px !important;
height: 16px;
}
::v-deep .el-switch__core::after {
width: 14px;
height: 14px;
margin-top: -1px;
}
::v-deep .el-switch.is-checked .el-switch__core::after {
margin-left: -15px;
}
::v-deep .el-switch__label * {
font-size: 12px;
}
/* 自定义样式 */
.custom-date-picker ::v-deep .el-input__prefix{
display: none;
}
.month {
display: inline-block;
width: 75px;
//height: 30px;
//line-height: 30px;
text-align: center;
border-radius: 20px 0px 0px 20px;
//border-radius: 0px 20px 20px 0px;
//border: 1px solid #4C81F5;
font-size: 14px;
color: #FFFFFF;
::v-deep .el-input--suffix .el-input__inner{
}
::v-deep .el-input__inner{
padding-left: 0 !important;
padding-right: 0 !important;
border-radius: 20px 0px 0px 20px !important;
text-align: center !important;
font-size: 14px !important;
color: #FFFFFF !important;
border: 1px solid #4C81F5 !important;
background-color: transparent !important;
}
/* 自定义清除按钮样式 */
.custom-date-picker ::v-deep .el-input__suffix {
right: 0 !important;
padding-right: 10px !important;
}
.custom-date-picker ::v-deep .el-input__suffix-inner {
display: flex;
align-items: center;
}
.custom-date-picker ::v-deep .el-input__icon {
line-height: 1 !important;
}
}
.numRed {
color: red !important;
}
.numGreen {
color: green !important;
}
.webTab {
flex: 1;
}
.flexCls {
display: flex;
flex-direction: column;
}
</style>