1.0
longz
2026-06-24 e45e74e5ae207f7642be9a88ed99b593de89fb3b
yuyueMiniProgram/src/pages/commonVisitors/index.vue
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { fetchMiniFieldConfigs } from '@/api/lookup'
import { fetchMyVisitors, setDefaultVisitor } from '@/api/visitor'
import { deleteVisitor, fetchMyVisitors, setDefaultVisitor } from '@/api/visitor'
import { FIELD_CODES, formatVisitorFieldLabels } from '@/constants/fieldCodes'
import type { MiniFieldConfigItem } from '@/types/lookup'
import type { MiniVisitorListItem } from '@/types/visitor'
@@ -29,7 +29,13 @@
const visitors = ref<MiniVisitorListItem[]>([])
const loading = ref(false)
const settingDefaultId = ref<number | null>(null)
const deletingId = ref<number | null>(null)
const openSwipeId = ref<number | null>(null)
const fieldConfigs = ref<MiniFieldConfigItem[]>([])
let touchStartX = 0
let touchStartY = 0
let touchingId: number | null = null
const showName = computed(() => isVisible(FIELD_CODES.VISITOR_NAME))
const showGender = computed(() => isVisible(FIELD_CODES.GENDER))
@@ -50,6 +56,7 @@
}
function handleEdit(item: MiniVisitorListItem) {
  openSwipeId.value = null
  uni.navigateTo({
    url: `/pages/addPerson/index?id=${item.id}&uuid=${item.uuid}`,
  })
@@ -67,6 +74,7 @@
  if (item.isDefault || settingDefaultId.value) {
    return
  }
  openSwipeId.value = null
  settingDefaultId.value = item.id
  try {
    await setDefaultVisitor(item.id, item.uuid)
@@ -81,6 +89,73 @@
  }
  finally {
    settingDefaultId.value = null
  }
}
function handleTouchStart(item: MiniVisitorListItem, event: any) {
  const touch = event.touches?.[0]
  if (!touch) {
    return
  }
  touchingId = item.id
  touchStartX = touch.clientX
  touchStartY = touch.clientY
}
function handleTouchEnd(item: MiniVisitorListItem, event: any) {
  if (touchingId !== item.id) {
    return
  }
  const touch = event.changedTouches?.[0]
  touchingId = null
  if (!touch) {
    return
  }
  const deltaX = touch.clientX - touchStartX
  const deltaY = touch.clientY - touchStartY
  if (Math.abs(deltaX) <= Math.abs(deltaY) + 10) {
    return
  }
  if (deltaX < -40) {
    openSwipeId.value = item.id
  } else if (deltaX > 20) {
    openSwipeId.value = null
  }
}
function confirmDelete(item: MiniVisitorListItem) {
  if (deletingId.value) {
    return
  }
  uni.showModal({
    title: '删除确认',
    content: `确认删除来访人「${displayName(item)}」?`,
    confirmText: '删除',
    confirmColor: '#FF4D4F',
    success: (res) => {
      if (res.confirm) {
        void handleDelete(item)
      }
    },
  })
}
async function handleDelete(item: MiniVisitorListItem) {
  deletingId.value = item.id
  try {
    await deleteVisitor(item.id, item.uuid)
    openSwipeId.value = null
    uni.showToast({ title: '已删除', icon: 'none' })
    await loadVisitors()
  }
  catch (error) {
    uni.showToast({
      title: error instanceof Error ? error.message : '删除来访人失败',
      icon: 'none',
    })
  }
  finally {
    deletingId.value = null
  }
}
@@ -140,42 +215,53 @@
    <view v-else class="relative z-10 px-[24rpx] pb-[200rpx] mt-[5vh]">
      <view class="w-full flex flex-col gap-[16rpx]">
        <view
          v-for="(item, index) in visitors"
          v-for="item in visitors"
          :key="item.id"
          class="relative flex items-center justify-between px-[24rpx] pt-[24rpx] pb-[64rpx] bg-white rounded-[8rpx] shadow-[0_4rpx_8rpx_rgba(6,47,108,0.1608)]"
          class="relative overflow-hidden rounded-[8rpx] bg-[#FF4D4F]"
        >
          <view class="flex items-center w-[160rpx] shrink-0">
            <text class="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 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">
                {{ showIdCard ? (item.idCardMasked || '-') : '-' }}
              </text>
            </view>
            <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.visitorCompany || '-' }}</text>
            </view>
            <text v-if="item.missingFieldCodes?.length" class="mt-[8rpx] text-[22rpx] text-[#F5222D] truncate">
              缺失:{{ formatMissingFields(item.missingFieldCodes) }}
            </text>
          </view>
          <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 class="absolute right-0 top-0 bottom-0 w-[132rpx] bg-[#FF4D4F] flex items-center justify-center z-0" @click.stop="confirmDelete(item)">
            <text class="text-[28rpx] text-white">{{ deletingId === item.id ? '删除中' : '删除' }}</text>
          </view>
          <view
            class="absolute right-[24rpx] bottom-[18rpx] h-[36rpx] px-[18rpx] rounded-full flex items-center justify-center"
            :class="item.isDefault ? 'bg-[#E6F0FF]' : 'bg-[#F5F7FA]'"
            @click.stop="handleSetDefault(item)"
            class="relative z-10 flex items-center justify-between px-[24rpx] pt-[24rpx] pb-[64rpx] bg-white rounded-[8rpx] shadow-[0_4rpx_8rpx_rgba(6,47,108,0.1608)] transition-transform duration-200"
            :style="{ transform: openSwipeId === item.id ? 'translateX(-132rpx)' : 'translateX(0)' }"
            @touchstart="handleTouchStart(item, $event)"
            @touchend="handleTouchEnd(item, $event)"
          >
            <text class="text-[22rpx]" :class="item.isDefault ? 'text-[#0068FF]' : 'text-[#999999]'">
              {{ item.isDefault ? '默认来访人' : (settingDefaultId === item.id ? '设置中...' : '设为默认') }}
            </text>
            <view class="flex items-center w-[160rpx] shrink-0">
              <text class="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 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">
                  {{ showIdCard ? (item.idCardMasked || '-') : '-' }}
                </text>
              </view>
              <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.visitorCompany || '-' }}</text>
              </view>
              <text v-if="item.missingFieldCodes?.length" class="mt-[8rpx] text-[22rpx] text-[#F5222D] truncate">
                缺失:{{ formatMissingFields(item.missingFieldCodes) }}
              </text>
            </view>
            <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
              class="absolute right-[24rpx] bottom-[18rpx] h-[36rpx] px-[18rpx] rounded-full flex items-center justify-center"
              :class="item.isDefault ? 'bg-[#E6F0FF]' : 'bg-[#F5F7FA]'"
              @click.stop="handleSetDefault(item)"
            >
              <text class="text-[22rpx]" :class="item.isDefault ? 'text-[#0068FF]' : 'text-[#999999]'">
                {{ item.isDefault ? '默认来访人' : (settingDefaultId === item.id ? '设置中...' : '设为默认') }}
              </text>
            </view>
          </view>
        </view>
      </view>