fixbug: 🐛 用户页面显示ip内容,登录页面修改,NGINX配置修改

This commit is contained in:
bunny 2024-10-15 15:24:52 +08:00
parent 24e2990da9
commit f1e88d4e6b
13 changed files with 203 additions and 217 deletions

View File

@ -5,7 +5,7 @@ VITE_PORT=7000
VITE_ROUTER_HISTORY="hash" VITE_ROUTER_HISTORY="hash"
# 基础请求路径 # 基础请求路径
VITE_BASE_API=/api VITE_BASE_API=/admin
# 跨域代理地址 # 跨域代理地址
VITE_APP_URL=http://localhost:7070 VITE_APP_URL=http://localhost:7070

3
ReadMe.md Normal file
View File

@ -0,0 +1,3 @@
# Docker配置详情
![img.png](images/img.png)

View File

@ -1,52 +1,52 @@
import { pathResolve } from "./utils"; import { pathResolve } from './utils';
import type { BuildOptions } from "vite"; import type { BuildOptions } from 'vite';
export const buildEnvironment = () => { export const buildEnvironment = () => {
const environment: BuildOptions = { const environment: BuildOptions = {
target: "es2015", target: 'es2015',
assetsInlineLimit: 20000, assetsInlineLimit: 20000,
// 构建输出的目录,默认值为"dist" // 构建输出的目录,默认值为"dist"
outDir: "dist", outDir: 'docker/dist',
// 用于指定使用的代码压缩工具。在这里minify 被设置为 'terser',表示使用 Terser 进行代码压缩。默认值terser // 用于指定使用的代码压缩工具。在这里minify 被设置为 'terser',表示使用 Terser 进行代码压缩。默认值terser
// esbuild 打包更快,但是不能去除 console.logterser打包慢但能去除 console.log // esbuild 打包更快,但是不能去除 console.logterser打包慢但能去除 console.log
minify: "terser", minify: 'terser',
// 用于配置 Terser 的选项 // 用于配置 Terser 的选项
terserOptions: { terserOptions: {
// 用于配置压缩选项 // 用于配置压缩选项
compress: { compress: {
drop_console: true, // 是否删除代码中的 console 语句, 默认值false drop_console: true, // 是否删除代码中的 console 语句, 默认值false
drop_debugger: true // 是否删除代码中的 debugger 语句, 默认值false drop_debugger: true, // 是否删除代码中的 debugger 语句, 默认值false
} },
}, },
// 禁用 gzip 压缩大小报告,可略微减少打包时间 // 禁用 gzip 压缩大小报告,可略微减少打包时间
reportCompressedSize: false, reportCompressedSize: false,
// 用于指定是否生成源映射文件。源映射文件可以帮助调试和定位源代码中的错误。当设置为false时构建过程不会生成源映射文件 // 用于指定是否生成源映射文件。源映射文件可以帮助调试和定位源代码中的错误。当设置为false时构建过程不会生成源映射文件
sourcemap: false, sourcemap: false,
// 用于配置 CommonJS 模块的选项 // 用于配置 CommonJS 模块的选项
commonjsOptions: { commonjsOptions: {
// 用于指定是否忽略 CommonJS 模块中的 try-catch 语句。当设置为false时构建过程会保留 CommonJS 模块中的 try-catch 语句 // 用于指定是否忽略 CommonJS 模块中的 try-catch 语句。当设置为false时构建过程会保留 CommonJS 模块中的 try-catch 语句
ignoreTryCatch: false ignoreTryCatch: false,
}, },
// 规定触发警告的 chunk 大小, 当某个代码分块的大小超过该限制时Vite 会发出警告 // 规定触发警告的 chunk 大小, 当某个代码分块的大小超过该限制时Vite 会发出警告
chunkSizeWarningLimit: 2000, chunkSizeWarningLimit: 2000,
rollupOptions: { rollupOptions: {
input: { input: {
index: pathResolve("../index.html", import.meta.url) index: pathResolve('../index.html', import.meta.url),
}, },
// 静态资源分类打包 // 静态资源分类打包
output: { output: {
chunkFileNames: "static/js/[name]-[hash].js", chunkFileNames: 'static/js/[name]-[hash].js',
entryFileNames: "static/js/[name]-[hash].js", entryFileNames: 'static/js/[name]-[hash].js',
assetFileNames: "static/[ext]/[name]-[hash].[ext]", assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
manualChunks: id => { manualChunks: id => {
// 如果是包含在包中则打包成 vendor // 如果是包含在包中则打包成 vendor
if (id.includes("node_modules")) { if (id.includes('node_modules')) {
return "vendor"; return 'vendor';
} }
} },
} },
} },
}; };
return environment; return environment;
}; };

