<script setup lang="ts">
|
import { fetchMiniFieldConfigs } from '@/api/lookup'
|
import { deleteVisitor, fetchMyVisitors, setDefaultVisitor } from '@/api/visitor'
|
import { FIELD_CODES, formatVisitorFieldLabels } from '@/constants/fieldCodes'
|
import type { MiniFieldConfigItem } from '@/types/lookup'
|
import type { MiniVisitorListItem } from '@/types/visitor'
|
|
definePage({
|
name: 'commonVisitors',
|
layout: 'default',
|
style: {
|
navigationStyle: 'custom',
|
},
|
})
|
|
const router = useRouter()
|
const choosePersonAssetBase = '/static/choosePerson'
|
|
const sysInfo = uni.getSystemInfoSync()
|
const statusBarHeight = sysInfo.statusBarHeight || 0
|
const navBarHeight = 44
|
|
const appointmentIcons = {
|
male: '/static/appointment/man.png',
|
female: '/static/appointment/woman.png',
|
company: '/static/appointment/company.png',
|
}
|
|
const visitors = ref<MiniVisitorListItem[]>([])
|
const loading = ref(false)
|
const settingDefaultId = ref<number | null>(null)
|
const deletingId = ref<number | null>(null)
|
const openSwipeId = ref<number | null>(null)
|
const fieldConfigs = ref<MiniFieldConfigItem[]>([])
|
|
let touchStartX = 0
|
let touchStartY = 0
|
let touchingId: number | null = null
|
|
const showName = computed(() => isVisible(FIELD_CODES.VISITOR_NAME))
|
const showGender = computed(() => isVisible(FIELD_CODES.GENDER))
|
const showIdCard = computed(() => isVisible(FIELD_CODES.ID_CARD))
|
const showCompany = computed(() => isVisible(FIELD_CODES.COMPANY))
|
|
function isVisible(code: string) {
|
const config = fieldConfigs.value.find(item => item.fieldCode === code)
|
return config ? config.visible === 1 : true
|
}
|
|
function displayName(item: MiniVisitorListItem) {
|
return showName.value ? item.visitorName || '来访人' : '来访人'
|
}
|
|
function formatMissingFields(codes?: string[]) {
|
return formatVisitorFieldLabels(codes, fieldConfigs.value)
|
}
|
|
function handleEdit(item: MiniVisitorListItem) {
|
openSwipeId.value = null
|
uni.navigateTo({
|
url: `/pages/addPerson/index?id=${item.id}&uuid=${item.uuid}`,
|
})
|
}
|
|
function handleBack() {
|
router.back()
|
}
|
|
function handleAdd() {
|
uni.navigateTo({ url: '/pages/addPerson/index' })
|
}
|
|
async function handleSetDefault(item: MiniVisitorListItem) {
|
if (item.isDefault || settingDefaultId.value) {
|
return
|
}
|
openSwipeId.value = null
|
settingDefaultId.value = item.id
|
try {
|
await setDefaultVisitor(item.id, item.uuid)
|
uni.showToast({ title: '已设为默认来访人', icon: 'none' })
|
await loadVisitors()
|
}
|
catch (error) {
|
uni.showToast({
|
title: error instanceof Error ? error.message : '设置默认来访人失败',
|
icon: 'none',
|
})
|
}
|
finally {
|
settingDefaultId.value = null
|
}
|
}
|
|
function handleTouchStart(item: MiniVisitorListItem, event: any) {
|
const touch = event.touches?.[0]
|
if (!touch) {
|
return
|
}
|
touchingId = item.id
|
touchStartX = touch.clientX
|
touchStartY = touch.clientY
|
}
|
|
function handleTouchEnd(item: MiniVisitorListItem, event: any) {
|
if (touchingId !== item.id) {
|
return
|
}
|
const touch = event.changedTouches?.[0]
|
touchingId = null
|
if (!touch) {
|
return
|
}
|
const deltaX = touch.clientX - touchStartX
|
const deltaY = touch.clientY - touchStartY
|
if (Math.abs(deltaX) <= Math.abs(deltaY) + 10) {
|
return
|
}
|
if (deltaX < -40) {
|
openSwipeId.value = item.id
|
} else if (deltaX > 20) {
|
openSwipeId.value = null
|
}
|
}
|
|
function confirmDelete(item: MiniVisitorListItem) {
|
if (deletingId.value) {
|
return
|
}
|
uni.showModal({
|
title: '删除确认',
|
content: `确认删除来访人「${displayName(item)}」?`,
|
confirmText: '删除',
|
confirmColor: '#FF4D4F',
|
success: (res) => {
|
if (res.confirm) {
|
void handleDelete(item)
|
}
|
},
|
})
|
}
|
|
async function handleDelete(item: MiniVisitorListItem) {
|
deletingId.value = item.id
|
try {
|
await deleteVisitor(item.id, item.uuid)
|
openSwipeId.value = null
|
uni.showToast({ title: '已删除', icon: 'none' })
|
await loadVisitors()
|
}
|
catch (error) {
|
uni.showToast({
|
title: error instanceof Error ? error.message : '删除来访人失败',
|
icon: 'none',
|
})
|
}
|
finally {
|
deletingId.value = null
|
}
|
}
|
|
async function loadVisitors() {
|
loading.value = true
|
try {
|
const [configs, list] = await Promise.all([
|
fetchMiniFieldConfigs(),
|
fetchMyVisitors(),
|
])
|
fieldConfigs.value = configs
|
visitors.value = list
|
}
|
catch (error) {
|
uni.showToast({
|
title: error instanceof Error ? error.message : '加载来访人失败',
|
icon: 'none',
|
})
|
}
|
finally {
|
loading.value = false
|
}
|
}
|
|
onShow(() => {
|
void loadVisitors()
|
})
|
</script>
|
|
<template>
|
<view class="min-h-screen w-full relative box-border bg-[#F6FBFF] overflow-hidden">
|
<image
|
:src="`${choosePersonAssetBase}/bg@2x.png`"
|
class="absolute left-0 top-0 w-full z-0"
|
mode="widthFix"
|
/>
|
|
<view class="relative z-50 w-full" :style="{ height: (statusBarHeight + navBarHeight) + 'px' }">
|
<view class="flex items-center justify-between w-full absolute left-0" :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>
|
|
<view v-if="!loading && visitors.length === 0" class="absolute top-[268rpx] left-0 w-full z-10 flex flex-col items-center">
|
<image :src="`${choosePersonAssetBase}/noData.png`" class="w-[380rpx] h-[380rpx]" mode="aspectFit" />
|
<text class="text-[28rpx] text-[#B3B3B3]">
|
暂无来访人信息,请先添加~
|
</text>
|
</view>
|
|
<view v-else class="relative z-10 px-[24rpx] pb-[200rpx] mt-[5vh]">
|
<view class="w-full flex flex-col gap-[16rpx]">
|
<view
|
v-for="item in visitors"
|
:key="item.id"
|
class="relative overflow-hidden rounded-[8rpx] bg-[#FF4D4F]"
|
>
|
<view class="absolute right-0 top-0 bottom-0 w-[132rpx] bg-[#FF4D4F] flex items-center justify-center z-0" @click.stop="confirmDelete(item)">
|
<text class="text-[28rpx] text-white">{{ deletingId === item.id ? '删除中' : '删除' }}</text>
|
</view>
|
|
<view
|
class="relative z-10 flex items-center justify-between px-[24rpx] pt-[24rpx] pb-[64rpx] bg-white rounded-[8rpx] shadow-[0_4rpx_8rpx_rgba(6,47,108,0.1608)] transition-transform duration-200"
|
:style="{ transform: openSwipeId === item.id ? 'translateX(-132rpx)' : 'translateX(0)' }"
|
@touchstart="handleTouchStart(item, $event)"
|
@touchend="handleTouchEnd(item, $event)"
|
>
|
<view class="flex items-center w-[160rpx] shrink-0">
|
<text class="text-[28rpx] text-[#333333] font-medium truncate">{{ displayName(item) }}</text>
|
</view>
|
|
<view class="flex flex-col justify-center flex-1 mx-[20rpx] min-w-0">
|
<view v-if="showGender || showIdCard" class="flex items-center">
|
<image :src="item.gender === '女' ? appointmentIcons.female : appointmentIcons.male" class="w-[32rpx] h-[32rpx] shrink-0" />
|
<text class="ml-[20rpx] text-[28rpx] text-[#B3B3B3] truncate">
|
{{ showIdCard ? (item.idCardMasked || '-') : '-' }}
|
</text>
|
</view>
|
<view v-if="showCompany" class="flex items-center mt-[8rpx]">
|
<image :src="appointmentIcons.company" class="w-[32rpx] h-[32rpx] shrink-0" />
|
<text class="ml-[20rpx] text-[28rpx] text-[#B3B3B3] truncate">{{ item.visitorCompany || '-' }}</text>
|
</view>
|
<text v-if="item.missingFieldCodes?.length" class="mt-[8rpx] text-[22rpx] text-[#F5222D] truncate">
|
缺失:{{ formatMissingFields(item.missingFieldCodes) }}
|
</text>
|
</view>
|
|
<view class="w-[40rpx] h-[40rpx] shrink-0 flex items-center justify-center" @click.stop="handleEdit(item)">
|
<image :src="`${choosePersonAssetBase}/edit.png`" class="w-[33rpx] h-[33rpx]" />
|
</view>
|
|
<view
|
class="absolute right-[24rpx] bottom-[18rpx] h-[36rpx] px-[18rpx] rounded-full flex items-center justify-center"
|
:class="item.isDefault ? 'bg-[#E6F0FF]' : 'bg-[#F5F7FA]'"
|
@click.stop="handleSetDefault(item)"
|
>
|
<text class="text-[22rpx]" :class="item.isDefault ? 'text-[#0068FF]' : 'text-[#999999]'">
|
{{ item.isDefault ? '默认来访人' : (settingDefaultId === item.id ? '设置中...' : '设为默认') }}
|
</text>
|
</view>
|
</view>
|
</view>
|
</view>
|
</view>
|
|
<view class="fixed bottom-[48rpx] left-0 w-full px-[32rpx] box-border z-[100]">
|
<view class="h-[96rpx] rounded-full bg-[#0068FF] flex items-center justify-center shadow-[0_8rpx_24rpx_rgba(0,104,255,0.2)]" @click="handleAdd">
|
<text class="text-[32rpx] text-white font-medium">添加来访人</text>
|
</view>
|
</view>
|
</view>
|
</template>
|
|
<style scoped>
|
</style>
|