| | |
| | | <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' |
| | |
| | | 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)) |
| | |
| | | } |
| | | |
| | | function handleEdit(item: MiniVisitorListItem) { |
| | | openSwipeId.value = null |
| | | uni.navigateTo({ |
| | | url: `/pages/addPerson/index?id=${item.id}&uuid=${item.uuid}`, |
| | | }) |
| | |
| | | if (item.isDefault || settingDefaultId.value) { |
| | | return |
| | | } |
| | | openSwipeId.value = null |
| | | settingDefaultId.value = item.id |
| | | try { |
| | | await setDefaultVisitor(item.id, item.uuid) |
| | |
| | | } |
| | | 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 |
| | | } |
| | | } |
| | | |
| | |
| | | <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> |