Files
property-manager-side/wuye-manager-uniapp/pageSubPack/statistics/statistics.vue
2026-08-17 09:51:52 +08:00

339 lines
10 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>
<view class="page_padding">
<!-- 全部项目 -->
<view class="filter-section">
<view class="filter-row-full">
<picker mode="selector" :range="projectOptions" range-key="label" @change="onProjectChange">
<view class="filter-label">
<text>{{ selectedProjectLabel || '全部项目' }}</text>
<text class="filter-arrow">&#9660;</text>
</view>
</picker>
</view>
</view>
<!-- 统计卡片 -->
<view class="overview-card">
<view class="card-left">
<text class="card-num">{{ manageArea }}</text>
<text class="card-label">管理面积(m²)</text>
</view>
<view class="card-right">
<text class="card-num">{{ serviceHouseholds }}</text>
<text class="card-label">服务户数(户)</text>
</view>
</view>
<!-- 入住率 / 出租率 -->
<view class="rate-row">
<view class="rate-item rate-occupy">
<text class="rate-text">入住率</text>
<text class="rate-num">{{ occupancyRate }}%</text>
</view>
<view class="rate-item rate-rental">
<text class="rate-text">出租率</text>
<text class="rate-num">{{ rentalRate }}%</text>
</view>
</view>
<!-- 收费总览 -->
<FeeOverviewCard
:fee-type-options="feeTypeOptions"
:selected-fee-type-label="selectedFeeTypeLabel"
:active-tab="activeTab"
:arcbar-percent="arcbarPercent"
:metric-list="metricList"
:chart-loading="feeLoading"
@tab-change="switchTab"
@fee-type-change="onFeeTypeChange"
/>
<!-- 车位收费总览 -->
<ParkingFeeCard
:parking-count="parkingCount"
:parking-year-receivable="parkingYearReceivable"
:parking-year-received="parkingYearReceived"
:parking-arcbar-percent="parkingArcbarPercent"
/>
<!-- 押金余额统计 -->
<DepositCard
:deposit-balance="depositBalance"
:deposit-items="depositItems"
/>
<!-- 巡检总览 -->
<InspectionOverview
:inspection-list="inspectionList"
/>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { getVillageList, getFeeTypes } from '@/api/api.js'
import FeeOverviewCard from './components/FeeOverviewCard.vue'
import ParkingFeeCard from './components/ParkingFeeCard.vue'
import DepositCard from './components/DepositCard.vue'
import InspectionOverview from './components/InspectionOverview.vue'
import { getManagedArea, getRate, getFeeSummary, getParkingFee, getDepositBalance, getInspection } from '@/api/statisticsApi.js'
const manageArea = ref(0)
const serviceHouseholds = ref(0)
const occupancyRate = ref(100)
const rentalRate = ref(0)
const activeTab = ref('day')
const feeTypeOptions = ref([])
const selectedFeeType = ref('')
const selectedFeeTypeLabel = ref('全部收费类型')
const projectOptions = ref([])
const selectedProject = ref('')
const selectedProjectLabel = ref('全部项目')
const feeLoading = ref(false)
const arcbarPercent = ref(0)
const metricList = ref([
{ label: '应收总额', value: '0.00', yoy: '-', mom: '-' },
{ label: '实收总额', value: '0.00', yoy: '-', mom: '-' },
{ label: '欠收总额', value: '0.00', yoy: '-', mom: '-' }
])
// 车位收费总览
const parkingCount = ref(0)
const parkingYearReceivable = ref(0)
const parkingYearReceived = ref(0)
const parkingArcbarPercent = ref(0)
// 押金余额统计
const depositBalance = ref('0')
const depositItems = ref([])
// 巡检总览
const inspectionList = ref([
{ label: '巡检任务', value: 0, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-task.png' },
{ label: '已完成', value: 0, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-done.png' },
{ label: '已过期', value: 0, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-expire.png' },
{ label: '待处理', value: 0, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-pending.png' }
])
const switchTab = (tab) => {
activeTab.value = tab
feeLoading.value = true
fetchFeeSummary()
}
const onProjectChange = (e) => {
const opt = projectOptions.value[e.detail.value]
selectedProject.value = opt.value
selectedProjectLabel.value = opt.label
feeLoading.value = true
fetchManagedArea(opt.value)
fetchRate(opt.value)
fetchFeeSummary()
fetchParkingFee(opt.value)
fetchDepositBalance(opt.value)
fetchInspection(opt.value)
}
const onFeeTypeChange = (opt) => {
selectedFeeType.value = opt.value
selectedFeeTypeLabel.value = opt.label
feeLoading.value = true
fetchFeeSummary()
}
const fetchVillageList = () => {
return getVillageList().then(res => {
if (res.code === 200 && res.data) {
projectOptions.value = [{ value: '', label: '全部项目' }, ...res.data.map(item => ({ value: String(item.id), label: item.villageName }))]
selectedProject.value = ''
selectedProjectLabel.value = '全部项目'
}
}).catch(() => {})
}
const fetchManagedArea = (villageId) => {
getManagedArea({ villageId: villageId || '' }).then(res => {
if (res.code === 200 && res.data) {
manageArea.value = res.data.manageArea
serviceHouseholds.value = res.data.serviceHouseCount
}
}).catch(() => {})
}
const fetchRate = (villageId) => {
getRate({ villageId: villageId || '' }).then(res => {
if (res.code === 200 && res.data) {
occupancyRate.value = res.data.occupancyRate
rentalRate.value = res.data.rentalRate
}
}).catch(() => {})
}
// 车位收费总览
const fetchParkingFee = (villageId) => {
getParkingFee({ villageId: villageId || '' }).then(res => {
if (res.code === 200 && res.data) {
const d = res.data
parkingCount.value = Number(d.parkingCount) || 0
parkingYearReceivable.value = Number(d.receivableAmount) || 0
parkingYearReceived.value = Number(d.receivedAmount) || 0
parkingArcbarPercent.value = d.receivedPercent
}
}).catch(() => {})
}
// 收费总览
const fetchFeeSummary = () => {
getFeeSummary({
villageId: selectedProject.value || '',
chargeTypeId: selectedFeeType.value || '',
timeScope: activeTab.value
}).then(res => {
if (res.code === 200 && res.data) {
const d = res.data
arcbarPercent.value = Number(d.receivedPercent) || 0
metricList.value = [
{ label: '应收总额', value: (Number(d.receivableAmount) || 0).toFixed(2), yoy: formatRate(d.receivableYoyRate), mom: formatRate(d.receivableMomRate) },
{ label: '实收总额', value: (Number(d.receivedAmount) || 0).toFixed(2), yoy: formatRate(d.receivedYoyRate), mom: formatRate(d.receivedMomRate) },
{ label: '欠收总额', value: (Number(d.unpaidAmount) || 0).toFixed(2), yoy: formatRate(d.unpaidYoyRate), mom: formatRate(d.unpaidMomRate) }
]
}
}).catch(() => {}).finally(() => {
feeLoading.value = false
})
}
const formatRate = (val) => {
if (val == null || val === '') return '-'
const num = Number(val)
if (isNaN(num)) return '-'
return (num >= 0 ? '+' : '') + num.toFixed(2) + '%'
}
const fetchFeeTypes = () => {
return getFeeTypes().then(res => {
if (res.code === 200 && res.data) {
feeTypeOptions.value = [{ value: '', label: '全部收费类型' }, ...res.data.map(item => ({ value: String(item.id), label: item.name }))]
selectedFeeType.value = ''
selectedFeeTypeLabel.value = '全部收费类型'
}
}).catch(() => {})
}
// 押金余额统计
const fetchDepositBalance = (villageId) => {
getDepositBalance({ villageId: villageId}).then(res => {
if (res.code === 200 && res.data) {
const d = res.data
depositBalance.value = d.totalBalance
depositItems.value = d.items || []
}
}).catch(() => {})
}
// 巡检总览
const fetchInspection = (villageId) => {
getInspection({ villageId: villageId || '' }).then(res => {
if (res.code === 200 && res.data) {
const d = res.data
inspectionList.value = [
{ label: '巡检任务', value: d.taskCount || 0, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-task.png' },
{ label: '已完成', value: d.completedCount || 0, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-done.png' },
{ label: '已过期', value: d.expiredCount || 0, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-expire.png' },
{ label: '待处理', value: d.pendingCount || 0, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-pending.png' }
]
}
}).catch(() => {})
}
onShow(() => {
Promise.all([fetchVillageList(), fetchFeeTypes()]).then(() => {
fetchManagedArea(selectedProject.value)
fetchRate(selectedProject.value)
fetchFeeSummary()
fetchParkingFee(selectedProject.value)
fetchDepositBalance(selectedProject.value)
fetchInspection(selectedProject.value)
})
})
</script>
<style scoped lang="scss">
.page_padding {
background: #F9F9F9;
padding: 0 30rpx;
max-height: none !important;
overflow-y: auto !important;
}
.filter-section {
padding: 16rpx 0 12rpx;
.filter-row-full .filter-label {
display: flex;
align-items: center;
justify-content: space-between;
background: #fff;
border-radius: 12rpx;
padding: 25rpx 24rpx;
font-size: 26rpx;
color: #333;
.filter-arrow { font-size: 20rpx; color: #999; margin-left: 8rpx; }
}
}
.overview-card {
margin-top: 20rpx;
background: linear-gradient(to right, #4D5DFA, #7A86FF);
border-radius: 8rpx;
padding: 30rpx 0;
display: flex;
align-items: center;
.card-left, .card-right {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.card-num {
font-size: 44rpx;
font-weight: 700;
color: #fff;
line-height: 1.2;
margin-bottom: 8rpx;
}
.card-label {
font-size: 24rpx;
color: rgba(255,255,255,.85);
}
}
.rate-row {
margin-top: 20rpx;
display: flex;
.rate-item {
flex: 1;
border-radius: 8rpx;
padding: 30rpx 18px;
display: flex;
flex-direction: column;
align-items: flex-start;
background-size: cover;
background-position: center;
}
.rate-item + .rate-item {
margin-left: 30rpx;
}
.rate-occupy {
background-image: url('https://wuye.ckdzkj.com/images/imgs/rate-occupy.png');
}
.rate-rental {
background-image: url('https://wuye.ckdzkj.com/images/imgs/rate-rental.png');
}
.rate-text { font-size: 24rpx; color: #fff; margin-bottom: 8rpx; }
.rate-num { font-size: 44rpx; font-weight: 700; color: #fff; line-height: 1.2; }
}
</style>