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