Files
AIGC/demo/frontend/src/views/ApiManagement.vue

549 lines
13 KiB
Vue
Raw Normal View History

<template>
<div class="api-management">
<!-- 左侧导航栏 -->
<aside class="sidebar">
<div class="logo">
<img src="/images/backgrounds/logo-admin.svg" alt="Logo" />
</div>
<nav class="nav-menu">
<div class="nav-item" @click="goToDashboard">
<el-icon><Grid /></el-icon>
2025-11-13 17:01:39 +08:00
<span>{{ $t('nav.dashboard') }}</span>
</div>
<div class="nav-item" @click="goToMembers">
<el-icon><User /></el-icon>
2025-11-13 17:01:39 +08:00
<span>{{ $t('nav.members') }}</span>
</div>
<div class="nav-item" @click="goToOrders">
<el-icon><ShoppingCart /></el-icon>
2025-11-13 17:01:39 +08:00
<span>{{ $t('nav.orders') }}</span>
</div>
<div class="nav-item active">
<el-icon><Document /></el-icon>
2025-11-13 17:01:39 +08:00
<span>{{ $t('nav.apiManagement') }}</span>
</div>
<div class="nav-item" @click="goToTasks">
<el-icon><Document /></el-icon>
2025-11-13 17:01:39 +08:00
<span>{{ $t('nav.tasks') }}</span>
</div>
<div class="nav-item" @click="goToSettings">
<el-icon><Setting /></el-icon>
2025-11-13 17:01:39 +08:00
<span>{{ $t('nav.systemSettings') }}</span>
</div>
</nav>
<div class="sidebar-footer">
<div class="online-users">
{{ $t('nav.todayVisitors') }}: <span class="highlight">{{ onlineUsers }}</span>
</div>
<div class="system-uptime">
2025-11-13 17:01:39 +08:00
{{ $t('nav.systemUptime') }}: <span class="highlight">{{ systemUptime }}</span>
</div>
</div>
</aside>
<!-- 主内容区域 -->
<main class="main-content">
<!-- 顶部搜索栏 -->
<header class="top-header">
<div class="search-bar">
<el-icon class="search-icon"><Search /></el-icon>
2025-11-13 17:01:39 +08:00
<input type="text" :placeholder="$t('common.searchPlaceholder')" class="search-input" />
</div>
<div class="header-actions">
2025-11-13 17:01:39 +08:00
<LanguageSwitcher />
<el-dropdown @command="handleUserCommand">
<div class="user-avatar">
<img src="/images/backgrounds/avatar-default.svg" alt="用户头像" />
<el-icon class="arrow-down"><ArrowDown /></el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="exitAdmin">
{{ $t('admin.exitAdmin') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</header>
<!-- API密钥输入内容 -->
<section class="api-content">
<div class="content-header">
2025-11-13 17:01:39 +08:00
<h2>{{ $t('apiManagement.title') }}</h2>
</div>
<div class="api-form-container">
<el-form :model="apiForm" label-width="120px" class="api-form">
2025-11-13 17:01:39 +08:00
<el-form-item :label="$t('apiManagement.apiKey')">
<el-input
v-model="apiForm.apiKey"
type="password"
2025-11-13 17:01:39 +08:00
:placeholder="$t('apiManagement.apiKeyPlaceholder')"
show-password
style="width: 100%; max-width: 600px;"
/>
</el-form-item>
2025-11-13 17:01:39 +08:00
<el-form-item :label="$t('apiManagement.tokenExpiration')">
<div style="display: flex; align-items: center; gap: 12px; width: 100%; max-width: 600px;">
<el-input
v-model.number="apiForm.jwtExpirationHours"
type="number"
2025-11-13 17:01:39 +08:00
:placeholder="$t('apiManagement.tokenPlaceholder')"
style="flex: 1;"
:min="1"
:max="720"
/>
2025-11-13 17:01:39 +08:00
<span style="color: #6b7280; font-size: 14px;">{{ $t('apiManagement.hours') }}</span>
<span style="color: #9ca3af; font-size: 12px;" v-if="apiForm.jwtExpirationHours">
({{ formatJwtExpiration(apiForm.jwtExpirationHours) }})
</span>
</div>
<div style="margin-top: 8px; color: #6b7280; font-size: 12px;">
2025-11-13 17:01:39 +08:00
{{ $t('apiManagement.rangeHint') }}
</div>
</el-form-item>
<el-form-item>
2025-11-13 17:01:39 +08:00
<el-button type="primary" @click="saveApiKey" :loading="saving">{{ $t('common.save') }}</el-button>
<el-button @click="resetForm">{{ $t('common.reset') }}</el-button>
</el-form-item>
</el-form>
</div>
</section>
</main>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
2025-11-13 17:01:39 +08:00
import { useI18n } from 'vue-i18n'
import {
Grid,
User,
ShoppingCart,
Document,
Setting,
Search,
ArrowDown
} from '@element-plus/icons-vue'
import api from '@/api/request'
2025-11-13 17:01:39 +08:00
import LanguageSwitcher from '@/components/LanguageSwitcher.vue'
const router = useRouter()
2025-11-13 17:01:39 +08:00
const { t } = useI18n()
const saving = ref(false)
const loading = ref(false)
2025-11-13 17:01:39 +08:00
// 系统状态数据
const onlineUsers = ref('0/500')
const systemUptime = ref(t('common.loading'))
const apiForm = reactive({
apiKey: '',
jwtExpirationHours: 24 // 默认24小时
})
// 导航功能
const goToDashboard = () => {
router.push('/admin/dashboard')
}
const goToMembers = () => {
router.push('/member-management')
}
const goToOrders = () => {
router.push('/admin/orders')
}
const goToTasks = () => {
router.push('/generate-task-record')
}
const goToSettings = () => {
router.push('/system-settings')
}
// 处理用户头像下拉菜单
const handleUserCommand = (command) => {
if (command === 'exitAdmin') {
// 退出后台,返回个人首页
router.push('/profile')
}
}
// 格式化JWT过期时间显示
const formatJwtExpiration = (hours) => {
if (!hours) return ''
if (hours < 24) {
2025-11-13 17:01:39 +08:00
return `${hours}${t('apiManagement.hours')}`
} else if (hours < 720) {
const days = Math.floor(hours / 24)
const remainingHours = hours % 24
if (remainingHours === 0) {
2025-11-13 17:01:39 +08:00
return `${days}${t('apiManagement.days')}`
}
2025-11-13 17:01:39 +08:00
return `${days}${t('apiManagement.days')}${remainingHours}${t('apiManagement.hours')}`
} else {
2025-11-13 17:01:39 +08:00
return `30${t('apiManagement.days')}`
}
}
// 加载当前API密钥和JWT配置仅显示部分
const loadApiKey = async () => {
loading.value = true
try {
const response = await api.get('/api-key')
if (response.data?.maskedKey) {
// 不显示掩码后的密钥,只用于验证
console.log('当前API密钥已配置')
}
// 加载JWT过期时间转换为小时
if (response.data?.jwtExpiration) {
apiForm.jwtExpirationHours = Math.round(response.data.jwtExpiration / 3600000)
} else if (response.data?.jwtExpirationHours) {
apiForm.jwtExpirationHours = Math.round(response.data.jwtExpirationHours)
}
} catch (error) {
console.error('加载配置失败:', error)
} finally {
loading.value = false
}
}
// 保存API密钥和JWT配置
const saveApiKey = async () => {
// 检查是否有任何输入
const hasApiKey = apiForm.apiKey && apiForm.apiKey.trim() !== ''
const hasJwtExpiration = apiForm.jwtExpirationHours != null && apiForm.jwtExpirationHours > 0
// 验证输入:至少需要提供一个配置项
if (!hasApiKey && !hasJwtExpiration) {
ElMessage.warning(t('apiManagement.atLeastOneRequired') || '请至少输入API密钥或设置Token过期时间')
return
}
// 验证JWT过期时间范围
if (hasJwtExpiration && (apiForm.jwtExpirationHours < 1 || apiForm.jwtExpirationHours > 720)) {
ElMessage.warning(t('apiManagement.tokenRangeError') || 'Token过期时间必须在1-720小时之间')
return
}
saving.value = true
try {
const requestData = {}
// 如果提供了API密钥添加到请求中
if (hasApiKey) {
requestData.apiKey = apiForm.apiKey.trim()
}
// 如果提供了JWT过期时间转换为毫秒并添加到请求中
if (hasJwtExpiration) {
requestData.jwtExpiration = apiForm.jwtExpirationHours * 3600000 // 转换为毫秒
}
const response = await api.put('/api-key', requestData)
if (response.data?.success) {
ElMessage.success(response.data.message || '配置保存成功,请重启应用以使配置生效')
// 清空API密钥输入框保留JWT过期时间
apiForm.apiKey = ''
} else {
ElMessage.error(response.data?.error || '保存失败')
}
} catch (error) {
console.error('保存配置失败:', error)
ElMessage.error('保存失败: ' + (error.response?.data?.message || error.message || '未知错误'))
} finally {
saving.value = false
}
}
// 重置表单
const resetForm = () => {
apiForm.apiKey = ''
// 重新加载JWT过期时间
loadApiKey()
}
// 页面加载时获取当前API密钥状态
onMounted(() => {
loadApiKey()
2025-11-13 17:01:39 +08:00
fetchSystemStats()
})
2025-11-13 17:01:39 +08:00
// 获取系统统计数据(当天访问人数和系统运行时间)
2025-11-13 17:01:39 +08:00
const fetchSystemStats = async () => {
try {
const response = await fetch('/api/admin/online-stats', {
headers: {
'Authorization': `Bearer ${sessionStorage.getItem('token')}`
}
})
const data = await response.json()
if (data.success) {
onlineUsers.value = data.todayVisitors || 0
systemUptime.value = data.uptime || $t('systemSettings.unknown')
} else {
onlineUsers.value = '0'
systemUptime.value = $t('systemSettings.unknown')
}
2025-11-13 17:01:39 +08:00
} catch (error) {
console.error('Get online stats failed:', error)
onlineUsers.value = '0'
systemUptime.value = $t('systemSettings.unknown')
2025-11-13 17:01:39 +08:00
}
}
</script>
<style scoped>
.api-management {
display: flex;
min-height: 100vh;
background: #f8f9fa;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
/* 左侧导航栏 */
.sidebar {
width: 240px;
background: white;
border-right: 1px solid #e9ecef;
display: flex;
flex-direction: column;
padding: 24px 0;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05);
}
.logo {
2025-11-13 17:01:39 +08:00
display: flex;
align-items: center;
justify-content: center;
padding: 0 24px;
margin-bottom: 32px;
}
.logo img {
2025-11-13 17:01:39 +08:00
width: 100%;
height: auto;
max-width: 180px;
2025-11-13 17:01:39 +08:00
object-fit: contain;
}
.nav-menu {
flex: 1;
padding: 0 16px;
}
.nav-item {
display: flex;
align-items: center;
padding: 12px 16px;
margin-bottom: 4px;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
color: #6b7280;
font-size: 14px;
font-weight: 500;
}
.nav-item:hover {
background: #f3f4f6;
color: #374151;
}
.nav-item.active {
background: #dbeafe;
color: #3b82f6;
}
.nav-item .el-icon {
margin-right: 12px;
font-size: 18px;
}
.nav-item span {
font-size: 14px;
font-weight: 500;
}
.sidebar-footer {
padding: 20px;
border-top: 1px solid #e9ecef;
background: #f8f9fa;
margin-top: auto;
}
.online-users,
.system-uptime {
font-size: 13px;
color: #6b7280;
margin-bottom: 8px;
line-height: 1.5;
}
.highlight {
color: #3b82f6;
font-weight: 600;
}
/* 主内容区域 */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
background: #f8f9fa;
}
/* 顶部搜索栏 */
.top-header {
background: white;
border-bottom: 1px solid #e9ecef;
padding: 16px 24px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.search-bar {
position: relative;
display: flex;
align-items: center;
}
.search-icon {
position: absolute;
left: 12px;
color: #9ca3af;
font-size: 16px;
z-index: 1;
}
.search-input {
width: 300px;
padding: 10px 12px 10px 40px;
border: 1px solid #d1d5db;
border-radius: 8px;
font-size: 14px;
background: white;
outline: none;
transition: border-color 0.2s ease;
}
.search-input:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.search-input::placeholder {
color: #9ca3af;
}
.header-actions {
display: flex;
align-items: center;
gap: 20px;
}
.user-avatar {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
padding: 4px 8px;
border-radius: 6px;
transition: background 0.2s ease;
}
.user-avatar:hover {
background: #f3f4f6;
}
.user-avatar img {
width: 32px;
height: 32px;
border-radius: 50%;
object-fit: cover;
}
.user-avatar .arrow-down {
font-size: 12px;
color: #6b7280;
}
/* API内容区域 */
.api-content {
padding: 24px;
flex: 1;
background: white;
margin: 24px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.content-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 32px;
}
.content-header h2 {
font-size: 24px;
font-weight: 600;
color: #1e293b;
margin: 0;
}
.api-form-container {
max-width: 800px;
}
.api-form {
background: #f9fafb;
padding: 32px;
border-radius: 8px;
}
/* 响应式设计 */
@media (max-width: 1024px) {
.api-management {
flex-direction: column;
}
.sidebar {
width: 100%;
height: auto;
}
.nav-menu {
display: flex;
overflow-x: auto;
padding: 0 16px;
}
.nav-item {
white-space: nowrap;
margin-right: 16px;
margin-bottom: 0;
}
.sidebar-footer {
display: none;
}
.search-input {
width: 200px;
}
.api-content {
padding: 16px;
}
}
</style>