improve:优化多个前端页面,将输入租户ID改为下拉搜索框
This commit is contained in:
1 parent
4b544d7069
commit
5a71b3faff
8 files changed
+197
-67
No files matched your search
+19
-17
@@ -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{}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user