This commit is contained in:
2026-03-06 13:41:22 +08:00
commit f39c6a705f
394 changed files with 159599 additions and 0 deletions

View File

@ -0,0 +1,356 @@
<template>
<view class="order-info-cell" @click.stop="clickAction">
<template v-if="cellType === 'text'">
<view class="info-top-view">
<!-- <image :src="infoIcon" mode="aspectFit" class="cell-icon" /> -->
<text v-if="isRequired" class="required">*</text>
<view class="title-view">
<text class="app-fc-main fs-24">{{ title }}</text>
</view>
<view class="info-view" v-if="info">
<text class="app-fc-main fs-24">{{ info }}</text>
</view>
<view class="info-view" v-else>
<text style="color: #9B939A;" class="app-fc-main fs-24">{{ placeholder }}</text>
</view>
<image v-if="isCanClick" class="right-icon" :src="`${imgPrefix}right-arrow.png`" mode="widthFix">
</image>
<view v-else class="right-icon" />
</view>
</template>
<!-- 时间信息-->
<template v-if="cellType === 'time'">
<view class="info-top-view">
<!-- <image :src="infoIcon" mode="aspectFit" class="cell-icon" /> -->
<text class="required">*</text>
<view class="title-view">
<text class="app-fc-main fs-24">{{ title }}</text>
</view>
<view class="info-bottom-view" v-if="info">
<text class="app-fc-main fs-24">{{ info }}</text>
</view>
<view class="info-view" v-else>
<text style="color: #9B939A;" class="app-fc-main fs-24">{{ placeholder }}</text>
</view>
<image class="right-icon" :src="`${imgPrefix}right-arrow.png`" mode="widthFix"></image>
</view>
<text v-if="infoTime == 5" class="s">(该时间段需收取夜间费)</text>
</template>
<!-- 地址信息-->
<template v-if="cellType === 'address'">
<view class="info-top-view">
<!-- <image :src="infoIcon" mode="aspectFit" class="cell-icon" /> -->
<text class="required">*</text>
<view class="title-view">
<text class="app-fc-main fs-24">{{ title }}</text>
</view>
<view class="info-bottom-view" style="max-width: 60%;" v-if="addressInfo">
<view class="user-info-view">
<text class="app-fc-main fs-24 app-font-bold">{{
addressInfo.recipient_name
}}</text>
<text class="app-fc-main fs-24 phone-text app-font-bold">{{
addressInfo.phone
}}</text>
</view>
<view class="address-view">
<text class="app-fc-normal fs-24 address-text">{{
`${addressInfo.area_name} ${addressInfo.full_address}`
}}</text>
</view>
</view>
<view class="info-view" v-else>
<text style="color: #9B939A;" class="app-fc-main fs-24">{{ placeholder }}</text>
</view>
<image class="right-icon" :src="`${imgPrefix}right-arrow.png`" mode="widthFix"></image>
</view>
<text class="nightFee">以下区域需收取调度费奉贤区嘉定区青浦区松江区崇明区金山区</text>
</template>
<!-- 价格-->
<template v-if="cellType === 'price'">
<view class="info-top-view">
<view class="title-view">
<text class="app-fc-main fs-28">{{ title }}</text>
</view>
<text v-if="price" class="app-fc-mark fs-32 app-font-bold-700">{{
`¥${price}`
}}</text>
<text v-else class="app-fc-main fs-28">---</text>
</view>
</template>
<template v-if="cellType === 'park'">
<view class="info-top-view">
<view class="title-view">
<text class="app-fc-main fs-24">{{ title }}</text>
</view>
</view>
<view class="list-view">
<view class="list-card" v-for="item in park_conditions" :key="item"
:class="{ 'selected-list-card': parkState === item }" @click.stop="changeParkState(item)">
<text class="fs-24 app-fc-main" :class="{ 'app-fc-mark': parkState === item }">{{ item }}</text>
</view>
</view>
<view class="input-container" v-if="parkState === '其他'">
<textarea :focus="true" :value="otherParkState" class="input-view fs-24 app-fc-main"
placeholder="点击输入停车信息" placeholder-class="placeholder-class" @input="onChange" />
</view>
</template>
</view>
</template>
<script>
import {
imgPrefix
} from "@/utils/common";
/**
* info-cell 预约信息显示
* @property {String} cellType - 显示类型 默认text text/address/time/price/park
* @value text 文本类型
* @value address 地址类型
* @value time 时间类型
* @value price 价格类型
* @value park 停车状况
* @property {String} infoIcon - 左侧图标
* @property {String} title - 标题
* @property {String} info - 显示内容
* @property {Object} addressInfo - 地址信息
* @property {String} price - 价格
* @property {String} parkState - 停车状况
*
* @property {String} placeholder - 占位
*
* @event {Function} clickAction 点击cell触发事件
* @event {Function} changeParkState 停车状况改变触发事件
*/
export default {
props: {
isCanClick: {
type: Boolean,
default: true,
},
cellType: {
type: String,
default: "info",
},
infoIcon: {
type: String | null,
default: require("@/static/images/arrow_right.png"),
},
title: {
type: String,
default: "",
},
infoTime: {
type: String,
default: "",
},
info: {
type: String,
default: "",
},
addressInfo: {
type: Object | null,
default: () => {
return null;
},
},
price: {
type: String,
default: "",
},
parkState: {
type: String,
default: "",
},
otherParkState: {
type: String,
default: "",
},
placeholder: {
type: String,
default: "",
},
isRequired: {
type: Boolean,
default: false,
}
},
data() {
return {
park_conditions: ["小区", "地库", "路边", "其他"],
imgPrefix
};
},
methods: {
clickAction() {
this.isCanClick && this.$emit("clickAction");
},
changeParkState(state) {
this.$emit("changeParkState", state);
},
onChange(e) {
this.$emit("changeOtherParkState", e.detail.value);
},
},
};
</script>
<style lang="scss" scoped>
.order-info-cell {
width: 100%;
background-color: #fff;
display: flex;
flex-direction: column;
padding: 36rpx 20rpx;
box-sizing: border-box;
position: relative;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 20rpx;
right: 20rpx;
height: 2rpx;
background-color: #ececec;
}
.info-top-view {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
box-sizing: border-box;
.cell-icon {
width: 36rpx;
height: 36rpx;
flex-shrink: 0;
margin-right: 20rpx;
}
.title-view {
display: flex;
flex: 1;
align-items: center;
}
.info-view {
display: flex;
flex: 1;
align-items: center;
justify-content: flex-end;
}
.right-icon {
margin-left: 16rpx;
width: 10rpx;
height: 5rpx;
flex-shrink: 0;
}
}
.info-bottom-view {
// width: calc(100% - 56rpx);
// margin-top: 32rpx;
// margin-left: 56rpx;
// padding-right: 104rpx;
display: flex;
flex-direction: column;
box-sizing: border-box;
.user-info-view {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
.phone-text {
margin-left: 24rpx;
}
}
.address-view {
width: 100%;
display: flex;
align-items: center;
.address-text {
margin-top: 12rpx;
}
}
}
.nightFee {
color: #ff19a0;
font-family: PingFangSC;
font-size: 20rpx;
font-weight: normal;
margin-top: 20rpx;
}
.s {
color: #ff19a0;
font-family: PingFangSC;
font-size: 24rpx;
margin: 6rpx 0 0 52rpx;
}
.list-view {
margin-top: 16rpx;
width: 100%;
padding-right: 30rpx;
box-sizing: border-box;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
gap: 24rpx;
.list-card {
padding: 12rpx 20rpx;
border-radius: 182px;
background-color: #f5f5f5;
color: #000;
}
.selected-list-card {
background-color: #fee9f3;
}
}
.input-container {
width: 100%;
height: 200rpx;
padding: 0 30rpx 0 0;
box-sizing: border-box;
margin-top: 32rpx;
.input-view {
width: 100%;
height: 100%;
padding: 32rpx;
border-radius: 30rpx;
color: #333;
box-sizing: border-box;
background-color: #f9f7f9;
}
.placeholder-class {
color: #666262;
font-size: 28rpx;
}
}
}
.required {
color: #FF19A0;
}
</style>

View File

@ -0,0 +1,184 @@
<template>
<select-modal @close="closeAction" title="随车购商品" class="order-goods-modal">
<view class="goods-container">
<scroll-view class="goods-scroll-view" :scroll-y="true">
<view class="goods-item" v-for="item in goodsList" :key="item.order_id" @click.stop="gotoDetail(item)">
<image mode="aspectFit" class="goods-img" :src="item.goods_pic"/>
<view class="goods-info-view">
<text class="app-fc-main fs-32 app-font-bold-500 app-text-ellipse">
{{ item.goods_name || '--' }}
</text>
<text class="app-fc-normal fs-24 goods-num-text">{{ `数量X${item.number}` }}</text>
<text class="app-fc-main fs-28 goods-price-text" >实付款
<text class="fs-28 app-fc-alarm">{{ `¥${item.goods_price}` }}</text>
</text>
</view>
<image src="@/static/images/arrow_right_black.png" mode="aspectFit" class="good-arrow"/>
</view>
</scroll-view>
</view>
<view class="goods-price-container">
<view class="goods-left-view">
<view class="price-view">
<text class="app-fc-main fs-28">¥</text>
<text class="app-fc-main app-font-bold-700 fs-50 price-text">{{ price }}</text>
</view>
<view class="price-tips-view">
<image src="/pageHome/static/tips.png" mode="aspectFit" class="tips-img"/>
<text class="fs-24">商品总价</text>
</view>
</view>
<view class="submit-btn" @click.stop="closeAction">
<text class="app-fc-white fs-30">确定</text>
</view>
</view>
</select-modal>
</template>
<script>
import SelectModal from "@/components/select-modal.vue";
export default {
components: { SelectModal },
props: {
goodsList: {
type: Array,
default: () => []
},
price: {
type: Number,
default: 0
}
},
data() {
return {};
},
options: {
styleIsolation: "shared",
},
methods: {
closeAction() {
this.$emit('close');
},
gotoDetail(item){
this.$emit('gotoDetail', item.goods_id);
},
},
}
</script>
<style lang="scss" scoped>
.order-goods-modal {
::v-deep {
.selected-modal .model-container {
background: #fff0f5;
position: relative;
}
}
.goods-container {
width: 100%;
height: 800rpx;
position: relative;
.goods-scroll-view {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
padding: 0 32rpx;
box-sizing: border-box;
.goods-item {
width: 100%;
height: 200rpx;
display: flex;
align-items: center;
background-color: #fff;
border-radius: 40rpx;
margin-top: 32rpx;
padding: 20rpx;
box-sizing: border-box;
.goods-img {
width: 160rpx;
height: 160rpx;
margin-right: 24rpx;
}
.goods-info-view {
display: flex;
flex-direction: column;
width: calc(100% - 160rpx - 24rpx - 40rpx);
.goods-num-text {
margin-top: 16rpx;
}
.goods-price-text {
margin-top: 40rpx;
}
}
.good-arrow {
width: 20rpx;
height: 20rpx;
margin-left: 20rpx;
}
}
}
}
.goods-price-container {
width: 100%;
background-color: #fff;
padding: 26rpx 32rpx;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
.goods-left-view {
display: flex;
flex-direction: column;
.price-view {
display: flex;
flex-direction: row;
align-items: flex-end;
.price-text {
margin-left: 8rpx;
line-height: 50rpx;
}
}
.price-tips-view {
margin-top: 10rpx;
display: flex;
flex-direction: row;
align-items: center;
.tips-img {
width: 24rpx;
height: 24rpx;
margin-right: 2rpx;
}
}
}
.submit-btn {
width: 260rpx;
height: 92rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #FE019B;
border-radius: 46rpx;
}
}
}
</style>

