mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-02 15:52:26 +08:00
refactor(frontend): 优化用户视图页面
- 改进 Announcements, ModelCatalog, MyApiKeys 页面及其组件
This commit is contained in:
@@ -492,6 +492,7 @@ import SelectItem from '@/components/ui/select-item.vue'
|
|||||||
import { AlertDialog } from '@/components/common'
|
import { AlertDialog } from '@/components/common'
|
||||||
import { Bell, AlertCircle, AlertTriangle, Info, Pin, Wrench, Loader2, Plus, SquarePen, Trash2 } from 'lucide-vue-next'
|
import { Bell, AlertCircle, AlertTriangle, Info, Pin, Wrench, Loader2, Plus, SquarePen, Trash2 } from 'lucide-vue-next'
|
||||||
import { useToast } from '@/composables/useToast'
|
import { useToast } from '@/composables/useToast'
|
||||||
|
import { log } from '@/utils/logger'
|
||||||
import { marked } from 'marked'
|
import { marked } from 'marked'
|
||||||
import { sanitizeMarkdown } from '@/utils/sanitize'
|
import { sanitizeMarkdown } from '@/utils/sanitize'
|
||||||
|
|
||||||
@@ -544,25 +545,13 @@ async function loadAnnouncements(page = 1) {
|
|||||||
total.value = response.total
|
total.value = response.total
|
||||||
unreadCount.value = response.unread_count || 0
|
unreadCount.value = response.unread_count || 0
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载公告失败:', error)
|
log.error('加载公告失败:', error)
|
||||||
showError('加载公告失败')
|
showError('加载公告失败')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function markAsRead(announcement: Announcement) {
|
|
||||||
try {
|
|
||||||
await announcementApi.markAsRead(announcement.id)
|
|
||||||
announcement.is_read = true
|
|
||||||
unreadCount.value = Math.max(0, unreadCount.value - 1)
|
|
||||||
success('已标记为已读')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('标记失败:', error)
|
|
||||||
showError('标记失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function viewAnnouncementDetail(announcement: Announcement) {
|
async function viewAnnouncementDetail(announcement: Announcement) {
|
||||||
// 标记为已读
|
// 标记为已读
|
||||||
if (!announcement.is_read && !isAdmin.value) {
|
if (!announcement.is_read && !isAdmin.value) {
|
||||||
@@ -571,7 +560,7 @@ async function viewAnnouncementDetail(announcement: Announcement) {
|
|||||||
announcement.is_read = true
|
announcement.is_read = true
|
||||||
unreadCount.value = Math.max(0, unreadCount.value - 1)
|
unreadCount.value = Math.max(0, unreadCount.value - 1)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('标记已读失败:', error)
|
log.error('标记已读失败:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -614,7 +603,7 @@ async function toggleAnnouncementPin(announcement: Announcement, newStatus: bool
|
|||||||
announcement.is_pinned = newStatus
|
announcement.is_pinned = newStatus
|
||||||
success(newStatus ? '已置顶' : '已取消置顶')
|
success(newStatus ? '已置顶' : '已取消置顶')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('更新置顶状态失败:', error)
|
log.error('更新置顶状态失败:', error)
|
||||||
showError('更新置顶状态失败')
|
showError('更新置顶状态失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -627,7 +616,7 @@ async function toggleAnnouncementActive(announcement: Announcement, newStatus: b
|
|||||||
announcement.is_active = newStatus
|
announcement.is_active = newStatus
|
||||||
success(newStatus ? '已启用' : '已禁用')
|
success(newStatus ? '已启用' : '已禁用')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('更新启用状态失败:', error)
|
log.error('更新启用状态失败:', error)
|
||||||
showError('更新启用状态失败')
|
showError('更新启用状态失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -652,7 +641,7 @@ async function saveAnnouncement() {
|
|||||||
dialogOpen.value = false
|
dialogOpen.value = false
|
||||||
loadAnnouncements(currentPage.value)
|
loadAnnouncements(currentPage.value)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存失败:', error)
|
log.error('保存失败:', error)
|
||||||
showError('保存失败')
|
showError('保存失败')
|
||||||
} finally {
|
} finally {
|
||||||
saving.value = false
|
saving.value = false
|
||||||
@@ -674,7 +663,7 @@ async function deleteAnnouncement() {
|
|||||||
deleteDialogOpen.value = false
|
deleteDialogOpen.value = false
|
||||||
loadAnnouncements(currentPage.value)
|
loadAnnouncements(currentPage.value)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('删除失败:', error)
|
log.error('删除失败:', error)
|
||||||
showError('删除失败')
|
showError('删除失败')
|
||||||
} finally {
|
} finally {
|
||||||
deleting.value = false
|
deleting.value = false
|
||||||
@@ -707,19 +696,6 @@ function getIconColor(type: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIconBgClass(type: string) {
|
|
||||||
switch (type) {
|
|
||||||
case 'important':
|
|
||||||
return 'bg-red-50 dark:bg-red-900/20'
|
|
||||||
case 'warning':
|
|
||||||
return 'bg-yellow-50 dark:bg-yellow-900/20'
|
|
||||||
case 'maintenance':
|
|
||||||
return 'bg-orange-50 dark:bg-orange-900/20'
|
|
||||||
default:
|
|
||||||
return 'bg-primary/10'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTypeTextColor(type: string): string {
|
function getTypeTextColor(type: string): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'important':
|
case 'important':
|
||||||
@@ -733,19 +709,6 @@ function getTypeTextColor(type: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTypeBadgeVariant(type: string): 'default' | 'success' | 'destructive' | 'warning' | 'secondary' {
|
|
||||||
switch (type) {
|
|
||||||
case 'important':
|
|
||||||
return 'destructive'
|
|
||||||
case 'warning':
|
|
||||||
return 'warning'
|
|
||||||
case 'maintenance':
|
|
||||||
return 'secondary'
|
|
||||||
default:
|
|
||||||
return 'default'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTypeLabel(type: string): string {
|
function getTypeLabel(type: string): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'important':
|
case 'important':
|
||||||
@@ -791,7 +754,7 @@ function renderMarkdown(content: string): string {
|
|||||||
function getPlainText(content: string): string {
|
function getPlainText(content: string): string {
|
||||||
// 简单地移除 Markdown 标记,用于预览
|
// 简单地移除 Markdown 标记,用于预览
|
||||||
return content
|
return content
|
||||||
.replace(/[#*_`~\[\]()]/g, '')
|
.replace(/[#*_`~[\]()]/g, '')
|
||||||
.replace(/\n+/g, ' ')
|
.replace(/\n+/g, ' ')
|
||||||
.trim()
|
.trim()
|
||||||
.substring(0, 200)
|
.substring(0, 200)
|
||||||
|
|||||||
@@ -282,6 +282,7 @@ import {
|
|||||||
} from '@/api/endpoints'
|
} from '@/api/endpoints'
|
||||||
import UserModelDetailDrawer from './components/UserModelDetailDrawer.vue'
|
import UserModelDetailDrawer from './components/UserModelDetailDrawer.vue'
|
||||||
import { useRowClick } from '@/composables/useRowClick'
|
import { useRowClick } from '@/composables/useRowClick'
|
||||||
|
import { log } from '@/utils/logger'
|
||||||
|
|
||||||
const { success, error: showError } = useToast()
|
const { success, error: showError } = useToast()
|
||||||
|
|
||||||
@@ -377,7 +378,7 @@ async function toggleCapability(modelName: string, capName: string) {
|
|||||||
// 更新本地状态
|
// 更新本地状态
|
||||||
modelCapabilitySettings.value = newSettings
|
modelCapabilitySettings.value = newSettings
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('保存能力配置失败:', err)
|
log.error('保存能力配置失败:', err)
|
||||||
showError('保存失败,请重试')
|
showError('保存失败,请重试')
|
||||||
} finally {
|
} finally {
|
||||||
savingCapability.value = null
|
savingCapability.value = null
|
||||||
@@ -429,7 +430,7 @@ async function loadModels() {
|
|||||||
const response = await getPublicGlobalModels({ limit: 1000 })
|
const response = await getPublicGlobalModels({ limit: 1000 })
|
||||||
models.value = response.models || []
|
models.value = response.models || []
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('加载模型失败:', err)
|
log.error('加载模型失败:', err)
|
||||||
showError(err.response?.data?.detail || err.message, '加载模型失败')
|
showError(err.response?.data?.detail || err.message, '加载模型失败')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -445,7 +446,7 @@ async function loadCapabilities() {
|
|||||||
availableCapabilities.value = userCaps
|
availableCapabilities.value = userCaps
|
||||||
allCapabilities.value = allCaps
|
allCapabilities.value = allCaps
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load capabilities:', err)
|
log.error('Failed to load capabilities:', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,7 +455,7 @@ async function loadModelCapabilitySettings() {
|
|||||||
const response = await meApi.getModelCapabilitySettings()
|
const response = await meApi.getModelCapabilitySettings()
|
||||||
modelCapabilitySettings.value = response.model_capability_settings || {}
|
modelCapabilitySettings.value = response.model_capability_settings || {}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load model capability settings:', err)
|
log.error('Failed to load model capability settings:', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,7 +485,7 @@ async function copyToClipboard(text: string) {
|
|||||||
await navigator.clipboard.writeText(text)
|
await navigator.clipboard.writeText(text)
|
||||||
success('已复制')
|
success('已复制')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('复制失败:', err)
|
log.error('复制失败:', err)
|
||||||
showError('复制失败')
|
showError('复制失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -474,6 +474,7 @@ import {
|
|||||||
import RefreshButton from '@/components/ui/refresh-button.vue'
|
import RefreshButton from '@/components/ui/refresh-button.vue'
|
||||||
import { Plus, Key, Copy, Trash2, Loader2, Activity, CheckCircle, Power, Check } from 'lucide-vue-next'
|
import { Plus, Key, Copy, Trash2, Loader2, Activity, CheckCircle, Power, Check } from 'lucide-vue-next'
|
||||||
import { useToast } from '@/composables/useToast'
|
import { useToast } from '@/composables/useToast'
|
||||||
|
import { log } from '@/utils/logger'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
const { success, error: showError } = useToast()
|
const { success, error: showError } = useToast()
|
||||||
@@ -516,7 +517,7 @@ async function loadCapabilities() {
|
|||||||
try {
|
try {
|
||||||
availableCapabilities.value = await getAllCapabilities()
|
availableCapabilities.value = await getAllCapabilities()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load capabilities:', error)
|
log.error('Failed to load capabilities:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,7 +526,7 @@ async function loadApiKeys() {
|
|||||||
try {
|
try {
|
||||||
apiKeys.value = await meApi.getApiKeys()
|
apiKeys.value = await meApi.getApiKeys()
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('加载 API 密钥失败:', error)
|
log.error('加载 API 密钥失败:', error)
|
||||||
if (!error.response) {
|
if (!error.response) {
|
||||||
showError('无法连接到服务器,请检查后端服务是否运行')
|
showError('无法连接到服务器,请检查后端服务是否运行')
|
||||||
} else if (error.response.status === 401) {
|
} else if (error.response.status === 401) {
|
||||||
@@ -554,7 +555,7 @@ async function createApiKey() {
|
|||||||
await loadApiKeys()
|
await loadApiKeys()
|
||||||
success('API 密钥创建成功')
|
success('API 密钥创建成功')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('创建 API 密钥失败:', error)
|
log.error('创建 API 密钥失败:', error)
|
||||||
showError('创建 API 密钥失败')
|
showError('创建 API 密钥失败')
|
||||||
} finally {
|
} finally {
|
||||||
creating.value = false
|
creating.value = false
|
||||||
@@ -576,7 +577,7 @@ async function deleteApiKey() {
|
|||||||
showDeleteDialog.value = false
|
showDeleteDialog.value = false
|
||||||
success('API 密钥已删除')
|
success('API 密钥已删除')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('删除 API 密钥失败:', error)
|
log.error('删除 API 密钥失败:', error)
|
||||||
showError('删除 API 密钥失败')
|
showError('删除 API 密钥失败')
|
||||||
} finally {
|
} finally {
|
||||||
deleting.value = false
|
deleting.value = false
|
||||||
@@ -593,7 +594,7 @@ async function toggleApiKey(apiKey: ApiKey) {
|
|||||||
}
|
}
|
||||||
success(updated.is_active ? '密钥已启用' : '密钥已禁用')
|
success(updated.is_active ? '密钥已启用' : '密钥已禁用')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('切换密钥状态失败:', error)
|
log.error('切换密钥状态失败:', error)
|
||||||
showError('操作失败')
|
showError('操作失败')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -635,7 +636,7 @@ async function toggleCapability(apiKey: ApiKey, capName: string) {
|
|||||||
apiKeys.value[index].force_capabilities = capabilitiesData
|
apiKeys.value[index].force_capabilities = capabilitiesData
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('保存能力配置失败:', err)
|
log.error('保存能力配置失败:', err)
|
||||||
showError('保存失败,请重试')
|
showError('保存失败,请重试')
|
||||||
} finally {
|
} finally {
|
||||||
savingCapability.value = null
|
savingCapability.value = null
|
||||||
@@ -649,7 +650,7 @@ async function copyApiKey(apiKey: ApiKey) {
|
|||||||
await copyTextToClipboard(response.key, false) // 不显示内部提示
|
await copyTextToClipboard(response.key, false) // 不显示内部提示
|
||||||
success('完整密钥已复制到剪贴板')
|
success('完整密钥已复制到剪贴板')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('复制密钥失败:', error)
|
log.error('复制密钥失败:', error)
|
||||||
showError('复制失败,请重试')
|
showError('复制失败,请重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -681,7 +682,7 @@ async function copyTextToClipboard(text: string, showToast: boolean = true) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('复制失败:', error)
|
log.error('复制失败:', error)
|
||||||
showError('复制失败,请手动选择文本进行复制')
|
showError('复制失败,请手动选择文本进行复制')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user