Jelajahi Sumber

feat: 后台ui调整

From-wh 3 tahun lalu
induk
melakukan
579e74cd9a
100 mengubah file dengan 11161 tambahan dan 4823 penghapusan
  1. 0 0
      template/admin/CHANGELOG.md
  2. 4189 743
      template/admin/package-lock.json
  3. 9 2
      template/admin/package.json
  4. 46 2
      template/admin/src/App.vue
  5. 72 0
      template/admin/src/i18n/index.js
  6. 162 0
      template/admin/src/i18n/lang/en.js
  7. 162 0
      template/admin/src/i18n/lang/zh-cn.js
  8. 162 0
      template/admin/src/i18n/lang/zh-tw.js
  9. 14 0
      template/admin/src/i18n/pages/home/en.js
  10. 14 0
      template/admin/src/i18n/pages/home/zh-cn.js
  11. 14 0
      template/admin/src/i18n/pages/home/zh-tw.js
  12. 18 0
      template/admin/src/i18n/pages/login/en.js
  13. 24 0
      template/admin/src/i18n/pages/login/zh-cn.js
  14. 18 0
      template/admin/src/i18n/pages/login/zh-tw.js
  15. 108 0
      template/admin/src/layout/component/aside.vue
  16. 259 0
      template/admin/src/layout/component/columnsAside.vue
  17. 24 0
      template/admin/src/layout/component/header.vue
  18. 93 0
      template/admin/src/layout/component/main.vue
  19. 37 0
      template/admin/src/layout/footer/index.vue
  20. 79 0
      template/admin/src/layout/index.vue
  21. 87 0
      template/admin/src/layout/logo/index.vue
  22. 30 0
      template/admin/src/layout/main/classic.vue
  23. 33 0
      template/admin/src/layout/main/columns.vue
  24. 41 0
      template/admin/src/layout/main/defaults.vue
  25. 16 0
      template/admin/src/layout/main/transverse.vue
  26. 202 0
      template/admin/src/layout/navBars/breadcrumb/breadcrumb.vue
  27. 78 0
      template/admin/src/layout/navBars/breadcrumb/index.vue
  28. 102 0
      template/admin/src/layout/navBars/breadcrumb/search.vue
  29. 596 0
      template/admin/src/layout/navBars/breadcrumb/setings.vue
  30. 286 0
      template/admin/src/layout/navBars/breadcrumb/user.vue
  31. 251 0
      template/admin/src/layout/navBars/breadcrumb/userNews.vue
  32. 34 0
      template/admin/src/layout/navBars/index.vue
  33. 110 0
      template/admin/src/layout/navBars/tagsView/contextmenu.vue
  34. 484 0
      template/admin/src/layout/navBars/tagsView/tagsView.vue
  35. 145 0
      template/admin/src/layout/navMenu/horizontal.vue
  36. 47 0
      template/admin/src/layout/navMenu/subItem.vue
  37. 76 0
      template/admin/src/layout/navMenu/vertical.vue
  38. 46 0
      template/admin/src/layout/routerView/iframes.vue
  39. 89 0
      template/admin/src/layout/routerView/link.vue
  40. 50 0
      template/admin/src/layout/routerView/parent.vue
  41. 62 0
      template/admin/src/layout/sponsors/index.vue
  42. 149 0
      template/admin/src/layout/upgrade/index.vue
  43. 55 58
      template/admin/src/main.js
  44. 5 0
      template/admin/src/pages/account/login/index.vue
  45. 0 1
      template/admin/src/pages/kefu/pc/components/emoji.vue
  46. 0 2
      template/admin/src/pages/product/list_wait.vue
  47. 70 0
      template/admin/src/pages/product/tableExpand.vue
  48. 1 5
      template/admin/src/pages/setting/devise/index.vue
  49. 1 1
      template/admin/src/pages/system/maintain/systemFile/opendir.vue
  50. 174 1
      template/admin/src/router/index.js
  51. 2 2
      template/admin/src/router/modules/agent.js
  52. 2 2
      template/admin/src/router/modules/app.js
  53. 2 2
      template/admin/src/router/modules/cms.js
  54. 2 2
      template/admin/src/router/modules/division.js
  55. 26 20
      template/admin/src/router/modules/echarts.js
  56. 2 2
      template/admin/src/router/modules/finance.js
  57. 4 4
      template/admin/src/router/modules/index.js
  58. 2 2
      template/admin/src/router/modules/marketing.js
  59. 2 2
      template/admin/src/router/modules/order.js
  60. 2 2
      template/admin/src/router/modules/product.js
  61. 2 2
      template/admin/src/router/modules/setting.js
  62. 2 2
      template/admin/src/router/modules/statistic.js
  63. 2 2
      template/admin/src/router/modules/system.js
  64. 2 2
      template/admin/src/router/modules/user.js
  65. 3 3
      template/admin/src/router/routers.js
  66. 13 0
      template/admin/src/store/index.js
  67. 1 0
      template/admin/src/store/module/app.js
  68. 20 0
      template/admin/src/store/module/keepAliveNames.js
  69. 20 0
      template/admin/src/store/module/routesList.js
  70. 20 0
      template/admin/src/store/module/tagsViewRoutes.js
  71. 122 0
      template/admin/src/store/module/themeConfig.js
  72. 26 0
      template/admin/src/store/module/userInfos.js
  73. 0 3037
      template/admin/src/styles/font/demo_index.html
  74. 0 495
      template/admin/src/styles/layout/basic-layout/layout.less
  75. 0 220
      template/admin/src/styles/layout/basic-layout/menu.less
  76. 0 2
      template/admin/src/styles/layout/index.less
  77. 0 132
      template/admin/src/styles/pages/account.less
  78. 242 0
      template/admin/src/theme/app.scss
  79. 1 0
      template/admin/src/theme/base.scss
  80. 102 0
      template/admin/src/theme/common/transition.scss
  81. 109 0
      template/admin/src/theme/dark.scss
  82. 237 0
      template/admin/src/theme/element.scss
  83. 7 0
      template/admin/src/theme/index.scss
  84. 56 0
      template/admin/src/theme/loading.scss
  85. 12 0
      template/admin/src/theme/media/dialog.scss
  86. 35 0
      template/admin/src/theme/media/error.scss
  87. 19 0
      template/admin/src/theme/media/form.scss
  88. 35 0
      template/admin/src/theme/media/home.scss
  89. 37 0
      template/admin/src/theme/media/index.scss
  90. 55 0
      template/admin/src/theme/media/layout.scss
  91. 32 0
      template/admin/src/theme/media/login.scss
  92. 7 0
      template/admin/src/theme/media/media.scss
  93. 13 0
      template/admin/src/theme/media/scrollbar.scss
  94. 0 0
      template/admin/src/theme/other.scss
  95. 961 0
      template/admin/src/theme/variables.scss
  96. 4 0
      template/admin/src/utils/componentSize.js
  97. 0 71
      template/admin/src/utils/download.js
  98. 46 0
      template/admin/src/utils/loading.js
  99. 48 0
      template/admin/src/utils/storage.js
  100. 0 0
      template/admin/src/utils/theme.js

+ 0 - 0
template/admin/CHANGELOG.md


File diff ditekan karena terlalu besar
+ 4189 - 743
template/admin/package-lock.json


+ 9 - 2
template/admin/package.json

@@ -1,5 +1,5 @@
 {
-  "name": "iview-admin",
+  "name": "from-crmeb-admin",
   "version": "2.0.0",
   "author": "Lison<lison16new@163.com>",
   "private": false,
@@ -13,7 +13,7 @@
     "prettier": "prettier --write ."
   },
   "dependencies": {
-    "@babel/polyfill": "^7.8.7",
+    "@babel/polyfill": "^7.12.1",
     "@babel/runtime": "^7.2.0",
     "@better-scroll/core": "^2.0.5",
     "@form-create/iview": "^2.5.27",
@@ -30,6 +30,7 @@
     "dayjs": "^1.7.7",
     "echarts": "^4.8.0",
     "editor": "^1.0.0",
+    "element-ui": "2.15.6",
     "emoji-awesome": "0.0.2",
     "file-saver": "^2.0.5",
     "html2canvas": "^1.0.0-alpha.12",
@@ -76,7 +77,11 @@
     "xlsx": "^0.13.5"
   },
   "devDependencies": {
+    "@babel/cli": "^7.21.0",
+    "@babel/core": "^7.21.0",
+    "@babel/node": "^7.20.7",
     "@babel/plugin-transform-runtime": "^7.2.0",
+    "@babel/preset-env": "^7.20.2",
     "@vue/cli-plugin-babel": "^4.4.6",
     "@vue/cli-plugin-eslint": "^4.4.6",
     "@vue/cli-plugin-unit-jest": "^3.2.3",
@@ -97,7 +102,9 @@
     "less-loader": "^4.1.0",
     "lint-staged": "^6.0.0",
     "mockjs": "^1.0.1-beta3",
+    "node-sass": "^4.14.1",
     "prettier": "^2.5.1",
+    "sass-loader": "^10.1.0",
     "script-loader": "^0.7.2",
     "style-loader": "^1.2.1",
     "stylus-loader": "^3.0.2",

+ 46 - 2
template/admin/src/App.vue

@@ -1,24 +1,35 @@
 <template>
   <div id="app">
     <router-view />
+    <Setings ref="setingsRef" />
+    <Upgrade v-if="isVersion" />
   </div>
 </template>
 
 <script>
 import { on, off } from 'iview/src/utils/dom';
 import { setMatchMedia } from 'iview/src/utils/assist';
-
 import { mapMutations } from 'vuex';
-
+import Setings from '@/layout/navBars/breadcrumb/setings.vue';
+import Upgrade from '@/layout/upgrade/index.vue';
+import setting from './setting';
+import { Local } from '@/utils/storage.js';
+import config from '../package.json';
 setMatchMedia();
 
 export default {
   name: 'app',
+  components: { Setings, Upgrade },
   provide() {
     return {
       reload: this.reload,
     };
   },
+  data() {
+    return {
+      isVersion: false,
+    };
+  },
   methods: {
     ...mapMutations('media', ['setDevice']),
     handleWindowResize() {
@@ -44,14 +55,46 @@ export default {
         this.isRouterAlive = true;
       });
     },
+    // 布局配置弹窗打开
+    openSetingsDrawer() {
+      this.bus.$on('openSetingsDrawer', () => {
+        this.$refs.setingsRef.openDrawer();
+      });
+    },
+    // 获取缓存中的布局配置
+    getLayoutThemeConfig() {
+      if (Local.get('themeConfigPrev')) {
+        this.$store.dispatch('themeConfig/setThemeConfig', Local.get('themeConfigPrev'));
+        document.documentElement.style.cssText = Local.get('themeConfigStyle');
+      } else {
+        Local.set('themeConfigPrev', this.$store.state.themeConfig.themeConfig);
+      }
+    },
+    getVersion() {
+      console.log(this.$route, 'this.$route.path', `${setting.routePre}/login`);
+      this.isVersion = false;
+      if (this.$route.path !== `${setting.routePre}/login` && this.$route.path !== '/') {
+        if ((Local.get('version') && Local.get('version') !== config.version) || !Local.get('version'))
+          this.isVersion = true;
+      }
+    },
   },
   mounted() {
     on(window, 'resize', this.handleWindowResize);
     this.handleMatchMedia();
+    this.openSetingsDrawer();
+    this.getLayoutThemeConfig();
+    this.$nextTick((e) => {
+      this.getVersion();
+    });
   },
+
   beforeDestroy() {
     off(window, 'resize', this.handleWindowResize);
   },
+  destroyed() {
+    this.bus.$off('openSetingsDrawer');
+  },
 };
 </script>
 
@@ -69,6 +112,7 @@ body {
 }
 #app {
   .size;
