对接实收统计功能

This commit is contained in:
zhouyanli
2026-07-24 15:06:11 +08:00
parent 255a8791d0
commit 3c33d8a116
4 changed files with 582 additions and 44 deletions

View File

@@ -100,3 +100,13 @@ export const changePhone = data => {
export const authDeleteAccount = () => { export const authDeleteAccount = () => {
return get('/api/butler/auth/cancelAccount') return get('/api/butler/auth/cancelAccount')
} }
// =======实收统计========
// 收费类型下拉数据
export const getFeeTypes = () => {
return get('/api/butler/actualReceipt/feeTypes')
}
// 表格列表数据
export const getTableList = (data) => {
return get('/api/butler/actualReceipt/stats', data)
}

View File

@@ -1,55 +1,582 @@
<template> <template>
<view class="page_padding"> <view class="page_padding">
<view class="revenue_statistics_box flex-1 flex flex-col"> <!-- 状态栏 -->
<view class="nav flex items-center"> <view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
<view class="line"></view>
<text class="title">实收统计</text> <!-- 导航栏 -->
<uni-nav-bar title="实收统计" backgroundColor="transparent" :border="false" left-icon="left" @click-left="goBack" />
<!-- 筛选条件 -->
<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> </view>
<view class="content flex-1 flex flex-col items-center justify-center"> </picker>
<text class="placeholder">实收统计页面</text> </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> </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">
<!-- 循环渲染每个表格 -->
<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> </view>
</template> </template>
<script setup lang="ts"> <script setup>
import { ref } from 'vue' import { ref, reactive } from 'vue'
import { onShow } from '@dcloudio/uni-app' import { onShow } from '@dcloudio/uni-app'
import { getVillageList,getFeeTypes,getTableList} from '../../api/api.js'
onShow(() => { // ============ 系统信息 ============
// TODO: 加载实收统计数据 const statusBarHeight = ref(uni.getSystemInfoSync().statusBarHeight || 0)
})
// ============ 筛选数据 ============
// 项目选项(从接口获取)
const projectOptions = ref([])
const selectedProject = ref('')
const selectedProjectLabel = ref('全部项目')
// 收费类型选项(从接口获取)
const feeTypeOptions = ref([])
const selectedFeeType = ref('')
const selectedFeeTypeLabel = ref('全部收费类型')
// 日期范围
const datetimerange = ref([])
// ============ 表格数据 ============
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 = () => {
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 = () => {
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]
}
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 goBack = () => {
uni.navigateBack()
}
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> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.revenue_statistics_box { .page_padding {
background: #f9f9f9; background: linear-gradient(to left bottom, #e2efff 0%, #f9f9f9 50%);
padding: 20rpx 40rpx; padding: 0 30rpx;
position: relative; position: relative;
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
display: flex;
flex-direction: column;
height: 100vh;
.nav { .status-bar {
margin-bottom: 40rpx; width: 100%;
font-weight: 550; flex-shrink: 0;
font-size: 32rpx; }
// ===== 筛选区域(取消背景) =====
.filter-section {
padding: 24rpx 0 16rpx;
flex-shrink: 0;
.filter-row {
display: flex;
align-items: center;
gap: 20rpx;
.filter-item {
flex: 1;
min-width: 0;
.filter-label {
display: flex;
align-items: center;
justify-content: space-between;
background: transparent;
border-radius: 12rpx;
padding: 16rpx 24rpx;
font-size: 26rpx;
color: #333333; color: #333333;
line-height: 40rpx; border: 2rpx solid #DCDFE6;
transition: all 0.2s;
.line { .filter-arrow {
width: 6rpx; font-size: 20rpx;
height: 28rpx; color: #999;
background: #4d5dfa; margin-left: 8rpx;
margin-right: 10rpx; }
&:active {
background: #f5f7fa;
}
}
} }
} }
.content { // 日期选择器
.placeholder { .date-row {
margin-top: 16rpx;
.date-picker {
// 透明背景
}
}
// 搜索 & 重置按钮
.btn-row {
display: flex;
margin-top: 24rpx;
gap: 24rpx;
.reset-btn,
.search-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
height: 80rpx;
border-radius: 40rpx;
font-size: 28rpx; 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 {
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; 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 {
gap: 12rpx;
.filter-item {
.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> </style>

View File

@@ -129,7 +129,8 @@
{ {
"path": "revenue_statistics", "path": "revenue_statistics",
"style": { "style": {
"navigationBarTitleText": "实收统计" "navigationBarTitleText": "实收统计",
"navigationStyle": "custom"
} }
} }
] ]

View File

@@ -255,8 +255,8 @@ const functionList = [
action: 'goToReplacement_record' action: 'goToReplacement_record'
}, },
{ {
permKey: 'butler:revenue:statistics', permKey: 'butler:actualReceipt:stats',
icon: 'https://wuyeadmin.bugtc.com/images/imgs/revenue-statistics.png', icon: 'https://wuye.ckdzkj.com/images/imgs/revenue-statistics.png',
text: '实收统计', text: '实收统计',
action: 'goToRevenueStatistics' action: 'goToRevenueStatistics'
} }