451 lines
11 KiB
Vue
451 lines
11 KiB
Vue
<script setup lang="ts">
|
||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||
|
||
import { onMounted, ref } from 'vue';
|
||
|
||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||
|
||
import { Button } from 'ant-design-vue';
|
||
|
||
import { statisticsByTime,
|
||
countByRentalType,
|
||
countByCusType,
|
||
countRenewRate,
|
||
countByCusScore,
|
||
countOrderAndAmount,
|
||
countAchievedRate,
|
||
countAchieved
|
||
} from '#/api/property/reportStatistics';
|
||
|
||
const orderLineRef = ref<EchartsUIType>();
|
||
const leasePieRef = ref<EchartsUIType>();
|
||
const customerTypesBarRef = ref<EchartsUIType>();
|
||
const customerRenewalLineRef = ref<EchartsUIType>();
|
||
const conservationTasksBarRef = ref<EchartsUIType>();
|
||
const maintenanceQualityScoresPeiRef = ref<EchartsUIType>();
|
||
|
||
const { renderEcharts } = useEcharts(orderLineRef);
|
||
const { renderEcharts: renderLeasePie } = useEcharts(leasePieRef);
|
||
const { renderEcharts: renderCustomerTypesBar } =
|
||
useEcharts(customerTypesBarRef);
|
||
const { renderEcharts: renderCustomerRenewalLine } = useEcharts(
|
||
customerRenewalLineRef,
|
||
);
|
||
const { renderEcharts: renderConservationTasksBar } = useEcharts(
|
||
conservationTasksBarRef,
|
||
);
|
||
const { renderEcharts: renderMaintenanceQualityScoresPei } = useEcharts(
|
||
maintenanceQualityScoresPeiRef,
|
||
);
|
||
const timeUnit = ref<number>(1)
|
||
|
||
const countOrderAndAmountDataAmount = ref<number>(0);
|
||
const countOrderAndAmountDataOrder = ref<number>(0);
|
||
const countAchievedRateData = ref<any>(null);
|
||
onMounted(async () => {
|
||
// 任务数
|
||
const countOrderAndAmountData= await countOrderAndAmount();
|
||
countOrderAndAmountDataAmount.value = countOrderAndAmountData.amount;
|
||
countOrderAndAmountDataOrder.value = countOrderAndAmountData.num;
|
||
const countAchievedRateDataRes: any = await countAchievedRate();
|
||
countAchievedRateData.value = countAchievedRateDataRes.rate;
|
||
// 查询订单数量趋势
|
||
const res = await statisticsByTime({ timeUnit: timeUnit.value });
|
||
const xAxisData = res?.time ?? [];
|
||
const seriesData = res?.counts ?? [];
|
||
// 租赁金额分布
|
||
const data = await countByRentalType();//返回的内容是amount: 1, type: "单点"
|
||
// 转换字段名为value和name
|
||
const convertedData = data.map((item: { amount: number; type: string }) => ({
|
||
value: item.amount,
|
||
name: item.type
|
||
}));
|
||
// 客户类型分配
|
||
const countByCusTypeData:any = await countByCusType();
|
||
//客户续租率趋势
|
||
const countRenewRateData:any = await countRenewRate();
|
||
// 养护任务完成情况
|
||
const countAchievedData:any = await countAchieved();
|
||
// 养护质量评分分布
|
||
const countByCusScoreData:any = await countByCusScore();
|
||
const countByCusScoreDataList = countByCusScoreData.map((item: { score: string; count: number }) => ({
|
||
value: item.count,
|
||
name: item.score
|
||
}));
|
||
renderEcharts({
|
||
tooltip: { trigger: 'axis' },
|
||
xAxis: {
|
||
type: 'category',
|
||
data: xAxisData,
|
||
boundaryGap: false,
|
||
},
|
||
yAxis: { type: 'value' },
|
||
series: [
|
||
{
|
||
name: '订单数',
|
||
type: 'line',
|
||
data: seriesData ||[],
|
||
smooth: true,
|
||
},
|
||
],
|
||
});
|
||
renderLeasePie({
|
||
title: { text: '租赁金额分布', left: 'center' },
|
||
tooltip: { trigger: 'item' },
|
||
legend: { orient: 'vertical', left: 'left' },
|
||
series: [
|
||
{
|
||
// name: '金额',
|
||
type: 'pie',
|
||
radius: '60%',
|
||
center: ['50%', '50%'],
|
||
data:convertedData || [],
|
||
emphasis: {
|
||
itemStyle: {
|
||
shadowBlur: 10,
|
||
shadowOffsetX: 0,
|
||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||
},
|
||
},
|
||
label: {
|
||
formatter: '{b}: {c} ({d}%)',
|
||
show: true,
|
||
},
|
||
},
|
||
],
|
||
});
|
||
renderCustomerTypesBar({
|
||
title: { text: '客户类型分配' },
|
||
tooltip: { trigger: 'axis' },
|
||
xAxis: {
|
||
type: 'category',
|
||
data: countByCusTypeData.type || [],
|
||
boundaryGap: true,
|
||
},
|
||
yAxis: { type: 'value' },
|
||
series: [
|
||
{
|
||
name: '订单数',
|
||
type: 'bar',
|
||
data: countByCusTypeData.counts || [],
|
||
},
|
||
],
|
||
});
|
||
renderCustomerRenewalLine({
|
||
title: { text: '客户续租率趋势' },
|
||
tooltip: { trigger: 'axis' },
|
||
xAxis: {
|
||
type: 'category',
|
||
data: countRenewRateData.month || [],
|
||
boundaryGap: false,
|
||
},
|
||
yAxis: { type: 'value' },
|
||
series: [
|
||
{
|
||
name: '订单数',
|
||
type: 'line',
|
||
data: countRenewRateData.rate || [],
|
||
smooth: true,
|
||
},
|
||
],
|
||
});
|
||
renderConservationTasksBar({
|
||
title: { text: '养护任务完成情况' },
|
||
tooltip: {
|
||
trigger: 'axis',
|
||
axisPointer: { type: 'shadow' },
|
||
},
|
||
legend: {
|
||
data: ['计划任务数', '已完成数', '完成率'],
|
||
},
|
||
xAxis: [
|
||
{
|
||
type: 'category',
|
||
data: countAchievedData.type || [],
|
||
},
|
||
],
|
||
yAxis: [
|
||
{
|
||
type: 'value',
|
||
name: '任务数',
|
||
min: 0,
|
||
max: 200,
|
||
position: 'left',
|
||
},
|
||
{
|
||
type: 'value',
|
||
name: '完成率',
|
||
min: 0,
|
||
max: 100,
|
||
position: 'right',
|
||
axisLabel: {
|
||
formatter: '{value}%',
|
||
},
|
||
},
|
||
],
|
||
series: [
|
||
{
|
||
name: '计划任务数',
|
||
type: 'bar',
|
||
data: countAchievedData.toral || [],
|
||
},
|
||
{
|
||
name: '已完成数',
|
||
type: 'bar',
|
||
data: countAchievedData.finish || [],
|
||
},
|
||
{
|
||
name: '完成率',
|
||
type: 'line',
|
||
yAxisIndex: 1,
|
||
data: countAchievedData.rate || [],
|
||
},
|
||
],
|
||
});
|
||
renderMaintenanceQualityScoresPei({
|
||
title: { text: '养护质量评分分布', left: 'center' },
|
||
tooltip: {
|
||
trigger: 'item',
|
||
formatter: '{b} : {d}%',
|
||
},
|
||
legend: {
|
||
orient: 'horizontal',
|
||
left: 'center',
|
||
bottom: 10,
|
||
data: ['一星', '两星', '三星', '四星', '五星'],
|
||
},
|
||
series: [
|
||
{
|
||
name: '评分',
|
||
type: 'pie',
|
||
radius: '60%',
|
||
center: ['50%', '50%'],
|
||
data: countByCusScoreDataList || [],
|
||
label: {
|
||
formatter: '{b} {d}%',
|
||
show: true,
|
||
},
|
||
},
|
||
],
|
||
});
|
||
});
|
||
const nodeOptions = [
|
||
{ label: '日', value: 1 },
|
||
{ label: '周', value: 2 },
|
||
{ label: '月', value: 3 },
|
||
];
|
||
function handleAssociationChange(e: any) {
|
||
timeUnit.value = e.target.value;
|
||
}
|
||
function formatNumber(num: number | string) {
|
||
if (!num && num !== 0) {
|
||
return '';
|
||
}
|
||
num = num.toString();
|
||
const parts = num.split('.');
|
||
let integerPart: string = parts[0] || '0';
|
||
const decimalPart = parts.length > 1 ? '.' + parts[1] : '';
|
||
const rgx = /(\d+)(\d{3})/;//整体表示匹配一组由多个数字后跟三个数字组成的字符串
|
||
while (rgx.test(integerPart)) {
|
||
integerPart = integerPart.replace(rgx, `$1${','}$2`);
|
||
}
|
||
|
||
return integerPart + decimalPart;
|
||
}
|
||
</script>
|
||
<template>
|
||
<div class="main">
|
||
<div class="box">
|
||
<div class="title">
|
||
<div class="title-text">绿植租赁业务统计报表:</div>
|
||
<div class="title-operate">
|
||
<div class="export">
|
||
<Button size="large" style="color: #fff; background-color: #22c55e">
|
||
导出数据
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="content">
|
||
<div class="row">
|
||
<div class="box">
|
||
<div class="title">总订单数</div>
|
||
<div class="number">{{formatNumber(countOrderAndAmountDataAmount)}}</div>
|
||
<!-- <div class="percent">8.9%</div> -->
|
||
</div>
|
||
<div class="box">
|
||
<div class="title">累计租赁金额</div>
|
||
<div class="number">{{ formatNumber(countOrderAndAmountDataOrder) }}</div>
|
||
</div>
|
||
<div class="box">
|
||
<div class="title">绿植养护完成率</div>
|
||
<div class="number">{{ countAchievedRateData }}</div>
|
||
</div>
|
||
</div>
|
||
<div class="row-first">
|
||
<div class="item1">
|
||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;">
|
||
<span style="font-size: 18px; font-weight: bold;">订单数量趋势</span>
|
||
<div>
|
||
<RadioGroup
|
||
v-model:value="timeUnit"
|
||
:options="nodeOptions"
|
||
button-style="solid"
|
||
option-type="button"
|
||
@change="handleAssociationChange"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<EchartsUI
|
||
ref="orderLineRef"
|
||
height="350px"
|
||
width="100%"
|
||
style="background: #fff; border-radius: 8px"
|
||
/>
|
||
</div>
|
||
<div class="item2">
|
||
<EchartsUI
|
||
ref="leasePieRef"
|
||
height="350px"
|
||
width="100%"
|
||
style="background: #fff; border-radius: 8px"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div class="row-second">
|
||
<div class="item1">
|
||
<EchartsUI
|
||
ref="customerTypesBarRef"
|
||
height="350px"
|
||
width="100%"
|
||
style="background: #fff; border-radius: 8px"
|
||
/>
|
||
</div>
|
||
<div class="item2">
|
||
<EchartsUI
|
||
ref="customerRenewalLineRef"
|
||
height="350px"
|
||
width="100%"
|
||
style="background: #fff; border-radius: 8px"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div class="row-third">
|
||
<EchartsUI
|
||
ref="conservationTasksBarRef"
|
||
height="100%"
|
||
width="100%"
|
||
style="background: #fff; border-radius: 8px"
|
||
/>
|
||
</div>
|
||
<div class="row-fouth">
|
||
<EchartsUI
|
||
ref="maintenanceQualityScoresPeiRef"
|
||
height="100%"
|
||
width="100%"
|
||
style="background: #fff; border-radius: 8px"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<style lang="scss" scoped>
|
||
.main {
|
||
width: 100%;
|
||
|
||
.box {
|
||
height: 100%;
|
||
margin: 40px;
|
||
|
||
.title {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
|
||
.title-text {
|
||
font-size: 25px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.title-operate {
|
||
display: flex;
|
||
|
||
.export {
|
||
margin-left: 20px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.content {
|
||
flex: 1;
|
||
height: 100%;
|
||
padding: 10px;
|
||
.row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
.box{
|
||
width: 300px;
|
||
height: 120px;
|
||
background-color: #fff;
|
||
border-radius: 8px;
|
||
margin: 40px 0px;
|
||
padding: 10px;
|
||
.title{
|
||
font-size: 20px;
|
||
}
|
||
.number{
|
||
font-size: 25px;
|
||
font-weight: bold;
|
||
}
|
||
.percent{
|
||
font-size: 15px; }
|
||
}
|
||
}
|
||
|
||
.row-first {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
height: 400px;
|
||
margin-bottom: 50px;
|
||
}
|
||
|
||
.row-second {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
height: 400px;
|
||
margin-bottom: 50px;
|
||
|
||
}
|
||
|
||
.row-third {
|
||
height: 400px;
|
||
margin-bottom: 50px;
|
||
|
||
}
|
||
|
||
.row-fouth {
|
||
height: 400px;
|
||
}
|
||
|
||
.item1 {
|
||
width: 45%;
|
||
height: 100%;
|
||
background-color: #fff;
|
||
padding: 10px;
|
||
border-radius: 8px;
|
||
// margin: 20px;
|
||
|
||
}
|
||
|
||
.item2 {
|
||
width: 50%;
|
||
height: 100%;
|
||
background-color: #fff;
|
||
padding: 10px;
|
||
border-radius: 8px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|