修改生活缴费功能
This commit is contained in:
447
property-uniapp-project/pageSubPack/payment-list/billPayment.vue
Normal file
447
property-uniapp-project/pageSubPack/payment-list/billPayment.vue
Normal file
@@ -0,0 +1,447 @@
|
||||
<template>
|
||||
<view class="wrap">
|
||||
<scroll-view class="page" scroll-y @scrolltolower="onLoadMore">
|
||||
<view class="empty-state" v-if="!loading && billList.length === 0">
|
||||
<text class="empty-text">暂无账单数据</text>
|
||||
</view>
|
||||
|
||||
<up-collapse v-if="billList.length" :border="false">
|
||||
<up-collapse-item
|
||||
v-for="(item, index) in billList"
|
||||
:key="index"
|
||||
:name="String(index)"
|
||||
>
|
||||
<!-- 标题:勾选框 + 月份 -->
|
||||
<template #title>
|
||||
<view class="collapse-header">
|
||||
<view class="check-circle" :class="{ checked: item.selected }" @click.stop="toggleSelect(index)">
|
||||
<text v-if="item.selected" class="check-icon">✓</text>
|
||||
</view>
|
||||
<text class="month-text">{{ item.month }}</text>
|
||||
</view>
|
||||
</template>
|
||||
<!-- 右侧金额 -->
|
||||
<template #value>
|
||||
<text class="price-text">¥{{ item.totalAmount }}</text>
|
||||
</template>
|
||||
<!-- 展开内容:每条明细 -->
|
||||
<view class="collapse-body">
|
||||
<view class="detail-row fee-row" v-for="(detail, di) in item.details" :key="di">
|
||||
<text class="detail-label">{{ detail.name }}</text>
|
||||
<text class="detail-value">¥{{ detail.amount }}</text>
|
||||
</view>
|
||||
<view class="detail-row meta-row" v-if="item.unitNo || item.date">
|
||||
<text class="detail-label">{{ item.unitNo }}</text>
|
||||
<text class="detail-value">{{ item.date }}</text>
|
||||
</view>
|
||||
<view class="detail-row meta-row">
|
||||
<text class="detail-label">原金额:¥{{ item.totalAmount }}</text>
|
||||
<text class="detail-value">优惠:¥{{ formatNum(item.discount) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</up-collapse-item>
|
||||
</up-collapse>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="load-more-status" v-if="loadingMore">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
<view class="load-more-status" v-else-if="!hasMore && billList.length >= pageSize">
|
||||
<text class="no-more-text">— 没有更多了 —</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<view class="bottom-bar" v-if="billList.length">
|
||||
<view class="select-all" @click="toggleSelectAll">
|
||||
<view class="check-circle" :class="{ checked: isAllSelected }">
|
||||
<text v-if="isAllSelected" class="check-icon">✓</text>
|
||||
</view>
|
||||
<text class="select-all-text">全选</text>
|
||||
</view>
|
||||
<view class="total-box">
|
||||
<text class="total-label">合计:</text>
|
||||
<text class="total-price">¥{{ selectedTotal.toFixed(2) }}</text>
|
||||
</view>
|
||||
<button class="pay-btn" :disabled="!canPay" @click="handleBatchPay">立即缴费</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getTopUpDetailsByMonth } from '/pageSubPack/api/apiSub.js'
|
||||
|
||||
const loading = ref(false)
|
||||
const billList = ref([])
|
||||
const pageNum = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
const hasMore = ref(true)
|
||||
const loadingMore = ref(false)
|
||||
|
||||
// 从 paymentIndex 传来的数据中取 billResidentId
|
||||
const cacheItem = uni.getStorageSync('arrearsPayment') || {}
|
||||
const billResidentId = ref(cacheItem.billResidentId || cacheItem.id || '')
|
||||
|
||||
onLoad(() => {
|
||||
fetchBills()
|
||||
})
|
||||
|
||||
// 拉取按月汇总账单
|
||||
const fetchBills = async (isLoadMore = false) => {
|
||||
if (!billResidentId.value) return
|
||||
if (isLoadMore) {
|
||||
if (!hasMore.value || loadingMore.value) return
|
||||
pageNum.value++
|
||||
loadingMore.value = true
|
||||
} else {
|
||||
pageNum.value = 1
|
||||
hasMore.value = true
|
||||
loading.value = true
|
||||
}
|
||||
try {
|
||||
const res = await getTopUpDetailsByMonth({
|
||||
billResidentId: billResidentId.value,
|
||||
villageId: uni.getStorageSync('currentVillageId') || '',
|
||||
pageNum: pageNum.value,
|
||||
pageSize: pageSize.value
|
||||
})
|
||||
const rows = res.rows || []
|
||||
const typeNameMap = { '0': '物业管理服务费', '1': '车位管理费', '2': '生活垃圾处理费' }
|
||||
const mapped = rows.map(r => {
|
||||
const firstDetail = (r.details && r.details.length) ? r.details[0] : {}
|
||||
const restDetails = (r.details || []).slice(1)
|
||||
return {
|
||||
month: r.month || '',
|
||||
totalAmount: formatNum(r.totalAmount),
|
||||
discount: 0,
|
||||
selected: false,
|
||||
periodDisplay: firstDetail.periodDisplay || '',
|
||||
unitNo: cacheItem.unitNo || cacheItem.fullAddress || '',
|
||||
date: r.month || '',
|
||||
details: restDetails.map(d => ({
|
||||
name: typeNameMap[d.type] || d.name || '缴费费用',
|
||||
amount: formatNum(d.amount),
|
||||
id: d.id,
|
||||
payStatus: d.payStatus || '0'
|
||||
})),
|
||||
billIds: (r.details || []).map(d => d.id).filter(Boolean)
|
||||
}
|
||||
})
|
||||
|
||||
if (isLoadMore) {
|
||||
billList.value = [...billList.value, ...mapped]
|
||||
} else {
|
||||
billList.value = mapped
|
||||
}
|
||||
total.value = res.total || 0
|
||||
hasMore.value = billList.value.length < total.value
|
||||
} catch (e) {
|
||||
if (isLoadMore) pageNum.value--
|
||||
console.error('获取账单失败', e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
loadingMore.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const onLoadMore = () => {
|
||||
fetchBills(true)
|
||||
}
|
||||
|
||||
const formatNum = (val) => {
|
||||
return (Number(val || 0)).toFixed(2)
|
||||
}
|
||||
|
||||
const toggleSelect = (index) => {
|
||||
const current = billList.value[index]
|
||||
if (!current.selected) {
|
||||
// 选中:检查前面所有项是否都已选中,不允许跳过
|
||||
for (let i = 0; i < index; i++) {
|
||||
if (!billList.value[i].selected) {
|
||||
uni.showToast({ title: '请按顺序从第一个月开始选择', icon: 'none' })
|
||||
return
|
||||
}
|
||||
}
|
||||
billList.value[index].selected = true
|
||||
} else {
|
||||
// 取消选中:同时取消该项及后面所有已选中的
|
||||
for (let i = index; i < billList.value.length; i++) {
|
||||
billList.value[i].selected = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isAllSelected = computed(() =>
|
||||
billList.value.length > 0 && billList.value.every(m => m.selected)
|
||||
)
|
||||
|
||||
const toggleSelectAll = () => {
|
||||
const val = !isAllSelected.value
|
||||
billList.value.forEach(m => { m.selected = val })
|
||||
}
|
||||
|
||||
const selectedTotal = computed(() =>
|
||||
billList.value.filter(m => m.selected).reduce((sum, m) => sum + Number(m.totalAmount || 0), 0)
|
||||
)
|
||||
|
||||
const canPay = computed(() => {
|
||||
const selected = billList.value.filter(m => m.selected)
|
||||
if (!selected.length) return false
|
||||
// 必须从第一个开始且连续
|
||||
return billList.value[0].selected && selected.length > 0
|
||||
})
|
||||
|
||||
// 批量缴费
|
||||
const handleBatchPay = () => {
|
||||
const selected = billList.value.filter(m => m.selected)
|
||||
if (!selected.length) return
|
||||
|
||||
const allIds = selected.flatMap(m => m.billIds)
|
||||
const token = uni.getStorageSync('token') || ''
|
||||
const userId = uni.getStorageSync('userId') || ''
|
||||
|
||||
const query = [
|
||||
['acct', userId],
|
||||
['orderType', '10'],
|
||||
['billId', allIds.join(',')],
|
||||
['deviceType', '3'],
|
||||
['residentUserId', userId],
|
||||
['token', token]
|
||||
].map(([k, v]) => encodeURIComponent(k) + '=' + encodeURIComponent(v)).join('&')
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
uni.showLoading({ title: '加载中...' })
|
||||
plus.share.getServices((services) => {
|
||||
const wx = services.find(s => s.id === 'weixin')
|
||||
uni.hideLoading()
|
||||
if (!wx) return uni.showToast({ title: '未检测到微信客户端', icon: 'none' })
|
||||
if (!wx.launchMiniProgram) return uni.showToast({ title: '当前微信版本太低,不支持拉起小程序', icon: 'none' })
|
||||
wx.launchMiniProgram({
|
||||
id: 'gh_a765aef0172c',
|
||||
type: 1,
|
||||
path: 'pages/arrearsPayment/arrearsPayment?' + query,
|
||||
}, (res) => {
|
||||
console.log('小程序回调:', JSON.stringify(res))
|
||||
}, (err) => {
|
||||
console.error('拉起失败:', JSON.stringify(err))
|
||||
uni.showToast({ title: '拉起支付小程序失败', icon: 'none' })
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
page {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.page {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20rpx 30rpx;
|
||||
box-sizing: border-box;
|
||||
padding-bottom: 140rpx;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 30rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.load-more-status {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.no-more-text {
|
||||
font-size: 26rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
/* ---- 折叠面板 ---- */
|
||||
::v-deep .u-collapse-item {
|
||||
margin-bottom: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
::v-deep .u-collapse-item__content {
|
||||
background: #F5FAFF;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::v-deep .u-cell {
|
||||
padding: 20rpx 18rpx !important;
|
||||
}
|
||||
|
||||
/* 标题区 */
|
||||
.collapse-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.check-circle {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
border: 3rpx solid #ddd;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.check-circle.checked {
|
||||
background-color: #1677ff;
|
||||
border-color: #1677ff;
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.month-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
/* 金额 */
|
||||
.price-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #FF8F40;
|
||||
}
|
||||
|
||||
/* 展开内容 */
|
||||
.collapse-body {
|
||||
padding: 20rpx 24rpx 30rpx;
|
||||
margin: 0 24rpx;
|
||||
background: #F5FAFF;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 14rpx 0;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 明细费用名 - 深色 */
|
||||
.fee-row .detail-label,
|
||||
.fee-row .detail-value {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
/* 日期/原金额/优惠 - 浅色 */
|
||||
.meta-row .detail-label,
|
||||
.meta-row .detail-value {
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
/* ---- 底部操作栏 ---- */
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
background: #fff;
|
||||
box-shadow: 0 -2rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.select-all {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
flex-shrink: 0;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.select-all-text {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.total-box {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.total-label {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.total-price {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #FF8F40;
|
||||
}
|
||||
|
||||
.pay-btn {
|
||||
flex-shrink: 0;
|
||||
width: 180rpx;
|
||||
height: 72rpx;
|
||||
line-height: 72rpx;
|
||||
background-color: #1677ff;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
border-radius: 8rpx;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.pay-btn[disabled] {
|
||||
background-color: #ccc;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user