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/index/index.vue |  660 +++++++++++++++++++++++++++++++++++++++--------------------
 1 files changed, 435 insertions(+), 225 deletions(-)

diff --git a/yuyueMiniProgram/src/pages/index/index.vue b/yuyueMiniProgram/src/pages/index/index.vue
index d90c754..1c21593 100644
--- a/yuyueMiniProgram/src/pages/index/index.vue
+++ b/yuyueMiniProgram/src/pages/index/index.vue
@@ -1,150 +1,369 @@
 <script setup lang="ts">
-import { ref } from 'vue'
+import QRCode from 'qrcode'
+import { fetchMyApprovalTodos, fetchMyVisitorCodes, fetchVisitRecords } from '@/api/appointment'
+import { useMiniAuthStore } from '@/store/auth'
+import type { MiniMyVisitorCodeOrderItem, MiniMyVisitorCodeTicketItem } from '@/types/appointment'
 
-const { safeAreaInsets } = uni.getSystemInfoSync()
+interface VisitorCodeDisplayTicket extends MiniMyVisitorCodeTicketItem {
+  qrCode: string
+}
 
-// 弹窗显示状态
-const showLoginPopup = ref(false)
+interface VisitorCodeDisplayOrder extends Omit<MiniMyVisitorCodeOrderItem, 'qrCodes'> {
+  timeText: string
+  qrCodes: VisitorCodeDisplayTicket[]
+}
 
-// 登录状态
-const isLoggedIn = ref(true)
-
-// 用户角色: 'employee' 员工 | 'visitor' 普通访客
-const userRole = ref<'employee' | 'visitor'>('visitor')
-
-// 访客码数据
-const visitorList = ref([
-  {
-    id: 1,
-    name: '王敏',
-    department: '管理部',
-    area: 'AAAAA',
-    time: '2023-10-10 11:30至2023-10-15 18:30',
-    qrCode: '/static/home/成都喜望科技有限公司.png',
-    visitorName: '王琦',
-  },
-  {
-    id: 2,
-    name: '李华',
-    department: '技术部',
-    area: 'BBBBB',
-    time: '2023-11-01 09:00至2023-11-01 18:00',
-    qrCode: '/static/home/成都喜望科技有限公司.png',
-    visitorName: '张三',
-  },
-])
-const currentVisitorIndex = ref(0)
+const systemInfo = uni.getSystemInfoSync()
+const { safeAreaInsets } = systemInfo
+const parkFlowBottomPx = Math.max(24, Math.round(systemInfo.windowHeight / 8))
 const router = useRouter()
+const authStore = useMiniAuthStore()
+const visitorOrders = ref<VisitorCodeDisplayOrder[]>([])
+const currentOrderIndex = ref(0)
+const loadingVisitorCodes = ref(false)
+const qrRenderToken = ref(0)
+const ticketIndexMap = ref<Record<number, number>>({})
+const approvalTodoCount = ref(0)
+const visitRecordCount = ref(0)
 
