Browse Source

feat(订单): 添加订单取消、评价和伴宠师选择功能

- 新增订单取消弹窗组件,支持用户取消订单并填写取消原因
- 添加订单评价页面,用户可对已完成订单进行星级评分和文字评价
- 新增伴宠师选择页面,用户可选择之前服务过的伴宠师
- 在订单详情页和订单列表页集成取消订单和评价订单功能
- 优化订单操作流程,提升用户体验
master
前端-胡立永 3 months ago
parent
commit
685f777872
10 changed files with 1694 additions and 15 deletions
  1. +0
    -9
      api/system/user.js
  2. +55
    -0
      components/configPopup.vue
  3. +18
    -0
      pages.json
  4. +216
    -0
      pages_order/components/order/CancelOrderPopup.vue
  5. +190
    -0
      pages_order/components/order/CompanionSelectPopup.vue
  6. +434
    -0
      pages_order/order/companionSelect.vue
  7. +29
    -2
      pages_order/order/orderDetail.vue
  8. +37
    -4
      pages_order/order/orderList.vue
  9. +407
    -0
      pages_order/order/orderModify.vue
  10. +308
    -0
      pages_order/order/orderReview.vue

+ 0
- 9
api/system/user.js View File

@ -148,15 +148,6 @@ export function getPhoneNumber(data){
})
}
// 取消订单
export function cancelOrder(orderId){
return request({
url: `/h5/order/cancel/${orderId}`,
method: 'post',
headers:{ "isToken":true}
})
}


+ 55
- 0
components/configPopup.vue View File

@ -0,0 +1,55 @@
<template>
<view class="configPopup">
<uv-popup :show="showConfirmOrder" mode="bottom" @close="close" :round="10" :closeable="true"
>
<view class="content">
<up-parse :content="content"></up-parse>
</view>
</uv-popup>
</view>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
name: 'configPoup',
data() {
return {
content : '',
showConfirmOrder : false,
}
},
onShow(){
},
methods: {
//
open(key){
this.content = this.configList[key].paramValueArea
this.showConfirmOrder = true
},
openText(content){
this.content = content
this.showConfirmOrder = true
},
close(){
this.showConfirmOrder = false
},
},
computed : {
...mapGetters(['configList'])
}
}
</script>
<style lang="scss" scoped>
.configPopup {
.content{
min-height: 50vh;
max-height: 70vh;
padding: 30rpx 20rpx;
overflow: scroll;
height: 100%;
box-sizing: border-box;
}
}
</style>

+ 18
- 0
pages.json View File

@ -375,6 +375,24 @@
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "order/orderReview",
"style": {
"navigationBarTitleText": "评价订单",
"navigationBarBackgroundColor": "#FFBF60",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "order/companionSelect",
"style": {
"navigationBarTitleText": "服务过的伴宠师",
"navigationBarBackgroundColor": "#FFBF60",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
}
]
}


+ 216
- 0
pages_order/components/order/CancelOrderPopup.vue View File

