- 新增订单取消弹窗组件,支持用户取消订单并填写取消原因 - 添加订单评价页面,用户可对已完成订单进行星级评分和文字评价 - 新增伴宠师选择页面,用户可选择之前服务过的伴宠师 - 在订单详情页和订单列表页集成取消订单和评价订单功能 - 优化订单操作流程,提升用户体验master
| @ -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> | |||
| @ -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> | |||
| @ -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> | |||
| @ -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> | |||
| @ -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> | |||
| @ -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> | |||