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,74 @@
// 数字格式化函数
export function formatNumber(num) {
if (num === null || num === undefined) return '0'
const absNum = Math.abs(num)
if (absNum >= 1e9) {
return (num / 1e9).toFixed(2) + 'B'
} else if (absNum >= 1e6) {
return (num / 1e6).toFixed(2) + 'M'
} else if (absNum >= 1e3) {
return (num / 1e3).toFixed(1) + 'K'
}
return num.toLocaleString()
}
// 日期格式化函数
export function formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') {
if (!date) return ''
const d = new Date(date)
const year = d.getFullYear()
const month = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
const hours = String(d.getHours()).padStart(2, '0')
const minutes = String(d.getMinutes()).padStart(2, '0')
const seconds = String(d.getSeconds()).padStart(2, '0')
return format
.replace('YYYY', year)
.replace('MM', month)
.replace('DD', day)
.replace('HH', hours)
.replace('mm', minutes)
.replace('ss', seconds)
}
// 相对时间格式化
export function formatRelativeTime(date) {
if (!date) return ''
const now = new Date()
const past = new Date(date)
const diffMs = now - past
const diffSecs = Math.floor(diffMs / 1000)
const diffMins = Math.floor(diffSecs / 60)
const diffHours = Math.floor(diffMins / 60)
const diffDays = Math.floor(diffHours / 24)
if (diffDays > 0) {
return `${diffDays}天前`
} else if (diffHours > 0) {
return `${diffHours}小时前`
} else if (diffMins > 0) {
return `${diffMins}分钟前`
} else {
return '刚刚'
}
}
// 字节格式化
export function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes'
const k = 1024
const dm = decimals < 0 ? 0 : decimals
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
}

View File

@@ -0,0 +1,68 @@
// Toast 通知管理
let toastContainer = null
let toastId = 0
export function showToast(message, type = 'info', title = '', duration = 3000) {
// 创建容器
if (!toastContainer) {
toastContainer = document.createElement('div')
toastContainer.id = 'toast-container'
toastContainer.style.cssText = 'position: fixed; top: 20px; right: 20px; z-index: 10000;'
document.body.appendChild(toastContainer)
}
// 创建 toast
const id = ++toastId
const toast = document.createElement('div')
toast.className = `toast rounded-2xl p-4 shadow-2xl backdrop-blur-sm toast-${type}`
toast.style.cssText = `
position: relative;
min-width: 320px;
max-width: 500px;
margin-bottom: 16px;
transform: translateX(100%);
transition: transform 0.3s ease-in-out;
`
const iconMap = {
success: 'fas fa-check-circle',
error: 'fas fa-times-circle',
warning: 'fas fa-exclamation-triangle',
info: 'fas fa-info-circle'
}
toast.innerHTML = `
<div class="flex items-start gap-3">
<div class="flex-shrink-0 mt-0.5">
<i class="${iconMap[type]} text-lg"></i>
</div>
<div class="flex-1 min-w-0">
${title ? `<h4 class="font-semibold text-sm mb-1">${title}</h4>` : ''}
<p class="text-sm opacity-90 leading-relaxed">${message}</p>
</div>
<button onclick="this.parentElement.parentElement.remove()"
class="flex-shrink-0 text-white/70 hover:text-white transition-colors ml-2">
<i class="fas fa-times"></i>
</button>
</div>
`
toastContainer.appendChild(toast)
// 触发动画
setTimeout(() => {
toast.style.transform = 'translateX(0)'
}, 10)
// 自动移除
if (duration > 0) {
setTimeout(() => {
toast.style.transform = 'translateX(100%)'
setTimeout(() => {
toast.remove()
}, 300)
}, duration)
}
return id
}