@ -0,0 +1,216 @@
<template>
<uv-popup ref="popup" :safeAreaInsetBottom="false" :round="10">
<view class="cancel-popup">
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">取消订单</text>
</view>
<view class="popup-body">
<text class="popup-text">请添加客服微信方便为您解决订单疑问或退订服务</text>
<view class="qrcode-container">
<image class="qrcode-image" :src="qrCodeUrl" mode="aspectFit" :show-menu-by-longpress="true"></image>
</view>
<text class="popup-text">取消订单原因选填</text>
<view class="reason-container">
<textarea class="reason-input" v-model="cancelReason" placeholder="请填写取消原因,方便我们提升服务"></textarea>
</view>
</view>
<view class="popup-footer">
<view class="popup-btn cancel-btn" @click="close">
<text>不取消</text>
</view>
<view class="popup-btn confirm-btn" @click="confirmCancel">
<text>确认取消</text>
</view>
</view>
</view>
</view>
</uv-popup>
</template>
<script>
import { cancelOrder } from "@/api/system/user.js"
export default {
props: {
qrCodeUrl: {
type: String,
default: 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/details/QR_Code.png'
}
},
data(){
return {
order : {},
cancelReason: '',
}
},
methods: {
//
open(order){
this.order = order;
this.$refs.popup.open();
},
//
close() {
this.cancelReason = ''; //
this.$refs.popup.close();
},
//
confirmCancel() {
if (!this.order || !this.order.id) {
uni.showToast({
title: '订单信息不完整',
icon: 'none'
});
return;
}
//
uni.showLoading({
title: '处理中...'
});
// API
cancelOrder(this.order.id, this.cancelReason).then(res => {
uni.hideLoading();
if (res && res.code === 200) {
//
uni.showToast({
title: '订单已取消',
icon: 'success'
});
//
this.$emit('cancel', this.order, this.cancelReason);
//
this.close();
} else {
//
uni.showToast({
title: res?.msg || '取消失败,请联系客服',
icon: 'none'
});
}
}).catch(error => {
uni.hideLoading();
uni.showToast({
title: '取消失败,请稍后再试',
icon: 'none'
});
});
}
}
}
</script>
<style lang="scss" scoped>
.cancel-popup {
// position: fixed;
// top: 0;
// left: 0;
// right: 0;
// bottom: 0;
// z-index: 999;
display: flex;
justify-content: center;
align-items: center;
.popup-content {
position: relative;
width: 600rpx;
background-color: #FFFFFF;
border-radius: 12rpx;
overflow: hidden;
z-index: 1000;
}
.popup-header {
padding: 30rpx;
text-align: center;
background-color: #FFAA48;
}
.popup-title {
font-size: 32rpx;
font-weight: bold;
color: #fff;
}
.popup-body {
padding: 30rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.popup-text {
font-size: 28rpx;
color: #333333;
margin-bottom: 20rpx;
text-align: center;
}
.popup-subtext {
font-size: 24rpx;
color: #999999;
margin-bottom: 20rpx;
text-align: center;
}
.qrcode-container {
width: 300rpx;
height: 300rpx;
margin: 20rpx 0;
display: flex;
justify-content: center;
align-items: center;
}
.qrcode-image {
width: 100%;
height: 100%;
}
.popup-footer {
display: flex;
border-top: 1px solid #EEEEEE;
}
.popup-btn {
flex: 1;
padding: 30rpx 0;
text-align: center;
font-size: 28rpx;
}
.cancel-btn {
color: #666666;
border-right: 1px solid #EEEEEE;
}
.confirm-btn {
color: #FFAA48;
}
.reason-container {
width: 100%;
margin-bottom: 20rpx;
background-color: #F7F7F7;
border-radius: 8rpx;
}
.reason-input {
width: 100%;
height: 150rpx;
font-size: 28rpx;
color: #666;
line-height: 1.5;
padding: 20rpx;
}
}
</style>

+ 190
- 0
pages_order/components/order/CompanionSelectPopup.vue View File

@ -0,0 +1,190 @@
<template>
<uv-popup ref="popup" :round="10">
<view class="companion-popup" v-if="type == 0">
<view class="popup-header">
<text class="popup-title">是否指定之前服务过的伴宠师</text>
<view class="popup-close" @click="close">
<uni-icons type="close" size="20" color="#999"></uni-icons>
</view>
</view>
<view class="popup-content">
<view class="option-item" @click="selectOption('yes')">
<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')">
<text class="option-text"></text>
<view class="option-circle" :class="{'selected': selectedOption === 'no'}">
<view class="option-inner" v-if="selectedOption === 'no'"></view>
</view>
</view>
</view>
</view>
<view class="companion-popup" v-else>
<view class="popup-header">
<text class="popup-title">请选择您喜欢的下单方式</text>
<view class="popup-close" @click="close">
<uni-icons type="close" size="20" color="#999"></uni-icons>
</view>
</view>
<view class="popup-content">
<view class="option-item" @click="selectOption('yes')">
<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')">
<text class="option-text">指定伴宠师</text>
<view class="option-circle" :class="{'selected': selectedOption === 'no'}">
<view class="option-inner" v-if="selectedOption === 'no'"></view>
</view>
</view>
</view>
</view>
</uv-popup>
</template>
<script>
export default {
data() {
return {
selectedOption: '',
type : 0,
}
},
methods: {
//
open() {
this.selectedOption = '';
this.type = 0;
this.$refs.popup.open('bottom');
},
//
close() {
this.$refs.popup.close();
},
//
selectOption(option) {
this.selectedOption = option;
if(this.type == 1){
if (option === 'yes') {
this.close();
setTimeout(() => {
uni.navigateTo({
url: '/pages/newOrder/serviceNew'
});
}, 300);
} else if (option === 'no') {
this.close();
setTimeout(() => {
uni.navigateTo({
url: '/pages/companionPetList/companionPetList'
});
}, 300);
}
return
}
// ""
if (option === 'yes') {
this.close();
setTimeout(() => {
uni.navigateTo({
url: '/pages_order/order/companionSelect'
});
}, 300);
} else if (option === 'no') {
this.type = 1;
this.selectedOption = '';
}
}
}
}
</script>
<style lang="scss" scoped>
.companion-popup {
position: relative;
background-color: #FFFFFF;
border-radius: 20rpx;
.popup-header {
display: flex;
align-items: center;
justify-content: center;
padding: 30rpx 20rpx;
position: relative;
.popup-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
text-align: center;
}
.popup-close {
position: absolute;
right: 20rpx;
top: 30rpx;
}
}
.popup-content {
padding: 20rpx 30rpx 50rpx;
.option-item {
display: flex;
align-items: center;
justify-content: space-between;
height: 100rpx;
border-radius: 10rpx;
background-color: #F8F8F8;
margin-bottom: 20rpx;
padding: 0 30rpx;
// &:first-child {
// background-color: #FFF9E6;
// .option-text {
// color: #FFAA48;
// }
// }
&:last-child {
margin-bottom: 0;
}
.option-text {
font-size: 28rpx;
color: #333;
}
.option-circle {
width: 36rpx;
height: 36rpx;
border-radius: 50%;
border: 2rpx solid #DDDDDD;
display: flex;
align-items: center;
justify-content: center;
&.selected {
border-color: #FFAA48;
}
.option-inner {
width: 24rpx;
height: 24rpx;
border-radius: 50%;
background-color: #FFAA48;
}
}
}
}
}
</style>

