Files
property-manager-side/wuye-manager-uniapp/pageSubPack/instrument/revenue_statistics.vue
2026-08-20 09:17:05 +08:00

592 lines
20 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">
<!-- 全部项目 -->
<view class="filter-item">
<picker mode="selector" :range="projectOptions" range-key="label" @change="onProjectChange">
<view class="filter-label">
<text>{{ selectedProjectLabel || '全部项目' }}</text>
<text class="filter-arrow">▾</text>
</view>
</picker>
</view>
<!-- 全部收费类型 -->
<view class="filter-item">
<picker mode="selector" :range="feeTypeOptions" range-key="label" @change="onFeeTypeChange">
<view class="filter-label">
<text>{{ selectedFeeTypeLabel || '全部收费类型' }}</text>
<text class="filter-arrow">▾</text>
</view>
</picker>
</view>
</view>
<!-- 日期范围 -->
<view class="date-row">
<uni-datetime-picker
class="date-picker"
v-model="datetimerange"
type="daterange"
rangeSeparator="至"
placeholder="请选择日期区间"
/>
</view>
<!-- 搜索 & 重置按钮 -->
<view class="btn-row">
<button class="reset-btn" @click="onReset">
重置
</button>
<button class="search-btn" @click="onSearch">
搜索
</button>
</view>
</view>
<!-- 统计表格列表 -->
<scroll-view
class="table-scroll"
scroll-y
show-scrollbar="false"
refresher-enabled
:refresher-triggered="refresherTriggered"
@refresherrefresh="onPullRefresh"
>
<!-- 循环渲染每个表格 -->
<view v-for="(table, index) in tableConfigs" :key="index" class="table-wrapper">
<!-- 标题独立于卡片外 -->
<view class="table-title">{{ table.title }}</view>
<!-- 卡片区域(仅包含表格内容) -->
<view class="table-card">
<!-- 表头 -->
<view class="table-header">
<text class="col-name">{{ table.colName }}</text>
<text class="col-amount">实际收款(元)</text>
<text class="col-discount">折扣(元)</text>
</view>
<!-- 暂无数据 -->
<view v-if="!table.data || table.data.length === 0" class="empty-row">
<text>暂无数据</text>
</view>
<!-- 数据行 -->
<template v-else>
<view class="table-row" v-for="item in table.data" :key="item.name">
<text class="col-name">{{ item.name }}</text>
<text class="col-amount">{{ item.amount.toFixed(2) }}</text>
<text class="col-discount">{{ item.discount.toFixed(2) }}</text>
</view>
<!-- 汇总行 -->
<view class="table-row total-row">
<text class="col-name">汇总</text>
<text class="col-amount">{{ table.total.amount.toFixed(2) }}</text>
<text class="col-discount">{{ table.total.discount.toFixed(2) }}</text>
</view>
</template>
</view>
</view>
<!-- 底部占位 -->
<view class="bottom-placeholder"></view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { getVillageList,getFeeTypes,getTableList} from '../../api/api.js'
// ============ 筛选数据 ============
// 项目选项(从接口获取)
const projectOptions = ref([])
const selectedProject = ref('')
const selectedProjectLabel = ref('全部项目')
// 收费类型选项(从接口获取)
const feeTypeOptions = ref([])
const selectedFeeType = ref('')
const selectedFeeTypeLabel = ref('全部收费类型')
// 日期范围
const datetimerange = ref([])
// 下拉刷新触发状态
const refresherTriggered = ref(false)
// ============ 表格数据 ============
const tableConfigs = reactive([
{
title: '项目总览',
colName: '项目名称',
data: [],
total: { amount: 0, discount: 0 }
},
{
title: '收费类型总览',
colName: '收费类型',
data: [],
total: { amount: 0, discount: 0 }
},
{
title: '房屋类型总览',
colName: '房屋类型',
data: [],
total: { amount: 0, discount: 0 }
},
])
// ============ 获取小区列表 ============
const fetchVillageList = () => {
return getVillageList()
.then((res) => {
if (res.code === 200 && res.data) {
const list = res.data.map((item) => ({
value: String(item.id),
label: item.villageName
}))
projectOptions.value = [
{ value: '', label: '全部项目' },
...list
]
// 默认选中全部项目
selectedProject.value = ''
selectedProjectLabel.value = '全部项目'
}
})
.catch(() => {})
}
// ============ 获取收费类型列表 ============
const fetchFeeTypes = () => {
return getFeeTypes()
.then((res) => {
if (res.code === 200 && res.data) {
const list = res.data.map((item) => ({
value: String(item.id),
label: item.name
}))
feeTypeOptions.value = [
{ value: '', label: '全部收费类型' },
...list
]
selectedFeeType.value = ''
selectedFeeTypeLabel.value = '全部收费类型'
}
})
.catch(() => {})
}
// ============ 获取表格数据 ============
const fetchTableList = () => {
const params = {
pageNum: '1',
pageSize: '10'
}
if (selectedProject.value) {
params.villageId = selectedProject.value
}
if (selectedFeeType.value) {
params.chargeTypeId = selectedFeeType.value
}
if (datetimerange.value && datetimerange.value.length === 2) {
params.startDate = datetimerange.value[0]
params.endDate = datetimerange.value[1]
}
return getTableList(params)
.then((res) => {
if (res.code === 200 && res.data) {
// 项目总览
const { projectOverview, feeTypeOverview, houseTypeOverview } = res.data
if (projectOverview) {
tableConfigs[0].data = (projectOverview.items).map((item) => ({
name: item.name,
amount: Number(item.actualAmount),
discount: Number(item.discountAmount)
}))
tableConfigs[0].total = {
amount: Number(projectOverview.totalActualAmount),
discount: Number(projectOverview.totalDiscountAmount)
}
}
// 收费类型总览
if (feeTypeOverview) {
tableConfigs[1].data = (feeTypeOverview.items).map((item) => ({
name: item.name,
amount: Number(item.actualAmount),
discount: Number(item.discountAmount)
}))
tableConfigs[1].total = {
amount: Number(feeTypeOverview.totalActualAmount),
discount: Number(feeTypeOverview.totalDiscountAmount)
}
}
// 房屋类型总览
if (houseTypeOverview) {
tableConfigs[2].data = (houseTypeOverview.items).map((item) => ({
name: item.name,
amount: Number(item.actualAmount),
discount: Number(item.discountAmount)
}))
tableConfigs[2].total = {
amount: Number(houseTypeOverview.totalActualAmount),
discount: Number(houseTypeOverview.totalDiscountAmount)
}
}
}
})
.catch(() => {})
}
// ============ 生命周期 ============
onShow(() => {
fetchVillageList()
fetchFeeTypes()
fetchTableList()
})
// 下拉刷新
const onPullRefresh = () => {
refresherTriggered.value = true
Promise.allSettled([
fetchVillageList(),
fetchFeeTypes(),
fetchTableList()
]).finally(() => {
refresherTriggered.value = false
})
}
// ============ 事件处理 ============
const onProjectChange = (e) => {
const index = e.detail.value
const option = projectOptions.value[index]
selectedProject.value = option.value
selectedProjectLabel.value = option.label
}
const onFeeTypeChange = (e) => {
const index = e.detail.value
const option = feeTypeOptions.value[index]
selectedFeeType.value = option.value
selectedFeeTypeLabel.value = option.label
}
const onSearch = () => {
fetchTableList()
}
const onReset = () => {
selectedProject.value = ''
selectedProjectLabel.value = '全部项目'
selectedFeeType.value = ''
selectedFeeTypeLabel.value = '全部收费类型'
datetimerange.value = []
fetchTableList()
}
</script>
<style scoped lang="scss">
.page_padding {
background: linear-gradient(to left bottom, #e2efff 0%, #f9f9f9 50%);
padding: 0 30rpx;
position: relative;
flex: 1;
overflow: hidden;
box-sizing: border-box;
display: flex;
flex-direction: column;
height: 100vh;
// ===== 筛选区域(取消背景) =====
.filter-section {
padding: 24rpx 0 16rpx;
flex-shrink: 0;
.filter-row {
display: flex;
align-items: center;
.filter-item {
flex: 1;
min-width: 0;
& + .filter-item {
margin-left: 20rpx;
}
.filter-label {
display: flex;
align-items: center;
justify-content: space-between;
background: transparent;
border-radius: 12rpx;
padding: 16rpx 24rpx;
font-size: 26rpx;
color: #333333;
border: 2rpx solid #DCDFE6;
transition: all 0.2s;
.filter-arrow {
font-size: 20rpx;
color: #999;
margin-left: 8rpx;
}
&:active {
background: #f5f7fa;
}
}
}
}
// 日期选择器
.date-row {
margin-top: 16rpx;
.date-picker {
// 透明背景
}
}
// 搜索 & 重置按钮
.btn-row {
display: flex;
margin-top: 24rpx;
.reset-btn,
.search-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
height: 80rpx;
border-radius: 40rpx;
font-size: 28rpx;
font-weight: 500;
&:active {
opacity: 0.85;
transform: scale(0.98);
}
}
.reset-btn {
background: #ffffff;
color: #4D5DFA;
border: 2rpx solid #4D5DFA;
}
.search-btn {
margin-left: 24rpx;
background: linear-gradient(135deg, #4D5DFA 0%, #6C7AFF 100%);
color: #ffffff;
box-shadow: 0 8rpx 20rpx rgba(77, 93, 250, 0.25);
}
}
}
// ===== 表格滚动容器 =====
.table-scroll {
flex: 1;
overflow: hidden;
// 每个表格的外层包装
.table-wrapper {
margin-bottom: 32rpx;
// 标题独立于卡片外
.table-title {
font-size: 34rpx;
font-weight: 600;
color: #1a2332;
padding: 0 0 16rpx 8rpx;
letter-spacing: 2rpx;
}
// 卡片(仅包含表格内容,不含标题)
.table-card {
background: #ffffff;
border-radius: 24rpx;
padding: 24rpx 28rpx;
box-shadow: 0 6rpx 30rpx rgba(0, 0, 0, 0.05);
// 表头(添加背景色 #EBEDFF)
.table-header {
display: flex;
align-items: center;
padding: 16rpx 20rpx;
background-color: #EBEDFF;
border-radius: 16rpx 16rpx 0 0;
font-size: 26rpx;
color: #2c3a4b;
font-weight: 600;
.col-name {
flex: 2;
text-align: left;
}
.col-amount {
flex: 1.2;
text-align: right;
padding-right: 8rpx;
}
.col-discount {
flex: 1;
text-align: right;
padding-right: 4rpx;
}
}
// 数据行
.table-row {
display: flex;
align-items: center;
padding: 18rpx 20rpx;
font-size: 28rpx;
color: #666;
border-bottom: 1rpx solid #f7f8fa;
&:last-of-type {
border-bottom: none;
}
.col-name {
flex: 2;
text-align: left;
font-weight: 400;
color: #2c3a4b;
}
.col-amount {
flex: 1.2;
text-align: right;
padding-right: 8rpx;
font-weight: 500;
color: #1a2332;
}
.col-discount {
flex: 1;
text-align: right;
padding-right: 4rpx;
color: #666666;
font-weight: 500;
}
// 汇总行
&.total-row {
background: #f7f9fc;
border-radius: 0 0 12rpx 12rpx;
margin-top: 4rpx;
padding: 18rpx 20rpx;
font-weight: 600;
border-bottom: none;
.col-name {
color: #4D5DFA;
font-weight: 600;
}
.col-amount {
color: #4D5DFA;
font-weight: 600;
}
.col-discount {
color: #4D5DFA;
font-weight: 600;
}
}
}
// 隔行变色(数据行)
.table-row:not(.total-row):nth-child(even) {
background: #fafbfc;
border-radius: 0;
}
.empty-row {
display: flex;
justify-content: center;
align-items: center;
padding: 60rpx 0;
font-size: 26rpx;
color: #999;
}
}
}
.bottom-placeholder {
height: 40rpx;
}
}
}
// ===== 响应式适配 =====
@media screen and (max-width: 374px) {
.page_padding {
padding: 0 20rpx;
.filter-section {
padding: 16rpx 0 12rpx;
.filter-row {
.filter-item {
& + .filter-item {
margin-left: 12rpx;
}
.filter-label {
padding: 12rpx 16rpx;
font-size: 24rpx;
}
}
}
}
.table-scroll .table-wrapper {
.table-card {
padding: 16rpx 16rpx;
.table-header,
.table-row {
padding: 14rpx 16rpx;
font-size: 24rpx;
}
}
.table-title {
font-size: 30rpx;
padding-bottom: 12rpx;
}
}
}
}
@media screen and (min-width: 600px) {
.page_padding {
padding: 0 60rpx;
.filter-section {
padding: 30rpx 0 20rpx;
}
.table-scroll .table-wrapper {
.table-card {
padding: 32rpx 40rpx;
}
.table-title {
font-size: 38rpx;
}
}
}
}
// ===== uni-datetime-picker 透明背景 =====
:deep(.uni-date-x) {
background: transparent !important;
}
</style>