You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

516 lines
13 KiB
Vue

<template>
<view class="visit">
<view class="reception">
<view class="receptionist">
接待人员
</view>
<view class="receptionInfo">
<view class="base">
<text class="name">
名字{{ receptionInfo.name }}
</text>
<text class="department">
部门{{ receptionInfo.department }}
</text>
</view>
<view class="phone">
联系电话{{ receptionInfo.phone }}
</view>
</view>
</view>
<view class="form" v-if="!isAppointmentSuccess" label-position="center">
<uni-forms class="form-box" ref="formRef" :modelValue="formData" :rules="rules" label-width="0">
<!-- 姓名表单项 -->
<uni-forms-item class="form-item" name="name" style="position: relative;">
<text-input>
<template #text>
名字
</template>
<template #input>
<input class="input" type="text" placeholder="请输入名字" v-model="formData.name"
@input="handleInput('name')" placeholder-style="font-size:30rpx" />
</template>
</text-input>
<!-- <template #error="{error}">
<view class="custom-error" v-if="error">{{ error }}</view>
</template> -->
</uni-forms-item>
<!-- 证件类型表单项 -->
<uni-forms-item name="idType">
<text-input>
<template #text>
证件类型
</template>
<template #input>
<picker style="font-style: 30rpx;" @change="onIdTypeChange" :value="formData.idType"
:range="documentTypeArray">
<view class="uni-input" style="font-style: 30rpx;">
{{documentTypeArray[formData.idType]}}
</view>
</picker>
</template>
</text-input>
</uni-forms-item>
<!-- 证件号表单项 -->
<uni-forms-item name="idCard">
<text-input>
<template #text>
证件号
</template>
<template #input>
<input class="input" type="text" placeholder="请输入证件号" v-model="formData.idCard"
@input="handleInput('idCard')" placeholder-style="font-size:30rpx" />
</template>
</text-input>
</uni-forms-item>
<!-- 手机表单项 -->
<uni-forms-item name="phone">
<text-input>
<template #text>
手机号
</template>
<template #input>
<input class="input" type="text" placeholder="请输入手机号" v-model="formData.phone"
@input="handleInput('phone')" placeholder-style="font-size:30rpx" />
</template>
</text-input>
</uni-forms-item>
<!-- 预约时间表单项 -->
<uni-forms-item name="visitTime">
<text-input>
<template #text>
预约时间
</template>
<template #input>
<uni-datetime-picker type="datetime" v-model="formData.visitTime" :start="today" end=""
@change="onDateChange" />
<!-- <picker mode="datetime" :value="formData.visitTime" :start="startDate" :end="endDate" @change="onDateChange">
<view class="uni-input" style="font-style: 30rpx;">{{formData.visitTime}}</view>
</picker> -->
</template>
</text-input>
</uni-forms-item>
<!-- 证件类型表单项 -->
<uni-forms-item name="visitDays">
<text-input>
<template #text>
拜访天数
</template>
<template #input>
<uni-number-box v-model="visitDays" :min="1"></uni-number-box>
</template>
</text-input>
</uni-forms-item>
<!-- Todo:照片认证 -->
<uni-forms-item name="photo" class="photo">
<view class="photo-box">
<button class="chooseImage" @click="chooseImage"></button>
<view class="imageBox">
<image :src="chooseImageUrl" mode="aspectFill"></image>
</view>
</view>
</uni-forms-item>
<!-- 提交按钮 -->
<button type="primary" @click="submitForm" class="submit-btn" :disabled="!isFormFilled">提交预约</button>
</uni-forms>
</view>
<view class="success" v-else>
预约成功
</view>
</view>
</template>
<script setup>
import {
ref,
reactive,
computed
} from 'vue'
import {
pathToBase64
} from 'image-tools';
import {
postForm,
matchFace,
matchIdAnName
} from '../../utils/form.js'
const isAppointmentSuccess = ref(false)
const documentTypeArray = ['身份证']
let pickIdIndex = ref(0)
// 添加加载状态
const isSubmitting = ref(false)
// 选择认证图片的url
const chooseImageUrl = ref('')
// 选择图片的base64数据
const base64Data = ref('');
// 接待人员信息
const receptionInfo = reactive({
name: 'doodyx',
department: '技术服务部',
phone: '18879308852'
})
// 日期选择相关
const visitDays = ref(1)
// 计算今天的日期
const today = new Date().getTime()
const bindPickerChange = (e) => {
pickIdIndex.value = e.detail.value
}
const formData = reactive({
name: '',
idType: 0, // 证件类型索引,默认身份证
idCard: '',
phone: '',
visitTime: new Date().getTime(), // 格式 YYYY-MM-DD
leaveTime: '',
})
// 验证规则
const rules = {
name: {
rules: [{
required: true,
errorMessage: '名字不能为空'
},
{
pattern: /^[\u4e00-\u9fa5a-zA-Z]+$/,
errorMessage: '只能包含中文和字母'
}
]
},
idType: {
rules: [{
required: true,
errorMessage: '请选择证件类型'
}]
},
idCard: {
rules: [{
required: true,
errorMessage: '证件号不能为空'
},
{
pattern: /^[0-9Xx]{18}$/,
errorMessage: '身份证号格式不正确'
}
]
},
phone: {
rules: [{
required: true,
errorMessage: '手机号不能为空'
},
{
pattern: /^1[3-9]\d{9}$/,
errorMessage: '手机号格式不正确'
}
]
},
visitTime: {
rules: [{
required: true,
errorMessage: '请选择预约时间'
}]
},
}
// 表单组件引用
const formRef = ref(null)
// 输入事件(可选,用于手动触发验证)
const handleInput = (field) => {
// uni-forms 会自动验证,无需额外操作
// 如果需要实时清除错误,可以调用 formRef.value.clearError(field)
}
// 证件类型选择事件
const onIdTypeChange = (e) => {
formData.idType = e.detail.value
// 手动触发该字段验证
formRef.value.validateField('idType')
}
// 日期选择事件
const onDateChange = (e) => {
formData.visitTime = e
formRef.value.validateField('visitTime')
}
// 判断表单是否全部已填写
const isFormFilled = computed(() => {
// 名字:非空字符串
if (!formData.name.trim()) return false
// 证件类型:索引必须 >=0默认0是身份证表示已选
if (typeof formData.idType !== 'number' || formData.idType < 0) return false
// 证件号:非空字符串
if (!formData.idCard.trim()) return false
// 手机号:非空字符串
if (!formData.phone.trim()) return false
return true
})
// 计算离开时间(预约日期 + 拜访天数 - 1 天 的 23:59:59
const leaveTime = computed(() => {
// 如果预约时间或天数为空,则返回空字符串
if (!formData.visitTime || !visitDays.value) return '';
// 解析预约时间(可能包含具体时间)
const visitDate = new Date(formData.visitTime);
if (isNaN(visitDate.getTime())) return '';
// 创建新日期对象,基于预约日期的零点
const leaveDate = new Date(visitDate);
leaveDate.setHours(0, 0, 0, 0); // 先设为当日零点
leaveDate.setDate(leaveDate.getDate() + visitDays.value - 1); // 加上 (天数-1) 天
leaveDate.setHours(23, 59, 59, 999); // 设为当天的最后一刻
// 格式化为 "YYYY-MM-DD HH:mm:ss"
const year = leaveDate.getFullYear();
const month = (leaveDate.getMonth() + 1).toString().padStart(2, '0');
const day = leaveDate.getDate().toString().padStart(2, '0');
const hours = leaveDate.getHours().toString().padStart(2, '0');
const minutes = leaveDate.getMinutes().toString().padStart(2, '0');
const seconds = leaveDate.getSeconds().toString().padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
});
// 选择图片
const chooseImage = () => {
uni.chooseImage({
count: 1,
success(res) {
const tempFilePath = res.tempFilePaths[0];
chooseImageUrl.value = tempFilePath
pathToBase64(tempFilePath)
.then(base64 => {
base64Data.value = base64.split(',')[1];
console.log('转换成功Base64长度:', base64.length);
})
.catch(error => {
console.error('转换失败:', error);
uni.showToast({
title: '转换失败',
icon: 'none'
});
});
}
})
}
// 提交表单
const submitForm = () => {
if (isSubmitting.value) return // 防止重复提交
isSubmitting.value = true
formRef.value.validate().then(res => {
// 验证通过
matchIdAnName({
idCardNumber: formData.idCard,
name: formData.name
})
.then((res) => {
console.log('post', res);
if (res.code === 200) {
matchFace({
name: formData.name,
idCardNumber: formData.idCard,
image: base64Data.value,
imageType: 'BASE64'
}).then(res => {
if(res.code === 200){
postForm(formData).then(res => {
if (res.code === 200) {
uni.showToast({
title: '提交成功',
icon: 'success'
})
isAppointmentSuccess.value = true
}
}).catch(err => {
uni.showToast({
title: '提交失败',
icon: 'fail'
})
})
}
})
.catch(err => {
uni.showToast({
title: '人脸验证失败',
icon: 'fail'
})
})
}
})
.catch(err => {
uni.showToast({
title: '名字和身份证不一致',
icon: 'fail'
})
})
})
.catch(error => {
})
}
</script>
<style lang="scss" scoped>
.visit {
// 定义颜色变量(可放在全局或文件顶部)
$card-bg: #ffffff;
$text-primary: #1e293b; // 标题色
$text-secondary: #475569; // 次要文字
$text-regular: #334155; // 普通文字
$border-light: #e9edf4;
$border-dashed: #e2e8f0;
$shadow-color: rgba(0, 0, 0, 0.04);
$shadow-hover: rgba(0, 0, 0, 0.08);
.reception {
background-color: $card-bg;
border-radius: 16px;
padding: 18px 16px;
box-shadow: 0 4px 12px $shadow-color;
margin: 12px 16px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
transition: box-shadow 0.2s ease;
&:active {
box-shadow: 0 6px 16px $shadow-hover;
}
// 标题:接待人员
.receptionist {
font-size: 18px;
font-weight: 600;
color: $text-primary;
letter-spacing: 0.3px;
margin-bottom: 14px;
padding-bottom: 8px;
border-bottom: 1.5px solid $border-light;
}
// 信息区域
.receptionInfo {
display: flex;
flex-direction: column;
gap: 12px;
// 名字和部门的组合行
.base {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 16px;
.name {
font-size: 15px;
font-weight: 500;
color: $text-primary;
}
.department {
font-size: 15px;
color: $text-secondary;
}
}
// 联系电话行
.phone {
font-size: 15px;
color: $text-regular;
display: flex;
align-items: center;
gap: 6px;
margin-top: 4px;
padding-top: 8px;
border-top: 1px dashed $border-dashed;
}
}
}
.form {
.submit-btn {
width: 90%;
}
.form-box {
.photo {
padding: 0 25px;
.photo-box {
display: flex;
justify-content: start;
align-items: center;
.chooseImage {
margin: 0;
height: 80rpx;
font-size: 30rpx;
color: #fff;
background-color: #007aff;
}
.imageBox {
width: 200rpx;
aspect-ratio: 1;
margin-left: 100rpx;
image {
width: 100%;
height: 100%;
}
}
}
}
.form-item {
.uni-forms-item__error {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: auto;
/* 取消默认的100%宽度 */
background: none;
/* 去掉背景 */
text-align: right;
}
}
}
}
}
/* 覆盖默认错误样式 */
::v-deep.uni-forms-item__error {
position: absolute;
top: 100%;
left: 26%;
transform: translateY(-50%);
width: auto;
/* 取消默认的100%宽度 */
background: none;
/* 去掉背景 */
text-align: right;
}
</style>