+ 434
- 0
pages_order/order/companionSelect.vue View File

@ -0,0 +1,434 @@
<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>
</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/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);
}
}
</style>

+ 29
- 2
pages_order/order/orderDetail.vue View File

@ -22,17 +22,26 @@
<!-- 底部按钮区域 -->
<view class="order-detail-footer">
<view class="footer-btn cancel-btn" v-if="orderDetail.status === '1'">
<view class="footer-btn cancel-btn" v-if="orderDetail.status === '1'" @click="$refs.cancelOrderPopup.open()">
<text>取消订单</text>
</view>
<view class="footer-btn pay-btn" v-if="orderDetail.status === '1'" @click="goToPay">
<text>去付款</text>
</view>
<view class="footer-btn review-btn" @click="goToReview">
<text>评价订单</text>
</view>
<view class="footer-btn contact-btn">
<text>联系客服</text>
</view>
</view>
<!-- 取消订单弹窗 -->
<cancel-order-popup
ref="cancelOrderPopup"
@cancel="handleCancelOrder"
></cancel-order-popup>
<!-- 客服组件 -->
<Kefu></Kefu>
</view>
@ -45,6 +54,7 @@
import ServiceItems from '@/pages_order/components/order/ServiceItems.vue'
import ServiceRemarks from '@/pages_order/components/order/ServiceRemarks.vue'
import OrderInfo from '@/pages_order/components/order/OrderInfo.vue'
import CancelOrderPopup from '@/pages_order/components/order/CancelOrderPopup.vue'
import { getOrderList } from "@/api/system/user.js"
import { getOpenIdKey } from '@/utils/auth'
@ -55,7 +65,8 @@
ServicePets,
ServiceItems,
ServiceRemarks,
OrderInfo
OrderInfo,
CancelOrderPopup
},
data() {
return {
@ -188,6 +199,17 @@
uni.navigateTo({
url: `/pages/details/order?id=${this.orderId}`
});
},
//
goToReview() {
uni.navigateTo({
url: `/pages_order/order/orderReview?id=${this.orderId}`
});
},
//
handleCancelOrder() {
}
}
}
@ -232,6 +254,11 @@
color: #FFFFFF;
}
.review-btn {
background-color: #FFAA48;
color: #FFFFFF;
}
.contact-btn {
background-color: #FFFFFF;
color: #666;


+ 37
- 4
pages_order/order/orderList.vue View File

@ -65,12 +65,19 @@
<!-- 订单操作 -->
<view class="order-actions">
<view class="action-btn details-btn" v-if="order.status == 0"
@click="$refs.cancelOrderPopup.open()">
<text>取消订单</text>
</view>
<view class="action-btn details-btn" @click="viewOrderDetails(order.id)">
<text>查看详情</text>
</view>
<view class="action-btn pay-btn" v-if="order.status === 'unpaid'" @click="goToPay(order.id)">
<view class="action-btn pay-btn" v-if="order.status == 0" @click="goToPay(order.id)">
<text>去付款</text>
</view>
<view class="action-btn pay-btn" v-if="order.status == 3" @click="handleReorder(order)">
<text>再来一单</text>
</view>
</view>
</view>
@ -91,9 +98,19 @@
</view>
</view>
</scroll-view>
<!-- 取消订单弹窗 -->
<cancel-order-popup
ref="cancelOrderPopup"
@cancel="handleCancelOrder"
></cancel-order-popup>
<!-- 客服组件 -->
<Kefu></Kefu>
<!-- 伴宠师选择弹窗 -->
<companion-select-popup
ref="companionSelectPopup"/>
</view>
</template>
@ -101,10 +118,14 @@
import Kefu from '@/pages/common/kefu.vue'
import { getOrderList } from "@/api/system/user.js"
import { getOpenIdKey } from '@/utils/auth'
import CancelOrderPopup from '@/pages_order/components/order/CancelOrderPopup.vue'
import CompanionSelectPopup from '@/pages_order/components/order/CompanionSelectPopup.vue'
export default {
components: {
Kefu
Kefu,
CancelOrderPopup,
CompanionSelectPopup,
},
data() {
return {
@ -122,11 +143,12 @@
hasMore: true,
loading: false,
refreshing: false,
currentOrder: null,
// API
exampleData: [
{
id: '1',
status: 'unpaid',
status: '0',
statusText: '待付款',
amount: '264',
pets: [
@ -175,6 +197,10 @@
this.refreshing = false;
});
},
handleCancelOrder(){
},
//
loadMore() {
@ -252,7 +278,14 @@
}
}, 500);
});
}
},
//
handleReorder(order) {
this.currentOrder = order;
this.$refs.companionSelectPopup.open();
},
},
onLoad() {
//


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

@ -0,0 +1,407 @@
<template>
<view class="order-modify-page">
<!-- 页面头部 -->
<view class="page-header">
<text class="header-title">修改订单</text>
</view>
<!-- 订单内容区域 -->
<view class="order-modify-content">
<!-- 订单修改说明 -->
<view class="modify-notice">
<view class="notice-item">
<text class="notice-icon">🐾</text>
<text class="notice-text">您可以对<text class="highlight-text">未服务或未支付</text>的订单进行修改或取消</text>
</view>
<view class="notice-item">
<text class="notice-icon">🐾</text>
<text class="notice-text">若需修改已支付的服务项目增加服务时间/服务项目可点击下方按钮<text class="highlight-text">联系客服</text>寻求帮助感谢</text>
</view>
</view>
<!-- 服务修改信息 -->
<view class="modify-info-card">
<view class="card-title">
<text>服务修改信息</text>
</view>
<view class="info-content">
<view class="info-item">
<text class="info-label">联系人</text>
<text class="info-value">{{modifyInfo.contactName}}</text>
</view>
<view class="info-item">
<text class="info-label">联系方式</text>
<text class="info-value">{{modifyInfo.contactPhone}}</text>
</view>
<view class="info-item payment-method">
<text class="info-label">销售支持方式</text>
<view class="info-value payment-value">
<text>{{modifyInfo.paymentMethod}}</text>
<text class="arrow-right">></text>
</view>
</view>
</view>
</view>
<!-- 修改原因 -->
<view class="modify-reason-card">
<view class="card-title">
<text>修改原因</text>
</view>
<view class="reason-content">
<textarea
class="reason-input"
v-model="modifyReason"
placeholder="请输入修改原因(选填)"
maxlength="200"
></textarea>
<view class="word-count">
<text>{{modifyReason.length}}/200</text>
</view>
</view>
</view>
</view>
<!-- 底部按钮区域 -->
<view class="order-modify-footer">
<view class="footer-btn cancel-service-btn" @click="$refs.cancelPopup.open()">
<text>取消服务</text>
</view>
<view class="footer-btn confirm-modify-btn" @click="confirmModify">
<text>确认修改</text>
</view>
</view>
<!-- 取消订单弹窗 -->
<cancel-order-popup
ref="cancelPopup"
@cancel="handleCancelOrder"
></cancel-order-popup>
<!-- 客服组件 -->
<Kefu></Kefu>
</view>
</template>
<script>
import CancelOrderPopup from '../../components/order/CancelOrderPopup.vue';
export default {
components: {
CancelOrderPopup
},
data() {
return {
orderId: '', // ID
modifyInfo: {
contactName: '张小二',
contactPhone: '18888888888',
paymentMethod: '存子快递宝'
},
modifyReason: '', //
showCancelOrderPopup: false //
};
},
onLoad(options) {
// ID
if (options.id) {
this.orderId = options.id;
//
this.loadOrderData();
}
},
methods: {
//
loadOrderData() {
// API
//
/*
const params = {
openId: getOpenIdKey(),
orderId: this.orderId
};
getOrderDetail(params).then(res => {
if (res && res.code === 200) {
//
this.modifyInfo = {
contactName: res.data.contactName,
contactPhone: res.data.contactPhone,
paymentMethod: res.data.paymentMethod
};
}
}).catch(err => {
console.error('获取订单详情失败', err);
uni.showToast({
title: '获取订单信息失败',
icon: 'none'
});
});
*/
// 使
console.log('加载订单数据,ID:', this.orderId);
},
//
confirmModify() {
// API
//
/*
const params = {
openId: getOpenIdKey(),
orderId: this.orderId,
reason: this.modifyReason
};
modifyOrder(params).then(res => {
if (res && res.code === 200) {
uni.showToast({
title: '订单修改成功',
icon: 'success'
});
//
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
}).catch(err => {
console.error('修改订单失败', err);
uni.showToast({
title: '修改订单失败',
icon: 'none'
});
});
*/
// 使
uni.showToast({
title: '订单修改成功',
icon: 'success'
});
// 1.5
setTimeout(() => {
uni.navigateBack();
}, 1500);
},
//
handleCancelOrder(orderId) {
this.hideCancelPopup();
// API
//
/*
const params = {
openId: getOpenIdKey(),
orderId: this.orderId,
reason: this.modifyReason
};
cancelOrder(params).then(res => {
if (res && res.code === 200) {
uni.showToast({
title: '订单已取消',
icon: 'success'
});
//
setTimeout(() => {
uni.navigateBack({delta: 2});
}, 1500);
}
}).catch(err => {
console.error('取消订单失败', err);
});
*/
// 使
uni.showToast({
title: '订单已取消',
icon: 'success'
});
// 1.5
setTimeout(() => {
uni.navigateBack({delta: 2});
}, 1500);
}
}
};
</script>
<style lang="scss" scoped>
.order-modify-page {
background-color: #f5f5f5;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.page-header {
background-color: #FFAA48;
padding: 20rpx 30rpx;
color: #FFFFFF;
.header-title {
font-size: 36rpx;
font-weight: bold;
}
}
.order-modify-content {
flex: 1;
padding: 20rpx;
}
.modify-notice {
background-color: #FFF9F0;
border-radius: 20rpx;
padding: 20rpx;
margin-bottom: 20rpx;
.notice-item {
display: flex;
align-items: flex-start;
margin-bottom: 10rpx;
&:last-child {
margin-bottom: 0;
}
.notice-icon {
margin-right: 10rpx;
font-size: 28rpx;
}
.notice-text {
font-size: 26rpx;
color: #666;
line-height: 1.5;
flex: 1;
.highlight-text {
color: #FFAA48;
}
}
}
}
.modify-info-card, .modify-reason-card {
background-color: #FFFFFF;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.card-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
display: flex;
align-items: center;
&::before {
content: '';
display: inline-block;
width: 8rpx;
height: 32rpx;
background-color: #FFAA48;
margin-right: 16rpx;
border-radius: 4rpx;
}
}
.info-content {
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1px solid #EEEEEE;
&:last-child {
border-bottom: none;
}
.info-label {
font-size: 28rpx;
color: #666;
}
.info-value {
font-size: 28rpx;
color: #333;
}
&.payment-method {
.payment-value {
display: flex;
align-items: center;
.arrow-right {
margin-left: 10rpx;
color: #999;
}
}
}
}
}
.reason-content {
.reason-input {
width: 100%;
height: 200rpx;
background-color: #F8F8F8;
border-radius: 10rpx;
padding: 20rpx;
font-size: 28rpx;
color: #333;
box-sizing: border-box;
}
.word-count {
text-align: right;
margin-top: 10rpx;
text {
font-size: 24rpx;
color: #999;
}
}
}
.order-modify-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
background-color: #FFFFFF;
border-top: 1px solid #EEEEEE;
.footer-btn {
padding: 16rpx 30rpx;
border-radius: 30rpx;
font-size: 28rpx;
text-align: center;
width: 45%;
}
.cancel-service-btn {
background-color: #F5F5F5;
color: #666;
border: 1px solid #DDDDDD;
}
.confirm-modify-btn {
background-color: #FFAA48;
color: #FFFFFF;
}
}
</style>

