@@ -1,790 +1,103 @@
< template >
< view class = "contentStyle" style = "" >
< view class = "contentStyle" >
< 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 v-if = "currentStep === 2 " class="container " >
<view class = "success-icon" > < text class = "icon-check" > ✓ < / text > < / view >
< view class = "title" > 缴费成功 < / view >
<!-- 提示文案 -- >
< view class = "desc" >
成功缴纳 { { paymentItem . type || '' } } 费用 ¥ { { payNum || payAmount } } 元
< / view >
< view class = "desc" > 成功缴纳 { { paymentItem . type || '' } } 费用 < / 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 >
< button class = "pay-btn" @click ="handleBackToList" > 返回 < / button >
< / view >
< / view >
< / template >
< script setup >
import {
ref ,
reactive ,
onMounted ,
computed
import { ref , reactive } from 'vue'
import { onShow , onLoad } from '@dcloudio/uni-app'
import { getAppPaymentData , clearAppPaymentData , buildPaymentItemFromApp } from '/pageSubPack/api/appLaunch.js'
} from 'vue'
import {
onShow ,
onReady ,
onReachBottom ,
onLoad ,
onUnload ,
onPullDownRefresh
} from '@dcloudio/uni-app'
const paymentItem = reactive ( { } )
const currentStep = ref ( 1 )
const navigatedToPayment = ref ( false )
import {
getTopUp ,
getToWxPay ,
getopenIdByCode ,
postSubscribeAuth
} from '/pageSubPack/api/apiSub.js'
onLoad ( ( ) => {
const data = loadPaymentItem ( )
Object . assign ( paymentItem , data )
} )
// 响应式数据
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 . switchTab ( {
url : '/pages/paymentIndex/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 == '水表' ? '2' : paymentItem.type == '电表' ? '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 == '水表' ? '2' : paymentItem.type == '电表' ? '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 )
showApiError ( err )
}
}
// 【双模板ID】你提供的 水表+电表 催缴模板
const TEMPLATE _IDS = {
water : 'DowYnoGKNtl_eYycRCq-nt7DAoFjH7aVcGUQciZl1v0' ,
elec : 'PE7Neemv0z77JnBIPmb-Qg2Q3pJSUDGaILCryFCxmaw'
}
// 转成数组( 微信API要求)
const TEMPLATE _ID _ARRAY = Object . values ( TEMPLATE _IDS )
// 1. 检测 水表+电表 订阅状态
const checkAllSubscribeStatus = ( ) => {
uni . getSetting ( {
withSubscriptions : true ,
success : ( res ) => {
const itemSettings = res . subscriptionsSetting ? . itemSettings || { }
console . log ( '水表订阅状态:' , itemSettings [ TEMPLATE _IDS . water ] )
console . log ( '电表订阅状态:' , 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 : '请前往设置页打开水表/电表提醒开关' ,
confirmText : '去设置' ,
success : ( modalRes ) => {
if ( modalRes . confirm ) uni . openSetting ( )
}
} )
return
}
// 正常弹出授权框
requestMultiSubscribe ( )
}
} )
}
// 3. 调用微信授权(根据类型传对应模板)
const requestMultiSubscribe = ( ) => {
const tmplIds = paymentItem . type == '水表' ? [ TEMPLATE _IDS . water ] : paymentItem . type == '电表' ? [ TEMPLATE _IDS . elec ] : TEMPLATE _ID _ARRAY
uni . requestSubscribeMessage ( {
tmplIds : tmplIds ,
success : async ( res ) => {
console . log ( '双模板授权结果:' , res )
let successCount = 0
// 水表授权成功
if ( res [ TEMPLATE _IDS . water ] === 'accept' ) successCount ++
// 电表授权成功
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'
} )
}
} ,
onShow ( ( ) => {
if ( navigatedToPayment . value ) {
navigatedToPayment . value = false
wx . navigateBackApp ( {
success : ( ) => console . log ( '已返回花开物业 App' ) ,
fail : ( ) => {
uni . showToast ( {
title : '授权失败' ,
icon : 'none'
} )
currentStep . value = 2
}
} )
}
} )
// 4. 传给后端:保存订阅信息
const saveUserSubscribe = async ( ) => {
const loadPaymentItem = ( ) => {
const appData = getAppPaymentData ( )
if ( appData ) {
console . log ( '检测到 App 跳转参数:' , appData )
clearAppPaymentData ( )
const item = buildPaymentItemFromApp ( appData )
uni . setStorageSync ( 'arrearsPayment' , item )
return item
}
const stored = uni . getStorageSync ( 'arrearsPayment' )
return ( typeof stored === 'object' && stored !== null ) ? stored : { }
}
const res = await postSubscribeAuth ( {
openid : uni . getStorageSync ( 'openid' ) ,
templateIds : paymentItem . type == '水表' ? [ TEMPLATE _IDS . water ] : paymentItem . type == '电表' ? [ TEMPLATE _IDS . elec ] : TEMPLATE _ID _ARRAY ,
// deviceCode: paymentItem.deviceCode,
// deviceType: paymentItem.type == '水表' ? '2' : paymentItem.type == '电表' ? '1' : ''
} )
if ( res . data == 200 ) {
console . log ( '111111111111111: ' , res )
} else {
uni . showToast ( {
title : ` ${ res . msg } ` ,
icon : 'none'
} )
const goBack = ( ) => uni . redirectTo ( { url : '/pages/paymentIndex/paymentIndex' } )
const handlePay = ( ) => {
navigatedToPayment . value = true
wx . navigateToMiniProgram ( {
appId : 'wxc0189436c58b59cd' ,
path : 'pages/pay/pay' ,
extraData : { ... paymentItem } ,
envVersion : 'trial' ,
success : ( ) => console . log ( '已跳转到日照银行支付小程序' ) ,
fail : ( err ) => {
navigatedToPayment . value = false
console . error ( '跳转日照银行支付失败' , err )
uni . showToast ( { title : '拉起支付失败' , icon : 'none' } )
}
} )
}
}
// 返回缴费列表
const handleBackToList = ( ) => {
uni . switchTab ( {
url : '/pages/paymentIndex/paymentIndex'
} ) ;
}
const handleBackToList = ( ) => uni . redirectTo ( { url : '/pages/paymentIndex/paymentIndex' } )
< / script >
< style lang = "scss" >
page {
background - color : # f9f9f9 ;
}
page { background - color : # f9f9f9 ; }
< / style >
< style scoped >
. contentStyle {
display : flex ;
flex - dir ection: column ;
height : 100 vh ;
/* 关键:占满屏幕高度 */
background - color : # f9f9f9 ;
}
. page {
flex : 1 ;
overflow - y : auto ;
padding : 0 px 40 rpx ;
box - sizing : border - box ;
background - color : #
/* fff */
;
}
/* 1. 物业费详情页 */
. pay - page {
width : 100 % ;
/* min-height: 100vh; */
/* background-color: #fff; */
}
. amount - section {
padding : 60 rpx 0 ;
text - align : center ;
}
. amount - text {
font - size : 56 rpx ;
font - weight : 600 ;
}
. outstandingPaymentClass {
color : # E34D59 ;
}
. paymentClass {
color : # 00 aaff ;
}
. info - section {
padding - top : 20 rpx ;
/* margin-bottom: 40rpx; */
}
. info - item {
display : flex ;
justify - content : space - between ;
align - items : center ;
height : 80 rpx ;
}
. info - label {
font - size : 30 rpx ;
color : # 666 ;
}
. info - value {
font - size : 30 rpx ;
color : # 333 ;
font - weight : 600 ;
}
. info - payAmountItem {
height : 100 rpx ;
line - height : 100 rpx ;
margin - bottom : 60 rpx ;
}
. info - payAmount {
font - size : 50 rpx ;
color : # 333 ;
font - weight : 600 ;
}
. input - section {
/* padding: 030rpx; */
}
. amount - input {
width : 100 % ;
height : 100 rpx ;
font - size : 32 rpx ;
line - height : 100 rpx ;
border : none ;
outline : none ;
margin - right : 10 rpx ;
}
. btn - section {
padding : 20 rpx 30 rpx 40 rpx ;
display : flex ;
gap : 20 rpx ;
}
. pay - btn {
width : 100 % ;
height : 100 rpx ;
line - height : 100 rpx ;
background - color : # 007 aff ;
color : # fff ;
border - radius : 16 rpx ;
font - size : 32 rpx ;
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 : 24 rpx 24 rpx 0 0 ;
padding - bottom : 40 rpx ;
}
. modal - header {
display : flex ;
align - items : center ;
justify - content : space - between ;
height : 100 rpx ;
padding : 0 30 rpx ;
border - bottom : 2 rpx solid # f0f0f0 ;
}
. close - icon {
font - size : 44 rpx ;
width : 80 rpx ;
height : 100 rpx ;
display : flex ;
align - items : center ;
}
. modal - title {
font - size : 32 rpx ;
font - weight : 500 ;
color : # 000 ;
}
. header - right {
width : 80 rpx ;
}
. pay - info {
padding : 40 rpx 30 rpx ;
text - align : center ;
}
. merchant - name {
display : block ;
font - size : 32 rpx ;
color : # 333 ;
margin - bottom : 20 rpx ;
}
. pay - amount {
font - size : 64 rpx ;
font - weight : 500 ;
color : # 000 ;
}
. pay - way {
display : flex ;
align - items : center ;
justify - content : space - between ;
padding : 30 rpx ;
border - bottom : 2 rpx solid # f0f0f0 ;
}
. way - label {
font - size : 30 rpx ;
color : # 666 ;
}
. way - content {
display : flex ;
align - items : center ;
}
. bank - icon {
width : 32 rpx ;
height : 32 rpx ;
margin - right : 6 rpx ;
}
. bank - name {
font - size : 30 rpx ;
color : # 333 ;
margin - right : 16 rpx ;
}
. arrow - icon {
font - size : 28 rpx ;
color : # 999 ;
}
/* 银行卡列表 */
. bank - list {
padding : 030 rpx ;
border - bottom : 2 rpx solid # f0f0f0 ;
}
. bank - item {
display : flex ;
align - items : center ;
height : 100 rpx ;
border - bottom : 2 rpx solid # f5f5f5 ;
}
. bank - item : last - child {
border - bottom : none ;
}
. bank - item - icon {
width : 48 rpx ;
height : 48 rpx ;
margin - right : 20 rpx ;
}
. bank - item - name {
flex : 1 ;
font - size : 30 rpx ;
color : # 333 ;
}
. check - icon {
font - size : 36 rpx ;
color : # 007 aff ;
}
/* 密码输入框 */
. password - input {
display : flex ;
justify - content : space - between ;
padding : 40 rpx 30 rpx ;
}
. password - item {
width : 100 rpx ;
height : 100 rpx ;
/* border: 2rpx solid #dcdcdc; */
background - color : # efefef ;
border - radius : 8 rpx ;
display : flex ;
align - items : center ;
justify - content : center ;
}
. password - dot {
width : 24 rpx ;
height : 24 rpx ;
background - color : # 000 ;
border - radius : 50 % ;
}
/* 数字键盘 */
. keyboard {
width : 100 % ;
}
. keyboard - row {
display : flex ;
}
. keyboard - item {
flex : 1 ;
height : 54 px ;
line - height : 54 px ;
text - align : center ;
font - size : 48 rpx ;
color : # 000 ;
border : 2 rpx 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 : 220 rpx ;
height : 220 rpx ;
border - radius : 50 % ;
background - color : # 1677 ff ;
display : flex ;
align - items : center ;
justify - content : center ;
margin - top : 200 rpx ;
margin - bottom : 60 rpx ;
}
. icon - check {
color : # fff ;
font - size : 130 rpx ;
font - weight : bold ;
line - height : 1 ;
}
/* 主标题 */
. title {
font - size : 40 rpx ;
font - weight : bold ;
color : # 000 ;
margin - bottom : 20 rpx ;
}
/* 提示文案 */
. desc {
font - size : 32 rpx ;
color : # 666 ;
line - height : 1.6 ;
text - align : center ;
margin - bottom : 150 rpx ;
}
/* 核心:欠费金额行样式 */
. arrears - row {
display : flex ;
align - items : center ;
justify - content : space - between ;
/* padding:30rpx; */
margin - bottom : 60 rpx ;
}
. arrears - text {
font - size : 28 rpx ;
color : # 999 ;
}
. auto - fill - btn {
font - size : 28 rpx ;
color : # 007 aff ;
/* 蓝色,匹配设计图 */
font - weight : 500 ;
padding : 10 rpx 0 ;
}
. status - bar {
width : 100 vw ;
height : 46 px ;
}
< / style >
. contentStyle { display : flex ; flex - direction : column ; height : 100 vh ; background - color : # f9f9f9 ; }
. page { flex : 1 ; overflow - y : auto ; padding : 0 40 rpx ; box - sizing : border - box ; }
. btn - s ection { padding : 20 rpx 30 rpx 40 rpx ; }
. pay - btn { width : 100 % ; height : 100 rpx ; line - height : 100 rpx ; background - color : # 007 aff ; color : # fff ; border - radius : 16 rpx ; font - size : 32 rpx ; border : none ; }
. container { display : flex ; flex - direction : column ; align - items : center ; }
. success - icon { width : 220 rpx ; height : 220 rpx ; border - radius : 50 % ; background - color : # 1677 ff ; display : flex ; align - items : center ; justify - content : center ; margin - top : 200 rpx ; margin - bottom : 60 rpx ; }
. icon - check { color : # fff ; font - size : 130 rpx ; font - weight : bold ; }
. title { font - size : 40 rpx ; font - weight : bold ; color : # 000 ; margin - bottom : 20 rpx ; }
. desc { font - size : 32 rpx ; color : # 666 ; text - align : center ; margin - bottom : 150 rpx ; }
. status - bar { width : 100 vw ; height : 46 px ; }
< / style >