Browse Source

feat(订单): 实现再来一单功能并优化伴宠师选择流程

新增再来一单功能,支持从历史订单重新下单并保留原订单数据。重构伴宠师选择组件为独立组件,优化多选交互体验。添加10元再来一单费用计算逻辑,完善订单修改流程。

主要变更:
1. 新增CompanionItem组件统一伴宠师展示样式
2. 实现订单数据保留和定位信息处理
3. 添加再来一单费用计算和显示
4. 优化伴宠师多选交互和默认选中逻辑
5. 完善订单状态判断和服务记录查看条件
master
前端-胡立永 1 month ago
parent
commit
15edbb0f76
13 changed files with 1017 additions and 608 deletions
  1. +1
    -0
      .trae/rules/project_rules.md
  2. +13
    -1
      api/order/order.js
  3. +176
    -0
      components/CompanionItem/CompanionItem.vue
  4. +1
    -1
      pages.json
  5. +18
    -4
      pages/newOrder/confirmOrder.vue
  6. +94
    -2
      pages/newOrder/serviceNew2.vue
  7. +18
    -156
      pages_order/companionPetList/companionPetList.vue
  8. +275
    -10
      pages_order/components/order/CompanionSelectPopup.vue
  9. +408
    -426
      pages_order/order/companionSelect.vue
  10. +3
    -3
      pages_order/order/orderDetail.vue
  11. +6
    -5
      pages_order/order/orderList.vue
  12. +2
    -0
      pages_order/order/orderModify.vue
  13. +2
    -0
      store/index.js

+ 1
- 0
.trae/rules/project_rules.md View File

@ -0,0 +1 @@
当前项目是uniapp开发微信小程序,请你写出来的代码要符合微信小程序的规范,并且需要后期能够方便的进行维护扩展,方便uniapp转其他平台的开发

+ 13
- 1
api/order/order.js View File

@ -180,7 +180,7 @@ export const orderPay = (data) => {
})
}
// 再次支付订单
// 更新订单基本信息
export const updateBaseOrder = (params) => {
return request({
url: '/applet/mall/order/updateBaseOrder',
@ -190,4 +190,16 @@ export const updateBaseOrder = (params) => {
method: "POST",
params
})
}
// 查询服务过的伴宠师
export const getTecByUser = (params) => {
return request({
headers: {
"isToken": true
},
url: "/applet/mall/order/getTecByUser",
method: 'get',
params
})
}

+ 176
- 0
components/CompanionItem/CompanionItem.vue View File

@ -0,0 +1,176 @@
<template>
<uni-row :span="12">
<uni-card :is-shadow="false" padding=0 margin="6rpx 20rpx" @click="handleClick">
<view class="personal-list-item">
<view class="personal-info">
<view>
<image class="people-img" slot='cover' :src="item.userImage"></image>
</view>
<view class="personal-info-1">
<view class="personal-info-2">
<view class="personal-info-title">
<view class="personal-name">
{{ item.userName || '匿名' }}
</view>
<view class="personal-sex">
<img :src="item.appletUsersTeacher.sex == 0?'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/pet/sex_m.png':
'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/pet/sex_f.png'" alt="sex"
style="width: 20px;height: 20px;" />
</view>
</view>
<view class="personal-star" @click.stop="handleLikeClick">
<text style="color: #FFB13F;">点赞数{{ item.appletUsersTeacher.thumbsUp || 0 }}</text>
<uni-icons v-if="isLike" type="hand-up-filled" size="20" color="#FFB13F"></uni-icons>
<uni-icons v-else type="hand-up" size="20" color="#FFB13F"></uni-icons>
</view>
</view>
<view class="personal-info-3" style="width: 100%;">
<view class="ellipsis" v-if="item.distanceText">
距离{{ item.distanceText }}km
</view>
<view class="ellipsis" v-else>
{{ '<' }}1km
</view>
</view>
<view class="personal-info-4" style="width: 100%;">
<view class="ellipsis" style="max-width: 225px;">
简介{{ item.appletUsersTeacher.userBrief || '暂无' }}
</view>
</view>
</view>
</view>
</view>
<view>
<view class="personal-item-bottom">
<text class="personal-item-bottom-text">养宠{{ item.experience || 0 }} | 评价{{ item.commentNum || 0 }} | 服务小结{{ item.serviceSummaryNum || 0 }}</text>
</view>
</view>
</uni-card>
</uni-row>
</template>
<script>
export default {
name: 'CompanionItem',
props: {
item: {
type: Object,
required: true
}
},
data() {
return {
isLike: false
}
},
methods: {
//
handleClick() {
this.$emit('click', this.item)
},
//
handleLikeClick() {
this.isLike = !this.isLike
this.$emit('like', this.item)
}
}
}
</script>
<style lang="scss" scoped>
.personal-list-item {
padding: 10px 0px 0px 0;
.personal-info {
display: flex;
align-items: center;
justify-content: flex-start;
.people-img {
width: 168rpx;
height: 168rpx;
border: #FEA714 5rpx solid;
border-radius: 20rpx;
}
.personal-info-1 {
margin-left: 10px;
width: 100%;
.personal-info-2 {
display: flex;
.personal-info-title {
display: flex;
flex-wrap: wrap;
width: 60%;
}
.personal-name {
color: #333;
font-size: 28rpx;
margin-right: 10rpx;
font-weight: 900;
font-style: normal;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 200rpx;
}
.personal-star {
color: #FFAA48;
font-size: 24rpx;
font-weight: 400;
line-height: 32rpx;
flex-shrink: 0;
margin-left: auto;
}
}
.personal-info-3 {
display: flex;
align-items: baseline;
font-size: 28rpx;
line-height: 32rpx;
margin-top: 5px;
color: #FFAA48;
font-weight: 900;
}
.personal-info-4 {
display: flex;
align-items: baseline;
font-size: 24rpx;
margin-top: 10px;
color: #7D8196;
font-weight: 400;
line-height: 32rpx;
}
}
}
}
.personal-item-bottom {
height: 60rpx;
background-color: #FFF4E5;
margin: 20rpx 0 20rpx 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8rpx;
.personal-item-bottom-text {
color: #A94F20;
margin: 14rpx;
font-size: 24rpx;
font-weight: 400;
}
}
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>

+ 1
- 1
pages.json View File

@ -415,7 +415,7 @@
"style": {
"navigationBarTitleText": "服务过的伴宠师",
"navigationBarBackgroundColor": "#FFBF60",
"enablePullDownRefresh": false,
"enablePullDownRefresh": true,
"navigationBarTextStyle": "white"
}
},


