feat: 初始化考培练系统项目

- 从服务器拉取完整代码
- 按框架规范整理项目结构
- 配置 Drone CI 测试环境部署
- 包含后端(FastAPI)、前端(Vue3)、管理端

技术栈: Vue3 + TypeScript + FastAPI + MySQL
This commit is contained in:
111
2026-01-24 19:33:28 +08:00
commit 998211c483
1197 changed files with 228429 additions and 0 deletions

223
frontend/src/config/env.ts Normal file
View File

@@ -0,0 +1,223 @@
/**
* 环境配置管理
*/
// 环境变量接口
// Vite已提供ImportMetaEnv接口此处仅作为文档参考
/*
interface ImportMetaEnv {
// 应用配置
readonly VITE_APP_TITLE: string
readonly VITE_APP_VERSION: string
readonly VITE_APP_ENV: 'development' | 'production' | 'staging'
readonly VITE_APP_BASE_URL: string
// API配置
readonly VITE_API_BASE_URL: string
readonly VITE_API_TIMEOUT: string
readonly VITE_USE_MOCK_DATA: string
// WebSocket配置
readonly VITE_WS_BASE_URL: string
readonly VITE_WS_RECONNECT_INTERVAL: string
readonly VITE_WS_MAX_RECONNECT_ATTEMPTS: string
// 上传配置
readonly VITE_UPLOAD_BASE_URL: string
readonly VITE_UPLOAD_MAX_SIZE: string
readonly VITE_UPLOAD_ALLOWED_TYPES: string
// 第三方服务配置
readonly VITE_COZE_BOT_ID: string
readonly VITE_DIFY_API_KEY: string
// 功能开关
readonly VITE_ENABLE_MOCK: string
readonly VITE_ENABLE_DEVTOOLS: string
readonly VITE_ENABLE_ERROR_REPORTING: string
readonly VITE_ENABLE_ANALYTICS: string
// 安全配置
readonly VITE_JWT_EXPIRE_TIME: string
readonly VITE_REFRESH_TOKEN_EXPIRE_TIME: string
// 性能配置
readonly VITE_ENABLE_LAZY_LOADING: string
readonly VITE_ENABLE_CODE_SPLITTING: string
readonly VITE_CHUNK_SIZE_WARNING_LIMIT: string
// 调试配置
readonly VITE_LOG_LEVEL: string
readonly VITE_ENABLE_REQUEST_LOG: string
readonly VITE_ENABLE_PERFORMANCE_LOG: string
}
*/
// ImportMeta接口扩展已由Vite提供此处仅作为类型说明
// interface ImportMeta {
// readonly env: ImportMetaEnv
// }
// 环境配置类
class EnvConfig {
// 应用配置
public readonly APP_TITLE = import.meta.env.VITE_APP_TITLE || '考培练系统'
public readonly APP_VERSION = import.meta.env.VITE_APP_VERSION || '1.0.0'
public readonly APP_ENV = import.meta.env.VITE_APP_ENV || 'development'
public readonly APP_BASE_URL = import.meta.env.VITE_APP_BASE_URL || '/'
// API配置
public readonly API_BASE_URL = (() => {
// 开发环境使用空字符串让请求使用相对路径通过Vite proxy转发
if (import.meta.env.DEV || import.meta.env.VITE_APP_ENV === 'development') {
console.log('[开发] 使用相对路径API请求通过Vite proxy转发')
return ''
}
// 生产环境:始终使用当前访问的域名,支持多域名部署
// 这样同一份构建可以部署到任何域名
return window.location.origin
})()
public readonly API_TIMEOUT = parseInt(import.meta.env.VITE_API_TIMEOUT || '10000')
public readonly USE_MOCK_DATA = import.meta.env.VITE_USE_MOCK_DATA === 'true'
// WebSocket配置
public readonly WS_BASE_URL = (() => {
// 开发环境
if (import.meta.env.DEV || import.meta.env.VITE_APP_ENV === 'development') {
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
return `${wsProtocol}//${window.location.host}`
}
// 生产环境使用wss + 当前域名,支持多域名部署
return `wss://${window.location.host}`
})()
public readonly WS_RECONNECT_INTERVAL = parseInt(import.meta.env.VITE_WS_RECONNECT_INTERVAL || '5000')
public readonly WS_MAX_RECONNECT_ATTEMPTS = parseInt(import.meta.env.VITE_WS_MAX_RECONNECT_ATTEMPTS || '5')
// 上传配置
public readonly UPLOAD_BASE_URL = import.meta.env.VITE_UPLOAD_BASE_URL || `${this.API_BASE_URL}/api/v1/upload`
public readonly UPLOAD_MAX_SIZE = parseInt(import.meta.env.VITE_UPLOAD_MAX_SIZE || '15728640') // 15MB (Dify限制)
public readonly UPLOAD_ALLOWED_TYPES = import.meta.env.VITE_UPLOAD_ALLOWED_TYPES?.split(',') || [
'.txt', '.md', '.mdx', '.pdf', '.html', '.htm', '.xlsx', '.xls', '.docx', '.csv', '.vtt', '.properties'
]
// 第三方服务配置
public readonly COZE_BOT_ID = import.meta.env.VITE_COZE_BOT_ID || ''
public readonly DIFY_API_KEY = import.meta.env.VITE_DIFY_API_KEY || ''
// 功能开关
public readonly ENABLE_MOCK = import.meta.env.VITE_ENABLE_MOCK === 'true'
public readonly ENABLE_DEVTOOLS = import.meta.env.VITE_ENABLE_DEVTOOLS === 'true'
public readonly ENABLE_ERROR_REPORTING = import.meta.env.VITE_ENABLE_ERROR_REPORTING === 'true'
public readonly ENABLE_ANALYTICS = import.meta.env.VITE_ENABLE_ANALYTICS === 'true'
// 安全配置
public readonly JWT_EXPIRE_TIME = parseInt(import.meta.env.VITE_JWT_EXPIRE_TIME || '86400') // 24小时
public readonly REFRESH_TOKEN_EXPIRE_TIME = parseInt(import.meta.env.VITE_REFRESH_TOKEN_EXPIRE_TIME || '604800') // 7天
// 性能配置
public readonly ENABLE_LAZY_LOADING = import.meta.env.VITE_ENABLE_LAZY_LOADING === 'true'
public readonly ENABLE_CODE_SPLITTING = import.meta.env.VITE_ENABLE_CODE_SPLITTING === 'true'
public readonly CHUNK_SIZE_WARNING_LIMIT = parseInt(import.meta.env.VITE_CHUNK_SIZE_WARNING_LIMIT || '1000')
// 调试配置
public readonly LOG_LEVEL = import.meta.env.VITE_LOG_LEVEL || 'info'
public readonly ENABLE_REQUEST_LOG = import.meta.env.VITE_ENABLE_REQUEST_LOG === 'true'
public readonly ENABLE_PERFORMANCE_LOG = import.meta.env.VITE_ENABLE_PERFORMANCE_LOG === 'true'
// 环境判断
public readonly isDevelopment = this.APP_ENV === 'development'
public readonly isProduction = this.APP_ENV === 'production'
public readonly isStaging = this.APP_ENV === 'staging'
/**
* 获取完整的API地址
*/
public getApiUrl(path: string): string {
// 移除路径开头的斜杠,避免重复
const cleanPath = path.startsWith('/') ? path.slice(1) : path
// 确保API基础URL不以斜杠结尾
const baseUrl = this.API_BASE_URL.endsWith('/') ? this.API_BASE_URL.slice(0, -1) : this.API_BASE_URL
return `${baseUrl}/${cleanPath}`
}
/**
* 获取完整的WebSocket地址
*/
public getWsUrl(path: string): string {
const cleanPath = path.startsWith('/') ? path.slice(1) : path
const baseUrl = this.WS_BASE_URL.endsWith('/') ? this.WS_BASE_URL.slice(0, -1) : this.WS_BASE_URL
return `${baseUrl}/${cleanPath}`
}
/**
* 获取完整的上传地址
*/
public getUploadUrl(path?: string): string {
if (!path) return this.UPLOAD_BASE_URL
const cleanPath = path.startsWith('/') ? path.slice(1) : path
const baseUrl = this.UPLOAD_BASE_URL.endsWith('/') ? this.UPLOAD_BASE_URL.slice(0, -1) : this.UPLOAD_BASE_URL
return `${baseUrl}/${cleanPath}`
}
/**
* 检查文件类型是否允许
*/
public isAllowedFileType(fileName: string): boolean {
const extension = fileName.toLowerCase().substring(fileName.lastIndexOf('.'))
return this.UPLOAD_ALLOWED_TYPES.includes(extension)
}
/**
* 检查文件大小是否超限
*/
public isFileSizeValid(size: number): boolean {
return size <= this.UPLOAD_MAX_SIZE
}
/**
* 格式化文件大小
*/
public formatFileSize(bytes: number): string {
if (bytes === 0) return '0 Bytes'
const k = 1024
const sizes = ['Bytes', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
/**
* 获取最大上传大小的友好显示
*/
public getMaxUploadSizeText(): string {
return this.formatFileSize(this.UPLOAD_MAX_SIZE)
}
/**
* 打印环境配置信息(仅开发环境)
*/
public printConfig(): void {
if (!this.isDevelopment) return
console.group('🔧 环境配置信息')
console.log('应用环境:', this.APP_ENV)
console.log('应用标题:', this.APP_TITLE)
console.log('应用版本:', this.APP_VERSION)
console.log('API地址:', this.API_BASE_URL)
console.log('WebSocket地址:', this.WS_BASE_URL)
console.log('使用模拟数据:', this.USE_MOCK_DATA)
console.log('启用开发工具:', this.ENABLE_DEVTOOLS)
console.log('最大上传大小:', this.getMaxUploadSizeText())
console.log('允许的文件类型:', this.UPLOAD_ALLOWED_TYPES.join(', '))
console.groupEnd()
}
}
// 导出环境配置实例
export const env = new EnvConfig()
// 在开发环境下打印配置信息
if (env.isDevelopment && env.ENABLE_REQUEST_LOG) {
env.printConfig()
}
export default env