diff --git a/backend/internal/router/admin/announcements.go b/backend/internal/router/admin/announcements.go
index 508b7dd..b72b9bb 100644
--- a/backend/internal/router/admin/announcements.go
+++ b/backend/internal/router/admin/announcements.go
@@ -1,4 +1,4 @@
-package admin
+package admin
import (
"fmt"
@@ -28,6 +28,10 @@ func handleGetAllAnnouncements(c *gin.Context) {
page := c.DefaultQuery("page", "1")
pageSize := c.DefaultQuery("page_size", "20")
+ applicationIDFilter := c.Query("application_id")
+ typeFilter := c.Query("type")
+ statusFilter := c.Query("status")
+ searchFilter := c.Query("search")
var total int64
@@ -59,7 +63,36 @@ func handleGetAllAnnouncements(c *gin.Context) {
log.Printf("[DEBUG] appNameMap: %v\n", appNameMap)
- database.DB.Model(&model.Announcement{}).Where("application_id IN ?", appIDs).Count(&total)
+ countQuery := database.DB.Model(&model.Announcement{}).Where("application_id IN ?", appIDs)
+ if applicationIDFilter != "" {
+ countQuery = countQuery.Where("application_id = ?", applicationIDFilter)
+ }
+ if typeFilter != "" {
+ countQuery = countQuery.Where("type = ?", typeFilter)
+ }
+ if statusFilter != "" {
+ countQuery = countQuery.Where("status = ?", statusFilter)
+ }
+ if searchFilter != "" {
+ searchLower := strings.ToLower(searchFilter)
+ countQuery = countQuery.Where("LOWER(title) LIKE ? OR LOWER(content) LIKE ?", "%"+searchLower+"%", "%"+searchLower+"%")
+ }
+ countQuery.Count(&total)
+
+ query := database.DB.Where("application_id IN ?", appIDs)
+ if applicationIDFilter != "" {
+ query = query.Where("application_id = ?", applicationIDFilter)
+ }
+ if typeFilter != "" {
+ query = query.Where("type = ?", typeFilter)
+ }
+ if statusFilter != "" {
+ query = query.Where("status = ?", statusFilter)
+ }
+ if searchFilter != "" {
+ searchLower := strings.ToLower(searchFilter)
+ query = query.Where("LOWER(title) LIKE ? OR LOWER(content) LIKE ?", "%"+searchLower+"%", "%"+searchLower+"%")
+ }
var announcements []model.Announcement
offset := 0
@@ -72,7 +105,7 @@ func handleGetAllAnnouncements(c *gin.Context) {
limit = pageSizeInt
}
- if err := database.DB.Where("application_id IN ?", appIDs).Order("is_top DESC, created_at DESC").Limit(limit).Offset(offset).Find(&announcements).Error; err != nil {
+ if err := query.Order("is_top DESC, created_at DESC").Limit(limit).Offset(offset).Find(&announcements).Error; err != nil {
response.Error(c, 500, "获取公告列表失败")
return
}
diff --git a/backend/internal/router/admin/cloud.go b/backend/internal/router/admin/cloud.go
index 501bf2b..e21d25a 100644
--- a/backend/internal/router/admin/cloud.go
+++ b/backend/internal/router/admin/cloud.go
@@ -722,6 +722,7 @@ func handleGetCloudVariableRecords(c *gin.Context) {
userIDFilter := c.Query("user_id")
startDate := c.Query("start_date")
endDate := c.Query("end_date")
+ searchQuery := c.Query("search")
var total int64
query := database.DB.Model(&model.CloudVariableRecord{}).Where("cloud_variable_id = ?", variable.ID)
@@ -734,6 +735,102 @@ func handleGetCloudVariableRecords(c *gin.Context) {
if endDate != "" {
query = query.Where("created_at <= ?", endDate+" 23:59:59")
}
+
+ if searchQuery != "" {
+ var allRecords []model.CloudVariableRecord
+ if err := query.Order("created_at DESC").Find(&allRecords).Error; err != nil {
+ response.Error(c, 500, "获取记录失败")
+ return
+ }
+
+ var filteredRecords []model.CloudVariableRecord
+ searchLower := strings.ToLower(searchQuery)
+ for _, record := range allRecords {
+ if strings.Contains(strings.ToLower(fmt.Sprintf("%d", record.ID)), searchLower) {
+ filteredRecords = append(filteredRecords, record)
+ continue
+ }
+
+ if record.AppUserID != nil {
+ var appUser model.AppUser
+ if err := database.DB.Select("username").First(&appUser, *record.AppUserID).Error; err == nil {
+ if strings.Contains(strings.ToLower(appUser.Username), searchLower) {
+ filteredRecords = append(filteredRecords, record)
+ continue
+ }
+ }
+ }
+
+ if strings.Contains(strings.ToLower(record.CreatedAt), searchLower) {
+ filteredRecords = append(filteredRecords, record)
+ continue
+ }
+
+ if record.Data != "" {
+ if strings.Contains(strings.ToLower(record.Data), searchLower) {
+ filteredRecords = append(filteredRecords, record)
+ continue
+ }
+
+ var parsedData interface{}
+ if err := json.Unmarshal([]byte(record.Data), &parsedData); err == nil {
+ if searchData(parsedData, searchLower) {
+ filteredRecords = append(filteredRecords, record)
+ continue
+ }
+ }
+ }
+ }
+
+ total = int64(len(filteredRecords))
+ start := (page - 1) * pageSize
+ end := start + pageSize
+ if start > len(filteredRecords) {
+ filteredRecords = []model.CloudVariableRecord{}
+ } else {
+ if end > len(filteredRecords) {
+ end = len(filteredRecords)
+ }
+ filteredRecords = filteredRecords[start:end]
+ }
+
+ result := make([]gin.H, len(filteredRecords))
+ for i, r := range filteredRecords {
+ var parsedData interface{}
+ if r.Data != "" {
+ json.Unmarshal([]byte(r.Data), &parsedData)
+ }
+ if parsedData == nil && r.Data != "" {
+ parsedData = r.Data
+ }
+ record := gin.H{
+ "id": r.ID,
+ "data": parsedData,
+ "created_at": r.CreatedAt,
+ }
+ if r.AppUserID != nil {
+ record["user_id"] = r.AppUserID
+ var appUser model.AppUser
+ if err := database.DB.Select("id, username").First(&appUser, *r.AppUserID).Error; err == nil {
+ record["user"] = gin.H{
+ "id": appUser.ID,
+ "username": appUser.Username,
+ }
+ }
+ }
+ result[i] = record
+ }
+
+ response.Success(c, gin.H{
+ "records": result,
+ "total": total,
+ "page": page,
+ "page_size": pageSize,
+ "total_pages": (total + int64(pageSize) - 1) / int64(pageSize),
+ })
+ return
+ }
+
query.Count(&total)
var records []model.CloudVariableRecord
@@ -779,6 +876,36 @@ func handleGetCloudVariableRecords(c *gin.Context) {
})
}
+func searchData(data interface{}, searchLower string) bool {
+ switch v := data.(type) {
+ case map[string]interface{}:
+ for _, value := range v {
+ if searchData(value, searchLower) {
+ return true
+ }
+ }
+ case []interface{}:
+ for _, item := range v {
+ if searchData(item, searchLower) {
+ return true
+ }
+ }
+ case string:
+ if strings.Contains(strings.ToLower(v), searchLower) {
+ return true
+ }
+ case float64, int, int64, float32:
+ if strings.Contains(strings.ToLower(fmt.Sprintf("%v", v)), searchLower) {
+ return true
+ }
+ case bool:
+ if strings.Contains(strings.ToLower(fmt.Sprintf("%v", v)), searchLower) {
+ return true
+ }
+ }
+ return false
+}
+
func handleDeleteCloudVariableRecords(c *gin.Context) {
userID := c.GetUint("user_id")
id := c.Param("id")
diff --git a/backend/internal/router/admin/devices.go b/backend/internal/router/admin/devices.go
index c4e04df..0d3fd3b 100644
--- a/backend/internal/router/admin/devices.go
+++ b/backend/internal/router/admin/devices.go
@@ -42,6 +42,8 @@ func handleGetDevices(c *gin.Context) {
userIDFilter := c.Query("user_id")
deviceIDFilter := c.Query("device_id")
+ applicationIDFilter := c.Query("application_id")
+ statusFilter := c.Query("status")
var devices []model.UserDevice
var appHeartbeatTimeoutMap map[uint]int
@@ -95,6 +97,12 @@ func handleGetDevices(c *gin.Context) {
if deviceIDFilter != "" {
countQuery = countQuery.Where("device_id = ?", deviceIDFilter)
}
+ if applicationIDFilter != "" {
+ countQuery = countQuery.Where("application_id = ?", applicationIDFilter)
+ }
+ if statusFilter != "" {
+ countQuery = countQuery.Where("status = ?", statusFilter)
+ }
countQuery.Count(&total)
query := database.DB.Preload("User").Preload("Application").Where("application_id IN ?", appIDs)
@@ -107,6 +115,14 @@ func handleGetDevices(c *gin.Context) {
query = query.Where("device_id = ?", deviceIDFilter)
}
+ if applicationIDFilter != "" {
+ query = query.Where("application_id = ?", applicationIDFilter)
+ }
+
+ if statusFilter != "" {
+ query = query.Where("status = ?", statusFilter)
+ }
+
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
offset := (page - 1) * pageSize
@@ -339,6 +355,11 @@ func handleGetSessions(c *gin.Context) {
userID := c.GetUint("user_id")
deviceIDFilter := c.Query("device_id")
appIDFilter := c.Query("app_id")
+ usernameFilter := c.Query("username")
+ searchFilter := c.Query("search")
+
+ page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
+ pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
var ownApps []model.Application
if err := database.DB.Where("user_id = ?", userID).Find(&ownApps).Error; err != nil {
@@ -377,7 +398,7 @@ func handleGetSessions(c *gin.Context) {
}
if len(appIDs) == 0 {
- response.Success(c, gin.H{"sessions": []SessionWithDetails{}})
+ response.Success(c, gin.H{"sessions": []SessionWithDetails{}, "total": 0, "page": page, "page_size": pageSize})
return
}
@@ -391,8 +412,13 @@ func handleGetSessions(c *gin.Context) {
query = query.Where("application_id = ?", appIDFilter)
}
+ var total int64
+ query.Count(&total)
+
+ offset := (page - 1) * pageSize
+
var sessions []model.DeviceSession
- if err := query.Find(&sessions).Error; err != nil {
+ if err := query.Offset(offset).Limit(pageSize).Find(&sessions).Error; err != nil {
response.Error(c, 500, "获取会话列表失败")
return
}
@@ -418,6 +444,22 @@ func handleGetSessions(c *gin.Context) {
timeoutThreshold := time.Now().Add(-time.Duration(heartbeatTimeout) * time.Second)
isOnline := session.LastHeartbeat != nil && session.LastHeartbeat.After(timeoutThreshold)
+ if usernameFilter != "" && user.Username != usernameFilter {
+ continue
+ }
+
+ if searchFilter != "" {
+ searchLower := strings.ToLower(searchFilter)
+ instanceIDMatch := strings.Contains(strings.ToLower(session.InstanceID), searchLower)
+ deviceIDMatch := strings.Contains(strings.ToLower(device.DeviceID), searchLower)
+ usernameMatch := strings.Contains(strings.ToLower(user.Username), searchLower)
+ deviceNameMatch := strings.Contains(strings.ToLower(device.DeviceName), searchLower)
+
+ if !instanceIDMatch && !deviceIDMatch && !usernameMatch && !deviceNameMatch {
+ continue
+ }
+ }
+
sessionsWithDetails = append(sessionsWithDetails, SessionWithDetails{
DeviceSession: session,
DeviceID: device.DeviceID,
@@ -428,7 +470,7 @@ func handleGetSessions(c *gin.Context) {
})
}
- response.Success(c, gin.H{"sessions": sessionsWithDetails})
+ response.Success(c, gin.H{"sessions": sessionsWithDetails, "total": total, "page": page, "page_size": pageSize})
}
func handleDeleteSession(c *gin.Context) {
diff --git a/backend/internal/router/admin/users.go b/backend/internal/router/admin/users.go
index 9d5b7b1..cdb4770 100644
--- a/backend/internal/router/admin/users.go
+++ b/backend/internal/router/admin/users.go
@@ -3,6 +3,7 @@ package admin
import (
"fmt"
"log"
+ "strconv"
"time"
"verification-platform-backend/internal/database"
"verification-platform-backend/internal/model"
@@ -57,10 +58,14 @@ func handleGetUsers(c *gin.Context) {
userID := c.GetUint("user_id")
log.Printf("[DEBUG] handleGetUsers called, userID: %d", userID)
- var users []model.AppUser
- var appHeartbeatTimeoutMap map[uint]int
+ page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
+ pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
applicationID := c.Query("application_id")
+ statusFilter := c.Query("status")
+
+ var users []model.AppUser
+ var appHeartbeatTimeoutMap map[uint]int
if applicationID != "" {
var appID uint
if _, err := fmt.Sscanf(applicationID, "%d", &appID); err != nil {
@@ -81,8 +86,13 @@ func handleGetUsers(c *gin.Context) {
}
appHeartbeatTimeoutMap[app.ID] = timeout
+ query := database.DB.Preload("Application").Where("application_id = ?", appID)
+ if statusFilter != "" {
+ query = query.Where("status = ?", statusFilter)
+ }
+
if app.UserID == userID {
- if err := database.DB.Preload("Application").Where("application_id = ?", appID).Find(&users).Error; err != nil {
+ if err := query.Find(&users).Error; err != nil {
response.Error(c, 500, "获取用户列表失败")
return
}
@@ -102,7 +112,11 @@ func handleGetUsers(c *gin.Context) {
}
if len(cardUserIDs) > 0 {
- if err := database.DB.Preload("Application").Where("id IN ?", cardUserIDs).Find(&users).Error; err != nil {
+ query = database.DB.Preload("Application").Where("id IN ?", cardUserIDs)
+ if statusFilter != "" {
+ query = query.Where("status = ?", statusFilter)
+ }
+ if err := query.Find(&users).Error; err != nil {
response.Error(c, 500, "获取用户列表失败")
return
}
@@ -137,7 +151,11 @@ func handleGetUsers(c *gin.Context) {
}
if len(ownAppIDs) > 0 {
- if err := database.DB.Preload("Application").Where("application_id IN ?", ownAppIDs).Find(&users).Error; err != nil {
+ query := database.DB.Preload("Application").Where("application_id IN ?", ownAppIDs)
+ if statusFilter != "" {
+ query = query.Where("status = ?", statusFilter)
+ }
+ if err := query.Find(&users).Error; err != nil {
response.Error(c, 500, "获取用户列表失败")
return
}
@@ -179,8 +197,12 @@ func handleGetUsers(c *gin.Context) {
}
if len(allCardUserIDs) > 0 {
+ query := database.DB.Preload("Application").Where("id IN ?", allCardUserIDs)
+ if statusFilter != "" {
+ query = query.Where("status = ?", statusFilter)
+ }
var appUsers []model.AppUser
- if err := database.DB.Preload("Application").Where("id IN ?", allCardUserIDs).Find(&appUsers).Error; err != nil {
+ if err := query.Find(&appUsers).Error; err != nil {
log.Printf("[DEBUG] Failed to get users: %v", err)
} else {
users = append(users, appUsers...)
@@ -246,9 +268,21 @@ func handleGetUsers(c *gin.Context) {
})
}
+ offset := (page - 1) * pageSize
+ end := offset + pageSize
+ if offset > len(usersWithStatus) {
+ offset = len(usersWithStatus)
+ }
+ if end > len(usersWithStatus) {
+ end = len(usersWithStatus)
+ }
+ paginatedUsers := usersWithStatus[offset:end]
+
responseData := gin.H{
- "users": usersWithStatus,
+ "users": paginatedUsers,
"total": totalCount,
+ "page": page,
+ "page_size": pageSize,
"online_count": onlineCount,
"offline_count": offlineCount,
"banned_count": bannedCount,
diff --git a/backend/internal/router/admin/versions.go b/backend/internal/router/admin/versions.go
index 213d164..c1bcf0c 100644
--- a/backend/internal/router/admin/versions.go
+++ b/backend/internal/router/admin/versions.go
@@ -1,4 +1,4 @@
-package admin
+package admin
import (
"archive/zip"
@@ -40,6 +40,10 @@ func handleGetAllVersions(c *gin.Context) {
page := c.DefaultQuery("page", "1")
pageSize := c.DefaultQuery("page_size", "20")
+ applicationIDFilter := c.Query("application_id")
+ updateStrategyFilter := c.Query("update_strategy")
+ updateMethodFilter := c.Query("update_method")
+ searchFilter := c.Query("search")
var total int64
@@ -71,7 +75,36 @@ func handleGetAllVersions(c *gin.Context) {
log.Printf("[DEBUG] appNameMap: %v\n", appNameMap)
- database.DB.Model(&model.Version{}).Where("application_id IN ?", appIDs).Count(&total)
+ countQuery := database.DB.Model(&model.Version{}).Where("application_id IN ?", appIDs)
+ if applicationIDFilter != "" {
+ countQuery = countQuery.Where("application_id = ?", applicationIDFilter)
+ }
+ if updateStrategyFilter != "" {
+ countQuery = countQuery.Where("update_strategy = ?", updateStrategyFilter)
+ }
+ if updateMethodFilter != "" {
+ countQuery = countQuery.Where("update_method = ?", updateMethodFilter)
+ }
+ if searchFilter != "" {
+ searchLower := strings.ToLower(searchFilter)
+ countQuery = countQuery.Where("LOWER(version) LIKE ? OR LOWER(description) LIKE ?", "%"+searchLower+"%", "%"+searchLower+"%")
+ }
+ countQuery.Count(&total)
+
+ query := database.DB.Where("application_id IN ?", appIDs)
+ if applicationIDFilter != "" {
+ query = query.Where("application_id = ?", applicationIDFilter)
+ }
+ if updateStrategyFilter != "" {
+ query = query.Where("update_strategy = ?", updateStrategyFilter)
+ }
+ if updateMethodFilter != "" {
+ query = query.Where("update_method = ?", updateMethodFilter)
+ }
+ if searchFilter != "" {
+ searchLower := strings.ToLower(searchFilter)
+ query = query.Where("LOWER(version) LIKE ? OR LOWER(description) LIKE ?", "%"+searchLower+"%", "%"+searchLower+"%")
+ }
var versions []model.Version
offset := 0
@@ -84,7 +117,7 @@ func handleGetAllVersions(c *gin.Context) {
limit = pageSizeInt
}
- if err := database.DB.Where("application_id IN ?", appIDs).Order("created_at DESC").Limit(limit).Offset(offset).Find(&versions).Error; err != nil {
+ if err := query.Order("created_at DESC").Limit(limit).Offset(offset).Find(&versions).Error; err != nil {
response.Error(c, 500, "获取版本列表失败")
return
}
diff --git a/frontend/src/components/ui/date-picker/DateTimePicker.vue b/frontend/src/components/ui/date-picker/DateTimePicker.vue
index c768308..7c17e23 100644
--- a/frontend/src/components/ui/date-picker/DateTimePicker.vue
+++ b/frontend/src/components/ui/date-picker/DateTimePicker.vue
@@ -57,6 +57,12 @@ function emitValue() {
}
}
+watch(open, (isOpen) => {
+ if (!isOpen && selectedDate.value) {
+ emitValue()
+ }
+})
+
function resetTime() {
selectedDate.value = undefined
selectedHour.value = '00'
diff --git a/frontend/src/pages/admin/announcements/index.vue b/frontend/src/pages/admin/announcements/index.vue
index 2fa917e..e73e7a5 100644
--- a/frontend/src/pages/admin/announcements/index.vue
+++ b/frontend/src/pages/admin/announcements/index.vue
@@ -1,6 +1,6 @@
@@ -288,7 +304,8 @@ onMounted(() => {
{
return Array.from(keys).sort()
})
+const filteredRecords = computed(() => {
+ return records.value
+})
+
+const paginatedRecords = computed(() => {
+ return records.value
+})
+
const columns = computed[]>(() => {
const cols: ColumnDef[] = [SelectColumn as ColumnDef]
@@ -278,9 +286,16 @@ watch([page, pageSize], () => {
fetchRecords()
})
-watch(dateRange, () => {
+watch(searchQuery, () => {
page.value = 1
fetchRecords()
+})
+
+watch(dateRange, (newVal, oldVal) => {
+ if (newVal.from !== oldVal.from || newVal.to !== oldVal.to) {
+ page.value = 1
+ fetchRecords()
+ }
}, { deep: true })
async function fetchVariable() {
@@ -306,9 +321,18 @@ async function fetchRecords() {
const params = new URLSearchParams()
params.append('page', String(page.value))
params.append('page_size', String(pageSize.value))
- if (dateRange.value.from) params.append('start_date', dateRange.value.from)
- if (dateRange.value.to) params.append('end_date', dateRange.value.to)
- if (searchQuery.value) params.append('search', searchQuery.value)
+
+ if (dateRange.value.from) {
+ const fromDate = dateRange.value.from.split('T')[0]
+ params.append('start_date', fromDate)
+ }
+ if (dateRange.value.to) {
+ const toDate = dateRange.value.to.split('T')[0]
+ params.append('end_date', toDate)
+ }
+ if (searchQuery.value.trim()) {
+ params.append('search', searchQuery.value.trim())
+ }
const data = await api.get<{ records: VariableRecord[], total: number }>(`/dev/cloud-variables/${variable.value.id}/records?${params}`)
records.value = data?.records || []
@@ -374,11 +398,6 @@ async function handleBatchDelete() {
}
}
-function handleSearch() {
- page.value = 1
- fetchRecords()
-}
-
onMounted(async () => {
await fetchVariable()
if (variable.value) {
@@ -472,7 +491,6 @@ onMounted(async () => {
v-model="searchQuery"
:placeholder="t('admin.cloudVariables.records.searchPlaceholder')"
class="pl-8 h-8 w-[150px] lg:w-[250px]"
- @keydown.enter="handleSearch"
/>
import { Ban, CheckCircle, Layers, Monitor } from 'lucide-vue-next'
-import { computed, onMounted, ref } from 'vue'
+import { computed, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import { toast } from 'vue-sonner'
@@ -35,6 +35,9 @@ const users = ref([])
const appFilter = ref('')
const statusFilter = ref('')
const userIdFilter = ref('')
+const currentPage = ref(1)
+const pageSize = ref(20)
+const total = ref(0)
const deleteDialogOpen = ref(false)
const deleteTarget = ref(null)
@@ -43,27 +46,9 @@ const batchDeleteIds = ref<(string | number)[]>([])
const forceOfflineDialogOpen = ref(false)
const forceOfflineTarget = ref(null)
-const filteredDevices = computed(() => {
- let result = devices.value
-
- if (appFilter.value) {
- result = result.filter(device => String(device.application_id) === appFilter.value)
- }
-
- if (statusFilter.value) {
- result = result.filter(device => device.status === statusFilter.value)
- }
-
- if (userIdFilter.value) {
- result = result.filter(device => String(device.user_id) === userIdFilter.value)
- }
-
- return result
-})
-
-const totalDevices = computed(() => filteredDevices.value.length)
-const onlineSessionCount = computed(() => filteredDevices.value.reduce((sum, d) => sum + (d.online_sessions || 0), 0))
-const bannedCount = computed(() => filteredDevices.value.filter(device => device.status === 'banned').length)
+const totalDevices = computed(() => total.value)
+const onlineSessionCount = computed(() => devices.value.reduce((sum, d) => sum + (d.online_sessions || 0), 0))
+const bannedCount = computed(() => devices.value.filter(device => device.status === 'banned').length)
const applicationOptions = computed(() => {
return applications.value.map(app => ({
@@ -110,20 +95,29 @@ async function fetchDevices() {
const userId = route.query.user_id as string
const deviceId = route.query.device_id as string
- let url = '/dev/devices'
- const params: string[] = []
+ const params = new URLSearchParams()
+ params.append('page', String(currentPage.value))
+ params.append('page_size', String(pageSize.value))
+
if (userId) {
- params.push(`user_id=${userId}`)
+ params.append('user_id', userId)
}
if (deviceId) {
- params.push(`device_id=${deviceId}`)
+ params.append('device_id', deviceId)
}
- if (params.length > 0) {
- url += `?${params.join('&')}`
+ if (appFilter.value) {
+ params.append('application_id', appFilter.value)
+ }
+ if (statusFilter.value) {
+ params.append('status', statusFilter.value)
+ }
+ if (userIdFilter.value) {
+ params.append('user_id', userIdFilter.value)
}
- const data = await api.get<{ devices: Device[] }>(url)
+ const data = await api.get<{ devices: Device[], total: number }>(`/dev/devices?${params.toString()}`)
devices.value = Array.isArray(data?.devices) ? data.devices : []
+ total.value = data?.total || 0
}
catch (error) {
console.error('获取设备列表失败:', error)
@@ -235,6 +229,15 @@ onMounted(() => {
fetchUsers()
fetchDevices()
})
+
+watch([currentPage, pageSize], () => {
+ fetchDevices()
+})
+
+watch([appFilter, statusFilter, userIdFilter], () => {
+ currentPage.value = 1
+ fetchDevices()
+})
@@ -307,7 +310,8 @@ onMounted(() => {
import { Boxes, Monitor, Users, Wifi } from 'lucide-vue-next'
-import { computed, onMounted, ref } from 'vue'
+import { computed, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import { toast } from 'vue-sonner'
@@ -28,6 +28,9 @@ const applications = ref([])
const appFilter = ref('')
const searchFilter = ref('')
const usernameFilter = ref('')
+const currentPage = ref(1)
+const pageSize = ref(20)
+const total = ref(0)
const forceOfflineDialogOpen = ref(false)
const forceOfflineTarget = ref(null)
@@ -50,32 +53,27 @@ const usernameOptions = computed(() => {
const filteredSessions = computed(() => {
let result = sessions.value.filter(s => s.is_online)
- if (appFilter.value) {
- result = result.filter(session => String(session.application_id) === appFilter.value)
- }
-
- if (usernameFilter.value) {
- result = result.filter(session => session.username === usernameFilter.value)
- }
-
- if (searchFilter.value) {
- const search = searchFilter.value.toLowerCase()
- result = result.filter(session =>
- session.instance_id?.toLowerCase().includes(search)
- || session.device_identifier?.toLowerCase().includes(search)
- || session.username?.toLowerCase().includes(search)
- || session.device_name?.toLowerCase().includes(search),
- )
- }
-
return result
})
-const totalSessions = computed(() => filteredSessions.value.length)
+const totalSessions = computed(() => total.value)
const onlineApps = computed(() => new Set(filteredSessions.value.map(s => s.application_id)).size)
const onlineDevices = computed(() => new Set(filteredSessions.value.filter(s => s.device_identifier).map(s => s.device_identifier)).size)
const onlineUsers = computed(() => new Set(filteredSessions.value.filter(s => s.username).map(s => s.username)).size)
+const serverPagination = computed(() => ({
+ page: currentPage.value,
+ pageSize: pageSize.value,
+ total: total.value,
+ onPageChange: (newPage: number) => {
+ currentPage.value = newPage
+ },
+ onPageSizeChange: (newPageSize: number) => {
+ pageSize.value = newPageSize
+ currentPage.value = 1
+ },
+}))
+
async function fetchApplications() {
try {
const data = await api.get<{ applications: Application[] }>('/dev/applications')
@@ -89,8 +87,23 @@ async function fetchApplications() {
async function fetchSessions() {
loading.value = true
try {
- const data = await api.get<{ sessions: Session[] }>('/dev/sessions')
+ const params = new URLSearchParams()
+ params.append('page', String(currentPage.value))
+ params.append('page_size', String(pageSize.value))
+
+ if (appFilter.value) {
+ params.append('app_id', appFilter.value)
+ }
+ if (usernameFilter.value) {
+ params.append('username', usernameFilter.value)
+ }
+ if (searchFilter.value) {
+ params.append('search', searchFilter.value)
+ }
+
+ const data = await api.get<{ sessions: Session[], total: number }>(`/dev/sessions?${params.toString()}`)
sessions.value = data?.sessions || []
+ total.value = data?.total || 0
}
catch (error: any) {
console.error('获取会话列表失败:', error)
@@ -133,6 +146,15 @@ onMounted(() => {
fetchApplications()
fetchSessions()
})
+
+watch([currentPage, pageSize], () => {
+ fetchSessions()
+})
+
+watch([appFilter, usernameFilter, searchFilter], () => {
+ currentPage.value = 1
+ fetchSessions()
+})
@@ -205,6 +227,7 @@ onMounted(() => {
import { Ban, CheckCircle, Clock, Plus, Users } from 'lucide-vue-next'
-import { computed, onActivated, onMounted, ref } from 'vue'
+import { computed, onActivated, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { toast } from 'vue-sonner'
@@ -34,6 +34,9 @@ const lastLoginStartDate = ref('')
const lastLoginEndDate = ref('')
const createdStartDate = ref('')
const createdEndDate = ref('')
+const currentPage = ref(1)
+const pageSize = ref(20)
+const total = ref(0)
const deleteDialogOpen = ref(false)
const deleteTarget = ref(null)
@@ -43,18 +46,10 @@ const batchDeleteIds = ref<(string | number)[]>([])
const filteredUsers = computed(() => {
let result = users.value
- if (appFilter.value) {
- result = result.filter(user => String(user.application_id) === appFilter.value)
- }
-
if (statusFilter.value) {
result = result.filter(user => user.online_status === statusFilter.value)
}
- if (accountStatusFilter.value) {
- result = result.filter(user => user.status === accountStatusFilter.value)
- }
-
if (lastLoginStartDate.value) {
const fromDateTime = lastLoginStartDate.value.includes('T')
? lastLoginStartDate.value.replace('T', ' ')
@@ -94,9 +89,22 @@ const filteredUsers = computed(() => {
return result
})
-const onlineCount = computed(() => filteredUsers.value.filter(user => user.online_status === 'online').length)
-const offlineCount = computed(() => filteredUsers.value.filter(user => user.online_status === 'offline').length)
-const bannedCount = computed(() => filteredUsers.value.filter(user => user.status === 'banned').length)
+const onlineCount = computed(() => users.value.filter(user => user.online_status === 'online').length)
+const offlineCount = computed(() => users.value.filter(user => user.online_status === 'offline').length)
+const bannedCount = computed(() => users.value.filter(user => user.status === 'banned').length)
+
+const serverPagination = computed(() => ({
+ page: currentPage.value,
+ pageSize: pageSize.value,
+ total: total.value,
+ onPageChange: (newPage: number) => {
+ currentPage.value = newPage
+ },
+ onPageSizeChange: (newPageSize: number) => {
+ pageSize.value = newPageSize
+ currentPage.value = 1
+ },
+}))
const applicationOptions = computed(() => {
return applications.value.map(app => ({
@@ -128,8 +136,20 @@ async function fetchApplications() {
async function fetchUsers() {
loading.value = true
try {
- const data = await api.get<{ users: User[] }>('/dev/app-users')
+ 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 (accountStatusFilter.value) {
+ params.append('status', accountStatusFilter.value)
+ }
+
+ const data = await api.get<{ users: User[], total: number }>(`/dev/app-users?${params.toString()}`)
users.value = Array.isArray(data?.users) ? data.users : []
+ total.value = data?.total || 0
}
catch (error) {
console.error('获取用户列表失败:', error)
@@ -231,6 +251,15 @@ onMounted(() => {
onActivated(() => {
fetchUsers()
})
+
+watch([currentPage, pageSize], () => {
+ fetchUsers()
+})
+
+watch([appFilter, statusFilter, accountStatusFilter, lastLoginStartDate, lastLoginEndDate, createdStartDate, createdEndDate], () => {
+ currentPage.value = 1
+ fetchUsers()
+})
@@ -311,6 +340,7 @@ onActivated(() => {
ref="tableRef"
:loading
:data="filteredUsers"
+ :server-pagination="serverPagination"
:on-edit="goToEdit"
:on-toggle-status="toggleUserStatus"
:on-delete="confirmDeleteUser"
diff --git a/frontend/src/pages/admin/versions/index.vue b/frontend/src/pages/admin/versions/index.vue
index c80a24a..6f1d610 100644
--- a/frontend/src/pages/admin/versions/index.vue
+++ b/frontend/src/pages/admin/versions/index.vue
@@ -1,6 +1,6 @@
@@ -293,7 +312,8 @@ onMounted(() => {