Files
property-resident-side/property-uniapp-project/pageSubPack/visitor/visitor-add.vue
2026-04-28 08:50:00 +08:00

526 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="visitor-invite">
<!-- 标签切换车辆来访/人员来访 -->
<view class="tab-wrap">
<view
class="tab-item"
:class="{ active: activeTab === 'car' }"
@click="switchTab('car')"
>
车辆来访
</view>
<view
class="tab-item"
:class="{ active: activeTab === 'person' }"
@click="switchTab('person')"
>
人员来访
</view>
</view>
<!-- 表单区域 -->
<view class="form-wrap">
<!-- 房屋信息 -->
<view class="section-title">房屋信息</view>
<view class="form-section">
<view class="form-item">
<view class="label">
<text class="required">*</text>来访地址
</view>
<view class="value" @click="openAddressModal">
{{ form.address }}
<uni-icons type="right" size="16" color="#999"></uni-icons>
</view>
</view>
</view>
<!-- 访客信息 -->
<view class="section-title">访客信息</view>
<view class="form-section">
<view class="form-item">
<view class="label">访客姓名</view>
<input
class="input"
placeholder="请输入访客姓名"
v-model="form.name"
/>
</view>
<view class="form-item">
<view class="label">
<text class="required">*</text>访客性别
</view>
<view class="value" @click="openGenderModal">
{{ form.gender }}
<uni-icons type="right" size="16" color="#999"></uni-icons>
</view>
</view>
<view class="form-item">
<view class="label">
<text class="required">*</text>访客联系方式
</view>
<input
class="input"
placeholder="请输入联系方式"
v-model="form.phone"
type="number"
/>
</view>
<!-- 车牌号仅车辆来访显示 -->
<view class="form-item" v-if="activeTab === 'car'">
<view class="label">
<text class="required">*</text>车牌号
</view>
<view class="car-number-wrap">
<input
class="car-input"
v-for="(item, index) in 8"
:key="index"
v-model="form.carNumber[index]"
maxlength="1"
type="text"
/>
</view>
</view>
<view class="form-item">
<view class="label">
<text class="required">*</text>随行人数
</view>
<input
class="input"
placeholder="请输入随行人数"
v-model="form.peopleNum"
type="number"
/>
</view>
</view>
<!-- 有效期 -->
<view class="section-title">有效期</view>
<view class="form-section">
<view class="form-item">
<view class="label">
<text class="required">*</text>来访时间
</view>
<view class="value">
<uni-datetime-picker type="datetime" v-model="form.visitTime" @change="handleTimeChange('visit')" :border="false"/>
</view>
</view>
<view class="form-item">
<view class="label">
<text class="required">*</text>离开时间
</view>
<view class="value">
<uni-datetime-picker type="datetime" v-model="form.leaveTime" @change="handleTimeChange('leave')" :border="false"/>
</view>
</view>
<view class="tips">预计来访时间和预计离开时间间隔不能超过24小时</view>
<view class="tips-2">车辆停留请勿超出有效期需及时驶出小区</view>
</view>
</view>
<!-- 底部按钮 -->
<view class="btn-wrap">
<button
class="submit-btn"
@click="submitForm"
>
{{ activeTab === 'car' ? '确认邀请' : '生成通行二维码' }}
</button>
</view>
<!-- 地址选择弹窗 -->
<uni-popup ref="addressModalRef" type="bottom" :mask-click="false">
<view class="modal-wrap">
<view class="modal-title">选择来访地址</view>
<view class="address-list">
<view
class="address-item"
:class="{ active: item === form.address }"
v-for="item in addressList"
:key="item"
@click="selectAddress(item)"
>
{{ item }}
</view>
</view>
<uni-icons
type="closeempty"
size="20"
class="modal-close"
@click="closeAddressModal"
></uni-icons>
</view>
</uni-popup>
<!-- 性别选择弹窗 -->
<uni-popup ref="genderModalRef" type="bottom" :mask-click="false">
<view class="modal-wrap">
<view class="gender-list">
<view
class="gender-item"
:class="{ active: item === form.gender }"
v-for="item in genderList"
:key="item"
@click="selectGender(item)"
>
{{ item }}
</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { ref, reactive} from 'vue';
import{onLoad } from "@dcloudio/uni-app"
import moment from 'moment';
// 标签激活状态
const activeTab = ref('car');
// 表单数据(完全保留原有默认值)
const form = reactive({
address: '',
name: '',
gender: '',
phone: '',
carNumber: ['', '', '', '', '', '', ''],
peopleNum: '',
visitTime: '',
leaveTime: ''
});
// 地址列表(原数据不变)
const addressList = ref([
'桂花园别墅1号楼1层',
'桂花园别墅2号楼2层'
]);
// 性别列表
const genderList = ref(['男', '女']);
// 弹窗实例
const addressModalRef = ref(null);
const genderModalRef = ref(null);
// 页面初始化
onLoad(() => {
// 用moment设置默认时间格式与原逻辑一致
// const now = moment();
// form.visitTime = now.format('YYYY-MM-DD HH:mm');
// 默认离开时间为1小时后
// form.leaveTime = now.clone().add(1, 'hours').format('YYYY-MM-DD HH:mm');
});
// 切换tab
const switchTab = (tab) => {
activeTab.value = tab;
resetForm();
};
// 打开地址弹窗
const openAddressModal = () => {
addressModalRef.value.open();
};
// 关闭地址弹窗
const closeAddressModal = () => {
addressModalRef.value.close();
};
// 选择地址
const selectAddress = (item) => {
form.address = item;
closeAddressModal();
};
// 打开性别弹窗
const openGenderModal = () => {
genderModalRef.value.open();
};
// 选择性别
const selectGender = (item) => {
form.gender = item;
genderModalRef.value.close();
};
// 用moment处理时间
const handleTimeChange = (type) => {
if (!form.visitTime || !form.leaveTime) return;
const visitMoment = moment(form.visitTime);
const leaveMoment = moment(form.leaveTime);
// 校验1离开时间不能早于来访时间
if (leaveMoment.isBefore(visitMoment)) {
uni.showToast({
title: '离开时间不能早于来访时间',
icon: 'none'
});
form.leaveTime = visitMoment.clone().add(1, 'hours').format('YYYY-MM-DD HH:mm');
return;
}
// 校验2间隔不超过24小时
const diffHours = leaveMoment.diff(visitMoment, 'hours', true);
if (diffHours > 24) {
uni.showToast({
title: '时间间隔不能超过24小时',
icon: 'none'
});
form.leaveTime = visitMoment.clone().add(24, 'hours').format('YYYY-MM-DD HH:mm');
}
};
// 提交前时间校验
const checkTimeInterval = () => {
if (!form.visitTime) {
uni.showToast({ title: '请选择来访时间', icon: 'none' });
return false;
}
if (!form.leaveTime) {
uni.showToast({ title: '请选择离开时间', icon: 'none' });
return false;
}
const visitMoment = moment(form.visitTime);
const leaveMoment = moment(form.leaveTime);
if (!visitMoment.isValid() || !leaveMoment.isValid()) {
uni.showToast({ title: '时间格式错误,请重新选择', icon: 'none' });
return false;
}
if (leaveMoment.isBefore(visitMoment)) {
uni.showToast({ title: '离开时间不能早于来访时间', icon: 'none' });
return false;
}
const diffHours = leaveMoment.diff(visitMoment, 'hours', true);
if (diffHours > 24) {
uni.showToast({ title: '时间间隔不能超过24小时', icon: 'none' });
return false;
}
return true;
};
// 表单提交(仅替换时间校验逻辑,其余完全不变)
const submitForm = () => {
if (!form.phone) {
uni.showToast({ title: '请输入访客联系方式', icon: 'none' });
return;
}
if (!/^1[3-9]\d{9}$/.test(form.phone)) {
uni.showToast({ title: '请输入正确的手机号', icon: 'none' });
return;
}
if (activeTab === 'car' && form.carNumber.join('').length < 7) {
uni.showToast({ title: '请填写完整车牌号', icon: 'none' });
return;
}
if (!form.peopleNum || form.peopleNum < 0) {
uni.showToast({ title: '请输入正确的随行人数', icon: 'none' });
return;
}
if (!form.leaveTime) {
uni.showToast({ title: '请选择离开时间', icon: 'none' });
return;
}
// 时间校验
if (!checkTimeInterval()) return;
uni.showLoading({ title: '提交中...' });
setTimeout(() => {
uni.hideLoading();
uni.showToast({
title: activeTab === 'car' ? '邀请成功' : '二维码生成成功',
icon: 'success'
});
}, 1000);
};
// 重置表单
const resetForm = () => {
form.address = '';
form.name = '';
form.gender = '';
form.phone = '';
form.carNumber = ['', '', '', '', '', '', ''];
form.peopleNum = '';
form.visitTime = '';
form.leaveTime = '';
};
</script>
<style scoped>
.visitor-invite {
background-color: #f5f5f5;
min-height: 100vh;
}
/* 导航栏 */
.nav-bar {
display: flex;
align-items: center;
padding: 20rpx;
background-color: #fff;
}
.nav-title {
font-size: 36rpx;
font-weight: 500;
margin-left: 20rpx;
}
/* 标签切换 */
.tab-wrap {
display: flex;
background-color: #fff;
border-bottom: 1rpx solid #eee;
}
.tab-item {
/* flex: 1; */
display: flex;
padding: 30rpx;
text-align: center;
font-size: 32rpx;
color: #333;
}
.tab-item.active {
color: #007aff;
/* border-bottom: 4rpx solid #007aff; */
}
/* 表单区域 */
.form-wrap {
padding: 10rpx;
}
.form-section {
background-color: #fff;
margin-bottom: 10rpx;
padding: 20rpx 40rpx;
}
.section-title {
font-size: 28rpx;
font-weight: 500;
/* margin-bottom: 20rpx; */
margin: 30rpx 40rpx 10rpx;
}
.form-item {
display: flex;
align-items: center;
padding: 30rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.form-item:last-child {
border-bottom: none;
}
.label {
width: 205rpx;
font-size: 32rpx;
color: #333;
}
.required {
color: red;
margin-right: 4rpx;
}
.value {
flex: 1;
font-size: 30rpx;
color: #666;
display: flex;
justify-content: flex-end;
align-items: center;
}
.input {
flex: 1;
font-size: 30rpx;
color: #666;
text-align: right;
}
/* 车牌号输入 */
.car-number-wrap {
flex: 1;
display: flex;
justify-content: space-between;
}
.car-input {
width: 52rpx;
height: 60rpx;
border: 1rpx solid #eee;
text-align: center;
font-size: 30rpx;
}
/* 提示文字 */
.tips {
font-size: 24rpx;
color: #999;
margin-top: 44rpx;
text-align: center;
}
.tips-2{
font-size: 24rpx;
color: #999;
margin: 20rpx;
text-align: center;
}
/* 底部按钮 */
.btn-wrap {
padding: 20rpx 40rpx 50rpx;
}
.submit-btn {
background-color: #007aff;
color: #fff;
font-size: 32rpx;
height: 80rpx;
line-height: 80rpx;
border-radius: 8rpx;
}
/* 弹窗样式 */
.modal-wrap {
background-color: #fff;
padding: 30rpx;
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
position: relative;
}
.modal-title {
font-size: 32rpx;
text-align: center;
margin-bottom: 30rpx;
}
.modal-close {
position: absolute;
top: 20rpx;
right: 20rpx;
color: #999;
}
.address-list, .gender-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.address-item, .gender-item {
padding: 20rpx;
background-color: #f5f5f5;
border-radius: 8rpx;
text-align: center;
font-size: 30rpx;
}
.address-item.active, .gender-item.active {
background-color: #007aff;
color: #fff;
}
</style>