admin-vben5/apps/web-antd/src/views/property/greenPlantRentalManagement/reportStatistics/index.vue

451 lines
11 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.

<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>