初次提交
This commit is contained in:
+400
@@ -0,0 +1,400 @@
|
||||
import wasm from 'vite-plugin-wasm'
|
||||
import glsl from 'vite-plugin-glsl'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { index, uniqueIndex } from 'drizzle-orm'
|
||||
|
||||
const customSeoConfig: Record<string, unknown> | null = (() => {
|
||||
const raw = process.env.NUXT_PUBLIC_SEO_CONFIG
|
||||
if (!raw) return null
|
||||
try {
|
||||
const parsed = JSON.parse(raw)
|
||||
if (parsed && typeof parsed === 'object') return parsed as Record<string, unknown>
|
||||
return null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
})()
|
||||
|
||||
function readNumberEnv(name: string, defaultValue: number): number {
|
||||
const raw = process.env[name]
|
||||
if (raw === undefined || raw === '') return defaultValue
|
||||
const num = Number(raw)
|
||||
return Number.isFinite(num) ? num : defaultValue
|
||||
}
|
||||
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: '2026-01-30',
|
||||
future: {
|
||||
compatibilityVersion: 4
|
||||
},
|
||||
srcDir: 'app',
|
||||
serverDir: fileURLToPath(new URL('./server', import.meta.url)),
|
||||
dir: {
|
||||
public: fileURLToPath(new URL('./public', import.meta.url))
|
||||
},
|
||||
devtools: { enabled: process.env.NODE_ENV === 'development' },
|
||||
devServer: {
|
||||
host: '0.0.0.0',
|
||||
port: 3000
|
||||
},
|
||||
modules: [
|
||||
'@nuxtjs/tailwindcss',
|
||||
...(process.env.NODE_ENV === 'development' || process.env.npm_lifecycle_event?.includes('lint')
|
||||
? ['@nuxt/eslint']
|
||||
: [])
|
||||
],
|
||||
|
||||
// 引入全局CSS
|
||||
css: [
|
||||
'~/assets/css/variables.css',
|
||||
'~/assets/css/components.css',
|
||||
'~/assets/css/main.css',
|
||||
'~/assets/css/transitions.css',
|
||||
'~/assets/css/mobile-admin.css',
|
||||
'~/assets/css/print-fix.css',
|
||||
'~/assets/css/sf-pro-icons.css'
|
||||
],
|
||||
|
||||
build: {
|
||||
transpile: []
|
||||
},
|
||||
|
||||
// 实验性优化功能
|
||||
experimental: {
|
||||
payloadExtraction: true,
|
||||
renderJsonPayloads: true,
|
||||
viewTransition: true
|
||||
},
|
||||
|
||||
// 配置运行时配置
|
||||
runtimeConfig: {
|
||||
// 服务器私有键(不会暴露到客户端)
|
||||
jwtSecret: process.env.JWT_SECRET || 'your-secret-key',
|
||||
// Redis配置(可选)
|
||||
redisUrl: process.env.REDIS_URL || '',
|
||||
redisKeyPrefix: process.env.REDIS_KEY_PREFIX || '',
|
||||
// Sentry 服务端配置(不会暴露到客户端)
|
||||
sentry: {
|
||||
serverDsn: process.env.SENTRY_SERVER_DSN || process.env.SENTRY_DSN || '',
|
||||
dsn: process.env.SENTRY_DSN || '',
|
||||
errorSampleRate: readNumberEnv('SENTRY_ERROR_SAMPLE_RATE', 1.0),
|
||||
tracesSampleRate: readNumberEnv('SENTRY_TRACES_SAMPLE_RATE', 1.0),
|
||||
replaySessionSampleRate: readNumberEnv('SENTRY_REPLAY_SESSION_SAMPLE_RATE', 0),
|
||||
replayOnErrorSampleRate: readNumberEnv('SENTRY_REPLAY_ON_ERROR_SAMPLE_RATE', 0),
|
||||
release: process.env.SENTRY_RELEASE || '',
|
||||
environment: process.env.SENTRY_ENVIRONMENT || process.env.NODE_ENV || 'production'
|
||||
},
|
||||
// 公共键(会暴露到客户端)
|
||||
public: {
|
||||
host: process.env.NUXT_PUBLIC_HOST || '',
|
||||
apiBase: '/api',
|
||||
oauth: {
|
||||
github: !!process.env.GITHUB_CLIENT_ID,
|
||||
casdoor: !!process.env.CASDOOR_CLIENT_ID,
|
||||
google: !!process.env.GOOGLE_CLIENT_ID,
|
||||
aggregate: !!process.env.AGGREGATE_OAUTH_APP_ID
|
||||
},
|
||||
siteTitle: process.env.NUXT_PUBLIC_SITE_TITLE || '校园广播站点歌系统',
|
||||
siteLogo: process.env.NUXT_PUBLIC_SITE_LOGO || '',
|
||||
siteDescription:
|
||||
process.env.NUXT_PUBLIC_SITE_DESCRIPTION || '校园广播站点歌系统 - 让你的声音被听见',
|
||||
isNetlify: process.env.NETLIFY === 'true',
|
||||
// SEO 自定义配置(通过 NUXT_PUBLIC_SEO_CONFIG 注入 JSON 字符串)
|
||||
seoConfig: customSeoConfig ?? {},
|
||||
// 图形验证码相关
|
||||
captcha: {
|
||||
enabled: (process.env.CAPTCHA_ENABLED || 'false') === 'true',
|
||||
provider: process.env.CAPTCHA_PROVIDER || 'turnstile',
|
||||
siteKey: process.env.CAPTCHA_SITE_KEY || ''
|
||||
},
|
||||
// PWA 相关结构(本地不启用模块,但保留配置项以便上层应用使用)
|
||||
pwa: {
|
||||
enabled: (process.env.PWA_ENABLED || 'false') === 'true',
|
||||
display: process.env.PWA_DISPLAY || 'standalone',
|
||||
backgroundColor: process.env.PWA_BACKGROUND_COLOR || '#000000',
|
||||
themeColor: process.env.PWA_THEME_COLOR || '#111111'
|
||||
},
|
||||
// 主题配置
|
||||
defaultTheme: process.env.NUXT_PUBLIC_DEFAULT_THEME || 'dark',
|
||||
enabledThemes: (process.env.NUXT_PUBLIC_ENABLED_THEMES || 'dark,light')
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean),
|
||||
// Sentry 客户端配置(仅公开 DSN 与采样率)
|
||||
sentry: {
|
||||
dsn: process.env.SENTRY_DSN || '',
|
||||
tracesSampleRate: readNumberEnv('SENTRY_TRACES_SAMPLE_RATE', 1.0),
|
||||
replaySessionSampleRate: readNumberEnv('SENTRY_REPLAY_SESSION_SAMPLE_RATE', 0),
|
||||
replayOnErrorSampleRate: readNumberEnv('SENTRY_REPLAY_ON_ERROR_SAMPLE_RATE', 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 配置环境变量
|
||||
app: {
|
||||
head: {
|
||||
title: process.env.NUXT_PUBLIC_SITE_TITLE || '校园广播站点歌系统',
|
||||
htmlAttrs: { lang: 'zh-CN' },
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{
|
||||
name: 'viewport',
|
||||
content:
|
||||
'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover'
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
content:
|
||||
process.env.NUXT_PUBLIC_SITE_DESCRIPTION || '校园广播站点歌系统 - 让你的声音被听见'
|
||||
},
|
||||
// 移动端优化
|
||||
{ name: 'theme-color', content: '#111111' },
|
||||
{ name: 'apple-mobile-web-app-capable', content: 'yes' },
|
||||
{ name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' },
|
||||
{ name: 'apple-mobile-web-app-title', content: 'VoiceHub管理' },
|
||||
{ name: 'mobile-web-app-capable', content: 'yes' },
|
||||
{ name: 'format-detection', content: 'telephone=no' }
|
||||
],
|
||||
script: [
|
||||
{
|
||||
type: 'text/javascript',
|
||||
tagPosition: 'head',
|
||||
children: [
|
||||
`(function(){try{`,
|
||||
` var k='voicehub:theme', d=document, r=d.documentElement, s=null;`,
|
||||
` try{s=localStorage.getItem(k)}catch(e){s=null}`,
|
||||
` if(!s){`,
|
||||
` try{`,
|
||||
` var meta=document.querySelector('meta[name="x-default-theme"]');`,
|
||||
` if(meta && meta.content){s=meta.content}`,
|
||||
` }catch(e){s=null}`,
|
||||
` if(!s){s=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}`,
|
||||
` }`,
|
||||
` r.setAttribute('data-theme', s);`,
|
||||
` if(s==='dark'){r.classList.add('dark'); r.style.colorScheme='dark'}`,
|
||||
` else if(s==='light'){r.classList.add('light'); r.style.colorScheme='light'}`,
|
||||
` else{r.style.colorScheme='light dark'}`,
|
||||
` try{`,
|
||||
` var tm=d.querySelector('meta[name="theme-color"]');`,
|
||||
` if(tm){tm.content=(s==='light'?'#f8fafc':'#111111')}`,
|
||||
` }catch(e){}`,
|
||||
`}catch(e){}})();`
|
||||
].join('')
|
||||
}
|
||||
],
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
|
||||
{ rel: 'preload', as: 'image', href: '/images/logo.png' },
|
||||
{ rel: 'preload', as: 'image', href: '/images/site-logo.png' },
|
||||
{ rel: 'preload', as: 'image', href: '/images/school-logo-white.png' },
|
||||
// 优先加载常规字体,确保页面快速显示
|
||||
{
|
||||
rel: 'preload',
|
||||
as: 'style',
|
||||
href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Regular.min.css'
|
||||
},
|
||||
{
|
||||
rel: 'stylesheet',
|
||||
href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Regular.min.css'
|
||||
},
|
||||
// 延迟加载其他字重,避免阻塞页面渲染
|
||||
{
|
||||
rel: 'preload',
|
||||
as: 'style',
|
||||
href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Medium.min.css',
|
||||
onload: "this.onload=null;this.rel='stylesheet'"
|
||||
},
|
||||
{
|
||||
rel: 'preload',
|
||||
as: 'style',
|
||||
href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Semibold.min.css',
|
||||
onload: "this.onload=null;this.rel='stylesheet'"
|
||||
},
|
||||
{
|
||||
rel: 'preload',
|
||||
as: 'style',
|
||||
href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Bold.min.css',
|
||||
onload: "this.onload=null;this.rel='stylesheet'"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
features: {
|
||||
inlineStyles: false
|
||||
},
|
||||
|
||||
// TypeScript配置
|
||||
typescript: {
|
||||
strict: true
|
||||
},
|
||||
|
||||
// 服务器端配置
|
||||
nitro: {
|
||||
compressPublicAssets: true,
|
||||
preset: process.env.VERCEL
|
||||
? 'vercel'
|
||||
: process.env.NETLIFY
|
||||
? 'netlify'
|
||||
: process.env.NITRO_PRESET || 'node-server',
|
||||
// 增强错误处理和稳定性
|
||||
experimental: {
|
||||
wasm: true,
|
||||
asyncContext: true
|
||||
},
|
||||
timing: true,
|
||||
// 增加请求超时时间
|
||||
routeRules: {
|
||||
// 完全禁用所有API路由的缓存,确保每次都请求数据库
|
||||
'/api/**': {
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
Pragma: 'no-cache',
|
||||
Expires: '0',
|
||||
Connection: 'keep-alive'
|
||||
}
|
||||
},
|
||||
// 静态资源文件缓存配置
|
||||
'/_nuxt/**': {
|
||||
headers: {
|
||||
'Cache-Control': 'public, max-age=31536000, immutable'
|
||||
}
|
||||
},
|
||||
'/assets/**': {
|
||||
headers: {
|
||||
'Cache-Control': 'public, max-age=31536000, immutable'
|
||||
}
|
||||
},
|
||||
'/favicon.ico': {
|
||||
headers: {
|
||||
'Cache-Control': 'public, max-age=86400'
|
||||
}
|
||||
},
|
||||
// 图片、CSS、JS等静态资源缓存
|
||||
'/**/*.{png,jpg,jpeg,gif,webp,svg,ico}': {
|
||||
headers: {
|
||||
'Cache-Control': 'public, max-age=31536000, immutable'
|
||||
}
|
||||
},
|
||||
'/**/*.{css,js}': {
|
||||
headers: {
|
||||
'Cache-Control': 'public, max-age=31536000, immutable'
|
||||
}
|
||||
},
|
||||
// 认证相关页面不缓存
|
||||
'/login': {
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
Pragma: 'no-cache',
|
||||
Expires: '0'
|
||||
}
|
||||
},
|
||||
'/dashboard': {
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
Pragma: 'no-cache',
|
||||
Expires: '0'
|
||||
}
|
||||
},
|
||||
'/change-password': {
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
Pragma: 'no-cache',
|
||||
Expires: '0'
|
||||
}
|
||||
},
|
||||
'/auth/**': {
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
Pragma: 'no-cache',
|
||||
Expires: '0'
|
||||
}
|
||||
},
|
||||
'/notification-settings': {
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
Pragma: 'no-cache',
|
||||
Expires: '0'
|
||||
}
|
||||
}
|
||||
},
|
||||
// 根据部署环境调整配置
|
||||
...(process.env.VERCEL
|
||||
? {
|
||||
// Vercel 环境:使用标准配置
|
||||
}
|
||||
: process.env.NETLIFY
|
||||
? {
|
||||
// Netlify 环境:确保 Drizzle 正确打包
|
||||
experimental: {
|
||||
wasm: true
|
||||
}
|
||||
}
|
||||
: {
|
||||
// 其他环境:使用标准配置
|
||||
})
|
||||
},
|
||||
|
||||
// Vite 配置
|
||||
vite: {
|
||||
build: {
|
||||
chunkSizeWarningLimit: 2000,
|
||||
cssCodeSplit: true,
|
||||
rollupOptions: {
|
||||
maxParallelFileOps: 2,
|
||||
output: {
|
||||
manualChunks(id) {
|
||||
if (id.includes('node_modules/lucide-vue-next')) {
|
||||
return 'lucide'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
alias: [
|
||||
{
|
||||
find: '@applemusic-like-lyrics/core/style.css',
|
||||
replacement: fileURLToPath(
|
||||
new URL('./vendor/amll-core/src/styles/index.css', import.meta.url)
|
||||
)
|
||||
},
|
||||
{
|
||||
find: '@applemusic-like-lyrics/core',
|
||||
replacement: fileURLToPath(new URL('./vendor/amll-core/src/index.ts', import.meta.url))
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [wasm(), glsl()],
|
||||
optimizeDeps: {
|
||||
include: ['drizzle-orm'],
|
||||
exclude: ['@applemusic-like-lyrics/vue', '@applemusic-like-lyrics/lyric']
|
||||
},
|
||||
build: {
|
||||
sourcemap: false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks(id) {
|
||||
if (!id.includes('node_modules')) return
|
||||
if (id.includes('lucide-vue-next')) return 'icons'
|
||||
if (id.includes('@pixi')) return 'pixi'
|
||||
if (id.includes('@applemusic-like-lyrics')) return 'lyric-engine'
|
||||
if (id.includes('drizzle-orm') || id.includes('postgres')) return 'database'
|
||||
if (id.includes('xlsx') || id.includes('jspdf') || id.includes('jszip')) return 'office'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 添加 WASM 支持配置
|
||||
assetsInclude: ['**/*.wasm'],
|
||||
// SSR配置
|
||||
ssr: {
|
||||
noExternal: process.env.NETLIFY
|
||||
? ['drizzle-orm', 'postgres']
|
||||
: process.env.VERCEL
|
||||
? []
|
||||
: ['drizzle-orm', 'postgres']
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user