feat(billing):添加交易记录的查询条件
This commit is contained in:
1 parent
06aff72726
commit
664e083cf6
3 files changed
+160
-17
No files matched your search
@@ -22,9 +22,16 @@ type TenantWalletRes struct {
|
||||
}
|
||||
|
||||
type TenantWalletTransactionsReq struct {
|
||||
g.Meta `path:"/wallet/transactions" method:"get" mime:"json" tags:"租户控制台-钱包" summary:"交易记录"`
|
||||
Page int `json:"page" in:"query" d:"1"`
|
||||
PageSize int `json:"page_size" in:"query" d:"20"`
|
||||
g.Meta `path:"/wallet/transactions" method:"get" mime:"json" tags:"租户控制台-钱包" summary:"交易记录"`
|
||||
Page int `json:"page" in:"query" d:"1"`
|
||||
PageSize int `json:"page_size" in:"query" d:"20"`
|
||||
Type string `json:"type" in:"query" dc:"交易类型筛选"`
|
||||
StartDate string `json:"start_date" in:"query" dc:"开始日期"`
|
||||
EndDate string `json:"end_date" in:"query" dc:"结束日期"`
|
||||
AmountMin float64 `json:"amount_min" in:"query" dc:"最小金额"`
|
||||
AmountMax float64 `json:"amount_max" in:"query" dc:"最大金额"`
|
||||
Username string `json:"username" in:"query" dc:"用户名筛选"`
|
||||
ModelName string `json:"model_name" in:"query" dc:"模型名称筛选"`
|
||||
}
|
||||
|
||||
type TenantWalletTransactionsRes struct {
|
||||
@@ -36,8 +43,15 @@ type TenantWalletTransactionsRes struct {
|
||||
|
||||
// TenantWalletTransactionsExportReq 导出交易记录请求
|
||||
type TenantWalletTransactionsExportReq struct {
|
||||
g.Meta `path:"/wallet/transactions/export" method:"get" mime:"json" tags:"租户控制台-钱包" summary:"导出交易记录"`
|
||||
Format string `json:"format" in:"query" d:"csv" v:"in:csv,xlsx" dc:"导出格式:csv / xlsx"`
|
||||
g.Meta `path:"/wallet/transactions/export" method:"get" mime:"json" tags:"租户控制台-钱包" summary:"导出交易记录"`
|
||||
Format string `json:"format" in:"query" d:"csv" v:"in:csv,xlsx" dc:"导出格式:csv / xlsx"`
|
||||
Type string `json:"type" in:"query" dc:"交易类型筛选"`
|
||||
StartDate string `json:"start_date" in:"query" dc:"开始日期"`
|
||||
EndDate string `json:"end_date" in:"query" dc:"结束日期"`
|
||||
AmountMin float64 `json:"amount_min" in:"query" dc:"最小金额"`
|
||||
AmountMax float64 `json:"amount_max" in:"query" dc:"最大金额"`
|
||||
Username string `json:"username" in:"query" dc:"用户名筛选"`
|
||||
ModelName string `json:"model_name" in:"query" dc:"模型名称筛选"`
|
||||
}
|
||||
|
||||
type TenantWalletTransactionsExportRes struct{}
|
||||
|
||||
@@ -84,6 +84,28 @@ func (s *sTenant) WalletTransactions(ctx context.Context, req *v1.TenantWalletTr
|
||||
query := dao.BilTransactions.Ctx(ctx).
|
||||
Where("bil_transactions.tenant_id", tenantID)
|
||||
|
||||
if req.Type != "" {
|
||||
query = query.Where("bil_transactions.type", req.Type)
|
||||
}
|
||||
if req.StartDate != "" {
|
||||
query = query.Where("bil_transactions.created_at >= ?", req.StartDate+" 00:00:00")
|
||||
}
|
||||
if req.EndDate != "" {
|
||||
query = query.Where("bil_transactions.created_at <= ?", req.EndDate+" 23:59:59")
|
||||
}
|
||||
if req.AmountMin != 0 {
|
||||
query = query.Where("bil_transactions.amount >= ?", req.AmountMin)
|
||||
}
|
||||
if req.AmountMax != 0 {
|
||||
query = query.Where("bil_transactions.amount <= ?", req.AmountMax)
|
||||
}
|
||||
if req.Username != "" {
|
||||
query = query.Where("tu.username LIKE ?", "%"+req.Username+"%")
|
||||
}
|
||||
if req.ModelName != "" {
|
||||
query = query.Where("bil_transactions.model_name LIKE ?", "%"+req.ModelName+"%")
|
||||
}
|
||||
|
||||
type transactionRow struct {
|
||||
Id int64 `json:"id"`
|
||||
Type string `json:"type"`
|
||||
@@ -410,10 +432,30 @@ func (s *sTenant) ExportWalletTransactions(ctx context.Context, req *v1.TenantWa
|
||||
CreatedAt *gtime.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
q := dao.BilTransactions.Ctx(ctx).
|
||||
Where("tenant_id", tenantID)
|
||||
|
||||
if req.Type != "" {
|
||||
q = q.Where("type", req.Type)
|
||||
}
|
||||
if req.StartDate != "" {
|
||||
q = q.Where("created_at >= ?", req.StartDate+" 00:00:00")
|
||||
}
|
||||
if req.EndDate != "" {
|
||||
q = q.Where("created_at <= ?", req.EndDate+" 23:59:59")
|
||||
}
|
||||
if req.AmountMin != 0 {
|
||||
q = q.Where("amount >= ?", req.AmountMin)
|
||||
}
|
||||
if req.AmountMax != 0 {
|
||||
q = q.Where("amount <= ?", req.AmountMax)
|
||||
}
|
||||
if req.ModelName != "" {
|
||||
q = q.Where("model_name LIKE ?", "%"+req.ModelName+"%")
|
||||
}
|
||||
|
||||
var records []transactionRow
|
||||
err := dao.BilTransactions.Ctx(ctx).
|
||||
Where("tenant_id", tenantID).
|
||||
Fields("id, type, amount, balance_after, user_id, request_id, model_name, description, created_at").
|
||||
err := q.Fields("id, type, amount, balance_after, user_id, request_id, model_name, description, created_at").
|
||||
OrderDesc("created_at").
|
||||
Limit(1000).Offset(offset).
|
||||
Scan(&records)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import Icon from '@/components/common/Icon.vue'
|
||||
import BaseSelect from '@/components/common/BaseSelect.vue'
|
||||
import request from '@/utils/request'
|
||||
import { useExport } from '@/composables/useExport'
|
||||
|
||||
@@ -10,9 +11,27 @@ const page = ref(1)
|
||||
const pageSize = 20
|
||||
const total = ref(0)
|
||||
|
||||
// Filter state
|
||||
const filterType = ref('')
|
||||
const filterStartDate = ref('')
|
||||
const filterEndDate = ref('')
|
||||
const filterAmountMin = ref('')
|
||||
const filterAmountMax = ref('')
|
||||
const filterUsername = ref('')
|
||||
const filterModel = ref('')
|
||||
|
||||
const showExportDropdown = ref(false)
|
||||
const { exporting, exportFile } = useExport({
|
||||
url: '/tenant/wallet/transactions/export',
|
||||
getFilters: () => ({
|
||||
type: filterType.value,
|
||||
start_date: filterStartDate.value,
|
||||
end_date: filterEndDate.value,
|
||||
amount_min: filterAmountMin.value || undefined,
|
||||
amount_max: filterAmountMax.value || undefined,
|
||||
username: filterUsername.value,
|
||||
model_name: filterModel.value,
|
||||
}),
|
||||
})
|
||||
|
||||
const txTypeLabel: Record<string, string> = {
|
||||
@@ -37,6 +56,18 @@ const txTypeBadgeClass: Record<string, string> = {
|
||||
unfreeze: 'badge-gray',
|
||||
}
|
||||
|
||||
const typeOptions = [
|
||||
{ value: '', label: '全部' },
|
||||
{ value: 'recharge', label: '充值' },
|
||||
{ value: 'consume', label: '消费' },
|
||||
{ value: 'pre_deduct', label: '预扣' },
|
||||
{ value: 'settle', label: '结算' },
|
||||
{ value: 'refund', label: '退款' },
|
||||
{ value: 'adjust', label: '调整' },
|
||||
{ value: 'freeze', label: '冻结' },
|
||||
{ value: 'unfreeze', label: '解冻' },
|
||||
]
|
||||
|
||||
function formatAmount(amount: number): string {
|
||||
if (amount >= 0) return '+$' + amount.toFixed(6)
|
||||
return '-$' + Math.abs(amount).toFixed(6)
|
||||
@@ -45,8 +76,17 @@ function formatAmount(amount: number): string {
|
||||
async function fetchTransactions() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params: any = { page: page.value, page_size: pageSize }
|
||||
if (filterType.value) params.type = filterType.value
|
||||
if (filterStartDate.value) params.start_date = filterStartDate.value
|
||||
if (filterEndDate.value) params.end_date = filterEndDate.value
|
||||
if (filterAmountMin.value) params.amount_min = filterAmountMin.value
|
||||
if (filterAmountMax.value) params.amount_max = filterAmountMax.value
|
||||
if (filterUsername.value) params.username = filterUsername.value
|
||||
if (filterModel.value) params.model_name = filterModel.value
|
||||
|
||||
const res: any = await request.get('/tenant/wallet/transactions', {
|
||||
params: { page: page.value, page_size: pageSize },
|
||||
params,
|
||||
})
|
||||
const raw = res.data?.data
|
||||
transactions.value = Array.isArray(raw) ? raw : (raw?.data || raw?.list || [])
|
||||
@@ -58,6 +98,23 @@ async function fetchTransactions() {
|
||||
}
|
||||
}
|
||||
|
||||
function applyFilters() {
|
||||
page.value = 1
|
||||
fetchTransactions()
|
||||
}
|
||||
|
||||
function resetFilters() {
|
||||
filterType.value = ''
|
||||
filterStartDate.value = ''
|
||||
filterEndDate.value = ''
|
||||
filterAmountMin.value = ''
|
||||
filterAmountMax.value = ''
|
||||
filterUsername.value = ''
|
||||
filterModel.value = ''
|
||||
page.value = 1
|
||||
fetchTransactions()
|
||||
}
|
||||
|
||||
function prevPage() {
|
||||
if (page.value > 1) {
|
||||
page.value--
|
||||
@@ -102,6 +159,44 @@ onMounted(fetchTransactions)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-500 whitespace-nowrap">开始日期</label>
|
||||
<input v-model="filterStartDate" type="date" class="input" style="width:140px" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-500 whitespace-nowrap">结束日期</label>
|
||||
<input v-model="filterEndDate" type="date" class="input" style="width:140px" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-500 whitespace-nowrap">类型</label>
|
||||
<BaseSelect v-model="filterType" :options="typeOptions" container-class="w-[100px]" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-500 whitespace-nowrap">金额范围</label>
|
||||
<input v-model="filterAmountMin" type="number" step="0.01" placeholder="最小" class="input" style="width:100px" @keyup.enter="applyFilters" />
|
||||
<span class="text-gray-400">~</span>
|
||||
<input v-model="filterAmountMax" type="number" step="0.01" placeholder="最大" class="input" style="width:100px" @keyup.enter="applyFilters" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-500 whitespace-nowrap">用户名</label>
|
||||
<input v-model="filterUsername" type="text" placeholder="搜索用户" class="input" style="width:120px" @keyup.enter="applyFilters" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-500 whitespace-nowrap">模型</label>
|
||||
<input v-model="filterModel" type="text" placeholder="例如:gpt-4o" class="input" style="width:150px" @keyup.enter="applyFilters" />
|
||||
</div>
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<button class="btn btn-primary btn-sm" @click="applyFilters">搜索</button>
|
||||
<button class="btn btn-secondary btn-sm" @click="resetFilters">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Transactions -->
|
||||
<div class="card">
|
||||
<div v-if="loading" class="p-8 flex justify-center">
|
||||
@@ -114,7 +209,6 @@ onMounted(fetchTransactions)
|
||||
<tr>
|
||||
<th class="min-w-20">类型</th>
|
||||
<th class="min-w-30">金额</th>
|
||||
<th class="min-w-30">扣费来源</th>
|
||||
<th class="min-w-30">余额</th>
|
||||
<th class="min-w-35">用户</th>
|
||||
<th class="min-w-50">请求ID</th>
|
||||
@@ -133,13 +227,6 @@ onMounted(fetchTransactions)
|
||||
<td :class="tx.amount >= 0 ? 'text-emerald-600 font-semibold' : 'text-red-600 font-semibold'">
|
||||
{{ formatAmount(tx.amount) }}
|
||||
</td>
|
||||
<td class="text-gray-700">
|
||||
<template v-if="tx.type === 'consume' || tx.type === 'settle'">
|
||||
<span v-if="tx.plan_deduction > 0" class="badge badge-primary text-xs mr-1">套餐抵扣</span>
|
||||
<span v-if="tx.wallet_deduction > 0" class="badge badge-gray text-xs">钱包</span>
|
||||
</template>
|
||||
<template v-else>--</template>
|
||||
</td>
|
||||
<td class="text-gray-700">${{ tx.balance_after?.toFixed(6) ?? '--' }}</td>
|
||||
<td class="text-gray-700 text-sm">{{ tx.username || '--' }}</td>
|
||||
<td class="text-gray-500 text-xs font-mono">{{ tx.request_id || '--' }}</td>
|
||||
|
||||
Reference in new issue
Block a user