-// 切换访客码
-function handlePrevVisitor() {
-  if (currentVisitorIndex.value > 0) {
-    currentVisitorIndex.value--
+const profile = computed(() => authStore.profile)
+const isEmployee = computed(() => profile.value?.userType === 'EMPLOYEE')
+const userRoleText = computed(() => {
+  if (!profile.value) {
+    return ''
+  }
+  if (profile.value.userType === 'EMPLOYEE') {
+    return profile.value.deptLeaderFlag ? '部门领导' : '员工'
+  }
+  return '普通访客'
+})
+const secondaryText = computed(() => {
+  if (!profile.value) {
+    return ''
+  }
+  if (profile.value.userType === 'EMPLOYEE') {
+    return profile.value.deptName || '未分配部门'
+  }
+  return profile.value.companyName || ''
+})
+const currentOrder = computed(() => visitorOrders.value[currentOrderIndex.value] || null)
+const currentTicket = computed(() => {
+  if (!currentOrder.value) {
+    return null
+  }
+  const index = ticketIndexMap.value[currentOrder.value.orderId] ?? 0
+  return currentOrder.value.qrCodes[index] || currentOrder.value.qrCodes[0] || null
+})
+
+function handlePrevOrder() {
+  if (currentOrderIndex.value > 0) {
+    currentOrderIndex.value--
   }
 }
 
-function handleNextVisitor() {
-  if (currentVisitorIndex.value < visitorList.value.length - 1) {
-    currentVisitorIndex.value++
+function handleNextOrder() {
+  if (currentOrderIndex.value < visitorOrders.value.length - 1) {
+    currentOrderIndex.value++
   }
 }
 
-function onSwiperChange(e: any) {
-  currentVisitorIndex.value = e.detail.current
+function onSwiperChange(e: { detail?: { current?: number } }) {
+  currentOrderIndex.value = e.detail?.current || 0
 }
 
-// 下载访客码
-function handleDownloadVisitorCode(qrCodeUrl: string) {
-  // 小程序环境中获取图片信息并保存到相册
-  uni.showLoading({ title: '保存中...' })
+function currentTicketIndex(orderId: number) {
+  return ticketIndexMap.value[orderId] ?? 0
+}
 
-  // 这里如果是本地路径如 /static/xxx,可以直接用。如果后续换成网络图,需要先 uni.downloadFile
-  // 为了演示,统一走 uni.getImageInfo 以校验文件存在并保存
-  uni.getImageInfo({
-    src: qrCodeUrl,
-    success: (image) => {
-      uni.saveImageToPhotosAlbum({
-        filePath: image.path,
-        success: () => {
-          uni.hideLoading()
-          uni.showToast({
-            title: '保存成功',
-            icon: 'success',
-          })
-        },
-        fail: (err) => {
-          uni.hideLoading()
-          // 拒绝授权后会走 fail
-          if (err.errMsg === 'saveImageToPhotosAlbum:fail auth deny') {
-            uni.showModal({
-              title: '提示',
-              content: '需要您授权保存相册',
-              success(res) {
-                if (res.confirm) {
-                  uni.openSetting({
-                    success(settingdata) {
-                      if (settingdata.authSetting['scope.writePhotosAlbum']) {
-                        uni.showToast({ title: '授权成功,请重新点击下载', icon: 'none' })
-                      }
-                      else {
-                        uni.showToast({ title: '授权失败,无法保存', icon: 'none' })
-                      }
-                    },
-                  })
-                }
-              },
-            })
-          }
-          else {
-            uni.showToast({
-              title: '保存失败',
-              icon: 'none',
-            })
-          }
-        },
+function handlePrevTicket(orderId: number) {
+  const currentIndex = currentTicketIndex(orderId)
+  if (currentIndex <= 0) {
+    return
+  }
+  ticketIndexMap.value = {
+    ...ticketIndexMap.value,
+    [orderId]: currentIndex - 1,
+  }
+}
+
+function handleNextTicket(orderId: number) {
+  const order = visitorOrders.value.find(item => item.orderId === orderId)
+  const currentIndex = currentTicketIndex(orderId)
+  if (!order || currentIndex >= order.qrCodes.length - 1) {
+    return
+  }
+  ticketIndexMap.value = {
+    ...ticketIndexMap.value,
+    [orderId]: currentIndex + 1,
+  }
+}
+
+function formatVisitTime(order: MiniMyVisitorCodeOrderItem) {
+  return order.visitTimeText || '暂无有效期'
+}
+
+function formatDateTime(value?: string | null) {
+  if (!value) {
+    return ''
+  }
+  return value.replace('T', ' ').slice(0, 16)
+}
+
+function buildVisitorOrders(orders: MiniMyVisitorCodeOrderItem[]): VisitorCodeDisplayOrder[] {
+  return orders.map(order => ({
+    ...order,
+    timeText: formatVisitTime(order),
+    qrCodes: order.qrCodes.map((ticket: MiniMyVisitorCodeTicketItem) => ({
+      ...ticket,
+      qrCode: '',
+    })),
+  }))
+}
+
+async function loadVisitorCodes() {
+  loadingVisitorCodes.value = true
+  const currentToken = qrRenderToken.value + 1
+  qrRenderToken.value = currentToken
+  try {
+    const orders = await fetchMyVisitorCodes()
+    const displayOrders = buildVisitorOrders(orders)
+    visitorOrders.value = displayOrders
+    currentOrderIndex.value = 0
+    ticketIndexMap.value = Object.fromEntries(displayOrders.map(item => [item.orderId, 0]))
+    await nextTick()
+    const hydratedOrders = await Promise.all(displayOrders.map(async order => ({
+      ...order,
+      qrCodes: await Promise.all(order.qrCodes.map(async ticket => ({
+        ...ticket,
+        qrCode: await createQrImage(ticket.payload),
+      }))),
+    })))
+    if (qrRenderToken.value === currentToken) {
+      visitorOrders.value = hydratedOrders
+    }
+  }
+  catch (error) {
+    visitorOrders.value = []
+    ticketIndexMap.value = {}
+    uni.showToast({
+      title: error instanceof Error ? error.message : '加载访客码失败',
+      icon: 'none',
+    })
+  }
+  finally {
+    if (qrRenderToken.value === currentToken) {
+      loadingVisitorCodes.value = false
+    }
+  }
+}
+
+async function loadEmployeeDashboardCounts() {
+  if (!isEmployee.value) {
+    approvalTodoCount.value = 0
+    visitRecordCount.value = 0
+    return
+  }
+  try {
+    const [todoResult, visitRecordResult] = await Promise.all([
+      fetchMyApprovalTodos({ page: 1, size: 1 }),
+      fetchVisitRecords({ page: 1, size: 1, futureOnly: true }),
+    ])
+    approvalTodoCount.value = todoResult.total
+    visitRecordCount.value = visitRecordResult.total
+  }
+  catch (error) {
+    approvalTodoCount.value = 0
+    visitRecordCount.value = 0
+    console.warn('load employee dashboard counts failed', error)
+  }
+}
+
+async function createQrImage(payload: string) {
+  // #ifdef H5
+  return QRCode.toDataURL(payload, {
+    errorCorrectionLevel: 'M',
+    margin: 1,
+    width: 300,
+  })
+  // #endif
+
+  const qr = QRCode.create(payload, {
+    errorCorrectionLevel: 'M',
+    margin: 1,
+  })
+  const size = 300
+  const moduleCount = qr.modules.size
+  const tileSize = size / moduleCount
+  const canvasId = 'visitorQrCanvas'
+
+  return new Promise<string>((resolve, reject) => {
+    const context = uni.createCanvasContext(canvasId)
+    context.setFillStyle('#FFFFFF')
+    context.fillRect(0, 0, size, size)
+    context.setFillStyle('#000000')
+
+    for (let row = 0; row < moduleCount; row += 1) {
+      for (let col = 0; col < moduleCount; col += 1) {
+        const index = row * moduleCount + col
+        if (!qr.modules.data[index]) {
+          continue
+        }
+        const x = Math.round(col * tileSize)
+        const y = Math.round(row * tileSize)
+        const width = Math.ceil((col + 1) * tileSize) - x
+        const height = Math.ceil((row + 1) * tileSize) - y
+        context.fillRect(x, y, width, height)
+      }
+    }
+
+    context.draw(false, () => {
+      uni.canvasToTempFilePath({
+        canvasId,
+        width: size,
+        height: size,
+        destWidth: size * 2,
+        destHeight: size * 2,
+        success: res => resolve(res.tempFilePath),
+        fail: reject,
       })
-    },
-    fail: () => {
+    })
+  })
+}
+
+function handleDownloadVisitorCode(qrCodeUrl: string) {
+  if (!qrCodeUrl) {
+    uni.showToast({
+      title: '访客码生成中',
+      icon: 'none',
+    })
+    return
+  }
+
+  uni.showLoading({ title: '保存中...' })
+  void resolveSaveFilePath(qrCodeUrl)
+    .then(filePath => saveImageToAlbum(filePath))
+    .catch((error) => {
       uni.hideLoading()
       uni.showToast({
-        title: '获取图片失败',
+        title: error instanceof Error ? error.message : '保存失败',
         icon: 'none',
       })
+    })
+}
+
+function resolveSaveFilePath(src: string) {
+  return new Promise<string>((resolve, reject) => {
+    if (!src) {
+      reject(new Error('访客码生成中'))
+      return
+    }
+
+    if (src.startsWith('http://') || src.startsWith('https://')) {
+      uni.downloadFile({
+        url: src,
+        success: (res) => {
+          if (res.statusCode === 200 && res.tempFilePath) {
+            resolve(res.tempFilePath)
+            return
+          }
+          reject(new Error('下载图片失败'))
+        },
+        fail: () => reject(new Error('下载图片失败')),
+      })
+      return
+    }
+
+    if (src.startsWith('wxfile://') || src.startsWith('file://') || src.startsWith('/')) {
+      resolve(src)
+      return
+    }
+
+    uni.getImageInfo({
+      src,
+      success: image => resolve(image.path),
+      fail: () => reject(new Error('获取图片失败')),
+    })
+  })
+}
+
+function saveImageToAlbum(filePath: string) {
+  uni.saveImageToPhotosAlbum({
+    filePath,
+    success: () => {
+      uni.hideLoading()
+      uni.showToast({
+        title: '保存成功',
+        icon: 'success',
+      })
+    },
+    fail: (err) => {
+      uni.hideLoading()
+      const errMsg = err?.errMsg || ''
+      if (errMsg.includes('auth deny') || errMsg.includes('authorize no response')) {
+        uni.showModal({
+          title: '提示',
+          content: '需要您授权保存相册',
+          success(res) {
+            if (res.confirm) {
+              uni.openSetting({
+                success(settingdata) {
+                  if (settingdata.authSetting['scope.writePhotosAlbum']) {
+                    uni.showToast({ title: '授权成功,请重新点击下载', icon: 'none' })
+                  }
+                  else {
+                    uni.showToast({ title: '授权失败,无法保存', icon: 'none' })
+                  }
+                },
+              })
+            }
+          },
+        })
+        return
+      }
+      uni.showToast({
+        title: '保存失败',
+        icon: 'none',
+      })
+      console.error('save visitor code failed', err)
     },
   })
 }
 
-// 触发弹窗显示
-function handleShowLoginPopup() {
-  showLoginPopup.value = true
+function handleOpenParkFlow() {
+  uni.navigateTo({
+    url: `/pages/aboutUs/index?title=${encodeURIComponent('入园流程')}&code=CONTENT_PARK_FLOW_HTML`,
+  })
 }
 
-// 取消弹窗
-function handleCancelLogin() {
-  showLoginPopup.value = false
+function handleOpenApprovalTodo() {
+  uni.navigateTo({ url: '/pages/reviewList/index' })
 }
 
-// 确认登录(获取手机号回调)
-function handleConfirmLogin(e: any) {
-  showLoginPopup.value = false
-  if (e.detail.errMsg === 'getPhoneNumber:ok') {
-    // 用户同意授权,这里可以拿到 e.detail.code,发送给后端换取手机号
-    console.log('获取手机号成功:', e.detail)
-    isLoggedIn.value = true // 模拟登录成功状态
-    uni.showToast({
-      title: '登录成功',
-      icon: 'success',
-    })
-  }
-  else {
-    // 用户拒绝授权
-    console.log('用户拒绝授权手机号:', e.detail.errMsg)
-    uni.showToast({
-      title: '已取消登录',
-      icon: 'none',
-    })
-  }
+function handleOpenVisitRecords() {
+  uni.navigateTo({
+    url: '/pages/reception/index',
+  })
 }
+
+function buildVisitorCodeSharePath(orderUuid?: string | null) {
+  const uuid = orderUuid || currentOrder.value?.orderUuid
+  return uuid ? `/pages/visitorCode/index?uuid=${encodeURIComponent(uuid)}` : '/pages/index/index'
+}
+
+onShareAppMessage((res) => {
+  const orderUuid = res?.target?.dataset?.orderUuid as string | undefined
+  const visitedUserDisplay = res?.target?.dataset?.visitedUserDisplay as string | undefined
+  return {
+    title: visitedUserDisplay ? `${visitedUserDisplay}的访客码` : '访客码分享',
+    path: buildVisitorCodeSharePath(orderUuid),
+  }
+})
+
+onShow(() => {
+  authStore.restoreSession()
+  void loadVisitorCodes()
+  void loadEmployeeDashboardCounts()
+})
 
 definePage({
   name: 'home',
@@ -157,48 +376,39 @@
 
 <template>
   <view class="relative">
-    <!-- 背景图 -->
     <image src="/static/home/bg.png" class="absolute left-0 top-0 z-0 h-[1624rpx] w-full" mode="scaleToFill" />
 
-    <!-- 内容区 -->
     <view class="relative z-4" :style="{ paddingTop: `${safeAreaInsets?.top || 44}px` }">
-      <!-- 导航栏标题 -->
       <view class="mb-[64rpx] h-[88rpx] flex items-center px-[32rpx]">
         <text class="text-[36rpx] text-white font-medium">
           预约小程序
         </text>
       </view>
 
-      <!-- 欢迎使用区 -->
-      <view class="mb-[36rpx] flex items-center pl-[26rpx] pr-[22rpx]" @click="!isLoggedIn && handleShowLoginPopup()">
+      <view class="mb-[36rpx] flex items-center pl-[26rpx] pr-[22rpx]">
         <image src="/static/home/hi.png" class="mr-[24rpx] h-[96rpx] w-[212rpx]" mode="aspectFit" />
-        <view class="welcome-title flex items-center text-[56rpx] text-white font-[600]">
-          <template v-if="!isLoggedIn">
-            <text>欢迎使用,</text>
-            <view class="login-text relative">
-              请登录
-              <view class="login-text__underline absolute bottom-0 left-0 h-[4rpx] w-full bg-white" />
-            </view>
-          </template>
-          <template v-else>
-            <text>王丽</text>
-          </template>
+        <view class="flex flex-col">
+          <view class="welcome-title flex items-center text-[56rpx] text-white font-[600]">
+            <text>{{ profile?.displayName || '用户' }}</text>
+          </view>
+          <text class="mt-[8rpx] text-[24rpx] text-white/90">
+            {{ userRoleText }}<template v-if="secondaryText"> · {{ secondaryText }}</template>
+          </text>
         </view>
       </view>
 
-      <!-- 待办/待接待区 -->
-      <view v-if="userRole === 'employee'" class="mb-[32rpx] w-full pl-[26rpx] pr-[22rpx]" style="box-sizing: border-box;">
+      <view v-if="isEmployee" class="mb-[32rpx] w-full pl-[26rpx] pr-[22rpx]" style="box-sizing: border-box;">
         <view
           class="rounded-[16rpx] p-[24rpx] pb-[26rpx]"
           style="background: linear-gradient(180deg, #CCE4FE 0%, #FAFCFD 40%, #FDFDFD 100%); box-shadow: 0 4rpx 8rpx 0 rgba(0, 104, 255, 0.2);border: 1rpx solid #fff;"
         >
           <view class="flex gap-[30rpx]">
-            <!-- 待办 -->
             <view
               class="h-[126rpx] w-[312rpx] flex rounded-[20rpx] pl-[38rpx] pt-[20rpx]"
               style="background: url('/static/home/toDo_bg.png') no-repeat center center;
               background-size: 100%;box-sizing: border-box;
               box-shadow: 0 2rpx 8rpx 0 rgba(119, 144, 213, 0.3)"
+              @click="handleOpenApprovalTodo"
             >
               <image src="/static/home/toDo.png" class="mr-[14rpx] h-[100rpx] w-[108rpx]" />
               <view class="mt-[8rpx] flex flex-col">
@@ -206,16 +416,16 @@
                   待办
                 </text>
                 <text class="text-[32rpx] text-[#056DFF] font-bold">
-                  -
+                  {{ approvalTodoCount }}
                 </text>
               </view>
             </view>
-            <!-- 待接待 -->
             <view
               class="h-[126rpx] w-[312rpx] flex items-center rounded-[20rpx] pl-[38rpx] pt-[20rpx]"
               style="background: url('/static/home/wait_bg.png') no-repeat center center;
               background-size: 100%;box-sizing: border-box;
               box-shadow: 0 2rpx 8rpx 0 rgba(119, 144, 213, 0.3)"
+              @click="handleOpenVisitRecords"
             >
               <image src="/static/home/wait.png" class="mr-[14rpx] h-[100rpx] w-[108rpx]" />
               <view class="mt-[8rpx] flex flex-col">
@@ -223,7 +433,7 @@
                   待接待
                 </text>
                 <text class="text-[32rpx] text-[#F86911] font-bold">
-                  -
+                  {{ visitRecordCount }}
                 </text>
               </view>
             </view>
@@ -231,9 +441,7 @@
         </view>
       </view>
 
-      <!-- 功能区 -->
       <view class="flex gap-[16rpx] pl-[24rpx] pr-[22rpx]">
-        <!-- 预约 -->
         <view
           class="h-[240rpx] w-[344rpx] flex flex-1 flex-col items-center justify-center rounded-[20rpx]"
           style="background: linear-gradient(180deg, #DEF4FF 0%, #FFFFFF 100%);border: 1rpx solid #fff;box-shadow: 0 4rpx 10rpx 0 rgba(0, 104, 255, 0.16)"
@@ -244,10 +452,9 @@
             预约
           </text>
         </view>
-        <!-- 预约记录 & 常用来访人 -->
         <view class="flex flex-1 flex-col gap-[16rpx]">
-          <view 
-            class="flex flex-1 items-center rounded-[20rpx] px-[40rpx]" 
+          <view
+            class="flex flex-1 items-center rounded-[20rpx] px-[40rpx]"
             style="background: linear-gradient(180deg, #FFF7E0 0%, #FFFFFF 100%);box-shadow: 0 4rpx 10rpx 0 rgba(0, 104, 255, 0.16)"
             @click="() => { router.push({ name: 'record' }) }"
           >
@@ -259,6 +466,7 @@
           <view
             class="flex flex-1 items-center rounded-[20rpx] px-[40rpx]"
             style="background: linear-gradient(180deg, #E0FAEE 0%, #FFFFFF 100%);border: 1rpx solid #fff;box-shadow: 0 4rpx 10rpx 0 rgba(0, 104, 255, 0.16)"
+            @click="uni.navigateTo({ url: '/pages/choosePerson/index?mode=manage' })"
           >
             <image src="/static/home/visitor.png" class="mr-[16rpx] h-[64rpx] w-[64rpx]" />
             <text class="text-[28rpx] text-[#000000]">
@@ -267,74 +475,111 @@
           </view>
         </view>
       </view>
-      <!-- 登录后:我的访客码区域 -->
-      <view v-if="isLoggedIn" class="mt-[32rpx] px-[24rpx]">
-        <!-- 头部标题与切换 -->
+
+      <view class="mt-[32rpx] px-[24rpx]">
         <view class="mb-[20rpx] flex items-center justify-between">
           <text class="text-[34rpx] text-[#1a1a1a]">
             我的访客码
           </text>
-          <view class="flex items-center">
+          <view v-if="visitorOrders.length > 0" class="flex items-center">
             <image
               src="/static/home/chevron_left.png"
               class="h-[40rpx] w-[40rpx] pr-[24rpx]"
-              @click="handlePrevVisitor"
+              @click="handlePrevOrder"
             />
             <text class="text-[28rpx] text-[#999999] line-height-[40rpx]">
-              {{ currentVisitorIndex + 1 }}/{{ visitorList.length }}
+              {{ currentOrderIndex + 1 }}/{{ visitorOrders.length }}
             </text>
             <image
               src="/static/home/chevron_right.png"
               class="h-[40rpx] w-[40rpx] pl-[24rpx]"
-              @click="handleNextVisitor"
+              @click="handleNextOrder"
             />
           </view>
         </view>
 
-        <!-- 访客码卡片轮播 -->
+        <view
+          v-if="loadingVisitorCodes"
+          class="h-[420rpx] flex items-center justify-center rounded-[12rpx] bg-white text-[28rpx] text-[#999999]"
+          style="box-shadow: 0 6rpx 10rpx 0 rgba(0, 104, 255, 0.16);"
+        >
+          加载访客码中...
+        </view>
+
+        <view
+          v-else-if="visitorOrders.length === 0"
+          class="h-[420rpx] flex items-center justify-center rounded-[12rpx] bg-white text-[28rpx] text-[#999999]"
+          style="box-shadow: 0 6rpx 10rpx 0 rgba(0, 104, 255, 0.16);"
+        >
+          暂无可展示的访客码
+        </view>
+
         <swiper
-          class="min-h-[840rpx] w-full"
-          :current="currentVisitorIndex"
+          v-else
+          class="min-h-[760rpx] w-full"
+          :current="currentOrderIndex"
           style="box-shadow: 0 6rpx 10rpx 0 rgba(0, 104, 255, 0.16);"
           @change="onSwiperChange"
         >
-          <swiper-item v-for="(item) in visitorList" :key="item.id">
+          <swiper-item v-for="order in visitorOrders" :key="order.orderId">
             <view
               class="h-full w-full flex flex-col rounded-[12rpx] bg-white py-[24rpx]"
               style="box-sizing: border-box;overflow: hidden;"
             >
-              <!-- 顶部信息 -->
               <view class="flex flex-col items-center">
-                <text class="mb-[16rpx] text-[28rpx] text-[#1a1a1a]">
-                  {{ item.name }}({{ item.department }})
+                <text class="mb-[16rpx] px-[32rpx] text-center text-[28rpx] text-[#1a1a1a]">
+                  {{ order.visitedUserDisplay }}
                 </text>
                 <view
-                  class="mb-[28rpx] mt-[16rpx] h-[48rpx] w-full flex items-center justify-center"
+                  class="mb-[28rpx] mt-[16rpx] h-[48rpx] w-full flex items-center justify-center px-[20rpx]"
                   style="background: linear-gradient(90deg, rgba(0,104,255,0) 0%, rgba(0,104,255) 50%, rgba(0,104,255,0) 100%);"
                 >
-                  <text class="text-[24rpx] text-white">
-                    {{ item.time }}
+                  <text class="truncate text-[24rpx] text-white">
+                    {{ order.timeText }}
                   </text>
                 </view>
               </view>
 
-              <!-- 二维码区 -->
-              <view class="relative flex items-center justify-center px-[64rpx]">
-                <image src="/static/home/left.png" class="absolute left-[32rpx] h-[48rpx] w-[24rpx]" @click="handlePrevVisitor" />
-                <image :src="item.qrCode" class="h-[448rpx] w-[448rpx]" mode="aspectFit" />
-                <image src="/static/home/right.png" class="absolute right-[32rpx] h-[48rpx] w-[24rpx]" @click="handleNextVisitor" />
+              <view class="relative flex items-center justify-center px-[72rpx]">
+                <image
+                  v-if="order.qrCodes.length > 1"
+                  src="/static/home/left.png"
+                  class="absolute left-[32rpx] h-[48rpx] w-[24rpx]"
+                  @click="handlePrevTicket(order.orderId)"
+                />
+                <image
+                  :src="currentOrder?.orderId === order.orderId ? currentTicket?.qrCode : order.qrCodes[0]?.qrCode"
+                  class="h-[360rpx] w-[360rpx]"
+                  mode="aspectFit"
+                />
+                <image
+                  v-if="order.qrCodes.length > 1"
+                  src="/static/home/right.png"
+                  class="absolute right-[32rpx] h-[48rpx] w-[24rpx]"
+                  @click="handleNextTicket(order.orderId)"
+                />
               </view>
 
-              <!-- 访客名称 -->
-              <view class="mt-[20rpx] flex justify-center pb-[36rpx]">
+              <view class="mt-[12rpx] flex flex-col items-center justify-center gap-[8rpx] pb-[24rpx]">
                 <text class="text-[24rpx] text-[rgba(26,26,26,0.5)]">
-                  {{ item.visitorName }}的访客码
+                  {{ (currentOrder?.orderId === order.orderId ? currentTicket?.visitorName : order.qrCodes[0]?.visitorName) }}的访客码
+                </text>
+                <text
+                  v-if="order.accessAreaText"
+                  class="px-[40rpx] text-center text-[22rpx] text-[rgba(26,26,26,0.45)]"
+                >
+                  通行区域:{{ order.accessAreaText }}
+                </text>
+                <text v-if="order.qrCodes.length > 1" class="text-[22rpx] text-[rgba(26,26,26,0.45)]">
+                  {{ currentTicketIndex(order.orderId) + 1 }}/{{ order.qrCodes.length }}
                 </text>
               </view>
 
-              <!-- 底部操作 -->
-              <view class="h-[112rpx] flex items-center justify-center gap-[32rpx] pt-[28rpx]" style="border-top: 2rpx dashed #D8D8D8;border-radius: 12rpx;">
-                <view class="w-[144rpx] flex flex-col items-center justify-center" @click="handleDownloadVisitorCode(item.qrCode)">
+              <view class="h-[104rpx] flex items-center justify-center gap-[32rpx] pt-[20rpx]" style="border-top: 2rpx dashed #D8D8D8;border-radius: 12rpx;">
+                <view
+                  class="w-[144rpx] flex flex-col items-center justify-center"
+                  @click="handleDownloadVisitorCode(currentOrder?.orderId === order.orderId ? currentTicket?.qrCode || '' : order.qrCodes[0]?.qrCode || '')"
+                >
                   <image src="/static/home/download.png" class="mb-[8rpx] h-[40rpx] w-[40rpx]" />
                   <text class="text-[24rpx] text-[#1a1a1a]">
                     下载访客码
@@ -343,6 +588,8 @@
                 <button
                   class="m-0 w-[144rpx] flex flex-col items-center justify-center border-none bg-transparent p-0 line-height-normal after:border-none"
                   open-type="share"
+                  :data-order-uuid="order.orderUuid"
+                  :data-visited-user-display="order.visitedUserDisplay"
                 >
                   <image src="/static/home/share.png" class="mb-[8rpx] h-[40rpx] w-[40rpx]" />
                   <text class="text-[24rpx] text-[#1a1a1a]">
@@ -355,55 +602,20 @@
         </swiper>
       </view>
 
-      <!-- 备案号 (登录后显示在底部) -->
-      <view v-if="isLoggedIn" class="mt-[48rpx] flex justify-center pb-[32rpx]">
+      <view class="mt-[48rpx] flex justify-center pb-[32rpx]">
         <text class="text-[24rpx] text-[#999999]">
           备案号:2435345342
         </text>
       </view>
     </view>
 
-    <!-- 悬浮流程图标 -->
-    <image src="/static/home/process.png" class="fixed bottom-[482rpx] right-[-42rpx] z-5 h-[128rpx] w-[128rpx]" />
-
-    <!-- 未登录提示弹窗 -->
-    <wd-popup v-model="showLoginPopup" custom-class="login-popup" position="center">
-      <view
-        class="box-border w-[590rpx] flex flex-col items-center px-[40rpx] pb-[32rpx] pt-[32rpx]"
-        style="background: linear-gradient(180deg, #D1E6FE 0%, #FFFFFF 34%, #FFFFFF 100%);"
-      >
-        <!-- 标题 -->
-        <text class="text-[36rpx] text-[#000000] font-bold leading-[50rpx]">
-          提示
-        </text>
-
-        <!-- 图标 -->
-        <image src="/static/home/tips.png" class="mt-[56rpx] h-[202rpx] w-[202rpx]" mode="aspectFit" />
-
-        <!-- 提示文案 -->
-        <text class="mt-[14rpx] text-[32rpx] text-[#000000] leading-[44rpx]">
-          您还未登录,请先登录
-        </text>
-
-        <!-- 操作按钮 -->
-        <view class="mt-[32rpx] w-full flex justify-between gap-[24rpx]">
-          <view class="h-[80rpx] flex flex-1 items-center justify-center rounded-[40rpx] bg-[#F5F5F5]" @click="handleCancelLogin">
-            <text class="text-[32rpx] text-[#989898] font-medium">
-              再想想
-            </text>
-          </view>
-          <button
-            class="m-0 h-[80rpx] flex flex-1 items-center justify-center rounded-[40rpx] border-none bg-[#0C6FFF] p-0 after:border-none"
-            open-type="getPhoneNumber"
-            @getphonenumber="handleConfirmLogin"
-          >
-            <text class="text-[32rpx] text-[#FFFFFF] font-medium">
-              去登录
-            </text>
-          </button>
-        </view>
-      </view>
-    </wd-popup>
+    <image
+      src="/static/home/process.png"
+      class="fixed right-[-42rpx] z-5 h-[128rpx] w-[128rpx]"
+      :style="{ bottom: `${parkFlowBottomPx}px` }"
+      @click="handleOpenParkFlow"
+    />
+    <canvas canvas-id="visitorQrCanvas" class="qr-canvas" />
   </view>
 </template>
 
@@ -416,14 +628,12 @@
   font-family: 'YouSheBiaoTiHei', sans-serif;
 }
 
-.login-text {
-  display: inline-block;
-  position: relative;
-  border-bottom: 4rpx solid #fff;
-  padding-bottom: 2rpx;
-}
-
-:deep(.login-popup) {
-  border-radius: 32rpx;
+.qr-canvas {
+  position: fixed;
+  left: -9999px;
+  top: -9999px;
+  width: 300px;
+  height: 300px;
+  pointer-events: none;
 }
 </style>

--
Gitblit v1.7.1