Browse Source

前端框架vue-next-admin与官方仓库同步升级至2.3.0(大改版)

zuohuaijun 3 năm trước cách đây
mục cha
commit
9e539f80b6
100 tập tin đã thay đổi với 1787 bổ sung1235 xóa
  1. 2 2
      Admin.NET/Admin.NET.Core/SeedData/SysMenuSeedData.cs
  2. 21 0
      Web/CHANGELOG.md
  3. 5 2
      Web/README.md
  4. 211 97
      Web/package-lock.json
  5. 24 24
      Web/package.json
  6. 18 23
      Web/src/App.vue
  7. 41 68
      Web/src/components/editor/index.vue
  8. 0 60
      Web/src/components/editor/toolbar.ts
  9. 1 1
      Web/src/i18n/index.ts
  10. 3 1
      Web/src/i18n/lang/en.ts
  11. 3 1
      Web/src/i18n/lang/zh-cn.ts
  12. 3 1
      Web/src/i18n/lang/zh-tw.ts
  13. 1 1
      Web/src/i18n/pages/login/zh-cn.ts
  14. 15 13
      Web/src/layout/component/aside.vue
  15. 34 34
      Web/src/layout/component/columnsAside.vue
  16. 5 14
      Web/src/layout/component/header.vue
  17. 34 76
      Web/src/layout/component/main.vue
  18. 3 21
      Web/src/layout/footer/index.vue
  19. 4 4
      Web/src/layout/index.vue
  20. 4 3
      Web/src/layout/lockScreen/index.vue
  21. 1 1
      Web/src/layout/logo/index.vue
  22. 54 12
      Web/src/layout/main/classic.vue
  23. 57 20
      Web/src/layout/main/columns.vue
  24. 50 19
      Web/src/layout/main/defaults.vue
  25. 55 8
      Web/src/layout/main/transverse.vue
  26. 11 10
      Web/src/layout/navBars/breadcrumb/index.vue
  27. 35 30
      Web/src/layout/navBars/breadcrumb/search.vue
  28. 98 21
      Web/src/layout/navBars/breadcrumb/setings.vue
  29. 16 44
      Web/src/layout/navBars/breadcrumb/user.vue
  30. 5 4
      Web/src/layout/navBars/index.vue
  31. 61 26
      Web/src/layout/navBars/tagsView/tagsView.vue
  32. 22 9
      Web/src/layout/navMenu/horizontal.vue
  33. 12 1
      Web/src/layout/navMenu/subItem.vue
  34. 16 5
      Web/src/layout/navMenu/vertical.vue
  35. 71 43
      Web/src/layout/routerView/iframes.vue
  36. 68 16
      Web/src/layout/routerView/link.vue
  37. 44 10
      Web/src/layout/routerView/parent.vue
  38. 5 15
      Web/src/main.ts
  39. 2 0
      Web/src/router/backEnd.ts
  40. 2 0
      Web/src/router/frontEnd.ts
  41. 11 6
      Web/src/router/index.ts
  42. 1 1
      Web/src/router/route.ts
  43. 2 0
      Web/src/stores/interface/index.ts
  44. 1 2
      Web/src/stores/keepAliveNames.ts
  45. 4 0
      Web/src/stores/themeConfig.ts
  46. 61 17
      Web/src/theme/app.scss
  47. 53 0
      Web/src/theme/common/transition.scss
  48. 5 0
      Web/src/theme/dark.scss
  49. 30 5
      Web/src/theme/element.scss
  50. 1 0
      Web/src/theme/media/form.scss
  51. 48 37
      Web/src/theme/media/login.scss
  52. 2 2
      Web/src/theme/other.scss
  53. 22 0
      Web/src/types/mitt.ts
  54. 6 4
      Web/src/utils/loading.ts
  55. 9 0
      Web/src/utils/mitt.ts
  56. 2 2
      Web/src/utils/other.ts
  57. 18 23
      Web/src/views/chart/index.vue
  58. 23 37
      Web/src/views/error/401.vue
  59. 17 35
      Web/src/views/error/404.vue
  60. 16 14
      Web/src/views/fun/clipboard/index.vue
  61. 1 1
      Web/src/views/fun/countup/index.vue
  62. 6 5
      Web/src/views/fun/cropper/index.vue
  63. 3 21
      Web/src/views/fun/echartsMap/index.vue
  64. 1 1
      Web/src/views/fun/gridLayout/index.vue
  65. 1 1
      Web/src/views/fun/printJs/index.vue
  66. 6 5
      Web/src/views/fun/qrcode/index.vue
  67. 1 1
      Web/src/views/fun/splitpanes/index.vue
  68. 11 10
      Web/src/views/fun/tagsView/index.vue
  69. 10 8
      Web/src/views/fun/wangEditor/index.vue
  70. 4 4
      Web/src/views/home/notice/index.vue
  71. 1 1
      Web/src/views/limits/backEnd/page/index.vue
  72. 9 8
      Web/src/views/limits/frontEnd/btn/index.vue
  73. 1 1
      Web/src/views/limits/frontEnd/page/index.vue
  74. 10 4
      Web/src/views/login/component/account.vue
  75. 4 4
      Web/src/views/login/component/mobile.vue
  76. 21 11
      Web/src/views/login/component/scan.vue
  77. 181 120
      Web/src/views/login/index.vue
  78. 5 4
      Web/src/views/make/noticeBar/index.vue
  79. 5 4
      Web/src/views/make/selector/index.vue
  80. 1 1
      Web/src/views/make/svgDemo/index.vue
  81. 1 1
      Web/src/views/menu/menu1/menu11/index.vue
  82. 1 1
      Web/src/views/menu/menu1/menu12/menu121/index.vue
  83. 1 1
      Web/src/views/menu/menu1/menu12/menu122/index.vue
  84. 1 1
      Web/src/views/menu/menu1/menu13/index.vue
  85. 1 1
      Web/src/views/menu/menu2/index.vue
  86. 1 1
      Web/src/views/pages/awesome/index.vue
  87. 1 1
      Web/src/views/pages/drag/index.vue
  88. 8 6
      Web/src/views/pages/dynamicForm/index.vue
  89. 1 1
      Web/src/views/pages/element/index.vue
  90. 3 25
      Web/src/views/pages/filtering/details.vue
  91. 3 25
      Web/src/views/pages/filtering/details1.vue
  92. 1 1
      Web/src/views/pages/filtering/index.vue
  93. 1 1
      Web/src/views/pages/formAdapt/index.vue
  94. 5 4
      Web/src/views/pages/formI18n/index.vue
  95. 19 17
      Web/src/views/pages/formRules/index.vue
  96. 1 1
      Web/src/views/pages/iocnfont/index.vue
  97. 1 1
      Web/src/views/pages/lazyImg/index.vue
  98. 1 1
      Web/src/views/pages/listAdapt/index.vue
  99. 1 1
      Web/src/views/pages/preview/index.vue
  100. 4 4
      Web/src/views/pages/steps/index.vue

+ 2 - 2
Admin.NET/Admin.NET.Core/SeedData/SysMenuSeedData.cs

@@ -142,8 +142,8 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
             new SysMenu{ Id=252885263005640, Pid=252885263005600, Title="系统接口", Path="/develop/api", Name="sysApi", Component="layout/routerView/iframe", IsIframe=true, OutLink="https://localhost:44326/api/", Icon="ele-Help", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Order=103 },
 
             new SysMenu{ Id=252885263005700, Pid=0, Title="帮助文档", Path="/doc", Name="doc", Component="Layout", Redirect="/doc/furion", Icon="ele-Notebook", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Order=500 },
-            new SysMenu{ Id=252885263005710, Pid=252885263005700, Title="后台教程", Path="/doc/furion", Name="sysFurion", Component="IFrame", IsIframe=false, IsKeepAlive=false, OutLink="https://furion.baiqian.ltd/", Icon="ele-Promotion", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Order=100 },
-            new SysMenu{ Id=252885263005711, Pid=252885263005700, Title="前端教程", Path="/doc/element", Name="sysElement", Component="IFrame", IsIframe=false, IsKeepAlive=false, OutLink="https://element-plus.gitee.io/zh-CN/", Icon="ele-Position", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Order=101 },
+            new SysMenu{ Id=252885263005710, Pid=252885263005700, Title="后台教程", Path="/doc/furion", Name="sysFurion", Component="layout/routerView/link", IsIframe=false, IsKeepAlive=false, OutLink="https://furion.baiqian.ltd/", Icon="ele-Promotion", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Order=100 },
+            new SysMenu{ Id=252885263005711, Pid=252885263005700, Title="前端教程", Path="/doc/element", Name="sysElement", Component="layout/routerView/link", IsIframe=false, IsKeepAlive=false, OutLink="https://element-plus.gitee.io/zh-CN/", Icon="ele-Position", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), Order=101 },
         };
     }
 }

+ 21 - 0
Web/CHANGELOG.md

@@ -2,6 +2,27 @@
 
 🎉🎉🔥 `vue-next-admin` 基于 vue3.x 、Typescript、vite、Element plus 等,适配手机、平板、pc 的后台开源免费模板库(vue2.x 请切换 vue-prev-admin 分支)
 
