Files
estate/src/views/system/statistics/Payment/index.vue
2026-04-27 16:43:36 +08:00

499 lines
12 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="personnel p-2 h-full flex flex-col">
<div class="flex flex-items-center searchBox">
<div class="searchLabel">选择日期</div>
<el-date-picker
v-model="date"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
style="width: 400px; margin-right: 10px"
></el-date-picker>
<el-button type="primary">查询</el-button>
<el-button>重置</el-button>
</div>
<el-row :gutter="20">
<el-col :span="16">
<div class="cardbox">
<div>收入来源</div>
<div class="cardbody" style="height: 200px">
<div class="flex flex-col flex-items-center flex-1">
<Echarts :options="options"></Echarts>
<div>停车费</div>
</div>
<div class="flex flex-col flex-items-center flex-1">
<Echarts :options="options"></Echarts>
<div>物业费</div>
</div>
<div class="flex flex-col flex-items-center flex-1">
<Echarts :options="options"></Echarts>
<div>商铺租金</div>
</div>
<div class="flex flex-col flex-items-center flex-1">
<Echarts :options="options"></Echarts>
<div>水电费</div>
</div>
<div class="flex flex-col flex-items-center flex-1">
<Echarts :options="options"></Echarts>
<div>维修费</div>
</div>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="cardbox">
<div>缴费方式</div>
<div class="cardbody w-full" style="height: 150px">
<Echarts :options="options2"></Echarts>
</div>
</div>
</el-col>
</el-row>
<div class="" style="height: 20px"></div>
<el-row :gutter="20">
<el-col :span="16">
<div class="cardbox w-full" style="height: 400px">
<Echarts :options="options3"></Echarts>
</div>
</el-col>
<el-col :span="8">
<div class="cardbox w-full" style="height: 400px">
<div class="" style="height: 50px; margin-bottom: 20px">缴费方式</div>
<div flex flex-items-center justify-center>
<ul class="legendbox">
<li>小程序</li>
<li>物业缴费</li>
<li>手机APP</li>
</ul>
<ul class="circlebox">
<li>70%</li>
<li>20%</li>
<li>10%</li>
</ul>
</div>
</div>
</el-col>
</el-row>
<div class="" style="height: 20px"></div>
<el-row>
<el-col :span="24">
<div class="cardbox w-full" style="height: 450px">
<Echarts :options="flowLineOption"></Echarts>
</div>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
// TODO 缴费分析
import { ref } from 'vue';
import Echarts from '@/components/Echarts/index.vue';
import { EChartsOption } from 'echarts';
import { getLast7Days, getLastNDays } from '@/utils/time';
/** 入住情况 */
const options = shallowRef<EChartsOption>({
tooltip: { show: false },
series: [
// 1. 底层细圆环(灰色底色)
{
type: 'pie',
radius: ['60%', '80%'], // 细一点
clockwise: true,
data: [100],
itemStyle: { color: '#ebeef5' },
emphasis: { disabled: true },
label: { show: false }
},
// 2. 上层粗圆环(进度条)
{
type: 'pie',
radius: ['60%', '85%'], // 粗一点
clockwise: true,
itemStyle: {
color: '#409eff',
borderRadius: 10
},
emphasis: { disabled: true },
label: {
show: true,
position: 'center',
fontSize: 20,
fontWeight: 'bold',
formatter: '85%'
},
data: [{ value: 85 }, { value: 15, itemStyle: { color: 'transparent' } }]
}
]
});
/** 入住类型 */
const options1 = shallowRef<EChartsOption>({
legend: {
top: 'center',
left: '0',
orient: 'vertical',
itemGap: 50
},
series: [
{
name: 'Access From',
type: 'pie',
radius: ['50%', '100%'],
avoidLabelOverlap: false,
left: '50%',
label: {
show: false,
position: 'center'
},
tooltip: {
show: false
},
emphasis: {
label: {
show: false,
fontSize: 20,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: 25, name: '业主', itemStyle: { color: '#0a74fe' } },
{ value: 25, name: '租户', itemStyle: { color: '#fe3d4e' } },
{ value: 25, name: '亲属', itemStyle: { color: '#00cf7f' } },
{ value: 25, name: '其他', itemStyle: { color: '#f48e3b' } }
]
}
]
});
/** 缴费方式 */
const options2 = shallowRef<EChartsOption>({
legend: {
top: 'center',
left: '0',
orient: 'vertical',
itemGap: 50
},
clockwise: true,
series: [
{
name: 'Access From',
type: 'pie',
startAngle: 280,
radius: ['50%', '100%'],
avoidLabelOverlap: false,
left: '50%',
label: {
show: false,
position: 'center'
},
tooltip: {
show: false
},
emphasis: {
label: {
show: false,
fontSize: 20,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: 50, name: '支付宝', itemStyle: { color: '#0a74fe' } },
{ value: 30, name: '微信', itemStyle: { color: '#f74043' } },
{ value: 20, name: '线下', itemStyle: { color: '#fcd152' } }
]
}
]
});
// ! 年龄分布 -------------------------------------------------------------------------------------
const agesX = ['6岁以下', '6-11岁', '11-14岁', '15-24岁', '25-30岁', '31-40岁', '41-50岁', '51-61岁', '61-65岁', '65-70岁', '70岁以上'];
const agesData = [520, 680, 460, 750, 1350, 1860, 1420, 960, 630, 380, 210];
// 🔥 关键:灰色背景数据 = 最大值 - 真实数据互补100%
const maxValue = 2000; // 设定总高度(可自行调整)
const middleHeight = 20;
const barwidth = '10%';
const bgData = agesData.map((item) => maxValue - item - middleHeight);
const bgData2 = agesData.map((item) => middleHeight);
/** 年龄分布 */
const options3 = shallowRef<EChartsOption>({
title: {
text: '年龄分布',
left: 0,
top: 0,
textStyle: { fontSize: 14, fontWeight: 'normal' }
},
xAxis: {},
grid: {
left: '3%',
right: '4%',
top: '12%',
bottom: '8%',
containLabel: true
},
yAxis: {},
series: [
{
symbolSize: 20,
data: [
[10.0, 8.04],
[8.07, 6.95],
[13.0, 7.58],
[9.05, 8.81],
[11.0, 8.33],
[14.0, 7.66],
[13.4, 6.81],
[10.0, 6.33],
[14.0, 8.96],
[12.5, 6.82],
[9.15, 7.2],
[11.5, 7.2],
[3.03, 4.23],
[12.2, 7.83],
[2.02, 4.47],
[1.05, 3.33],
[4.05, 4.96],
[6.03, 7.24],
[12.0, 6.26],
[12.0, 8.84],
[7.08, 5.82],
[5.02, 5.68]
],
type: 'scatter'
}
]
});
// ! 进十日缴费数量情况 -------------------------------------------------------------------------------------
// 近10天日期
const dayX = getLast7Days(10);
// 小区真实规模:人员流入 / 流出 数据
const inData = [97, 145, 63, 112, 88, 156, 74, 131, 102, 59];
const outData = [121, 85, 142, 71, 135, 93, 116, 66, 148, 104];
/** 进十日缴费数量情况 - 平滑折线图 */
const flowLineOption = shallowRef<EChartsOption>({
title: {
text: '进十日缴费数量趋势',
left: 0,
top: 0,
textStyle: { fontSize: 14, fontWeight: 'normal' }
},
tooltip: {
trigger: 'axis',
confine: true
},
grid: {
left: '3%',
right: '4%',
top: '12%',
bottom: '8%',
containLabel: true
},
legend: {
data: ['已缴费', '未缴费'],
right: 10,
top: 0
},
xAxis: {
type: 'category',
data: dayX,
boundaryGap: false,
axisLine: { lineStyle: { color: '#eee' } }
},
yAxis: {
type: 'value',
splitLine: {
show: true,
lineStyle: { color: '#eee' }
}
},
series: [
{
name: '已缴费',
type: 'line',
smooth: true, // 平滑折线 核心
data: inData,
symbol: 'circle',
symbolSize: 6,
itemStyle: { color: '#f94144' },
lineStyle: { width: 4 },
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: '#fbd7d9' },
{ offset: 1, color: '#fbd7d9' }
]
}
}
},
{
name: '未缴费',
type: 'line',
smooth: true, // 平滑折线 核心
data: outData,
symbol: 'circle',
symbolSize: 6,
itemStyle: { color: '#f9c74f' },
lineStyle: { width: 4 },
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: '#fcf2dc' },
{ offset: 1, color: '#fcf2dc' }
]
}
}
}
]
});
// ! 进十日缴费数量情况 -------------------------------------------------------------------------------------
</script>
<style scoped lang="scss">
.personnel {
background: linear-gradient(180deg, #deedff 0%, #f8f9fe 50%);
.el-button {
width: 80px;
height: 32px;
border-radius: 4px;
}
.searchBox {
margin-bottom: 20px;
}
.searchLabel {
width: 100px;
font-size: 0.8rem;
}
// 入住清空
.cardbox {
background: linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, #ffffff 70%);
border-radius: 16px 16px 16px 16px;
border: 2px solid #ffffff;
font-size: 20px;
padding: 20px;
min-height: 275px;
.cardbody {
padding: 0 30px;
display: flex;
align-items: center;
justify-content: space-between;
.card_leftbox {
text-align: center;
.labelbox {
font-size: 16px;
}
.valuebox {
font-weight: 600;
font-size: 36px;
color: #222222;
}
}
.echartsbox {
width: 400px;
}
}
}
.legendbox {
font-size: 0.8rem;
li {
margin-bottom: 20px;
position: relative;
width: 100px;
height: 50px;
line-height: 50px;
text-align: left;
padding-left: 30px;
&::after {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
display: block;
width: 20px;
height: 20px;
border-radius: 50%;
background: #c893fd;
}
&:first-of-type {
&::after {
background: #4a3aff;
}
}
&:last-of-type {
&::after {
background: #1e1b39;
}
}
}
}
.circlebox {
margin-left: 60px;
position: relative;
width: 300px;
height: 200px;
transform: translateY(-40px);
li {
position: absolute;
right: 0;
top: 0;
width: 150px;
height: 150px;
border-radius: 50%;
background-color: #c893fd;
border: 3px solid white;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 1.4rem;
font-weight: 450;
z-index: 10;
&:nth-child(1) {
width: 200px;
height: 200px;
right: inherit;
left: 0px;
font-size: 2rem;
top: 30px;
z-index: 9;
background-color: #4a3aff;
}
&:nth-child(3) {
width: 100px;
height: 100px;
left: inherit;
top: inherit;
right: 50px;
font-size: 1.1rem;
bottom: -20px;
z-index: 11;
background-color: #1e1b39;
}
}
}
}
</style>