Files
wagoo-douy3/src/pageHome/service/payment-confirm.vue
2026-03-06 16:54:32 +08:00

1037 lines
27 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="order-detail-container">
<view class="detail-container">
<uni-load-more v-if="isLoading" status="loading" :show-text="false" />
<scroll-view v-if="!isLoading" class="scroll-view" :scroll-y="true">
<!-- 用户和地址信息 -->
<view class="info-cell address-cell">
<view class="info-title-view">
<image src="@/static/images/address.png" mode="aspectFit" class="info-icon" />
<text class="app-fc-main fs-28">{{ userName || '--' }}</text>
<text class="app-fc-main fs-28 phone-text" style="color: #666262;">{{ userPhone || '--' }}</text>
<text class="default-tag">默认</text>
</view>
<view class="address-view">
<view class="info-icon" />
<text class="app-fc-normal fs-24 address-text">{{ (orderInfo && orderInfo.order && orderInfo.order.address)
|| '--' }}</text>
<!-- <text class="modify-btn" @click="handleModifyAddress">修改</text> -->
</view>
</view>
<!-- 备注信息统一白色容器 -->
<view class="info-cell remark-cell">
<view class="remark-row">
<text class="info-label">WIFI密码</text>
<text class="remark-text">
{{ (orderInfo && orderInfo.order && orderInfo.order.wifi_password) || '暂无备注' }}
</text>
</view>
<view class="remark-row">
<text class="info-label">停车状况</text>
<text class="remark-text">
{{ (orderInfo && orderInfo.order && orderInfo.order.park_desc) || '暂无备注' }}
</text>
</view>
<view class="remark-row">
<text class="info-label">宠物备注</text>
<text class="remark-text">{{ petRemark || '暂无备注' }}</text>
</view>
<view class="remark-row">
<text class="info-label">钥匙交接备注</text>
<text class="remark-text">
{{ (orderInfo && orderInfo.order && orderInfo.order.key_handover_remark) || '暂无备注' }}
</text>
</view>
</view>
<!-- 按日期显示的服务详情 -->
<view class="service-dates-section">
<view class="service-summary-header">
<text class="service-days-text">上门服务{{ serviceDays }}</text>
<text class="pet-count-text">{{ petCount }}{{ petTypeName }}</text>
</view>
<view v-for="(dateGroup, dateIndex) in groupedSlots" :key="dateIndex" class="date-service-cell">
<view class="date-header" @click="toggleDate(dateIndex)">
<view class="date-info">
<text class="date-text">{{ dateGroup.date }}</text>
<text class="service-name">{{ getServiceName() }}</text>
<text class="service-price">¥{{ dateGroup.price || '0.00' }}</text>
<text class="toggle-btn">{{ dateGroup.expanded ? '收起' : '展开' }}</text>
</view>
</view>
<view v-if="dateGroup.expanded" class="date-detail">
<view v-for="(pet, petIndex) in (orderInfo && orderInfo.pets) || []" :key="petIndex"
class="pet-detail-item">
<image :src="pet.pet_avatar" mode="aspectFit" class="pet-avatar" />
<view class="pet-info">
<text class="pet-name">{{ pet.pet_name || '--' }}</text>
<text class="pet-desc">{{ getPetDesc(pet) }}</text>
</view>
</view>
<view class="service-list">
<view v-for="(service, serviceIndex) in (orderInfo && orderInfo.order && orderInfo.order.rules) || []" :key="serviceIndex" class="service-item">
<text class="service-item-name">{{ service.name }}</text>
<text class="service-item-count">{{ service.value }}</text>
</view>
</view>
</view>
</view>
<!-- 夜间费和合计 -->
<!-- <view class="price-row night-fee-row">
<text class="price-label">夜间费<text class="remaining-count">(剩余{{ nightFeeRemaining }})</text></text>
<view class="night-fee-info">
<text class="night-fee-discount-tag">会员{{ memberDiscount }}折优惠</text>
<text class="night-fee-original">¥{{ nightFeeOriginal }}</text>
<text class="night-fee-price">¥{{ nightFeePrice }}</text>
</view>
</view> -->
<view class="price-row total-row">
<text class="price-total">合计</text>
<view class="discount-info">
<text class="discount-price">¥{{ originalTotalPrice }}</text>
</view>
</view>
</view>
<!-- 价格明细 -->
<view class="info-cell price-cell">
<view class="price-row">
<text class="price-label">订单总价</text>
<text class="price-value">¥{{ orderTotalPrice }}</text>
</view>
<!-- <view class="price-row">
<text class="price-label">优惠总价</text>
<text class="price-value discount">-¥{{ discountTotal }}</text>
</view> -->
<view class="price-divider"></view>
<view class="price-row final-row">
<text class="price-label">需付款</text>
<text class="price-value final-price">¥{{ finalAmount }}</text>
</view>
</view>
<!-- 支付方式 -->
<view class="info-cell payment-method-cell">
<view class="payment-option" @click="selectPaymentMethod('wechat')">
<image src="@/static/images/douy.png" mode="aspectFit" class="payment-icon" />
<text class="payment-name">抖音支付</text>
<!-- 未选中状态 -->
<image v-if="paymentMethod !== 'wechat'" src="@/static/images/w.png" mode="widthFix" class="not-selected" />
<!-- 选中状态 -->
<image v-if="paymentMethod === 'wechat'" src="@/static/images/y.png" mode="widthFix" class="not-selected" />
</view>
</view>
<!-- 预定须知 -->
<view class="info-cell notice-cell">
<text class="notice-title">预定须知</text>
<text class="notice-content">
请确认您的{{ petTypeName }}已接种疫苗,性格温顺无攻击性,不在发情期怀孕期或哺乳期,且无任何生命体征或致命性传染性疾病等服务协议中所提及的情况请确认您填写的信息真实完整无误,并符合平台上门服务要求
</text>
</view>
</scroll-view>
<!-- 底部付款区域 -->
<view class="footer-actions" v-if="!isLoading">
<!-- 第一排协议文本 -->
<view class="agreement-text">
<text class="agreement-item">下单默认同意</text>
<text class="agreement-item agreement-link" @click="viewServiceRefundsAgreement">服务与退款协议</text>
<text class="agreement-item"></text>
<text class="agreement-item agreement-link" @click="viewServiceRefundAgreement">喂养服务保障协议</text>
</view>
<!-- 第二排价格和支付按钮 -->
<view class="footer-bottom-row">
<view class="footer-price-info">
<text class="footer-price">¥{{ finalAmount }}</text>
<text class="footer-original-price">¥{{ originalTotalPrice }}</text>
<!-- <text class="footer-discount">共优惠¥{{ discountTotal }}</text> -->
</view>
<view class="pay-btn" @click="handlePay">
<text class="pay-btn-text">付款</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import { getOrderWideDetail, payOrder } from '@/api/order';
import moment from 'moment';
import { imgPrefix } from '@/utils/common';
export default {
name: 'HomeServiceOrderDetail',
data() {
return {
isLoading: false,
orderInfo: null,
orderId: '',
source: 'home_service',
petRemark: '',
paymentMethod: 'wechat',
nightFeeRemaining: 1,
nightFeeOriginal: '285',
nightFeePrice: '60',
memberDiscount: 10,
expandedDates: {}
};
},
computed: {
userName() {
// 从订单信息中获取用户名,如果没有则从其他地方获取
return this.orderInfo?.order?.user_name || '--';
},
userPhone() {
// 从订单信息中获取用户电话
return this.orderInfo?.order?.phone || '--';
},
serviceDays() {
return this.orderInfo?.slots?.length || 0;
},
petCount() {
return this.orderInfo?.pets?.length || 0;
},
petTypeName() {
// 根据服务类型判断宠物类型
const serviceType = this.orderInfo?.order?.service_type;
if (serviceType === 'FEED') {
// 上门喂养显示猫咪
return '猫咪';
} else if (serviceType === 'WALK') {
// 上门遛宠显示狗狗
return '狗狗';
}
},
groupedSlots() {
if (!this.orderInfo?.slots) return [];
// 按日期分组
const grouped = {};
this.orderInfo.slots.forEach((slot, index) => {
const date = moment(slot.service_date).format('YYYY-MM-DD');
if (!grouped[date]) {
// 第一个日期默认展开
const isFirstDate = index === 0;
grouped[date] = {
date: date,
slots: [],
expanded: this.expandedDates[date] !== undefined ? this.expandedDates[date] : isFirstDate,
price: this.orderInfo?.order?.service_actual_price || '0.00',
services: this.getServiceList(),
};
}
grouped[date].slots.push(slot);
});
return Object.values(grouped);
},
originalTotalPrice() {
// 原始总价
const basePrice = parseFloat(this.orderInfo?.order?.service_actual_price || 0);
const days = this.serviceDays;
return (basePrice * days).toFixed(2);
},
discountTotalPrice() {
// 会员折扣后的价格
const original = parseFloat(this.originalTotalPrice);
return (original * this.memberDiscount / 10).toFixed(2);
},
orderTotalPrice() {
// 订单总价(折扣后的价格)
return this.discountTotalPrice;
},
// discountTotal() {
// // 优惠总价
// const original = parseFloat(this.originalTotalPrice);
// const discounted = parseFloat(this.discountTotalPrice);
// return (original - discounted).toFixed(2);
// },
finalAmount() {
// 需付款金额
const total = parseFloat(this.orderTotalPrice);
// const discount = parseFloat(this.discountTotal);
// return (total - discount).toFixed(2);
return total.toFixed(2);
},
petAvatar() {
// 宠物头像,这里使用默认图片
return require('@/static/images/dog.png');
}
},
onLoad(options) {
// 获取订单IDURL参数名可能是 orderId 或 order_id
if (options.order_id) {
this.orderId = options.order_id;
} else if (options.orderId) {
this.orderId = options.orderId;
}
// 如果有 source 参数,使用它
if (options.source) {
this.source = options.source;
}
// 调用订单详情接口
if (this.orderId) {
this.getOrderDetail();
}
},
methods: {
getOrderDetail() {
if (!this.orderId) {
return;
}
this.isLoading = true;
uni.showLoading({
title: '加载中...'
});
getOrderWideDetail({
source: this.source,
order_id: parseInt(this.orderId)
}).then(res => {
uni.hideLoading();
this.isLoading = false;
// 处理订单详情数据
if (res.code === 0 && res.data) {
console.log('订单详情:', res.data);
// 更新订单数据
this.orderInfo = res.data;
// 设置宠物备注
if (res.data.order?.remark) {
this.petRemark = res.data.order.remark;
}
} else {
uni.showToast({
title: res.msg || res.message || '获取订单详情失败',
icon: 'none'
});
}
}).catch(err => {
uni.hideLoading();
this.isLoading = false;
console.error('获取订单详情失败:', err);
uni.showToast({
title: '获取订单详情失败',
icon: 'none'
});
});
},
toggleDate(dateIndex) {
const dateGroup = this.groupedSlots[dateIndex];
if (dateGroup) {
this.$set(this.expandedDates, dateGroup.date, !this.expandedDates[dateGroup.date]);
}
},
getServiceName() {
// 根据服务类型返回服务名称
const serviceType = this.orderInfo?.order?.service_type;
if (serviceType === 'WALK') {
return '遛宠服务';
} else if (serviceType === 'FEED') {
return '喂养服务';
}
return this.orderInfo?.order?.service_name || '上门服务';
},
getServiceList() {
// 返回服务项目列表
return [
{ name: '实时视频' },
{ name: '喂食' },
{ name: '换水' },
{ name: '猫砂盆清理' },
{ name: '15分钟陪玩' },
{ name: '健康监测' },
{ name: '实时反馈' }
];
},
getPetDesc(pet) {
// 构建宠物描述信息(不包含宠物名称,因为已单独显示)
// 这里需要根据实际数据结构来构建
return `${pet.breed_name}/${pet.gender === 'male' ? '男生' : '女生'}/${pet.age}`
},
handleModifyAddress() {
// 修改地址
uni.showToast({
title: '修改地址功能开发中',
icon: 'none'
});
},
selectPaymentMethod(method) {
this.paymentMethod = method;
},
viewServiceRefundsAgreement() {
// 跳转到服务与退款协议 webview 页面
const url = imgPrefix + 'servicesRefunds.html';
uni.navigateTo({
url: `/pages/webview/index?url=${encodeURIComponent(url)}&title=${encodeURIComponent('服务与退款协议')}`
});
},
viewServiceRefundAgreement() {
// 跳转到服务与退款协议 webview 页面
const url = imgPrefix + 'feedingServiceAgreement.html';
uni.navigateTo({
url: `/pages/webview/index?url=${encodeURIComponent(url)}&title=${encodeURIComponent('喂养服务保障协议')}`
});
},
handlePay() {
// 检查是否选择了支付方式
if (this.paymentMethod !== 'wechat') {
uni.showToast({
title: '请选择支付方式',
icon: 'none'
});
return;
}
// 检查订单信息
if (!this.orderInfo || !this.orderInfo.order) {
uni.showToast({
title: '订单信息不存在',
icon: 'none'
});
return;
}
const order = this.orderInfo.order;
const orderId = order.id;
const orderNo = order.order_no;
const totalFee = parseFloat(this.finalAmount);
if (!orderId || !orderNo) {
uni.showToast({
title: '订单信息不完整',
icon: 'none'
});
return;
}
// 显示加载提示
uni.showLoading({
title: '正在支付...',
mask: true
});
// 调用支付接口
// 根据服务类型确定 type: WALK(遛宠) 或 FEED(喂宠) 可能对应不同的 type
// 这里先使用 type: 7 (参考 training-booking.vue),可能需要根据实际情况调整
const paymentType = order.service_type === 'WALK' ? 8 : (order.service_type === 'FEED' ? 9 : 7);
payOrder({
type: 7,
order_id: orderId,
order_no: orderNo,
total_fee: totalFee
}).then((res) => {
uni.hideLoading();
if (res.code !== 0) {
uni.showToast({
title: res.msg || res.message || '支付失败',
icon: 'none'
});
return;
}
tt.pay({
orderInfo: {
order_id:res.data.orderInfo.order_id,
order_token:res.data.orderInfo.order_token,
},
service:5,
success: (payRes) => {
console.log('支付成功:', payRes);
uni.showToast({
title: '支付成功',
icon: 'success'
});
// 支付成功后跳转到订单列表页面并刷新
setTimeout(() => {
// 触发刷新事件,通知订单列表页面刷新
uni.$emit('refreshOrderList');
// 跳转到订单列表页面
uni.redirectTo({
url: '/pages/client/petOrder/index'
});
}, 800);
},
fail: (err) => {
console.error('支付失败:', err);
uni.showToast({
title: '支付失败',
icon: 'none'
});
}
});
}).catch((err) => {
uni.hideLoading();
console.error('支付接口调用失败:', err);
uni.showToast({
title: err.msg || err.message || '支付失败',
icon: 'none'
});
});
}
}
};
</script>
<style lang="scss" scoped>
.order-detail-container {
width: 100%;
height: 100vh;
background-color: #fbf8fc;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding: 20rpx;
}
.detail-container {
flex: 1;
display: flex;
flex-direction: column;
padding-bottom: 240rpx;
}
.scroll-view {
flex: 1;
height: 100%;
}
.order-info-section {
padding: 20rpx;
}
.info-cell {
background-color: #fff;
border-radius: 16rpx;
padding: 20rpx;
margin-bottom: 20rpx;
box-sizing: border-box;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
.address-cell {
.info-title-view {
display: flex;
align-items: center;
margin-bottom: 16rpx;
.info-icon {
width: 32rpx;
height: 32rpx;
margin-right: 16rpx;
}
.phone-text {
margin-left: 16rpx;
}
.default-tag {
margin-left: 16rpx;
padding: 4rpx 12rpx;
background-color: #fee9f3;
color: #FF19A0;
border-radius: 4rpx;
font-size: 20rpx;
}
}
.address-view {
display: flex;
align-items: flex-start;
.info-icon {
width: 32rpx;
height: 32rpx;
margin-right: 16rpx;
flex-shrink: 0;
}
.address-text {
flex: 1;
line-height: 1.6;
}
.modify-btn {
color: #FF19A0;
font-size: 28rpx;
margin-left: 16rpx;
}
}
}
.remark-cell {
display: flex;
flex-direction: column;
.remark-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
.info-label {
font-size: 24rpx;
color: #9B939A;
font-weight: 500;
margin-right: 24rpx;
flex-shrink: 0;
}
.remark-text {
font-size: 24rpx;
font-weight: 500;
color: #272427;
max-width: 60%;
word-break: break-all;
white-space: normal;
}
}
}
.service-dates-section {
background-color: #fff;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
border-radius: 24rpx;
padding: 20rpx;
.price-row {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 32rpx;
&.night-fee-row {
.night-fee-info {
display: flex;
align-items: center;
gap: 12rpx;
.night-fee-discount-tag {
font-size: 20rpx;
color: #FF19A0;
background-color: #fee9f3;
padding: 4rpx 8rpx;
border-radius: 4rpx;
}
.night-fee-original {
font-size: 24rpx;
color: #999;
text-decoration: line-through;
}
.night-fee-price {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
}
}
&.total-row {
.discount-info {
display: flex;
align-items: center;
gap: 12rpx;
.price-total {
font-size: 26rpx;
color: #3D3D3D;
}
.original-price {
font-size: 24rpx;
color: #999;
text-decoration: line-through;
}
.discount-price {
font-size: 32rpx;
color: #FF19A0;
font-weight: 500;
}
.discount-tag {
font-size: 20rpx;
color: #FF19A0;
background-color: #fee9f3;
padding: 4rpx 8rpx;
border-radius: 4rpx;
}
}
}
.price-label {
font-size: 24rpx;
color: #9B939A;
.remaining-count {
font-size: 24rpx;
color: #FF19A0;
}
}
}
.service-summary-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32rpx;
.service-days-text {
font-size: 28rpx;
color: #272427;
font-weight: 500;
}
.pet-count-text {
font-size: 24rpx;
color: #272427;
}
}
.date-service-cell {
background: #F5F5F5;
padding: 20rpx;
border-radius: 20rpx;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
.date-header {
display: flex;
align-items: center;
.date-info {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
.date-text {
font-size: 24rpx;
color: #9B939A;
font-weight: 500;
}
.service-name {
font-size: 24rpx;
color: #272427;
}
.service-price {
font-size: 24rpx;
color: #272427;
font-weight: 500;
}
.toggle-btn {
color: #FF19A0;
font-size: 24rpx;
}
}
}
.date-detail {
margin-top: 24rpx;
.pet-detail-item {
display: flex;
align-items: flex-start;
margin-bottom: 24rpx;
.pet-avatar {
width: 52rpx;
height: 52rpx;
border-radius: 50%;
margin-right: 8rpx;
flex-shrink: 0;
}
.pet-info {
flex: 1;
display: flex;
flex-direction: column;
.pet-name {
font-size: 24rpx;
color: #333333;
font-weight: 500;
}
.pet-desc {
font-size: 20rpx;
color: #666;
}
}
}
.service-list {
.service-item {
display: flex;
justify-content: space-between;
margin-bottom: 24rpx;
&:last-child {
border-bottom: none;
margin-bottom: 0;
}
.service-item-name {
font-size: 24rpx;
color: #9B939A;
}
.service-item-count {
font-size: 24rpx;
color: #272427;
}
}
}
}
}
}
.price-cell {
margin-top: 20rpx;
.price-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
&:last-child {
margin-bottom: 0;
}
&.night-fee-row {
.night-fee-info {
display: flex;
align-items: center;
gap: 12rpx;
.night-fee-discount-tag {
font-size: 20rpx;
color: #FF19A0;
background-color: #fee9f3;
padding: 4rpx 8rpx;
border-radius: 4rpx;
}
.night-fee-original {
font-size: 24rpx;
color: #999;
text-decoration: line-through;
}
.night-fee-price {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
}
}
&.total-row {
.discount-info {
display: flex;
align-items: center;
gap: 12rpx;
.original-price {
font-size: 24rpx;
color: #999;
text-decoration: line-through;
}
.discount-price {
font-size: 32rpx;
color: #FF19A0;
font-weight: 500;
}
.discount-tag {
font-size: 20rpx;
color: #FF19A0;
background-color: #fee9f3;
padding: 4rpx 8rpx;
border-radius: 4rpx;
}
}
}
&.final-row {
.final-price {
font-size: 36rpx;
color: #FF19A0;
font-weight: 600;
}
.price-label {
font-size: 26rpx;
color: #3D3D3D;
}
}
.price-label {
font-size: 24rpx;
color: #9B939A;
}
.price-value {
font-size: 24rpx;
color: #272427;
&.discount {
color: #FF19A0;
}
}
}
.price-divider {
height: 1px;
background-color: #f0f0f0;
margin-top: 24rpx;
margin-bottom: 32rpx;
}
}
.payment-method-cell {
.payment-option {
display: flex;
align-items: center;
.payment-icon {
width: 48rpx;
height: 48rpx;
margin-right: 16rpx;
}
.payment-name {
flex: 1;
font-size: 28rpx;
color: #333;
}
.not-selected {
width: 36rpx;
height: 36rpx;
}
}
}
.notice-cell {
.notice-title {
font-size: 24rpx;
color: #000000;
font-weight: 500;
margin-bottom: 16rpx;
display: block;
}
.notice-content {
font-size: 24rpx;
color: #808080;
line-height: 1.6;
}
}
.footer-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
padding: 24rpx 32rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.08);
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 16rpx;
border-radius: 32rpx 32rpx 0px 0px;
.agreement-text {
color: #9B939A;
text-align: center;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
.agreement-item {
font-size: 22rpx;
}
.agreement-link {
color: #FF19A0;
}
}
.footer-bottom-row {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.footer-price-info {
display: flex;
gap: 4rpx;
flex: 1;
align-items: flex-end;
.footer-price {
font-size: 48rpx;
color: #FF19A0;
font-weight: 600;
}
.footer-original-price {
font-size: 24rpx;
color: #999;
text-decoration: line-through;
}
.footer-discount {
font-size: 24rpx;
color: #FF19A0;
}
}
.pay-btn {
width: 238rpx;
height: 96rpx;
background-color: #FF19A0;
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
margin-left: 24rpx;
flex-shrink: 0;
.pay-btn-text {
font-size: 32rpx;
color: #fff;
font-weight: 500;
}
}
}
</style>