解决登录显示、首页显示bug

This commit is contained in:
2026-05-19 11:06:46 +08:00
parent 2f7e097e6c
commit 00a859c551
181 changed files with 55329 additions and 998 deletions

View File

@@ -0,0 +1,628 @@
<template>
<view class="booking-detail-page">
<ServicePageScaffold title="预约服务" fallback-url="/pages/mall/consumer/home-service/index">
<view class="detail-service-summary">
<view class="detail-summary-cover">
<text class="detail-summary-cover-text">{{ serviceImageText }}</text>
</view>
<view class="detail-summary-main">
<text class="detail-summary-title">{{ serviceTitle }}</text>
<text class="detail-summary-desc">{{ serviceSubtitle }}</text>
<text class="detail-summary-duration">服务时长:{{ serviceDuration }}</text>
<view class="detail-summary-tags">
<text v-for="tag in guaranteeTags" :key="tag.id" class="detail-summary-tag">{{ tag.label }}</text>
</view>
<view class="detail-summary-price-row">
<text class="detail-summary-price-prefix">¥</text>
<text class="detail-summary-price">{{ servicePrice }}</text>
<text class="detail-summary-price-unit">起 / 次</text>
</view>
</view>
</view>
<ServicePanel title="服务说明卡" subtitle="围绕服务内容、适用人群和注意事项建立预约预期。">
<view class="detail-info-list">
<view class="detail-info-item">
<text class="detail-info-label">服务内容</text>
<text class="detail-info-value">{{ serviceSubtitle }}</text>
</view>
<view class="detail-info-item">
<text class="detail-info-label">适用人群</text>
<text class="detail-info-value">{{ serviceSuitableFor }}</text>
</view>
<view class="detail-info-item">
<text class="detail-info-label">服务时长</text>
<text class="detail-info-value">{{ serviceDuration }}</text>
</view>
<view class="detail-info-item">
<text class="detail-info-label">注意事项</text>
<text class="detail-info-value">预约成功后请保持电话畅通,首次上门建议家属在场。</text>
</view>
<view class="detail-info-item">
<text class="detail-info-label">不包含项目</text>
<text class="detail-info-value">{{ serviceExcludeText }}</text>
</view>
</view>
</ServicePanel>
<ServicePanel title="Step1 服务地址" subtitle="没有选过地址时先提示用户补全上门地址。">
<view class="booking-step-card">
<text class="step-card-title">上门服务地址</text>
<text v-if="addressText == ''" class="step-card-placeholder">请选择上门服务地址</text>
<text v-else class="step-card-value">{{ addressText }}</text>
<view class="step-card-action" @click="selectAddress">选择地址</view>
</view>
</ServicePanel>
<ServicePanel title="Step2 服务机构 / 服务人员" subtitle="当前先展示静态推荐机构,后续可接入真实人员选择。">
<view class="booking-step-card agency-card">
<view class="agency-card-top">
<text class="step-card-title">{{ agency.name }}</text>
<text class="agency-card-distance">{{ agency.distance }}</text>
</view>
<text class="agency-card-meta">评分 {{ agency.rating }}</text>
<text class="step-card-value">{{ agency.summary }}</text>
<text class="agency-card-todo">TODO后续接入真实服务机构 / 服务人员选择接口。</text>
</view>
</ServicePanel>
<ServicePanel title="Step3 上门时间" subtitle="先完成日期和时间段选择,再提交预约申请。">
<scroll-view class="booking-day-scroll" direction="horizontal" :show-scrollbar="false">
<view class="booking-day-row">
<view
v-for="item in bookingDays"
:key="item.id"
:class="['booking-day-card', selectedDayId == item.id ? 'booking-day-card-active' : '']"
@click="selectDay(item.id)"
>
<text :class="['booking-day-label', selectedDayId == item.id ? 'booking-day-label-active' : '']">{{ item.label }}</text>
<text :class="['booking-day-date', selectedDayId == item.id ? 'booking-day-date-active' : '']">{{ item.dateText }}</text>
<text :class="['booking-day-weekday', selectedDayId == item.id ? 'booking-day-weekday-active' : '']">{{ item.weekday }}</text>
</view>
</view>
</scroll-view>
<view class="booking-slot-grid">
<view
v-for="item in bookingSlots"
:key="item.id"
:class="['booking-slot-card', selectedSlotId == item.id ? 'booking-slot-card-active' : '', item.available ? '' : 'booking-slot-card-disabled']"
@click="selectSlot(item.id, item.available)"
>
<text :class="['booking-slot-label', selectedSlotId == item.id ? 'booking-slot-label-active' : '']">{{ item.label }}</text>
</view>
</view>
</ServicePanel>
<ServicePanel title="Step4 联系人信息" subtitle="使用现有 input 组件完成预约联系人填写。">
<view class="contact-form-item">
<text class="contact-form-label">联系人姓名</text>
<input v-model="contactName" class="contact-form-input" placeholder="请输入联系人姓名" />
</view>
<view class="contact-form-item">
<text class="contact-form-label">联系电话</text>
<input v-model="contactPhone" class="contact-form-input" type="number" placeholder="请输入联系电话" />
</view>
<view class="contact-form-item">
<text class="contact-form-label">性别</text>
<view class="gender-row">
<view :class="['gender-pill', contactGender == '先生' ? 'gender-pill-active' : '']" @click="contactGender = '先生'">先生</view>
<view :class="['gender-pill', contactGender == '女士' ? 'gender-pill-active' : '']" @click="contactGender = '女士'">女士</view>
</view>
</view>
<view class="contact-form-item">
<text class="contact-form-label">备注</text>
<textarea v-model="remarkText" class="contact-form-textarea" placeholder="如行动不便、术后照护重点、上门注意事项"></textarea>
</view>
</ServicePanel>
<ServicePanel title="用户保障卡" subtitle="让预约页更像服务平台而不是普通商品页。">
<view class="guarantee-grid">
<view v-for="item in guaranteeTags" :key="item.id" class="guarantee-grid-item">
<text class="guarantee-grid-text">{{ item.label }}</text>
</view>
</view>
</ServicePanel>
<view class="detail-page-bottom-space"></view>
</ServicePageScaffold>
<view class="booking-bottom-bar">
<view class="booking-bottom-main">
<view>
<text class="booking-bottom-price-prefix">¥</text>
<text class="booking-bottom-price">{{ servicePrice }}</text>
<text class="booking-bottom-unit">起</text>
</view>
<text class="booking-bottom-time">{{ selectedTimeText }}</text>
</view>
<view class="booking-submit-btn" @click="submitBooking">立即预约</view>
</view>
</view>
</template>
<script setup lang="uts">
import { computed, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import ServicePageScaffold from '@/components/homeService/ServicePageScaffold.uvue'
import ServicePanel from '@/components/homeService/ServicePanel.uvue'
import { createHomeServiceApplication, fetchHomeServiceCatalog } from '@/services/homeServiceService.uts'
import { HomeServiceApplicationDraftType, HomeServiceCatalogType } from '@/types/home-service.uts'
import {
BookingDayOptionType,
BookingTimeSlotType,
HomeServiceAgencyType,
HomeServiceGuaranteeItemType,
getBookingDayOptions,
getBookingTimeSlots,
getHomeServiceItems,
getRecommendedAgency,
getServiceExcludes,
getServiceGuarantees
} from '@/utils/homeServiceUiMock.uts'
const serviceId = ref('svc-001')
const serviceTitle = ref('基础上门护理')
const serviceSubtitle = ref('覆盖生命体征监测、基础照护、风险提醒。')
const servicePrice = ref(168)
const serviceDuration = ref('约 2 小时')
const serviceSuitableFor = ref('行动不便、术后恢复、慢病随访老人')
const serviceImageText = ref('照护')
const serviceExcludeText = ref('高风险处置、住院陪护、急诊陪诊')
const bookingDays = ref<Array<BookingDayOptionType>>([])
const bookingSlots = ref<Array<BookingTimeSlotType>>([])
const guaranteeTags = ref<Array<HomeServiceGuaranteeItemType>>([])
const agency = ref<HomeServiceAgencyType>({
id: 'agency-001',
name: '梅江居家护理服务中心',
distance: '距您 1.2km',
rating: '4.9',
summary: '提供基础照护、上门护理和长者陪伴服务。'
})
const addressText = ref('')
const selectedDayId = ref('day-1')
const selectedSlotId = ref('slot-1')
const contactName = ref('李晓兰')
const contactPhone = ref('13800138000')
const contactGender = ref('女士')
const remarkText = ref('老人需要基础照护与血压监测。')
const selectedTimeText = computed((): string => {
let selectedDayLabel = ''
for (let i = 0; i < bookingDays.value.length; i++) {
if (bookingDays.value[i].id == selectedDayId.value) {
selectedDayLabel = bookingDays.value[i].label + ' ' + bookingDays.value[i].dateText
break
}
}
let selectedSlotLabel = ''
for (let i = 0; i < bookingSlots.value.length; i++) {
if (bookingSlots.value[i].id == selectedSlotId.value) {
selectedSlotLabel = bookingSlots.value[i].label
break
}
}
if (selectedDayLabel == '' || selectedSlotLabel == '') {
return '请选择上门时间'
}
return selectedDayLabel + ' ' + selectedSlotLabel
})
async function loadData() {
bookingDays.value = getBookingDayOptions()
bookingSlots.value = getBookingTimeSlots()
guaranteeTags.value = getServiceGuarantees()
agency.value = getRecommendedAgency(serviceId.value)
serviceExcludeText.value = getServiceExcludes(serviceId.value).join('')
const catalog = await fetchHomeServiceCatalog()
let matchedService: HomeServiceCatalogType | null = null
for (let i = 0; i < catalog.length; i++) {
if (catalog[i].id == serviceId.value) {
matchedService = catalog[i]
break
}
}
if (matchedService == null) {
const fallbackItems = getHomeServiceItems(catalog)
if (fallbackItems.length > 0) {
serviceTitle.value = fallbackItems[0].title
serviceSubtitle.value = fallbackItems[0].subtitle
servicePrice.value = fallbackItems[0].price
serviceSuitableFor.value = fallbackItems[0].suitableFor
serviceImageText.value = fallbackItems[0].imageText
}
return
}
serviceTitle.value = matchedService.name
serviceSubtitle.value = matchedService.summary
servicePrice.value = matchedService.price
serviceDuration.value = matchedService.durationText
serviceSuitableFor.value = matchedService.suitableFor
const mappedItems = getHomeServiceItems([matchedService])
if (mappedItems.length > 0) {
serviceImageText.value = mappedItems[0].imageText
}
}
function selectAddress() {
if (addressText.value == '') {
addressText.value = '梅州市梅江区学海路 18 号 2 栋 602'
uni.showToast({ title: '已填入示例地址', icon: 'none' })
return
}
uni.navigateTo({ url: '/pages/mall/consumer/address-list' })
}
function selectDay(dayId: string) {
selectedDayId.value = dayId
}
function selectSlot(slotId: string, available: boolean) {
if (!available) {
uni.showToast({ title: '该时段暂不可约', icon: 'none' })
return
}
selectedSlotId.value = slotId
}
async function submitBooking() {
if (addressText.value == '') {
uni.showToast({ title: '请选择上门服务地址', icon: 'none' })
return
}
if (contactName.value == '' || contactPhone.value == '') {
uni.showToast({ title: '请补全联系人信息', icon: 'none' })
return
}
const draft: HomeServiceApplicationDraftType = {
serviceId: serviceId.value,
serviceName: serviceTitle.value,
applicantName: contactName.value,
elderName: contactName.value,
age: 78,
phone: contactPhone.value,
address: addressText.value,
preferredTime: selectedTimeText.value,
demandSummary: remarkText.value == '' ? serviceSubtitle.value : remarkText.value
}
const created = await createHomeServiceApplication(draft)
uni.showToast({ title: '预约已提交', icon: 'success' })
uni.navigateTo({ url: '/pages/mall/consumer/home-service/order-detail?id=' + created.id })
}
onLoad((options) => {
const id = options['id']
if (id != null) {
serviceId.value = id as string
}
const mode = options['mode']
if (mode != null && mode == 'booking') {
addressText.value = '梅州市梅江区学海路 18 号 2 栋 602'
}
loadData()
})
</script>
<style scoped>
.booking-detail-page {
background: #f4f8fb;
}
.detail-service-summary {
background: #ffffff;
border-radius: 32rpx;
padding: 28rpx;
box-shadow: 0 12rpx 24rpx rgba(15, 23, 42, 0.06);
flex-direction: row;
align-items: flex-start;
margin-bottom: 24rpx;
}
.detail-summary-cover {
width: 172rpx;
height: 172rpx;
border-radius: 32rpx;
background: linear-gradient(180deg, #eafbf7 0%, #eff6ff 100%);
align-items: center;
justify-content: center;
}
.detail-summary-cover-text {
font-size: 34rpx;
font-weight: 700;
color: #0f766e;
}
.detail-summary-main {
flex: 1;
min-width: 0;
margin-left: 24rpx;
}
.detail-summary-title {
font-size: 34rpx;
font-weight: 700;
color: #16324f;
}
.detail-summary-desc,
.detail-summary-duration,
.detail-info-value,
.step-card-value,
.step-card-placeholder,
.agency-card-meta,
.agency-card-todo,
.booking-bottom-time {
margin-top: 10rpx;
font-size: 24rpx;
line-height: 34rpx;
color: #64748b;
}
.detail-summary-tags,
.gender-row,
.guarantee-grid,
.agency-card-top,
.booking-bottom-bar,
.booking-bottom-main {
flex-direction: row;
align-items: center;
}
.detail-summary-tags {
flex-wrap: wrap;
margin-top: 16rpx;
}
.detail-summary-tag {
padding: 10rpx 16rpx;
border-radius: 999rpx;
background: #eff6ff;
font-size: 22rpx;
color: #476072;
margin-right: 10rpx;
margin-bottom: 10rpx;
}
.detail-summary-price-row {
margin-top: 14rpx;
flex-direction: row;
align-items: flex-end;
}
.detail-summary-price-prefix,
.detail-summary-price,
.booking-bottom-price-prefix,
.booking-bottom-price {
font-size: 38rpx;
font-weight: 700;
color: #0f766e;
}
.detail-summary-price-unit,
.booking-bottom-unit {
font-size: 22rpx;
color: #64748b;
margin-left: 8rpx;
margin-bottom: 4rpx;
}
.detail-info-item,
.contact-form-item {
margin-bottom: 24rpx;
}
.detail-info-label,
.contact-form-label,
.step-card-title {
font-size: 28rpx;
font-weight: 700;
color: #16324f;
}
.booking-step-card {
padding: 24rpx;
border-radius: 24rpx;
background: #f8fbfd;
}
.step-card-placeholder {
color: #94a3b8;
}
.step-card-action {
margin-top: 20rpx;
height: 68rpx;
border-radius: 999rpx;
background: #16a085;
color: #ffffff;
font-size: 24rpx;
font-weight: 700;
align-items: center;
justify-content: center;
}
.agency-card-distance {
font-size: 22rpx;
color: #0f766e;
}
.agency-card-todo {
font-size: 22rpx;
color: #94a3b8;
}
.booking-day-scroll {
height: 156rpx;
}
.booking-day-row {
flex-direction: row;
padding-right: 20rpx;
}
.booking-day-card {
width: 160rpx;
padding: 20rpx;
border-radius: 24rpx;
background: #f8fbfd;
margin-right: 16rpx;
box-sizing: border-box;
}
.booking-day-card-active {
background: #0f766e;
}
.booking-day-label,
.booking-day-date,
.booking-day-weekday {
font-size: 24rpx;
line-height: 34rpx;
color: #476072;
}
.booking-day-date,
.booking-day-weekday {
margin-top: 6rpx;
}
.booking-day-label-active,
.booking-day-date-active,
.booking-day-weekday-active {
color: #ffffff;
}
.booking-slot-grid {
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
margin-top: 20rpx;
}
.booking-slot-card {
width: 48.5%;
height: 82rpx;
border-radius: 22rpx;
background: #f8fbfd;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
border-width: 2rpx;
border-style: solid;
border-color: transparent;
box-sizing: border-box;
}
.booking-slot-card-active {
background: #eafbf7;
border-color: #16a085;
}
.booking-slot-card-disabled {
background: #f1f5f9;
opacity: 0.55;
}
.booking-slot-label {
font-size: 24rpx;
color: #476072;
}
.booking-slot-label-active {
color: #0f766e;
font-weight: 700;
}
.contact-form-input,
.contact-form-textarea {
width: 100%;
background: #f8fbfd;
border-radius: 22rpx;
font-size: 26rpx;
color: #16324f;
box-sizing: border-box;
margin-top: 14rpx;
}
.contact-form-input {
height: 84rpx;
padding: 0 24rpx;
}
.contact-form-textarea {
height: 160rpx;
padding: 20rpx 24rpx;
}
.gender-pill {
height: 64rpx;
padding: 0 28rpx;
border-radius: 999rpx;
background: #f1f5f9;
font-size: 24rpx;
color: #476072;
align-items: center;
justify-content: center;
margin-right: 16rpx;
margin-top: 14rpx;
}
.gender-pill-active {
background: #eafbf7;
color: #0f766e;
font-weight: 700;
}
.guarantee-grid {
flex-wrap: wrap;
justify-content: space-between;
}
.guarantee-grid-item {
width: 48%;
height: 72rpx;
border-radius: 20rpx;
background: #f8fbfd;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
}
.guarantee-grid-text {
font-size: 24rpx;
color: #476072;
}
.detail-page-bottom-space {
height: 160rpx;
}
.booking-bottom-bar {
position: fixed;
left: 24rpx;
right: 24rpx;
bottom: 28rpx;
background: #ffffff;
border-radius: 30rpx;
padding: 20rpx 22rpx;
box-shadow: 0 12rpx 28rpx rgba(15, 23, 42, 0.12);
justify-content: space-between;
}
.booking-submit-btn {
height: 84rpx;
padding: 0 34rpx;
border-radius: 999rpx;
background: #16a085;
font-size: 28rpx;
font-weight: 700;
color: #ffffff;
align-items: center;
justify-content: center;
}
</style>