diff --git a/.env.production b/.env.production index 7dd8fec..22b39b8 100644 --- a/.env.production +++ b/.env.production @@ -5,7 +5,7 @@ VITE_PORT=7000 VITE_ROUTER_HISTORY="hash" # 基础请求路径 -VITE_BASE_API=/api +VITE_BASE_API=/admin # 跨域代理地址 VITE_APP_URL=http://localhost:7070 diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 0000000..370752b --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,3 @@ +# Docker配置详情 + +![img.png](images/img.png) \ No newline at end of file diff --git a/build/buildEnv.ts b/build/buildEnv.ts index bd62c61..7e68d7a 100644 --- a/build/buildEnv.ts +++ b/build/buildEnv.ts @@ -1,52 +1,52 @@ -import { pathResolve } from "./utils"; -import type { BuildOptions } from "vite"; +import { pathResolve } from './utils'; +import type { BuildOptions } from 'vite'; export const buildEnvironment = () => { - const environment: BuildOptions = { - target: "es2015", - assetsInlineLimit: 20000, - // 构建输出的目录,默认值为"dist" - outDir: "dist", - // 用于指定使用的代码压缩工具。在这里,minify 被设置为 'terser',表示使用 Terser 进行代码压缩。默认值terser - // esbuild 打包更快,但是不能去除 console.log,terser打包慢,但能去除 console.log - minify: "terser", - // 用于配置 Terser 的选项 - terserOptions: { - // 用于配置压缩选项 - compress: { - drop_console: true, // 是否删除代码中的 console 语句, 默认值false - drop_debugger: true // 是否删除代码中的 debugger 语句, 默认值false - } - }, - // 禁用 gzip 压缩大小报告,可略微减少打包时间 - reportCompressedSize: false, - // 用于指定是否生成源映射文件。源映射文件可以帮助调试和定位源代码中的错误。当设置为false时,构建过程不会生成源映射文件 - sourcemap: false, - // 用于配置 CommonJS 模块的选项 - commonjsOptions: { - // 用于指定是否忽略 CommonJS 模块中的 try-catch 语句。当设置为false时,构建过程会保留 CommonJS 模块中的 try-catch 语句 - ignoreTryCatch: false - }, - // 规定触发警告的 chunk 大小, 当某个代码分块的大小超过该限制时,Vite 会发出警告 - chunkSizeWarningLimit: 2000, - rollupOptions: { - input: { - index: pathResolve("../index.html", import.meta.url) - }, - // 静态资源分类打包 - output: { - chunkFileNames: "static/js/[name]-[hash].js", - entryFileNames: "static/js/[name]-[hash].js", - assetFileNames: "static/[ext]/[name]-[hash].[ext]", - manualChunks: id => { - // 如果是包含在包中则打包成 vendor - if (id.includes("node_modules")) { - return "vendor"; - } - } - } - } - }; + const environment: BuildOptions = { + target: 'es2015', + assetsInlineLimit: 20000, + // 构建输出的目录,默认值为"dist" + outDir: 'docker/dist', + // 用于指定使用的代码压缩工具。在这里,minify 被设置为 'terser',表示使用 Terser 进行代码压缩。默认值terser + // esbuild 打包更快,但是不能去除 console.log,terser打包慢,但能去除 console.log + minify: 'terser', + // 用于配置 Terser 的选项 + terserOptions: { + // 用于配置压缩选项 + compress: { + drop_console: true, // 是否删除代码中的 console 语句, 默认值false + drop_debugger: true, // 是否删除代码中的 debugger 语句, 默认值false + }, + }, + // 禁用 gzip 压缩大小报告,可略微减少打包时间 + reportCompressedSize: false, + // 用于指定是否生成源映射文件。源映射文件可以帮助调试和定位源代码中的错误。当设置为false时,构建过程不会生成源映射文件 + sourcemap: false, + // 用于配置 CommonJS 模块的选项 + commonjsOptions: { + // 用于指定是否忽略 CommonJS 模块中的 try-catch 语句。当设置为false时,构建过程会保留 CommonJS 模块中的 try-catch 语句 + ignoreTryCatch: false, + }, + // 规定触发警告的 chunk 大小, 当某个代码分块的大小超过该限制时,Vite 会发出警告 + chunkSizeWarningLimit: 2000, + rollupOptions: { + input: { + index: pathResolve('../index.html', import.meta.url), + }, + // 静态资源分类打包 + output: { + chunkFileNames: 'static/js/[name]-[hash].js', + entryFileNames: 'static/js/[name]-[hash].js', + assetFileNames: 'static/[ext]/[name]-[hash].[ext]', + manualChunks: id => { + // 如果是包含在包中则打包成 vendor + if (id.includes('node_modules')) { + return 'vendor'; + } + }, + }, + }, + }; - return environment; + return environment; }; diff --git a/build/plugins.ts b/build/plugins.ts index 591a2bb..2958f34 100644 --- a/build/plugins.ts +++ b/build/plugins.ts @@ -1,66 +1,60 @@ -import { cdn } from "./cdn"; -import vue from "@vitejs/plugin-vue"; -import { pathResolve } from "./utils"; -import { viteBuildInfo } from "./info"; -import svgLoader from "vite-svg-loader"; -import type { PluginOption } from "vite"; -import vueJsx from "@vitejs/plugin-vue-jsx"; -import Inspector from "vite-plugin-vue-inspector"; -import { configCompressPlugin } from "./compress"; -import removeNoMatch from "vite-plugin-router-warn"; -import { visualizer } from "rollup-plugin-visualizer"; -import removeConsole from "vite-plugin-remove-console"; -import { themePreprocessorPlugin } from "@pureadmin/theme"; -import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite"; -import { genScssMultipleScopeVars } from "../src/layout/theme"; -import { vitePluginFakeServer } from "vite-plugin-fake-server"; +import { cdn } from './cdn'; +import vue from '@vitejs/plugin-vue'; +import { pathResolve } from './utils'; +import { viteBuildInfo } from './info'; +import svgLoader from 'vite-svg-loader'; +import type { PluginOption } from 'vite'; +import vueJsx from '@vitejs/plugin-vue-jsx'; +import Inspector from 'vite-plugin-vue-inspector'; +import { configCompressPlugin } from './compress'; +import removeNoMatch from 'vite-plugin-router-warn'; +import { visualizer } from 'rollup-plugin-visualizer'; +import removeConsole from 'vite-plugin-remove-console'; +import { themePreprocessorPlugin } from '@pureadmin/theme'; +import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'; +import { genScssMultipleScopeVars } from '../src/layout/theme'; +import { vitePluginFakeServer } from 'vite-plugin-fake-server'; -export function getPluginsList( - VITE_CDN: boolean, - VITE_COMPRESSION: ViteCompression, - VITE_PORT: number -): PluginOption[] { - const lifecycle = process.env.npm_lifecycle_event; - return [ - vue(), - // jsx、tsx语法支持 - vueJsx(), - VueI18nPlugin({ - jitCompilation: false, - include: [pathResolve("../locales/**")] - }), - // 按下Command(⌘)+Shift(⇧),然后点击页面元素会自动打开本地IDE并跳转到对应的代码位置 - Inspector(), - viteBuildInfo(VITE_PORT), - /** - * 开发环境下移除非必要的vue-router动态路由警告No match found for location with path - * 非必要具体看 https://github.com/vuejs/router/issues/521 和 https://github.com/vuejs/router/issues/359 - * vite-plugin-router-warn只在开发环境下启用,只处理vue-router文件并且只在服务启动或重启时运行一次,性能消耗可忽略不计 - */ - removeNoMatch(), - // mock支持 - vitePluginFakeServer({ - logger: false, - include: "mock", - infixName: false, - enableProd: true - }), - // 自定义主题 - themePreprocessorPlugin({ - scss: { - multipleScopeVars: genScssMultipleScopeVars(), - extract: true - } - }), - // svg组件化支持 - svgLoader(), - VITE_CDN ? cdn : null, - configCompressPlugin(VITE_COMPRESSION), - // 线上环境删除console - removeConsole({ external: ["src/assets/iconfont/iconfont.js"] }), - // 打包分析 - lifecycle === "report" - ? visualizer({ open: true, brotliSize: true, filename: "report.html" }) - : (null as any) - ]; +export function getPluginsList(VITE_CDN: boolean, VITE_COMPRESSION: ViteCompression, VITE_PORT: number): PluginOption[] { + const lifecycle = process.env.npm_lifecycle_event; + return [ + vue(), + // jsx、tsx语法支持 + vueJsx(), + VueI18nPlugin({ + jitCompilation: false, + include: [pathResolve('../locales/**')], + }), + // 按下Command(⌘)+Shift(⇧),然后点击页面元素会自动打开本地IDE并跳转到对应的代码位置 + Inspector(), + viteBuildInfo(VITE_PORT), + /** + * 开发环境下移除非必要的vue-router动态路由警告No match found for location with path + * 非必要具体看 https://github.com/vuejs/router/issues/521 和 https://github.com/vuejs/router/issues/359 + * vite-plugin-router-warn只在开发环境下启用,只处理vue-router文件并且只在服务启动或重启时运行一次,性能消耗可忽略不计 + */ + removeNoMatch(), + // mock支持 + vitePluginFakeServer({ + logger: false, + include: 'mock', + infixName: false, + enableProd: true, + }), + // 自定义主题 + themePreprocessorPlugin({ + scss: { + multipleScopeVars: genScssMultipleScopeVars(), + extract: true, + }, + }), + // svg组件化支持 + svgLoader(), + VITE_CDN ? cdn : null, + configCompressPlugin(VITE_COMPRESSION), + // 线上环境删除console + removeConsole({ external: ['src/assets/iconfont/iconfont.js'] }), + // 打包分析 + lifecycle === 'report' ? visualizer({ open: true, brotliSize: true, filename: 'report.html' }) : (null as any), + ]; } diff --git a/build/utils.ts b/build/utils.ts index 5a26eee..8c927a9 100644 --- a/build/utils.ts +++ b/build/utils.ts @@ -1,15 +1,9 @@ -import dayjs from "dayjs"; -import { readdir, stat } from "node:fs"; -import { fileURLToPath } from "node:url"; -import { dirname, resolve } from "node:path"; -import { formatBytes, sum } from "@pureadmin/utils"; -import { - dependencies, - devDependencies, - engines, - name, - version -} from "../package.json"; +import dayjs from 'dayjs'; +import { readdir, stat } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, resolve } from 'node:path'; +import { formatBytes, sum } from '@pureadmin/utils'; +import { dependencies, devDependencies, engines, name, version } from '../package.json'; /** 启动`node`进程时所在工作目录的绝对路径 */ const root: string = process.cwd(); @@ -19,93 +13,91 @@ const root: string = process.cwd(); * @param dir 路径片段,默认`build` * @param metaUrl 模块的完整`url`,如果在`build`目录外调用必传`import.meta.url` */ -const pathResolve = (dir = ".", metaUrl = import.meta.url) => { - // 当前文件目录的绝对路径 - const currentFileDir = dirname(fileURLToPath(metaUrl)); - // build 目录的绝对路径 - const buildDir = resolve(currentFileDir, "build"); - // 解析的绝对路径 - const resolvedPath = resolve(currentFileDir, dir); - // 检查解析的绝对路径是否在 build 目录内 - if (resolvedPath.startsWith(buildDir)) { - // 在 build 目录内,返回当前文件路径 - return fileURLToPath(metaUrl); - } - // 不在 build 目录内,返回解析后的绝对路径 - return resolvedPath; +const pathResolve = (dir = '.', metaUrl = import.meta.url) => { + // 当前文件目录的绝对路径 + const currentFileDir = dirname(fileURLToPath(metaUrl)); + // build 目录的绝对路径 + const buildDir = resolve(currentFileDir, 'build'); + // 解析的绝对路径 + const resolvedPath = resolve(currentFileDir, dir); + // 检查解析的绝对路径是否在 build 目录内 + if (resolvedPath.startsWith(buildDir)) { + // 在 build 目录内,返回当前文件路径 + return fileURLToPath(metaUrl); + } + // 不在 build 目录内,返回解析后的绝对路径 + return resolvedPath; }; /** 设置别名 */ const alias: Record = { - "@": pathResolve("../src"), - "@build": pathResolve() + '@': pathResolve('../src'), + '@build': pathResolve(), }; /** 平台的名称、版本、运行所需的`node`和`pnpm`版本、依赖、最后构建时间的类型提示 */ const __APP_INFO__ = { - pkg: { name, version, engines, dependencies, devDependencies }, - lastBuildTime: dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss") + pkg: { name, version, engines, dependencies, devDependencies }, + lastBuildTime: dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss'), }; /** 处理环境变量 */ const wrapperEnv = (envConf: Recordable): ViteEnv => { - // 默认值 - const ret: ViteEnv = { - VITE_PORT: 8848, - VITE_PUBLIC_PATH: "", - VITE_ROUTER_HISTORY: "", - VITE_APP_URL: "", - VITE_CDN: false, - VITE_HIDE_HOME: "false", - VITE_COMPRESSION: "none" - }; + // 默认值 + const ret: ViteEnv = { + VITE_PORT: 8848, + VITE_PUBLIC_PATH: '', + VITE_ROUTER_HISTORY: '', + VITE_APP_URL: '', + VITE_CDN: false, + VITE_HIDE_HOME: 'false', + VITE_COMPRESSION: 'none', + }; - for (const envName of Object.keys(envConf)) { - let realName = envConf[envName].replace(/\\n/g, "\n"); - realName = - realName === "true" ? true : realName === "false" ? false : realName; + for (const envName of Object.keys(envConf)) { + let realName = envConf[envName].replace(/\\n/g, '\n'); + realName = realName === 'true' ? true : realName === 'false' ? false : realName; - if (envName === "VITE_PORT") { - realName = Number(realName); - } - ret[envName] = realName; - if (typeof realName === "string") { - process.env[envName] = realName; - } else if (typeof realName === "object") { - process.env[envName] = JSON.stringify(realName); - } - } - return ret; + if (envName === 'VITE_PORT') { + realName = Number(realName); + } + ret[envName] = realName; + if (typeof realName === 'string') { + process.env[envName] = realName; + } else if (typeof realName === 'object') { + process.env[envName] = JSON.stringify(realName); + } + } + return ret; }; const fileListTotal: number[] = []; /** 获取指定文件夹中所有文件的总大小 */ const getPackageSize = options => { - const { folder = "dist", callback, format = true } = options; - readdir(folder, (err, files: string[]) => { - if (err) throw err; - let count = 0; - const checkEnd = () => { - ++count == files.length && - callback(format ? formatBytes(sum(fileListTotal)) : sum(fileListTotal)); - }; - files.forEach((item: string) => { - stat(`${folder}/${item}`, async (err, stats) => { - if (err) throw err; - if (stats.isFile()) { - fileListTotal.push(stats.size); - checkEnd(); - } else if (stats.isDirectory()) { - getPackageSize({ - folder: `${folder}/${item}/`, - callback: checkEnd - }); - } - }); - }); - files.length === 0 && callback(0); - }); + const { folder = 'dist', callback, format = true } = options; + readdir(folder, (err, files: string[]) => { + if (err) throw err; + let count = 0; + const checkEnd = () => { + ++count == files.length && callback(format ? formatBytes(sum(fileListTotal)) : sum(fileListTotal)); + }; + files.forEach((item: string) => { + stat(`${folder}/${item}`, async (err, stats) => { + if (err) throw err; + if (stats.isFile()) { + fileListTotal.push(stats.size); + checkEnd(); + } else if (stats.isDirectory()) { + getPackageSize({ + folder: `${folder}/${item}/`, + callback: checkEnd, + }); + } + }); + }); + files.length === 0 && callback(0); + }); }; export { root, pathResolve, alias, __APP_INFO__, wrapperEnv, getPackageSize }; diff --git a/docker/Dockerfile b/docker/Dockerfile index 6e2e99f..821028c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,7 +5,7 @@ FROM nginx RUN rm /etc/nginx/conf.d/default.conf # 将自定义的 Nginx 配置文件复制到容器中 -COPY nginx.conf /etc/nginx/conf.d/ +COPY nginx.conf /etc/nginx/conf.d/default.conf # 设置时区,构建镜像时执行的命令 RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime diff --git a/docker/nginx.conf b/docker/nginx.conf index 7aab7ef..8f09f63 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -15,24 +15,13 @@ server { } # 后端跨域请求 - location ~/api/ { - proxy_pass http://192.168.3.98:8200; - } - - # 配置WebSocket - location ~/ws/ { - # WebSocket 代理配置 - proxy_pass http://192.168.3.98:8200; # WebSocket 服务器地址和端口 - proxy_http_version 1.1; # 使用 HTTP 1.1 版本 - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - proxy_read_timeout 600s; # 保持连接的超时时间,根据需要调整 - proxy_redirect off; # 关闭重定向 - } - - # mock 跨域 - location ~/mock/ { - proxy_pass http://192.168.3.98:8200; + location ~/admin/ { + #proxy_pass http://z-bunny.cn:7070; + proxy_pass http://172.17.0.1:7070; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; } error_page 404 404.html; @@ -40,4 +29,4 @@ server { location = /50x.html { root html; } -} \ No newline at end of file +} diff --git a/images/img.png b/images/img.png new file mode 100644 index 0000000..6fe246b Binary files /dev/null and b/images/img.png differ diff --git a/src/api/service/request.ts b/src/api/service/request.ts index f72469d..4f9a153 100644 --- a/src/api/service/request.ts +++ b/src/api/service/request.ts @@ -118,7 +118,7 @@ class PureHttp { private httpInterceptorsResponse(): void { const instance = PureHttp.axiosInstance; instance.interceptors.response.use( - (response: PureHttpResponse) => { + async (response: PureHttpResponse) => { const $config = response.config; const data = response.data; @@ -128,8 +128,8 @@ class PureHttp { // 登录过期,和异常处理 if (data.code === 208) { message(data.message, { type: 'warning' }); - router.push('/').then(); removeToken(); + await router.push('/login'); } else if (data.code >= 201 && data.code < 300) { message(data.message, { type: 'warning' }); } else if (data.code > 300) { diff --git a/src/api/v1/i18n.ts b/src/api/v1/i18n.ts index 5c01685..4d6db35 100644 --- a/src/api/v1/i18n.ts +++ b/src/api/v1/i18n.ts @@ -40,7 +40,7 @@ export const fetchDeleteI18n = (data: any) => { * 多语言类型管理---获取多语言类型列表 */ export const fetchGetI18nTypeList = () => { - return http.request>('get', 'i18nType/getI18nTypeList'); + return http.request>('get', 'i18nType/noAuth/getI18nTypeList'); }; /** diff --git a/src/views/login/index.vue b/src/views/login/index.vue index 782b51c..53a96e9 100644 --- a/src/views/login/index.vue +++ b/src/views/login/index.vue @@ -3,7 +3,7 @@ import Motion from './utils/motion'; import { useNav } from '@/layout/hooks/useNav'; import { useLayout } from '@/layout/hooks/useLayout'; import { avatar, bg, illustration } from './utils/static'; -import { toRaw } from 'vue'; +import { onMounted, toRaw } from 'vue'; import { useTranslationLang } from '@/layout/hooks/useTranslationLang'; import { useDataThemeChange } from '@/layout/hooks/useDataThemeChange'; @@ -27,6 +27,10 @@ dataThemeChange(overallStyle.value); const { title, getDropdownItemStyle, getDropdownItemClass } = useNav(); const { locale, translation } = useTranslationLang(); const i18nTypeStore = userI18nTypeStore(); + +onMounted(() => { + i18nTypeStore.getI18nTypeList(); +}); - {{ userStore.readMeDay }}天免登录 + {{ userStore.readMeDay }}天免登录(邮箱验证码随便输入,后端校验验证码已注释) diff --git a/src/views/system/adminUser/utils/columns.tsx b/src/views/system/adminUser/utils/columns.tsx index 0a3bad1..141feaa 100644 --- a/src/views/system/adminUser/utils/columns.tsx +++ b/src/views/system/adminUser/utils/columns.tsx @@ -26,6 +26,10 @@ export const columns: TableColumnList = [ { label: $t('adminUser_sex'), prop: 'sex', slot: 'sex' }, // 个人描述 { label: $t('adminUser_summary'), prop: 'summary', width: 460 }, + // 登录的IP地址 + { label: $t('lastLoginIp'), prop: 'lastLoginIp', width: 130 }, + // IP地区 + { label: $t('lastLoginIpAddress'), prop: 'lastLoginIpAddress', width: 130 }, { label: $t('table.updateTime'), prop: 'updateTime', sortable: true, width: 160 }, { label: $t('table.createTime'), prop: 'createTime', sortable: true, width: 160 }, { label: $t('table.updateUser'), prop: 'updateUser', slot: 'updateUser', width: 90, fixed: 'right' },