Files
shangbangbang/uniapp/pages/payment/purchase.vue
T
solosw 99b11b04e4 1.0
2026-01-05 14:11:34 +08:00

805 lines
17 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="container">
<!-- 页面标题 -->
<view class="page-header">
<text class="page-title">确认付款</text>
</view>
<!-- 订单信息 -->
<view class="order-section">
<view class="section-title">订单信息</view>
<view class="order-item">
<image :src="getFullImageUrl(productInfo.image)" class="product-image" mode="aspectFill" v-if="productInfo.image"></image>
<view class="product-details">
<text class="product-name">{{ productInfo.name }}</text>
<view class="product-price-info">
<text class="unit-price">单价:¥{{ orderInfo.unit_price }}</text>
<text class="quantity">数量{{ orderInfo.quantity }}</text>
</view>
</view>
</view>
<view class="total-amount">
<text class="total-label">总金额</text>
<text class="total-price">¥{{ orderInfo.total_amount }}</text>
</view>
</view>
<!-- 收货地址 -->
<view class="address-section">
<view class="section-title">收货地址</view>
<view class="address-selector" @tap="selectAddress">
<view class="address-content" v-if="selectedAddress">
<view class="address-info">
<text class="consignee">{{ selectedAddress.consignee_name }}</text>
<text class="phone">{{ selectedAddress.consignee_phone }}</text>
</view>
<text class="address-detail">{{ formatAddress(selectedAddress) }}</text>
</view>
<view class="no-address" v-else>
<text class="no-address-text">请选择收货地址</text>
</view>
<uni-icons type="right" size="16" color="#ccc"></uni-icons>
</view>
</view>
<!-- 付款方式 -->
<view class="payment-section">
<view class="section-title">请扫码付款</view>
<view class="payment-qr-container">
<!-- 管理员付款码一级商品 -->
<view class="qr-section" v-if="orderInfo.product_type === 1">
<view class="qr-title">
<text class="qr-label">请扫描以下二维码付款</text>
<text class="qr-subtitle">官方收款账户</text>
</view>
<view class="qr-code-wrapper">
<image :src="getFullImageUrl(adminPaymentQr.main)"
class="qr-code"
mode="aspectFit"
v-if="adminPaymentQr.main"
@tap="previewQrCode(adminPaymentQr.main)">
</image>
<view class="qr-placeholder" v-else>
<text class="placeholder-text">暂无付款码</text>
</view>
</view>
<view class="backup-qr" v-if="adminPaymentQr.sub">
<text class="backup-label">备用付款码</text>
<image :src="getFullImageUrl(adminPaymentQr.sub)"
class="backup-qr-image"
mode="aspectFit"
@tap="previewQrCode(adminPaymentQr.sub)">
</image>
</view>
</view>
<!-- 卖家付款码二级商品 -->
<view class="qr-section" v-if="orderInfo.product_type === 2">
<view class="qr-title">
<text class="qr-label">请扫描以下二维码付款</text>
<text class="qr-subtitle">卖家收款账户</text>
</view>
<view class="seller-info">
<image :src="getFullImageUrl(sellerInfo.avatar)" class="seller-avatar" mode="aspectFill" v-if="sellerInfo.avatar"></image>
<view class="seller-avatar-placeholder" v-else>
<uni-icons type="person" size="30" color="#ccc"></uni-icons>
</view>
<view class="seller-details">
<text class="seller-name">{{ sellerInfo.customer_name || sellerInfo.real_name }}</text>
<text class="seller-code">ID: {{ sellerInfo.identity_code }}</text>
</view>
</view>
<view class="qr-code-wrapper">
<image :src="getFullImageUrl(sellerPaymentQr.main)"
class="qr-code"
mode="aspectFit"
v-if="sellerPaymentQr.main"
@tap="previewQrCode(sellerPaymentQr.main)">
</image>
<view class="qr-placeholder" v-else>
<text class="placeholder-text">卖家暂未设置付款码</text>
</view>
</view>
<view class="backup-qr" v-if="sellerPaymentQr.sub">
<text class="backup-label">备用付款码</text>
<image :src="getFullImageUrl(sellerPaymentQr.sub)"
class="backup-qr-image"
mode="aspectFit"
@tap="previewQrCode(sellerPaymentQr.sub)">
</image>
</view>
</view>
</view>
</view>
<!-- 上传付款凭证 -->
<view class="upload-section">
<view class="section-title">上传付款凭证</view>
<view class="upload-container">
<view class="upload-item" @tap="uploadPaymentProof">
<image v-if="paymentProofImage"
:src="getFullImageUrl(paymentProofImage)"
class="proof-image"
mode="aspectFill">
</image>
<view v-else class="upload-placeholder">
<uni-icons type="camera" size="40" color="#ccc"></uni-icons>
<text class="upload-text">点击上传付款截图</text>
</view>
</view>
<view class="upload-tips">
<text class="tip-text">请上传完整的付款截图包含金额和时间信息</text>
</view>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-section">
<view class="action-buttons">
<button class="cancel-btn" @tap="cancelOrder" v-if="canCancel">
取消订单
</button>
<button class="submit-btn" @tap="submitOrder" :disabled="!canSubmit || submitting">
<text v-if="submitting">提交中...</text>
<text v-else>提交付款凭证</text>
</button>
</view>
</view>
</view>
</template>
<script>
import request from '@/utils/request.js'
export default {
data() {
return {
orderId: '',
orderNo: '',
orderInfo: {},
productInfo: {
name: '',
image: ''
},
selectedAddress: null,
adminPaymentQr: {
main: '',
sub: ''
},
sellerInfo: {},
sellerPaymentQr: {
main: '',
sub: ''
},
paymentProofImage: '',
submitting: false,
canCancel: true,
productType:1
}
},
computed: {
canSubmit() {
return this.paymentProofImage && !this.submitting
}
},
onLoad(options) {
// 检查登录状态
const token = uni.getStorageSync('token')
if (!token) {
uni.reLaunch({
url: '/pages/login/login'
})
return
}
// 获取订单信息
this.orderId = options.order_id || ''
this.orderNo = options.order_no || ''
if(options.is_second=="true"){
this.productType=2
}else
{
this.productType=1
}
if (!this.orderId) {
uni.showToast({
title: '订单信息有误',
icon: 'none'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
return
}
this.loadOrderData()
},
methods: {
// 获取完整的图片URL
getFullImageUrl(url) {
return request.getImageUrl(url)
},
// 加载订单数据
async loadOrderData() {
try {
// 获取订单详情
const orderRes = await request.get(`/back/user/orders/purchase?order_id=${this.orderId}`)
if (orderRes.success && orderRes.data.list && orderRes.data.list.length > 0) {
this.orderInfo = orderRes.data.list[0]
this.selectedAddress = this.orderInfo.address
// 设置商品信息
this.productInfo = {
name: this.orderInfo.product_name,
image: this.getFirstImage(this.orderInfo.product_images)
}
// 检查订单状态
if (this.orderInfo.order_status !== 0) {
this.canCancel = false
}
// 如果是二级商品,获取卖家信息
if (this.orderInfo.product_type === 2 && this.orderInfo.seller_id) {
this.sellerInfo = this.orderInfo.seller
}
// 加载付款信息
await this.loadPaymentInfo()
} else {
throw new Error('订单不存在')
}
} catch (error) {
console.error('加载订单失败:', error)
uni.showToast({
title: '加载订单失败',
icon: 'none'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
},
// 获取商品的第一张图片
getFirstImage(images) {
if (!images) return ''
if (typeof images === 'string') {
return images.split(',')[0].trim()
}
if (Array.isArray(images) && images.length > 0) {
return images[0]
}
return ''
},
// 加载付款信息
async loadPaymentInfo() {
try {
if (this.orderInfo.product_type === 1) { // 一级商品
// 加载管理员付款码
const res = await request.get('/back/admin/payment/info')
if (res.success) {
this.adminPaymentQr = {
main: res.data.main_payment_qr_image || '',
sub: res.data.sub_payment_qr_image || ''
}
}
} else { // 二级商品
// 加载卖家付款码
const res = await request.get(`/back/user/seller/${this.orderInfo.seller_id}/payment`)
if (res.success) {
this.sellerPaymentQr = {
main: res.data.main_payment_qr_image || '',
sub: res.data.sub_payment_qr_image || ''
}
}
}
} catch (error) {
console.error('加载付款信息失败:', error)
}
},
// 格式化地址
formatAddress(address) {
if (!address) return ''
return `${address.province} ${address.city} ${address.district} ${address.detail_address}`
},
// 选择收货地址
selectAddress() {
uni.navigateTo({
url: '/pages/address/select'
})
},
// 预览二维码
previewQrCode(qrCode) {
if (!qrCode) return
uni.previewImage({
urls: [this.getFullImageUrl(qrCode)],
current: 0
})
},
// 上传付款凭证
uploadPaymentProof() {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['camera', 'album'],
success: async (res) => {
const filePath = res.tempFilePaths[0]
await this.uploadImage(filePath)
},
fail: (err) => {
console.error('选择图片失败:', err)
uni.showToast({
title: '选择图片失败',
icon: 'none'
})
}
})
},
// 上传图片
async uploadImage(filePath) {
try {
uni.showLoading({
title: '上传中...',
mask: true
})
const token = uni.getStorageSync('token')
const res = await request.upload('/back/upload', filePath, {
token: token,
name: 'file'
})
if (res.success) {
this.paymentProofImage = res.data.url
uni.showToast({
title: '上传成功',
icon: 'success'
})
} else {
throw new Error(res.message || '上传失败')
}
} catch (error) {
console.error('上传图片失败:', error)
uni.showToast({
title: '上传失败',
icon: 'none'
})
} finally {
uni.hideLoading()
}
},
// 提交付款凭证
async submitOrder() {
if (!this.canSubmit) {
if (!this.paymentProofImage) {
uni.showToast({
title: '请上传付款凭证',
icon: 'none'
})
}
return
}
this.submitting = true
try {
const res = await request.put(`/back/user/orders/purchase/${this.orderId}/payment`, {
payment_proof_image: this.paymentProofImage,
product_type :this.productType
})
if (res.success) {
uni.showToast({
title: '付款凭证提交成功',
icon: 'success'
})
// 延迟跳转到订单页面
setTimeout(() => {
uni.redirectTo({
url: '/pages/orders/orders'
})
}, 1500)
} else {
throw new Error(res.message || '提交付款凭证失败')
}
} catch (error) {
console.error('提交付款凭证失败:', error)
uni.showToast({
title: error.message || '提交失败',
icon: 'none'
})
} finally {
this.submitting = false
}
},
// 取消订单
async cancelOrder() {
uni.showModal({
title: '确认取消',
content: '确定要取消这个订单吗?',
success: async (res) => {
if (res.confirm) {
try {
const result = await request.put(`/back/user/orders/purchase/${this.orderId}/cancel`)
if (result.success) {
uni.showToast({
title: '订单已取消',
icon: 'success'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
} else {
throw new Error(result.message || '取消订单失败')
}
} catch (error) {
console.error('取消订单失败:', error)
uni.showToast({
title: error.message || '取消失败',
icon: 'none'
})
}
}
}
})
}
}
}
</script>
<style scoped>
.container {
background-color: #f5f5f5;
min-height: 100vh;
padding-bottom: 120rpx;
}
.page-header {
background: white;
padding: 30rpx;
text-align: center;
border-bottom: 1rpx solid #eee;
}
.page-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.order-section,
.address-section,
.payment-section,
.upload-section {
background: white;
margin: 20rpx;
border-radius: 20rpx;
padding: 30rpx;
}
.section-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.order-item {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.product-image {
width: 120rpx;
height: 120rpx;
border-radius: 10rpx;
margin-right: 20rpx;
}
.product-details {
flex: 1;
}
.product-name {
font-size: 28rpx;
color: #333;
font-weight: bold;
display: block;
margin-bottom: 10rpx;
}
.product-price-info {
display: flex;
gap: 20rpx;
}
.unit-price,
.quantity {
font-size: 24rpx;
color: #666;
}
.total-amount {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 20rpx;
border-top: 1rpx solid #eee;
}
.total-label {
font-size: 28rpx;
color: #333;
}
.total-price {
font-size: 36rpx;
color: #FF4444;
font-weight: bold;
}
.address-selector {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx;
background: #f8f8f8;
border-radius: 15rpx;
min-height: 100rpx;
}
.address-content {
flex: 1;
}
.address-info {
display: flex;
gap: 20rpx;
margin-bottom: 8rpx;
}
.consignee {
font-size: 28rpx;
color: #333;
font-weight: bold;
}
.phone {
font-size: 28rpx;
color: #666;
}
.address-detail {
font-size: 26rpx;
color: #666;
line-height: 1.4;
}
.no-address {
flex: 1;
text-align: center;
}
.no-address-text {
font-size: 28rpx;
color: #999;
}
.payment-qr-container {
text-align: center;
}
.qr-section {
margin-bottom: 30rpx;
}
.qr-title {
margin-bottom: 20rpx;
}
.qr-label {
font-size: 28rpx;
color: #333;
display: block;
margin-bottom: 8rpx;
}
.qr-subtitle {
font-size: 24rpx;
color: #666;
}
.seller-info {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20rpx;
padding: 15rpx;
background: #f8f8f8;
border-radius: 15rpx;
}
.seller-avatar {
width: 60rpx;
height: 60rpx;
border-radius: 30rpx;
margin-right: 15rpx;
}
.seller-avatar-placeholder {
width: 60rpx;
height: 60rpx;
border-radius: 30rpx;
background: #e5e5e5;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15rpx;
}
.seller-details {
text-align: left;
}
.seller-name {
font-size: 26rpx;
color: #333;
font-weight: bold;
display: block;
margin-bottom: 5rpx;
}
.seller-code {
font-size: 22rpx;
color: #666;
}
.qr-code-wrapper {
display: flex;
justify-content: center;
margin-bottom: 20rpx;
}
.qr-code {
width: 400rpx;
height: 400rpx;
border: 2rpx solid #eee;
border-radius: 15rpx;
}
.qr-placeholder {
width: 400rpx;
height: 400rpx;
border: 2rpx dashed #ddd;
border-radius: 15rpx;
display: flex;
align-items: center;
justify-content: center;
}
.placeholder-text {
font-size: 26rpx;
color: #999;
}
.backup-qr {
margin-top: 20rpx;
}
.backup-label {
font-size: 24rpx;
color: #666;
display: block;
margin-bottom: 10rpx;
}
.backup-qr-image {
width: 200rpx;
height: 200rpx;
border: 1rpx solid #eee;
border-radius: 10rpx;
}
.upload-container {
text-align: center;
}
.upload-item {
width: 400rpx;
height: 300rpx;
margin: 0 auto 20rpx;
border: 2rpx dashed #ddd;
border-radius: 15rpx;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.proof-image {
width: 100%;
height: 100%;
}
.upload-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #ccc;
}
.upload-text {
font-size: 26rpx;
color: #999;
margin-top: 15rpx;
}
.upload-tips {
margin-top: 10rpx;
}
.tip-text {
font-size: 24rpx;
color: #666;
line-height: 1.4;
}
.submit-section {
padding: 40rpx 30rpx;
}
.action-buttons {
display: flex;
gap: 20rpx;
}
.cancel-btn {
flex: 1;
height: 80rpx;
background: #f5f5f5;
border: 1rpx solid #ddd;
border-radius: 40rpx;
color: #666;
font-size: 32rpx;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
}
.submit-btn {
flex: 2;
height: 80rpx;
background: linear-gradient(135deg, #FF4444 0%, #FF6666 100%);
border: none;
border-radius: 40rpx;
color: white;
font-size: 32rpx;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
}
.submit-btn:disabled {
background: #ccc;
}
</style>