You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

951 lines
25 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view>
<view class="detail-page">
<!-- 顶部导航 -->
<u-navbar
title="查看详情"
:is-back="true"
back-icon-color="#fff"
:background="{'background':'#1479ff'}"
title-color="#fff"
:border-bottom="false"
:custom-back="customBack"
></u-navbar>
<view class="b-border"></view>
<!-- 订单信息 -->
<view class="order-info">
<view class="info-item">
<text class="label">订单号</text>
<text class="value">{{orderInfo.no}}</text>
</view>
<view class="info-item">
<text class="label">服务项目</text>
<text class="value">{{orderInfo.accompany_product?orderInfo.accompany_product.name:''}}</text>
</view>
<view class="info-item">
<text class="label">服务时间</text>
<text class="value">{{orderInfo.time}}</text>
</view>
<view class="info-item" v-if="orderInfo.type === 2">
<text class="label">服务地址</text>
<text class="value">{{orderInfo.city?orderInfo.city:'' }}</text>
</view>
<view class="info-item" v-else>
<text class="label">就诊医院</text>
<text class="value">{{orderInfo.hospital?orderInfo.hospital.name:''}}</text>
</view>
<view class="info-item">
<text class="label">联系人</text>
<text class="value">{{orderInfo.appoint_name?orderInfo.appoint_name:''}}</text>
</view>
<view class="info-item">
<text class="label">联系方式</text>
<text class="value">{{orderInfo.appoint_mobile?orderInfo.appoint_mobile:''}}</text>
</view>
<view class="info-item">
<text class="label">被服务人</text>
<text class="value">{{orderInfo.user_archive?orderInfo.user_archive.name:''}}</text>
</view>
<view class="info-item">
<text class="label">是否可以自理</text>
<text class="value">{{orderInfo.my_provide===1?'可以自理':'不能自理'}}</text>
</view>
<view class="info-item info-item-reason">
<text class="label">就诊资料</text>
<view class="value">
<view class="order-item-img">
<image v-for="(item,index) in imgs" :src="item" @click="imgPreview(index)"></image>
</view>
</view>
</view>
<view class="info-item">
<text class="label">护工工资</text>
<text class="value">{{orderInfo.nurse_salary?orderInfo.nurse_salary:''}}</text>
</view>
<view class="info-item info-item-reason">
<text class="label">其他需求</text>
<text class="value">{{orderInfo.content?orderInfo.content:''}}</text>
</view>
<view class="info-item">
<text class="label">购买数量</text>
<text class="value">{{orderInfo.can_multi_num || 1}}</text>
</view>
<view class="info-item">
<text class="label">订单金额</text>
<text class="value">¥{{orderInfo.price}}</text>
</view>
<view class="info-item">
<text class="label">原始金额</text>
<text class="value">¥{{orderInfo.from_price}}</text>
</view>
<view class="info-item" v-if="orderInfo.status===3||orderInfo.status===4">
<text class="label">待付金额</text>
<text class="value price">¥{{orderInfo.diff_price}}</text>
</view>
<view class="info-item info-item-reason">
<text class="label">修改原因</text>
<text class="value reason">{{orderInfo.update_price_reason?orderInfo.update_price_reason:''}}</text>
</view>
</view>
<!-- 支付区域 -->
<view class="payment-popup" v-if="orderInfo.pay_status === 0" id="pay-qrcode-section">
<!-- Tab 切换 -->
<view class="payment-tabs">
<view
class="tab-item"
:class="{ 'tab-active': activeTab === 'qrcode' }"
@click="switchTab('qrcode')"
>
付款二维码
</view>
<view
class="tab-item"
:class="{ 'tab-active': activeTab === 'pay' }"
@click="switchTab('pay')"
>
立即支付
</view>
</view>
<!-- Tab 内容 -->
<view class="tab-content">
<!-- 付款二维码 Tab -->
<view v-if="activeTab === 'qrcode'" class="tab-panel">
<view class="amount">
<text class="symbol">¥</text>
<text class="number">{{orderInfo.price}}</text>
</view>
<view class="qr-code">
<!-- 支付二维码 -->
<view v-if="myQrcode" style="border: 1px solid #ccc; padding: 10rpx; position: relative;">
<!-- 显示图片(可长按识别) -->
<image
v-if="qrcodeImagePath"
:src="qrcodeImagePath"
mode="aspectFit"
show-menu-by-longpress
:class="{ 'qrcode-disabled': qrcodeExpired }"
style="width: 400rpx; height: 400rpx; display: block; margin: 0 auto;"
></image>
<!-- Canvas 绘制(使用 hide 属性隐藏,但保持可渲染) -->
<uqrcode
v-else
ref="payQrcode"
id="pay-qrcode"
canvas-id="pay-qrcode"
:value="myQrcode"
:size="400"
:sizeUnit="'rpx'"
:hide="true"
:options="{
margin: 10,
}"
@complete="onQrcodeComplete"
></uqrcode>
<!-- 二维码失效遮罩层 -->
<view v-if="qrcodeExpired" class="qrcode-overlay" @click="regetQrcode">
<view class="reget-btn">
<text>重新获取</text>
</view>
</view>
</view>
<view v-else style="text-align: center; color: #999; padding: 40rpx;">
正在生成二维码...
</view>
</view>
<view class="tips">
<text>请扫码支付订单金额</text>
<text class="sub-tips">支付完成后订单将自动完成</text>
</view>
<!-- 分享按钮 -->
<view class="share-section">
<u-button
type="primary"
size="medium"
@click="showShareGuide"
:custom-style="{
background: 'linear-gradient(to right, #1479ff, #4a90e2)',
color: '#fff',
borderRadius: '40rpx',
fontSize: '28rpx'
}"
>
<u-icon name="share" color="#fff" size="16" style="margin-right: 8rpx;"></u-icon>
转发给微信好友
</u-button>
<!-- 返回首页按钮(仅分享进入时显示) -->
<u-button
v-if="isFromShare"
type="default"
size="medium"
@click="goToHome"
:custom-style="{
marginTop: '20rpx',
borderRadius: '40rpx',
fontSize: '28rpx'
}"
>
<u-icon name="home" color="#1479ff" size="16" style="margin-right: 8rpx;"></u-icon>
返回首页
</u-button>
</view>
</view>
<!-- 立即支付 Tab -->
<view v-if="activeTab === 'pay'" class="tab-panel">
<view class="amount">
<text class="symbol">¥</text>
<text class="number">{{orderInfo.price}}</text>
</view>
<view class="pay-button-section">
<u-button
type="primary"
shape="circle"
@click="scrollToPayQr"
:disabled="payCooldownSeconds > 0"
class="pay-btn-large"
:custom-style="{
width: '100%',
height: '88rpx',
fontSize: '32rpx',
fontWeight: '500',
opacity: payCooldownSeconds > 0 ? 0.6 : 1
}"
>
{{ payCooldownSeconds > 0 ? `支付成功,${payCooldownSeconds}秒后可再次支付` : '立即支付' }}
</u-button>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import uqrcode from "@/uni_modules/Sansnn-uQRCode/components/uqrcode/uqrcode.vue";
export default {
components: {
uqrcode
},
data() {
return {
myQrcode: '',
qrcodeImagePath: '', // 二维码图片路径
qrcodeExpired: false, // 二维码是否失效
imgs: [],
orderId: '',
orderInfo: {},
parStyle: {
'background': 'linear-gradient(to right, #476de4, #7bb9f7)',
'color': '#fff',
'font-size': '28rpx',
'padding': '0 30rpx',
'margin-left': '15rpx'
},
payTimer: null, // 支付状态检查定时器
isFromShare: false,
activeTab: 'qrcode', // 当前选中的 tab: 'qrcode' 或 'pay'
paySuccessTime: null, // 支付成功的时间戳
payCooldownSeconds: 0, // 支付冷却倒计时(秒)
payCooldownTimer: null, // 支付冷却定时器
}
},
onLoad(options) {
// 获取订单详情
this.orderId = options.id
// 检查是否从分享链接进入
this.isFromShare = uni.getStorageSync('isFromShare')
const isOrderDetailShare = uni.getStorageSync('isOrderDetailShare')
if (this.isFromShare && isOrderDetailShare) {
console.log('订单详情页检测到从分享链接进入')
console.log('分享查询参数:', uni.getStorageSync('shareQuery'))
console.log('分享推荐者信息:', uni.getStorageSync('shareReferrer'))
console.log('分享进入时间:', uni.getStorageSync('shareLaunchTime'))
}
this.getOrderDetailStaff(options.id)
},
onUnload() {
// 清除支付状态检查定时器
this.stopPayCheck()
// 清除支付冷却定时器
this.stopPayCooldown()
// 清除分享相关存储
this.clearShareCache()
},
// 微信小程序分享给好友
onShareAppMessage() {
if (this.orderInfo.pay_status === 0) {
return {
title: `医康养${this.orderInfo.accompany_product ? this.orderInfo.accompany_product.name : '服务'}订单`,
desc: `订单号:${this.orderInfo.no},金额:¥${this.orderInfo.price}`,
path: `/package_sub/order/orderStaffDetail?id=${this.orderId}`,
imageUrl: this.orderInfo.accompany_product && this.orderInfo.accompany_product.cover ? this.orderInfo.accompany_product.cover.url : ''
}
}
return {
title: '医康养服务',
path: '/pages/index/index'
}
},
// 微信小程序分享到朋友圈
onShareTimeline() {
if (this.orderInfo.pay_status === 0) {
return {
title: `医康养${this.orderInfo.accompany_product ? this.orderInfo.accompany_product.name : '服务'}订单`,
query: `id=${this.orderId}`,
imageUrl: this.orderInfo.accompany_product && this.orderInfo.accompany_product.cover ? this.orderInfo.accompany_product.cover.url : ''
}
}
return {
title: '医康养服务',
query: '',
imageUrl: ''
}
},
methods: {
// 自定义返回逻辑
customBack() {
// 检查页面栈
const pages = getCurrentPages()
console.log('当前页面栈:', pages.length)
// 检查是否从分享链接进入
const isFromShare = uni.getStorageSync('isFromShare')
const isOrderDetailShare = uni.getStorageSync('isOrderDetailShare')
if ((isFromShare && isOrderDetailShare) || pages.length <= 1) {
// 从订单详情分享进入或页面栈只有一页时,返回首页
console.log('从订单详情分享进入或页面栈只有一页,返回首页')
uni.showToast({
title: '返回首页',
icon: 'none'
})
// 清除分享相关的缓存数据
this.clearShareCache()
uni.reLaunch({
url: '/package_sub/login/login'
})
} else {
// 正常进入时,使用默认返回逻辑
uni.navigateBack({
delta: 1
})
}
},
imgPreview(index) {
uni.previewImage({
current: index, // 当前显示图片索引
urls: this.imgs // 需要预览的图片http链接列表
});
},
async getOrderDetailStaff(orderId) {
// 调用接口获取订单详情
const res = await this.$u.api.accompanyOrderDetail({
id: orderId
})
this.orderInfo = res
let _arr = []
if (res.files.length > 0) {
res.files.map(item => {
_arr.push(item.url)
})
this.imgs = _arr
}
// 如果订单已支付,停止支付状态检测
if (res.pay_status !== 0) {
this.stopPayCheck()
this.myQrcode = ''
this.qrcodeImagePath = ''
this.qrcodeExpired = false
return
}
// 只在当前是二维码 tab 且订单未支付时,才获取二维码
if (res.pay_status === 0 && this.activeTab === 'qrcode') {
await this.getCode(res.no)
}
},
async getCode(no) {
// 如果已经有定时器在运行,先清除(避免重复创建)
this.stopPayCheck()
try {
const res = await this.$u.api.accompanyPay({
no: no
})
if(res.errcode && res.errcode===10002){
// 设置二维码失效状态
this.qrcodeExpired = true
uni.showToast({
title: res.errmsg,
icon: 'none'
});
return;
}
// 如果成功获取二维码,重置失效状态
this.qrcodeExpired = false
const result = res.result || res
if (result && result.code_url) {
this.myQrcode = result.code_url
// 重置图片路径,等待二维码生成完成后转换为图片
this.qrcodeImagePath = ''
// 开始检查支付状态(只在二维码 tab 时启动)
if (this.activeTab === 'qrcode') {
this.startPayCheck()
}
} else {
uni.showToast({
icon: 'none',
title: result.err_code_des || '获取二维码失败'
})
}
} catch (error) {
uni.showToast({
icon: 'none',
title: '获取二维码失败'
})
}
},
// 重新获取二维码
regetQrcode() {
if (this.orderInfo && this.orderInfo.no) {
this.getCode(this.orderInfo.no)
}
},
// 二维码生成完成回调
onQrcodeComplete(e) {
// 当二维码成功生成后,将其转换为图片
if (e && e.success && this.$refs.payQrcode) {
// 延迟一下,确保 canvas 完全渲染完成
setTimeout(() => {
if (this.$refs.payQrcode) {
this.$refs.payQrcode.toTempFilePath({
success: (res) => {
// 获取临时文件路径
this.qrcodeImagePath = res.tempFilePath
console.log('二维码图片生成成功:', this.qrcodeImagePath)
},
fail: (err) => {
console.error('二维码转图片失败:', err)
// 如果转换失败,仍然显示 canvas虽然不能长按识别
uni.showToast({
icon: 'none',
title: '二维码图片生成失败'
})
}
})
}
}, 500) // 延迟 500ms 确保 canvas 渲染完成
}
},
// 停止支付状态检测
stopPayCheck() {
if (this.payTimer) {
clearInterval(this.payTimer)
this.payTimer = null
}
},
// 启动支付冷却期30秒
startPayCooldown() {
// 先清除旧的定时器
this.stopPayCooldown()
// 设置冷却时间为30秒
this.payCooldownSeconds = 30
this.paySuccessTime = Date.now()
// 启动倒计时定时器
this.payCooldownTimer = setInterval(() => {
this.payCooldownSeconds--
if (this.payCooldownSeconds <= 0) {
// 冷却期结束
this.stopPayCooldown()
}
}, 1000) // 每秒更新一次
},
// 停止支付冷却期
stopPayCooldown() {
if (this.payCooldownTimer) {
clearInterval(this.payCooldownTimer)
this.payCooldownTimer = null
}
this.payCooldownSeconds = 0
this.paySuccessTime = null
},
// 开始检查支付状态
startPayCheck() {
// 先清除旧的定时器,避免重复创建
this.stopPayCheck()
// 如果订单已支付或不在二维码 tab不启动检测
if (this.orderInfo.pay_status !== 0 || this.activeTab !== 'qrcode') {
return
}
this.payTimer = setInterval(async () => {
// 如果不在二维码 tab 或订单已支付,停止检测
if (this.activeTab !== 'qrcode' || this.orderInfo.pay_status !== 0) {
this.stopPayCheck()
return
}
try {
const res = await this.$u.api.accompanyOrderDetail({
id: this.orderId
})
// 更新订单信息
if (res.pay_status !== this.orderInfo.pay_status) {
this.orderInfo = res
}
if (res.pay_status === 1) {
// 支付成功,停止检测
this.stopPayCheck()
// 隐藏支付二维码
this.myQrcode = ''
this.qrcodeImagePath = ''
// 刷新订单详情
await this.getOrderDetailStaff(this.orderId)
uni.showToast({
title: '支付成功',
icon: 'success'
})
}
} catch (err) {
console.error('检查支付状态失败', err)
// 如果连续失败,可以考虑停止检测或增加重试逻辑
}
}, 3000) // 每3秒检查一次
},
isPayCode() {
// 停止支付状态检测
this.stopPayCheck()
this.myQrcode = ''
this.qrcodeImagePath = ''
this.qrcodeExpired = false
// 重新获取订单详情
this.getOrderDetailStaff(this.orderId)
},
showShareGuide() {
// 显示分享引导
uni.showModal({
title: '转发给微信好友',
content: `订单号:${this.orderInfo.no}\n金额¥${this.orderInfo.price}\n服务${this.orderInfo.accompany_product ? this.orderInfo.accompany_product.name : '医康养服务'}\n\n请点击右上角"..."按钮,选择"转发"`,
confirmText: '知道了',
showCancel: false,
success: () => {
// 显示分享菜单
// #ifdef MP-WEIXIN
wx.showShareMenu({
withShareTicket: true,
menus: ['shareAppMessage'],
success: () => {
uni.showToast({
title: '请点击右上角转发',
icon: 'none',
duration: 2000
})
}
})
// #endif
}
})
},
// 测试分享状态的方法
testShareStatus() {
const isFromShare = uni.getStorageSync('isFromShare')
const shareQuery = uni.getStorageSync('shareQuery')
const shareReferrer = uni.getStorageSync('shareReferrer')
const shareLaunchTime = uni.getStorageSync('shareLaunchTime')
console.log('=== 分享状态测试 ===')
console.log('是否从分享进入:', isFromShare)
console.log('分享查询参数:', shareQuery)
console.log('分享推荐者:', shareReferrer)
console.log('分享进入时间:', shareLaunchTime)
uni.showModal({
title: '分享状态测试',
content: `从分享进入: ${isFromShare}\n查询参数: ${JSON.stringify(shareQuery)}\n推荐者: ${JSON.stringify(shareReferrer)}`,
showCancel: false
})
},
goToHome() {
// 返回首页的逻辑
console.log('返回首页')
// 清除分享相关的缓存数据
this.clearShareCache()
uni.reLaunch({
url: '/package_sub/login/login'
})
},
clearShareCache() {
// 清除分享相关的缓存数据
console.log('清除分享相关的缓存数据')
// 清除所有分享相关的存储
uni.removeStorageSync('isFromShare')
uni.removeStorageSync('shareLaunchTime')
uni.removeStorageSync('shareQuery')
uni.removeStorageSync('shareReferrer')
uni.removeStorageSync('isOrderDetailShare')
console.log('分享缓存数据已清除下次进入将重新进行token校验')
},
// Tab 切换
async switchTab(tab) {
const previousTab = this.activeTab
this.activeTab = tab
// 如果切换到非二维码 tab停止支付状态检测
if (tab !== 'qrcode') {
this.stopPayCheck()
}
// 每次切换 tab 都重新请求接口,获取最新的订单信息(包含最新的 no
await this.getOrderDetailStaff(this.orderId)
// getOrderDetailStaff 中会根据 activeTab 和 pay_status 决定是否调用 getCode
// 所以切换到 qrcode tab 时,如果订单未支付,会自动获取二维码并启动检测
},
scrollToPayQr: async function() {
// 检查是否在支付冷却期内
if (this.payCooldownSeconds > 0) {
uni.showToast({
title: `支付成功后${this.payCooldownSeconds}秒内不能重复支付`,
icon: 'none',
duration: 2000
})
return
}
// 每次点击都重新获取最新的订单信息(包含最新的 no
await this.getOrderDetailStaff(this.orderId)
try {
// 1. 获取微信支付参数(使用最新的 no
const res = await this.$u.api.accompanyOrderPayParams({ no: this.orderInfo.no });
console.log("获取支付参数:", res,res.errcode && res.errcode===10002);
if(res.errcode && res.errcode===10002){
uni.showToast({
title: res.errmsg,
icon: 'none'
});
return;
}
// 从嵌套的config对象中获取支付参数
const payConfig = res.config || res;
// 2. 发起微信支付
await uni.requestPayment({
provider: 'wxpay',
timeStamp: payConfig.timestamp || payConfig.timeStamp,
nonceStr: payConfig.nonceStr,
package: payConfig.package,
signType: payConfig.signType,
paySign: payConfig.paySign,
success: () => {
uni.showToast({ title: '支付成功', icon: 'success' });
// 支付成功后刷新订单详情
this.getOrderDetailStaff(this.orderId || this.orderInfo.id);
// 启动支付冷却期30秒
this.startPayCooldown();
},
fail: (err) => {
if (err.errMsg && err.errMsg.indexOf('cancel') > -1) {
uni.showToast({ title: '已取消支付', icon: 'none' });
} else {
uni.showToast({ title: '支付失败', icon: 'none' });
console.log("支付失败",err)
}
}
});
} catch (e) {
uni.showToast({ title: e.errmsg || '拉起支付失败', icon: 'none' });
}
},
},
computed: {
// 获取登录角色
loginRole() {
return uni.getStorageSync('login_role') || '';
}
}
}
</script>
<style lang="scss" scoped>
.detail-page {
min-height: 100vh;
background: #f5f5f5;
padding-bottom: 120rpx;
.b-border {
width: 100%;
height: 30rpx;
border-radius: 0 0 120rpx 120rpx;
background-color: #1479ff;
}
.order-info {
background: #fff;
border-radius: 20rpx;
margin: 30rpx;
box-shadow: 0 4rpx 16rpx #e6eaf1;
padding: 0 30rpx;
.info-item {
padding: 30rpx;
display: flex;
justify-content: space-between;
// margin-bottom: 30rpx;
border-bottom: 1rpx solid rgba(0, 0, 0, 0.1);
.label {
color: #666;
font-size: 28rpx;
width:180rpx;
}
.value {
color: #333;
font-size: 28rpx;
width:calc(100% - 190rpx);
text-align: right;
// &.price {
// color: #f56c6c;
// font-weight: bold;
// }
&.reason {
color: #999;
}
}
}
.info-item-reason {
flex-wrap: wrap;
&>text {
width: 100%
}
}
.order-item-img {
display: flex;
flex-wrap: wrap;
image {
width: 140rpx;
height: 140rpx;
margin: 10rpx;
}
}
}
.payment-popup {
background: #fff;
border-radius: 20rpx;
margin: 30rpx;
box-shadow: 0 4rpx 16rpx #e6eaf1;
padding: 30rpx;
z-index: 1;
.payment-tabs {
display: flex;
border-bottom: 2rpx solid #e5e5e5;
margin-bottom: 30rpx;
.tab-item {
flex: 1;
text-align: center;
padding: 20rpx 0;
font-size: 30rpx;
color: #666;
position: relative;
transition: all 0.3s;
&.tab-active {
color: #1479ff;
font-weight: 500;
&::after {
content: '';
position: absolute;
bottom: -2rpx;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 4rpx;
background: #1479ff;
border-radius: 2rpx;
}
}
}
}
.tab-content {
.tab-panel {
min-height: 400rpx;
}
.pay-button-section {
padding: 60rpx 0;
display: flex;
justify-content: center;
align-items: center;
}
}
.title {
font-size: 32rpx;
color: #333;
text-align: center;
font-weight: 500;
margin-bottom: 20rpx;
}
.amount {
text-align: center;
margin-bottom: 20rpx;
.symbol {
font-size: 36rpx;
color: #000;
}
.number {
font-size: 48rpx;
color: #d80808;
font-weight: bold;
}
}
.qr-code {
width: 400rpx;
height: 400rpx;
margin: 0 auto 20rpx;
background: #fff;
padding: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
justify-content: center;
image {
width: 100%;
height: 100%;
&.qrcode-disabled {
filter: grayscale(100%);
opacity: 0.5;
}
}
.qrcode-overlay {
position: absolute;
top: 10rpx;
left: 10rpx;
right: 10rpx;
bottom: 10rpx;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
border-radius: 8rpx;
.reget-btn {
background: #1479ff;
color: #fff;
padding: 20rpx 40rpx;
border-radius: 40rpx;
font-size: 28rpx;
font-weight: 500;
cursor: pointer;
&:active {
opacity: 0.8;
}
}
}
}
.tips {
text-align: center;
margin-bottom: 20rpx;
text {
display: block;
color: #333;
font-size: 28rpx;
line-height: 1.8;
}
.sub-tips {
color: #999;
font-size: 24rpx;
}
}
.close-btn {
width: 80%;
margin: 0 auto;
display: flex;
justify-content: center;
}
.share-section {
text-align: center;
margin-top: 20rpx;
}
}
}
.bottom-pay-btn {
position: fixed;
left: 0;
bottom: 0;
width: 100vw;
background: #fff;
z-index: 9999;
box-shadow: 0 -2rpx 8rpx #00000010;
padding: 20rpx 30rpx 40rpx 30rpx;
display: flex;
justify-content: center;
.pay-btn {
width: 100%;
font-size: 32rpx;
border-radius: 40rpx;
font-weight: 500;
}
}
</style>