开发收费总览页面,对接中

This commit is contained in:
zhouyanli
2026-08-06 18:03:06 +08:00
parent d9ede93745
commit e106ce523d
28 changed files with 12943 additions and 1 deletions

View File

@@ -0,0 +1,300 @@
<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"
@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-renovation="depositRenovation"
:deposit-rental="depositRental"
/>
<!-- 巡检总览 -->
<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 } 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 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('33322')
const depositRenovation = ref(18)
const depositRental = ref(23)
// 巡检总览
const inspectionList = ref([
{ label: '巡检任务', value: 124, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-task.png' },
{ label: '已完成', value: 98, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-done.png' },
{ label: '已过期', value: 12, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-expire.png' },
{ label: '待处理', value: 14, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-pending.png' }
])
const switchTab = (tab) => {
activeTab.value = tab
fetchFeeSummary()
}
const onProjectChange = (e) => {
const opt = projectOptions.value[e.detail.value]
selectedProject.value = opt.value
selectedProjectLabel.value = opt.label
fetchManagedArea(opt.value)
fetchRate(opt.value)
fetchFeeSummary()
fetchParkingFee(opt.value)
}
const onFeeTypeChange = (opt) => {
selectedFeeType.value = opt.value
selectedFeeTypeLabel.value = opt.label
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 = d.parkingCount || 0
parkingYearReceivable.value = d.receivableAmount || 0
parkingYearReceived.value = d.receivedAmount || 0
parkingArcbarPercent.value = d.receivedPercent != null ? d.receivedPercent / 100 : 0
}
}).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 = d.receivedPercent != null ? d.receivedPercent / 100 : 0
metricList.value = [
{ label: '应收总额', value: d.receivableAmount != null ? d.receivableAmount.toFixed(2) : '0.00', yoy: formatRate(d.receivableYoyRate), mom: formatRate(d.receivableMomRate) },
{ label: '实收总额', value: d.receivedAmount != null ? d.receivedAmount.toFixed(2) : '0.00', yoy: formatRate(d.receivedYoyRate), mom: formatRate(d.receivedMomRate) },
{ label: '欠收总额', value: d.unpaidAmount != null ? d.unpaidAmount.toFixed(2) : '0.00', yoy: formatRate(d.unpaidYoyRate), mom: formatRate(d.unpaidMomRate) }
]
}
}).catch(() => {})
}
const formatRate = (val) => {
if (val == null) return '-'
return (val >= 0 ? '+' : '') + val.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(() => {})
}
onShow(() => {
Promise.all([fetchVillageList(), fetchFeeTypes()]).then(() => {
fetchManagedArea(selectedProject.value)
fetchRate(selectedProject.value)
fetchFeeSummary()
fetchParkingFee(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;
gap: 30rpx;
.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-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>