Files
ShaFaFanXin/前端/pages/booking/index.uvue

612 lines
13 KiB
Plaintext
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.

<template>
<view class="page">
<scroll-view class="page-scroll" scroll-y>
<!-- 表单头部 -->
<view class="header-section">
<text class="header-title">预约翻新服务</text>
<text class="header-desc">填写以下信息,我们会尽快与您联系</text>
</view>
<!-- 表单内容 -->
<view class="form-section">
<!-- 姓名 -->
<view class="form-item">
<text class="form-label">
<text class="required">*</text>
您的姓名
</text>
<input
class="form-input"
type="text"
v-model="formData.userName"
placeholder="请输入您的姓名"
placeholder-class="placeholder"
/>
</view>
<!-- 电话 -->
<view class="form-item">
<text class="form-label">
<text class="required">*</text>
联系电话
</text>
<input
class="form-input"
type="number"
v-model="formData.phone"
placeholder="请输入您的手机号"
placeholder-class="placeholder"
maxlength="11"
/>
</view>
<!-- 地址 -->
<view class="form-item">
<text class="form-label">
<text class="required">*</text>
您的地址
</text>
<input
class="form-input"
type="text"
v-model="formData.address"
placeholder="请输入详细地址"
placeholder-class="placeholder"
/>
</view>
<!-- 服务类型 -->
<view class="form-item">
<text class="form-label">
<text class="required">*</text>
服务类型
</text>
<view class="type-grid">
<view
class="type-item"
:class="{ 'type-active': formData.serviceId == item.id }"
v-for="item in serviceList"
:key="item.id"
@click="selectService(item.id)"
>
<text
class="type-text"
:class="{ 'type-text-active': formData.serviceId == item.id }"
>{{ item.name }}</text>
</view>
</view>
</view>
<!-- 预约时间 -->
<view class="form-item">
<text class="form-label">
<text class="required">*</text>
预约时间
</text>
<picker
mode="date"
:value="formData.appointmentDate"
:start="minDate"
@change="onDateChange"
>
<view class="picker-value">
<text class="picker-text" v-if="formData.appointmentDate">{{ formData.appointmentDate }}</text>
<text class="picker-placeholder" v-else>请选择预约日期</text>
</view>
</picker>
</view>
<!-- 问题描述 -->
<view class="form-item">
<text class="form-label">问题描述</text>
<textarea
class="form-textarea"
v-model="formData.problem"
placeholder="请描述沙发的问题(如:皮面开裂、褪色、塌陷等)"
placeholder-class="placeholder"
maxlength="500"
></textarea>
<text class="textarea-count">{{ formData.problem.length }}/500</text>
</view>
<!-- 上传图片 -->
<view class="form-item">
<text class="form-label">上传图片(可选)</text>
<view class="upload-grid">
<view
class="upload-item"
v-for="(item, index) in imageList"
:key="index"
>
<image class="upload-image" :src="item" mode="aspectFill"></image>
<view class="upload-delete" @click="deleteImage(index)">
<text class="delete-icon">×</text>
</view>
</view>
<view class="upload-add" v-if="imageList.length < 9" @click="chooseImage">
<text class="add-icon">+</text>
<text class="add-text">添加图片</text>
</view>
</view>
<text class="upload-tip">提示:当前仅支持预览,图片暂不会上传到服务器</text>
</view>
</view>
<!-- 底部间距 -->
<view class="bottom-space"></view>
</scroll-view>
<!-- 提交按钮 -->
<view class="submit-bar">
<view class="submit-btn" @click="handleSubmit">
<text class="submit-text">提交预约</text>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { submitBooking, getActiveServices } from '@/api/index.uts'
// 表单类型
type FormData = {
userName : string
phone : string
address : string
serviceId : number
appointmentDate : string
problem : string
}
// 服务类型
type ServiceItem = {
id : number
name : string
type : string
description : string
price : number
}
// 表单数据
const formData = ref<FormData>({
userName: '',
phone: '',
address: '',
serviceId: 0,
appointmentDate: '',
problem: ''
})
// 图片列表
const imageList = ref<string[]>([])
// 服务列表
const serviceList = ref<ServiceItem[]>([])
// 提交中
const submitting = ref(false)
// 最小日期(今天)
const minDate = ref('')
// 加载服务列表
const loadServices = async () => {
try {
const res = await getActiveServices()
console.log('服务列表响应:', res)
if (res.code == 0 && res.data != null) {
const data = res.data as UTSJSONObject
// 兼容两种格式:直接数组 或 {list: [], total: n}
let list : UTSJSONObject[] = []
if (Array.isArray(data)) {
list = data as UTSJSONObject[]
} else {
list = data['list'] as UTSJSONObject[] || []
}
console.log('解析的服务列表:', list)
serviceList.value = list.map((item) : ServiceItem => {
const basePrice = item['basePrice'] as string || '0'
return {
id: item['id'] as number,
name: item['name'] as string,
type: item['type'] as string,
description: item['description'] as string,
price: parseFloat(basePrice)
} as ServiceItem
})
console.log('最终服务列表:', serviceList.value)
} else {
console.error('服务列表响应异常code:', res.code, 'data:', res.data)
}
} catch (e) {
console.error('加载服务列表失败', e)
}
}
// 初始化最小日期和默认日期(明天)
const initMinDate = () => {
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
minDate.value = `${year}-${month}-${day}`
// 设置默认预约时间为明天
const tomorrow = new Date()
tomorrow.setDate(tomorrow.getDate() + 1)
const tYear = tomorrow.getFullYear()
const tMonth = String(tomorrow.getMonth() + 1).padStart(2, '0')
const tDay = String(tomorrow.getDate()).padStart(2, '0')
formData.value.appointmentDate = `${tYear}-${tMonth}-${tDay}`
}
// 选择服务
const selectService = (id : number) => {
formData.value.serviceId = id
}
// 日期选择
const onDateChange = (e : any) => {
formData.value.appointmentDate = e.detail.value
}
// 选择图片
const chooseImage = () => {
uni.chooseImage({
count: 9 - imageList.value.length,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
imageList.value = [...imageList.value, ...res.tempFilePaths]
}
})
}
// 删除图片
const deleteImage = (index : number) => {
imageList.value.splice(index, 1)
}
// 验证表单
const validateForm = () : boolean => {
if (formData.value.userName.trim() == '') {
uni.showToast({
title: '请输入您的姓名',
icon: 'none'
})
return false
}
const phone = formData.value.phone.trim()
if (phone == '') {
uni.showToast({
title: '请输入联系电话',
icon: 'none'
})
return false
}
// 简单验证手机号
if (phone.length != 11) {
uni.showToast({
title: '请输入正确的手机号',
icon: 'none'
})
return false
}
if (formData.value.address.trim() == '') {
uni.showToast({
title: '请输入您的地址',
icon: 'none'
})
return false
}
if (formData.value.serviceId == 0) {
uni.showToast({
title: '请选择服务类型',
icon: 'none'
})
return false
}
if (formData.value.appointmentDate == '') {
uni.showToast({
title: '请选择预约时间',
icon: 'none'
})
return false
}
return true
}
// 提交预约
const handleSubmit = async () => {
if (!validateForm()) return
if (submitting.value) return
submitting.value = true
try {
// 注意当前暂不支持图片上传如需上传图片请先将图片上传到图床获取URL
// 过滤掉微信临时路径后端需要永久URL
const validImages = imageList.value.filter((url) => {
return url.startsWith('http://') || url.startsWith('https://')
})
if (imageList.value.length > 0 && validImages.length === 0) {
console.log('警告:图片为微信临时路径,暂不支持上传')
}
// 构造后端需要的数据格式
const data = {
serviceId: formData.value.serviceId,
contactName: formData.value.userName,
contactPhone: formData.value.phone,
address: formData.value.address,
appointmentTime: formData.value.appointmentDate + 'T10:00:00.000Z', // 默认上午10点
requirements: formData.value.problem,
images: validImages // 只提交有效的URL
} as UTSJSONObject
console.log('提交预约数据:', data)
const res = await submitBooking(data)
console.log('预约提交结果:', res)
// request.uts 会自动处理失败情况并显示 toast这里只处理成功
uni.showModal({
title: '预约成功',
content: '我们会尽快与您联系,请保持电话畅通',
showCancel: false,
success: () => {
// 返回上一页或首页
uni.navigateBack({
fail: () => {
uni.switchTab({
url: '/pages/index/index'
})
}
})
}
})
} catch (e) {
console.error('提交预约异常:', e)
// request.uts 已经显示了错误 toast这里只需要记录日志
}
submitting.value = false
}
onLoad(() => {
initMinDate()
loadServices()
})
</script>
<style lang="scss">
.page {
flex: 1;
background-color: #f5f5f5;
}
.page-scroll {
flex: 1;
}
/* 头部 */
.header-section {
background: linear-gradient(135deg, #D4A574 0%, #B8895A 100%);
padding: 48rpx 32rpx;
align-items: center;
}
.header-title {
font-size: 40rpx;
font-weight: 600;
color: #ffffff;
margin-bottom: 12rpx;
}
.header-desc {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
}
/* 表单区域 */
.form-section {
background-color: #ffffff;
margin: 24rpx;
border-radius: 16rpx;
padding: 32rpx;
}
.form-item {
margin-bottom: 32rpx;
}
.form-label {
font-size: 28rpx;
font-weight: 600;
color: #333333;
margin-bottom: 16rpx;
}
.required {
color: #F56C6C;
margin-right: 4rpx;
}
.form-input {
background-color: #f5f5f5;
border-radius: 12rpx;
padding: 24rpx;
font-size: 28rpx;
height:40px;
}
.placeholder {
color: #C0C4CC;
}
/* 日期选择器 */
.picker-value {
background-color: #f5f5f5;
border-radius: 12rpx;
padding: 24rpx;
height: 40px;
justify-content: center;
}
.picker-text {
font-size: 28rpx;
color: #303133;
}
.picker-placeholder {
font-size: 28rpx;
color: #C0C4CC;
}
/* 沙发类型选择 */
.type-grid {
flex-direction: row;
flex-wrap: wrap;
}
.type-item {
padding: 16rpx 32rpx;
background-color: #f5f5f5;
border-radius: 8rpx;
margin-right: 16rpx;
margin-bottom: 16rpx;
}
.type-active {
background-color: #D4A574;
}
.type-text {
font-size: 26rpx;
color: #606266;
}
.type-text-active {
color: #ffffff;
}
/* 文本域 */
.form-textarea {
background-color: #f5f5f5;
border-radius: 12rpx;
padding: 24rpx;
font-size: 28rpx;
height: 200rpx;
width: 100%;
}
.textarea-count {
font-size: 24rpx;
color: #909399;
text-align: right;
margin-top: 8rpx;
}
/* 图片上传 */
.upload-grid {
flex-direction: row;
flex-wrap: wrap;
}
.upload-item {
width: 200rpx;
height: 200rpx;
margin-right: 16rpx;
margin-bottom: 16rpx;
position: relative;
}
.upload-image {
width: 100%;
height: 100%;
border-radius: 12rpx;
}
.upload-delete {
position: absolute;
top: -16rpx;
right: -16rpx;
width: 40rpx;
height: 40rpx;
background-color: #F56C6C;
border-radius: 50%;
align-items: center;
justify-content: center;
}
.delete-icon {
font-size: 28rpx;
color: #ffffff;
}
.upload-add {
width: 200rpx;
height: 200rpx;
background-color: #f5f5f5;
border-radius: 12rpx;
align-items: center;
justify-content: center;
border-width: 2rpx;
border-style: dashed;
border-color: #DCDFE6;
}
.add-icon {
font-size: 48rpx;
color: #909399;
}
.add-text {
font-size: 24rpx;
color: #909399;
margin-top: 8rpx;
}
.upload-tip {
font-size: 24rpx;
color: #909399;
margin-top: 16rpx;
}
/* 底部间距 */
.bottom-space {
height: 160rpx;
}
/* 提交按钮 */
.submit-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #ffffff;
padding: 24rpx 32rpx;
padding-bottom: 48rpx;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.08);
}
.submit-btn {
background-color: #D4A574;
border-radius: 999rpx;
padding: 28rpx 0;
align-items: center;
}
.submit-text {
font-size: 32rpx;
font-weight: 600;
color: #ffffff;
}
</style>