| | |
| | | <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', |
| | |
| | | |
| | | 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', |
| | |
| | | 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" |
| | | > |
| | |
| | | </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> |
| | |
| | | </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> |