View File

@ -0,0 +1,49 @@
<template>
<select-modal @close="closeAction" title="价格说明">
<view class="price-description-container">
<view class="price-info">
<text class="app-fc-main fs-32">{{priceInfo.desc}}</text>
</view>
</view>
</select-modal>
</template>
<script>
import SelectModal from "@/components/select-modal.vue";
export default {
components: { SelectModal },
props: {
priceInfo: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {};
},
methods: {
closeAction() {
this.$emit('close');
},
},
}
</script>
<style lang="scss" scoped>
.price-description-container {
width: 100%;
padding: 0 40rpx 10rpx;
box-sizing: border-box;
.price-info {
width: 100%;
padding: 36rpx 0;
box-sizing: border-box;
border-bottom: 1px solid #F7F3F7;
}
}
</style>

View File

@ -0,0 +1,230 @@
<template>
<select-modal @close="closeAction" title="选择服务地址">
<view class="select-address-container">
<view v-if="!isLoading && addressList.length" class="address-container">
<scroll-view class="scroll-view" scroll-y @scrolltolower="loadMoreAction"
refresher-background="transparent">
<view class="address-item" v-for="(item, index) in addressList" :key="item.id"
@click.stop="changeAddress(item)">
<view class="address-info-view">
<view class="address-name-view">
<text v-if="item.is_default" class="default-address fs-18">默认</text>
<text class="app-fc-main fs-30 app-font-bold name-text">{{ item.recipient_name }}</text>
<text class="app-fc-main fs-30 app-font-bold">{{ item.phone }}</text>
</view>
<text class="app-fc-normal fs-26">{{ getAddressText(item) }}</text>
</view>
<image @click.stop="goToEditAddress(item)" src="@/static/images/address_edit.png" mode="aspectFit" class="address-edit-icon"/>
</view>
</scroll-view>
</view>
<view class="address-container flex-center" v-if="isLoading">
<uni-load-more status="loading" :show-text="false"/>
</view>
<view v-if="addressList.length === 0 && !isLoading" class="address-container">
<image src="https://activity.wagoo.live/no_address.png" class="no-address-img" mode="widthFix"/>
<!-- <text class="app-fc-normal fs-32 no-text">暂无地址信息</text> -->
</view>
<view class="add-btn" @click.stop="gotoAddAddress">
<image class="add-icon" src="@/static/images/add.png" mode="aspectFit"/>
<text class="app-fc-white fs-30">添加地址</text>
</view>
</view>
</select-modal>
</template>
<script>
import SelectModal from "@/components/select-modal.vue";
import { getAddressList } from "@/api/address";
export default {
components: { SelectModal },
props: {
selectAddress: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
addressList: [],
isLoading: true,
pageNumber: 1,
pageSize: 10,
isLoadMore: false,
isNoMore: false,
};
},
computed: {
userInfo() {
return this.$store.state?.user?.userInfo || {};
}
},
mounted() {
this.pageNumber = 1;
this.getData();
},
methods: {
closeAction() {
this.$emit('close');
},
changeAddress(address) {
this.$emit('changeAddress', address);
},
loadMoreAction() {
if (this.isNoMore || this.isLoadMore) {
return;
}
this.pageNumber++;
this.isLoadMore = true;
this.getData()
},
getData() {
getAddressList({ user_id: this.userInfo.userID }).then((res) => {
let list = res?.data || [];
if (this.pageNumber === 1) {
this.addressList = list;
} else {
this.addressList = [...this.addressList, ...list];
}
this.isLoading = false;
this.isLoadMore = false;
this.isNoMore = list.length < this.pageSize;
}).catch(() => {
if (this.pageNumber !== 1) {
this.pageNumber--;
}
this.isLoading = false;
this.isLoadMore = false;
})
},
getAddressText(item) {
// 组合地址信息:省市区 + 详细地址
const region = [item.province, item.city, item.district].filter(Boolean).join('');
return region ? `${region}${item.full_address}` : item.full_address;
},
gotoAddAddress() {
uni.navigateTo({
url: `/pages/client/address/edit?isAdd=1`,
events: {
refreshAddress: () => {
this.isLoading = true;
this.isNoMore = false;
this.isLoadMore = false;
this.pageNumber = 1;
this.getData();
},
},
})
},
goToEditAddress(item){
uni.navigateTo({
url: `/pages/client/address/edit?id=${item?.id || ""}`,
events: {
refreshAddress: () => {
this.isLoading = true;
this.isNoMore = false;
this.isLoadMore = false;
this.pageNumber = 1;
this.getData();
},
},
})
}
}
}
</script>
<style lang="scss" scoped>
.select-address-container {
width: 100%;
padding: 0 56rpx 10rpx;
box-sizing: border-box;
.address-container {
width: 100%;
height: 444rpx;
display: flex;
flex-direction: column;
margin-bottom: 18rpx;
.no-address-img {
margin-top: 30rpx;
width: 348rpx;
align-self: center;
}
.no-text {
margin: 0 0 24rpx;
align-self: center;
}
.scroll-view {
width: 100%;
height: 100%;
}
.address-item {
width: 100%;
display: flex;
align-items: center;
padding: 30rpx 0rpx;
box-sizing: border-box;
border-bottom: 2rpx solid #F7F3F7;
.address-info-view {
display: flex;
flex: 1;
flex-direction: column;
margin-right: 30rpx;
.address-name-view {
display: flex;
flex: 1;
align-items: center;
margin-bottom: 12rpx;
.default-address {
padding: 2rpx 6rpx;
color: #40ae36;
background: rgba(64, 174, 54, 0.2);
border-radius: 6rpx;
margin-right: 16rpx;
}
.name-text {
margin-right: 16rpx;
}
}
}
.address-edit-icon {
width: 48rpx;
height: 48rpx;
}
}
}
.add-btn {
width: 100%;
height: 90rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 45rpx;
background-color: $app_color_main;
.add-icon {
width: 34rpx;
height: 34rpx;
flex-shrink: 0;
margin-right: 18rpx;
}
}
}
</style>

View File

@ -0,0 +1,201 @@
<template>
<select-modal @close="closeAction" title="选择宠物">
<view class="select-pet-container">
<view v-if="!isLoading && petList.length" class="pet-container">
<scroll-view class="scroll-view" scroll-y @scrolltolower="loadMoreAction"
refresher-background="transparent">
<view class="pet-item" v-for="(item, index) in petList" :key="item.chongwu_id" @click.stop="changePet(item)">
<image v-if="item.chongwu_pic_url" class="pet-icon" mode="scaleToFill" :src="item.chongwu_pic_url"/>
<image v-else mode="scaleToFill" class="pet-icon" src="https://activity.wagoo.live/record_avator.png"/>
<view class="pet-name-view">
<text class="app-fc-main fs-32 app-font-bold-500">{{ item.name }}</text>
</view>
<image
v-if="selectPetInfo.chongwu_id === item.chongwu_id"
src="@/static/images/cart_checked.png"
mode="widthFix"
class="select-icon"
/>
<image
v-else
src="@/static/images/unchecked.png"
mode="widthFix"
class="select-icon"
/>
</view>
</scroll-view>
</view>
<view class="pet-container flex-center" v-if="isLoading">
<uni-load-more status="loading" :show-text="false"/>
</view>
<view v-if="petList.length === 0 && !isLoading" class="pet-container" @click.stop="gotoAddPet">
<image src="https://activity.wagoo.live/no_pet.png" class="no-pet-img" mode="heightFix" />
</view>
<view class="add-btn" @click.stop="gotoAddPet">
<image class="add-icon" src="@/static/images/add.png" mode="aspectFit"/>
<text class="app-fc-white fs-30">添加宠物</text>
</view>
</view>
</select-modal>
</template>
<script>
import SelectModal from "@/components/select-modal.vue";
import { getPetList } from "@/api/common";
import appConfig from '../../constants/app.config';
export default {
components: { SelectModal },
props: {
petType: {
type: Number,
default: 0
},
selectPetInfo: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
isLoading: true,
pageNumber: 1,
pageSize: 10,
petList: [],
isLoadMore: false,
isNoMore: false,
appConfig,
};
},
mounted() {
this.pageNumber = 1;
this.getData();
},
methods: {
closeAction() {
this.$emit('close');
},
changePet(pet) {
this.$emit('changePet', pet);
},
loadMoreAction() {
if (this.isNoMore || this.isLoadMore) {
return;
}
this.pageNumber++;
this.isLoadMore = true;
this.getData()
},
getData() {
getPetList(null, this.pageNumber, this.pageSize).then((res) => {
let list = res?.info || [];
if (this.pageNumber === 1) {
this.petList = list;
} else {
this.petList = [...this.petList, ...list];
}
this.isLoading = false;
this.isLoadMore = false;
this.isNoMore = list.length < this.pageSize;
}).catch(() => {
if (this.pageNumber !== 1) {
this.pageNumber--;
}
this.isLoading = false;
this.isLoadMore = false;
})
},
gotoAddPet() {
uni.navigateTo({
url: `/pages/client/record/edit?type=${this.petType }&typeId=aaa`,
events: {
addPetSuccess: () => {
this.isLoading = true;
this.isNoMore = false;
this.isLoadMore = false;
this.pageNumber = 1;
this.getData();
},
},
})
}
}
}
</script>
<style lang="scss" scoped>
.select-pet-container {
width: 100%;
padding: 0 60rpx 10rpx;
box-sizing: border-box;
.pet-container {
width: 100%;
height: 444rpx;
display: flex;
flex-direction: column;
margin-bottom: 18rpx;
.no-pet-img {
height: calc(1289 / 1363 * 550rpx);
align-self: center;
}
.no-text {
margin: 44rpx 0 26rpx;
align-self: center;
}
.scroll-view {
width: 100%;
height: 100%;
}
.pet-item {
width: 100%;
display: flex;
align-items: center;
padding: 34rpx 22rpx;
box-sizing: border-box;
border-bottom: 2rpx solid #F7F3F7;
.pet-icon {
width: 80rpx;
height: 80rpx;
border-radius: 40rpx;
overflow: hidden;
}
.pet-name-view {
display: flex;
flex: 1;
margin-left: 24rpx;
}
.select-icon {
width: 27rpx;
}
}
}
.add-btn {
width: 100%;
height: 90rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 45rpx;
background-color: $app_color_main;
.add-icon {
width: 34rpx;
height: 34rpx;
flex-shrink: 0;
margin-right: 18rpx;
}
}
}
</style>

View File

