1406 lines
33 KiB
Vue
1406 lines
33 KiB
Vue
<template>
|
||
<view class="housekeeper-detail">
|
||
<!-- 导航栏 -->
|
||
<hx-navbar
|
||
:fixed="true"
|
||
:background-color="[0,[0,0,0,0],[0,0,0,0]]"
|
||
:pageScroll.sync="scrollData"
|
||
statusBarFontColor="#ffffff"
|
||
barPlaceholder="hidden"
|
||
transparent="auto"
|
||
color='#ffffff'
|
||
title="管家详情"
|
||
/>
|
||
|
||
<!-- 管家基本信息区 -->
|
||
<view class="housekeeper-header">
|
||
<view class="header-bg">
|
||
<image
|
||
:src="housekeeperInfo.backgroundImage || defaultBg"
|
||
class="bg-image"
|
||
mode="aspectFill"
|
||
/>
|
||
<view class="bg-overlay"></view>
|
||
</view>
|
||
|
||
<view class="header-content">
|
||
<!-- 专业标签 -->
|
||
<view class="tags-section">
|
||
<view class="section-title">TA能帮你</view>
|
||
<view class="tags-grid">
|
||
<view
|
||
v-for="(tag, index) in housekeeperInfo.tags || defaultTags"
|
||
:key="index"
|
||
class="tag-item"
|
||
>
|
||
<image :src="tag.icon" class="tag-icon" />
|
||
<text class="tag-text">{{ tag.text }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 金牌管家认证 -->
|
||
<view class="certification-section" v-if="housekeeperInfo.isGoldHousekeeper">
|
||
<view class="cert-header">
|
||
<image src="/static/images/gold-badge.png" class="cert-icon" />
|
||
<text class="cert-title">金牌管家</text>
|
||
</view>
|
||
<view class="cert-content">
|
||
<text class="cert-desc">高品质服务,助力顺畅沟通</text>
|
||
<view class="cert-features">
|
||
<text class="feature">安全守护·接急应证</text>
|
||
<text class="feature">皮对突发装况</text>
|
||
</view>
|
||
</view>
|
||
<view class="cert-stats">
|
||
<text class="stat">出行天数:C1驾照(5年驾龄),提供途径改变或小众自驾路线规划</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 服务项目列表 -->
|
||
<view class="services-section">
|
||
<view class="section-header">
|
||
<text class="section-title">服务项目</text>
|
||
<text class="section-subtitle">精选优质服务,满足您的需求</text>
|
||
</view>
|
||
|
||
<view class="services-list">
|
||
<view
|
||
v-for="(service, index) in serviceList"
|
||
:key="index"
|
||
class="service-item"
|
||
@click="selectService(service)"
|
||
>
|
||
<view class="service-content">
|
||
<view class="service-header">
|
||
<text class="service-name">{{ service.name }}</text>
|
||
<view class="service-price">
|
||
<text class="price-symbol">¥</text>
|
||
<text class="price-amount">{{ service.price }}</text>
|
||
<text class="price-unit">/{{ service.unit }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="service-desc">{{ service.description }}</view>
|
||
|
||
<view class="service-features">
|
||
<view
|
||
v-for="(feature, fIndex) in service.features"
|
||
:key="fIndex"
|
||
class="feature-tag"
|
||
>
|
||
{{ feature }}
|
||
</view>
|
||
</view>
|
||
|
||
<view class="service-meta">
|
||
<text class="duration">服务时长:{{ service.duration }}</text>
|
||
<text class="booking-count">已预约{{ service.bookingCount }}次</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="service-action">
|
||
<view class="book-btn" @click.stop="bookService(service)">
|
||
立即预约
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: 'HousekeeperDetail',
|
||
data() {
|
||
return {
|
||
scrollData: {
|
||
scrollTop: 0,
|
||
lastScrollTop: 0,
|
||
showBackToTop: false,
|
||
navbarVisible: true
|
||
},
|
||
|
||
// 性能优化相关
|
||
reviewsLoading: false,
|
||
hasMoreReviews: true,
|
||
|
||
housekeeperId: '',
|
||
|
||
// 默认数据
|
||
defaultBg: '/static/images/housekeeper-bg.jpg',
|
||
defaultAvatar: '/static/images/default-avatar.png',
|
||
defaultTags: [
|
||
{ icon: '/static/images/tag-schedule.png', text: '设计行程' },
|
||
{ icon: '/static/images/tag-food.png', text: '推荐美食' },
|
||
{ icon: '/static/images/tag-guide.png', text: '游玩向导' },
|
||
{ icon: '/static/images/tag-custom.png', text: '预定门票' },
|
||
{ icon: '/static/images/tag-recommend.png', text: '特色推荐' },
|
||
{ icon: '/static/images/tag-culture.png', text: '文化讲解' },
|
||
{ icon: '/static/images/tag-private.png', text: '私人定制' },
|
||
{ icon: '/static/images/tag-memory.png', text: '记忆保存' }
|
||
],
|
||
|
||
// 管家信息
|
||
housekeeperInfo: {
|
||
id: 1,
|
||
name: '甄美丽',
|
||
title: '旅居管家',
|
||
avatar: '/static/images/housekeeper-avatar.jpg',
|
||
backgroundImage: '/static/images/housekeeper-bg.jpg',
|
||
rating: 5.0,
|
||
serviceYears: 3,
|
||
isOnline: true,
|
||
isGoldHousekeeper: true,
|
||
isFavorite: false,
|
||
description: '擅长行程规划,专注带来深度体验乡村旅游,农耕体验、古村探秘、山野徒步、农家美食,量身规划沉浸式乡村旅程。',
|
||
tags: [
|
||
{ icon: '/static/images/tag-schedule.png', text: '设计行程' },
|
||
{ icon: '/static/images/tag-food.png', text: '推荐美食' },
|
||
{ icon: '/static/images/tag-guide.png', text: '游玩向导' },
|
||
{ icon: '/static/images/tag-custom.png', text: '预定门票' }
|
||
]
|
||
},
|
||
|
||
// 服务列表
|
||
serviceList: [
|
||
{
|
||
id: 1,
|
||
name: '一日游规划',
|
||
price: 299,
|
||
unit: '天',
|
||
duration: '8小时',
|
||
bookingCount: 128,
|
||
description: '专业定制一日游行程,包含景点推荐、路线规划、美食指南',
|
||
features: ['个性化定制', '全程陪同', '拍照服务']
|
||
},
|
||
{
|
||
id: 2,
|
||
name: '多日游策划',
|
||
price: 199,
|
||
unit: '天',
|
||
duration: '全天',
|
||
bookingCount: 86,
|
||
description: '深度多日游策划,涵盖住宿、交通、景点的全方位安排',
|
||
features: ['行程规划', '住宿推荐', '交通安排']
|
||
},
|
||
{
|
||
id: 3,
|
||
name: '美食探店',
|
||
price: 158,
|
||
unit: '次',
|
||
duration: '3小时',
|
||
bookingCount: 203,
|
||
description: '带您探索当地特色美食,品尝地道农家菜和特色小吃',
|
||
features: ['美食推荐', '预约订座', '文化讲解']
|
||
}
|
||
],
|
||
|
||
// 评价列表
|
||
reviewsList: [
|
||
{
|
||
id: 1,
|
||
username: '张**',
|
||
userAvatar: '/static/images/user1.jpg',
|
||
rating: 5,
|
||
date: '2024-01-15',
|
||
content: '甄美丽管家服务非常专业,行程安排得很合理,带我们体验了很多当地特色,强烈推荐!',
|
||
images: ['/static/images/review1.jpg', '/static/images/review2.jpg']
|
||
},
|
||
{
|
||
id: 2,
|
||
username: '李**',
|
||
userAvatar: '/static/images/user2.jpg',
|
||
rating: 5,
|
||
date: '2024-01-10',
|
||
content: '第一次体验管家服务,超出预期!美丽很贴心,帮我们安排了很多有趣的活动。',
|
||
images: []
|
||
},
|
||
{
|
||
id: 3,
|
||
username: '王**',
|
||
userAvatar: '/static/images/user3.jpg',
|
||
rating: 4,
|
||
date: '2024-01-08',
|
||
content: '服务态度很好,对当地很熟悉,推荐的美食都很棒,下次还会选择她的服务。',
|
||
images: ['/static/images/review3.jpg']
|
||
}
|
||
],
|
||
|
||
// 预约表单
|
||
showBookingForm: false,
|
||
showDatePicker: false,
|
||
selectedService: {},
|
||
bookingForm: {
|
||
date: '',
|
||
time: '',
|
||
phone: '',
|
||
requirements: ''
|
||
},
|
||
formErrors: {
|
||
date: '',
|
||
time: '',
|
||
phone: ''
|
||
},
|
||
availableTimeSlots: [
|
||
{ label: '09:00', value: '09:00', disabled: false },
|
||
{ label: '10:00', value: '10:00', disabled: false },
|
||
{ label: '11:00', value: '11:00', disabled: false },
|
||
{ label: '14:00', value: '14:00', disabled: false },
|
||
{ label: '15:00', value: '15:00', disabled: false },
|
||
{ label: '16:00', value: '16:00', disabled: false },
|
||
{ label: '17:00', value: '17:00', disabled: false },
|
||
{ label: '18:00', value: '18:00', disabled: false }
|
||
]
|
||
}
|
||
},
|
||
|
||
computed: {
|
||
displayReviews() {
|
||
return this.reviewsList.slice(0, 3)
|
||
},
|
||
|
||
// 表单是否有效
|
||
isFormValid() {
|
||
return this.bookingForm.date &&
|
||
this.bookingForm.time &&
|
||
this.bookingForm.phone &&
|
||
this.isValidPhone(this.bookingForm.phone) &&
|
||
!this.formErrors.date &&
|
||
!this.formErrors.time &&
|
||
!this.formErrors.phone
|
||
},
|
||
|
||
// 最小日期(今天)
|
||
minDate() {
|
||
return new Date().toISOString().split('T')[0]
|
||
},
|
||
|
||
// 最大日期(30天后)
|
||
maxDate() {
|
||
const date = new Date()
|
||
date.setDate(date.getDate() + 30)
|
||
return date.toISOString().split('T')[0]
|
||
}
|
||
},
|
||
|
||
onLoad(options) {
|
||
this.housekeeperId = options.id || '1'
|
||
this.loadHousekeeperDetail()
|
||
|
||
// 预加载关键资源
|
||
this.$nextTick(() => {
|
||
this.preloadResources()
|
||
})
|
||
|
||
},
|
||
|
||
onUnload() {
|
||
// 移除事件监听
|
||
uni.$off('openBooking')
|
||
},
|
||
|
||
onPageScroll(e) {
|
||
// 使用优化的滚动处理
|
||
// this.optimizedScrollHandler()(e)
|
||
},
|
||
|
||
onReachBottom() {
|
||
// 触底加载更多评价
|
||
this.loadMoreReviews()
|
||
},
|
||
|
||
methods: {
|
||
// 加载管家详情
|
||
async loadHousekeeperDetail() {
|
||
try {
|
||
uni.showLoading({ title: '加载中...' })
|
||
// 这里调用API获取管家详情
|
||
// const res = await getHousekeeperDetail(this.housekeeperId)
|
||
// this.housekeeperInfo = res.data
|
||
// this.serviceList = res.data.services
|
||
// this.reviewsList = res.data.reviews
|
||
} catch (error) {
|
||
uni.showToast({ title: '加载失败', icon: 'error' })
|
||
} finally {
|
||
uni.hideLoading()
|
||
}
|
||
},
|
||
|
||
// 选择服务
|
||
selectService(service) {
|
||
this.selectedService = service
|
||
this.showBookingForm = true
|
||
},
|
||
|
||
// 预约服务
|
||
bookService(service) {
|
||
this.selectService(service)
|
||
},
|
||
|
||
// 快速预约
|
||
quickBook() {
|
||
if (this.serviceList.length > 0) {
|
||
this.selectService(this.serviceList[0])
|
||
}
|
||
},
|
||
|
||
// 关闭预约表单
|
||
closeBookingForm() {
|
||
this.showBookingForm = false
|
||
this.resetBookingForm()
|
||
},
|
||
|
||
// 重置预约表单
|
||
resetBookingForm() {
|
||
this.bookingForm = {
|
||
date: '',
|
||
time: '',
|
||
phone: '',
|
||
requirements: ''
|
||
}
|
||
this.formErrors = {
|
||
date: '',
|
||
time: '',
|
||
phone: ''
|
||
}
|
||
},
|
||
|
||
// 弹窗状态变化
|
||
onPopupChange(e) {
|
||
if (!e.show) {
|
||
this.closeBookingForm()
|
||
}
|
||
},
|
||
|
||
// 日期变化
|
||
onDateChange(value) {
|
||
this.bookingForm.date = value
|
||
this.formErrors.date = ''
|
||
this.showDatePicker = false
|
||
this.updateAvailableTimeSlots()
|
||
},
|
||
|
||
// 选择时间段
|
||
selectTimeSlot(slot) {
|
||
if (slot.disabled) return
|
||
|
||
this.bookingForm.time = slot.value
|
||
this.formErrors.time = ''
|
||
},
|
||
|
||
// 更新可用时间段
|
||
updateAvailableTimeSlots() {
|
||
const selectedDate = new Date(this.bookingForm.date)
|
||
const today = new Date()
|
||
const isToday = selectedDate.toDateString() === today.toDateString()
|
||
|
||
if (isToday) {
|
||
const currentHour = today.getHours()
|
||
this.availableTimeSlots.forEach(slot => {
|
||
const slotHour = parseInt(slot.value.split(':')[0])
|
||
slot.disabled = slotHour <= currentHour
|
||
})
|
||
} else {
|
||
this.availableTimeSlots.forEach(slot => {
|
||
slot.disabled = false
|
||
})
|
||
}
|
||
},
|
||
|
||
// 验证手机号
|
||
validatePhone() {
|
||
if (!this.bookingForm.phone) {
|
||
this.formErrors.phone = '请输入联系电话'
|
||
} else if (!this.isValidPhone(this.bookingForm.phone)) {
|
||
this.formErrors.phone = '请输入正确的手机号'
|
||
} else {
|
||
this.formErrors.phone = ''
|
||
}
|
||
},
|
||
|
||
// 检查手机号格式
|
||
isValidPhone(phone) {
|
||
return /^1[3-9]\d{9}$/.test(phone)
|
||
},
|
||
|
||
// 提交预约
|
||
async submitBooking() {
|
||
// 如果表单无效,不允许提交
|
||
if (!this.isFormValid) {
|
||
this.validateForm()
|
||
return
|
||
}
|
||
|
||
try {
|
||
uni.showLoading({ title: '提交中...' })
|
||
|
||
const bookingData = {
|
||
housekeeperId: this.housekeeperId,
|
||
serviceId: this.selectedService.id,
|
||
serviceName: this.selectedService.name,
|
||
date: this.bookingForm.date,
|
||
time: this.bookingForm.time,
|
||
phone: this.bookingForm.phone,
|
||
requirements: this.bookingForm.requirements,
|
||
totalPrice: this.selectedService.price
|
||
}
|
||
|
||
// 调用预约API
|
||
// const result = await this.$api.housekeeper.submitBooking(bookingData)
|
||
|
||
// 模拟API调用
|
||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||
|
||
uni.showToast({ title: '预约成功', icon: 'success' })
|
||
this.closeBookingForm()
|
||
|
||
// 跳转到订单页面
|
||
setTimeout(() => {
|
||
uni.navigateTo({ url: '/pages/order/MyOrder/index' })
|
||
}, 1500)
|
||
|
||
} catch (error) {
|
||
console.error('预约失败:', error)
|
||
uni.showToast({ title: '预约失败,请重试', icon: 'error' })
|
||
} finally {
|
||
uni.hideLoading()
|
||
}
|
||
},
|
||
|
||
// 验证整个表单
|
||
validateForm() {
|
||
let hasError = false
|
||
|
||
if (!this.bookingForm.date) {
|
||
this.formErrors.date = '请选择预约日期'
|
||
hasError = true
|
||
}
|
||
|
||
if (!this.bookingForm.time) {
|
||
this.formErrors.time = '请选择预约时间'
|
||
hasError = true
|
||
}
|
||
|
||
if (!this.bookingForm.phone) {
|
||
this.formErrors.phone = '请输入联系电话'
|
||
hasError = true
|
||
} else if (!this.isValidPhone(this.bookingForm.phone)) {
|
||
this.formErrors.phone = '请输入正确的手机号'
|
||
hasError = true
|
||
}
|
||
|
||
if (hasError) {
|
||
uni.showToast({ title: '请完善表单信息', icon: 'none' })
|
||
}
|
||
|
||
return !hasError
|
||
},
|
||
|
||
// 联系管家
|
||
contactHousekeeper() {
|
||
// 跳转到聊天页面或拨打电话
|
||
uni.navigateTo({
|
||
url: `/pages/chat/index?housekeeperId=${this.housekeeperId}`
|
||
})
|
||
},
|
||
|
||
// 收藏管家
|
||
async favoriteHousekeeper() {
|
||
try {
|
||
// 调用收藏API
|
||
// await toggleFavoriteHousekeeper(this.housekeeperId)
|
||
|
||
this.housekeeperInfo.isFavorite = !this.housekeeperInfo.isFavorite
|
||
const message = this.housekeeperInfo.isFavorite ? '收藏成功' : '取消收藏'
|
||
uni.showToast({ title: message, icon: 'success' })
|
||
|
||
} catch (error) {
|
||
uni.showToast({ title: '操作失败', icon: 'error' })
|
||
}
|
||
},
|
||
|
||
// 查看更多评价
|
||
viewMoreReviews() {
|
||
uni.navigateTo({
|
||
url: `/pages/reviews/index?housekeeperId=${this.housekeeperId}`
|
||
})
|
||
},
|
||
|
||
// 预览图片
|
||
previewImage(images, current) {
|
||
uni.previewImage({
|
||
urls: images,
|
||
current: current
|
||
})
|
||
},
|
||
|
||
// 图片懒加载
|
||
onImageLoad(e) {
|
||
// 图片加载成功后的处理
|
||
console.log('图片加载成功:', e)
|
||
},
|
||
|
||
onImageError(e) {
|
||
// 图片加载失败的处理
|
||
console.log('图片加载失败:', e)
|
||
// 可以设置默认图片
|
||
},
|
||
|
||
// 节流函数 - 用于滚动事件优化
|
||
throttle(func, delay) {
|
||
let timeoutId
|
||
let lastExecTime = 0
|
||
return function (...args) {
|
||
const currentTime = Date.now()
|
||
|
||
if (currentTime - lastExecTime > delay) {
|
||
func.apply(this, args)
|
||
lastExecTime = currentTime
|
||
} else {
|
||
clearTimeout(timeoutId)
|
||
timeoutId = setTimeout(() => {
|
||
func.apply(this, args)
|
||
lastExecTime = Date.now()
|
||
}, delay - (currentTime - lastExecTime))
|
||
}
|
||
}
|
||
},
|
||
|
||
// 防抖函数 - 用于搜索等输入优化
|
||
debounce(func, delay) {
|
||
let timeoutId
|
||
return function (...args) {
|
||
clearTimeout(timeoutId)
|
||
timeoutId = setTimeout(() => func.apply(this, args), delay)
|
||
}
|
||
},
|
||
|
||
// 优化的滚动处理
|
||
optimizedScrollHandler() {
|
||
return this.throttle((e) => {
|
||
this.scrollData.scrollTop = e.detail.scrollTop
|
||
|
||
// 当滚动到一定位置时显示返回顶部按钮
|
||
this.scrollData.showBackToTop = e.detail.scrollTop > 500
|
||
|
||
// 滚动时隐藏/显示导航栏
|
||
if (e.detail.scrollTop > this.scrollData.lastScrollTop) {
|
||
// 向下滚动
|
||
this.scrollData.navbarVisible = false
|
||
} else {
|
||
// 向上滚动
|
||
this.scrollData.navbarVisible = true
|
||
}
|
||
this.scrollData.lastScrollTop = e.detail.scrollTop
|
||
}, 16) // 约60fps
|
||
},
|
||
|
||
// 返回顶部
|
||
backToTop() {
|
||
uni.pageScrollTo({
|
||
scrollTop: 0,
|
||
duration: 300
|
||
})
|
||
},
|
||
|
||
// 预加载关键资源
|
||
preloadResources() {
|
||
// 预加载管家头像
|
||
if (this.housekeeperInfo.avatar) {
|
||
const img = new Image()
|
||
img.src = this.housekeeperInfo.avatar
|
||
}
|
||
|
||
// 预加载服务图片
|
||
this.serviceList.forEach(service => {
|
||
if (service.image) {
|
||
const img = new Image()
|
||
img.src = service.image
|
||
}
|
||
})
|
||
},
|
||
|
||
// 数据分页加载
|
||
async loadMoreReviews() {
|
||
if (this.reviewsLoading || !this.hasMoreReviews) return
|
||
|
||
this.reviewsLoading = true
|
||
|
||
try {
|
||
// 模拟API调用
|
||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||
|
||
const newReviews = [
|
||
// 模拟新的评价数据
|
||
{
|
||
id: Date.now(),
|
||
user: {
|
||
name: '新用户',
|
||
avatar: '/static/images/default-avatar.png'
|
||
},
|
||
rating: 5,
|
||
content: '新加载的评价内容...',
|
||
date: '2024-01-20',
|
||
images: []
|
||
}
|
||
]
|
||
|
||
this.reviewsList.push(...newReviews)
|
||
|
||
// 检查是否还有更多数据
|
||
this.hasMoreReviews = this.reviewsList.length < 50 // 假设最多50条评价
|
||
|
||
} catch (error) {
|
||
console.error('加载更多评价失败:', error)
|
||
uni.showToast({ title: '加载失败', icon: 'error' })
|
||
} finally {
|
||
this.reviewsLoading = false
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.housekeeper-detail {
|
||
background: #f5f5f5;
|
||
min-height: 100vh;
|
||
padding-bottom: 120rpx;
|
||
}
|
||
|
||
// 管家头部信息区
|
||
.housekeeper-header {
|
||
position: relative;
|
||
background: #fff;
|
||
|
||
.header-bg {
|
||
position: relative;
|
||
overflow: hidden;
|
||
height: 791rpx;
|
||
border-radius: 0 0 200rpx 200rpx;
|
||
.bg-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.bg-overlay {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: linear-gradient(180deg, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0.6) 100%);
|
||
}
|
||
}
|
||
|
||
.header-content {
|
||
position: relative;
|
||
margin-top: -120rpx;
|
||
padding: 0 32rpx 40rpx;
|
||
z-index: 2;
|
||
}
|
||
}
|
||
|
||
// 管家基本信息
|
||
.housekeeper-info {
|
||
display: flex;
|
||
margin-bottom: 40rpx;
|
||
|
||
.avatar-section {
|
||
position: relative;
|
||
margin-right: 24rpx;
|
||
|
||
.avatar {
|
||
width: 120rpx;
|
||
height: 120rpx;
|
||
border-radius: 60rpx;
|
||
border: 4rpx solid #fff;
|
||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.15);
|
||
}
|
||
|
||
.online-status {
|
||
position: absolute;
|
||
bottom: 8rpx;
|
||
right: 8rpx;
|
||
width: 24rpx;
|
||
height: 24rpx;
|
||
background: #fff;
|
||
border-radius: 12rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.status-dot {
|
||
width: 12rpx;
|
||
height: 12rpx;
|
||
background: #52c41a;
|
||
border-radius: 6rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.info-section {
|
||
flex: 1;
|
||
|
||
.name-row {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 16rpx;
|
||
|
||
.name {
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
color: #fff;
|
||
margin-right: 16rpx;
|
||
}
|
||
|
||
.title-badge {
|
||
background: rgba(255,255,255,0.9);
|
||
padding: 6rpx 16rpx;
|
||
border-radius: 20rpx;
|
||
|
||
.title {
|
||
font-size: 24rpx;
|
||
color: #333;
|
||
}
|
||
}
|
||
}
|
||
|
||
.rating-row {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 16rpx;
|
||
|
||
.stars {
|
||
display: flex;
|
||
margin-right: 12rpx;
|
||
|
||
.star {
|
||
font-size: 28rpx;
|
||
color: #ddd;
|
||
|
||
&.active {
|
||
color: #ffd700;
|
||
}
|
||
}
|
||
}
|
||
|
||
.rating-text {
|
||
font-size: 28rpx;
|
||
color: #fff;
|
||
margin-right: 16rpx;
|
||
}
|
||
|
||
.service-years {
|
||
font-size: 24rpx;
|
||
color: rgba(255,255,255,0.8);
|
||
}
|
||
}
|
||
|
||
.description {
|
||
font-size: 28rpx;
|
||
line-height: 40rpx;
|
||
color: rgba(255,255,255,0.9);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 专业标签区
|
||
.tags-section {
|
||
margin-bottom: 40rpx;
|
||
|
||
.section-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.tags-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 24rpx;
|
||
|
||
.tag-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 20rpx;
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
|
||
|
||
.tag-icon {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.tag-text {
|
||
font-size: 24rpx;
|
||
color: #333;
|
||
text-align: center;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 金牌管家认证
|
||
.certification-section {
|
||
background: linear-gradient(135deg, #fff8e1 0%, #fff3c4 100%);
|
||
border-radius: 20rpx;
|
||
padding: 24rpx;
|
||
border: 2rpx solid #ffd700;
|
||
|
||
.cert-header {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 16rpx;
|
||
|
||
.cert-icon {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
margin-right: 12rpx;
|
||
}
|
||
|
||
.cert-title {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: #d4a017;
|
||
}
|
||
}
|
||
|
||
.cert-content {
|
||
margin-bottom: 16rpx;
|
||
|
||
.cert-desc {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.cert-features {
|
||
display: flex;
|
||
gap: 16rpx;
|
||
|
||
.feature {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
}
|
||
}
|
||
|
||
.cert-stats {
|
||
.stat {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
line-height: 32rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 服务项目区
|
||
.services-section {
|
||
margin: 24rpx 32rpx;
|
||
|
||
.section-header {
|
||
margin-bottom: 24rpx;
|
||
|
||
.section-title {
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.section-subtitle {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
}
|
||
}
|
||
|
||
.services-list {
|
||
.service-item {
|
||
background: #fff;
|
||
border-radius: 20rpx;
|
||
padding: 32rpx;
|
||
margin-bottom: 24rpx;
|
||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.08);
|
||
|
||
.service-content {
|
||
.service-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
margin-bottom: 16rpx;
|
||
|
||
.service-name {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.service-price {
|
||
display: flex;
|
||
align-items: baseline;
|
||
color: #ff4757;
|
||
|
||
.price-symbol {
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.price-amount {
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.price-unit {
|
||
font-size: 24rpx;
|
||
margin-left: 4rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.service-desc {
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
line-height: 40rpx;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.service-features {
|
||
display: flex;
|
||
gap: 12rpx;
|
||
margin-bottom: 16rpx;
|
||
|
||
.feature-tag {
|
||
background: #f0f9ff;
|
||
color: #0ea5e9;
|
||
padding: 6rpx 16rpx;
|
||
border-radius: 20rpx;
|
||
font-size: 24rpx;
|
||
}
|
||
}
|
||
|
||
.service-meta {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
}
|
||
|
||
.service-action {
|
||
margin-top: 24rpx;
|
||
|
||
.book-btn {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: #fff;
|
||
text-align: center;
|
||
padding: 20rpx;
|
||
border-radius: 50rpx;
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 用户评价区
|
||
.reviews-section {
|
||
margin: 24rpx 32rpx;
|
||
|
||
.section-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 24rpx;
|
||
|
||
.section-title {
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.view-more {
|
||
display: flex;
|
||
align-items: center;
|
||
color: #666;
|
||
font-size: 26rpx;
|
||
|
||
.arrow {
|
||
margin-left: 8rpx;
|
||
font-size: 20rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.reviews-list {
|
||
.review-item {
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 24rpx;
|
||
margin-bottom: 16rpx;
|
||
|
||
.review-header {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 16rpx;
|
||
|
||
.user-avatar {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
border-radius: 30rpx;
|
||
margin-right: 16rpx;
|
||
}
|
||
|
||
.user-info {
|
||
flex: 1;
|
||
|
||
.username {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.review-rating {
|
||
display: flex;
|
||
|
||
.star {
|
||
font-size: 20rpx;
|
||
color: #ddd;
|
||
|
||
&.active {
|
||
color: #ffd700;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.review-date {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
}
|
||
|
||
.review-content {
|
||
font-size: 28rpx;
|
||
line-height: 40rpx;
|
||
color: #333;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.review-images {
|
||
display: flex;
|
||
gap: 12rpx;
|
||
|
||
.review-img {
|
||
width: 120rpx;
|
||
height: 120rpx;
|
||
border-radius: 8rpx;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 预约表单
|
||
.booking-form {
|
||
background: #fff;
|
||
border-radius: 20rpx 20rpx 0 0;
|
||
max-height: 80vh;
|
||
|
||
.form-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 32rpx;
|
||
border-bottom: 1rpx solid #f0f0f0;
|
||
|
||
.form-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.close-btn {
|
||
font-size: 48rpx;
|
||
color: #999;
|
||
line-height: 1;
|
||
}
|
||
}
|
||
|
||
.form-content {
|
||
padding: 32rpx;
|
||
max-height: 60vh;
|
||
overflow-y: auto;
|
||
|
||
.selected-service {
|
||
background: #f8f9fa;
|
||
padding: 24rpx;
|
||
border-radius: 16rpx;
|
||
margin-bottom: 32rpx;
|
||
|
||
.service-name {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.service-price {
|
||
font-size: 32rpx;
|
||
color: #ff4757;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 32rpx;
|
||
|
||
.label {
|
||
display: block;
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.input-field {
|
||
width: 100%;
|
||
padding: 24rpx;
|
||
border: 1rpx solid #e0e0e0;
|
||
border-radius: 12rpx;
|
||
font-size: 28rpx;
|
||
background: #fff;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
|
||
&.error {
|
||
border-color: #ff4757;
|
||
}
|
||
|
||
.arrow {
|
||
color: #999;
|
||
}
|
||
}
|
||
|
||
.textarea-field {
|
||
width: 100%;
|
||
min-height: 120rpx;
|
||
padding: 24rpx;
|
||
border: 1rpx solid #e0e0e0;
|
||
border-radius: 12rpx;
|
||
font-size: 28rpx;
|
||
background: #fff;
|
||
resize: none;
|
||
|
||
&.error {
|
||
border-color: #ff4757;
|
||
}
|
||
}
|
||
|
||
.error-message {
|
||
color: #ff4757;
|
||
font-size: 24rpx;
|
||
margin-top: 8rpx;
|
||
}
|
||
|
||
.char-counter {
|
||
text-align: right;
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-top: 8rpx;
|
||
}
|
||
|
||
// 时间选择器
|
||
.time-slots {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 16rpx;
|
||
margin-top: 16rpx;
|
||
|
||
.time-slot {
|
||
padding: 20rpx;
|
||
border: 1rpx solid #e0e0e0;
|
||
border-radius: 12rpx;
|
||
text-align: center;
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
background: #fff;
|
||
transition: all 0.3s;
|
||
|
||
&.selected {
|
||
background: #667eea;
|
||
color: #fff;
|
||
border-color: #667eea;
|
||
}
|
||
|
||
&.disabled {
|
||
background: #f5f5f5;
|
||
color: #ccc;
|
||
border-color: #f0f0f0;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 总价显示
|
||
.total-price {
|
||
background: #f8f9fa;
|
||
padding: 24rpx;
|
||
border-radius: 12rpx;
|
||
margin-top: 16rpx;
|
||
|
||
.price-label {
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.price-amount {
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
color: #ff4757;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.form-footer {
|
||
padding: 32rpx;
|
||
border-top: 1rpx solid #f0f0f0;
|
||
|
||
.submit-btn {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: #fff;
|
||
text-align: center;
|
||
padding: 24rpx;
|
||
border-radius: 50rpx;
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
transition: all 0.3s;
|
||
|
||
&.disabled {
|
||
background: #f0f0f0;
|
||
color: #ccc;
|
||
cursor: not-allowed;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 返回顶部按钮
|
||
.back-to-top {
|
||
position: fixed;
|
||
right: 30rpx;
|
||
bottom: 200rpx;
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 999;
|
||
transition: all 0.3s ease;
|
||
|
||
.back-icon {
|
||
color: #ffffff;
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
}
|
||
|
||
&:active {
|
||
transform: scale(0.9);
|
||
}
|
||
}
|
||
|
||
// 加载更多样式
|
||
.loading-more,
|
||
.load-more,
|
||
.no-more {
|
||
padding: 40rpx 0;
|
||
text-align: center;
|
||
|
||
.loading-text,
|
||
.load-more-text,
|
||
.no-more-text {
|
||
font-size: 28rpx;
|
||
color: #999999;
|
||
}
|
||
}
|
||
|
||
.load-more {
|
||
cursor: pointer;
|
||
|
||
.load-more-text {
|
||
color: #007AFF;
|
||
}
|
||
|
||
&:active {
|
||
opacity: 0.7;
|
||
}
|
||
}
|
||
|
||
// 底部操作栏
|
||
.bottom-actions {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
background: #fff;
|
||
padding: 20rpx 32rpx;
|
||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||
border-top: 1rpx solid #f0f0f0;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 24rpx;
|
||
z-index: 100;
|
||
|
||
.action-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
|
||
.action-icon {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.action-text {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
}
|
||
}
|
||
|
||
.primary-action {
|
||
flex: 1;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: #fff;
|
||
text-align: center;
|
||
padding: 24rpx;
|
||
border-radius: 50rpx;
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
|
||
// 响应式适配
|
||
@media screen and (min-width: 768px) {
|
||
.housekeeper-detail {
|
||
max-width: 750rpx;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.tags-grid {
|
||
grid-template-columns: repeat(8, 1fr);
|
||
}
|
||
}
|
||
</style> |