|
|
@@ -0,0 +1,545 @@
|
|
|
+<script setup>
|
|
|
+import {
|
|
|
+ queryPersonInfo,
|
|
|
+ adminUpdatePersonInfo,
|
|
|
+ getQrCodeInfo,
|
|
|
+ uploadFile
|
|
|
+} from '@/services/api'
|
|
|
+import { Form } from '@primevue/forms'
|
|
|
+import { zodResolver } from '@primevue/forms/resolvers/zod'
|
|
|
+import { useDateFormat } from '@vueuse/core'
|
|
|
+import Button from 'primevue/button'
|
|
|
+import Column from 'primevue/column'
|
|
|
+import DataTable from 'primevue/datatable'
|
|
|
+import Dialog from 'primevue/dialog'
|
|
|
+import Select from 'primevue/select'
|
|
|
+import FloatLabel from 'primevue/floatlabel'
|
|
|
+import IconField from 'primevue/iconfield'
|
|
|
+import InputIcon from 'primevue/inputicon'
|
|
|
+import InputText from 'primevue/inputtext'
|
|
|
+import Textarea from 'primevue/textarea'
|
|
|
+import FileUpload from 'primevue/fileupload'
|
|
|
+import Image from 'primevue/image'
|
|
|
+import Message from 'primevue/message'
|
|
|
+import Tag from 'primevue/tag'
|
|
|
+import { useToast } from 'primevue/usetoast'
|
|
|
+import { computed, onMounted, ref } from 'vue'
|
|
|
+import { z } from 'zod'
|
|
|
+
|
|
|
+const toast = useToast()
|
|
|
+
|
|
|
+// 表格数据
|
|
|
+const tableData = ref({
|
|
|
+ content: [],
|
|
|
+ metadata: {
|
|
|
+ page: 0,
|
|
|
+ size: 20,
|
|
|
+ total: 0
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 筛选条件
|
|
|
+const filters = ref({
|
|
|
+ name: '',
|
|
|
+ phone: '',
|
|
|
+ qrCode: '',
|
|
|
+ gender: null
|
|
|
+})
|
|
|
+
|
|
|
+// 性别选项
|
|
|
+const genderOptions = [
|
|
|
+ { label: '全部', value: null },
|
|
|
+ { label: '男', value: 'male' },
|
|
|
+ { label: '女', value: 'female' },
|
|
|
+ { label: '其他', value: 'other' }
|
|
|
+]
|
|
|
+
|
|
|
+// 获取性别名称
|
|
|
+const getGenderName = (gender) => {
|
|
|
+ const map = {
|
|
|
+ male: '男',
|
|
|
+ female: '女',
|
|
|
+ other: '其他'
|
|
|
+ }
|
|
|
+ return map[gender] || '-'
|
|
|
+}
|
|
|
+
|
|
|
+// 格式化日期
|
|
|
+const formatDate = (date) => {
|
|
|
+ if (!date) return '-'
|
|
|
+ return useDateFormat(new Date(date), 'YYYY-MM-DD HH:mm:ss').value
|
|
|
+}
|
|
|
+
|
|
|
+// 获取数据
|
|
|
+const fetchData = async () => {
|
|
|
+ try {
|
|
|
+ const params = {
|
|
|
+ page: tableData.value.metadata.page,
|
|
|
+ pageSize: tableData.value.metadata.size
|
|
|
+ }
|
|
|
+
|
|
|
+ if (filters.value.name) params.name = filters.value.name
|
|
|
+ if (filters.value.phone) params.phone = filters.value.phone
|
|
|
+ if (filters.value.qrCode) params.qrCode = filters.value.qrCode
|
|
|
+ if (filters.value.gender) params.gender = filters.value.gender
|
|
|
+
|
|
|
+ const response = await queryPersonInfo(params)
|
|
|
+ tableData.value.content = response.data
|
|
|
+ tableData.value.metadata.total = response.total
|
|
|
+ } catch (error) {
|
|
|
+ toast.add({
|
|
|
+ severity: 'error',
|
|
|
+ summary: '错误',
|
|
|
+ detail: error.message || '获取数据失败',
|
|
|
+ life: 3000
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 分页
|
|
|
+const handlePageChange = (event) => {
|
|
|
+ tableData.value.metadata.page = event.page
|
|
|
+ tableData.value.metadata.size = event.rows
|
|
|
+ fetchData()
|
|
|
+}
|
|
|
+
|
|
|
+// 重置筛选并刷新
|
|
|
+const resetAndRefresh = () => {
|
|
|
+ filters.value = {
|
|
|
+ name: '',
|
|
|
+ phone: '',
|
|
|
+ qrCode: '',
|
|
|
+ gender: null
|
|
|
+ }
|
|
|
+ tableData.value.metadata.page = 0
|
|
|
+ fetchData()
|
|
|
+}
|
|
|
+
|
|
|
+// 编辑对话框
|
|
|
+const editDialog = ref(false)
|
|
|
+const editForm = ref({
|
|
|
+ qrCodeId: null,
|
|
|
+ photoUrl: '',
|
|
|
+ name: '',
|
|
|
+ gender: 'male',
|
|
|
+ phone: '',
|
|
|
+ specialNote: '',
|
|
|
+ emergencyContactName: '',
|
|
|
+ emergencyContactPhone: '',
|
|
|
+ emergencyContactEmail: ''
|
|
|
+})
|
|
|
+const editFormLoading = ref(false)
|
|
|
+const uploadedPhotoUrl = ref('')
|
|
|
+
|
|
|
+// 表单验证
|
|
|
+const editFormResolver = zodResolver(
|
|
|
+ z.object({
|
|
|
+ name: z.string().min(1, { message: '姓名不能为空' }),
|
|
|
+ gender: z.enum(['male', 'female', 'other'], { message: '请选择性别' }),
|
|
|
+ phone: z.string().min(1, { message: '电话不能为空' }),
|
|
|
+ emergencyContactName: z.string().min(1, { message: '紧急联系人姓名不能为空' }),
|
|
|
+ emergencyContactPhone: z.string().min(1, { message: '紧急联系人电话不能为空' }),
|
|
|
+ emergencyContactEmail: z.string().email({ message: '邮箱格式不正确' }).optional().or(z.literal(''))
|
|
|
+ })
|
|
|
+)
|
|
|
+
|
|
|
+// 打开编辑对话框
|
|
|
+const openEditDialog = async (person) => {
|
|
|
+ editForm.value = {
|
|
|
+ qrCodeId: person.qrCodeId,
|
|
|
+ photoUrl: person.photoUrl || '',
|
|
|
+ name: person.name,
|
|
|
+ gender: person.gender,
|
|
|
+ phone: person.phone,
|
|
|
+ specialNote: person.specialNote || '',
|
|
|
+ emergencyContactName: person.emergencyContactName,
|
|
|
+ emergencyContactPhone: person.emergencyContactPhone,
|
|
|
+ emergencyContactEmail: person.emergencyContactEmail || ''
|
|
|
+ }
|
|
|
+ uploadedPhotoUrl.value = person.photoUrl || ''
|
|
|
+ editDialog.value = true
|
|
|
+}
|
|
|
+
|
|
|
+// 照片上传
|
|
|
+const handlePhotoUpload = async (event) => {
|
|
|
+ const file = event.files[0]
|
|
|
+ if (!file) return
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await uploadFile(file)
|
|
|
+ uploadedPhotoUrl.value = response.url
|
|
|
+ editForm.value.photoUrl = response.url
|
|
|
+ toast.add({
|
|
|
+ severity: 'success',
|
|
|
+ summary: '成功',
|
|
|
+ detail: '照片上传成功',
|
|
|
+ life: 3000
|
|
|
+ })
|
|
|
+ } catch (error) {
|
|
|
+ toast.add({
|
|
|
+ severity: 'error',
|
|
|
+ summary: '错误',
|
|
|
+ detail: error.message || '照片上传失败',
|
|
|
+ life: 3000
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 保存编辑
|
|
|
+const saveEdit = async ({ valid, values }) => {
|
|
|
+ if (!valid) return
|
|
|
+
|
|
|
+ editFormLoading.value = true
|
|
|
+ try {
|
|
|
+ const submitData = {
|
|
|
+ qrCodeId: editForm.value.qrCodeId,
|
|
|
+ photoUrl: uploadedPhotoUrl.value,
|
|
|
+ name: values.name,
|
|
|
+ gender: values.gender,
|
|
|
+ phone: values.phone,
|
|
|
+ specialNote: values.specialNote || '',
|
|
|
+ emergencyContactName: values.emergencyContactName,
|
|
|
+ emergencyContactPhone: values.emergencyContactPhone,
|
|
|
+ emergencyContactEmail: values.emergencyContactEmail || ''
|
|
|
+ }
|
|
|
+
|
|
|
+ await adminUpdatePersonInfo(submitData)
|
|
|
+ toast.add({
|
|
|
+ severity: 'success',
|
|
|
+ summary: '成功',
|
|
|
+ detail: '人员信息更新成功',
|
|
|
+ life: 3000
|
|
|
+ })
|
|
|
+ editDialog.value = false
|
|
|
+ fetchData()
|
|
|
+ } catch (error) {
|
|
|
+ toast.add({
|
|
|
+ severity: 'error',
|
|
|
+ summary: '错误',
|
|
|
+ detail: error.message || '更新失败',
|
|
|
+ life: 3000
|
|
|
+ })
|
|
|
+ } finally {
|
|
|
+ editFormLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 查看详情对话框
|
|
|
+const detailDialog = ref(false)
|
|
|
+const selectedPerson = ref(null)
|
|
|
+const qrCodeDetail = ref(null)
|
|
|
+
|
|
|
+const viewDetail = async (person) => {
|
|
|
+ try {
|
|
|
+ selectedPerson.value = person
|
|
|
+ const response = await getQrCodeInfo(person.qrCode)
|
|
|
+ qrCodeDetail.value = response
|
|
|
+ detailDialog.value = true
|
|
|
+ } catch (error) {
|
|
|
+ toast.add({
|
|
|
+ severity: 'error',
|
|
|
+ summary: '错误',
|
|
|
+ detail: error.message || '获取详情失败',
|
|
|
+ life: 3000
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 复制到剪贴板
|
|
|
+const copyToClipboard = (text) => {
|
|
|
+ navigator.clipboard.writeText(text).then(() => {
|
|
|
+ toast.add({
|
|
|
+ severity: 'success',
|
|
|
+ summary: '成功',
|
|
|
+ detail: '已复制到剪贴板',
|
|
|
+ life: 2000
|
|
|
+ })
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ fetchData()
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="rounded-lg p-4 bg-[var(--p-content-background)]">
|
|
|
+ <DataTable :value="tableData.content" :paginator="true"
|
|
|
+ paginatorTemplate="CurrentPageReport FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown"
|
|
|
+ currentPageReportTemplate="{totalRecords} 条记录" :rows="tableData.metadata.size"
|
|
|
+ :rowsPerPageOptions="[10, 20, 50, 100]" :totalRecords="tableData.metadata.total" @page="handlePageChange" lazy
|
|
|
+ scrollable>
|
|
|
+ <template #header>
|
|
|
+ <div class="flex flex-wrap items-center gap-2">
|
|
|
+ <!-- 筛选条件 - 左侧 -->
|
|
|
+ <IconField>
|
|
|
+ <InputIcon>
|
|
|
+ <i class="pi pi-user" />
|
|
|
+ </InputIcon>
|
|
|
+ <InputText v-model="filters.name" placeholder="姓名" size="small" class="w-32" />
|
|
|
+ </IconField>
|
|
|
+ <IconField>
|
|
|
+ <InputIcon>
|
|
|
+ <i class="pi pi-phone" />
|
|
|
+ </InputIcon>
|
|
|
+ <InputText v-model="filters.phone" placeholder="电话" size="small" class="w-32" />
|
|
|
+ </IconField>
|
|
|
+ <IconField>
|
|
|
+ <InputIcon>
|
|
|
+ <i class="pi pi-qrcode" />
|
|
|
+ </InputIcon>
|
|
|
+ <InputText v-model="filters.qrCode" placeholder="二维码编号" size="small" class="w-32" />
|
|
|
+ </IconField>
|
|
|
+ <Select v-model="filters.gender" :options="genderOptions" optionLabel="label" optionValue="value"
|
|
|
+ placeholder="性别" class="w-30" size="small" />
|
|
|
+ <Button icon="pi pi-search" @click="fetchData" label="查询" size="small" />
|
|
|
+ <Button icon="pi pi-refresh" @click="resetAndRefresh" label="刷新" size="small" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <Column field="qrCode" header="二维码编号" style="min-width: 180px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <code class="text-xs">{{ slotProps.data.qrCode }}</code>
|
|
|
+ <Button icon="pi pi-copy" size="small" text rounded @click="copyToClipboard(slotProps.data.qrCode)" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+ <Column field="photoUrl" header="照片" style="min-width: 80px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ <Image v-if="slotProps.data.photoUrl" :src="slotProps.data.photoUrl" alt="照片" width="50" preview />
|
|
|
+ <span v-else class="text-gray-400">-</span>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+ <Column field="name" header="姓名" style="min-width: 100px"></Column>
|
|
|
+ <Column field="gender" header="性别" style="min-width: 80px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ {{ getGenderName(slotProps.data.gender) }}
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+ <Column field="phone" header="电话" style="min-width: 130px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ <a :href="`tel:${slotProps.data.phone}`" class="text-blue-600 hover:underline">
|
|
|
+ {{ slotProps.data.phone }}
|
|
|
+ </a>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+ <Column field="emergencyContactName" header="紧急联系人" style="min-width: 120px"></Column>
|
|
|
+ <Column field="emergencyContactPhone" header="紧急联系人电话" style="min-width: 150px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ <a :href="`tel:${slotProps.data.emergencyContactPhone}`" class="text-blue-600 hover:underline">
|
|
|
+ {{ slotProps.data.emergencyContactPhone }}
|
|
|
+ </a>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+ <Column field="createdAt" header="创建时间" style="min-width: 180px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ {{ formatDate(slotProps.data.createdAt) }}
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+ <Column header="操作" style="min-width: 150px" frozen alignFrozen="right">
|
|
|
+ <template #body="slotProps">
|
|
|
+ <div class="flex gap-1">
|
|
|
+ <Button icon="pi pi-eye" severity="info" size="small" text rounded v-tooltip.top="'查看详情'"
|
|
|
+ @click="viewDetail(slotProps.data)" />
|
|
|
+ <Button icon="pi pi-pencil" severity="warn" size="small" text rounded v-tooltip.top="'编辑'"
|
|
|
+ @click="openEditDialog(slotProps.data)" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+ </DataTable>
|
|
|
+
|
|
|
+ <!-- 编辑对话框 -->
|
|
|
+ <Dialog v-model:visible="editDialog" :modal="true" header="编辑人员信息" :style="{ width: '650px' }" position="center">
|
|
|
+ <Form v-slot="$form" :resolver="editFormResolver" :initialValues="editForm" @submit="saveEdit" class="p-fluid">
|
|
|
+ <!-- 照片上传 -->
|
|
|
+ <div class="field mb-4">
|
|
|
+ <label class="block mb-2">照片</label>
|
|
|
+ <div class="flex gap-4 items-start">
|
|
|
+ <Image v-if="uploadedPhotoUrl" :src="uploadedPhotoUrl" alt="照片预览" width="120" preview />
|
|
|
+ <FileUpload mode="basic" accept="image/*" :maxFileSize="5000000" chooseLabel="上传照片"
|
|
|
+ @select="handlePhotoUpload" :auto="true" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="grid grid-cols-2 gap-4">
|
|
|
+ <div class="field">
|
|
|
+ <FloatLabel variant="on">
|
|
|
+ <InputText id="name" name="name" v-model="editForm.name" fluid />
|
|
|
+ <label for="name">姓名 *</label>
|
|
|
+ </FloatLabel>
|
|
|
+ <Message v-if="$form.name?.invalid" severity="error" size="small" variant="simple">
|
|
|
+ {{ $form.name.error?.message }}
|
|
|
+ </Message>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="field">
|
|
|
+ <FloatLabel variant="on">
|
|
|
+ <Select id="gender" name="gender" v-model="editForm.gender" :options="[
|
|
|
+ { label: '男', value: 'male' },
|
|
|
+ { label: '女', value: 'female' },
|
|
|
+ { label: '其他', value: 'other' }
|
|
|
+ ]" optionLabel="label" optionValue="value" fluid />
|
|
|
+ <label for="gender">性别 *</label>
|
|
|
+ </FloatLabel>
|
|
|
+ <Message v-if="$form.gender?.invalid" severity="error" size="small" variant="simple">
|
|
|
+ {{ $form.gender.error?.message }}
|
|
|
+ </Message>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="field mt-4">
|
|
|
+ <FloatLabel variant="on">
|
|
|
+ <InputText id="phone" name="phone" v-model="editForm.phone" fluid />
|
|
|
+ <label for="phone">电话 *</label>
|
|
|
+ </FloatLabel>
|
|
|
+ <Message v-if="$form.phone?.invalid" severity="error" size="small" variant="simple">
|
|
|
+ {{ $form.phone.error?.message }}
|
|
|
+ </Message>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="field mt-4">
|
|
|
+ <FloatLabel variant="on">
|
|
|
+ <Textarea id="specialNote" name="specialNote" v-model="editForm.specialNote" rows="3" fluid />
|
|
|
+ <label for="specialNote">特别说明(如血型、过敏史等)</label>
|
|
|
+ </FloatLabel>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="field mt-4">
|
|
|
+ <FloatLabel variant="on">
|
|
|
+ <InputText id="emergencyContactName" name="emergencyContactName" v-model="editForm.emergencyContactName"
|
|
|
+ fluid />
|
|
|
+ <label for="emergencyContactName">紧急联系人姓名 *</label>
|
|
|
+ </FloatLabel>
|
|
|
+ <Message v-if="$form.emergencyContactName?.invalid" severity="error" size="small" variant="simple">
|
|
|
+ {{ $form.emergencyContactName.error?.message }}
|
|
|
+ </Message>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="field mt-4">
|
|
|
+ <FloatLabel variant="on">
|
|
|
+ <InputText id="emergencyContactPhone" name="emergencyContactPhone" v-model="editForm.emergencyContactPhone"
|
|
|
+ fluid />
|
|
|
+ <label for="emergencyContactPhone">紧急联系人电话 *</label>
|
|
|
+ </FloatLabel>
|
|
|
+ <Message v-if="$form.emergencyContactPhone?.invalid" severity="error" size="small" variant="simple">
|
|
|
+ {{ $form.emergencyContactPhone.error?.message }}
|
|
|
+ </Message>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="field mt-4">
|
|
|
+ <FloatLabel variant="on">
|
|
|
+ <InputText id="emergencyContactEmail" name="emergencyContactEmail" v-model="editForm.emergencyContactEmail"
|
|
|
+ fluid />
|
|
|
+ <label for="emergencyContactEmail">紧急联系人邮箱</label>
|
|
|
+ </FloatLabel>
|
|
|
+ <Message v-if="$form.emergencyContactEmail?.invalid" severity="error" size="small" variant="simple">
|
|
|
+ {{ $form.emergencyContactEmail.error?.message }}
|
|
|
+ </Message>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="flex justify-end gap-2 mt-4">
|
|
|
+ <Button label="取消" severity="secondary" type="button" @click="editDialog = false"
|
|
|
+ :disabled="editFormLoading" />
|
|
|
+ <Button label="保存" type="submit" :loading="editFormLoading" />
|
|
|
+ </div>
|
|
|
+ </Form>
|
|
|
+ </Dialog>
|
|
|
+
|
|
|
+ <!-- 详情对话框 -->
|
|
|
+ <Dialog v-model:visible="detailDialog" :modal="true" header="人员信息详情" :style="{ width: '600px' }" position="center">
|
|
|
+ <div v-if="selectedPerson" class="space-y-4">
|
|
|
+ <!-- 照片 -->
|
|
|
+ <div v-if="selectedPerson.photoUrl" class="text-center">
|
|
|
+ <Image :src="selectedPerson.photoUrl" alt="照片" width="150" preview />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 基本信息 -->
|
|
|
+ <div class="border rounded p-4">
|
|
|
+ <h4 class="font-semibold mb-3">基本信息</h4>
|
|
|
+ <div class="grid grid-cols-2 gap-3">
|
|
|
+ <div>
|
|
|
+ <div class="text-sm text-gray-500">二维码</div>
|
|
|
+ <div class="font-mono">{{ selectedPerson.qrCode }}</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="text-sm text-gray-500">姓名</div>
|
|
|
+ <div>{{ selectedPerson.name }}</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="text-sm text-gray-500">性别</div>
|
|
|
+ <div>{{ getGenderName(selectedPerson.gender) }}</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="text-sm text-gray-500">电话</div>
|
|
|
+ <div>
|
|
|
+ <a :href="`tel:${selectedPerson.phone}`" class="text-blue-600 hover:underline">
|
|
|
+ {{ selectedPerson.phone }}
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-if="selectedPerson.specialNote" class="mt-3">
|
|
|
+ <div class="text-sm text-gray-500">特别说明</div>
|
|
|
+ <div class="whitespace-pre-wrap">{{ selectedPerson.specialNote }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 紧急联系人信息 -->
|
|
|
+ <div class="border rounded p-4">
|
|
|
+ <h4 class="font-semibold mb-3">紧急联系人</h4>
|
|
|
+ <div class="space-y-2">
|
|
|
+ <div>
|
|
|
+ <div class="text-sm text-gray-500">姓名</div>
|
|
|
+ <div>{{ selectedPerson.emergencyContactName }}</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="text-sm text-gray-500">电话</div>
|
|
|
+ <div>
|
|
|
+ <a :href="`tel:${selectedPerson.emergencyContactPhone}`" class="text-blue-600 hover:underline">
|
|
|
+ {{ selectedPerson.emergencyContactPhone }}
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-if="selectedPerson.emergencyContactEmail">
|
|
|
+ <div class="text-sm text-gray-500">邮箱</div>
|
|
|
+ <div>
|
|
|
+ <a :href="`mailto:${selectedPerson.emergencyContactEmail}`" class="text-blue-600 hover:underline">
|
|
|
+ {{ selectedPerson.emergencyContactEmail }}
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 二维码统计信息 -->
|
|
|
+ <div v-if="qrCodeDetail" class="border rounded p-4">
|
|
|
+ <h4 class="font-semibold mb-3">统计信息</h4>
|
|
|
+ <div class="grid grid-cols-2 gap-3">
|
|
|
+ <div>
|
|
|
+ <div class="text-sm text-gray-500">激活状态</div>
|
|
|
+ <Tag :value="qrCodeDetail.isActivated ? '已激活' : '未激活'"
|
|
|
+ :severity="qrCodeDetail.isActivated ? 'success' : 'secondary'" />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="text-sm text-gray-500">扫描次数</div>
|
|
|
+ <div class="font-semibold text-lg">{{ qrCodeDetail.scanCount || 0 }} 次</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="text-sm text-gray-500">创建时间</div>
|
|
|
+ <div>{{ formatDate(selectedPerson.createdAt) }}</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="text-sm text-gray-500">更新时间</div>
|
|
|
+ <div>{{ formatDate(selectedPerson.updatedAt) }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <Button label="关闭" @click="detailDialog = false" />
|
|
|
+ </template>
|
|
|
+ </Dialog>
|
|
|
+ </div>
|
|
|
+</template>
|