@ -0,0 +1,262 @@
<template>
<select-modal @close="closeAction" title="选择预约时间">
<view class="select-time-container">
<view class="calendar-container">
<uni-calendar
:insert="true"
class="uni-calendar--hook"
:start-date="startDate"
:end-date="endDate"
:date="selectDate"
:showMonth="false"
@change="changeDate"/>
</view>
<view class="tips-view">
<image src="/pageHome/static/tips.png" class="tips-img" mode="aspectFit"></image>
<text class="fs-24 app-fc-normal">
{{isAfternoon? '可以预约的时间范围是后天及之后的 14 天' : '可以预约的时间范围是明天及之后的 14 天' }}
</text>
</view>
<text class="fs-32 app-fc-main time-header-text">可预约时段</text>
<view class="time-list-container">
<scroll-view v-if="!isLoading && dateList.length" class="list-scroll-view" :scroll-y="true">
<view
@click.stop="changeTimeAction(item)"
class="time-item"
:class="{'selected-time-item': selectTimeRange.shiduan_id === item.shiduan_id, 'disabled-time-item': item.num === 0}"
v-for="item in dateList"
:key="item.shiduan_id">
<text class="fs-28 app-fc-main">{{ `${item.start}-${item.end}` }}</text>
<text class="fs-28 app-fc-main">{{ item.num === 0 ? '已约满' : `剩余${item.num}` }}</text>
</view>
</scroll-view>
<view v-if="isLoading" class="loading-view">
<uni-load-more status="loading"/>
</view>
<view v-if="!isLoading && dateList.length === 0" class="loading-view">
<text class="fs-28 app-fc-main">无可预约时间</text>
</view>
</view>
<view class="submit-btn" @click.stop="changeReservationTime">
<text class="app-fc-white fs-30">确定</text>
</view>
</view>
</select-modal>
</template>
<script>
import SelectModal from "@/components/select-modal.vue";
import moment from "moment";
import "moment/locale/zh-cn";
import { getYuYueTimeList } from "@/api/order";
export default {
components: { SelectModal },
props: {
selectTime: {
type: Object,
default: () => {
return {};
}
}
},
data() {
return {
selectTimeRange: {},
isLoading: false,
selectDate: moment().isAfter(moment().format('YYYY-MM-DD 12:00:00')) ? moment().add(2, 'days').format('YYYY-MM-DD') : moment().add(1, 'days').format('YYYY-MM-DD'),
startDate: moment().isAfter(moment().format('YYYY-MM-DD 12:00:00')) ? moment().add(2, 'days').format('YYYY-MM-DD') : moment().add(1, 'days').format('YYYY-MM-DD'),
endDate: moment().isAfter(moment().format('YYYY-MM-DD 12:00:00')) ? moment().add(16, 'days').format('YYYY-MM-DD') : moment().add(15, 'days').format('YYYY-MM-DD'),
dateList: [],
//是下午
isAfternoon: moment().isAfter(moment().format('YYYY-MM-DD 12:00:00'))
};
},
mounted() {
moment.locale('zh-cn'); // 设置中文本地化
if (Object.keys(this.selectTime).length > 0) {
this.selectDate = this.selectTime.date;
this.selectTimeRange = this.selectTime;
this.getTimeArray(this.selectTime.date)
} else {
this.getTimeArray(this.selectDate)
}
},
methods: {
closeAction() {
this.$emit('close');
},
changeDate(e) {
console.log(e,'--')
this.selectTimeRange = {};
this.selectDate = e.fulldate;
this.getTimeArray(e.fulldate)
},
changeTimeAction(item) {
if (item.num === 0) {
uni.showToast({
title: '当前时间已约满',
icon: 'none'
})
} else {
this.selectTimeRange = item;
}
},
changeReservationTime() {
if (this.dateList.length === 0) {
uni.showToast({
title: '暂无可预约时间',
icon: 'none'
})
return;
}
if (Object.keys(this.selectTimeRange).length === 0) {
uni.showToast({
title: '请选择一个预约时段',
icon: 'none'
})
return;
}
let dateLabel;
if (moment().format('YYYY-MM-DD') === moment(this.selectDate).format('YYYY-MM-DD')) {
dateLabel = `今天${moment(this.selectDate).format('M月D日')}`
} else if (moment().add(1, "days").format('YYYY-MM-DD') === moment(this.selectDate).format('YYYY-MM-DD')) {
dateLabel = `明天${moment(this.selectDate).format('M月D日')}`
} else if (moment().add(2, "days").format('YYYY-MM-DD') === moment(this.selectDate).format('YYYY-MM-DD')) {
dateLabel = `后天${moment(this.selectDate).format('M月D日')}`
} else {
dateLabel = `${moment(this.selectDate).format('M月D日')}`
}
this.$emit('changeReservationTime', {
dateLabel,
date: this.selectDate,
...this.selectTimeRange,
});
},
getTimeArray(t) {
this.isLoading = true
this.dateList = [];
let isCurrent = moment().format('YYYY-MM-DD') === t;
return getYuYueTimeList(t).then((res) => {
let list = (res?.info || []).map((item) => {
return {
...item,
start: item.start.substring(0, 5),
end: item.end.substring(0, 5),
};
});
this.dateList = list.filter((item) => {
let end = moment(item.end, 'HH:mm');
let now = moment();
if (isCurrent) {
return end.isAfter(now);
} else {
return true;
}
});
this.isLoading = false;
return Promise.resolve();
}).catch(() => {
this.isLoading = false
})
}
},
}
</script>
<style lang="scss" scoped>
.select-time-container {
width: 100%;
height: 82vh;
box-sizing: border-box;
display: flex;
flex-direction: column;
.calendar-container {
width: 100%;
}
.tips-view {
width: 100%;
display: flex;
align-items: center;
padding: 24rpx 30rpx;
box-sizing: border-box;
background-color: #F5F5F5;
margin-bottom: 40rpx;
.tips-img {
width: 24rpx;
height: 24rpx;
margin-right: 6rpx;
}
}
.time-header-text {
margin-left: 28rpx;
}
.time-list-container {
margin-left: 28rpx;
width: calc(100% - 56rpx);
margin-top: 20rpx;
display: flex;
flex: 1;
position: relative;
.loading-view {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.list-scroll-view {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
.time-item {
width: 100%;
height: 102rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
border-radius: 20rpx;
border: 2rpx solid #E5E7EB;
background-color: #fff;
opacity: 1;
margin-bottom: 22rpx;
padding: 0 30rpx;
box-sizing: border-box;
}
.selected-time-item {
border: 2rpx solid $app_color_main;
background-color: $app_color_mark_bg_color;
}
.disabled-time-item {
background-color: #FBF7FC;
opacity: 0.5;
}
}
}
.submit-btn {
width: calc(100% - 120rpx);
margin-left: 60rpx;
margin-bottom: 10rpx;
height: 90rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 45rpx;
background-color: $app_color_main;
}
}
</style>

View File

@ -0,0 +1,79 @@
export const ORDER_STATUS_UNPAY = 1; // 未支付
export const ORDER_STATUS_RESERVED = 2; // 已预约
export const ORDER_STATUS_SEND = 3; // 已派单
export const ORDER_STATUS_SERVICE = 4; //服务中
export const ORDER_STATUS_COMPLETED = 5; // 已完成
export const ORDER_STATUS_CANCELED = 6; // 已取消
export const ORDER_STATUS_REFUND = 7; // 退款中
export const PRICE_DIFF_TYPE_SERVICE = '1'; //差价类型 服务
export const orderStatusList = [
{
value: ORDER_STATUS_UNPAY,
label: '未支付',
},
{
value: ORDER_STATUS_RESERVED,
label: '已预约',
},
{
value: ORDER_STATUS_SEND,
label: '已派单',
},
{
value: ORDER_STATUS_SERVICE,
label: '服务中',
},
{
value: ORDER_STATUS_COMPLETED,
label: '已完成',
},
{
value: ORDER_STATUS_CANCELED,
label: '已取消',
},
{
value: ORDER_STATUS_REFUND,
label: '退款中',
},
]
//展示的状态
export const showOrderStatus = [
{
value: '',
label: '全部',
},
{
value: ORDER_STATUS_RESERVED,
label: '已预约',
},
{
value: ORDER_STATUS_SERVICE,
label: '服务中',
},
{
value: ORDER_STATUS_COMPLETED,
label: '已完成',
},
{
value: ORDER_STATUS_CANCELED,
label: '已取消',
},
]
//充值相关
export const rechargeStatus = [
{
value: '1',
label: '充值',
},
{
value: '2',
label: '充值记录',
}
]

View File

@ -0,0 +1,851 @@
<template>
<view class="order-detail-container">
<view class="detail-container">
<uni-load-more v-if="isLoading" status="loading" :show-text="false"/>
<scroll-view v-if="!isLoading" class="scroll-view" scroll-y>
<view class="info-cell info-cell-guanjia" v-if="carNumber || managerPhoneNum || managerName">
<image v-if="managerPic" :src="managerPic" mode="aspectFit" class="head-icon"/>
<image v-else src="/pageHome/static/head.png" mode="aspectFit" class="head-icon"/>
<view class="guan-jia-info-container">
<text class="app-fc-main fs-32 app-font-bold-700">{{ carNumber }}</text>
<text class="app-fc-normal fs-24">{{ `${managerName}管家` }}</text>
</view>
<view class="phone-view" v-if="managerPhoneNum" @click.stop="isShowCallManagerModal = true">
<image src="../static/phone.png" mode="aspectFit" class="call-img"/>
<text class="app-fc-main fs-24">电话</text>
</view>
</view>
<view class="info-cell">
<view class="info-title-view">
<image src="@/static/images/address.png" mode="aspectFit" class="info-icon"/>
<text class="app-fc-main fs-28">{{ addressInfo.name }}</text>
<text class="app-fc-main fs-28 phone-text">{{ addressInfo.phone }}</text>
</view>
<view class="address-view">
<view class="info-icon"/>
<text class="app-fc-normal fs-24 address-text">
{{ orderInfo.address || `${addressInfo.area_name}${addressInfo.address}` || '--' }}
</text>
</view>
</view>
<view class="info-cell pet-info-container">
<view class="pet-info-cell">
<image v-if="petInfo.chongwu_pic" mode="scaleToFill" class="pet-icon"
:src="petInfo.chongwu_pic"/>
<image v-else mode="scaleToFill" class="pet-icon" src="https://activity.wagoo.live/record_avator.png"/>
<view class="pet-info-view">
<text class="fs-26 app-fc-main app-font-bold-700">{{ petInfo.name }}</text>
<text class="app-fc-normal fs-24">{{ petDesc }}</text>
<text class="app-fc-normal fs-24">{{ `出生日期:${petInfo.birthday}` }}</text>
</view>
</view>
<view class="order-goods-container" v-if="goodsList.length" @click.stop="isShowGoodsModal = true">
<view class="order-goods-view">
<scroll-view class="goods-scroll-view" scroll-x>
<image :src="goods.goods_pic" mode="aspectFit" class="good-icon" v-for="goods in goodsList"
:key="goods.goods_id"/>
</scroll-view>
</view>
<view class="order-goods-num-view">
<text class="app-fc-main fs-24">{{ `随车购商品x${goodsNum}` }}</text>
<image class="arrow-icon" src="@/static/images/arrow_right_black.png" mode="aspectFit"/>
</view>
</view>
</view>
<view class="info-cell info-cell-gap-20">
<detail-cell title="实际宠物重量区间" :content="orderInfo.new_weight_name || orderInfo.weight_name"/>
<view class="order-price-view">
<detail-cell title="预约支付金额" :content="`¥${orderInfo.price}`" :content-mark="false"/>
<detail-cell v-if="orderInfo.dikou_id" title="优惠券" :content="`-¥${couponPrice}`" :content-mark="false"/>
<detail-cell v-if="orderInfo.fuwuquan_id" title="服务券金额" :content="`¥${servicePrice}`"
:content-mark="false"/>
</view>
<template v-if="orderInfo.new_weight_name && orderInfo.weight_chajia">
<detail-cell v-if="Number(orderInfo.weight_chajia) < 0" title="附加服务费退款金额"
:content="`¥${orderInfo.weight_chajia}`"/>
<detail-cell v-else title="附加服务费支付金额" :content="`¥${orderInfo.weight_chajia}`"/>
</template>
<view class="line-view"/>
<detail-cell title="金额合计" :content="`¥${totalMoney}`" :title-mark="true" :content-mark="true"
:custom-content-style="{'color': '#E70E0E'}"/>
</view>
<view class="info-cell info-cell-gap-20">
<detail-cell title="服务订单编号" :content="orderInfo.order_no" :content-mark="false"/>
<view class="line-view"/>
<detail-cell v-if="isShowReservationTime" title="预约时间" :content="reservationTime" :content-mark="false"/>
<detail-cell v-if="!isUnPay && payTime" title="付款时间" :content="payTime" :content-mark="false"/>
<detail-cell v-if="serviceStartTime" title="服务开始时间" :content="serviceStartTime" :content-mark="false"/>
<detail-cell v-if="serviceEndTime" title="服务完成时间" :content="serviceEndTime" :content-mark="false"/>
<view class="line-view"/>
<detail-cell title="停车状况" :content="orderInfo.tingche_desc" :content-mark="false"/>
</view>
</scroll-view>
<add-service-pay-modal
v-if="isShowPayModal"
@close="hidePayModal"
:order-info="orderInfo"
:new-weight="otherWeight"
@changeWeight="selectWeight"
@paymentConfirm="paymentConfirm"
/>
<select-weight-modal
v-if="isShowWeight"
:weight-list="weightList"
:pet-weight="otherWeight"
@close="closeWeightModal"
@changeWeight="changeOtherWeight"
/>
<pay-success-modal
v-if="isShowPaySuccessModal"
@close="isShowPaySuccessModal = false"
@ok="okPayAction"
/>
</view>
<view class="bottom-view" v-if="isHasHandle">
<view class="handle-btn" v-if="isCanCancelOrder" @click.stop="clickCancel">
<text class="fs-24 app-fc-main">取消预约</text>
</view>
<view class="handle-btn" v-if="isService" @click.stop="showGoods">
<text class="app-fc-main fs-24">随车购商品</text>
</view>
<view class="handle-btn handle-mark-btn" v-if="isCanAddService" @click.stop="showAddService">
<text class="fs-24 app-fc-white">调整服务费</text>
</view>
<view class="handle-btn" v-if="isUnPay" @click.stop="payOrderAction">
<text class="fs-24 app-fc-main">立即支付</text>
</view>
<view class="handle-btn" v-if="isNeedPayService" @click.stop="payNewWeightOrderAction">
<text class="fs-24 app-fc-main">支付服务费</text>
</view>
<view class="handle-btn" v-if="isCompleted" @click.stop="isShowCallModal = true">
<text class="app-fc-main fs-24">联系售后</text>
</view>
<view class="handle-btn " v-if="isHasWashImgs" @click.stop="isShowCompareModal = true">
<text class="app-fc-main fs-24">洗护对比图</text>
</view>
<view class="handle-btn handle-mark-btn" v-if="isCompleted" @click.stop="gotoEvaluate">
<text class="fs-24 app-fc-white">{{ orderInfo.pinglun_id ? '查看评价' : '去评价' }}</text>
</view>
</view>
<call-modal
:phone-number="orderInfo.phone"
v-if="isShowCallModal"
@close="isShowCallModal = false"
/>
<call-modal
:phone-number="managerPhoneNum"
v-if="isShowCallManagerModal"
@close="isShowCallManagerModal = false"
/>
<order-goods-modal
v-if="isShowGoodsModal"
@close="isShowGoodsModal = false"
@gotoDetail="gotoGoodsDetail"
:goods-list="goodsList"
:price="goodsPrice"
/>
<CompareModal
v-if="isShowCompareModal"
@close="isShowCompareModal = false"
:is-can-share="true"
:is-can-remark="!orderInfo.pinglun_id"
:after-imgs="afterImages"
:before-imgs="beforeImages"
@add="selectImgsChange"
@share="shareToCommunity"
/>
<pop-up-modal
v-if="isShowCancelModal"
content="确定要取消预约吗?"
@confirm="cancelOrder"
@cancel="isShowCancelModal = false"
/>
<success-modal
v-if="showSuccessModal"
title="转发成功"
message="请前往宠圈查看"
@ok="jumpToCommunity"
/>
</view>
</template>
<script>
import DetailCell from "@/components/petOrder/detail-cell.vue";
import {
ORDER_STATUS_CANCELED,
ORDER_STATUS_COMPLETED,
ORDER_STATUS_RESERVED,
ORDER_STATUS_SEND,
ORDER_STATUS_SERVICE,
ORDER_STATUS_UNPAY,
PRICE_DIFF_TYPE_SERVICE
} from "@/pageHome/constants/home";
import { addServicePay, cancelOrder, getOrderDetail, payOrder } from "@/api/order";
import {
ORDER_TYPE_PET_SERVICE, ORDER_TYPE_RESERVATION, PET_HAIR_LONG,
PET_IS_STERILIZE_YES,
PET_SEX_MALE,
PET_TYPE_CAT,
PET_TYPE_DOG
} from "@/constants/app.business";
import { shareCommunity } from '../../api/community';
import { cancelPetOrderRefund } from "../../api/login";
import AddServicePayModal from "@/components/petOrder/add-service-pay-modal.vue";
import SelectWeightModal from "@/components/petOrder/select-weight-modal.vue";
import { getWeightList } from "@/api/common";
import appConfig from '@/constants/app.config'
import PaySuccessModal from "@/components/petOrder/pay-success-modal.vue";
import moment from "moment";
import CallModal from "@/components/petOrder/call-modal.vue";
import OrderGoodsModal from "@/pageHome/components/order-goods-modal.vue";
import CompareModal from "@/components/CompareModal.vue";
import PopUpModal from "@/components/PopUpModal.vue";
import SuccessModal from "@/components/SuccessModal.vue";
export default {
components: {
PopUpModal,
CompareModal,
OrderGoodsModal,
CallModal,
SelectWeightModal,
AddServicePayModal,
DetailCell,
PaySuccessModal,
SuccessModal
},
onLoad(option) {
uni.$on('createRemarkSuccess', this.refreshOrderDetail)
if (option.orderId) {
this.orderId = option.orderId;
}
},
onShow() {
this.getData();
},
data() {
return {
orderId: '',
isLoading: true,
orderInfo: {},
isShowPayModal: false,
otherWeight: {},
isShowWeight: false,
allWeightList: [],
appConfig,
isShowPaySuccessModal: false,
isShowCallModal: false,
isShowGoodsModal: false,
isShowCallManagerModal: false,
isShowCompareModal: false,
isShowCancelModal: false,
showSuccessModal: false,
};
},
computed: {
goodsList() {
return this.orderInfo?.order_list || [];
},
goodsPrice() {
const price = this.goodsList.reduce((total, item) => {
return total + item.goods_price * item.number;
}, 0);
return +price.toFixed(2)
},
goodsNum() {
let num = 0
this.goodsList.forEach(item => {
num = num + item.number;
})
return num;
},
addressInfo() {
return this.orderInfo?.address_info || {};
},
petInfo() {
return this.orderInfo?.chongwu_info || {};
},
weightInfo() {
return this.orderInfo?.weight_info || {};
},
varietyInfo() {
return this.orderInfo?.pinzhong_info || {};
},
isShowReservationTime() {
return `${this.orderInfo?.order_type}` === `${ORDER_TYPE_RESERVATION}`
},
reservationTime() {
return `${this.orderInfo?.yuyue_date} ${this.orderInfo?.yuyue_time}`
},
petDesc() {
const varietyName = this.varietyInfo?.pinzhong_name || '';
const sex = `${this.petInfo.sex}` === `${PET_SEX_MALE}` ? '男生' : '女生';
const pet = `${this.petInfo.type}` === `${PET_TYPE_CAT}` ? '猫' : '狗';
let hair = '';
if (`${this.petInfo.type}` === `${PET_TYPE_CAT}`) {
hair = `/${`${this.petInfo.maofa}` === `${PET_HAIR_LONG}` ? '长毛' : '短毛'}`
}
const sterilize = `${this.petInfo.is_jueyu}` === `${PET_IS_STERILIZE_YES}` ? '已绝育' : '未绝育';
const birthDate = new Date(this.petInfo.birthday);
const today = new Date();
let age = today.getFullYear() - birthDate.getFullYear();
const monthDiff = today.getMonth() - birthDate.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
age--;
}
const weightName = this.weightInfo?.weight_name || '';
return `${varietyName}${hair}/${sex}${pet}/${sterilize}/${age}岁/${weightName}`;
},
isCompleted() {
return this.orderInfo?.status === ORDER_STATUS_COMPLETED
},
isReceived() {
return this.orderInfo?.status === ORDER_STATUS_RESERVED || this.orderInfo?.status === ORDER_STATUS_SEND
},
//已取消
isCanceled() {
return this.orderInfo?.status === ORDER_STATUS_CANCELED
},
//服务中
isService() {
return this.orderInfo?.status === ORDER_STATUS_SERVICE
},
isCanCancelOrder() {
return this.isReceived
},
isCanAddService() {
return (this.isReceived || this.isService) && !this.isHasNewWeight
},
isUnPay() {
return this.orderInfo?.status === ORDER_STATUS_UNPAY
},
isNeedPayService() {
return this.isReceived && this.isHasNewWeight && `${this.orderInfo.new_weight_status}` !== `${ORDER_STATUS_RESERVED}`
},
weightList() {
if (this.allWeightList.length && this.orderInfo) {
//使用优惠券后付款为0后 只增不减
if (this.orderInfo.dikou_id && Number(this.orderInfo.pay_price) === 0) {
const currentWeightSort = this.allWeightList.find((item) => item.weight_id === this.orderInfo.weight_id)?.sort || 0;
if (this.orderInfo.type === PET_TYPE_DOG) {
return this.allWeightList.filter((item) => item.type === this.orderInfo.type && item.weight_id !== this.orderInfo.weight_id && Number(item.sort) > Number(currentWeightSort));
} else {
return this.allWeightList.filter((item) => item.type === this.orderInfo.type && item.weight_id !== this.orderInfo.weight_id && item.maofa === this.petInfo.maofa && Number(item.sort) > Number(currentWeightSort));
}
} else {
if (this.orderInfo.type === PET_TYPE_DOG) {
return this.allWeightList.filter((item) => item.type === this.orderInfo.type && item.weight_id !== this.orderInfo.weight_id);
} else {
return this.allWeightList.filter((item) => item.type === this.orderInfo.type && item.weight_id !== this.orderInfo.weight_id && item.maofa === this.petInfo.maofa);
}
}
} else {
return []
}
},
isHasNewWeight() {
return this.orderInfo?.new_weight_name && this.orderInfo?.weight_chajia
},
totalMoney() {
let changeMoney = Number(this.orderInfo.weight_chajia || 0);
let price = Number(this.orderInfo.pay_price || 0);
return (price + changeMoney).toFixed(2);
},
isHasHandle() {
return !this.isLoading && (this.isCanCancelOrder || this.isCanAddService || this.isNeedPayService || this.isUnPay || this.isCompleted || this.isService)
},
payTime() {
if (this.orderInfo.pay_time) {
return moment.unix(this.orderInfo.pay_time).format('YYYY-MM-DD HH:mm');
}
return ''
},
couponPrice() {
return +this.orderInfo.dikou_price || 0;
},
servicePrice() {
return +this.orderInfo.fuwuquan_price || 0;
},
carNumber() {
return this.orderInfo?.car_info?.car_no || ''
},
managerPhoneNum() {
return this.orderInfo?.guanjia_info?.mobile || ''
},
managerName() {
return this.orderInfo?.guanjia_info?.name || ''
},
isHasWashImgs() {
return this.isCompleted && (this.orderInfo.json_hou || this.orderInfo.json_qian);
},
afterImages() {
const houUrlList = (this.orderInfo?.json_hou || '').split(',')
const houFullUrlList = this.orderInfo?.hou_list || []
return houUrlList.map((v, i) => ({
url: v,
fullUrl: houFullUrlList[i]
}))
},
beforeImages() {
const qianUrlList = (this.orderInfo?.json_qian || '').split(',')
const qianFullUrlList = this.orderInfo?.qian_list || []
return qianUrlList.map((v, i) => ({
url: v,
fullUrl: qianFullUrlList[i]
}))
},
serviceStartTime() {
if (this.orderInfo.fuwu_time) {
return moment.unix(this.orderInfo.fuwu_time).format('YYYY-MM-DD HH:mm');
}
return ''
},
serviceEndTime() {
if (this.orderInfo.over_time) {
return moment.unix(this.orderInfo.over_time).format('YYYY-MM-DD HH:mm');
}
return ''
},
managerPic() {
return this.orderInfo?.guanjia_info?.guanjia_pic || ''
}
},
beforeDestroy() {
uni.$off('createRemarkSuccess', this.refreshOrderDetail);
const eventChannel = this.getOpenerEventChannel();
eventChannel.emit('refreshList');
},
methods: {
// 一键转发到宠圈
shareToCommunity(data) {
const { front = [], end = [] } = data;
uni.showLoading({
title: "处理中...",
mask: true,
})
shareCommunity({
order_id: this.orderInfo.order_id,
old_pic: front.join(','),
new_pic: end.join(',')
}).then(() => {
uni.hideLoading()
this.isShowCompareModal = false
this.showSuccessModal = true
})
},
jumpToCommunity() {
this.showSuccessModal = false
uni.navigateTo({
url: '/pages/client/index/index?activePageId=communityPage'
})
},
refreshOrderDetail() {
this.isLoading = true;
this.getData();
},
getData() {
getWeightList().then((res) => {
this.allWeightList = res?.info || [];
})
getOrderDetail(this.orderId).then((res) => {
this.orderInfo = res?.info || {};
this.isLoading = false;
}).catch(() => {
this.isLoading = false;
})
},
showAddService() {
this.isShowPayModal = true;
},
hidePayModal() {
this.isShowPayModal = false;
this.otherWeight = {};
},
selectWeight() {
this.isShowPayModal = false;
this.isShowWeight = true;
},
closeWeightModal() {
this.isShowWeight = false;
this.isShowPayModal = true;
},
changeOtherWeight(data) {
this.otherWeight = data;
this.isShowWeight = false;
this.isShowPayModal = true;
},
paymentConfirm(data) {
this.isShowPayModal = false;
uni.showLoading({
title: '处理中',
mask: true
})
addServicePay(this.orderId, this.otherWeight.weight_id)
.then((res) => {
const { code, msg } = res;
if (`${code}` === '-121') {
uni.hideLoading();
uni.showToast({
title: msg,
icon: 'none',
duration: 4000
})
} else {
this.wxPayAction(data.needRefund, PRICE_DIFF_TYPE_SERVICE)
}
})
.catch((err) => {
uni.showToast({
title: err || '创建订单失败',
icon: 'none'
})
uni.hideLoading();
})
},
clickCancel() {
this.isShowCancelModal = true;
},
cancelOrder() {
this.isShowCancelModal = false;
uni.showLoading({
title: '正在取消订单',
mask: true
});
const data = {
business_id:this.orderId,
business_type:2
}
cancelPetOrderRefund(data).then(() => {
uni.hideLoading();
this.isShowPaySuccessModal = true;
}).catch((err) => {
uni.hideLoading();
uni.showToast({
title: err || '取消订单失败',
icon: 'none'
});
})
},
payOrderAction() {
uni.showLoading({
title: '正在支付',
mask: true
});
this.wxPayAction(false);
},
payNewWeightOrderAction() {
uni.showLoading({
title: '正在支付',
mask: true
});
this.wxPayAction(false, PRICE_DIFF_TYPE_SERVICE);
},
wxPayAction(needRefund, chaJiaType) {
if (needRefund) {
uni.hideLoading();
this.isShowPaySuccessModal = true;
return;
}
payOrder(this.orderId, chaJiaType).then((res) => {
const payData = res?.info?.pay_data || {};
uni.requestPayment({
provider: 'wxpay',
timeStamp: payData.timeStamp,
nonceStr: payData.nonceStr,
package: payData.package,
signType: payData.signType,
paySign: payData.sign,
success: (res) => {
console.log('success:' + JSON.stringify(res));
uni.hideLoading();
uni.showToast({
title: '支付成功',
icon: 'none'
})
uni.navigateBack()
},
fail: (err) => {
console.log('fail:' + JSON.stringify(err));
uni.showToast({
title: err?.msg || '支付失败',
icon: 'none'
})
uni.hideLoading();
}
});
}).catch((err) => {
uni.showToast({
title: err || '支付失败',
icon: 'none'
})
})
},
okPayAction() {
this.isShowPaySuccessModal = false;
uni.navigateBack()
},
gotoEvaluate() {
if (this.orderInfo.pinglun_id) {
uni.navigateTo({
url: `/pages/client/remark/details?remarkId=${this.orderInfo.pinglun_id}`,
});
} else {
uni.navigateTo({
url: `/pages/client/order/remark?orderId=${this.orderId}&orderType=${ORDER_TYPE_PET_SERVICE}`,
});
}
},
showGoods() {
uni.navigateTo({
url: `/pages/client/category/index?petOrderId=${this.orderId}&addressId=${this.addressInfo.address_id}`,
});
},
gotoGoodsDetail(goods_id) {
uni.navigateTo({
url: `/pages/client/shop/details?id=${goods_id}&petOrderId=${this.orderId}&petOrderAddressId=${this.addressInfo.address_id}`,
});
},
selectImgsChange(imgs) {
uni.navigateTo({
url: `/pages/client/order/remark?orderId=${this.orderId}&orderType=${ORDER_TYPE_PET_SERVICE}`,
success: (res) => {
res.eventChannel.emit('remarkInfo', {
remarkImages: imgs,
afterImages: this.afterImages,
beforeImages: this.beforeImages,
})
}
});
},
},
}
</script>
<style lang="scss" scoped>
.order-detail-container {
width: 100%;
height: 100%;
box-sizing: border-box;
background-color: #F7F8FA;
display: flex;
flex-direction: column;
.detail-container {
display: flex;
flex-direction: column;
flex: 1;
position: relative;
box-sizing: border-box;
margin: 20rpx 32rpx;
align-items: center;
justify-content: center;
.scroll-view {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
.info-cell {
display: flex;
flex-direction: column;
width: 100%;
padding: 40rpx;
box-sizing: border-box;
border-radius: 30rpx;
background-color: #FFFFFF;
margin-bottom: 32rpx;
.order-price-view {
width: 100%;
padding: 20rpx 24rpx;
box-sizing: border-box;
background-color: #F5F5F5;
border-radius: 30rpx;
}
.info-title-view {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
.phone-text {
margin-left: 24rpx;
}
}
.address-view {
width: 100%;
display: flex;
align-items: center;
.address-text {
margin-top: 12rpx;
}
}
.info-icon {
width: 36rpx;
height: 36rpx;
flex-shrink: 0;
margin-right: 20rpx;
}
.line-view {
width: 100%;
height: 2rpx;
background-color: #ECECEC;
margin: 20rpx 0 18rpx;
}
.head-icon {
width: 80rpx;
height: 80rpx;
flex-shrink: 0;
margin-right: 16rpx;
overflow: hidden;
border-radius: 40rpx;
}
.guan-jia-info-container {
display: flex;
flex: 1;
flex-direction: column;
justify-content: space-around;
}
.phone-view {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.call-img {
width: 60rpx;
height: 60rpx;
margin-bottom: 12rpx;
}
}
}
.info-cell-guanjia {
display: flex;
flex-direction: row;
align-items: center;
}
.info-cell-gap-20 {
padding: 20rpx 36rpx;
}
.pet-info-container {
padding: 40rpx 36rpx;
}
.pet-info-cell {
display: flex;
width: 100%;
flex-direction: row;
.pet-icon {
width: 136rpx;
height: 136rpx;
flex-shrink: 0;
border-radius: 20rpx;
overflow: hidden;
}
.pet-info-view {
display: flex;
flex: 1;
flex-direction: column;
justify-content: space-around;
margin-left: 24rpx;
}
}
.order-goods-container {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 30rpx;
.order-goods-view {
display: flex;
flex: 1;
height: 76rpx;
flex-direction: row;
position: relative;
.goods-scroll-view {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
white-space: nowrap;
.good-icon {
width: 76rpx;
height: 76rpx;
margin-right: 20rpx;
background-color: #E5E7EB;
}
}
}
.order-goods-num-view {
display: flex;
flex-shrink: 0;
flex-direction: row;
align-items: center;
margin-left: 20rpx;
.arrow-icon {
width: 18rpx;
height: 18rpx;
margin-left: 10rpx;
}
}
}
}
}
.bottom-view {
width: 100%;
padding: 36rpx 16rpx;
box-sizing: border-box;
display: flex;
flex-direction: row;
justify-content: flex-end;
background-color: #FFFFFF;
.handle-btn {
width: 152rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
border: 2rpx solid #9B939A;
border-radius: 32rpx;
margin-right: 24rpx;
}
.handle-mark-btn {
border: 2rpx solid $app_color_main;
background-color: $app_color_main;
}
}
}
</style>

View File

@ -0,0 +1,939 @@
<template>
<view class="reservation-container">
<nav-bar title="预约" />
<view class="body-container">
<scroll-view class="scroll-view" :scroll-y="true">
<view class="form-content">
<view class="order-tab-list">
<view :class="orderType === ORDER_TYPE_RESERVATION ? 'activeItem' : 'tabItem'"
@click.stop="selectOrderType(ORDER_TYPE_RESERVATION)">
预约单
</view>
<view :class="orderType === ORDER_TYPE_SITE ? 'activeItem' : 'tabItem'"
@click.stop="selectOrderType(ORDER_TYPE_SITE)">
现场单
</view>
</view>
<view class="reservation-info-container">
<!-- 其他内容 -->
<view class="formWrapper">
<view class="info-top-view" v-if="orderType != ORDER_TYPE_RESERVATION">
<view class="top">
<view class="title-view">
<text class="required">*</text>
<text class="app-fc-main fs-24">服务码</text>
</view>
<view class="info-view">
<input class="identity-input fs-24" type="number" v-model="xwID"
placeholder="请输入或者扫一扫服务码">
<image :src='`${imgPrefix}scan.png`' mode="aspectFit" class="scan-img"
@click="scanCode" />
</view>
</view>
</view>
<view class="form-section form-section-pet">
<view class="form-label-row">
<text class="required">*</text>
<text class="form-label">选择宠物</text>
</view>
<view class="add-pet-wrapper">
<view v-for="(pet, index) in selectedPetsDisplay" :key="pet.id || index"
class="selected-pet-avatar">
<image class="pet-avatar-img"
:src="(pet.chongwu_pic || pet.pet_avatar || pet.avatar) || `${imgPrefix}record_avator.png`"
mode="aspectFill" />
<view class="remove-pet-btn" @click.stop="removePet(index)">×</view>
<text class="pet-avatar-name">{{ getPetShortName(pet) }}</text>
</view>
<view class="add-pet-cell" @click="goToSelectPet">
<view class="add-pet-btn">
<text class="plus-icon">+</text>
</view>
<text class="add-pet-text">添加宠物</text>
</view>
</view>
</view>
<!-- <info-cell key="weight" cell-type="text" title="宠物重量区间" :info="petInfo.weight_name"
placeholder='先选择宠物' :is-can-click="false" />
<info-cell v-if="selectedPetType === PET_TYPE_CAT" key="mofa" cell-type="text" title="宠物毛发"
:info="petInfo.hairName" placeholder='先选择宠物' :is-can-click="false" /> -->
<info-cell v-if="orderType === ORDER_TYPE_RESERVATION" key="time" cell-type="time"
title="选择预约时间" :info="Object.keys(reservationTime).length > 0
? `${reservationTime.dateLabel} ${reservationTime.start}-${reservationTime.end}`
: ''
" :infoTime="reservationTime.shiduan_id" @clickAction="goToSelectTime" placeholder='请选择' />
<info-cell key="address" cell-type="address"
title="选择服务地址" :address-info="address" @clickAction="goToSelectAddress"
placeholder='请选择' />
<info-cell v-if="orderType != ORDER_TYPE_SITE" key="park" cell-type="park" title="停车状况"
:park-state="parkState" :other-park-state="otherParkState"
@changeParkState="changeParkState" @changeOtherParkState="changeOtherParkState" />
</view>
<view v-if="tip" class="tip-view">
<image class="tip-icon" :src="imgPrefix + 'reservationTime-notice.png'" mode="aspectFit" />
<text class="tip-text">{{ tip }}</text>
</view>
<view class="payFooter">
<view class="leftPay">
<view class="priceWrapper">
<text class="text">预估</text>
<text class="unitText">
¥<text class="price">{{ totalDisplayPrice }}</text>
</text>
</view>
<!-- <view class="vipPrice" v-if="discount === 0">
<view>
<image :src="`${imgPrefix}vipPrice.png`" mode="widthFix"
class="vip-price-img" />
<text style="font-size: 24rpx;">¥{{ price && discount ? (price *
discount).toFixed(2) : (price ? (price *
0.8).toFixed(2) : "0.00") }}</text>
</view>
</view> -->
</view>
<view class="payBtn" @click.stop="paymentConfirm">
下一步
</view>
</view>
</view>
</view>
<!-- 广告 -->
<view class="ad-container">
<image :src='imgPrefix + "1.png"' mode="widthFix" class="ad-image"></image>
<image :src='imgPrefix + "2.png"' mode="widthFix" class="ad-image"></image>
<image :src='imgPrefix + "3.png"' mode="widthFix" class="ad-image"></image>
<image :src='imgPrefix + "4.png"' mode="widthFix" class="ad-image"></image>
<image :src='imgPrefix + "5.png"' mode="widthFix" class="ad-image"></image>
<image :src='imgPrefix + "6.png"' mode="widthFix" class="ad-image"></image>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
import InfoCell from "@/page-reser/components/info-cell.vue";
import {
gitDiscountfee
} from "../../api/login";
import {
ARTICLE_TYPE_RESERVATION_CAT,
ARTICLE_TYPE_RESERVATION_DOG,
ORDER_TYPE_RESERVATION,
ORDER_TYPE_SITE,
PET_HAIR_LONG,
PET_TYPE_CAT,
PET_TYPE_DOG,
} from "@/constants/app.business";
import {
getArticleDetail
} from "@/api/article";
import appConfig from "@/constants/app.config";
import {
getCityIsOpen,
checkWaExists
} from "@/api/order";
import {
imgPrefix
} from "@/utils/common";
export default {
components: {
InfoCell,
},
data() {
return {
imgPrefix,
xwID: '',
PET_TYPE_CAT,
PET_TYPE_DOG,
ORDER_TYPE_RESERVATION,
ORDER_TYPE_SITE,
orderType: ORDER_TYPE_RESERVATION,
selectedPetType: PET_TYPE_CAT, // 默认选中“猫”
petInfo: {}, // 兼容下游,取 selectedPets[0]
selectedPets: [], // 多选宠物列表
parkState: "",
otherParkState: "",
price: "",
discount: '',
discount_price: [], // 接口返回的折扣价数组,展示为数组之和
reservationTime: {},
address: null,
catHtmlData: "",
dogHtmlData: "",
tip: ''
};
},
mounted() {
this.initData();
},
computed: {
// 展示价格discount_price 数组相加的总和
totalDisplayPrice() {
const arr = Array.isArray(this.discount_price) ? this.discount_price : [];
const sum = arr.reduce((s, p) => s + Number(p || 0), 0);
return sum.toFixed(2);
},
selectedPetsDisplay() {
return this.selectedPets && this.selectedPets.length > 0 ? this.selectedPets : [];
}
},
methods: {
getPetShortName(pet) {
const n = pet.name || pet.pet_name || pet.pet_nickname || '';
return n.length > 2 ? n.slice(0, 2) + '…' : n.slice(0, 2);
},
initData() {
getArticleDetail(ARTICLE_TYPE_RESERVATION_CAT).then((res) => {
this.catHtmlData = this.processHtmlContent(res?.info?.content || "");
});
},
processHtmlContent(html) {
return html.replace(/max-width/g, "width");
},
selectOrderType(orderType) {
if (this.orderType === orderType) {
return;
}
this.reservationTime = {};
this.parkState = "";
this.address = null;
this.price = "";
this.discount_price = [];
this.tip = "";
this.petInfo = {};
this.selectedPets = [];
this.orderType = orderType;
},
changeParkState(state) {
this.parkState = state;
this.otherParkState = "";
},
changeOtherParkState(state) {
this.otherParkState = state;
},
goToSelectPet() {
// 跳转到选择宠物页面(多选:每次返回添加一只)
const selectPetInfo = this.selectedPets.length > 0
? encodeURIComponent(JSON.stringify(this.selectedPets[0]))
: '';
uni.navigateTo({
url: `/pageHome/selectPet/index?petType=${this.selectedPetType}${selectPetInfo ? `&selectPetInfo=${selectPetInfo}` : ''}`,
events: {
changePet: (pet) => {
this.changePet(pet);
}
}
});
},
changePet(item) {
item.hairName = item.hair === 1 ? '长毛' : '短毛';
const exists = this.selectedPets.some(p => p.id === item.id);
if (!exists) {
this.selectedPets.push(item);
}
this.petInfo = this.selectedPets[0] || {};
this.tryFetchDiscount();
},
removePet(index) {
this.selectedPets.splice(index, 1);
this.petInfo = this.selectedPets[0] || {};
this.tryFetchDiscount();
},
goToSelectAddress() {
const selectAddress = this.address && Object.keys(this.address).length > 0
? encodeURIComponent(JSON.stringify(this.address))
: '';
uni.navigateTo({
url: `/pageHome/selectAddress/index${selectAddress ? `?selectAddress=${selectAddress}` : ''}`,
events: {
changeAddress: (address) => {
this.changeAddress(address);
}
}
});
},
changeAddress(address) {
address.area_name = `${address.province} ${address.city} ${address.district}`;
this.address = address;
this.tryFetchDiscount();
},
scanCode() {
uni.scanCode({
success: (res) => {
this.xwID = res.result;
uni.showToast({
title: '扫码成功',
icon: 'success'
});
},
fail: (err) => {
console.error('扫码失败:', err);
uni.showToast({
title: '扫码失败',
icon: 'none'
});
}
});
},
goToSelectTime() {
const selectTime = this.reservationTime && Object.keys(this.reservationTime).length > 0 ?
encodeURIComponent(JSON.stringify(this.reservationTime)) :
'';
uni.navigateTo({
url: `/pageHome/selectTime/index${selectTime ? `?selectTime=${selectTime}` : ''}`,
events: {
changeReservationTime: (timeData) => {
this.changeReservationTime(timeData);
}
}
});
},
changeReservationTime(timeData) {
this.reservationTime = timeData;
this.tryFetchDiscount();
},
// 仅当「选择宠物」「选择服务地址」「选择预约时间」三者都选好时调用 gitDiscountfee
tryFetchDiscount() {
const hasPet = this.selectedPets && this.selectedPets.length > 0;
const petIds = hasPet ? this.selectedPets.map(p => +p.id) : [];
const hasAddress = this.address && Object.keys(this.address).length > 0;
const hasTime = this.reservationTime && Object.keys(this.reservationTime).length > 0;
const regionId = this.address?.region_id;
// 现场单:只需宠物即可拉取价格
if (this.orderType === this.ORDER_TYPE_SITE) {
if (hasPet && hasAddress) {
gitDiscountfee({
pet_id: petIds,
region_id: regionId
}).then((res) => {
const discountArr = Array.isArray(res.data.discount_price) ? res.data.discount_price : [res.data.discount_price];
const originArr = Array.isArray(res.data.price) ? res.data.price : [res.data.price];
// 按顺序将价格赋值给对应宠物:原价 + 折后价
discountArr.forEach((discountPrice, index) => {
if (this.selectedPets[index]) {
const originPrice = originArr[index] != null ? originArr[index] : discountPrice;
this.selectedPets[index].basePrice = Number(originPrice || 0); // 原价
this.selectedPets[index].discountBasePrice = Number(discountPrice || 0); // 折后价
}
});
// 计算总价:用折后价数组
this.price = discountArr.reduce((sum, p) => sum + Number(p || 0), 0);
this.discount = res.data.discount;
this.discount_price = Array.isArray(res.data.discount_price) ? res.data.discount_price : (res.data.discount_price != null ? [res.data.discount_price] : []);
this.tip = res.data.tip;
});
}
return;
}
// 预约单:三个都选了才调用
if (hasPet && hasAddress && hasTime) {
gitDiscountfee({
region_id: regionId,
pet_id: petIds,
order_date: this.reservationTime.date
}).then((res) => {
const discountArr = Array.isArray(res.data.discount_price) ? res.data.discount_price : [res.data.discount_price];
const originArr = Array.isArray(res.data.price) ? res.data.price : [res.data.price];
// 按顺序将价格赋值给对应宠物:原价 + 折后价
discountArr.forEach((discountPrice, index) => {
if (this.selectedPets[index]) {
const originPrice = originArr[index] != null ? originArr[index] : discountPrice;
this.selectedPets[index].basePrice = Number(originPrice || 0); // 原价
this.selectedPets[index].discountBasePrice = Number(discountPrice || 0); // 折后价
}
});
// 计算总价:用折后价数组
this.price = discountArr.reduce((sum, p) => sum + Number(p || 0), 0);
this.discount = res.data.discount;
this.discount_price = Array.isArray(res.data.discount_price) ? res.data.discount_price : (res.data.discount_price != null ? [res.data.discount_price] : []);
this.tip = res.data.tip;
});
}
},
paymentConfirm() {
if (!this.selectedPets.length) {
uni.showToast({
title: "请选择宠物",
icon: "none",
});
return;
}
if (
Object.keys(this.reservationTime).length === 0 &&
this.orderType === ORDER_TYPE_RESERVATION
) {
uni.showToast({
title: "请选择预约时间",
icon: "none",
});
return;
}
// 预约单、现场单均需校验服务地址
if (!this.address) {
uni.showToast({
title: "请选择服务地址",
icon: "none",
});
return;
}
// 仅预约单校验停车状况
if (this.orderType !== ORDER_TYPE_SITE) {
if (!this.parkState) {
uni.showToast({
title: "请选择停车状况",
icon: "none",
});
return;
}
if (this.parkState === "其他" && !this.otherParkState) {
uni.showToast({
title: "请输入停车信息",
icon: "none",
});
return;
}
}
// 如果是现场单,需要校验服务码
if (this.orderType === ORDER_TYPE_SITE) {
if (!this.xwID || !this.xwID.trim()) {
uni.showToast({
title: "请输入服务码",
icon: "none",
});
return;
}
// 校验服务码是否为数字
const waCode = Number(this.xwID.trim());
if (isNaN(waCode) || waCode <= 0) {
uni.showToast({
title: "服务码格式不正确",
icon: "none",
});
return;
}
// 调用接口校验服务码
uni.showLoading({
title: "校验服务码中...",
mask: true,
});
checkWaExists({ wa_code: waCode })
.then((res) => {
if (!res.data.exists) {
uni.showToast({
title: "服务码错误",
icon: "none",
});
return;
} else {
this.navigateToAdditional();
}
uni.hideLoading();
})
.catch((err) => {
uni.hideLoading();
uni.showToast({
title: err.msg || "服务码校验失败,请检查服务码是否正确",
icon: "none",
duration: 2000,
});
});
return;
}
// 非现场单,直接跳转
this.navigateToAdditional();
},
navigateToAdditional() {
uni.navigateTo({
url: "/pageHome/order/additional",
// url: '/pageHome/reservation/payment-confirm-page',
success: (res) => {
res.eventChannel.emit("reservationInfo", {
petInfo: this.petInfo,
selectedPets: this.selectedPets,
orderInfo: {
parkState: this.otherParkState || this.parkState,
orderType: this.orderType,
xwID: this.xwID
},
addresInfo: this.address,
reservationTime: this.reservationTime,
price: this.totalDisplayPrice, // 全部价格
discount: this.discount,
discount_price: this.discount_price,
});
},
events: {
clearData: () => {
this.selectedPetType = PET_TYPE_CAT;
this.petInfo = {};
this.selectedPets = [];
this.parkState = "";
this.price = "";
this.reservationTime = {};
this.address = null;
},
},
});
},
// paymentConfirm() {
// if (!this.petInfo.chongwu_id) {
// uni.showToast({
// title: "请选择宠物",
// icon: "none",
// });
// return;
// }
// if (
// Object.keys(this.reservationTime).length === 0 &&
// this.orderType === ORDER_TYPE_RESERVATION
// ) {
// uni.showToast({
// title: "请选择预约时间",
// icon: "none",
// });
// return;
// }
// if (!this.address) {
// uni.showToast({
// title: "请选择服务地址",
// icon: "none",
// });
// return;
// }
// if (!this.parkState) {
// uni.showToast({
// title: "请选择停车状况",
// icon: "none",
// });
// return;
// }
// if (this.parkState === "其他" && !this.otherParkState) {
// uni.showToast({
// title: "请输入停车信息",
// icon: "none",
// });
// return;
// }
// const data = {
// user_id:this.petInfo.member_id,
// pet_id:this.petInfo.chongwu_id,
// };
// isPet(data).then((res) => {
// if(res.data){
// uni.navigateTo({
// url: "/pageHome/order/additional",
// success: (res) => {
// res.eventChannel.emit("reservationInfo", {
// petInfo: this.petInfo,
// parkState: this.otherParkState || this.parkState,
// petWeight: this.petWeight,
// reservationTime: this.reservationTime,
// address: this.address,
// price: this.price,
// discount:this.discount,
// estimatePrice: this.totalDisplayPrice,
// orderType: this.orderType,
// chongwu_id: this.petInfo.chongwu_id,
// });
// },
// events: {
// clearData: () => {
// this.selectedPetType = PET_TYPE_CAT;
// this.petInfo = {};
// this.parkState = "";
// this.petWeight = {};
// this.price = "";
// this.reservationTime = {};
// this.address = null;
// },
// },
// });
// }else{
// uni.showModal({
// content: '该宠物必须与会员卡绑定才能享受会员折扣',
// showCancel: true,
// cancelText: '取消',
// confirmText: '去绑定',
// success: res => {
// if (res.confirm) {
// uni.navigateTo({
// url: `/pages/client/recharge/my-card?user_id=${this.petInfo.member_id}`,
// })
// } else {
// console.log('用户取消操作');
// }
// }
// });
// }
// });
// },
},
onShareAppMessage(res) {
return {
title: appConfig.appShareName,
path: "/pages/client/index/index",
};
},
};
</script>
<style lang="scss" scoped>
.reservation-container {
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
height: 100%;
box-sizing: border-box;
background-color: #ffecf3;
.body-container {
display: flex;
flex-direction: column;
flex: 1;
box-sizing: border-box;
position: relative;
.scroll-view {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
box-sizing: border-box;
.form-content {
padding: 0 20rpx;
box-sizing: border-box;
}
.order-tab-list {
display: flex;
align-items: flex-end;
margin-top: 20rpx;
.tabItem {
flex: 1;
text-align: center;
background-color: #ffd8e6;
border-radius: 16rpx 16rpx 0px 0px;
height: 92rpx;
line-height: 92rpx;
font-size: 24rpx;
}
.activeItem {
background-color: #fff;
flex: 1;
text-align: center;
height: 92rpx;
line-height: 92rpx;
border-radius: 16rpx 16rpx 0px 0px;
font-size: 28rpx;
font-weight: 500;
}
}
.reservation-info-container {
display: flex;
flex-direction: column;
width: 100%;
box-sizing: border-box;
.formWrapper {
background-color: #fff;
padding: 0;
border-radius: 0px 0px 16rpx 16rpx;
overflow: hidden;
.info-top-view {
width: 100%;
display: flex;
flex-direction: column;
// flex-direction: row;
// align-items: center;
box-sizing: border-box;
height: 104rpx;
padding: 0 20rpx;
position: relative;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 20rpx;
right: 20rpx;
height: 2rpx;
background-color: #ececec;
}
.top {
width: 100%;
height: 100%;
display: flex;
// flex-direction: column;
flex-direction: row;
align-items: center;
box-sizing: border-box;
.title-view {
display: flex;
flex: 1;
align-items: center;
}
.info-view {
display: flex;
flex: 1;
align-items: center;
justify-content: flex-end;
.identity-input {
text-align: right;
// direction: rtl;
width: 100%;
}
}
}
.right-icon {
margin-left: 16rpx;
width: 10rpx;
height: 5rpx;
flex-shrink: 0;
}
}
.form-section-pet {
display: flex;
flex-direction: column;
width: calc(100% - 40rpx);
margin: 0 auto;
padding: 36rpx 0;
border-bottom: 2rpx solid #ececec;
}
.form-label-row {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 0;
}
.form-label {
font-size: 28rpx;
color: #333;
margin-left: 4rpx;
}
.add-pet-wrapper {
display: flex;
// align-items: center;
gap: 16rpx;
margin-top: 20rpx;
flex-wrap: wrap;
}
.selected-pet-avatar {
position: relative;
width: 64rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
}
.pet-avatar-img {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
background-color: #f0f0f0;
}
.pet-avatar-name {
font-size: 24rpx;
color: #666;
margin-top: 8rpx;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 64rpx;
}
.remove-pet-btn {
position: absolute;
top: -8rpx;
right: -8rpx;
width: 32rpx;
height: 32rpx;
border-radius: 50%;
background-color: #FF19A0;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
line-height: 1;
border: 2rpx solid #fff;
}
.add-pet-cell {
display: flex;
flex-direction: column;
align-items: center;
}
.add-pet-btn {
width: 64rpx;
height: 64rpx;
border: 1rpx dashed #3D3D3D;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: #EBEBEB;
}
.plus-icon {
font-size: 60rpx;
color: #999;
line-height: 1;
}
.add-pet-text {
font-size: 24rpx;
color: #666;
margin-top: 8rpx;
}
}
// .content-section :last-child {
// border-bottom:none;
// /* CSS 规则 */
// }
.tip-view {
background-color: #fff;
padding: 12rpx 20rpx;
margin-top: 16rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
gap: 12rpx;
}
.tip-icon {
width: 30rpx;
height: 30rpx;
flex-shrink: 0;
}
.tip-text {
color: #FF19A0;
font-size: 24rpx;
flex: 1;
}
.payFooter {
background-color: #fff;
border-radius: 16rpx;
margin-top: 16rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
.leftPay {
.priceWrapper {
.text {
font-size: 24rpx;
color: #272427;
}
.unitText {
font-size: 24rpx;
font-weight: 500;
color: #FF19A0;
}
.price {
color: #FF19A0;
font-size: 48rpx;
}
}
.vipPrice {
margin-top: 14rpx;
font-size: 20rpx;
color: #9B939A;
view {
display: flex;
align-items: center;
}
.vip-price-img {
width: 43px;
height: 14px;
margin-right: 8rpx;
}
}
}
.payBtn {
background-color: #FF19A0;
color: #fff;
padding: 30rpx 80rpx;
border-radius: 100px;
font-size: 32rpx;
}
}
}
.ad-container {
width: 100%;
padding: 0;
box-sizing: border-box;
display: flex;
flex-direction: column;
margin-top: 16rpx;
padding-bottom: 20rpx;
.ad-image {
width: 100%;
height: auto;
display: block;
}
.ad-image:last-child {
margin-bottom: 0;
}
.ad-view {
width: 100%;
}
}
}
}
}
.required {
color: #FF19A0;
}
.scan-img {
width: 40rpx;
height: 40rpx;
margin-left: 8rpx;
}
</style>

View File

@ -0,0 +1,447 @@
<template>
<view class="payment-confirm-container" v-if="!initializing">
<view class="payment-body-container">
<view class="info-cell" v-if="reservationInfo.address">
<view class="info-title-view">
<image src="@/static/images/address.png" mode="aspectFit" class="info-icon"/>
<text class="app-fc-main fs-28">{{ reservationInfo.address.name }}</text>
<text class="app-fc-main fs-28 phone-text">{{ reservationInfo.address.phone }}</text>
</view>
<view class="address-view">
<view class="info-icon"/>
<text class="app-fc-normal fs-24 address-text">
{{ `${reservationInfo.address.area_name} ${reservationInfo.address.address}` }}
</text>
</view>
</view>
<view class="info-cell">
<view class="info-title-view">
<image src="/pageHome/static/time.png" mode="aspectFit" class="info-icon"/>
<text class="app-fc-main fs-28">{{ reservationTime }}</text>
</view>
</view>
<view class="info-cell info-pet-cell">
<detail-cell title="宠物类型" :content="petType"/>
<detail-cell v-if="petHair" title="毛型分类" :content="petHair"/>
<detail-cell title="宠物姓名" :content="petName"/>
<detail-cell title="宠物重量区间" :content="petWeight.weight_name"/>
<detail-cell v-if="Object.keys(selectCoupon).length === 0" title="优惠券"
:content="couponList.length === 0 ? '暂无可用' : '未选优惠券'" :is-show-arrow="true"
:custom-content-style="{'color': '#9B939A'}" @clickAction="isShowCouponModal=true"/>
<detail-cell v-else title="优惠券" :content="showCoupon" :is-show-arrow="true"
:custom-content-style="{'color': '#FF19A0'}" @clickAction="isShowCouponModal=true"/>
<detail-cell v-if="Object.keys(selectService).length === 0" title="服务券"
:content="serviceList.length === 0 ? '暂无可用' : '未选服务券'"
:is-show-arrow="true"
:custom-content-style="{'color': '#9B939A'}" @clickAction="isShowServiceModal=true"/>
<detail-cell v-else title="服务券" :content="showService" :is-show-arrow="true"
:custom-content-style="{'color': '#FF19A0'}" @clickAction="isShowServiceModal=true"/>
<view class="line-view"></view>
<detail-cell title="费用预估" :content="`¥${showPrice || '0.00'}`" :title-mark="true"/>
</view>
<view class="info-cell info-pet-cell">
<detail-cell title="停车状况" :content="reservationInfo.parkState" :content-mark="false" :title-mark="true"/>
</view>
</view>
<view class="handle-view">
<view class="handle-info">
<view class="handle-info-cell">
<text class="app-fc-main fs-30">¥</text>
<text class="app-fc-main fs-40 app-font-bold">{{ showPrice || '0.00' }}</text>
</view>
<view class="handle-info-cell tip-cell">
<image src="/static/images/notice.png" mode="aspectFit" class="tips-icon"/>
<text class="app-fc-normal fs-24">以实际服务宠物的重量为准</text>
</view>
</view>
<view class="handle-btn" @click.stop="createPetOrder">
<text class="app-fc-white fs-30">确认支付</text>
</view>
</view>
<RechargeCouponModal
v-if="isShowCouponModal"
:couponList="couponList"
:price="reservationInfo.price"
:selected-item="selectCoupon"
@useCoupon="useCoupon"
@close="isShowCouponModal = false"
/>
<ServiceCouponModal
v-if="isShowServiceModal"
:service-list="serviceList"
@useService="useService"
@close="isShowServiceModal = false"
:weight-id="petWeight.weight_id"
:price="reservationInfo.price"
:selected-item="selectService"
/>
<success-modal
v-if="showSuccessModal"
:img-src="'https://activity.wagoo.live/order_success.png'"
:img-style="{width: '200rpx', height: '200rpx'}"
title="预约成功"
message="预约状态可在订单中进行查看"
@ok="paySuccessAction"
/>
</view>
</template>
<script>
import DetailCell from "@/components/petOrder/detail-cell.vue";
import {
COUPON_TYPE_PET,
ORDER_TYPE_SITE,
PET_HAIR_LONG,
PET_TYPE_CAT
} from "@/constants/app.business";
import { createOrder, payOrder } from "@/api/order";
import {
getCouponListByOrderPrice,
getServiceCouponListByWeightId
} from "@/api/coupon";
import RechargeCouponModal from "@/components/coupon/RechargeCouponModal.vue";
import ServiceCouponModal from "@/components/coupon/ServiceCouponModal.vue";
import SuccessModal from "@/components/SuccessModal.vue";
import moment from "moment";
import { ORDER_STATUS_RESERVED } from "@/pageHome/constants/home";
export default {
components: { SuccessModal, ServiceCouponModal, RechargeCouponModal, DetailCell },
onLoad(option) {
const eventChannel = this.getOpenerEventChannel();
eventChannel.on('reservationInfo', (data) => {
this.initializing = false;
this.reservationInfo = data;
Promise.all([this.getMyServiceList(), this.getMyCouponList()]).then((result) => {
if (this.serviceList.length) {
let selectService = this.serviceList[0];
this.serviceList.map((data) => {
if (Number(data.price) > Number(selectService.price)) {
selectService = data;
}
});
this.selectService = selectService;
} else if (this.couponList.length) {
let selectCoupon = this.couponList[0]
this.couponList.map((data) => {
if (Number(data.card_money) > Number(selectCoupon.card_money)) {
selectCoupon = data;
}
});
this.selectCoupon = selectCoupon;
}
})
})
},
computed: {
petType() {
return `${this.reservationInfo?.petInfo?.type}` === `${PET_TYPE_CAT}` ? '喵喵' : '汪汪'
},
petName() {
return this.reservationInfo?.petInfo?.name || '';
},
petWeight() {
return this.reservationInfo?.petWeight || {};
},
petHair() {
if (`${this.reservationInfo?.petInfo?.type}` === `${PET_TYPE_CAT}`) {
return `${this.reservationInfo?.petInfo.maofa}` === `${PET_HAIR_LONG}` ? '长毛' : '短毛';
} else {
return ''
}
},
reservationTime() {
if (this.reservationInfo.orderType === ORDER_TYPE_SITE) {
return '今天 ' + moment().format('M月D日');
}
return `${this.reservationInfo?.reservationTime?.dateLabel} ${this.reservationInfo?.reservationTime?.start}-${this.reservationInfo?.reservationTime?.end}`
},
showService() {
return this.selectService.name || '';
},
showCoupon() {
return '-¥' + (+this.selectCoupon.card_money || 0);
},
showPrice() {
if (Object.keys(this.selectService).length) {
return '0.00'
} else {
let couponMoney = this.selectCoupon?.card_money || 0;
return (Number(this.reservationInfo.price) - couponMoney).toFixed(2);
}
},
},
data() {
return {
reservationInfo: {},
initializing: true,
serviceList: [],
selectService: {},
isShowServiceModal: false,
couponList: [],
selectCoupon: {},
isShowCouponModal: false,
showSuccessModal: false
};
},
methods: {
getMyCouponList() {
return getCouponListByOrderPrice(this.reservationInfo.price,COUPON_TYPE_PET).then((res)=>{
this.couponList = res?.info || [];
return Promise.resolve();
}).catch(()=>{
this.couponList = [];
this.selectCoupon = {};
return Promise.resolve();
})
},
getMyServiceList() {
return getServiceCouponListByWeightId(this.reservationInfo?.petWeight?.weight_id).then((res)=>{
this.serviceList = res?.info || [];
return Promise.resolve();
}).catch(()=>{
this.serviceList = []
this.selectService = {};
return Promise.resolve();
})
},
useCoupon(item) {
if (this.selectCoupon.fafang_id === item.fafang_id) {
this.selectCoupon = {};
} else {
this.selectCoupon = item;
this.isShowCouponModal = false;
}
},
useService(item) {
if (this.selectService.order_id === item.order_id) {
this.selectService = {};
} else {
this.selectService = item;
this.isShowServiceModal = false;
}
},
createPetOrder() {
if (Object.keys(this.selectService).length && Object.keys(this.selectCoupon).length) {
uni.showToast({
title: '服务券和优惠券不能同时使用',
icon: 'none'
})
return;
}
uni.showLoading({
title: '正在创建订单',
mask: true
});
const data = {
chongwu_id: this.reservationInfo?.petInfo?.chongwu_id,
type: this.reservationInfo?.petInfo?.type,
weight_id: this.reservationInfo?.petWeight?.weight_id,
address_id: this.reservationInfo?.address?.address_id,
shiduan_id: this.reservationInfo.orderType === ORDER_TYPE_SITE ? 0 : this.reservationInfo?.reservationTime?.shiduan_id,
yuyue_date: this.reservationInfo.orderType === ORDER_TYPE_SITE ? moment().format('YYYY-MM-DD') : this.reservationInfo?.reservationTime?.date,
tingche_desc: this.reservationInfo.parkState,
desc: '',
order_type: this.reservationInfo.orderType,
}
if (this.selectCoupon.fafang_id) {
data.dikou_id = this.selectCoupon.fafang_id;
}
if (this.selectService.order_id) {
data.fuwuquan_id = this.selectService.order_id;
}
if (`${this.reservationInfo?.petInfo?.type}` === `${PET_TYPE_CAT}`) {
data.maofa = this.reservationInfo?.petInfo.maofa;
}
createOrder(data).then((res) => {
if (Object.keys(this.selectService).length || `${this.showPrice}` === '0.00') {
const eventChannel = this.getOpenerEventChannel();
eventChannel.emit('clearData')
uni.hideLoading();
this.showSuccessModal = true;
} else {
this.weixinPay(res.info)
}
}).catch(() => {
uni.showToast({
title: '创建订单失败',
icon: 'none'
})
uni.hideLoading();
});
},
weixinPay(orderId) {
payOrder(orderId).then((res) => {
const payData = res?.info?.pay_data || {};
uni.requestPayment({
provider: 'wxpay',
timeStamp: payData.timeStamp,
nonceStr: payData.nonceStr,
package: payData.package,
signType: payData.signType,
paySign: payData.sign,
success: (res) => {
uni.hideLoading();
const eventChannel = this.getOpenerEventChannel();
eventChannel.emit('clearData')
this.showSuccessModal = true;
},
fail: (err) => {
uni.showToast({
title: '创建订单失败',
icon: 'none'
})
uni.hideLoading();
}
});
}).catch(() => {
uni.showToast({
title: '创建订单失败',
icon: 'none'
})
uni.hideLoading();
})
},
paySuccessAction() {
uni.redirectTo({
url:'/pages/client/petOrder/index',
complete() {
uni.$emit('changeTabBar', {
pageId: 'orderPage',
orderState: ORDER_STATUS_RESERVED
})
}
});
// uni.navigateBack({
// delta: 1,
// complete() {
// uni.$emit('changeTabBar', {
// pageId: 'orderPage',
// orderState: ORDER_STATUS_RESERVED
// })
// }
// })
}
}
}
</script>
<style lang="scss" scoped>
.payment-confirm-container {
width: 100%;
height: 100%;
box-sizing: border-box;
background-color: #FBF8FC;
display: flex;
flex-direction: column;
.payment-body-container {
display: flex;
flex-direction: column;
flex: 1;
padding: 20rpx 32rpx;
.info-cell {
width: 100%;
padding: 40rpx;
box-sizing: border-box;
border-radius: 30rpx;
background-color: #FFFFFF;
margin-bottom: 32rpx;
.info-title-view {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
.phone-text {
margin-left: 24rpx;
}
}
.address-view {
width: 100%;
display: flex;
align-items: center;
.address-text {
margin-top: 12rpx;
}
}
.info-icon {
width: 36rpx;
height: 36rpx;
flex-shrink: 0;
margin-right: 20rpx;
}
.info-view {
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-bottom: 40rpx;
}
.line-view {
width: 100%;
height: 2rpx;
background-color: #ECECEC;
margin: 20rpx 0;
}
}
.info-pet-cell {
padding: 20rpx 40rpx;
}
}
.handle-view {
width: 100%;
padding: 20rpx 36rpx 20rpx;
box-sizing: border-box;
background-color: #fff;
display: flex;
flex-direction: row;
align-items: center;
.handle-info {
display: flex;
flex: 1;
flex-direction: column;
justify-content: space-between;
.handle-info-cell {
display: flex;
flex-direction: row;
align-items: flex-end;
width: 100%;
.tips-icon {
width: 24rpx;
height: 24rpx;
}
}
.tip-cell {
align-items: center;
}
}
.handle-btn {
width: 260rpx;
height: 90rpx;
border-radius: 45rpx;
background-color: $app_color_main;
display: flex;
align-items: center;
justify-content: center;
}
}
}
</style>