appointment_system/miniprogram/src/pages/appointment/medical-consultation-page.vue
2025-12-20 20:29:21 +08:00

420 lines
13 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=""
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 === 'itinerary' }"
style="width: 680rpx; margin-top: 14rpx;" id="fieldItinerary">
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>行程</text>
<up-input placeholder="请输入行程信息(如:北京-上海)" border="surround" v-model="itinerary"></up-input>
</view>
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
<!-- 医院名称 -->
<view class="column" :class="{ 'flash-animation': flashingField === 'hospitalName' }"
style="width: 680rpx; margin-top: 14rpx;" id="fieldHospitalName">
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>医院名称</text>
<up-input placeholder="请输入医院名称" border="surround" v-model="hospitalName"></up-input>
</view>
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
<!-- 病情描述 (textarea) -->
<view class="column" :class="{ 'flash-animation': flashingField === 'conditionDescription' }"
style="width: 680rpx; margin-top: 14rpx;" id="fieldConditionDescription">
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>病情描述</text>
<up-textarea
v-model="conditionDescription"
placeholder="请详细描述您的病情或咨询需求"
:maxlength="1000"
:count="true"
height="200"
border="surround"
></up-textarea>
</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'
const appServer = new AppServer()
export default {
data() {
return {
serviceId: "",
hotServiceId: "",
source: "",
serviceTitle: "",
userName: "",
userWechat: "",
userPhone: "",
userWhats: "",
remark: "",
reservationDate: "",
itinerary: "",
hospitalName: "",
conditionDescription: "",
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)
}
},
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: 'itinerary',
selector: '#fieldItinerary',
check: () => !this.itinerary.trim(),
message: '请输入行程信息'
},
{
field: 'hospitalName',
selector: '#fieldHospitalName',
check: () => !this.hospitalName.trim(),
message: '请输入医院名称'
},
{
field: 'conditionDescription',
selector: '#fieldConditionDescription',
check: () => !this.conditionDescription.trim(),
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: 'medical_consultation',
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,
itinerary: this.itinerary.trim(),
hospitalName: this.hospitalName.trim(),
conditionDescription: this.conditionDescription.trim(),
}
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;
}
</style>