From 25df88d0ab9d7444ed1d162b9efa1259ac2f6f66 Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期一, 27 四月 2026 10:13:53 +0800
Subject: [PATCH] 小程序对接finish

---
 yuyueMiniProgram/src/pages/addPerson/index.vue |  238 ++++++++++++++++++++++++++++++++++++++---------------------
 1 files changed, 153 insertions(+), 85 deletions(-)

diff --git a/yuyueMiniProgram/src/pages/addPerson/index.vue b/yuyueMiniProgram/src/pages/addPerson/index.vue
index 5ba6cbe..d4e792b 100644
--- a/yuyueMiniProgram/src/pages/addPerson/index.vue
+++ b/yuyueMiniProgram/src/pages/addPerson/index.vue
@@ -1,27 +1,37 @@
 <script setup lang="ts">
-import { reactive } from 'vue'
+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'
+    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: 'male',
+  gender: '男' as '男' | '女',
 })
 
 const errors = reactive({
@@ -31,35 +41,61 @@
   idCard: '',
 })
 
-function validate() {
-  let isValid = true
+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 = ''
+}
 
-  if (!formData.name) {
+function validate() {
+  let isValid = true
+  resetErrors()
+
+  if (isVisible(FIELD_CODES.VISITOR_NAME) && isRequired(FIELD_CODES.VISITOR_NAME) && !formData.name.trim()) {
     errors.name = '请输入来访人姓名'
     isValid = false
   }
-  
-  if (!formData.phone) {
+  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 (!/^1[3-9]\d{9}$/.test(formData.phone)) {
+  }
+  else if (formData.phone && !/^\d{11}$/.test(formData.phone.trim())) {
     errors.phone = '请确保电话号码录入正确!'
     isValid = false
   }
-  
-  if (!formData.company) {
+
+  if (isVisible(FIELD_CODES.COMPANY) && isRequired(FIELD_CODES.COMPANY) && !formData.company.trim()) {
     errors.company = '请输入来访人单位'
     isValid = false
   }
-  
-  if (!formData.idCard) {
+
+  if (isVisible(FIELD_CODES.ID_CARD) && isRequired(FIELD_CODES.ID_CARD) && !formData.idCard.trim()) {
     errors.idCard = '请输入身份证号码'
     isValid = false
-  } else if (!/(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(formData.idCard)) {
+  }
+  else if (formData.idCard && !/(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(formData.idCard.trim())) {
     errors.idCard = '请确保身份证号码录入正确!'
     isValid = false
   }
@@ -67,145 +103,177 @@
   return isValid
 }
 
-function handleSave() {
-  if (validate()) {
+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()
-    }, 1500)
+    }, 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 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 class="w-[88rpx]" />
       </view>
     </view>
 
-    <!-- 表单区域 -->
-    <view class="bg-white px-[64rpx] py-[48rpx]">
-      
-      <!-- 来访人姓名 -->
-      <view class="py-[28rpx] flex items-start justify-between border-b1">
+    <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 class="text-[28rpx] text-[#F5222D] ml-[4rpx]">*</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]"
-        />
+        <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 class=" py-[28rpx] flex items-start justify-between border-b1">
+      <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 class="text-[28rpx] text-[#F5222D] ml-[4rpx]">*</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]"
-        />
+        <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 class=" py-[28rpx] flex items-start justify-between border-b1">
+      <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 class="text-[28rpx] text-[#F5222D] ml-[4rpx]">*</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]"
-        />
+        <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 class=" py-[28rpx] flex items-start justify-between border-b1">
+      <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 class="text-[28rpx] text-[#F5222D] ml-[4rpx]">*</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]"
-        />
+        <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 class="py-[28rpx] flex items-start justify-between">
+      <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 class="text-[28rpx] text-[#F5222D] ml-[4rpx]">*</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 = 'male'">
-            <view class="w-[32rpx] h-[32rpx] rounded-full border-[2rpx] border-solid flex items-center justify-center transition-colors duration-200"
-              :class="formData.gender === 'male' ? 'border-[#0068FF]' : 'border-[#CFCFCF]'">
-              <view v-if="formData.gender === 'male'" class="w-[24rp x] h-[24rpx] rounded-full bg-[#0068FF]"></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 class="flex items-center" @click="formData.gender = 'female'">
-            <view class="w-[32rpx] h-[32rpx] rounded-full border-[2rpx] border-solid flex items-center justify-center transition-colors duration-200"
-              :class="formData.gender === 'female' ? 'border-[#0068FF]' : 'border-[#CFCFCF]'">
-              <view v-if="formData.gender === 'female'" class="w-[24rpx] h-[24rpx] rounded-full bg-[#0068FF]"></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">保存</text>
+      <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>
 

--
Gitblit v1.7.1