From e45e74e5ae207f7642be9a88ed99b593de89fb3b Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期三, 24 六月 2026 16:54:46 +0800
Subject: [PATCH] 1.0

---
 yuyueMiniProgram/src/pages/commonVisitors/index.vue |  148 +++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 117 insertions(+), 31 deletions(-)

diff --git a/yuyueMiniProgram/src/pages/commonVisitors/index.vue b/yuyueMiniProgram/src/pages/commonVisitors/index.vue
index 0f01bf9..ce8c5f1 100644
--- a/yuyueMiniProgram/src/pages/commonVisitors/index.vue
+++ b/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>

--
Gitblit v1.7.1