refactor: 前端全面替换 any 为 unknown 并统一错误处理,后端用量记录补写请求头/体

- 前端 API 层、stores、conversation 解析器、组件全面替换 any 为 unknown/具体类型
- 错误处理统一使用 parseApiError/getErrorStatus 替代 err.response?.data?.detail 模式
- 后端 handler/TaskService/UsageLifecycle/StreamTracker 链路传递 request_headers/request_body
- streaming/pending 状态更新时可补写客户端和提供商的请求头及请求体
- 新增 TaskService 和 UsageService 相关测试
This commit is contained in:
fawney19
2026-02-22 00:43:41 +08:00
parent 98e60e8f74
commit cf2eee222e
121 changed files with 1652 additions and 1179 deletions
+32 -30
View File
@@ -719,6 +719,7 @@
<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue'
import { useUsersStore } from '@/stores/users'
import type { User, ApiKey } from '@/api/users'
import { useToast } from '@/composables/useToast'
import { useConfirm } from '@/composables/useConfirm'
import { useClipboard } from '@/composables/useClipboard'
@@ -767,6 +768,7 @@ import {
// 功能组件
import UserFormDialog, { type UserFormData } from '@/features/users/components/UserFormDialog.vue'
import { parseApiError } from '@/utils/errorParser'
import { log } from '@/utils/logger'
const { success, error } = useToast()
@@ -782,8 +784,8 @@ const userFormDialogRef = ref<InstanceType<typeof UserFormDialog>>()
// API Keys 对话框状态
const showApiKeysDialog = ref(false)
const showNewApiKeyDialog = ref(false)
const selectedUser = ref<any>(null)
const userApiKeys = ref<any[]>([])
const selectedUser = ref<User | null>(null)
const userApiKeys = ref<ApiKey[]>([])
const newApiKey = ref('')
const creatingApiKey = ref(false)
const apiKeyInput = ref<HTMLInputElement>()
@@ -861,7 +863,7 @@ async function loadUserStats() {
loadingStats.value = true
try {
const data = await usageApi.getUsageByUser()
userStats.value = data.reduce((acc: any, stat: any) => {
userStats.value = data.reduce((acc: Record<string, UsageByUser>, stat: UsageByUser) => {
acc[stat.user_id] = stat
return acc
}, {})
@@ -886,7 +888,7 @@ function formatNumber(value?: number | null): string {
return numericValue.toLocaleString()
}
async function toggleUserStatus(user: any) {
async function toggleUserStatus(user: User) {
const action = user.is_active ? '禁用' : '启用'
const confirmed = await confirmDanger(
`确定要${action}用户 ${user.username} 吗?`,
@@ -899,8 +901,8 @@ async function toggleUserStatus(user: any) {
try {
await usersStore.updateUser(user.id, { is_active: !user.is_active })
success(`用户已${action}`)
} catch (err: any) {
error(err.response?.data?.error?.message || err.response?.data?.detail || '未知错误', `${action}用户失败`)
} catch (err: unknown) {
error(parseApiError(err, '未知错误'), `${action}用户失败`)
}
}
@@ -911,7 +913,7 @@ function openCreateDialog() {
showUserFormDialog.value = true
}
function editUser(user: any) {
function editUser(user: User) {
// 创建数组副本,避免与 store 数据共享引用
editingUser.value = {
id: user.id,
@@ -937,7 +939,7 @@ async function handleUserFormSubmit(data: UserFormData & { password?: string })
try {
if (data.id) {
// 更新用户
const updateData: any = {
const updateData: Record<string, unknown> = {
username: data.username,
email: data.email || undefined,
quota_usd: data.quota_usd,
@@ -955,10 +957,10 @@ async function handleUserFormSubmit(data: UserFormData & { password?: string })
// 创建用户
const newUser = await usersStore.createUser({
username: data.username,
password: data.password!,
password: data.password ?? '',
email: data.email || undefined,
quota_usd: data.quota_usd,
unlimited: (data as any).unlimited,
unlimited: (data as Record<string, unknown>).unlimited as boolean | undefined,
role: data.role,
allowed_providers: data.allowed_providers,
allowed_api_formats: data.allowed_api_formats,
@@ -971,15 +973,15 @@ async function handleUserFormSubmit(data: UserFormData & { password?: string })
success('用户创建成功')
}
closeUserFormDialog()
} catch (err: any) {
} catch (err: unknown) {
const title = data.id ? '更新用户失败' : '创建用户失败'
error(err.response?.data?.error?.message || err.response?.data?.detail || '未知错误', title)
error(parseApiError(err, '未知错误'), title)
} finally {
userFormDialogRef.value?.setSaving(false)
}
}
async function manageApiKeys(user: any) {
async function manageApiKeys(user: User) {
selectedUser.value = user
showApiKeysDialog.value = true
await loadUserApiKeys(user.id)
@@ -1006,8 +1008,8 @@ async function createApiKey() {
newApiKey.value = response.key || ''
showNewApiKeyDialog.value = true
await loadUserApiKeys(selectedUser.value.id)
} catch (err: any) {
error(err.response?.data?.error?.message || err.response?.data?.detail || '未知错误', '创建 API Key 失败')
} catch (err: unknown) {
error(parseApiError(err, '未知错误'), '创建 API Key 失败')
} finally {
creatingApiKey.value = false
}
@@ -1026,7 +1028,7 @@ async function closeNewApiKeyDialog() {
newApiKey.value = ''
}
async function deleteApiKey(apiKey: any) {
async function deleteApiKey(apiKey: ApiKey) {
const confirmed = await confirmDanger(
`确定要删除这个API Key吗?\n\n${apiKey.key_display || 'sk-****'}\n\n此操作无法撤销。`,
'删除 API Key'
@@ -1038,12 +1040,12 @@ async function deleteApiKey(apiKey: any) {
await usersStore.deleteApiKey(selectedUser.value.id, apiKey.id)
await loadUserApiKeys(selectedUser.value.id)
success('API Key已删除')
} catch (err: any) {
error(err.response?.data?.error?.message || err.response?.data?.detail || '未知错误', '删除 API Key 失败')
} catch (err: unknown) {
error(parseApiError(err, '未知错误'), '删除 API Key 失败')
}
}
async function toggleLockApiKey(apiKey: any) {
async function toggleLockApiKey(apiKey: ApiKey) {
try {
const response = await adminApi.toggleLockApiKey(apiKey.id)
// 更新本地状态
@@ -1052,24 +1054,24 @@ async function toggleLockApiKey(apiKey: any) {
userApiKeys.value[index].is_locked = response.is_locked
}
success(response.message)
} catch (err: any) {
} catch (err: unknown) {
log.error('切换密钥锁定状态失败:', err)
error(err.response?.data?.error?.message || err.response?.data?.detail || '操作失败', '锁定/解锁失败')
error(parseApiError(err, '操作失败'), '锁定/解锁失败')
}
}
async function copyFullKey(apiKey: any) {
async function copyFullKey(apiKey: ApiKey) {
try {
// 调用后端 API 获取完整密钥
const response = await adminApi.getFullApiKey(apiKey.id)
await copyToClipboard(response.key)
} catch (err: any) {
} catch (err: unknown) {
log.error('复制密钥失败:', err)
error(err.response?.data?.error?.message || err.response?.data?.detail || '未知错误', '复制密钥失败')
error(parseApiError(err, '未知错误'), '复制密钥失败')
}
}
async function resetQuota(user: any) {
async function resetQuota(user: User) {
const confirmed = await confirmWarning(
`确定要重置用户 ${user.username} 的配额使用量吗?\n\n这将把已使用金额重置为0。`,
'重置配额'
@@ -1080,12 +1082,12 @@ async function resetQuota(user: any) {
try {
await usersStore.resetUserQuota(user.id)
success('配额已重置')
} catch (err: any) {
error(err.response?.data?.error?.message || err.response?.data?.detail || '未知错误', '重置配额失败')
} catch (err: unknown) {
error(parseApiError(err, '未知错误'), '重置配额失败')
}
}
async function deleteUser(user: any) {
async function deleteUser(user: User) {
const confirmed = await confirmDanger(
`确定要删除用户 ${user.username} 吗?\n\n此操作将删除:\n• 用户账户\n• 所有API密钥\n• 所有使用记录\n\n此操作无法撤销!`,
'删除用户'
@@ -1096,8 +1098,8 @@ async function deleteUser(user: any) {
try {
await usersStore.deleteUser(user.id)
success('用户已删除')
} catch (err: any) {
error(err.response?.data?.error?.message || err.response?.data?.detail || '未知错误', '删除用户失败')
} catch (err: unknown) {
error(parseApiError(err, '未知错误'), '删除用户失败')
}
}
</script>