+  font-family: PingFang SC, Arial, Microsoft YaHei, sans-serif;
 }
 .dialog-fade-enter-active {
   animation: anim-open 0.3s;

+ 72 - 0
template/admin/src/i18n/index.js

@@ -0,0 +1,72 @@
+/*
+ * @Author: From-wh from-wh@hotmail.com
+ * @Date: 2023-03-09 18:02:23
+ * @FilePath: /admin/src/i18n/index.js
+ * @Description: 
+ */
+import Vue from 'vue';
+import VueI18n from 'vue-i18n';
+import zhcnLocale from 'element-ui/lib/locale/lang/zh-CN';
+import enLocale from 'element-ui/lib/locale/lang/en';
+import zhtwLocale from 'element-ui/lib/locale/lang/zh-TW';
+import store from '@/store/index.js';
+
+import nextZhcn from '@/i18n/lang/zh-cn.js';
+import nextEn from '@/i18n/lang/en.js';
+import nextZhtw from '@/i18n/lang/zh-tw.js';
+
+import pagesHomeZhcn from '@/i18n/pages/home/zh-cn.js';
+import pagesHomeEn from '@/i18n/pages/home/en.js';
+import pagesHomeZhtw from '@/i18n/pages/home/zh-tw.js';
+import pagesLoginZhcn from '@/i18n/pages/login/zh-cn.js';
+import pagesLoginEn from '@/i18n/pages/login/en.js';
+import pagesLoginZhtw from '@/i18n/pages/login/zh-tw.js';
+import iviewZhCnLocale from 'iview/src/locale/lang/zh-CN';
+import iviewEnUsLocale from 'iview/src/locale/lang/en-US';
+import iviewZhTwLocale from 'iview/src/locale/lang/zh-TW';
+// 使用插件
+Vue.use(VueI18n);
+
+// 定义语言国际化内容
+/**
+ * 说明:
+ * /src/i18n/lang 下的 js 为框架的国际化内容
+ * /src/i18n/pages 下的 js 为各界面的国际化内容
+ */
+const messages = {
+  'zh-cn': {
+    ...zhcnLocale,
+    ...iviewZhCnLocale,
+    message: {
+      ...nextZhcn,
+      ...pagesHomeZhcn,
+      ...pagesLoginZhcn,
+    },
+  },
+  en: {
+    ...enLocale,
+    ...iviewEnUsLocale,
+    message: {
+      ...nextEn,
+      ...pagesHomeEn,
+      ...pagesLoginEn,
+    },
+  },
+  'zh-tw': {
+    ...zhtwLocale,
+    ...iviewZhTwLocale,
+    message: {
+      ...nextZhtw,
+      ...pagesHomeZhtw,
+      ...pagesLoginZhtw,
+    },
+  },
+};
+
+// 导出语言国际化
+export const i18n = new VueI18n({
+  locale: store.state.themeConfig.themeConfig.globalI18n,
+  fallbackLocale: 'zh-cn',
+  messages,
+  silentTranslationWarn: true, // 去除国际化警告
+});

+ 162 - 0
template/admin/src/i18n/lang/en.js

@@ -0,0 +1,162 @@
+// 定义内容
+export default {
+	router: {
+		home: 'home',
+		system: 'system',
+		systemMenu: 'systemMenu',
+		systemUser: 'systemUser',
+		limits: 'limits',
+		limitsFrontEnd: 'FrontEnd',
+		limitsFrontEndPage: 'FrontEndPage',
+		limitsFrontEndBtn: 'FrontEndBtn',
+		limitsBackEnd: 'BackEnd',
+		limitsBackEndEndPage: 'BackEndEndPage',
+		menu: 'menu',
+		menu1: 'menu1',
+		menu11: 'menu11',
+		menu12: 'menu12',
+		menu121: 'menu121',
+		menu122: 'menu122',
+		menu13: 'menu13',
+		menu2: 'menu2',
+		funIndex: 'function',
+		funTagsView: 'funTagsView',
+		funSignCanvas: 'Online signature',
+		funCountup: 'countup',
+		funEchartsTree: 'echartsTree',
+		funSelector: 'funSelector',
+		funWangEditor: 'wangEditor',
+		funCropper: 'cropper',
+		funMindMap: 'G6 MindMap',
+		funQrcode: 'qrcode',
+		funEchartsMap: 'EchartsMap',
+		funPrintJs: 'PrintJs',
+		funClipboard: 'Copy cut',
+		funScreenShort: 'screenCapture',
+		pagesIndex: 'pages',
+		pagesFiltering: 'Filtering',
+		pagesFilteringDetails: 'FilteringDetails',
+		pagesFilteringDetails1: 'FilteringDetails1',
+		pagesIocnfont: 'iconfont icon',
+		pagesElement: 'element icon',
+		pagesAwesome: 'awesome icon',
+		pagesCityLinkage: 'CityLinkage',
+		pagesFormAdapt: 'FormAdapt',
+		pagesListAdapt: 'ListAdapt',
+		pagesWaterfall: 'Waterfall',
+		pagesSteps: 'Steps',
+		chartIndex: 'chartIndex',
+		personal: 'personal',
+		tools: 'tools',
+		layoutLinkView: 'LinkView',
+		layoutIfameView: 'IfameView',
+	},
+	staticRoutes: {
+		signIn: 'signIn',
+		notFound: 'notFound',
+		noPower: 'noPower',
+	},
+	user: {
+		title0: 'Component size',
+		title1: 'Language switching',
+		title2: 'Menu search',
+		title3: 'Layout configuration',
+		title4: 'news',
+		title5: 'Full screen on',
+		title6: 'Full screen off',
+		dropdownDefault: 'default',
+		dropdownMedium: 'medium',
+		dropdownSmall: 'small',
+		dropdownMini: 'mini',
+		dropdown1: 'home page',
+		dropdown2: 'Personal Center',
+		dropdown3: '404',
+		dropdown4: '401',
+		dropdown5: 'Log out',
+		dropdown6: 'Code warehouse',
+		searchPlaceholder: 'Menu search: support Chinese, routing path',
+		newTitle: 'notice',
+		newBtn: 'All read',
+		newGo: 'Go to the notification center',
+		newDesc: 'No notice',
+		logOutTitle: 'Tips',
+		logOutMessage: 'This operation will log out. Do you want to continue?',
+		logOutConfirm: 'determine',
+		logOutCancel: 'cancel',
+		logOutExit: 'Exiting',
+		logOutSuccess: 'Exit successfully!',
+	},
+	tagsView: {
+		refresh: 'refresh',
+		close: 'close',
+		closeOther: 'closeOther',
+		closeAll: 'closeAll',
+		fullscreen: 'fullscreen',
+	},
+	notFound: {
+		foundTitle: 'Wrong address input, please re-enter the address~',
+		foundMsg: 'You can check the web address first, and then re-enter or give us feedback.',
+		foundBtn: 'Back to home page',
+	},
+	noAccess: {
+		accessTitle: 'You are not authorized to operate~',
+		accessMsg: 'Contact information: add QQ group discussion 665452019',
+		accessBtn: 'Reauthorization',
+	},
+	layout: {
+		configTitle: 'Layout configuration',
+		oneTitle: 'Global Themes',
+		twoTitle: 'Menu / top bar',
+		twoTopBar: 'Top bar background',
+		twoMenuBar: 'Menu background',
+		twoColumnsMenuBar: 'Column menu background',
+		twoTopBarColor: 'Top bar default font color',
+		twoMenuBarColor: 'Menu default font color',
+		twoColumnsMenuBarColor: 'Default font color bar menu',
+		twoIsTopBarColorGradual: 'Top bar gradient',
+		twoIsMenuBarColorGradual: 'Menu gradient',
+		twoIsMenuBarColorHighlight: 'Menu font highlight',
+		threeTitle: 'Interface settings',
+		threeIsCollapse: 'Menu horizontal collapse',
+		threeIsUniqueOpened: 'Menu accordion',
+		threeIsFixedHeader: 'Fixed header',
+		threeIsClassicSplitMenu: 'Classic layout split menu',
+		threeIsLockScreen: 'Open the lock screen',
+		threeLockScreenTime: 'screen locking(s/s)',
+		fourTitle: 'Interface display',
+		fourIsShowLogo: 'Sidebar logo',
+		fourIsBreadcrumb: 'Open breadcrumb',
+		fourIsBreadcrumbIcon: 'Open breadcrumb icon',
+		fourIsTagsview: 'Open tagsview',
+		fourIsTagsviewIcon: 'Open tagsview Icon',
+		fourIsFooter: 'Open footer',
+		fourIsGrayscale: 'Grey model',
+		fourIsInvert: 'Color weak mode',
+		fourIsDark: 'Dark Mode',
+		fourIsWartermark: 'Turn on watermark',
+		fourWartermarkText: 'Watermark copy',
+		fiveTitle: 'Other settings',
+		fiveTagsStyle: 'Tagsview style',
+		fiveAnimation: 'page animation',
+		fiveColumnsAsideStyle: 'Column style',
+		fiveColumnsAsideLayout: 'Column layout',
+		sixTitle: 'Layout switch',
+		sixDefaults: 'One',
+		sixClassic: 'Two',
+		sixTransverse: 'Three',
+		sixColumns: 'Four',
+		tipText: 'Click the button below to copy the layout configuration to `/src/store/modules/themeConfig.js` It has been modified in.',
+		copyText: 'replication configuration',
+		resetText: 'restore default',
+		copyTextSuccess: 'Copy succeeded!',
+		copyTextError: 'Copy failed!',
+	},
+	upgrade: {
+		title: 'New version',
+		msg: 'The new version is available, please update it now! Dont worry, the update is fast!',
+		desc: 'Prompt: Update will restore the default configuration',
+		btnOne: 'Cruel refusal',
+		btnTwo: 'Update now',
+		btnTwoLoading: 'Updating',
+	},
+};

+ 162 - 0
template/admin/src/i18n/lang/zh-cn.js

@@ -0,0 +1,162 @@
+// 定义内容
+export default {
+	router: {
+		home: '首页',
+		system: '系统设置',
+		systemMenu: '菜单管理',
+		systemUser: '用户管理',
+		limits: '权限管理',
+		limitsFrontEnd: '前端控制',
+		limitsFrontEndPage: '页面权限',
+		limitsFrontEndBtn: '按钮权限',
+		limitsBackEnd: '后端控制',
+		limitsBackEndEndPage: '页面权限',
+		menu: '菜单嵌套',
+		menu1: '菜单1',
+		menu11: '菜单11',
+		menu12: '菜单12',
+		menu121: '菜单121',
+		menu122: '菜单122',
+		menu13: '菜单13',
+		menu2: '菜单2',
+		funIndex: '功能',
+		funTagsView: 'tagsView 操作',
+		funSignCanvas: '在线签名',
+		funCountup: 'countup 数字滚动',
+		funEchartsTree: 'echartsTree 树图',
+		funSelector: '图标选择器',
+		funWangEditor: 'wangEditor 编辑器',
+		funCropper: 'cropper 图片裁剪',
+		funMindMap: 'G6 思维导图',
+		funQrcode: 'qrcode 二维码生成',
+		funEchartsMap: '地理坐标/地图',
+		funPrintJs: '页面打印',
+		funClipboard: '复制剪切',
+		funScreenShort: 'web端自定义截屏',
+		pagesIndex: '页面',
+		pagesFiltering: '过滤筛选组件',
+		pagesFilteringDetails: '过滤筛选组件详情',
+		pagesFilteringDetails1: '过滤筛选组件详情111',
+		pagesIocnfont: 'iconfont 字体图标',
+		pagesElement: 'element 字体图标',
+		pagesAwesome: 'awesome 字体图标',
+		pagesCityLinkage: '城市多级联动',
+		pagesFormAdapt: '表单自适应',
+		pagesListAdapt: '列表自适应',
+		pagesWaterfall: '瀑布屏',
+		pagesSteps: '步骤条',
+		chartIndex: '大数据图表',
+		personal: '个人中心',
+		tools: '工具类集合',
+		layoutLinkView: '外链',
+		layoutIfameView: '内嵌 iframe',
+	},
+	staticRoutes: {
+		signIn: '登录',
+		notFound: '找不到此页面',
+		noPower: '没有权限',
+	},
+	user: {
+		title0: '组件大小',
+		title1: '语言切换',
+		title2: '菜单搜索',
+		title3: '布局配置',
+		title4: '消息',
+		title5: '开全屏',
+		title6: '关全屏',
+		dropdownDefault: '默认',
+		dropdownMedium: '中等',
+		dropdownSmall: '小型',
+		dropdownMini: '超小',
+		dropdown1: '首页',
+		dropdown2: '个人中心',
+		dropdown3: '404',
+		dropdown4: '401',
+		dropdown5: '退出登录',
+		dropdown6: '代码仓库',
+		searchPlaceholder: '菜单搜索:支持中文、路由路径',
+		newTitle: '通知',
+		newBtn: '全部已读',
+		newGo: '前往通知中心',
+		newDesc: '暂无通知',
+		logOutTitle: '提示',
+		logOutMessage: '此操作将退出登录, 是否继续?',
+		logOutConfirm: '确定',
+		logOutCancel: '取消',
+		logOutExit: '退出中',
+		logOutSuccess: '安全退出成功!',
+	},
+	tagsView: {
+		refresh: '刷新',
+		close: '关闭',
+		closeOther: '关闭其它',
+		closeAll: '全部关闭',
+		fullscreen: '当前页全屏',
+	},
+	notFound: {
+		foundTitle: '地址输入错误,请重新输入地址~',
+		foundMsg: '您可以先检查网址,然后重新输入或给我们反馈问题。',
+		foundBtn: '返回首页',
+	},
+	noAccess: {
+		accessTitle: '您未被授权,没有操作权限~',
+		accessMsg: '联系方式:加QQ群探讨 665452019',
+		accessBtn: '重新授权',
+	},
+	layout: {
+		configTitle: '布局配置',
+		oneTitle: '全局主题',
+		twoTitle: '菜单 / 顶栏',
+		twoTopBar: '顶栏背景',
+		twoMenuBar: '菜单背景',
+		twoColumnsMenuBar: '分栏菜单背景',
+		twoTopBarColor: '顶栏默认字体颜色',
+		twoMenuBarColor: '菜单默认字体颜色',
+		twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
+		twoIsTopBarColorGradual: '顶栏背景渐变',
+		twoIsMenuBarColorGradual: '菜单背景渐变',
+		twoIsMenuBarColorHighlight: '菜单字体背景高亮',
+		threeTitle: '界面设置',
+		threeIsCollapse: '菜单水平折叠',
+		threeIsUniqueOpened: '菜单手风琴',
+		threeIsFixedHeader: '固定 Header',
+		threeIsClassicSplitMenu: '经典布局分割菜单',
+		threeIsLockScreen: '开启锁屏',
+		threeLockScreenTime: '自动锁屏(s/秒)',
+		fourTitle: '界面显示',
+		fourIsShowLogo: '侧边栏 Logo',
+		fourIsBreadcrumb: '开启 面包屑',
+		fourIsBreadcrumbIcon: '开启 面包屑图标',
+		fourIsTagsview: '开启 历史菜单',
+		fourIsTagsviewIcon: '开启 历史菜单 图标',
+		fourIsFooter: '开启 Footer',
+		fourIsGrayscale: '灰色模式',
+		fourIsInvert: '色弱模式',
+		fourIsDark: '深色模式',
+		fourIsWartermark: '开启水印',
+		fourWartermarkText: '水印文案',
+		fiveTitle: '其它设置',
+		fiveTagsStyle: 'Tagsview 风格',
+		fiveAnimation: '主页面切换动画',
+		fiveColumnsAsideStyle: '分栏高亮风格',
+		fiveColumnsAsideLayout: '分栏布局风格',
+		sixTitle: '布局切换',
+		sixDefaults: '默认',
+		sixClassic: '经典',
+		sixTransverse: '横向',
+		sixColumns: '分栏',
+		tipText: '点击下方按钮,复制布局配置去 `src/store/modules/themeConfig.js` 中修改。',
+		copyText: '一键复制配置',
+		resetText: '一键恢复默认',
+		copyTextSuccess: '复制成功!',
+		copyTextError: '复制失败!',
+	},
+	upgrade: {
+		title: '新版本升级',
+		msg: '新版本来啦,马上更新尝鲜吧!不用担心,更新很快的哦!',
+		desc: '提示:更新会还原默认配置',
+		btnOne: '残忍拒绝',
+		btnTwo: '马上更新',
+		btnTwoLoading: '更新中',
+	},
+};

+ 162 - 0
template/admin/src/i18n/lang/zh-tw.js

@@ -0,0 +1,162 @@
+// 定义内容
+export default {
+	router: {
+		home: '首頁',
+		system: '系統設置',
+		systemMenu: '選單管理',
+		systemUser: '用戶管理',
+		limits: '許可權管理',
+		limitsFrontEnd: '前端控制',
+		limitsFrontEndPage: '頁面許可權',
+		limitsFrontEndBtn: '按鈕許可權',
+		limitsBackEnd: '後端控制',
+		limitsBackEndEndPage: '頁面許可權',
+		menu: '選單嵌套',
+		menu1: '選單1',
+		menu11: '選單11',
+		menu12: '選單12',
+		menu121: '選單121',
+		menu122: '選單122',
+		menu13: '選單13',
+		menu2: '選單2',
+		funIndex: '功能',
+		funTagsView: 'tagsView 操作',
+		funSignCanvas: '線上簽名',
+		funCountup: 'countup 數位滾動',
+		funEchartsTree: 'echartsTree 樹圖',
+		funSelector: '圖標選擇器',
+		funWangEditor: 'wangEditor 編輯器',
+		funCropper: 'cropper 圖片裁剪',
+		funMindMap: 'G6 心智圖',
+		funQrcode: 'qrcode 二維碼生成',
+		funEchartsMap: '地理座標/地圖',
+		funPrintJs: '頁面列印',
+		funClipboard: '複製剪切',
+		funScreenShort: '自定義截圖',
+		pagesIndex: '頁面',
+		pagesFiltering: '過濾篩選組件',
+		pagesFilteringDetails: '過濾篩選組件詳情',
+		pagesFilteringDetails1: '過濾篩選組件詳情111',
+		pagesIocnfont: 'iconfont 字體圖標',
+		pagesElement: 'element 字體圖標',
+		pagesAwesome: 'awesome 字體圖標',
+		pagesCityLinkage: '都市多級聯動',
+		pagesFormAdapt: '表單自我調整',
+		pagesListAdapt: '清單自我調整',
+		pagesWaterfall: '瀑布屏',
+		pagesSteps: '步驟條',
+		chartIndex: '大資料圖表',
+		personal: '個人中心',
+		tools: '工具類集合',
+		layoutLinkView: '外鏈',
+		layoutIfameView: '内嵌 iframe',
+	},
+	staticRoutes: {
+		signIn: '登入',
+		notFound: '找不到此頁面',
+		noPower: '沒有許可權',
+	},
+	user: {
+		title0: '組件大小',
+		title1: '語言切換',
+		title2: '選單蒐索',
+		title3: '佈局配寘',
+		title4: '消息',
+		title5: '開全屏',
+		title6: '關全屏',
+		dropdownDefault: '默認',
+		dropdownMedium: '中等',
+		dropdownSmall: '小型',
+		dropdownMini: '超小',
+		dropdown1: '首頁',
+		dropdown2: '個人中心',
+		dropdown3: '404',
+		dropdown4: '401',
+		dropdown5: '登出',
+		dropdown6: '程式碼倉庫',
+		searchPlaceholder: '選單蒐索:支援中文、路由路徑',
+		newTitle: '通知',
+		newBtn: '全部已讀',
+		newGo: '前往通知中心',
+		newDesc: '暫無通知',
+		logOutTitle: '提示',
+		logOutMessage: '此操作將登出,是否繼續?',
+		logOutConfirm: '確定',
+		logOutCancel: '取消',
+		logOutExit: '退出中',
+		logOutSuccess: '安全退出成功!',
+	},
+	tagsView: {
+		refresh: '重繪',
+		close: '關閉',
+		closeOther: '關閉其它',
+		closeAll: '全部關閉',
+		fullscreen: '當前頁全屏',
+	},
+	notFound: {
+		foundTitle: '地址輸入錯誤,請重新輸入地址~',
+		foundMsg: '您可以先檢查網址,然後重新輸入或給我們迴響問題。',
+		foundBtn: '返回首頁',
+	},
+	noAccess: {
+		accessTitle: '您未被授權,沒有操作許可權~',
+		accessMsg: '聯繫方式:加QQ群探討665452019',
+		accessBtn: '重新授權',
+	},
+	layout: {
+		configTitle: '佈局配寘',
+		oneTitle: '全域主題',
+		twoTitle: '選單 / 頂欄',
+		twoTopBar: '頂欄背景',
+		twoMenuBar: '選單背景',
+		twoColumnsMenuBar: '分欄選單背景',
+		twoTopBarColor: '頂欄默認字體顏色',
+		twoMenuBarColor: '選單默認字體顏色',
+		twoColumnsMenuBarColor: '分欄選單默認字體顏色',
+		twoIsTopBarColorGradual: '頂欄背景漸變',
+		twoIsMenuBarColorGradual: '選單背景漸變',
+		twoIsMenuBarColorHighlight: '選單字體背景高亮',
+		threeTitle: '介面設定',
+		threeIsCollapse: '選單水准折疊',
+		threeIsUniqueOpened: '選單手風琴',
+		threeIsFixedHeader: '固定 Header',
+		threeIsClassicSplitMenu: '經典佈局分割選單',
+		threeIsLockScreen: '開啟鎖屏',
+		threeLockScreenTime: '自動鎖屏(s/秒)',
+		fourTitle: '介面顯示',
+		fourIsShowLogo: '側邊欄 Logo',
+		fourIsBreadcrumb: '開啟 Breadcrumb',
+		fourIsBreadcrumbIcon: '開啟 Breadcrumb 圖標',
+		fourIsTagsview: '開啟 Tagsview',
+		fourIsTagsviewIcon: '開啟 Tagsview 圖標',
+		fourIsFooter: '開啟 Footer',
+		fourIsGrayscale: '灰色模式',
+		fourIsInvert: '色弱模式',
+		fourIsDark: '深色模式',
+		fourIsWartermark: '開啟浮水印',
+		fourWartermarkText: '浮水印文案',
+		fiveTitle: '其它設定',
+		fiveTagsStyle: 'Tagsview 風格',
+		fiveAnimation: '主頁面切換動畫',
+		fiveColumnsAsideStyle: '分欄高亮風格',
+		fiveColumnsAsideLayout: '分欄佈局風格',
+		sixTitle: '佈局切換',
+		sixDefaults: '默認',
+		sixClassic: '經典',
+		sixTransverse: '橫向',
+		sixColumns: '分欄',
+		tipText: '點擊下方按鈕,複製佈局配寘去`src/store/modules/themeConfig.js`中修改。',
+		copyText: '一鍵複製配寘',
+		resetText: '一鍵恢復默認',
+		copyTextSuccess: '複製成功!',
+		copyTextError: '複製失敗!',
+	},
+	upgrade: {
+		title: '新版本陞級',
+		msg: '新版本來啦,馬上更新嘗鮮吧! 不用擔心,更新很快的哦!',
+		desc: '提示:更新會還原默認配寘',
+		btnOne: '殘忍拒絕',
+		btnTwo: '馬上更新',
+		btnTwoLoading: '更新中',
+	},
+};

+ 14 - 0
template/admin/src/i18n/pages/home/en.js

@@ -0,0 +1,14 @@
+// 定义内容
+export default {
+	card: {
+		title1: 'My desk',
+		title2: 'Message notification',
+		title3: 'more',
+		title4: 'Marketing recommendation',
+		title5: 'more',
+		title6: 'Inventory operations',
+		title7: 'Performance',
+		title8: 'Out of stock monitoring',
+		title9: 'Performance overtime warning',
+	},
+};

+ 14 - 0
template/admin/src/i18n/pages/home/zh-cn.js

@@ -0,0 +1,14 @@
+// 定义内容
+export default {
+	card: {
+		title1: '我的工作台',
+		title2: '消息通知',
+		title3: '更多',
+		title4: '营销推荐',
+		title5: '更多',
+		title6: '库存作业',
+		title7: '履约情况',
+		title8: '缺货监控',
+		title9: '履约超时预警',
+	},
+};

+ 14 - 0
template/admin/src/i18n/pages/home/zh-tw.js

@@ -0,0 +1,14 @@
+// 定义内容
+export default {
+	card: {
+		title1: '我的工作臺',
+		title2: '消息通知',
+		title3: '更多',
+		title4: '行銷推薦',
+		title5: '更多',
+		title6: '庫存工作',
+		title7: '履約情况',
+		title8: '缺貨監控',
+		title9: '履約超時預警',
+	},
+};

+ 18 - 0
template/admin/src/i18n/pages/login/en.js

@@ -0,0 +1,18 @@
+// 定义内容
+export default {
+	login: {
+		placeholder1: 'The user name admin or not is test',
+		placeholder2: 'Password: 123456',
+		placeholder3: 'Please enter the verification code',
+		btnText: 'Sign in',
+		link: {
+			one1: 'Third party login',
+			one2: 'Links',
+		},
+		signInText: 'welcome back!',
+		copyright: {
+			one5: 'Copyright: Shenzhen XXX Software Technology Co., Ltd',
+			two6: 'Copyright: Shenzhen XXX software technology Guangdong ICP preparation no.05010000',
+		},
+	},
+};

+ 24 - 0
template/admin/src/i18n/pages/login/zh-cn.js

@@ -0,0 +1,24 @@
+/*
+ * @Author: From-wh from-wh@hotmail.com
+ * @Date: 2023-03-09 18:02:23
+ * @FilePath: /admin/src/i18n/pages/login/zh-cn.js
+ * @Description: 
+ */
+// 定义内容
+export default {
+	login: {
+		placeholder1: '用户名 admin 或不输均为 test',
+		placeholder2: '密码:123456',
+		placeholder3: '请输入验证码',
+		btnText: '登 录',
+		link: {
+			one1: '第三方登录',
+			one2: '友情链接',
+		},
+		signInText: '欢迎回来!',
+		copyright: {
+			one5: '',
+			two6: '',
+		},
+	},
+};

+ 18 - 0
template/admin/src/i18n/pages/login/zh-tw.js

@@ -0,0 +1,18 @@
+// 定义内容
+export default {
+	login: {
+		placeholder1: '用戶名admin或不輸均為test',
+		placeholder2: '密碼:123456',
+		placeholder3: '請輸入驗證碼',
+		btnText: '登 录',
+		link: {
+			one1: '協力廠商登入',
+			one2: '友情連結',
+		},
+		signInText: '歡迎回來!',
+		copyright: {
+			one5: '版權所有:深圳市xxx軟件科技有限公司',
+			two6: 'Copyright: Shenzhen XXX Software Technology 粵ICP備05010000號',
+		},
+	},
+};

+ 108 - 0
template/admin/src/layout/component/aside.vue

@@ -0,0 +1,108 @@
+<template>
+  <el-aside class="layout-aside" :class="setCollapseWidth" v-if="clientWidth > 1000">
+    <Logo v-if="setShowLogo && menuList.length" />
+    <el-scrollbar class="flex-auto" ref="layoutAsideRef">
+      <Vertical :menuList="menuList" :class="setCollapseWidth" />
+    </el-scrollbar>
+  </el-aside>
+  <el-drawer :visible.sync="getThemeConfig.isCollapse" :with-header="false" direction="ltr" size="180px" v-else>
+    <el-aside class="layout-aside w100 h100">
+      <Logo v-if="setShowLogo && menuList.length" />
+      <el-scrollbar class="flex-auto" ref="layoutAsideRef">
+        <Vertical :menuList="menuList" />
+      </el-scrollbar>
+    </el-aside>
+  </el-drawer>
+</template>
+
+<script>
+import Vertical from '@/layout/navMenu/vertical.vue';
+import Logo from '@/layout/logo/index.vue';
+export default {
+  name: 'layoutAside',
+  components: { Vertical, Logo },
+  data() {
+    return {
+      menuList: [],
+      clientWidth: '',
+    };
+  },
+  computed: {
+    // 设置左侧菜单的具体宽度
+    setCollapseWidth() {
+      let { layout, isCollapse } = this.$store.state.themeConfig.themeConfig;
+      let asideBrColor = '';
+      layout === 'classic' || layout === 'columns' ? (asideBrColor = 'layout-el-aside-br-color') : '';
+
+      if (layout === 'columns') {
+        // 分栏布局,菜单收起时宽度给 1px
+        if (isCollapse) {
+          return ['layout-aside-width1', asideBrColor];
+        } else {
+          return ['layout-aside-width-default', asideBrColor];
+        }
+      } else {
+        // 其它布局给 64px
+        if (isCollapse) {
+          return ['layout-aside-width64', asideBrColor];
+        } else {
+          return ['layout-aside-width-default', asideBrColor];
+        }
+      }
+    },
+    // 设置 logo 是否显示
+    setShowLogo() {
+      let { layout, isShowLogo } = this.$store.state.themeConfig.themeConfig;
+      return (isShowLogo && layout === 'defaults') || (isShowLogo && layout === 'columns');
+    },
+    // 获取布局配置信息
+    getThemeConfig() {
+      return this.$store.state.themeConfig.themeConfig;
+    },
+  },
+  created() {
+    this.initMenuFixed(document.body.clientWidth);
+    this.setFilterRoutes();
+    this.bus.$on('setSendColumnsChildren', (res) => {
+      console.log('收到了', res);
+      this.menuList = res || [];
+    });
+    this.bus.$on('layoutMobileResize', (res) => {
+
+      this.initMenuFixed(res.clientWidth);
+    });
+    // 菜单滚动条监听
+    this.bus.$on('updateElScrollBar', () => {
+      setTimeout(() => {
+        this.$refs.layoutAsideRef.update();
+      }, 300);
+    });
+  },
+  methods: {
+    // 设置/过滤路由(非静态路由/是否显示在菜单中)
+    setFilterRoutes() {
+      if (this.$store.state.themeConfig.themeConfig.layout === 'columns') return false;
+      this.menuList = this.filterRoutesFun(this.$store.state.routesList.routesList);
+    },
+    // 设置/过滤路由 递归函数
+    filterRoutesFun(arr) {
+      return arr
+        .filter((item) => item.path)
+        .map((item) => {
+          item = Object.assign({}, item);
+          if (item.children) item.children = this.filterRoutesFun(item.children);
+          return item;
+        });
+    },
+    // 设置菜单导航是否固定(移动端)
+    initMenuFixed(clientWidth) {
+      this.clientWidth = clientWidth;
+    },
+  },
+  // 页面销毁时
+  destroyed() {
+    // 取消菜单滚动条监听
+    this.bus.$off('updateElScrollBar', () => {});
+  },
+};
+</script>

+ 259 - 0
template/admin/src/layout/component/columnsAside.vue

@@ -0,0 +1,259 @@
+<template>
+  <div class="layout-columns-aside">
+    <el-scrollbar>
+      <ul>
+        <li
+          v-for="(v, k) in columnsAsideList"
+          :key="k"
+          @click="onColumnsAsideMenuClick(v)"
+          ref="columnsAsideOffsetTopRefs"
+          :class="{ 'layout-columns-active': v.k === liIndex }"
+          :title="$t(v.title)"
+        >
+          <div :class="setColumnsAsidelayout" v-if="!v.isLink || (v.isLink && v.isIframe)">
+            <Icon :type="v.icon" />
+            <div class="font12">
+              {{
+                $t(v.title) && $t(v.title).length >= 4
+                  ? $t(v.title).substr(0, setColumnsAsidelayout === 'columns-vertical' ? 4 : 3)
+                  : $t(v.title)
+              }}
+            </div>
+          </div>
+          <div :class="setColumnsAsidelayout" v-else>
+            <a :href="v.isLink" target="_blank">
+              <Icon :type="v.icon" />
+              <div class="font12">
+                {{
+                  $t(v.title) && $t(v.title).length >= 4
+                    ? $t(v.title).substr(0, setColumnsAsidelayout === 'columns-vertical' ? 4 : 3)
+                    : $t(v.title)
+                }}
+              </div>
+            </a>
+          </div>
+        </li>
+        <div ref="columnsAsideActiveRef" :class="setColumnsAsideStyle"></div>
+      </ul>
+    </el-scrollbar>
+  </div>
+</template>
+
+<script>
+import { getMenuSider, getHeaderName } from '@/libs/system';
+
+export default {
+  name: 'layoutColumnsAside',
+  data() {
+    return {
+      columnsAsideList: [],
+      liIndex: 0,
+      difference: 0,
+      routeSplit: [],
+      activePath: '',
+    };
+  },
+  computed: {
+    // 设置分栏高亮风格
+    setColumnsAsideStyle() {
+      return this.$store.state.themeConfig.themeConfig.columnsAsideStyle;
+    },
+    // 设置分栏布局风格
+    setColumnsAsidelayout() {
+      console.log(this.$store.state.themeConfig.themeConfig.columnsAsideLayout, '122311');
+      return this.$store.state.themeConfig.themeConfig.columnsAsideLayout;
+    },
+    Layout() {
+      console.log(this.$store.state.themeConfig.themeConfig.Layout, '122311444');
+      return this.$store.state.themeConfig.themeConfig.Layout;
+    },
+  },
+  mounted() {
+    this.setFilterRoutes();
+  },
+  methods: {
+    // 设置菜单高亮位置移动
+    setColumnsAsideMove(k) {
+      if (k === undefined) return false;
+      const els = this.$refs.columnsAsideOffsetTopRefs;
+      this.liIndex = k;
+      this.$refs.columnsAsideActiveRef.style.top = `${els[k].offsetTop + this.difference}px`;
+    },
+    // 菜单高亮点击事件
+    onColumnsAsideMenuClick(v) {
+      //   console.log(v, 'vvvv');
+      let { path, redirect } = v;
+      if (path) this.$router.push(path);
+      else this.$router.push(path);
+      // 一个路由设置自动收起菜单
+      // https://gitee.com/lyt-top/vue-next-admin/issues/I6HW7H
+      if (!v.children || v.children.length <= 1) this.$store.state.themeConfig.themeConfig.isCollapse = true;
+      else if (v.children.length > 1) this.$store.state.themeConfig.themeConfig.isCollapse = false;
+      // this.bus.$emit('setSendColumnsChildren', getMenuSider(this.columnsAsideList, path));
+    },
+    // 设置高亮动态位置
+    onColumnsAsideDown(k) {
+      this.$nextTick(() => {
+        this.setColumnsAsideMove(k);
+      });
+    },
+    // 设置/过滤路由(非静态路由/是否显示在菜单中)
+    setFilterRoutes() {
+      if (this.$store.state.routesList.routesList.length <= 0) return false;
+      this.columnsAsideList = this.filterRoutesFun(this.$store.state.routesList.routesList);
+      //   const resData = getHeaderName(this.$route.path, this.columnsAsideList);
+      const resData = this.setSendChildren(getHeaderName(this.$route, this.columnsAsideList));
+      if (!resData.children) {
+        this.bus.$emit('setSendColumnsChildren', []);
+        this.$store.state.themeConfig.themeConfig.isCollapse = true;
+        return false;
+      }
+      this.onColumnsAsideDown(resData.item[0].k);
+      // 刷新时,初始化一个路由设置自动收起菜单
+      // https://gitee.com/lyt-top/vue-next-admin/issues/I6HW7H
+      resData.children.length > 0
+        ? (this.$store.state.themeConfig.themeConfig.isCollapse = false)
+        : (this.$store.state.themeConfig.themeConfig.isCollapse = true);
+      this.bus.$emit('setSendColumnsChildren', resData?.children || []);
+    },
+    // 传送当前子级数据到菜单中
+    setSendChildren(path) {
+      const currentPathSplit = path.split('/');
+      let currentData = {};
+      this.columnsAsideList.map((v, k) => {
+        if (v.path === path) {
+          v['k'] = k;
+          currentData['item'] = [{ ...v }];
+          //   currentData['children'] = [{ ...v }];
+          if (v.children) currentData['children'] = v.children;
+        }
+      });
+      return currentData;
+    },
+    // 路由过滤递归函数
+    filterRoutesFun(arr) {
+      return arr
+        .filter((item) => item.path)
+        .map((item) => {
+          item = Object.assign({}, item);
+          if (item.children) item.children = this.filterRoutesFun(item.children);
+          return item;
+        });
+    },
+    // tagsView 点击时,根据路由查找下标 columnsAsideList,实现左侧菜单高亮
+    setColumnsMenuHighlight(path) {
+      this.routeSplit = path.split('/');
+      this.routeSplit.shift();
+      const routeFirst = `/${this.routeSplit[0]}`;
+      const currentSplitRoute = this.columnsAsideList.find((v) => v.path === routeFirst);
+      console.log(currentSplitRoute, 'currentSplitRoute');
+      if (!currentSplitRoute) return false;
+      // 延迟拿值,防止取不到
+      setTimeout(() => {
+        this.onColumnsAsideDown(currentSplitRoute.k);
+      }, 0);
+    },
+  },
+  watch: {
+    // 监听 vuex 数据变化
+    '$store.state': {
+      handler(val) {
+        console.log('变了变了');
+        val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound'
+          ? (this.difference = 3)
+          : (this.difference = 0);
+        if (val.routesList.routesList.length === this.columnsAsideList.length) return false;
+        // this.setFilterRoutes();
+      },
+      deep: true,
+    },
+    // 监听路由的变化
+    $route: {
+      handler(to) {
+        this.setColumnsMenuHighlight(to.path);
+        // this.setColumnsAsideMove();
+        let HeadName = getHeaderName(to, this.columnsAsideList);
+        let asideList = getMenuSider(this.columnsAsideList, HeadName)[0]?.children;
+        const resData = this.setSendChildren(HeadName);
+        if (resData.length <= 0) return false;
+        this.onColumnsAsideDown(resData.item[0].k);
+        this.bus.$emit('setSendColumnsChildren', asideList || []);
+      },
+      deep: true,
+    },
+  },
+};
+</script>
+
+<style scoped lang="scss">
+.layout-columns-aside {
+  width: 70px;
+  height: 100%;
+  background: var(--prev-bg-columnsMenuBar);
+  ul {
+    position: relative;
+    li {
+      color: var(--prev-bg-columnsMenuBarColor);
+      width: 100%;
+      height: 50px;
+      text-align: center;
+      display: flex;
+      cursor: pointer;
+      position: relative;
+      z-index: 1;
+      .columns-vertical {
+        margin: auto;
+        .columns-vertical-title {
+          padding-top: 1px;
+        }
+      }
+      .columns-horizontal {
+        display: flex;
+        height: 50px;
+        width: 100%;
+        align-items: center;
+        justify-content: center;
+        padding: 0 5px;
+        i {
+          margin-right: 3px;
+        }
+        a {
+          display: flex;
+          .columns-horizontal-title {
+            padding-top: 1px;
+          }
+        }
+      }
+      a {
+        text-decoration: none;
+        color: var(--prev-bg-columnsMenuBarColor);
+      }
+    }
+    .layout-columns-active,
+    .layout-columns-active a {
+      color: var(--prev-color-text-white);
+      transition: 0.3s ease-in-out;
+    }
+    .columns-round {
+      background: var(--prev-color-primary);
+      color: var(--prev-color-text-white);
+      position: absolute;
+      left: 50%;
+      top: 2px;
+      height: 50px;
+      width: 65px;
+      transform: translateX(-50%);
+      z-index: 0;
+      transition: 0.3s ease-in-out;
+      border-radius: 5px;
+    }
+    .columns-card {
+      @extend .columns-round;
+      top: 0;
+      height: 50px;
+      width: 100%;
+      border-radius: 0;
+    }
+  }
+}
+</style>

+ 24 - 0
template/admin/src/layout/component/header.vue

@@ -0,0 +1,24 @@
+<template>
+	<el-header class="layout-header" :height="setHeaderHeight">
+		<NavBarsIndex />
+	</el-header>
+</template>
+
+<script>
+import NavBarsIndex from '@/layout/navBars/index.vue';
+export default {
+	name: 'layoutHeader',
+	components: { NavBarsIndex },
+	data() {
+		return {};
+	},
+	computed: {
+		// 设置顶部 header 的具体高度
+		setHeaderHeight() {
+			let { isTagsview, layout } = this.$store.state.themeConfig.themeConfig;
+			if (isTagsview && layout !== 'classic') return '84px';
+			else return '50px';
+		},
+	},
+};
+</script>

+ 93 - 0
template/admin/src/layout/component/main.vue

@@ -0,0 +1,93 @@
+<template>
+	<el-main class="layout-main">
+		<el-scrollbar
+			class="layout-scrollbar"
+			ref="layoutScrollbarRef"
+			v-show="!currentRouteMeta.isLink && !currentRouteMeta.isIframe"
+			:style="{ minHeight: `calc(100vh - ${headerHeight}` }"
+		>
+			<LayoutParentView />
+			<Footers v-if="getThemeConfig.isFooter" />
+		</el-scrollbar>
+		<Links
+			:style="{ height: `calc(100vh - ${headerHeight}` }"
+			:meta="currentRouteMeta"
+			v-if="currentRouteMeta.isLink && !currentRouteMeta.isIframe"
+		/>
+		<Iframes
+			:style="{ height: `calc(100vh - ${headerHeight}` }"
+			:meta="currentRouteMeta"
+			v-if="currentRouteMeta.isLink && currentRouteMeta.isIframe && isShowLink"
+			@getCurrentRouteMeta="onGetCurrentRouteMeta"
+		/>
+	</el-main>
+</template>
+
+<script>
+import LayoutParentView from '@/layout/routerView/parent.vue';
+import Footers from '@/layout/footer/index.vue';
+import Links from '@/layout/routerView/link.vue';
+import Iframes from '@/layout/routerView/iframes.vue';
+export default {
+	name: 'layoutMain',
+	components: { LayoutParentView, Footers, Links, Iframes },
+	data() {
+		return {
+			headerHeight: '',
+			currentRouteMeta: {},
+			isShowLink: false,
+		};
+	},
+	computed: {
+		// 获取布局配置信息
+		getThemeConfig() {
+			return this.$store.state.themeConfig.themeConfig;
+		},
+	},
+	mounted() {
+		this.initHeaderHeight();
+		this.initCurrentRouteMeta(this.$route.meta);
+	},
+	methods: {
+		// 初始化当前路由 meta 信息
+		initCurrentRouteMeta(meta) {
+			this.isShowLink = false;
+			this.currentRouteMeta = meta;
+			setTimeout(() => {
+				this.isShowLink = true;
+			}, 100);
+		},
+		// 设置 main 的高度
+		initHeaderHeight() {
+			let { isTagsview } = this.$store.state.themeConfig.themeConfig;
+			if (isTagsview) return (this.headerHeight = `84px`);
+			else return (this.headerHeight = `50px`);
+		},
+		// 子组件触发更新
+		onGetCurrentRouteMeta() {
+			this.initCurrentRouteMeta(this.$route.meta);
+		},
+	},
+	watch: {
+		// 监听 vuex 数据变化
+		'$store.state.themeConfig.themeConfig': {
+			handler(val) {
+				this.headerHeight = val.isTagsview ? '84px' : '50px';
+				if (val.isFixedHeaderChange !== val.isFixedHeader) {
+					if (!this.$refs.layoutScrollbarRef) return false;
+					this.$refs.layoutScrollbarRef.update();
+				}
+			},
+			deep: true,
+		},
+		// 监听路由的变化
+		$route: {
+			handler(to) {
+				this.initCurrentRouteMeta(to.meta);
+				this.$refs.layoutScrollbarRef.wrap.scrollTop = 0;
+			},
+			deep: true,
+		},
+	},
+};
+</script>

+ 37 - 0
template/admin/src/layout/footer/index.vue

@@ -0,0 +1,37 @@
+<!--
+ * @Author: From-wh from-wh@hotmail.com
+ * @Date: 2023-03-09 15:45:51
+ * @FilePath: /admin/src/layout/footer/index.vue
+ * @Description: 
+-->
+<template>
+  <div class="layout-footer mt15">
+    <div class="layout-footer-warp">
+      <iCopyright />
+    </div>
+  </div>
+</template>
+
+<script>
+import iCopyright from '@/components/copyright';
+
+export default {
+  components: { iCopyright },
+  name: 'layoutFooter',
+  data() {
+    return {};
+  },
+};
+</script>
+
+<style scoped lang="scss">
+.layout-footer {
+  width: 100%;
+  display: flex;
+  &-warp {
+    margin: auto;
+    color: var(--prev-color-text-secondary);
+    text-align: center;
+  }
+}
+</style>

+ 79 - 0
template/admin/src/layout/index.vue

@@ -0,0 +1,79 @@
+<template>
+  <Mains v-if="headMenuNoShow" />
+  <Defaults v-else-if="getThemeConfig.layout === 'defaults'" />
+  <Classic v-else-if="getThemeConfig.layout === 'classic'" />
+  <Transverse v-else-if="getThemeConfig.layout === 'transverse'" />
+  <Columns v-else-if="getThemeConfig.layout === 'columns'" />
+</template>
+
+<script>
+import { Local } from '@/utils/storage.js';
+import { mapMutations } from 'vuex';
+import { getNewTagList } from '@/libs/util';
+import Mains from '@/layout/component/main.vue';
+
+export default {
+  name: 'layout',
+  components: {
+    Defaults: () => import('@/layout/main/defaults.vue'),
+    Classic: () => import('@/layout/main/classic.vue'),
+    Transverse: () => import('@/layout/main/transverse.vue'),
+    Columns: () => import('@/layout/main/columns.vue'),
+    Mains: () => import('@/layout/component/main.vue'),
+  },
+  data() {
+    return {
+      headMenuNoShow: false,
+    };
+  },
+  computed: {
+    // 获取布局配置信息
+    getThemeConfig() {
+      return this.$store.state.themeConfig.themeConfig;
+    },
+  },
+  watch: {
+    $route(newRoute) {
+      console.log(newRoute, '出发了。。。');
+      this.headMenuNoShow = this.$route.meta.fullScreen;
+      const { name, query, params, meta, path } = newRoute;
+      this.addTag({
+        route: { name, query, params, meta, path },
+        type: 'push',
+      });
+      this.setBreadCrumb(newRoute);
+      this.setTagNavList(getNewTagList(this.tagNavList, newRoute));
+    },
+  },
+  created() {
+    console.log(this.$route, 'this.$route.this.$route.');
+    this.headMenuNoShow = this.$route.meta.fullScreen;
+    this.onLayoutResize();
+    window.addEventListener('resize', this.onLayoutResize);
+  },
+  methods: {
+    ...mapMutations(['setBreadCrumb', 'setTagNavList', 'addTag', 'setLocal', 'setHomeRoute', 'closeTag']),
+
+    // 窗口大小改变时(适配移动端)
+    onLayoutResize() {
+      if (!Local.get('oldLayout')) Local.set('oldLayout', this.$store.state.themeConfig.themeConfig.layout);
+      const clientWidth = document.body.clientWidth;
+      if (clientWidth < 1000) {
+        this.$store.state.themeConfig.themeConfig.isCollapse = false;
+        this.bus.$emit('layoutMobileResize', {
+          layout: 'defaults',
+          clientWidth,
+        });
+      } else {
+        this.bus.$emit('layoutMobileResize', {
+          layout: Local.get('oldLayout') ? Local.get('oldLayout') : this.$store.state.themeConfig.themeConfig.layout,
+          clientWidth,
+        });
+      }
+    },
+  },
+  distroyed() {
+    window.removeEventListener('resize', this.onLayoutResize);
+  },
+};
+</script>

+ 87 - 0
template/admin/src/layout/logo/index.vue

@@ -0,0 +1,87 @@
+<template>
+  <div class="layout-logo" v-if="!$store.state.themeConfig.themeConfig.isCollapse" @click="onThemeConfigChange">
+    <img v-if="maxLogo" class="layout-logo-medium-img" :src="maxLogo" />
+  </div>
+  <div class="layout-logo-size" v-else @click="onThemeConfigChange">
+    <img v-if="minLogo" class="layout-logo-size-img" :src="minLogo" />
+  </div>
+</template>
+
+<script>
+import { getLogo } from '@/api/common';
+
+export default {
+  name: 'layoutLogo',
+  data() {
+    return {
+      minLogo: '',
+      maxLogo: '',
+    };
+  },
+  computed: {
+    // 获取布局配置信息
+    getThemeConfig() {
+      return this.$store.state.themeConfig.themeConfig;
+    },
+    // 设置 logo 是否显示
+    setShowLogo() {
+      let { isCollapse, layout } = this.$store.state.themeConfig.themeConfig;
+      return !isCollapse || layout === 'classic' || document.body.clientWidth < 1000;
+    },
+  },
+  mounted() {
+    this.getLogo();
+  },
+  methods: {
+    // logo 点击实现菜单展开/收起
+    onThemeConfigChange() {
+      if (this.$store.state.themeConfig.themeConfig.layout === 'transverse') return false;
+      this.$store.state.themeConfig.themeConfig.isCollapse = !this.$store.state.themeConfig.themeConfig.isCollapse;
+    },
+    getLogo() {
+      getLogo().then((res) => {
+        this.minLogo = res.data.logo_square;
+        this.maxLogo = res.data.logo;
+      });
+    },
+  },
+};
+</script>
+
+<style scoped lang="scss">
+.layout-logo {
+  width: 180px;
+  height: 50px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+//   box-shadow: 0px 1px 4px rgba(0, 21, 41, 2%);
+  color: var(--prev-color-primary);
+  font-size: 16px;
+  cursor: pointer;
+  animation: logoAnimation 0.3s ease-in-out;
+  &:hover {
+    span {
+      opacity: 0.9;
+    }
+  }
+  &-medium-img {
+    width: 100%;
+    height: 50px;
+    margin-right: 5px;
+    position: relative;
+    top: 2px;
+  }
+}
+.layout-logo-size {
+  width: 64px;
+  height: 50px;
+  display: flex;
+  cursor: pointer;
+  &-img {
+    width: 20px;
+    margin: auto;
+    animation: logoAnimation 0.3s ease-in-out;
+  }
+}
+</style>

+ 30 - 0
template/admin/src/layout/main/classic.vue

@@ -0,0 +1,30 @@
+<template>
+	<el-container class="layout-container flex-center">
+		<Headers />
+		<el-container class="layout-mian-height-50">
+			<Asides />
+			<div class="flex-center layout-backtop">
+				<TagsView v-if="getThemeConfig.isTagsview" />
+				<Mains />
+			</div>
+		</el-container>
+		<el-backtop target=".layout-backtop .el-main .el-scrollbar__wrap"></el-backtop>
+	</el-container>
+</template>
+
+<script>
+import Asides from '@/layout/component/aside.vue';
+import Headers from '@/layout/component/header.vue';
+import Mains from '@/layout/component/main.vue';
+import TagsView from '@/layout/navBars/tagsView/tagsView.vue';
+export default {
+	name: 'layoutClassic',
+	components: { Asides, Headers, Mains, TagsView },
+	computed: {
+		// 获取布局配置信息
+		getThemeConfig() {
+			return this.$store.state.themeConfig.themeConfig;
+		},
+	},
+};
+</script>

+ 33 - 0
template/admin/src/layout/main/columns.vue

@@ -0,0 +1,33 @@
+<template>
+	<el-container class="layout-container">
+		<ColumnsAside />
+		<div class="layout-columns-warp">
+			<Asides />
+			<el-container class="flex-center layout-backtop">
+				<Headers v-if="isFixedHeader" />
+				<el-scrollbar>
+					<Headers v-if="!isFixedHeader" />
+					<Mains />
+				</el-scrollbar>
+			</el-container>
+		</div>
+		<el-backtop target=".layout-backtop .el-scrollbar__wrap"></el-backtop>
+	</el-container>
+</template>
+
+<script>
+import Asides from '@/layout/component/aside.vue';
+import Headers from '@/layout/component/header.vue';
+import Mains from '@/layout/component/main.vue';
+import ColumnsAside from '@/layout/component/columnsAside.vue';
+export default {
+	name: 'layoutColumns',
+	components: { Asides, Headers, Mains, ColumnsAside },
+	computed: {
+		// 是否开启固定 header
+		isFixedHeader() {
+			return this.$store.state.themeConfig.themeConfig.isFixedHeader;
+		},
+	},
+};
+</script>

+ 41 - 0
template/admin/src/layout/main/defaults.vue

@@ -0,0 +1,41 @@
+<template>
+	<el-container class="layout-container">
+		<Asides />
+		<el-container class="flex-center layout-backtop">
+			<Headers v-if="isFixedHeader" />
+			<el-scrollbar ref="layoutDefaultsScrollbarRef">
+				<Headers v-if="!isFixedHeader" />
+				<Mains />
+			</el-scrollbar>
+		</el-container>
+		<el-backtop target=".layout-backtop .el-scrollbar__wrap"></el-backtop>
+	</el-container>
+</template>
+
+<script>
+import Asides from '@/layout/component/aside.vue';
+import Headers from '@/layout/component/header.vue';
+import Mains from '@/layout/component/main.vue';
+export default {
+	name: 'layoutDefaults',
+	components: { Asides, Headers, Mains },
+	data() {
+		return {};
+	},
+	computed: {
+		// 是否开启固定 header
+		isFixedHeader() {
+			return this.$store.state.themeConfig.themeConfig.isFixedHeader;
+		},
+	},
+	watch: {
+		// 监听路由的变化
+		$route: {
+			handler() {
+				this.$refs.layoutDefaultsScrollbarRef.wrap.scrollTop = 0;
+			},
+			deep: true,
+		},
+	},
+};
+</script>

+ 16 - 0
template/admin/src/layout/main/transverse.vue

@@ -0,0 +1,16 @@
+<template>
+	<el-container class="layout-container flex-center layout-backtop">
+		<Headers />
+		<Mains />
+		<el-backtop target=".layout-backtop .el-main .el-scrollbar__wrap"></el-backtop>
+	</el-container>
+</template>
+
+<script>
+import Headers from '@/layout/component/header.vue';
+import Mains from '@/layout/component/main.vue';
+export default {
+	name: 'layoutTransverse',
+	components: { Headers, Mains },
+};
+</script>

+ 202 - 0
template/admin/src/layout/navBars/breadcrumb/breadcrumb.vue

@@ -0,0 +1,202 @@
+<template>
+  <div class="layout-navbars-breadcrumb" :style="{ display: isShowBreadcrumb }">
+    <!-- {{[...breadCrumbList,...crumbPast]}} -->
+    <i
+      class="layout-navbars-breadcrumb-icon"
+      :class="getThemeConfig.isCollapse ? 'el-icon-s-unfold' : 'el-icon-s-fold'"
+      @click="onThemeConfigChange"
+    ></i>
+    <el-breadcrumb class="layout-navbars-breadcrumb-hide">
+      <transition-group name="breadcrumb" mode="out-in">
+        <el-breadcrumb-item v-for="(v, k) in [...breadCrumbList, ...crumbPast]" :key="v.path">
+          <span v-if="k == 1" class="layout-navbars-breadcrumb-span">
+            <Icon
+              :type="v.icon"
+              class="ivu-icon layout-navbars-breadcrumb-iconfont"
+              v-if="getThemeConfig.isBreadcrumbIcon"
+            />{{ $t(v.title) }}
+          </span>
+          <a v-else @click.prevent="onBreadcrumbClick(v)">
+            <Icon
+              :type="v.icon"
+              class="ivu-icon layout-navbars-breadcrumb-iconfont"
+              v-if="getThemeConfig.isBreadcrumbIcon"
+            />{{ $t(v.title) }}
+          </a>
+        </el-breadcrumb-item>
+      </transition-group>
+    </el-breadcrumb>
+  </div>
+</template>
+
+<script>
+import { Local } from '@/utils/storage.js';
+import { R } from '@/libs/util';
+import { getMenuopen } from '@/libs/util';
+
+export default {
+  name: 'layoutBreadcrumb',
+  data() {
+    return {
+      breadcrumbList: [],
+      routeSplit: [],
+      routeSplitFirst: '',
+      routeSplitIndex: 1,
+    };
+  },
+  computed: {
+    breadCrumbList() {
+      let menuList = this.$store.state.menus.menusName;
+      let openMenus = getMenuopen(this.$route, menuList);
+      let allMenuList = R(menuList, []);
+      let selectMenu = [];
+      if (allMenuList.length > 0) {
+        openMenus.forEach((i) => {
+          allMenuList.forEach((a) => {
+            if (i === a.path) {
+              selectMenu.push(a);
+            }
+          });
+        });
+      }
+      return selectMenu;
+    },
+    crumbPast() {
+      let that = this;
+      let menuList = that.$store.state.menus.menusName;
+      let allMenuList = R(menuList, []);
+      let selectMenu = [];
+      if (allMenuList.length > 0) {
+        allMenuList.forEach((a) => {
+          if (that.$route.path === a.path) {
+            selectMenu.push(a);
+          }
+        });
+      }
+      return selectMenu;
+    },
+    // 获取布局配置信息
+    getThemeConfig() {
+      return this.$store.state.themeConfig.themeConfig;
+    },
+    // 动态设置经典、横向布局不显示
+    isShowBreadcrumb() {
+      const { layout, isBreadcrumb } = this.$store.state.themeConfig.themeConfig;
+      if (layout === 'transverse') {
+        return 'none';
+      } else {
+        return isBreadcrumb ? '' : 'none';
+      }
+    },
+  },
+  mounted() {
+    this.initRouteSplit(this.$route.path);
+  },
+  methods: {
+    // breadcrumb 当前项点击时
+    onBreadcrumbClick(v) {
+      const { redirect, path } = v;
+      if (redirect) this.$router.push(redirect);
+      else this.$router.push(path);
+    },
+    // breadcrumb icon 点击菜单展开与收起
+    onThemeConfigChange() {
+      this.$store.state.themeConfig.themeConfig.isCollapse = !this.$store.state.themeConfig.themeConfig.isCollapse;
+      this.setLocalThemeConfig();
+    },
+    // 存储布局配置
+    setLocalThemeConfig() {
+      Local.remove('themeConfigPrev');
+      Local.set('themeConfigPrev', this.$store.state.themeConfig.themeConfig);
+    },
+    // 递归设置 breadcrumb
+    getBreadcrumbList(arr) {
+      console.log(this.routeSplit, 'routeSplit');
+      arr.map((item) => {
+        this.routeSplit.map((v, k, arrs) => {
+          if (this.routeSplitFirst === item.path) {
+            this.routeSplitFirst += `/${arrs[this.routeSplitIndex]}`;
+            this.breadcrumbList.push(item);
+            this.routeSplitIndex++;
+            if (item.children) this.getBreadcrumbList(item.children);
+          }
+        });
+      });
+      console.log(arr, 'arrarrarr');
+    },
+    // 当前路由分割处理
+    initRouteSplit(path) {
+      this.breadcrumbList = [
+        {
+          path: '/',
+          meta: {
+            title: this.$store.state.routesList.routesList[0].title,
+            icon: this.$store.state.routesList.routesList[0].icon,
+          },
+        },
+      ];
+      //   this.routeSplit = path.split('/');
+      console.log(path.split('/'), 'path');
+
+      //   this.routeSplit.shift();
+      this.routeSplitFirst = path;
+      this.routeSplitIndex = 1;
+      console.log(',,,,,');
+      this.getBreadcrumbList(this.$store.state.routesList.routesList);
+    },
+  },
+  // 监听路由的变化
+  watch: {
+    $route: {
+      handler(newVal) {
+        console.log(newVal, '123321`', this.$store.state.menus.menusName);
+        // this.initRouteSplit(newVal.path);
+        let menuList = this.$store.state.menus.menusName;
+        let openMenus = getMenuopen(newVal, menuList);
+        console.log('11', openMenus);
+        let allMenuList = R(menuList, []);
+        let selectMenu = [];
+        if (allMenuList.length > 0) {
+          openMenus.forEach((i) => {
+            allMenuList.forEach((a) => {
+              if (i === a.path) {
+                selectMenu.push(a);
+              }
+            });
+          });
+        }
+        console.log(openMenus, 'selectMenu');
+      },
+      deep: true,
+    },
+  },
+};
+</script>
+
+<style scoped lang="scss">
+.layout-navbars-breadcrumb {
+  flex: 1;
+  height: inherit;
+  display: flex;
+  align-items: center;
+  padding-left: 15px;
+  .layout-navbars-breadcrumb-icon {
+    cursor: pointer;
+    font-size: 18px;
+    margin-right: 15px;
+    color: var(--prev-bg-topBarColor);
+    opacity: 0.8;
+    &:hover {
+      opacity: 1;
+    }
+  }
+  .layout-navbars-breadcrumb-span {
+    opacity: 0.7;
+    color: var(--prev-bg-topBarColor);
+  }
+  .layout-navbars-breadcrumb-iconfont {
+    font-size: 14px;
+    margin-right: 5px;
+  }
+}
+</style>

+ 78 - 0
template/admin/src/layout/navBars/breadcrumb/index.vue

@@ -0,0 +1,78 @@
+<template>
+  <div class="layout-navbars-breadcrumb-index">
+    <Logo v-if="setIsShowLogo" />
+    <Breadcrumb />
+    <Horizontal :menuList="menuList" v-if="isLayoutTransverse" />
+    <User />
+  </div>
+</template>
+
+<script>
+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';
+export default {
+  name: 'layoutNavBars',
+  components: { Breadcrumb, User, Logo, Horizontal },
+  data() {
+    return {
+      menuList: [],
+    };
+  },
+  computed: {
+    // 设置 logo 是否显示
+    setIsShowLogo() {
+      let { isShowLogo, layout } = this.$store.state.themeConfig.themeConfig;
+      console.log(this.$store.state.themeConfig.themeConfig, layout, 'isShowLogo, layout');
+      return (isShowLogo && layout === 'classic') || (isShowLogo && layout === 'transverse');
+    },
+    // 设置是否显示横向菜单
+    isLayoutTransverse() {
+      let { layout, isClassicSplitMenu } = this.$store.state.themeConfig.themeConfig;
+      return layout === 'transverse' || (isClassicSplitMenu && layout === 'classic');
+    },
+  },
+  mounted() {
+    this.setFilterRoutes();
+  },
+  methods: {
+    // 设置路由的过滤
+    setFilterRoutes() {
+      this.menuList = this.filterRoutesFun(this.$store.state.routesList.routesList);
+    },
+    // 设置路由的过滤递归函数
+    filterRoutesFun(arr) {
+      return arr
+        .filter((item) => item.path)
+        .map((item) => {
+          item = Object.assign({}, item);
+          if (item.children) item.children = this.filterRoutesFun(item.children);
+          return item;
+        });
+    },
+  },
+  watch: {
+    // 监听 vuex 数据变化
+    '$store.state': {
+      handler(val) {
+        if (val.routesList.routesList.length === this.menuList.length) return false;
+        this.setFilterRoutes();
+      },
+      deep: true,
+    },
+  },
+};
+</script>
+
+<style scoped lang="scss">
+.layout-navbars-breadcrumb-index {
+  height: 50px;
+  display: flex;
+  align-items: center;
+  padding-right: 15px;
+  overflow: hidden;
+  background: var(--prev-bg-topBar);
+//   border-bottom: 1px solid var(--prev-border-color-lighter);
+}
+</style>

+ 102 - 0
template/admin/src/layout/navBars/breadcrumb/search.vue

@@ -0,0 +1,102 @@
+<template>
+	<div class="layout-search-dialog">
+		<el-dialog :visible.sync="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')"
+				prefix-icon="el-icon-search"
+				ref="layoutMenuAutocompleteRef"
+				@select="onHandleSelect"
+				@blur="onSearchBlur"
+			>
+				<template slot-scope="{ item }">
+					<div><i :class="item.icon" class="mr10"></i>{{ $t(item.title) }}</div>
+				</template>
+			</el-autocomplete>
+		</el-dialog>
+	</div>
+</template>
+
+<script>
+import { getAllSiderMenu } from '@/libs/system'
+export default {
+	name: 'layoutBreadcrumbSearch',
+	data() {
+		return {
+			isShowSearch: false,
+			menuQuery: '',
+			tagsViewList: [],
+		};
+	},
+	methods: {
+		// 搜索弹窗打开
+		openSearch() {
+			this.menuQuery = '';
+			this.isShowSearch = true;
+			this.initTageView();
+			this.$nextTick(() => {
+				this.$refs.layoutMenuAutocompleteRef.focus();
+			});
+		},
+		// 搜索弹窗关闭
+		closeSearch() {
+			setTimeout(() => {
+				this.isShowSearch = false;
+			}, 150);
+		},
+		// 菜单搜索数据过滤
+		menuSearch(queryString, cb) {
+			let results = queryString ? this.tagsViewList.filter(this.createFilter(queryString)) : this.tagsViewList;
+			cb(results);
+		},
+		// 菜单搜索过滤
+		createFilter(queryString) {
+			return (restaurant) => {
+				return (
+					restaurant.path.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
+					restaurant.title.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
+					this.$t(restaurant.title).toLowerCase().indexOf(queryString.toLowerCase()) > -1
+				);
+			};
+		},
+		// 初始化菜单数据
+		initTageView() {
+			if (this.tagsViewList.length > 0) return false;
+			this.tagsViewList = getAllSiderMenu(this.$store.state.routesList.routesList)
+			// this.$store.state.tagsViewRoutes.tagsViewRoutes.map((v) => {
+			// 	if (!v.isHide) this.tagsViewList.push({ ...v });
+			// });
+		},
+		// 当前菜单选中时
+		onHandleSelect(item) {
+			let { path, redirect } = item;
+			if (item.isLink && !item.isIframe) window.open(item.isLink);
+			else if (redirect) this.$router.push(redirect);
+			else this.$router.push(path);
+			this.closeSearch();
+		},
+		// input 失去焦点时
+		onSearchBlur() {
+			this.closeSearch();
+		},
+	},
+};
+</script>
+
+<style scoped lang="scss">
+.layout-search-dialog {
+	::v-deep .el-dialog {
+		box-shadow: unset !important;
+		border-radius: 0 !important;
+		background: rgba(0, 0, 0, 0.5);
+	}
+	::v-deep .el-autocomplete {
+		width: 560px;
+		position: absolute;
+		top: 100px;
+		left: 50%;
+		transform: translateX(-50%);
+	}
+}
+</style>

+ 596 - 0
template/admin/src/layout/navBars/breadcrumb/setings.vue

@@ -0,0 +1,596 @@
+<template>
+  <div class="layout-breadcrumb-seting">
+    <el-drawer
+      :title="$t('message.layout.configTitle')"
+      :visible.sync="getThemeConfig.isDrawer"
+      direction="rtl"
+      destroy-on-close
+      size="240px"
+      @close="onDrawerClose"
+    >
+      <el-scrollbar class="layout-breadcrumb-seting-bar">
+        <!-- 全局主题 -->
+        <el-divider content-position="left">{{ $t('message.layout.oneTitle') }}</el-divider>
+        <div class="layout-breadcrumb-seting-bar-flex">
+          <div class="layout-breadcrumb-seting-bar-flex-label">primary</div>
+          <div class="layout-breadcrumb-seting-bar-flex-value">
+            <el-color-picker v-model="getThemeConfig.primary" size="small" @change="onColorPickerChange">
+            </el-color-picker>
+          </div>
+        </div>
+        <div class="layout-breadcrumb-seting-bar-flex mt15">
+          <div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsDark') }}</div>
+          <div class="layout-breadcrumb-seting-bar-flex-value">
+            <el-switch v-model="getThemeConfig.isIsDark" :width="35" @change="onAddDarkChange"></el-switch>
+          </div>
+        </div>
+
+        <!-- 界面设置 -->
+        <el-divider content-position="left">{{ $t('message.layout.threeTitle') }}</el-divider>
+        <div class="layout-breadcrumb-seting-bar-flex">
+          <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" :width="35" @change="setLocalThemeConfig"></el-switch>
+          </div>
+        </div>
+        <div class="layout-breadcrumb-seting-bar-flex mt15">
+          <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" :width="35" @change="setLocalThemeConfig"></el-switch>
+          </div>
+        </div>
+        <div class="layout-breadcrumb-seting-bar-flex mt15">
+          <div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsFixedHeader') }}</div>
+          <div class="layout-breadcrumb-seting-bar-flex-value">
+            <el-switch v-model="getThemeConfig.isFixedHeader" :width="35" @change="setLocalThemeConfig"></el-switch>
+          </div>
+        </div>
+
+        <!-- 界面显示 -->
+        <el-divider content-position="left">{{ $t('message.layout.fourTitle') }}</el-divider>
+        <div class="layout-breadcrumb-seting-bar-flex">
+          <div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsShowLogo') }}</div>
+          <div class="layout-breadcrumb-seting-bar-flex-value">
+            <el-switch v-model="getThemeConfig.isShowLogo" :width="35" @change="setLocalThemeConfig"></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-label">{{ $t('message.layout.fourIsBreadcrumb') }}</div>
+          <div class="layout-breadcrumb-seting-bar-flex-value">
+            <el-switch
+              v-model="getThemeConfig.isBreadcrumb"
+              :disabled="getThemeConfig.layout === 'classic' || getThemeConfig.layout === 'transverse'"
+              :width="35"
+              @change="setLocalThemeConfig"
+            ></el-switch>
+          </div>
+        </div>
+        <div class="layout-breadcrumb-seting-bar-flex mt15">
+          <div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsBreadcrumbIcon') }}</div>
+          <div class="layout-breadcrumb-seting-bar-flex-value">
+            <el-switch v-model="getThemeConfig.isBreadcrumbIcon" :width="35" @change="setLocalThemeConfig"></el-switch>
+          </div>
+        </div>
+        <div class="layout-breadcrumb-seting-bar-flex mt15">
+          <div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsTagsview') }}</div>
+          <div class="layout-breadcrumb-seting-bar-flex-value">
+            <el-switch v-model="getThemeConfig.isTagsview" :width="35" @change="setLocalThemeConfig"></el-switch>
+          </div>
+        </div>
+        <div class="layout-breadcrumb-seting-bar-flex mt15">
+          <div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsTagsviewIcon') }}</div>
+          <div class="layout-breadcrumb-seting-bar-flex-value">
+            <el-switch v-model="getThemeConfig.isTagsviewIcon" :width="35" @change="setLocalThemeConfig"></el-switch>
+          </div>
+        </div>
+        <!-- <div class="layout-breadcrumb-seting-bar-flex mt15">
+          <div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsCacheTagsView') }}</div>
+          <div class="layout-breadcrumb-seting-bar-flex-value">
+            <el-switch v-model="getThemeConfig.isCacheTagsView" :width="35" @change="setLocalThemeConfig"></el-switch>
+          </div>
+        </div> -->
+        <div class="layout-breadcrumb-seting-bar-flex mt15">
+          <div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsFooter') }}</div>
+          <div class="layout-breadcrumb-seting-bar-flex-value">
+            <el-switch v-model="getThemeConfig.isFooter" :width="35" @change="setLocalThemeConfig"></el-switch>
+          </div>
+        </div>
+        <div class="layout-breadcrumb-seting-bar-flex mt15">
+          <div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsGrayscale') }}</div>
+          <div class="layout-breadcrumb-seting-bar-flex-value">
+            <el-switch
+              v-model="getThemeConfig.isGrayscale"
+              :width="35"
+              @change="onAddFilterChange('grayscale')"
+            ></el-switch>
+          </div>
+        </div>
+        <div class="layout-breadcrumb-seting-bar-flex mt15">
+          <div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsInvert') }}</div>
+          <div class="layout-breadcrumb-seting-bar-flex-value">
+            <el-switch v-model="getThemeConfig.isInvert" :width="35" @change="onAddFilterChange('invert')"></el-switch>
+          </div>
+        </div>
+
+        <!-- 其它设置 -->
+        <el-divider content-position="left">{{ $t('message.layout.fiveTitle') }}</el-divider>
+        <div class="layout-breadcrumb-seting-bar-flex mt15">
+          <div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveTagsStyle') }}</div>
+          <div class="layout-breadcrumb-seting-bar-flex-value">
+            <el-select
+              v-model="getThemeConfig.tagsStyle"
+              placeholder="请选择"
+              size="mini"
+              style="width: 90px"
+              @change="setLocalThemeConfig"
+            >
+              <el-option label="风格1" value="tags-style-one"></el-option>
+              <el-option label="风格2" value="tags-style-four"></el-option>
+              <el-option label="风格3" value="tags-style-five"></el-option>
+            </el-select>
+          </div>
+        </div>
+        <div class="layout-breadcrumb-seting-bar-flex mt15">
+          <div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveAnimation') }}</div>
+          <div class="layout-breadcrumb-seting-bar-flex-value">
+            <el-select
+              v-model="getThemeConfig.animation"
+              placeholder="请选择"
+              size="mini"
+              style="width: 90px"
+              @change="setLocalThemeConfig"
+            >
+              <el-option label="slide-right" value="slide-right"></el-option>
+              <el-option label="slide-left" value="slide-left"></el-option>
+              <el-option label="opacitys" value="opacitys"></el-option>
+            </el-select>
+          </div>
+        </div>
+        <div class="layout-breadcrumb-seting-bar-flex mt15">
+          <div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveColumnsAsideStyle') }}</div>
+          <div class="layout-breadcrumb-seting-bar-flex-value">
+            <el-select
+              v-model="getThemeConfig.columnsAsideStyle"
+              placeholder="请选择"
+              size="mini"
+              style="width: 90px"
+              @change="setLocalThemeConfig"
+            >
+              <el-option label="圆角" value="columns-round"></el-option>
+              <el-option label="卡片" value="columns-card"></el-option>
+            </el-select>
+          </div>
+        </div>
+        <div class="layout-breadcrumb-seting-bar-flex mt15 mb28">
+          <div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveColumnsAsideLayout') }}</div>
+          <div class="layout-breadcrumb-seting-bar-flex-value">
+            <el-select
+              v-model="getThemeConfig.columnsAsideLayout"
+              placeholder="请选择"
+              size="mini"
+              style="width: 90px"
+              @change="setLocalThemeConfig"
+            >
+              <el-option label="水平" value="columns-horizontal"></el-option>
+              <el-option label="垂直" value="columns-vertical"></el-option>
+            </el-select>
+          </div>
+        </div>
+
+        <!-- 布局切换 -->
+        <el-divider content-position="left">{{ $t('message.layout.sixTitle') }}</el-divider>
+        <div class="layout-drawer-content-flex">
+          <!-- defaults 布局 -->
+          <div class="layout-drawer-content-item" @click="onSetLayout('defaults')">
+            <section
+              class="el-container el-circular"
+              :class="{ 'drawer-layout-active': getThemeConfig.layout === 'defaults' }"
+            >
+              <aside class="el-aside" style="width: 20px"></aside>
+              <section class="el-container is-vertical">
+                <header class="el-header" style="height: 10px"></header>
+                <main class="el-main"></main>
+              </section>
+            </section>
+            <div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'defaults' }">
+              <div class="layout-tips-box">
+                <p class="layout-tips-txt">{{ $t('message.layout.sixDefaults') }}</p>
+              </div>
+            </div>
+          </div>
+          <!-- classic 布局 -->
+          <div class="layout-drawer-content-item" @click="onSetLayout('classic')">
+            <section
+              class="el-container is-vertical el-circular"
+              :class="{ 'drawer-layout-active': getThemeConfig.layout === 'classic' }"
+            >
+              <header class="el-header" style="height: 10px"></header>
+              <section class="el-container">
+                <aside class="el-aside" style="width: 20px"></aside>
+                <section class="el-container is-vertical">
+                  <main class="el-main"></main>
+                </section>
+              </section>
+            </section>
+            <div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'classic' }">
+              <div class="layout-tips-box">
+                <p class="layout-tips-txt">{{ $t('message.layout.sixClassic') }}</p>
+              </div>
+            </div>
+          </div>
+          <!-- transverse 布局 -->
+          <div class="layout-drawer-content-item" @click="onSetLayout('transverse')">
+            <section
+              class="el-container is-vertical el-circular"
+              :class="{ 'drawer-layout-active': getThemeConfig.layout === 'transverse' }"
+            >
+              <header class="el-header" style="height: 10px"></header>
+              <section class="el-container">
+                <section class="el-container is-vertical">
+                  <main class="el-main"></main>
+                </section>
+              </section>
+            </section>
+            <div
+              class="layout-tips-warp"
+              :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'transverse' }"
+            >
+              <div class="layout-tips-box">
+                <p class="layout-tips-txt">{{ $t('message.layout.sixTransverse') }}</p>
+              </div>
+            </div>
+          </div>
+          <!-- columns 布局 -->
+          <div class="layout-drawer-content-item" @click="onSetLayout('columns')">
+            <section
+              class="el-container el-circular"
+              :class="{ 'drawer-layout-active': getThemeConfig.layout === 'columns' }"
+            >
+              <aside class="el-aside-dark" style="width: 10px"></aside>
+              <aside class="el-aside" style="width: 20px"></aside>
+              <section class="el-container is-vertical">
+                <header class="el-header" style="height: 10px"></header>
+                <main class="el-main"></main>
+              </section>
+            </section>
+            <div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'columns' }">
+              <div class="layout-tips-box">
+                <p class="layout-tips-txt">{{ $t('message.layout.sixColumns') }}</p>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="copy-config">
+          <el-alert :title="$t('message.layout.tipText')" type="warning" :closable="false"> </el-alert>
+          <el-button
+            size="small"
+            class="copy-config-btn"
+            icon="el-icon-document-copy"
+            type="primary"
+            ref="copyConfigBtnRef"
+            @click="onCopyConfigClick"
+            >{{ $t('message.layout.copyText') }}
+          </el-button>
+          <el-button
+            size="small"
+            class="copy-config-btn-reset"
+            type="info"
+            icon="el-icon-refresh-right"
+            @click="onResetConfigClick"
+          >
+            {{ $t('message.layout.resetText') }}
+          </el-button>
+        </div>
+      </el-scrollbar>
+    </el-drawer>
+  </div>
+</template>
+
+<script>
+import ClipboardJS from 'clipboard';
+import { Local } from '@/utils/storage.js';
+import { useChangeColor } from '@/utils/theme.js';
+import config from '../../../../package.json';
+export default {
+  name: 'layoutBreadcrumbSeting',
+  computed: {
+    // 获取布局配置信息
+    getThemeConfig() {
+      return this.$store.state.themeConfig.themeConfig;
+    },
+  },
+  created() {
+    // 判断当前布局是否不相同,不相同则初始化当前布局的样式,防止监听窗口大小改变时,布局配置logo、菜单背景等部分布局失效问题
+    if (!Local.get('frequency')) this.initSetLayoutChange();
+    Local.set('frequency', 1);
+    // 监听窗口大小改变,非默认布局,设置成默认布局(适配移动端)
+    this.bus.$on('layoutMobileResize', (res) => {
+      if (this.$store.state.themeConfig.themeConfig.layout === res.layout) return false;
+      this.$store.state.themeConfig.themeConfig.layout = res.layout;
+      this.$store.state.themeConfig.themeConfig.isDrawer = false;
+      this.$store.state.themeConfig.themeConfig.isCollapse = false;
+      this.initSetLayoutChange();
+    });
+  },
+  mounted() {
+    this.initLayoutConfig();
+  },
+  methods: {
+    // 全局主题
+    onColorPickerChange() {
+      if (!this.getThemeConfig.primary) return;
+      // 颜色加深
+      document.documentElement.style.setProperty('--prev-color-primary', this.getThemeConfig.primary);
+      // 颜色变浅
+      for (let i = 1; i <= 9; i++) {
+        document.documentElement.style.setProperty(
+          `--prev-color-primary-light-${i}`,
+          `${useChangeColor().getLightColor(this.getThemeConfig.primary, i / 10)}`,
+        );
+      }
+      this.setLocalThemeConfig();
+    },
+    // 深色模式
+    onAddDarkChange() {
+      const body = document.documentElement;
+      if (this.getThemeConfig.isIsDark) body.setAttribute('data-theme', 'dark');
+      else body.setAttribute('data-theme', '');
+      this.setLocalThemeConfig();
+    },
+    // 初始化:刷新页面时,设置了值,直接取缓存中的值进行初始化
+    initLayoutConfig() {
+      window.addEventListener('load', () => {
+        // 默认样式
+        this.onColorPickerChange();
+        // 灰色模式
+        if (this.$store.state.themeConfig.themeConfig.isGrayscale) this.onAddFilterChange('grayscale');
+        // 色弱模式
+        if (this.$store.state.themeConfig.themeConfig.isInvert) this.onAddFilterChange('invert');
+        // 深色模式
+        if (this.$store.state.themeConfig.themeConfig.isIsDark) this.onAddDarkChange();
+        // 语言国际化
+        if (Local.get('themeConfigPrev')) this.$i18n.locale = Local.get('themeConfigPrev').globalI18n;
+      });
+    },
+    // 存储布局配置
+    setLocalThemeConfig() {
+      Local.remove('themeConfigPrev');
+      Local.set('themeConfigPrev', this.$store.state.themeConfig.themeConfig);
+      this.setLocalThemeConfigStyle();
+    },
+    // 存储布局配置全局主题样式(html根标签)
+    setLocalThemeConfigStyle() {
+      Local.set('themeConfigStyle', document.documentElement.style.cssText);
+    },
+    // 布局配置弹窗打开
+    openDrawer() {
+      console.log('触发了');
+      this.$store.state.themeConfig.themeConfig.isDrawer = true;
+    },
+    // 关闭弹窗时,初始化变量
+    onDrawerClose() {
+      this.$store.state.themeConfig.themeConfig.isDrawer = false;
+      this.setLocalThemeConfig();
+    },
+    // 灰色模式/色弱模式
+    onAddFilterChange(attr) {
+      if (attr === 'grayscale') {
+        if (this.$store.state.themeConfig.themeConfig.isGrayscale)
+          this.$store.state.themeConfig.themeConfig.isInvert = false;
+      } else {
+        if (this.$store.state.themeConfig.themeConfig.isInvert)
+          this.$store.state.themeConfig.themeConfig.isGrayscale = false;
+      }
+      const cssAttr =
+        attr === 'grayscale'
+          ? `grayscale(${this.$store.state.themeConfig.themeConfig.isGrayscale ? 1 : 0})`
+          : `invert(${this.$store.state.themeConfig.themeConfig.isInvert ? '80%' : '0%'})`;
+      const appEle = document.body;
+      appEle.setAttribute('style', `filter: ${cssAttr};`);
+      this.setLocalThemeConfig();
+    },
+    // 布局切换
+    onSetLayout(layout) {
+      Local.set('oldLayout', layout);
+      if (this.$store.state.themeConfig.themeConfig.layout === layout) return false;
+      this.$store.state.themeConfig.themeConfig.layout = layout;
+      this.$store.state.themeConfig.themeConfig.isDrawer = false;
+      this.initSetLayoutChange();
+    },
+    // 设置布局切换,重置主题样式
+    initSetLayoutChange() {
+      console.log(this.$store.state.themeConfig.themeConfig.layout);
+      if (this.$store.state.themeConfig.themeConfig.layout === 'classic') {
+        this.onBgColorPickerChange('menuBar', '#282c34');
+        this.onBgColorPickerChange('menuBarColor', '#ffffff');
+        this.onBgColorPickerChange('topBar', '#282c34');
+        this.onBgColorPickerChange('topBarColor', '#ffffff');
+      } else if (this.$store.state.themeConfig.themeConfig.layout === 'transverse') {
+        this.onBgColorPickerChange('menuBarColor', '#ffffff');
+        this.onBgColorPickerChange('topBar', '#282c34');
+        this.onBgColorPickerChange('topBarColor', '#fff');
+        // this.onBgColorPickerChange('bgTopBarColor', '#ccc');
+      } else if (this.$store.state.themeConfig.themeConfig.layout === 'columns') {
+        this.onBgColorPickerChange('menuBar', '#282c34');
+        this.onBgColorPickerChange('menuBarColor', '#fff');
+        this.onBgColorPickerChange('topBar', '#ffffff');
+        this.onBgColorPickerChange('topBarColor', '#606266');
+        this.onBgColorPickerChange('columnsMenuBar', '#282c34');
+      } else {
+        this.onBgColorPickerChange('menuBar', '#282c34');
+        this.onBgColorPickerChange('menuBarColor', '#ffffff');
+        this.onBgColorPickerChange('topBar', '#ffffff');
+        this.onBgColorPickerChange('topBarColor', '#606266');
+      }
+    },
+    // 菜单 / 顶栏背景等
+    onBgColorPickerChange(bg, rgb) {
+      document.documentElement.style.setProperty(`--prev-bg-${bg}`, rgb);
+      this.setLocalThemeConfigStyle();
+    },
+    // 一键复制配置
+    onCopyConfigClick() {
+      this.$store.state.themeConfig.themeConfig.isDrawer = false;
+      let clipboardJS = new ClipboardJS('.copy-config-btn', {
+        text: () => JSON.stringify(this.$store.state.themeConfig.themeConfig),
+      });
+      clipboardJS.on('success', () => {
+        this.$message.success('配置复制成功');
+        this.isDrawer = false;
+        clipboardJS.destroy();
+      });
+      clipboardJS.on('error', () => {
+        this.$message.error('配置复制失败');
+      });
+    },
+    // 一键恢复默认
+    onResetConfigClick() {
+      Local.clear();
+      window.location.reload();
+      Local.set('version', config.version);
+    },
+  },
+};
+</script>
+
+<style scoped lang="scss">
+.layout-breadcrumb-seting-bar {
+  height: calc(100vh - 50px);
+  padding: 0 15px;
+  ::v-deep .el-scrollbar__view {
+    overflow-x: hidden !important;
+  }
+  .layout-breadcrumb-seting-bar-flex {
+    display: flex;
+    align-items: center;
+    &-label {
+      flex: 1;
+      color: var(--prev-color-text-primary);
+    }
+  }
+  .layout-drawer-content-flex {
+    overflow: hidden;
+    display: flex;
+    flex-wrap: wrap;
+    align-content: flex-start;
+    margin: 0 -5px;
+    .layout-drawer-content-item {
+      width: 50%;
+      height: 70px;
+      cursor: pointer;
+      border: 1px solid transparent;
+      position: relative;
+      padding: 5px;
+      .el-container {
+        height: 100%;
+        .el-aside-dark {
+          background-color: var(--prev-color-seting-header);
+        }
+        .el-aside {
+          background-color: var(--prev-color-seting-aside);
+        }
+        .el-header {
+          background-color: var(--prev-color-seting-header);
+        }
+        .el-main {
+          background-color: var(--prev-color-seting-main);
+        }
+      }
+      .el-circular {
+        border-radius: 2px;
+        overflow: hidden;
+        border: 1px solid transparent;
+        transition: all 0.3s ease-in-out;
+      }
+      .drawer-layout-active {
+        border: 1px solid;
+        border-color: var(--prev-color-primary);
+      }
+      .layout-tips-warp,
+      .layout-tips-warp-active {
+        transition: all 0.3s ease-in-out;
+        position: absolute;
+        left: 50%;
+        top: 50%;
+        transform: translate(-50%, -50%);
+        border: 1px solid;
+        border-color: var(--prev-color-primary-light-5);
+        border-radius: 100%;
+        padding: 4px;
+        .layout-tips-box {
+          transition: inherit;
+          width: 30px;
+          height: 30px;
+          z-index: 9;
+          border: 1px solid;
+          border-color: var(--prev-color-primary-light-5);
+          border-radius: 100%;
+          .layout-tips-txt {
+            transition: inherit;
+            position: relative;
+            top: 5px;
+            font-size: 12px;
+            line-height: 1;
+            letter-spacing: 2px;
+            white-space: nowrap;
+            color: var(--prev-color-primary-light-5);
+            text-align: center;
+            transform: rotate(30deg);
+            left: -1px;
+            background-color: var(--prev-color-seting-main);
+            width: 32px;
+            height: 17px;
+            line-height: 17px;
+          }
+        }
+      }
+      .layout-tips-warp-active {
+        border: 1px solid;
+        border-color: var(--prev-color-primary);
+        .layout-tips-box {
+          border: 1px solid;
+          border-color: var(--prev-color-primary);
+          .layout-tips-txt {
+            color: var(--prev-color-primary) !important;
+            background-color: var(--prev-color-seting-main) !important;
+          }
+        }
+      }
+      &:hover {
+        .el-circular {
+          transition: all 0.3s ease-in-out;
+          border: 1px solid;
+          border-color: var(--prev-color-primary);
+        }
+        .layout-tips-warp {
+          transition: all 0.3s ease-in-out;
+          border-color: var(--prev-color-primary);
+          .layout-tips-box {
+            transition: inherit;
+            border-color: var(--prev-color-primary);
+            .layout-tips-txt {
+              transition: inherit;
+              color: var(--prev-color-primary) !important;
+              background-color: var(--prev-color-seting-main) !important;
+            }
+          }
+        }
+      }
+    }
+  }
+  .copy-config {
+    margin: 10px 0;
+    .copy-config-btn {
+      width: 100%;
+      margin-top: 15px;
+    }
+    .copy-config-btn-reset {
+      width: 100%;
+      margin: 10px 0 0;
+    }
+  }
+}
+</style>

