415 lines
12 KiB
Vue
415 lines
12 KiB
Vue
<template>
|
||
<view class="page">
|
||
<!-- 固定头部 -->
|
||
<view class="header">
|
||
<view class="center" style="width: 50rpx; height: 50rpx; margin-left: 32rpx;">
|
||
<image src="/static/ic_back.png" @click="back" style="width: 48rpx; height: 48rpx;" mode=""></image>
|
||
</view>
|
||
<text style="font-size: 30rpx;">{{ $t('infoEntry.title') }}</text>
|
||
<view style="width: 50rpx;margin-right: 32rpx;"></view>
|
||
</view>
|
||
|
||
<!-- 可滚动内容区域 -->
|
||
<view class="scroll-content">
|
||
<view class="content">
|
||
<!-- 预约登记规则区域 -->
|
||
<view class="booking-rules-box" v-if="bookingRules">
|
||
<view class="rules-title">预约登记规则</view>
|
||
<view class="rules-content">{{ bookingRules }}</view>
|
||
</view>
|
||
<view v-else class=""
|
||
style="width: 680rpx; height: 396rpx; background-image: linear-gradient(-45deg, #60D7FF, #68BBD7); margin-top: 32rpx; border-radius: 20rpx; box-shadow: 0 0 10rpx 10rpx rgba(0, 0, 0, 0.1);">
|
||
</view>
|
||
|
||
<view class="" style="width: 100%; font-size: 40rpx; padding-left: 54rpx; margin-top: 38rpx;">
|
||
{{ $t('infoEntry.personalInfo') }}
|
||
</view>
|
||
|
||
<!-- 真实姓名 -->
|
||
<view class="column" :class="{ 'flash-animation': flashingField === 'userName' }"
|
||
style="width: 680rpx; margin-top: 38rpx;" id="fieldUserName">
|
||
<text style="font-size: 30rpx;"><text
|
||
style="color: #FF0000;">*</text>{{ $t('infoEntry.realName') }}</text>
|
||
<up-input :placeholder="$t('infoEntry.realNamePlaceholder')" border="surround"
|
||
v-model="userName"></up-input>
|
||
</view>
|
||
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
|
||
|
||
<!-- 微信号 -->
|
||
<view class="column" :class="{ 'flash-animation': flashingField === 'contact' }"
|
||
style="width: 680rpx; margin-top: 14rpx;" id="fieldContact">
|
||
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>{{ $t('infoEntry.wechat') }}
|
||
({{ $t('infoEntry.contactMethod') }})</text>
|
||
<up-input :placeholder="$t('infoEntry.wechatPlaceholder')" border="surround"
|
||
v-model="userWechat"></up-input>
|
||
</view>
|
||
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
|
||
|
||
<!-- 手机号 -->
|
||
<view class="column" :class="{ 'flash-animation': flashingField === 'contact' }"
|
||
style="width: 680rpx; margin-top: 14rpx;">
|
||
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>{{ $t('infoEntry.phone') }}
|
||
({{ $t('infoEntry.contactMethod') }})</text>
|
||
<view class="row" style="margin-top: 10rpx; margin-bottom: 10rpx;">
|
||
<aure-country-picker v-model="selectedDialCode" :title="$t('infoEntry.selectCountry')"
|
||
:height="'70%'" :width="'60vw'" :duration="350" :position="'bottom'" :round="true"
|
||
:radius="'24rpx'" :mask-closable="true"></aure-country-picker>
|
||
<up-input :placeholder="$t('infoEntry.phonePlaceholder')" border="surround"
|
||
v-model="userPhone"></up-input>
|
||
</view>
|
||
</view>
|
||
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
|
||
|
||
<!-- whatsApp -->
|
||
<view class="column" :class="{ 'flash-animation': flashingField === 'contact' }"
|
||
style="width: 680rpx; margin-top: 14rpx;">
|
||
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>{{ $t('infoEntry.whatsapp') }}
|
||
({{ $t('infoEntry.contactMethod') }})</text>
|
||
<up-input :placeholder="$t('infoEntry.whatsappPlaceholder')" border="surround"
|
||
v-model="userWhats"></up-input>
|
||
</view>
|
||
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
|
||
|
||
<!-- 备注 -->
|
||
<view class="column" style="width: 680rpx; margin-top: 14rpx;">
|
||
<text style="font-size: 30rpx;">{{ $t('infoEntry.remark') }}</text>
|
||
<up-input :placeholder="$t('infoEntry.remarkPlaceholder')" border="surround"
|
||
v-model="remark"></up-input>
|
||
</view>
|
||
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
|
||
|
||
<!-- 服务预约信息 -->
|
||
<view class="" style="width: 100%; font-size: 40rpx; padding-left: 54rpx; margin-top: 38rpx;">
|
||
{{ $t('infoEntry.serviceInfo') }}
|
||
</view>
|
||
|
||
<!-- 预约日期 -->
|
||
<view class="column" :class="{ 'flash-animation': flashingField === 'reservationDate' }"
|
||
style="width: 680rpx; margin-top: 14rpx;" id="fieldReservationDate">
|
||
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>预约日期</text>
|
||
<view class="date-item" @click="openCalendar">
|
||
<text class="date-text" :class="{ 'date-placeholder': !reservationDate }">
|
||
{{ reservationDate || '请选择预约日期' }}
|
||
</text>
|
||
<image src="/static/arrow_right2.png" style="width: 32rpx; height: 32rpx;" mode="aspectFit">
|
||
</image>
|
||
</view>
|
||
</view>
|
||
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
|
||
|
||
<!-- 服务天数 -->
|
||
<view class="column" :class="{ 'flash-animation': flashingField === 'serviceDays' }"
|
||
style="width: 680rpx; margin-top: 14rpx;" id="fieldServiceDays">
|
||
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>服务天数</text>
|
||
<up-input placeholder="请输入服务天数" border="surround" v-model="serviceDays" type="number"></up-input>
|
||
</view>
|
||
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
|
||
|
||
<view class="center" @click="checkData()"
|
||
style="width: 642rpx; height: 72rpx; background-color: #57C9DD; border-radius: 16rpx; box-shadow: 0 0 10rpx 10rpx rgba(0, 0, 0, 0.1); margin-top: 50rpx; margin-bottom: 100rpx;">
|
||
{{ $t('common.submit') }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<!-- 日历选择器 -->
|
||
<up-calendar :show="showCalendar" mode="single" :minDate="minDate" :maxDate="maxDate"
|
||
@confirm="onCalendarConfirm" @close="closeCalendar" :confirmText="$t('common.confirm')" color="#57C9DD">
|
||
</up-calendar>
|
||
</view>
|
||
</template>
|
||
|
||
|
||
<script>
|
||
import { AppServer } from '@/modules/api/AppServer.js'
|
||
import bookingRulesMixin from '@/mixins/bookingRulesMixin.js'
|
||
const appServer = new AppServer()
|
||
|
||
export default {
|
||
mixins: [bookingRulesMixin],
|
||
data() {
|
||
return {
|
||
serviceId: "",
|
||
hotServiceId: "",
|
||
source: "",
|
||
serviceTitle: "",
|
||
userName: "",
|
||
userWechat: "",
|
||
userPhone: "",
|
||
userWhats: "",
|
||
remark: "",
|
||
reservationDate: "",
|
||
serviceDays: "",
|
||
submitting: false,
|
||
flashingField: '',
|
||
selectedDialCode: '86',
|
||
showCalendar: false,
|
||
minDate: '',
|
||
maxDate: ''
|
||
}
|
||
},
|
||
onLoad(options) {
|
||
this.initDateRange()
|
||
if (options.id) {
|
||
this.source = options.source || ''
|
||
if (this.source === 'hot') {
|
||
this.hotServiceId = options.id
|
||
} else {
|
||
this.serviceId = options.id
|
||
}
|
||
}
|
||
if (options.title) {
|
||
this.serviceTitle = decodeURIComponent(options.title)
|
||
}
|
||
// 加载预约规则
|
||
this.loadBookingRules('guide_translation')
|
||
},
|
||
methods: {
|
||
initDateRange() {
|
||
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')
|
||
this.minDate = `${year}-${month}-${day}`
|
||
this.maxDate = `${year + 2}-12-31`
|
||
},
|
||
openCalendar() {
|
||
this.initDateRange()
|
||
this.$nextTick(() => {
|
||
this.showCalendar = true
|
||
})
|
||
},
|
||
closeCalendar() {
|
||
this.showCalendar = false
|
||
},
|
||
onCalendarConfirm(dates) {
|
||
if (dates && dates.length > 0) {
|
||
this.reservationDate = dates[0]
|
||
}
|
||
this.showCalendar = false
|
||
},
|
||
checkData() {
|
||
const validations = [{
|
||
field: 'userName',
|
||
selector: '#fieldUserName',
|
||
check: () => !this.userName.trim(),
|
||
message: '请输入真实姓名'
|
||
},
|
||
{
|
||
field: 'contact',
|
||
selector: '#fieldContact',
|
||
check: () => !this.userWechat.trim() && !this.userPhone.trim() && !this.userWhats.trim(),
|
||
message: '请至少填写一种联系方式(微信号/手机号/WhatsApp)'
|
||
},
|
||
{
|
||
field: 'reservationDate',
|
||
selector: '#fieldReservationDate',
|
||
check: () => !this.reservationDate,
|
||
message: '请选择预约日期'
|
||
},
|
||
{
|
||
field: 'serviceDays',
|
||
selector: '#fieldServiceDays',
|
||
check: () => !this.serviceDays || parseInt(this.serviceDays) <= 0,
|
||
message: '请输入有效的服务天数'
|
||
}
|
||
]
|
||
|
||
for (const validation of validations) {
|
||
if (validation.check()) {
|
||
uni.showToast({
|
||
title: validation.message,
|
||
icon: 'none'
|
||
})
|
||
this.scrollToElement(validation.selector)
|
||
this.flashingField = validation.field
|
||
setTimeout(() => {
|
||
this.flashingField = ''
|
||
}, 1500)
|
||
return
|
||
}
|
||
}
|
||
|
||
this.submitAppointment()
|
||
},
|
||
async submitAppointment() {
|
||
if (this.submitting) return
|
||
this.submitting = true
|
||
|
||
uni.showLoading({
|
||
title: '提交中...',
|
||
mask: true
|
||
})
|
||
|
||
try {
|
||
const appointmentData = {
|
||
serviceId: this.serviceId || null,
|
||
hotServiceId: this.hotServiceId ? parseInt(this.hotServiceId) : null,
|
||
serviceType: 'guide_translation',
|
||
realName: this.userName.trim(),
|
||
wechatId: this.userWechat.trim() || null,
|
||
phone: this.userPhone.trim() || null,
|
||
phoneCountryCode: this.userPhone.trim() ? this.selectedDialCode : null,
|
||
whatsapp: this.userWhats.trim() || null,
|
||
notes: this.remark.trim() || null,
|
||
appointmentDate: this.reservationDate,
|
||
serviceDays: parseInt(this.serviceDays) || 0,
|
||
}
|
||
|
||
const result = await appServer.CreateAppointment(appointmentData)
|
||
|
||
uni.hideLoading()
|
||
|
||
if (result.success || result.code === 0) {
|
||
uni.showToast({
|
||
title: '预约提交成功',
|
||
icon: 'success'
|
||
})
|
||
setTimeout(() => {
|
||
uni.navigateBack({
|
||
delta: 1
|
||
})
|
||
}, 1500)
|
||
} else {
|
||
uni.showToast({
|
||
title: result.error?.message || '提交失败,请重试',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
} catch (error) {
|
||
uni.hideLoading()
|
||
console.error('提交预约失败:', error)
|
||
uni.showToast({
|
||
title: '网络错误,请重试',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.submitting = false
|
||
}
|
||
},
|
||
scrollToElement(selector) {
|
||
const systemInfo = uni.getSystemInfoSync()
|
||
const screenHeight = systemInfo.windowHeight
|
||
const query = uni.createSelectorQuery().in(this)
|
||
query.select(selector).boundingClientRect()
|
||
query.selectViewport().scrollOffset()
|
||
query.exec((res) => {
|
||
if (res[0] && res[1]) {
|
||
const rect = res[0]
|
||
const scrollInfo = res[1]
|
||
const targetScrollTop = scrollInfo.scrollTop + rect.top - (screenHeight / 2) + (rect.height / 2)
|
||
uni.pageScrollTo({
|
||
scrollTop: Math.max(0, targetScrollTop),
|
||
duration: 300
|
||
})
|
||
}
|
||
})
|
||
},
|
||
back() {
|
||
uni.navigateBack({
|
||
delta: 1
|
||
});
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
|
||
<style lang="scss">
|
||
.page {
|
||
min-height: 100vh;
|
||
background-color: #F3F3F3;
|
||
}
|
||
|
||
.header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
width: 100%;
|
||
padding-top: 88rpx;
|
||
padding-bottom: 20rpx;
|
||
background-color: #F3F3F3;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
z-index: 100;
|
||
}
|
||
|
||
.scroll-content {
|
||
padding-top: 140rpx;
|
||
background-color: #F3F3F3;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
background-color: #F3F3F3;
|
||
min-height: 100%;
|
||
}
|
||
|
||
.date-item {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
width: 100%;
|
||
height: 80rpx;
|
||
padding: 0 10rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.date-text {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.date-placeholder {
|
||
color: #c0c4cc;
|
||
}
|
||
|
||
@keyframes flash {
|
||
0% {
|
||
background-color: #F3F3F3;
|
||
}
|
||
|
||
50% {
|
||
background-color: #ff6666;
|
||
}
|
||
|
||
100% {
|
||
background-color: #F3F3F3;
|
||
}
|
||
}
|
||
|
||
.flash-animation {
|
||
animation: flash 0.5s ease-in-out 3;
|
||
}
|
||
|
||
/* 预约登记规则样式 */
|
||
.booking-rules-box {
|
||
width: 680rpx;
|
||
margin-top: 32rpx;
|
||
padding: 30rpx;
|
||
background-image: linear-gradient(-45deg, #60D7FF, #68BBD7);
|
||
border-radius: 20rpx;
|
||
box-shadow: 0 0 10rpx 10rpx rgba(0, 0, 0, 0.1);
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.rules-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #fff;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.rules-content {
|
||
font-size: 26rpx;
|
||
color: #fff;
|
||
line-height: 1.6;
|
||
white-space: pre-wrap;
|
||
}
|
||
</style>
|