<script setup lang="ts">
|
import { createVisitor, fetchVisitorDetail, updateVisitor } from '@/api/visitor'
|
import { fetchMiniFieldConfigs } from '@/api/lookup'
|
import { FIELD_CODES } from '@/constants/fieldCodes'
|
import type { MiniFieldConfigItem } from '@/types/lookup'
|
import type { MiniVisitorSaveRequest } from '@/types/visitor'
|
|
definePage({
|
name: 'addPerson',
|
layout: 'default',
|
style: {
|
navigationStyle: 'custom',
|
},
|
})
|
|
const router = useRouter()
|
|
const sysInfo = uni.getSystemInfoSync()
|
const statusBarHeight = sysInfo.statusBarHeight || 0
|
const navBarHeight = 44
|
|
const pageMode = ref<'add' | 'edit'>('add')
|
const visitorId = ref<number>()
|
const visitorUuid = ref('')
|
const loading = ref(false)
|
const saving = ref(false)
|
const fieldConfigs = ref<MiniFieldConfigItem[]>([])
|
|
const formData = reactive({
|
name: '',
|
phone: '',
|
company: '',
|
idCard: '',
|
gender: '男' as '男' | '女',
|
})
|
|
const errors = reactive({
|
name: '',
|
phone: '',
|
company: '',
|
idCard: '',
|
})
|
|
const pageTitle = computed(() => pageMode.value === 'edit' ? '编辑来访人' : '新增来访人')
|
|
function findConfig(code: string) {
|
return fieldConfigs.value.find(item => item.fieldCode === code)
|
}
|
|
function isVisible(code: string) {
|
const config = findConfig(code)
|
return config ? config.visible === 1 : true
|
}
|
|
function isRequired(code: string) {
|
const config = findConfig(code)
|
return config ? config.requiredFlag === 1 : false
|
}
|
|
function resetErrors() {
|
errors.name = ''
|
errors.phone = ''
|
errors.company = ''
|
errors.idCard = ''
|
}
|
|
function validate() {
|
let isValid = true
|
resetErrors()
|
|
if (isVisible(FIELD_CODES.VISITOR_NAME) && isRequired(FIELD_CODES.VISITOR_NAME) && !formData.name.trim()) {
|
errors.name = '请输入来访人姓名'
|
isValid = false
|
}
|
else if (formData.name && formData.name.trim().length > 30) {
|
errors.name = '来访人姓名不能超过30个字'
|
isValid = false
|
}
|
|
if (isVisible(FIELD_CODES.MOBILE) && isRequired(FIELD_CODES.MOBILE) && !formData.phone.trim()) {
|
errors.phone = '请输入来访人电话'
|
isValid = false
|
}
|
else if (formData.phone && !/^\d{11}$/.test(formData.phone.trim())) {
|
errors.phone = '请确保电话号码录入正确!'
|
isValid = false
|
}
|
|
if (isVisible(FIELD_CODES.COMPANY) && isRequired(FIELD_CODES.COMPANY) && !formData.company.trim()) {
|
errors.company = '请输入来访人单位'
|
isValid = false
|
}
|
|
if (isVisible(FIELD_CODES.ID_CARD) && isRequired(FIELD_CODES.ID_CARD) && !formData.idCard.trim()) {
|
errors.idCard = '请输入身份证号码'
|
isValid = false
|
}
|
else if (formData.idCard && !/(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(formData.idCard.trim())) {
|
errors.idCard = '请确保身份证号码录入正确!'
|
isValid = false
|
}
|
|
return isValid
|
}
|
|
function buildPayload(): MiniVisitorSaveRequest {
|
return {
|
visitorName: formData.name.trim(),
|
visitorMobile: formData.phone.trim(),
|
visitorCompany: formData.company.trim(),
|
idCard: formData.idCard.trim(),
|
gender: formData.gender,
|
}
|
}
|
|
async function loadFieldConfigs() {
|
fieldConfigs.value = await fetchMiniFieldConfigs()
|
}
|
|
async function loadDetail() {
|
if (pageMode.value !== 'edit' || !visitorId.value || !visitorUuid.value) {
|
return
|
}
|
const detail = await fetchVisitorDetail(visitorId.value, visitorUuid.value)
|
formData.name = detail.visitorName || ''
|
formData.phone = detail.visitorMobile || ''
|
formData.company = detail.visitorCompany || ''
|
formData.idCard = detail.idCard || ''
|
formData.gender = detail.gender === '女' ? '女' : '男'
|
}
|
|
async function initPage() {
|
loading.value = true
|
try {
|
await loadFieldConfigs()
|
await loadDetail()
|
}
|
catch (error) {
|
uni.showToast({
|
title: error instanceof Error ? error.message : '加载来访人信息失败',
|
icon: 'none',
|
})
|
}
|
finally {
|
loading.value = false
|
}
|
}
|
|
async function handleSave() {
|
if (saving.value || !validate()) {
|
return
|
}
|
saving.value = true
|
try {
|
if (pageMode.value === 'edit' && visitorId.value && visitorUuid.value) {
|
await updateVisitor(visitorId.value, visitorUuid.value, buildPayload())
|
}
|
else {
|
await createVisitor(buildPayload())
|
}
|
uni.showToast({ title: '保存成功', icon: 'success' })
|
setTimeout(() => {
|
router.back()
|
}, 300)
|
}
|
catch (error) {
|
uni.showToast({
|
title: error instanceof Error ? error.message : '保存来访人失败',
|
icon: 'none',
|
})
|
}
|
finally {
|
saving.value = false
|
}
|
}
|
|
onLoad((options) => {
|
visitorId.value = options?.id ? Number(options.id) : undefined
|
visitorUuid.value = options?.uuid || ''
|
pageMode.value = visitorId.value && visitorUuid.value ? 'edit' : 'add'
|
void initPage()
|
})
|
</script>
|
|
<template>
|
<view class="min-h-screen bg-[#F6FBFF] box-border relative pb-[200rpx]">
|
<view class="relative z-50 w-full bg-[#F6FBFF]" :style="{ height: (statusBarHeight + navBarHeight) + 'px' }">
|
<view class="flex items-center justify-between w-full absolute left-0" :style="{ top: statusBarHeight + 'px', height: navBarHeight + 'px' }">
|
<view class="flex h-full w-[88rpx] items-center justify-center relative z-10" @click="router.back()">
|
<wd-icon name="arrow-left" size="40rpx" color="#333333" />
|
</view>
|
<view class="absolute left-0 w-full text-center text-[34rpx] font-medium text-[#333333] z-0">
|
{{ pageTitle }}
|
</view>
|
<view class="w-[88rpx]" />
|
</view>
|
</view>
|
|
<view v-if="loading" class="px-[64rpx] py-[80rpx] text-center text-[28rpx] text-[#999999]">
|
加载中...
|
</view>
|
|
<view v-else class="bg-white px-[64rpx] py-[48rpx]">
|
<view v-if="isVisible(FIELD_CODES.VISITOR_NAME)" class="py-[28rpx] flex items-start justify-between border-b1">
|
<view class="flex flex-col shrink-0 mr-[32rpx]">
|
<view class="flex items-center h-[40rpx]">
|
<text class="text-[28rpx] text-[#000000]">来访人姓名</text>
|
<text v-if="isRequired(FIELD_CODES.VISITOR_NAME)" class="text-[28rpx] text-[#F5222D] ml-[4rpx]">*</text>
|
</view>
|
<text v-if="errors.name" class="text-[20rpx] text-[#F5222D] mt-[8rpx]">{{ errors.name }}</text>
|
</view>
|
<input v-model="formData.name" placeholder="请输入来访人姓名" placeholder-class="text-[#B3B3B3] text-[28rpx]" class="text-[28rpx] text-right text-[#333333] flex-1 h-[40rpx] min-h-[40rpx]" />
|
</view>
|
|
<view v-if="isVisible(FIELD_CODES.MOBILE)" class="py-[28rpx] flex items-start justify-between border-b1">
|
<view class="flex flex-col shrink-0 mr-[32rpx]">
|
<view class="flex items-center h-[40rpx]">
|
<text class="text-[28rpx] text-[#000000]">来访人电话</text>
|
<text v-if="isRequired(FIELD_CODES.MOBILE)" class="text-[28rpx] text-[#F5222D] ml-[4rpx]">*</text>
|
</view>
|
<text v-if="errors.phone" class="text-[20rpx] text-[#F5222D] mt-[8rpx]">{{ errors.phone }}</text>
|
</view>
|
<input v-model="formData.phone" type="number" maxlength="11" placeholder="请输入来访人电话" placeholder-class="text-[#B3B3B3] text-[28rpx]" class="text-[28rpx] text-right text-[#333333] flex-1 h-[40rpx] min-h-[40rpx]" />
|
</view>
|
|
<view v-if="isVisible(FIELD_CODES.COMPANY)" class="py-[28rpx] flex items-start justify-between border-b1">
|
<view class="flex flex-col shrink-0 mr-[32rpx]">
|
<view class="flex items-center h-[40rpx]">
|
<text class="text-[28rpx] text-[#000000]">来访人单位</text>
|
<text v-if="isRequired(FIELD_CODES.COMPANY)" class="text-[28rpx] text-[#F5222D] ml-[4rpx]">*</text>
|
</view>
|
<text v-if="errors.company" class="text-[20rpx] text-[#F5222D] mt-[8rpx]">{{ errors.company }}</text>
|
</view>
|
<input v-model="formData.company" placeholder="请输入来访人单位" placeholder-class="text-[#B3B3B3] text-[28rpx]" class="text-[28rpx] text-right text-[#333333] flex-1 h-[40rpx] min-h-[40rpx]" />
|
</view>
|
|
<view v-if="isVisible(FIELD_CODES.ID_CARD)" class="py-[28rpx] flex items-start justify-between border-b1">
|
<view class="flex flex-col shrink-0 mr-[32rpx]">
|
<view class="flex items-center h-[40rpx]">
|
<text class="text-[28rpx] text-[#000000]">身份证号码</text>
|
<text v-if="isRequired(FIELD_CODES.ID_CARD)" class="text-[28rpx] text-[#F5222D] ml-[4rpx]">*</text>
|
</view>
|
<text v-if="errors.idCard" class="text-[20rpx] text-[#F5222D] mt-[8rpx]">{{ errors.idCard }}</text>
|
</view>
|
<input v-model="formData.idCard" type="idcard" placeholder="请输入身份证号码" placeholder-class="text-[#B3B3B3] text-[28rpx]" class="text-[28rpx] text-right text-[#333333] flex-1 h-[40rpx] min-h-[40rpx]" />
|
</view>
|
|
<view v-if="isVisible(FIELD_CODES.GENDER)" class="py-[28rpx] flex items-start justify-between">
|
<view class="flex flex-col shrink-0 mr-[32rpx]">
|
<view class="flex items-center h-[40rpx]">
|
<text class="text-[28rpx] text-[#000000]">性别</text>
|
<text v-if="isRequired(FIELD_CODES.GENDER)" class="text-[28rpx] text-[#F5222D] ml-[4rpx]">*</text>
|
</view>
|
</view>
|
<view class="flex items-center space-x-[48rpx] h-[40rpx]">
|
<view class="flex items-center" @click="formData.gender = '男'">
|
<view class="w-[32rpx] h-[32rpx] rounded-full border-[2rpx] border-solid flex items-center justify-center transition-colors duration-200" :class="formData.gender === '男' ? 'border-[#0068FF]' : 'border-[#CFCFCF]'">
|
<view v-if="formData.gender === '男'" class="w-[24rpx] h-[24rpx] rounded-full bg-[#0068FF]" />
|
</view>
|
<text class="ml-[12rpx] text-[28rpx] text-[#333333]">男</text>
|
</view>
|
<view class="flex items-center" @click="formData.gender = '女'">
|
<view class="w-[32rpx] h-[32rpx] rounded-full border-[2rpx] border-solid flex items-center justify-center transition-colors duration-200" :class="formData.gender === '女' ? 'border-[#0068FF]' : 'border-[#CFCFCF]'">
|
<view v-if="formData.gender === '女'" class="w-[24rpx] h-[24rpx] rounded-full bg-[#0068FF]" />
|
</view>
|
<text class="ml-[12rpx] text-[28rpx] text-[#333333]">女</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
|
<view class="fixed bottom-[94rpx] left-0 w-full px-[32rpx] box-border z-[10]">
|
<view class="h-[96rpx] rounded-full bg-[#0068FF] flex items-center justify-center shadow-[0_8rpx_24rpx_rgba(0,104,255,0.2)]" @click="handleSave">
|
<text class="text-[32rpx] text-white font-medium">{{ saving ? '保存中...' : '保存' }}</text>
|
</view>
|
</view>
|
</view>
|
</template>
|
|
<style scoped>
|
.border-b1 {
|
border-bottom: 1rpx solid #F5F5F5;
|
}
|
</style>
|