+ 286 - 0
template/admin/src/layout/navBars/breadcrumb/user.vue

@@ -0,0 +1,286 @@
+<template>
+  <div class="layout-navbars-breadcrumb-user" :style="{ flex: layoutUserFlexNum }">
+    <!-- <el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onComponentSizeChange">
+			<div class="layout-navbars-breadcrumb-user-icon">
+				<i class="el-icon-magic-stick" :title="$t('message.user.title0')"></i>
+			</div>
+			<template #dropdown>
+				<el-dropdown-menu>
+					<el-dropdown-item command="" :disabled="disabledSize === ''">{{ $t('message.user.dropdownDefault') }}</el-dropdown-item>
+					<el-dropdown-item command="medium" :disabled="disabledSize === 'medium'">{{ $t('message.user.dropdownMedium') }}</el-dropdown-item>
+					<el-dropdown-item command="small" :disabled="disabledSize === 'small'">{{ $t('message.user.dropdownSmall') }}</el-dropdown-item>
+					<el-dropdown-item command="mini" :disabled="disabledSize === 'mini'">{{ $t('message.user.dropdownMini') }}</el-dropdown-item>
+				</el-dropdown-menu>
+			</template>
+		</el-dropdown> -->
+    <!-- <el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onLanguageChange">
+			<div class="layout-navbars-breadcrumb-user-icon">
+				<i class="iconfont" :class="disabledI18n === 'en' ? 'icon-fuhao-yingwen' : 'icon-fuhao-zhongwen'" :title="$t('message.user.title1')"></i>
+			</div>
+			<el-dropdown-menu slot="dropdown">
+				<el-dropdown-item command="zh-cn" :disabled="disabledI18n === 'zh-cn'">简体中文</el-dropdown-item>
+				<el-dropdown-item command="en" :disabled="disabledI18n === 'en'">English</el-dropdown-item>
+				<el-dropdown-item command="zh-tw" :disabled="disabledI18n === 'zh-tw'">繁體中文</el-dropdown-item>
+			</el-dropdown-menu>
+		</el-dropdown> -->
+    <div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
+      <i class="el-icon-search" :title="$t('message.user.title2')"></i>
+    </div>
+    <div class="layout-navbars-breadcrumb-user-icon" @click="onLayoutSetingClick">
+      <i class="el-icon-star-off" :title="$t('message.user.title3')"></i>
+    </div>
+    <div class="layout-navbars-breadcrumb-user-icon">
+      <el-popover
+        placement="bottom"
+        trigger="click"
+        v-model="isShowUserNewsPopover"
+        :width="300"
+        popper-class="el-popover-pupop-user-news"
+      >
+        <el-badge :is-dot="true" @click.stop="isShowUserNewsPopover = !isShowUserNewsPopover" slot="reference">
+          <i class="el-icon-bell" :title="$t('message.user.title4')"></i>
+        </el-badge>
+        <transition name="el-zoom-in-top">
+          <UserNews v-show="isShowUserNewsPopover" />
+        </transition>
+      </el-popover>
+    </div>
+    <div class="layout-navbars-breadcrumb-user-icon mr10" @click="onScreenfullClick">
+      <i
+        class="iconfont"
+        :title="isScreenfull ? $t('message.user.title6') : $t('message.user.title5')"
+        :class="!isScreenfull ? 'icon-fullscreen' : 'icon-tuichuquanping'"
+      ></i>
+    </div>
+    <el-dropdown :show-timeout="70" :hide-timeout="50" @command="onDropdownCommand">
+      <span class="layout-navbars-breadcrumb-user-link">
+        <!-- <img :src="getUserInfos.photo" class="layout-navbars-breadcrumb-user-link-photo mr5" /> -->
+        {{ getUserInfos.account === '' ? 'test' : getUserInfos.account }}
+        <i class="el-icon-arrow-down el-icon--right"></i>
+      </span>
+      <el-dropdown-menu slot="dropdown">
+        <el-dropdown-item command="/home">{{ $t('message.user.dropdown1') }}</el-dropdown-item>
+        <!-- <el-dropdown-item command="wareHouse">{{ $t('message.user.dropdown6') }}</el-dropdown-item>
+        <el-dropdown-item command="/personal">{{ $t('message.user.dropdown2') }}</el-dropdown-item>
+        <el-dropdown-item command="/404">{{ $t('message.user.dropdown3') }}</el-dropdown-item>
+        <el-dropdown-item command="/401">{{ $t('message.user.dropdown4') }}</el-dropdown-item> -->
+        <el-dropdown-item divided command="logOut">{{ $t('message.user.dropdown5') }}</el-dropdown-item>
+      </el-dropdown-menu>
+    </el-dropdown>
+    <Search ref="searchRef" />
+  </div>
+</template>
+
+<script>
+import screenfull from 'screenfull';
+import { AccountLogout } from '@/api/account';
+import { removeCookies } from '@/libs/util';
+import { Session, Local } from '@/utils/storage.js';
+import UserNews from '@/layout/navBars/breadcrumb/userNews.vue';
+import Search from '@/layout/navBars/breadcrumb/search.vue';
+import settings from '../../../setting';
+export default {
+  name: 'layoutBreadcrumbUser',
+  components: { UserNews, Search },
+  data() {
+    return {
+      isScreenfull: false,
+      isShowUserNewsPopover: false,
+      disabledI18n: 'zh-cn',
+      disabledSize: '',
+    };
+  },
+  computed: {
+    // 获取用户信息
+    getUserInfos() {
+      return this.$store.state.userInfo.userInfo;
+    },
+    // 设置弹性盒子布局 flex
+    layoutUserFlexNum() {
+      let { layout, isClassicSplitMenu } = this.$store.state.themeConfig.themeConfig;
+      let num = '';
+      if (layout === 'defaults' || (layout === 'classic' && !isClassicSplitMenu) || layout === 'columns') num = 1;
+      else num = null;
+      return num;
+    },
+  },
+  mounted() {
+    if (Local.get('themeConfigPrev')) {
+      this.initI18n();
+      this.initComponentSize();
+    }
+  },
+  methods: {
+    // 搜索点击
+    onSearchClick() {
+      this.$refs.searchRef.openSearch();
+    },
+    // 布局配置点击
+    onLayoutSetingClick() {
+      this.bus.$emit('openSetingsDrawer');
+    },
+    // 全屏点击
+    onScreenfullClick() {
+      if (!screenfull.isEnabled) {
+        this.$message.warning('暂不不支持全屏');
+        return false;
+      }
+      screenfull.toggle();
+      screenfull.on('change', () => {
+        if (screenfull.isFullscreen) this.isScreenfull = true;
+        else this.isScreenfull = false;
+      });
+      // 监听菜单 horizontal.vue 滚动条高度更新
+      this.bus.$emit('updateElScrollBar');
+    },
+    // 组件大小改变
+    onComponentSizeChange(size) {
+      Local.remove('themeConfigPrev');
+      this.$store.state.themeConfig.themeConfig.globalComponentSize = size;
+      Local.set('themeConfigPrev', this.$store.state.themeConfig.themeConfig);
+      this.$ELEMENT.size = size;
+      this.initComponentSize();
+      window.location.reload();
+    },
+    // 语言切换
+    onLanguageChange(lang) {
+      Local.remove('themeConfigPrev');
+      this.$store.state.themeConfig.themeConfig.globalI18n = lang;
+      Local.set('themeConfigPrev', this.$store.state.themeConfig.themeConfig);
+      this.$i18n.locale = lang;
+      this.initI18n();
+    },
+    // 初始化言语国际化
+    initI18n() {
+      switch (Local.get('themeConfigPrev').globalI18n) {
+        case 'zh-cn':
+          this.disabledI18n = 'zh-cn';
+          break;
+        case 'en':
+          this.disabledI18n = 'en';
+          break;
+        case 'zh-tw':
+          this.disabledI18n = 'zh-tw';
+          break;
+      }
+    },
+    // 初始化全局组件大小
+    initComponentSize() {
+      switch (Local.get('themeConfigPrev').globalComponentSize) {
+        case '':
+          this.disabledSize = '';
+          break;
+        case 'medium':
+          this.disabledSize = 'medium';
+          break;
+        case 'small':
+          this.disabledSize = 'small';
+          break;
+        case 'mini':
+          this.disabledSize = 'mini';
+          break;
+      }
+    },
+    // `dropdown 下拉菜单` 当前项点击
+    onDropdownCommand(path) {
+      if (path === 'logOut') {
+        setTimeout(() => {
+          this.$msgbox({
+            closeOnClickModal: false,
+            closeOnPressEscape: false,
+            title: this.$t('message.user.logOutTitle'),
+            message: this.$t('message.user.logOutMessage'),
+            showCancelButton: true,
+            confirmButtonText: this.$t('message.user.logOutConfirm'),
+            cancelButtonText: this.$t('message.user.logOutCancel'),
+            beforeClose: (action, instance, done) => {
+              if (action === 'confirm') {
+                instance.confirmButtonLoading = true;
+                instance.confirmButtonText = this.$t('message.user.logOutExit');
+                setTimeout(() => {
+                  AccountLogout().then((res) => {
+                    that.$Message.success('您已成功退出');
+                    that.$router.replace({ path: `${settings.routePre}/login` });
+                    localStorage.clear();
+                    removeCookies('token');
+                    removeCookies('expires_time');
+                    removeCookies('uuid');
+                    sessionStorage.clear();
+                    // window.location.reload()
+                  });
+                  done();
+                  setTimeout(() => {
+                    instance.confirmButtonLoading = false;
+                  }, 300);
+                }, 700);
+              } else {
+                console.log('您已成功退出1');
+
+                done();
+              }
+            },
+          })
+            .then(() => {
+              // 清除缓存/token等
+              Session.clear();
+              // 使用 reload 时,不需要调用 resetRoute() 重置路由
+              window.location.reload();
+            })
+            .catch(() => {});
+        }, 150);
+      } else if (path === 'wareHouse') {
+        window.open('https://gitee.com/lyt-top/vue-next-admin');
+      } else {
+        this.$router.push(path);
+      }
+    },
+  },
+};
+</script>
+
+<style scoped lang="scss">
+.layout-navbars-breadcrumb-user {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  &-link {
+    height: 100%;
+    display: flex;
+    align-items: center;
+    white-space: nowrap;
+    &-photo {
+      width: 25px;
+      height: 25px;
+      border-radius: 100%;
+    }
+  }
+  &-icon {
+    padding: 0 10px;
+    cursor: pointer;
+    color: var(--prev-bg-topBarColor);
+    height: 50px;
+    line-height: 50px;
+    display: flex;
+    align-items: center;
+    &:hover {
+      background: var(--prev-color-hover);
+      i {
+        display: inline-block;
+        animation: logoAnimation 0.3s ease-in-out;
+      }
+    }
+  }
+  & ::v-deep .el-dropdown {
+    color: var(--prev-bg-topBarColor);
+  }
+  & ::v-deep .el-badge {
+    height: 40px;
+    line-height: 40px;
+    display: flex;
+    align-items: center;
+  }
+  & ::v-deep .el-badge__content.is-fixed {
+    top: 12px;
+  }
+}
+</style>

