a6cc190751
P1b Task 9: apps/admin based on art-design-pro v3.0.2 (tag, commit d224dd4; source recorded in apps/admin/SOURCE.md), trimmed to layout/login/i18n/request/table-form core. Login wired to real POST /v1/auth/password (single-token session), Bearer header on all requests, 401 interceptor logout+redirect, request-driven sliding renewal (refresh at ~80% TTL, single-flight), best-effort POST /v1/auth/logout on user-menu logout. One protected stub page (相册管理). Dev port 5176 strictPort + /v1 proxy to 8080. Admin keeps its own eslint9 flat config; root eslint ignores apps/admin/**. vitest unit tests for renewal pacing + Playwright e2e (guard redirect, localized wrong-password error, login lands on stub, token persists across reload, logout clears session). Co-Authored-By: Claude Code <noreply@anthropic.com>
152 lines
4.5 KiB
TypeScript
152 lines
4.5 KiB
TypeScript
import { defineConfig, loadEnv } from 'vite'
|
||
import vue from '@vitejs/plugin-vue'
|
||
import path from 'path'
|
||
import { fileURLToPath } from 'url'
|
||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||
import viteCompression from 'vite-plugin-compression'
|
||
import Components from 'unplugin-vue-components/vite'
|
||
import AutoImport from 'unplugin-auto-import/vite'
|
||
import ElementPlus from 'unplugin-element-plus/vite'
|
||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||
import tailwindcss from '@tailwindcss/vite'
|
||
|
||
// @photo/admin:dev 固定 5176(CORS_ORIGINS 已包含);/v1 代理到本机 API(8080),
|
||
// 仅作用于 `vite dev`——生产由网关同源挂载 /v1(同 apps/viewer 约定)。
|
||
// host 显式 127.0.0.1:Windows 下 localhost 默认可能只绑 [::1],代理/e2e 探活走 IPv4。
|
||
export default ({ mode }: { mode: string }) => {
|
||
const root = process.cwd()
|
||
const env = loadEnv(mode, root)
|
||
// .env 不入库(仓库约定):缺省时给兜底值,保证全新 clone 可直接构建
|
||
const VITE_VERSION = env.VITE_VERSION || '0.1.0'
|
||
const VITE_BASE_URL = env.VITE_BASE_URL || '/'
|
||
|
||
console.log(`🚀 VERSION = ${VITE_VERSION}`)
|
||
|
||
return defineConfig({
|
||
define: {
|
||
__APP_VERSION__: JSON.stringify(VITE_VERSION)
|
||
},
|
||
base: VITE_BASE_URL,
|
||
server: {
|
||
host: '127.0.0.1',
|
||
port: 5176,
|
||
strictPort: true,
|
||
proxy: {
|
||
'/v1': {
|
||
target: 'http://127.0.0.1:8080',
|
||
changeOrigin: true
|
||
}
|
||
}
|
||
},
|
||
preview: {
|
||
host: '127.0.0.1',
|
||
port: 5176,
|
||
strictPort: true
|
||
},
|
||
// 路径别名
|
||
resolve: {
|
||
alias: {
|
||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||
'@views': resolvePath('src/views'),
|
||
'@imgs': resolvePath('src/assets/images'),
|
||
'@icons': resolvePath('src/assets/icons'),
|
||
'@utils': resolvePath('src/utils'),
|
||
'@stores': resolvePath('src/store'),
|
||
'@styles': resolvePath('src/assets/styles')
|
||
}
|
||
},
|
||
build: {
|
||
target: 'es2015',
|
||
outDir: 'dist',
|
||
chunkSizeWarningLimit: 2000,
|
||
minify: 'terser',
|
||
terserOptions: {
|
||
compress: {
|
||
// 生产环境去除 console
|
||
drop_console: true,
|
||
// 生产环境去除 debugger
|
||
drop_debugger: true
|
||
}
|
||
},
|
||
dynamicImportVarsOptions: {
|
||
warnOnError: true,
|
||
exclude: [],
|
||
include: ['src/views/**/*.vue']
|
||
}
|
||
},
|
||
plugins: [
|
||
vue(),
|
||
tailwindcss(),
|
||
// 自动按需导入 API
|
||
AutoImport({
|
||
imports: ['vue', 'vue-router', 'pinia', '@vueuse/core'],
|
||
dts: 'src/types/import/auto-imports.d.ts',
|
||
resolvers: [ElementPlusResolver()],
|
||
eslintrc: {
|
||
enabled: true,
|
||
filepath: './.auto-import.json',
|
||
globalsPropValue: true
|
||
}
|
||
}),
|
||
// 自动按需导入组件
|
||
Components({
|
||
dts: 'src/types/import/components.d.ts',
|
||
resolvers: [ElementPlusResolver()]
|
||
}),
|
||
// 按需定制主题配置
|
||
ElementPlus({
|
||
useSource: true
|
||
}),
|
||
// 压缩
|
||
viteCompression({
|
||
verbose: false, // 是否在控制台输出压缩结果
|
||
disable: false, // 是否禁用
|
||
algorithm: 'gzip', // 压缩算法
|
||
ext: '.gz', // 压缩后的文件名后缀
|
||
threshold: 10240, // 只有大小大于该值的资源会被处理 10240B = 10KB
|
||
deleteOriginFile: false // 压缩后是否删除原文件
|
||
}),
|
||
vueDevTools()
|
||
],
|
||
// 依赖预构建:避免运行时重复请求与转换,提升首次加载速度
|
||
// @photo/admin 裁剪:echarts/xlsx/xgplayer/file-saver/vue-img-cutter 等演示依赖已移除
|
||
optimizeDeps: {
|
||
include: [
|
||
'crypto-js',
|
||
'element-plus/es',
|
||
'element-plus/es/components/*/style/css',
|
||
'element-plus/es/components/*/style/index'
|
||
]
|
||
},
|
||
css: {
|
||
preprocessorOptions: {
|
||
// sass variable and mixin
|
||
scss: {
|
||
additionalData: `
|
||
@use "@styles/core/el-light.scss" as *;
|
||
@use "@styles/core/mixin.scss" as *;
|
||
`
|
||
}
|
||
},
|
||
postcss: {
|
||
plugins: [
|
||
{
|
||
postcssPlugin: 'internal:charset-removal',
|
||
AtRule: {
|
||
charset: (atRule) => {
|
||
if (atRule.name === 'charset') {
|
||
atRule.remove()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
]
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
function resolvePath(paths: string) {
|
||
return path.resolve(fileURLToPath(new URL('.', import.meta.url)), paths)
|
||
}
|