longz
2026-04-27 25df88d0ab9d7444ed1d162b9efa1259ac2f6f66
yuyueMiniProgram/src/pages/review/index.vue
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { ref } from 'vue'
import { cancelMyAppointment, deleteMyAppointment, fetchMyAppointmentDetail } from '@/api/appointment'
import type { ApprovalFlowNodeItem, MiniMyAppointmentDetailItem } from '@/types/appointment'
definePage({
  name: 'review',
@@ -16,51 +17,12 @@
const showCancelModal = ref(false)
const showDeleteModal = ref(false)
const appointmentStatus = ref('reviewing') // 'reviewing', 'pending_effect', 'effective', 'cancelled', 'invalid', 'rejected'
// 接收页面参数设置初始状态
import { onLoad } from '@dcloudio/uni-app'
onLoad((options) => {
  if (options && options.status) {
    appointmentStatus.value = options.status
  }
})
function handleBack() {
  router.back()
}
function handleCancel() {
  showCancelModal.value = true
}
function handleDelete() {
  showDeleteModal.value = true
}
function confirmCancel() {
  showCancelModal.value = false
  uni.showToast({ title: '已取消预约', icon: 'success' })
  setTimeout(() => {
    router.back()
  }, 1000)
}
function confirmDelete() {
  showDeleteModal.value = false
  uni.showToast({ title: '已删除记录', icon: 'success' })
  setTimeout(() => {
    router.back()
  }, 1000)
}
function handleRebook() {
  uni.navigateTo({ url: '/pages/appointment/index' })
}
function handleViewVisitorCode() {
  uni.navigateTo({ url: '/pages/visitorCode/index' })
}
const detail = ref<MiniMyAppointmentDetailItem | null>(null)
const loading = ref(false)
const canceling = ref(false)
const deleting = ref(false)
const orderId = ref<number>()
const orderUuid = ref('')
const appointmentIcons = {
  male: '/static/appointment/man.png',
@@ -68,342 +30,323 @@
  company: '/static/appointment/company.png',
}
const visitors = [
  {
    id: 1,
    name: '张三',
    gender: 'male',
    phone: '5120**********258',
    company: '成都xxxxxx有限公司',
    plate: '苏U123456'
  },
  {
    id: 2,
    name: '张三',
    gender: 'female',
    phone: '5120**********258',
    company: '成都xxxxxx有限公司'
  },
  {
    id: 3,
    name: '张三',
    gender: 'female',
    phone: '5120**********258',
    company: '成都xxxxxx有限公司'
const appointmentStatus = computed(() => detail.value?.displayStatus || 'PENDING_VISIT')
const invalidLike = computed(() => appointmentStatus.value === 'INVALID')
const headerBg = computed(() => invalidLike.value ? '/static/review/bg-grey.png' : '/static/review/bg-t.png')
const titleText = computed(() => detail.value?.statusText || '预约记录')
const showVisitorCodeEntry = computed(() => appointmentStatus.value === 'PENDING_VISIT' || appointmentStatus.value === 'EFFECTIVE')
function handleBack() {
  router.back()
}
function handleCancel() {
  if (!detail.value?.cancelable) {
    return
  }
]
  showCancelModal.value = true
}
async function confirmCancel() {
  if (!orderId.value || !orderUuid.value || canceling.value) {
    return
  }
  canceling.value = true
  try {
    await cancelMyAppointment(orderId.value, orderUuid.value)
    showCancelModal.value = false
    uni.showToast({ title: '已取消预约', icon: 'success' })
    await loadDetail()
  }
  catch (error) {
    uni.showToast({
      title: error instanceof Error ? error.message : '取消预约失败',
      icon: 'none',
    })
  }
  finally {
    canceling.value = false
  }
}
function handleRebook() {
  if (!detail.value) {
    return
  }
  uni.navigateTo({
    url: `/pages/appointment/index?rebookOrderId=${detail.value.orderId}&rebookOrderUuid=${encodeURIComponent(detail.value.orderUuid)}`,
  })
}
function handleDelete() {
  if (!detail.value?.deletable) {
    return
  }
  showDeleteModal.value = true
}
async function confirmDelete() {
  if (!orderId.value || !orderUuid.value || deleting.value) {
    return
  }
  deleting.value = true
  try {
    await deleteMyAppointment(orderId.value, orderUuid.value)
    showDeleteModal.value = false
    uni.showToast({ title: '已删除记录', icon: 'success' })
    setTimeout(() => {
      uni.navigateBack()
    }, 500)
  }
  catch (error) {
    uni.showToast({
      title: error instanceof Error ? error.message : '删除记录失败',
      icon: 'none',
    })
  }
  finally {
    deleting.value = false
  }
}
function handleViewVisitorCode() {
  uni.switchTab({ url: '/pages/index/index' })
}
function visitorGenderIcon(gender?: string | null) {
  return gender === 'FEMALE' ? appointmentIcons.female : appointmentIcons.male
}
function flowStatusText(status?: string | null) {
  switch (status) {
    case 'APPROVED':
      return '已通过'
    case 'REJECTED':
      return '已驳回'
    case 'CANCELLED':
      return '已取消'
    case 'SKIPPED':
      return '已跳过'
    case 'PENDING':
      return '审批中'
    default:
      return '待处理'
  }
}
function flowStatusClass(status?: string | null) {
  switch (status) {
    case 'APPROVED':
      return 'text-[#52C41A]'
    case 'REJECTED':
      return 'text-[#FF4D4F]'
    case 'CANCELLED':
      return 'text-[#999999]'
    case 'PENDING':
      return 'text-[#0068FF]'
    default:
      return 'text-[#999999]'
  }
}
function flowUsers(node: ApprovalFlowNodeItem) {
  return node.groups.flatMap(group => group.users.map(user => ({
    id: user.taskId,
    name: user.userName || group.groupName || '审批人',
    status: user.taskStatus,
    time: user.actionTime,
  })))
}
async function loadDetail() {
  if (!orderId.value || !orderUuid.value) {
    uni.showToast({ title: '缺少预约参数', icon: 'none' })
    return
  }
  loading.value = true
  try {
    detail.value = await fetchMyAppointmentDetail(orderId.value, orderUuid.value)
  }
  catch (error) {
    uni.showToast({
      title: error instanceof Error ? error.message : '加载预约详情失败',
      icon: 'none',
    })
  }
  finally {
    loading.value = false
  }
}
onLoad((options) => {
  orderId.value = options?.orderId ? Number(options.orderId) : undefined
  orderUuid.value = options?.orderUuid || ''
  void loadDetail()
})
</script>
<template>
  <view class="min-h-screen bg-[#F6FBFF] w-full relative box-border flex flex-col pb-[160rpx]">
    <!-- 顶部背景图 (被覆盖层) -->
    <view class="absolute left-0 top-0 z-0 h-[480rpx] w-full overflow-hidden">
      <image :src="['cancelled', 'invalid', 'rejected'].includes(appointmentStatus) ? '/static/review/bg-grey.png' : '/static/review/bg-t.png'" class="w-full h-full" mode="aspectFill" />
      <image :src="headerBg" class="w-full h-full" mode="aspectFill" />
    </view>
    <!-- 整体内容背景图 (通过 z-index 层级置于顶部背景之上,利用背景图自身弧度产生遮盖效果) -->
    <view class="absolute left-0 top-[410rpx] w-full z-0 pointer-events-none" style="height: calc(100% - 380rpx);">
      <image src="/static/review/contentbg.png" class="w-full h-full" mode="scaleToFill" />
    </view>
    <!-- 自定义导航栏 -->
    <view class="relative z-50 w-full shrink-0" :style="{ height: (statusBarHeight + navBarHeight) + 'px' }">
      <!-- 固定在顶部的导航栏 -->
      <view class="fixed left-0 top-0 w-full z-50 overflow-hidden" :style="{ height: (statusBarHeight + navBarHeight) + 'px' }">
        <!-- 截取顶部背景图作为 navBar 背景,防止滚动时内容透出 -->
        <image :src="['cancelled', 'invalid', 'rejected'].includes(appointmentStatus) ? '/static/review/bg-grey.png' : '/static/review/bg-t.png'" class="absolute left-0 top-0 w-full z-0" mode="widthFix" />
        <view class="flex items-center justify-between w-full absolute left-0 z-10"
          :style="{ top: statusBarHeight + 'px', height: navBarHeight + 'px' }">
        <image :src="headerBg" class="absolute left-0 top-0 w-full z-0" mode="widthFix" />
        <view class="flex items-center justify-between w-full absolute left-0 z-10" :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>
    <!-- 内容区 -->
    <view class="relative z-10 px-[24rpx] flex-1">
      <!-- 审批状态区 -->·
      <view class="px-[24rpx] mb-[108rpx]" v-if="['reviewing', 'rejected'].includes(appointmentStatus)">
        <view class="flex items-center mb-[10rpx]">
          <image v-if="appointmentStatus === 'reviewing'" src="/static/review/chapter.png" class="w-[40rpx] h-[40rpx] mr-[8rpx]" mode="aspectFit" />
          <image v-else-if="appointmentStatus === 'rejected'" src="/static/review/Pending.png" class="w-[40rpx] h-[40rpx] mr-[8rpx]" mode="aspectFit" />
          <text class="text-[32rpx] text-white font-bold">审批中...</text>
        </view>
        <!-- 分割线 -->
        <view class="w-full h-[2rpx] bg-white/20 mb-[24rpx]"></view>
      <view v-if="loading" class="px-[24rpx] pt-[120rpx] text-center text-white text-[28rpx]">
        加载中...
      </view>
        <!-- 进度条模拟 -->
        <view class="flex items-start justify-between relative px-[16rpx]">
      <template v-else-if="detail">
        <view class="px-[24rpx] mb-[32rpx]" :class="showVisitorCodeEntry ? '' : 'mt-[76rpx]'">
          <view class="flex items-center mb-[20rpx]">
            <image
              :src="appointmentStatus === 'EFFECTIVE' ? '/static/review/success.png' : invalidLike ? '/static/review/cancel.png' : '/static/review/Pending.png'"
              class="w-[40rpx] h-[40rpx] mr-[8rpx]"
              mode="aspectFit"
            />
            <text class="text-[32rpx] text-white font-bold">{{ titleText }}</text>
          </view>
          <!-- 步骤1 -->
          <view class="flex flex-col items-center relative z-10 w-[60rpx]">
            <view class="w-[40rpx] h-[40rpx] rounded-full bg-white flex items-center justify-center mb-[24rpx] relative">
              <!-- 波纹动画层 -->
              <template v-if="['reviewing', 'rejected'].includes(appointmentStatus)">
                <view class="ripple-wave absolute w-full h-full rounded-full bg-white"></view>
                <view class="ripple-wave ripple-delay absolute w-full h-full rounded-full bg-white"></view>
              </template>
              <text class="text-[#0068FF] text-[24rpx] font-bold z-10">1</text>
          <view class="w-full h-[2rpx] bg-white/30 mb-[24rpx]" />
          <view v-if="showVisitorCodeEntry" class="flex items-center mb-[24rpx]">
            <view class="bg-[#52C41A] w-full rounded-full h-[60rpx] px-[18rpx] flex items-center justify-center">
              <text class="text-white text-[26rpx] font-medium">{{ detail.visitTimeText }}</text>
            </view>
            <text class="text-white text-[20rpx] mb-[4rpx] whitespace-nowrap">经办人</text>
            <text class="text-white/80 text-[18rpx] whitespace-nowrap">2-3 15:05</text>
          </view>
          <!-- 箭头 1 -->
          <view class="flex-1 flex justify-center pt-[0rpx]">
            <image src="/static/review/arrow.png" class="w-[64rpx] h-[40rpx]" mode="aspectFit" />
          <view v-if="showVisitorCodeEntry" class="flex justify-center mb-[48rpx]">
            <view class="border-[2rpx] border-solid border-white/60 rounded-full w-[280rpx] h-[64rpx] flex items-center justify-center bg-white/20 transition-opacity active:opacity-70" @click="handleViewVisitorCode">
              <wd-icon name="view" size="32rpx" color="#ffffff" class="mr-[12rpx]" />
              <text class="text-white text-[28rpx]">查看访客码</text>
            </view>
          </view>
        </view>
        <view class="flex items-end mb-[26rpx] px-[8rpx]">
          <text class="text-[32rpx] text-[#000000] font-[500]">拜访信息</text>
          <text class="text-[24rpx] text-[#676767] font-[350] ml-[16rpx] mb-[4rpx]">您要见的人</text>
        </view>
        <view class="bg-white rounded-[16rpx] shadow-[0_4rpx_8rpx_rgba(6,47,108,0.1608)] w-full mb-[48rpx] px-[40rpx] py-[12rpx] box-border">
          <view class="flex flex-col">
            <view class="flex justify-between items-center h-[96rpx] border-b1">
              <text class="text-[#000000] text-[28rpx] font-normal">被访人姓名</text>
              <text class="text-[#666666] text-[28rpx] font-[350]">{{ detail.visitedUserDisplay }}</text>
            </view>
            <view class="flex justify-between items-center h-[96rpx] border-b1">
              <text class="text-[#000000] text-[28rpx] font-normal">拜访事由</text>
              <text class="text-[#666666] text-[28rpx] font-[350]">{{ detail.visitReasonText }}</text>
            </view>
            <view class="flex justify-between items-center h-[96rpx]">
              <text class="text-[#000000] text-[28rpx] font-normal">拜访时间</text>
              <text class="text-[#666666] text-[28rpx] font-[350] text-right ml-[24rpx]">{{ detail.visitTimeText }}</text>
            </view>
          </view>
        </view>
        <view class="flex items-end mb-[26rpx] px-[8rpx]">
          <text class="text-[32rpx] text-[#000000] font-[500]">来访人信息</text>
          <text class="text-[24rpx] text-[#676767] font-[350] ml-[16rpx] mb-[4rpx]">选择您及同行人员</text>
        </view>
        <view class="bg-white rounded-[16rpx] shadow-[0_4rpx_8rpx_rgba(6,47,108,0.1608)] w-full mb-[24rpx] box-border pb-[24rpx]">
          <view class="flex justify-between items-center px-[40rpx] py-[28rpx] border-b1">
            <text class="text-[#000000] text-[28rpx] font-normal">进场类型</text>
            <text class="text-[#616161] text-[28rpx] font-[350]">{{ detail.visitTypeText }}</text>
          </view>
          <!-- 步骤2 -->
          <view class="flex flex-col items-center relative z-10 w-[60rpx]">
            <view
              class="w-[40rpx] h-[40rpx] rounded-full flex items-center justify-center mb-[24rpx] relative"
              :class="appointmentStatus === 'rejected' ? 'bg-[#FF4D4F]' : 'bg-[#3B72FF]'"
          <view class="px-[32rpx] py-[16rpx]">
            <view
              v-for="(visitor, index) in detail.visitors"
              :key="visitor.visitorId"
              class="flex items-center py-[24rpx] border-b2 mb-[16rpx]"
              :class="index === detail.visitors.length - 1 ? 'last-visitor' : ''"
            >
              <template v-if="['reviewing', 'rejected'].includes(appointmentStatus)">
                <!-- 波纹动画层 (蓝底/红底白边) -->
                <view class="ripple-wave absolute w-full h-full rounded-full border-[4rpx] border-solid border-white box-border"></view>
                <view class="ripple-wave ripple-delay absolute w-full h-full rounded-full border-[4rpx] border-solid border-white box-border"></view>
                <!-- 内圈白边 -->
                <view class="absolute w-[40rpx] h-[40rpx] rounded-full border-[2rpx] border-solid border-white top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 box-border z-0"></view>
                <!-- 外圈半透明白边(固定光晕) -->
                <view class="absolute w-[52rpx] h-[52rpx] rounded-full border-[4rpx] border-solid border-white/40 top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 box-border z-0"></view>
              </template>
              <template v-if="appointmentStatus === 'rejected'">
                <!-- 驳回红叉 -->
                <wd-icon name="close" size="24rpx" color="#ffffff" class="z-10" />
              </template>
              <template v-else>
                <text class="text-white text-[24rpx] font-bold z-10">2</text>
              </template>
            </view>
            <text class="text-white text-[20rpx] font-medium whitespace-nowrap">部门负责人</text>
            <text v-if="appointmentStatus === 'rejected'" class="text-white/80 text-[18rpx] whitespace-nowrap">2-3 15:05</text>
          </view>
          <!-- 箭头 2 -->
          <view class="flex-1 flex justify-center pt-[0rpx]">
            <image src="/static/review/arrow.png" class="w-[64rpx] h-[40rpx] opacity-40" mode="aspectFit" />
          </view>
          <!-- 步骤3 -->
          <view class="flex flex-col items-center relative z-10 w-[60rpx]">
            <view class="w-[40rpx] h-[40rpx] rounded-full bg-white flex items-center justify-center mb-[24rpx]">
              <text class="text-[#989898] text-[24rpx] font-bold">3</text>
            </view>
            <text class="text-white text-[20rpx] whitespace-nowrap">属地单位领导</text>
          </view>
          <!-- 箭头 3 -->
          <view class="flex-1 flex justify-center pt-[0rpx]">
            <image src="/static/review/arrow.png" class="w-[64rpx] h-[40rpx] opacity-40" mode="aspectFit" />
          </view>
          <!-- 步骤4 -->
          <view class="flex flex-col items-center relative z-10 w-[60rpx]">
            <view class="w-[40rpx] h-[40rpx] rounded-full bg-white flex items-center justify-center mb-[24rpx]">
              <text class="text-[#989898] text-[24rpx] font-bold">4</text>
            </view>
            <text class="text-white text-[20rpx] whitespace-nowrap">访客主管部门</text>
          </view>
          <!-- 箭头 4 -->
          <view class="flex-1 flex justify-center pt-[0rpx]">
            <image src="/static/review/arrow.png" class="w-[64rpx] h-[40rpx] opacity-40" mode="aspectFit" />
          </view>
          <!-- 步骤5 -->
          <view class="flex flex-col items-center relative z-10 w-[60rpx]">
            <view class="w-[40rpx] h-[40rpx] rounded-full bg-white flex items-center justify-center mb-[24rpx]">
              <text class="text-[#989898] text-[24rpx] font-bold">5</text>
            </view>
            <text class="text-white text-[20rpx] whitespace-nowrap">主管部门领导</text>
          </view>
        </view>
        <!-- 驳回提示信息 -->
        <view v-if="appointmentStatus === 'rejected'" class="mt-[16rpx] ml-[-16rpx] w-[702rpx] bg-[rgba(255,230,189,0.6)] border-[2rpx] border-solid border-[#E73232] rounded-full h-[64rpx] flex items-center justify-center box-border relative -left-[8rpx]">
          <wd-icon name="info-circle" size="28rpx" color="#F5222D" class="mr-[12rpx]" />
          <text class="text-[#F5222D] text-[26rpx]">部门领导该日外出,请另外预约</text>
        </view>
      </view>
      <!-- 待生效状态区 -->
      <view class="px-[24rpx] mb-[32rpx]" v-if="appointmentStatus === 'pending_effect'">
        <view class="flex items-center mb-[20rpx]">
          <image src="/static/review/Pending.png" class="w-[40rpx] h-[40rpx] mr-[8rpx]" mode="aspectFit" />
          <text class="text-[32rpx] text-white font-bold">待生效...</text>
        </view>
        <!-- 分割线 -->
        <view class="w-full h-[2rpx] bg-white/30 mb-[24rpx]"></view>
        <!-- 有效期 -->
        <view class="flex items-center mb-[24rpx]">
          <view class="bg-[#52C41A] w-full rounded-full h-[60rpx] px-[18rpx] flex items-center justify-center">
            <text class="text-white text-[28rpx] font-medium">有效期: 2023-10-13 10:00 至 2023-10-13 18:00</text>
          </view>
        </view>
        <!-- 查看访客码按钮 -->
        <view class="flex justify-center mb-[85rpx]">
          <view class="border-[2rpx] border-solid border-white/60 rounded-full w-[280rpx] h-[64rpx] flex items-center justify-center bg-white/20 transition-opacity active:opacity-70" @click="handleViewVisitorCode">
            <wd-icon name="view" size="32rpx" color="#ffffff" class="mr-[12rpx]" />
            <text class="text-white text-[28rpx]">查看访客码</text>
          </view>
        </view>
      </view>
      <!-- 生效中状态区 -->
      <view class="px-[24rpx] mb-[32rpx]" v-if="appointmentStatus === 'effective'">
        <view class="flex items-center mb-[20rpx]">
          <image src="/static/review/success.png" class="w-[40rpx] h-[40rpx] mr-[8rpx]" mode="aspectFit" />
          <text class="text-[32rpx] text-white font-bold">生效中</text>
        </view>
        <!-- 分割线 -->
        <view class="w-full h-[2rpx] bg-white/30 mb-[24rpx]"></view>
        <!-- 有效期 -->
        <view class="flex items-center mb-[24rpx]">
          <view class="bg-[#52C41A] w-full rounded-full h-[60rpx] px-[18rpx] flex items-center justify-center">
            <text class="text-white text-[28rpx] font-medium">有效期: 2023-10-13 10:00 至 2023-10-13 18:00</text>
          </view>
        </view>
        <!-- 查看访客码按钮 -->
        <view class="flex justify-center mb-[85rpx]">
          <view class="border-[2rpx] border-solid border-white/60 rounded-full w-[280rpx] h-[64rpx] flex items-center justify-center bg-white/20 transition-opacity active:opacity-70" @click="handleViewVisitorCode">
            <wd-icon name="view" size="32rpx" color="#ffffff" class="mr-[12rpx]" />
            <text class="text-white text-[28rpx]">查看访客码</text>
          </view>
        </view>
      </view>
      <!-- 已失效/已取消 状态区 -->
      <view class="px-[24rpx] mb-[148rpx] mt-[76rpx] flex flex-col items-center" v-if="['cancelled', 'invalid'].includes(appointmentStatus)">
        <image src="/static/review/cancel.png" class="w-[40rpx] h-[40rpx] mb-[16rpx]" mode="aspectFit" />
        <text class="text-[32rpx] text-white font-bold">{{ appointmentStatus === 'cancelled' ? '已取消' : '已失效' }}</text>
      </view>
      <!-- 拜访信息标题 -->
      <view class="flex items-end mb-[26rpx] px-[8rpx]">
        <text class="text-[32rpx] text-[#000000] font-[500]">拜访信息</text>
        <text class="text-[24rpx] text-[#676767] font-[350] ml-[16rpx] mb-[4rpx]">您要见的人</text>
      </view>
      <!-- 拜访信息卡片 -->
      <view class="bg-white rounded-[16rpx] shadow-[0_4rpx_8rpx_rgba(6,47,108,0.1608)] w-full mb-[48rpx] px-[40rpx] py-[12rpx] box-border">
        <view class="flex flex-col">
          <view class="flex justify-between items-center h-[96rpx] border-b1">
            <text class="text-[#000000] text-[28rpx] font-normal">被访人姓名</text>
            <text class="text-[#666666] text-[28rpx] font-[350]">员工A(管理部)</text>
          </view>
          <view class="flex justify-between items-center h-[96rpx] border-b1">
            <text class="text-[#000000] text-[28rpx] font-normal">拜访事由</text>
            <text class="text-[#666666] text-[28rpx] font-[350]">xx区域/xx区域</text>
          </view>
          <view class="flex justify-between items-center h-[96rpx]">
            <text class="text-[#000000] text-[28rpx] font-normal">拜访时间</text>
            <text class="text-[#666666] text-[28rpx] font-[350]">2023-10-12 00:00 -2023-10-13 23:59</text>
          </view>
        </view>
      </view>
      <!-- 来访人信息标题 -->
      <view class="flex items-end mb-[26rpx] px-[8rpx]">
        <text class="text-[32rpx] text-[#000000] font-[500]">来访人信息</text>
        <text class="text-[24rpx] text-[#676767] font-[350] ml-[16rpx] mb-[4rpx]">选择您及同行人员</text>
      </view>
      <!-- 来访人信息卡片 -->
      <view class="bg-white rounded-[16rpx] shadow-[0_4rpx_8rpx_rgba(6,47,108,0.1608)] w-full mb-[24rpx] box-border pb-[24rpx]">
        <view class="flex justify-between items-center px-[40rpx] py-[28rpx] border-b1">
          <text class="text-[#000000] text-[28rpx] font-normal">进场类型</text>
          <text class="text-[#616161] text-[28rpx] font-[350]">人员+车辆</text>
        </view>
        <view class="px-[32rpx] py-[16rpx]">
          <view
            v-for="(visitor, index) in visitors"
            :key="visitor.id"
            class="flex items-center py-[24rpx] border-b2 mb-[16rpx]"
            :class="index !== visitors.length - 1 ? '' : ''"
          >
            <text class="text-[#333333] text-[30rpx] font-medium w-[100rpx] pl-[20rpx] shrink-0">{{ visitor.name }}</text>
            <view class="flex flex-col gap-[12rpx] flex-1 min-w-0 pl-[36rpx]">
              <view class="flex items-center text-[#B3B3B3] text-[26rpx]">
                <image :src="visitor.gender === 'male' ? appointmentIcons.male : appointmentIcons.female" class="w-[32rpx] h-[32rpx] mr-[12rpx] shrink-0" mode="aspectFit" />
                <text>{{ visitor.phone }}</text>
              </view>
              <view class="flex items-center text-[#B3B3B3] text-[26rpx]">
                <image :src="appointmentIcons.company" class="w-[32rpx] h-[32rpx] mr-[12rpx] shrink-0" mode="aspectFit" />
                <text class="truncate">{{ visitor.company }}</text>
              </view>
              <view v-if="visitor.plate" class="flex items-center text-[#B3B3B3] text-[26rpx]">
                <view class="w-[28rpx] h-[20rpx] border-[2rpx] border-solid border-[#0068FF] rounded-[4rpx] flex items-center justify-center mr-[12rpx]">
                  <text class="text-[#0068FF] text-[12rpx] transform scale-75">NO.</text>
              <text class="text-[#333333] text-[30rpx] font-medium w-[100rpx] pl-[20rpx] shrink-0">{{ visitor.name }}</text>
              <view class="flex flex-col gap-[12rpx] flex-1 min-w-0 pl-[36rpx]">
                <view class="flex items-center text-[#B3B3B3] text-[26rpx]">
                  <image :src="visitorGenderIcon(visitor.gender)" class="w-[32rpx] h-[32rpx] mr-[12rpx] shrink-0" mode="aspectFit" />
                  <text>{{ visitor.mobileMasked || '-' }}</text>
                </view>
                <text>{{ visitor.plate }}</text>
                <view class="flex items-center text-[#B3B3B3] text-[26rpx]">
                  <image :src="appointmentIcons.company" class="w-[32rpx] h-[32rpx] mr-[12rpx] shrink-0" mode="aspectFit" />
                  <text class="truncate">{{ visitor.companyName || '-' }}</text>
                </view>
                <view v-if="visitor.plateNo" class="flex items-center text-[#B3B3B3] text-[26rpx]">
                  <view class="w-[28rpx] h-[20rpx] border-[2rpx] border-solid border-[#0068FF] rounded-[4rpx] flex items-center justify-center mr-[12rpx]">
                    <text class="text-[#0068FF] text-[12rpx] transform scale-75">NO.</text>
                  </view>
                  <text>{{ visitor.plateNo }}</text>
                </view>
              </view>
            </view>
          </view>
        </view>
      </view>
      <!-- 提交时间卡片 -->
      <view class="bg-white rounded-[16rpx] shadow-[0_4rpx_8rpx_rgba(6,47,108,0.1608)] w-full mb-[32rpx] px-[40rpx] py-[12rpx] box-border">
        <view class="flex flex-col">
          <view class="flex justify-between items-center h-[96rpx]" :class="(appointmentStatus !== 'reviewing' && appointmentStatus !== 'invalid') ? 'border-b1' : ''">
            <text class="text-[#000000] text-[28rpx] font-normal">提交时间</text>
            <text class="text-[#666666] text-[28rpx] font-[350]">2023-10-13 12:00</text>
          </view>
          <view v-if="['pending_effect', 'effective'].includes(appointmentStatus)" class="flex justify-between items-center h-[96rpx]">
            <text class="text-[#000000] text-[28rpx] font-normal">审核通过时间</text>
            <text class="text-[#666666] text-[28rpx] font-[350]">2023-10-13 12:00</text>
          </view>
          <view v-if="appointmentStatus === 'cancelled'" class="flex justify-between items-center h-[96rpx]">
            <text class="text-[#000000] text-[28rpx] font-normal">取消时间</text>
            <text class="text-[#666666] text-[28rpx] font-[350]">2023-10-13 12:00</text>
        <view class="flex items-end mb-[26rpx] px-[8rpx]">
          <text class="text-[32rpx] text-[#000000] font-[500]">审批情况</text>
        </view>
        <view class="bg-white rounded-[16rpx] shadow-[0_4rpx_8rpx_rgba(6,47,108,0.1608)] w-full mb-[32rpx] px-[40rpx] py-[24rpx] box-border">
          <view v-for="node in detail.flowTree" :key="node.nodeCode" class="py-[20rpx]" :class="node !== detail.flowTree[detail.flowTree.length - 1] ? 'border-b1' : ''">
            <view class="flex items-center justify-between mb-[12rpx]">
              <text class="text-[#000000] text-[28rpx] font-medium">{{ node.nodeName }}</text>
              <text class="text-[24rpx]" :class="flowStatusClass(node.nodeStatus)">{{ flowStatusText(node.nodeStatus) }}</text>
            </view>
            <view v-for="user in flowUsers(node)" :key="user.id" class="flex items-center justify-between py-[8rpx]">
              <text class="text-[#666666] text-[24rpx]">{{ user.name }}</text>
              <text class="text-[#999999] text-[22rpx]">{{ user.time ? user.time.replace('T', ' ').slice(0, 16) : flowStatusText(user.status) }}</text>
            </view>
          </view>
        </view>
      </view>
        <view class="bg-white rounded-[16rpx] shadow-[0_4rpx_8rpx_rgba(6,47,108,0.1608)] w-full mb-[32rpx] px-[40rpx] py-[12rpx] box-border">
          <view class="flex justify-between items-center h-[96rpx]">
            <text class="text-[#000000] text-[28rpx] font-normal">提交时间</text>
            <text class="text-[#666666] text-[28rpx] font-[350]">{{ detail.createTime.replace('T', ' ').slice(0, 16) }}</text>
          </view>
        </view>
      </template>
    </view>
    <!-- 底部操作按钮 -->
    <view class="fixed bottom-0 left-0 w-[750rpx] h-[108rpx] bg-[#FFFFFF] rounded-t-[20rpx] box-content pb-[env(safe-area-inset-bottom)] z-[100] shadow-[inset_0_2rpx_0_0_#E1E1E1] flex justify-end items-center gap-[32rpx]" >
      <view
        v-if="appointmentStatus === 'reviewing'"
    <view class="fixed bottom-0 left-0 w-[750rpx] h-[108rpx] bg-[#FFFFFF] rounded-t-[20rpx] box-content pb-[env(safe-area-inset-bottom)] z-[100] shadow-[inset_0_2rpx_0_0_#E1E1E1] flex justify-end items-center gap-[32rpx]">
      <view
        v-if="detail?.cancelable"
        class="w-[240rpx] h-[76rpx] rounded-full flex items-center justify-center bg-[#F5F5F5] transition-opacity active:opacity-70"
        @click="handleCancel"
      >
        <image src="/static/review/reduce.png" class="w-[40rpx] h-[40rpx] mr-[8rpx]" mode="aspectFit" />
        <text class="text-[30rpx] text-[#999999] font-medium">取消预约</text>
      </view>
      <view
        v-if="appointmentStatus === 'rejected'"
        class="w-[240rpx] h-[76rpx] rounded-full flex items-center justify-center bg-white border-[2rpx] border-solid border-[#0068FF] transition-opacity active:opacity-70 box-border"
      >
        <text class="text-[30rpx] text-[#0068FF] font-medium">重新提交</text>
      </view>
      <view
        v-if="['cancelled', 'invalid'].includes(appointmentStatus)"
      <view
        v-else-if="detail?.deletable"
        class="w-[240rpx] h-[76rpx] rounded-full flex items-center justify-center bg-[#F5F5F5] transition-opacity active:opacity-70"
        @click="handleDelete"
      >
        <image src="/static/appointment/delete.png" class="w-[40rpx] h-[40rpx] mr-[8rpx]" mode="aspectFit" />
        <text class="text-[30rpx] text-[#999999] font-medium">删除</text>
        <wd-icon name="delete-thin" size="32rpx" color="#999999" class="mr-[8rpx]" />
        <text class="text-[30rpx] text-[#999999] font-medium">删除记录</text>
      </view>
      <view
      <view
        class="w-[240rpx] h-[76rpx] mr-[40rpx] rounded-full flex items-center justify-center bg-[#0068FF] shadow-[0_8rpx_24rpx_rgba(0,104,255,0.2)] transition-opacity active:opacity-70"
        @click="handleRebook"
      >
@@ -412,49 +355,36 @@
      </view>
    </view>
    <!-- 取消预约弹窗 -->
    <wd-popup v-model="showCancelModal" custom-style="border-radius: 32rpx; background: transparent;" :safe-area-inset-bottom="false">
      <view class="w-[600rpx] bg-white rounded-[32rpx] flex flex-col items-center pt-[48rpx] pb-[48rpx] px-[40rpx] box-border relative overflow-hidden" style="background: linear-gradient(180deg, #EBF4FF 0%, #FFFFFF 40%);">
        <text class="text-[34rpx] text-[#333333] font-bold mb-[32rpx]">提示</text>
        <image src="/static/review/cancelIcon.png" class="w-[202rpx] h-[202rpx] mb-[32rpx]" mode="aspectFit" />
        <text class="text-[30rpx] text-[#333333] mb-[64rpx]">是否取消本次预约</text>
        <view class="flex w-full justify-center gap-[32rpx]">
          <view
            class="w-[212rpx] h-[80rpx] rounded-full bg-[#F5F5F5] flex items-center justify-center transition-opacity active:opacity-70"
            @click="showCancelModal = false"
          >
          <view class="w-[212rpx] h-[80rpx] rounded-full bg-[#F5F5F5] flex items-center justify-center transition-opacity active:opacity-70" @click="showCancelModal = false">
            <text class="text-[#999999] text-[30rpx]">再想想</text>
          </view>
          <view
            class="w-[212rpx] h-[80rpx] rounded-full bg-[#0068FF] flex items-center justify-center transition-opacity active:opacity-70"
            @click="confirmCancel"
          >
            <text class="text-white text-[30rpx]">确认取消</text>
          <view class="w-[212rpx] h-[80rpx] rounded-full bg-[#0068FF] flex items-center justify-center transition-opacity active:opacity-70" @click="confirmCancel">
            <text class="text-white text-[30rpx]">{{ canceling ? '取消中...' : '确认取消' }}</text>
          </view>
        </view>
      </view>
    </wd-popup>
    <!-- 删除记录弹窗 -->
    <wd-popup v-model="showDeleteModal" custom-style="border-radius: 32rpx; background: transparent;" :safe-area-inset-bottom="false">
      <view class="w-[600rpx] bg-white rounded-[32rpx] flex flex-col items-center pt-[48rpx] pb-[48rpx] px-[40rpx] box-border relative overflow-hidden" style="background: linear-gradient(180deg, #EBF4FF 0%, #FFFFFF 40%);">
        <text class="text-[34rpx] text-[#333333] font-bold mb-[32rpx]">提示</text>
        <image src="/static/review/cancelIcon.png" class="w-[202rpx] h-[202rpx] mb-[32rpx]" mode="aspectFit" />
        <text class="text-[30rpx] text-[#333333] mb-[64rpx]">是否有确认删除此记录?</text>
        <wd-icon name="delete-thin" size="120rpx" color="#999999" class="mb-[32rpx]" />
        <text class="text-[30rpx] text-[#333333] mb-[24rpx]">删除后将不再出现在我的预约记录中</text>
        <text class="text-[24rpx] text-[#999999] mb-[64rpx]">仅当前账号隐藏,不影响后台查看</text>
        <view class="flex w-full justify-center gap-[32rpx]">
          <view
            class="w-[212rpx] h-[80rpx] rounded-full bg-[#F5F5F5] flex items-center justify-center transition-opacity active:opacity-70"
            @click="showDeleteModal = false"
          >
            <text class="text-[#999999] text-[30rpx]">再想想</text>
          <view class="w-[212rpx] h-[80rpx] rounded-full bg-[#F5F5F5] flex items-center justify-center transition-opacity active:opacity-70" @click="showDeleteModal = false">
            <text class="text-[#999999] text-[30rpx]">取消</text>
          </view>
          <view
            class="w-[212rpx] h-[80rpx] rounded-full bg-[#0068FF] flex items-center justify-center transition-opacity active:opacity-70"
            @click="confirmDelete"
          >
            <text class="text-white text-[30rpx]">确认删除</text>
          <view class="w-[212rpx] h-[80rpx] rounded-full bg-[#0068FF] flex items-center justify-center transition-opacity active:opacity-70" @click="confirmDelete">
            <text class="text-white text-[30rpx]">{{ deleting ? '删除中...' : '确认删除' }}</text>
          </view>
        </view>
      </view>
@@ -463,34 +393,21 @@
</template>
<style scoped lang="scss">
.border-b1{
.border-b1 {
  border-bottom: 2rpx solid #F5F5F5;
}
.border-b2{
.border-b2 {
  border-bottom: 2rpx dashed #F5F5F5;
  &:last-child{
  &:last-child {
    border-bottom: none;
    margin-bottom: 0;
  }
}
@keyframes ripple {
  0% {
    transform: scale(1);
    opacity: 0.6;
  }
  100% {
    transform: scale(1.6);
    opacity: 0;
  }
}
.ripple-wave {
  animation: ripple 2s cubic-bezier(0.4, 0, 0.2, 1) infinite;
.last-visitor {
  border-bottom: none;
  margin-bottom: 0;
}
.ripple-delay {
  animation-delay: 1s;
}
</style>
</style>