优化二维码池筛选和日期中文化
This commit is contained in:
@@ -21,6 +21,8 @@ import {
|
||||
updateQrCode,
|
||||
type ChatQrCode,
|
||||
type CreateQrCodePayload,
|
||||
type QrCodeBooleanFilter,
|
||||
type QrCodeExpireStatus,
|
||||
type QrCodeStatus,
|
||||
type QrCodeStats,
|
||||
} from '@/features/admin/api/adminQrCodes'
|
||||
@@ -40,7 +42,24 @@ const canManageQrCodes = computed(() => adminSession.hasPermission('qrcode:manag
|
||||
const currentPage = ref(1)
|
||||
const currentPageSize = ref(20)
|
||||
const total = ref(0)
|
||||
const statusFilter = ref<QrCodeStatus | ''>('')
|
||||
|
||||
type QrCodeFilters = {
|
||||
keyword: string
|
||||
status: QrCodeStatus | ''
|
||||
wecom_renamed: QrCodeBooleanFilter | ''
|
||||
bound: QrCodeBooleanFilter | ''
|
||||
expire_status: QrCodeExpireStatus | ''
|
||||
created_range: string[]
|
||||
}
|
||||
|
||||
const filters = reactive<QrCodeFilters>({
|
||||
keyword: '',
|
||||
status: '',
|
||||
wecom_renamed: '',
|
||||
bound: '',
|
||||
expire_status: '',
|
||||
created_range: [],
|
||||
})
|
||||
|
||||
// 上传弹窗
|
||||
const uploadVisible = ref(false)
|
||||
@@ -81,6 +100,23 @@ const statusOptions: { label: string; value: QrCodeStatus | ''; type: string }[]
|
||||
{ label: '已停用', value: 'disabled', type: 'danger' },
|
||||
]
|
||||
|
||||
const renameFilterOptions: { label: string; value: QrCodeBooleanFilter }[] = [
|
||||
{ label: '已改名', value: 'true' },
|
||||
{ label: '未改名', value: 'false' },
|
||||
]
|
||||
|
||||
const boundFilterOptions: { label: string; value: QrCodeBooleanFilter }[] = [
|
||||
{ label: '已绑定', value: 'true' },
|
||||
{ label: '未绑定', value: 'false' },
|
||||
]
|
||||
|
||||
const expireStatusOptions: { label: string; value: QrCodeExpireStatus }[] = [
|
||||
{ label: '未过期', value: 'valid' },
|
||||
{ label: '即将过期', value: 'expiring_soon' },
|
||||
{ label: '已过期', value: 'expired' },
|
||||
{ label: '永久有效', value: 'permanent' },
|
||||
]
|
||||
|
||||
const statusMap = computed(() => {
|
||||
const m: Record<string, { label: string; type: string }> = {}
|
||||
statusOptions.forEach(o => {
|
||||
@@ -104,11 +140,25 @@ const selectedRows = ref<ChatQrCode[]>([])
|
||||
const selectedCount = computed(() => selectedRows.value.length)
|
||||
const selectedIssuedCount = computed(() => selectedRows.value.filter(qrcodeWasIssued).length)
|
||||
|
||||
function filterDateToISO(value?: string) {
|
||||
if (!value) return undefined
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) return undefined
|
||||
return date.toISOString()
|
||||
}
|
||||
|
||||
async function loadList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const [createdStart, createdEnd] = filters.created_range || []
|
||||
const res = await fetchQrCodes({
|
||||
status: statusFilter.value || undefined,
|
||||
status: filters.status || undefined,
|
||||
keyword: filters.keyword.trim() || undefined,
|
||||
wecom_renamed: filters.wecom_renamed || undefined,
|
||||
bound: filters.bound || undefined,
|
||||
expire_status: filters.expire_status || undefined,
|
||||
created_start: filterDateToISO(createdStart),
|
||||
created_end: filterDateToISO(createdEnd),
|
||||
page: currentPage.value,
|
||||
limit: currentPageSize.value,
|
||||
})
|
||||
@@ -133,6 +183,16 @@ function handleFilter() {
|
||||
loadList()
|
||||
}
|
||||
|
||||
function resetFilters() {
|
||||
filters.keyword = ''
|
||||
filters.status = ''
|
||||
filters.wecom_renamed = ''
|
||||
filters.bound = ''
|
||||
filters.expire_status = ''
|
||||
filters.created_range = []
|
||||
handleFilter()
|
||||
}
|
||||
|
||||
function handlePageChange() {
|
||||
loadList()
|
||||
}
|
||||
@@ -452,11 +512,20 @@ onMounted(reloadAll)
|
||||
<el-card shadow="never" class="toolbar-card">
|
||||
<div class="toolbar">
|
||||
<div class="filter-area">
|
||||
<el-input
|
||||
v-model="filters.keyword"
|
||||
clearable
|
||||
class="keyword-filter"
|
||||
:prefix-icon="Search"
|
||||
placeholder="群名 / 备注 / 绑定群 / ID"
|
||||
@clear="handleFilter"
|
||||
@keyup.enter="handleFilter"
|
||||
/>
|
||||
<el-select
|
||||
v-model="statusFilter"
|
||||
v-model="filters.status"
|
||||
placeholder="状态筛选"
|
||||
clearable
|
||||
style="width: 140px"
|
||||
class="compact-filter"
|
||||
@change="handleFilter"
|
||||
>
|
||||
<el-option
|
||||
@@ -466,7 +535,61 @@ onMounted(reloadAll)
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button :icon="Search" @click="handleFilter">筛选</el-button>
|
||||
<el-select
|
||||
v-model="filters.wecom_renamed"
|
||||
placeholder="企微改名"
|
||||
clearable
|
||||
class="compact-filter"
|
||||
@change="handleFilter"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in renameFilterOptions"
|
||||
:key="`rename-${opt.value}`"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-model="filters.bound"
|
||||
placeholder="绑定群"
|
||||
clearable
|
||||
class="compact-filter"
|
||||
@change="handleFilter"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in boundFilterOptions"
|
||||
:key="`bound-${opt.value}`"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-model="filters.expire_status"
|
||||
placeholder="过期状态"
|
||||
clearable
|
||||
class="compact-filter"
|
||||
@change="handleFilter"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in expireStatusOptions"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-date-picker
|
||||
v-model="filters.created_range"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="上传开始"
|
||||
end-placeholder="上传结束"
|
||||
format="YYYY-MM-DD HH:mm"
|
||||
value-format="YYYY-MM-DDTHH:mm:ss"
|
||||
class="date-range-filter"
|
||||
@change="handleFilter"
|
||||
/>
|
||||
<el-button type="primary" :icon="Search" @click="handleFilter">筛选</el-button>
|
||||
<el-button @click="resetFilters">重置</el-button>
|
||||
</div>
|
||||
<div class="action-area">
|
||||
<span v-if="selectedCount" class="selection-summary">已选 {{ selectedCount }} 条</span>
|
||||
@@ -833,7 +956,7 @@ onMounted(reloadAll)
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
@@ -845,6 +968,29 @@ onMounted(reloadAll)
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-area {
|
||||
flex: 1 1 auto;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.action-area {
|
||||
flex: 0 0 auto;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.keyword-filter {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.compact-filter {
|
||||
width: 128px;
|
||||
}
|
||||
|
||||
.date-range-filter {
|
||||
width: 330px;
|
||||
}
|
||||
|
||||
.qrcode-table {
|
||||
border-radius: 10px;
|
||||
box-shadow: none;
|
||||
@@ -992,6 +1138,30 @@ onMounted(reloadAll)
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
.toolbar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.action-area {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.keyword-filter,
|
||||
.compact-filter,
|
||||
.date-range-filter {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.filter-area,
|
||||
.action-area {
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user