+ 308
- 0
pages_order/order/orderReview.vue View File

@ -0,0 +1,308 @@
<template>
<view class="order-review-page">
<!-- 伴宠师信息区域 -->
<view class="companion-info-card">
<view class="profile-header">
<view class="companion-avatar">
<image :src="companion.avatar" mode="aspectFill"></image>
</view>
<view class="companion-detail">
<view class="companion-name">
<text>{{companion.name}}</text>
<image v-if="companion.gender" :src="companion.gender === '男生' ? '/static/images/details/boy.svg' : '/static/images/details/girl.svg'" class="gender-icon"></image>
<view class="companion-tag" v-if="companion.isOfficial">
<text>初级伴宠师</text>
</view>
</view>
</view>
</view>
</view>
<!-- 评价内容区域 -->
<view class="review-content">
<!-- 评分 -->
<view class="review-item">
<text class="review-label">评价星级</text>
<view class="review-stars">
<uni-rate
v-model="rating"
:size="24"
:value="rating"
:max="5"
:margin="5"
:is-fill="true"
:touchable="true"
@change="ratingChange"
></uni-rate>
</view>
</view>
<!-- 评价内容 -->
<view class="review-item">
<text class="review-label">评价内容</text>
<view class="review-textarea-box">
<textarea
class="review-textarea"
v-model="reviewContent"
placeholder="服务态度,态度满意,环境满意"
maxlength="500"
></textarea>
<view class="word-count">
<text>{{reviewContent.length}}/500</text>
</view>
</view>
</view>
</view>
<!-- 底部按钮区域 -->
<view class="review-footer">
<view class="submit-btn" @click="submitReview">
<text>提交评价</text>
</view>
</view>
</view>
</template>
<script>
import { getOpenIdKey } from '@/utils/auth'
export default {
data() {
return {
orderId: null,
rating: 5,
reviewContent: '',
companion: {
name: '宠小二',
avatar: '/static/images/personal/pet.png',
isOfficial: true,
gender: '女生'
}
};
},
onLoad(options) {
if (options.id) {
this.orderId = options.id;
this.getOrderInfo();
}
},
methods: {
//
getOrderInfo() {
// API
//
/*
const params = {
openId: getOpenIdKey(),
orderId: this.orderId
};
getOrderDetail(params).then(res => {
if (res && res.code === 200) {
this.companion = res.data.companion;
}
}).catch(err => {
console.error('获取订单详情失败', err);
});
*/
// 使
console.log('获取订单详情,ID:', this.orderId);
},
//
ratingChange(e) {
this.rating = e.value;
},
//
submitReview() {
if (this.rating === 0) {
uni.showToast({
title: '请选择评分',
icon: 'none'
});
return;
}
if (!this.reviewContent.trim()) {
uni.showToast({
title: '请输入评价内容',
icon: 'none'
});
return;
}
// API
//
/*
const params = {
openId: getOpenIdKey(),
orderId: this.orderId,
rating: this.rating,
content: this.reviewContent
};
submitOrderReview(params).then(res => {
if (res && res.code === 200) {
uni.showToast({
title: '评价成功',
icon: 'success'
});
//
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
}).catch(err => {
console.error('提交评价失败', err);
});
*/
// 使
uni.showToast({
title: '评价成功',
icon: 'success'
});
// 1.5
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
}
}
</script>
<style lang="scss" scoped>
.order-review-page {
background: linear-gradient(180deg, #FFBF60 0%, #FFF5E6 20%, #FFFFFF 50%);
min-height: 100vh;
display: flex;
flex-direction: column;
padding-bottom: 120rpx;
}
.companion-info-card {
padding: 30rpx;
margin-bottom: 20rpx;
background-color: transparent;
.profile-header {
display: flex;
align-items: center;
}
.companion-avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
overflow: hidden;
margin-right: 20rpx;
border: 2rpx solid #FFFFFF;
box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.1);
image {
width: 100%;
height: 100%;
}
}
.companion-detail {
flex: 1;
.companion-name {
display: flex;
align-items: center;
font-size: 32rpx;
font-weight: bold;
color: #333;
.gender-icon {
width: 32rpx;
height: 32rpx;
margin-left: 10rpx;
}
.companion-tag {
background-color: #FF9500;
color: #FFFFFF;
font-size: 20rpx;
padding: 4rpx 10rpx;
border-radius: 20rpx;
margin-left: 16rpx;
}
}
}
}
.review-content {
background-color: #FFFFFF;
padding: 30rpx;
border-radius: 16rpx 16rpx 0 0;
margin-top: 20rpx;
.review-item {
margin-bottom: 40rpx;
.review-label {
font-size: 28rpx;
color: #333;
margin-bottom: 20rpx;
display: block;
font-weight: bold;
}
.review-stars {
display: flex;
align-items: center;
}
.review-textarea-box {
position: relative;
.review-textarea {
width: 100%;
height: 200rpx;
background-color: #F7F7F7;
border-radius: 12rpx;
padding: 20rpx;
font-size: 28rpx;
color: #666;
box-sizing: border-box;
}
.word-count {
position: absolute;
bottom: 10rpx;
right: 20rpx;
font-size: 24rpx;
color: #999;
}
}
}
}
.review-footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #FFFFFF;
padding: 20rpx 30rpx;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
.submit-btn {
background-color: #FFAA48;
color: #FFFFFF;
height: 88rpx;
line-height: 88rpx;
border-radius: 44rpx;
text-align: center;
font-size: 32rpx;
font-weight: bold;
box-shadow: 0 4rpx 8rpx rgba(255, 170, 72, 0.3);
}
}
</style>

Loading…
Cancel
Save