修复二维码库存与过期时间编辑

This commit is contained in:
yml
2026-06-17 20:02:41 +08:00
parent 12f589f0df
commit ac98841992
7 changed files with 286 additions and 95 deletions
@@ -36,9 +36,11 @@ export interface CreateQrCodePayload {
}
export interface UpdateQrCodePayload {
image_url?: string
note?: string
status?: QrCodeStatus
expires_at?: string | null
clear_expires_at?: boolean
}
export async function fetchQrCodes(query: QrCodeListQuery = {}) {
@@ -49,17 +51,7 @@ export async function fetchQrCodes(query: QrCodeListQuery = {}) {
silent: true,
}
)
// 后端返回 { data: [...], pagination: { total, page, limit } },适配为 PaginatedResult
const resp = data.data as unknown as {
data: ChatQrCode[]
pagination: { total: number; page: number; limit: number }
}
return {
items: resp.data,
total: resp.pagination.total,
page: resp.pagination.page,
page_size: resp.pagination.limit,
} as PaginatedResult<ChatQrCode>
return data.data
}
export async function fetchQrCodeStats() {
@@ -39,8 +39,10 @@ const uploadForm = reactive({
// 编辑弹窗
const editVisible = ref(false)
const editUploading = ref(false)
const editForm = reactive({
id: 0,
image_url: '',
note: '',
status: 'unused' as QrCodeStatus,
expires_at: '' as string,
@@ -144,17 +146,37 @@ async function submitUpload() {
function openEdit(row: ChatQrCode) {
editForm.id = row.id
editForm.image_url = row.image_url
editForm.note = row.note
editForm.status = row.status
editForm.expires_at = row.expires_at ? row.expires_at.replace('T', ' ').slice(0, 16) : ''
editVisible.value = true
}
function clearEditExpiresAt() {
editForm.expires_at = ''
}
async function handleEditImageUpload(options: { file: File }) {
editUploading.value = true
try {
const uploaded = await uploadAdminFile(options.file, 'qrcode')
editForm.image_url = uploaded.url
ElMessage.success('图片已上传')
} catch {
ElMessage.error('图片上传失败')
} finally {
editUploading.value = false
}
}
async function submitEdit() {
await updateQrCode(editForm.id, {
image_url: editForm.image_url,
note: editForm.note,
status: editForm.status,
expires_at: editForm.expires_at ? new Date(editForm.expires_at).toISOString() : null,
expires_at: editForm.expires_at ? new Date(editForm.expires_at).toISOString() : undefined,
clear_expires_at: !editForm.expires_at,
})
ElMessage.success('已更新')
editVisible.value = false
@@ -233,61 +255,67 @@ onMounted(reloadAll)
<!-- 列表 -->
<el-card shadow="never" class="table-card">
<el-table v-loading="loading" :data="qrcodes" class="table-panel">
<el-table-column label="二维码" width="120">
<el-table v-loading="loading" :data="qrcodes" class="table-panel qrcode-table" :fit="true">
<el-table-column label="二维码" class-name="qr-col" align="center">
<template #default="{ row }">
<AuthImage
:source="row.image_url"
fit="cover"
:image-style="{ width: '72px', height: '72px', borderRadius: '6px' }"
:preview-src-list="[row.image_url]"
/>
<div class="qrcode-thumb">
<AuthImage
:source="row.image_url"
fit="cover"
:image-style="{ width: '56px', height: '56px', borderRadius: '6px' }"
:preview-src-list="[row.image_url]"
/>
</div>
</template>
</el-table-column>
<el-table-column label="状态" width="100">
<el-table-column label="状态" class-name="status-col" align="center">
<template #default="{ row }">
<el-tag :type="(statusMap[row.status]?.type as any) || 'info'">
<el-tag :type="(statusMap[row.status]?.type as any) || 'info'" size="small">
{{ statusMap[row.status]?.label || row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="备注" min-width="160" show-overflow-tooltip>
<template #default="{ row }">{{ row.note || '-' }}</template>
</el-table-column>
<el-table-column label="绑定发布群" width="120">
<el-table-column label="备注" class-name="note-col" show-overflow-tooltip>
<template #default="{ row }">
{{ row.conversation_id ? `#${row.conversation_id}` : '-' }}
<span class="note-cell">{{ row.note || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="过期时间" width="170">
<el-table-column label="绑定群" class-name="group-col" align="center">
<template #default="{ row }">
<span :class="{ 'expired-tag': row.expires_at && new Date(row.expires_at) < new Date() }">
<span class="group-cell">{{ row.conversation_id ? `#${row.conversation_id}` : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="过期时间" class-name="expire-col" align="center">
<template #default="{ row }">
<span
class="expire-cell"
:class="{ 'expired-tag': row.expires_at && new Date(row.expires_at) < new Date() }"
>
{{ row.expires_at ? formatDateTime(row.expires_at) : '永久' }}
</span>
</template>
</el-table-column>
<el-table-column label="创建时间" width="170">
<template #default="{ row }">{{ formatDateTime(row.created_at) }}</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<el-table-column label="操作" class-name="action-col" align="center">
<template #default="{ row }">
<el-button link type="primary" size="small" @click="openEdit(row)">编辑</el-button>
<el-button
v-if="row.status !== 'disabled'"
link
type="warning"
size="small"
@click="handleDisable(row)"
>停用</el-button
>
<el-button
v-if="row.status === 'unused'"
link
type="danger"
size="small"
@click="handleDelete(row)"
>删除</el-button
>
<div class="table-actions">
<el-button text type="primary" size="small" @click="openEdit(row)">编辑</el-button>
<el-button
v-if="row.status !== 'disabled'"
text
type="warning"
size="small"
@click="handleDisable(row)"
>停用</el-button
>
<el-button
v-if="row.status === 'unused'"
text
type="danger"
size="small"
@click="handleDelete(row)"
>删除</el-button
>
</div>
</template>
</el-table-column>
</el-table>
@@ -355,6 +383,27 @@ onMounted(reloadAll)
<!-- 编辑弹窗 -->
<el-dialog v-model="editVisible" title="编辑二维码" width="440px">
<el-form label-width="90px">
<el-form-item label="二维码图片">
<div class="edit-image-area">
<AuthImage
v-if="editForm.image_url"
:source="editForm.image_url"
fit="cover"
:image-style="{ width: '96px', height: '96px', borderRadius: '6px' }"
/>
<el-upload
:auto-upload="true"
:show-file-list="false"
:http-request="(opt: any) => handleEditImageUpload(opt)"
accept="image/*"
:disabled="editUploading"
>
<el-button size="small" :loading="editUploading">
{{ editForm.image_url ? '替换图片' : '上传图片' }}
</el-button>
</el-upload>
</div>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="editForm.status" style="width: 100%">
<el-option label="待用" value="unused" />
@@ -365,14 +414,17 @@ onMounted(reloadAll)
<el-input v-model="editForm.note" placeholder="7月企微群A" maxlength="50" />
</el-form-item>
<el-form-item label="过期时间">
<el-date-picker
v-model="editForm.expires_at"
type="datetime"
placeholder="留空为永久"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DDTHH:mm"
style="width: 100%"
/>
<div class="expire-editor">
<el-date-picker
v-model="editForm.expires_at"
type="datetime"
placeholder="留空为永久"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DDTHH:mm"
style="width: 100%"
/>
<el-button :disabled="!editForm.expires_at" @click="clearEditExpiresAt">清空</el-button>
</div>
</el-form-item>
</el-form>
<template #footer>
@@ -400,6 +452,15 @@ onMounted(reloadAll)
text-align: center;
}
.toolbar-card,
.table-card {
border-radius: 10px;
}
.table-card :deep(.el-card__body) {
padding: 20px;
}
.stat-value {
font-size: 28px;
font-weight: 600;
@@ -426,6 +487,7 @@ onMounted(reloadAll)
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.filter-area,
@@ -435,6 +497,101 @@ onMounted(reloadAll)
align-items: center;
}
.qrcode-table {
border-radius: 10px;
box-shadow: none;
padding: 0;
}
.qrcode-table :deep(.el-table__inner-wrapper) {
min-width: 0;
}
.qrcode-table :deep(.el-table__header th) {
height: 42px;
background: #f8f9fb;
}
.qrcode-table :deep(.el-table__cell) {
padding: 10px 0;
}
.qrcode-table :deep(.cell) {
padding: 0 14px;
}
.qrcode-table :deep(.el-table__fixed-right) {
box-shadow: -8px 0 12px rgba(31, 45, 61, 0.04);
}
.qrcode-table :deep(.el-table__header),
.qrcode-table :deep(.el-table__body) {
width: 100% !important;
}
.qrcode-table :deep(.el-table__header-wrapper),
.qrcode-table :deep(.el-table__body-wrapper) {
overflow-x: hidden;
}
.qrcode-table :deep(.qr-col) {
width: 12%;
}
.qrcode-table :deep(.status-col) {
width: 10%;
}
.qrcode-table :deep(.note-col) {
width: 40%;
}
.qrcode-table :deep(.group-col) {
width: 10%;
}
.qrcode-table :deep(.expire-col) {
width: 16%;
}
.qrcode-table :deep(.action-col) {
width: 12%;
}
.qrcode-thumb {
display: inline-flex;
align-items: center;
justify-content: center;
width: 64px;
height: 64px;
border: 1px solid #eef1f5;
border-radius: 8px;
background: #ffffff;
}
.note-cell {
color: #303846;
font-weight: 500;
}
.group-cell,
.expire-cell {
color: #5f6b7a;
font-size: 13px;
}
.table-actions {
display: flex;
justify-content: center;
gap: 6px;
white-space: nowrap;
}
.table-actions :deep(.el-button) {
margin-left: 0;
padding: 4px 6px;
}
.upload-preview {
display: flex;
flex-direction: column;
@@ -463,4 +620,17 @@ onMounted(reloadAll)
.expired-tag {
color: var(--el-color-danger);
}
.edit-image-area {
display: flex;
align-items: center;
gap: 12px;
}
.expire-editor {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
}
</style>