修复工单、日志、卡密管理页面的分页和筛选功能
- 工单管理:后端添加分页和筛选支持(status, priority, type, 日期范围) - 日志管理:前端使用服务端分页和筛选(后端已支持) - 卡密管理:前端使用服务端分页和筛选(后端已支持) - 所有页面移除前端筛选逻辑,改为服务端筛选 - 修复分页总数与筛选数据不匹配的问题 - 添加watch监听筛选条件变化,自动刷新数据
This commit is contained in:
@@ -34,6 +34,9 @@ const cards = ref<Card[]>([])
|
||||
const tableRef = ref()
|
||||
const applications = ref<Application[]>([])
|
||||
const cardTypes = ref<CardType[]>([])
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const total = ref(0)
|
||||
|
||||
const appFilter = ref<string>('')
|
||||
const cardTypeFilter = ref<string>('')
|
||||
@@ -56,52 +59,9 @@ const filteredCardTypes = computed(() => {
|
||||
return cardTypes.value
|
||||
})
|
||||
|
||||
const filteredCards = computed(() => {
|
||||
let result = cards.value
|
||||
|
||||
if (appFilter.value) {
|
||||
result = result.filter(card => String(card.application_id) === appFilter.value)
|
||||
}
|
||||
|
||||
if (cardTypeFilter.value) {
|
||||
result = result.filter(card => String(card.card_type_id) === cardTypeFilter.value)
|
||||
}
|
||||
|
||||
if (statusFilter.value) {
|
||||
result = result.filter(card => card.status === statusFilter.value)
|
||||
}
|
||||
|
||||
if (startDate.value) {
|
||||
const fromDateTime = startDate.value.includes('T')
|
||||
? startDate.value.replace('T', ' ')
|
||||
: `${startDate.value} 00:00`
|
||||
result = result.filter(card => card.created_at >= fromDateTime)
|
||||
}
|
||||
|
||||
if (endDate.value) {
|
||||
const toDateTime = endDate.value.includes('T')
|
||||
? endDate.value.replace('T', ' ')
|
||||
: `${endDate.value} 23:59`
|
||||
result = result.filter(card => card.created_at <= toDateTime)
|
||||
}
|
||||
|
||||
if (searchFilter.value) {
|
||||
const search = searchFilter.value.toLowerCase()
|
||||
result = result.filter(card =>
|
||||
card.card_key?.toLowerCase().includes(search)
|
||||
|| card.application?.name?.toLowerCase().includes(search)
|
||||
|| card.card_type?.name?.toLowerCase().includes(search)
|
||||
|| card.creator?.username?.toLowerCase().includes(search)
|
||||
|| card.app_user?.username?.toLowerCase().includes(search),
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
const unusedCount = computed(() => filteredCards.value.filter(card => card.status === 'unused').length)
|
||||
const usedCount = computed(() => filteredCards.value.filter(card => card.status === 'used').length)
|
||||
const bannedCount = computed(() => filteredCards.value.filter(card => card.status === 'banned').length)
|
||||
const unusedCount = computed(() => cards.value.filter(card => card.status === 'unused').length)
|
||||
const usedCount = computed(() => cards.value.filter(card => card.status === 'used').length)
|
||||
const bannedCount = computed(() => cards.value.filter(card => card.status === 'banned').length)
|
||||
|
||||
const applicationOptions = computed(() => {
|
||||
return applications.value.map(app => ({
|
||||
@@ -149,16 +109,32 @@ async function fetchCardTypes() {
|
||||
async function fetchCards() {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await api.get<any>('/dev/cards')
|
||||
if (data && typeof data === 'object' && data.cards) {
|
||||
cards.value = data.cards
|
||||
const params = new URLSearchParams()
|
||||
params.append('page', String(currentPage.value))
|
||||
params.append('page_size', String(pageSize.value))
|
||||
|
||||
if (appFilter.value) {
|
||||
params.append('application_id', appFilter.value)
|
||||
}
|
||||
else if (Array.isArray(data)) {
|
||||
cards.value = data
|
||||
if (cardTypeFilter.value) {
|
||||
params.append('card_type_id', cardTypeFilter.value)
|
||||
}
|
||||
else {
|
||||
cards.value = []
|
||||
if (statusFilter.value) {
|
||||
params.append('status', statusFilter.value)
|
||||
}
|
||||
if (startDate.value) {
|
||||
params.append('start_date', startDate.value.split('T')[0])
|
||||
}
|
||||
if (endDate.value) {
|
||||
params.append('end_date', endDate.value.split('T')[0])
|
||||
}
|
||||
if (searchFilter.value) {
|
||||
params.append('search', searchFilter.value.trim())
|
||||
}
|
||||
|
||||
const data = await api.get<{ cards: Card[], total: number }>(`/dev/cards?${params.toString()}`)
|
||||
cards.value = data?.cards || []
|
||||
total.value = data?.total || 0
|
||||
}
|
||||
catch (error) {
|
||||
console.error('获取卡密列表失败:', error)
|
||||
@@ -281,6 +257,21 @@ onMounted(() => {
|
||||
fetchCardTypes()
|
||||
fetchCards()
|
||||
})
|
||||
|
||||
watch([appFilter, cardTypeFilter, statusFilter, startDate, endDate, searchFilter], () => {
|
||||
currentPage.value = 1
|
||||
fetchCards()
|
||||
})
|
||||
|
||||
const serverPagination = computed(() => ({
|
||||
currentPage: currentPage.value,
|
||||
pageSize: pageSize.value,
|
||||
total: total.value,
|
||||
onChange: (page: number) => {
|
||||
currentPage.value = page
|
||||
fetchCards()
|
||||
},
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -307,7 +298,7 @@ onMounted(() => {
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
<div class="text-2xl font-bold">
|
||||
{{ filteredCards.length }}
|
||||
{{ total }}
|
||||
</div>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
@@ -360,7 +351,8 @@ onMounted(() => {
|
||||
<DataTable
|
||||
ref="tableRef"
|
||||
:loading
|
||||
:data="filteredCards"
|
||||
:data="cards"
|
||||
:server-pagination="serverPagination"
|
||||
:on-toggle-status="toggleStatus"
|
||||
:on-delete="confirmDeleteCard"
|
||||
:search-filter="searchFilter"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { Activity, CheckCircle, FileText, XCircle } from 'lucide-vue-next'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import type { Log } from '@/pages/admin/logs/data/schema'
|
||||
@@ -21,6 +21,9 @@ interface Application {
|
||||
const loading = ref(true)
|
||||
const logs = ref<Log[]>([])
|
||||
const applications = ref<Application[]>([])
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const total = ref(0)
|
||||
|
||||
const appFilter = ref<string>('')
|
||||
const typeFilter = ref<string>('')
|
||||
@@ -28,41 +31,9 @@ const statusFilter = ref<string>('')
|
||||
const startDate = ref<string>('')
|
||||
const endDate = ref<string>('')
|
||||
|
||||
const filteredLogs = computed(() => {
|
||||
let result = logs.value
|
||||
|
||||
if (appFilter.value) {
|
||||
result = result.filter(log => String(log.application_id) === appFilter.value)
|
||||
}
|
||||
|
||||
if (typeFilter.value) {
|
||||
result = result.filter(log => log.log_type === typeFilter.value)
|
||||
}
|
||||
|
||||
if (statusFilter.value) {
|
||||
result = result.filter(log => log.status === statusFilter.value)
|
||||
}
|
||||
|
||||
if (startDate.value) {
|
||||
const fromDateTime = startDate.value.includes('T')
|
||||
? startDate.value.replace('T', ' ')
|
||||
: `${startDate.value} 00:00`
|
||||
result = result.filter(log => log.created_at >= fromDateTime)
|
||||
}
|
||||
|
||||
if (endDate.value) {
|
||||
const toDateTime = endDate.value.includes('T')
|
||||
? endDate.value.replace('T', ' ')
|
||||
: `${endDate.value} 23:59`
|
||||
result = result.filter(log => log.created_at <= toDateTime)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
const successCount = computed(() => filteredLogs.value.filter(log => log.status === 'success').length)
|
||||
const failedCount = computed(() => filteredLogs.value.filter(log => log.status === 'failed').length)
|
||||
const operationCount = computed(() => filteredLogs.value.filter(log => log.log_type === 'operation').length)
|
||||
const successCount = computed(() => logs.value.filter(log => log.status === 'success').length)
|
||||
const failedCount = computed(() => logs.value.filter(log => log.status === 'failed').length)
|
||||
const operationCount = computed(() => logs.value.filter(log => log.log_type === 'operation').length)
|
||||
|
||||
const applicationOptions = computed(() => {
|
||||
return applications.value.map(app => ({
|
||||
@@ -95,8 +66,29 @@ async function fetchApplications() {
|
||||
async function fetchLogs() {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await api.get<{ logs: Log[] }>('/dev/logs')
|
||||
const params = new URLSearchParams()
|
||||
params.append('page', String(currentPage.value))
|
||||
params.append('page_size', String(pageSize.value))
|
||||
|
||||
if (appFilter.value) {
|
||||
params.append('application_id', appFilter.value)
|
||||
}
|
||||
if (typeFilter.value) {
|
||||
params.append('type', typeFilter.value)
|
||||
}
|
||||
if (statusFilter.value) {
|
||||
params.append('status', statusFilter.value)
|
||||
}
|
||||
if (startDate.value) {
|
||||
params.append('start_date', startDate.value.split('T')[0])
|
||||
}
|
||||
if (endDate.value) {
|
||||
params.append('end_date', endDate.value.split('T')[0])
|
||||
}
|
||||
|
||||
const data = await api.get<{ logs: Log[], total: number }>(`/dev/logs?${params.toString()}`)
|
||||
logs.value = data?.logs || []
|
||||
total.value = data?.total || 0
|
||||
}
|
||||
catch (error) {
|
||||
console.error('获取日志记录失败:', error)
|
||||
@@ -111,6 +103,21 @@ onMounted(() => {
|
||||
fetchApplications()
|
||||
fetchLogs()
|
||||
})
|
||||
|
||||
watch([appFilter, typeFilter, statusFilter, startDate, endDate], () => {
|
||||
currentPage.value = 1
|
||||
fetchLogs()
|
||||
})
|
||||
|
||||
const serverPagination = computed(() => ({
|
||||
currentPage: currentPage.value,
|
||||
pageSize: pageSize.value,
|
||||
total: total.value,
|
||||
onChange: (page: number) => {
|
||||
currentPage.value = page
|
||||
fetchLogs()
|
||||
},
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -130,7 +137,7 @@ onMounted(() => {
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
<div class="text-2xl font-bold">
|
||||
{{ filteredLogs.length }}
|
||||
{{ total }}
|
||||
</div>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
@@ -182,7 +189,8 @@ onMounted(() => {
|
||||
<UiCardContent class="p-6">
|
||||
<DataTable
|
||||
:loading
|
||||
:data="filteredLogs"
|
||||
:data="logs"
|
||||
:server-pagination="serverPagination"
|
||||
@refresh="fetchLogs"
|
||||
>
|
||||
<template #filters>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { AlertCircle, CheckCircle, Clock, MessageSquare, Plus } from 'lucide-vue-next'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { toast } from 'vue-sonner'
|
||||
@@ -21,6 +21,9 @@ const router = useRouter()
|
||||
const loading = ref(true)
|
||||
const tickets = ref<Ticket[]>([])
|
||||
const tableRef = ref()
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const total = ref(0)
|
||||
|
||||
const statusFilter = ref<string>('')
|
||||
const priorityFilter = ref<string>('')
|
||||
@@ -35,52 +38,6 @@ const deleteTarget = ref<Ticket | null>(null)
|
||||
const batchDeleteDialogOpen = ref(false)
|
||||
const batchDeleteIds = ref<(string | number)[]>([])
|
||||
|
||||
const filteredTickets = computed(() => {
|
||||
let result = [...tickets.value]
|
||||
|
||||
if (statusFilter.value) {
|
||||
result = result.filter(t => t.status === statusFilter.value)
|
||||
}
|
||||
|
||||
if (priorityFilter.value) {
|
||||
result = result.filter(t => t.priority === priorityFilter.value)
|
||||
}
|
||||
|
||||
if (typeFilter.value) {
|
||||
result = result.filter(t => t.type === typeFilter.value)
|
||||
}
|
||||
|
||||
if (createdStartDate.value) {
|
||||
const fromDateTime = createdStartDate.value.includes('T')
|
||||
? createdStartDate.value.replace('T', ' ')
|
||||
: `${createdStartDate.value} 00:00`
|
||||
result = result.filter(t => t.created_at >= fromDateTime)
|
||||
}
|
||||
|
||||
if (createdEndDate.value) {
|
||||
const toDateTime = createdEndDate.value.includes('T')
|
||||
? createdEndDate.value.replace('T', ' ')
|
||||
: `${createdEndDate.value} 23:59`
|
||||
result = result.filter(t => t.created_at <= toDateTime)
|
||||
}
|
||||
|
||||
if (updatedStartDate.value) {
|
||||
const fromDateTime = updatedStartDate.value.includes('T')
|
||||
? updatedStartDate.value.replace('T', ' ')
|
||||
: `${updatedStartDate.value} 00:00`
|
||||
result = result.filter(t => t.updated_at && t.updated_at >= fromDateTime)
|
||||
}
|
||||
|
||||
if (updatedEndDate.value) {
|
||||
const toDateTime = updatedEndDate.value.includes('T')
|
||||
? updatedEndDate.value.replace('T', ' ')
|
||||
: `${updatedEndDate.value} 23:59`
|
||||
result = result.filter(t => t.updated_at && t.updated_at <= toDateTime)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
const openCount = computed(() => tickets.value.filter(t => t.status === 'open').length)
|
||||
const pendingCount = computed(() => tickets.value.filter(t => t.status === 'processing').length)
|
||||
const resolvedCount = computed(() => tickets.value.filter(t => t.status === 'resolved').length)
|
||||
@@ -107,8 +64,35 @@ const typeOptions = computed(() => [
|
||||
async function fetchTickets() {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await api.get<{ tickets: Ticket[] }>('/dev/tickets')
|
||||
const params = new URLSearchParams()
|
||||
params.append('page', String(currentPage.value))
|
||||
params.append('page_size', String(pageSize.value))
|
||||
|
||||
if (statusFilter.value) {
|
||||
params.append('status', statusFilter.value)
|
||||
}
|
||||
if (priorityFilter.value) {
|
||||
params.append('priority', priorityFilter.value)
|
||||
}
|
||||
if (typeFilter.value) {
|
||||
params.append('type', typeFilter.value)
|
||||
}
|
||||
if (createdStartDate.value) {
|
||||
params.append('created_start_date', createdStartDate.value.split('T')[0])
|
||||
}
|
||||
if (createdEndDate.value) {
|
||||
params.append('created_end_date', createdEndDate.value.split('T')[0])
|
||||
}
|
||||
if (updatedStartDate.value) {
|
||||
params.append('updated_start_date', updatedStartDate.value.split('T')[0])
|
||||
}
|
||||
if (updatedEndDate.value) {
|
||||
params.append('updated_end_date', updatedEndDate.value.split('T')[0])
|
||||
}
|
||||
|
||||
const data = await api.get<{ tickets: Ticket[], total: number }>(`/dev/tickets?${params.toString()}`)
|
||||
tickets.value = data?.tickets || []
|
||||
total.value = data?.total || 0
|
||||
}
|
||||
catch (error) {
|
||||
console.error('获取工单列表失败:', error)
|
||||
@@ -183,6 +167,21 @@ async function handleBatchDelete() {
|
||||
onMounted(() => {
|
||||
fetchTickets()
|
||||
})
|
||||
|
||||
watch([statusFilter, priorityFilter, typeFilter, createdStartDate, createdEndDate, updatedStartDate, updatedEndDate], () => {
|
||||
currentPage.value = 1
|
||||
fetchTickets()
|
||||
})
|
||||
|
||||
const serverPagination = computed(() => ({
|
||||
currentPage: currentPage.value,
|
||||
pageSize: pageSize.value,
|
||||
total: total.value,
|
||||
onChange: (page: number) => {
|
||||
currentPage.value = page
|
||||
fetchTickets()
|
||||
},
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -251,7 +250,7 @@ onMounted(() => {
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
<div class="text-2xl font-bold">
|
||||
{{ filteredTickets.length }}
|
||||
{{ total }}
|
||||
</div>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
@@ -262,7 +261,8 @@ onMounted(() => {
|
||||
<DataTable
|
||||
ref="tableRef"
|
||||
:loading
|
||||
:data="filteredTickets"
|
||||
:data="tickets"
|
||||
:server-pagination="serverPagination"
|
||||
:on-update-status="updateTicketStatus"
|
||||
:on-view-detail="openTicketDetail"
|
||||
:on-delete="confirmDeleteTicket"
|
||||
|
||||
Reference in New Issue
Block a user