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/choosePerson/index.vue |  235 +++++++++++++++++++++++++++++++++++++++-------------------
 1 files changed, 156 insertions(+), 79 deletions(-)

diff --git a/yuyueMiniProgram/src/pages/choosePerson/index.vue b/yuyueMiniProgram/src/pages/choosePerson/index.vue
index 5b95aad..09dcacc 100644
--- a/yuyueMiniProgram/src/pages/choosePerson/index.vue
+++ b/yuyueMiniProgram/src/pages/choosePerson/index.vue
@@ -1,5 +1,9 @@
 <script setup lang="ts">
-import { ref } from 'vue'
+import { fetchMiniFieldConfigs } from '@/api/lookup'
+import { fetchMyVisitors } from '@/api/visitor'
+import { FIELD_CODES } from '@/constants/fieldCodes'
+import type { MiniFieldConfigItem } from '@/types/lookup'
+import type { MiniVisitorListItem } from '@/types/visitor'
 
 definePage({
   name: 'choosePerson',
@@ -8,6 +12,8 @@
     navigationStyle: 'custom',
   },
 })
+
+type PageMode = 'manage' | 'pick' | 'driver'
 
 const router = useRouter()
 const choosePersonAssetBase = '/static/choosePerson'
@@ -22,43 +28,82 @@
   company: '/static/appointment/company.png',
 }
 