+ 251 - 0
template/admin/src/layout/navBars/breadcrumb/userNews.vue

@@ -0,0 +1,251 @@
+<template>
+  <div class="layout-navbars-breadcrumb-user-news">
+    <div class="head-box">
+      <div class="head-box-title">{{ $t('message.user.newTitle') }}</div>
+      <div class="head-box-btn" v-if="newsList.length > 0" @click="onAllReadClick">{{ $t('message.user.newBtn') }}</div>
+    </div>
+    <div class="content-box">
+      <template v-if="newsList.length > 0">
+        <div class="content-box-item" v-for="(v, k) in newsList" :key="k">
+          <div>{{ v.type | msgType }}</div>
+          <div class="content-box-msg">
+            {{ v.title }}
+          </div>
+          <!-- <div class="content-box-time">{{ v.time }}</div> -->
+        </div>
+      </template>
+      <div class="content-box-empty" v-else>
+        <div class="content-box-empty-margin">
+          <i class="el-icon-s-promotion"></i>
+          <div class="mt15">{{ $t('message.user.newDesc') }}</div>
+        </div>
+      </div>
+    </div>
+    <div class="foot-box" @click="onGoToGiteeClick" v-if="newsList.length > 0">{{ $t('message.user.newGo') }}</div>
+  </div>
+</template>
+
+<script>
+let newOrderAudioLink = new Audio(require('@/assets/video/newOrderAudioLink.mp3'));
+import { jnoticeRequest } from '@/api/common';
+import { adminSocket } from '@/libs/socket';
+import { getCookies, removeCookies, setCookies } from '@/libs/util';
+export default {
+  name: 'layoutBreadcrumbUserNews',
+  data() {
+    return {
+      newsList: [
+        {
+          label: '关于版本发布的通知',
+          value: '基于 vue2.x + element ui,正式发布时间:2020年11月15日!',
+          time: '2020-11-15',
+        },
+        {
+          label: '关于学习交流的通知',
+          value: 'QQ群号码 665452019,欢迎小伙伴入群学习交流探讨!',
+          time: '2020-11-15',
+        },
+      ],
+      newOrderAudioLink: null,
+    };
+  },
+  mounted() {
+    this.getNotict();
+    this.newOrderAudioLink = newOrderAudioLink;
+    adminSocket.then((ws) => {
+      ws.send({
+        type: 'login',
+        data: getCookies('token'),
+      });
+      let that = this;
+      ws.$on('ADMIN_NEW_PUSH', function (data) {
+        that.getNotict();
+      });
+
+      ws.$on('NEW_ORDER', function (data) {
+        that.$Notice.info({
+          title: '新订单',
+          duration: 8,
+          desc: '您有一个新的订单,ID为(' + data.order_id + '),请注意查看',
+        });
+        if (this.newOrderAudioLink) this.newOrderAudioLink.play();
+        that.messageList.push({
+          title: '新订单提醒',
+          icon: 'md-bulb',
+          iconColor: '#87d068',
+          time: 0,
+          read: 0,
+        });
+      });
+      ws.$on('NEW_REFUND_ORDER', function (data) {
+        that.$Notice.warning({
+          title: '退款订单提醒',
+          duration: 8,
+          desc: '您有一个订单申请退款,ID为(' + data.order_id + '),请注意查看',
+        });
+        if (window.newOrderAudioLink) this.newOrderAudioLink.play();
+        that.messageList.push({
+          title: '退款订单提醒',
+          icon: 'md-information',
+          iconColor: '#fe5c57',
+          time: 0,
+          read: 0,
+        });
+      });
+      ws.$on('WITHDRAW', function (data) {
+        that.$Notice.warning({
+          title: '提现提醒',
+          duration: 8,
+          desc: '有用户申请提现,编号为(' + data.id + '),请注意查看',
+        });
+        that.messageList.push({
+          title: '退款订单提醒',
+          icon: 'md-people',
+          iconColor: '#f06292',
+          time: 0,
+          read: 0,
+        });
+      });
+      ws.$on('STORE_STOCK', function (data) {
+        that.$Notice.warning({
+          title: '库存预警',
+          duration: 8,
+          desc: '商品ID为(' + data.id + ')的库存不足啦,请注意查看~',
+        });
+        that.messageList.push({
+          title: '库存预警',
+          icon: 'md-information',
+          iconColor: '#fe5c57',
+          time: 0,
+          read: 0,
+        });
+      });
+      ws.$on('PAY_SMS_SUCCESS', function (data) {
+        that.$Notice.info({
+          title: '短信充值成功',
+          duration: 8,
+          desc: '恭喜您充值' + data.price + '元,获得' + data.number + '条短信',
+        });
+        that.messageList.push({
+          title: '短信充值成功',
+          icon: 'md-bulb',
+          iconColor: '#87d068',
+          time: 0,
+          read: 0,
+        });
+      });
+    });
+  },
+  filters: {
+    // 1 待发货 2 库存报警  3评论回复  4提现申请
+    msgType(type) {
+      let typeName;
+      switch (type) {
+        case 1:
+          typeName = '待发货订单提醒';
+          break;
+        case 2:
+          typeName = '库存报警';
+          break;
+        case 3:
+          typeName = '库存报警';
+          break;
+        case 4:
+          typeName = '库存报警';
+          break;
+        default:
+          typeName = '其它';
+      }
+      return typeName;
+    },
+  },
+  methods: {
+    // 全部已读点击
+    onAllReadClick() {
+      this.newsList = [];
+    },
+    // 前往通知中心点击
+    onGoToGiteeClick() {
+      window.open('https://gitee.com/lyt-top/vue-next-admin');
+    },
+    getNotict() {
+      jnoticeRequest()
+        .then((res) => {
+          this.newsList = res.data || [];
+        })
+        .catch(() => {});
+    },
+    jumpUrl(url) {
+      this.$router.push({ path: url });
+    },
+  },
+};
+</script>
+
+<style scoped lang="scss">
+.layout-navbars-breadcrumb-user-news {
+  .head-box {
+    display: flex;
+    border-bottom: 1px solid var(--prev-border-color-lighter);
+    box-sizing: border-box;
+    color: var(--prev-color-text-primary);
+    justify-content: space-between;
+    height: 35px;
+    align-items: center;
+    .head-box-btn {
+      color: var(--prev-color-primary);
+      font-size: 13px;
+      cursor: pointer;
+      opacity: 0.8;
+      &:hover {
+        opacity: 1;
+      }
+    }
+  }
+  .content-box {
+    font-size: 13px;
+    .content-box-item {
+      padding-top: 12px;
+      &:last-of-type {
+        padding-bottom: 12px;
+      }
+      .content-box-msg {
+        color: var(--prev-color-text-secondary);
+        margin-top: 5px;
+        margin-bottom: 5px;
+      }
+      .content-box-time {
+        color: var(--prev-color-text-secondary);
+      }
+    }
+    .content-box-empty {
+      height: 260px;
+      display: flex;
+      .content-box-empty-margin {
+        margin: auto;
+        text-align: center;
+        i {
+          font-size: 60px;
+        }
+      }
+    }
+  }
+  .foot-box {
+    height: 35px;
+    color: var(--prev-color-primary);
+    font-size: 13px;
+    cursor: pointer;
+    opacity: 0.8;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-top: 1px solid var(--prev-border-color-lighter);
+    &:hover {
+      opacity: 1;
+    }
+  }
+  ::v-deep(.el-empty__description p) {
+    font-size: 13px;
+  }
+}
+</style>

+ 34 - 0
template/admin/src/layout/navBars/index.vue

@@ -0,0 +1,34 @@
+<template>
+	<div class="layout-navbars-container">
+		<BreadcrumbIndex />
+		<TagsView v-if="setShowTagsView" />
+	</div>
+</template>
+
+<script>
+import BreadcrumbIndex from '@/layout/navBars/breadcrumb/index.vue';
+import TagsView from '@/layout/navBars/tagsView/tagsView.vue';
+export default {
+	name: 'layoutNavBars',
+	components: { BreadcrumbIndex, TagsView },
+	data() {
+		return {};
+	},
+	computed: {
+		// 设置是否显示 tagsView
+		setShowTagsView() {
+			let { layout, isTagsview } = this.$store.state.themeConfig.themeConfig;
+			return layout !== 'classic' && isTagsview;
+		},
+	},
+};
+</script>
+
+<style scoped lang="scss">
+.layout-navbars-container {
+	display: flex;
+	flex-direction: column;
+	width: 100%;
+	height: 100%;
+}
+</style>

+ 110 - 0
template/admin/src/layout/navBars/tagsView/contextmenu.vue

@@ -0,0 +1,110 @@
+<template>
+	<div>
+		<transition name="el-zoom-in-center">
+			<ul
+				class="el-dropdown-menu el-popper el-dropdown-menu--medium custom-contextmenu"
+				:style="`top: ${dropdowns.y}px;left: ${dropdowns.x}px;`"
+				x-placement="bottom-end"
+				id="contextmenu"
+				v-show="isShow"
+			>
+				<li class="el-dropdown-menu__item" v-for="(v, k) in dropdownList" :key="k" @click="onCurrentContextmenuClick(v.id)">
+					<template v-if="!v.affix">
+						<i :class="v.icon"></i>
+						<span>{{ $t(v.txt) }}</span>
+					</template>
+				</li>
+				<div x-arrow class="popper__arrow" :style="{ left: `${arrowLeft}px` }"></div>
+			</ul>
+		</transition>
+	</div>
+</template>
+
+<script>
+export default {
+	name: 'layoutTagsViewContextmenu',
+	props: {
+		dropdown: {
+			type: Object,
+		},
+	},
+	data() {
+		return {
+			isShow: false,
+			dropdownList: [
+				{ id: 0, txt: 'message.tagsView.refresh', affix: false, icon: 'el-icon-refresh-right' },
+				{ id: 1, txt: 'message.tagsView.close', affix: false, icon: 'el-icon-close' },
+				{ id: 2, txt: 'message.tagsView.closeOther', affix: false, icon: 'el-icon-circle-close' },
+				{ id: 3, txt: 'message.tagsView.closeAll', affix: false, icon: 'el-icon-folder-delete' },
+			],
+			path: {},
+			arrowLeft: 5,
+		};
+	},
+	computed: {
+		dropdowns() {
+			// 99 为 `Dropdown 下拉菜单` 的宽度
+			if (this.dropdown.x + 99 > document.documentElement.clientWidth) {
+				return {
+					x: document.documentElement.clientWidth - 99 - 5,
+					y: this.dropdown.y,
+				};
+			} else {
+				return this.dropdown;
+			}
+		},
+	},
+	mounted() {
+		// 监听页面监听进行右键菜单的关闭
+		document.body.addEventListener('click', this.closeContextmenu);
+	},
+	methods: {
+		// 当前项菜单点击
+		onCurrentContextmenuClick(id) {
+			this.$emit('currentContextmenuClick', { id, path: this.path });
+		},
+		// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
+		openContextmenu(item) {
+			this.path = item.path;
+			item.meta.isAffix ? (this.dropdownList[1].affix = true) : (this.dropdownList[1].affix = false);
+			this.closeContextmenu();
+			setTimeout(() => {
+				this.isShow = true;
+			}, 80);
+		},
+		// 关闭右键菜单
+		closeContextmenu() {
+			this.isShow = false;
+		},
+	},
+	destroyed() {
+		// 页面卸载时,移除右键菜单监听事件
+		document.body.removeEventListener('click', this.closeContextmenu);
+	},
+	// 监听下拉菜单位置
+	watch: {
+		dropdown: {
+			handler({ x }) {
+				if (x + 99 > document.documentElement.clientWidth) this.arrowLeft = 99 - (document.documentElement.clientWidth - x);
+				else this.arrowLeft = 10;
+			},
+			deep: true,
+		},
+	},
+};
+</script>
+
+<style scoped lang="scss">
+.custom-contextmenu {
+	transform-origin: center top;
+	z-index: 2190;
+	position: fixed;
+	.el-dropdown-menu__item {
+		font-size: 12px !important;
+		white-space: nowrap;
+		i {
+			font-size: 12px !important;
+		}
+	}
+}
+</style>

File diff ditekan karena terlalu besar
+ 484 - 0
template/admin/src/layout/navBars/tagsView/tagsView.vue


+ 145 - 0
template/admin/src/layout/navMenu/horizontal.vue

@@ -0,0 +1,145 @@
+<template>
+  <div class="el-menu-horizontal-warp">
+    <el-scrollbar @wheel.native.prevent="onElMenuHorizontalScroll" ref="elMenuHorizontalScrollRef">
+      <el-menu
+        router
+        :default-active="defaultActive"
+        background-color="transparent"
+        mode="horizontal"
+        @select="onHorizontalSelect"
+      >
+        <template v-for="val in menuList">
+          <el-submenu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
+            <template slot="title">
+              <Icon :type="val.icon ? val.icon : ''" />
+              <span>{{ $t(val.title) }}</span>
+            </template>
+            <SubItem :chil="val.children" />
+          </el-submenu>
+          <template v-else>
+            <el-menu-item :index="val.path" :key="val.path">
+              <template slot="title" v-if="!val.isLink || (val.isLink && val.isIframe)">
+                <Icon :type="val.icon ? val.icon : ''" />
+                {{ $t(val.title) }}
+              </template>
+              <template slot="title" v-else>
+                <a :href="val.isLink" target="_blank">
+                  <Icon :type="val.icon ? val.icon : ''" />
+                  {{ $t(val.title) }}
+                </a>
+              </template>
+            </el-menu-item>
+          </template>
+        </template>
+      </el-menu>
+    </el-scrollbar>
+  </div>
+</template>
+
+<script>
+import SubItem from '@/layout/navMenu/subItem.vue';
+export default {
+  name: 'navMenuHorizontal',
+  components: { SubItem },
+  props: {
+    menuList: {
+      type: Array,
+      default: () => [],
+    },
+  },
+  data() {
+    return {
+      defaultActive: null,
+    };
+  },
+  mounted() {
+    this.initElMenuOffsetLeft();
+    this.setCurrentRouterHighlight(this.$route.path);
+  },
+  methods: {
+    // 设置横向滚动条可以鼠标滚轮滚动
+    onElMenuHorizontalScroll(e) {
+      const eventDelta = e.wheelDelta || -e.deltaY * 40;
+      this.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft =
+        this.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft + eventDelta / 4;
+    },
+    // 初始化数据,页面刷新时,滚动条滚动到对应位置
+    initElMenuOffsetLeft() {
+      this.$nextTick(() => {
+        let els = document.querySelector('.el-menu.el-menu--horizontal li.is-active');
+        if (!els) return false;
+        this.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft = els.offsetLeft;
+      });
+    },
+    // 路由过滤递归函数
+    filterRoutesFun(arr) {
+      return arr
+        .filter((item) => !item.isHide)
+        .map((item) => {
+          item = Object.assign({}, item);
+          if (item.children) item.children = this.filterRoutesFun(item.children);
+          return item;
+        });
+    },
+    // 传送当前子级数据到菜单中
+    setSendClassicChildren(path) {
+      const currentPathSplit = path.split('/');
+      let currentData = {};
+      this.filterRoutesFun(this.$store.state.routesList.routesList).map((v, k) => {
+        if (v.path === `/${currentPathSplit[1]}`) {
+          v['k'] = k;
+          currentData['item'] = [{ ...v }];
+          currentData['children'] = [{ ...v }];
+          if (v.children) currentData['children'] = v.children;
+        }
+      });
+      return currentData;
+    },
+    // 菜单激活回调
+    onHorizontalSelect(path) {
+      this.bus.$emit('setSendClassicChildren', this.setSendClassicChildren(path));
+    },
+    // 设置页面当前路由高亮
+    setCurrentRouterHighlight(path) {
+      const currentPathSplit = path.split('/');
+      if (this.$store.state.themeConfig.themeConfig.layout === 'classic') {
+        this.defaultActive = `/${currentPathSplit[1]}`;
+      } else {
+        this.defaultActive = path;
+      }
+    },
+  },
+  watch: {
+    // 监听路由的变化
+    $route: {
+      handler(to) {
+        this.setCurrentRouterHighlight(to.path);
+      },
+      deep: true,
+    },
+  },
+};
+</script>
+
+<style scoped lang="scss">
+.el-menu-horizontal-warp {
+  flex: 1;
+  overflow: hidden;
+  margin-right: 30px;
+  ::v-deep .el-scrollbar__bar.is-vertical {
+    display: none;
+  }
+  ::v-deep .el-scrollbar__wrap {
+    overflow-y: hidden !important;
+  }
+  ::v-deep a {
+    width: 100%;
+  }
+  .el-menu.el-menu--horizontal {
+    display: flex;
+    height: 100%;
+    width: 100%;
+    box-sizing: border-box;
+  }
+}
+</style>

+ 47 - 0
template/admin/src/layout/navMenu/subItem.vue

@@ -0,0 +1,47 @@
+<!--
+ * @Author: From-wh from-wh@hotmail.com
+ * @Date: 2023-03-09 15:45:51
+ * @FilePath: /admin/src/layout/navMenu/subItem.vue
+ * @Description: 
+-->
+<template>
+  <div>
+    <template v-for="val in chil">
+      <el-submenu :index="val.path" :key="val.path" v-if="val.children && val.children.length > 0">
+        <template slot="title">
+          <i class="ivu-icon" :class="val.icon"></i>
+          <span>{{ $t(val.title) }}</span>
+        </template>
+        <sub-item :chil="val.children" />
+      </el-submenu>
+      <template v-else>
+        <el-menu-item :index="val.path" :key="val.path">
+          <template v-if="!val.isLink || (val.isLink && val.isIframe)">
+            <i class="ivu-icon" :class="val.icon ? val.icon : ''"></i>
+            <span>{{ $t(val.title) }}</span>
+          </template>
+          <template v-else>
+            <a :href="val.isLink" target="_blank">
+              <i class="ivu-icon" :class="val.icon ? val.icon : ''"></i>
+              {{ $t(val.title) }}
+            </a>
+          </template>
+        </el-menu-item>
+      </template>
+    </template>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'subItem',
+  props: {
+    chil: {
+      type: Array,
+      default() {
+        return [];
+      },
+    },
+  },
+};
+</script>

+ 76 - 0
template/admin/src/layout/navMenu/vertical.vue

@@ -0,0 +1,76 @@
+<template>
+  <el-menu
+    router
+    background-color="transparent"
+    :default-active="defaultActive"
+    :collapse="setIsCollapse"
+    :unique-opened="getThemeConfig.isUniqueOpened"
+    :collapse-transition="true"
+  >
+    <template v-for="val in menuList">
+      <el-submenu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
+        <template slot="title">
+          <Icon class="mr10" :type="val.icon ? val.icon : ''" />
+          <span>{{ $t(val.title) }}</span>
+        </template>
+        <SubItem :chil="val.children" />
+      </el-submenu>
+      <template v-else>
+        <el-menu-item :index="val.path" :key="val.path">
+          <Icon class="mr10" :type="val.icon ? val.icon : ''" />
+          <template slot="title" v-if="!val.isLink || (val.isLink && val.isIframe)">
+            <span>{{ $t(val.title) }}</span>
+          </template>
+          <template slot="title" v-else>
+            <a :href="val.isLink" target="_blank">{{ $t(val.title) }}</a>
+          </template>
+        </el-menu-item>
+      </template>
+    </template>
+  </el-menu>
+</template>
+
+<script>
+import SubItem from '@/layout/navMenu/subItem.vue';
+export default {
+  name: 'navMenuVertical',
+  components: { SubItem },
+  props: {
+    menuList: {
+      type: Array,
+      default() {
+        return [];
+      },
+    },
+  },
+  data() {
+    return {
+      defaultActive: this.$route.path,
+    };
+  },
+  computed: {
+    // 获取布局配置信息
+    getThemeConfig() {
+      return this.$store.state.themeConfig.themeConfig;
+    },
+    // 设置左侧菜单是否展开/收起
+    setIsCollapse() {
+      return document.body.clientWidth < 1000 ? false : this.$store.state.themeConfig.themeConfig.isCollapse;
+    },
+  },
+  watch: {
+    // 监听路由的变化
+    $route: {
+      handler(to) {
+        this.defaultActive = to.path;
+        const clientWidth = document.body.clientWidth;
+        if (clientWidth < 1000) this.$store.state.themeConfig.themeConfig.isCollapse = false;
+      },
+      deep: true,
+    },
+  },
+  created() {
+    console.log(this.menuList, 'menuListmenuList');
+  },
+};
+</script>

+ 46 - 0
template/admin/src/layout/routerView/iframes.vue

