improve:优化多个前端页面,将输入租户ID改为下拉搜索框

This commit is contained in:
qian committed 2026-05-23 21:33:25 +08:00
1 parent 4b544d7069
commit 5a71b3faff
8 files changed
+197 -67

No files matched your search

+19 -17
View File
@@ -101,15 +101,16 @@ type AdminDashboardRecentAlertsRes struct {
// === 用量日志 ===
type AdminUsageLogListReq struct {
g.Meta `path:"/usage-logs" method:"get" mime:"json" tags:"管理后台-用量" summary:"用量日志列表"`
Page int `json:"page" d:"1" dc:"页码"`
PageSize int `json:"page_size" d:"20" v:"between:1,100" dc:"每页数量"`
TenantID int64 `json:"tenant_id" dc:"租户ID"`
Username string `json:"username" dc:"用户名(模糊匹配)"`
Model string `json:"model" dc:"模型名称"`
Status string `json:"status" dc:"状态"`
StartDate string `json:"start_date" dc:"开始日期"`
EndDate string `json:"end_date" dc:"结束日期"`
g.Meta `path:"/usage-logs" method:"get" mime:"json" tags:"管理后台-用量" summary:"用量日志列表"`
Page int `json:"page" d:"1" dc:"页码"`
PageSize int `json:"page_size" d:"20" v:"between:1,100" dc:"每页数量"`
TenantID int64 `json:"tenant_id" dc:"租户ID"`
Username string `json:"username" dc:"用户名(模糊匹配)"`
Model string `json:"model" dc:"模型名称"`
Status string `json:"status" dc:"状态"`
RequestType int `json:"request_type" dc:"请求类型: 1=同步, 2=流式, 3=异步, 4=WebSocket"`
StartDate string `json:"start_date" dc:"开始日期"`
EndDate string `json:"end_date" dc:"结束日期"`
}
type AdminUsageLogItem struct {
@@ -301,14 +302,15 @@ type AdminWalletSetWarningThresholdRes struct{}
// AdminUsageLogExportReq 导出用量日志请求
type AdminUsageLogExportReq struct {
g.Meta `path:"/usage-logs/export" method:"get" mime:"json" tags:"管理后台-用量" summary:"导出用量日志"`
Format string `json:"format" in:"query" d:"csv" v:"in:csv,xlsx" dc:"导出格式:csv / xlsx"`
TenantID int64 `json:"tenant_id" in:"query" dc:"租户ID"`
Username string `json:"username" in:"query" dc:"用户名(模糊匹配)"`
Model string `json:"model" in:"query" dc:"模型名称"`
Status string `json:"status" in:"query" dc:"状态"`
StartDate string `json:"start_date" in:"query" dc:"开始日期"`
EndDate string `json:"end_date" in:"query" dc:"结束日期"`
g.Meta `path:"/usage-logs/export" method:"get" mime:"json" tags:"管理后台-用量" summary:"导出用量日志"`
Format string `json:"format" in:"query" d:"csv" v:"in:csv,xlsx" dc:"导出格式:csv / xlsx"`
TenantID int64 `json:"tenant_id" in:"query" dc:"租户ID"`
Username string `json:"username" in:"query" dc:"用户名(模糊匹配)"`
Model string `json:"model" in:"query" dc:"模型名称"`
Status string `json:"status" in:"query" dc:"状态"`
RequestType int `json:"request_type" in:"query" dc:"请求类型: 1=同步, 2=流式, 3=异步, 4=WebSocket"`
StartDate string `json:"start_date" in:"query" dc:"开始日期"`
EndDate string `json:"end_date" in:"query" dc:"结束日期"`
}
type AdminUsageLogExportRes struct{}
+8
View File
@@ -218,6 +218,10 @@ func (s *sAdmin) GetAllUsageLogs(ctx context.Context, req *v1.AdminUsageLogListR
conditions = append(conditions, "u.status = ?")
args = append(args, req.Status)
}
if req.RequestType > 0 {
conditions = append(conditions, "u.request_type = ?")
args = append(args, req.RequestType)
}
if req.StartDate != "" {
conditions = append(conditions, "u.created_at >= ?")
args = append(args, req.StartDate+" 00:00:00")
@@ -592,6 +596,10 @@ func (s *sAdmin) ExportUsageLogs(ctx context.Context, req *v1.AdminUsageLogExpor
conditions = append(conditions, "u.status = ?")
args = append(args, req.Status)
}
if req.RequestType > 0 {
conditions = append(conditions, "u.request_type = ?")
args = append(args, req.RequestType)
}
if req.StartDate != "" {
conditions = append(conditions, "u.created_at >= ?")
args = append(args, req.StartDate+" 00:00:00")
@@ -14,7 +14,7 @@ const pagination = reactive({ current: 1, pageSize: 20 })
const filterChannelId = ref<number | undefined>(undefined)
const filterCategory = ref('')
const filterStatusCode = ref<number | undefined>(undefined)
const filterDateRange = ref<string[]>([])
const filterDateRange = ref<string[] | undefined>(undefined)
const filterKeyword = ref('')
// Stats
@@ -156,7 +156,7 @@ async function fetchData() {
if (filterCategory.value) params.error_category = filterCategory.value
if (filterStatusCode.value) params.status_code = filterStatusCode.value
if (filterKeyword.value) params.keyword = filterKeyword.value
if (filterDateRange.value?.length === 2) {
if (filterDateRange.value && filterDateRange.value.length === 2) {
params.start_date = filterDateRange.value[0]
params.end_date = filterDateRange.value[1]
}
@@ -182,7 +182,7 @@ function handleReset() {
filterChannelId.value = undefined
filterCategory.value = ''
filterStatusCode.value = undefined
filterDateRange.value = []
filterDateRange.value = undefined
filterKeyword.value = ''
pagination.current = 1
fetchData()
@@ -269,11 +269,11 @@ onMounted(() => {
@input="(val: string) => filterStatusCode = val ? Number(val) : undefined"
/>
<DatePicker
:model-value="filterDateRange as any"
:model-value="filterDateRange"
range
style="width: 240px"
format="YYYY-MM-DD"
@change="(val: any) => filterDateRange = val || []"
@change="(val: any) => filterDateRange = val"
/>
<Button type="primary" @click="handleSearch">搜索</Button>
<Button @click="handleReset">重置</Button>
@@ -30,6 +30,8 @@ const jobNameMap: Record<string, string> = {
usage_log_cleanup: '用量日志清理',
oauth_token_refresh: 'OAuth 令牌刷新',
cron_execution_cleanup: '执行记录清理',
prededuct_orphan_cleanup: '预扣孤儿清理',
plugin_example_daily_cleanup: '插件示例数据清理',
}
function formatDuration(ms: number): string {
@@ -20,7 +20,7 @@ const pagination = reactive({
pageSizeOptions: [10, 20, 50],
})
const filter = reactive({
tenant_id: '',
tenant_id: null as number | null,
api_key_id: '',
username: '',
request_id: '',
@@ -30,6 +30,21 @@ const filter = reactive({
date_range: [] as string[],
})
const tenantOptions = ref<{ label: string; value: number }[]>([])
async function fetchTenantOptions(keyword: string) {
try {
const res: any = await request.get('/admin/tenants', {
params: { page: 1, page_size: 20, keyword, status: 'active' },
})
const list = res.data?.data?.list || res.data?.list || []
tenantOptions.value = list.map((t: any) => ({
label: `${t.name}(${t.code})`,
value: t.id,
}))
} catch { /* ignore */ }
}
const detailLoading = ref(false)
const showDetail = ref(false)
const detailRecord = ref<any>(null)
@@ -180,7 +195,7 @@ async function fetchData() {
page: pagination.current,
page_size: pagination.pageSize,
}
if (filter.tenant_id) params.tenant_id = parseInt(filter.tenant_id)
if (filter.tenant_id) params.tenant_id = filter.tenant_id
if (filter.api_key_id) params.api_key_id = parseInt(filter.api_key_id)
if (filter.username) params.username = filter.username
if (filter.method) params.method = filter.method
@@ -229,7 +244,7 @@ function handleFilter() {
}
function handleReset() {
filter.tenant_id = ''
filter.tenant_id = null
filter.api_key_id = ''
filter.username = ''
filter.request_id = ''
@@ -242,6 +257,7 @@ function handleReset() {
}
onMounted(() => {
fetchTenantOptions('')
const route = useRoute()
if (route.query.request_id) {
filter.request_id = String(route.query.request_id)
@@ -259,12 +275,17 @@ onMounted(() => {
<a-card :bordered="false" class="mb-4">
<a-space wrap>
<a-input
<a-select
v-model="filter.tenant_id"
placeholder="租户ID"
:options="tenantOptions"
placeholder="选择租户"
allow-clear
style="width: 100px"
@keydown.enter="handleFilter"
allow-search
:filter-option="false"
style="width: 220px"
@search="fetchTenantOptions"
@change="handleFilter"
@clear="handleFilter"
/>
<a-input
v-model="filter.api_key_id"
@@ -373,10 +394,7 @@ onMounted(() => {
<a-descriptions-item label="任务ID">
<div style="display:flex;align-items:center;gap:8px">
<span>{{ detailRecord.task_id }}</span>
<a-button size="mini" type="text" @click="goToTask(detailRecord.task_id)">
<template #icon><icon-external-link /></template>
查看任务
</a-button>
<a-button size="mini" type="text" @click="goToTask(detailRecord.task_id)">查看任务</a-button>
</div>
</a-descriptions-item>
<a-descriptions-item label="任务状态">
@@ -36,7 +36,23 @@ const methodOptions = [
{ label: '恢复码', value: 'backup_code' },
]
const tenantOptions = ref<{ label: string; value: number }[]>([])
async function fetchTenantOptions(keyword: string) {
try {
const res: any = await request.get('/admin/tenants', {
params: { page: 1, page_size: 20, keyword, status: 'active' },
})
const list = res.data?.data?.list || res.data?.list || []
tenantOptions.value = list.map((t: any) => ({
label: `${t.name}(${t.code})`,
value: t.id,
}))
} catch { /* ignore */ }
}
onMounted(() => {
fetchTenantOptions('')
fetchData()
})
@@ -122,8 +138,17 @@ function onDateChange(dateString: string | undefined, type: 'start' | 'end') {
<ACard :bordered="false" style="margin-bottom: 16px">
<AForm :model="searchForm" layout="inline" @submit="onSearch">
<AFormItem label="租户ID">
<AInputNumber v-model="searchForm.tenant_id" placeholder="租户ID" allow-clear style="width: 120px" />
<AFormItem label="租户">
<ASelect
v-model="searchForm.tenant_id"
:options="tenantOptions"
placeholder="选择租户"
allow-clear
allow-search
:filter-option="false"
style="width: 220px"
@search="fetchTenantOptions"
/>
</AFormItem>
<AFormItem label="用户名">
<AInput v-model="searchForm.username" placeholder="搜索用户名" allow-clear style="width: 140px" />
+28 -9
View File
@@ -21,7 +21,21 @@ const pagination = reactive({
const filterStatus = ref<string | null>(null)
const filterRole = ref<string | null>(null)
const filterSearch = ref('')
const filterTenantId = ref<string>('')
const filterTenantId = ref<string | null>(null)
const filterTenantOptions = ref<{ label: string; value: number }[]>([])
async function fetchFilterTenantOptions(keyword: string) {
try {
const res: any = await request.get('/admin/tenants', {
params: { page: 1, page_size: 20, keyword, status: 'active' },
})
const list = res.data?.data?.list || res.data?.list || []
filterTenantOptions.value = list.map((t: any) => ({
label: `${t.name}(${t.code})`,
value: t.id,
}))
} catch { /* ignore */ }
}
// Create member
const showCreateModal = ref(false)
@@ -255,6 +269,7 @@ function handleFilter() {
}
onMounted(() => {
fetchFilterTenantOptions('')
fetchData()
})
@@ -287,6 +302,18 @@ const { exporting, exportFile } = useExport({
<!-- Filters -->
<ACard :bordered="false" class="mb-4">
<ASpace>
<ASelect
v-model="filterTenantId"
:options="filterTenantOptions"
placeholder="选择租户"
allow-clear
allow-search
:filter-option="false"
style="width: 220px"
@search="fetchFilterTenantOptions"
@change="handleFilter"
@clear="handleFilter"
/>
<ASelect
v-model="filterStatus"
:options="statusOptions"
@@ -311,14 +338,6 @@ const { exporting, exportFile } = useExport({
@keydown.enter="handleFilter"
@clear="handleFilter"
/>
<AInput
v-model="filterTenantId"
placeholder="租户ID"
allow-clear
style="width: 100px"
@keydown.enter="handleFilter"
@clear="handleFilter"
/>
<AButton @click="handleFilter">搜索</AButton>
</ASpace>
</ACard>
+79 -23
View File
@@ -18,29 +18,73 @@ const pagination = reactive({
pageSizeOptions: [10, 20, 50, 100],
})
const filterTenantId = ref('')
const filterTenantId = ref<number | undefined>(undefined)
const filterUsername = ref('')
const filterModel = ref('')
const filterModel = ref<string | undefined>(undefined)
const filterStatus = ref<string | undefined>(undefined)
const filterRequestType = ref<string | undefined>(undefined)
const filterDateRange = ref<string[]>([])
const tenantOptions = ref<{ label: string; value: number }[]>([])
const modelOptions = ref<{ label: string; value: string }[]>([])
const statusOptions = [
{ label: '全部状态', value: '' },
{ label: '成功', value: 'success' },
{ label: '失败', value: 'failed' },
{ label: '中断', value: 'interrupted' },
{ label: '失败', value: 'error' },
{ label: '超时', value: 'timeout' },
{ label: '已取消', value: 'cancelled' },
]
const requestTypeOptions = [
{ label: '全部类型', value: '' },
{ label: '同步', value: '1' },
{ label: '流式', value: '2' },
{ label: '异步', value: '3' },
{ label: 'WebSocket', value: '4' },
]
let tenantSearchTimer: ReturnType<typeof setTimeout> | null = null
let modelSearchTimer: ReturnType<typeof setTimeout> | null = null
async function fetchTenantOptions(keyword = '') {
try {
const res: any = await request.get('/admin/tenants/select', {
params: { page: 1, page_size: 50, keyword }
})
const list = res.data?.data?.list || []
tenantOptions.value = list.map((t: any) => ({
label: `${t.name}(${t.code})`,
value: t.id,
}))
} catch {
tenantOptions.value = []
}
}
function handleTenantSearch(value: string) {
if (tenantSearchTimer) clearTimeout(tenantSearchTimer)
tenantSearchTimer = setTimeout(() => fetchTenantOptions(value), 300)
}
async function fetchModelOptions(search = '') {
try {
const res: any = await request.get('/admin/models', {
params: { page: 1, page_size: 50, status: 'active', search }
})
const list = res.data?.data?.list || []
modelOptions.value = list.map((m: any) => ({
label: m.model_name,
value: m.model_name,
}))
} catch {
modelOptions.value = []
}
}
function handleModelSearch(value: string) {
if (modelSearchTimer) clearTimeout(modelSearchTimer)
modelSearchTimer = setTimeout(() => fetchModelOptions(value), 300)
}
const statusTagColor: Record<string, string> = {
success: 'green',
failed: 'red',
@@ -158,7 +202,7 @@ async function fetchData() {
page: pagination.current,
page_size: pagination.pageSize,
}
if (filterTenantId.value) params.tenant_id = parseInt(filterTenantId.value)
if (filterTenantId.value) params.tenant_id = filterTenantId.value
if (filterUsername.value) params.username = filterUsername.value
if (filterModel.value) params.model = filterModel.value
if (filterStatus.value) params.status = filterStatus.value
@@ -186,9 +230,9 @@ function handleFilter() {
}
function handleReset() {
filterTenantId.value = ''
filterTenantId.value = undefined
filterUsername.value = ''
filterModel.value = ''
filterModel.value = undefined
filterStatus.value = undefined
filterRequestType.value = undefined
filterDateRange.value = []
@@ -197,6 +241,8 @@ function handleReset() {
}
onMounted(() => {
fetchTenantOptions()
fetchModelOptions()
fetchData()
})
@@ -231,13 +277,18 @@ const { exporting, exportFile } = useExport({
<a-card :bordered="false" class="mb-4">
<a-space wrap>
<a-input
v-model="filterTenantId"
placeholder="租户ID"
allow-clear
style="width: 120px"
@keydown.enter="handleFilter"
/>
<a-select
v-model="filterTenantId"
:options="tenantOptions"
placeholder="搜索租户"
allow-search
allow-clear
:filter-option="false"
style="width: 200px"
@search="handleTenantSearch"
@change="handleFilter"
@clear="handleFilter"
/>
<a-input
v-model="filterUsername"
placeholder="用户名"
@@ -245,13 +296,18 @@ const { exporting, exportFile } = useExport({
style="width: 120px"
@keydown.enter="handleFilter"
/>
<a-input
v-model="filterModel"
placeholder="模型名"
allow-clear
style="width: 160px"
@keydown.enter="handleFilter"
/>
<a-select
v-model="filterModel"
:options="modelOptions"
placeholder="搜索模型"
allow-search
allow-clear
:filter-option="false"
style="width: 200px"
@search="handleModelSearch"
@change="handleFilter"
@clear="handleFilter"
/>
<a-select
v-model="filterStatus"
:options="statusOptions"