|
|
@@ -0,0 +1,505 @@
|
|
|
+<script setup>
|
|
|
+import { queryGoodsInfo, adminUpdateGoodsInfo, 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 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: '',
|
|
|
+ contactName: '',
|
|
|
+ contactPhone: ''
|
|
|
+})
|
|
|
+
|
|
|
+// 格式化日期
|
|
|
+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,
|
|
|
+ size: tableData.value.metadata.size
|
|
|
+ }
|
|
|
+
|
|
|
+ if (filters.value.name) params.name = filters.value.name
|
|
|
+ if (filters.value.contactName) params.contactName = filters.value.contactName
|
|
|
+ if (filters.value.contactPhone) params.contactPhone = filters.value.contactPhone
|
|
|
+
|
|
|
+ const response = await queryGoodsInfo(params)
|
|
|
+ const content = response?.content ?? []
|
|
|
+ const metadata = response?.metadata ?? {}
|
|
|
+
|
|
|
+ tableData.value.content = content
|
|
|
+ tableData.value.metadata = {
|
|
|
+ page: metadata.page ?? params.page,
|
|
|
+ size: metadata.size ?? params.size,
|
|
|
+ total: metadata.total ?? content.length
|
|
|
+ }
|
|
|
+ } 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: '',
|
|
|
+ contactName: '',
|
|
|
+ contactPhone: ''
|
|
|
+ }
|
|
|
+ tableData.value.metadata.page = 0
|
|
|
+ fetchData()
|
|
|
+}
|
|
|
+
|
|
|
+// 编辑对话框
|
|
|
+const editDialog = ref(false)
|
|
|
+const editForm = ref({
|
|
|
+ qrCodeId: null,
|
|
|
+ photoUrl: '',
|
|
|
+ name: '',
|
|
|
+ contactName: '',
|
|
|
+ contactPhone: '',
|
|
|
+ contactEmail: '',
|
|
|
+ remark: ''
|
|
|
+})
|
|
|
+const editFormLoading = ref(false)
|
|
|
+const uploadedPhotoUrl = ref('')
|
|
|
+
|
|
|
+// 表单验证
|
|
|
+const editFormResolver = zodResolver(
|
|
|
+ z.object({
|
|
|
+ name: z.string().min(1, { message: '物品名称不能为空' }),
|
|
|
+ contactName: z.string().min(1, { message: '联系人姓名不能为空' }),
|
|
|
+ contactPhone: z.string().min(1, { message: '联系电话不能为空' }),
|
|
|
+ contactEmail: z.string().email({ message: '邮箱格式不正确' }).optional().or(z.literal('')),
|
|
|
+ remark: z.string().optional()
|
|
|
+ })
|
|
|
+)
|
|
|
+
|
|
|
+// 打开编辑对话框
|
|
|
+const openEditDialog = async (goods) => {
|
|
|
+ editForm.value = {
|
|
|
+ qrCodeId: goods.qrCodeId,
|
|
|
+ photoUrl: goods.photoUrl || '',
|
|
|
+ name: goods.name,
|
|
|
+ contactName: goods.contactName,
|
|
|
+ contactPhone: goods.contactPhone,
|
|
|
+ contactEmail: goods.contactEmail || '',
|
|
|
+ remark: goods.remark || ''
|
|
|
+ }
|
|
|
+ uploadedPhotoUrl.value = goods.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 || editForm.value.photoUrl || '',
|
|
|
+ name: values.name,
|
|
|
+ contactName: values.contactName,
|
|
|
+ contactPhone: values.contactPhone,
|
|
|
+ contactEmail: values.contactEmail || '',
|
|
|
+ remark: values.remark || ''
|
|
|
+ }
|
|
|
+
|
|
|
+ await adminUpdateGoodsInfo(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 selectedGoods = ref(null)
|
|
|
+const qrCodeDetail = ref(null)
|
|
|
+const detailLoading = ref(false)
|
|
|
+
|
|
|
+const resolveQrCodeValue = (record) => record?.qrCode ?? record?.qrCodeId ?? ''
|
|
|
+const resolveQrCodeId = (record) => record?.qrCodeId ?? null
|
|
|
+const resolveRemark = (record) => record?.remark ?? record?.remarks ?? ''
|
|
|
+
|
|
|
+const viewDetail = async (goods) => {
|
|
|
+ try {
|
|
|
+ detailDialog.value = true
|
|
|
+ detailLoading.value = true
|
|
|
+ selectedGoods.value = null
|
|
|
+ qrCodeDetail.value = null
|
|
|
+
|
|
|
+ const qrCodeId = resolveQrCodeId(goods)
|
|
|
+ const qrInfo = qrCodeId ? await getQrCodeInfo(qrCodeId) : null
|
|
|
+ const normalizedDetail = qrInfo?.info
|
|
|
+ ? {
|
|
|
+ ...qrInfo.info,
|
|
|
+ qrCode: qrInfo.info.qrCode ?? qrInfo.qrCode,
|
|
|
+ qrCodeId: qrInfo.info.qrCodeId ?? qrInfo.id ?? qrInfo.qrCodeId
|
|
|
+ }
|
|
|
+ : null
|
|
|
+ selectedGoods.value = normalizedDetail || goods
|
|
|
+ qrCodeDetail.value = qrInfo
|
|
|
+ } catch (error) {
|
|
|
+ detailDialog.value = false
|
|
|
+ toast.add({
|
|
|
+ severity: 'error',
|
|
|
+ summary: '错误',
|
|
|
+ detail: error.message || '获取详情失败',
|
|
|
+ life: 3000
|
|
|
+ })
|
|
|
+ } finally {
|
|
|
+ detailLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 复制到剪贴板
|
|
|
+const copyToClipboard = (text) => {
|
|
|
+ navigator.clipboard.writeText(String(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-box" />
|
|
|
+ </InputIcon>
|
|
|
+ <InputText v-model="filters.name" placeholder="物品名称" size="small" class="w-32" />
|
|
|
+ </IconField>
|
|
|
+ <IconField>
|
|
|
+ <InputIcon>
|
|
|
+ <i class="pi pi-user" />
|
|
|
+ </InputIcon>
|
|
|
+ <InputText v-model="filters.contactName" placeholder="联系人" size="small" class="w-32" />
|
|
|
+ </IconField>
|
|
|
+ <IconField>
|
|
|
+ <InputIcon>
|
|
|
+ <i class="pi pi-phone" />
|
|
|
+ </InputIcon>
|
|
|
+ <InputText v-model="filters.contactPhone" placeholder="联系电话" size="small" class="w-32" />
|
|
|
+ </IconField>
|
|
|
+ <Button icon="pi pi-search" @click="fetchData" label="查询" size="small" />
|
|
|
+ <Button icon="pi pi-refresh" @click="resetAndRefresh" label="刷新" size="small" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <Column field="qrCodeId" header="二维码ID" style="min-width: 160px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ <div class="flex items-center gap-2">
|
|
|
+ <code class="text-xs">{{ resolveQrCodeValue(slotProps.data) || '-' }}</code>
|
|
|
+ <Button icon="pi pi-copy" size="small" text rounded
|
|
|
+ @click="copyToClipboard(resolveQrCodeValue(slotProps.data))"
|
|
|
+ :disabled="!resolveQrCodeValue(slotProps.data)" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+ <Column field="name" header="物品名称" style="min-width: 150px"></Column>
|
|
|
+ <Column field="contactName" header="联系人" style="min-width: 100px"></Column>
|
|
|
+ <Column field="contactPhone" header="联系电话" style="min-width: 130px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ <a :href="`tel:${slotProps.data.contactPhone}`" class="text-blue-600 hover:underline">
|
|
|
+ {{ slotProps.data.contactPhone }}
|
|
|
+ </a>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+ <Column field="contactEmail" header="邮箱" style="min-width: 180px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ <a v-if="slotProps.data.contactEmail" :href="`mailto:${slotProps.data.contactEmail}`"
|
|
|
+ class="text-blue-600 hover:underline">
|
|
|
+ {{ slotProps.data.contactEmail }}
|
|
|
+ </a>
|
|
|
+ <span v-else class="text-gray-400">-</span>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+ <Column field="remark" header="备注" style="min-width: 220px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ <span class="whitespace-pre-line break-words">{{ resolveRemark(slotProps.data) || '-' }}</span>
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+ <Column field="createdAt" header="创建时间" style="min-width: 180px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ {{ formatDate(slotProps.data.createdAt) }}
|
|
|
+ </template>
|
|
|
+ </Column>
|
|
|
+ <Column field="updatedAt" header="更新时间" style="min-width: 180px">
|
|
|
+ <template #body="slotProps">
|
|
|
+ {{ formatDate(slotProps.data.updatedAt) }}
|
|
|
+ </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: '550px' }" 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="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 mt-4">
|
|
|
+ <FloatLabel variant="on">
|
|
|
+ <InputText id="contactName" name="contactName" v-model="editForm.contactName" fluid />
|
|
|
+ <label for="contactName">联系人姓名 *</label>
|
|
|
+ </FloatLabel>
|
|
|
+ <Message v-if="$form.contactName?.invalid" severity="error" size="small" variant="simple">
|
|
|
+ {{ $form.contactName.error?.message }}
|
|
|
+ </Message>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="field mt-4">
|
|
|
+ <FloatLabel variant="on">
|
|
|
+ <InputText id="contactPhone" name="contactPhone" v-model="editForm.contactPhone" fluid />
|
|
|
+ <label for="contactPhone">联系电话 *</label>
|
|
|
+ </FloatLabel>
|
|
|
+ <Message v-if="$form.contactPhone?.invalid" severity="error" size="small" variant="simple">
|
|
|
+ {{ $form.contactPhone.error?.message }}
|
|
|
+ </Message>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="field mt-4">
|
|
|
+ <FloatLabel variant="on">
|
|
|
+ <InputText id="contactEmail" name="contactEmail" v-model="editForm.contactEmail" fluid />
|
|
|
+ <label for="contactEmail">联系邮箱</label>
|
|
|
+ </FloatLabel>
|
|
|
+ <Message v-if="$form.contactEmail?.invalid" severity="error" size="small" variant="simple">
|
|
|
+ {{ $form.contactEmail.error?.message }}
|
|
|
+ </Message>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="field mt-4">
|
|
|
+ <FloatLabel variant="on">
|
|
|
+ <Textarea id="remark" name="remark" v-model="editForm.remark" rows="3" autoResize fluid />
|
|
|
+ <label for="remark">备注</label>
|
|
|
+ </FloatLabel>
|
|
|
+ <Message v-if="$form.remark?.invalid" severity="error" size="small" variant="simple">
|
|
|
+ {{ $form.remark.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: '750px' }" position="center">
|
|
|
+ <div v-if="detailLoading" class="py-10 text-center text-gray-500">详情加载中...</div>
|
|
|
+ <div v-else-if="selectedGoods" class="space-y-4">
|
|
|
+ <!-- 照片 -->
|
|
|
+ <div v-if="selectedGoods.photoUrl" class="text-center">
|
|
|
+ <Image :src="selectedGoods.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-4">
|
|
|
+ <div>
|
|
|
+ <div class="text-sm text-gray-500">物品名称</div>
|
|
|
+ <div class="font-medium">{{ selectedGoods.name || '-' }}</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="text-sm text-gray-500">联系人</div>
|
|
|
+ <div class="font-medium">{{ selectedGoods.contactName || '-' }}</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="text-sm text-gray-500">联系电话</div>
|
|
|
+ <div>
|
|
|
+ <a v-if="selectedGoods.contactPhone" :href="`tel:${selectedGoods.contactPhone}`"
|
|
|
+ class="text-blue-600 hover:underline">
|
|
|
+ {{ selectedGoods.contactPhone }}
|
|
|
+ </a>
|
|
|
+ <span v-else>-</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="text-sm text-gray-500">联系邮箱</div>
|
|
|
+ <div>
|
|
|
+ <a v-if="selectedGoods.contactEmail" :href="`mailto:${selectedGoods.contactEmail}`"
|
|
|
+ class="text-blue-600 hover:underline">
|
|
|
+ {{ selectedGoods.contactEmail }}
|
|
|
+ </a>
|
|
|
+ <span v-else>-</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-if="resolveRemark(selectedGoods)" class="col-span-2">
|
|
|
+ <div class="text-sm text-gray-500">备注</div>
|
|
|
+ <div class="whitespace-pre-wrap">{{ resolveRemark(selectedGoods) }}</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-4">
|
|
|
+ <div>
|
|
|
+ <div class="text-sm text-gray-500">二维码编号</div>
|
|
|
+ <div class="font-mono text-sm break-all">{{ qrCodeDetail.qrCode || '-' }}</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="text-sm text-gray-500">类型</div>
|
|
|
+ <div class="font-medium">{{ qrCodeDetail.qrType || '-' }}</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="text-sm text-gray-500">激活状态</div>
|
|
|
+ <div>
|
|
|
+ <Tag :value="qrCodeDetail.isActivated ? '已激活' : '未激活'"
|
|
|
+ :severity="qrCodeDetail.isActivated ? 'success' : 'secondary'" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="text-sm text-gray-500">扫描次数</div>
|
|
|
+ <div class="font-semibold text-lg text-blue-600">{{ qrCodeDetail.scanCount || 0 }} 次</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="text-sm text-gray-500">创建时间</div>
|
|
|
+ <div>{{ formatDate(selectedGoods.createdAt) }}</div>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="text-sm text-gray-500">更新时间</div>
|
|
|
+ <div>{{ formatDate(selectedGoods.updatedAt) }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <Button label="关闭" @click="detailDialog = false" />
|
|
|
+ </template>
|
|
|
+ </Dialog>
|
|
|
+ </div>
|
|
|
+</template>
|