acdr-ui/src/pages/service/detail.vue
2024-09-19 13:57:28 +08:00

444 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<route lang="json5">
{
style: {
navigationBarTitleText: '宠托师服务详情',
navigationStyle: 'custom',
},
}
</route>
<template>
<TopBar />
<view class="bg-[#F5F5F5] h-[135vh]">
<!-- 顶部背景和头像 -->
<view class="relative">
<image :src="serviceData.bgUrl" class="w-full h-40 object-cover" mode="widthFix"></image>
<view class="absolute left-4 bottom-[-20px]">
<image
:src="serviceData.userAvatar"
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
v-show="mapShow"
:locationName="'服务位置'"
:initialLatitude="serviceData.latitude"
:initialLongitude="serviceData.longitude"
/>
</view>
</view>
<!-- 底部操作栏 -->
<view class="fixed bottom-0 w-full bg-white flex justify-between items-center mt-4 z-10 py-3">
<text class="text-red-500 text-lg">¥{{ serviceData.price }}/次 起</text>
<view class="flex space-x-4 pr-10px">
<button @click="message" class="bg-gray-200 text-gray-600 rounded-full w-100px">
消息
</button>
<button class="bg-[#ffc107] text-white rounded-full" @click="openReservationModal">
预约宠托师
</button>
</view>
</view>
</view>
<!-- 预约弹窗 -->
<wd-overlay :show="showReservationModal">
<view class="bg-white rounded-lg p-4 w-full pos-absolute top-[40%] flex flex-col gap-5px">
<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]' : ''"
>
<image :src="imgUrl(pet.profileUrl)" class="w-20 h-20 rounded-full object-cover"></image>
<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>
<!-- 操作按钮 -->
<view class="fixed bottom-0 left-0 w-full bg-white shadow-up py-[10px]">
<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>
</wd-overlay>
<LoadingAnimation v-model="loading" />
</template>
<script lang="js" setup>
import { ref } from 'vue'
import { httpGet } from '@/utils/http'
import { baseUrl, imgUrl, toast, toPath } from '@/utils/commUtils'
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({})
const mapShow = ref(true)
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
mapShow.value = false
}
// 关闭预约弹窗
const closeReservationModal = () => {
showReservationModal.value = false
mapShow.value = true
}
// 选择宠物
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
mapShow.value = false
// 延迟五秒
setTimeout(() => {
if (!loading.value && mapShow.value) {
loading.value = false
mapShow.value = true
}
}, 5000)
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
mapShow.value = true
})
</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>