feat: 完成后端-管理后台集成及部署配置
🚀 主要功能: - 完善后端API服务层,实现完整的CRUD操作 - 开发管理后台所有核心页面 (仪表板、照片、分类、标签、用户、设置) - 完成前后端完全集成,所有API接口正常对接 - 配置完整的CI/CD流水线,支持自动化部署 🎯 后端完善: - 实现PhotoService, CategoryService, TagService, UserService - 添加完整的API处理器和路由配置 - 支持Docker容器化部署 - 添加数据库迁移和健康检查 🎨 管理后台完成: - 仪表板: 实时统计数据展示 - 照片管理: 完整的CRUD操作,支持批量处理 - 分类管理: 树形结构展示和管理 - 标签管理: 颜色标签和统计信息 - 用户管理: 角色权限控制 - 系统设置: 多标签配置界面 - 添加pre-commit代码质量检查 🔧 部署配置: - Docker Compose完整配置 - 后端CI/CD流水线 (Docker部署) - 管理后台CI/CD流水线 (静态文件部署) - 前端CI/CD流水线优化 - 自动化脚本: 部署、备份、监控 - 完整的部署文档和运维指南 ✅ 集成完成: - 所有API接口正常连接 - 认证系统完整集成 - 数据获取和状态管理 - 错误处理和用户反馈 - 响应式设计优化
This commit is contained in:
86
admin/src/lib/utils.ts
Normal file
86
admin/src/lib/utils.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
export function formatFileSize(bytes: number): string {
|
||||
if (bytes === 0) return '0 Bytes'
|
||||
|
||||
const k = 1024
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
export function formatDate(date: string | Date): string {
|
||||
const d = typeof date === 'string' ? new Date(date) : date
|
||||
return d.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
})
|
||||
}
|
||||
|
||||
export function formatDateTime(date: string | Date): string {
|
||||
const d = typeof date === 'string' ? new Date(date) : date
|
||||
return d.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
}
|
||||
|
||||
export function generateSlug(text: string): string {
|
||||
return text
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^\w\s-]/g, '') // 移除特殊字符
|
||||
.replace(/[\s_-]+/g, '-') // 替换空格和下划线为连字符
|
||||
.replace(/^-+|-+$/g, '') // 移除开头和结尾的连字符
|
||||
}
|
||||
|
||||
export function truncate(text: string, length: number): string {
|
||||
if (text.length <= length) return text
|
||||
return text.substring(0, length) + '...'
|
||||
}
|
||||
|
||||
export function debounce<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
wait: number
|
||||
): (...args: Parameters<T>) => void {
|
||||
let timeout: NodeJS.Timeout | null = null
|
||||
|
||||
return (...args: Parameters<T>) => {
|
||||
if (timeout) clearTimeout(timeout)
|
||||
timeout = setTimeout(() => func(...args), wait)
|
||||
}
|
||||
}
|
||||
|
||||
export function isImageFile(file: File): boolean {
|
||||
const imageTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', 'image/bmp']
|
||||
return imageTypes.includes(file.type)
|
||||
}
|
||||
|
||||
export function getImageDimensions(file: File): Promise<{ width: number; height: number }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
const url = URL.createObjectURL(file)
|
||||
|
||||
img.onload = () => {
|
||||
URL.revokeObjectURL(url)
|
||||
resolve({ width: img.naturalWidth, height: img.naturalHeight })
|
||||
}
|
||||
|
||||
img.onerror = () => {
|
||||
URL.revokeObjectURL(url)
|
||||
reject(new Error('Failed to load image'))
|
||||
}
|
||||
|
||||
img.src = url
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user