完善服务模块缺少付款页的bug

This commit is contained in:
2026-06-02 11:35:31 +08:00
parent c3324d459a
commit 881262940c
35 changed files with 29069 additions and 557 deletions

View File

@@ -37,7 +37,8 @@
</view>
</ServicePanel>
<ServicePanel title="Step3 上门时间" subtitle="可直接选择推荐时段,也支持手动输入。">
<ServicePanel title="Step3 预约上门时间" subtitle="请选择服务人员预计到达的日期和时间段,提交前系统将重新校验可预约状态。">
<text class="booking-hint">请至少提前30分钟预约以便安排服务人员到达现场。</text>
<scroll-view class="booking-day-scroll" direction="horizontal" :show-scrollbar="false">
<view class="booking-day-row">
<view
@@ -48,6 +49,7 @@
>
<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-date-active' : '']">{{ item.weekday }}</text>
</view>
</view>
</scroll-view>
@@ -61,9 +63,18 @@
<text :class="['booking-slot-label', selectedSlotId == item.id ? 'booking-slot-label-active' : '']">{{ item.label }}</text>
</view>
</view>
<view class="form-item form-item-last">
<text class="label">期望时间</text>
<input v-model="form.preferredTime" class="input" placeholder="例如 2026-05-14 上午" />
<view class="custom-slot-section">
<text class="custom-slot-title">其他上门时间</text>
<picker
mode="selector"
:range="customSlotLabels"
:value="customSlotIndex"
@change="handleCustomSlotChange"
>
<view class="custom-slot-picker">
<text class="custom-slot-picker-text">{{ customSlotDisplayText }}</text>
</view>
</picker>
</view>
</ServicePanel>
@@ -112,45 +123,88 @@ import { computed, reactive, 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 { createHomeServiceApplication, fetchHomeServiceCatalog, fetchHomeServicePackages } from '@/services/homeServiceService.uts'
import { HomeServiceApplicationDraftType, HomeServiceCatalogType } from '@/types/home-service.uts'
import { BookingDayOptionType, BookingTimeSlotType, getBookingDayOptions, getBookingTimeSlots } from '@/utils/homeServiceUiMock.uts'
import {
BookingDayOptionType,
BookingTimeSlotType,
buildBookingDays,
buildCustomSlots,
buildPresetSlots,
formatStandardAppointmentTime,
hasAnyAvailableSlots,
validateSelectedAppointmentTime
} from '@/utils/homeServiceBookingTime.uts'
const services = ref<Array<HomeServiceCatalogType>>([])
const selectedServiceId = ref('svc-001')
const ageText = ref('78')
const bookingDays = ref<Array<BookingDayOptionType>>([])
const bookingSlots = ref<Array<BookingTimeSlotType>>([])
const selectedDayId = ref('day-1')
const selectedSlotId = ref('slot-1')
const customSlots = ref<Array<BookingTimeSlotType>>([])
const selectedDayId = ref('')
const selectedSlotId = ref('')
const selectedCustomSlotId = ref('')
const customSlotIndex = ref(-1)
const form = reactive({
serviceId: 'svc-001',
serviceName: '基础上门护理',
selectedPackageId: '',
selectedPackageName: '',
applicantName: '李晓兰',
elderName: '李奶奶',
age: 78,
gender: '女',
phone: '13800138000',
address: '梅州市梅江区学海路 18 号 2 栋 602',
preferredTime: '2026-05-14 上午',
preferredTime: '',
appointmentStartAt: 0,
appointmentEndAt: 0,
appointmentDate: '',
slotSource: '',
minAdvanceMinutesSnapshot: 30,
demandSummary: '老人需要基础照护、血压监测和跌倒风险提醒。'
} as HomeServiceApplicationDraftType)
const selectedPackagePrice = ref('0')
const selectedPrice = computed((): string => {
for (let i = 0; i < services.value.length; i++) {
if (services.value[i].id == selectedServiceId.value) {
return services.value[i].price.toString()
}
if (selectedPackagePrice.value != '0') {
return selectedPackagePrice.value
}
return '0'
})
function initDefaultDaySelection(now: Date) {
let firstAvailableIndex = -1
for (let i = 0; i < bookingDays.value.length; i++) {
if (hasAnyAvailableSlots(bookingDays.value[i].dateKey, now)) {
firstAvailableIndex = i
break
}
}
if (firstAvailableIndex >= 0) {
selectedDayId.value = bookingDays.value[firstAvailableIndex].id
bookingSlots.value = buildPresetSlots(bookingDays.value[firstAvailableIndex].dateKey, now)
customSlots.value = buildCustomSlots(bookingDays.value[firstAvailableIndex].dateKey, now)
} else {
selectedDayId.value = ''
bookingSlots.value = [] as Array<BookingTimeSlotType>
customSlots.value = [] as Array<BookingTimeSlotType>
}
selectedSlotId.value = ''
selectedCustomSlotId.value = ''
customSlotIndex.value = -1
}
async function loadData() {
bookingDays.value = getBookingDayOptions()
bookingSlots.value = getBookingTimeSlots()
const now = new Date()
bookingDays.value = buildBookingDays(now)
initDefaultDaySelection(now)
services.value = await fetchHomeServiceCatalog()
if (services.value.length > 0) {
selectService(services.value[0].id, services.value[0].name)
await selectService(services.value[0].id, services.value[0].name)
return
}
selectedServiceId.value = ''
@@ -158,43 +212,113 @@ async function loadData() {
form.serviceName = ''
}
function selectService(serviceId: string, serviceName: string) {
async function selectService(serviceId: string, serviceName: string) {
selectedServiceId.value = serviceId
form.serviceId = serviceId
form.serviceName = serviceName
form.selectedPackageId = ''
form.selectedPackageName = ''
selectedPackagePrice.value = '0'
const packages = await fetchHomeServicePackages(serviceId)
if (packages.length > 0) {
form.selectedPackageId = packages[0].id
form.selectedPackageName = packages[0].packageName
selectedPackagePrice.value = packages[0].price.toString()
}
}
function syncPreferredTime() {
let selectedDay = ''
const selectedDay = computed((): BookingDayOptionType | null => {
for (let i = 0; i < bookingDays.value.length; i++) {
if (bookingDays.value[i].id == selectedDayId.value) {
selectedDay = bookingDays.value[i].label + ' ' + bookingDays.value[i].dateText
break
return bookingDays.value[i]
}
}
let selectedSlot = ''
return null
})
const selectedSlot = computed((): BookingTimeSlotType | null => {
for (let i = 0; i < bookingSlots.value.length; i++) {
if (bookingSlots.value[i].id == selectedSlotId.value) {
selectedSlot = bookingSlots.value[i].label
break
return bookingSlots.value[i]
}
}
if (selectedDay != '' && selectedSlot != '') {
form.preferredTime = selectedDay + ' ' + selectedSlot
return null
})
const customSlotLabels = computed((): Array<string> => {
const labels: Array<string> = []
for (let i = 0; i < customSlots.value.length; i++) {
labels.push(customSlots.value[i].label)
}
return labels
})
const customSlotDisplayText = computed((): string => {
if (selectedCustomSlotId.value == '') {
return '请选择其他时间段'
}
for (let i = 0; i < customSlots.value.length; i++) {
if (customSlots.value[i].id == selectedCustomSlotId.value) {
return customSlots.value[i].label
}
}
return '请选择其他时间段'
})
function syncPreferredTime() {
const day = selectedDay.value
const slot = selectedSlot.value
if (day != null && slot != null) {
form.preferredTime = day.label + ' ' + day.dateText + ' ' + day.weekday + ' ' + slot.label
form.appointmentStartAt = slot.startAt
form.appointmentEndAt = slot.endAt
form.appointmentDate = day.dateKey
form.slotSource = slot.source
}
}
function selectDay(dayId: string) {
selectedDayId.value = dayId
selectedSlotId.value = ''
selectedCustomSlotId.value = ''
customSlotIndex.value = -1
const day = selectedDay.value
if (day != null) {
const now = new Date()
bookingSlots.value = buildPresetSlots(day.dateKey, now)
customSlots.value = buildCustomSlots(day.dateKey, now)
}
syncPreferredTime()
}
function selectSlot(slotId: string, available: boolean) {
if (!available) {
uni.showToast({ title: '该时段暂不可约', icon: 'none' })
uni.showToast({ title: '该时间段已无法预约,请选择稍晚时间', icon: 'none' })
return
}
selectedSlotId.value = slotId
selectedCustomSlotId.value = ''
customSlotIndex.value = -1
syncPreferredTime()
}
function handleCustomSlotChange(e: any) {
const detail = e.detail
const index = typeof detail.value == 'number' ? detail.value : parseInt(detail.value)
if (isNaN(index) || index < 0 || index >= customSlots.value.length) {
return
}
const slot = customSlots.value[index]
if (slot == null) {
return
}
if (!slot.available) {
uni.showToast({ title: '该时间段已无法预约,请选择稍晚时间', icon: 'none' })
return
}
selectedCustomSlotId.value = slot.id
selectedSlotId.value = slot.id
customSlotIndex.value = index
syncPreferredTime()
}
@@ -203,10 +327,29 @@ async function submitApplication() {
uni.showToast({ title: '当前没有可预约的服务项目', icon: 'none' })
return
}
if (form.applicantName == '' || form.elderName == '' || form.phone == '' || form.address == '' || form.preferredTime == '') {
if (form.applicantName == '' || form.elderName == '' || form.phone == '' || form.address == '') {
uni.showToast({ title: '请补全申请信息', icon: 'none' })
return
}
const now = new Date()
const day = selectedDay.value
const slot = selectedSlot.value
const validation = validateSelectedAppointmentTime(day, slot, now)
if (!validation.valid) {
uni.showToast({ title: validation.message, icon: 'none' })
if (day != null && slot != null && !slot.available) {
bookingSlots.value = buildPresetSlots(day.dateKey, now)
customSlots.value = buildCustomSlots(day.dateKey, now)
selectedSlotId.value = ''
selectedCustomSlotId.value = ''
customSlotIndex.value = -1
}
return
}
const standardTime = formatStandardAppointmentTime(day, slot)
if (standardTime != '') {
form.preferredTime = standardTime
}
const parsedAge = parseInt(ageText.value)
form.age = isNaN(parsedAge) ? 0 : parsedAge
@@ -460,4 +603,39 @@ onLoad(() => {
font-weight: 700;
color: #ffffff;
}
.booking-hint {
font-size: 24rpx;
color: #64748b;
margin-bottom: 18rpx;
line-height: 34rpx;
}
.custom-slot-section {
margin-top: 10rpx;
}
.custom-slot-title {
font-size: 26rpx;
font-weight: 600;
color: #16324f;
margin-bottom: 14rpx;
}
.custom-slot-picker {
height: 82rpx;
border-radius: 22rpx;
background: #f8fbfc;
border-width: 1rpx;
border-style: solid;
border-color: #eef2f6;
align-items: center;
justify-content: center;
box-sizing: border-box;
}
.custom-slot-picker-text {
font-size: 24rpx;
color: #476072;
}
</style>