View File

@ -1,66 +1,60 @@
import { cdn } from "./cdn"; import { cdn } from './cdn';
import vue from "@vitejs/plugin-vue"; import vue from '@vitejs/plugin-vue';
import { pathResolve } from "./utils"; import { pathResolve } from './utils';
import { viteBuildInfo } from "./info"; import { viteBuildInfo } from './info';
import svgLoader from "vite-svg-loader"; import svgLoader from 'vite-svg-loader';
import type { PluginOption } from "vite"; import type { PluginOption } from 'vite';
import vueJsx from "@vitejs/plugin-vue-jsx"; import vueJsx from '@vitejs/plugin-vue-jsx';
import Inspector from "vite-plugin-vue-inspector"; import Inspector from 'vite-plugin-vue-inspector';
import { configCompressPlugin } from "./compress"; import { configCompressPlugin } from './compress';
import removeNoMatch from "vite-plugin-router-warn"; import removeNoMatch from 'vite-plugin-router-warn';
import { visualizer } from "rollup-plugin-visualizer"; import { visualizer } from 'rollup-plugin-visualizer';
import removeConsole from "vite-plugin-remove-console"; import removeConsole from 'vite-plugin-remove-console';
import { themePreprocessorPlugin } from "@pureadmin/theme"; import { themePreprocessorPlugin } from '@pureadmin/theme';
import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite"; import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
import { genScssMultipleScopeVars } from "../src/layout/theme"; import { genScssMultipleScopeVars } from '../src/layout/theme';
import { vitePluginFakeServer } from "vite-plugin-fake-server"; import { vitePluginFakeServer } from 'vite-plugin-fake-server';
export function getPluginsList( export function getPluginsList(VITE_CDN: boolean, VITE_COMPRESSION: ViteCompression, VITE_PORT: number): PluginOption[] {
VITE_CDN: boolean, const lifecycle = process.env.npm_lifecycle_event;
VITE_COMPRESSION: ViteCompression, return [
VITE_PORT: number vue(),
): PluginOption[] { // jsx、tsx语法支持
const lifecycle = process.env.npm_lifecycle_event; vueJsx(),
return [ VueI18nPlugin({
vue(), jitCompilation: false,
// jsx、tsx语法支持 include: [pathResolve('../locales/**')],
vueJsx(), }),
VueI18nPlugin({ // 按下Command(⌘)+Shift(⇧)然后点击页面元素会自动打开本地IDE并跳转到对应的代码位置
jitCompilation: false, Inspector(),
include: [pathResolve("../locales/**")] viteBuildInfo(VITE_PORT),
}), /**
// 按下Command(⌘)+Shift(⇧)然后点击页面元素会自动打开本地IDE并跳转到对应的代码位置 * vue-router动态路由警告No match found for location with path
Inspector(), * https://github.com/vuejs/router/issues/521 和 https://github.com/vuejs/router/issues/359
viteBuildInfo(VITE_PORT), * vite-plugin-router-warn只在开发环境下启用vue-router文件并且只在服务启动或重启时运行一次
/** */
* vue-router动态路由警告No match found for location with path removeNoMatch(),
* https://github.com/vuejs/router/issues/521 和 https://github.com/vuejs/router/issues/359 // mock支持
* vite-plugin-router-warn只在开发环境下启用vue-router文件并且只在服务启动或重启时运行一次 vitePluginFakeServer({
*/ logger: false,
removeNoMatch(), include: 'mock',
// mock支持 infixName: false,
vitePluginFakeServer({ enableProd: true,
logger: false, }),
include: "mock", // 自定义主题
infixName: false, themePreprocessorPlugin({
enableProd: true scss: {
}), multipleScopeVars: genScssMultipleScopeVars(),
// 自定义主题 extract: true,
themePreprocessorPlugin({ },
scss: { }),
multipleScopeVars: genScssMultipleScopeVars(), // svg组件化支持
extract: true svgLoader(),
} VITE_CDN ? cdn : null,
}), configCompressPlugin(VITE_COMPRESSION),
// svg组件化支持 // 线上环境删除console
svgLoader(), removeConsole({ external: ['src/assets/iconfont/iconfont.js'] }),
VITE_CDN ? cdn : null, // 打包分析
configCompressPlugin(VITE_COMPRESSION), lifecycle === 'report' ? visualizer({ open: true, brotliSize: true, filename: 'report.html' }) : (null as any),
// 线上环境删除console ];
removeConsole({ external: ["src/assets/iconfont/iconfont.js"] }),
// 打包分析
lifecycle === "report"
? visualizer({ open: true, brotliSize: true, filename: "report.html" })
: (null as any)
];
} }

View File

@ -1,15 +1,9 @@
import dayjs from "dayjs"; import dayjs from 'dayjs';
import { readdir, stat } from "node:fs"; import { readdir, stat } from 'node:fs';
import { fileURLToPath } from "node:url"; import { fileURLToPath } from 'node:url';
import { dirname, resolve } from "node:path"; import { dirname, resolve } from 'node:path';
import { formatBytes, sum } from "@pureadmin/utils"; import { formatBytes, sum } from '@pureadmin/utils';
import { import { dependencies, devDependencies, engines, name, version } from '../package.json';
dependencies,
devDependencies,
engines,
name,
version
} from "../package.json";
/** 启动`node`进程时所在工作目录的绝对路径 */ /** 启动`node`进程时所在工作目录的绝对路径 */
const root: string = process.cwd(); const root: string = process.cwd();
@ -19,93 +13,91 @@ const root: string = process.cwd();
* @param dir `build` * @param dir `build`
* @param metaUrl `url``build``import.meta.url` * @param metaUrl `url``build``import.meta.url`
*/ */
const pathResolve = (dir = ".", metaUrl = import.meta.url) => { const pathResolve = (dir = '.', metaUrl = import.meta.url) => {
// 当前文件目录的绝对路径 // 当前文件目录的绝对路径
const currentFileDir = dirname(fileURLToPath(metaUrl)); const currentFileDir = dirname(fileURLToPath(metaUrl));
// build 目录的绝对路径 // build 目录的绝对路径
const buildDir = resolve(currentFileDir, "build"); const buildDir = resolve(currentFileDir, 'build');
// 解析的绝对路径 // 解析的绝对路径
const resolvedPath = resolve(currentFileDir, dir); const resolvedPath = resolve(currentFileDir, dir);
// 检查解析的绝对路径是否在 build 目录内 // 检查解析的绝对路径是否在 build 目录内
if (resolvedPath.startsWith(buildDir)) { if (resolvedPath.startsWith(buildDir)) {
// 在 build 目录内,返回当前文件路径 // 在 build 目录内,返回当前文件路径
return fileURLToPath(metaUrl); return fileURLToPath(metaUrl);
} }
// 不在 build 目录内,返回解析后的绝对路径 // 不在 build 目录内,返回解析后的绝对路径
return resolvedPath; return resolvedPath;
}; };
/** 设置别名 */ /** 设置别名 */
const alias: Record<string, string> = { const alias: Record<string, string> = {
"@": pathResolve("../src"), '@': pathResolve('../src'),
"@build": pathResolve() '@build': pathResolve(),
}; };
/** 平台的名称、版本、运行所需的`node`和`pnpm`版本、依赖、最后构建时间的类型提示 */ /** 平台的名称、版本、运行所需的`node`和`pnpm`版本、依赖、最后构建时间的类型提示 */
const __APP_INFO__ = { const __APP_INFO__ = {
pkg: { name, version, engines, dependencies, devDependencies }, pkg: { name, version, engines, dependencies, devDependencies },
lastBuildTime: dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss") lastBuildTime: dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss'),
}; };
/** 处理环境变量 */ /** 处理环境变量 */
const wrapperEnv = (envConf: Recordable): ViteEnv => { const wrapperEnv = (envConf: Recordable): ViteEnv => {
// 默认值 // 默认值
const ret: ViteEnv = { const ret: ViteEnv = {
VITE_PORT: 8848, VITE_PORT: 8848,
VITE_PUBLIC_PATH: "", VITE_PUBLIC_PATH: '',
VITE_ROUTER_HISTORY: "", VITE_ROUTER_HISTORY: '',
VITE_APP_URL: "", VITE_APP_URL: '',
VITE_CDN: false, VITE_CDN: false,
VITE_HIDE_HOME: "false", VITE_HIDE_HOME: 'false',
VITE_COMPRESSION: "none" VITE_COMPRESSION: 'none',
}; };
for (const envName of Object.keys(envConf)) { for (const envName of Object.keys(envConf)) {
let realName = envConf[envName].replace(/\\n/g, "\n"); let realName = envConf[envName].replace(/\\n/g, '\n');
realName = realName = realName === 'true' ? true : realName === 'false' ? false : realName;
realName === "true" ? true : realName === "false" ? false : realName;
if (envName === "VITE_PORT") { if (envName === 'VITE_PORT') {
realName = Number(realName); realName = Number(realName);
} }
ret[envName] = realName; ret[envName] = realName;
if (typeof realName === "string") { if (typeof realName === 'string') {
process.env[envName] = realName; process.env[envName] = realName;
} else if (typeof realName === "object") { } else if (typeof realName === 'object') {
process.env[envName] = JSON.stringify(realName); process.env[envName] = JSON.stringify(realName);
} }
} }
return ret; return ret;
}; };
const fileListTotal: number[] = []; const fileListTotal: number[] = [];
/** 获取指定文件夹中所有文件的总大小 */ /** 获取指定文件夹中所有文件的总大小 */
const getPackageSize = options => { const getPackageSize = options => {
const { folder = "dist", callback, format = true } = options; const { folder = 'dist', callback, format = true } = options;
readdir(folder, (err, files: string[]) => { readdir(folder, (err, files: string[]) => {
if (err) throw err; if (err) throw err;
let count = 0; let count = 0;
const checkEnd = () => { const checkEnd = () => {
++count == files.length && ++count == files.length && callback(format ? formatBytes(sum(fileListTotal)) : sum(fileListTotal));
callback(format ? formatBytes(sum(fileListTotal)) : sum(fileListTotal)); };
}; files.forEach((item: string) => {
files.forEach((item: string) => { stat(`${folder}/${item}`, async (err, stats) => {
stat(`${folder}/${item}`, async (err, stats) => { if (err) throw err;
if (err) throw err; if (stats.isFile()) {
if (stats.isFile()) { fileListTotal.push(stats.size);
fileListTotal.push(stats.size); checkEnd();
checkEnd(); } else if (stats.isDirectory()) {
} else if (stats.isDirectory()) { getPackageSize({
getPackageSize({ folder: `${folder}/${item}/`,
folder: `${folder}/${item}/`, callback: checkEnd,
callback: checkEnd });
}); }
} });
}); });
}); files.length === 0 && callback(0);
files.length === 0 && callback(0); });
});
}; };
export { root, pathResolve, alias, __APP_INFO__, wrapperEnv, getPackageSize }; export { root, pathResolve, alias, __APP_INFO__, wrapperEnv, getPackageSize };

View File

@ -5,7 +5,7 @@ FROM nginx
RUN rm /etc/nginx/conf.d/default.conf RUN rm /etc/nginx/conf.d/default.conf
# 将自定义的 Nginx 配置文件复制到容器中 # 将自定义的 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 RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

View File

@ -15,24 +15,13 @@ server {
} }
# 后端跨域请求 # 后端跨域请求
location ~/api/ { location ~/admin/ {
proxy_pass http://192.168.3.98:8200; #proxy_pass http://z-bunny.cn:7070;
} proxy_pass http://172.17.0.1:7070;
proxy_set_header Host $http_host;
# 配置WebSocket proxy_set_header X-Real-IP $remote_addr;
location ~/ws/ { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# WebSocket 代理配置 proxy_set_header X-Forwarded-Proto $scheme;
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;
} }
error_page 404 404.html; error_page 404 404.html;