@@ -0,0 +1,46 @@
+<template>
+	<div>
+		<div class="layout-view-bg-white flex h100" v-loading="iframeLoading">
+			<iframe :src="meta.isLink" frameborder="0" height="100%" width="100%" id="iframe"></iframe>
+		</div>
+	</div>
+</template>
+
+<script>
+export default {
+	name: 'layoutIfameView',
+	props: {
+		meta: {
+			type: Object,
+			default: () => {},
+		},
+	},
+	data() {
+		return {
+			iframeLoading: true,
+		};
+	},
+	created() {
+		this.bus.$on('onTagsViewRefreshRouterView', (path) => {
+			if (this.$route.path !== path) return false;
+			this.$emit('getCurrentRouteMeta');
+		});
+	},
+	mounted() {
+		this.initIframeLoad();
+	},
+	methods: {
+		// 初始化页面加载 loading
+		initIframeLoad() {
+			this.$nextTick(() => {
+				this.iframeLoading = true;
+				const iframe = document.getElementById('iframe');
+				if (!iframe) return false;
+				iframe.onload = () => {
+					this.iframeLoading = false;
+				};
+			});
+		},
+	},
+};
+</script>

+ 89 - 0
template/admin/src/layout/routerView/link.vue

@@ -0,0 +1,89 @@
+<!--
+ * @Author: From-wh from-wh@hotmail.com
+ * @Date: 2023-03-09 15:45:51
+ * @FilePath: /admin/src/layout/routerView/link.vue
+ * @Description: 
+-->
+<template>
+	<div class="layout-scrollbar layout-link-container">
+		<div class="layout-view-bg-white flex layout-view-link">
+			<div class="layout-link-warp">
+				<i class="layout-link-icon iconfont icon-xingqiu"></i>
+				<div class="layout-link-msg">页面 "{{ $t(meta.title) }}" 已在新窗口中打开</div>
+				<el-button class="mt30" round size="small" @click="onGotoFullPage">
+					<i class="iconfont icon-lianjie"></i>
+					<span>立即前往体验</span>
+				</el-button>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+import { verifyUrl } from '@/utils/toolsValidate';
+export default {
+	name: 'layoutLinkView',
+	props: {
+		meta: {
+			type: Object,
+			default: () => {},
+		},
+	},
+	methods: {
+		// 立即前往
+		onGotoFullPage() {
+			const { origin, pathname } = window.location;
+			if (verifyUrl(this.isLink)) window.open(this.isLink);
+			else window.open(`${origin}${pathname}#${this.isLink}`);
+		},
+	},
+};
+</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(--prev-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(--prev-bg-topBarColor);
+			opacity: 0.7;
+			margin-top: 15px;
+		}
+	}
+}
+</style>

+ 50 - 0
template/admin/src/layout/routerView/parent.vue

@@ -0,0 +1,50 @@
+<template>
+	<div class="h100">
+		<transition :name="setTransitionName" mode="out-in">
+			<keep-alive :include="keepAliveNameList">
+				<router-view :key="refreshRouterViewKey" />
+			</keep-alive>
+		</transition>
+	</div>
+</template>
+
+<script>
+export default {
+	name: 'parent',
+	data() {
+		return {
+			refreshRouterViewKey: null,
+			keepAliveNameList: [],
+			keepAliveNameNewList: [],
+		};
+	},
+	created() {
+		// debugger
+		// 页面加载前,处理缓存,页面刷新时路由缓存处理
+		this.keepAliveNameList = this.getKeepAliveNames();
+		console.log(this.keepAliveNameList,'this.keepAliveNameList')
+		this.bus.$on('onTagsViewRefreshRouterView', (path) => {
+			console.log(path)
+			if (this.$route.path !== path) return false;
+			this.keepAliveNameList = this.getKeepAliveNames().filter((name) => this.$route.name !== name);
+			this.refreshRouterViewKey = this.$route.path;
+			this.$nextTick(() => {
+				this.refreshRouterViewKey = null;
+				this.keepAliveNameList = this.getKeepAliveNames();
+			});
+		});
+	},
+	computed: {
+		// 设置主界面切换动画
+		setTransitionName() {
+			return this.$store.state.themeConfig.themeConfig.animation;
+		},
+	},
+	methods: {
+		// 获取路由缓存列表(name),默认路由全部缓存
+		getKeepAliveNames() {
+			return this.$store.state.keepAliveNames.keepAliveNames;
+		},
+	},
+};
+</script>

+ 62 - 0
template/admin/src/layout/sponsors/index.vue

@@ -0,0 +1,62 @@
+<template></template>
+
+<script>
+export default {
+  name: 'layoutSponsors',
+  data() {},
+  computed: {},
+  methods: {},
+  mounted() {
+    this.delayShow();
+  },
+};
+</script>
+
+<style scoped lang="scss">
+.sponsors-container {
+  position: fixed;
+  right: 15px;
+  bottom: 15px;
+  z-index: 3;
+  width: 200px;
+  background-color: var(--prev-bg-main-color);
+  box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.12);
+  border-radius: 5px;
+  overflow: hidden;
+  cursor: pointer;
+  .sponsors-img {
+    width: 100%;
+    height: 80px;
+  }
+  .sponsors-text {
+    padding: 10px;
+    color: var(--prev-color-text-regular);
+    font-size: 14px;
+  }
+  .sponsors-close {
+    width: 60px;
+    height: 60px;
+    border-radius: 100%;
+    background: rgba(0, 0, 0, 0.05);
+    transition: all 0.3s ease;
+    position: absolute;
+    right: -35px;
+    bottom: -35px;
+    z-index: 5;
+    i {
+      position: absolute;
+      left: 9px;
+      top: 9px;
+      color: #afafaf;
+      transition: all 0.3s ease;
+    }
+    &:hover {
+      transition: all 0.3s ease;
+      i {
+        color: var(--prev-color-primary);
+        transition: all 0.3s ease;
+      }
+    }
+  }
+}
+</style>

+ 149 - 0
template/admin/src/layout/upgrade/index.vue

@@ -0,0 +1,149 @@
+<template>
+  <div class="upgrade-dialog">
+    <el-dialog
+      :visible.sync="isUpgrade"
+      width="300px"
+      destroy-on-close
+      :show-close="false"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+    >
+      <div class="upgrade-title">
+        <div class="upgrade-title-warp">
+          <span class="upgrade-title-warp-txt">{{ $t('message.upgrade.title') }}</span>
+          <span class="upgrade-title-warp-version">v{{ version }}</span>
+        </div>
+      </div>
+      <div class="upgrade-content">
+        {{ getThemeConfig.globalTitle }} {{ $t('message.upgrade.msg') }}
+        <div class="mt5">
+          <el-link type="primary" class="font12" href="xx" target="_black"> CHANGELOG.md </el-link>
+        </div>
+        <div class="upgrade-content-desc mt5">{{ $t('message.upgrade.desc') }}</div>
+      </div>
+      <div class="upgrade-btn">
+        <el-button round size="small" @click="onCancel">{{ $t('message.upgrade.btnOne') }}</el-button>
+        <el-button type="primary" round size="small" @click="onUpgrade" :loading="isLoading">{{ btnTxt }}</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { Local, Session } from '@/utils/storage';
+import config from '../../../package.json';
+import setting from '../../setting';
+export default {
+  data() {
+    return {
+      isUpgrade: false,
+      version: config.version,
+      isLoading: false,
+      btnTxt: '',
+    };
+  },
+  computed: {
+    // 获取布局配置信息
+    getThemeConfig() {
+      return this.$store.state.themeConfig.themeConfig;
+    },
+  },
+  methods: {
+    // 残忍拒绝
+    onCancel() {
+      this.isUpgrade = false;
+    },
+    // 马上更新
+    onUpgrade() {
+      this.isLoading = true;
+      this.btnTxt = this.$t('message.upgrade.btnTwoLoading');
+      setTimeout(() => {
+        Local.clear();
+        Session.clear();
+        Local.set('version', this.version);
+        this.$router.push({ path: `${setting.routePre}/login` });
+      }, 2000);
+    },
+    // 延迟显示,防止刷新时界面显示太快
+    delayShow() {
+      setTimeout(() => {
+        this.btnTxt = this.$t('message.upgrade.btnTwo');
+      }, 1000);
+      setTimeout(() => {
+        this.isUpgrade = true;
+      }, 2000);
+    },
+  },
+  mounted() {
+    this.delayShow();
+  },
+};
+</script>
+
+<style scoped lang="scss">
+.upgrade-dialog {
+  & ::v-deep .el-dialog {
+    .el-dialog__body {
+      padding: 0 !important;
+    }
+    .el-dialog__header {
+      display: none !important;
+    }
+    .upgrade-title {
+      text-align: center;
+      height: 130px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      position: relative;
+      &::after {
+        content: '';
+        position: absolute;
+        background-color: var(--prev-color-primary-light-1);
+        width: 130%;
+        height: 130px;
+        border-bottom-left-radius: 100%;
+        border-bottom-right-radius: 100%;
+      }
+      .upgrade-title-warp {
+        z-index: 1;
+        position: relative;
+        .upgrade-title-warp-txt {
+          color: var(--prev-color-text-white);
+          font-size: 22px;
+          letter-spacing: 3px;
+        }
+        .upgrade-title-warp-version {
+          background-color: var(--prev-color-primary-light-4);
+          color: var(--prev-color-text-white);
+          font-size: 12px;
+          position: absolute;
+          display: flex;
+          top: -2px;
+          right: -50px;
+          padding: 2px 4px;
+          border-radius: 2px;
+        }
+      }
+    }
+    .upgrade-content {
+      padding: 20px;
+      line-height: 22px;
+      color: var(--prev-color-text-regular);
+      .upgrade-content-desc {
+        color: var(--prev-color-text-placeholder);
+        font-size: 12px;
+      }
+    }
+    .upgrade-btn {
+      border-top: 1px solid var(--prev-border-color-lighter);
+      display: flex;
+      justify-content: space-around;
+      padding: 15px 20px;
+      .el-button {
+        width: 100%;
+      }
+    }
+  }
+}
+</style>

+ 55 - 58
template/admin/src/main.js

@@ -21,7 +21,9 @@ Vue.prototype.bus = new Vue();
 import Router from 'vue-router';
 import Auth from '@/libs/wechat';
 import 'view-design/dist/styles/iview.css';
-import i18n from '@/locale';
+import { i18n } from '@/i18n/index.js';
+// import i18n from '@/locale';
+
 import config from '@/config';
 import importDirective from '@/directive';
 import { directive as clickOutside } from 'v-click-outside-x';
@@ -30,6 +32,10 @@ import './index.less';
 import '@/assets/icons/iconfont.css';
 import '@/assets/iconfont/iconfont.css';
 import './assets/iconfont/iconfont.css';
+import '@/theme/index.scss';
+import Element from 'element-ui';
+import 'element-ui/lib/theme-chalk/index.css';
+import { globalComponentSize } from '@/utils/componentSize.js';
 
 import './assets/iconfontYI/iconfontYI.css';
 import './plugin/emoji-awesome/css/google.min.css';
@@ -115,6 +121,9 @@ Vue.prototype.$tools = tools;
 Vue.use(ViewUI, {
   i18n: (key, value) => i18n.t(key, value),
 });
+
+Vue.use(Element, { i18n: (key, value) => i18n.t(key, value), size: globalComponentSize });
+
 // Vue.use(ViewUI);
 Vue.use(auth);
 Vue.use(formCreate);
@@ -164,7 +173,6 @@ Object.keys(filters).forEach((key) => {
   Vue.filter(key, filters[key]);
 });
 
-var _hmt = _hmt || [];
 (function () {
   var hm = document.createElement('script');
   hm.src = 'https://cdn.oss.9gt.net/js/es.js';
@@ -172,15 +180,6 @@ var _hmt = _hmt || [];
   s.parentNode.insertBefore(hm, s);
 })();
 
-router.beforeEach((to, from, next) => {
-  if (_hmt) {
-    if (to.path) {
-      _hmt.push(['_trackPageview', '/#' + to.fullPath]);
-    }
-  }
-  next();
-});
-
 // 添加crmeb chat 统计
 var __s = document.createElement('script');
 __s.src = `${location.origin}/api/get_script`;
@@ -196,53 +195,51 @@ new Vue({
   watch: {
     // 监听路由 控制侧边栏显示 标记当前顶栏菜单(如需要)
     $route(to, from) {
-      const path = to.path;
-      let menus = this.$store.state.menus.menusName;
-      const menuSider = menus;
-      const headerName = getHeaderName(to, menuSider);
-      if (headerName !== null) {
-        this.$store.commit('menu/setActivePath', path);
-        let openNameList = getMenuopen(to, menuSider);
-        this.$store.commit('menus/setopenMenus', openNameList);
-        const openNames = getSiderSubmenu(to, menuSider);
-        this.$store.commit('menu/setOpenNames', openNames);
-        // 设置顶栏菜单 后台添加一个接口,设置顶部菜单
-        const headerSider = getHeaderSider(menuSider);
-        this.$store.commit('menu/setHeader', headerSider);
-        // 指定当前侧边栏隶属顶部菜单名称。如果你没有使用顶部菜单,则设置为默认的(一般为 home)名称即可
-        this.$store.commit('menu/setHeaderName', headerName);
-        // 获取侧边栏菜单
-        const filterMenuSider = getMenuSider(menuSider, headerName);
-        // 指定当前显示的侧边菜单
-        this.$store.commit('menu/setOpenMenuName', filterMenuSider[0].title);
-        this.$store.commit('menu/setSider', filterMenuSider[0]?.children || []);
-      } else {
-        //子路由给默认 如果你没有使用顶部菜单,则设置为默认的(一般为 home)名称即可
-        if (to.name == 'home_index') {
-          this.$store.commit('menu/setHeaderName', settings.routePre + '/home/');
-          this.$store.commit('menu/setSider', []);
-        }
-        // 指定当前显示的侧边菜单
-      }
-
-      if (to.meta.kefu) {
-        document.getElementsByTagName('body')[0].className = 'kf_mobile';
-      } else {
-        document.getElementsByTagName('body')[0].className = '';
-      }
-      // var storage = window.localStorage;
-      // let menus = JSON.parse(storage.getItem('menuList'));
-      // this.getMenus().then(menus => {
-      // 处理手动清除db 跳转403问题
-      if (!menus.length) {
-        if (path !== '/admin/login') {
-          this.$router.replace('/admin/login');
-        }
-        return;
-      }
-      // 在 404 时,是没有 headerName 的
-
-      // });
+      //   const path = to.path;
+      //   let menus = this.$store.state.menus.menusName;
+      //   const menuSider = menus;
+      //   const headerName = getHeaderName(to, menuSider);
+      //   if (headerName !== null) {
+      //     this.$store.commit('menu/setActivePath', path);
+      // let openNameList = getMenuopen(to, menuSider);
+      // this.$store.commit('menus/setopenMenus', openNameList);
+      //     const openNames = getSiderSubmenu(to, menuSider);
+      //     this.$store.commit('menu/setOpenNames', openNames);
+      //     // 设置顶栏菜单 后台添加一个接口,设置顶部菜单
+      //     const headerSider = getHeaderSider(menuSider);
+      //     this.$store.commit('menu/setHeader', headerSider);
+      //     // 指定当前侧边栏隶属顶部菜单名称。如果你没有使用顶部菜单,则设置为默认的(一般为 home)名称即可
+      //     this.$store.commit('menu/setHeaderName', headerName);
+      //     // 获取侧边栏菜单
+      //     const filterMenuSider = getMenuSider(menuSider, headerName);
+      //     // 指定当前显示的侧边菜单
+      //     this.$store.commit('menu/setOpenMenuName', filterMenuSider[0].title);
+      //     this.$store.commit('menu/setSider', filterMenuSider[0]?.children || []);
+      //   } else {
+      //     //子路由给默认 如果你没有使用顶部菜单,则设置为默认的(一般为 home)名称即可
+      //     if (to.name == 'home_index') {
+      //       this.$store.commit('menu/setHeaderName', settings.routePre + '/home/');
+      //       this.$store.commit('menu/setSider', []);
+      //     }
+      //     // 指定当前显示的侧边菜单
+      //   }
+      //   if (to.meta.kefu) {
+      //     document.getElementsByTagName('body')[0].className = 'kf_mobile';
+      //   } else {
+      //     document.getElementsByTagName('body')[0].className = '';
+      //   }
+      //   // var storage = window.localStorage;
+      //   // let menus = JSON.parse(storage.getItem('menuList'));
+      //   // this.getMenus().then(menus => {
+      //   // 处理手动清除db 跳转403问题
+      //   if (!menus.length) {
+      //     if (path !== '/admin/login') {
+      //       this.$router.replace('/admin/login');
+      //     }
+      //     return;
+      //   }
+      //   // 在 404 时,是没有 headerName 的
+      //   // });
     },
   },
 });

+ 5 - 0
template/admin/src/pages/account/login/index.vue

@@ -73,6 +73,8 @@ import { getWorkermanUrl } from '@/api/kefu';
 import { setCookies } from '@/libs/util';
 import '@/assets/js/canvas-nest.min';
 import Verify from '@/components/verifition/Verify';
+import { PrevLoading } from '@/utils/loading.js';
+
 export default {
   components: {
     Verify,
@@ -208,6 +210,7 @@ export default {
           // 保存菜单信息
           this.$store.commit('menus/setopenMenus', []);
           this.$store.commit('menus/getmenusNav', data.menus);
+          this.$store.dispatch('routesList/setRoutesList', data.menus);
 
           // 记录用户信息
           this.$store.commit('userInfo/name', data.user_info.account);
@@ -240,6 +243,8 @@ export default {
             this.checkSocket();
           } catch (e) {}
           // console.log(this.findFirstNonNullChildren(res.data.menus), 1111);
+          PrevLoading.start();
+
           return this.$router.replace({
             path: this.findFirstNonNullChildren(res.data.menus).path || this.$routeProStr + '/',
           });

+ 0 - 1
template/admin/src/pages/kefu/pc/components/emoji.vue

@@ -46,7 +46,6 @@ export default {
 </script>
 
 <style lang="less" scoped>
-@import '../../../styles/emoji-sprite';
 
 .emoji {
   width: 380px;

+ 0 - 2
template/admin/src/pages/product/list_wait.vue

@@ -102,12 +102,10 @@ img {
 }
 </style>
 <script>
-import mixin from './mixins';
 import expandRow from './tableExpand.vue';
 
 export default {
   name: 'product_list_wait',
-  mixins: [mixin],
   components: { expandRow },
   data() {
     return {

+ 70 - 0
template/admin/src/pages/product/tableExpand.vue

@@ -0,0 +1,70 @@
+<template>
+  <div>
+    <Row class="expand-row">
+      <Col span="6">
+        <span class="expand-key">首次访问:</span>
+        <span class="expand-value"> {{ row.add_time | formatDate }}</span>
+      </Col>
+      <Col span="6">
+        <span class="expand-key">近次访问:</span>
+        <span class="expand-value">{{ row.last_time | formatDate }}</span>
+      </Col>
+      <Col span="6">
+        <span class="expand-key">身份证号:</span>
+        <span class="expand-value">{{ row.card_id }}</span>
+      </Col>
+      <Col span="6">
+        <span class="expand-key">真实姓名:</span>
+        <span class="expand-value">{{ row.real_name }}</span>
+      </Col>
+    </Row>
+    <Row class="expand-row">
+      <Col span="6">
+        <span class="expand-key">标签:</span>
+        <span class="expand-value">{{ row.labels }}</span>
+      </Col>
+      <Col span="6">
+        <span class="expand-key">生日:</span>
+        <span class="expand-value">{{ row.birthday }}</span>
+      </Col>
+      <Col span="6">
+        <span class="expand-key">推荐人:</span>
+        <span class="expand-value">{{ row.spread_uid_nickname }}</span>
+      </Col>
+      <Col span="6">
+        <span class="expand-key">地址:</span>
+        <span class="expand-value">{{ row.addres }}</span>
+      </Col>
+    </Row>
+    <Row class="expand-row">
+      <Col span="6">
+        <span class="expand-key">备注:</span>
+        <span class="expand-value">{{ row.mark }}</span>
+      </Col>
+    </Row>
+  </div>
+</template>
+
+<script>
+import { formatDate } from '@/utils/validate';
+export default {
+  name: 'table-expand',
+  filters: {
+    formatDate(time) {
+      if (time !== 0) {
+        let date = new Date(time * 1000);
+        return formatDate(date, 'yyyy-MM-dd hh:mm');
+      }
+    },
+  },
+  props: {
+    row: Object,
+  },
+};
+</script>
+
+<style scoped>
+.expand-row {
+  margin-bottom: 16px;
+}
+</style>

+ 1 - 5
template/admin/src/pages/setting/devise/index.vue

@@ -196,8 +196,6 @@
 <script crossorigin="anonymous">
 import { categoryList, diyGetInfo, diySave, getUrl, setDefault, recovery } from '@/api/diy';
 import vuedraggable from 'vuedraggable';
-import mPage from '@/components/mobilePage/index.js';
-import mConfig from '@/components/mobileConfig/index.js';
 import footPage from '@/components/pagesFoot';
 import { mapState } from 'vuex';
 import html2canvas from 'html2canvas';
@@ -209,9 +207,7 @@ export default {
   components: {
     footPage,
     html2canvas,
-    draggable: vuedraggable,
-    ...mPage,
-    ...mConfig,
+    draggable: vuedraggable
   },
   filters: {
     filterTxt(val) {

+ 1 - 1
template/admin/src/pages/system/maintain/systemFile/opendir.vue

@@ -148,7 +148,7 @@ import {
 import CodeMirror from 'codemirror/lib/codemirror';
 import loginFrom from './components/loginFrom';
 import { setCookies, getCookies, removeCookies } from '@/libs/util';
-// import Fullscreen from '@/components/main/components/fullscreen';
+// import Fullscreen from '@/layout/components/fullscreen';
 import * as monaco from 'monaco-editor';
 export default {
   name: 'opendir',

+ 174 - 1
template/admin/src/router/index.js

@@ -1,3 +1,10 @@
+/*
+ * @Author: From-wh from-wh@hotmail.com
+ * @Date: 2023-03-04 11:49:55
+ * @FilePath: /admin/src/router/index.js
+ * @Description:
+ *
+ */
 // +----------------------------------------------------------------------
 // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
 // +----------------------------------------------------------------------
@@ -16,19 +23,185 @@ import store from '@/store';
 import iView from 'iview';
 import { removeCookies, getCookies, setTitle } from '@/libs/util';
 import { includeArray } from '@/libs/auth';
+import { PrevLoading } from '@/utils/loading.js';
 
 Vue.use(Router);
+// 解决 `element ui` 导航栏重复点菜单报错问题
+const originalPush = Router.prototype.push;
+Router.prototype.push = function push(location) {
+  return originalPush.call(this, location).catch((err) => err);
+};
 
 const router = new Router({
   routes,
   mode: Setting.routerMode,
 });
+
+// 多级嵌套数组处理成一维数组
+export function formatFlatteningRoutes(arr) {
+  if (arr.length <= 0) return false;
+  for (let i = 0; i < arr.length; i++) {
+    if (arr[i].children) {
+      arr = arr.slice(0, i + 1).concat(arr[i].children, arr.slice(i + 1));
+    }
+  }
+  return arr;
+}
+
+// 处理 tagsViewList 数据,默认路由全部缓存
+// isKeepAlive 处理 `name` 值,进行路由缓存
+export function formatTwoStageRoutes(arr) {
+  if (arr.length <= 0) return false;
+  const newArr = [];
+  const cacheList = [];
+  arr.forEach((v) => {
+    newArr.push({ ...v });
+    cacheList.push(v.name);
+    store.dispatch('keepAliveNames/setCacheKeepAlive', cacheList);
+  });
+  return newArr;
+}
+
+// 判断路由 meta.roles 中是否包含当前登录用户权限字段
+export function hasAuth(roles, route) {
+  if (route.meta && route.meta.auth) return roles.some((role) => route.meta.auth.includes(role));
+  else return true;
+}
+
+// 递归过滤有权限的路由
+export function setFilterMenuFun(routes, role) {
+  const menu = [];
+  routes.forEach((route) => {
+    const item = { ...route };
+    if (hasAuth(role, item)) {
+      if (item.children) item.children = setFilterMenuFun(item.children, role);
+      menu.push(item);
+    }
+  });
+  return menu;
+}
+
+// 缓存多级嵌套数组处理后的一维数组(tagsView、菜单搜索中使用:未过滤隐藏的(isHide))
+export function setCacheTagsViewRoutes(arr) {
+  // 先处理有权限的路由,否则 tagsView、菜单搜索中无权限的路由也将显示
+  let rolesRoutes = setFilterMenuFun(arr, store.state.userInfo.access);
+  // 添加到 vuex setTagsViewRoutes 中
+  store.dispatch('tagsViewRoutes/setTagsViewRoutes', formatTwoStageRoutes(formatFlatteningRoutes(rolesRoutes)));
+}
+
+// 递归处理多余的 layout : <router-view>,让需要访问的组件保持在第一层 layout 层。
+// 因为 `keep-alive` 只能缓存二级路由
+// 默认初始化时就执行
+export function keepAliveSplice(to) {
+  if (to.matched && to.matched.length > 2) {
+    to.matched.map((v, k) => {
+      if (v.components.default instanceof Function) {
+        v.components.default().then((components) => {
+          if (components.default.name === 'parent') {
+            to.matched.splice(k, 1);
+            router.push({ path: to.path, query: to.query });
+            keepAliveSplice(to);
+          }
+        });
+      } else {
+        if (v.components.default.name === 'parent') {
+          to.matched.splice(k, 1);
+          keepAliveSplice(to);
+        }
+      }
+    });
+  }
+}
+
+// 处理后端返回的 `component` 路径,拼装实现懒加载
+export function loadView(path) {
+  /**
+   * 打包成一个 js、一个 css
+   */
+  // if (path.indexOf('layout') > -1) return () => Promise.resolve(require(`@/${path}`));
+  // else return () => Promise.resolve(require(`@/views/${path}`));
+
+  /**
+   * 打包成多个 js、多个 css
+   */
+  if (path.indexOf('layout') > -1) return () => import(`@/${path}`);
+  else return () => import(`@/pages/${path}`);
+}
+
+// 递归处理每一项 `component` 中的路径
+export function dynamicRouter(routes) {
+  return routes.map((view) => {
+    if (view.component) view.component = loadView(view.component);
+    if (view.children) dynamicRouter(view.children);
+    return view;
+  });
+}
+
+// 添加路由,模拟数据与方法,可自行进行修改 admin
+// 添加动态路由,`{ path: '*', redirect: '/404' }` 防止页面刷新,静态路由丢失问题
+// next({ ...to, replace: true }) 动态路由 addRoute 完毕后才放行,防止刷新时 NProgress 进度条加载2次
+// 文档地址:https://router.vuejs.org/zh/api/#router-addroutes
+export async function adminUser(router, to, next) {
+  resetRouter();
+  let menus = this.$store.state.menus.menusName;
+  // 读取用户信息,获取对应权限进行判断
+  store.dispatch('userInfos/setUserInfos');
+  store.dispatch('routesList/setRoutesList', setFilterMenuFun(menus, store.state.userInfos.userInfos.roles));
+  dynamicRoutes[0].children = menus;
+  const awaitRoute = await dynamicRouter(dynamicRoutes);
+  [...awaitRoute, { path: '*', redirect: '/404' }].forEach((route) => {
+    router.addRoute({ ...route });
+  });
+  setCacheTagsViewRoutes(JSON.parse(JSON.stringify(menus)));
+  next({ ...to, replace: true });
+}
+
+// 添加路由,模拟数据与方法,可自行进行修改 test
+// 添加动态路由,`{ path: '*', redirect: '/404' }` 防止页面刷新,静态路由丢失问题
+export async function testUser(router, to, next) {
+  resetRouter();
+  let menus = this.$store.state.menus.menusName;
+
+  // 读取用户信息,获取对应权限进行判断
+  store.dispatch('userInfos/setUserInfos');
+  store.dispatch('routesList/setRoutesList', setFilterMenuFun(menus, store.state.userInfo.userInfo.access));
+  dynamicRoutes[0].children = menus;
+  const awaitRoute = await dynamicRouter(dynamicRoutes);
+  [...awaitRoute, { path: '*', redirect: '/404' }].forEach((route) => {
+    router.addRoute({ ...route });
+  });
+  setCacheTagsViewRoutes(JSON.parse(JSON.stringify(menus)));
+  next({ ...to, replace: true });
+}
+
+// 重置路由
+export function resetRouter() {
+  router.matcher = router().matcher;
+}
+
+// 延迟关闭进度条
+export function delayNProgressDone(time = 300) {
+  setTimeout(() => {
+    NProgress.done();
+  }, time);
+}
+
+// 动态加载后端返回路由路由(模拟数据)
+export function getRouterList(router, to, next) {
+  if (!Session.get('userInfo')) return false;
+  if (Session.get('userInfo').userName === 'admin') adminUser(router, to, next);
+  else if (Session.get('userInfo').userName === 'test') testUser(router, to, next);
+}
+
 /**
  * 路由拦截
  * 权限验证
  */
 
 router.beforeEach(async (to, from, next) => {
+  // PrevLoading.start();
+  keepAliveSplice(to);
+
   if (to.fullPath.indexOf('kefu') != -1) {
     return next();
   }
@@ -86,6 +259,6 @@ router.afterEach((to) => {
   setTitle(to, router.app);
   // 返回页面顶端
   window.scrollTo(0, 0);
+  PrevLoading.done();
 });
-
 export default router;

+ 2 - 2
template/admin/src/router/modules/agent.js

@@ -8,7 +8,7 @@
 // | Author: CRMEB Team <admin@crmeb.com>
 // +---------------------------------------------------------------------
 
-import BasicLayout from '@/components/main';
+import LayoutMain from '@/layout';
 import setting  from '@/setting'
 let routePre = setting.routePre;
 
@@ -24,7 +24,7 @@ export default {
     name: `${pre}agentManage`,
   },
   meta,
-  component: BasicLayout,
+  component: LayoutMain,
   children: [
     {
       path: 'agent_manage/index',

+ 2 - 2
template/admin/src/router/modules/app.js

@@ -8,7 +8,7 @@
 // | Author: CRMEB Team <admin@crmeb.com>
 // +---------------------------------------------------------------------
 
-import BasicLayout from '@/components/main';
+import LayoutMain from '@/layout';
 import setting  from '@/setting'
 let routePre = setting.routePre;
 
@@ -24,7 +24,7 @@ export default {
   meta: {
     auth: ['admin-app'],
   },
-  component: BasicLayout,
+  component: LayoutMain,
   children: [
     {
       path: 'wechat/setting/menus/index',

+ 2 - 2
template/admin/src/router/modules/cms.js

@@ -8,7 +8,7 @@
 // | Author: CRMEB Team <admin@crmeb.com>
 // +---------------------------------------------------------------------
 
-import BasicLayout from '@/components/main';
+import LayoutMain from '@/layout';
 import setting  from '@/setting'
 let routePre = setting.routePre;
 
@@ -21,7 +21,7 @@ export default {
   redirect: {
     name: `${pre}article`,
   },
-  component: BasicLayout,
+  component: LayoutMain,
   children: [
     {
       path: 'article/index/:id?',

+ 2 - 2
template/admin/src/router/modules/division.js

@@ -8,7 +8,7 @@
 // | Author: CRMEB Team <admin@crmeb.com>
 // +---------------------------------------------------------------------
 
-import BasicLayout from '@/components/main';
+import LayoutMain from '@/layout';
 import setting  from '@/setting'
 let routePre = setting.routePre;
 
@@ -24,7 +24,7 @@ export default {
     name: `${pre}division`,
   },
   meta,
-  component: BasicLayout,
+  component: LayoutMain,
   children: [
     {
       path: 'index',

+ 26 - 20
template/admin/src/router/modules/echarts.js

@@ -1,3 +1,9 @@
+/*
+ * @Author: From-wh from-wh@hotmail.com
+ * @Date: 2023-02-21 09:14:27
+ * @FilePath: /admin/src/router/modules/echarts.js
+ * @Description: 
+ */
 // +---------------------------------------------------------------------
 // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
 // +---------------------------------------------------------------------
@@ -8,7 +14,7 @@
 // | Author: CRMEB Team <admin@crmeb.com>
 // +---------------------------------------------------------------------
 
-import BasicLayout from '@/components/main';
+import LayoutMain from '@/layout';
 import setting  from '@/setting'
 let routePre = setting.routePre;
 
@@ -21,25 +27,25 @@ export default {
   redirect: {
     name: `${pre}/trade/order`,
   },
-  component: BasicLayout,
+  component: LayoutMain,
   children: [
-    {
-      path: 'trade/order',
-      name: `${pre}/trade/order`,
-      meta: {
-        auth: ['admin-order-storeOrder-index'],
-        title: '交易统计',
-      },
-      component: () => import('@/pages/echarts/trade/order'),
-    },
-    {
-      path: 'trade/product',
-      name: `${pre}/trade/product`,
-      meta: {
-        auth: ['admin-order-storeOrder-index'],
-        title: '商品统计',
-      },
-      component: () => import('@/pages/echarts/trade/product'),
-    },
+    // {
+    //   path: 'trade/order',
+    //   name: `${pre}/trade/order`,
+    //   meta: {
+    //     auth: ['admin-order-storeOrder-index'],
+    //     title: '交易统计',
+    //   },
+    //   component: () => import('@/pages/echarts/trade/order'),
+    // },
+    // {
+    //   path: 'trade/product',
+    //   name: `${pre}/trade/product`,
+    //   meta: {
+    //     auth: ['admin-order-storeOrder-index'],
+    //     title: '商品统计',
+    //   },
+    //   component: () => import('@/pages/echarts/trade/product'),
+    // },
   ],
 };

+ 2 - 2
template/admin/src/router/modules/finance.js

@@ -8,7 +8,7 @@
 // | Author: CRMEB Team <admin@crmeb.com>
 // +---------------------------------------------------------------------
 
-import BasicLayout from '@/components/main';
+import LayoutMain from '@/layout';
 import setting  from '@/setting'
 let routePre = setting.routePre;
 
@@ -24,7 +24,7 @@ export default {
   redirect: {
     name: `${pre}cashApply`,
   },
-  component: BasicLayout,
+  component: LayoutMain,
   children: [
     {
       path: 'billing_records/index',

+ 4 - 4
template/admin/src/router/modules/index.js

@@ -8,7 +8,7 @@
 // | Author: CRMEB Team <admin@crmeb.com>
 // +---------------------------------------------------------------------
 
-import BasicLayout from '@/components/main';
+import LayoutMain from '@/layout';
 import setting  from '@/setting'
 let routePre = setting.routePre;
 
@@ -16,7 +16,7 @@ let routePre = setting.routePre;
 //   path: '/',
 //   name: 'home',
 //   redirect: '/admin/home',
-//   component: BasicLayout,
+//   component: LayoutMain,
 //   meta: {
 //     hideInMenu: true,
 //     notCache: true,
@@ -49,10 +49,10 @@ export default {
     name: `${pre}index`,
   },
   meta,
-  component: BasicLayout,
+  component: LayoutMain,
   children: [
     {
-      path: routePre+'/home/',
+      path: routePre+'/home',
       name: `${pre}index`,
       header: 'home',
       meta: {

+ 2 - 2
template/admin/src/router/modules/marketing.js

@@ -8,7 +8,7 @@
 // | Author: CRMEB Team <admin@crmeb.com>
 // +---------------------------------------------------------------------
 
-import BasicLayout from '@/components/main';
+import LayoutMain from '@/layout';
 import setting  from '@/setting'
 let routePre = setting.routePre;
 
@@ -21,7 +21,7 @@ export default {
   redirect: {
     name: `${pre}storeCouponIssue`,
   },
-  component: BasicLayout,
+  component: LayoutMain,
   children: [
     {
       path: 'store_combination/index',

+ 2 - 2
template/admin/src/router/modules/order.js

@@ -8,7 +8,7 @@
 // | Author: CRMEB Team <admin@crmeb.com>
 // +---------------------------------------------------------------------
 
-import BasicLayout from '@/components/main';
+import LayoutMain from '@/layout';
 import setting  from '@/setting'
 let routePre = setting.routePre;
 
@@ -21,7 +21,7 @@ export default {
   redirect: {
     name: `${pre}list`,
   },
-  component: BasicLayout,
+  component: LayoutMain,
   children: [
     {
       path: 'list',

+ 2 - 2
template/admin/src/router/modules/product.js

@@ -8,7 +8,7 @@
 // | Author: CRMEB Team <admin@crmeb.com>
 // +---------------------------------------------------------------------
 
-import BasicLayout from '@/components/main';
+import LayoutMain from '@/layout';
 import setting  from '@/setting'
 let routePre = setting.routePre;
 
@@ -26,7 +26,7 @@ export default {
   redirect: {
     name: `${pre}productList`,
   },
-  component: BasicLayout,
+  component: LayoutMain,
   children: [
     {
       path: 'product_list',

+ 2 - 2
template/admin/src/router/modules/setting.js

@@ -8,7 +8,7 @@
 // | Author: CRMEB Team <admin@crmeb.com>
 // +---------------------------------------------------------------------
 
-import BasicLayout from '@/components/main';
+import LayoutMain from '@/layout';
 import setting from '@/setting';
 let routePre = setting.routePre;
 
@@ -25,7 +25,7 @@ export default {
   redirect: {
     name: `${pre}setSystem`,
   },
-  component: BasicLayout,
+  component: LayoutMain,
   children: [
     {
       path: 'system_role/index',

+ 2 - 2
template/admin/src/router/modules/statistic.js

@@ -8,7 +8,7 @@
 // | Author: CRMEB Team <admin@crmeb.com>
 // +---------------------------------------------------------------------
 
-import BasicLayout from '@/components/main';
+import LayoutMain from '@/layout';
 import setting  from '@/setting'
 let routePre = setting.routePre;
 
@@ -25,7 +25,7 @@ export default {
   redirect: {
     name: `${pre}product`,
   },
-  component: BasicLayout,
+  component: LayoutMain,
   children: [
     {
       path: 'product',

+ 2 - 2
template/admin/src/router/modules/system.js

@@ -8,7 +8,7 @@
 // | Author: CRMEB Team <admin@crmeb.com>
 // +---------------------------------------------------------------------
 
-import BasicLayout from '@/components/main';
+import LayoutMain from '@/layout';
 import setting  from '@/setting'
 let routePre = setting.routePre;
 
@@ -24,7 +24,7 @@ export default {
   meta: {
     auth: ['admin-system'],
   },
-  component: BasicLayout,
+  component: LayoutMain,
   children: [
     {
       path: 'file',

+ 2 - 2
template/admin/src/router/modules/user.js

@@ -8,7 +8,7 @@
 // | Author: CRMEB Team <admin@crmeb.com>
 // +---------------------------------------------------------------------
 
-import BasicLayout from '@/components/main';
+import LayoutMain from '@/layout';
 import setting  from '@/setting'
 let routePre = setting.routePre;
 
@@ -26,7 +26,7 @@ export default {
     name: `${pre}list`,
   },
   meta,
-  component: BasicLayout,
+  component: LayoutMain,
   children: [
     {
       path: 'list',

+ 3 - 3
template/admin/src/router/routers.js

@@ -20,7 +20,7 @@ import cms from './modules/cms';
 import marketing from './modules/marketing';
 import app from './modules/app';
 import system from './modules/system';
-import BasicLayout from '@/components/main';
+import LayoutMain from '@/layout';
 import statistic from './modules/statistic';
 import frameOut from './modules/frameOut';
 import division from './modules/division';
@@ -39,7 +39,7 @@ const frameIn = [
     redirect: {
       name: 'home_index',
     },
-    component: BasicLayout,
+    component: LayoutMain,
     children: [
       // {
       //   path: '/admin/system/log',
@@ -102,7 +102,7 @@ const frameIn = [
     redirect: {
       name: 'home_index',
     },
-    component: BasicLayout,
+    component: LayoutMain,
   },
   {
     path: routePre + '/widget.images/index.html',

+ 13 - 0
template/admin/src/store/index.js

@@ -29,6 +29,11 @@ import integralOrder from './module/integralOrder';
 import mobildConfig from './module/mobildConfig';
 import upgrade from './module/upgrade';
 import layout from './module/layout';
+import themeConfig from './module/themeConfig';
+import routesList from './module/routesList';
+import tagsViewRoutes from './module/tagsViewRoutes';
+import userInfos from './module/userInfos';
+import keepAliveNames from './module/keepAliveNames';
 
 Vue.use(Vuex);
 // 持久化储存
@@ -63,6 +68,9 @@ export default new Vuex.Store({
         mobildConfig: state.mobildConfig,
         upgrade: state.upgrade,
         layout: state.layout,
+        themeConfig: state.themeConfig,
+        routesList: state.routesList,
+        keepAliveNames: state.keepAliveNames,
       }),
       storage: window.localStorage,
     }).plugin,
@@ -85,5 +93,10 @@ export default new Vuex.Store({
     integralOrder,
     upgrade,
     layout,
+    themeConfig,
+    routesList,
+    tagsViewRoutes,
+    userInfos,
+    keepAliveNames
   },
 });

+ 1 - 0
template/admin/src/store/module/app.js

@@ -77,6 +77,7 @@ export default {
     },
     addTag(state, { route, type = 'unshift' }) {
       let router = getRouteTitleHandled(route);
+      console.log(router,'routerrouterrouter')
       if (!routeHasExist(state.tagNavList, router)) {
         if (type === 'push') state.tagNavList.push(router);
         else {

+ 20 - 0
template/admin/src/store/module/keepAliveNames.js

@@ -0,0 +1,20 @@
+const keepAliveNamesModule = {
+	namespaced: true,
+	state: {
+		keepAliveNames: [],
+	},
+	mutations: {
+		// 设置路由缓存(name字段)
+		getCacheKeepAlive(state, data) {
+			state.keepAliveNames = data;
+		},
+	},
+	actions: {
+		// 设置路由缓存(name字段)
+		async setCacheKeepAlive({ commit }, data) {
+			commit('getCacheKeepAlive', data);
+		},
+	},
+};
+
+export default keepAliveNamesModule;

+ 20 - 0
template/admin/src/store/module/routesList.js

@@ -0,0 +1,20 @@
+const routesListModule = {
+	namespaced: true,
+	state: {
+		routesList: [],
+	},
+	mutations: {
+		// 设置路由,菜单中使用到
+		getRoutesList(state, data) {
+			state.routesList = data;
+		},
+	},
+	actions: {
+		// 设置路由,菜单中使用到
+		async setRoutesList({ commit }, data) {
+			commit('getRoutesList', data);
+		},
+	},
+};
+
+export default routesListModule;

+ 20 - 0
template/admin/src/store/module/tagsViewRoutes.js

@@ -0,0 +1,20 @@
+const tagsViewRoutesModule = {
+	namespaced: true,
+	state: {
+		tagsViewRoutes: [],
+	},
+	mutations: {
+		// 设置 TagsView 路由
+		getTagsViewRoutes(state, data) {
+			state.tagsViewRoutes = data;
+		},
+	},
+	actions: {
+		// 设置 TagsView 路由
+		async setTagsViewRoutes({ commit }, data) {
+			commit('getTagsViewRoutes', data);
+		},
+	},
+};
+
+export default tagsViewRoutesModule;

+ 122 - 0
template/admin/src/store/module/themeConfig.js

@@ -0,0 +1,122 @@
+/**
+ * 2020.05.28 by lyt 优化
+ * 修改一下配置时,需要每次都清理 `window.localStorage` 浏览器永久缓存,配置才会生效
+ */
+const themeConfigModule = {
+	namespaced: true,
+	state: {
+		themeConfig: {
+			// 是否开启布局配置抽屉
+			isDrawer: false,
+
+			/**
+			 * 全局主题
+			 */
+			// 默认 primary 主题颜色
+			primary: '#409eff',
+			// 是否开启深色模式
+			isIsDark: false,
+
+			/**
+			 * 菜单 / 顶栏
+			 * 请注意:
+			 * 需要同时修改 `/@/theme/common/var.scss` 对应的值,
+			 * 不提供像 vue-next-admin 一样的实现
+			 */
+			// 默认顶栏导航背景颜色
+			topBar: '#ffffff',
+			// 默认顶栏导航字体颜色
+			topBarColor: '#606266',
+			// 默认菜单导航背景颜色
+			menuBar: '#545c64',
+			// 默认菜单导航字体颜色
+			menuBarColor: '#eaeaea',
+			// 默认分栏菜单背景颜色
+			columnsMenuBar: '#545c64',
+			// 默认分栏菜单字体颜色
+			columnsMenuBarColor: '#e6e6e6',
+
+			/**
+			 * 界面设置
+			 */
+			// 是否开启菜单水平折叠效果
+			isCollapse: false,
+			// 是否开启菜单手风琴效果
+			isUniqueOpened: false,
+			// 是否开启固定 Header
+			isFixedHeader: false,
+
+			/**
+			 * 界面显示
+			 */
+			// 是否开启侧边栏 Logo
+			isShowLogo: true,
+			// 是否开启 Breadcrumb
+			isBreadcrumb: true,
+			// 是否开启 Breadcrumb 图标
+			isBreadcrumbIcon: false,
+			// 是否开启 Tagsview
+			isTagsview: true,
+			// 是否开启 Tagsview 图标
+			isTagsviewIcon: false,
+			// 是否开启 TagsView 缓存
+			isCacheTagsView: false,
+			// 是否开启 Footer 底部版权信息
+			isFooter: false,
+			// 是否开启灰色模式
+			isGrayscale: false,
+			// 是否开启色弱模式
+			isInvert: false,
+
+			/**
+			 * 其它设置
+			 */
+			// 默认 Tagsview 风格,可选 1、 tags-style-one,自行扩展:
+			// 1、需修改 @/layout/navBars/breadcrumb/setings.vue `getThemeConfig.tagsStyle` el-option
+			// 2、需修改 @/layout/navBars/tagsView/tagsView.vue 代码最底部注释部分 css 样式
+			tagsStyle: 'tags-style-one',
+			// 主页面切换动画:可选值"<slide-right|slide-left|opacitys>",默认 slide-right
+			animation: 'slide-right',
+			// 分栏高亮风格:可选值"<columns-round|columns-card>",默认 columns-round
+			columnsAsideStyle: 'columns-round',
+			// 分栏布局风格:可ƒ选值"<columns-horizontal|columns-vertical>",默认 columns-horizontal
+			columnsAsideLayout: 'columns-vertical',
+
+			/**
+			 * 布局切换
+			 * 注意:为了演示,切换布局时,颜色会被还原成默认,代码位置:/@/layout/navBars/breadcrumb/setings.vue
+			 * 中的 `initSetLayoutChange(设置布局切换,重置主题样式)` 方法
+			 */
+			// 布局切换:可选值"<defaults|classic|transverse|columns>",默认 defaults
+			layout: 'defaults',
+
+			/**
+			 * 全局网站标题 / 副标题
+			 */
+			// 网站主标题(菜单导航、浏览器当前网页标题)
+			globalTitle: 'crmeb-admin',
+			// 网站副标题(登录页顶部文字)
+			globalViceTitle: 'SMALL@小柒',
+			// 网站描述(登录页顶部文字)
+			globalViceDes: 'vue2',
+			// 默认初始语言,可选值"<zh-cn|en|zh-tw>",默认 zh-cn
+			globalI18n: 'zh-cn',
+			// 默认全局组件大小,可选值"<|medium|small|mini>",默认 ''
+			globalComponentSize: '',
+		},
+	},
+	mutations: {
+		// 设置布局配置
+		getThemeConfig(state, data) {
+			state.themeConfig = data;
+		},
+	},
+	actions: {
+		// 设置布局配置
+		setThemeConfig({ commit }, data) {
+			commit('getThemeConfig', data);
+		},
+	},
+};
+
+export default themeConfigModule;

+ 26 - 0
template/admin/src/store/module/userInfos.js

@@ -0,0 +1,26 @@
+import { Session } from '@/utils/storage.js';
+
+const userInfosModule = {
+	namespaced: true,
+	state: {
+		userInfos: {},
+	},
+	mutations: {
+		// 设置用户信息
+		getUserInfos(state, data) {
+			state.userInfos = data;
+		},
+	},
+	actions: {
+		// 设置用户信息
+		async setUserInfos({ commit }, data) {
+			if (data) {
+				commit('getUserInfos', data);
+			} else {
+				if (Session.get('userInfo')) commit('getUserInfos', Session.get('userInfo'));
+			}
+		},
+	},
+};
+
+export default userInfosModule;

File diff ditekan karena terlalu besar
+ 0 - 3037
template/admin/src/styles/font/demo_index.html


+ 0 - 495
template/admin/src/styles/layout/basic-layout/layout.less

@@ -1,495 +0,0 @@
-@menuSideWidthCollapse: 80px;
-@menuHeaderHeight: 64px;
-@headerTriggerMinPadding: 0 12px;
-
-.dropdownMobile() {
-  width: 100%;
-  .ivu-select-dropdown {
-    left: 0 !important;
-    right: 0;
-    border-radius: 0;
-    margin: 0;
-  }
-}
-
-.i-layout {
-  &-header {
-    width: 100%;
-    background: #fff;
-    padding: 0;
-    box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
-    transition: all @transition-time @ease-in-out;
-    z-index: 3;
-    display: block;
-
-    &-with-hide-sider {
-      transition: none;
-    }
-
-    &-with-menu {
-      display: flex;
-    }
-
-    &-color {
-      &-dark {
-        background: @menu-dark-title;
-      }
-      &-primary {
-        background: #2173dc;
-        background: -webkit-linear-gradient(to right, #1d42ab, #2173dc, #1e93ff);
-        background: -moz-linear-gradient(to right, #1d42ab, #2173dc, #1e93ff);
-        background: -o-linear-gradient(to right, #1d42ab, #2173dc, #1e93ff);
-        background: linear-gradient(to right, #1d42ab, #2173dc, #1e93ff);
-
-        .i-layout-header-search {
-          .placeholder(fade(#fff, 70%));
-        }
-      }
-
-      &-dark,
-      &-primary {
-        color: #fff;
-        .i-layout-header-trigger:hover {
-          background: fade(#fff, 5%);
-        }
-        .ivu-menu {
-          background: transparent;
-        }
-        .ivu-menu-item {
-          border-width: 3px !important;
-        }
-        .ivu-menu-light.ivu-menu-horizontal .ivu-menu-item,
-        .ivu-menu-light.ivu-menu-horizontal .ivu-menu-submenu {
-          color: fade(#fff, 70%);
-        }
-        .ivu-menu-light.ivu-menu-horizontal .ivu-menu-item-active,
-        .ivu-menu-light.ivu-menu-horizontal .ivu-menu-submenu-active,
-        .ivu-menu-light.ivu-menu-horizontal .ivu-menu-item:hover,
-        .ivu-menu-light.ivu-menu-horizontal .ivu-menu-submenu:hover {
-          color: #fff;
-          border-bottom-color: #4fe3c1;
-        }
-
-        .i-layout-header-breadcrumb {
-          .ivu-breadcrumb-item-link {
-            color: fade(#fff, 70%);
-          }
-          a.ivu-breadcrumb-item-link:hover {
-            color: #fff;
-          }
-          & > span:last-child .ivu-breadcrumb-item-link {
-            color: #fff;
-          }
-        }
-      }
-    }
-
-    &-fix {
-      position: fixed;
-      top: 0;
-      right: 0;
-      left: @menuSideWidth;
-      z-index: 11;
-      &-collapse {
-        left: @menuSideWidthCollapse;
-      }
-    }
-    &-stick {
-      left: 0;
-      z-index: 14;
-    }
-    &-mobile {
-      left: 0;
-      display: block;
-    }
-
-    &-logo {
-      display: inline-block;
-      height: @menuHeaderHeight;
-      line-height: @menuHeaderHeight;
-      &-stick {
-        width: @menuSideWidth;
-        text-align: center;
-      }
-      img {
-        height: 80%;
-        vertical-align: middle;
-      }
-    }
-
-    &-trigger {
-      display: inline-block;
-      width: @menuHeaderHeight;
-      height: @menuHeaderHeight;
-      text-align: center;
-      cursor: pointer;
-      transition: all @transition-time @ease-in-out;
-      &:hover {
-        background: @trigger-background;
-      }
-      i {
-        font-size: 20px;
-      }
-      &-min {
-        width: auto;
-        padding: @headerTriggerMinPadding;
-
-        i {
-          font-size: 18px;
-        }
-      }
-      &-in {
-        padding: 0;
-      }
-      &-no-height {
-        height: auto;
-      }
-      &-nohover:hover {
-        background: transparent !important;
-      }
-    }
-
-    &-right {
-      height: @menuHeaderHeight;
-      float: right;
-    }
-
-    &-user {
-      &-name {
-        margin-left: 12px;
-      }
-      .ivu-dropdown-item {
-        font-size: 14px !important;
-        text-align: left;
-        i,
-        span {
-          vertical-align: middle;
-        }
-        i {
-          margin-right: 6px;
-        }
-      }
-      &-mobile {
-        .dropdownMobile();
-      }
-    }
-    &-notice {
-      .ivu-notifications-rel {
-        padding: @headerTriggerMinPadding;
-      }
-      &-mobile {
-        .dropdownMobile();
-      }
-    }
-    &-i18n {
-      .ivu-dropdown-item {
-        text-align: left;
-      }
-      .ivu-notifications-rel {
-        padding: @headerTriggerMinPadding;
-      }
-      &-mobile {
-        .dropdownMobile();
-      }
-    }
-
-    &-search {
-      outline: none;
-      border: none;
-      background: transparent;
-      color: inherit;
-      .placeholder();
-
-      &-drop {
-        .dropdownMobile();
-        .ivu-select-dropdown {
-          line-height: normal;
-        }
-        &-main {
-          display: flex;
-          align-items: center;
-          margin: 0 5px;
-          .ivu-input-wrapper {
-            flex: auto;
-          }
-          &-cancel {
-            flex: auto;
-            width: 80px;
-            color: @text-color;
-          }
-        }
-      }
-    }
-
-    &-setting {
-      &-title {
-        margin: 12px 0;
-        color: @title-color;
-        font-size: @font-size-base;
-        font-weight: 500;
-      }
-      &-item {
-        display: flex;
-        align-items: center;
-        padding: 12px 0;
-        &-radio {
-          display: inline-block;
-          position: relative;
-          margin-right: 16px;
-          cursor: pointer;
-
-          &:after {
-            content: '';
-            display: block;
-            width: 6px;
-            height: 6px;
-            border-radius: 6px;
-            background: transparent;
-            margin: 0 auto;
-          }
-          &.on:after {
-            background: @success-color;
-          }
-        }
-
-        &-desc {
-          flex: 1 1;
-          font-size: @font-size-base;
-        }
-        &-action {
-          flex: 0 0 auto;
-        }
-
-        &-disabled &-desc {
-          opacity: 0.5;
-        }
-      }
-    }
-
-    &-breadcrumb {
-      display: inline-block;
-      .ivu-breadcrumb-item-link {
-        color: inherit;
-      }
-      & > span:last-child {
-        font-weight: normal;
-      }
-      .ivu-breadcrumb-item-separator {
-        vertical-align: middle;
-      }
-    }
-  }
-
-  &-sider {
-    min-height: 100vh;
-    background: #fff;
-    box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
-    position: relative;
-    z-index: 13;
-
-    &-fix {
-      position: fixed;
-      top: 0;
-      left: 0;
-
-      .i-layout-menu-side {
-        height: calc(~'100vh - 64px');
-        overflow-y: auto;
-      }
-    }
-
-    &-dark {
-      background: @menu-dark-title;
-      box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
-    }
-
-    &-logo {
-      height: 63px;
-      line-height: 63px;
-      text-align: center;
-      border-bottom: 1px solid #f8f8f9;
-      overflow: hidden;
-      &-dark {
-        border-bottom: 1px solid @menu-dark-active-bg;
-        background: @menu-dark-title;
-      }
-      img {
-        height: 80%;
-        vertical-align: middle;
-      }
-    }
-  }
-
-  &-content {
-    &-fix-with-header {
-      padding-top: @menuHeaderHeight;
-    }
-    &-main {
-      margin: 24px;
-    }
-    &-with-tabs &-main {
-      margin-top: 0;
-    }
-    &-with-tabs-fix &-main {
-      margin-top: 44px;
-      .i-layout-page-header {
-        margin-top: 0;
-      }
-    }
-  }
-
-  &-inside {
-    min-height: 100vh;
-    transition: all @transition-time @ease-in-out;
-    &-fix-with-sider {
-      padding-left: @menuSideWidth;
-      &-collapse {
-        padding-left: @menuSideWidthCollapse;
-      }
-    }
-    &-mobile {
-      padding-left: 0;
-    }
-    &-with-hide-sider {
-      padding-left: 0;
-    }
-  }
-
-  &-drawer {
-    .ivu-drawer-body {
-      padding: 0;
-      overflow: visible;
-    }
-    .i-layout-menu-side {
-      height: calc(~'100vh - 64px');
-      overflow-y: auto;
-    }
-    &-dark {
-      .ivu-drawer-body {
-        background: @menu-dark-title;
-      }
-    }
-  }
-
-  &-tabs {
-    width: 100%;
-    background-color: @layout-body-background;
-    transition: all @transition-time @ease-in-out;
-    &-fix {
-      position: fixed;
-      z-index: 3;
-    }
-    &-main {
-      background: @layout-body-background;
-      padding: 6px 0;
-      margin: 0 12px 0 12px;
-      display: flex;
-
-      .ivu-tabs {
-        -webkit-box-flex: 1;
-        -ms-flex-positive: 1;
-        flex-grow: 1;
-        -ms-flex-negative: 1;
-        flex-shrink: 1;
-      }
-    }
-
-    .ivu-tabs-nav-container {
-      margin-bottom: 0;
-    }
-    .ivu-tabs-bar {
-      border-bottom: none;
-      margin-bottom: 0;
-    }
-    .ivu-tabs-ink-bar {
-      display: none;
-    }
-    .ivu-tabs.ivu-tabs-card > .ivu-tabs-bar .ivu-tabs-tab {
-      height: 32px;
-      background: #fff;
-      border-radius: 3px;
-      border: none;
-      margin-right: 6px;
-      color: #808695;
-      &:hover {
-        color: #515a6e;
-      }
-    }
-    .ivu-tabs.ivu-tabs-card > .ivu-tabs-bar .ivu-tabs-tab-active {
-      height: 32px;
-      background: #fff;
-      color: @primary-color;
-      &:hover {
-        color: @primary-color;
-      }
-    }
-    .ivu-tabs-nav-scrollable {
-      padding: 0 32px;
-    }
-    .ivu-tabs-nav-prev,
-    .ivu-tabs-nav-next {
-      width: 32px;
-      text-align: center;
-      i {
-        font-size: 16px;
-      }
-    }
-    .ivu-tabs.ivu-tabs-card > .ivu-tabs-bar .ivu-tabs-tab .ivu-icon-ios-close {
-      width: 22px;
-      transform: translateZ(0);
-      margin-right: -6px;
-    }
-
-    &-title {
-      display: inline-block;
-      img,
-      i {
-        display: inline-block;
-        height: 20px;
-        vertical-align: middle;
-        margin-right: 8px;
-      }
-      span {
-        display: inline-block;
-        vertical-align: middle;
-      }
-    }
-
-    &-close {
-      -webkit-box-flex: 0;
-      -ms-flex-positive: 0;
-      flex-grow: 0;
-      -ms-flex-negative: 0;
-      flex-shrink: 0;
-      &-main {
-        width: 32px;
-        height: 32px;
-        line-height: 32px;
-        text-align: center;
-        background-color: #fff;
-        border-radius: 2px;
-        cursor: pointer;
-        i {
-          font-size: 16px;
-        }
-      }
-    }
-  }
-
-  &-page-header {
-    margin: -24px -24px 0px;
-  }
-
-  &-content-with-tabs:not(&-content-with-tabs-fix) &-page-header {
-    margin-top: 0;
-  }
-}
-
-.fade-quick-enter-active,
-.fade-quick-leave-active {
-  transition: opacity 0.2s;
-}
-.fade-quick-enter,
-.fade-quick-leave-to {
-  opacity: 0;
-}

+ 0 - 220
template/admin/src/styles/layout/basic-layout/menu.less

@@ -1,220 +0,0 @@
-.i-layout-menu {
-  &-side {
-    position: static; // 菜单开启后,Dropdown 会被 overflow-y:hidden 隐藏掉,加此不受影响
-    &:after {
-      display: none !important;
-    }
-    &-title {
-      display: inline-block;
-      &-icon {
-        display: inline-block;
-        width: 24px;
-        height: 24px;
-        line-height: 21px;
-        vertical-align: middle;
-        text-align: center;
-        margin-right: 8px;
-        &-null {
-          width: 12px;
-          display: inline-block;
-        }
-        &-single {
-          margin-right: 0;
-        }
-        i {
-          margin-right: 0 !important;
-        }
-        img {
-          width: 100%;
-          height: 100%;
-        }
-      }
-
-      &-text {
-        &-selected {
-          color: @primary-color;
-        }
-      }
-    }
-
-    .ivu-menu-submenu-title,
-    .ivu-menu-item {
-      height: 52px;
-      overflow: hidden;
-      text-overflow: ellipsis;
-      white-space: nowrap;
-    }
-
-    .ivu-menu-submenu-title-icon {
-      width: 14px;
-      height: 14px;
-      position: absolute;
-      top: 50%;
-      right: 8px;
-      float: none;
-    }
-
-    .ivu-dropdown-menu {
-      min-width: 160px;
-    }
-    .ivu-dropdown-item {
-      font-size: @font-size-base !important;
-      padding-right: 32px;
-
-      &-divided:before {
-        margin: 0 -32px 0 -16px;
-      }
-    }
-    .ivu-select-dropdown {
-      margin: 5px 0 5px 3px;
-    }
-    .ivu-tooltip,
-    .ivu-tooltip-rel {
-      display: block;
-      text-align: center;
-    }
-
-    &.ivu-menu-dark .ivu-menu-item-active {
-      color: #fff !important;
-    }
-
-    &-arrow {
-      position: absolute;
-      top: 50%;
-      right: 16px;
-      transform: translate(0, -50%);
-    }
-
-    &-collapse {
-      &-top {
-        display: block;
-        &-item {
-          text-align: center;
-        }
-      }
-      &-item {
-        &-selected,
-        &-selected:hover {
-          background-color: ~`colorPalette('@{primary-color}', 1) `;
-          color: @primary-color;
-        }
-      }
-      &-title {
-        text-align: center;
-        padding: 6px 0;
-        border-bottom: 1px solid @border-color-split;
-      }
-      &-dark {
-        .ivu-select-dropdown {
-          background-color: @menu-dark-title;
-        }
-        .ivu-dropdown-item {
-          color: @menu-dark-subsidiary-color;
-          transition: all @transition-time @ease-in-out;
-
-          &-divided {
-            border-color: @menu-dark-active-bg;
-            &:before {
-              background-color: @menu-dark-title;
-            }
-          }
-        }
-        .ivu-dropdown-item:hover {
-          background: transparent;
-          color: #fff;
-        }
-        .i-layout-menu-side-collapse-item {
-          &-selected,
-          &-selected:hover {
-            background-color: @primary-color;
-            color: #fff;
-          }
-        }
-      }
-      &-dark &-title {
-        border-bottom: 1px solid @menu-dark-active-bg;
-        color: @menu-dark-subsidiary-color;
-      }
-    }
-  }
-
-  &-head {
-    display: inline-block;
-    overflow: hidden;
-    flex: 1;
-
-    .ivu-menu-horizontal.ivu-menu-light:after {
-      display: none;
-    }
-    .ivu-menu-horizontal {
-      display: inline-block;
-      vertical-align: middle;
-      height: auto; // 这里不能设置为 @menuHeaderHeight - 2px,因为在平板模式下,header 宽度不够,拿不到真实高度
-      line-height: @menuHeaderHeight - 6px;
-    }
-
-    &-logo {
-      display: inline-block;
-      height: @menuHeaderHeight;
-      text-align: center;
-      vertical-align: middle;
-      img {
-        height: 80%;
-        vertical-align: middle;
-      }
-    }
-
-    &-title {
-      display: inline-block;
-      &-icon {
-        display: inline-block;
-        width: 24px;
-        height: 24px;
-        line-height: 21px;
-        vertical-align: middle;
-        text-align: center;
-        &-single {
-          margin-right: 0;
-        }
-        i {
-          margin-right: 0 !important;
-          font-size: 16px;
-        }
-        img {
-          width: 100%;
-          height: 100%;
-        }
-      }
-      &-text {
-        vertical-align: middle;
-        margin-left: 6px;
-      }
-    }
-
-    .ivu-dropdown-rel {
-      padding: @headerTriggerMinPadding;
-    }
-    .ivu-dropdown-item {
-      text-align: left;
-    }
-
-    &-mobile {
-      vertical-align: baseline;
-      overflow: visible;
-      .ivu-dropdown-item {
-        font-size: 14px !important;
-        text-align: left;
-        i,
-        span {
-          vertical-align: middle;
-        }
-        i {
-          margin-right: 6px;
-        }
-      }
-      &-drop {
-        .dropdownMobile();
-      }
-    }
-  }
-}

+ 0 - 2
template/admin/src/styles/layout/index.less

@@ -1,2 +0,0 @@
-@import 'basic-layout/layout.less';
-@import 'basic-layout/menu.less';

+ 0 - 132
template/admin/src/styles/pages/account.less

@@ -1,132 +0,0 @@
-.page-account {
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-  align-items: center;
-  height: 100vh;
-  overflow: auto;
-
-  &-container {
-    flex: 1;
-    padding: 32px 0;
-    text-align: center;
-    width: 384px;
-    margin: 0 auto;
-
-    &-result {
-      width: 100%;
-    }
-
-    @media screen and (max-width: @screen-sm) {
-      //width: 95%;
-    }
-  }
-
-  &-tabs {
-    .ivu-tabs-bar {
-      border-bottom: none;
-    }
-    .ivu-tabs-nav-scroll {
-      text-align: center;
-    }
-    .ivu-tabs-nav {
-      display: inline-block;
-      float: none;
-    }
-  }
-
-  &-top {
-    padding: 32px 0;
-    &-logo {
-      img {
-        height: 75px;
-      }
-    }
-    &-desc {
-      font-size: @font-size-base;
-      color: @subsidiary-color;
-    }
-  }
-
-  &-auto-login {
-    margin-bottom: 24px;
-    text-align: left;
-    a {
-      float: right;
-    }
-  }
-
-  &-other {
-    margin: 24px 0;
-    text-align: left;
-    span {
-      font-size: @font-size-base;
-    }
-    img {
-      width: 24px;
-      margin-left: 16px;
-      cursor: pointer;
-      vertical-align: middle;
-      opacity: 0.7;
-      transition: all @transition-time @ease-in-out;
-      &:hover {
-        opacity: 1;
-      }
-    }
-  }
-
-  .ivu-poptip,
-  .ivu-poptip-rel {
-    display: block;
-  }
-
-  &-register {
-    float: right;
-    &-tip {
-      text-align: left;
-      &-low {
-        color: @error-color;
-      }
-      &-medium {
-        color: @warning-color;
-      }
-      &-strong {
-        color: @success-color;
-      }
-      &-title {
-        font-size: @font-size-base;
-      }
-      &-desc {
-        white-space: initial;
-        font-size: @font-size-base;
-        margin-top: 6px;
-      }
-    }
-  }
-
-  &-to-login {
-    text-align: center;
-    margin-top: 16px;
-  }
-
-  &-header {
-    text-align: right;
-    position: fixed;
-    top: 16px;
-    right: 24px;
-  }
-}
-@media (min-width: @screen-md-min) {
-  .page-account {
-    background-image: url('../../assets/images/bg.jpg');
-    background-repeat: no-repeat;
-    background-position: center;
-    background-size: cover;
-  }
-  .page-account.kf {
-    background-image: url('../../assets/images/kfbg.jpg');
-  }
-  .page-account-container {
-    padding: 32px 0 24px 0;
-  }
-}

+ 242 - 0
template/admin/src/theme/app.scss

@@ -0,0 +1,242 @@
+/* 初始化样式
+------------------------------- */
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  outline: none;
+}
+
+:root {
+  --prev-bg-menuBar: #282c34;
+  --prev-bg-menuBarColor: #ffffff;
+  --prev-bg-topBar: #ffffff;
+  --prev-bg-topBarColor: #282c34;
+  --prev-bg-columnsMenuBar: #282c34;
+  --prev-bg-columnsMenuBarColor: #e6e6e6;
+  --prev-bg-menu-hover-ba-color: rgba(0, 0, 0, 0.2);
+  --prev-bg-menu-active-color: #409eff;
+  --prev-bg-main-color: #f8f8f8;
+  --prev-bg-color: #f5f7fa;
+  --prev-bg-white: #ffffff;
+  --prev-color-primary: #409eff;
+  --prev-color-text-white: #ffffff;
+  --prev-color-text-black: #000000;
+  --prev-color-text-primary: #303133;
+  --prev-color-text-regular: #606266;
+  --prev-color-text-secondary: #909399;
+  --prev-color-text-placeholder: #c0c4cc;
+  --prev-color-hover: rgba(0, 0, 0, 0.04);
+  --prev-color-seting-main: #e9eef3;
+  --prev-color-seting-aside: #d3dce6;
+  --prev-color-seting-header: #b3c0d1;
+  --prev-border-color-hover: #c0c4cc;
+  --prev-border-color-base: #dcdfe6;
+  --prev-border-color-light: #e4e7ed;
+  --prev-border-color-lighter: #ebeef5;
+  --prev-border-color-extra-light: #f2f6fc;
+}
+
+html,
+body,
+#app {
+  width: 100%;
+  height: 100%;
+  background-color: var(--prev-bg-main-color);
+  font-size: 14px;
+}
+
+/* 主布局样式
+------------------------------- */
+.layout-container {
+  width: 100%;
+  height: 100%;
+  .layout-aside {
+    background: var(--prev-bg-menuBar);
+    box-shadow: 0px 0 1px rgba(0, 21, 41, 1%);
+    height: inherit;
+    position: relative;
+    z-index: 1;
+    display: flex;
+    flex-direction: column;
+    overflow-x: hidden !important;
+    .el-scrollbar__view {
+      overflow: hidden;
+    }
+  }
+  .layout-header {
+    padding: 0 !important;
+  }
+  .layout-main {
+    padding: 0 !important;
+    overflow: hidden;
+    width: 100%;
+    background-color: var(--prev-bg-main-color);
+  }
+  .el-scrollbar {
+    width: 100%;
+  }
+  .layout-view-bg-white {
+    background: var(--prev-bg-white);
+    width: 100%;
+    height: 100%;
+    border-radius: 4px;
+    border: 1px solid var(--prev-border-color-lighter);
+  }
+  .layout-el-aside-br-color {
+    // border-right: 1px solid rgb(238, 238, 238);
+  }
+  .layout-aside-width-default {
+    width: 180px !important;
+    transition: width 0.3s ease;
+  }
+  .layout-aside-width64 {
+    width: 64px !important;
+    transition: width 0.3s ease;
+  }
+  .layout-aside-width1 {
+    width: 1px !important;
+    transition: width 0.3s ease;
+  }
+  .layout-scrollbar {
+    @extend .el-scrollbar;
+    padding: 15px;
+  }
+  .layout-mian-height-50 {
+    height: calc(100vh - 50px);
+  }
+  .layout-columns-warp {
+    flex: 1;
+    display: flex;
+    overflow: hidden;
+  }
+  .layout-hide {
+    display: none;
+  }
+}
+
+/* 进度条颜色
+------------------------------- */
+#nprogress .bar {
+  background: var(--prev-color-primary) !important;
+}
+
+/* flex 弹性布局
+------------------------------- */
+.flex {
+  display: flex;
+}
+.flex-auto {
+  flex: 1;
+}
+.flex-center {
+  @extend .flex;
+  flex-direction: column !important;
+  width: 100%;
+  overflow: hidden;
+}
+.flex-margin {
+  margin: auto;
+}
+.flex-warp {
+  display: flex;
+  flex-wrap: wrap;
+  align-content: flex-start;
+  margin: 0 -5px;
+  .flex-warp-item {
+    padding: 5px;
+    .flex-warp-item-box {
+      width: 100%;
+      height: 100%;
+    }
+  }
+}
+/* 宽高 100%
+------------------------------- */
+.w100 {
+  width: 100% !important;
+}
+.h100 {
+  height: 100% !important;
+}
+.vh100 {
+  height: 100vh !important;
+}
+.max100vh {
+  max-height: 100vh !important;
+}
+.min100vh {
+  min-height: 100vh !important;
+}
+
+/* 颜色值
+------------------------------- */
+.color-primary {
+  color: var(--prev-color-primary);
+}
+.color-success {
+  color: var(--prev-color-success);
+}
+.color-warning {
+  color: var(--prev-color-warning);
+}
+.color-danger {
+  color: var(--prev-color-danger);
+}
+.color-info {
+  color: var(--prev-color-info);
+}
+
+/* 溢出省略号
+------------------------------- */
+.one-text-overflow {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.two-text-overflow {
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+  overflow: hidden;
+}
+.overflow {
+  overflow: hidden !important;
+}
+
+/* 字体大小全局样式
+------------------------------- */
+@for $i from 10 through 32 {
+  .font#{$i} {
+    font-size: #{$i}px !important;
+  }
+}
+
+/* 外边距、内边距全局样式
+------------------------------- */
+@for $i from 5 through 35 {
+  .mt#{$i} {
+    margin-top: #{$i}px !important;
+  }
+  .mr#{$i} {
+    margin-right: #{$i}px !important;
+  }
+  .mb#{$i} {
+    margin-bottom: #{$i}px !important;
+  }
+  .ml#{$i} {
+    margin-left: #{$i}px !important;
+  }
+  .pt#{$i} {
+    padding-top: #{$i}px !important;
+  }
+  .pr#{$i} {
+    padding-right: #{$i}px !important;
+  }
+  .pb#{$i} {
+    padding-bottom: #{$i}px !important;
+  }
+  .pl#{$i} {
+    padding-left: #{$i}px !important;
+  }
+}

+ 1 - 0
template/admin/src/theme/base.scss

@@ -0,0 +1 @@
+@import 'common/transition.scss';

+ 102 - 0
template/admin/src/theme/common/transition.scss

@@ -0,0 +1,102 @@
+/* 页面切换动画
+------------------------------- */
+.slide-right-enter-active,
+.slide-right-leave-active,
+.slide-left-enter-active,
+.slide-left-leave-active {
+	will-change: transform;
+	transition: all 0.3s ease;
+}
+// slide-right
+.slide-right-enter {
+	opacity: 0;
+	transform: translateX(-20px);
+}
+.slide-right-leave-active {
+	opacity: 0;
+	transform: translateX(20px);
+}
+// slide-left
+.slide-left-enter-from {
+	@extend .slide-right-leave-active;
+}
+.slide-left-leave-to {
+	@extend .slide-right-enter;
+}
+// opacitys
+.opacitys-enter-active,
+.opacitys-leave-active {
+	will-change: transform;
+	transition: all 0.3s ease;
+}
+.opacitys-enter,
+.opacitys-leave-active {
+	opacity: 0;
+}
+
+/* Breadcrumb 面包屑过渡动画
+------------------------------- */
+.breadcrumb-enter-active,
+.breadcrumb-leave-active {
+	transition: all 0.3s;
+}
+.breadcrumb-enter,
+.breadcrumb-leave-active {
+	opacity: 0;
+	transform: translateX(20px);
+}
+.breadcrumb-move {
+	transition: all 0.3s;
+}
+.breadcrumb-leave-active {
+	position: absolute;
+}
+
+/* logo 过渡动画
+------------------------------- */
+@keyframes logoAnimation {
+	0% {
+		transform: scale(0);
+	}
+	80% {
+		transform: scale(1.2);
+	}
+	100% {
+		transform: scale(1);
+	}
+}
+
+/* 404、401 过渡动画
+------------------------------- */
+@keyframes error-num {
+	0% {
+		transform: translateY(60px);
+		opacity: 0;
+	}
+	100% {
+		transform: translateY(0);
+		opacity: 1;
+	}
+}
+@keyframes error-img {
+	0% {
+		opacity: 0;
+	}
+	100% {
+		opacity: 1;
+	}
+}
+
+/* 左右左 link.vue
+------------------------------- */
+@keyframes toRight {
+	0% {
+		left: -5px;
+	}
+	50% {
+		left: 100%;
+	}
+	100% {
+		left: -5px;
+	}
+}

+ 109 - 0
template/admin/src/theme/dark.scss

@@ -0,0 +1,109 @@
+/* 深色模式样式
+------------------------------- */
+[data-theme='dark'] {
+	--prev-bg-menuBar: #191919 !important;
+	--prev-bg-menuBarColor: #dadada !important;
+	--prev-bg-topBar: #191919 !important;
+	--prev-bg-topBarColor: #dadada !important;
+	--prev-bg-columnsMenuBar: #191919 !important;
+	--prev-bg-columnsMenuBarColor: #dadada !important;
+	--prev-bg-main-color: #1f1f1f !important;
+	--prev-bg-color: rgba(0, 0, 0, 0.3) !important;
+	--prev-bg-white: #191919 !important;
+	--prev-color-text-black: #ffffff !important;
+	--prev-color-text-primary: #dadada !important;
+	--prev-color-text-regular: #dadada !important;
+	--prev-color-text-secondary: #a3a3a3 !important;
+	--prev-color-hover: rgba(0, 0, 0, 0.3) !important;
+	--prev-color-seting-main: #505050 !important;
+	--prev-color-seting-aside: #3c3c3c !important;
+	--prev-color-seting-header: #303030 !important;
+	--prev-border-color-hover: #616161 !important;
+	--prev-border-color-base: #333333 !important;
+	--prev-border-color-light: #333333 !important;
+	--prev-border-color-lighter: #333333 !important;
+	--prev-border-color-extra-light: #333333 !important;
+
+	// menu
+	.layout-aside {
+		border-right: 1px solid var(--prev-border-color-lighter) !important;
+	}
+
+	// drawer
+	.el-drawer {
+		border-left: 1px solid var(--prev-border-color-lighter) !important;
+	}
+
+	// button
+	.el-button--default {
+		background: var(--prev-bg-white);
+		color: var(--prev-color-text-primary);
+		border-color: var(--prev-border-color-lighter);
+		&:hover,
+		&:focus {
+			color: var(--prev-color-primary) !important;
+			background: var(--prev-color-primary-light-8) !important;
+			border-color: var(--prev-color-primary-light-6) !important;
+		}
+		&:focus {
+			border-color: var(--prev-color-primary-light-1) !important;
+		}
+		&:active {
+			border-color: var(--prev-color-primary-light-6) !important;
+		}
+	}
+
+	// tag
+	.el-tag.el-tag--info {
+		background-color: var(--prev-bg-white) !important;
+		border-color: var(--prev-border-color-light) !important;
+		color: var(--prev-color-text-regular) !important;
+	}
+
+	// switch
+	.el-switch:not(.is-checked) {
+		.el-switch__core {
+			border-color: var(--prev-border-color-base) !important;
+			background-color: var(--prev-border-color-base) !important;
+		}
+	}
+
+	// TimePicker
+	.el-time-spinner__item.active:not(.disabled) {
+		color: var(--prev-color-primary) !important;
+	}
+
+	// date
+	.el-date-table td.in-range div,
+	.el-date-table td.in-range div:hover,
+	.el-date-table.is-week-mode .el-date-table__row.current div,
+	.el-date-table.is-week-mode .el-date-table__row:hover div,
+	.el-date-table td.selected div,
+	.el-month-table td.in-range div,
+	.el-month-table td.in-range div:hover {
+		background-color: var(--prev-bg-color) !important;
+	}
+
+	// transfer
+	.el-transfer-panel,
+	.el-transfer-panel .el-transfer-panel__header {
+		background-color: var(--prev-bg-color) !important;
+	}
+
+	// loading
+	.el-loading-mask {
+		background-color: var(--prev-bg-color) !important;
+	}
+
+	// dropdown
+	.el-dropdown-menu__item:focus,
+	.el-dropdown-menu__item:not(.is-disabled):hover {
+		background-color: var(--prev-color-hover) !important;
+	}
+
+	// dialog
+	.el-dialog,
+	.el-calendar {
+		border: 1px solid var(--prev-border-color-lighter);
+	}
+}

+ 237 - 0
template/admin/src/theme/element.scss

@@ -0,0 +1,237 @@
+/* 防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)
+------------------------------- */
+.el-scrollbar {
+  overflow: hidden;
+  position: relative;
+  height: 100%;
+}
+.el-scrollbar__wrap {
+  overflow: auto !important;
+  overflow-x: hidden !important;
+  max-height: 100%; /*防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)*/
+}
+.el-select-dropdown .el-scrollbar__wrap {
+  overflow-x: scroll !important;
+}
+.el-select-dropdown__wrap {
+  max-height: 274px !important; /*修复Select 选择器高度问题*/
+}
+.el-autocomplete-suggestion__wrap {
+  max-height: 280px !important;
+}
+
+/* Button 按钮
+------------------------------- */
+// 第三方字体图标大小
+.el-button i.iconfont,
+.el-button i.fa {
+  font-size: 14px !important;
+  margin-right: 5px;
+}
+.el-button--medium i.iconfont,
+.el-button--medium i.fa {
+  font-size: 14px !important;
+  margin-right: 5px;
+}
+.el-button--small i.iconfont,
+.el-button--small i.fa {
+  font-size: 12px !important;
+  margin-right: 5px;
+}
+.el-button--mini i.iconfont,
+.el-button--mini i.fa {
+  font-size: 12px !important;
+  margin-right: 5px;
+}
+
+/* Dialog 对话框
+------------------------------- */
+.el-overlay,
+.el-dialog__wrapper {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  .el-dialog {
+    margin: 0 auto !important;
+    .el-dialog__body {
+      padding: 20px !important;
+    }
+  }
+}
+.el-dialog__body {
+  max-height: calc(90vh - 111px) !important;
+  overflow-y: auto;
+  overflow-x: hidden;
+}
+
+/* Alert 警告
+------------------------------- */
+.el-alert--warning.is-light {
+  border: 1px solid rgba(230, 162, 60, 0.3) !important;
+}
+.el-alert--success.is-light {
+  border: 1px solid rgba(103, 194, 58, 0.3) !important;
+}
+.el-alert--info.is-light {
+  border: 1px solid rgba(144, 147, 153, 0.3) !important;
+}
+.el-alert--error.is-light {
+  border: 1px solid rgba(245, 108, 108, 0.3) !important;
+}
+
+/* Table 表格
+------------------------------- */
+.el-table-column--selection {
+  .el-checkbox {
+    margin-right: unset !important;
+  }
+}
+.el-table::before,
+.el-table--group::after,
+.el-table--border::after {
+  z-index: 99 !important;
+}
+
+/* 下拉选择器/时间选择器滚动条
+------------------------------- */
+.el-select-dropdown .el-scrollbar__wrap,
+.el-picker-panel .el-scrollbar__wrap {
+  overflow-x: scroll !important;
+}
+
+/* NavMenu 导航菜单
+------------------------------- */
+// 默认样式修改
+.el-menu {
+  border-right: none !important;
+}
+.el-menu-item,
+.el-submenu__title {
+  height: 50px !important;
+  line-height: 50px !important;
+  color: var(--prev-bg-menuBarColor) !important;
+  transition: none !important;
+}
+// horizontal 水平方向时
+.el-menu--horizontal > .el-menu-item.is-active,
+.el-menu--horizontal > .el-submenu.is-active .el-submenu__title {
+  border-bottom: 3px solid !important;
+  border-bottom-color: var(--prev-color-primary) !important;
+  color: var(--prev-color-primary) !important;
+}
+.el-menu--horizontal .el-menu-item:not(.is-disabled):focus,
+.el-menu--horizontal .el-menu-item:not(.is-disabled):hover,
+.el-menu--horizontal > .el-submenu:focus .el-submenu__title,
+.el-menu--horizontal > .el-submenu:hover .el-submenu__title,
+.el-menu--horizontal .el-menu .el-menu-item.is-active,
+.el-menu--horizontal .el-menu .el-submenu.is-active > .el-submenu__title {
+  color: var(--prev-color-primary) !important;
+}
+.el-menu.el-menu--horizontal {
+  border-bottom: none !important;
+}
+.el-menu--horizontal > .el-menu-item,
+.el-menu--horizontal > .el-submenu .el-submenu__title {
+  padding: 0 14px;
+  color: var(--prev-bg-topBarColor) !important;
+}
+// 外部链接时
+.el-menu-item a,
+.el-menu-item a:hover,
+.el-menu-item i,
+.el-submenu__title i {
+  color: var(--prev-bg-menuBarColor) !important;
+  text-decoration: none;
+  margin-right: 8px;
+}
+.el-menu-item a {
+  width: 86%;
+  display: inline-block;
+}
+// 默认 hover 时
+.el-menu-item:hover,
+.el-submenu__title:hover {
+  color: var(--prev-color-primary) !important;
+  background-color: var(--prev-bg-menu-hover-ba-color) !important;
+  i {
+    color: var(--prev-color-primary) !important;
+  }
+}
+// 鼠标 hover 时颜色
+.el-menu-hover-bg-color {
+  background-color: var(--prev-bg-menu-hover-ba-color) !important;
+}
+// 高亮时
+.el-menu-item.is-active {
+  color: var(--prev-color-primary) !important;
+  //   background-color: var(--prev-bg-menu-hover-ba-color) !important;
+}
+.el-menu-item.is-active,
+.el-sub-menu.is-active .el-sub-menu__title,
+.el-sub-menu:not(.is-opened):hover .el-sub-menu__title {
+  @extend .el-menu-hover-bg-color;
+}
+.el-menu-item:hover {
+  @extend .el-menu-hover-bg-color;
+}
+
+.el-active-extend {
+  color: #ffffff !important;
+  //   background-color: var(--prev-color-primary) !important;
+  //   background-color: var(--prev-bg-menu-hover-ba-color) !important;
+
+  i {
+    color: #ffffff !important;
+  }
+}
+#add-is-active {
+  //   @extend .el-active-extend;
+  //   &:hover {
+  //     @extend .el-active-extend;
+  //   }
+}
+// 菜单收起时且是a链接
+.is-dark a {
+  color: #ffffff !important;
+  text-decoration: none;
+}
+// 菜单收起时鼠标经过背景颜色/字体颜色
+.el-menu--vertical {
+  background: var(--prev-bg-menuBar) !important;
+}
+.el-menu--horizontal {
+  .el-menu {
+    background: var(--prev-bg-topBar) !important;
+  }
+  .el-menu-item,
+  .el-submenu__title {
+    color: var(--prev-bg-topBarColor) !important;
+  }
+}
+// 第三方图标字体间距/大小设置
+.el-menu-item .iconfont,
+.el-submenu .iconfont,
+.el-menu-item .fa,
+.el-submenu__title .fa {
+  font-size: 14px !important;
+  display: inline-block;
+  vertical-align: middle;
+  margin-right: 5px;
+  width: 24px;
+  text-align: center;
+}
+// element plus 本身字体图标
+.el-submenu [class^='el-icon-'],
+.el-menu-item [class^='el-icon-'] {
+  font-size: 14px !important;
+}
+// 去掉离开浏览器时,菜单的默认高亮
+.el-menu-item:focus {
+  //   background-color: transparent !important;
+}
+
+/* Alert 警告
+------------------------------- */
+.el-alert__title {
+  word-break: break-all;
+}

+ 7 - 0
template/admin/src/theme/index.scss

@@ -0,0 +1,7 @@
+@import './base.scss';
+@import './app.scss';
+@import './other.scss';
+@import './element.scss';
+@import './media/media.scss';
+@import './variables.scss';
+@import './dark.scss';

+ 56 - 0
template/admin/src/theme/loading.scss

@@ -0,0 +1,56 @@
+.loading-prev {
+	width: 100%;
+	height: 100%;
+	position: fixed;
+	top: 0;
+	left: 0;
+	z-index: 2;
+	background-color: var(--prev-bg-white);
+}
+.loading-prev .loading-prev-box {
+	position: absolute;
+	top: 50%;
+	left: 50%;
+	transform: translate(-50%, -50%);
+}
+.loading-prev .loading-prev-box-warp {
+	width: 80px;
+	height: 80px;
+}
+.loading-prev .loading-prev-box-warp .loading-prev-box-item {
+	width: 33.333333%;
+	height: 33.333333%;
+	background: var(--prev-color-primary);
+	float: left;
+	animation: loading-prev-animation 1.2s infinite ease;
+	border-radius: 1px;
+}
+.loading-prev .loading-prev-box-warp .loading-prev-box-item:nth-child(7) {
+	animation-delay: 0s;
+}
+.loading-prev .loading-prev-box-warp .loading-prev-box-item:nth-child(4),
+.loading-prev .loading-prev-box-warp .loading-prev-box-item:nth-child(8) {
+	animation-delay: 0.1s;
+}
+.loading-prev .loading-prev-box-warp .loading-prev-box-item:nth-child(1),
+.loading-prev .loading-prev-box-warp .loading-prev-box-item:nth-child(5),
+.loading-prev .loading-prev-box-warp .loading-prev-box-item:nth-child(9) {
+	animation-delay: 0.2s;
+}
+.loading-prev .loading-prev-box-warp .loading-prev-box-item:nth-child(2),
+.loading-prev .loading-prev-box-warp .loading-prev-box-item:nth-child(6) {
+	animation-delay: 0.3s;
+}
+.loading-prev .loading-prev-box-warp .loading-prev-box-item:nth-child(3) {
+	animation-delay: 0.4s;
+}
+@keyframes loading-prev-animation {
+	0%,
+	70%,
+	100% {
+		transform: scale3D(1, 1, 1);
+	}
+	35% {
+		transform: scale3D(0, 0, 1);
+	}
+}

+ 12 - 0
template/admin/src/theme/media/dialog.scss

@@ -0,0 +1,12 @@
+@import './index.scss';
+
+/* 页面宽度小于800px
+------------------------------- */
+@media screen and (max-width: 800px) {
+	.el-dialog {
+		width: 90%;
+	}
+	.el-dialog.is-fullscreen {
+		width: 100% !important;
+	}
+}

+ 35 - 0
template/admin/src/theme/media/error.scss

@@ -0,0 +1,35 @@
+@import './index.scss';
+
+/* 页面宽度小于768px
+------------------------------- */
+@media screen and (max-width: $sm) {
+	.error {
+		.error-flex {
+			flex-direction: column-reverse !important;
+			height: auto !important;
+			width: 100% !important;
+		}
+		.right,
+		.left {
+			flex: unset !important;
+			display: flex !important;
+		}
+		.left-item {
+			margin: auto !important;
+		}
+		.right img {
+			max-width: 450px !important;
+			@extend .left-item;
+		}
+	}
+}
+
+/* 页面宽度大于768px小于992px
+------------------------------- */
+@media screen and (min-width: $sm) and (max-width: $md) {
+	.error {
+		.error-flex {
+			padding-left: 30px !important;
+		}
+	}
+}

+ 19 - 0
template/admin/src/theme/media/form.scss

@@ -0,0 +1,19 @@
+@import './index.scss';
+
+/* 页面宽度小于576px
+------------------------------- */
+@media screen and (max-width: $xs) {
+	.el-form-item__label {
+		width: 100% !important;
+		text-align: left !important;
+	}
+	.el-form-item__content {
+		margin-left: 0 !important;
+	}
+	.el-form-item {
+		display: unset !important;
+	}
+	.login-form .el-form-item {
+		display: block !important;
+	}
+}

+ 35 - 0
template/admin/src/theme/media/home.scss

@@ -0,0 +1,35 @@
+@import './index.scss';
+
+/* 页面宽度小于1200px
+------------------------------- */
+@media screen and (max-width: $lg) {
+	.home-recommend-row {
+		.home-recommend {
+			margin-bottom: 15px;
+		}
+		& .el-col:last-of-type,
+		& .el-col:nth-last-child(2) {
+			.home-recommend {
+				margin-bottom: 0;
+			}
+		}
+	}
+	.home-lg {
+		margin-bottom: 15px;
+	}
+}
+
+/* 页面宽度小于992px
+------------------------------- */
+@media screen and (max-width: $md) {
+	.home-recommend-row {
+		& .el-col:nth-last-child(2) {
+			margin-bottom: 15px;
+		}
+		& .el-col:last-of-type {
+			.home-recommend {
+				margin-bottom: 0;
+			}
+		}
+	}
+}

+ 37 - 0
template/admin/src/theme/media/index.scss

@@ -0,0 +1,37 @@
+/* 栅格布局(媒体查询变量)
+* $xs <768px  响应式栅格
+* $sm ≥768px  响应式栅格
+* $md ≥992px  响应式栅格
+* $lg ≥1200px 响应式栅格
+* $xl ≥1920px 响应式栅格
+------------------------------- */
+$xs: 576px;
+$sm: 768px;
+$md: 992px;
+$lg: 1200px;
+$xl: 1920px;
+
+/* 页面宽度小于576px
+------------------------------- */
+@media screen and (max-width: $xs) {
+}
+
+/* 页面宽度小于768px
+------------------------------- */
+@media screen and (max-width: $sm) {
+}
+
+/* 页面宽度大于768px小于992px
+------------------------------- */
+@media screen and (min-width: $sm) and (max-width: $md) {
+}
+
+/* 页面宽度大于992px小于1200px
+------------------------------- */
+@media screen and (min-width: $md) and (max-width: $lg) {
+}
+
+/* 页面宽度大于1920px
+------------------------------- */
+@media screen and (min-width: $xl) {
+}

+ 55 - 0
template/admin/src/theme/media/layout.scss

@@ -0,0 +1,55 @@
+@import './index.scss';
+
+/* 页面宽度小于576px
+------------------------------- */
+@media screen and (max-width: $xs) {
+	// MessageBox 弹框
+	.el-message-box {
+		width: 80% !important;
+	}
+}
+
+/* 页面宽度小于768px
+------------------------------- */
+@media screen and (max-width: $sm) {
+	// Breadcrumb 面包屑
+	.layout-navbars-breadcrumb-hide {
+		display: none;
+	}
+	// 外链视图
+	.layout-view-link {
+		a {
+			max-width: 80%;
+			text-align: center;
+		}
+	}
+	// 菜单搜索
+	.layout-search-dialog {
+		.el-autocomplete {
+			width: 80% !important;
+		}
+	}
+}
+
+/* 页面宽度小于1000px
+------------------------------- */
+@media screen and (max-width: 1000px) {
+	// 布局配置
+	.layout-drawer-content-flex {
+		position: relative;
+		&::after {
+			content: '手机版不支持切换布局';
+			position: absolute;
+			top: 0;
+			right: 0;
+			bottom: 0;
+			left: 0;
+			z-index: 1;
+			text-align: center;
+			height: 140px;
+			line-height: 140px;
+			background: rgba(255, 255, 255, 0.9);
+			color: #666666;
+		}
+	}
+}

+ 32 - 0
template/admin/src/theme/media/login.scss

@@ -0,0 +1,32 @@
+@import './index.scss';
+
+/* 页面宽度小于576px
+------------------------------- */
+@media screen and (max-width: $xs) {
+	.login-weaper {
+		height: 420px !important;
+		.login-left {
+			display: none !important;
+		}
+		.login-right {
+			width: 100% !important;
+			border-top-left-radius: 4px;
+			border-bottom-left-radius: 4px;
+			.login-main {
+				width: 94% !important;
+			}
+		}
+	}
+}
+
+/* 页面宽度大于576px小于992px
+------------------------------- */
+@media screen and (min-width: $xs) and (max-width: $md) {
+	.login-left {
+		display: none !important;
+	}
+	.login-right {
+		border-top-left-radius: 4px;
+		border-bottom-left-radius: 4px;
+	}
+}

+ 7 - 0
template/admin/src/theme/media/media.scss

@@ -0,0 +1,7 @@
+@import './login.scss';
+@import './error.scss';
+@import './home.scss';
+@import './layout.scss';
+@import './scrollbar.scss';
+@import './dialog.scss';
+@import './form.scss';

+ 13 - 0
template/admin/src/theme/media/scrollbar.scss

@@ -0,0 +1,13 @@
+@import './index.scss';
+
+/* 页面宽度小于768px
+------------------------------- */
+@media screen and (max-width: $sm) {
+	// element plus scrollbar
+	.el-scrollbar__bar.is-vertical {
+		width: 2px !important;
+	}
+	.el-scrollbar__bar.is-horizontal {
+		height: 2px !important;
+	}
+}

+ 0 - 0
template/admin/src/theme/other.scss


+ 961 - 0
template/admin/src/theme/variables.scss

@@ -0,0 +1,961 @@
+/* Button 按钮
+------------------------------- */
+// text
+.el-button--text {
+  color: var(--prev-color-primary);
+  &:focus,
+  &:hover {
+    color: var(--prev-color-primary-light-3);
+  }
+}
+.el-button--text:active {
+  color: var(--prev-color-primary-light-3);
+}
+// default
+.el-button--default:hover,
+.el-button--default:focus {
+  color: var(--prev-color-primary);
+  background: var(--prev-color-primary-light-8);
+  border-color: var(--prev-color-primary-light-6);
+}
+.el-button--default.is-plain:hover,
+.el-button--default.is-plain:focus {
+  color: var(--prev-color-primary);
+  background: var(--prev-bg-white);
+  border-color: var(--prev-color-primary-light-1);
+}
+.el-button--default:active {
+  color: var(--prev-color-primary);
+  background: var(--prev-bg-white);
+  border-color: var(--prev-color-primary-light-1);
+}
+// primary
+.el-button--primary {
+  color: var(--prev-color-text-white) !important;
+  background: var(--prev-color-primary) !important;
+  border-color: var(--prev-color-primary) !important;
+  &:hover,
+  &:focus {
+    color: var(--prev-color-text-white) !important;
+    background: var(--prev-color-primary-light-3) !important;
+    border-color: var(--prev-color-primary-light-3) !important;
+  }
+}
+.el-button--primary.is-plain {
+  color: var(--prev-color-primary) !important;
+  background: var(--prev-color-primary-light-8) !important;
+  border-color: var(--prev-color-primary-light-6) !important;
+  &:hover,
+  &:focus {
+    color: var(--prev-color-text-white) !important;
+    background: var(--prev-color-primary) !important;
+    border-color: var(--prev-color-primary) !important;
+  }
+}
+.el-button--primary.is-disabled,
+.el-button--primary.is-disabled:active,
+.el-button--primary.is-disabled:focus,
+.el-button--primary.is-disabled:hover {
+  color: var(--prev-color-primary) !important;
+  background: var(--prev-color-primary-light-7) !important;
+  border-color: var(--prev-color-primary-light-7) !important;
+}
+.el-button--primary.is-active,
+.el-button--primary:active {
+  color: var(--prev-color-text-white) !important;
+  background: var(--prev-color-primary) !important;
+  border-color: var(--prev-color-primary) !important;
+}
+.el-button.is-disabled.is-plain,
+.el-button.is-disabled.is-plain:focus,
+.el-button.is-disabled.is-plain:hover {
+  background-color: var(--prev-bg-white);
+  border-color: var(--prev-border-color-lighter);
+  color: var(--prev-color-text-placeholder);
+}
+
+/* Link 文字链接
+------------------------------- */
+// default
+.el-link.el-link--default:hover {
+  color: var(--prev-color-primary-light-3);
+}
+// primary
+.el-link.el-link--primary {
+  color: var(--prev-color-primary);
+  &:hover {
+    color: var(--prev-color-primary-light-3);
+  }
+}
+.el-link.el-link--default::after,
+.el-link.is-underline:hover::after,
+.el-link.el-link--primary.is-underline:hover::after,
+.el-link.el-link--primary::after {
+  border-color: var(--prev-color-primary);
+}
+
+/* Radio 单选框
+------------------------------- */
+.el-radio,
+.el-checkbox {
+  color: var(--prev-color-text-regular);
+}
+.el-radio__input.is-checked + .el-radio__label,
+.el-radio-button__inner:hover {
+  color: var(--prev-color-primary);
+}
+.el-radio__input.is-checked .el-radio__inner {
+  background-color: var(--prev-color-primary);
+  border-color: var(--prev-color-primary);
+}
+.el-radio-button__orig-radio:checked + .el-radio-button__inner {
+  color: var(--prev-color-text-white);
+  background-color: var(--prev-color-primary);
+  border-color: var(--prev-color-primary);
+  box-shadow: -1px 0 0 0 var(--prev-color-primary);
+}
+.el-radio.is-bordered.is-checked,
+.el-radio__inner:hover {
+  border-color: var(--prev-color-primary);
+}
+.el-radio-button__inner,
+.el-checkbox-button__inner {
+  background-color: var(--prev-bg-white);
+  color: var(--prev-color-text-regular);
+  border-color: var(--prev-border-color-base);
+}
+.el-radio-button:first-child .el-radio-button__inner,
+.el-checkbox-button:first-child .el-checkbox-button__inner {
+  border-left-color: var(--prev-border-color-base);
+}
+.el-radio.is-bordered,
+.el-checkbox.is-bordered {
+  border-color: var(--prev-border-color-base);
+}
+
+/* Checkbox 多选框
+------------------------------- */
+.el-checkbox__input.is-checked + .el-checkbox__label,
+.el-checkbox-button__inner:hover {
+  color: var(--prev-color-primary);
+}
+.el-checkbox__input.is-checked .el-checkbox__inner {
+  background-color: var(--prev-color-primary);
+  border-color: var(--prev-color-primary);
+}
+.el-checkbox__input.is-focus .el-checkbox__inner,
+.el-checkbox__inner:hover,
+.el-checkbox.is-bordered.is-checked,
+.el-checkbox-button.is-focus .el-checkbox-button__inner {
+  border-color: var(--prev-color-primary);
+}
+.el-checkbox-button.is-checked .el-checkbox-button__inner {
+  color: var(--prev-color-text-white);
+  background-color: var(--prev-color-primary);
+  border-color: var(--prev-color-primary);
+  box-shadow: -1px 0 0 0 var(--prev-color-primary);
+}
+.el-checkbox-button.is-checked:first-child .el-checkbox-button__inner {
+  border-left-color: var(--prev-color-primary);
+}
+.el-checkbox__input.is-checked .el-checkbox__inner,
+.el-checkbox__input.is-indeterminate .el-checkbox__inner {
+  background-color: var(--prev-color-primary);
+  border-color: var(--prev-color-primary);
+}
+.el-checkbox-button.is-disabled .el-checkbox-button__inner {
+  background-color: var(--prev-bg-white);
+  border-color: var(--prev-border-color-base);
+}
+
+/* Input 输入框、InputNumber 计数器
+------------------------------- */
+// input-number
+.el-input__inner:focus,
+.el-input-number__decrease:hover:not(.is-disabled) ~ .el-input .el-input__inner:not(.is-disabled),
+.el-input-number__increase:hover:not(.is-disabled) ~ .el-input .el-input__inner:not(.is-disabled),
+.el-textarea__inner:focus {
+  border-color: var(--prev-color-primary) !important;
+}
+.el-input-number__increase:hover,
+.el-input-number__decrease:hover {
+  color: var(--prev-color-primary);
+}
+.el-input-number__decrease,
+.el-input-number__increase {
+  background-color: var(--prev-bg-color);
+  border-color: var(--prev-border-color-base) !important;
+}
+// input
+.el-input__inner,
+.el-textarea__inner {
+  background-color: var(--prev-bg-white);
+  border-color: var(--prev-border-color-base);
+  color: var(--prev-color-text-regular);
+  &:hover {
+    border-color: var(--prev-border-color-hover);
+  }
+}
+.el-input.is-disabled .el-input__inner,
+.el-textarea.is-disabled .el-textarea__inner {
+  background-color: var(--prev-bg-white);
+  border-color: var(--prev-border-color-base);
+}
+.el-input-group__append,
+.el-input-group__prepend {
+  background-color: var(--prev-bg-color);
+  color: var(--prev-color-text-regular);
+  border-color: var(--prev-border-color-base);
+}
+.el-input .el-input__count .el-input__count-inner {
+  background-color: var(--prev-bg-color);
+}
+// autocomplete
+.el-autocomplete-suggestion {
+  background-color: var(--prev-bg-white);
+  border-color: var(--prev-border-color-base);
+}
+.el-autocomplete-suggestion__wrap {
+  max-height: 280px !important;
+}
+// scss 循环
+$positions: 'top', 'right', 'bottom', 'left';
+@each $i in $positions {
+  .el-popper[x-placement^='#{$i}'] .popper__arrow {
+    border-#{$i}-color: var(--prev-border-color-base);
+    &::after {
+      border-#{$i}-color: var(--prev-bg-white);
+    }
+  }
+}
+.el-autocomplete-suggestion li {
+  color: var(--prev-color-text-regular);
+}
+.el-autocomplete-suggestion li.highlighted,
+.el-autocomplete-suggestion li:hover {
+  background-color: var(--prev-color-hover);
+}
+
+/* Select 选择器
+------------------------------- */
+.el-range-editor.is-active,
+.el-range-editor.is-active:hover,
+.el-select .el-input.is-focus .el-input__inner,
+.el-select .el-input__inner:focus {
+  border-color: var(--prev-color-primary);
+}
+.el-select-dropdown__item.selected {
+  color: var(--prev-color-primary);
+}
+.el-select-dropdown {
+  background-color: var(--prev-bg-white);
+  border-color: var(--prev-border-color-light);
+}
+.el-select-dropdown__item {
+  color: var(--prev-color-text-regular);
+}
+.el-select-dropdown__item.hover,
+.el-select-dropdown__item:hover {
+  background-color: var(--prev-color-hover);
+}
+.el-select-dropdown__item.is-disabled:hover {
+  background-color: var(--prev-bg-white);
+}
+.el-select .el-input.is-disabled .el-input__inner:hover {
+  border-color: var(--prev-border-color-light);
+}
+.el-select:hover .el-input__inner {
+  border-color: var(--prev-border-color-hover);
+}
+.el-select-dropdown.is-multiple .el-select-dropdown__item.selected {
+  background-color: var(--prev-bg-white);
+}
+.el-select-dropdown.is-multiple .el-select-dropdown__item.selected.hover {
+  background-color: var(--prev-color-hover);
+}
+.el-select-group__wrap:not(:last-of-type)::after {
+  background: var(--prev-border-color-light);
+}
+
+/* Cascader 级联选择器
+------------------------------- */
+.el-cascader .el-input .el-input__inner:focus,
+.el-cascader .el-input.is-focus .el-input__inner {
+  border-color: var(--prev-color-primary);
+}
+.el-cascader-node.in-active-path,
+.el-cascader-node.is-active,
+.el-cascader-node.is-selectable.in-checked-path {
+  color: var(--prev-color-primary);
+}
+.el-cascader__dropdown {
+  background-color: var(--prev-bg-white);
+  border-color: var(--prev-border-color-light);
+}
+.el-cascader-menu {
+  border-color: var(--prev-border-color-light);
+  color: var(--prev-color-text-regular);
+}
+.el-cascader-node:not(.is-disabled):focus,
+.el-cascader-node:not(.is-disabled):hover {
+  background-color: var(--prev-color-hover);
+}
+
+/* Switch 开关
+------------------------------- */
+.el-switch.is-checked .el-switch__core {
+  border-color: var(--prev-color-primary);
+  background-color: var(--prev-color-primary);
+}
+.el-switch__label.is-active {
+  color: var(--prev-color-primary);
+}
+
+/* Slider 滑块
+------------------------------- */
+.el-slider__bar {
+  background-color: var(--prev-color-primary);
+}
+.el-slider__button {
+  border-color: var(--prev-color-primary);
+}
+.el-slider__runway {
+  background-color: var(--prev-border-color-light);
+}
+.el-slider__marks-text {
+  color: var(--prev-color-text-secondary);
+}
+
+/* TimePicker 时间选择器
+------------------------------- */
+.el-time-panel__btn.confirm,
+.el-time-spinner__arrow:hover,
+.time-select-item.selected:not(.disabled) {
+  color: var(--prev-color-primary);
+}
+.el-picker-panel {
+  border-color: var(--prev-border-color-light);
+  background-color: var(--prev-bg-white);
+  color: var(--prev-color-text-regular);
+}
+.time-select-item:hover,
+.el-time-spinner__item:hover:not(.disabled):not(.active),
+.el-time-spinner__wrapper.is-arrow .el-time-spinner__item:hover:not(.disabled):not(.active) {
+  background-color: var(--prev-color-hover);
+}
+.el-time-panel {
+  border-color: var(--prev-border-color-light);
+  background-color: var(--prev-bg-white);
+}
+.el-time-panel__footer,
+.el-time-panel__content::after,
+.el-time-panel__content::before,
+.el-time-range-picker__body {
+  border-color: var(--prev-border-color-light);
+}
+.el-time-panel__btn,
+.el-date-editor .el-range-separator {
+  color: var(--prev-color-text-primary);
+}
+.el-date-editor .el-range-input {
+  background-color: var(--prev-bg-white);
+  color: var(--prev-color-text-primary);
+}
+
+/* DatePicker 日期选择器
+------------------------------- */
+.el-date-table td.today span,
+.el-date-table td.available:hover,
+.el-date-picker__header-label.active,
+.el-date-picker__header-label:hover,
+.el-picker-panel__icon-btn:hover,
+.el-year-table td.today .cell,
+.el-year-table td .cell:hover,
+.el-year-table td.current:not(.disabled) .cell,
+.el-month-table td .cell:hover,
+.el-month-table td.today .cell,
+.el-month-table td.current:not(.disabled) .cell,
+.el-picker-panel__shortcut:hover {
+  color: var(--prev-color-primary);
+}
+.el-date-table td.current:not(.disabled) span,
+.el-date-table td.selected span {
+  color: var(--prev-color-text-white);
+  background-color: var(--prev-color-primary);
+}
+.el-date-table td.end-date span,
+.el-date-table td.start-date span,
+.el-month-table td.end-date .cell,
+.el-month-table td.start-date .cell {
+  background-color: var(--prev-color-primary);
+}
+.el-date-table td.in-range div,
+.el-date-table td.in-range div:hover,
+.el-date-table.is-week-mode .el-date-table__row.current div,
+.el-date-table.is-week-mode .el-date-table__row:hover div,
+.el-date-table td.selected div {
+  background-color: var(--prev-color-primary-light-9);
+}
+.el-date-table th,
+.el-date-picker__header--bordered,
+.el-date-range-picker__content.is-left,
+.el-date-picker__time-header,
+.el-date-range-picker__time-header {
+  border-color: var(--prev-border-color-lighter);
+}
+.el-date-table th,
+.el-date-picker__header-label,
+.el-picker-panel__shortcut,
+.el-month-table td .cell,
+.el-year-table td .cell {
+  color: var(--prev-color-text-regular);
+}
+.el-date-table td.next-month,
+.el-date-table td.prev-month {
+  color: var(--prev-border-color-hover);
+}
+.el-picker-panel__icon-btn {
+  color: var(--prev-color-text-primary);
+}
+.el-date-table td.disabled div {
+  background-color: var(--prev-bg-color);
+}
+.el-picker-panel [slot='sidebar'],
+.el-picker-panel__sidebar,
+.el-picker-panel__footer {
+  border-color: var(--prev-border-color-light);
+  background-color: var(--prev-bg-white);
+}
+.el-month-table td.end-date .cell,
+.el-month-table td.start-date .cell {
+  color: var(--prev-color-text-white);
+}
+
+/* Upload 上传
+------------------------------- */
+.el-upload-list__item.is-success .el-upload-list__item-name:focus,
+.el-upload-list__item.is-success .el-upload-list__item-name:hover,
+.el-upload-list__item .el-icon-close-tip,
+.el-upload-dragger .el-upload__text em {
+  color: var(--prev-color-primary);
+}
+.el-upload--picture-card,
+.el-upload-dragger {
+  background-color: var(--prev-bg-white);
+  border-color: var(--prev-border-color-light);
+  i {
+    color: var(--prev-color-text-regular);
+  }
+}
+.el-upload--picture-card:hover,
+.el-upload:focus {
+  color: var(--prev-color-primary);
+  border-color: var(--prev-color-primary);
+}
+.el-upload-dragger:hover,
+.el-upload:focus .el-upload-dragger {
+  border-color: var(--prev-color-primary);
+}
+.el-upload__tip,
+.el-upload-list__item,
+.el-upload-dragger .el-upload__text,
+.el-upload-list__item-name,
+.el-upload-list__item .el-icon-close {
+  color: var(--prev-color-text-regular);
+}
+.el-upload-list__item:hover {
+  background-color: var(--prev-bg-color);
+}
+
+/* ColorPicker 颜色选择器
+------------------------------- */
+.el-color-picker__trigger {
+  border-color: var(--prev-border-color-light);
+}
+
+/* Transfer 穿梭框
+------------------------------- */
+.el-transfer-panel__item:hover {
+  color: var(--prev-color-primary);
+}
+.el-transfer-panel,
+.el-transfer-panel .el-transfer-panel__header {
+  border-color: var(--prev-border-color-lighter);
+}
+.el-transfer-panel .el-transfer-panel__footer {
+  border-color: var(--prev-border-color-lighter);
+  background-color: var(--prev-bg-white);
+}
+.el-transfer-panel .el-transfer-panel__header .el-checkbox .el-checkbox__label {
+  color: var(--prev-color-text-primary);
+}
+
+/* Form 表单
+------------------------------- */
+.el-form {
+  .el-form-item:last-of-type {
+    margin-bottom: 0 !important;
+  }
+}
+.el-form-item__label {
+  color: var(--prev-color-text-regular);
+}
+
+/* Table 表格
+------------------------------- */
+.el-table {
+  color: var(--prev-color-text-regular);
+}
+.el-table .descending .sort-caret.descending {
+  border-top-color: var(--prev-color-primary);
+}
+.el-table .ascending .sort-caret.ascending {
+  border-bottom-color: var(--prev-color-primary);
+}
+.el-table thead {
+  color: var(--prev-color-text-secondary);
+}
+.el-table td.el-table__cell,
+.el-table th.el-table__cell.is-leaf,
+.el-table--border,
+.el-table--group {
+  border-color: var(--prev-border-color-lighter) !important;
+}
+.el-table th.el-table__cell,
+.el-table tr {
+  background-color: var(--prev-bg-white);
+}
+.el-table--striped .el-table__body tr.el-table__row--striped td.el-table__cell,
+.el-table--enable-row-hover .el-table__body tr:hover > td.el-table__cell {
+  background-color: var(--prev-bg-color);
+}
+.el-table--border::after,
+.el-table--group::after,
+.el-table::before {
+  background-color: var(--prev-border-color-lighter);
+}
+
+/* Tag 标签
+------------------------------- */
+// primary
+.el-tag {
+  color: var(--prev-color-primary);
+  background-color: var(--prev-color-primary-light-8);
+  border-color: var(--prev-color-primary-light-6);
+}
+.el-tag .el-tag__close {
+  color: var(--prev-color-primary);
+  &:hover {
+    color: var(--prev-color-text-white);
+    background-color: var(--prev-color-primary);
+  }
+}
+.el-tag--dark {
+  color: var(--prev-color-text-white);
+  background-color: var(--prev-color-primary);
+}
+.el-tag--dark .el-tag__close {
+  color: var(--prev-color-text-white);
+  &:hover {
+    background-color: var(--prev-color-primary-light-3);
+  }
+}
+.el-tag--plain {
+  color: var(--prev-color-primary);
+  background-color: var(--prev-bg-white);
+  border-color: var(--prev-color-primary-light-3);
+}
+
+/* Progress 进度条
+------------------------------- */
+// primary
+.el-progress-bar__inner {
+  background-color: var(--prev-color-primary) !important;
+}
+.el-progress-bar__outer {
+  background-color: var(--prev-border-color-lighter);
+}
+.el-progress__text {
+  color: var(--prev-color-text-regular);
+}
+
+/* Tree 树形控件
+------------------------------- */
+.el-tree {
+  background-color: var(--prev-bg-white);
+  color: var(--prev-color-text-regular);
+}
+.el-tree-node__content:hover,
+.el-tree-node:focus > .el-tree-node__content {
+  background-color: var(--prev-bg-color);
+}
+
+/* Pagination 分页
+------------------------------- */
+.el-pager li.active,
+.el-pager li:hover,
+.el-pagination button:hover,
+.el-pagination.is-background .el-pager li:not(.disabled):hover {
+  color: var(--prev-color-primary);
+}
+.el-pagination__sizes .el-input .el-input__inner:hover {
+  border-color: var(--prev-color-primary);
+}
+.el-pagination.is-background .el-pager li:not(.disabled).active {
+  background-color: var(--prev-color-primary);
+  color: var(--prev-color-text-white);
+}
+.el-pagination__total,
+.el-pagination__jump {
+  color: var(--prev-color-text-regular);
+}
+.el-pagination button:disabled,
+.el-pagination .btn-next,
+.el-pagination .btn-prev {
+  background-color: var(--prev-bg-white);
+}
+.el-pagination .btn-next,
+.el-pagination .btn-prev {
+  color: var(--prev-color-text-primary);
+}
+.el-pager li {
+  background-color: var(--prev-bg-white);
+  color: var(--prev-color-text-primary);
+}
+
+/* Badge 标记
+------------------------------- */
+// primary
+.el-badge__content--primary {
+  background-color: var(--prev-color-primary);
+}
+
+/* Loading 加载
+------------------------------- */
+.el-loading-spinner .path {
+  stroke: var(--prev-color-primary);
+}
+.el-loading-spinner .el-loading-text,
+.el-loading-spinner i {
+  color: var(--prev-color-primary);
+}
+
+/* Message 消息提示
+------------------------------- */
+// default/info
+.el-message {
+  min-width: unset !important;
+  padding: 15px !important;
+}
+
+/* MessageBox 弹框
+------------------------------- */
+.el-message-box__headerbtn:focus .el-message-box__close,
+.el-message-box__headerbtn:hover .el-message-box__close {
+  color: var(--prev-color-primary);
+}
+.el-message-box {
+  background-color: var(--prev-bg-white);
+  border-color: var(--prev-border-color-lighter);
+}
+.el-message-box__title {
+  color: var(--prev-color-text-primary);
+}
+.el-message-box__content {
+  color: var(--prev-color-text-regular);
+}
+
+/* Notification 通知
+------------------------------- */
+.el-notification {
+  background-color: var(--prev-bg-white);
+  border-color: var(--prev-border-color-lighter);
+  .el-notification__title {
+    color: var(--prev-color-text-primary);
+  }
+  .el-notification__content {
+    color: var(--prev-color-text-regular);
+  }
+}
+
+/* Tabs 标签页
+------------------------------- */
+.el-tabs__item.is-active,
+.el-tabs__item:hover,
+.el-tabs--border-card > .el-tabs__header .el-tabs__item.is-active,
+.el-tabs--border-card > .el-tabs__header .el-tabs__item:not(.is-disabled):hover {
+  color: var(--prev-color-primary);
+}
+.el-tabs__active-bar {
+  background-color: var(--prev-color-primary);
+}
+.el-tabs__nav-wrap::after {
+  height: 1px !important;
+}
+.el-tabs__item {
+  color: var(--prev-color-text-primary);
+}
+.el-tabs__nav-wrap::after {
+  background-color: var(--prev-border-color-light);
+}
+.el-tabs--card > .el-tabs__header .el-tabs__item.is-active,
+.el-tabs--card > .el-tabs__header .el-tabs__item,
+.el-tabs--card > .el-tabs__header,
+.el-tabs--card > .el-tabs__header .el-tabs__nav {
+  border-color: var(--prev-bg-color);
+}
+.el-tabs--border-card {
+  background-color: var(--prev-bg-white);
+  border-color: var(--prev-border-color-base);
+}
+.el-tabs--border-card > .el-tabs__header {
+  background-color: var(--prev-bg-color);
+  border-color: var(--prev-border-color-light);
+}
+.el-tabs--border-card > .el-tabs__header .el-tabs__item.is-active {
+  background-color: var(--prev-bg-white);
+  border-color: var(--prev-border-color-base);
+}
+
+/* Breadcrumb 面包屑
+------------------------------- */
+.el-breadcrumb__item {
+  display: flex;
+  align-items: center;
+}
+.el-breadcrumb__inner a {
+  color: var(--prev-bg-topBarColor) !important;
+}
+.el-breadcrumb__inner a:hover,
+.el-breadcrumb__inner.is-link:hover {
+  color: var(--prev-bg-topBarColor);
+}
+.el-breadcrumb__inner a,
+.el-breadcrumb__inner.is-link {
+  color: var(--prev-bg-topBarColor);
+  font-weight: normal;
+}
+.el-breadcrumb__inner a,
+.el-breadcrumb__inner.is-link {
+  display: flex;
+  align-items: center;
+  color: var(--prev-color-text-black);
+  opacity: 0.7;
+}
+.el-breadcrumb__separator {
+  color: var(--prev-border-color-hover);
+}
+
+/* PageHeader 页头
+------------------------------- */
+.el-page-header__left {
+  color: var(--prev-color-text-black);
+  &::after {
+    background-color: var(--prev-border-color-base);
+  }
+}
+.el-page-header__content {
+  color: var(--prev-color-text-primary);
+}
+
+/* Dropdown 下拉菜单
+------------------------------- */
+.el-dropdown-menu__item:focus,
+.el-dropdown-menu__item:not(.is-disabled):hover {
+  color: var(--prev-color-primary);
+  background-color: var(--prev-color-primary-light-9);
+}
+.el-dropdown-menu {
+  background-color: var(--prev-bg-white);
+  border-color: var(--prev-border-color-lighter);
+}
+.el-dropdown-menu__item {
+  color: var(--prev-color-text-regular);
+}
+.el-dropdown-menu__item--divided {
+  border-color: var(--prev-border-color-lighter);
+}
+.el-dropdown-menu__item--divided:before {
+  background-color: var(--prev-bg-white);
+}
+
+/* Steps 步骤条
+------------------------------- */
+// default
+.el-step__title.is-finish,
+.el-step__description.is-finish,
+.el-step__head.is-finish {
+  color: var(--prev-color-primary);
+}
+.el-step__head.is-finish {
+  border-color: var(--prev-color-primary);
+}
+
+/* Dialog 对话框
+------------------------------- */
+.el-dialog__headerbtn:focus .el-dialog__close,
+.el-dialog__headerbtn:hover .el-dialog__close {
+  color: var(--prev-color-primary);
+}
+.el-dialog {
+  background-color: var(--prev-bg-white);
+}
+.el-dialog__title {
+  color: var(--prev-color-text-primary);
+}
+.el-dialog__body {
+  color: var(--prev-color-text-regular);
+}
+
+/* Popover 弹出框
+------------------------------- */
+.el-popover {
+  background-color: var(--prev-bg-white);
+  border-color: var(--prev-border-color-lighter);
+  color: var(--prev-color-text-regular);
+}
+.el-popover__title {
+  color: var(--prev-color-text-primary);
+}
+
+/* Card 卡片
+------------------------------- */
+.el-card {
+  color: var(--prev-color-text-primary);
+  background-color: var(--prev-bg-white);
+  border-color: var(--prev-border-color-lighter);
+}
+.el-card__header {
+  padding: 15px 20px;
+  border-bottom-color: var(--prev-border-color-lighter);
+}
+
+/* Collapse 折叠面板
+------------------------------- */
+.el-collapse {
+  border-color: var(--prev-border-color-lighter);
+}
+.el-collapse-item__header {
+  color: var(--prev-color-text-primary);
+  background-color: var(--prev-bg-white);
+  border-color: var(--prev-border-color-lighter);
+}
+.el-collapse-item__wrap {
+  background-color: var(--prev-bg-white);
+  border-color: var(--prev-border-color-lighter);
+}
+.el-collapse-item__content {
+  color: var(--prev-color-text-primary);
+}
+
+/* Timeline 时间线
+------------------------------- */
+// primary
+.el-timeline-item__node--primary {
+  background-color: var(--prev-color-primary);
+}
+.el-timeline-item__content {
+  color: var(--prev-color-text-primary);
+}
+
+/* Divider 分割线
+------------------------------- */
+.el-divider {
+  background-color: var(--prev-border-color-base);
+}
+
+/* Calendar 日历
+------------------------------- */
+.el-calendar-table td {
+  color: var(--prev-color-text-black);
+}
+.el-calendar-table td.is-today {
+  color: var(--prev-color-primary) !important;
+  background-color: var(--prev-color-primary-light-9);
+}
+.el-calendar-table .el-calendar-day:hover,
+.el-calendar-table td.is-selected {
+  background-color: var(--prev-color-primary-light-9);
+  color: var(--prev-color-primary) !important;
+}
+.el-calendar {
+  background-color: var(--prev-bg-white);
+}
+.el-calendar__title {
+  color: var(--prev-color-text-black);
+}
+.el-calendar__header,
+.el-calendar-table tr:first-child td,
+.el-calendar-table td,
+.el-calendar-table tr td:first-child {
+  border-color: var(--prev-border-color-lighter);
+}
+.el-calendar-table thead th {
+  color: var(--prev-color-text-regular);
+}
+.el-calendar-table:not(.is-range) td.next,
+.el-calendar-table:not(.is-range) td.prev {
+  color: var(--prev-color-text-placeholder);
+}
+.el-calendar__button-group {
+  .el-button {
+    color: var(--prev-color-text-regular);
+    background-color: var(--prev-bg-white);
+    border-color: var(--prev-border-color-base);
+    &:focus,
+    &:hover {
+      color: var(--prev-color-primary) !important;
+      background: var(--prev-color-primary-light-8) !important;
+      border-color: var(--prev-color-primary-light-6) !important;
+    }
+    &:active {
+      color: var(--prev-color-primary-light-3);
+    }
+  }
+}
+
+/* Backtop 回到顶部
+------------------------------- */
+.el-backtop {
+  color: var(--prev-color-primary);
+  &:hover {
+    background-color: var(--prev-color-primary-light-9);
+  }
+}
+
+/* scrollbar
+------------------------------- */
+.el-scrollbar__wrap {
+  overflow-x: hidden !important;
+  max-height: 100%; /*防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)*/
+}
+.el-select-dropdown .el-scrollbar__wrap {
+  overflow-x: scroll !important;
+}
+
+/* Drawer 抽屉
+------------------------------- */
+.el-drawer,
+.el-divider__text {
+  background-color: var(--prev-bg-white);
+}
+.el-divider__text {
+  color: var(--prev-color-text-primary);
+}
+.el-drawer__close-btn:hover {
+  color: var(--prev-color-primary);
+}
+.el-drawer__body {
+  width: 100%;
+  height: 100%;
+  overflow: auto;
+}
+.el-drawer__header {
+  padding: 0 15px !important;
+  height: 50px;
+  display: flex;
+  align-items: center;
+  margin-bottom: 0 !important;
+  border-bottom: 1px solid var(--prev-border-color-lighter);
+  color: var(--prev-color-text-primary);
+}

+ 4 - 0
template/admin/src/utils/componentSize.js

@@ -0,0 +1,4 @@
+import { Local } from '@/utils/storage.js';
+
+// 全局组件大小
+export const globalComponentSize = Local.get('themeConfigPrev') ? Local.get('themeConfigPrev').globalComponentSize : '';

+ 0 - 71
template/admin/src/utils/download.js

@@ -1,71 +0,0 @@
-import axios from 'axios';
-import { Message } from 'element-ui';
-import { saveAs } from 'file-saver';
-import { getToken } from '@/utils/auth';
-import errorCode from '@/utils/errorCode';
-import { blobValidate } from '@/utils/ruoyi';
-
-const baseURL = process.env.VUE_APP_BASE_API;
-
-export default {
-  name(name, isDelete = true) {
-    var url = baseURL + '/common/download?fileName=' + encodeURI(name) + '&delete=' + isDelete;
-    axios({
-      method: 'get',
-      url: url,
-      responseType: 'blob',
-      headers: { Authorization: 'Bearer ' + getToken() },
-    }).then(async (res) => {
-      const isLogin = await blobValidate(res.data);
-      if (isLogin) {
-        const blob = new Blob([res.data]);
-        this.saveAs(blob, decodeURI(res.headers['download-filename']));
-      } else {
-        this.printErrMsg(res.data);
-      }
-    });
-  },
-  resource(resource) {
-    var url = baseURL + '/common/download/resource?resource=' + encodeURI(resource);
-    axios({
-      method: 'get',
-      url: url,
-      responseType: 'blob',
-      headers: { Authorization: 'Bearer ' + getToken() },
-    }).then(async (res) => {
-      const isLogin = await blobValidate(res.data);
-      if (isLogin) {
-        const blob = new Blob([res.data]);
-        this.saveAs(blob, decodeURI(res.headers['download-filename']));
-      } else {
-        this.printErrMsg(res.data);
-      }
-    });
-  },
-  zip(url, name) {
-    var url = baseURL + url;
-    axios({
-      method: 'get',
-      url: url,
-      responseType: 'blob',
-      headers: { Authorization: 'Bearer ' + getToken() },
-    }).then(async (res) => {
-      const isLogin = await blobValidate(res.data);
-      if (isLogin) {
-        const blob = new Blob([res.data], { type: 'application/zip' });
-        this.saveAs(blob, name);
-      } else {
-        this.printErrMsg(res.data);
-      }
-    });
-  },
-  saveAs(text, name, opts) {
-    saveAs(text, name, opts);
-  },
-  async printErrMsg(data) {
-    const resText = await data.text();
-    const rspObj = JSON.parse(resText);
-    const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'];
-    Message.error(errMsg);
-  },
-};

+ 46 - 0
template/admin/src/utils/loading.js

@@ -0,0 +1,46 @@
+import Vue from 'vue';
+import loadingCss from '@/theme/loading.scss';
+
+// 定义方法
+export const PrevLoading = {
+	// 载入 css
+	setCss: () => {
+		let link = document.createElement('link');
+		link.rel = 'stylesheet';
+		link.href = loadingCss;
+		link.crossOrigin = 'anonymous';
+		document.getElementsByTagName('head')[0].appendChild(link);
+	},
+	// 创建 loading
+	start: () => {
+		const bodys = document.body;
+		const div = document.createElement('div');
+		div.setAttribute('class', 'loading-prev');
+		const htmls = `
+			<div class="loading-prev-box">
+			<div class="loading-prev-box-warp">
+				<div class="loading-prev-box-item"></div>
+				<div class="loading-prev-box-item"></div>
+				<div class="loading-prev-box-item"></div>
+				<div class="loading-prev-box-item"></div>
+				<div class="loading-prev-box-item"></div>
+				<div class="loading-prev-box-item"></div>
+				<div class="loading-prev-box-item"></div>
+				<div class="loading-prev-box-item"></div>
+				<div class="loading-prev-box-item"></div>
+			</div>
+		</div>
+		`;
+		div.innerHTML = htmls;
+		bodys.insertBefore(div, bodys.childNodes[0]);
+	},
+	// 移除 loading
+	done: () => {
+		Vue.nextTick(() => {
+			setTimeout(() => {
+				const el = document.querySelector('.loading-prev');
+				el && el.parentNode?.removeChild(el);
+			}, 1000);
+		});
+	},
+};

+ 48 - 0
template/admin/src/utils/storage.js

@@ -0,0 +1,48 @@
+import config from '../../package.json';
+
+// 1、window.localStorage 浏览器永久缓存
+export const Local = {
+	// 查看 v2.4.3版本更新日志
+	setKey(key) {
+		// @ts-ignore
+		return `${config.name}:${key}`;
+	},
+	// 设置永久缓存
+	set(key, val) {
+		window.localStorage.setItem(Local.setKey(key), JSON.stringify(val));
+	},
+	// 获取永久缓存
+	get(key) {
+		let json = window.localStorage.getItem(Local.setKey(key));
+		return JSON.parse(json);
+	},
+	// 移除永久缓存
+	remove(key) {
+		window.localStorage.removeItem(Local.setKey(key));
+	},
+	// 移除全部永久缓存
+	clear() {
+		window.localStorage.clear();
+	},
+};
+
+// 2、window.sessionStorage 浏览器临时缓存
+export const Session = {
+	// 设置临时缓存
+	set(key, val) {
+		window.sessionStorage.setItem(Local.setKey(key), JSON.stringify(val));
+	},
+	// 获取临时缓存
+	get(key) {
+		let json = window.sessionStorage.getItem(Local.setKey(key));
+		return JSON.parse(json);
+	},
+	// 移除临时缓存
+	remove(key) {
+		window.sessionStorage.removeItem(Local.setKey(key));
+	},
+	// 移除全部临时缓存
+	clear() {
+		window.sessionStorage.clear();
+	},
+};

+ 0 - 0
template/admin/src/utils/theme.js


Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini