2024-09-13 11:21:59 +08:00
|
|
|
|
<route lang="json5">
|
|
|
|
|
{
|
|
|
|
|
style: {
|
|
|
|
|
navigationBarTitleText: '宠托师服务详情',
|
|
|
|
|
navigationStyle: 'custom',
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
</route>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<TopBar />
|
|
|
|
|
<view class="bg-[#F5F5F5] h-[135vh]">
|
|
|
|
|
<!-- 顶部背景和头像 -->
|
|
|
|
|
<view class="relative">
|
2024-09-19 13:57:28 +08:00
|
|
|
|
<image :src="serviceData.bgUrl" class="w-full h-40 object-cover" mode="widthFix"></image>
|
2024-09-13 11:21:59 +08:00
|
|
|
|
<view class="absolute left-4 bottom-[-20px]">
|
|
|
|
|
<image
|
2024-09-19 07:17:37 +08:00
|
|
|
|
:src="serviceData.userAvatar"
|
2024-09-13 11:21:59 +08:00
|
|
|
|
mode="aspectFill"
|
|
|
|
|
class="w-20 h-20 object-cover rounded-full border-4 border-white"
|
|
|
|
|
></image>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 宠托师信息 -->
|
|
|
|
|
<view class="bg-white p-4">
|
|
|
|
|
<text class="text-2xl font-bold">{{ serviceData.userName }}</text>
|
|
|
|
|
<view class="flex items-center mt-2">
|
|
|
|
|
<text class="text-sm text-gray-500 mr-2">认证{{ serviceData.certificationTime }}</text>
|
|
|
|
|
<text class="text-sm text-gray-500">服务过 {{ serviceData.serviceNumber }} 次</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="flex items-center mt-2">
|
|
|
|
|
<wd-icon name="location" size="20" class="text-[#ffc107]"></wd-icon>
|
|
|
|
|
<text class="ml-2 text-gray-600">{{ serviceData.address }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<text class="mt-4 text-gray-600">{{ serviceData.description }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 服务和费用 -->
|
|
|
|
|
<view class="bg-white p-4">
|
|
|
|
|
<text class="text-lg font-bold mb-2">{{ serviceData.serviceName }}服务费</text>
|
|
|
|
|
<view class="flex justify-between items-center mb-4">
|
|
|
|
|
<view class="flex items-center space-x-4">
|
|
|
|
|
<view class="flex flex-col items-center">
|
|
|
|
|
<wd-icon name="bowl" size="24" class="text-pink-500"></wd-icon>
|
|
|
|
|
<text class="text-pink-500 text-sm">食具清洁</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="flex flex-col items-center">
|
|
|
|
|
<wd-icon name="water" size="24" class="text-pink-500"></wd-icon>
|
|
|
|
|
<text class="text-pink-500 text-sm">添粮换水</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="flex flex-col items-center">
|
|
|
|
|
<wd-icon name="litter" size="24" class="text-pink-500"></wd-icon>
|
|
|
|
|
<text class="text-pink-500 text-sm">铲屎添砂</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
<text class="text-lg text-red-500">¥{{ serviceData.price }}/次</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 其他费用 -->
|
|
|
|
|
<view class="bg-white p-4">
|
|
|
|
|
<text class="text-lg font-bold mb-2">其他费用</text>
|
|
|
|
|
<view class="flex justify-between items-center mb-4">
|
|
|
|
|
<view class="flex flex-col items-center">
|
|
|
|
|
<wd-icon name="home" size="24" class="text-gray-500"></wd-icon>
|
|
|
|
|
<text class="text-gray-500 text-sm">基础上门费</text>
|
|
|
|
|
<text class="text-gray-500 text-sm">1km以内23元</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="flex flex-col items-center">
|
|
|
|
|
<wd-icon name="scooter" size="24" class="text-gray-500"></wd-icon>
|
|
|
|
|
<text class="text-gray-500 text-sm">每公里加价</text>
|
|
|
|
|
<text class="text-gray-500 text-sm">每多1km加10元</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 用户评价 -->
|
|
|
|
|
<view class="bg-white p-4">
|
|
|
|
|
<text class="text-lg font-bold mb-2">用户评价</text>
|
|
|
|
|
<view class="mb-4" v-if="serviceData.comment">
|
|
|
|
|
<view class="flex items-center mb-2">
|
|
|
|
|
<text class="text-pink-500 text-lg">{{ serviceData.comment.star }}</text>
|
|
|
|
|
<view class="flex items-center ml-2">
|
|
|
|
|
<wd-icon
|
|
|
|
|
v-for="num in serviceData.comment.star"
|
|
|
|
|
name="star-on"
|
|
|
|
|
size="20"
|
|
|
|
|
class="text-[#ffc107]"
|
|
|
|
|
></wd-icon>
|
|
|
|
|
</view>
|
|
|
|
|
<text class="text-gray-500 ml-2">({{ serviceData.comment.commentNum }}条评论)</text>
|
|
|
|
|
</view>
|
|
|
|
|
<text class="text-gray-600">{{ serviceData.comment.comment }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
<view class="text-gray-500 pt-2 w-full text-center" v-else>暂无评论</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 底部地图展示 -->
|
|
|
|
|
<view class="flex flex-col justify-center items-center bg-[#ffff] pb-[50px]">
|
|
|
|
|
<text class="text-lg font-bold mb-2 w-full pl-[20px]" style="text-align: left">服务位置</text>
|
|
|
|
|
<view class="w-full h-[20vh]">
|
|
|
|
|
<Map
|
2024-09-19 13:57:28 +08:00
|
|
|
|
v-show="mapShow"
|
2024-09-13 11:21:59 +08:00
|
|
|
|
:locationName="'服务位置'"
|
|
|
|
|
:initialLatitude="serviceData.latitude"
|
|
|
|
|
:initialLongitude="serviceData.longitude"
|
|
|
|
|
/>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 底部操作栏 -->
|
2024-09-19 13:57:28 +08:00
|
|
|
|
<view class="fixed bottom-0 w-full bg-white flex justify-between items-center mt-4 z-10 py-3">
|
2024-09-13 11:21:59 +08:00
|
|
|
|
<text class="text-red-500 text-lg">¥{{ serviceData.price }}/次 起</text>
|
2024-09-19 07:17:37 +08:00
|
|
|
|
<view class="flex space-x-4 pr-10px">
|
|
|
|
|
<button @click="message" class="bg-gray-200 text-gray-600 rounded-full w-100px">
|
2024-09-13 11:21:59 +08:00
|
|
|
|
消息
|
|
|
|
|
</button>
|
2024-09-19 13:57:28 +08:00
|
|
|
|
<button class="bg-[#ffc107] text-white rounded-full" @click="openReservationModal">
|
2024-09-13 11:21:59 +08:00
|
|
|
|
预约宠托师
|
|
|
|
|
</button>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 预约弹窗 -->
|
2024-09-19 07:17:37 +08:00
|
|
|
|
<wd-overlay :show="showReservationModal">
|
|
|
|
|
<view class="bg-white rounded-lg p-4 w-full pos-absolute top-[40%] flex flex-col gap-5px">
|
2024-09-13 11:21:59 +08:00
|
|
|
|
<view class="text-lg font-bold mb-4">选择预约信息</view>
|
|
|
|
|
|
|
|
|
|
<!-- 服务宠物选择 -->
|
|
|
|
|
<text class="text-sm text-gray-500 mb-2">服务宠物</text>
|
|
|
|
|
<view v-if="pets.length > 0" class="flex space-x-4 mb-4 scroll-x overflow-x-auto" scroll-x>
|
|
|
|
|
<view
|
|
|
|
|
v-for="pet in pets"
|
|
|
|
|
:key="pet.id"
|
|
|
|
|
class="flex flex-col items-center"
|
|
|
|
|
@click="selectPet(pet.id)"
|
|
|
|
|
:class="selectedPetId == pet.id ? 'border-4 border-[#ffc107] color-[#ffc107]' : ''"
|
|
|
|
|
>
|
2024-09-19 13:57:28 +08:00
|
|
|
|
<image :src="imgUrl(pet.profileUrl)" class="w-20 h-20 rounded-full object-cover"></image>
|
2024-09-13 11:21:59 +08:00
|
|
|
|
<text class="text-sm">{{ pet.name }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
<view v-else class="pet-item add-pet" @click="toPath('/pages/pet/pet-add-page')">
|
|
|
|
|
<w-avatar :size="80" class="pet-avatar add-avatar">
|
|
|
|
|
<view class="add-icon">+</view>
|
|
|
|
|
</w-avatar>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 预约时间 -->
|
|
|
|
|
<text class="text-sm text-gray-500 mb-2">预约时间</text>
|
|
|
|
|
<picker mode="date" :start="today" :end="weekFromToday" @change="handleDateChange">
|
|
|
|
|
<view class="bg-gray-100 p-2 rounded mb-4">{{ reservationDate || '选择日期' }}</view>
|
|
|
|
|
</picker>
|
|
|
|
|
|
|
|
|
|
<!-- 选择预约小时 -->
|
|
|
|
|
<text class="text-sm text-gray-500 mb-2">选择服务时长</text>
|
|
|
|
|
<view>
|
|
|
|
|
<wd-input-number v-model="hours" @change="hoursHandleChange" :min="1" :max="12" />
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 服务地址选择 -->
|
|
|
|
|
<text class="text-sm text-gray-500 mb-2">选择地址</text>
|
|
|
|
|
<picker
|
|
|
|
|
v-if="addressList.length > 0"
|
|
|
|
|
mode="selector"
|
|
|
|
|
range-key="display"
|
|
|
|
|
:range="addressList"
|
|
|
|
|
@change="handleAddressChange"
|
|
|
|
|
>
|
|
|
|
|
<view class="bg-gray-100 p-2 rounded mb-4">
|
|
|
|
|
{{ selectedAddress.display || '请选择地址' }}
|
|
|
|
|
</view>
|
|
|
|
|
</picker>
|
|
|
|
|
<view v-else class="pet-item add-pet" @click="toPath('/pages/address/index')">
|
|
|
|
|
<w-avatar :size="80" class="pet-avatar add-avatar">
|
|
|
|
|
<view class="add-icon">+</view>
|
|
|
|
|
</w-avatar>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<!-- 操作按钮 -->
|
2024-09-19 07:17:37 +08:00
|
|
|
|
<view class="fixed bottom-0 left-0 w-full bg-white shadow-up py-[10px]">
|
2024-09-13 11:21:59 +08:00
|
|
|
|
<view class="flex justify-between">
|
|
|
|
|
<button
|
|
|
|
|
class="flex-1 mx-2 py-2 px-6 bg-gray-200 text-gray-600 rounded-full"
|
|
|
|
|
@click="closeReservationModal"
|
|
|
|
|
>
|
|
|
|
|
取消
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
class="flex-1 mx-2 py-2 px-6 bg-[#ffc107] text-white rounded-full"
|
|
|
|
|
@click="confirmReservation"
|
|
|
|
|
>
|
|
|
|
|
确认预约
|
|
|
|
|
</button>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
2024-09-19 07:17:37 +08:00
|
|
|
|
</wd-overlay>
|
2024-09-13 11:21:59 +08:00
|
|
|
|
|
|
|
|
|
<LoadingAnimation v-model="loading" />
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script lang="js" setup>
|
|
|
|
|
import { ref } from 'vue'
|
|
|
|
|
import { httpGet } from '@/utils/http'
|
2024-09-19 13:57:28 +08:00
|
|
|
|
import { baseUrl, imgUrl, toast, toPath } from '@/utils/commUtils'
|
2024-09-13 11:21:59 +08:00
|
|
|
|
import { pay } from '@/logic/pay'
|
|
|
|
|
import TopBar from '@/components/TopBar.vue'
|
|
|
|
|
import Map from '@/components/Map.vue'
|
|
|
|
|
import LoadingAnimation from '@/components/LoadingAnimation.vue'
|
|
|
|
|
|
|
|
|
|
// 获取当前日期
|
|
|
|
|
const nowday = new Date()
|
|
|
|
|
const year = nowday.getFullYear()
|
|
|
|
|
const month = (nowday.getMonth() + 1).toString().padStart(2, '0') // 月份从0开始,需加1
|
|
|
|
|
const day = nowday.getDate().toString().padStart(2, '0')
|
|
|
|
|
const reservationDate = ref(`${year}-${month}-${day}`)
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const serviceData = ref({})
|
|
|
|
|
const pets = ref([])
|
|
|
|
|
const addressList = ref([])
|
|
|
|
|
const showReservationModal = ref(false)
|
|
|
|
|
const selectedPetId = ref('')
|
|
|
|
|
const today = ref('')
|
|
|
|
|
const weekFromToday = ref('')
|
|
|
|
|
const personalServiceId = ref('')
|
|
|
|
|
const hours = ref(1)
|
|
|
|
|
const selectedAddress = ref({})
|
2024-09-19 13:57:28 +08:00
|
|
|
|
const mapShow = ref(true)
|
2024-09-13 11:21:59 +08:00
|
|
|
|
|
|
|
|
|
const hoursHandleChange = ({ value }) => {
|
|
|
|
|
if (value >= 1 && value <= 12) {
|
|
|
|
|
hours.value = value
|
|
|
|
|
} else {
|
|
|
|
|
toast('选择的服务时长在1-12小时')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const message = () => {}
|
|
|
|
|
|
|
|
|
|
const detailPay = async () => {
|
|
|
|
|
const order = {
|
|
|
|
|
reservationTime: `${reservationDate.value} 00:00:00`,
|
|
|
|
|
personalServiceId: personalServiceId.value,
|
|
|
|
|
personalServiceUserId: serviceData.value.serviceUserId,
|
|
|
|
|
price: serviceData.value.price * hours.value,
|
|
|
|
|
paymentMethod: 'wxpay',
|
|
|
|
|
address: selectedAddress.value.id,
|
|
|
|
|
pet: selectedPetId.value,
|
|
|
|
|
serviceHours: hours.value,
|
|
|
|
|
}
|
|
|
|
|
await pay(order)
|
|
|
|
|
closeReservationModal()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取当前日期并计算一周后的日期
|
|
|
|
|
const setDateRange = () => {
|
|
|
|
|
const now = new Date()
|
|
|
|
|
today.value = now.toISOString().split('T')[0]
|
|
|
|
|
|
|
|
|
|
const weekLater = new Date(now)
|
|
|
|
|
weekLater.setDate(now.getDate() + 7)
|
|
|
|
|
weekFromToday.value = weekLater.toISOString().split('T')[0]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 选择时间
|
|
|
|
|
const handleDateChange = (e) => {
|
|
|
|
|
reservationDate.value = e.detail.value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 选择地址
|
|
|
|
|
const handleAddressChange = (e) => {
|
|
|
|
|
selectedAddress.value = addressList.value[e.detail.value]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 打开预约弹窗
|
|
|
|
|
const openReservationModal = () => {
|
|
|
|
|
showReservationModal.value = true
|
2024-09-19 13:57:28 +08:00
|
|
|
|
mapShow.value = false
|
2024-09-13 11:21:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 关闭预约弹窗
|
|
|
|
|
const closeReservationModal = () => {
|
|
|
|
|
showReservationModal.value = false
|
2024-09-19 13:57:28 +08:00
|
|
|
|
mapShow.value = true
|
2024-09-13 11:21:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 选择宠物
|
|
|
|
|
const selectPet = (id) => {
|
|
|
|
|
selectedPetId.value = id
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 确认预约
|
|
|
|
|
const confirmReservation = async () => {
|
|
|
|
|
if (!selectedPetId.value || !reservationDate.value || !selectedAddress.value.display) {
|
|
|
|
|
toast('请选择宠物、日期和地址')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
await detailPay()
|
|
|
|
|
closeReservationModal()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 页面加载时获取服务详情数据、宠物信息和地址信息并初始化地图
|
|
|
|
|
onLoad(async (options) => {
|
|
|
|
|
loading.value = true
|
2024-09-19 13:57:28 +08:00
|
|
|
|
mapShow.value = false
|
|
|
|
|
|
|
|
|
|
// 延迟五秒
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
if (!loading.value && mapShow.value) {
|
|
|
|
|
loading.value = false
|
|
|
|
|
mapShow.value = true
|
|
|
|
|
}
|
|
|
|
|
}, 5000)
|
|
|
|
|
|
2024-09-13 11:21:59 +08:00
|
|
|
|
try {
|
|
|
|
|
if (!options.id) {
|
|
|
|
|
toast('该服务不存在!')
|
|
|
|
|
// 返回到上一级页面
|
|
|
|
|
uni.navigateBack()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
const id = options.id
|
|
|
|
|
personalServiceId.value = options.id
|
|
|
|
|
setDateRange()
|
|
|
|
|
|
|
|
|
|
// 获取服务详情
|
|
|
|
|
const serviceResponse = await httpGet(`/personal-service/service/${id}`)
|
|
|
|
|
if (serviceResponse.code === 200) {
|
|
|
|
|
serviceData.value = serviceResponse.data
|
|
|
|
|
} else {
|
|
|
|
|
toast(serviceResponse.message)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取宠物信息
|
|
|
|
|
const petsResponse = await httpGet('/petInfo/my')
|
|
|
|
|
if (petsResponse.code === 200) {
|
|
|
|
|
pets.value = petsResponse.data
|
|
|
|
|
if (pets.value.length !== 0) selectedPetId.value = pets.value[0].id
|
|
|
|
|
} else {
|
|
|
|
|
toast(petsResponse.message)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取地址信息
|
|
|
|
|
const addressResponse = await httpGet('/china-address/my')
|
|
|
|
|
if (addressResponse.code === 200) {
|
|
|
|
|
addressList.value = addressResponse.data
|
|
|
|
|
addressList.value.forEach((item) => {
|
|
|
|
|
item.display = `${item.province} ${item.city} ${item.district} ${item.detailAddress}`
|
|
|
|
|
})
|
|
|
|
|
selectedAddress.value = addressList.value[0]
|
|
|
|
|
} else {
|
|
|
|
|
toast(addressResponse.message)
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.log(e)
|
|
|
|
|
toast('获取服务详情失败!')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loading.value = false
|
2024-09-19 13:57:28 +08:00
|
|
|
|
mapShow.value = true
|
2024-09-13 11:21:59 +08:00
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.pet-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pet-avatar {
|
|
|
|
|
border: 2px solid #fcd038;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.add-avatar {
|
|
|
|
|
width: 102rpx;
|
|
|
|
|
height: 102rpx;
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
border: 2px dashed #fcd038;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pet-label {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #666;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.add-icon {
|
|
|
|
|
font-size: 36px;
|
|
|
|
|
line-height: 45px;
|
|
|
|
|
color: #fcd038;
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.options {
|
|
|
|
|
position: fixed;
|
|
|
|
|
bottom: 0;
|
|
|
|
|
width: 100%;
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
}
|
|
|
|
|
.mapContainer {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 20vh;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.fixed {
|
|
|
|
|
position: fixed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bottom-0 {
|
|
|
|
|
bottom: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.left-0 {
|
|
|
|
|
left: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.right-0 {
|
|
|
|
|
right: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bg-white {
|
|
|
|
|
background-color: white;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.rounded-full {
|
|
|
|
|
border-radius: 9999px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.shadow {
|
|
|
|
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.text-red-500 {
|
|
|
|
|
color: #f56565;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.border-4 {
|
|
|
|
|
border-width: 4px;
|
|
|
|
|
}
|
|
|
|
|
</style>
|