appointment_system/miniprogram/src/pages/appointment/guide-translation-page.vue
2025-12-21 00:32:03 +08:00

415 lines
12 KiB
Vue
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">
<!-- 固定头部 -->
<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>