agent后台finance和users添加服务端分页和统计
This commit is contained in:
@@ -3,6 +3,7 @@ package agent
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -432,6 +433,11 @@ func handleGetUsers(c *gin.Context) {
|
||||
var total int64
|
||||
database.DB.Model(&model.AppUser{}).Where("application_id IN ?", appIDs).Count(&total)
|
||||
|
||||
var activeCount, disabledCount, bannedCount int64
|
||||
database.DB.Model(&model.AppUser{}).Where("application_id IN ? AND status = ?", appIDs, "active").Count(&activeCount)
|
||||
database.DB.Model(&model.AppUser{}).Where("application_id IN ? AND status = ?", appIDs, "disabled").Count(&disabledCount)
|
||||
database.DB.Model(&model.AppUser{}).Where("application_id IN ? AND status = ?", appIDs, "banned").Count(&bannedCount)
|
||||
|
||||
var users []model.AppUser
|
||||
offset := 0
|
||||
if pageInt, err := strconv.Atoi(page); err == nil && pageInt > 1 {
|
||||
@@ -446,8 +452,11 @@ func handleGetUsers(c *gin.Context) {
|
||||
database.DB.Where("application_id IN ?", appIDs).Order("created_at DESC").Limit(limit).Offset(offset).Find(&users)
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"users": users,
|
||||
"total": total,
|
||||
"users": users,
|
||||
"total": total,
|
||||
"active_count": activeCount,
|
||||
"disabled_count": disabledCount,
|
||||
"banned_count": bannedCount,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -460,12 +469,113 @@ func handleGetFinance(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var records []model.RechargeRecord
|
||||
database.DB.Where("user_id = ?", userID).Order("created_at DESC").Limit(20).Find(&records)
|
||||
typeFilter := c.Query("type")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
type TransactionItem struct {
|
||||
ID uint `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Amount float64 `json:"amount"`
|
||||
Description string `json:"description"`
|
||||
Balance float64 `json:"balance"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
var allTransactions []TransactionItem
|
||||
|
||||
var rechargeRecords []model.RechargeRecord
|
||||
database.DB.Where("user_id = ? AND status = ?", userID, "success").Order("created_at DESC").Find(&rechargeRecords)
|
||||
for _, r := range rechargeRecords {
|
||||
txType := "recharge"
|
||||
if r.PaymentType == "refund" || r.Remark != "" && strings.Contains(strings.ToLower(r.Remark), "refund") {
|
||||
txType = "refund"
|
||||
}
|
||||
desc := r.Remark
|
||||
if desc == "" {
|
||||
desc = "充值"
|
||||
}
|
||||
allTransactions = append(allTransactions, TransactionItem{
|
||||
ID: r.ID,
|
||||
Type: txType,
|
||||
Amount: r.Amount,
|
||||
Description: desc,
|
||||
Balance: user.Balance,
|
||||
CreatedAt: r.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
})
|
||||
}
|
||||
|
||||
var consumptionRecords []model.ConsumptionRecord
|
||||
database.DB.Where("user_id = ?", userID).Order("created_at DESC").Find(&consumptionRecords)
|
||||
for _, r := range consumptionRecords {
|
||||
desc := r.Description
|
||||
if desc == "" {
|
||||
desc = r.Content
|
||||
}
|
||||
if desc == "" {
|
||||
desc = "消费"
|
||||
}
|
||||
allTransactions = append(allTransactions, TransactionItem{
|
||||
ID: r.ID,
|
||||
Type: "consume",
|
||||
Amount: r.Amount,
|
||||
Description: desc,
|
||||
Balance: r.BalanceAfter,
|
||||
CreatedAt: r.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(allTransactions, func(i, j int) bool {
|
||||
return allTransactions[i].CreatedAt > allTransactions[j].CreatedAt
|
||||
})
|
||||
|
||||
var filtered []TransactionItem
|
||||
if typeFilter != "" {
|
||||
for _, tx := range allTransactions {
|
||||
if tx.Type == typeFilter {
|
||||
filtered = append(filtered, tx)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filtered = allTransactions
|
||||
}
|
||||
|
||||
total := len(filtered)
|
||||
|
||||
rechargeCount := 0
|
||||
consumeCount := 0
|
||||
var totalRecharge, totalConsume float64
|
||||
for _, tx := range allTransactions {
|
||||
if tx.Type == "recharge" || tx.Type == "refund" {
|
||||
rechargeCount++
|
||||
totalRecharge += tx.Amount
|
||||
} else if tx.Type == "consume" {
|
||||
consumeCount++
|
||||
totalConsume += tx.Amount
|
||||
}
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
end := offset + pageSize
|
||||
if offset > total {
|
||||
offset = total
|
||||
}
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
|
||||
paginated := filtered[offset:end]
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"balance": user.Balance,
|
||||
"records": records,
|
||||
"balance": user.Balance,
|
||||
"transactions": paginated,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"recharge_count": rechargeCount,
|
||||
"consume_count": consumeCount,
|
||||
"total_recharge": totalRecharge,
|
||||
"total_consume": totalConsume,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import DataTableToolbar from '@/pages/agent/finance/components/data-table-toolba
|
||||
|
||||
const props = defineProps<Omit<DataTableProps<Transaction>, 'columns'> & {
|
||||
typeFilter?: string
|
||||
serverPagination?: DataTableProps<Transaction>['serverPagination']
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -34,6 +35,7 @@ const table = generateVueTable<Transaction>({
|
||||
get data() { return props.data },
|
||||
get loading() { return props.loading },
|
||||
columns: columns.value,
|
||||
serverPagination: props.serverPagination,
|
||||
})
|
||||
|
||||
const columnLabels: Record<string, string> = {
|
||||
@@ -51,7 +53,7 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DataTable :columns="columns" :data :loading :table @refresh="emit('refresh')">
|
||||
<DataTable :columns="columns" :data :loading :table :server-pagination @refresh="emit('refresh')">
|
||||
<template #toolbar>
|
||||
<div class="space-y-3">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
|
||||
@@ -16,42 +16,48 @@ const balance = ref(0)
|
||||
const transactions = ref<Transaction[]>([])
|
||||
const tableRef = ref()
|
||||
const typeFilter = ref('')
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const total = ref(0)
|
||||
const rechargeCount = ref(0)
|
||||
const consumeCount = ref(0)
|
||||
const totalRecharge = ref(0)
|
||||
const totalConsume = ref(0)
|
||||
|
||||
const filteredTransactions = computed(() => {
|
||||
let result = transactions.value.filter(tx => tx)
|
||||
if (typeFilter.value) {
|
||||
result = result.filter(tx => tx.type === typeFilter.value)
|
||||
}
|
||||
return result
|
||||
})
|
||||
const serverPagination = computed(() => ({
|
||||
page: currentPage.value,
|
||||
pageSize: pageSize.value,
|
||||
total: total.value,
|
||||
onPageChange: (newPage: number) => {
|
||||
currentPage.value = newPage
|
||||
fetchFinance()
|
||||
},
|
||||
onPageSizeChange: (newPageSize: number) => {
|
||||
pageSize.value = newPageSize
|
||||
currentPage.value = 1
|
||||
fetchFinance()
|
||||
},
|
||||
}))
|
||||
|
||||
const totalRecharge = computed(() => {
|
||||
return transactions.value
|
||||
.filter(tx => tx.type === 'recharge' || tx.type === 'refund')
|
||||
.reduce((sum, tx) => sum + (tx.amount || 0), 0)
|
||||
})
|
||||
|
||||
const totalConsume = computed(() => {
|
||||
return transactions.value
|
||||
.filter(tx => tx.type === 'consume')
|
||||
.reduce((sum, tx) => sum + (tx.amount || 0), 0)
|
||||
})
|
||||
|
||||
const rechargeCount = computed(() => transactions.value.filter(tx => tx.type === 'recharge' || tx.type === 'refund').length)
|
||||
const consumeCount = computed(() => transactions.value.filter(tx => tx.type === 'consume').length)
|
||||
|
||||
onMounted(async () => {
|
||||
async function fetchFinance() {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await api.get<any>('/agent/finance')
|
||||
const params = new URLSearchParams()
|
||||
params.append('page', String(currentPage.value))
|
||||
params.append('page_size', String(pageSize.value))
|
||||
if (typeFilter.value) {
|
||||
params.append('type', typeFilter.value)
|
||||
}
|
||||
|
||||
const data = await api.get<any>(`/agent/finance?${params.toString()}`)
|
||||
if (data) {
|
||||
balance.value = data.balance || 0
|
||||
if (Array.isArray(data.transactions)) {
|
||||
transactions.value = data.transactions
|
||||
} else if (Array.isArray(data.data)) {
|
||||
transactions.value = data.data
|
||||
} else {
|
||||
transactions.value = []
|
||||
}
|
||||
transactions.value = data.transactions || []
|
||||
total.value = data.total || 0
|
||||
rechargeCount.value = data.recharge_count || 0
|
||||
consumeCount.value = data.consume_count || 0
|
||||
totalRecharge.value = data.total_recharge || 0
|
||||
totalConsume.value = data.total_consume || 0
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
@@ -61,6 +67,10 @@ onMounted(async () => {
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchFinance()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -166,10 +176,11 @@ onMounted(async () => {
|
||||
v-else
|
||||
ref="tableRef"
|
||||
:loading
|
||||
:data="filteredTransactions"
|
||||
:data="transactions"
|
||||
:server-pagination="serverPagination"
|
||||
:type-filter="typeFilter"
|
||||
@refresh="() => {}"
|
||||
@update:typeFilter="typeFilter = $event"
|
||||
@refresh="fetchFinance"
|
||||
@update:typeFilter="typeFilter = $event; currentPage = 1; fetchFinance()"
|
||||
/>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
|
||||
@@ -16,6 +16,7 @@ import DataTableToolbar from '@/pages/agent/users/components/data-table-toolbar.
|
||||
|
||||
const props = defineProps<Omit<DataTableProps<User>, 'columns'> & {
|
||||
searchFilter?: string
|
||||
serverPagination?: DataTableProps<User>['serverPagination']
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -34,6 +35,7 @@ const table = generateVueTable<User>({
|
||||
get data() { return props.data },
|
||||
get loading() { return props.loading },
|
||||
columns: columns.value,
|
||||
serverPagination: props.serverPagination,
|
||||
})
|
||||
|
||||
const columnLabels: Record<string, string> = {
|
||||
@@ -51,7 +53,7 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DataTable :columns="columns" :data :loading :table @refresh="emit('refresh')">
|
||||
<DataTable :columns="columns" :data :loading :table :server-pagination @refresh="emit('refresh')">
|
||||
<template #toolbar>
|
||||
<div class="space-y-3">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
|
||||
@@ -15,36 +15,44 @@ const loading = ref(true)
|
||||
const users = ref<User[]>([])
|
||||
const tableRef = ref()
|
||||
const searchFilter = ref('')
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const total = ref(0)
|
||||
const activeCount = ref(0)
|
||||
const disabledCount = ref(0)
|
||||
const bannedCount = ref(0)
|
||||
|
||||
const filteredUsers = computed(() => {
|
||||
let result = users.value.filter(u => u)
|
||||
if (searchFilter.value) {
|
||||
const query = searchFilter.value.toLowerCase()
|
||||
result = result.filter(u =>
|
||||
u.username?.toLowerCase().includes(query)
|
||||
|| u.email?.toLowerCase().includes(query),
|
||||
)
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
const activeCount = computed(() => users.value.filter(user => user.status === 'active').length)
|
||||
const disabledCount = computed(() => users.value.filter(user => user.status === 'disabled').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
|
||||
fetchUsers()
|
||||
},
|
||||
onPageSizeChange: (newPageSize: number) => {
|
||||
pageSize.value = newPageSize
|
||||
currentPage.value = 1
|
||||
fetchUsers()
|
||||
},
|
||||
}))
|
||||
|
||||
async function fetchUsers() {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await api.get<any>('/agent/users')
|
||||
if (Array.isArray(data)) {
|
||||
users.value = data
|
||||
} else if (data?.users) {
|
||||
users.value = data.users
|
||||
} else if (data?.data) {
|
||||
users.value = Array.isArray(data.data) ? data.data : []
|
||||
} else {
|
||||
users.value = []
|
||||
const params = new URLSearchParams()
|
||||
params.append('page', String(currentPage.value))
|
||||
params.append('page_size', String(pageSize.value))
|
||||
if (searchFilter.value) {
|
||||
params.append('search', searchFilter.value.trim())
|
||||
}
|
||||
|
||||
const data = await api.get<any>(`/agent/users?${params.toString()}`)
|
||||
users.value = data?.users || []
|
||||
total.value = data?.total || 0
|
||||
activeCount.value = data?.active_count || 0
|
||||
disabledCount.value = data?.disabled_count || 0
|
||||
bannedCount.value = data?.banned_count || 0
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Load users failed:', error)
|
||||
@@ -81,7 +89,7 @@ onMounted(() => {
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
<div class="text-2xl font-bold">
|
||||
{{ users.length }}
|
||||
{{ total }}
|
||||
</div>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
@@ -139,10 +147,11 @@ onMounted(() => {
|
||||
v-else
|
||||
ref="tableRef"
|
||||
:loading
|
||||
:data="filteredUsers"
|
||||
:data="users"
|
||||
:server-pagination="serverPagination"
|
||||
:search-filter="searchFilter"
|
||||
@refresh="fetchUsers"
|
||||
@update:search-filter="searchFilter = $event"
|
||||
@update:search-filter="searchFilter = $event; currentPage = 1; fetchUsers()"
|
||||
/>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
|
||||
Reference in New Issue
Block a user