-interface Visitor {
-  id: number
-  name: string
-  phone: string
-  company: string
-  gender: 'male' | 'female'
+const mode = ref<PageMode>('manage')
+const visitors = ref<MiniVisitorListItem[]>([])
+const loading = ref(false)
+const fieldConfigs = ref<MiniFieldConfigItem[]>([])
+const selectedId = ref<number | null>(null)
+const selectedIds = ref<number[]>([])
+const vehicleIndex = ref<number>(-1)
+
+const showName = computed(() => isVisible(FIELD_CODES.VISITOR_NAME))
+const showGender = computed(() => isVisible(FIELD_CODES.GENDER))
+const showIdCard = computed(() => isVisible(FIELD_CODES.ID_CARD))
+const showCompany = computed(() => isVisible(FIELD_CODES.COMPANY))
+
+function isVisible(code: string) {
+  const config = fieldConfigs.value.find(item => item.fieldCode === code)
+  return config ? config.visible === 1 : true
 }
 
-// 模拟有数据状态
-const visitors = ref<Visitor[]>([
-  { id: 1, name: '张三', phone: '5120********258', company: '成都xxxxxx有限公司', gender: 'male' },
-  { id: 2, name: '张三', phone: '5120********258', company: '成都xxxxxx有限公司', gender: 'female' },
-  { id: 3, name: '张三', phone: '5120********258', company: '成都xxxxxx有限公司', gender: 'female' },
-])
+function displayName(item: MiniVisitorListItem) {
+  return showName.value ? item.visitorName || '来访人' : '来访人'
+}
 
-const selectedId = ref<number>(1)
+function normalizeGender(gender?: string | null) {
+  return gender === '女' ? 'female' : 'male'
+}
 
 function handleSelect(id: number) {
+  if (mode.value === 'pick') {
+    if (selectedIds.value.includes(id)) {
+      selectedIds.value = selectedIds.value.filter(item => item !== id)
+      return
+    }
+    selectedIds.value = [...selectedIds.value, id]
+    return
+  }
   selectedId.value = id
 }
 
-function handleEdit(id: number) {
-  // 跳转到编辑页面
-  uni.showToast({ title: '编辑功能开发中', icon: 'none' })
+function handleEdit(item: MiniVisitorListItem) {
+  uni.navigateTo({
+    url: `/pages/addPerson/index?id=${item.id}&uuid=${item.uuid}`,
+  })
+}
+
+function buildPayload(items: MiniVisitorListItem[]) {
+  return items.map(item => ({
+    id: item.id,
+    uuid: item.uuid,
+    name: item.visitorName,
+    phone: '',
+    company: item.visitorCompany || '',
+    gender: normalizeGender(item.gender),
+    idCardMasked: item.idCardMasked || '',
+  }))
 }
 
 function handleConfirm() {
-  if (visitors.value.length > 0) {
-    const selectedVisitor = visitors.value.find(v => v.id === selectedId.value)
-    if (selectedVisitor) {
-      uni.$emit('acceptVisitor', selectedVisitor)
-    }
-    uni.showToast({ title: '选择成功', icon: 'success' })
-    setTimeout(() => {
-      router.back()
-    }, 1000)
+  if (mode.value === 'manage') {
+    router.back()
+    return
   }
+
+  const picked = mode.value === 'pick'
+    ? visitors.value.filter(item => selectedIds.value.includes(item.id))
+    : visitors.value.filter(item => item.id === selectedId.value)
+
+  uni.$emit('selectVisitors', {
+    mode: mode.value,
+    vehicleIndex: vehicleIndex.value,
+    visitors: buildPayload(picked),
+  })
+
+  uni.showToast({ title: '选择成功', icon: 'success' })
+  setTimeout(() => {
+    router.back()
+  }, 300)
 }
 
 function handleBack() {
@@ -68,65 +113,98 @@
 function handleAdd() {
   uni.navigateTo({ url: '/pages/addPerson/index' })
 }
+
+async function loadVisitors() {
+  loading.value = true
+  try {
+    const [configs, list] = await Promise.all([
+      fetchMiniFieldConfigs(),
+      fetchMyVisitors(),
+    ])
+    fieldConfigs.value = configs
+    visitors.value = list
+
+    if (mode.value === 'pick') {
+      selectedIds.value = selectedIds.value.filter(id => list.some(item => item.id === id))
+    }
+    else {
+      selectedId.value = list.find(item => item.id === selectedId.value)?.id ?? list[0]?.id ?? null
+    }
+  }
+  catch (error) {
+    uni.showToast({
+      title: error instanceof Error ? error.message : '加载来访人失败',
+      icon: 'none',
+    })
+  }
+  finally {
+    loading.value = false
+  }
+}
+
+onLoad((options) => {
+  const pageMode = options?.mode as PageMode | undefined
+  if (pageMode === 'pick' || pageMode === 'driver' || pageMode === 'manage') {
+    mode.value = pageMode
+  }
+
+  vehicleIndex.value = options?.vehicleIndex ? Number(options.vehicleIndex) : -1
+
+  if (mode.value === 'pick' && options?.selectedIds) {
+    selectedIds.value = options.selectedIds
+      .split(',')
+      .map(item => Number(item))
+      .filter(item => !Number.isNaN(item))
+  }
+
+  if (mode.value === 'driver' && options?.selectedId) {
+    const id = Number(options.selectedId)
+    selectedId.value = Number.isNaN(id) ? null : id
+  }
+})
+
+onShow(() => {
+  void loadVisitors()
+})
 </script>
 
 <template>
   <view class="min-h-screen w-full relative box-border bg-[#F6FBFF] overflow-hidden">
-    <!-- 背景图 -->
-    <image 
-      :src="`${choosePersonAssetBase}/bg@2x.png`" 
+    <image
+      :src="`${choosePersonAssetBase}/bg@2x.png`"
       class="absolute left-0 top-0 w-full z-0"
       mode="widthFix"
     />
 
-    <!-- 自定义导航栏 -->
     <view class="relative z-50 w-full" :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="handleBack">
           <wd-icon name="arrow-left" size="40rpx" color="#ffffff" />
         </view>
         <view class="absolute left-0 w-full text-center text-[34rpx] font-medium text-white z-0">
           选择来访人
         </view>
-        <view class="w-[88rpx]"></view>
+        <view class="w-[88rpx]" />
       </view>
     </view>
 
-    <!-- 内容区 -->
-    <view v-if="visitors.length === 0" class="absolute top-[268rpx] left-0 w-full z-10 flex flex-col items-center">
-      <!-- 缺省图 -->
-      <image 
-        :src="`${choosePersonAssetBase}/noData.png`" 
-        class="w-[380rpx] h-[380rpx]"
-        mode="aspectFit"
-      />
-      <!-- 提示文案 -->
+    <view v-if="!loading && visitors.length === 0" class="absolute top-[268rpx] left-0 w-full z-10 flex flex-col items-center">
+      <image :src="`${choosePersonAssetBase}/noData.png`" class="w-[380rpx] h-[380rpx]" mode="aspectFit" />
       <text class="text-[28rpx] text-[#B3B3B3]">
         暂无来访人信息,请先添加~
       </text>
 
-      <!-- 立即添加按钮 -->
-      <view 
-        class="mt-[94rpx] w-[486rpx] h-[108rpx] rounded-full bg-[#0068FF] flex items-center justify-center shadow-[0_8rpx_24rpx_rgba(0,104,255,0.2)]"
-        @click="handleAdd"
-      >
+      <view class="mt-[94rpx] w-[486rpx] h-[108rpx] rounded-full bg-[#0068FF] flex items-center justify-center shadow-[0_8rpx_24rpx_rgba(0,104,255,0.2)]" @click="handleAdd">
         <image :src="`${choosePersonAssetBase}/add.png`" class="w-[40rpx] h-[40rpx]" />
         <text class="ml-[12rpx] text-[32rpx] text-white font-medium">立即添加</text>
       </view>
 
-      <!-- 返回按钮 -->
-      <view 
-        class="mt-[48rpx] w-[486rpx] h-[108rpx] rounded-full border-[2rpx] border-solid border-[#0068FF] bg-white flex items-center justify-center"
-        @click="handleBack"
-      >
+      <view class="mt-[48rpx] w-[486rpx] h-[108rpx] rounded-full border-[2rpx] border-solid border-[#0068FF] bg-white flex items-center justify-center" @click="handleBack">
         <text class="text-[32rpx] text-[#0068FF] font-medium">返回</text>
       </view>
     </view>
 
-    <!-- 有数据时的列表展示 -->
-    <view v-else class="relative z-10 px-[24rpx] pb-[200rpx]" >
-      <!-- 顶部添加按钮 -->
+    <view v-else class="relative z-10 px-[24rpx] pb-[200rpx]">
       <view class="w-full bg-white/50 rounded-[8rpx] h-[144rpx] flex items-center justify-center shadow-[0_4rpx_8rpx_rgba(6,47,108,0.1608)] mb-[24rpx]" @click="handleAdd">
         <view class="flex items-center justify-center w-[252rpx] h-[64rpx] rounded-full border-[2rpx] border-solid border-[#0068FF] bg-transparent">
           <wd-icon name="add-circle" size="28rpx" color="#1677FF" />
@@ -134,53 +212,52 @@
         </view>
       </view>
 
-      <!-- 人员卡片列表 -->
       <view class="w-full bg-white rounded-[8rpx] shadow-[0_4rpx_8rpx_rgba(6,47,108,0.1608)] overflow-hidden">
-        <view 
-          v-for="(item, index) in visitors" 
+        <view
+          v-for="(item, index) in visitors"
           :key="item.id"
           class="relative flex items-center justify-between px-[24rpx] py-[24rpx]"
           @click="handleSelect(item.id)"
         >
-          <!-- 底部分隔线 (最后一个元素不显示) -->
-          <view v-if="index < visitors.length - 1" class="absolute bottom-0 right-0 w-[574rpx] h-[2rpx] bg-[#F5F5F5]"></view>
+          <view v-if="index < visitors.length - 1" class="absolute bottom-0 right-0 w-[574rpx] h-[2rpx] bg-[#F5F5F5]" />
 
-          <!-- 左侧:单选按钮和名字 -->
           <view class="flex items-center w-[160rpx] shrink-0">
-            <!-- 自定义单选框 -->
-            <view class="w-[32rpx] h-[32rpx] rounded-full border-[2rpx] border-solid flex items-center justify-center transition-colors duration-200 shrink-0"
-              :class="selectedId === item.id ? 'border-[#0068FF]' : 'border-[#CFCFCF]'">
-              <view v-if="selectedId === item.id" class="w-[16rpx] h-[16rpx] rounded-full bg-[#0068FF]"></view>
+            <view
+              class="w-[32rpx] h-[32rpx] rounded-full border-[2rpx] border-solid flex items-center justify-center transition-colors duration-200 shrink-0"
+              :class="mode === 'pick'
+                ? (selectedIds.includes(item.id) ? 'border-[#0068FF]' : 'border-[#CFCFCF]')
+                : (selectedId === item.id ? 'border-[#0068FF]' : 'border-[#CFCFCF]')"
+            >
+              <view v-if="mode === 'pick' ? selectedIds.includes(item.id) : selectedId === item.id" class="w-[16rpx] h-[16rpx] rounded-full bg-[#0068FF]" />
             </view>
-            <text class="ml-[16rpx] text-[28rpx] text-[#333333] font-medium truncate">{{ item.name }}</text>
+            <text class="ml-[16rpx] text-[28rpx] text-[#333333] font-medium truncate">{{ displayName(item) }}</text>
           </view>
 
-          <!-- 中间:详细信息 -->
           <view class="flex flex-col justify-center flex-1 mx-[20rpx] min-w-0">
-            <view class="flex items-center">
-              <image :src="item.gender === 'female' ? appointmentIcons.female : appointmentIcons.male" class="w-[32rpx] h-[32rpx] shrink-0" />
-              <text class="ml-[20rpx] text-[28rpx] text-[#B3B3B3] truncate">{{ item.phone }}</text>
+            <view v-if="showGender || showIdCard" class="flex items-center">
+              <image :src="item.gender === '女' ? appointmentIcons.female : appointmentIcons.male" class="w-[32rpx] h-[32rpx] shrink-0" />
+              <text class="ml-[20rpx] text-[28rpx] text-[#B3B3B3] truncate">
+                {{ [showGender ? item.gender : '', showIdCard ? item.idCardMasked || '-' : ''].filter(Boolean).join(' ') }}
+              </text>
             </view>
-            <view class="flex items-center mt-[8rpx]">
+            <view v-if="showCompany" class="flex items-center mt-[8rpx]">
               <image :src="appointmentIcons.company" class="w-[32rpx] h-[32rpx] shrink-0" />
-              <text class="ml-[20rpx] text-[28rpx] text-[#B3B3B3] truncate">{{ item.company }}</text>
+              <text class="ml-[20rpx] text-[28rpx] text-[#B3B3B3] truncate">{{ item.visitorCompany || '-' }}</text>
             </view>
+            <text v-if="item.missingFieldCodes?.length" class="mt-[8rpx] text-[22rpx] text-[#F5222D] truncate">
+              缺失:{{ item.missingFieldCodes.join('、') }}
+            </text>
           </view>
 
-          <!-- 右侧:编辑图标 -->
-          <view class="w-[40rpx] h-[40rpx] shrink-0 flex items-center justify-center" @click.stop="handleEdit(item.id)">
+          <view class="w-[40rpx] h-[40rpx] shrink-0 flex items-center justify-center" @click.stop="handleEdit(item)">
             <image :src="`${choosePersonAssetBase}/edit.png`" class="w-[33rpx] h-[33rpx]" />
           </view>
         </view>
       </view>
     </view>
 
-    <!-- 底部确认按钮 -->
     <view v-if="visitors.length > 0" class="fixed bottom-[48rpx] left-0 w-full px-[32rpx] box-border z-[100]">
-      <view 
-        class="h-[96rpx] rounded-full bg-[#0068FF] flex items-center justify-center shadow-[0_8rpx_24rpx_rgba(0,104,255,0.2)]"
-        @click="handleConfirm"
-      >
+      <view class="h-[96rpx] rounded-full bg-[#0068FF] flex items-center justify-center shadow-[0_8rpx_24rpx_rgba(0,104,255,0.2)]" @click="handleConfirm">
         <text class="text-[32rpx] text-white font-medium">确认</text>
       </view>
     </view>

--
Gitblit v1.7.1