对接银联和日照银行分开页面
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
"name" : "花开物业",
|
||||
"appid" : "__UNI__29C3D60",
|
||||
"description" : "智慧社区居民端面向小区业主使用,支持注册、验证码、账号密码多种登录方式,提供密码找回功能,账号使用安全便捷。支持多小区切换,首页设有轮播 banner、社区公告,集成远程开门、生活缴费、在线报修、访客邀请、社区活动常用服务。全服务板块统一涵盖门禁开门、线上缴费、报修申报、访客预约、投诉反馈、问卷调研、活动报名、社区公示、一键联系物业功能。个人中心可管理房屋、住户、车位、车辆信息,支持更换手机号、修改密码、消息通知设置、版本更新及账号退出,一站式满足业主居家生活、社区服务、房产车辆管理需求,实现社区生活数字化、便捷化。",
|
||||
"versionName" : "1.2.5",
|
||||
"versionCode" : 139,
|
||||
"versionName" : "1.2.7",
|
||||
"versionCode" : 141,
|
||||
"transformPx" : false,
|
||||
"uniCloud" : {
|
||||
"provider" : "aliyun",
|
||||
@@ -57,7 +57,7 @@
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>"
|
||||
],
|
||||
"schemes" : "",
|
||||
"schemes" : "myapppay",
|
||||
"targetSdkVersion" : 30
|
||||
},
|
||||
/* ios打包配置 */
|
||||
@@ -120,7 +120,8 @@
|
||||
"__platform__" : [ "ios", "android" ]
|
||||
},
|
||||
"weixin" : {
|
||||
"__platform__" : [ "ios", "android" ]
|
||||
"__platform__" : [ "ios", "android" ],
|
||||
"appid" : "wx84b02b2ec80715f4"
|
||||
}
|
||||
},
|
||||
"oauth" : {
|
||||
|
||||
@@ -180,7 +180,7 @@ export const getopenIdByCode = (data) => {
|
||||
|
||||
// 银联支付下单
|
||||
export const getUnionPayTn = (data) => {
|
||||
return post('/api/topUp/unionPay/getTn', data)
|
||||
return post('/api/unionPay/getTn', data)
|
||||
}
|
||||
// 获取日照银行小程序拉起参数
|
||||
export const getBankMiniProgramParams = (data) => {
|
||||
|
||||
@@ -2,8 +2,8 @@ import { navigateToLogin } from '@/common/checkLogin.js'
|
||||
|
||||
let baseUrl = '';
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
baseUrl = 'http://192.168.1.222:8080'; // 开发环境
|
||||
// baseUrl = "https://during-immovable-eats.ngrok-free.dev";
|
||||
baseUrl = 'http://192.168.0.222:8080'; // 开发环境
|
||||
// baseUrl = 'https://wuyeapi.ckdzkj.com';
|
||||
} else {
|
||||
baseUrl = 'https://wuyeapi.ckdzkj.com'; // 生产环境
|
||||
// baseUrl = 'https://during-immovable-eats.ngrok-free.dev';
|
||||
|
||||
@@ -0,0 +1,700 @@
|
||||
<template>
|
||||
<view class="contentStyle" style="">
|
||||
<view class="status-bar"></view>
|
||||
<uni-nav-bar :title="switchStatus(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"
|
||||
:value="payNum"
|
||||
@input="(e) => payNum = e.detail.value"
|
||||
class="amount-input"
|
||||
placeholder="点击输入缴费金额"
|
||||
/>
|
||||
</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>
|
||||
</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">
|
||||
成功缴纳{{ switchStatus(paymentItem.type) || '' }}费用 ¥{{ arrearsAmount || 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
|
||||
} from 'vue'
|
||||
import {
|
||||
onShow,
|
||||
onLoad
|
||||
} from '@dcloudio/uni-app'
|
||||
import {getSelectByDeviceCode} from '@/pageSubPack/api/apiSub.js'
|
||||
|
||||
|
||||
// 响应式数据
|
||||
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')
|
||||
|
||||
// 页面加载时初始化数据
|
||||
onLoad(() => {
|
||||
initPaymentData()
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
getBalance()
|
||||
})
|
||||
|
||||
// 初始化缴费数据
|
||||
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 getBalance = () => {
|
||||
if (!paymentItem.deviceCode) return
|
||||
const deviceTypeMap = { '电费': '1', '水费': '2' }
|
||||
getSelectByDeviceCode({
|
||||
deviceCode: paymentItem.deviceCode,
|
||||
type: deviceTypeMap[paymentItem.type]
|
||||
}).then(res => {
|
||||
const data = res.data
|
||||
if (data && data.balance !== undefined && data.balance !== null) {
|
||||
arrearsAmount.value = Math.abs(data.balance || 0).toFixed(2)
|
||||
payAmount.value = Math.abs(data.balance || 0)
|
||||
outstandingPayment.value = data.balance < 0
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log('查询余额失败', err)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 返回
|
||||
const goBack = () => {
|
||||
uni.navigateBack();
|
||||
}
|
||||
|
||||
// 立即缴费 — 通过微信拉起日照银行小程序进行支付
|
||||
const handlePay = () => {
|
||||
const amount = arrearsAmount.value
|
||||
|
||||
try {
|
||||
uni.showLoading({ title: '加载中...' })
|
||||
|
||||
// 设备类型映射:电费→1(电表),水费→2(水表)
|
||||
const deviceTypeMap = { '电费': '1', '水费': '2' }
|
||||
const mappedDeviceType = deviceTypeMap[paymentItem.type] || paymentItem.deviceType || ''
|
||||
|
||||
// 直接从本地参数拼 path 查询字符串传给支付小程序,不走接口
|
||||
const token = uni.getStorageSync('token') || ''
|
||||
const params = {
|
||||
acct: uni.getStorageSync('userId'),
|
||||
orderType: '20',
|
||||
deviceCode: paymentItem.deviceCode || '',
|
||||
deviceType: mappedDeviceType,
|
||||
billId: paymentItem.billId || '',
|
||||
residentUserId: uni.getStorageSync('userId'),
|
||||
// goodName: paymentItem.name || switchStatus(paymentItem.type) || '',
|
||||
// remark: '服务费',
|
||||
token: token
|
||||
}
|
||||
const queryString = Object.keys(params).map(k =>
|
||||
encodeURIComponent(k) + '=' + encodeURIComponent(params[k])
|
||||
).join('&')
|
||||
|
||||
uni.hideLoading()
|
||||
console.log('传入中间小程序的path参数:', queryString)
|
||||
// #ifdef APP-PLUS
|
||||
// 原生App:通过微信SDK拉起小程序
|
||||
plus.share.getServices((services) => {
|
||||
const weixin = services.find(s => s.id === 'weixin')
|
||||
if (!weixin) {
|
||||
uni.showToast({ title: '未检测到微信客户端', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!weixin.launchMiniProgram) {
|
||||
uni.showToast({ title: '当前微信版本太低,不支持拉起小程序', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
weixin.launchMiniProgram({
|
||||
id: 'gh_a765aef0172c',
|
||||
type: 2, // 0 正式版 / 1开发版 / 2体验版
|
||||
path: 'pages/arrearsPayment/arrearsPayment?' + queryString,
|
||||
}, (res) => {
|
||||
console.log('回调---回调结果(App)', JSON.stringify(res))
|
||||
}, (err) => {
|
||||
console.error('err---拉起失败(App)', JSON.stringify(err))
|
||||
uni.showToast({ title: '拉起支付小程序失败', icon: 'none' })
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
} catch (err) {
|
||||
uni.hideLoading()
|
||||
console.error('拉起支付小程序异常', err)
|
||||
uni.showToast({ title: '请求异常,请重试', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
// 缴费成功推送通知
|
||||
const sendPaySuccessPush = () => {
|
||||
const userId = uni.getStorageSync('userId')
|
||||
if (userId) {
|
||||
getPushSingle({
|
||||
userId: userId,
|
||||
channel: 'resident',
|
||||
title: '缴费成功',
|
||||
content: `您已成功缴纳${switchStatus(paymentItem.type) || ''}费用 ¥${arrearsAmount.value || payAmount.value}元`
|
||||
}).catch(err => {
|
||||
console.log('推送发送失败', err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 返回缴费列表
|
||||
const handleBackToList = () => {
|
||||
uni.navigateBack();
|
||||
}
|
||||
|
||||
// 类型码转中文名展示
|
||||
const switchStatus = (type) => {
|
||||
const typeMap = {
|
||||
'0': '物业费',
|
||||
'1': '车位费',
|
||||
'2': '垃圾处理费'
|
||||
}
|
||||
return typeMap[type] || type
|
||||
}
|
||||
</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;
|
||||
}
|
||||
|
||||
/* 支付方式选择 */
|
||||
.pay-method-section {
|
||||
margin-top: 30rpx;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
.pay-method-title {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
margin-bottom: 20rpx;
|
||||
display: block;
|
||||
}
|
||||
.pay-method-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
.pay-method-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 30rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
border: 2rpx solid #ededed;
|
||||
}
|
||||
.pay-method-item.active {
|
||||
border-color: #1677ff;
|
||||
background: #f0f7ff;
|
||||
}
|
||||
.pay-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
.pay-name {
|
||||
flex: 1;
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
}
|
||||
.pay-check {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border-radius: 50%;
|
||||
background: #1677ff;
|
||||
position: relative;
|
||||
}
|
||||
.pay-check::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 10rpx;
|
||||
top: 6rpx;
|
||||
width: 8rpx;
|
||||
height: 14rpx;
|
||||
border: 2rpx solid #fff;
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
/* 核心:欠费金额行样式 */
|
||||
.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>
|
||||
@@ -0,0 +1,464 @@
|
||||
<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, onShow, onPullDownRefresh } 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 || '')
|
||||
|
||||
const isFirstShow = ref(true)
|
||||
|
||||
onLoad(() => {
|
||||
fetchBills()
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
// 首次进入由 onLoad 拉取,避免重复请求
|
||||
if (isFirstShow.value) {
|
||||
isFirstShow.value = false
|
||||
return
|
||||
}
|
||||
fetchBills()
|
||||
})
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
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
|
||||
uni.stopPullDownRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
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'],
|
||||
['billIds', allIds.join(',')],
|
||||
// ['deviceType', '3'],
|
||||
['residentUserId', userId],
|
||||
['token', token]
|
||||
].map(([k, v]) => encodeURIComponent(k) + '=' + encodeURIComponent(v)).join('&')
|
||||
console.log('往小程序传递的参数',query)
|
||||
|
||||
// #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: 0,//0正式版,1开发版,2体验版
|
||||
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>
|
||||
@@ -210,44 +210,31 @@ const canPay = computed(() => {
|
||||
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 allIds = selected.flatMap(m => m.billIds).filter(Boolean)
|
||||
if (!allIds.length) {
|
||||
uni.showToast({ title: '账单信息异常,请稍后重试', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const userId = uni.getStorageSync('userId') || ''
|
||||
|
||||
const query = [
|
||||
['acct', userId],
|
||||
['orderType', '10'],
|
||||
['billIds', allIds.join(',')],
|
||||
// ['deviceType', '3'],
|
||||
['residentUserId', userId],
|
||||
['token', token]
|
||||
].map(([k, v]) => encodeURIComponent(k) + '=' + encodeURIComponent(v)).join('&')
|
||||
console.log('往小程序传递的参数',query)
|
||||
|
||||
// #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: 0,//0正式版,1开发版,2体验版
|
||||
path: 'pages/arrearsPayment/arrearsPayment?' + query,
|
||||
}, (res) => {
|
||||
console.log('小程序回调:', JSON.stringify(res))
|
||||
}, (err) => {
|
||||
console.error('拉起失败:', JSON.stringify(err))
|
||||
uni.showToast({ title: '拉起支付小程序失败', icon: 'none' })
|
||||
})
|
||||
// 收银台页面(yinlianPayTn)从 payData 中取参数
|
||||
uni.setStorageSync('payData', {
|
||||
...cacheItem,
|
||||
amount: selectedTotal.value.toFixed(2),
|
||||
billIds: allIds,
|
||||
type: cacheItem.type || cacheItem.name || '物业费',
|
||||
residentUserId: userId
|
||||
})
|
||||
|
||||
uni.navigateTo({
|
||||
url: '/pageSubPack/payment-list/yinlianPayTn'
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -228,7 +228,8 @@ const goToBill = (item) => {
|
||||
})
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: '/pageSubPack/payment-list/billPayment'
|
||||
url: '/pageSubPack/payment-list/billPay-yidang'
|
||||
// url: '/pageSubPack/payment-list/billPayment'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,11 +50,16 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onUnmounted } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||
import { getUnionPayTn } from '/pageSubPack/api/apiSub.js'
|
||||
import { getPushSingle } from '/pageSubPack/api/api.js'
|
||||
|
||||
// 支付宝和微信支付
|
||||
// 银联商务全民付为 App 原生 UTS 插件,不支持小程序/H5,必须条件编译引入,否则其他端构建失败
|
||||
// #ifdef APP-PLUS
|
||||
import { upPayWithChannel, upPayRegistry, setPayEnviroment } from '@/uni_modules/xh-uppay'
|
||||
// #endif
|
||||
|
||||
// 银联商务全民付收银台:微信 / 支付宝
|
||||
|
||||
const amount = ref('0.00')
|
||||
const countdownTime = ref('10:00')
|
||||
@@ -92,7 +97,6 @@
|
||||
}
|
||||
|
||||
const selectedType = ref('wx')
|
||||
const goBack = () => uni.navigateBack()
|
||||
|
||||
const selectPayType = (type) => {
|
||||
selectedType.value = type
|
||||
@@ -101,28 +105,69 @@
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 银联商务渠道号:01 微信原生支付 / 02 支付宝支付 / 03 支付宝小程序 / 11 支付宝2.0
|
||||
// 渠道号必须与后端下单的产品对应(响应里的 targetSys),对不上会静默失败:
|
||||
// 微信 targetSys = WXPay → 01
|
||||
// 支付宝 targetSys = Alipay 2.0 → 11(插件 Android 端 '11' 分支是空壳,未实现)
|
||||
// 支付宝 targetSys = 支付宝小程序 → 03(需后端改下这个小程序产品才会生效)
|
||||
const CHANNEL_MAP = { wx: '01', alipay: '03' }
|
||||
|
||||
// 传给后端的支付方式,沿用原有枚举 0=微信 / 1=支付宝
|
||||
const PAY_TYPE_MAP = { wx: '0', alipay: '1' }
|
||||
|
||||
// 银联环境:0 正式 / 1 测试。必须与后端下单时所用的银联环境一致,
|
||||
// 这是后端的配置项、不是前端构建环境;联调报「交易不存在/签名错误」多半是这里对不上。
|
||||
// 注意:这里只对 iOS 生效,Android 要改插件 utssdk/app-android/AndroidManifest.xml 的 CurrentEnvironment(PROD/TEST)。
|
||||
const UNIONPAY_ENV = process.env.NODE_ENV === 'development' ? 1 : 0
|
||||
|
||||
// 以下常量只在 iOS 生效(Android 的 upPayRegistry 是空实现),且每一项都必须与
|
||||
// xh-uppay/utssdk/app-ios/info.plist 里 CFBundleURLTypes 的取值完全一致,
|
||||
// 否则 iOS 上支付完成后回不到本应用。
|
||||
const WX_APPID = 'wx84b02b2ec80715f4' // 需同步改 info.plist 的 weixin scheme(现在是 demo 值)
|
||||
// 存疑:暂用项目里微信分享的 Universal Link,但银联商务支付链路可能要求用他们备案的域名,需与其确认
|
||||
const WX_UNIVERSAL_LINK = 'https://verification.ckdzkj.com/uni-universallinks/__UNI__29C3D60/'
|
||||
const SCHEME_CLOUD = 'unifyPayDemo' // info.plist 中 ucloud(云闪付回调,暂不用云闪付)
|
||||
const SCHEME_ALI_MINI = 'qmfpppay' // info.plist 中 aliminipay(支付宝小程序回调)
|
||||
const SCHEME_ALI = 'umsunifyalisafepay' // info.plist 中 alisafepay(支付宝回调)
|
||||
|
||||
const handlePayClick = () => {
|
||||
handlePay()
|
||||
}
|
||||
|
||||
// 账单明细 id:接口要求 string,多个账单逗号拼接,兼容单个 billId 的旧场景
|
||||
const toBillIds = (billIds, billId) => {
|
||||
if (Array.isArray(billIds) && billIds.length) {
|
||||
return billIds.filter(Boolean).join(',')
|
||||
}
|
||||
if (typeof billIds === 'string' && billIds) {
|
||||
return billIds
|
||||
}
|
||||
return billId ? String(billId) : ''
|
||||
}
|
||||
|
||||
const handlePay = async () => {
|
||||
try {
|
||||
uni.showLoading({ title: '发起支付中' })
|
||||
const payTypeMap = { wx: '0', alipay: '1' }
|
||||
const backendPayType = payTypeMap[selectedType.value] || '1'
|
||||
|
||||
const res = await getUnionPayTn({
|
||||
amount: amount.value,
|
||||
const billIds = toBillIds(payData.value.billIds, payData.value.billId)
|
||||
if (!billIds) {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '账单信息异常,请返回重新选择', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// deviceCode/deviceType 仅水电表充值传;无值即物业费/停车费/垃圾处理费
|
||||
const payload = {
|
||||
amount: Number(amount.value),
|
||||
orderDesc: (payData.value.type || '缴费') + '缴费',
|
||||
residentUserId: uni.getStorageSync('userId'),
|
||||
payType: backendPayType,
|
||||
deviceCode: payData.value.deviceCode || '',
|
||||
deviceType: '2',
|
||||
chargeTypeId: payData.value.chargeTypeId,
|
||||
chargeItemId: payData.value.chargeItemId,
|
||||
billId: payData.value.billId,
|
||||
houseId: payData.value.houseId
|
||||
})
|
||||
residentUserId: payData.value.residentUserId || uni.getStorageSync('userId'),
|
||||
payType: PAY_TYPE_MAP[selectedType.value],
|
||||
billIds
|
||||
}
|
||||
if (payData.value.deviceCode) payload.deviceCode = payData.value.deviceCode
|
||||
if (payData.value.deviceType) payload.deviceType = payData.value.deviceType
|
||||
|
||||
const res = await getUnionPayTn(payload)
|
||||
console.log('支付接口返回:', JSON.stringify(res.data))
|
||||
uni.hideLoading()
|
||||
|
||||
@@ -131,15 +176,20 @@
|
||||
return
|
||||
}
|
||||
|
||||
const payType = selectedType.value
|
||||
// 后端下发银联商务的 appPayRequest,整串 JSON 原样交给 SDK,不要拆开解析
|
||||
const data = res.data
|
||||
const payDataStr = typeof data === 'string'
|
||||
? data
|
||||
: (data.appPayRequest || data.appRequest || '')
|
||||
|
||||
if (payType === 'wx') {
|
||||
invokeWxPay(data.wx || data.wxPayInfo || data)
|
||||
} else if (payType === 'alipay') {
|
||||
invokeAlipay(data.alipay || data.aliPayInfo || data)
|
||||
if (!payDataStr || typeof payDataStr !== 'string' || !payDataStr.trim().startsWith('{')) {
|
||||
console.error('未获取到有效的 appPayRequest,后端返回:', JSON.stringify(data))
|
||||
uni.showToast({ title: '支付参数异常,请联系客服', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
invokeUnionPay(payDataStr, CHANNEL_MAP[selectedType.value])
|
||||
|
||||
} catch (err) {
|
||||
uni.hideLoading()
|
||||
console.log('支付异常', err)
|
||||
@@ -147,49 +197,96 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 微信支付
|
||||
const invokeWxPay = (data) => {
|
||||
const orderInfo = {
|
||||
appid: data.appId,
|
||||
partnerid: data.partnerId || data.partnerid || '',
|
||||
prepayid: data.prepayId || data.prepayid || (data.package || '').replace('prepay_id=', ''),
|
||||
package: 'Sign=WXPay',
|
||||
noncestr: data.nonceStr,
|
||||
timestamp: data.timeStamp,
|
||||
sign: data.paySign
|
||||
// 银联商务综合支付:拉起微信 / 支付宝完成付款
|
||||
const invokeUnionPay = (payData, channel) => {
|
||||
// #ifdef APP-PLUS
|
||||
ensureRegistered()
|
||||
paying = true
|
||||
upPayWithChannel({ payData, channel }, (code, info) => {
|
||||
handlePayResult(String(code), info)
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifndef APP-PLUS
|
||||
uni.showToast({ title: '当前端暂不支持银联支付', icon: 'none' })
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 注册和支付环境设置都只对 iOS 生效;Android 调 setPayEnviroment 只会刷警告,
|
||||
// Android 的环境要改插件 utssdk/app-android/AndroidManifest.xml 里的 CurrentEnvironment。
|
||||
let registered = false
|
||||
const ensureRegistered = () => {
|
||||
if (registered) return
|
||||
registered = true
|
||||
// #ifdef APP-PLUS
|
||||
if (uni.getSystemInfoSync().platform === 'ios') {
|
||||
setPayEnviroment(UNIONPAY_ENV)
|
||||
upPayRegistry({
|
||||
appId: WX_APPID,
|
||||
link: WX_UNIVERSAL_LINK,
|
||||
cloudUrl: SCHEME_CLOUD,
|
||||
aliminipayUrl: SCHEME_ALI_MINI,
|
||||
aliUrl: SCHEME_ALI
|
||||
})
|
||||
}
|
||||
|
||||
uni.requestPayment({
|
||||
provider: 'wxpay',
|
||||
orderInfo: orderInfo,
|
||||
success: (res) => {
|
||||
console.log('微信支付成功', res)
|
||||
paySuccess()
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('微信支付失败或取消', err)
|
||||
uni.showToast({ title: '支付取消或失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 支付宝支付
|
||||
const invokeAlipay = (data) => {
|
||||
const orderInfo = data.orderInfo || data
|
||||
uni.requestPayment({
|
||||
provider: 'alipay',
|
||||
orderInfo: orderInfo,
|
||||
success: (res) => {
|
||||
console.log('支付宝支付成功', res)
|
||||
paySuccess()
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('支付宝支付失败或取消', err)
|
||||
uni.showToast({ title: '支付取消或失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
||||
let paying = false // 支付请求是否已发出
|
||||
let payDone = false // 是否已给出结果提示,避免重复
|
||||
|
||||
// Android 端插件不触发回调,用户从微信/支付宝切回来时靠 onShow 兜底;
|
||||
// iOS 有回调,交给 handlePayResult 处理,这里不插手。
|
||||
onShow(() => {
|
||||
if (!paying) return
|
||||
if (uni.getSystemInfoSync().platform === 'android') {
|
||||
paying = false
|
||||
notifyPayPending()
|
||||
}
|
||||
})
|
||||
|
||||
// 结果码见 xh-uppay readme:0000 只表示「支付请求发送成功」,不代表付款成功,
|
||||
// 真实结果以银联商务异步通知到后端的结果为准。所以这里不做成功判定,
|
||||
// 统一引导用户回账单列表看实际状态。
|
||||
const handlePayResult = (code, info) => {
|
||||
console.log('银联支付回调', code, JSON.stringify(info))
|
||||
paying = false
|
||||
switch (code) {
|
||||
case '0000':
|
||||
case '2001': // 订单处理中,结果未知(有可能已支付成功)
|
||||
notifyPayPending()
|
||||
break
|
||||
case '1000':
|
||||
payDone = true
|
||||
uni.showToast({ title: '支付已取消', icon: 'none' })
|
||||
break
|
||||
case '1001':
|
||||
payDone = true
|
||||
uni.showToast({ title: '支付参数错误', icon: 'none' })
|
||||
break
|
||||
case '1002':
|
||||
payDone = true
|
||||
uni.showToast({ title: '网络异常,请重试', icon: 'none' })
|
||||
break
|
||||
case '1003':
|
||||
payDone = true
|
||||
uni.showToast({ title: '未安装对应的支付客户端', icon: 'none' })
|
||||
break
|
||||
default:
|
||||
payDone = true
|
||||
uni.showToast({ title: (info && info.resultMsg) || '支付失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
const notifyPayPending = () => {
|
||||
if (payDone) return
|
||||
payDone = true
|
||||
uni.showToast({ title: '支付结果确认中,请稍后在账单中查看', icon: 'none' })
|
||||
setTimeout(() => uni.navigateBack(), 2000)
|
||||
}
|
||||
|
||||
// 支付成功流转:等后端提供「订单查询」接口后,查单确认已支付时再调用这里。
|
||||
// 目前 SDK 的结果码不足以判定付款成功,所以暂时没有调用点。
|
||||
const paySuccess = () => {
|
||||
sendPaySuccessPush()
|
||||
uni.showToast({ title: '支付成功', icon: 'success' })
|
||||
|
||||
@@ -461,6 +461,13 @@
|
||||
"navigationBarTitleText": "缴费账单",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "billPay-yidang",
|
||||
"style": {
|
||||
"navigationBarTitleText": "缴费账单",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user