Files
property-resident-side/property-uniapp-project/pageSubPack/payment-list/arrearsPayment.vue
2026-06-01 11:17:04 +08:00

793 lines
16 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="contentStyle" style="">
<view class="status-bar"></view>
<uni-nav-bar :title="paymentItem.type || '缴费'" left-icon="left" @clickLeft="goBack"
backgroundColor="transparent" :border="false">
</uni-nav-bar>
<!-- 表单区域 -->
<scroll-view class="page" scroll-y="true">
<view class="container">
<!-- 1. 缴费详情 -->
<view v-if="currentStep === 1" class="pay-page">
<!-- 物业费缴费 -->
<!-- 金额展示 -->
<view class="amount-section" v-if="outstandingPayment">
<text class="amount-text"
:class="{ 'outstandingPaymentClass':outstandingPayment }">{{outstandingPayment?'-¥'+payAmount:'¥'+payAmount}}</text>
</view>
<view class="amount-section" v-else>
<text class="amount-text paymentClass">{{'¥'+payAmount}}</text>
</view>
<view style="border-bottom: 2rpx solid #ededed;margin:10rpx 0rpx;"></view>
<!-- 缴费信息 -->
<view class="info-section" style="">
<view>
<view class="info-item">
<text class="info-label">户号</text>
<text class="info-value">{{ paymentItem.deviceCode || '-' }}</text>
</view>
<view class="info-item">
<text class="info-label">户主</text>
<text class="info-value">{{ paymentItem.name || '-' }}</text>
</view>
<view class="info-item">
<text class="info-label">地址</text>
<text class="info-value">{{ paymentItem.fullAddress || '-' }}</text>
</view>
<!-- <view style="border-bottom: 2rpx solid #c7c7c7;padding-bottom:20rpx;"></view> -->
<!-- 缴费金额输入 -->
<view style="border-bottom: 2rpx solid #ededed;margin:10rpx 0rpx;"></view>
<view class="input-section" style="display: flex;">
<text class="amount-input" style="width: auto;margin: 0rpx 20rpx;">¥</text>
<input type="digit" v-model="payNum" class="amount-input" placeholder="点击输入缴费金额">
</input>
</view>
<view style="border-bottom: 2rpx solid #ededed;margin:10rpx 0rpx;"></view>
<view class="arrears-row" v-if="outstandingPayment==true">
<text class="arrears-text">当前欠费金额¥{{ arrearsAmount }}</text>
<text class="auto-fill-btn" @click="handleAutoFill">点击自动填入</text>
</view>
<view class="arrears-row" v-if="outstandingPayment==false">
<text class="arrears-text">暂未查询到欠费</text>
</view>
</view>
</view>
</view>
<!-- 2. 缴费成功页 -->
<view class="container" v-if="currentStep==2">
<!-- 成功图标 -->
<view class="success-icon">
<text class="icon-check"></text>
</view>
<!-- 主标题 -->
<view class="title">缴费成功</view>
<!-- 提示文案 -->
<view class="desc">
成功缴纳{{ paymentItem.type || '' }}费用 ¥{{ payNum || payAmount }}
</view>
</view>
</view>
</scroll-view>
<!-- 立即缴费按钮 -->
<view class="btn-section" v-if="currentStep === 1">
<button class="pay-btn" @click="handlePay">立即缴费</button>
</view>
<!-- 返回按钮 -->
<view class="btn-section" v-if="currentStep === 2">
<button class="pay-btn" @click="handleBackToList">返回生活缴费</button>
</view>
</view>
</template>
<script setup>
import {
ref,
reactive,
onMounted,
computed
} from 'vue'
import {
onShow,
onReady,
onReachBottom,
onLoad,
onUnload,
onPullDownRefresh
} from '@dcloudio/uni-app'
import {
getTopUp,
getToWxPay,
getopenIdByCode,
postSubscribeAuth
} from '/pageSubPack/api/apiSub.js'
// 响应式数据
const statusBarHeight = ref(20)
const outstandingPayment = ref(true)
const storageValue = uni.getStorageSync('arrearsPayment')
const paymentItem = reactive(typeof storageValue === 'object' && storageValue !== null ? storageValue : {})
const currentStep = ref(1)
const payAmount = ref('0.00')
const arrearsAmount = ref('0.00')
const payNum = ref('')
// 页面加载时初始化数据
onLoad(() => {
initPaymentData()
checkAllSubscribeStatus()
})
// 初始化缴费数据
const initPaymentData = () => {
if (paymentItem) {
arrearsAmount.value = Math.abs(paymentItem.balance || 0).toFixed(2)
payAmount.value = Math.abs(paymentItem.balance || 0)
outstandingPayment.value = paymentItem.balance < 0
}
}
// 自动填充欠费金额
const handleAutoFill = () => {
payNum.value = arrearsAmount.value
uni.vibrateShort({
type: 'light'
})
}
// 返回
const goBack = () => {
uni.redirectTo({
url: '/pageSubPack/payment-list/paymentIndex'
});
}
// 立即缴费
const handlePay = () => {
if (!payNum.value || Number(payNum.value) <= 0) {
uni.showToast({
title: '请输入正确的缴费金额',
icon: 'none'
})
return
}
requestPayOrder()
}
// 统一下单后后端返回的支付参数
const payInfo = ref({
appId: '', // 公众号/小程序ID
timeStamp: '', // 时间戳
nonceStr: '', // 随机串
package: '', // 数据包 如 prepay_id=xxx
signType: 'MD5', // 签名方式
paySign: '' // 签名
})
// 1. 先请求后端接口生成预支付订单
const requestPayOrder = async () => {
try {
uni.showLoading({
title: '发起支付中'
})
// // 获取 openid
// const loginRes = await wx.login()
// if (!loginRes.code) {
// uni.hideLoading()
// uni.showToast({
// title: '登录失败',
// icon: 'none'
// })
// return
// }
// // 用 code 换 openid
// const openIdRes = await getopenIdByCode({
// code: loginRes.code
// })
// if (openIdRes.code !== 200 || !openIdRes.data) {
// uni.hideLoading()
// uni.showToast({
// title: '获取openid失败',
// icon: 'none'
// })
// return
// }
// const openid = openIdRes.data
const openid = uni.getStorageSync('openid')
// 调用充值接口创建订单
console.log('1111111111', paymentItem);
const topUpRes = await getTopUp({
residentUserId: uni.getStorageSync('userId'),
rechargeAmount: payNum.value,
money: payNum.value || payAmount.value,
deviceCode: paymentItem.deviceCode,
// type: paymentItem.type == '物业费A' ? '2' : paymentItem.type == '物业费B' ? '1' : ''
type:'2'
})
uni.hideLoading()
const topUpData = topUpRes
console.log(topUpData);
if (topUpData.code !== 200) {
uni.showToast({
title: topUpData.msg || '下单失败',
icon: 'none'
})
return
}
// 调用微信支付接口获取支付参数
const payRes = await getToWxPay({
orderId: topUpData.data.orderId,
openid: openid,
// rechargeAmount: payNum.value || payAmount.value,
// type: paymentItem.type == '物业费A' ? '2' : paymentItem.type == '物业费B' ? '1' : ''
})
if (payRes.code !== 200) {
uni.showToast({
title: payRes.msg || '获取支付参数失败',
icon: 'none'
})
return
}
const payData = payRes.data
console.log('payData', payData);
// 赋值支付参数
payInfo.value = {
appId: payData.appId,
timeStamp: payData.timeStamp,
nonceStr: payData.nonceStr,
package: payData.package.startsWith('prepay_id=') ? payData.package : 'prepay_id=' + payData
.package,
signType: payData.signType,
paySign: payData.paySign
}
// 2. 调起微信支付
wx.requestPayment({
...payInfo.value,
success(res) {
console.log('支付成功', res)
currentStep.value = 2
},
fail(err) {
console.log('支付失败', err)
uni.showToast({
title: '支付取消或失败',
icon: 'none'
})
}
})
handleWaterElecSubscribe()
} catch (err) {
uni.hideLoading()
console.log('请求异常', err)
uni.showToast({
title: '支付请求异常',
icon: 'none'
})
}
}
// 【双模板ID】你提供的 物业费A+物业费B 催缴模板
const TEMPLATE_IDS = {
water: 'DowYnoGKNtl_eYycRCq-nt7DAoFjH7aVcGUQciZl1v0',
elec: 'PE7Neemv0z77JnBIPmb-Qg2Q3pJSUDGaILCryFCxmaw'
}
// 转成数组微信API要求
const TEMPLATE_ID_ARRAY = Object.values(TEMPLATE_IDS)
// 1. 检测 物业费A+物业费B 订阅状态
const checkAllSubscribeStatus = () => {
uni.getSetting({
withSubscriptions: true,
success: (res) => {
const itemSettings = res.subscriptionsSetting?.itemSettings || {}
console.log('物业费A订阅状态', itemSettings[TEMPLATE_IDS.water])
console.log('物业费B订阅状态', itemSettings[TEMPLATE_IDS.elec])
}
})
}
// 2. 点击开启订阅(主方法)
const handleWaterElecSubscribe = () => {
uni.getSetting({
withSubscriptions: true,
success: (res) => {
const itemSettings = res.subscriptionsSetting?.itemSettings || {}
const waterStatus = itemSettings[TEMPLATE_IDS.water]
const elecStatus = itemSettings[TEMPLATE_IDS.elec]
// 如果任意一个被永久拒绝 → 引导去设置
if ((waterStatus === 'reject' || waterStatus === 'ban') ||
(elecStatus === 'reject' || elecStatus === 'ban')) {
uni.showModal({
title: '开启消息通知',
content: '请前往设置页打开物业费A/物业费B提醒开关',
confirmText: '去设置',
success: (modalRes) => {
if (modalRes.confirm) uni.openSetting()
}
})
return
}
// 正常弹出授权框
requestMultiSubscribe()
}
})
}
// 3. 调用微信授权(根据类型传对应模板)
const requestMultiSubscribe = () => {
const tmplIds = paymentItem.type == '物业费A' ? [TEMPLATE_IDS.water] : paymentItem.type == '物业费B' ? [TEMPLATE_IDS.elec] : TEMPLATE_ID_ARRAY
uni.requestSubscribeMessage({
tmplIds: tmplIds,
success: async (res) => {
console.log('双模板授权结果:', res)
let successCount = 0
// 物业费A授权成功
if (res[TEMPLATE_IDS.water] === 'accept') successCount++
// 物业费B授权成功
if (res[TEMPLATE_IDS.elec] === 'accept') successCount++
if (successCount > 0) {
uni.showToast({
title: `已开启${successCount}项提醒`,
icon: 'success'
})
// 保存用户openid到后端
await saveUserSubscribe()
} else {
uni.showToast({
title: '已取消授权',
icon: 'none'
})
}
},
fail: () => {
uni.showToast({
title: '授权失败',
icon: 'none'
})
}
})
}
// 4. 传给后端:保存订阅信息
const saveUserSubscribe = async () => {
const res = await postSubscribeAuth({
openid: uni.getStorageSync('openid'),
templateIds: paymentItem.type == '物业费A' ? [TEMPLATE_IDS.water] : paymentItem.type == '物业费B' ? [TEMPLATE_IDS.elec] : TEMPLATE_ID_ARRAY,
// deviceCode: paymentItem.deviceCode,
// deviceType: paymentItem.type == '物业费A' ? '2' : paymentItem.type == '物业费B' ? '1' : ''
})
if (res.data == 200) {
console.log('111111111111111', res)
} else {
uni.showToast({
title: `${res.msg}`,
icon: 'none'
})
}
}
// 返回缴费列表
const handleBackToList = () => {
uni.redirectTo({
url: '/pageSubPack/payment-list/paymentIndex'
});
}
</script>
<style lang="scss">
page {
background-color: #f9f9f9;
}
</style>
<style scoped>
.contentStyle {
display: flex;
flex-direction: column;
height: 100vh;
/* 关键:占满屏幕高度 */
background-color: #f9f9f9;
}
.page {
flex: 1;
overflow-y: auto;
padding: 0px 40rpx;
box-sizing: border-box;
background-color: #
/* fff */
;
}
/* 1. 物业费详情页 */
.pay-page {
width: 100%;
/* min-height: 100vh; */
/* background-color: #fff; */
}
.amount-section {
padding: 60rpx 0;
text-align: center;
}
.amount-text {
font-size: 56rpx;
font-weight: 600;
}
.outstandingPaymentClass {
color: #E34D59;
}
.paymentClass {
color: #00aaff;
}
.info-section {
padding-top: 20rpx;
/* margin-bottom: 40rpx; */
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
height: 80rpx;
}
.info-label {
font-size: 30rpx;
color: #666;
}
.info-value {
font-size: 30rpx;
color: #333;
font-weight: 600;
}
.info-payAmountItem {
height: 100rpx;
line-height: 100rpx;
margin-bottom: 60rpx;
}
.info-payAmount {
font-size: 50rpx;
color: #333;
font-weight: 600;
}
.input-section {
/* padding: 030rpx; */
}
.amount-input {
width: 100%;
height: 100rpx;
font-size: 32rpx;
line-height: 100rpx;
border: none;
outline: none;
margin-right: 10rpx;
}
.btn-section {
padding: 20rpx 30rpx 40rpx;
display: flex;
gap: 20rpx;
}
.pay-btn {
width: 100%;
height: 100rpx;
line-height: 100rpx;
background-color: #007aff;
color: #fff;
border-radius: 16rpx;
font-size: 32rpx;
border: none;
outline: none;
}
/* 2. 支付密码弹窗 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
display: flex;
align-items: flex-end;
}
.password-modal {
width: 100%;
background-color: #fff;
border-radius: 24rpx 24rpx 0 0;
padding-bottom: 40rpx;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 100rpx;
padding: 0 30rpx;
border-bottom: 2rpx solid #f0f0f0;
}
.close-icon {
font-size: 44rpx;
width: 80rpx;
height: 100rpx;
display: flex;
align-items: center;
}
.modal-title {
font-size: 32rpx;
font-weight: 500;
color: #000;
}
.header-right {
width: 80rpx;
}
.pay-info {
padding: 40rpx 30rpx;
text-align: center;
}
.merchant-name {
display: block;
font-size: 32rpx;
color: #333;
margin-bottom: 20rpx;
}
.pay-amount {
font-size: 64rpx;
font-weight: 500;
color: #000;
}
.pay-way {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
border-bottom: 2rpx solid #f0f0f0;
}
.way-label {
font-size: 30rpx;
color: #666;
}
.way-content {
display: flex;
align-items: center;
}
.bank-icon {
width: 32rpx;
height: 32rpx;
margin-right: 6rpx;
}
.bank-name {
font-size: 30rpx;
color: #333;
margin-right: 16rpx;
}
.arrow-icon {
font-size: 28rpx;
color: #999;
}
/* 银行卡列表 */
.bank-list {
padding: 030rpx;
border-bottom: 2rpx solid #f0f0f0;
}
.bank-item {
display: flex;
align-items: center;
height: 100rpx;
border-bottom: 2rpx solid #f5f5f5;
}
.bank-item:last-child {
border-bottom: none;
}
.bank-item-icon {
width: 48rpx;
height: 48rpx;
margin-right: 20rpx;
}
.bank-item-name {
flex: 1;
font-size: 30rpx;
color: #333;
}
.check-icon {
font-size: 36rpx;
color: #007aff;
}
/* 密码输入框 */
.password-input {
display: flex;
justify-content: space-between;
padding: 40rpx 30rpx;
}
.password-item {
width: 100rpx;
height: 100rpx;
/* border: 2rpx solid #dcdcdc; */
background-color: #efefef;
border-radius: 8rpx;
display: flex;
align-items: center;
justify-content: center;
}
.password-dot {
width: 24rpx;
height: 24rpx;
background-color: #000;
border-radius: 50%;
}
/* 数字键盘 */
.keyboard {
width: 100%;
}
.keyboard-row {
display: flex;
}
.keyboard-item {
flex: 1;
height: 54px;
line-height: 54px;
text-align: center;
font-size: 48rpx;
color: #000;
border: 2rpx solid #f0f0f0;
background-color: #fff;
}
.keyboard-item:active {
background-color: #e5e5e5;
}
.keyboard-item.empty {
background-color: #f5f5f5;
border: none;
}
.keyboard-item.delete {
background-color: #f5f5f5;
border: none;
}
/* 3. 缴费成功页 */
/* 全局容器 */
.container {
display: flex;
flex-direction: column;
align-items: center;
}
/* 成功图标 */
.success-icon {
width: 220rpx;
height: 220rpx;
border-radius: 50%;
background-color: #1677ff;
display: flex;
align-items: center;
justify-content: center;
margin-top: 200rpx;
margin-bottom: 60rpx;
}
.icon-check {
color: #fff;
font-size: 130rpx;
font-weight: bold;
line-height: 1;
}
/* 主标题 */
.title {
font-size: 40rpx;
font-weight: bold;
color: #000;
margin-bottom: 20rpx;
}
/* 提示文案 */
.desc {
font-size: 32rpx;
color: #666;
line-height: 1.6;
text-align: center;
margin-bottom: 150rpx;
}
/* 核心:欠费金额行样式 */
.arrears-row {
display: flex;
align-items: center;
justify-content: space-between;
/* padding:30rpx; */
margin-bottom: 60rpx;
}
.arrears-text {
font-size: 28rpx;
color: #999;
}
.auto-fill-btn {
font-size: 28rpx;
color: #007aff;
/* 蓝色,匹配设计图 */
font-weight: 500;
padding: 10rpx 0;
}
.status-bar {
width: 100vw;
height: 46px;
}
</style>