+ 18
- 4
pages/newOrder/confirmOrder.vue View File

@ -181,6 +181,10 @@
<view style="color: #FF530A;">-¥{{ parseFloat(memberDiscount).toFixed(2) }}</view>
</view>
</view>
<view class="total-cost" v-if="$globalData.newOrderData.moreOrderPrice && $globalData.newOrderData.moreOrderPrice > 0">
<view>再来一单费用</view>
<view style="color: #FF530A;">+¥{{ parseFloat($globalData.newOrderData.moreOrderPrice).toFixed(2) }}</view>
</view>
<view class="total-cost">
<view>应付费用</view>
<view style="font-weight: 500;font-size: 32rpx;">¥{{ parseFloat(finalPrice).toFixed(2) }}</view>
@ -837,6 +841,11 @@
order.orderId = this.$globalData.newOrderData.orderId
}
//
if(this.$globalData.newOrderData.moreOrderPrice){
order.moreOrderPrice = this.$globalData.newOrderData.moreOrderPrice
}
console.log(order)
return order
},
@ -902,14 +911,16 @@
needPreFamiliarize: []
}
uni.reLaunch({
url: '/pages_order/order/payOrderSuccessful'
// url: '/pages/details/successful'
url: '/pages_order/order/orderList'
});
},
fail: (err) => {
this.loading = false
console.log('支付失败', err)
this.$modal.showToast('支付失败')
uni.reLaunch({
url: '/pages_order/order/'
});
},
complete: () => {
this.loading = false
@ -938,9 +949,12 @@
const originalPriceBeforeMemberDiscount = this.originalTotalPrice / this.$store.state.memberRate
this.memberDiscount = (originalPriceBeforeMemberDiscount - this.originalTotalPrice).toFixed(2)
//
const moreOrderPrice = this.$globalData.newOrderData.moreOrderPrice || 0
//
// finalPrice: = -
this.finalPrice = (this.originalTotalPrice - this.discount).toFixed(2)
// finalPrice: = + -
this.finalPrice = (this.originalTotalPrice + moreOrderPrice - this.discount).toFixed(2)
},
getCouponAmountOrDiscount(item) {
if (item.stockType == "PDISCOUNT") {


+ 94
- 2
pages/newOrder/serviceNew2.vue View File

@ -457,6 +457,53 @@ export default {
return d * this.distancePrice
},
getCustomServicesByOrder(pet, date){
//
const currentCustomServices = JSON.parse(this.customServicesStr || '[]')
const customServices = []
//
currentCustomServices.forEach(service => {
let quantity = 0 // 0
//
if(this.$globalData.newOrderData.originalOrderData) {
const originalOrderData = this.$globalData.newOrderData.originalOrderData
if(originalOrderData.orderItemList && originalOrderData.orderItemList.length > 0) {
//
const originalItem = originalOrderData.orderItemList.find(item => {
// categoryId78skuId
if(item.productCategoryId === 78 && item.isMainProduct !== 1 && item.skuId === service.skuId) {
//
const matchingService = originalOrderData.orderServiceList.find(serviceRecord => {
return serviceRecord.id === item.orderServiceId &&
serviceRecord.petId === pet.id &&
serviceRecord.serviceDate === date
})
return !!matchingService
}
return false
})
if(originalItem) {
quantity = originalItem.quantity
}
}
}
//
customServices.push({
skuId: service.skuId,
name: service.name,
price: service.price,
quantity: quantity,
isMainProduct: false
})
})
return customServices
},
initNewOrderData() {
const needPreFamiliarize = this.$globalData.newOrderData.needPreFamiliarize.length > 0
const pets = []
@ -470,6 +517,9 @@ export default {
//
const serviceDateList = pet.selectedDate.map(item => item.date)
serviceDateList.forEach(date => {
//
const originalCustomServices = this.getCustomServicesByOrder(pet, date)
pets.push({
petId: pet.id,
serviceDate: date,
@ -480,7 +530,7 @@ export default {
photo: pet.photo,
feedCount: 1,
selectedTimeSlots: [],
customServices: [],
customServices: originalCustomServices,
//
additionalCost: 0
})
@ -550,6 +600,27 @@ export default {
this.feedCount = selectedPet ? selectedPet.feedCount : 1
//
this.customServices = selectedPet.customServices.length > 0 ? selectedPet.customServices : JSON.parse(this.customServicesStr)
/*
if (selectedPet && selectedPet.customServices.length > 0) {
// 使
this.customServices = selectedPet.customServices
} else {
// 使
this.customServices = JSON.parse(this.customServicesStr)
//
if (this.$globalData.newOrderData.originalOrderData) {
const currentPet = this.currentPets.find(pet => pet.id === this.currentPetId)
if (currentPet) {
const originalCustomServices = this.getCustomServicesByOrder(currentPet, this.currentMonthDay.fullDate)
if (originalCustomServices.length > 0) {
this.customServices = originalCustomServices
}
}
}
}
*/
this.getCurrentDayPrice(this.currentMonthDay.fullDate)
},
@ -825,12 +896,16 @@ export default {
this.customServiceItemCount = customServiceItemCount
this.customServicesTotalCost = customServicesTotalCost
//
const moreOrderPrice = this.$globalData.newOrderData.moreOrderPrice || 0;
this.totalPrice = Number(this.needPreFamiliarizeCost)
+ Number(this.baseServiceTotalCost)
+ Number(this.additionalTotalCost)
+ Number(this.multServicesTotalCost)
+ Number(this.companionLevelPrice())
+ Number(this.customServicesTotalCost); //
+ Number(this.customServicesTotalCost)
+ Number(moreOrderPrice); //
this.getCurrentDayPrice(this.currentMonthDay.fullDate)
},
@ -850,6 +925,7 @@ export default {
},
goNext() {
console.log('this.$globalData.newOrderData', this.$globalData.newOrderData)
this.calculateTotalPrice(); //
this.showPriceDetails = false;
this.$globalData.newOrderData.currentPetsByDay = this.newOrderData.pets
@ -861,6 +937,7 @@ export default {
goBack() {
this.showPriceDetails = false
let len = getCurrentPages().length;
this.loading = false
if (len >= 2) {
@ -1023,6 +1100,21 @@ export default {
item: customServiceItem
})
}
//
const moreOrderPrice = this.$globalData.newOrderData.moreOrderPrice || 0;
if (moreOrderPrice > 0) {
priceDetails.push({
name: '再来一单',
item: [{
itemName: '再来一单费用',
price: moreOrderPrice,
quantity: 1,
unit: '次'
}]
})
}
this.priceDetails = priceDetails
this.calculateTotalPrice()
},


+ 18
- 156
pages_order/companionPetList/companionPetList.vue View File

@ -106,64 +106,13 @@
</view>
<view>
<view v-if="companionList.length > 0" style="padding: 10rpx 0;">
<uni-row :span="12" v-for="(item,index) in companionList" :key="index">
<uni-card :is-shadow="false" padding=0 margin="6rpx 20rpx"
@click="getInfo(item.userId)">
<view class="personal-list-item">
<view class="personal-info">
<view>
<!-- <image class="people-img" slot='cover'
:src="item&&item.staffImages&&item.staffImages.length>0?item.staffImages[0].url:defaultStaffIamge">
</image> -->
<image class="people-img" slot='cover'
:src="item.userImage">
</image>
</view>
<view class="personal-info-1">
<view class="personal-info-2">
<view class="personal-info-title">
<view class="personal-name">
{{ item.userName || '匿名' }}
</view>
<view class="personal-sex">
<img :src="item.appletUsersTeacher.sex == 0?'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/pet/sex_m.png':
'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/pet/sex_f.png'" alt="sex"
style="width: 20px;height: 20px;" />
</view>
</view>
<view class="personal-star" @click.stop="checkIsLike">
<!-- 客户 -->
<text style="color: #FFB13F;">点赞数{{ item.appletUsersTeacher.thumbsUp || 0 }}</text>
<uni-icons v-if="isLike" type="hand-up-filled" size="20"
color="#FFB13F"></uni-icons>
<uni-icons v-else type="hand-up" size="20" color="#FFB13F"></uni-icons>
</view>
</view>
<view class="personal-info-3" style="width: 100%;">
<view class="ellipsis" v-if="item.distanceText">
距离{{ item.distanceText }}km
</view>
<view class="ellipsis" v-else>
{{ '<' }}1km
</view>
</view>
<view class="personal-info-4" style="width: 100%;">
<view class="ellipsis" style="max-width: 225px;">
简介{{ item.appletUsersTeacher.userBrief || '暂无' }}
</view>
</view>
</view>
</view>
</view>
<view>
<view class="personal-item-bottom">
<text class="personal-item-bottom-text">养宠{{ item.experience || 0 }} | 评价{{ item.commentNum || 0 }} | 服务小结{{ item.serviceSummaryNum || 0 }}</text>
</view>
</view>
</uni-card>
</uni-row>
<companion-item
v-for="(item,index) in companionList"
:key="index"
:item="item"
@click="handleItemClick"
@like="handleItemLike">
</companion-item>
</view>
<view v-else
style="
@ -180,6 +129,7 @@
import uniPopup from '@/uni_modules/uni-popup/components/uni-popup/uni-popup.vue';
import uniFab from '@/uni_modules/uni-fab/components/uni-fab/uni-fab.vue'
import FilterPopup from '@/components/FilterPopup/FilterPopup.vue'
import CompanionItem from '@/components/CompanionItem/CompanionItem.vue'
import positionMixin from '../../mixins/position';
// import {
// getCompanionList,
@ -222,14 +172,13 @@
petTypes: ['1', '2'],
value: '',
companionList: [],
isLike: false,
likeNum: 560,
defaultStaffIamge: 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/banner/gold_people.png',
}
},
components: {
uniPopup,
FilterPopup
FilterPopup,
CompanionItem
},
onLoad: function(option) {
let info = JSON.parse(decodeURIComponent(option.info));
@ -256,9 +205,14 @@
this.selectedDateShowText = this.allInfo.selectedDate[0].date.replace(/-/g, "/") + '...' + ' '
}
},
checkIsLike() {
this.isLike = !this.isLike
this.$forceUpdate()
//
handleItemClick(item) {
this.getInfo(item.userId)
},
//
handleItemLike(item) {
//
console.log('点赞伴宠师:', item)
},
changeOrderType() {
uni.navigateTo({
@ -551,99 +505,7 @@
justify-content: center;
}
.personal-list-item {
padding: 10px 0px 0px 0;
.personal-info {
display: flex;
align-items: center;
justify-content: flex-start;
.people-img {
width: 168rpx;
height: 168rpx;
border: #FEA714 5rpx solid;
border-radius: 20rpx;
}
.personal-info-1 {
margin-left: 10px;
width: 100%;
.personal-info-2 {
display: flex;
// flex-wrap: wrap;
.personal-info-title {
display: flex;
flex-wrap: wrap;
width: 60%;
}
.personal-name {
color: #333;
font-size: 28rpx;
// line-height: 32rpx;
margin-right: 10rpx;
font-weight: 900;
font-style: normal;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 200rpx;
}
.personal-star {
color: #FFAA48;
font-size: 24rpx;
font-weight: 400;
line-height: 32rpx;
flex-shrink: 0;
margin-left: auto;
}
}
.personal-info-3 {
display: flex;
align-items: baseline;
font-size: 28rpx;
line-height: 32rpx;
margin-top: 5px;
color: #FFAA48;
font-weight: 900;
}
.personal-info-4 {
display: flex;
align-items: baseline;
font-size: 24rpx;
margin-top: 10px;
color: #7D8196;
font-weight: 400;
line-height: 32rpx;
}
}
}
}
.personal-item-bottom {
height: 60rpx;
background-color: #FFF4E5;
margin: 20rpx 0 20rpx 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8rpx;
.personal-item-bottom-text {
color: #A94F20;
margin: 14rpx;
font-size: 24rpx;
font-weight: 400;
}
}
.popupBottom {
z-index: 99;


+ 275
- 10
pages_order/components/order/CompanionSelectPopup.vue View File

@ -30,13 +30,13 @@
</view>
</view>
<view class="popup-content">
<view class="option-item" @click="selectOption('yes')">
<view class="option-item" @click="selectOption('系统下单')">
<text class="option-text">系统下单</text>
<view class="option-circle" :class="{'selected': selectedOption === 'yes'}">
<view class="option-inner" v-if="selectedOption === 'yes'"></view>
</view>
</view>
<view class="option-item" @click="selectOption('no')">
<view class="option-item" @click="selectOption('指定伴宠师')">
<text class="option-text">指定伴宠师</text>
<view class="option-circle" :class="{'selected': selectedOption === 'no'}">
<view class="option-inner" v-if="selectedOption === 'no'"></view>
@ -48,42 +48,247 @@
</template>
<script>
import { getOrderDetail, getTeacherDetail } from '@/api/order/order.js'
import { getAddressDetails } from '@/api/system/address.js'
import { getOpenIdKey } from '@/utils/auth'
import { mapState } from 'vuex'
import positionMixin from '@/mixins/position'
export default {
mixins: [positionMixin],
data() {
return {
selectedOption: '',
type : 0,
teacherId: null, // ID
orderId: null, // ID
originalOrderData: null, //
buyInfo: {
teacher: null //
}
}
},
computed: {
...mapState(['teacherLevelList']),
},
methods: {
//
open() {
async open(teacherId = null, orderId = null) {
this.selectedOption = '';
this.type = 0;
this.teacherId = teacherId;
this.orderId = orderId;
// ID
if (this.orderId) {
await this.loadOrderData();
}
this.$refs.popup.open('bottom');
},
//
async loadOrderData() {
if (!this.orderId) return;
const params = {
openId: getOpenIdKey(),
orderId: this.orderId
};
try {
const res = await getOrderDetail(params);
if (res) {
this.originalOrderData = res;
console.log('获取订单详情成功:', res);
} else {
console.error('获取订单详情失败');
}
} catch (error) {
console.error('获取订单数据失败', error);
}
},
//
close() {
this.$refs.popup.close();
},
// async submit() {
// //
// if (this.originalOrderData) {
// await this.processOrderData();
// }
// uni.navigateTo({
// url: `/pages/newOrder/serviceNew`
// });
// },
//
async processOrderData() {
if (!this.originalOrderData) return;
uni.showLoading({
title: '加载中...',
mask: true
})
const order = this.originalOrderData;
//
this.$globalData.newOrderData.originalOrderData = order;
this.$globalData.newOrderData.moreOrderPrice = 10;
//
if (order.addressId) {
try {
const addressRes = await getAddressDetails(order.addressId);
if (addressRes && addressRes.id) {
//
this.$globalData.newOrderData.currentAddress = {
id: order.addressId,
name: order.receiverName,
phone: order.receiverPhone,
province: order.receiverProvince,
city: order.receiverCity,
district: order.receiverDistrict,
detailAddress: order.receiverDetailAddress,
latitude: parseFloat(order.latitude) || 0,
longitude: parseFloat(order.longitude) || 0,
}
console.log('设置地址信息成功,包含定位:', {
latitude: order.latitude,
longitude: order.longitude
});
} else {
//
console.log('地址不存在,addressId:', order.addressId);
this.$globalData.newOrderData.currentAddress = {};
}
} catch (error) {
//
this.$globalData.newOrderData.currentAddress = {};
}
} else {
// ID
this.$globalData.newOrderData.currentAddress = {};
}
//
// if (order.teacherId) {
// try {
// const teacherRes = await getTeacherDetail({
// userId: order.teacherId
// });
// if (teacherRes) {
// let companionInfo = teacherRes;
// //
// if (teacherRes.appletAddresseList && this.$globalData.newOrderData.latitude && this.$globalData.newOrderData.longitude) {
// companionInfo.distanceText = this.calculateDistanceAddress(teacherRes.appletAddresseList);
// }
// this.buyInfo.teacher = companionInfo;
// //
// this.$globalData.newOrderData.selectedTeacher = companionInfo;
// console.log(':', companionInfo);
// }
// } catch (error) {
// console.error(':', error);
// }
// }
//
if (order.companionLevel) {
this.$globalData.newOrderData.companionLevel =
this.teacherLevelList.find(item => item.paramValueNum == order.companionLevel);
}
//
if (order.needPreFamiliarize) {
this.$globalData.newOrderData.needPreFamiliarize = ['是否提前熟悉'];
}
//
if (order.petVOList && order.petVOList.length > 0) {
this.$globalData.newOrderData.currentPets = order.petVOList.map(pet => {
//
const petServices = order.orderServiceList.filter(service => service.petId === pet.id);
const selectedDate = petServices.map(service => ({
date: service.serviceDate,
info: "预定"
}));
return {
...pet,
checked: ['checked'], //
selectedDate,
};
});
}
uni.hideLoading();
},
//
selectOption(option) {
async selectOption(option) {
this.selectedOption = option;
if(this.type == 1){
if (option === 'yes') {
if (option === '系统下单') {
//
if (this.originalOrderData) {
await this.processOrderData();
}
this.close();
setTimeout(() => {
uni.navigateTo({
url: '/pages/newOrder/serviceNew'
});
}, 300);
} else if (option === 'no') {
} else if (option === '指定伴宠师') {
//
if (this.originalOrderData) {
await this.processOrderData();
}
let locationInfo = this.$globalData.newOrderData.currentAddress;
if(!locationInfo ||
!locationInfo.latitude ||
!locationInfo.longitude
){
this.selectLocation();
return
}
let address = locationInfo.province + locationInfo.city + locationInfo.district + locationInfo.detailAddress;
//
const allInfo = {
isCheckLocation: true,
locationName: address,
locationLongitude: locationInfo.longitude,
locationLatitude: locationInfo.latitude,
locationAddress: address,
selectedDate: [],
isCheckTime: false,
selectedDateShowText: '',
};
this.$store.commit('setPosition', {
address: address,
longitude: locationInfo.longitude,
latitude: locationInfo.latitude,
date : [],
})
this.close();
setTimeout(() => {
uni.navigateTo({
url: '/pages_order/companionPetList/companionPetList'
url: '/pages_order/companionPetList/companionPetList?info=' + encodeURIComponent(JSON.stringify(allInfo))
});
}, 300);
}
@ -93,16 +298,76 @@ export default {
// ""
if (option === 'yes') {
this.close();
setTimeout(() => {
setTimeout(async () => {
//
if (this.originalOrderData) {
await this.processOrderData();
}
let url = '/pages_order/order/companionSelect';
let params = [];
// IDURL
if (this.teacherId) {
params.push(`teacherId=${this.teacherId}`);
}
// IDURL
if (this.orderId) {
params.push(`orderId=${this.orderId}`);
}
if (params.length > 0) {
url += `?${params.join('&')}`;
}
uni.navigateTo({
url: '/pages_order/order/companionSelect'
url: url
});
}, 300);
} else if (option === 'no') {
this.type = 1;
this.selectedOption = '';
}
}
},
//
selectLocation() {
wx.chooseLocation({
type: 'gcj02',
success: (res) => {
console.log('选择的位置:', res);
//
const allInfo = {
isCheckLocation: true,
locationName: res.address,
locationLongitude: res.longitude,
locationLatitude: res.latitude,
locationAddress: res.address,
selectedDate: [],
isCheckTime: false,
selectedDateShowText: '',
};
this.$store.commit('setPosition', {
address: res.address,
longitude: res.longitude,
latitude: res.latitude,
date : [],
})
//
uni.navigateTo({
url: `/pages_order/companionPetList/companionPetList?info=` + encodeURIComponent(JSON.stringify(allInfo))
})
},
fail: (err) => {
console.error('选择位置失败:', err);
uni.showToast({
title: '选择位置失败,请重试',
icon: 'none',
duration: 2000
})
}
});
},
}
}
</script>


+ 408
- 426
pages_order/order/companionSelect.vue View File

@ -1,434 +1,416 @@
<template>
<view class="companion-select-page">
<!-- 顶部警告提示 -->
<view class="warning-tip">
<view class="warning-icon">
<uni-icons type="info" size="16" color="#A94F20"></uni-icons>
</view>
<view class="warning-text">
<text>相距距离仅供参考伴宠师位置可能不是实时位置请提前10天下单</text>
</view>
</view>
<!-- 伴宠师列表 -->
<scroll-view scroll-y class="companion-scroll">
<view class="companion-list">
<view
class="companion-item"
v-for="(item, index) in companionList"
:key="index"
@click="selectCompanion(item)"
:class="{'selected': selectedCompanionId === item.id}"
>
<!-- 左侧选中标记 -->
<view class="select-icon">
<view class="radio-circle" :class="{'checked': selectedCompanionId === item.id}">
<view class="radio-inner" v-if="selectedCompanionId === item.id"></view>
</view>
</view>
<!-- 伴宠师卡片内容 -->
<view class="companion-card">
<!-- 头像和基本信息 -->
<view class="card-header">
<view class="companion-avatar">
<image :src="item.avatar" mode="aspectFill"></image>
<image v-if="item.verified" class="verified-icon" src="/static/images/details/verified.svg"></image>
</view>
<view class="companion-basic-info">
<view class="companion-name-row">
<text class="companion-name">{{item.name}}</text>
<image :src="item.gender === '男生' ? '/static/images/details/boy.svg' : '/static/images/details/girl.svg'" class="gender-icon"></image>
</view>
<view class="companion-rating">
<text class="client-rating">客户点赞: {{item.likes}}</text>
<image src="/static/images/details/like.svg" class="like-icon"></image>
</view>
<view class="companion-distance">
<text>距离您: {{item.distance}} km</text>
</view>
</view>
</view>
<!-- 描述信息 -->
<view class="companion-desc">
<text>{{item.description || '简介:有一只3岁金猫-忽悠,热爱小宠物...'}}</text>
</view>
<!-- 伴宠师经验描述 -->
<view class="companion-experience">
<text>{{item.experience}}</text>
</view>
<!-- 查看详情按钮 -->
<view class="view-detail-btn" @click.stop="viewCompanionDetail(item.id)">
<text>查看详情</text>
</view>
</view>
</view>
<!-- 无数据提示 -->
<view class="no-data" v-if="companionList.length === 0">
<image src="/static/images/personal/no-data.png" mode="aspectFit"></image>
<text>暂无伴宠师数据</text>
</view>
</view>
</scroll-view>
<!-- 底部按钮 -->
<view class="footer-buttons">
<view class="cancel-btn" @click="cancel">
<text>取消</text>
</view>
<view class="confirm-btn" @click="confirm">
<text>确定</text>
</view>
</view>
</view>
<view class="companion-select-page">
<!-- 顶部警告提示 -->
<view class="warning-tip">
<view class="warning-icon">
<uni-icons type="info" size="16" color="#A94F20"></uni-icons>
</view>
<view class="warning-text">
<text>指定之前服务过的伴宠师需要额外收10元哦可多选最终由系统根据伴宠师的接单时间安排为您安排您喜欢的伴宠师</text>
</view>
</view>
<!-- 伴宠师列表 -->
<scroll-view scroll-y class="companion-scroll">
<view class="companion-list">
<view v-for="(item, index) in companionList" :key="index" class="companion-wrapper"
:class="{ 'selected': selectedCompanionIds.includes(item.userId) }">
<!-- 左侧选中标记 -->
<view class="select-icon" @click="selectCompanion(item)">
<view class="checkbox-circle" :class="{ 'checked': selectedCompanionIds.includes(item.userId) }">
<view class="checkbox-inner" v-if="selectedCompanionIds.includes(item.userId)"></view>
</view>
</view>
<!-- 使用CompanionItem组件 -->
<view class="companion-item-wrapper">
<companion-item :item="item" @click="handleItemClick" @like="handleItemLike">
</companion-item>
</view>
</view>
<!-- 无数据提示 -->
<view class="no-data" v-if="companionList.length === 0">
<image src="/static/images/personal/no-data.png" mode="aspectFit"></image>
<text>暂无伴宠师数据</text>
</view>
</view>
</scroll-view>
<!-- 底部按钮 -->
<view class="footer-buttons">
<view class="cancel-btn" @click="cancel">
<text>取消</text>
</view>
<view class="confirm-btn" @click="confirm">
<text>确定{{ selectedCompanionIds.length > 0 ? `(${selectedCompanionIds.length})` : '' }}</text>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
companionList: [
{
id: '1',
name: '宠物宝贝',
gender: '男生',
avatar: 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/details/QR_Code.png',
verified: true,
rating: 5.0,
likes: 5601,
distance: 10.8,
description: '简介:有一只3岁金猫-忽悠,热爱小宠物...',
experience: '养宠4年 | 评价11条 | 服务小结13份'
},
{
id: '2',
name: '宠物宝贝',
gender: '女生',
avatar: 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/details/QR_Code.png',
verified: true,
rating: 5.0,
likes: 5601,
distance: 10.8,
description: '简介:有一只3岁金猫-忽悠,热爱小宠物...',
experience: '养宠4年 | 评价11条 | 服务小结13份'
}
],
selectedCompanionId: '',
};
},
computed: {
//
selectedCompanion() {
if (!this.selectedCompanionId) return null;
return this.companionList.find(item => item.id === this.selectedCompanionId);
}
},
onLoad(options) {
//
this.getServicedCompanions();
},
methods: {
//
getServicedCompanions() {
// API
//
/*
getServicedCompanions().then(res => {
if (res && res.code === 200) {
this.companionList = res.data || [];
}
}).catch(err => {
console.error('获取服务过的伴宠师失败', err);
});
*/
// 使
console.log('获取服务过的伴宠师列表');
},
//
selectCompanion(companion) {
this.selectedCompanionId = companion.id;
},
//
cancel() {
uni.navigateBack();
},
//
confirm() {
if (!this.selectedCompanionId) {
uni.showToast({
title: '请选择伴宠师',
icon: 'none'
});
return;
}
//
//
//
/*
const pages = getCurrentPages();
const prevPage = pages[pages.length - 2];
prevPage.$vm.setSelectedCompanion(this.selectedCompanion);
*/
uni.navigateBack();
},
//
viewCompanionDetail(companionId) {
//
uni.navigateTo({
url: `/pages_order/companionPetList/companionPetInfo?id=${companionId}`
});
}
}
}
import CompanionItem from '@/components/CompanionItem/CompanionItem.vue'
import { getTecByUser, getOrderDetail, getTeacherDetail } from '@/api/order/order.js'
import { getOpenIdKey } from '@/utils/auth'
import { getAddressDetails } from '@/api/system/address.js'
import { mapState } from 'vuex'
import positionMixin from '@/mixins/position'
export default {
mixins: [positionMixin],
components: {
CompanionItem
},
data() {
return {
companionList: [],
selectedCompanionIds: [],
defaultTeacherId: null, // ID
originalOrderData: null, //
orderId: null, // ID
};
},
computed: {
...mapState(['teacherLevelList']),
//
selectedCompanions() {
if (this.selectedCompanionIds.length === 0) return [];
return this.companionList.filter(item => this.selectedCompanionIds.includes(item.userId));
}
},
onLoad(options) {
// ID
if (options.teacherId) {
this.defaultTeacherId = options.teacherId;
}
// ID
if (options.orderId) {
this.orderId = options.orderId;
this.loadOrderData();
}
//
this.getServicedCompanions();
},
onPullDownRefresh() {
this.getServicedCompanions()
},
methods: {
//
getServicedCompanions() {
console.log(getOpenIdKey())
getTecByUser({
openId: getOpenIdKey()
}).then(res => {
uni.stopPullDownRefresh()
if (res && res.code == 200) {
this.companionList = res.data || [];
// ID
if (this.defaultTeacherId) {
const defaultCompanion = this.companionList.find(item => item.userId == this.defaultTeacherId);
if (defaultCompanion && !this.selectedCompanionIds.includes(this.defaultTeacherId)) {
this.selectedCompanionIds.push(this.defaultTeacherId);
}
}
}
}).catch(err => {
uni.stopPullDownRefresh()
console.error('获取服务过的伴宠师失败', err);
});
},
//
async loadOrderData() {
if (!this.orderId) return;
const params = {
openId: getOpenIdKey(),
orderId: this.orderId
};
try {
const res = await getOrderDetail(params);
if (res) {
this.originalOrderData = res;
} else {
console.error('获取订单详情失败');
}
} catch (error) {
console.error('获取订单数据失败', error);
}
},
//
selectCompanion(companion) {
const userId = companion.userId;
const index = this.selectedCompanionIds.indexOf(userId);
if (index > -1) {
//
this.selectedCompanionIds.splice(index, 1);
} else {
//
this.selectedCompanionIds.push(userId);
}
},
//
handleItemClick(item) {
//
this.selectCompanion(item);
},
//
handleItemLike(item) {
//
console.log('点赞伴宠师:', item);
},
//
cancel() {
uni.navigateBack();
},
//
confirm() {
if (this.selectedCompanionIds.length === 0) {
uni.showToast({
title: '请至少选择一个伴宠师',
icon: 'none'
});
return;
}
this.submit()
},
async submit() {
let order = this.originalOrderData
this.$globalData.newOrderData.originalOrderData = order
this.$globalData.newOrderData.moreOrderPrice = 10
//
if (order.addressId) {
try {
const addressRes = await getAddressDetails(order.addressId);
if (addressRes && addressRes.id) {
//
this.$globalData.newOrderData.currentAddress = {
id: order.addressId,
name: order.receiverName,
phone: order.receiverPhone,
province: order.receiverProvince,
city: order.receiverCity,
district: order.receiverDistrict,
detailAddress: order.receiverDetailAddress,
latitude: order.latitude,
longitude: order.longitude,
}
} else {
//
console.log('地址不存在,addressId:', order.addressId);
this.$globalData.newOrderData.currentAddress = {};
}
} catch (error) {
console.error('验证地址失败:', error);
//
this.$globalData.newOrderData.currentAddress = {};
}
} else {
// ID
this.$globalData.newOrderData.currentAddress = {};
}
if (order.teacherId) {
getTeacherDetail({
userId: order.teacherId
}).then(response => {
if (response) {
let companionInfo = response
companionInfo.distanceText = this.calculateDistanceAddress(response.appletAddresseList)
this.buyInfo.teacher = companionInfo
}
})
}
if (order.companionLevel) {
this.$globalData.newOrderData.companionLevel =
this.teacherLevelList.find(item => item.paramValueNum == order.companionLevel);
}
//
if (order.needPreFamiliarize) {
this.$globalData.newOrderData.needPreFamiliarize = ['是否提前熟悉']
}
//
if (order.petVOList && order.petVOList.length > 0) {
this.$globalData.newOrderData.currentPets = order.petVOList.map(pet => {
//
const petServices = order.orderServiceList.filter(service => service.petId === pet.id);
const selectedDate = petServices.map(service => ({
date: service.serviceDate,
info: "预定"
}));
return {
...pet,
checked: ['checked'], //
selectedDate,
};
});
}
uni.navigateTo({
url: `/pages/newOrder/serviceNew`
});
},
//
viewCompanionDetail(companionId) {
//
uni.navigateTo({
url: `/pages_order/companionPetList/companionPetInfo?id=${companionId}`
});
}
}
}
</script>
<style lang="scss" scoped>
.companion-select-page {
background-color: #F5F5F5;
min-height: 100vh;
display: flex;
flex-direction: column;
padding-bottom: 120rpx;
}
.warning-tip {
background-color: #FFF4E5;
padding: 20rpx 30rpx;
display: flex;
align-items: center;
margin: 20rpx;
.warning-icon {
margin-right: 10rpx;
}
.warning-text {
flex: 1;
font-size: 24rpx;
color: #A94F20;
line-height: 1.4;
}
}
.companion-scroll {
flex: 1;
height: calc(100vh - 250rpx);
}
.companion-list {
padding: 20rpx;
}
.companion-item {
background-color: #FFFFFF;
border-radius: 16rpx;
padding: 20rpx;
margin-bottom: 20rpx;
display: flex;
align-items: flex-start;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
border: 2rpx solid #fff;
.select-icon {
width: 40rpx;
height: 40rpx;
margin-right: 20rpx;
display: flex;
align-items: center;
justify-content: center;
.radio-circle {
width: 32rpx;
height: 32rpx;
border: 2rpx solid #DDDDDD;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
&.checked {
border-color: #FFAA48;
}
.radio-inner {
width: 18rpx;
height: 18rpx;
background-color: #FFAA48;
border-radius: 50%;
}
}
}
&.selected {
border: 2rpx solid #FFAA48;
}
.companion-card {
flex: 1;
}
.card-header {
display: flex;
margin-bottom: 16rpx;
}
.companion-avatar {
position: relative;
width: 100rpx;
height: 100rpx;
margin-right: 20rpx;
image {
width: 100%;
height: 100%;
border-radius: 10rpx;
}
.verified-icon {
position: absolute;
right: -10rpx;
bottom: -10rpx;
width: 30rpx;
height: 30rpx;
}
}
.companion-basic-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.companion-name-row {
display: flex;
align-items: center;
margin-bottom: 6rpx;
.companion-name {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-right: 10rpx;
}
.gender-icon {
width: 24rpx;
height: 24rpx;
}
}
.companion-rating {
display: flex;
align-items: center;
margin-bottom: 6rpx;
.client-rating {
font-size: 24rpx;
color: #666;
margin-right: 6rpx;
}
.like-icon {
width: 24rpx;
height: 24rpx;
}
}
.companion-distance {
font-size: 24rpx;
color: #999;
}
.companion-desc {
font-size: 24rpx;
color: #666;
margin-bottom: 16rpx;
line-height: 1.4;
}
.companion-experience {
font-size: 24rpx;
color: #999;
background-color: #FFF9E6;
padding: 16rpx;
border-radius: 8rpx;
margin-bottom: 16rpx;
color: #be721b;
text-align: center;
}
.view-detail-btn {
align-self: flex-end;
background-color: #FFAA48;
color: #FFFFFF;
font-size: 26rpx;
padding: 12rpx 30rpx;
border-radius: 30rpx;
text-align: center;
width: fit-content;
margin-left: auto;
}
}
.no-data {
text-align: center;
padding: 100rpx 0;
image {
width: 200rpx;
height: 200rpx;
margin-bottom: 20rpx;
}
text {
font-size: 28rpx;
color: #999;
}
}
.footer-buttons {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
padding: 20rpx 30rpx;
background-color: #FFFFFF;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
.cancel-btn, .confirm-btn {
flex: 1;
height: 88rpx;
line-height: 88rpx;
text-align: center;
border-radius: 44rpx;
font-size: 30rpx;
}
.cancel-btn {
background-color: #FFFFFF;
color: #666;
border: 1px solid #DDDDDD;
margin-right: 20rpx;
}
.confirm-btn {
background-color: #FFAA48;
color: #FFFFFF;
box-shadow: 0 4rpx 8rpx rgba(255, 170, 72, 0.3);
}
}
.companion-select-page {
background-color: #F5F5F5;
min-height: 100vh;
display: flex;
flex-direction: column;
padding-bottom: 120rpx;
}
.warning-tip {
background-color: #FFF4E5;
padding: 20rpx 30rpx;
display: flex;
align-items: center;
margin: 20rpx;
.warning-icon {
margin-right: 10rpx;
}
.warning-text {
flex: 1;
font-size: 24rpx;
color: #A94F20;
line-height: 1.4;
}
}
.companion-scroll {
flex: 1;
height: calc(100vh - 250rpx);
}
.companion-list {
padding: 10rpx;
padding-right: 0;
}
.companion-wrapper {
display: flex;
align-items: flex-start;
margin-bottom: 20rpx;
border-radius: 16rpx;
padding: 10rpx;
display: flex;
align-items: center;
.select-icon {
margin-right: 10rpx;
.checkbox-circle {
width: 45rpx;
height: 45rpx;
border: 2rpx solid #DDDDDD;
background-color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
&.checked {
border-color: #FFAA48;
background-color: #FFAA48;
}
.checkbox-inner {
width: 16rpx;
height: 10rpx;
border: 2rpx solid #FFFFFF;
border-top: none;
border-right: none;
transform: rotate(-45deg);
margin-top: -4rpx;
}
}
}
.companion-item-wrapper {
flex: 1;
}
}
.no-data {
text-align: center;
image {
width: 200rpx;
height: 200rpx;
margin-bottom: 20rpx;
}
text {
font-size: 28rpx;
color: #999;
}
}
.footer-buttons {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
padding: 20rpx 30rpx;
background-color: #FFFFFF;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
.cancel-btn,
.confirm-btn {
flex: 1;
height: 88rpx;
line-height: 88rpx;
text-align: center;
border-radius: 44rpx;
font-size: 30rpx;
}
.cancel-btn {
background-color: #FFFFFF;
color: #666;
border: 1px solid #DDDDDD;
margin-right: 20rpx;
}
.confirm-btn {
background-color: #FFAA48;
color: #FFFFFF;
box-shadow: 0 4rpx 8rpx rgba(255, 170, 72, 0.3);
}
}
</style>

+ 3
- 3
pages_order/order/orderDetail.vue View File

@ -34,13 +34,13 @@
<view class="footer-btn modify-btn" v-if="[0, 1].includes(orderDetail.status)" @click="modifyOrder">
<text>修改订单</text>
</view>
<view class="footer-btn pay-btn" v-if="[4].includes(orderDetail.status) && !order.evaluation" @click="goToReview">
<view class="footer-btn pay-btn" v-if="[3].includes(orderDetail.status) && !order.evaluation" @click="goToReview">
<text>去评价</text>
</view>
<view class="footer-btn pay-btn" v-if="orderDetail.status == '4'" @click="handleReorder">
<view class="footer-btn pay-btn" v-if="orderDetail.status == '3'" @click="handleReorder">
<text>再来一单</text>
</view>
<view class="footer-btn pay-btn" v-if="[2,4,11].includes(orderDetail.status)" @click="viewServiceRecord">
<view class="footer-btn pay-btn" v-if="[2,3,11].includes(orderDetail.status)" @click="viewServiceRecord">
<text>查看服务记录</text>
</view>
<view class="footer-btn contact-btn">


+ 6
- 5
pages_order/order/orderList.vue View File

@ -78,10 +78,10 @@
@click="goToReview(order)">
<text>去评价</text>
</view>
<view class="action-btn pay-btn" v-if="order.status == 4" @click="handleReorder(order)">
<view class="action-btn pay-btn" v-if="[3, 11].includes(order.status)" @click="handleReorder(order)">
<text>再来一单</text>
</view>
<view class="action-btn pay-btn" v-if="[2,4,11].includes(order.status)" @click="viewServiceRecord(order.orderId)">
<view class="action-btn pay-btn" v-if="[2,3,11].includes(order.status)" @click="viewServiceRecord(order.orderId)">
<text>查看服务记录</text>
</view>
</view>
@ -206,10 +206,10 @@
}
},
onPullDownRefresh() {
this.refreshing()
this.refresh()
},
onShow() {
this.refreshing()
this.refresh()
},
onReachBottom() {
this.loadMore()
@ -435,7 +435,8 @@
//
handleReorder(order) {
this.currentOrder = order;
this.$refs.companionSelectPopup.open();
// IDID
this.$refs.companionSelectPopup.open(order.teacherId, order.orderId);
},
//


+ 2
- 0
pages_order/order/orderModify.vue View File

@ -246,6 +246,8 @@ export default {
let order = this.originalOrderData
this.$globalData.newOrderData.originalOrderData = order
//
if(order.addressId) {
try {


+ 2
- 0
store/index.js View File

@ -5,6 +5,7 @@ import getters from './getters'
import { getConfigList } from '@/api/system/configList'
import { getPersonalInfo } from "@/api/system/personal.js"
import { setOpenIdKey } from '@/utils/auth'
Vue.use(Vuex)
@ -73,6 +74,7 @@ const store = new Vuex.Store({
},
setUserInfo(state, userInfo){
state.userInfo = userInfo;
setOpenIdKey(userInfo.openId)
},
setNewUserCoupon(state, coupon){
state.NewUserCoupon = coupon


Loading…
Cancel
Save