import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; import { VitePWA } from 'vite-plugin-pwa'; import path from 'node:path'; export default defineConfig({ plugins: [ vue(), VitePWA({ registerType: 'autoUpdate', injectRegister: 'auto', // 自动注入 register 脚本 includeAssets: ['favicon-16x16.png', 'favicon-32x32.png'], manifest: { id: '/', name: 'CarLog 洗车管理系统', short_name: 'CarLog', description: '记录洗车/加油/充电/保养/保险/车品的全能车辆账本', lang: 'zh-CN', dir: 'ltr', start_url: '/', scope: '/', display: 'standalone', orientation: 'portrait', background_color: '#F5F8FC', theme_color: '#1B6EF3', categories: ['productivity', 'lifestyle', 'utilities'], icons: [ { src: '/pwa/pwa-192x192.png', sizes: '192x192', type: 'image/png', purpose: 'any' }, { src: '/pwa/pwa-512x512.png', sizes: '512x512', type: 'image/png', purpose: 'any' }, { src: '/pwa/pwa-maskable-512x512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable', }, { src: '/pwa/apple-touch-icon.png', sizes: '180x180', type: 'image/png', purpose: 'any', }, ], shortcuts: [ { name: '拍照录入', short_name: '拍照', url: '/washes/new?capture=1', description: '打开相机快速拍照记录', }, { name: '新建洗车', short_name: '洗车', url: '/washes/new', description: '快速记录一次洗车', }, { name: '新建加油', short_name: '加油', url: '/refuel/new', description: '快速记录一次加油', }, { name: '新建保养', short_name: '保养', url: '/maintenance/new', description: '快速记录一次保养', }, ], // 拍照快捷方式行为 capture_links: ['/washes/new?capture=1'], launch_handler: { client_mode: 'auto' }, }, workbox: { globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2,webmanifest}'], navigateFallback: '/index.html', navigateFallbackDenylist: [/^\/api/, /^\/uploads/], runtimeCaching: [ { urlPattern: ({ url }) => url.pathname.startsWith('/api/') && url.pathname.includes('/static'), handler: 'CacheFirst', options: { cacheName: 'api-static', expiration: { maxEntries: 200, maxAgeSeconds: 60 * 60 * 24 * 30 }, }, }, { urlPattern: ({ url }) => url.pathname.startsWith('/uploads/'), handler: 'CacheFirst', options: { cacheName: 'uploads', expiration: { maxEntries: 200, maxAgeSeconds: 60 * 60 * 24 * 30 }, }, }, { urlPattern: ({ request }) => request.destination === 'image', handler: 'StaleWhileRevalidate', options: { cacheName: 'images' }, }, { urlPattern: ({ request }) => request.destination === 'font', handler: 'CacheFirst', options: { cacheName: 'fonts' }, }, ], }, devOptions: { enabled: false, // dev 不开 SW(避免 HMR 干扰) }, }), ], resolve: { alias: { '@': path.resolve(__dirname, './src'), }, }, server: { port: 5173, proxy: { '/api': { target: 'http://127.0.0.1:8787', changeOrigin: true, }, }, }, optimizeDeps: { include: ['vue', 'pinia', 'axios', 'chart.js/auto'], }, build: { outDir: 'dist', emptyOutDir: true, cssCodeSplit: true, target: 'es2018', rollupOptions: { output: { manualChunks(id) { if (id.includes('node_modules')) { if (id.includes('chart.js') || id.includes('vue-chartjs')) return 'chart'; if (id.includes('echarts') || id.includes('zrender')) return 'chart'; if (id.includes('workbox') || id.includes('vite-plugin-pwa')) return 'pwa'; if (id.includes('vue') || id.includes('pinia') || id.includes('@vue')) return 'vue'; if ( id.includes('axios') || id.includes('dayjs') || id.includes('dompurify') ) return 'utils'; return 'vendor'; } }, // 文件名带 hash,长期缓存友好 entryFileNames: 'assets/[name]-[hash].js', chunkFileNames: 'assets/[name]-[hash].js', assetFileNames: 'assets/[name]-[hash][extname]', }, }, chunkSizeWarningLimit: 600, }, });