feat: 全新的Vue3管理后台(admin-spa)和路由重构

🎨 新增功能:
- 使用Vue3 + Vite构建的全新管理后台界面
- 支持Tab切换的API统计页面(统计查询/使用教程)
- 优雅的胶囊式Tab切换设计
- 同步了PR #106的会话窗口管理功能
- 完整的响应式设计和骨架屏加载状态

🔧 路由调整:
- 新版管理后台部署在 /admin-next/ 路径
- 将根路径 / 重定向到 /admin-next/api-stats
- 将 /web 页面路由重定向到新版,保留 /web/auth/* 认证路由
- 将 /apiStats 页面路由重定向到新版,保留API端点

🗑️ 清理工作:
- 删除旧版 web/admin/ 静态文件
- 删除旧版 web/apiStats/ 静态文件
- 清理相关的文件服务代码

🐛 修复问题:
- 修复重定向循环问题
- 修复环境变量配置
- 修复路由404错误
- 优化构建配置

🚀 生成方式:使用 Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shaw
2025-07-29 12:40:51 +08:00
parent c98de2aca5
commit 414856f152
70 changed files with 18748 additions and 10314 deletions

View File

@@ -0,0 +1,151 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { apiClient } from '@/config/api'
export const useSettingsStore = defineStore('settings', () => {
// 状态
const oemSettings = ref({
siteName: 'Claude Relay Service',
siteIcon: '',
siteIconData: '',
updatedAt: null
})
const loading = ref(false)
const saving = ref(false)
// 移除自定义API请求方法使用统一的apiClient
// Actions
const loadOemSettings = async () => {
loading.value = true
try {
const result = await apiClient.get('/admin/oem-settings')
if (result && result.success) {
oemSettings.value = { ...oemSettings.value, ...result.data }
// 应用设置到页面
applyOemSettings()
}
return result
} catch (error) {
console.error('Failed to load OEM settings:', error)
throw error
} finally {
loading.value = false
}
}
const saveOemSettings = async (settings) => {
saving.value = true
try {
const result = await apiClient.put('/admin/oem-settings', settings)
if (result && result.success) {
oemSettings.value = { ...oemSettings.value, ...result.data }
// 应用设置到页面
applyOemSettings()
}
return result
} catch (error) {
console.error('Failed to save OEM settings:', error)
throw error
} finally {
saving.value = false
}
}
const resetOemSettings = async () => {
const defaultSettings = {
siteName: 'Claude Relay Service',
siteIcon: '',
siteIconData: '',
updatedAt: null
}
oemSettings.value = { ...defaultSettings }
return await saveOemSettings(defaultSettings)
}
// 应用OEM设置到页面
const applyOemSettings = () => {
// 更新页面标题
if (oemSettings.value.siteName) {
document.title = `${oemSettings.value.siteName} - 管理后台`
}
// 更新favicon
if (oemSettings.value.siteIconData || oemSettings.value.siteIcon) {
const favicon = document.querySelector('link[rel="icon"]') || document.createElement('link')
favicon.rel = 'icon'
favicon.href = oemSettings.value.siteIconData || oemSettings.value.siteIcon
if (!document.querySelector('link[rel="icon"]')) {
document.head.appendChild(favicon)
}
}
}
// 格式化日期时间
const formatDateTime = (dateString) => {
if (!dateString) return ''
return new Date(dateString).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})
}
// 验证文件上传
const validateIconFile = (file) => {
const errors = []
// 检查文件大小 (350KB)
if (file.size > 350 * 1024) {
errors.push('图标文件大小不能超过 350KB')
}
// 检查文件类型
const allowedTypes = ['image/x-icon', 'image/png', 'image/jpeg', 'image/jpg', 'image/svg+xml']
if (!allowedTypes.includes(file.type)) {
errors.push('不支持的文件类型,请选择 .ico, .png, .jpg 或 .svg 文件')
}
return {
isValid: errors.length === 0,
errors
}
}
// 将文件转换为Base64
const fileToBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = (e) => resolve(e.target.result)
reader.onerror = reject
reader.readAsDataURL(file)
})
}
return {
// State
oemSettings,
loading,
saving,
// Actions
loadOemSettings,
saveOemSettings,
resetOemSettings,
applyOemSettings,
formatDateTime,
validateIconFile,
fileToBase64
}
})