BIN
images/img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@ -118,7 +118,7 @@ class PureHttp {
private httpInterceptorsResponse(): void { private httpInterceptorsResponse(): void {
const instance = PureHttp.axiosInstance; const instance = PureHttp.axiosInstance;
instance.interceptors.response.use( instance.interceptors.response.use(
(response: PureHttpResponse) => { async (response: PureHttpResponse) => {
const $config = response.config; const $config = response.config;
const data = response.data; const data = response.data;
@ -128,8 +128,8 @@ class PureHttp {
// 登录过期,和异常处理 // 登录过期,和异常处理
if (data.code === 208) { if (data.code === 208) {
message(data.message, { type: 'warning' }); message(data.message, { type: 'warning' });
router.push('/').then();
removeToken(); removeToken();
await router.push('/login');
} else if (data.code >= 201 && data.code < 300) { } else if (data.code >= 201 && data.code < 300) {
message(data.message, { type: 'warning' }); message(data.message, { type: 'warning' });
} else if (data.code > 300) { } else if (data.code > 300) {

View File

@ -40,7 +40,7 @@ export const fetchDeleteI18n = (data: any) => {
* --- * ---
*/ */
export const fetchGetI18nTypeList = () => { export const fetchGetI18nTypeList = () => {
return http.request<BaseResult<ResultTable>>('get', 'i18nType/getI18nTypeList'); return http.request<BaseResult<ResultTable>>('get', 'i18nType/noAuth/getI18nTypeList');
}; };
/** /**

View File

@ -3,7 +3,7 @@ import Motion from './utils/motion';
import { useNav } from '@/layout/hooks/useNav'; import { useNav } from '@/layout/hooks/useNav';
import { useLayout } from '@/layout/hooks/useLayout'; import { useLayout } from '@/layout/hooks/useLayout';
import { avatar, bg, illustration } from './utils/static'; import { avatar, bg, illustration } from './utils/static';
import { toRaw } from 'vue'; import { onMounted, toRaw } from 'vue';
import { useTranslationLang } from '@/layout/hooks/useTranslationLang'; import { useTranslationLang } from '@/layout/hooks/useTranslationLang';
import { useDataThemeChange } from '@/layout/hooks/useDataThemeChange'; import { useDataThemeChange } from '@/layout/hooks/useDataThemeChange';
@ -27,6 +27,10 @@ dataThemeChange(overallStyle.value);
const { title, getDropdownItemStyle, getDropdownItemClass } = useNav(); const { title, getDropdownItemStyle, getDropdownItemClass } = useNav();
const { locale, translation } = useTranslationLang(); const { locale, translation } = useTranslationLang();
const i18nTypeStore = userI18nTypeStore(); const i18nTypeStore = userI18nTypeStore();
onMounted(() => {
i18nTypeStore.getI18nTypeList();
});
</script> </script>
<template> <template>

View File

@ -130,7 +130,7 @@ onBeforeUnmount(() => {
</template> </template>
</el-input> </el-input>
<el-checkbox v-model="userStore.isRemembered"> <el-checkbox v-model="userStore.isRemembered">
<el-text size="small" type="primary">{{ userStore.readMeDay }}天免登录</el-text> <el-text size="small" type="primary">{{ userStore.readMeDay }}天免登录(邮箱验证码随便输入,后端校验验证码已注释) </el-text>
</el-checkbox> </el-checkbox>
</el-form-item> </el-form-item>
</Motion> </Motion>

View File

@ -26,6 +26,10 @@ export const columns: TableColumnList = [
{ label: $t('adminUser_sex'), prop: 'sex', slot: 'sex' }, { label: $t('adminUser_sex'), prop: 'sex', slot: 'sex' },
// 个人描述 // 个人描述
{ label: $t('adminUser_summary'), prop: 'summary', width: 460 }, { 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.updateTime'), prop: 'updateTime', sortable: true, width: 160 },
{ label: $t('table.createTime'), prop: 'createTime', 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' }, { label: $t('table.updateUser'), prop: 'updateUser', slot: 'updateUser', width: 90, fixed: 'right' },