+## 2.3.0
+
+`2022.11.16`
+
+- 🌟 更新 依赖更新最新版本
+- 🎉 新增 新版登录页
+- 🎉 新增 tagsview 鼠标中键 `关闭当前 tagsview`
+- 🎉 新增 `分栏菜单鼠标悬停预加载`。[分栏模式如何去掉鼠标悬浮父级菜单,分栏菜单自动加载的功能啊](https://gitee.com/lyt-top/vue-next-admin/issues/I5RUY7)。操作路径:`布局配置 -> 分栏设置`
+- 🐞 修复 [vue-i18n](https://vue-i18n.intlify.dev/api/general.html#createi18n) 报错,[!39 修复 i18n 兼容性问题](https://toscode.gitee.com/lyt-top/vue-next-admin/pulls/39/files),感谢[@随心](https://toscode.gitee.com/jiangqiang1996)
+- 🐞 修复 顶栏搜索功能点击蒙蔽弹窗不关闭
+- 🐞 修复 [!38 fix: bug refreshRouterViewKey 值为 null 导致路由缓存第一次无效](https://toscode.gitee.com/lyt-top/vue-next-admin/pulls/38/files),感谢[@P)](https://toscode.gitee.com/foxp8y)
+- 🐞 修复 `路由参数 -> 普通路由/动态路由` 国际化演示时,`tagsView` 和 `浏览器标题` 显示异常。[演示中:路由参数界面 -> 动态路由,国际化显示时面包屑、浏览器标题有 bug](https://gitee.com/lyt-top/vue-next-admin/issues/I5JRJG)
+- 🐞 修复 `路由参数 -> 普通路由/动态路由` 动态设置 `tagsViewName` 时,`tagsView 右键菜单刷新` 功能失效(也就是路由后面有参数时,query、params)。[普通或动态路由新建页面后点击 tagview 刷新无效](https://gitee.com/lyt-top/vue-next-admin/issues/I5K3YO),感谢[@dejavuuuuu](https://gitee.com/zc19951010)
+- 🐞 修复 [表单(el-form)中,字体图标偏移问题](https://gitee.com/lyt-top/vue-next-admin/issues/I5K1PM)
+- 🐞 修复 路由 `router.addRoute` 时,一直提示 `No match found for location with path 'xxx'`
+- 🎯 优化 全局 `getCurrentInstance` 替换成 [`provide/inject`](https://cn.vuejs.org/api/application.html#app-provide) 或通过 `ref` 处理
+- 🎯 优化 引入组件方式 `(import xxx from xxx)` 改成 `defineAsyncComponent(() => import(xxx))`
+- 🎯 优化 页面高度 100% 问题,重写布局配置 `界面设置 -> 固定 Header` 多余的 `el-scrollbar` 逻辑、重写各界面需 `计算属性 computed` 设置动态高度问题(改为 css `flex` 设置自适应高度,具体查看文档:[设置可视区高度 100%](https://lyt-top.gitee.io/vue-next-admin-doc-preview/config/otherIssues/#%E8%AE%BE%E7%BD%AE%E5%8F%AF%E8%A7%86%E5%8C%BA%E9%AB%98%E5%BA%A6-100)。[!31 修复页面样式无法通过百分比设置的问题](https://toscode.gitee.com/lyt-top/vue-next-admin/pulls/31),感谢[@LostDeer](https://toscode.gitee.com/lyt-top/vue-next-admin/pulls/31/files)。`(改动较大,删除多余代码)`
+- 🎯 优化 [wangeditor](https://www.wangeditor.com/) 组件,`@wangeditor/editor-for-vue`。可自行修改,组件位置:`/src/components/editor`。相关 Issues:[wangeditor 编辑器多个菜单不能回弹](https://gitee.com/lyt-top/vue-next-admin/issues/I5M5H7)
+- 🌈 重构 外链、内嵌 iframe 逻辑 + 美化,iframe 支持缓存
+
 ## 2.2.0
 
 `2022.07.10`

+ 5 - 2
Web/README.md

@@ -68,10 +68,13 @@ cnpm run dev
 cnpm run build
 ```
 
-#### 💯 学习交流加 QQ 群
+#### 📚 开发文档
 
-- 若加群了没同意(一般秒过),那就是群满了(500 人群),请换一个群试试。群会定期清理半年(6 个月)未发言的群友,资源有限,请谅解。建议勿加多群,可能会误伤!
 - 查看开发文档:<a href="https://lyt-top.gitee.io/vue-next-admin-doc-preview" target="_blank">vue-next-admin-doc</a>
+
+#### 💯 学习交流加 QQ 群
+
+- 若加群了没同意(一般秒过),那就是群满了(500 人群),请换一个群试试。群会定期清理半年(6 个月)未发言的群友,资源有限,请谅解。建议勿加多群,可能会误伤!微信群由于只有 `7天有效` 就不放这里了。
 - 群号码:
   1 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=RdUY97Vx0T0vZ_1OOu-X1yFNkWgDwbjC&jump_from=webapi">665452019</a>
   2 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=zVfy3gNy7pNWVK3kMduDzwU369PZg2fw&jump_from=webapi">766356862</a>

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 211 - 97
Web/package-lock.json


+ 24 - 24
Web/package.json

@@ -1,6 +1,6 @@
 {
 	"name": "vue-next-admin",
-	"version": "2.2.0",
+	"version": "2.3.0",
 	"description": "vue3 vite next admin template",
 	"author": "lyt_20201208",
 	"license": "MIT",
@@ -10,53 +10,53 @@
 		"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
 	},
 	"dependencies": {
-		"@element-plus/icons-vue": "^2.0.6",
-		"@wangeditor/editor": "^5.1.11",
+		"@element-plus/icons-vue": "^2.0.10",
+		"@wangeditor/editor-for-vue": "^5.1.11",
 		"@microsoft/signalr": "^6.0.10",
-		"axios": "^0.27.2",
+		"axios": "^1.1.3",
 		"countup.js": "^2.3.2",
 		"cropperjs": "^1.5.12",
-		"echarts": "^5.3.3",
+		"echarts": "^5.4.0",
 		"echarts-gl": "^2.0.9",
 		"echarts-wordcloud": "^2.0.0",
-		"element-plus": "^2.2.9",
+		"element-plus": "^2.2.21",
 		"js-cookie": "^3.0.1",
 		"jsplumb": "^2.15.6",
 		"mitt": "^3.0.0",
 		"nprogress": "^0.2.0",
-		"pinia": "^2.0.16",
+		"pinia": "^2.0.23",
 		"print-js": "^1.6.0",
 		"qrcodejs2-fixes": "^0.0.2",
 		"screenfull": "^6.0.2",
 		"sortablejs": "^1.15.0",
-		"splitpanes": "^3.1.1",
+		"splitpanes": "^3.1.5",
 		"vform3-builds": "^3.0.8",
-		"vue": "^3.2.37",
+		"vue": "^3.2.45",
 		"vue-clipboard3": "^2.0.0",
 		"vue-grid-layout": "^3.0.0-beta1",
 		"vue-i18n": "^9.2.2",
-		"vue-router": "^4.1.2",
+		"vue-router": "^4.1.6",
 		"vue-json-pretty": "^2.2.2",
 		"vue-signature-pad": "^3.0.2",
 		"vue3-tree-org": "^4.1.1"
 	},
 	"devDependencies": {
-		"@types/node": "^18.0.6",
+		"@types/node": "^18.11.9",
 		"@types/nprogress": "^0.2.0",
-		"@types/sortablejs": "^1.13.0",
-		"@typescript-eslint/eslint-plugin": "^5.30.7",
-		"@typescript-eslint/parser": "^5.30.7",
-		"@vitejs/plugin-vue": "^3.1.0",
-		"@vue/compiler-sfc": "^3.2.37",
-		"dotenv": "^16.0.1",
-		"eslint": "^8.20.0",
-		"eslint-plugin-vue": "^9.2.0",
+		"@types/sortablejs": "^1.15.0",
+		"@typescript-eslint/eslint-plugin": "^5.43.0",
+		"@typescript-eslint/parser": "^5.43.0",
+		"@vitejs/plugin-vue": "^3.2.0",
+		"@vue/compiler-sfc": "^3.2.45",
+		"dotenv": "^16.0.3",
+		"eslint": "^8.27.0",
+		"eslint-plugin-vue": "^9.7.0",
 		"prettier": "^2.7.1",
-		"sass": "^1.53.0",
-		"sass-loader": "^13.0.2",
-		"typescript": "^4.7.4",
-		"vite": "^3.0.4",
-		"vue-eslint-parser": "^9.0.3"
+		"sass": "^1.56.1",
+		"sass-loader": "^13.2.0",
+		"typescript": "^4.9.3",
+		"vite": "^3.2.4",
+		"vue-eslint-parser": "^9.1.0"
 	},
 	"browserslist": [
 		"> 1%",

+ 18 - 23
Web/src/App.vue

@@ -1,5 +1,5 @@
 <template>
-	<el-config-provider :size="getGlobalComponentSize" :locale="i18nLocale">
+	<el-config-provider :size="getGlobalComponentSize" :locale="getGlobalI18n">
 		<router-view v-show="themeConfig.lockScreenTime > 1" />
 		<LockScreen v-if="themeConfig.isLockScreen" />
 		<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime > 1" />
@@ -8,39 +8,39 @@
 </template>
 
 <script lang="ts">
-import { computed, ref, getCurrentInstance, onBeforeMount, onMounted, onUnmounted, nextTick, defineComponent, watch, reactive, toRefs } from 'vue';
+import { defineAsyncComponent, computed, ref, onBeforeMount, onMounted, onUnmounted, nextTick, defineComponent, watch } from 'vue';
 import { useRoute } from 'vue-router';
+import { useI18n } from 'vue-i18n';
 import { storeToRefs } from 'pinia';
 import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
 import { useThemeConfig } from '/@/stores/themeConfig';
 import other from '/@/utils/other';
 import { Local, Session } from '/@/utils/storage';
+import mittBus from '/@/utils/mitt';
 import setIntroduction from '/@/utils/setIconfont';
-import LockScreen from '/@/layout/lockScreen/index.vue';
-import Setings from '/@/layout/navBars/breadcrumb/setings.vue';
-import CloseFull from '/@/layout/navBars/breadcrumb/closeFull.vue';
 
 export default defineComponent({
 	name: 'app',
-	components: { LockScreen, Setings, CloseFull },
+	components: {
+		LockScreen: defineAsyncComponent(() => import('/@/layout/lockScreen/index.vue')),
+		Setings: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/setings.vue')),
+		CloseFull: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/closeFull.vue')),
+	},
 	setup() {
-		const { proxy } = <any>getCurrentInstance();
+		const { messages, locale } = useI18n();
 		const setingsRef = ref();
 		const route = useRoute();
 		const stores = useTagsViewRoutes();
 		const storesThemeConfig = useThemeConfig();
 		const { themeConfig } = storeToRefs(storesThemeConfig);
-		const state = reactive({
-			i18nLocale: null,
-		});
 		// 获取全局组件大小
 		const getGlobalComponentSize = computed(() => {
 			return other.globalComponentSize();
 		});
-		// 布局配置弹窗打开
-		const openSetingsDrawer = () => {
-			setingsRef.value.openDrawer();
-		};
+		// 获取全局 i18n
+		const getGlobalI18n = computed(() => {
+			return messages.value[locale.value];
+		});
 		// 设置初始化,防止刷新时恢复默认
 		onBeforeMount(() => {
 			// 设置批量第三方 icon 图标
@@ -52,12 +52,8 @@ export default defineComponent({
 		onMounted(() => {
 			nextTick(() => {
 				// 监听布局配置弹窗点击打开
-				proxy.mittBus.on('openSetingsDrawer', () => {
-					openSetingsDrawer();
-				});
-				// 设置 i18n,App.vue 中的 el-config-provider
-				proxy.mittBus.on('getI18nConfig', (locale: string) => {
-					(state.i18nLocale as string | null) = locale;
+				mittBus.on('openSetingsDrawer', () => {
+					setingsRef.value.openDrawer();
 				});
 				// 获取缓存中的布局配置
 				if (Local.get('themeConfig')) {
@@ -72,8 +68,7 @@ export default defineComponent({
 		});
 		// 页面销毁时,关闭监听布局配置/i18n监听
 		onUnmounted(() => {
-			proxy.mittBus.off('openSetingsDrawer', () => {});
-			proxy.mittBus.off('getI18nConfig', () => {});
+			mittBus.off('openSetingsDrawer', () => {});
 		});
 		// 监听路由的变化,设置网站标题
 		watch(
@@ -89,7 +84,7 @@ export default defineComponent({
 			themeConfig,
 			setingsRef,
 			getGlobalComponentSize,
-			...toRefs(state),
+			getGlobalI18n,
 		};
 	},
 });

+ 41 - 68
Web/src/components/editor/index.vue

@@ -1,40 +1,29 @@
 <template>
 	<div class="editor-container">
-		<div ref="editorToolbar"></div>
-		<div ref="editorContent" :style="{ height }"></div>
+		<Toolbar :editor="editorRef" :mode="mode" />
+		<Editor :mode="mode" :defaultConfig="editorConfig" :style="{ height }" v-model="editorVal" @onCreated="handleCreated" @onChange="handleChange" />
 	</div>
 </template>
 
 <script lang="ts">
-import { toRefs, reactive, onMounted, watch, defineComponent } from 'vue';
-import { createEditor, createToolbar, IEditorConfig, IToolbarConfig, IDomEditor } from '@wangeditor/editor';
+// https://www.wangeditor.com/v5/for-frame.html#vue3
 import '@wangeditor/editor/dist/css/style.css';
-import { toolbarKeys } from './toolbar';
-
-// 定义接口来定义对象的类型
-interface WangeditorState {
-	editorToolbar: HTMLDivElement | null;
-	editorContent: HTMLDivElement | null;
-	editor: any;
-}
+import { defineComponent, reactive, toRefs, shallowRef, watch, onBeforeUnmount } from 'vue';
+import { Toolbar, Editor } from '@wangeditor/editor-for-vue';
 
 export default defineComponent({
 	name: 'wngEditor',
+	components: { Toolbar, Editor },
 	props: {
-		// 节点 id
-		id: {
-			type: String,
-			default: () => 'wangeditor',
-		},
 		// 是否禁用
-		isDisable: {
+		disable: {
 			type: Boolean,
-			default: () => false,
+			default: () => true,
 		},
 		// 内容框默认 placeholder
 		placeholder: {
 			type: String,
-			default: () => '请输入内容',
+			default: () => '请输入内容...',
 		},
 		// 双向绑定:双向绑定值,字段名为固定,改了之后将不生效
 		// 参考:https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
@@ -52,62 +41,46 @@ export default defineComponent({
 		},
 	},
 	setup(props, { emit }) {
-		const state = reactive<WangeditorState>({
-			editorToolbar: null,
-			editor: null,
-			editorContent: null,
+		const editorRef = shallowRef();
+		const state = reactive({
+			editorConfig: {
+				placeholder: props.placeholder,
+			},
+			editorVal: props.modelValue,
 		});
-		// 富文本配置
-		const wangeditorConfig = () => {
-			const editorConfig: Partial<IEditorConfig> = { MENU_CONF: {} };
-			props.isDisable ? (editorConfig.readOnly = true) : (editorConfig.readOnly = false);
-			editorConfig.placeholder = props.placeholder;
-			editorConfig.onChange = (editor: IDomEditor) => {
-				// console.log('content', editor.children);
-				// console.log('html', editor.getHtml());
-				emit('update:modelValue', editor.getHtml());
-			};
-			(<any>editorConfig).MENU_CONF['uploadImage'] = {
-				base64LimitSize: 10 * 1024 * 1024,
-			};
-			return editorConfig;
+		// 编辑器回调函数
+		const handleCreated = (editor: any) => {
+			editorRef.value = editor;
 		};
-		//
-		const toolbarConfig = () => {
-			const toolbarConfig: Partial<IToolbarConfig> = {};
-			toolbarConfig.toolbarKeys = toolbarKeys;
-			return toolbarConfig;
+		// 编辑器内容改变时
+		const handleChange = (editor: any) => {
+			// console.log(editor.getText());
+			// console.log(editor.getHtml());
+			emit('update:modelValue', editor.getHtml());
 		};
-		// 初始化富文本
-		// https://www.wangeditor.com/
-		const initWangeditor = () => {
-			state.editor = createEditor({
-				html: props.modelValue,
-				selector: state.editorContent!,
-				config: wangeditorConfig(),
-				mode: props.mode,
-			});
-			createToolbar({
-				editor: state.editor,
-				selector: state.editorToolbar!,
-				mode: props.mode,
-				config: toolbarConfig(),
-			});
-		};
-		// 页面加载时
-		onMounted(() => {
-			initWangeditor();
-		});
-		// 监听双向绑定值的改变
+		// 监听是否禁用改变
 		// https://gitee.com/lyt-top/vue-next-admin/issues/I4LM7I
 		watch(
-			() => props.modelValue,
-			(value) => {
-				state.editor.clear();
-				state.editor.dangerouslyInsertHtml(value);
+			() => props.disable,
+			(bool) => {
+				const editor = editorRef.value;
+				if (editor == null) return;
+				bool ? editor.disable() : editor.enable();
+			},
+			{
+				deep: true,
 			}
 		);
+		// 页面销毁时
+		onBeforeUnmount(() => {
+			const editor = editorRef.value;
+			if (editor == null) return;
+			editor.destroy();
+		});
 		return {
+			editorRef,
+			handleCreated,
+			handleChange,
 			...toRefs(state),
 		};
 	},

+ 0 - 60
Web/src/components/editor/toolbar.ts

@@ -1,60 +0,0 @@
-/**
- * 工具栏配置
- */
-export const toolbarKeys = [
-	'headerSelect',
-	'blockquote',
-	'|',
-	'bold',
-	'underline',
-	'italic',
-	{
-		key: 'group-more-style',
-		title: '更多',
-		iconSvg:
-			'<svg viewBox="0 0 1024 1024"><path d="M204.8 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path><path d="M505.6 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path><path d="M806.4 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path></svg>',
-		menuKeys: ['through', 'code', 'sup', 'sub', 'clearStyle'],
-	},
-	'color',
-	'bgColor',
-	'|',
-	'fontSize',
-	'fontFamily',
-	'lineHeight',
-	'|',
-	'bulletedList',
-	'numberedList',
-	'todo',
-	{
-		key: 'group-justify',
-		title: '对齐',
-		iconSvg:
-			'<svg viewBox="0 0 1024 1024"><path d="M768 793.6v102.4H51.2v-102.4h716.8z m204.8-230.4v102.4H51.2v-102.4h921.6z m-204.8-230.4v102.4H51.2v-102.4h716.8zM972.8 102.4v102.4H51.2V102.4h921.6z"></path></svg>',
-		menuKeys: ['justifyLeft', 'justifyRight', 'justifyCenter', 'justifyJustify'],
-	},
-	{
-		key: 'group-indent',
-		title: '缩进',
-		iconSvg:
-			'<svg viewBox="0 0 1024 1024"><path d="M0 64h1024v128H0z m384 192h640v128H384z m0 192h640v128H384z m0 192h640v128H384zM0 832h1024v128H0z m0-128V320l256 192z"></path></svg>',
-		menuKeys: ['indent', 'delIndent'],
-	},
-	'|',
-	'emotion',
-	'insertLink',
-	{
-		key: 'group-image',
-		title: '图片',
-		iconSvg:
-			'<svg viewBox="0 0 1024 1024"><path d="M959.877 128l0.123 0.123v767.775l-0.123 0.122H64.102l-0.122-0.122V128.123l0.122-0.123h895.775zM960 64H64C28.795 64 0 92.795 0 128v768c0 35.205 28.795 64 64 64h896c35.205 0 64-28.795 64-64V128c0-35.205-28.795-64-64-64zM832 288.01c0 53.023-42.988 96.01-96.01 96.01s-96.01-42.987-96.01-96.01S682.967 192 735.99 192 832 234.988 832 288.01zM896 832H128V704l224.01-384 256 320h64l224.01-192z"></path></svg>',
-		menuKeys: ['uploadImage'],
-	},
-	'insertTable',
-	'codeBlock',
-	'divider',
-	'|',
-	'undo',
-	'redo',
-	'|',
-	'fullScreen',
-];

+ 1 - 1
Web/src/i18n/index.ts

@@ -57,12 +57,12 @@ const { themeConfig } = storeToRefs(stores);
 // 导出语言国际化
 // https://vue-i18n.intlify.dev/guide/essentials/fallback.html#explicit-fallback-with-one-locale
 export const i18n = createI18n({
+	legacy: false,
 	silentTranslationWarn: true,
 	missingWarn: false,
 	silentFallbackWarn: true,
 	fallbackWarn: false,
 	locale: themeConfig.value.globalI18n,
 	fallbackLocale: zhcnLocale.name,
-	legacy: false,
 	messages,
 });

+ 3 - 1
Web/src/i18n/lang/en.ts

@@ -71,7 +71,8 @@ export default {
 		personal: 'personal',
 		tools: 'tools',
 		layoutLinkView: 'LinkView',
-		layoutIfameView: 'IfameView',
+		layoutIframeViewOne: 'IframeViewOne',
+		layoutIframeViewTwo: 'IframeViewTwo',
 	},
 	staticRoutes: {
 		signIn: 'signIn',
@@ -139,6 +140,7 @@ export default {
 		twoColumnsMenuBar: 'Column menu background',
 		twoColumnsMenuBarColor: 'Default font color bar menu',
 		twoIsColumnsMenuBarColorGradual: 'Column gradient',
+		twoIsColumnsMenuHoverPreload: 'Column Menu Hover Preload',
 		threeTitle: 'Interface settings',
 		threeIsCollapse: 'Menu horizontal collapse',
 		threeIsUniqueOpened: 'Menu accordion',

+ 3 - 1
Web/src/i18n/lang/zh-cn.ts

@@ -71,7 +71,8 @@ export default {
 		personal: '个人中心',
 		tools: '工具类集合',
 		layoutLinkView: '外链',
-		layoutIfameView: '内嵌 iframe',
+		layoutIframeViewOne: '内嵌 iframe1',
+		layoutIframeViewTwo: '内嵌 iframe2',
 	},
 	staticRoutes: {
 		signIn: '登录',
@@ -139,6 +140,7 @@ export default {
 		twoColumnsMenuBar: '分栏菜单背景',
 		twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
 		twoIsColumnsMenuBarColorGradual: '分栏菜单背景渐变',
+		twoIsColumnsMenuHoverPreload: '分栏菜单鼠标悬停预加载',
 		threeTitle: '界面设置',
 		threeIsCollapse: '菜单水平折叠',
 		threeIsUniqueOpened: '菜单手风琴',

+ 3 - 1
Web/src/i18n/lang/zh-tw.ts

@@ -71,7 +71,8 @@ export default {
 		personal: '個人中心',
 		tools: '工具類集合',
 		layoutLinkView: '外鏈',
-		layoutIfameView: '内嵌 iframe',
+		layoutIframeViewOne: '内嵌 iframe1',
+		layoutIframeViewTwo: '内嵌 iframe2',
 	},
 	staticRoutes: {
 		signIn: '登入',
@@ -139,6 +140,7 @@ export default {
 		twoColumnsMenuBar: '分欄選單背景',
 		twoColumnsMenuBarColor: '分欄選單默認字體顏色',
 		twoIsColumnsMenuBarColorGradual: '分欄選單背景漸變',
+		twoIsColumnsMenuHoverPreload: '分欄選單滑鼠懸停預加載',
 		threeTitle: '介面設定',
 		threeIsCollapse: '選單水准折疊',
 		threeIsUniqueOpened: '選單手風琴',

+ 1 - 1
Web/src/i18n/pages/login/zh-cn.ts

@@ -19,7 +19,7 @@ export default {
 		placeholder2: '请输入验证码',
 		codeText: '获取验证码',
 		btnText: '登 录',
-		msgText: '温馨提示:建议使用谷歌、火狐、Edge浏览器,360请使用极速模式',
+		msgText: '* 温馨提示:建议使用谷歌、Microsoft Edge,版本 79.0.1072.62 及以上浏览器,360浏览器请使用极速模式',
 	},
 	scan: {
 		text: '打开手机扫一扫,快速登录/注册',

+ 15 - 13
Web/src/layout/component/aside.vue

@@ -10,20 +10,22 @@
 </template>
 
 <script lang="ts">
-import { toRefs, reactive, computed, watch, getCurrentInstance, onBeforeMount, defineComponent } from 'vue';
+import { defineAsyncComponent, toRefs, reactive, computed, watch, onBeforeMount, defineComponent, ref } from 'vue';
 import { storeToRefs } from 'pinia';
 import pinia from '/@/stores/index';
 import { useRoutesList } from '/@/stores/routesList';
 import { useThemeConfig } from '/@/stores/themeConfig';
 import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
-import Logo from '/@/layout/logo/index.vue';
-import Vertical from '/@/layout/navMenu/vertical.vue';
+import mittBus from '/@/utils/mitt';
 
 export default defineComponent({
 	name: 'layoutAside',
-	components: { Logo, Vertical },
+	components: {
+		Logo: defineAsyncComponent(() => import('/@/layout/logo/index.vue')),
+		Vertical: defineAsyncComponent(() => import('/@/layout/navMenu/vertical.vue')),
+	},
 	setup() {
-		const { proxy } = <any>getCurrentInstance();
+		const layoutAsideScrollbarRef = ref();
 		const stores = useRoutesList();
 		const storesThemeConfig = useThemeConfig();
 		const storesTagsViewRoutes = useTagsViewRoutes();
@@ -105,14 +107,13 @@ export default defineComponent({
 		const onAsideEnterLeave = (bool: Boolean) => {
 			let { layout } = themeConfig.value;
 			if (layout !== 'columns') return false;
-			if (!bool) proxy.mittBus.emit('restoreDefault');
+			if (!bool) mittBus.emit('restoreDefault');
 			stores.setColumnsMenuHover(bool);
 		};
 		// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
 		watch(themeConfig.value, (val) => {
 			if (val.isShowLogoChange !== val.isShowLogo) {
-				if (!proxy.$refs.layoutAsideScrollbarRef) return false;
-				proxy.$refs.layoutAsideScrollbarRef.update();
+				if (layoutAsideScrollbarRef.value) layoutAsideScrollbarRef.value.update();
 			}
 		});
 		// 监听vuex值的变化,动态赋值给菜单中
@@ -131,27 +132,28 @@ export default defineComponent({
 		onBeforeMount(() => {
 			initMenuFixed(document.body.clientWidth);
 			setFilterRoutes();
-			// 此界面不需要取消监听(proxy.mittBus.off('setSendColumnsChildren))
+			// 此界面不需要取消监听(mittBus.off('setSendColumnsChildren))
 			// 因为切换布局时有的监听需要使用,取消了监听,某些操作将不生效
-			proxy.mittBus.on('setSendColumnsChildren', (res: any) => {
+			mittBus.on('setSendColumnsChildren', (res: any) => {
 				state.menuList = res.children;
 			});
-			proxy.mittBus.on('setSendClassicChildren', (res: any) => {
+			mittBus.on('setSendClassicChildren', (res: any) => {
 				let { layout, isClassicSplitMenu } = themeConfig.value;
 				if (layout === 'classic' && isClassicSplitMenu) {
 					state.menuList = [];
 					state.menuList = res.children;
 				}
 			});
-			proxy.mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
+			mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
 				setFilterRoutes();
 			});
-			proxy.mittBus.on('layoutMobileResize', (res: any) => {
+			mittBus.on('layoutMobileResize', (res: any) => {
 				initMenuFixed(res.clientWidth);
 				closeLayoutAsideMobileMode();
 			});
 		});
 		return {
+			layoutAsideScrollbarRef,
 			setCollapseStyle,
 			setShowLogo,
 			isTagsViewCurrenFull,

+ 34 - 34
Web/src/layout/component/columnsAside.vue

@@ -21,22 +21,14 @@
 					<div :class="themeConfig.columnsAsideLayout" v-if="!v.meta.isLink || (v.meta.isLink && v.meta.isIframe)">
 						<SvgIcon :name="v.meta.icon" />
 						<div class="columns-vertical-title font12">
-							{{
-								$t(v.meta.title) && $t(v.meta.title).length >= 4
-									? $t(v.meta.title).substr(0, themeConfig.columnsAsideLayout === 'columns-vertical' ? 4 : 3)
-									: $t(v.meta.title)
-							}}
+							{{ $t(v.meta.title) && $t(v.meta.title).length >= 4 ? $t(v.meta.title).substr(0, themeConfig.columnsAsideLayout === 'columns-vertical' ? 4 : 3) : $t(v.meta.title) }}
 						</div>
 					</div>
 					<div :class="themeConfig.columnsAsideLayout" v-else>
 						<a :href="v.meta.isLink" target="_blank">
 							<SvgIcon :name="v.meta.icon" />
 							<div class="columns-vertical-title font12">
-								{{
-									$t(v.meta.title) && $t(v.meta.title).length >= 4
-										? $t(v.meta.title).substr(0, themeConfig.columnsAsideLayout === 'columns-vertical' ? 4 : 3)
-										: $t(v.meta.title)
-								}}
+								{{ $t(v.meta.title) && $t(v.meta.title).length >= 4 ? $t(v.meta.title).substr(0, themeConfig.columnsAsideLayout === 'columns-vertical' ? 4 : 3) : $t(v.meta.title) }}
 							</div>
 						</a>
 					</div>
@@ -48,12 +40,13 @@
 </template>
 
 <script lang="ts">
-import { reactive, toRefs, ref, onMounted, nextTick, getCurrentInstance, watch, onUnmounted, defineComponent } from 'vue';
+import { reactive, toRefs, ref, onMounted, nextTick, watch, onUnmounted, defineComponent } from 'vue';
 import { useRoute, useRouter, onBeforeRouteUpdate, RouteRecordRaw } from 'vue-router';
 import { storeToRefs } from 'pinia';
 import pinia from '/@/stores/index';
 import { useRoutesList } from '/@/stores/routesList';
 import { useThemeConfig } from '/@/stores/themeConfig';
+import mittBus from '/@/utils/mitt';
 
 import logoMini from '/@/assets/logo-mini.svg';
 
@@ -73,7 +66,6 @@ export default defineComponent({
 	setup() {
 		const columnsAsideOffsetTopRefs: any = ref([]);
 		const columnsAsideActiveRef = ref();
-		const { proxy } = <any>getCurrentInstance();
 		const stores = useRoutesList();
 		const storesThemeConfig = useThemeConfig();
 		const { routesList, isColumnsMenuHover, isColumnsNavHover } = storeToRefs(stores);
@@ -103,11 +95,12 @@ export default defineComponent({
 		};
 		// 鼠标移入时,显示当前的子级菜单
 		const onColumnsAsideMenuMouseenter = (v: RouteRecordRaw, k: number) => {
+			if (!themeConfig.value.isColumnsMenuHoverPreload) return false;
 			let { path } = v;
 			state.liOldPath = path;
 			state.liOldIndex = k;
 			state.liHoverIndex = k;
-			// proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(path));
+			mittBus.emit('setSendColumnsChildren', setSendChildren(path));
 			stores.setColumnsMenuHover(false);
 			stores.setColumnsNavHover(true);
 		};
@@ -116,7 +109,7 @@ export default defineComponent({
 			await stores.setColumnsNavHover(false);
 			// 添加延时器,防止拿到的 store.state.routesList 值不是最新的
 			setTimeout(() => {
-				if (!isColumnsMenuHover && !isColumnsNavHover) proxy.mittBus.emit('restoreDefault');
+				if (!isColumnsMenuHover && !isColumnsNavHover) mittBus.emit('restoreDefault');
 			}, 100);
 		};
 		// 设置高亮动态位置
@@ -131,7 +124,7 @@ export default defineComponent({
 			const resData: any = setSendChildren(route.path);
 			if (Object.keys(resData).length <= 0) return false;
 			onColumnsAsideDown(resData.item[0].k);
-			proxy.mittBus.emit('setSendColumnsChildren', resData);
+			mittBus.emit('setSendColumnsChildren', resData);
 		};
 		// 传送当前子级数据到菜单中
 		const setSendChildren = (path: string) => {
@@ -176,11 +169,11 @@ export default defineComponent({
 				val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound' ? (state.difference = 3) : (state.difference = 0);
 				if (!val.routesList.isColumnsMenuHover && !val.routesList.isColumnsNavHover) {
 					state.liHoverIndex = null;
-					// proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(route.path));
+					mittBus.emit('setSendColumnsChildren', setSendChildren(route.path));
 				} else {
 					state.liHoverIndex = state.liOldIndex;
 					if (!state.liOldPath) return false;
-					// proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(state.liOldPath));
+					mittBus.emit('setSendColumnsChildren', setSendChildren(state.liOldPath));
 				}
 			},
 			{
@@ -191,19 +184,19 @@ export default defineComponent({
 		onMounted(() => {
 			setFilterRoutes();
 			// 销毁变量,防止鼠标再次移入时,保留了上次的记录
-			proxy.mittBus.on('restoreDefault', () => {
+			mittBus.on('restoreDefault', () => {
 				state.liOldIndex = null;
 				state.liOldPath = null;
 			});
 		});
 		// 页面卸载时
 		onUnmounted(() => {
-			proxy.mittBus.off('restoreDefault', () => {});
+			mittBus.off('restoreDefault', () => {});
 		});
 		// 路由更新时
 		onBeforeRouteUpdate((to) => {
 			setColumnsMenuHighlight(to.path);
-			proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(to.path));
+			mittBus.emit('setSendColumnsChildren', setSendChildren(to.path));
 		});
 		return {
 			logoMini,
@@ -222,20 +215,33 @@ export default defineComponent({
 
 <style scoped lang="scss">
 .layout-columns-aside {
-	width: 80px;
+	width: 70px;
 	height: 100%;
 	background: var(--next-bg-columnsMenuBar);
 	ul {
 		position: relative;
+		.layout-columns-active {
+			color: var(--next-bg-columnsMenuBarColor) !important;
+			transition: 0.3s ease-in-out;
+		}
+		.layout-columns-hover {
+			color: var(--el-color-primary);
+			a {
+				color: var(--el-color-primary);
+			}
+		}
 		li {
 			color: var(--next-bg-columnsMenuBarColor);
 			width: 100%;
-			height: 55px;
+			height: 50px;
 			text-align: center;
 			display: flex;
 			cursor: pointer;
 			position: relative;
 			z-index: 1;
+			&:hover {
+				@extend .layout-columns-hover;
+			}
 			.columns-vertical {
 				margin: auto;
 				.columns-vertical-title {
@@ -263,23 +269,13 @@ export default defineComponent({
 				color: var(--next-bg-columnsMenuBarColor);
 			}
 		}
-		.layout-columns-active {
-			color: var(--next-bg-columnsMenuBarColor) !important;
-			transition: 0.3s ease-in-out;
-		}
-		.layout-columns-hover {
-			color: var(--el-color-primary);
-			a {
-				color: var(--el-color-primary);
-			}
-		}
 		.columns-round {
 			background: var(--el-color-primary);
 			color: var(--el-color-white);
 			position: absolute;
 			left: 50%;
 			top: 2px;
-			height: 50px;
+			height: 44px;
 			width: 65px;
 			transform: translateX(-50%);
 			z-index: 0;
@@ -296,7 +292,6 @@ export default defineComponent({
 	}
 }
 .layout-logo {
-	width: 80px;
 	height: 50px;
 	display: flex;
 	align-items: center;
@@ -305,5 +300,10 @@ export default defineComponent({
 	&-medium-img {
 		width: 35px;
 	}
+	&:hover {
+		img {
+			animation: logoAnimation 0.3s ease-in-out;
+		}
+	}
 }
 </style>

+ 5 - 14
Web/src/layout/component/header.vue

@@ -1,32 +1,23 @@
 <template>
-	<el-header class="layout-header" :height="setHeaderHeight" v-show="!isTagsViewCurrenFull">
+	<el-header class="layout-header" v-show="!isTagsViewCurrenFull">
 		<NavBarsIndex />
 	</el-header>
 </template>
 
 <script lang="ts">
-import { computed, defineComponent } from 'vue';
+import { defineAsyncComponent, defineComponent } from 'vue';
 import { storeToRefs } from 'pinia';
-import { useThemeConfig } from '/@/stores/themeConfig';
 import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
-import NavBarsIndex from '/@/layout/navBars/index.vue';
 
 export default defineComponent({
 	name: 'layoutHeader',
-	components: { NavBarsIndex },
+	components: {
+		NavBarsIndex: defineAsyncComponent(() => import('/@/layout/navBars/index.vue')),
+	},
 	setup() {
 		const storesTagsViewRoutes = useTagsViewRoutes();
-		const storesThemeConfig = useThemeConfig();
-		const { themeConfig } = storeToRefs(storesThemeConfig);
 		const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
-		// 设置 header 的高度
-		const setHeaderHeight = computed(() => {
-			let { isTagsview, layout } = themeConfig.value;
-			if (isTagsview && layout !== 'classic') return '84px';
-			else return '50px';
-		});
 		return {
-			setHeaderHeight,
 			isTagsViewCurrenFull,
 		};
 	},

+ 34 - 76
Web/src/layout/component/main.vue

@@ -1,100 +1,58 @@
 <template>
-	<el-main class="layout-main">
-		<el-scrollbar
-			ref="layoutScrollbarRef"
-			:class="{
-				'layout-scrollbar':
-					(!isClassicOrTransverse && !currentRouteMeta.isLink && !currentRouteMeta.isIframe) ||
-					(!isClassicOrTransverse && currentRouteMeta.isLink && !currentRouteMeta.isIframe),
-			}"
-		>
-			<LayoutParentView
-				:style="{
-					padding: !isClassicOrTransverse || (currentRouteMeta.isLink && currentRouteMeta.isIframe) ? '0' : '15px',
-					transition: 'padding 0.3s ease-in-out',
-				}"
-			/>
-			<Footer v-if="themeConfig.isFooter" />
+	<el-main class="layout-main" :style="isFixedHeader ? `height: calc(100% - ${setMainHeight})` : `minHeight: calc(100% - ${setMainHeight})`">
+		<el-scrollbar ref="layoutMainScrollbarRef" class="layout-main-scroll" wrap-class="layout-main-scroll" view-class="layout-main-scroll">
+			<LayoutParentView />
+			<LayoutFooter v-if="isFooter" />
 		</el-scrollbar>
+		<el-backtop target=".layout-backtop .el-scrollbar__wrap" />
 	</el-main>
 </template>
 
 <script lang="ts">
-import { defineComponent, toRefs, reactive, getCurrentInstance, watch, onMounted, computed } from 'vue';
+import { defineAsyncComponent, defineComponent, onMounted, computed, ref } from 'vue';
 import { useRoute } from 'vue-router';
 import { storeToRefs } from 'pinia';
+import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
 import { useThemeConfig } from '/@/stores/themeConfig';
 import { NextLoading } from '/@/utils/loading';
-import LayoutParentView from '/@/layout/routerView/parent.vue';
-import Footer from '/@/layout/footer/index.vue';
-
-// 定义接口来定义对象的类型
-interface MainState {
-	headerHeight: string | number;
-	currentRouteMeta: any;
-}
 
 export default defineComponent({
 	name: 'layoutMain',
-	components: { LayoutParentView, Footer },
+	components: {
+		LayoutParentView: defineAsyncComponent(() => import('/@/layout/routerView/parent.vue')),
+		LayoutFooter: defineAsyncComponent(() => import('/@/layout/footer/index.vue')),
+	},
 	setup() {
-		const { proxy } = <any>getCurrentInstance();
+		const layoutMainScrollbarRef = ref('');
+		const route = useRoute();
+		const storesTagsViewRoutes = useTagsViewRoutes();
 		const storesThemeConfig = useThemeConfig();
 		const { themeConfig } = storeToRefs(storesThemeConfig);
-		const route = useRoute();
-		const state = reactive<MainState>({
-			headerHeight: '',
-			currentRouteMeta: {},
+		const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
+		// 设置 footer 显示/隐藏
+		const isFooter = computed(() => {
+			return themeConfig.value.isFooter && !route.meta.isIframe;
 		});
-		// 判断布局
-		const isClassicOrTransverse = computed(() => {
-			const { layout } = themeConfig.value;
-			return layout === 'classic' || layout === 'transverse';
+		// 设置 header 固定
+		const isFixedHeader = computed(() => {
+			return themeConfig.value.isFixedHeader;
+		});
+		// 设置主内容区的高度
+		const setMainHeight = computed(() => {
+			if (isTagsViewCurrenFull.value) return '0px';
+			const { isTagsview, layout } = themeConfig.value;
+			if (isTagsview && layout !== 'classic') return '85px';
+			else return '51px';
 		});
-		// 设置 main 的高度
-		const initHeaderHeight = () => {
-			const bool = state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe;
-			let { isTagsview } = themeConfig.value;
-			if (isTagsview) return (state.headerHeight = bool ? `86px` : `115px`);
-			else return (state.headerHeight = `80px`);
-		};
-		// 初始化获取当前路由 meta,用于设置 iframes padding
-		const initGetMeta = () => {
-			state.currentRouteMeta = route.meta;
-		};
 		// 页面加载前
-		onMounted(async () => {
-			await initGetMeta();
-			initHeaderHeight();
-			NextLoading.done();
+		onMounted(() => {
+			NextLoading.done(600);
 		});
-		// 监听路由变化
-		watch(
-			() => route.path,
-			() => {
-				state.currentRouteMeta = route.meta;
-				const bool = state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe;
-				state.headerHeight = bool ? `86px` : `115px`;
-				proxy.$refs.layoutScrollbarRef.update();
-			}
-		);
-		// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
-		watch(
-			themeConfig,
-			(val) => {
-				state.currentRouteMeta = route.meta;
-				const bool = state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe;
-				state.headerHeight = val.isTagsview ? (bool ? `86px` : `115px`) : '51px';
-				proxy.$refs?.layoutScrollbarRef?.update();
-			},
-			{
-				deep: true,
-			}
-		);
 		return {
-			themeConfig,
-			isClassicOrTransverse,
-			...toRefs(state),
+			layoutMainScrollbarRef,
+			isFooter,
+			isFixedHeader,
+			setMainHeight,
 		};
 	},
 });

+ 3 - 21
Web/src/layout/footer/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="layout-footer mt15" v-show="isDelayFooter">
+	<div class="layout-footer pb15">
 		<div class="layout-footer-warp">
 			<div>Admin.NET</div>
 			<div class="mt5">Copyright © 2022 Dilon All rights reserved.</div>
@@ -8,28 +8,10 @@
 </template>
 
 <script lang="ts">
-import { toRefs, reactive, defineComponent } from 'vue';
-import { onBeforeRouteUpdate } from 'vue-router';
+import { defineComponent } from 'vue';
 
 export default defineComponent({
 	name: 'layoutFooter',
-	setup() {
-		const state = reactive({
-			isDelayFooter: true,
-		});
-		// 路由改变时,等主界面动画加载完毕再显示 footer
-		onBeforeRouteUpdate(() => {
-			setTimeout(() => {
-				state.isDelayFooter = false;
-				setTimeout(() => {
-					state.isDelayFooter = true;
-				}, 800);
-			}, 0);
-		});
-		return {
-			...toRefs(state),
-		};
-	},
 });
 </script>
 
@@ -41,7 +23,7 @@ export default defineComponent({
 		margin: auto;
 		color: var(--el-text-color-secondary);
 		text-align: center;
-		animation: error-num 1s ease-in-out;
+		animation: error-num 0.3s ease;
 	}
 }
 </style>

+ 4 - 4
Web/src/layout/index.vue

@@ -3,10 +3,11 @@
 </template>
 
 <script lang="ts">
-import { onBeforeMount, onUnmounted, getCurrentInstance, defineComponent, defineAsyncComponent } from 'vue';
+import { onBeforeMount, onUnmounted, defineComponent, defineAsyncComponent } from 'vue';
 import { storeToRefs } from 'pinia';
 import { useThemeConfig } from '/@/stores/themeConfig';
 import { Local } from '/@/utils/storage';
+import mittBus from '/@/utils/mitt';
 
 export default defineComponent({
 	name: 'layout',
@@ -17,7 +18,6 @@ export default defineComponent({
 		columns: defineAsyncComponent(() => import('/@/layout/main/columns.vue')),
 	},
 	setup() {
-		const { proxy } = <any>getCurrentInstance();
 		const storesThemeConfig = useThemeConfig();
 		const { themeConfig } = storeToRefs(storesThemeConfig);
 		// 窗口大小改变时(适配移动端)
@@ -26,12 +26,12 @@ export default defineComponent({
 			const clientWidth = document.body.clientWidth;
 			if (clientWidth < 1000) {
 				themeConfig.value.isCollapse = false;
-				proxy.mittBus.emit('layoutMobileResize', {
+				mittBus.emit('layoutMobileResize', {
 					layout: 'defaults',
 					clientWidth,
 				});
 			} else {
-				proxy.mittBus.emit('layoutMobileResize', {
+				mittBus.emit('layoutMobileResize', {
 					layout: Local.get('oldLayout') ? Local.get('oldLayout') : themeConfig.value.layout,
 					clientWidth,
 				});

+ 4 - 3
Web/src/layout/lockScreen/index.vue

@@ -60,7 +60,7 @@
 </template>
 
 <script lang="ts">
-import { nextTick, onMounted, reactive, toRefs, ref, onUnmounted, getCurrentInstance, defineComponent } from 'vue';
+import { nextTick, onMounted, reactive, toRefs, ref, onUnmounted, defineComponent } from 'vue';
 import { formatDate } from '/@/utils/formatTime';
 import { Local } from '/@/utils/storage';
 import { storeToRefs } from 'pinia';
@@ -88,7 +88,7 @@ interface LockScreenState {
 export default defineComponent({
 	name: 'layoutLockScreen',
 	setup() {
-		const { proxy } = <any>getCurrentInstance();
+		const layoutLockScreenDateRef = ref();
 		const layoutLockScreenInputRef = ref();
 		const storesThemeConfig = useThemeConfig();
 		const { themeConfig } = storeToRefs(storesThemeConfig);
@@ -150,7 +150,7 @@ export default defineComponent({
 		// 获取要拖拽的初始元素
 		const initGetElement = () => {
 			nextTick(() => {
-				state.querySelectorEl = proxy.$refs.layoutLockScreenDateRef;
+				state.querySelectorEl = layoutLockScreenDateRef.value;
 			});
 		};
 		// 时间初始化
@@ -204,6 +204,7 @@ export default defineComponent({
 			window.clearInterval(state.isShowLockScreenIntervalTime);
 		});
 		return {
+			layoutLockScreenDateRef,
 			layoutLockScreenInputRef,
 			onDown,
 			onMove,

+ 1 - 1
Web/src/layout/logo/index.vue

@@ -48,7 +48,7 @@ export default defineComponent({
 
 <style scoped lang="scss">
 .layout-logo {
-	width: 200px;
+	width: 220px;
 	height: 50px;
 	display: flex;
 	align-items: center;

+ 54 - 12
Web/src/layout/main/classic.vue

@@ -1,34 +1,76 @@
 <template>
 	<el-container class="layout-container flex-center">
-		<Header />
+		<LayoutHeader />
 		<el-container class="layout-mian-height-50">
-			<Aside />
+			<LayoutAside />
 			<div class="flex-center layout-backtop">
-				<TagsView v-if="themeConfig.isTagsview" />
-				<Main />
+				<LayoutTagsView v-if="isTagsview" />
+				<LayoutMain ref="layoutMainRef" />
 			</div>
 		</el-container>
-		<el-backtop target=".layout-backtop .el-main .el-scrollbar__wrap"></el-backtop>
 	</el-container>
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue';
+import { defineAsyncComponent, defineComponent, computed, ref, watch, nextTick, onMounted } from 'vue';
+import { useRoute } from 'vue-router';
 import { storeToRefs } from 'pinia';
 import { useThemeConfig } from '/@/stores/themeConfig';
-import Aside from '/@/layout/component/aside.vue';
-import Header from '/@/layout/component/header.vue';
-import Main from '/@/layout/component/main.vue';
-import TagsView from '/@/layout/navBars/tagsView/tagsView.vue';
 
 export default defineComponent({
 	name: 'layoutClassic',
-	components: { Aside, Header, Main, TagsView },
+	components: {
+		LayoutAside: defineAsyncComponent(() => import('/@/layout/component/aside.vue')),
+		LayoutHeader: defineAsyncComponent(() => import('/@/layout/component/header.vue')),
+		LayoutMain: defineAsyncComponent(() => import('/@/layout/component/main.vue')),
+		LayoutTagsView: defineAsyncComponent(() => import('/@/layout/navBars/tagsView/tagsView.vue')),
+	},
 	setup() {
+		const layoutMainRef = ref<any>('');
+		const route = useRoute();
 		const storesThemeConfig = useThemeConfig();
 		const { themeConfig } = storeToRefs(storesThemeConfig);
-		return {
+		// 判断是否显示 tasgview
+		const isTagsview = computed(() => {
+			return themeConfig.value.isTagsview;
+		});
+		// 重置滚动条高度,更新子级 scrollbar
+		const updateScrollbar = () => {
+			layoutMainRef.value.layoutMainScrollbarRef.update();
+		};
+		// 重置滚动条高度,由于组件是异步引入的
+		const initScrollBarHeight = () => {
+			nextTick(() => {
+				setTimeout(() => {
+					updateScrollbar();
+					layoutMainRef.value.layoutMainScrollbarRef.wrap$.scrollTop = 0;
+				}, 500);
+			});
+		};
+		// 监听路由的变化,切换界面时,滚动条置顶
+		watch(
+			() => route.path,
+			() => {
+				initScrollBarHeight();
+			}
+		);
+		// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
+		watch(
 			themeConfig,
+			() => {
+				updateScrollbar();
+			},
+			{
+				deep: true,
+			}
+		);
+		// 页面加载时
+		onMounted(() => {
+			initScrollBarHeight();
+		});
+		return {
+			layoutMainRef,
+			isTagsview,
 		};
 	},
 });

+ 57 - 20
Web/src/layout/main/columns.vue

@@ -1,40 +1,77 @@
 <template>
 	<el-container class="layout-container">
 		<ColumnsAside />
-		<div class="layout-columns-warp">
-			<Aside />
-			<el-container class="flex-center layout-backtop" :class="{ 'layout-backtop': !isFixedHeader }">
-				<Header v-if="isFixedHeader" />
-				<el-scrollbar :class="{ 'layout-backtop': isFixedHeader }">
-					<Header v-if="!isFixedHeader" />
-					<Main />
-				</el-scrollbar>
-			</el-container>
-		</div>
-		<el-backtop target=".layout-backtop .el-scrollbar__wrap"></el-backtop>
+		<el-container class="layout-columns-warp layout-container-view h100">
+			<LayoutAside />
+			<el-scrollbar ref="layoutScrollbarRef" class="layout-backtop">
+				<LayoutHeader />
+				<LayoutMain ref="layoutMainRef" />
+			</el-scrollbar>
+		</el-container>
 	</el-container>
 </template>
 
 <script lang="ts">
-import { computed, defineComponent } from 'vue';
+import { defineAsyncComponent, watch, defineComponent, onMounted, nextTick, ref } from 'vue';
+import { useRoute } from 'vue-router';
 import { storeToRefs } from 'pinia';
 import { useThemeConfig } from '/@/stores/themeConfig';
-import Aside from '/@/layout/component/aside.vue';
-import Header from '/@/layout/component/header.vue';
-import Main from '/@/layout/component/main.vue';
-import ColumnsAside from '/@/layout/component/columnsAside.vue';
 
 export default defineComponent({
 	name: 'layoutColumns',
-	components: { Aside, Header, Main, ColumnsAside },
+	components: {
+		LayoutAside: defineAsyncComponent(() => import('/@/layout/component/aside.vue')),
+		LayoutHeader: defineAsyncComponent(() => import('/@/layout/component/header.vue')),
+		LayoutMain: defineAsyncComponent(() => import('/@/layout/component/main.vue')),
+		ColumnsAside: defineAsyncComponent(() => import('/@/layout/component/columnsAside.vue')),
+	},
 	setup() {
+		const layoutScrollbarRef = ref<any>('');
+		const layoutMainRef = ref<any>('');
+		const route = useRoute();
 		const storesThemeConfig = useThemeConfig();
 		const { themeConfig } = storeToRefs(storesThemeConfig);
-		const isFixedHeader = computed(() => {
-			return themeConfig.value.isFixedHeader;
+		// 重置滚动条高度
+		const updateScrollbar = () => {
+			// 更新父级 scrollbar
+			layoutScrollbarRef.value.update();
+			// 更新子级 scrollbar
+			layoutMainRef.value.layoutMainScrollbarRef.update();
+		};
+		// 重置滚动条高度,由于组件是异步引入的
+		const initScrollBarHeight = () => {
+			nextTick(() => {
+				setTimeout(() => {
+					updateScrollbar();
+					layoutScrollbarRef.value.wrap$.scrollTop = 0;
+					layoutMainRef.value.layoutMainScrollbarRef.wrap$.scrollTop = 0;
+				}, 500);
+			});
+		};
+		// 监听路由的变化,切换界面时,滚动条置顶
+		watch(
+			() => route.path,
+			() => {
+				initScrollBarHeight();
+			}
+		);
+		// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
+		watch(
+			themeConfig,
+			() => {
+				updateScrollbar();
+			},
+			{
+				deep: true,
+			}
+		);
+		// 页面加载时
+		onMounted(() => {
+			initScrollBarHeight();
 		});
 		return {
-			isFixedHeader,
+			layoutScrollbarRef,
+			layoutMainRef,
 		};
 	},
 });

+ 50 - 19
Web/src/layout/main/defaults.vue

@@ -1,46 +1,77 @@
 <template>
 	<el-container class="layout-container">
-		<Aside />
-		<el-container class="flex-center" :class="{ 'layout-backtop': !isFixedHeader }">
-			<Header v-if="isFixedHeader" />
-			<el-scrollbar ref="layoutDefaultsScrollbarRef" :class="{ 'layout-backtop': isFixedHeader }">
-				<Header v-if="!isFixedHeader" />
-				<Main />
+		<LayoutAside />
+		<el-container class="layout-container-view h100">
+			<el-scrollbar ref="layoutScrollbarRef" class="layout-backtop">
+				<LayoutHeader />
+				<LayoutMain ref="layoutMainRef" />
 			</el-scrollbar>
 		</el-container>
-		<el-backtop target=".layout-backtop .el-scrollbar__wrap"></el-backtop>
 	</el-container>
 </template>
 
 <script lang="ts">
-import { computed, getCurrentInstance, watch, defineComponent } from 'vue';
+import { defineAsyncComponent, watch, defineComponent, onMounted, nextTick, ref } from 'vue';
 import { useRoute } from 'vue-router';
 import { storeToRefs } from 'pinia';
 import { useThemeConfig } from '/@/stores/themeConfig';
-import Aside from '/@/layout/component/aside.vue';
-import Header from '/@/layout/component/header.vue';
-import Main from '/@/layout/component/main.vue';
+import { NextLoading } from '/@/utils/loading';
 
 export default defineComponent({
 	name: 'layoutDefaults',
-	components: { Aside, Header, Main },
+	components: {
+		LayoutAside: defineAsyncComponent(() => import('/@/layout/component/aside.vue')),
+		LayoutHeader: defineAsyncComponent(() => import('/@/layout/component/header.vue')),
+		LayoutMain: defineAsyncComponent(() => import('/@/layout/component/main.vue')),
+	},
 	setup() {
-		const { proxy } = <any>getCurrentInstance();
+		const layoutScrollbarRef = ref<any>('');
+		const layoutMainRef = ref<any>('');
 		const route = useRoute();
 		const storesThemeConfig = useThemeConfig();
 		const { themeConfig } = storeToRefs(storesThemeConfig);
-		const isFixedHeader = computed(() => {
-			return themeConfig.value.isFixedHeader;
-		});
-		// 监听路由的变化
+		// 重置滚动条高度
+		const updateScrollbar = () => {
+			// 更新父级 scrollbar
+			layoutScrollbarRef.value.update();
+			// 更新子级 scrollbar
+			layoutMainRef.value.layoutMainScrollbarRef.update();
+		};
+		// 重置滚动条高度,由于组件是异步引入的
+		const initScrollBarHeight = () => {
+			nextTick(() => {
+				setTimeout(() => {
+					updateScrollbar();
+					layoutScrollbarRef.value.wrap$.scrollTop = 0;
+					layoutMainRef.value.layoutMainScrollbarRef.wrap$.scrollTop = 0;
+				}, 500);
+			});
+		};
+		// 监听路由的变化,切换界面时,滚动条置顶
 		watch(
 			() => route.path,
 			() => {
-				proxy.$refs.layoutDefaultsScrollbarRef.wrap$.scrollTop = 0;
+				initScrollBarHeight();
 			}
 		);
+		// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
+		watch(
+			themeConfig,
+			() => {
+				updateScrollbar();
+			},
+			{
+				deep: true,
+			}
+		);
+		// 页面加载时
+		onMounted(() => {
+			initScrollBarHeight();
+			NextLoading.done(600);
+		});
 		return {
-			isFixedHeader,
+			layoutScrollbarRef,
+			layoutMainRef,
 		};
 	},
 });

+ 55 - 8
Web/src/layout/main/transverse.vue

@@ -1,17 +1,64 @@
 <template>
 	<el-container class="layout-container flex-center layout-backtop">
-		<Header />
-		<Main />
-		<el-backtop target=".layout-backtop .el-main .el-scrollbar__wrap"></el-backtop>
+		<LayoutHeader />
+		<LayoutMain ref="layoutMainRef" />
 	</el-container>
 </template>
 
 <script lang="ts">
-import Header from '/@/layout/component/header.vue';
-import Main from '/@/layout/component/main.vue';
+import { defineAsyncComponent, defineComponent, ref, watch, nextTick, onMounted } from 'vue';
+import { useRoute } from 'vue-router';
+import { storeToRefs } from 'pinia';
+import { useThemeConfig } from '/@/stores/themeConfig';
 
-export default {
+export default defineComponent({
 	name: 'layoutTransverse',
-	components: { Header, Main },
-};
+	components: {
+		LayoutHeader: defineAsyncComponent(() => import('/@/layout/component/header.vue')),
+		LayoutMain: defineAsyncComponent(() => import('/@/layout/component/main.vue')),
+	},
+	setup() {
+		const layoutMainRef = ref<any>('');
+		const storesThemeConfig = useThemeConfig();
+		const { themeConfig } = storeToRefs(storesThemeConfig);
+		const route = useRoute();
+		// 重置滚动条高度,更新子级 scrollbar
+		const updateScrollbar = () => {
+			layoutMainRef.value.layoutMainScrollbarRef.update();
+		};
+		// 重置滚动条高度,由于组件是异步引入的
+		const initScrollBarHeight = () => {
+			nextTick(() => {
+				setTimeout(() => {
+					updateScrollbar();
+					layoutMainRef.value.layoutMainScrollbarRef.wrap$.scrollTop = 0;
+				}, 500);
+			});
+		};
+		// 监听路由的变化,切换界面时,滚动条置顶
+		watch(
+			() => route.path,
+			() => {
+				initScrollBarHeight();
+			}
+		);
+		// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
+		watch(
+			themeConfig,
+			() => {
+				updateScrollbar();
+			},
+			{
+				deep: true,
+			}
+		);
+		// 页面加载时
+		onMounted(() => {
+			initScrollBarHeight();
+		});
+		return {
+			layoutMainRef,
+		};
+	},
+});
 </script>

+ 11 - 10
Web/src/layout/navBars/breadcrumb/index.vue

@@ -8,15 +8,12 @@
 </template>
 
 <script lang="ts">
-import { computed, reactive, toRefs, onMounted, onUnmounted, getCurrentInstance, defineComponent } from 'vue';
+import { defineAsyncComponent, computed, reactive, toRefs, onMounted, onUnmounted, defineComponent } from 'vue';
 import { useRoute } from 'vue-router';
 import { storeToRefs } from 'pinia';
 import { useRoutesList } from '/@/stores/routesList';
 import { useThemeConfig } from '/@/stores/themeConfig';
-import Breadcrumb from '/@/layout/navBars/breadcrumb/breadcrumb.vue';
-import User from '/@/layout/navBars/breadcrumb/user.vue';
-import Logo from '/@/layout/logo/index.vue';
-import Horizontal from '/@/layout/navMenu/horizontal.vue';
+import mittBus from '/@/utils/mitt';
 
 // 定义接口来定义对象的类型
 interface IndexState {
@@ -25,9 +22,13 @@ interface IndexState {
 
 export default defineComponent({
 	name: 'layoutBreadcrumbIndex',
-	components: { Breadcrumb, User, Logo, Horizontal },
+	components: {
+		Breadcrumb: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/breadcrumb.vue')),
+		User: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/user.vue')),
+		Logo: defineAsyncComponent(() => import('/@/layout/logo/index.vue')),
+		Horizontal: defineAsyncComponent(() => import('/@/layout/navMenu/horizontal.vue')),
+	},
 	setup() {
-		const { proxy } = <any>getCurrentInstance();
 		const stores = useRoutesList();
 		const storesThemeConfig = useThemeConfig();
 		const { themeConfig } = storeToRefs(storesThemeConfig);
@@ -52,7 +53,7 @@ export default defineComponent({
 			if (layout === 'classic' && isClassicSplitMenu) {
 				state.menuList = delClassicChildren(filterRoutesFun(routesList.value));
 				const resData = setSendClassicChildren(route.path);
-				proxy.mittBus.emit('setSendClassicChildren', resData);
+				mittBus.emit('setSendClassicChildren', resData);
 			} else {
 				state.menuList = filterRoutesFun(routesList.value);
 			}
@@ -91,13 +92,13 @@ export default defineComponent({
 		// 页面加载时
 		onMounted(() => {
 			setFilterRoutes();
-			proxy.mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
+			mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
 				setFilterRoutes();
 			});
 		});
 		// 页面卸载时
 		onUnmounted(() => {
-			proxy.mittBus.off('getBreadcrumbIndexSetFilterRoutes', () => {});
+			mittBus.off('getBreadcrumbIndexSetFilterRoutes', () => {});
 		});
 		return {
 			setIsShowLogo,

+ 35 - 30
Web/src/layout/navBars/breadcrumb/search.vue

@@ -1,26 +1,28 @@
 <template>
 	<div class="layout-search-dialog">
-		<el-dialog v-model="isShowSearch" width="300px" destroy-on-close :modal="false" fullscreen :show-close="false">
-			<el-autocomplete
-				v-model="menuQuery"
-				:fetch-suggestions="menuSearch"
-				:placeholder="$t('message.user.searchPlaceholder')"
-				ref="layoutMenuAutocompleteRef"
-				@select="onHandleSelect"
-				@blur="onSearchBlur"
-			>
-				<template #prefix>
-					<el-icon class="el-input__icon">
-						<ele-Search />
-					</el-icon>
-				</template>
-				<template #default="{ item }">
-					<div>
-						<SvgIcon :name="item.meta.icon" class="mr5" />
-						{{ $t(item.meta.title) }}
-					</div>
-				</template>
-			</el-autocomplete>
+		<el-dialog v-model="isShowSearch" destroy-on-close :show-close="false">
+			<template #footer>
+				<el-autocomplete
+					v-model="menuQuery"
+					:fetch-suggestions="menuSearch"
+					:placeholder="$t('message.user.searchPlaceholder')"
+					ref="layoutMenuAutocompleteRef"
+					@select="onHandleSelect"
+					:fit-input-width="true"
+				>
+					<template #prefix>
+						<el-icon class="el-input__icon">
+							<ele-Search />
+						</el-icon>
+					</template>
+					<template #default="{ item }">
+						<div>
+							<SvgIcon :name="item.meta.icon" class="mr5" />
+							{{ $t(item.meta.title) }}
+						</div>
+					</template>
+				</el-autocomplete>
+			</template>
 		</el-dialog>
 	</div>
 </template>
@@ -103,17 +105,12 @@ export default defineComponent({
 			else router.push(path);
 			closeSearch();
 		};
-		// input 失去焦点时
-		const onSearchBlur = () => {
-			closeSearch();
-		};
 		return {
 			layoutMenuAutocompleteRef,
 			openSearch,
 			closeSearch,
 			menuSearch,
 			onHandleSelect,
-			onSearchBlur,
 			...toRefs(state),
 		};
 	},
@@ -122,15 +119,23 @@ export default defineComponent({
 
 <style scoped lang="scss">
 .layout-search-dialog {
+	position: relative;
 	:deep(.el-dialog) {
-		box-shadow: unset !important;
-		border-radius: 0 !important;
-		background: rgba(0, 0, 0, 0.5);
+		.el-dialog__header,
+		.el-dialog__body {
+			display: none;
+		}
+		.el-dialog__footer {
+			position: absolute;
+			left: 50%;
+			transform: translateX(-50%);
+			top: -53vh;
+		}
 	}
 	:deep(.el-autocomplete) {
 		width: 560px;
 		position: absolute;
-		top: 100px;
+		top: 150px;
 		left: 50%;
 		transform: translateX(-50%);
 	}

+ 98 - 21
Web/src/layout/navBars/breadcrumb/setings.vue

@@ -1,6 +1,13 @@
 <template>
 	<div class="layout-breadcrumb-seting">
-		<el-drawer :title="$t('message.layout.configTitle')" v-model="getThemeConfig.isDrawer" direction="rtl" destroy-on-close size="260px" @close="onDrawerClose">
+		<el-drawer
+			:title="$t('message.layout.configTitle')"
+			v-model="getThemeConfig.isDrawer"
+			direction="rtl"
+			destroy-on-close
+			size="260px"
+			@close="onDrawerClose"
+		>
 			<el-scrollbar class="layout-breadcrumb-seting-bar">
 				<!-- 全局主题 -->
 				<el-divider content-position="left">{{ $t('message.layout.oneTitle') }}</el-divider>
@@ -60,25 +67,53 @@
 				</div>
 
 				<!-- 分栏设置 -->
-				<el-divider content-position="left" :style="{ opacity: getThemeConfig.layout !== 'columns' ? 0.5 : 1 }">{{ $t('message.layout.twoColumnsTitle') }}</el-divider>
+				<el-divider content-position="left" :style="{ opacity: getThemeConfig.layout !== 'columns' ? 0.5 : 1 }">{{
+					$t('message.layout.twoColumnsTitle')
+				}}</el-divider>
 				<div class="layout-breadcrumb-seting-bar-flex" :style="{ opacity: getThemeConfig.layout !== 'columns' ? 0.5 : 1 }">
 					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoColumnsMenuBar') }}</div>
 					<div class="layout-breadcrumb-seting-bar-flex-value">
-						<el-color-picker v-model="getThemeConfig.columnsMenuBar" size="default" @change="onBgColorPickerChange('columnsMenuBar')" :disabled="getThemeConfig.layout !== 'columns'">
+						<el-color-picker
+							v-model="getThemeConfig.columnsMenuBar"
+							size="default"
+							@change="onBgColorPickerChange('columnsMenuBar')"
+							:disabled="getThemeConfig.layout !== 'columns'"
+						>
 						</el-color-picker>
 					</div>
 				</div>
 				<div class="layout-breadcrumb-seting-bar-flex" :style="{ opacity: getThemeConfig.layout !== 'columns' ? 0.5 : 1 }">
 					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoColumnsMenuBarColor') }}</div>
 					<div class="layout-breadcrumb-seting-bar-flex-value">
-						<el-color-picker v-model="getThemeConfig.columnsMenuBarColor" size="default" @change="onBgColorPickerChange('columnsMenuBarColor')" :disabled="getThemeConfig.layout !== 'columns'">
+						<el-color-picker
+							v-model="getThemeConfig.columnsMenuBarColor"
+							size="default"
+							@change="onBgColorPickerChange('columnsMenuBarColor')"
+							:disabled="getThemeConfig.layout !== 'columns'"
+						>
 						</el-color-picker>
 					</div>
 				</div>
 				<div class="layout-breadcrumb-seting-bar-flex mt14" :style="{ opacity: getThemeConfig.layout !== 'columns' ? 0.5 : 1 }">
 					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoIsColumnsMenuBarColorGradual') }}</div>
 					<div class="layout-breadcrumb-seting-bar-flex-value">
-						<el-switch v-model="getThemeConfig.isColumnsMenuBarColorGradual" size="small" @change="onColumnsMenuBarGradualChange" :disabled="getThemeConfig.layout !== 'columns'"></el-switch>
+						<el-switch
+							v-model="getThemeConfig.isColumnsMenuBarColorGradual"
+							size="small"
+							@change="onColumnsMenuBarGradualChange"
+							:disabled="getThemeConfig.layout !== 'columns'"
+						></el-switch>
+					</div>
+				</div>
+				<div class="layout-breadcrumb-seting-bar-flex mt14" :style="{ opacity: getThemeConfig.layout !== 'columns' ? 0.5 : 1 }">
+					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoIsColumnsMenuHoverPreload') }}</div>
+					<div class="layout-breadcrumb-seting-bar-flex-value">
+						<el-switch
+							v-model="getThemeConfig.isColumnsMenuHoverPreload"
+							size="small"
+							@change="onColumnsMenuHoverPreloadChange"
+							:disabled="getThemeConfig.layout !== 'columns'"
+						></el-switch>
 					</div>
 				</div>
 
@@ -87,13 +122,23 @@
 				<div class="layout-breadcrumb-seting-bar-flex" :style="{ opacity: getThemeConfig.layout === 'transverse' ? 0.5 : 1 }">
 					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsCollapse') }}</div>
 					<div class="layout-breadcrumb-seting-bar-flex-value">
-						<el-switch v-model="getThemeConfig.isCollapse" :disabled="getThemeConfig.layout === 'transverse'" size="small" @change="onThemeConfigChange"></el-switch>
+						<el-switch
+							v-model="getThemeConfig.isCollapse"
+							:disabled="getThemeConfig.layout === 'transverse'"
+							size="small"
+							@change="onThemeConfigChange"
+						></el-switch>
 					</div>
 				</div>
 				<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: getThemeConfig.layout === 'transverse' ? 0.5 : 1 }">
 					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsUniqueOpened') }}</div>
 					<div class="layout-breadcrumb-seting-bar-flex-value">
-						<el-switch v-model="getThemeConfig.isUniqueOpened" :disabled="getThemeConfig.layout === 'transverse'" size="small" @change="setLocalThemeConfig"></el-switch>
+						<el-switch
+							v-model="getThemeConfig.isUniqueOpened"
+							:disabled="getThemeConfig.layout === 'transverse'"
+							size="small"
+							@change="setLocalThemeConfig"
+						></el-switch>
 					</div>
 				</div>
 				<div class="layout-breadcrumb-seting-bar-flex mt15">
@@ -105,7 +150,13 @@
 				<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: getThemeConfig.layout !== 'classic' ? 0.5 : 1 }">
 					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsClassicSplitMenu') }}</div>
 					<div class="layout-breadcrumb-seting-bar-flex-value">
-						<el-switch v-model="getThemeConfig.isClassicSplitMenu" :disabled="getThemeConfig.layout !== 'classic'" size="small" @change="onClassicSplitMenuChange"> </el-switch>
+						<el-switch
+							v-model="getThemeConfig.isClassicSplitMenu"
+							:disabled="getThemeConfig.layout !== 'classic'"
+							size="small"
+							@change="onClassicSplitMenuChange"
+						>
+						</el-switch>
 					</div>
 				</div>
 				<div class="layout-breadcrumb-seting-bar-flex mt15">
@@ -117,7 +168,16 @@
 				<div class="layout-breadcrumb-seting-bar-flex mt11">
 					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeLockScreenTime') }}</div>
 					<div class="layout-breadcrumb-seting-bar-flex-value">
-						<el-input-number v-model="getThemeConfig.lockScreenTime" controls-position="right" :min="1" :max="9999" @change="setLocalThemeConfig" size="default" style="width: 90px"> </el-input-number>
+						<el-input-number
+							v-model="getThemeConfig.lockScreenTime"
+							controls-position="right"
+							:min="1"
+							:max="9999"
+							@change="setLocalThemeConfig"
+							size="default"
+							style="width: 90px"
+						>
+						</el-input-number>
 					</div>
 				</div>
 
@@ -129,7 +189,10 @@
 						<el-switch v-model="getThemeConfig.isShowLogo" size="small" @change="onIsShowLogoChange"></el-switch>
 					</div>
 				</div>
-				<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: getThemeConfig.layout === 'classic' || getThemeConfig.layout === 'transverse' ? 0.5 : 1 }">
+				<div
+					class="layout-breadcrumb-seting-bar-flex mt15"
+					:style="{ opacity: getThemeConfig.layout === 'classic' || getThemeConfig.layout === 'transverse' ? 0.5 : 1 }"
+				>
 					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsBreadcrumb') }}</div>
 					<div class="layout-breadcrumb-seting-bar-flex-value">
 						<el-switch
@@ -167,7 +230,12 @@
 				<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: isMobile ? 0.5 : 1 }">
 					<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsSortableTagsView') }}</div>
 					<div class="layout-breadcrumb-seting-bar-flex-value">
-						<el-switch v-model="getThemeConfig.isSortableTagsView" :disabled="isMobile ? true : false" size="small" @change="onSortableTagsViewChange"></el-switch>
+						<el-switch
+							v-model="getThemeConfig.isSortableTagsView"
+							:disabled="isMobile ? true : false"
+							size="small"
+							@change="onSortableTagsViewChange"
+						></el-switch>
 					</div>
 				</div>
 				<div class="layout-breadcrumb-seting-bar-flex mt15">
@@ -351,8 +419,9 @@
 </template>
 
 <script lang="ts">
-import { nextTick, onUnmounted, onMounted, getCurrentInstance, defineComponent, computed, reactive, toRefs } from 'vue';
+import { nextTick, onUnmounted, onMounted, defineComponent, computed, reactive, toRefs } from 'vue';
 import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
 import { storeToRefs } from 'pinia';
 import { useThemeConfig } from '/@/stores/themeConfig';
 import { getLightColor, getDarkColor } from '/@/utils/theme';
@@ -361,11 +430,12 @@ import { Local } from '/@/utils/storage';
 import Watermark from '/@/utils/wartermark';
 import commonFunction from '/@/utils/commonFunction';
 import other from '/@/utils/other';
+import mittBus from '/@/utils/mitt';
 
 export default defineComponent({
 	name: 'layoutBreadcrumbSeting',
 	setup() {
-		const { proxy } = <any>getCurrentInstance();
+		const { locale } = useI18n();
 		const storesThemeConfig = useThemeConfig();
 		const { themeConfig } = storeToRefs(storesThemeConfig);
 		const { copyText } = commonFunction();
@@ -422,6 +492,11 @@ export default defineComponent({
 				setLocalThemeConfig();
 			}, 200);
 		};
+		// 2、分栏设置 ->
+		const onColumnsMenuHoverPreloadChange = () => {
+			mittBus.emit('setHoverPreload');
+			setLocalThemeConfig();
+		};
 		// 3、界面设置 --> 菜单水平折叠
 		const onThemeConfigChange = () => {
 			setDispatchThemeConfig();
@@ -435,7 +510,7 @@ export default defineComponent({
 		const onClassicSplitMenuChange = () => {
 			getThemeConfig.value.isBreadcrumb = false;
 			setLocalThemeConfig();
-			proxy.mittBus.emit('getBreadcrumbIndexSetFilterRoutes');
+			mittBus.emit('getBreadcrumbIndexSetFilterRoutes');
 		};
 		// 4、界面显示 --> 侧边栏 Logo
 		const onIsShowLogoChange = () => {
@@ -451,12 +526,12 @@ export default defineComponent({
 		};
 		// 4、界面显示 --> 开启 TagsView 拖拽
 		const onSortableTagsViewChange = () => {
-			proxy.mittBus.emit('openOrCloseSortable');
+			mittBus.emit('openOrCloseSortable');
 			setLocalThemeConfig();
 		};
 		// 4、界面显示 --> 开启 TagsView 共用
 		const onShareTagsViewChange = () => {
-			proxy.mittBus.emit('openShareTagsView');
+			mittBus.emit('openShareTagsView');
 			setLocalThemeConfig();
 		};
 		// 4、界面显示 --> 灰色模式/色弱模式
@@ -466,7 +541,8 @@ export default defineComponent({
 			} else {
 				if (getThemeConfig.value.isInvert) getThemeConfig.value.isGrayscale = false;
 			}
-			const cssAttr = attr === 'grayscale' ? `grayscale(${getThemeConfig.value.isGrayscale ? 1 : 0})` : `invert(${getThemeConfig.value.isInvert ? '80%' : '0%'})`;
+			const cssAttr =
+				attr === 'grayscale' ? `grayscale(${getThemeConfig.value.isGrayscale ? 1 : 0})` : `invert(${getThemeConfig.value.isInvert ? '80%' : '0%'})`;
 			const appEle: any = document.body;
 			appEle.setAttribute('style', `filter: ${cssAttr}`);
 			setLocalThemeConfig();
@@ -507,7 +583,7 @@ export default defineComponent({
 			onBgColorPickerChange('columnsMenuBar');
 			onBgColorPickerChange('columnsMenuBarColor');
 		};
-		// 关闭弹窗时,初始化变量。变量用于处理 proxy.$refs.layoutScrollbarRef.update()
+		// 关闭弹窗时,初始化变量。变量用于处理 layoutScrollbarRef.value.update() 更新滚动条高度
 		const onDrawerClose = () => {
 			getThemeConfig.value.isFixedHeaderChange = false;
 			getThemeConfig.value.isShowLogoChange = false;
@@ -560,7 +636,7 @@ export default defineComponent({
 				if (!Local.get('frequency')) initLayoutChangeFun();
 				Local.set('frequency', 1);
 				// 监听窗口大小改变,非默认布局,设置成默认布局(适配移动端)
-				proxy.mittBus.on('layoutMobileResize', (res: any) => {
+				mittBus.on('layoutMobileResize', (res: any) => {
 					getThemeConfig.value.layout = res.layout;
 					getThemeConfig.value.isDrawer = false;
 					initLayoutChangeFun();
@@ -578,14 +654,14 @@ export default defineComponent({
 					// 开启水印
 					onWartermarkChange();
 					// 语言国际化
-					if (Local.get('themeConfig')) proxy.$i18n.locale = Local.get('themeConfig').globalI18n;
+					if (Local.get('themeConfig')) locale.value = Local.get('themeConfig').globalI18n;
 					// 初始化菜单样式等
 					initSetStyle();
 				}, 100);
 			});
 		});
 		onUnmounted(() => {
-			proxy.mittBus.off('layoutMobileResize', () => {});
+			mittBus.off('layoutMobileResize', () => {});
 		});
 		return {
 			openDrawer,
@@ -594,6 +670,7 @@ export default defineComponent({
 			onTopBarGradualChange,
 			onMenuBarGradualChange,
 			onColumnsMenuBarGradualChange,
+			onColumnsMenuHoverPreloadChange,
 			onThemeConfigChange,
 			onIsFixedHeaderChange,
 			onIsShowLogoChange,

+ 16 - 44
Web/src/layout/navBars/breadcrumb/user.vue

@@ -74,7 +74,7 @@
 </template>
 
 <script lang="ts">
-import { ref, getCurrentInstance, computed, reactive, toRefs, onMounted, defineComponent } from 'vue';
+import { ref, computed, reactive, toRefs, onMounted, defineComponent } from 'vue';
 import { useRouter } from 'vue-router';
 import { ElMessageBox, ElMessage, ElNotification } from 'element-plus';
 import screenfull from 'screenfull';
@@ -83,6 +83,7 @@ import { storeToRefs } from 'pinia';
 import { useUserInfo } from '/@/stores/userInfo';
 import { useThemeConfig } from '/@/stores/themeConfig';
 import other from '/@/utils/other';
+import mittBus from '/@/utils/mitt';
 import { Local } from '/@/utils/storage';
 import UserNews from '/@/layout/navBars/breadcrumb/userNews.vue';
 import Search from '/@/layout/navBars/breadcrumb/search.vue';
@@ -91,12 +92,13 @@ import OnlineUser from '/@/views/system/onlineUser/index.vue';
 import { clearAccessTokens, getAPI } from '/@/utils/axios-utils';
 import { SysAuthApi, SysNoticeApi } from '/@/api-services/api';
 
+import { signalR } from '/@/views/system/onlineUser/signalR';
+
 export default defineComponent({
 	name: 'layoutBreadcrumbUser',
 	components: { UserNews, Search, OnlineUser },
 	setup() {
-		const { t } = useI18n();
-		const { proxy } = <any>getCurrentInstance();
+		const { locale, t } = useI18n();
 		const router = useRouter();
 		const stores = useUserInfo();
 		const storesThemeConfig = useThemeConfig();
@@ -133,7 +135,7 @@ export default defineComponent({
 		};
 		// 布局配置 icon 点击时
 		const onLayoutSetingClick = () => {
-			proxy.mittBus.emit('openSetingsDrawer');
+			mittBus.emit('openSetingsDrawer');
 		};
 		// 下拉菜单点击时
 		const onHandleCommandClick = (path: string) => {
@@ -183,7 +185,7 @@ export default defineComponent({
 			Local.remove('themeConfig');
 			themeConfig.value.globalComponentSize = size;
 			Local.set('themeConfig', themeConfig.value);
-			initComponentSize();
+			initI18nOrSize('globalComponentSize', 'disabledSize');
 			window.location.reload();
 		};
 		// 语言切换
@@ -191,57 +193,27 @@ export default defineComponent({
 			Local.remove('themeConfig');
 			themeConfig.value.globalI18n = lang;
 			Local.set('themeConfig', themeConfig.value);
-			proxy.$i18n.locale = lang;
-			initI18n();
+			locale.value = lang;
 			other.useTitle();
+			initI18nOrSize('globalI18n', 'disabledI18n');
 		};
-		// 设置 element plus 组件的国际化
-		const setI18nConfig = (locale: string) => {
-			proxy.mittBus.emit('getI18nConfig', proxy.i18n.global.messages.value[locale]);
-		};
-		// 初始化言语国际化
-		const initI18n = () => {
-			switch (Local.get('themeConfig').globalI18n) {
-				case 'zh-cn':
-					state.disabledI18n = 'zh-cn';
-					setI18nConfig('zh-cn');
-					break;
-				case 'en':
-					state.disabledI18n = 'en';
-					setI18nConfig('en');
-					break;
-				case 'zh-tw':
-					state.disabledI18n = 'zh-tw';
-					setI18nConfig('zh-tw');
-					break;
-			}
-		};
-		// 初始化全局组件大小
-		const initComponentSize = () => {
-			switch (Local.get('themeConfig').globalComponentSize) {
-				case 'large':
-					state.disabledSize = 'large';
-					break;
-				case 'default':
-					state.disabledSize = 'default';
-					break;
-				case 'small':
-					state.disabledSize = 'small';
-					break;
-			}
+		// 初始化组件大小/i18n
+		const initI18nOrSize = (value: string, attr: string) => {
+			(<any>state)[attr] = Local.get('themeConfig')[value];
 		};
 		// 页面加载时
 		onMounted(async () => {
 			if (Local.get('themeConfig')) {
-				initI18n();
-				initComponentSize();
+				initI18nOrSize('globalComponentSize', 'disabledSize');
+				initI18nOrSize('globalI18n', 'disabledI18n');
 			}
+
 			// 加载未读的站内信
 			var res = await getAPI(SysNoticeApi).sysNoticeUnReadListGet();
 			state.noticeList = res.data.result ?? [];
 
 			// 接收站内信
-			proxy.signalR.on('PublicNotice', reciveNotice);
+			signalR.on('PublicNotice', reciveNotice);
 		});
 		const reciveNotice = (msg: any) => {
 			state.noticeList.unshift(msg);

+ 5 - 4
Web/src/layout/navBars/index.vue

@@ -6,15 +6,16 @@
 </template>
 
 <script lang="ts">
-import { computed, defineComponent } from 'vue';
+import { defineAsyncComponent, computed, defineComponent } from 'vue';
 import { storeToRefs } from 'pinia';
 import { useThemeConfig } from '/@/stores/themeConfig';
-import BreadcrumbIndex from '/@/layout/navBars/breadcrumb/index.vue';
-import TagsView from '/@/layout/navBars/tagsView/tagsView.vue';
 
 export default defineComponent({
 	name: 'layoutNavBars',
-	components: { BreadcrumbIndex, TagsView },
+	components: {
+		BreadcrumbIndex: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/index.vue')),
+		TagsView: defineAsyncComponent(() => import('/@/layout/navBars/tagsView/tagsView.vue')),
+	},
 	setup() {
 		const storesThemeConfig = useThemeConfig();
 		const { themeConfig } = storeToRefs(storesThemeConfig);

+ 61 - 26
Web/src/layout/navBars/tagsView/tagsView.vue

@@ -9,6 +9,7 @@
 					:data-url="v.url"
 					:class="{ 'is-active': isActive(v) }"
 					@contextmenu.prevent="onContextmenu(v, $event)"
+					@mousedown="onMousedownMenu(v, $event)"
 					@click="onTagsClick(v, k)"
 					:ref="
 						(el) => {
@@ -47,6 +48,7 @@
 
 <script lang="ts">
 import {
+	defineAsyncComponent,
 	toRefs,
 	reactive,
 	onMounted,
@@ -56,7 +58,6 @@ import {
 	onBeforeUpdate,
 	onBeforeMount,
 	onUnmounted,
-	getCurrentInstance,
 	watch,
 	defineComponent,
 } from 'vue';
@@ -71,7 +72,7 @@ import { useKeepALiveNames } from '/@/stores/keepAliveNames';
 import { Session } from '/@/utils/storage';
 import { isObjectValueEqual } from '/@/utils/arrayOperation';
 import other from '/@/utils/other';
-import Contextmenu from '/@/layout/navBars/tagsView/contextmenu.vue';
+import mittBus from '/@/utils/mitt';
 
 // 定义接口来定义对象的类型
 interface TagsViewState {
@@ -104,9 +105,10 @@ interface CurrentContextmenu {
 
 export default defineComponent({
 	name: 'layoutTagsView',
-	components: { Contextmenu },
+	components: {
+		Contextmenu: defineAsyncComponent(() => import('/@/layout/navBars/tagsView/contextmenu.vue')),
+	},
 	setup() {
-		const { proxy } = <any>getCurrentInstance();
 		const tagsRefs = ref<any[]>([]);
 		const scrollbarRef = ref();
 		const contextmenuRef = ref();
@@ -237,13 +239,21 @@ export default defineComponent({
 					// 动态路由(xxx/:id/:name"):参数不同,开启多个 tagsview
 					if (!getThemeConfig.value.isShareTagsView) await solveAddTagsView(path, to);
 					else await singleAddTagsView(path, to);
-					if (state.tagsViewList.some((v: any) => v.path === to.meta.isDynamicPath)) return false;
+					if (state.tagsViewList.some((v: any) => v.path === to.meta.isDynamicPath)) {
+						// 防止首次进入界面时(登录进入) tagsViewList 不存浏览器中
+						addBrowserSetSession(state.tagsViewList);
+						return false;
+					}
 					item = state.tagsViewRoutesList.find((v: any) => v.path === to.meta.isDynamicPath);
 				} else {
 					// 普通路由:参数不同,开启多个 tagsview
 					if (!getThemeConfig.value.isShareTagsView) await solveAddTagsView(path, to);
 					else await singleAddTagsView(path, to);
-					if (state.tagsViewList.some((v: any) => v.path === path)) return false;
+					if (state.tagsViewList.some((v: any) => v.path === path)) {
+						// 防止首次进入界面时(登录进入) tagsViewList 不存浏览器中
+						addBrowserSetSession(state.tagsViewList);
+						return false;
+					}
 					item = state.tagsViewRoutesList.find((v: any) => v.path === path);
 				}
 				if (!item) return false;
@@ -258,12 +268,20 @@ export default defineComponent({
 		};
 		// 2、刷新当前 tagsView:
 		const refreshCurrentTagsView = async (fullPath: string) => {
-			const item = state.tagsViewList.find((v: any) => (getThemeConfig.value.isShareTagsView ? v.path === fullPath : v.url === fullPath));
-			if (item != null) {
-				await storesKeepALiveNames.delCachedView(item);
-				proxy.mittBus.emit('onTagsViewRefreshRouterView', fullPath);
-				if (item.meta.isKeepAlive) storesKeepALiveNames.addCachedView(item);
-			}
+			const decodeURIPath = decodeURI(fullPath);
+			let item: any = {};
+			state.tagsViewList.forEach((v: any) => {
+				v.transUrl = transUrlParams(v);
+				if (v.transUrl) {
+					if (v.transUrl === transUrlParams(v)) item = v;
+				} else {
+					if (v.path === decodeURIPath) item = v;
+				}
+			});
+			if (!item) return false;
+			await storesKeepALiveNames.delCachedView(item);
+			mittBus.emit('onTagsViewRefreshRouterView', fullPath);
+			if (item.meta.isKeepAlive) storesKeepALiveNames.addCachedView(item);
 		};
 		// 3、关闭当前 tagsView:如果是设置了固定的(isAffix),不可以关闭
 		const closeCurrentTagsView = (path: string) => {
@@ -396,11 +414,31 @@ export default defineComponent({
 			state.dropdown.y = clientY;
 			contextmenuRef.value.openContextmenu(v);
 		};
+		// 鼠标按下时,判断是鼠标中键就关闭当前 tasgview
+		const onMousedownMenu = (v: any, e: any) => {
+			if (!v.meta.isAffix && e.which === 2) {
+				const item = Object.assign({}, { contextMenuClickId: 1, ...v });
+				onCurrentContextmenuClick(item);
+			}
+		};
 		// 当前的 tagsView 项点击时
 		const onTagsClick = (v: any, k: number) => {
 			state.tagsRefsIndex = k;
 			router.push(v);
 		};
+		// 处理 url,地址栏链接有参数时,tagsview 右键菜单刷新功能失效问题
+		// https://gitee.com/lyt-top/vue-next-admin/issues/I5K3YO
+		const transUrlParams = (v: any) => {
+			let params = v.query && Object.keys(v.query).length > 0 ? v.query : v.params;
+			if (!params) return '';
+			let path = '';
+			for (let [key, value] of Object.entries(params)) {
+				if (v.meta.isDynamic) path += `/${value}`;
+				else path += `&${key}=${value}`;
+			}
+			// 判断是否是动态路由(xxx/:id/:name")isDynamic
+			return v.meta.isDynamic ? `${v.path.split(':')[0]}${path.replace(/^\//, '')}` : `${v.path}${path.replace(/^&/, '?')}`;
+		};
 		// 处理 tagsView 高亮(多标签详情时使用,单标签详情未使用)
 		const setTagsViewHighlight = (v: any) => {
 			let params = v.query && Object.keys(v.query).length > 0 ? v.query : v.params;
@@ -412,13 +450,9 @@ export default defineComponent({
 			// 判断是否是动态路由(xxx/:id/:name")
 			return `${v.meta.isDynamic ? v.meta.isDynamicPath : v.path}-${path}`;
 		};
-		// 更新滚动条显示
-		const updateScrollbar = () => {
-			proxy.$refs.scrollbarRef.update();
-		};
 		// 鼠标滚轮滚动
 		const onHandleScroll = (e: any) => {
-			proxy.$refs.scrollbarRef.$refs.wrap$.scrollLeft += e.wheelDelta / 4;
+			scrollbarRef.value.$refs.wrap$.scrollLeft += e.wheelDelta / 4;
 		};
 		// tagsView 横向滚动
 		const tagsViewmoveToCurrentTag = () => {
@@ -435,7 +469,7 @@ export default defineComponent({
 				// 最后 li
 				let liLast: any = tagsRefs.value[tagsRefs.value.length - 1];
 				// 当前滚动条的值
-				let scrollRefs = proxy.$refs.scrollbarRef.$refs.wrap$;
+				let scrollRefs = scrollbarRef.value.$refs.wrap$;
 				// 当前滚动条滚动宽度
 				let scrollS = scrollRefs.scrollWidth;
 				// 当前滚动条偏移宽度
@@ -469,7 +503,7 @@ export default defineComponent({
 					}
 				}
 				// 更新滚动条,防止不出现
-				updateScrollbar();
+				scrollbarRef.value.update();
 			});
 		};
 		// 获取 tagsView 的下标:用于处理 tagsView 点击时的横向滚动
@@ -520,17 +554,17 @@ export default defineComponent({
 			// 拖动问题,https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI
 			window.addEventListener('resize', onSortableResize);
 			// 监听非本页面调用 0 刷新当前,1 关闭当前,2 关闭其它,3 关闭全部 4 当前页全屏
-			proxy.mittBus.on('onCurrentContextmenuClick', (data: CurrentContextmenu) => {
+			mittBus.on('onCurrentContextmenuClick', (data: CurrentContextmenu) => {
 				onCurrentContextmenuClick(data);
 			});
 			// 监听布局配置界面开启/关闭拖拽
-			proxy.mittBus.on('openOrCloseSortable', () => {
+			mittBus.on('openOrCloseSortable', () => {
 				initSortable();
 			});
 			// 监听布局配置开启 TagsView 共用,为了演示还原默认值
-			proxy.mittBus.on('openShareTagsView', () => {
+			mittBus.on('openShareTagsView', () => {
 				if (getThemeConfig.value.isShareTagsView) {
-					router.push('/dashboard/home');
+					router.push('/home');
 					state.tagsViewList = [];
 					state.tagsViewRoutesList.map((v: any) => {
 						if (v.meta.isAffix && !v.meta.isHide) {
@@ -544,11 +578,11 @@ export default defineComponent({
 		// 页面卸载时
 		onUnmounted(() => {
 			// 取消非本页面调用监听
-			proxy.mittBus.off('onCurrentContextmenuClick', () => {});
+			mittBus.off('onCurrentContextmenuClick', () => {});
 			// 取消监听布局配置界面开启/关闭拖拽
-			proxy.mittBus.off('openOrCloseSortable', () => {});
+			mittBus.off('openOrCloseSortable', () => {});
 			// 取消监听布局配置开启 TagsView 共用
-			proxy.mittBus.off('openShareTagsView', () => {});
+			mittBus.off('openShareTagsView', () => {});
 			// 取消窗口 resize 监听
 			window.removeEventListener('resize', onSortableResize);
 		});
@@ -583,6 +617,7 @@ export default defineComponent({
 		return {
 			isActive,
 			onContextmenu,
+			onMousedownMenu,
 			onTagsClick,
 			tagsRefs,
 			contextmenuRef,

+ 22 - 9
Web/src/layout/navMenu/horizontal.vue

@@ -17,7 +17,7 @@
 								{{ $t(val.meta.title) }}
 							</template>
 							<template #title v-else>
-								<a :href="val.meta.isLink" target="_blank" rel="opener" class="w100">
+								<a class="w100" @click.prevent="onALinkClick(val)">
 									<SvgIcon :name="val.meta.icon" />
 									{{ $t(val.meta.title) }}
 								</a>
@@ -31,16 +31,19 @@
 </template>
 
 <script lang="ts">
-import { toRefs, reactive, computed, defineComponent, getCurrentInstance, onMounted, nextTick, onBeforeMount } from 'vue';
-import { useRoute, onBeforeRouteUpdate } from 'vue-router';
+import { defineAsyncComponent, toRefs, reactive, computed, defineComponent, onMounted, nextTick, onBeforeMount, ref } from 'vue';
+import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
 import { storeToRefs } from 'pinia';
 import { useRoutesList } from '/@/stores/routesList';
 import { useThemeConfig } from '/@/stores/themeConfig';
-import SubItem from '/@/layout/navMenu/subItem.vue';
+import { verifyUrl } from '/@/utils/toolsValidate';
+import mittBus from '/@/utils/mitt';
 
 export default defineComponent({
 	name: 'navMenuHorizontal',
-	components: { SubItem },
+	components: {
+		SubItem: defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue')),
+	},
 	props: {
 		menuList: {
 			type: Array,
@@ -48,12 +51,13 @@ export default defineComponent({
 		},
 	},
 	setup(props) {
-		const { proxy } = <any>getCurrentInstance();
+		const elMenuHorizontalScrollRef = ref();
 		const stores = useRoutesList();
 		const storesThemeConfig = useThemeConfig();
 		const { routesList } = storeToRefs(stores);
 		const { themeConfig } = storeToRefs(storesThemeConfig);
 		const route = useRoute();
+		const router = useRouter();
 		const state = reactive({
 			defaultActive: null,
 		});
@@ -64,14 +68,14 @@ export default defineComponent({
 		// 设置横向滚动条可以鼠标滚轮滚动
 		const onElMenuHorizontalScroll = (e: any) => {
 			const eventDelta = e.wheelDelta || -e.deltaY * 40;
-			proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap$.scrollLeft = proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap$.scrollLeft + eventDelta / 4;
+			elMenuHorizontalScrollRef.value.$refs.wrap$.scrollLeft = elMenuHorizontalScrollRef.value.$refs.wrap$.scrollLeft + eventDelta / 4;
 		};
 		// 初始化数据,页面刷新时,滚动条滚动到对应位置
 		const initElMenuOffsetLeft = () => {
 			nextTick(() => {
 				let els: any = document.querySelector('.el-menu.el-menu--horizontal li.is-active');
 				if (!els) return false;
-				proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap$.scrollLeft = els.offsetLeft;
+				elMenuHorizontalScrollRef.value.$refs.wrap$.scrollLeft = els.offsetLeft;
 			});
 		};
 		// 路由过滤递归函数
@@ -109,6 +113,13 @@ export default defineComponent({
 				else state.defaultActive = path;
 			}
 		};
+		// 打开外部链接
+		const onALinkClick = (val: any) => {
+			const { origin, pathname } = window.location;
+			router.push(val.path);
+			if (verifyUrl(val.meta.isLink)) window.open(val.meta.isLink);
+			else window.open(`${origin}${pathname}#${val.meta.isLink}`);
+		};
 		// 页面加载前
 		onBeforeMount(() => {
 			setCurrentRouterHighlight(route);
@@ -124,12 +135,14 @@ export default defineComponent({
 			// 修复经典布局开启切割菜单时,点击tagsView后左侧导航菜单数据不变的问题
 			let { layout, isClassicSplitMenu } = themeConfig.value;
 			if (layout === 'classic' && isClassicSplitMenu) {
-				proxy.mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path));
+				mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path));
 			}
 		});
 		return {
+			elMenuHorizontalScrollRef,
 			menuLists,
 			onElMenuHorizontalScroll,
+			onALinkClick,
 			...toRefs(state),
 		};
 	},

+ 12 - 1
Web/src/layout/navMenu/subItem.vue

@@ -14,7 +14,7 @@
 					<span>{{ $t(val.meta.title) }}</span>
 				</template>
 				<template v-else>
-					<a :href="val.meta.isLink" target="_blank" rel="opener" class="w100">
+					<a class="w100" @click.prevent="onALinkClick(val)">
 						<SvgIcon :name="val.meta.icon" />
 						{{ $t(val.meta.title) }}
 					</a>
@@ -26,6 +26,8 @@
 
 <script lang="ts">
 import { computed, defineComponent } from 'vue';
+import { useRouter } from 'vue-router';
+import { verifyUrl } from '/@/utils/toolsValidate';
 
 export default defineComponent({
 	name: 'navMenuSubItem',
@@ -36,12 +38,21 @@ export default defineComponent({
 		},
 	},
 	setup(props) {
+		const router = useRouter();
 		// 获取父级菜单数据
 		const chils = computed(() => {
 			return <any>props.chil;
 		});
+		// 打开外部链接
+		const onALinkClick = (val: any) => {
+			const { origin, pathname } = window.location;
+			router.push(val.path);
+			if (verifyUrl(val.meta.isLink)) window.open(val.meta.isLink);
+			else window.open(`${origin}${pathname}#${val.meta.isLink}`);
+		};
 		return {
 			chils,
+			onALinkClick,
 		};
 	},
 });

+ 16 - 5
Web/src/layout/navMenu/vertical.vue

@@ -22,7 +22,7 @@
 						<span>{{ $t(val.meta.title) }}</span>
 					</template>
 					<template #title v-else>
-						<a :href="val.meta.isLink" target="_blank" rel="opener" class="w100">{{ $t(val.meta.title) }}</a>
+						<a class="w100" @click.prevent="onALinkClick(val)">{{ $t(val.meta.title) }}</a>
 					</template>
 				</el-menu-item>
 			</template>
@@ -31,15 +31,17 @@
 </template>
 
 <script lang="ts">
-import { toRefs, reactive, computed, defineComponent, onMounted, watch } from 'vue';
-import { useRoute, onBeforeRouteUpdate } from 'vue-router';
+import { defineAsyncComponent, toRefs, reactive, computed, defineComponent, onMounted, watch } from 'vue';
+import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
 import { storeToRefs } from 'pinia';
 import { useThemeConfig } from '/@/stores/themeConfig';
-import SubItem from '/@/layout/navMenu/subItem.vue';
+import { verifyUrl } from '/@/utils/toolsValidate';
 
 export default defineComponent({
 	name: 'navMenuVertical',
-	components: { SubItem },
+	components: {
+		SubItem: defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue')),
+	},
 	props: {
 		menuList: {
 			type: Array,
@@ -50,6 +52,7 @@ export default defineComponent({
 		const storesThemeConfig = useThemeConfig();
 		const { themeConfig } = storeToRefs(storesThemeConfig);
 		const route = useRoute();
+		const router = useRouter();
 		const state = reactive({
 			// 修复:https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
 			defaultActive: route.meta.isDynamic ? route.meta.isDynamicPath : route.path,
@@ -70,6 +73,13 @@ export default defineComponent({
 			if (pathSplit.length >= 4 && meta.isHide) return pathSplit.splice(0, 3).join('/');
 			else return path;
 		};
+		// 打开外部链接
+		const onALinkClick = (val: any) => {
+			const { origin, pathname } = window.location;
+			router.push(val.path);
+			if (verifyUrl(val.meta.isLink)) window.open(val.meta.isLink);
+			else window.open(`${origin}${pathname}#${val.meta.isLink}`);
+		};
 		// 设置菜单的收起/展开
 		watch(
 			themeConfig.value,
@@ -94,6 +104,7 @@ export default defineComponent({
 		return {
 			menuLists,
 			getThemeConfig,
+			onALinkClick,
 			...toRefs(state),
 		};
 	},

+ 71 - 43
Web/src/layout/routerView/iframes.vue

@@ -1,65 +1,93 @@
 <template>
-	<div class="layout-view-bg-white flex mt1" :style="{ height: `calc(100vh - ${setIframeHeight}`, border: 'none' }" v-loading="iframeLoading">
-		<iframe :src="iframeUrl" frameborder="0" height="100%" width="100%" ref="iframeDom" v-show="!iframeLoading"></iframe>
+	<div class="layout-padding layout-padding-unset layout-iframe">
+		<div class="layout-padding-auto layout-padding-view">
+			<div class="w100" v-for="v in setIframeList" :key="v.path" v-loading="v.meta.loading" element-loading-background="white">
+				<transition-group :name="name" mode="out-in">
+					<iframe :src="v.meta.isLink" :key="v.path" frameborder="0" height="100%" width="100%" style="position: absolute" :data-url="v.path" v-show="getRoutePath === v.path" ref="iframeRef" />
+				</transition-group>
+			</div>
+		</div>
 	</div>
 </template>
 
 <script lang="ts">
-import { defineComponent, reactive, toRefs, onMounted, nextTick, watch, computed } from 'vue';
-import { storeToRefs } from 'pinia';
+import { defineComponent, computed, watch, ref, nextTick } from 'vue';
 import { useRoute } from 'vue-router';
-import { useThemeConfig } from '/@/stores/themeConfig';
-import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
 
 export default defineComponent({
-	name: 'layoutIfameView',
-	setup() {
-		const storesThemeConfig = useThemeConfig();
-		const storesTagsViewRoutes = useTagsViewRoutes();
-		const { themeConfig } = storeToRefs(storesThemeConfig);
-		const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
+	name: 'layoutIframeView',
+	props: {
+		refreshKey: {
+			type: String,
+			default: () => '',
+		},
+		name: {
+			type: String,
+			default: () => 'slide-right',
+		},
+		list: {
+			type: Array,
+			default: () => [],
+		},
+	},
+	setup(props) {
+		const iframeRef = ref();
 		const route = useRoute();
-		const state = reactive({
-			iframeDom: null as HTMLIFrameElement | null,
-			iframeLoading: true,
-			iframeUrl: '',
+		// 处理 list 列表,当打开时,才进行加载
+		const setIframeList = computed(() => {
+			return (<any>props.list).filter((v: any) => v.meta.isIframeOpen);
+		});
+		// 获取 iframe 当前路由 path
+		const getRoutePath = computed(() => {
+			return route.path;
 		});
-		// 初始化页面加载 loading
-		const initIframeLoad = () => {
-			state.iframeUrl = <any>route.meta.isLink;
+		// 关闭 iframe loading
+		const closeIframeLoading = (val: string, item: any) => {
 			nextTick(() => {
-				state.iframeLoading = true;
-				const iframe = state.iframeDom;
-				if (!iframe) return false;
-				iframe.onload = () => {
-					state.iframeLoading = false;
-				};
+				if (!iframeRef.value) return false;
+				iframeRef.value.forEach((v: any) => {
+					if (v.dataset.url === val) {
+						v.onload = () => {
+							if (item.meta.isIframeOpen && item.meta.loading) item.meta.loading = false;
+						};
+					}
+				});
 			});
 		};
-		// 设置 iframe 的高度
-		const setIframeHeight = computed(() => {
-			let { isTagsview } = themeConfig.value;
-			if (isTagsViewCurrenFull.value) {
-				return `1px`;
-			} else {
-				if (isTagsview) return `86px`;
-				else return `51px`;
+		// 监听路由变化,初始化 iframe 数据,防止多个 iframe 时,切换不生效
+		watch(
+			() => route.fullPath,
+			(val) => {
+				const item: any = props.list.find((v: any) => v.path === val);
+				if (!item) return false;
+				if (!item.meta.isIframeOpen) item.meta.isIframeOpen = true;
+				closeIframeLoading(val, item);
+			},
+			{
+				immediate: true,
 			}
-		});
-		// 页面加载时
-		onMounted(() => {
-			initIframeLoad();
-		});
-		// 监听路由变化,多个 iframe 时使用
+		);
+		// 监听 iframe refreshKey 变化,用于 tagsview 右键菜单刷新
 		watch(
-			() => route.path,
+			() => props.refreshKey,
 			() => {
-				initIframeLoad();
+				const item: any = props.list.find((v: any) => v.path === route.path);
+				if (!item) return false;
+				if (item.meta.isIframeOpen) item.meta.isIframeOpen = false;
+				setTimeout(() => {
+					item.meta.isIframeOpen = true;
+					item.meta.loading = true;
+					closeIframeLoading(route.fullPath, item);
+				});
+			},
+			{
+				deep: true,
 			}
 		);
 		return {
-			setIframeHeight,
-			...toRefs(state),
+			iframeRef,
+			setIframeList,
+			getRoutePath,
 		};
 	},
 });

+ 68 - 16
Web/src/layout/routerView/link.vue

@@ -1,16 +1,22 @@
 <template>
-	<div class="layout-view-bg-white flex layout-view-link" :style="{ height: `calc(100vh - ${setLinkHeight}` }">
-		<a :href="currentRouteMeta.isLink" target="_blank" rel="opener" class="flex-margin">
-			{{ $t(currentRouteMeta.title) }}:{{ currentRouteMeta.isLink }}
-		</a>
+	<div class="layout-padding layout-link-container">
+		<div class="layout-padding-auto layout-padding-view">
+			<div class="layout-link-warp">
+				<i class="layout-link-icon iconfont icon-xingqiu"></i>
+				<div class="layout-link-msg">页面 "{{ $t(currentRouteMeta.title) }}" 已在新窗口中打开</div>
+				<el-button class="mt30" round size="default" @click="onGotoFullPage">
+					<i class="iconfont icon-lianjie"></i>
+					<span>立即前往体验</span>
+				</el-button>
+			</div>
+		</div>
 	</div>
 </template>
 
 <script lang="ts">
-import { defineComponent, toRefs, reactive, computed, watch } from 'vue';
+import { defineComponent, toRefs, reactive, watch } from 'vue';
 import { useRoute, RouteMeta } from 'vue-router';
-import { storeToRefs } from 'pinia';
-import { useThemeConfig } from '/@/stores/themeConfig';
+import { verifyUrl } from '/@/utils/toolsValidate';
 
 // 定义接口来定义对象的类型
 interface LinkViewState {
@@ -27,8 +33,6 @@ interface LinkViewRouteMeta extends RouteMeta {
 export default defineComponent({
 	name: 'layoutLinkView',
 	setup() {
-		const storesThemeConfig = useThemeConfig();
-		const { themeConfig } = storeToRefs(storesThemeConfig);
 		const route = useRoute();
 		const state = reactive<LinkViewState>({
 			currentRouteMeta: {
@@ -36,12 +40,12 @@ export default defineComponent({
 				title: '',
 			},
 		});
-		// 设置 link 的高度
-		const setLinkHeight = computed(() => {
-			let { isTagsview } = themeConfig.value;
-			if (isTagsview) return `115px`;
-			else return `80px`;
-		});
+		// 立即前往
+		const onGotoFullPage = () => {
+			const { origin, pathname } = window.location;
+			if (verifyUrl(state.currentRouteMeta.isLink)) window.open(state.currentRouteMeta.isLink);
+			else window.open(`${origin}${pathname}#${state.currentRouteMeta.isLink}`);
+		};
 		// 监听路由的变化,设置内容
 		watch(
 			() => route.path,
@@ -53,9 +57,57 @@ export default defineComponent({
 			}
 		);
 		return {
-			setLinkHeight,
+			onGotoFullPage,
 			...toRefs(state),
 		};
 	},
 });
 </script>
+
+<style scoped lang="scss">
+.layout-link-container {
+	.layout-link-warp {
+		margin: auto;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+		i.layout-link-icon {
+			position: relative;
+			font-size: 100px;
+			color: var(--el-color-primary);
+			&::after {
+				content: '';
+				position: absolute;
+				left: 50px;
+				top: 0;
+				width: 15px;
+				height: 100px;
+				background: linear-gradient(
+					rgba(255, 255, 255, 0.01),
+					rgba(255, 255, 255, 0.01),
+					rgba(255, 255, 255, 0.01),
+					rgba(255, 255, 255, 0.05),
+					rgba(255, 255, 255, 0.05),
+					rgba(255, 255, 255, 0.05),
+					rgba(235, 255, 255, 0.5),
+					rgba(255, 255, 255, 0.05),
+					rgba(255, 255, 255, 0.05),
+					rgba(255, 255, 255, 0.05),
+					rgba(255, 255, 255, 0.01),
+					rgba(255, 255, 255, 0.01),
+					rgba(255, 255, 255, 0.01)
+				);
+				transform: rotate(-15deg);
+				animation: toRight 5s linear infinite;
+			}
+		}
+		.layout-link-msg {
+			font-size: 12px;
+			color: var(--next-bg-topBarColor);
+			opacity: 0.7;
+			margin-top: 15px;
+		}
+	}
+}
+</style>

+ 44 - 10
Web/src/layout/routerView/parent.vue

@@ -1,41 +1,52 @@
 <template>
-	<div class="h100">
+	<div class="layout-parent">
 		<router-view v-slot="{ Component }">
 			<transition :name="setTransitionName" mode="out-in">
 				<keep-alive :include="getKeepAliveNames">
-					<component :is="Component" :key="refreshRouterViewKey" class="w100" />
+					<component :is="Component" :key="refreshRouterViewKey" class="w100" v-show="!isIframePage" />
 				</keep-alive>
 			</transition>
 		</router-view>
+		<transition :name="setTransitionName" mode="out-in">
+			<Iframes class="w100" v-show="isIframePage" :refreshKey="iframeRefreshKey" :name="setTransitionName" :list="iframeList" />
+		</transition>
 	</div>
 </template>
 
 <script lang="ts">
-import { computed, defineComponent, toRefs, reactive, getCurrentInstance, onBeforeMount, onUnmounted, nextTick, watch, onMounted } from 'vue';
-import { useRoute } from 'vue-router';
+import { defineAsyncComponent, computed, defineComponent, toRefs, reactive, onBeforeMount, onUnmounted, nextTick, watch, onMounted } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
 import { storeToRefs } from 'pinia';
 import { useKeepALiveNames } from '/@/stores/keepAliveNames';
 import { useThemeConfig } from '/@/stores/themeConfig';
 import { Session } from '/@/utils/storage';
+import mittBus from '/@/utils/mitt';
 
 // 定义接口来定义对象的类型
 interface ParentViewState {
-	refreshRouterViewKey: null | string;
+	refreshRouterViewKey: string;
+	iframeRefreshKey: string;
 	keepAliveNameList: string[];
+	iframeList: string[];
 }
 
 export default defineComponent({
 	name: 'layoutParentView',
+	components: {
+		Iframes: defineAsyncComponent(() => import('/@/layout/routerView/iframes.vue')),
+	},
 	setup() {
-		const { proxy } = <any>getCurrentInstance();
 		const route = useRoute();
+		const router = useRouter();
 		const storesKeepAliveNames = useKeepALiveNames();
 		const storesThemeConfig = useThemeConfig();
 		const { keepAliveNames, cachedViews } = storeToRefs(storesKeepAliveNames);
 		const { themeConfig } = storeToRefs(storesThemeConfig);
 		const state = reactive<ParentViewState>({
-			refreshRouterViewKey: null,
+			refreshRouterViewKey: '', // 非 iframe tagsview 右键菜单刷新时
+			iframeRefreshKey: '', // iframe tagsview 右键菜单刷新时
 			keepAliveNameList: [],
+			iframeList: [],
 		});
 		// 设置主界面切换动画
 		const setTransitionName = computed(() => {
@@ -45,20 +56,37 @@ export default defineComponent({
 		const getKeepAliveNames = computed(() => {
 			return themeConfig.value.isTagsview ? cachedViews.value : state.keepAliveNameList;
 		});
+		// 设置 iframe 显示/隐藏
+		const isIframePage = computed(() => {
+			return route.meta.isIframe;
+		});
+		// 获取 iframe 组件列表(未进行渲染)
+		const getIframeListRoutes = async () => {
+			router.getRoutes().forEach((v: any) => {
+				if (v.meta.isIframe) {
+					v.meta.isIframeOpen = false;
+					v.meta.loading = true;
+					state.iframeList.push({ ...v });
+				}
+			});
+		};
 		// 页面加载前,处理缓存,页面刷新时路由缓存处理
 		onBeforeMount(() => {
 			state.keepAliveNameList = keepAliveNames.value;
-			proxy.mittBus.on('onTagsViewRefreshRouterView', (fullPath: string) => {
+			mittBus.on('onTagsViewRefreshRouterView', (fullPath: string) => {
 				state.keepAliveNameList = keepAliveNames.value.filter((name: string) => route.name !== name);
-				state.refreshRouterViewKey = null;
+				state.refreshRouterViewKey = '';
+				state.iframeRefreshKey = '';
 				nextTick(() => {
 					state.refreshRouterViewKey = fullPath;
+					state.iframeRefreshKey = fullPath;
 					state.keepAliveNameList = keepAliveNames.value;
 				});
 			});
 		});
 		// 页面加载时
 		onMounted(() => {
+			getIframeListRoutes();
 			// https://gitee.com/lyt-top/vue-next-admin/issues/I58U75
 			// https://gitee.com/lyt-top/vue-next-admin/issues/I59RXK
 			nextTick(() => {
@@ -69,18 +97,24 @@ export default defineComponent({
 		});
 		// 页面卸载时
 		onUnmounted(() => {
-			proxy.mittBus.off('onTagsViewRefreshRouterView', () => {});
+			mittBus.off('onTagsViewRefreshRouterView', () => {});
 		});
 		// 监听路由变化,防止 tagsView 多标签时,切换动画消失
+		// https://toscode.gitee.com/lyt-top/vue-next-admin/pulls/38/files
 		watch(
 			() => route.fullPath,
 			() => {
 				state.refreshRouterViewKey = decodeURI(route.fullPath);
+			},
+			{
+				immediate: true,
 			}
 		);
 		return {
+			route,
 			setTransitionName,
 			getKeepAliveNames,
+			isIframePage,
 			...toRefs(state),
 		};
 	},

+ 5 - 15
Web/src/main.ts

@@ -5,31 +5,21 @@ import router from './router';
 import { directive } from '/@/utils/directive';
 import { i18n } from '/@/i18n/index';
 import other from '/@/utils/other';
-import { signalR } from '/@/views/system/onlineUser/signalR';
 
 import ElementPlus from 'element-plus';
 import 'element-plus/dist/index.css';
-import VForm3 from 'vform3-builds'; // VForm3表单设计
-import 'vform3-builds/dist/designer.style.css';
 import '/@/theme/index.scss';
-import mitt from 'mitt';
 import VueGridLayout from 'vue-grid-layout';
+
+import VForm3 from 'vform3-builds'; // VForm3表单设计
+import 'vform3-builds/dist/designer.style.css'; // VForm3表单设计样式
 import VueSignaturePad from 'vue-signature-pad'; // 电子签名
 import vue3TreeOrg from 'vue3-tree-org'; // 组织架构图
-import 'vue3-tree-org/lib/vue3-tree-org.css';
+import 'vue3-tree-org/lib/vue3-tree-org.css'; // 组织架构图样式
 
 const app = createApp(App);
 
 directive(app);
 other.elSvg(app);
 
-app.use(pinia).use(router).use(ElementPlus, { i18n: i18n.global.t }).use(VForm3).use(i18n).use(VueGridLayout).use(VueSignaturePad).use(vue3TreeOrg).mount('#app');
-
-const globalProperties = {
-	mittBus: mitt(),
-	i18n,
-	signalR,
-};
-
-// 必须合并vue默认的变量,否则有问题
-app.config.globalProperties = Object.assign(app.config.globalProperties, globalProperties);
+app.use(pinia).use(router).use(ElementPlus, { i18n: i18n.global.t }).use(i18n).use(VueGridLayout).use(VForm3).use(VueSignaturePad).use(vue3TreeOrg).mount('#app');

+ 2 - 0
Web/src/router/backEnd.ts

@@ -80,6 +80,8 @@ export function setCacheTagsViewRoutes() {
  */
 export function setFilterRouteEnd() {
 	let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
+    // notFoundAndNoPower 防止 404、401 不在 layout 布局中,不设置的话,404、401 界面将全屏显示
+	// 关联问题 No match found for location with path 'xxx'
 	filterRouteEnd[0].children = [...filterRouteEnd[0].children, ...notFoundAndNoPower];
 	return filterRouteEnd;
 }

+ 2 - 0
Web/src/router/frontEnd.ts

@@ -64,6 +64,8 @@ export async function frontEndsResetRoute() {
  */
 export function setFilterRouteEnd() {
 	let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
+    // notFoundAndNoPower 防止 404、401 不在 layout 布局中,不设置的话,404、401 界面将全屏显示
+	// 关联问题 No match found for location with path 'xxx'
 	filterRouteEnd[0].children = [...setFilterRoute(filterRouteEnd[0].children), ...notFoundAndNoPower];
 	return filterRouteEnd;
 }

+ 11 - 6
Web/src/router/index.ts

@@ -7,7 +7,7 @@ import { useKeepALiveNames } from '/@/stores/keepAliveNames';
 import { useRoutesList } from '/@/stores/routesList';
 import { useThemeConfig } from '/@/stores/themeConfig';
 import { Session } from '/@/utils/storage';
-import { staticRoutes } from '/@/router/route';
+import { staticRoutes, notFoundAndNoPower } from '/@/router/route';
 import { initFrontEndControlRoutes } from '/@/router/frontEnd';
 import { initBackEndControlRoutes } from '/@/router/backEnd';
 
@@ -32,7 +32,13 @@ const { isRequestRoutes } = themeConfig.value;
  */
 export const router = createRouter({
 	history: createWebHashHistory(),
-	routes: staticRoutes,
+	/**
+	 * 说明:
+	 * 1、notFoundAndNoPower 默认添加 404、401 界面,防止一直提示 No match found for location with path 'xxx'
+	 * 2、backEnd.ts(后端控制路由)、frontEnd.ts(前端控制路由) 中也需要加 notFoundAndNoPower 404、401 界面。
+	 *    防止 404、401 不在 layout 布局中,不设置的话,404、401 界面将全屏显示
+	 */
+	routes: [...notFoundAndNoPower, ...staticRoutes],
 });
 
 /**
@@ -107,13 +113,12 @@ router.beforeEach(async (to, from, next) => {
 				if (isRequestRoutes) {
 					// 后端控制路由:路由数据初始化,防止刷新时丢失
 					await initBackEndControlRoutes();
-					// 动态添加路由:防止非首页刷新时跳转回首页的问题
-					// 确保 addRoute() 时动态添加的路由已经被完全加载上去
-					next({ ...to, replace: true });
+					// 解决刷新时,一直跳 404 页面问题,关联问题 No match found for location with path 'xxx'
+					next({ path: to.path });
 				} else {
 					// https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP
 					await initFrontEndControlRoutes();
-					next({ ...to, replace: true });
+					next({ path: to.path });
 				}
 			} else {
 				next();

+ 1 - 1
Web/src/router/route.ts

@@ -4,7 +4,7 @@ import { RouteRecordRaw } from 'vue-router';
  * 路由meta对象参数说明
  * meta: {
  *      title:          菜单栏及 tagsView 栏、菜单搜索名称(国际化)
- *      isLink:        是否超链接菜单,开启外链条件,`1、isLink: 链接地址不为空`
+ *      isLink:        是否超链接菜单,开启外链条件,`1、isLink: 链接地址不为空 2、isIframe:false`
  *      isHide:        是否隐藏此路由
  *      isKeepAlive:   是否缓存组件状态
  *      isAffix:       是否固定在 tagsView 栏上

+ 2 - 0
Web/src/stores/interface/index.ts

@@ -61,6 +61,7 @@ export interface ThemeConfigState {
 	columnsMenuBar: string;
 	columnsMenuBarColor: string;
 	isColumnsMenuBarColorGradual: boolean;
+	isColumnsMenuHoverPreload: boolean;
 	isCollapse: boolean;
 	isUniqueOpened: boolean;
 	isFixedHeader: boolean;
@@ -91,6 +92,7 @@ export interface ThemeConfigState {
 	isRequestRoutes: boolean;
 	globalTitle: string;
 	globalViceTitle: string;
+	globalViceTitleMsg: string;
 	globalI18n: string;
 	globalComponentSize: string;
 }

+ 1 - 2
Web/src/stores/keepAliveNames.ts

@@ -19,8 +19,7 @@ export const useKeepALiveNames = defineStore('keepALiveNames', {
 			this.keepAliveNames = data;
 		},
 		async addCachedView(view: any) {
-			if (this.cachedViews.includes(view.name)) return;
-			if (view.meta.isKeepAlive) this.cachedViews.push(view.name);
+			if (view.meta.isKeepAlive) this.cachedViews?.push(view.name);
 		},
 		async delCachedView(view: any) {
 			const index = this.cachedViews.indexOf(view.name);

+ 4 - 0
Web/src/stores/themeConfig.ts

@@ -47,6 +47,8 @@ export const useThemeConfig = defineStore('themeConfig', {
 			columnsMenuBarColor: '#F0F0F0',
 			// 是否开启分栏菜单背景颜色渐变
 			isColumnsMenuBarColorGradual: false,
+			// 是否开启分栏菜单鼠标悬停预加载(预览菜单)
+			isColumnsMenuHoverPreload: false,
 
 			/**
 			 * 界面设置
@@ -132,6 +134,8 @@ export const useThemeConfig = defineStore('themeConfig', {
 			globalTitle: 'Admin.NET',
 			// 网站副标题(登录页顶部文字)
 			globalViceTitle: 'Admin.NET',
+			// 网站副标题(登录页顶部文字)
+			globalViceTitleMsg: '有可能是.NET最好用的通用权限开发框架',
 			// 默认初始语言,可选值"<zh-cn|en|zh-tw>",默认 zh-cn
 			globalI18n: 'zh-cn',
 			// 默认全局组件大小,可选值"<large|'default'|small>",默认 'large'

+ 61 - 17
Web/src/theme/app.scss

@@ -22,7 +22,7 @@
 	--next-color-seting-main: #e9eef3;
 	--next-color-seting-aside: #d3dce6;
 	--next-color-seting-header: #b3c0d1;
-	// 重写全局样式
+    // 重写全局样式
 	//--el-text-color-primary: rgba(0, 0, 0, .75) !important;
     --el-text-color-regular: rgba(0, 0, 0, .75) !important;
 	//--el-text-color-secondary: var(--el-text-color-regular) !important;
@@ -50,6 +50,14 @@ body,
 .layout-container {
 	width: 100%;
 	height: 100%;
+	.layout-pd {
+		padding: 15px !important;
+	}
+	.layout-flex {
+		display: flex;
+		flex-direction: column;
+		flex: 1;
+	}
 	.layout-aside {
 		background: var(--next-bg-menuBar);
 		box-shadow: 2px 0 6px rgb(0 21 41 / 1%);
@@ -65,30 +73,70 @@ body,
 	}
 	.layout-header {
 		padding: 0 !important;
+		height: auto !important;
 	}
 	.layout-main {
 		padding: 0 !important;
 		overflow: hidden;
 		width: 100%;
 		background-color: var(--next-bg-main-color);
+		display: flex;
+		flex-direction: column;
+		// 内层 el-scrollbar样式,用于界面高度自适应(main.vue)
+		.layout-main-scroll {
+			@extend .layout-flex;
+			.layout-parent {
+				@extend .layout-flex;
+				position: relative;
+				padding: 8px; // 四周间隙
+			}
+		}
 	}
-	.el-scrollbar {
-		width: 100%;
+	// 用于界面高度自适应
+	.layout-padding {
+		@extend .layout-pd;
+		position: absolute;
+		left: 0;
+		top: 0;
+		height: 100%;
+		overflow: hidden;
+		@extend .layout-flex;
+		&-auto {
+			height: inherit;
+			@extend .layout-flex;
+		}
+		&-view {
+			background: var(--el-color-white);
+			width: 100%;
+			height: 100%;
+			border-radius: 4px;
+			border: 1px solid var(--el-border-color-light, #ebeef5);
+			overflow: hidden;
+		}
+	}
+	// 用于界面高度自适应,主视图区 main 的内边距,用于 iframe
+	.layout-padding-unset {
+		padding: 0 !important;
+		&-view {
+			border-radius: 0 !important;
+			border: none !important;
+		}
+	}
+	// 用于设置 iframe loading 时的高度(loading 垂直居中显示)
+	.layout-iframe {
+		.el-loading-parent--relative {
+			height: 100%;
+		}
 	}
-	// 此字段多次用到,建议不删除,如需修改,请重写覆盖样式
-	.layout-view-bg-white {
-		background: var(--el-color-white);
+	.el-scrollbar {
 		width: 100%;
-		height: 100%;
-		border-radius: 4px;
-		border: 1px solid var(--el-border-color-light, #ebeef5);
 	}
 	.layout-el-aside-br-color {
 		border-right: 1px solid var(--el-border-color-light, #ebeef5);
 	}
 	// pc端左侧导航样式
 	.layout-aside-pc-220 {
-		width: 200px !important;
+		width: 220px !important;
 		transition: width 0.3s ease;
 	}
 	.layout-aside-pc-64 {
@@ -103,12 +151,12 @@ body,
 	.layout-aside-mobile {
 		position: fixed;
 		top: 0;
-		left: -200px;
-		width: 200px;
+		left: -220px;
+		width: 220px;
 		z-index: 9999999;
 	}
 	.layout-aside-mobile-close {
-		left: -200px;
+		left: -220px;
 		transition: all 0.3s cubic-bezier(0.39, 0.58, 0.57, 1);
 	}
 	.layout-aside-mobile-open {
@@ -126,10 +174,6 @@ body,
 		z-index: 9999998;
 		animation: error-img 0.3s;
 	}
-	.layout-scrollbar {
-		@extend .el-scrollbar;
-		padding: 8px; // 四周间隙
-	}
 	.layout-mian-height-50 {
 		height: calc(100vh - 50px);
 	}

+ 53 - 0
Web/src/theme/common/transition.scss

@@ -92,3 +92,56 @@
 		opacity: 0;
 	}
 }
+
+/* 登录页动画
+------------------------------- */
+@keyframes loginLeft {
+	0% {
+		left: -100%;
+	}
+	50%,
+	100% {
+		left: 100%;
+	}
+}
+@keyframes loginTop {
+	0% {
+		top: -100%;
+	}
+	50%,
+	100% {
+		top: 100%;
+	}
+}
+@keyframes loginRight {
+	0% {
+		right: -100%;
+	}
+	50%,
+	100% {
+		right: 100%;
+	}
+}
+@keyframes loginBottom {
+	0% {
+		bottom: -100%;
+	}
+	50%,
+	100% {
+		bottom: 100%;
+	}
+}
+
+/* 左右左 link.vue
+------------------------------- */
+@keyframes toRight {
+	0% {
+		left: -5px;
+	}
+	50% {
+		left: 100%;
+	}
+	100% {
+		left: -5px;
+	}
+}

+ 5 - 0
Web/src/theme/dark.scss

@@ -233,4 +233,9 @@
 			border-color: var(--el-border-color-lighter) !important;
 		}
 	}
+
+	// loading
+	.el-loading-mask {
+		background-color: var(--next-bg-main) !important;
+	}
 }

+ 30 - 5
Web/src/theme/element.scss

@@ -44,6 +44,10 @@
 			margin-bottom: 18px !important;
 		}
 	}
+	// https://gitee.com/lyt-top/vue-next-admin/issues/I5K1PM
+	.el-form-item .el-form-item__label .el-icon {
+		margin-right: 0px;
+	}
 }
 
 /* Alert 警告
@@ -73,7 +77,7 @@
 // 默认样式修改
 .el-menu {
 	border-right: none !important;
-	width: 200px;
+	width: 220px;
 }
 .el-menu-item {
 	height: 50px !important;
@@ -179,11 +183,16 @@
 		.el-sub-menu.is-active .el-sub-menu__title {
 			color: var(--el-menu-active-color);
 		}
+		// 鼠标 hover 时背景色
+		.el-menu-item:hover {
+			background-color: var(--el-color-primary-light-9)
+		}
 	}
 }
 // 横向菜单(经典、横向)布局
 .el-menu.el-menu--horizontal {
 	border-bottom: none !important;
+
 	width: 100% !important;
 	.el-menu-item,
 	.el-sub-menu__title {
@@ -193,6 +202,15 @@
 	.el-menu-item:not(.is-active):hover,
 	.el-sub-menu:not(.is-active):hover .el-sub-menu__title {
 		color: var(--next-bg-topBarColor);
+		//border-right: solid 3px var(--el-color-primary);
+	}
+	// 鼠标 hover 时背景色
+	.el-menu-item:hover {
+		background-color: var(--el-color-primary-light-9)
+	}
+	// 菜单激活时去掉全局右边框
+	.el-menu-item.is-active{
+		border-right: none;
 	}
 }
 
@@ -277,7 +295,6 @@
 	color: var(--el-color-danger) !important;
 }
 
-
 /* Card 卡片
 ------------------------------- */
 .el-card__header {
@@ -305,22 +322,30 @@
 	float: right;
 	margin: 15px 0 15px 0 !important;
 }
+
 /* scrollbar
 ------------------------------- */
 .el-scrollbar__bar {
 	z-index: 4;
 }
+/*防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)*/
 .el-scrollbar__wrap {
-	max-height: 100%; /*防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)*/
+	max-height: 100%;
 }
 .el-select-dropdown .el-scrollbar__wrap {
 	overflow-x: scroll !important;
 }
+/*修复Select 选择器高度问题*/
 .el-select-dropdown__wrap {
-	max-height: 274px !important; /*修复Select 选择器高度问题*/
+	max-height: 274px !important;
 }
+/*修复Cascader 级联选择器高度问题*/
 .el-cascader-menu__wrap.el-scrollbar__wrap {
-	height: 204px !important; /*修复Cascader 级联选择器高度问题*/
+	height: 204px !important;
+}
+/*用于界面高度自适应(main.vue),区分 scrollbar__view,防止其它使用 scrollbar 的地方出现滚动条消失*/
+.layout-container-view .el-scrollbar__view {
+	height: 100%;
 }
 
 /* Drawer 抽屉

+ 1 - 0
Web/src/theme/media/form.scss

@@ -13,6 +13,7 @@
 		margin-left: 0 !important;
 	}
 	.el-form-item {
+		// 响应式表单时,登录页需要重新处理
 		display: unset !important;
 	}
 }

+ 48 - 37
Web/src/theme/media/login.scss

@@ -1,32 +1,23 @@
 @import './index.scss';
 
-/* 页面宽度小于992px
+/* 页面宽度小于1200px
 ------------------------------- */
-@media screen and (max-width: $lg) {
+@media screen and (max-width: $lg) and (min-width: $xs) {
 	.login-container {
-		.login-icon-group {
-			&::before {
-				content: '';
-				height: 70% !important;
-				transition: all 0.3s ease;
-			}
-			&::after {
-				content: '';
-				width: 100px !important;
-				height: 200px !important;
-				transition: all 0.3s ease;
+		.login-left {
+			.login-left-img {
+				top: 90% !important;
+				left: 12% !important;
+				width: 30% !important;
+				height: 18% !important;
 			}
 		}
-	}
-}
-
-/* 页面宽度小于992px
-------------------------------- */
-@media screen and (max-width: $md) {
-	.login-content {
-		right: unset !important;
-		left: 50% !important;
-		transform: translate(-50%, -50%) translate3d(0, 0, 0) !important;
+		.login-right {
+			position: absolute;
+			top: 50%;
+			left: 50%;
+			transform: translate(-50%, -50%);
+		}
 	}
 }
 
@@ -34,19 +25,34 @@
 ------------------------------- */
 @media screen and (max-width: $xs) {
 	.login-container {
-		.login-icon-group {
-			display: none !important;
+		.login-left {
+			display: none;
 		}
-		.login-content {
+		.login-right {
 			width: 100% !important;
-			height: 100% !important;
-			padding: 20px 0 !important;
-			border-radius: 0 !important;
-			box-shadow: unset !important;
-			border: none !important;
-		}
-		.el-form-item {
-			display: flex !important;
+			.login-right-warp {
+				width: 100% !important;
+				height: 100% !important;
+				border: none !important;
+				.login-right-warp-mian {
+					.el-form-item {
+						display: flex !important;
+					}
+					.login-right-warp-main-title {
+						font-size: 20px !important;
+					}
+				}
+				.login-right-warp-one {
+					&::after {
+						right: 0 !important;
+					}
+				}
+				.login-right-warp-two {
+					&::before {
+						bottom: 1px !important;
+					}
+				}
+			}
 		}
 	}
 }
@@ -55,9 +61,14 @@
 ------------------------------- */
 @media screen and (max-width: $us) {
 	.login-container {
-		.login-content-title {
-			font-size: 18px !important;
-			transition: all 0.3s ease;
+		.login-right {
+			.login-right-warp {
+				.login-right-warp-mian {
+					.login-right-warp-main-title {
+						font-size: 18px !important;
+					}
+				}
+			}
 		}
 	}
 }

+ 2 - 2
Web/src/theme/other.scss

@@ -1,7 +1,7 @@
-/* wangeditor富文本编辑器
+/* wangeditor 富文本编辑器
 ------------------------------- */
 .editor-container {
-	z-index: 9999;
+	z-index: 10; // 用于 wangeditor 点击全屏时
 	.w-e-toolbar {
 		border: 1px solid var(--el-border-color-light, #ebeef5) !important;
 		border-bottom: 1px solid var(--el-border-color-light, #ebeef5) !important;

+ 22 - 0
Web/src/types/mitt.ts

@@ -0,0 +1,22 @@
+// mitt 事件类型定义
+export type MittType = {
+	openSetingsDrawer?: string; // 打开布局设置弹窗
+	restoreDefault?: string; // 分栏布局,鼠标移入、移出数据显示
+	setSendColumnsChildren?: string; // 分栏布局,鼠标移入、移出菜单数据传入到 navMenu 下的菜单中
+	setSendClassicChildren?: string; // 经典布局,开启切割菜单时,菜单数据传入到 navMenu 下的菜单中
+	getBreadcrumbIndexSetFilterRoutes?: string; // 布局设置弹窗,开启切割菜单时,菜单数据传入到 navMenu 下的菜单中
+	layoutMobileResize?: object; // 浏览器窗口改变时,用于适配移动端界面显示
+	openOrCloseSortable?: string; // 布局设置弹窗,开启 TagsView 拖拽
+	openShareTagsView?: string; // 布局设置弹窗,开启 TagsView 共用
+	onTagsViewRefreshRouterView?: any; // tagsview 刷新界面
+	onCurrentContextmenuClick?: any; // tagsview 右键菜单每项点击时
+	setHoverPreload?: string; // 分栏菜单鼠标悬停预加载
+
+	// 自定义
+	submitRefresh?: string; // 通用编辑提交刷新
+	submitRefreshDictData?: string; // 字典编辑提交刷新
+	submitRefreshTable?: string; // 库表管理表刷新
+	submitRefreshColumn?: string; // 库表管理列刷新
+	addTableSubmitted?: string; // 库表管理增加表刷新
+	submitRefreshFk?: string; // 代码生成主键刷新
+};

+ 6 - 4
Web/src/utils/loading.ts

@@ -32,11 +32,13 @@ export const NextLoading = {
 		window.nextLoading = true;
 	},
 	// 移除 loading
-	done: () => {
+	done: (time: number = 0) => {
 		nextTick(() => {
-			window.nextLoading = false;
-			const el = <HTMLElement>document.querySelector('.loading-next');
-			el?.parentNode?.removeChild(el);
+			setTimeout(() => {
+				window.nextLoading = false;
+				const el = <HTMLElement>document.querySelector('.loading-next');
+				el?.parentNode?.removeChild(el);
+			}, time);
 		});
 	},
 };

+ 9 - 0
Web/src/utils/mitt.ts

@@ -0,0 +1,9 @@
+// https://www.npmjs.com/package/mitt
+import mitt, { Emitter } from 'mitt';
+import { MittType } from '/@/types/mitt';
+
+// 类型
+const emitter: Emitter<MittType> = mitt<MittType>();
+
+// 导出
+export default emitter;

+ 2 - 2
Web/src/utils/other.ts

@@ -51,10 +51,10 @@ export function setTagsViewNameI18n(item: any) {
 	let tagsViewName: any = '';
 	const { query, params, meta } = item;
 	if (query?.tagsViewName || params?.tagsViewName) {
-		if (/\/zh-cn|en|zh-tw\//.test(query?.tagsViewName) || /\/(zh-cn|en|zh-tw)\//.test(params?.tagsViewName)) {
+		if (/\/zh-cn|en|zh-tw\//.test(query?.tagsViewName) || /\/zh-cn|en|zh-tw\//.test(params?.tagsViewName)) {
 			// 国际化
 			const urlTagsParams = (query?.tagsViewName && JSON.parse(query?.tagsViewName)) || (params?.tagsViewName && JSON.parse(params?.tagsViewName));
-			tagsViewName = urlTagsParams[i18n.global.locale];
+			tagsViewName = urlTagsParams[i18n.global.locale.value];
 		} else {
 			// 非国际化
 			tagsViewName = query?.tagsViewName || params?.tagsViewName;

+ 18 - 23
Web/src/views/chart/index.vue

@@ -1,6 +1,6 @@
 <template>
-	<div class="chart-scrollbar layout-view-bg-white" :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
-		<div class="chart-warp">
+	<div class="chart-scrollbar layout-padding">
+		<div class="chart-warp layout-padding-auto layout-padding-view">
 			<div class="chart-warp-top">
 				<ChartHead />
 			</div>
@@ -202,12 +202,11 @@
 </template>
 
 <script lang="ts">
-import { toRefs, reactive, computed, onMounted, getCurrentInstance, watch, nextTick, onActivated, defineComponent } from 'vue';
+import { toRefs, reactive, onMounted, watch, nextTick, onActivated, defineComponent, ref } from 'vue';
 import ChartHead from '/@/views/chart/head.vue';
 import * as echarts from 'echarts';
 import 'echarts-wordcloud';
 import { storeToRefs } from 'pinia';
-import { useThemeConfig } from '/@/stores/themeConfig';
 import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
 import { skyList, dBtnList, chartData4List } from '/@/views/chart/chart';
 
@@ -215,10 +214,12 @@ export default defineComponent({
 	name: 'chartIndex',
 	components: { ChartHead },
 	setup() {
-		const { proxy } = <any>getCurrentInstance();
-		const storesThemeConfig = useThemeConfig();
+		const chartsCenterOneRef = ref();
+		const chartsSevenDaysRef = ref();
+		const chartsWarningRef = ref();
+		const chartsMonitorRef = ref();
+		const chartsInvestmentRef = ref();
 		const storesTagsViewRoutes = useTagsViewRoutes();
-		const { themeConfig } = storeToRefs(storesThemeConfig);
 		const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
 		const state = reactive({
 			skyList,
@@ -226,19 +227,9 @@ export default defineComponent({
 			chartData4List,
 			myCharts: [],
 		});
-		// 设置主内容的高度
-		const initTagViewHeight = computed(() => {
-			let { isTagsview } = themeConfig.value;
-			if (isTagsViewCurrenFull.value) {
-				return `30px`;
-			} else {
-				if (isTagsview) return `114px`;
-				else return `80px`;
-			}
-		});
 		// 初始化中间图表1
 		const initChartsCenterOne = () => {
-			const myChart = echarts.init(proxy.$refs.chartsCenterOneRef);
+			const myChart = echarts.init(chartsCenterOneRef.value);
 			const option = {
 				grid: {
 					top: 15,
@@ -299,7 +290,7 @@ export default defineComponent({
 		};
 		// 初始化近7天产品追溯扫码统计
 		const initChartsSevenDays = () => {
-			const myChart = echarts.init(proxy.$refs.chartsSevenDaysRef);
+			const myChart = echarts.init(chartsSevenDaysRef.value);
 			const option = {
 				grid: {
 					top: 15,
@@ -344,7 +335,7 @@ export default defineComponent({
 		};
 		// 初始化近30天预警总数
 		const initChartsWarning = () => {
-			const myChart = echarts.init(proxy.$refs.chartsWarningRef);
+			const myChart = echarts.init(chartsWarningRef.value);
 			const option = {
 				grid: {
 					top: 50,
@@ -379,7 +370,7 @@ export default defineComponent({
 		};
 		// 初始化当前设备监测
 		const initChartsMonitor = () => {
-			const myChart = echarts.init(proxy.$refs.chartsMonitorRef);
+			const myChart = echarts.init(chartsMonitorRef.value);
 			const option = {
 				grid: {
 					top: 15,
@@ -419,7 +410,7 @@ export default defineComponent({
 		};
 		// 初始化近7天投入品记录
 		const initChartsInvestment = () => {
-			const myChart = echarts.init(proxy.$refs.chartsInvestmentRef);
+			const myChart = echarts.init(chartsInvestmentRef.value);
 			const option = {
 				grid: {
 					top: 15,
@@ -480,7 +471,11 @@ export default defineComponent({
 			}
 		);
 		return {
-			initTagViewHeight,
+			chartsCenterOneRef,
+			chartsSevenDaysRef,
+			chartsWarningRef,
+			chartsMonitorRef,
+			chartsInvestmentRef,
 			...toRefs(state),
 		};
 	},

+ 23 - 37
Web/src/views/error/401.vue

@@ -1,55 +1,43 @@
 <template>
-	<div class="error layout-view-bg-white" :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
-		<div class="error-flex">
-			<div class="left">
-				<div class="left-item">
-					<div class="left-item-animation left-item-num">401</div>
-					<div class="left-item-animation left-item-title">{{ $t('message.noAccess.accessTitle') }}</div>
-					<div class="left-item-animation left-item-msg">{{ $t('message.noAccess.accessMsg') }}</div>
-					<div class="left-item-animation left-item-btn">
-						<el-button type="primary" round @click="onSetAuth">{{ $t('message.noAccess.accessBtn') }}</el-button>
+	<div class="error layout-padding">
+		<div class="layout-padding-auto layout-padding-view">
+			<div class="error-flex">
+				<div class="left">
+					<div class="left-item">
+						<div class="left-item-animation left-item-num">401</div>
+						<div class="left-item-animation left-item-title">{{ $t('message.noAccess.accessTitle') }}</div>
+						<div class="left-item-animation left-item-msg">{{ $t('message.noAccess.accessMsg') }}</div>
+						<div class="left-item-animation left-item-btn">
+							<el-button type="primary" size="default" round @click="onSetAuth">{{ $t('message.noAccess.accessBtn') }}</el-button>
+						</div>
 					</div>
 				</div>
-			</div>
-			<div class="right">
-				<img
-					src="https://img-blog.csdnimg.cn/3333f265772a4fa89287993500ecbf96.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
-				/>
+				<div class="right">
+					<img
+						src="https://img-blog.csdnimg.cn/3333f265772a4fa89287993500ecbf96.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
+					/>
+				</div>
 			</div>
 		</div>
 	</div>
 </template>
 
 <script lang="ts">
-import { defineComponent, computed } from 'vue';
-import { storeToRefs } from 'pinia';
-import { useThemeConfig } from '/@/stores/themeConfig';
-import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
-import { clearAccessTokens } from '/@/utils/axios-utils';
+import { defineComponent } from 'vue';
+import { Session } from '/@/utils/storage';
 
 export default defineComponent({
 	name: '401',
 	setup() {
-		const storesThemeConfig = useThemeConfig();
-		const storesTagsViewRoutes = useTagsViewRoutes();
-		const { themeConfig } = storeToRefs(storesThemeConfig);
-		const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
 		const onSetAuth = () => {
-			clearAccessTokens();
+			// https://gitee.com/lyt-top/vue-next-admin/issues/I5C3JS
+			// 清除缓存/token等
+			Session.clear();
+			// 使用 reload 时,不需要调用 resetRoute() 重置路由
+			window.location.reload();
 		};
-		// 设置主内容的高度
-		const initTagViewHeight = computed(() => {
-			let { isTagsview } = themeConfig.value;
-			if (isTagsViewCurrenFull.value) {
-				return `30px`;
-			} else {
-				if (isTagsview) return `114px`;
-				else return `80px`;
-			}
-		});
 		return {
 			onSetAuth,
-			initTagViewHeight,
 		};
 	},
 });
@@ -58,8 +46,6 @@ export default defineComponent({
 <style scoped lang="scss">
 .error {
 	height: 100%;
-	background-color: var(--el-color-white);
-	display: flex;
 	.error-flex {
 		margin: auto;
 		display: flex;

+ 17 - 35
Web/src/views/error/404.vue

@@ -1,56 +1,40 @@
 <template>
-	<div class="error layout-view-bg-white" :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
-		<div class="error-flex">
-			<div class="left">
-				<div class="left-item">
-					<div class="left-item-animation left-item-num">404</div>
-					<div class="left-item-animation left-item-title">{{ $t('message.notFound.foundTitle') }}</div>
-					<div class="left-item-animation left-item-msg">{{ $t('message.notFound.foundMsg') }}</div>
-					<div class="left-item-animation left-item-btn">
-						<el-button type="primary" round @click="onGoHome">{{ $t('message.notFound.foundBtn') }}</el-button>
+	<div class="error layout-padding">
+		<div class="layout-padding-auto layout-padding-view">
+			<div class="error-flex">
+				<div class="left">
+					<div class="left-item">
+						<div class="left-item-animation left-item-num">404</div>
+						<div class="left-item-animation left-item-title">{{ $t('message.notFound.foundTitle') }}</div>
+						<div class="left-item-animation left-item-msg">{{ $t('message.notFound.foundMsg') }}</div>
+						<div class="left-item-animation left-item-btn">
+							<el-button type="primary" size="default" round @click="onGoHome">{{ $t('message.notFound.foundBtn') }}</el-button>
+						</div>
 					</div>
 				</div>
-			</div>
-			<div class="right">
-				<img
-					src="https://img-blog.csdnimg.cn/9eb1d85a417f4ed1ba7107f149ce3da1.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
-				/>
+				<div class="right">
+					<img
+						src="https://img-blog.csdnimg.cn/9eb1d85a417f4ed1ba7107f149ce3da1.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
+					/>
+				</div>
 			</div>
 		</div>
 	</div>
 </template>
 
 <script lang="ts">
-import { defineComponent, computed } from 'vue';
+import { defineComponent } from 'vue';
 import { useRouter } from 'vue-router';
-import { storeToRefs } from 'pinia';
-import { useThemeConfig } from '/@/stores/themeConfig';
-import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
 
 export default defineComponent({
 	name: '404',
 	setup() {
-		const storesThemeConfig = useThemeConfig();
-		const storesTagsViewRoutes = useTagsViewRoutes();
-		const { themeConfig } = storeToRefs(storesThemeConfig);
-		const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
 		const router = useRouter();
 		const onGoHome = () => {
 			router.push('/');
 		};
-		// 设置主内容的高度
-		const initTagViewHeight = computed(() => {
-			let { isTagsview } = themeConfig.value;
-			if (isTagsViewCurrenFull.value) {
-				return `30px`;
-			} else {
-				if (isTagsview) return `114px`;
-				else return `80px`;
-			}
-		});
 		return {
 			onGoHome,
-			initTagViewHeight,
 		};
 	},
 });
@@ -59,8 +43,6 @@ export default defineComponent({
 <style scoped lang="scss">
 .error {
 	height: 100%;
-	background-color: var(--el-color-white);
-	display: flex;
 	.error-flex {
 		margin: auto;
 		display: flex;

+ 16 - 14
Web/src/views/fun/clipboard/index.vue

@@ -1,18 +1,20 @@
 <template>
-	<el-card shadow="hover" header="复制剪切演示">
-		<el-alert
-			title="感谢优秀的 `vue-clipboard3`,项目地址:https://github.com/JamieCurnow/vue-clipboard3`"
-			type="success"
-			:closable="false"
-			class="mb15"
-		></el-alert>
-		<el-input placeholder="请输入内容" v-model="copyVal">
-			<template #append>
-				<el-button @click="copyText(copyVal)">复制链接</el-button>
-			</template>
-		</el-input>
-		<el-input placeholder="先点击上方 `复制链接` 按钮,然后 `Ctrl + V` 进行粘贴! " v-model="shearVal" class="mt15"> </el-input>
-	</el-card>
+	<div class="layout-pd">
+		<el-card shadow="hover" header="复制剪切演示">
+			<el-alert
+				title="感谢优秀的 `vue-clipboard3`,项目地址:https://github.com/JamieCurnow/vue-clipboard3`"
+				type="success"
+				:closable="false"
+				class="mb15"
+			></el-alert>
+			<el-input placeholder="请输入内容" v-model="copyVal">
+				<template #append>
+					<el-button @click="copyText(copyVal)">复制链接</el-button>
+				</template>
+			</el-input>
+			<el-input placeholder="先点击上方 `复制链接` 按钮,然后 `Ctrl + V` 进行粘贴! " v-model="shearVal" class="mt15"> </el-input>
+		</el-card>
+	</div>
 </template>
 
 <script lang="ts">

+ 1 - 1
Web/src/views/fun/countup/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div>
+	<div class="layout-pd">
 		<el-card shadow="hover" header="数字滚动演示">
 			<el-alert
 				title="感谢优秀的 `countup.js`,项目地址:https://github.com/inorganik/countUp.js"

+ 6 - 5
Web/src/views/fun/cropper/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="croppers-container">
+	<div class="croppers-container layout-pd">
 		<el-card shadow="hover" header="cropper 图片裁剪">
 			<el-alert
 				title="感谢优秀的 `cropperjs`,项目地址:https://github.com/fengyuanchen/cropperjs"
@@ -24,16 +24,17 @@
 </template>
 
 <script lang="ts">
-import { ref, toRefs, reactive, defineComponent } from 'vue';
-import CropperDialog from '/@/components/cropper/index.vue';
+import { defineAsyncComponent, ref, toRefs, reactive, defineComponent } from 'vue';
 
 export default defineComponent({
 	name: 'funCropper',
-	components: { CropperDialog },
+	components: {
+		CropperDialog: defineAsyncComponent(() => import('/@/components/cropper/index.vue')),
+	},
 	setup() {
 		const cropperDialogRef = ref();
 		const state = reactive({
-			cropperImg: 'https://img1.baidu.com/it/u=2813520958,2218166536&fm=26&fmt=auto&gp=0.jpg',
+			cropperImg: 'https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500',
 		});
 		// 打开裁剪弹窗
 		const onCropperDialogOpen = () => {

+ 3 - 21
Web/src/views/fun/echartsMap/index.vue

@@ -1,42 +1,25 @@
 <template>
-	<div :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
-		<div class="layout-view-bg-white">
+	<div class="layout-padding">
+		<div class="layout-padding-auto layout-padding-view">
 			<div ref="echartsMap" style="height: 100%"></div>
 		</div>
 	</div>
 </template>
 
 <script lang="ts">
-import { toRefs, reactive, computed, onMounted, defineComponent } from 'vue';
+import { toRefs, reactive, onMounted, defineComponent } from 'vue';
 import * as echarts from 'echarts';
 import 'echarts/extension/bmap/bmap';
-import { storeToRefs } from 'pinia';
-import { useThemeConfig } from '/@/stores/themeConfig';
-import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
 import { echartsMapList, echartsMapData } from './mock';
 
 export default defineComponent({
 	name: 'funEchartsMap',
 	setup() {
-		const storesThemeConfig = useThemeConfig();
-		const storesTagsViewRoutes = useTagsViewRoutes();
-		const { themeConfig } = storeToRefs(storesThemeConfig);
-		const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
 		const state: any = reactive({
 			echartsMap: null,
 			echartsMapList,
 			echartsMapData,
 		});
-		// 设置主内容的高度
-		const initTagViewHeight = computed(() => {
-			let { isTagsview } = themeConfig.value;
-			if (isTagsViewCurrenFull.value) {
-				return `30px`;
-			} else {
-				if (isTagsview) return `114px`;
-				else return `80px`;
-			}
-		});
 		// echartsMap 将坐标信息和对应物理量的值合在一起
 		const convertData = (data: any) => {
 			let res = [];
@@ -133,7 +116,6 @@ export default defineComponent({
 			initEchartsMap();
 		});
 		return {
-			initTagViewHeight,
 			...toRefs(state),
 		};
 	},

+ 1 - 1
Web/src/views/fun/gridLayout/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="grid-layout-container">
+	<div class="grid-layout-container layout-pd">
 		<el-card shadow="hover" header="vue-grid-layout 拖拽布局演示">
 			<el-alert
 				title="感谢优秀的 `vue-grid-layout`,项目地址:https://github.com/jbaysolutions/vue-grid-layout"

+ 1 - 1
Web/src/views/fun/printJs/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div ref="printRef">
+	<div ref="printRef" class="layout-pd">
 		<el-card shadow="hover" header="打印演示">
 			<el-alert
 				title="感谢优秀的 `print-js`,项目地址:https://github.com/crabbly/Print.js。请在打印弹窗 `更多设置` 中开启 `背景图形。`"

+ 6 - 5
Web/src/views/fun/qrcode/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="qrcode-container">
+	<div class="qrcode-container layout-pd">
 		<el-card shadow="hover" header="qrcodejs2 二维码生成">
 			<el-alert
 				title="感谢优秀的 `qrcodejs2`,项目地址:https://github.com/davidshimjs/qrcodejs"
@@ -23,19 +23,19 @@
 </template>
 
 <script lang="ts">
-import { toRefs, reactive, onMounted, getCurrentInstance, defineComponent } from 'vue';
+import { toRefs, reactive, onMounted, defineComponent, ref } from 'vue';
 import QRCode from 'qrcodejs2-fixes';
 
 export default defineComponent({
 	name: 'funQrcode',
 	setup() {
-		const { proxy } = getCurrentInstance() as any;
+		const qrcodeRef = ref();
 		const state = reactive({
 			qrcode: '',
 		});
 		// 初始化生成二维码
 		const initQrcode = () => {
-			new QRCode(proxy.$refs.qrcodeRef, {
+			new QRCode(qrcodeRef.value, {
 				text: `https://lyt-top.gitee.io/vue-next-admin-preview/#/login?t=${new Date().getTime()}`,
 				width: 125,
 				height: 125,
@@ -45,7 +45,7 @@ export default defineComponent({
 		};
 		// 重新生成
 		const onInitQrcode = () => {
-			proxy.$refs.qrcodeRef.innerHTML = '';
+			qrcodeRef.value.innerHTML = '';
 			initQrcode();
 		};
 		// 页面加载时
@@ -53,6 +53,7 @@ export default defineComponent({
 			initQrcode();
 		});
 		return {
+			qrcodeRef,
 			onInitQrcode,
 			...toRefs(state),
 		};

+ 1 - 1
Web/src/views/fun/splitpanes/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="splitpanes-container">
+	<div class="splitpanes-container layout-pd">
 		<el-card shadow="hover" header="splitpanes 窗格拆分器">
 			<el-alert
 				title="感谢优秀的 `splitpanes`,项目地址:https://github.com/antoniandre/splitpanes"

+ 11 - 10
Web/src/views/fun/tagsView/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="fun-tagsview">
+	<div class="fun-tagsview layout-pd">
 		<NoticeBar
 			text="已删除非当前页 tagsView 演示,后续有时间可以再加回来!,tagsview 支持多标签(参数不同)、单标签共用(参数不同)"
 			background="#ecf5ff"
@@ -63,37 +63,38 @@
 </template>
 
 <script lang="ts">
-import { getCurrentInstance, reactive, toRefs, defineComponent } from 'vue';
-import NoticeBar from '/@/components/noticeBar/index.vue';
+import { defineAsyncComponent, reactive, toRefs, defineComponent } from 'vue';
 import { useRoute } from 'vue-router';
+import mittBus from '/@/utils/mitt';
 
 export default defineComponent({
 	name: 'funTagsView',
-	components: { NoticeBar },
+	components: {
+		NoticeBar: defineAsyncComponent(() => import('/@/components/noticeBar/index.vue')),
+	},
 	setup() {
-		const { proxy } = getCurrentInstance() as any;
 		const route = useRoute();
 		const state = reactive({});
 		// 0 刷新当前,1 关闭当前,2 关闭其它,3 关闭全部 4 当前页全屏
 		// 1、刷新当前 tagsView
 		const refreshCurrentTagsView = () => {
-			proxy.mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 0, ...route }));
+			mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 0, ...route }));
 		};
 		// 2、关闭当前 tagsView
 		const closeCurrentTagsView = () => {
-			proxy.mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 1, ...route }));
+			mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 1, ...route }));
 		};
 		// 3、关闭其它 tagsView
 		const closeOtherTagsView = () => {
-			proxy.mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 2, ...route }));
+			mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 2, ...route }));
 		};
 		// 4、关闭全部 tagsView
 		const closeAllTagsView = () => {
-			proxy.mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 3, ...route }));
+			mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 3, ...route }));
 		};
 		// 5、开启当前页面全屏
 		const openCurrenFullscreen = () => {
-			proxy.mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 4, ...route }));
+			mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 4, ...route }));
 		};
 		return {
 			refreshCurrentTagsView,

+ 10 - 8
Web/src/views/fun/wangEditor/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="editor-container">
+	<div class="editor-container layout-pd">
 		<el-card shadow="hover" header="wangeditor富文本编辑器">
 			<el-alert
 				title="感谢优秀的 `wangeditor`,项目地址:https://github.com/wangeditor-team/wangEditor"
@@ -7,24 +7,26 @@
 				:closable="false"
 				class="mb15"
 			></el-alert>
-			<Editor :is-disable="false" v-model="editorVal" />
+			<Editor v-model="editor.value" :disable="editor.disable" />
 		</el-card>
 	</div>
 </template>
 
 <script lang="ts">
-import { toRefs, reactive, onMounted, defineComponent } from 'vue';
-import Editor from '/@/components/editor/index.vue';
+import { defineAsyncComponent, toRefs, reactive, defineComponent } from 'vue';
 
 export default defineComponent({
 	name: 'funWangEditor',
-	components: { Editor },
+	components: {
+		Editor: defineAsyncComponent(() => import('/@/components/editor/index.vue')),
+	},
 	setup() {
 		const state = reactive({
-			editorVal: '',
+			editor: {
+				value: '<p><span style="color: rgb(51, 51, 51); background-color: rgb(255, 255, 255); font-size: 14px;">胡歌,1982年9月20日出生于上海市徐汇区,中国内地影视男演员、流行乐歌手,</span><a href="https://baike.baidu.com/item/%E6%B0%91%E7%9B%9F/1971441?fromModule=lemma_inlink" target="_blank" style="text-indent: 28px; text-align: start;">民盟</a><span style="color: rgb(51, 51, 51); background-color: rgb(255, 255, 255); font-size: 14px;">盟员</span><span style="color: rgb(51, 102, 204); background-color: rgb(255, 255, 255); font-size: 12px;"><sup> [1]</sup></span><a href="" target="" style="text-indent: 28px; text-align: start;"> </a><span style="color: rgb(51, 51, 51); background-color: rgb(255, 255, 255); font-size: 14px;"> ,毕业于</span><a href="https://baike.baidu.com/item/%E4%B8%8A%E6%B5%B7%E6%88%8F%E5%89%A7%E5%AD%A6%E9%99%A2/1736818?fromModule=lemma_inlink" target="_blank" style="text-indent: 28px; text-align: start;">上海戏剧学院</a><span style="color: rgb(51, 51, 51); background-color: rgb(255, 255, 255); font-size: 14px;">表演系。</span></p>',
+				disable: false,
+			},
 		});
-		// 页面加载时
-		onMounted(() => {});
 		return {
 			...toRefs(state),
 		};

+ 4 - 4
Web/src/views/home/notice/index.vue

@@ -70,8 +70,9 @@
 </template>
 
 <script lang="ts">
-import { toRefs, reactive, onMounted, defineComponent, onUnmounted, getCurrentInstance } from 'vue';
+import { toRefs, reactive, onMounted, defineComponent, onUnmounted } from 'vue';
 import commonFunction from '/@/utils/commonFunction';
+import mittBus from '/@/utils/mitt';
 
 import { getAPI } from '/@/utils/axios-utils';
 import { SysNoticeApi } from '/@/api-services/api';
@@ -81,7 +82,6 @@ export default defineComponent({
 	name: 'notice',
 	components: {},
 	setup() {
-		const { proxy } = getCurrentInstance() as any;
 		const { removeHtml } = commonFunction();
 		const state = reactive({
 			loading: false,
@@ -102,12 +102,12 @@ export default defineComponent({
 		onMounted(async () => {
 			handleQuery();
 
-			proxy.mittBus.on('submitRefresh', () => {
+			mittBus.on('submitRefresh', () => {
 				handleQuery();
 			});
 		});
 		onUnmounted(() => {
-			proxy.mittBus.off('submitRefresh');
+			mittBus.off('submitRefresh', () => {});
 		});
 		// 查询操作
 		const handleQuery = async () => {

+ 1 - 1
Web/src/views/limits/backEnd/page/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div>
+	<div class="layout-pd">
 		<el-alert
 			title="温馨提示:1、此页面无法模拟后端控制路由,因为 `gitee` 上所请求的 `json` 菜单数据线上会出现跨域的情况(json地址:
       https://gitee.com/lyt-top/vue-next-admin-images/raw/master/menu/adminMenu.json)。2、本地接口请求文件位置:`/src/api/menu/index.ts`。

+ 9 - 8
Web/src/views/limits/frontEnd/btn/index.vue

@@ -1,6 +1,6 @@
 <template>
-	<div>
-		<LimitsFrontEndPage />
+	<div class="layout-pd">
+		<LimitsFrontEndPage style="padding: 0 !important" />
 		<!-- 演示1:组件方式 -->
 		<el-card shadow="hover" header="演示1:组件方式" class="mt15">
 			<el-row class="mb10" style="color: #808080">单个权限验证(:value="xxx"):</el-row>
@@ -335,17 +335,18 @@
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue';
+import { defineAsyncComponent, defineComponent } from 'vue';
 import { ElMessage } from 'element-plus';
-import LimitsFrontEndPage from '/@/views/limits/frontEnd/page/index.vue';
-import Auth from '/@/components/auth/auth.vue';
-import Auths from '/@/components/auth/auths.vue';
-import AuthAll from '/@/components/auth/authAll.vue';
 import { auth, auths, authAll } from '/@/utils/authFunction';
 
 export default defineComponent({
 	name: 'limitsFrontEndBtn',
-	components: { LimitsFrontEndPage, Auth, Auths, AuthAll },
+	components: {
+		LimitsFrontEndPage: defineAsyncComponent(() => import('/@/views/limits/frontEnd/page/index.vue')),
+		Auth: defineAsyncComponent(() => import('/@/components/auth/auth.vue')),
+		Auths: defineAsyncComponent(() => import('/@/components/auth/auths.vue')),
+		AuthAll: defineAsyncComponent(() => import('/@/components/auth/authAll.vue')),
+	},
 	setup() {
 		// 单个权限验证
 		const onAuthClick = () => {

+ 1 - 1
Web/src/views/limits/frontEnd/page/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div>
+	<div class="layout-pd">
 		<el-alert
 			title="温馨提示:此权限页面代码及效果只作为演示使用,若出现不可逆转的bug,请尝试 `F5` 刷新页面。若实际项目中非要实现此用户权限切换功能,
       请在切换方法 `onRadioChange` 最后面添加刷新代码 `window.location.reload()`。 请注意:按钮权限页面中的演示2(指令模式)、演示3(函数模式)

+ 10 - 4
Web/src/views/login/component/account.vue

@@ -1,7 +1,7 @@
 <template>
 	<el-form ref="ruleFormRef" :model="ruleForm" size="large" :rules="rules" class="login-content-form">
 		<el-form-item class="login-animation1" prop="account">
-			<el-input type="text" placeholder="请输入账号" v-model="ruleForm.account" clearable autocomplete="off">
+			<el-input text placeholder="请输入账号" v-model="ruleForm.account" clearable autocomplete="off">
 				<template #prefix>
 					<el-icon>
 						<ele-User />
@@ -23,7 +23,7 @@
 		</el-form-item>
 		<el-form-item class="login-animation3" prop="captcha" v-show="captchaEnabled">
 			<el-col :span="15">
-				<el-input type="text" maxlength="4" :placeholder="$t('message.account.accountPlaceholder3')" v-model="ruleForm.code" clearable autocomplete="off" @keyup.enter="openVerify">
+				<el-input text maxlength="4" :placeholder="$t('message.account.accountPlaceholder3')" v-model="ruleForm.code" clearable autocomplete="off" @keyup.enter="openVerify">
 					<template #prefix>
 						<el-icon>
 							<ele-Position />
@@ -39,7 +39,7 @@
 			</el-col>
 		</el-form-item>
 		<el-form-item class="login-animation4">
-			<el-button type="primary" class="login-content-submit" round @click="openVerify" :loading="loading.signIn">
+			<el-button type="primary" class="login-content-submit" round v-waves @click="openVerify" :loading="loading.signIn">
 				<span>{{ $t('message.account.accountBtnText') }}</span>
 			</el-button>
 		</el-form-item>
@@ -47,7 +47,7 @@
 	</el-form>
 
 	<div class="dialog-header">
-		<el-dialog v-model="verifyVisible" width="300px" center :show-close="false">
+		<el-dialog v-model="verifyVisible" width="290px" center :show-close="false" :modal-append-to-body="false">
 			<DragVerifyImgRotate
 				ref="dragRef"
 				:imgsrc="verifyImg"
@@ -210,6 +210,12 @@ export default defineComponent({
 		.el-dialog__header {
 			display: none;
 		}
+		.el-dialog__wrapper {
+			position: absolute !important;
+		}
+		.v-modal {
+			position: absolute !important;
+		}
 	}
 }
 

+ 4 - 4
Web/src/views/login/component/mobile.vue

@@ -1,7 +1,7 @@
 <template>
 	<el-form size="large" class="login-content-form">
 		<el-form-item class="login-animation1">
-			<el-input type="text" :placeholder="$t('message.mobile.placeholder1')" v-model="ruleForm.userName" clearable autocomplete="off">
+			<el-input text :placeholder="$t('message.mobile.placeholder1')" v-model="ruleForm.userName" clearable autocomplete="off">
 				<template #prefix>
 					<i class="iconfont icon-dianhua el-input__icon"></i>
 				</template>
@@ -9,7 +9,7 @@
 		</el-form-item>
 		<el-form-item class="login-animation2">
 			<el-col :span="15">
-				<el-input type="text" maxlength="4" :placeholder="$t('message.mobile.placeholder2')" v-model="ruleForm.code" clearable autocomplete="off">
+				<el-input text maxlength="4" :placeholder="$t('message.mobile.placeholder2')" v-model="ruleForm.code" clearable autocomplete="off">
 					<template #prefix>
 						<el-icon class="el-input__icon"><ele-Position /></el-icon>
 					</template>
@@ -17,11 +17,11 @@
 			</el-col>
 			<el-col :span="1"></el-col>
 			<el-col :span="8">
-				<el-button class="login-content-code">{{ $t('message.mobile.codeText') }}</el-button>
+				<el-button v-waves class="login-content-code">{{ $t('message.mobile.codeText') }}</el-button>
 			</el-col>
 		</el-form-item>
 		<el-form-item class="login-animation3">
-			<el-button round type="primary" class="login-content-submit">
+			<el-button round type="primary" v-waves class="login-content-submit">
 				<span>{{ $t('message.mobile.btnText') }}</span>
 			</el-button>
 		</el-form-item>

+ 21 - 11
Web/src/views/login/component/scan.vue

@@ -1,12 +1,15 @@
 <template>
 	<div class="login-scan-container">
 		<div ref="qrcodeRef"></div>
-		<div class="font12 mt20 login-msg">{{ $t('message.scan.text') }}</div>
+		<div class="font12 mt20 login-msg">
+			<i class="iconfont icon-saoyisao mr5"></i>
+			<span>{{ $t('message.scan.text') }}</span>
+		</div>
 	</div>
 </template>
 
 <script lang="ts">
-import { ref, defineComponent, onMounted } from 'vue';
+import { defineComponent, ref, onMounted, nextTick } from 'vue';
 import QRCode from 'qrcodejs2-fixes';
 
 export default defineComponent({
@@ -15,20 +18,24 @@ export default defineComponent({
 		const qrcodeRef = ref<HTMLElement | null>(null);
 		// 初始化生成二维码
 		const initQrcode = () => {
-			(qrcodeRef.value as HTMLElement).innerHTML = '';
-			new QRCode(qrcodeRef.value, {
-				text: `https://gitee.com/zuohuaijun/Admin.NET`,
-				width: 260,
-				height: 260,
-				colorDark: '#000000',
-				colorLight: '#ffffff',
+			nextTick(() => {
+				(<HTMLElement>qrcodeRef.value).innerHTML = '';
+				new QRCode(qrcodeRef.value, {
+					text: `https://gitee.com/zuohuaijun/Admin.NET`,
+					width: 260,
+					height: 260,
+					colorDark: '#000000',
+					colorLight: '#ffffff',
+				});
 			});
 		};
 		// 页面加载时
 		onMounted(() => {
 			initQrcode();
 		});
-		return { qrcodeRef };
+		return {
+			qrcodeRef,
+		};
 	},
 });
 </script>
@@ -41,7 +48,7 @@ export default defineComponent({
 	animation-fill-mode: forwards;
 }
 .login-scan-container {
-	padding: 20px;
+	padding: 0 20px 20px;
 	display: flex;
 	flex-direction: column;
 	text-align: center;
@@ -51,6 +58,9 @@ export default defineComponent({
 		margin: auto;
 	}
 	.login-msg {
+		display: flex;
+		align-items: center;
+		justify-content: center;
 		color: var(--el-text-color-placeholder);
 		@extend .login-scan-animation;
 		animation-delay: 0.2s;

+ 181 - 120
Web/src/views/login/index.vue

@@ -1,11 +1,14 @@
 <template>
-	<div class="login-container">
-		<div class="login-icon-group">
-			<!-- <div class="login-icon-group-title">
+	<div class="login-container flex">
+		<div class="login-left flex-margin">
+			<div class="login-left-logo">
 				<img :src="logoMini" />
-				<div class="login-icon-group-title-text font25">{{ getThemeConfig.globalViceTitle }}</div>
-			</div> -->
-			<el-carousel height="550px" style="width: 100%; padding-right: 38%; top: 50%; transform: translateY(-50%) translate3d(0, 0, 0)">
+				<div class="login-left-logo-text">
+					<span>{{ getThemeConfig.globalViceTitle }}</span>
+					<span class="login-left-logo-text-msg">{{ getThemeConfig.globalViceTitleMsg }}</span>
+				</div>
+			</div>
+			<el-carousel height="500px">
 				<el-carousel-item>
 					<img :src="loginIconTwo" class="login-icon-group-icon" />
 				</el-carousel-item>
@@ -17,23 +20,29 @@
 				</el-carousel-item>
 			</el-carousel>
 		</div>
-		<div class="login-content">
-			<div class="login-content-main">
-				<h4 class="login-content-title">{{ getThemeConfig.globalTitle }}</h4>
-				<div v-if="!isScan">
-					<el-tabs v-model="tabsActiveName">
-						<el-tab-pane :label="$t('message.label.one1')" name="account">
-							<Account />
-						</el-tab-pane>
-						<el-tab-pane :label="$t('message.label.two2')" name="mobile">
-							<Mobile />
-						</el-tab-pane>
-					</el-tabs>
-				</div>
-				<Scan v-if="isScan" />
-				<div class="login-content-main-sacn" @click="isScan = !isScan">
-					<i class="iconfont" :class="isScan ? 'icon-diannao1' : 'icon-barcode-qr'"></i>
-					<div class="login-content-main-sacn-delta"></div>
+		<div class="login-right flex">
+			<div class="login-right-warp flex-margin">
+				<span class="login-right-warp-one"></span>
+				<span class="login-right-warp-two"></span>
+				<div class="login-right-warp-mian">
+					<div class="login-right-warp-main-title">{{ getThemeConfig.globalTitle }}</div>
+					<div class="login-right-warp-main-form">
+						<div v-if="!isScan">
+							<el-tabs v-model="tabsActiveName">
+								<el-tab-pane :label="$t('message.label.one1')" name="account">
+									<Account />
+								</el-tab-pane>
+								<el-tab-pane :label="$t('message.label.two2')" name="mobile">
+									<Mobile />
+								</el-tab-pane>
+							</el-tabs>
+						</div>
+						<Scan v-if="isScan" />
+						<div class="login-content-main-sacn" @click="isScan = !isScan">
+							<i class="iconfont" :class="isScan ? 'icon-diannao1' : 'icon-barcode-qr'"></i>
+							<div class="login-content-main-sacn-delta"></div>
+						</div>
+					</div>
 				</div>
 			</div>
 		</div>
@@ -41,31 +50,26 @@
 </template>
 
 <script lang="ts">
-import { toRefs, reactive, computed, defineComponent, onMounted } from 'vue';
+import { defineAsyncComponent, defineComponent, onMounted, reactive, toRefs, computed } from 'vue';
 import { storeToRefs } from 'pinia';
 import { useThemeConfig } from '/@/stores/themeConfig';
+import { NextLoading } from '/@/utils/loading';
 import logoMini from '/@/assets/logo-mini.svg';
 import loginIconTwo from '/@/assets/login-icon-two.svg';
 import loginIconTwo1 from '/@/assets/login-icon-two1.svg';
 import loginIconTwo2 from '/@/assets/login-icon-two2.svg';
-import { NextLoading } from '/@/utils/loading';
-import Account from '/@/views/login/component/account.vue';
-import Mobile from '/@/views/login/component/mobile.vue';
-import Scan from '/@/views/login/component/scan.vue';
-
-// 定义接口来定义对象的类型
-interface LoginState {
-	tabsActiveName: string;
-	isScan: boolean;
-}
 
 export default defineComponent({
 	name: 'loginIndex',
-	components: { Account, Mobile, Scan },
+	components: {
+		Account: defineAsyncComponent(() => import('/@/views/login/component/account.vue')),
+		Mobile: defineAsyncComponent(() => import('/@/views/login/component/mobile.vue')),
+		Scan: defineAsyncComponent(() => import('/@/views/login/component/scan.vue')),
+	},
 	setup() {
 		const storesThemeConfig = useThemeConfig();
 		const { themeConfig } = storeToRefs(storesThemeConfig);
-		const state = reactive<LoginState>({
+		const state = reactive({
 			tabsActiveName: 'account',
 			isScan: false,
 		});
@@ -91,35 +95,41 @@ export default defineComponent({
 
 <style scoped lang="scss">
 .login-container {
-	width: 100%;
 	height: 100%;
-	position: relative;
 	background: var(--el-color-white);
-
-	.login-icon-group {
-		width: 100%;
-		height: 100%;
-		position: relative;
-
-		.login-icon-group-title {
+	.login-left {
+		flex: 1;
+		// position: relative;
+		// background-color: rgba(211, 239, 255, 1);
+		// margin-right: 100px;
+		.login-left-logo {
+			display: flex;
+			align-items: center;
 			position: absolute;
 			top: 50px;
 			left: 80px;
-			display: flex;
-			align-items: center;
-
+			z-index: 1;
+			animation: logoAnimation 0.3s ease;
 			img {
 				width: 64px;
 				height: 64px;
 			}
-
-			// &-text {
-			// 	padding-left: 15px;
-			// 	color: var(--el-color-primary);
-			// }
+			.login-left-logo-text {
+				display: flex;
+				flex-direction: column;
+				span {
+					margin-left: 10px;
+					font-size: 30px;
+					font-weight: 700;
+					color: var(--el-color-primary);
+				}
+				.login-left-logo-text-msg {
+					font-size: 12px;
+					color: var(--el-color-primary);
+				}
+			}
 		}
-
-		&-icon {
+		.login-icon-group-icon {
 			width: 85%;
 			height: 85%;
 			position: absolute;
@@ -128,75 +138,126 @@ export default defineComponent({
 			transform: translateY(-50%) translate3d(0, 0, 0);
 		}
 	}
-
-	.login-content {
-		width: 500px;
-		height: 500px;
-		padding: 20px;
-		position: absolute;
-		right: 10%;
-		top: 50%;
-		transform: translateY(-50%) translate3d(0, 0, 0);
-		background-color: var(--el-color-white);
-		border: 1px solid var(--el-color-primary-light-8);
-		border-radius: 5px;
-		overflow: hidden;
-		z-index: 1;
-
-		.login-content-main {
-			margin: 0 auto;
-			width: 80%;
-
-			.login-content-title {
-				color: var(--el-text-color-primary);
-				font-weight: 800;
-				font-size: 32px;
-				text-align: center;
-				//letter-spacing: 4px;
-				margin: 5px 0 20px;
-				white-space: nowrap;
-				z-index: 5;
-				position: relative;
-				transition: all 0.3s ease;
-			}
-		}
-
-		.login-content-main-sacn {
-			position: absolute;
-			top: 0;
-			right: 0;
-			width: 50px;
-			height: 50px;
+	.login-right {
+		width: 700px;
+		.login-right-warp {
+			border: 1px solid var(--el-color-primary-light-3);
+			border-radius: 3px;
+			width: 500px;
+			height: 550px;
+			position: relative;
 			overflow: hidden;
-			cursor: pointer;
-			transition: all ease 0.3s;
-			color: var(--el-text-color-primary);
-
-			&-delta {
+			background-color: var(--el-color-white);
+			.login-right-warp-one,
+			.login-right-warp-two {
 				position: absolute;
-				width: 35px;
-				height: 70px;
-				z-index: 2;
-				top: 2px;
-				right: 21px;
-				background: var(--el-color-white);
-				transform: rotate(-45deg);
+				display: block;
+				width: inherit;
+				height: inherit;
+				&::before,
+				&::after {
+					content: '';
+					position: absolute;
+					z-index: 1;
+				}
 			}
-
-			&:hover {
-				opacity: 1;
-				transition: all ease 0.3s;
-				color: var(--el-color-primary) !important;
+			.login-right-warp-one {
+				&::before {
+					filter: hue-rotate(0deg);
+					top: 0px;
+					left: 0;
+					width: 100%;
+					height: 1px;
+					background: linear-gradient(90deg, transparent, var(--el-color-primary));
+					animation: loginLeft 3s linear infinite;
+				}
+				&::after {
+					filter: hue-rotate(0deg);
+					top: -100%;
+					right: 2px;
+					width: 1px;
+					height: 100%;
+					background: linear-gradient(180deg, transparent, var(--el-color-primary));
+					animation: loginTop 3s linear infinite;
+					animation-delay: 0.7s;
+				}
 			}
-
-			i {
-				width: 47px;
-				height: 50px;
-				display: inline-block;
-				font-size: 48px;
-				position: absolute;
-				right: 2px;
-				top: -1px;
+			.login-right-warp-two {
+				&::before {
+					filter: hue-rotate(0deg);
+					bottom: 2px;
+					right: -100%;
+					width: 100%;
+					height: 1px;
+					background: linear-gradient(270deg, transparent, var(--el-color-primary));
+					animation: loginRight 3s linear infinite;
+					animation-delay: 1.4s;
+				}
+				&::after {
+					filter: hue-rotate(0deg);
+					bottom: -100%;
+					left: 0px;
+					width: 1px;
+					height: 100%;
+					background: linear-gradient(360deg, transparent, var(--el-color-primary));
+					animation: loginBottom 3s linear infinite;
+					animation-delay: 2.1s;
+				}
+			}
+			.login-right-warp-mian {
+				display: flex;
+				flex-direction: column;
+				height: 100%;
+				.login-right-warp-main-title {
+					height: 130px;
+					line-height: 130px;
+					font-size: 32px;
+					font-weight: 800;
+					text-align: center;
+					//letter-spacing: 3px;
+					animation: logoAnimation 0.3s ease;
+					animation-delay: 0.3s;
+					color: var(--el-color-primary);
+				}
+				.login-right-warp-main-form {
+					flex: 1;
+					padding: 0 50px 50px;
+					.login-content-main-sacn {
+						position: absolute;
+						top: 0;
+						right: 0;
+						width: 50px;
+						height: 50px;
+						overflow: hidden;
+						cursor: pointer;
+						transition: all ease 0.3s;
+						color: var(--el-color-primary);
+						&-delta {
+							position: absolute;
+							width: 35px;
+							height: 70px;
+							z-index: 2;
+							top: 2px;
+							right: 21px;
+							background: var(--el-color-white);
+							transform: rotate(-45deg);
+						}
+						&:hover {
+							opacity: 1;
+							transition: all ease 0.3s;
+							color: var(--el-color-primary) !important;
+						}
+						i {
+							width: 47px;
+							height: 50px;
+							display: inline-block;
+							font-size: 48px;
+							position: absolute;
+							right: 1px;
+							top: 0px;
+						}
+					}
+				}
 			}
 		}
 	}

+ 5 - 4
Web/src/views/make/noticeBar/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="notice-bar-container">
+	<div class="notice-bar-container layout-pd">
 		<el-card shadow="hover" header="滚动通知栏:默认">
 			<NoticeBar
 				text="🎉🎉🔥基于vue3.x 、Typescript、vite、Element plus等,适配手机、平板、pc
@@ -48,12 +48,13 @@
 </template>
 
 <script lang="ts">
-import { toRefs, reactive, defineComponent } from 'vue';
-import NoticeBar from '/@/components/noticeBar/index.vue';
+import { defineAsyncComponent, toRefs, reactive, defineComponent } from 'vue';
 
 export default defineComponent({
 	name: 'makeNoticeBar',
-	components: { NoticeBar },
+	components: {
+		NoticeBar: defineAsyncComponent(() => import('/@/components/noticeBar/index.vue')),
+	},
 	setup() {
 		const state = reactive({
 			noticeList: [

+ 5 - 4
Web/src/views/make/selector/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="selector-container">
+	<div class="selector-container layout-pd">
 		<el-card shadow="hover" header="图标选择器(宽度自动):">
 			<IconSelector @get="onGetIcon" @clear="onClearIcon" v-model="modelIcon" />
 		</el-card>
@@ -26,12 +26,13 @@
 </template>
 
 <script lang="ts">
-import { toRefs, reactive, defineComponent } from 'vue';
-import IconSelector from '/@/components/iconSelector/index.vue';
+import { defineAsyncComponent, toRefs, reactive, defineComponent } from 'vue';
 
 export default defineComponent({
 	name: 'makeSelector',
-	components: { IconSelector },
+	components: {
+		IconSelector: defineAsyncComponent(() => import('/@/components/iconSelector/index.vue')),
+	},
 	setup() {
 		const state = reactive({
 			modelIcon: '',

+ 1 - 1
Web/src/views/make/svgDemo/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="svg-demo-container">
+	<div class="svg-demo-container layout-pd">
 		<el-card shadow="hover" header="svgIcon:演示(支持本地svg)">
 			<SvgIcon name="iconfont icon-shuju1" color="red" :size="30" />
 			<SvgIcon name="ele-Trophy" color="var(--el-color-primary)" :size="30" />

+ 1 - 1
Web/src/views/menu/menu1/menu11/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div>
+	<div class="layout-pd">
 		<el-input v-model="val" placeholder="menu11:请输入内容测试路由缓存"></el-input>
 	</div>
 </template>

+ 1 - 1
Web/src/views/menu/menu1/menu12/menu121/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div>
+	<div class="layout-pd">
 		<el-input v-model="val" placeholder="menu121:请输入内容测试路由缓存"></el-input>
 	</div>
 </template>

+ 1 - 1
Web/src/views/menu/menu1/menu12/menu122/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div>
+	<div class="layout-pd">
 		<el-input v-model="val" placeholder="menu122:请输入内容测试路由缓存"></el-input>
 	</div>
 </template>

+ 1 - 1
Web/src/views/menu/menu1/menu13/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div>
+	<div class="layout-pd">
 		<el-input v-model="val" placeholder="menu13:请输入内容测试路由缓存"></el-input>
 	</div>
 </template>

+ 1 - 1
Web/src/views/menu/menu2/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div>
+	<div class="layout-pd">
 		<el-input v-model="val" placeholder="menu2:请输入内容测试路由缓存"></el-input>
 	</div>
 </template>

+ 1 - 1
Web/src/views/pages/awesome/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="awesome-container">
+	<div class="awesome-container layout-pd">
 		<el-card shadow="hover" :header="`fontawesome 字体图标(自动载入):${sheetsIconList.length - 24}个`">
 			<el-row class="iconfont-row">
 				<el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="2" v-for="(v, k) in sheetsIconList" :key="k">

+ 1 - 1
Web/src/views/pages/drag/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="drag-container">
+	<div class="drag-container layout-pd">
 		<el-card shadow="hover" header="拖动指令效果(v-drag)作用于 Dialog 对话框">
 			<el-button type="primary" @click="dialogVisible = true" size="default">
 				<el-icon>

+ 8 - 6
Web/src/views/pages/dynamicForm/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="dynamic-form-container">
+	<div class="dynamic-form-container layout-pd">
 		<el-card shadow="hover" header="动态复杂表单">
 			<el-form :model="form" ref="formRulesOneRef" size="default" label-width="100px" class="mt35">
 				<el-row :gutter="35">
@@ -112,7 +112,8 @@
 </template>
 
 <script lang="ts">
-import { toRefs, reactive, onMounted, getCurrentInstance, defineComponent } from 'vue';
+import { toRefs, reactive, onMounted, defineComponent, ref } from 'vue';
+import { ElMessage } from 'element-plus';
 import { formData } from './mock';
 
 // 定义接口来定义对象的类型
@@ -146,7 +147,7 @@ interface DynamicFormState {
 export default defineComponent({
 	name: 'pagesDynamicForm',
 	setup() {
-		const { proxy } = <any>getCurrentInstance();
+		const formRulesOneRef = ref();
 		const state = reactive<DynamicFormState>({
 			formData,
 			form: {
@@ -178,9 +179,9 @@ export default defineComponent({
 		};
 		// 表单验证
 		const onSubmitForm = () => {
-			proxy.$refs.formRulesOneRef.validate((valid: boolean) => {
+			formRulesOneRef.value.validate((valid: boolean) => {
 				if (valid) {
-					proxy.$message.success('验证成功');
+					ElMessage.success('验证成功');
 				} else {
 					return false;
 				}
@@ -188,11 +189,12 @@ export default defineComponent({
 		};
 		// 重置表单
 		const onResetForm = () => {
-			proxy.$refs.formRulesOneRef.resetFields();
+			formRulesOneRef.value.resetFields();
 		};
 		// 页面加载时
 		onMounted(() => {});
 		return {
+			formRulesOneRef,
 			onAddRow,
 			onDelRow,
 			onSubmitForm,

+ 1 - 1
Web/src/views/pages/element/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="element-container">
+	<div class="element-container layout-pd">
 		<el-card shadow="hover" :header="`element plus 字体图标(自动载入,增加了 ele- 前缀,使用时:ele-Aim):${sheetsIconList.length}个`">
 			<el-row class="iconfont-row">
 				<el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="2" v-for="(v, k) in sheetsIconList" :key="k">

+ 3 - 25
Web/src/views/pages/filtering/details.vue

@@ -1,6 +1,6 @@
 <template>
-	<div :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
-		<div class="layout-view-bg-white">
+	<div class="layout-padding">
+		<div class="layout-padding-auto layout-padding-view">
 			<div class="w100 h100 flex">
 				<div class="flex-margin color-primary">filtering-details 测试界面</div>
 			</div>
@@ -9,31 +9,9 @@
 </template>
 
 <script lang="ts">
-import { computed, defineComponent } from 'vue';
-import { storeToRefs } from 'pinia';
-import { useThemeConfig } from '/@/stores/themeConfig';
-import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
+import { defineComponent } from 'vue';
 
 export default defineComponent({
 	name: 'pagesFilteringDetails',
-	setup() {
-		const storesTagsViewRoutes = useTagsViewRoutes();
-		const storesThemeConfig = useThemeConfig();
-		const { themeConfig } = storeToRefs(storesThemeConfig);
-		const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
-		// 设置主内容的高度
-		const initTagViewHeight = computed(() => {
-			let { isTagsview } = themeConfig.value;
-			if (isTagsViewCurrenFull.value) {
-				return `30px`;
-			} else {
-				if (isTagsview) return `114px`;
-				else return `80px`;
-			}
-		});
-		return {
-			initTagViewHeight,
-		};
-	},
 });
 </script>

+ 3 - 25
Web/src/views/pages/filtering/details1.vue

@@ -1,6 +1,6 @@
 <template>
-	<div :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
-		<div class="layout-view-bg-white">
+	<div class="layout-padding">
+		<div class="layout-padding-auto layout-padding-view">
 			<div class="w100 h100 flex">
 				<div class="flex-margin color-primary">测试界面</div>
 			</div>
@@ -9,31 +9,9 @@
 </template>
 
 <script lang="ts">
-import { computed, defineComponent } from 'vue';
-import { storeToRefs } from 'pinia';
-import { useThemeConfig } from '/@/stores/themeConfig';
-import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
+import { defineComponent } from 'vue';
 
 export default defineComponent({
 	name: 'pagesFilteringDetails1',
-	setup() {
-		const storesTagsViewRoutes = useTagsViewRoutes();
-		const storesThemeConfig = useThemeConfig();
-		const { themeConfig } = storeToRefs(storesThemeConfig);
-		const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
-		// 设置主内容的高度
-		const initTagViewHeight = computed(() => {
-			let { isTagsview } = themeConfig.value;
-			if (isTagsViewCurrenFull.value) {
-				return `30px`;
-			} else {
-				if (isTagsview) return `114px`;
-				else return `80px`;
-			}
-		});
-		return {
-			initTagViewHeight,
-		};
-	},
 });
 </script>

+ 1 - 1
Web/src/views/pages/filtering/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="filtering">
+	<div class="filtering layout-pd">
 		<el-card
 			shadow="hover"
 			class="filtering-list br-top-no"

+ 1 - 1
Web/src/views/pages/formAdapt/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="form-adapt-container">
+	<div class="form-adapt-container layout-pd">
 		<el-card shadow="hover" header="表单自适应演示(改变窗口查看效果)">
 			<el-form :model="form" size="default" label-width="100px" class="mt35 mb35">
 				<el-row :gutter="35">

+ 5 - 4
Web/src/views/pages/formI18n/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="form-i18n-container">
+	<div class="form-i18n-container layout-pd">
 		<el-card shadow="hover" header="表单国际化演示(不适用于动态项 form-item)">
 			<div style="text-align: center; margin-top: 15px">
 				<el-radio-group v-model="radio" size="default" @change="onRadioChange">
@@ -32,12 +32,13 @@
 </template>
 
 <script lang="ts">
-import { toRefs, reactive, defineComponent, getCurrentInstance } from 'vue';
+import { toRefs, reactive, defineComponent } from 'vue';
+import { useI18n } from 'vue-i18n';
 
 export default defineComponent({
 	name: 'pagesFormI18n',
 	setup() {
-		const { proxy } = <any>getCurrentInstance();
+		const { locale } = useI18n();
 		const state = reactive({
 			radio: 'zh-cn',
 			form: {
@@ -48,7 +49,7 @@ export default defineComponent({
 		});
 		// 单选框改变时
 		const onRadioChange = () => {
-			proxy.$i18n.locale = state.radio;
+			locale.value = state.radio;
 		};
 		return {
 			onRadioChange,

+ 19 - 17
Web/src/views/pages/formRules/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="form-rules-container">
+	<div class="form-rules-container layout-pd">
 		<el-card shadow="hover" header="表单组件1"> <FormRulesOne :data="formRulesOneData" ref="pagesFormRulesOneRef" /></el-card>
 		<el-card shadow="hover" header="表单组件2" class="mt15"><FormRulesTwo ref="pagesFormRulesTwoRef" /> </el-card>
 		<el-card shadow="hover" header="表单组件3" class="mt15"> <FormRulesThree ref="pagesFormRulesThreeRef" /></el-card>
@@ -19,21 +19,20 @@
 </template>
 
 <script lang="ts">
-import { toRefs, reactive, defineComponent, getCurrentInstance } from 'vue';
+import { defineAsyncComponent, toRefs, reactive, defineComponent, ref } from 'vue';
 import { ElMessage } from 'element-plus';
-import FormRulesOne from '/@/views/pages/formRules/component/formRulesOne.vue';
-import FormRulesTwo from '/@/views/pages/formRules/component/formRulesTwo.vue';
-import FormRulesThree from '/@/views/pages/formRules/component/formRulesThree.vue';
 
 export default defineComponent({
 	name: 'pagesFormRules',
 	components: {
-		FormRulesOne,
-		FormRulesTwo,
-		FormRulesThree,
+		FormRulesOne: defineAsyncComponent(() => import('/@/views/pages/formRules/component/formRulesOne.vue')),
+		FormRulesTwo: defineAsyncComponent(() => import('/@/views/pages/formRules/component/formRulesTwo.vue')),
+		FormRulesThree: defineAsyncComponent(() => import('/@/views/pages/formRules/component/formRulesThree.vue')),
 	},
 	setup() {
-		const { proxy } = <any>getCurrentInstance();
+		const pagesFormRulesOneRef = ref();
+		const pagesFormRulesTwoRef = ref();
+		const pagesFormRulesThreeRef = ref();
 		const state = reactive({
 			formRulesOneData: {
 				name: 'lyt',
@@ -43,25 +42,25 @@ export default defineComponent({
 			},
 		});
 		// 表单组件验证
-		const formRulesValidate = (pageRef: string, sonRef: string) => {
+		const formRulesValidate = (pageRef: any, sonRef: string) => {
 			return new Promise((resolve) => {
-				proxy.$refs[pageRef].$refs[sonRef].validate((valid: boolean) => {
+				pageRef.value.$refs[sonRef].validate((valid: boolean) => {
 					if (valid) resolve(valid);
 				});
 			});
 		};
 		// 表单组件重置
 		const formRulesResetFields = () => {
-			proxy.$refs.pagesFormRulesOneRef.$refs.formRulesOneRef.resetFields();
-			proxy.$refs.pagesFormRulesTwoRef.$refs.formRulesTwoRef.resetFields();
-			proxy.$refs.pagesFormRulesThreeRef.$refs.formRulesThreeRef.resetFields();
+			pagesFormRulesOneRef.value.$refs.formRulesOneRef.resetFields();
+			pagesFormRulesTwoRef.value.$refs.formRulesTwoRef.resetFields();
+			pagesFormRulesThreeRef.value.$refs.formRulesThreeRef.resetFields();
 		};
 		// 验证表单
 		const onSubmitForm = () => {
 			Promise.all([
-				formRulesValidate('pagesFormRulesOneRef', 'formRulesOneRef'),
-				formRulesValidate('pagesFormRulesTwoRef', 'formRulesTwoRef'),
-				formRulesValidate('pagesFormRulesThreeRef', 'formRulesThreeRef'),
+				formRulesValidate(pagesFormRulesOneRef, 'formRulesOneRef'),
+				formRulesValidate(pagesFormRulesTwoRef, 'formRulesTwoRef'),
+				formRulesValidate(pagesFormRulesThreeRef, 'formRulesThreeRef'),
 			]).then(() => {
 				ElMessage.success('表单全部验证成功');
 			});
@@ -71,6 +70,9 @@ export default defineComponent({
 			formRulesResetFields();
 		};
 		return {
+			pagesFormRulesOneRef,
+			pagesFormRulesTwoRef,
+			pagesFormRulesThreeRef,
 			onSubmitForm,
 			onResetForm,
 			...toRefs(state),

+ 1 - 1
Web/src/views/pages/iocnfont/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="iconfont-container">
+	<div class="iconfont-container layout-pd">
 		<el-card shadow="hover" :header="`iconfont 字体图标(自动载入):${sheetsIconList.length}个`">
 			<el-row class="iconfont-row">
 				<el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="2" v-for="(v, k) in sheetsIconList" :key="k">

+ 1 - 1
Web/src/views/pages/lazyImg/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="lazy-img-container">
+	<div class="lazy-img-container layout-pd">
 		<el-card shadow="hover" header="图片懒加载演示(F12 切换到 Network Img下进行图片加载查看)">
 			<div class="flex-warp" v-if="tableData.data.length > 0">
 				<el-row :gutter="15">

+ 1 - 1
Web/src/views/pages/listAdapt/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="list-adapt-container">
+	<div class="list-adapt-container layout-pd">
 		<el-card shadow="hover" header="列表自适应演示(改变窗口查看效果)">
 			<div class="flex-warp" v-if="tableData.data.length > 0">
 				<el-row :gutter="15">

+ 1 - 1
Web/src/views/pages/preview/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="preview-container">
+	<div class="preview-container layout-pd">
 		<el-card shadow="hover" header="element-plus 大图预览">
 			<el-image style="width: 100px; height: 100px; border-radius: 5px" :src="url" :preview-src-list="srcList" title="点击查看大图预览"> </el-image>
 		</el-card>

+ 4 - 4
Web/src/views/pages/steps/index.vue

@@ -1,20 +1,20 @@
 <template>
-	<div class="steps-container">
+	<div class="steps-container layout-pd">
 		<el-card shadow="hover" header="element-plus 步骤条">
 			<el-steps :active="stepsActive">
 				<el-step title="第一步">
 					<template #icon>
-						<SvgIcon name="iconfont icon-0_round_solid" :size="20" />
+						<SvgIcon name="iconfont icon-0_round_solid" :size="40" />
 					</template>
 				</el-step>
 				<el-step title="第二步">
 					<template #icon>
-						<SvgIcon name="iconfont icon-2_round_solid" :size="20" />
+						<SvgIcon name="iconfont icon-2_round_solid" :size="40" />
 					</template>
 				</el-step>
 				<el-step title="第三步">
 					<template #icon>
-						<SvgIcon name="iconfont icon-3_round_solid" :size="20" />
+						<SvgIcon name="iconfont icon-3_round_solid" :size="40" />
 					</template>
 				</el-step>
 			</el-steps>

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác