acdr-ui/src/pages/service/detail.vue

444 lines
13 KiB
Vue
Raw Normal View History

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>