longz
2026-04-27 25df88d0ab9d7444ed1d162b9efa1259ac2f6f66
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>