Przeglądaj źródła

feat: 主题模式 新增 postcss 注入 菜单优化...

lanceJiang 1 rok temu
rodzic
commit
004a701fab
88 zmienionych plików z 3665 dodań i 1399 usunięć
  1. 3 5
      .editorconfig
  2. 2 1
      package.json
  3. 5 0
      postcss.config.cjs
  4. 6 2
      src/App.vue
  5. 1 1
      src/api/system/menu.ts
  6. 4 8
      src/components/Breadcrumb/index.vue
  7. 7 3
      src/components/LangSelect/index.vue
  8. 14 10
      src/components/RightPanel/index.vue
  9. 1 1
      src/components/SizeSelect/index.vue
  10. 5 5
      src/components/ThemePicker/index.vue
  11. 32 0
      src/hooks/interface/index.ts
  12. 122 0
      src/hooks/useTheme.ts
  13. 4 4
      src/lang/index.ts
  14. 127 0
      src/layout/LayoutClassic/index.scss
  15. 51 0
      src/layout/LayoutClassic/index.vue
  16. 133 0
      src/layout/LayoutColumns/index.scss
  17. 110 0
      src/layout/LayoutColumns/index.vue
  18. 92 0
      src/layout/LayoutTransverse/index.scss
  19. 77 0
      src/layout/LayoutTransverse/index.vue
  20. 116 0
      src/layout/LayoutVertical/index.scss
  21. 59 0
      src/layout/LayoutVertical/index.vue
  22. 14 4
      src/layout/components/AppMain.vue
  23. 23 0
      src/layout/components/Footer/index.vue
  24. 25 0
      src/layout/components/Header/ToolBarLeft.vue
  25. 82 0
      src/layout/components/Header/ToolBarRight.vue
  26. 173 0
      src/layout/components/Header/components/Avatar.vue
  27. 140 0
      src/layout/components/Header/components/Breadcrumb.vue
  28. 34 0
      src/layout/components/Header/components/CollapseIcon.vue
  29. 49 0
      src/layout/components/Header/components/Language.vue
  30. 58 0
      src/layout/components/Menu/MenuIcon.vue
  31. 104 0
      src/layout/components/Menu/SubMenu.vue
  32. 322 45
      src/layout/components/Settings/index.vue
  33. 0 45
      src/layout/components/Sidebar/Link.vue
  34. 0 93
      src/layout/components/Sidebar/Logo.vue
  35. 0 101
      src/layout/components/Sidebar/SidebarItem.vue
  36. 0 36
      src/layout/components/Sidebar/index.vue
  37. 28 9
      src/layout/components/TagsView/index.vue
  38. 0 1
      src/layout/components/index.ts
  39. 32 95
      src/layout/index.vue
  40. 1 0
      src/layout/layout_common.scss
  41. 4 2
      src/main.ts
  42. 16 3
      src/permission.ts
  43. 133 13
      src/router/index.ts
  44. 19 1
      src/router/types.ts
  45. 40 11
      src/settings.ts
  46. 63 0
      src/store/interface/index.ts
  47. 18 18
      src/store/modules/app.ts
  48. 71 31
      src/store/modules/permission.ts
  49. 35 29
      src/store/modules/settings.ts
  50. 31 0
      src/styles/element-plus-dark.scss
  51. 14 5
      src/styles/element-plus.scss
  52. 5 4
      src/styles/index.scss
  53. 131 130
      src/styles/lance-element-ui.scss
  54. 76 75
      src/styles/lance-element-vue.scss
  55. 91 90
      src/styles/lance-element/draggableNest.scss
  56. 53 51
      src/styles/lance-element/formConfig.scss
  57. 114 114
      src/styles/lance-element/inputNumber.scss
  58. 6 3
      src/styles/lance-element/searchForm.scss
  59. 219 212
      src/styles/lance-element/table.scss
  60. 15 13
      src/styles/project_normal.scss
  61. 82 28
      src/styles/sidebar.scss
  62. 16 0
      src/styles/theme/aside.ts
  63. 25 0
      src/styles/theme/footer.ts
  64. 25 0
      src/styles/theme/header.ts
  65. 31 0
      src/styles/theme/menu.ts
  66. 69 9
      src/styles/transition.scss
  67. 37 21
      src/styles/variables.scss
  68. 4 0
      src/types/global.d.ts
  69. 42 4
      src/types/store.d.ts
  70. 59 0
      src/utils/color.ts
  71. 3 3
      src/views/components/components/InputNumberDemo.vue
  72. 3 3
      src/views/components/components/LeSelectDemo.vue
  73. 1 1
      src/views/components/components/SearchGroup2Popover.vue
  74. 1 1
      src/views/components/index.vue
  75. 1 1
      src/views/dashboard/index.vue
  76. 8 8
      src/views/demo/pageConfig/index.vue
  77. 9 5
      src/views/error-page/404.vue
  78. 2 2
      src/views/flow/create/components/BasicInfo.vue
  79. 13 13
      src/views/form/default.vue
  80. 2 3
      src/views/table/default.vue
  81. 4 4
      src/views/table/default_config.jsx
  82. 5 6
      src/views/table/expandTable.vue
  83. 3 3
      src/views/table/footerSummary.vue
  84. 2 3
      src/views/table/mergeCells.vue
  85. 2 2
      src/views/table/multipleHeader.vue
  86. 3 2
      src/views/table/resizeParentHeightTable.vue
  87. 2 2
      src/views/table/treeTable.vue
  88. 1 1
      src/views/test/componentCommunication/components/Comp2.vue

+ 3 - 5
.editorconfig

@@ -1,4 +1,3 @@
-# Editor configuration, see http://editorconfig.org
 root = true
 
 [*]
@@ -9,9 +8,8 @@ end_of_line = lf
 # 缩进风格(tab | space)
 indent_style = tab
 indent_size = 2
-# max_line_length = 150
-trim_trailing_whitespace = true
-insert_final_newline = true
+tab_width = 2
+max_line_length = 150
 
 # [*.{yml,yaml,json}]
 # indent_style = space
@@ -21,4 +19,4 @@ insert_final_newline = true
 # indent_style = space
 # indent_size = 2
 # 去除行首的任意空白字符
-
+trim_trailing_whitespace = true

+ 2 - 1
package.json

@@ -55,11 +55,12 @@
     "@typescript-eslint/parser": "^5.59.6",
     "@vitejs/plugin-vue": "^4.2.3",
     "@vitejs/plugin-vue-jsx": "^3.0.1",
-    "autoprefixer": "^10.4.14",
+		"autoprefixer": "^10.4.16",
     "eslint": "^8.40.0",
     "eslint-config-prettier": "^8.7.0",
     "eslint-plugin-prettier": "^4.2.1",
     "eslint-plugin-vue": "^9.9.0",
+		"postcss": "^8.4.30",
     "prettier": "^2.8.8",
     "rollup-plugin-visualizer": "^5.9.0",
     "sass": "^1.62.1",

+ 5 - 0
postcss.config.cjs

@@ -0,0 +1,5 @@
+module.exports = {
+	plugins: {
+		autoprefixer: {}
+	}
+}

+ 6 - 2
src/App.vue

@@ -9,6 +9,7 @@ import { computed, nextTick, onMounted, ref, watch } from 'vue'
 import { ElConfigProvider } from 'element-plus'
 import { ls } from '@/utils'
 import useAppStore from '@/store/modules/app'
+import { useTheme } from '@/hooks/useTheme'
 
 // 导入 Element Plus 语言包
 import { messages } from '@/lang'
@@ -18,14 +19,17 @@ const language = computed(() => app.language)
 const size = computed(() => app.size)
 
 const locale = ref()
-nextTick(() => {
+// 初始化 主题
+const { initTheme } = useTheme()
+initTheme()
+/*nextTick(() => {
 	// 尝试获取原主题样式 并做设置
 	const node: HTMLElement = document.documentElement
 	const styles = ls.get('style')
 	if (styles) {
 		node.style = styles
 	}
-})
+})*/
 watch(
 	language,
 	value => {

+ 1 - 1
src/api/system/menu.ts

@@ -5,7 +5,7 @@ import { local_permissionsRoutes } from '@/router'
 /**
  * 获取路由列表
  */
-export function listRoutes(): AxiosPromise {
+export function getMenuList(): AxiosPromise {
 	const isRequestAsyncRoutes = true // import.meta.env.VITE_APP_USE_LOCAL_ROUTES === '1'
 	// 请求本地路由配置
 	if (isRequestAsyncRoutes) {

+ 4 - 8
src/components/Breadcrumb/index.vue

@@ -3,8 +3,8 @@
 		<transition-group name="breadcrumb">
 			<el-breadcrumb-item v-for="(item, index) in breadcrumbs" :key="item.path">
 				<span v-if="item.redirect === 'noredirect' || index === breadcrumbs.length - 1" class="no-redirect">{{
-					generateTitle(item.meta.title)
-				}}</span>
+						generateTitle(item.meta.title)
+					}}</span>
 				<a v-else @click.prevent="handleLink(item)">
 					{{ generateTitle(item.meta.title) }}
 				</a>
@@ -80,11 +80,7 @@ onBeforeMount(() => {
 </script>
 
 <style lang="scss" scoped>
-.el-breadcrumb__inner,
-.el-breadcrumb__inner a {
-	font-weight: 400 !important;
-}
-
+/*
 .app-breadcrumb.el-breadcrumb {
 	display: inline-block;
 	font-size: 14px;
@@ -95,5 +91,5 @@ onBeforeMount(() => {
 		color: #97a8be;
 		cursor: text;
 	}
-}
+}*/
 </style>

+ 7 - 3
src/components/LangSelect/index.vue

@@ -5,8 +5,9 @@
 		</div>
 		<template #dropdown>
 			<el-dropdown-menu>
-				<el-dropdown-item :disabled="language === 'zh-cn'" command="zh-cn"> 中文 </el-dropdown-item>
-				<el-dropdown-item :disabled="language === 'en'" command="en"> English </el-dropdown-item>
+				<el-dropdown-item v-for="item in languageList" :key="item.value" :command="item.value" :disabled="language === item.value">
+					{{ item.label }}
+				</el-dropdown-item>
 			</el-dropdown-menu>
 		</template>
 	</el-dropdown>
@@ -20,7 +21,10 @@ import { ElMessage } from 'element-plus'
 const { app } = useStore()
 const language = computed(() => app.language)
 // import type { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
-
+const languageList = [
+	{ label: '简体中文', value: 'zh-cn' },
+	{ label: 'English', value: 'en' }
+]
 const { locale } = useI18n()
 
 function handleSetLanguage(lang: string) {

+ 14 - 10
src/components/RightPanel/index.vue

@@ -2,9 +2,9 @@
 	<div ref="rightPanel" :class="{ show }" class="rightPanel-container">
 		<div class="rightPanel-background" />
 		<div class="rightPanel">
-			<div class="handle-button" :style="{ top: buttonTop + 'px', 'background-color': theme }" @click="show = !show">
-				<Close class="icon" v-show="show" />
-				<Setting class="icon" v-show="!show" />
+			<div class="handle-button" :style="{ top: buttonTop + 'px', 'background-color': themeColor }" @click="show = !show">
+				<Close v-show="show" class="icon" />
+				<Setting v-show="!show" class="icon" />
 			</div>
 			<div class="rightPanel-items">
 				<slot />
@@ -24,7 +24,7 @@ import { ElColorPicker } from 'element-plus'
 
 const { setting } = useStore()
 
-const theme = computed(() => setting.theme)
+const themeColor = computed(() => setting.themeColor)
 const show = ref(false)
 
 defineProps({
@@ -98,23 +98,27 @@ onBeforeUnmount(() => {
 
 .rightPanel {
 	width: 100%;
-	max-width: 260px;
+	max-width: 280px;
 	height: 100vh;
 	position: fixed;
 	top: 0;
 	right: 0;
-	box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, 0.05);
-	transition: all 0.25s cubic-bezier(0.7, 0.3, 0.1, 1);
+	box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.05);
+	//transition: all 0.25s cubic-bezier(0.7, 0.3, 0.1, 1);
 	transform: translate(100%);
-	background: #fff;
-	z-index: 40000;
+	//background: #fff;
+	//background-color: var(--el-drawer-bg-color);
+	background-color: var(--el-bg-color);
+	//box-shadow: var(--el-box-shadow-dark);
+	transition: all var(--el-transition-duration);
+	z-index: 99;
 }
 
 .show {
 	transition: all 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
 
 	.rightPanel-background {
-		z-index: 20000;
+		z-index: 88;
 		opacity: 1;
 		width: 100%;
 		height: 100%;

+ 1 - 1
src/components/SizeSelect/index.vue

@@ -29,7 +29,7 @@ const sizeOptions = ref([
 ])
 
 function handleSetSize(size: string) {
-	app.setSize(size)
+	app.setSize(size) // todo 改成setting
 	ElMessage.success('切换布局大小成功')
 }
 </script>

+ 5 - 5
src/components/ThemePicker/index.vue

@@ -1,6 +1,6 @@
 <template>
 	<el-color-picker
-		v-model="theme"
+		v-model="themeColor"
 		:predefine="['#409EFF', '#1890ff', '#304156', '#212121', '#11a983', '#13c2c2', '#6959CD', '#f5222d']"
 		class="theme-picker"
 		popper-class="theme-picker-dropdown"
@@ -37,18 +37,18 @@ const mixBlack = '#000000'
 const node = document.documentElement
 
 const { setting } = useStore()
-const theme = computed(() => setting.theme)
+const themeColor = computed(() => setting.themeColor)
 
-watch(theme, (color: string) => {
+watch(themeColor, (color: string) => {
 	node.style.setProperty('--el-color-primary', color)
-	ls.set('theme', color)
+	// ls.set('themeColor', color)
 
 	for (let i = 1; i < 10; i += 1) {
 		node.style.setProperty(`--el-color-primary-light-${i}`, mix(color, mixWhite, i * 0.1))
 	}
 	node.style.setProperty('--el-color-primary-dark', mix(color, mixBlack, 0.1))
 
-	ls.set('style', node.style.cssText)
+	// ls.set('style', node.style.cssText)
 })
 </script>
 

+ 32 - 0
src/hooks/interface/index.ts

@@ -0,0 +1,32 @@
+/*export namespace Table {
+	export interface Pageable {
+		pageNum: number
+		pageSize: number
+		total: number
+	}
+	export interface StateProps {
+		tableData: any[]
+		pageable: Pageable
+		searchParam: {
+			[key: string]: any
+		}
+		searchInitParam: {
+			[key: string]: any
+		}
+		totalParam: {
+			[key: string]: any
+		}
+		icon?: {
+			[key: string]: any
+		}
+	}
+}
+
+export namespace HandleData {
+	export type MessageType = '' | 'success' | 'warning' | 'info' | 'error'
+}*/
+
+export namespace Theme {
+	export type ThemeType = 'light' | 'inverted' | 'dark'
+	export type GreyOrWeakType = 'grey' | 'weak'
+}

+ 122 - 0
src/hooks/useTheme.ts

@@ -0,0 +1,122 @@
+import { storeToRefs } from 'pinia'
+import { Theme } from './interface'
+// import { ElMessage } from 'element-plus'
+import useStore from '@/store'
+// import { useStore } from '@/stores/modules/global'
+import { getLightColor, getDarkColor } from '@/utils/color'
+import { menuTheme } from '@/styles/theme/menu'
+import { asideTheme } from '@/styles/theme/aside'
+import { headerTheme } from '@/styles/theme/header'
+import { footerTheme } from '@/styles/theme/footer'
+
+/**
+ * @description 全局主题 hooks
+ * */
+export const useTheme = () => {
+	const { setting } = useStore()
+	const { themeColor, layout, isDark, asideInverted, headerInverted, footerInverted /*isGrey, isWeak, */ } = storeToRefs(setting)
+
+	// 切换暗黑主题 ==> 同时修改主题颜色、侧边栏、头部颜色
+	const switchDark = () => {
+		const html = document.documentElement as HTMLElement
+		if (isDark.value) html.setAttribute('class', 'dark')
+		else html.setAttribute('class', '')
+		changeThemeColor(themeColor.value)
+		setAsideTheme()
+		setHeaderTheme()
+		setFooterTheme()
+	}
+
+	// 修改主题颜色
+	const changeThemeColor = (val: string) => {
+		// 计算主题颜色变化
+		document.documentElement.style.setProperty('--el-color-primary', val)
+		// todo...
+		// document.documentElement.style.setProperty('--el-color-primary-dark-2', isDark.value ? `${getLightColor(val, 0.2)}` : `${getDarkColor(val, 0.3)}`)
+		for (let i = 1; i < 10; i++) {
+			const primaryColor = isDark.value ? `${getDarkColor(val, i / 10)}` : `${getLightColor(val, i / 10)}`
+			document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, primaryColor)
+		}
+		// globalStore.setGlobalState('primary', val)
+		setting.changeSetting('themeColor', val)
+	}
+
+	// 灰色和弱色切换
+	const changeGreyOrWeak = (type: Theme.GreyOrWeakType, value: boolean) => {
+		const body = document.body as HTMLElement
+		if (!value) return body.removeAttribute('style')
+		const styles: Record<Theme.GreyOrWeakType, string> = {
+			grey: 'filter: grayscale(1)',
+			weak: 'filter: invert(80%)'
+		}
+		body.setAttribute('style', styles[type])
+		const propName = type === 'grey' ? 'isWeak' : 'isGrey'
+		setting.changeSetting(propName, false)
+		// globalStore.setGlobalState(propName, false)
+	}
+
+	// 设置菜单样式
+	const setMenuTheme = () => {
+		let type: Theme.ThemeType = 'light'
+		if (layout.value === 'transverse' && headerInverted.value) type = 'inverted'
+		if (layout.value !== 'transverse' && asideInverted.value) type = 'inverted'
+		if (isDark.value) type = 'dark'
+		const theme = menuTheme[type!]
+		// console.error(type, 'type theme', theme)
+		for (const [key, value] of Object.entries(theme)) {
+			document.documentElement.style.setProperty(key, value)
+		}
+	}
+
+	// 设置侧边栏样式
+	const setAsideTheme = () => {
+		let type: Theme.ThemeType = 'light'
+		if (asideInverted.value) type = 'inverted'
+		if (isDark.value) type = 'dark'
+		const theme = asideTheme[type!]
+		for (const [key, value] of Object.entries(theme)) {
+			document.documentElement.style.setProperty(key, value)
+		}
+		setMenuTheme()
+	}
+
+	// 设置头部样式
+	const setHeaderTheme = () => {
+		let type: Theme.ThemeType = 'light'
+		if (headerInverted.value) type = 'inverted'
+		if (isDark.value) type = 'dark'
+		const theme = headerTheme[type!]
+		for (const [key, value] of Object.entries(theme)) {
+			document.documentElement.style.setProperty(key, value)
+		}
+		setMenuTheme()
+	}
+	// 设置底部样式
+	const setFooterTheme = () => {
+		let type: Theme.ThemeType = 'light'
+		if (footerInverted.value) type = 'inverted'
+		if (isDark.value) type = 'dark'
+		const theme = footerTheme[type!]
+		for (const [key, value] of Object.entries(theme)) {
+			document.documentElement.style.setProperty(key, value)
+		}
+		setMenuTheme()
+	}
+
+	// init theme
+	const initTheme = () => {
+		switchDark()
+		// if (isGrey.value) changeGreyOrWeak('grey', true)
+		// if (isWeak.value) changeGreyOrWeak('weak', true)
+	}
+
+	return {
+		initTheme,
+		switchDark,
+		changeThemeColor,
+		changeGreyOrWeak,
+		setAsideTheme,
+		setHeaderTheme,
+		setFooterTheme
+	}
+}

+ 4 - 4
src/lang/index.ts

@@ -1,6 +1,6 @@
 // 自定义国际化配置
 import { createI18n } from 'vue-i18n'
-import { ls } from '@/utils'
+// import { ls } from '@/utils'
 
 // 导入 Element Plus 语言包
 import ElementCnLocale from 'element-plus/es/locale/lang/zh-cn'
@@ -30,13 +30,13 @@ export const messages = {
  * @returns zh-cn|en ...
  */
 export const getLanguage = () => {
-	// 本地缓存获取
+	/*// 本地缓存获取
 	let language = ls.get('language')
 	if (language) {
 		return language
-	}
+	}*/
 	// 浏览器使用语言
-	language = navigator.language.toLowerCase()
+	const language = navigator.language.toLowerCase()
 	const locales = Object.keys(messages)
 	for (const locale of locales) {
 		if (language.indexOf(locale) > -1) {

+ 127 - 0
src/layout/LayoutClassic/index.scss

@@ -0,0 +1,127 @@
+@import "../layout_common";
+.layout-wrap--classic {
+  width: 100%;
+  height: 100%;
+  //:deep() {
+	.el-header {
+    box-sizing: border-box;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    height: $header_height;
+    //padding: 0 15px 0 0;
+    padding: 0;
+    background-color: var(--el-header-bg-color);
+    border-bottom: 1px solid var(--el-header-border-color);
+    .header-lf {
+      display: flex;
+      align-items: center;
+      overflow: hidden;
+      white-space: nowrap;
+      height: 100%;
+      .logo {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        flex-shrink: 0;
+        width: 210px;
+        height: 100%;
+        //border-right: 1px solid #f00;
+        //margin-right: 16px;
+        .logo-img {
+          //width: 28px;
+          width: 36px;
+          height: 36px;
+          object-fit: contain;
+          //margin-right: 6px;
+          margin: 0 6px;
+        }
+        .logo-text {
+          color: var(--el-color-primary);
+          font-size: 15px;
+          font-weight: bold;
+          //color: var(--el-header-logo-text-color);
+          white-space: nowrap;
+        }
+      }
+    }
+  }
+  .classic-content {
+    display: flex;
+    height: calc(100% - $header_height);
+    .aside-box {
+      width: auto;
+      background-color: var(--el-menu-bg-color);
+      border-right: 1px solid var(--el-aside-border-color);
+      z-index: 1;
+      box-shadow: 2px 0 8px #1d23290d;
+      display: flex;
+      flex-direction: column;
+      height: 100%;
+      transition: width 0.3s ease;
+      .layout-menu-wrap {
+        width: 100%;
+        overflow-x: hidden;
+        border-right: none;
+/*				.menu-icon {
+					margin-right: 4px;
+					font-size: 18px;
+				}*/
+        // 折叠
+        &--collapse {
+          //background-color: transparent !important;
+					.menu-icon {
+						margin-right: 0;
+					}
+        }
+        .el-sub-menu {
+          &.is-active {
+						& > .el-sub-menu__title {
+              color: var(--el-menu-active-color);
+            }
+          }
+        }
+        $active_bg: var(--el-color-primary);
+        .el-menu-item,
+        .el-sub-menu__title {
+          margin-top: 5px;
+        }
+        .el-menu-item.is-active::before {
+          background-color: $active_bg;
+        }
+        .el-menu-item:hover {
+          &::before {
+            background-color: $active_bg;
+          }
+        }
+        .el-sub-menu__title:hover {
+          &::before {
+            background-color: $active_bg;
+          }
+        }
+        .el-menu-item::before,
+        .el-sub-menu__title::before {
+          z-index: auto;
+          content: "";
+          background-color: #0000;
+          opacity: 0.08;
+          position: absolute;
+          left: 8px;
+          right: 8px;
+          top: 0;
+          bottom: 0;
+          pointer-events: none;
+          border-radius: 3px;
+          transition: background-color .3s cubic-bezier(.4, 0, .2, 1);
+        }
+      }
+    }
+    //:deep(.el-aside) {
+    //
+    //}
+    .classic-main {
+      display: flex;
+      flex-direction: column;
+    }
+  }
+}

+ 51 - 0
src/layout/LayoutClassic/index.vue

@@ -0,0 +1,51 @@
+<!-- 经典布局 -->
+<template>
+	<el-container class="layout-wrap--classic">
+		<el-header>
+			<div class="header-lf mask-image">
+				<div class="logo">
+					<!--          <SvgIcon class="logo-img sidebar-logo" icon-class="logo" />-->
+					<img class="logo-img" src="@/assets/icons/logo.svg" alt="logo" />
+					<span :title="title" class="logo-text text-overflow_ellipsis">{{ title }}</span>
+				</div>
+				<ToolBarLeft />
+			</div>
+			<ToolBarRight class="header-ri" />
+		</el-header>
+		<el-container class="classic-content">
+			<el-aside class="aside-box" :style="{ width: isCollapse ? '65px' : '210px' }">
+				<el-scrollbar>
+					<el-menu class="layout-menu-wrap" :router="false" :default-active="activeMenu" :collapse="isCollapse" :unique-opened="accordion" :collapse-transition="false">
+						<SubMenu :menu-list="menuList" />
+					</el-menu>
+				</el-scrollbar>
+			</el-aside>
+			<el-container class="classic-main">
+				<AppMain />
+			</el-container>
+		</el-container>
+	</el-container>
+</template>
+
+<script setup lang="ts" name="layoutClassic">
+import { computed } from 'vue'
+import { useRoute } from 'vue-router'
+import AppMain from '@/layout/components/AppMain.vue'
+import SubMenu from '@/layout/components/Menu/SubMenu.vue'
+import ToolBarLeft from '@/layout/components/Header/ToolBarLeft.vue'
+import ToolBarRight from '@/layout/components/Header/ToolBarRight.vue'
+import useStore from '@/store'
+
+const title = import.meta.env.VITE_APP_TITLE
+const { permission, setting } = useStore()
+
+const route = useRoute()
+const accordion = computed(() => setting.accordion) // todo...
+const isCollapse = computed(() => setting.isCollapse)
+const menuList = computed(() => permission.showMenuList)
+const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string)
+</script>
+
+<style lang="scss">
+@import './index.scss';
+</style>

+ 133 - 0
src/layout/LayoutColumns/index.scss

@@ -0,0 +1,133 @@
+.layout-wrap--columns {
+  width: 100%;
+  height: 100%;
+  .aside-split {
+    display: flex;
+    flex-direction: column;
+    flex-shrink: 0;
+    width: 70px;
+    height: 100%;
+    background-color: var(--el-menu-bg-color);
+    border-right: 1px solid var(--el-aside-border-color);
+    .logo {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      box-sizing: border-box;
+      height: 55px;
+      .logo-img {
+        //width: 32px;
+        width: 36px;
+        height: 36px;
+        object-fit: contain;
+      }
+    }
+    .el-scrollbar {
+      height: calc(100% - 55px);
+      .split-list {
+        flex: 1;
+        .split-item {
+					position: relative;
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          justify-content: center;
+          height: 70px;
+          cursor: pointer;
+          transition: all 0.3s ease;
+					&::before {
+						z-index: auto;
+						content: "";
+						background-color: #0000;
+						opacity: 0.08;
+						position: absolute;
+						//left: 8px;
+						//right: 8px;
+						left: 0;
+						right: 0;
+						top: 0;
+						bottom: 0;
+						pointer-events: none;
+						border-radius: 3px;
+						transition: background-color .3s cubic-bezier(.4, 0, .2, 1);
+					}
+          /*.el-icon {
+            font-size: 20px;
+          }*/
+					.menu-icon {
+						font-size: 18px;
+					}
+          .title {
+            margin-top: 6px;
+            font-size: 12px;
+          }
+          .menu-icon,
+          .title {
+            color: var(--el-menu-text-color);
+          }
+        }
+        .split-active {
+					&::before {
+						background-color: var(--el-color-primary) !important;
+					}
+          .menu-icon,
+          .title {
+						color: var(--el-menu-active-color);
+            //color: #ffffff !important;
+          }
+        }
+      }
+    }
+  }
+  .not-aside {
+    width: 0 !important;
+    border-right: none !important;
+  }
+  .el-aside {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    overflow: hidden;
+    background-color: var(--el-menu-bg-color);
+    border-right: 1px solid var(--el-aside-border-color);
+    transition: width 0.3s ease;
+    .el-scrollbar {
+      height: calc(100% - 55px);
+      .layout-menu-wrap {
+        width: 100%;
+        overflow-x: hidden;
+        border-right: none;
+      }
+    }
+    .logo {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      box-sizing: border-box;
+      padding: 0 6px;
+      height: 55px;
+      .logo-text {
+        //font-size: 24px;
+        font-size: 15px;
+        font-weight: bold;
+        //color: var(--el-aside-logo-text-color);
+        color: var(--el-color-primary);
+        white-space: nowrap;
+      }
+    }
+  }
+  .el-header {
+    box-sizing: border-box;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    height: 55px;
+    //padding: 0 15px;
+		padding: 0;
+    background-color: var(--el-header-bg-color);
+    border-bottom: 1px solid var(--el-border-color-light);
+  }
+  .app-main {
+    min-height: 0;
+  }
+}

+ 110 - 0
src/layout/LayoutColumns/index.vue

@@ -0,0 +1,110 @@
+<!-- 分栏布局 -->
+<template>
+	<el-container class="layout-wrap--columns">
+		<div class="aside-split">
+			<div class="logo">
+				<!--				<img class="logo-img" src="@/assets/images/logo.svg" alt="logo" />-->
+				<!--          <SvgIcon class="logo-img sidebar-logo" icon-class="logo" />-->
+				<img class="logo-img" src="@/assets/icons/logo.svg" alt="logo" />
+			</div>
+			<el-scrollbar>
+				<div class="split-list">
+					<div
+						v-for="item in menuList"
+						:key="item.path"
+						class="split-item"
+						:class="{ 'split-active': splitActive === item.path || `/${splitActive.split('/')[1]}` === item.path }"
+						@click="changeSubMenu(item)"
+					>
+						<MenuIcon v-if="item.meta?.icon" :icon-class="item.meta.icon"></MenuIcon>
+<!--						<el-icon>
+							<component :is="item.meta.icon"></component>
+						</el-icon>-->
+						<span class="title">{{ generateTitle(item.meta.title) }}</span>
+					</div>
+				</div>
+			</el-scrollbar>
+		</div>
+		<el-aside :class="{ 'not-aside': !subMenuList.length }" :style="{ width: isCollapse ? '65px' : '210px' }">
+			<div class="logo">
+				<span v-show="subMenuList.length" class="text-overflow_ellipsis logo-text" :title="title">{{ title }}</span>
+			</div>
+			<el-scrollbar>
+				<el-menu class="layout-menu-wrap" :router="false" :default-active="activeMenu" :collapse="isCollapse" :unique-opened="accordion" :collapse-transition="false">
+					<SubMenu :menu-list="subMenuList" />
+				</el-menu>
+			</el-scrollbar>
+		</el-aside>
+		<el-container>
+			<el-header>
+				<ToolBarLeft />
+				<ToolBarRight />
+			</el-header>
+			<AppMain />
+		</el-container>
+	</el-container>
+</template>
+
+<script setup lang="ts" name="layoutColumns">
+import { ref, computed, watch } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+// import { useAuthStore } from '@/stores/modules/auth'
+// import { useGlobalStore } from '@/stores/modules/global'
+// import Main from '@/layouts/components/Main/index.vue'
+import AppMain from '@/layout/components/AppMain.vue'
+import ToolBarLeft from '@/layout/components/Header/ToolBarLeft.vue'
+import ToolBarRight from '@/layout/components/Header/ToolBarRight.vue'
+import SubMenu from '@/layout/components/Menu/SubMenu.vue'
+import MenuIcon from '@/layout/components/Menu/MenuIcon.vue'
+import useStore from '@/store'
+import { generateTitle } from '@/utils/i18n'
+const title = import.meta.env.VITE_APP_TITLE
+
+const route = useRoute()
+const router = useRouter()
+const { permission, setting, app } = useStore()
+// const authStore = useAuthStore()
+// const globalStore = useGlobalStore()
+// const accordion = computed(() => globalStore.accordion)
+// const isCollapse = computed(() => globalStore.isCollapse)
+// const menuList = computed(() => authStore.showMenuListGet)
+const accordion = computed(() => setting.accordion || false) // todo...
+const isCollapse = computed(() => setting.isCollapse)
+const menuList = computed(() => permission.showMenuList)
+const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string)
+
+const subMenuList = ref<Menu.MenuOptions[]>([])
+const splitActive = ref('')
+watch(
+	() => [menuList, route],
+	() => {
+		// 当前菜单没有数据直接 return
+		if (!menuList.value.length) return
+		splitActive.value = route.path
+		const menuItem = menuList.value.filter((item: Menu.MenuOptions) => {
+			return route.path === item.path || `/${route.path.split('/')[1]}` === item.path
+			// const pathArr = route.path.split('/')
+			// const childPath = pathArr.length > 1 ? `/${pathArr[1]}` : pathArr[0]
+			// return route.path === item.path || childPath === item.path
+		})
+		if (menuItem[0]?.children?.length) return (subMenuList.value = menuItem[0].children)
+		subMenuList.value = []
+	},
+	{
+		deep: true,
+		immediate: true
+	}
+)
+
+// change SubMenu
+const changeSubMenu = (item: Menu.MenuOptions) => {
+	splitActive.value = item.path
+	if (item.children?.length) return (subMenuList.value = item.children)
+	subMenuList.value = []
+	router.push(item.path)
+}
+</script>
+
+<style lang="scss">
+@import './index.scss';
+</style>

+ 92 - 0
src/layout/LayoutTransverse/index.scss

@@ -0,0 +1,92 @@
+.layout-wrap--transverse {
+  width: 100%;
+  height: 100%;
+  //:deep(.el-header) {
+  .el-header {
+    box-sizing: border-box;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    height: 55px;
+    //padding: 0 15px 0 0;
+    padding: 0;
+    background-color: var(--el-header-bg-color);
+    border-bottom: 1px solid var(--el-header-border-color);
+    .logo {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 210px;
+      margin-right: 10px;
+      .logo-img {
+        //width: 28px;
+        width: 36px;
+        height: 36px;
+        object-fit: contain;
+        //margin-right: 6px;
+        margin: 0 6px;
+      }
+      .logo-text {
+        font-size: 15px;
+        font-weight: bold;
+        color: var(--el-color-primary);
+        //color: var(--el-header-logo-text-color);
+        white-space: nowrap;
+      }
+    }
+    .layout-menu-wrap {
+      flex: 1;
+      height: 100%;
+      overflow: hidden;
+      border-bottom: none;
+			margin-top: 0;
+      .el-sub-menu__hide-arrow {
+        width: 65px;
+        height: 55px;
+      }
+      /*.el-menu-item.is-active {
+        //color: #ffffff !important;
+				&::before {
+					background-color: var(--el-color-primary);
+				}
+      }*/
+			.el-menu-item,
+			.el-sub-menu__title {
+				margin-top: 0;
+				&::before {
+					left: 0;
+					right: 0;
+				}
+			}
+      .is-active {
+        //background-color: var(--el-color-primary) !important;
+        border-bottom-color: var(--el-color-primary) !important;
+        /*&::before {
+          width: 0;
+        }*/
+        .el-sub-menu__title {
+          //color: #ffffff !important;
+          //background-color: var(--el-color-primary) !important;
+          border-bottom-color: var(--el-color-primary) !important;
+        }
+      }
+    }
+  }
+
+  @media screen and (width <= 730px) {
+    .logo {
+      display: none !important;
+    }
+  }
+  .app-main {
+    min-height: 0;
+  }
+}
+.layout-menu-popper-wrap.el-menu--horizontal {
+	--el-menu-horizontal-sub-item-height: 42px;
+	.el-menu-item::before,
+	.el-sub-menu__title::before {
+		left: 0;
+		right: 0;
+	}
+}

+ 77 - 0
src/layout/LayoutTransverse/index.vue

@@ -0,0 +1,77 @@
+<!-- 横向布局 -->
+<template>
+	<el-container class="layout-wrap--transverse">
+		<el-header>
+			<div class="logo">
+				<!--          <SvgIcon class="logo-img sidebar-logo" icon-class="logo" />-->
+				<img class="logo-img" src="@/assets/icons/logo.svg" alt="logo" />
+				<span class="logo-text text-overflow_ellipsis" :title="title">{{ title }}</span>
+			</div>
+			<el-menu class="layout-menu-wrap" mode="horizontal" :router="false" :default-active="activeMenu">
+				<!-- 不能直接使用 SubMenu 组件,无法触发 el-menu 隐藏省略功能 -->
+				<template v-for="subItem in menuList" :key="subItem.path">
+					<el-sub-menu
+						v-if="subItem.children?.length"
+						:key="subItem.path"
+						popper-class="layout-menu-popper-wrap"
+						:index="subItem.path + 'el-sub-menu'"
+					>
+						<template #title>
+							<!--							<el-icon>
+								<component :is="subItem.meta.icon"></component>
+							</el-icon>-->
+							<MenuIcon v-if="subItem.meta.icon" :icon-class="subItem.meta.icon" />
+							<span>{{ generateTitle(subItem.meta?.title) }}</span>
+						</template>
+						<SubMenu :menu-list="subItem.children" />
+					</el-sub-menu>
+					<el-menu-item
+						v-else
+						:key="subItem.path + 'el-menu-item'"
+						popper-class="layout-menu-popper-wrap"
+						:index="subItem.path"
+						@click="handleClickMenu(subItem)"
+					>
+						<MenuIcon v-if="subItem.meta.icon" :icon-class="subItem.meta.icon" />
+						<template #title>
+							<span>{{ generateTitle(subItem.meta?.title) }}</span>
+						</template>
+					</el-menu-item>
+				</template>
+			</el-menu>
+			<ToolBarRight />
+		</el-header>
+		<AppMain />
+	</el-container>
+</template>
+
+<script setup lang="ts" name="layoutTransverse">
+import { computed } from 'vue'
+// import { useAuthStore } from '@/stores/modules/auth'
+import { useRoute, useRouter } from 'vue-router'
+// import Main from '@/layouts/components/Main/index.vue'
+import AppMain from '@/layout/components/AppMain.vue'
+import ToolBarRight from '@/layout/components/Header/ToolBarRight.vue'
+import SubMenu from '@/layout/components/Menu/SubMenu.vue'
+import MenuIcon from '@/layout/components/Menu/MenuIcon.vue'
+import useStore from '@/store'
+import { generateTitle } from '@/utils/i18n'
+
+const title = import.meta.env.VITE_APP_TITLE
+const { permission, setting, app } = useStore()
+const route = useRoute()
+const router = useRouter()
+// const authStore = useAuthStore()
+// const menuList = computed(() => authStore.showMenuListGet)
+const menuList = computed(() => permission.showMenuList)
+const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string)
+
+const handleClickMenu = (subItem: Menu.MenuOptions) => {
+	if (subItem.meta.isLink) return window.open(subItem.meta.isLink, '_blank')
+	router.push(subItem.path)
+}
+</script>
+
+<style lang="scss">
+@import './index.scss';
+</style>

+ 116 - 0
src/layout/LayoutVertical/index.scss

@@ -0,0 +1,116 @@
+.layout-wrap--vertical {
+  width: 100%;
+  height: 100%;
+  //:deep(.el-aside) {
+  .el-aside {
+    width: auto;
+    background-color: var(--el-menu-bg-color);
+    border-right: 1px solid var(--el-aside-border-color);
+    .aside-box {
+      display: flex;
+      flex-direction: column;
+      height: 100%;
+      transition: width 0.3s ease;
+      .el-scrollbar {
+        height: calc(100% - 55px);
+        .layout-menu-wrap {
+          width: 100%;
+          overflow-x: hidden;
+          border-right: none;
+          /*.menu-icon {
+            margin-right: 4px;
+            font-size: 18px;
+          }*/
+          // 折叠
+          &--collapse {
+            //background-color: transparent !important;
+            .menu-icon {
+              margin-right: 0;
+            }
+          }
+          /*.el-sub-menu {
+            &.is-active {
+							& > .el-sub-menu__title {
+                color: var(--el-menu-active-color);
+              }
+            }
+          }
+          $active_bg: var(--el-color-primary);
+          .el-menu-item,
+          .el-sub-menu__title {
+            margin-top: 5px;
+          }
+          .el-menu-item.is-active::before {
+            background-color: $active_bg;
+          }
+          .el-menu-item:hover {
+            &::before {
+              background-color: $active_bg;
+            }
+          }
+          .el-sub-menu__title:hover {
+            &::before {
+              background-color: $active_bg;
+            }
+          }
+          .el-menu-item::before,
+          .el-sub-menu__title::before {
+            z-index: auto;
+            content: "";
+            background-color: #0000;
+            opacity: 0.08;
+            position: absolute;
+            left: 8px;
+            right: 8px;
+            top: 0;
+            bottom: 0;
+            pointer-events: none;
+            border-radius: 3px;
+            transition: background-color .3s cubic-bezier(.4, 0, .2, 1);
+          }*/
+        }
+      }
+      .logo {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        box-sizing: border-box;
+        height: 55px;
+        /*.logo-img {
+          width: 28px;
+          object-fit: contain;
+          margin-right: 6px;
+        }*/
+        .logo-img {
+          //width: 28px;
+          width: 36px;
+          height: 36px;
+          object-fit: contain;
+          //margin-right: 6px;
+          margin: 0 6px;
+        }
+        .logo-text {
+          font-size: 15px;
+          font-weight: bold;
+          //color: var(--el-aside-logo-text-color);
+          color: var(--el-color-primary);
+          white-space: nowrap;
+        }
+      }
+    }
+  }
+  .el-header {
+    box-sizing: border-box;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    height: 55px;
+    //padding: 0 15px;
+    padding: 0;
+    background-color: var(--el-header-bg-color);
+    border-bottom: 1px solid var(--el-header-border-color);
+  }
+	.app-main {
+		min-height: 0;
+	}
+}

+ 59 - 0
src/layout/LayoutVertical/index.vue

@@ -0,0 +1,59 @@
+<!-- 纵向布局 -->
+<template>
+	<el-container class="layout-wrap--vertical">
+		<el-aside>
+			<div class="aside-box" :style="{ width: isCollapse ? '65px' : '210px' }">
+				<div class="logo">
+					<!--          <SvgIcon class="logo-img sidebar-logo" icon-class="logo" />-->
+					<img class="logo-img" src="@/assets/icons/logo.svg" alt="logo" />
+					<span v-show="!isCollapse" class="text-overflow_ellipsis logo-text" :title="title">{{ title }}</span>
+				</div>
+				<el-scrollbar>
+					<el-menu class="layout-menu-wrap" :router="false" :default-active="activeMenu" :collapse="isCollapse" :unique-opened="accordion" :collapse-transition="false">
+						<SubMenu :menu-list="menuList" />
+					</el-menu>
+				</el-scrollbar>
+			</div>
+		</el-aside>
+		<el-container>
+			<el-header>
+				<ToolBarLeft />
+				<ToolBarRight />
+			</el-header>
+			<AppMain />
+		</el-container>
+	</el-container>
+</template>
+
+<script setup lang="ts" name="layoutVertical">
+import { computed } from 'vue'
+import { useRoute } from 'vue-router'
+// import { useAuthStore } from '@/stores/modules/auth'
+// import { useGlobalStore } from '@/stores/modules/global'
+// import Main from '@/layouts/components/Main/index.vue'
+import ToolBarLeft from '@/layout/components/Header/ToolBarLeft.vue'
+import ToolBarRight from '@/layout/components/Header/ToolBarRight.vue'
+import SubMenu from '@/layout/components/Menu/SubMenu.vue'
+import AppMain from '@/layout/components/AppMain.vue'
+import useStore from '@/store'
+
+const title = import.meta.env.VITE_APP_TITLE
+
+const route = useRoute()
+const { permission, setting } = useStore()
+// const authStore = useAuthStore()
+// const globalStore = useGlobalStore()
+// const accordion = computed(() => globalStore.accordion)
+// const isCollapse = computed(() => globalStore.isCollapse)
+// const menuList = computed(() => authStore.showMenuListGet)
+const accordion = computed(() => setting.accordion || false) // todo...
+const isCollapse = computed(() => setting.isCollapse)
+// const isCollapse = computed(() => !app.sidebar.opened)
+
+const menuList = computed(() => permission.showMenuList)
+const activeMenu = computed(() => (route.meta.activeMenu ? route.meta.activeMenu : route.path) as string)
+</script>
+
+<style lang="scss">
+@import './index.scss';
+</style>

+ 14 - 4
src/layout/components/AppMain.vue

@@ -1,22 +1,28 @@
 <template>
 	<section class="app-main">
+		<TagsView v-show="needTagsView" />
 		<router-view v-slot="{ Component, route }">
-			<transition name="router-fade" mode="out-in">
+			<transition :name="pageAnimateMode" mode="out-in">
 				<keep-alive :include="cachedViews">
 					<component :is="Component" :key="route.fullPath" />
 				</keep-alive>
 			</transition>
 		</router-view>
+		<div class="layout-footer" v-show="setting.footer">
+			<Footer />
+		</div>
 	</section>
 </template>
 
 <script setup lang="ts">
 import { computed } from 'vue'
 import useStore from '@/store'
-
-const { tagsView } = useStore()
-
+import Footer from '@/layout/components/Footer/index.vue'
+import TagsView from '@/layout/components/TagsView/index.vue'
+const { tagsView, setting } = useStore()
+const needTagsView = computed(() => setting.tagsView)
 const cachedViews = computed(() => tagsView.cachedViews)
+const pageAnimateMode = computed(() => setting.animate ? setting.animateMode : undefined)
 </script>
 
 <style lang="scss" scoped>
@@ -55,4 +61,8 @@ const cachedViews = computed(() => tagsView.cachedViews)
 		padding-right: 15px;
 	}
 }
+.layout-footer {
+	position: relative;
+	z-index: 1;
+}
 </style>

+ 23 - 0
src/layout/components/Footer/index.vue

@@ -0,0 +1,23 @@
+<template>
+	<div class="layout-footer">
+		<a href="https://github.com/LanceJiang" target="_blank"> 2028 © vue3_element_admin By LanceJiang. </a>
+	</div>
+</template>
+
+<style scoped lang="scss">
+.layout-footer {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	height: 30px;
+	background-color: var(--el-footer-bg-color);
+	border-top: 1px solid var(--el-border-color-light);
+	a {
+		font-size: 14px;
+		//color: var(--el-text-color-secondary);
+		color: var(--el-footer-text-color);
+		text-decoration: none;
+		letter-spacing: 0.5px;
+	}
+}
+</style>

+ 25 - 0
src/layout/components/Header/ToolBarLeft.vue

@@ -0,0 +1,25 @@
+<template>
+	<div class="tool-bar-lf">
+		<CollapseIcon id="collapseIcon" />
+		<Breadcrumb v-show="setting.breadcrumb" id="breadcrumb" />
+	</div>
+</template>
+
+<script setup lang="ts">
+// import { useGlobalStore } from '@/stores/modules/global'
+import useStore from '@/store'
+import CollapseIcon from './components/CollapseIcon.vue'
+import Breadcrumb from './components/Breadcrumb.vue'
+const { setting } = useStore()
+</script>
+
+<style scoped lang="scss">
+.tool-bar-lf {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+  height: 100%;
+	overflow: hidden;
+	white-space: nowrap;
+}
+</style>

+ 82 - 0
src/layout/components/Header/ToolBarRight.vue

@@ -0,0 +1,82 @@
+<template>
+	<div class="tool-bar-ri">
+		<div class="header-icon">
+			<!--			<AssemblySize id="assemblySize" />-->
+			<Screenfull id="screenfull" class="right-menu-item hover-effect" />
+			<el-tooltip content="布局大小" effect="dark" placement="bottom">
+				<SizeSelect id="size-select" class="right-menu-item hover-effect" />
+			</el-tooltip>
+			<Language id="language" class="right-menu-item hover-effect" />
+			<!--      todo...搜索 -->
+			<!--			<SearchMenu id="searchMenu" />-->
+			<!--			<ThemeSetting id="themeSetting" />-->
+			<!--			<Message id="message" />-->
+			<!--			<Fullscreen id="fullscreen" />-->
+		</div>
+		<Avatar />
+	</div>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+// import { useUserStore } from '@/stores/modules/user'
+// import AssemblySize from './components/AssemblySize.vue'
+import Language from './components/Language.vue'
+import Screenfull from '@/components/Screenfull/index.vue'
+import SizeSelect from '@/components/SizeSelect/index.vue'
+// import SearchMenu from './components/SearchMenu.vue'
+// import ThemeSetting from './components/ThemeSetting.vue'
+// import Message from './components/Message.vue'
+// import Fullscreen from './components/Fullscreen.vue'
+import Avatar from './components/Avatar.vue'
+
+// const userStore = useUserStore()
+// const username = computed(() => userStore.userInfo.name)
+</script>
+
+<style scoped lang="scss">
+.tool-bar-ri {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	height: 100%;
+	//padding-right: 25px;
+	&:deep(.header-icon) {
+		height: 100%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		.right-menu-item {
+			display: flex;
+			align-items: center;
+			padding: 0 8px;
+			height: 100%;
+			//height: 55px;
+			font-size: 18px;
+			//color: #5a5e66;
+			//color: var(--el-header-text-color);
+			color: var(--el-color-info);
+
+			&.hover-effect {
+				cursor: pointer;
+				transition: background 0.3s;
+
+				&:hover {
+					//background: rgba(0, 0, 0, 0.025);
+					background: var(--el-fill-color);
+				}
+			}
+		}
+		//& > * {
+		//	margin-left: 21px;
+		//}
+	}
+	.header-icon {
+	}
+	.username {
+		margin: 0 20px;
+		font-size: 15px;
+		color: var(--el-header-text-color);
+	}
+}
+</style>

+ 173 - 0
src/layout/components/Header/components/Avatar.vue

@@ -0,0 +1,173 @@
+<template>
+	<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click" size="default">
+		<div class="avatar-wrapper">
+			<span class="nickname">{{ userInfo.username || '' }}</span>
+			<el-avatar v-if="userInfo.avatar" :size="30" :src="userInfo.avatar" class="user-avatar" />
+			<ArrowDown style="width: 0.6em; height: 0.6em; margin-left: 5px; font-size: 24px" />
+		</div>
+
+		<template #dropdown>
+			<el-dropdown-menu>
+				<!--						<router-link to="/">
+          <el-dropdown-item>{{ $t('navbar.dashboard') }}</el-dropdown-item>
+        </router-link>-->
+				<el-dropdown-item @click="logout">
+					{{ $t('navbar.logout') }}
+				</el-dropdown-item>
+			</el-dropdown-menu>
+		</template>
+	</el-dropdown>
+</template>
+<script setup lang="ts">
+import { computed } from 'vue'
+// import { ArrowDown } from '@element-plus/icons-vue'
+import useStore from '@/store'
+import { useRoute, useRouter } from 'vue-router'
+
+import { ElMessageBox } from 'element-plus'
+const { setting, app, user } = useStore()
+const router = useRouter()
+const route = useRoute()
+const userInfo = computed(() => {
+	// const _info: Recordable = user.userInfo || {}
+	// return { ..._info, userNameF: _info.userName?.substring(0, 1) }
+	const _info: Recordable = user.cur_userInfo || {}
+	return { ..._info, userNameF: _info.userName?.substring(0, 1) }
+})
+function logout() {
+	ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
+		confirmButtonText: '确定',
+		cancelButtonText: '取消',
+		type: 'warning'
+	}).then(() => {
+		user.logout().then(() => {
+			router.push(`/login?redirect=${route.fullPath}`)
+		})
+	})
+}
+</script>
+
+<!--
+<template>
+	<el-dropdown trigger="click">
+		<div class="avatar">
+			<img src="@/assets/images/avatar.gif" alt="avatar" />
+		</div>
+		<template #dropdown>
+			<el-dropdown-menu>
+				<el-dropdown-item @click="openDialog('infoRef')">
+					<el-icon><User /></el-icon>{{ $t('header.personalData') }}
+				</el-dropdown-item>
+				<el-dropdown-item @click="openDialog('passwordRef')">
+					<el-icon><Edit /></el-icon>{{ $t('header.changePassword') }}
+				</el-dropdown-item>
+				<el-dropdown-item divided @click="logout">
+					<el-icon><SwitchButton /></el-icon>{{ $t('header.logout') }}
+				</el-dropdown-item>
+			</el-dropdown-menu>
+		</template>
+	</el-dropdown>
+	&lt;!&ndash; infoDialog &ndash;&gt;
+	<InfoDialog ref="infoRef"></InfoDialog>
+	&lt;!&ndash; passwordDialog &ndash;&gt;
+	<PasswordDialog ref="passwordRef"></PasswordDialog>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { LOGIN_URL } from '@/config'
+import { useRouter } from 'vue-router'
+import { logoutApi } from '@/api/modules/login'
+import { useUserStore } from '@/stores/modules/user'
+import { ElMessageBox, ElMessage } from 'element-plus'
+import InfoDialog from './InfoDialog.vue'
+import PasswordDialog from './PasswordDialog.vue'
+
+const router = useRouter()
+const userStore = useUserStore()
+
+// 退出登录
+const logout = () => {
+	ElMessageBox.confirm('您是否确认退出登录?', '温馨提示', {
+		confirmButtonText: '确定',
+		cancelButtonText: '取消',
+		type: 'warning'
+	}).then(async () => {
+		// 1.执行退出登录接口
+		await logoutApi()
+
+		// 2.清除 Token
+		userStore.setToken('')
+
+		// 3.重定向到登陆页
+		router.replace(LOGIN_URL)
+		ElMessage.success('退出登录成功!')
+	})
+}
+
+// 打开修改密码和个人信息弹窗
+const infoRef = ref<InstanceType<typeof InfoDialog> | null>(null)
+const passwordRef = ref<InstanceType<typeof PasswordDialog> | null>(null)
+const openDialog = (ref: string) => {
+	if (ref == 'infoRef') infoRef.value?.openDialog()
+	if (ref == 'passwordRef') passwordRef.value?.openDialog()
+}
+</script>
+
+<style scoped lang="scss">
+.avatar {
+	width: 40px;
+	height: 40px;
+	overflow: hidden;
+	cursor: pointer;
+	border-radius: 50%;
+	img {
+		width: 100%;
+		height: 100%;
+	}
+}
+</style>
+-->
+<style lang="scss" scoped>
+.right-menu-item {
+	padding: 0 8px;
+	height: 100%;
+	font-size: 18px;
+	//color: #5a5e66;
+	//color: var(--el-header-text-color);
+	color: var(--el-color-info);
+
+	&.hover-effect {
+		cursor: pointer;
+		transition: background 0.3s;
+
+		&:hover {
+			//background: rgba(0, 0, 0, 0.025);
+			background: var(--el-fill-color);
+		}
+	}
+}
+
+.avatar-container {
+	.avatar-wrapper {
+		display: flex;
+		align-items: center;
+		white-space: nowrap;
+		.nickname {
+			font-size: 14px;
+		}
+		.user-avatar {
+			cursor: pointer;
+			width: 26px;
+			height: 26px;
+			border-radius: 50%;
+			margin-left: 8px;
+		}
+
+		.el-icon-caret-bottom {
+			cursor: pointer;
+			font-size: 12px;
+		}
+	}
+}
+</style>

+ 140 - 0
src/layout/components/Header/components/Breadcrumb.vue

@@ -0,0 +1,140 @@
+<template>
+	<div :class="['breadcrumb-box']">
+		<el-breadcrumb :separator-icon="ArrowRight">
+			<transition-group name="breadcrumb">
+				<!--				<el-breadcrumb-item v-for="(item, index) in breadcrumbList" :key="item.path">-->
+				<el-breadcrumb-item v-for="(item, index) in breadcrumbs" :key="item.path">
+					<div class="el-breadcrumb__inner is-link" @click="onBreadcrumbClick(item, index)">
+						<MenuIcon v-if="setting.breadcrumbIcon && item.meta?.icon" :icon-class="item.meta.icon" class="breadcrumb-icon"/>
+						<!--						<el-icon v-show="item.meta?.icon && setting.breadcrumbIcon" class="breadcrumb-icon">
+							<component :is="item.meta.icon"></component>
+						</el-icon>-->
+						<span class="breadcrumb-title">{{ generateTitle(item.meta.title) }}</span>
+					</div>
+				</el-breadcrumb-item>
+			</transition-group>
+		</el-breadcrumb>
+	</div>
+</template>
+
+<script setup lang="ts">
+import { computed, onBeforeMount, ref, watch } from 'vue'
+// import { HOME_URL } from '@/config'
+import { RouteLocationMatched, useRoute, useRouter } from 'vue-router'
+import { ArrowRight } from '@element-plus/icons-vue'
+// import { useAuthStore } from '@/stores/modules/auth'
+// import { useGlobalStore } from '@/stores/modules/global'
+import useStore from '@/store'
+// import router from '@/router'
+import { generateTitle } from '@/utils/i18n'
+import MenuIcon from "@/layout/components/Menu/MenuIcon.vue";
+
+const route = useRoute()
+const router = useRouter()
+// const authStore = useAuthStore()
+// const globalStore = useGlobalStore()
+const { setting } = useStore()
+
+/*const breadcrumbList = computed(() => {
+	let breadcrumbData = authStore.breadcrumbListGet[route.matched[route.matched.length - 1].path] ?? []
+	// 🙅‍♀️不需要首页面包屑可删除以下判断
+	if (breadcrumbData[0].path !== HOME_URL) {
+		breadcrumbData = [{ path: HOME_URL, meta: { icon: 'HomeFilled', title: '首页' } }, ...breadcrumbData]
+	}
+	return breadcrumbData
+})*/
+const currentRoute = useRoute()
+
+const breadcrumbs = ref([] as Array<RouteLocationMatched>)
+
+function getBreadcrumb() {
+	// console.error(currentRoute, 'currentRoute')
+	let matched = currentRoute.matched.filter(item => item.meta && item.meta.title)
+	const first = matched[0]
+	if (!isDashboard(first)) {
+		matched = [{ path: '/dashboard', meta: { title: 'dashboard', icon: 'icon-homepage' } } as any].concat(matched)
+	}
+	breadcrumbs.value = matched
+	//   .filter(item => {
+	// 	return item.meta && item.meta.title && item.meta.breadcrumb !== false
+	// })
+}
+function isDashboard(route: RouteLocationMatched) {
+	const name = route && route.name
+	if (!name) {
+		return false
+	}
+	return name.toString().trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
+}
+watch(
+	() => currentRoute.path,
+	path => {
+		if (path.startsWith('/redirect/')) {
+			return
+		}
+		getBreadcrumb()
+	}
+)
+
+onBeforeMount(() => {
+	getBreadcrumb()
+})
+
+// Click Breadcrumb
+const onBreadcrumbClick = (item: Menu.MenuOptions, index: number) => {
+	if (item.redirect === 'noredirect' || index !== breadcrumbs.value.length - 1) router.push(item.path)
+	// if (item.redirect === 'noredirect' || index !== breadcrumbList.value.length - 1) router.push(item.path)
+}
+</script>
+
+<style scoped lang="scss">
+.breadcrumb-box {
+	display: flex;
+	align-items: center;
+	overflow: hidden;
+	margin-left: 6px;
+	padding-right: 50px;
+	//-webkit-mask-image: linear-gradient(90deg, #000000 0%, #000000 calc(100% - 50px), transparent);
+	mask-image: linear-gradient(90deg, #000000 0%, #000000 calc(100% - 50px), transparent);
+	.el-breadcrumb {
+		white-space: nowrap;
+		line-height: unset;
+		.el-breadcrumb__item {
+			position: relative;
+			display: inline-block;
+			float: none;
+			.el-breadcrumb__inner {
+				display: inline-flex;
+				//align-items: center;
+				&.is-link {
+					color: var(--el-header-text-color);
+					&:hover {
+						color: var(--el-color-primary);
+					}
+				}
+				.breadcrumb-icon {
+					//margin-top: 2px;
+					transform: translateY(2px);
+					margin-right: 6px;
+					//font-size: 16px;
+					font-size: 14px;
+				}
+				.breadcrumb-title {
+					//margin-top: 2px;
+					//margin-top: 3px;
+					//margin-top: 4px;
+				}
+			}
+			&:last-child .el-breadcrumb__inner,
+			&:last-child .el-breadcrumb__inner:hover {
+				color: var(--el-header-text-color-regular);
+			}
+			:deep(.el-breadcrumb__separator) {
+				position: relative;
+				//top: -1px;
+				top: 2px;
+			}
+		}
+	}
+}
+</style>

+ 34 - 0
src/layout/components/Header/components/CollapseIcon.vue

@@ -0,0 +1,34 @@
+<template>
+	<el-icon class="collapse-icon hover-effect" @click="changeCollapse">
+		<component :is="setting.isCollapse ? 'expand' : 'fold'"></component>
+	</el-icon>
+</template>
+
+<script setup lang="ts">
+// import { useGlobalStore } from '@/stores/modules/global'
+import useStore from '@/store'
+const { setting, app } = useStore()
+// const globalStore = useGlobalStore()		setting.changeSetting('themeColor', val)
+const changeCollapse = () => setting.changeSetting('isCollapse', !setting.isCollapse)
+</script>
+
+<style scoped lang="scss">
+.collapse-icon {
+	//margin-right: 20px;
+	height: 100%;
+	width: unset;
+	padding: 0 15px;
+	font-size: 16px;
+	color: var(--el-header-text-color);
+	//cursor: pointer;
+	&.hover-effect {
+		cursor: pointer;
+		transition: background 0.3s;
+
+		&:hover {
+			//background: rgba(0, 0, 0, 0.025);
+			background: var(--el-fill-color);
+		}
+	}
+}
+</style>

+ 49 - 0
src/layout/components/Header/components/Language.vue

@@ -0,0 +1,49 @@
+<template>
+	<el-dropdown class="lang-select" trigger="click" @command="handleSetLanguage">
+		<div class="lang-select__icon">
+			<LeIcon :icon-class="`le-lang_${language}`" />
+		</div>
+		<template #dropdown>
+			<el-dropdown-menu>
+				<el-dropdown-item v-for="item in languageList" :key="item.value" :command="item.value" :disabled="language === item.value">
+					{{ item.label }}
+				</el-dropdown-item>
+			</el-dropdown-menu>
+		</template>
+	</el-dropdown>
+</template>
+
+<script setup lang="ts">
+import { computed, onMounted } from 'vue'
+import useStore from '@/store'
+import { useI18n } from 'vue-i18n'
+import { ElMessage } from 'element-plus'
+const { app } = useStore()
+const language = computed(() => app.language)
+// import type { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
+const languageList = [
+	{ label: '简体中文', value: 'zh-cn' },
+	{ label: 'English', value: 'en' }
+]
+const { locale } = useI18n()
+
+function handleSetLanguage(lang: string) {
+	locale.value = lang
+	app.setLanguage(lang)
+	if (lang == 'en') {
+		ElMessage.success('Switch Language Successful!')
+	} else {
+		ElMessage.success('切换语言成功!')
+	}
+}
+onMounted(() => {
+	locale.value = language.value
+	app.setLanguage(language.value)
+})
+</script>
+
+<style lang="scss" scoped>
+/*.lang-select__icon {
+	line-height: 50px;
+}*/
+</style>

+ 58 - 0
src/layout/components/Menu/MenuIcon.vue

@@ -0,0 +1,58 @@
+<script setup lang="ts" name="MenuIcon">
+import { computed } from 'vue'
+const props = defineProps({
+	iconClass: {
+		type: String,
+		required: false
+	},
+	color: {
+		type: String,
+		default: ''
+	}
+})
+const icon = computed(() => {
+	const iconClass = props.iconClass || ''
+	let type = {
+		// 来自于 src/assets/icons 的svg: 'icon-[dir]-[name]'
+		icon: 'icon',
+		// 来自于 iconfont svg 链接: 'le-[name]'
+		le: 'iconfont'
+		// // 来自于 element svg 链接: 'el-[name]' => 实际icon name为: [name] el-仅用于标记
+		// el: 'element'
+	}[iconClass.split('-')[0]]
+	// 匹配不到icon- & le- 默认element
+	if (!type) type = 'element'
+	let component = iconClass
+	// if (type === 'element') {
+	// 	component = iconClass.replace('el-', '')
+	// }
+	// console.error(component, 'components.....')
+	return {
+		type,
+		component
+	}
+})
+const colorStyle = computed(() => {
+	return props.color ? `color: ${props.color}` : ''
+})
+</script>
+
+<template>
+	<template v-if="icon.type === 'element'">
+		<component :is="icon.component" class="menu-icon" :style="colorStyle" />
+	</template>
+	<LeIcon v-else :icon-class="icon.component" class="menu-icon" :color="color" />
+</template>
+
+<style scoped lang="scss">
+.menu-icon {
+	width: 1em;
+	height: 1em;
+	vertical-align: -0.15em;
+	fill: currentColor;
+	overflow: hidden;
+	&:focus {
+		outline: unset;
+	}
+}
+</style>

+ 104 - 0
src/layout/components/Menu/SubMenu.vue

@@ -0,0 +1,104 @@
+<template>
+	<template v-for="subItem in menuList" :key="subItem.path">
+		<el-sub-menu v-if="subItem.children?.length" popperClass="layout-menu-popper-wrap" :index="subItem.path">
+			<template #title>
+				<MenuIcon v-if="subItem.meta.icon" :icon-class="subItem.meta.icon" />
+				<!--				<el-icon v-if="subItem.meta.icon">
+					<component :is="subItem.meta.icon" />
+				</el-icon>-->
+				<span class="sle">{{ generateTitle(subItem.meta?.title) }}</span>
+			</template>
+			<SubMenu :menu-list="subItem.children" />
+		</el-sub-menu>
+		<el-menu-item v-else popperClass="layout-menu-popper-wrap" :index="subItem.path" @click="handleClickMenu(subItem)">
+			<MenuIcon v-if="subItem.meta.icon" :icon-class="subItem.meta.icon" />
+			<!--      <el-icon>
+				<component :is="subItem.meta.icon"></component>
+			</el-icon>-->
+			<template #title>
+				<span class="sle">{{ generateTitle(subItem.meta?.title) }}</span>
+			</template>
+		</el-menu-item>
+	</template>
+</template>
+
+<script setup lang="ts">
+import { useRouter } from 'vue-router'
+import { generateTitle } from '@/utils/i18n'
+import MenuIcon from './MenuIcon.vue'
+
+defineProps<{ menuList: Menu.MenuOptions[] }>()
+
+const router = useRouter()
+const handleClickMenu = (subItem: Menu.MenuOptions) => {
+	if (subItem.meta.isLink) return window.open(subItem.meta.isLink, '_blank')
+	router.push(subItem.path)
+	// router.push({ name: subItem.name })
+	// router.push('/404')
+}
+</script>
+
+<style lang="scss">
+.el-sub-menu .el-sub-menu__title:hover {
+	// 来自 vertical 关闭
+	//color: var(--el-menu-hover-text-color) !important;
+	//background-color: transparent !important;
+}
+.el-menu--collapse {
+	.el-sub-menu__title {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		padding: 0;
+		/*.menu-icon {
+			font-size: 18px;
+		}*/
+	}
+	.is-active {
+		.el-sub-menu__title {
+			&::before {
+				background-color: var(--el-color-primary) !important;
+			}
+			//color: #ffffff !important;
+			//background-color: var(--el-color-primary) !important;
+		}
+	}
+}
+.el-menu-item {
+	&:hover {
+		color: var(--el-menu-hover-text-color);
+	}
+	&.is-active {
+		color: var(--el-menu-active-color) !important;
+		//background-color: var(--el-menu-active-bg-color) !important;
+		/*&::before {
+			position: absolute;
+			top: 0;
+			bottom: 0;
+			width: 4px;
+			content: '';
+			background-color: var(--el-color-primary);
+		}*/
+	}
+}
+.vertical,
+.classic,
+.transverse {
+	.el-menu-item {
+		&.is-active {
+			&::before {
+				left: 0;
+			}
+		}
+	}
+}
+.columns {
+	.el-menu-item {
+		&.is-active {
+			&::before {
+				right: 0;
+			}
+		}
+	}
+}
+</style>

+ 322 - 45
src/layout/components/Settings/index.vue

@@ -1,95 +1,260 @@
 <template>
 	<div class="drawer-container">
-		<h3 class="drawer-title">系统布局配置</h3>
+		<h3 class="drawer-title">主题配置</h3>
+		<el-divider class="local-divider">
+			<el-icon><Operation /></el-icon>主题模式
+		</el-divider>
 		<div class="drawer-item">
 			<span>主题颜色</span>
-			<div style="float: right; height: 26px; margin: -3px 8px 0 0">
-				<theme-picker @change="themeChange" />
-			</div>
+			<theme-picker @change="themeChange" />
+		</div>
+		<div class="drawer-item" @click.stop="">
+			<span>暗黑主题</span>
+			<el-switch v-model="isDark" inline-prompt class="drawer-switch" active-icon="Sunny" inactive-icon="Moon" @change="switchDark" />
+		</div>
+		<div class="drawer-item">
+			<span>侧边栏深色</span>
+			<el-switch v-model="asideInverted" class="drawer-switch" @change="setAsideTheme" />
+		</div>
+		<div class="drawer-item">
+			<span>头部深色</span>
+			<el-switch v-model="headerInverted" class="drawer-switch" @change="setHeaderTheme" />
+		</div>
+		<div v-show="footer" class="drawer-item">
+			<span>底部深色</span>
+			<el-switch v-model="footerInverted" class="drawer-switch" @change="setFooterTheme" />
+		</div>
+
+		<el-divider class="local-divider">
+			<el-icon><Menu /></el-icon>布局模式
+		</el-divider>
+		<div class="layout-box">
+			<el-tooltip effect="dark" content="顶部菜单混合模式" placement="top" :show-after="200">
+				<div :class="['layout-item layout-classic', { 'is-active': layout == 'classic' }]" @click="setLayout('classic')">
+					<div class="layout-dark"></div>
+					<div class="layout-container">
+						<div class="layout-light"></div>
+						<div class="layout-content"></div>
+					</div>
+					<el-icon v-if="layout == 'classic'">
+						<CircleCheckFilled />
+					</el-icon>
+				</div>
+			</el-tooltip>
+			<el-tooltip effect="dark" content="顶部菜单模式" placement="top" :show-after="200">
+				<div :class="['layout-item layout-transverse', { 'is-active': layout == 'transverse' }]" @click="setLayout('transverse')">
+					<div class="layout-dark"></div>
+					<div class="layout-content"></div>
+					<el-icon v-if="layout == 'transverse'">
+						<CircleCheckFilled />
+					</el-icon>
+				</div>
+			</el-tooltip>
+			<el-tooltip effect="dark" content="左侧菜单模式" placement="top" :show-after="200">
+				<div :class="['layout-item layout-vertical', { 'is-active': layout == 'vertical' }]" @click="setLayout('vertical')">
+					<div class="layout-dark"></div>
+					<div class="layout-container">
+						<div class="layout-light"></div>
+						<div class="layout-content"></div>
+					</div>
+					<el-icon v-if="layout == 'vertical'">
+						<CircleCheckFilled />
+					</el-icon>
+				</div>
+			</el-tooltip>
+			<el-tooltip effect="dark" content="左侧菜单混合模式" placement="top" :show-after="200">
+				<div :class="['layout-item layout-columns', { 'is-active': layout == 'columns' }]" @click="setLayout('columns')">
+					<div class="layout-dark"></div>
+					<div class="layout-light"></div>
+					<div class="layout-content"></div>
+					<el-icon v-if="layout == 'columns'">
+						<CircleCheckFilled />
+					</el-icon>
+				</div>
+			</el-tooltip>
 		</div>
 
+		<el-divider class="local-divider">
+			<el-icon><Operation /></el-icon>界面功能
+		</el-divider>
 		<div class="drawer-item">
-			<span>开启 Tags-View</span>
+			<span>显示底部</span>
+			<el-switch v-model="footer" class="drawer-switch" />
+		</div>
+		<div class="drawer-item">
+			<span>面包屑</span>
+			<el-switch v-model="breadcrumb" class="drawer-switch" />
+		</div>
+		<div v-show="breadcrumb" class="drawer-item">
+			<span>面包屑图标</span>
+			<el-switch v-model="breadcrumbIcon" class="drawer-switch" />
+		</div>
+
+		<div class="drawer-item">
+			<span>多页签</span>
 			<el-switch v-model="tagsView" class="drawer-switch" />
 		</div>
 
 		<div class="drawer-item">
-			<span>固定 Header</span>
-			<el-switch v-model="fixedHeader" class="drawer-switch" />
+			<span>页面切换动画</span>
+			<el-switch v-model="animate" class="drawer-switch" />
 		</div>
 
 		<div class="drawer-item">
-			<span>侧边栏 Logo</span>
-			<el-switch v-model="sidebarLogo" class="drawer-switch" />
+			<span>页面切换动画类型</span>
+			<el-select v-model="animateMode" style="width: 100px;" class="drawer-switch">
+				<el-option v-for="v of animateList" :key="v.value" :value="v.value" :label="v.label" />
+			</el-select>
 		</div>
+
+		<!--
+				<div class="drawer-item">
+					<span>固定 Header</span>
+					<el-switch v-model="fixedHeader" class="drawer-switch" />
+				</div>
+		-->
+
+		<!--		<div class="drawer-item">
+					<span>侧边栏 Logo</span>
+					<el-switch v-model="sidebarLogo" class="drawer-switch" />
+				</div>-->
 	</div>
 </template>
 
 <script setup lang="ts">
-import { reactive, toRefs, watch } from 'vue'
-
+// import { reactive, toRefs, watch } from 'vue'
+import { storeToRefs } from 'pinia'
+import { LayoutType } from '@/store/interface'
 import ThemePicker from '@/components/ThemePicker/index.vue'
+// import { useDark, useToggle } from '@vueuse/core'
+import { useTheme } from '@/hooks/useTheme'
 
 import useStore from '@/store'
 
 const { setting } = useStore()
+const { switchDark, setAsideTheme, setHeaderTheme, setFooterTheme } = useTheme()
+// const isDark = useDark()
+// const toggleDark = () => useToggle(isDark)
 
-const state = reactive({
-	fixedHeader: setting.fixedHeader,
-	tagsView: setting.tagsView,
-	sidebarLogo: setting.sidebarLogo
-})
-
-const { fixedHeader, tagsView, sidebarLogo } = toRefs(state)
+// const state = reactive({
+// 	fixedHeader: setting.fixedHeader,
+// 	tagsView: setting.tagsView,
+// 	sidebarLogo: setting.sidebarLogo
+// })
+const animateList = [
+	{
+		label: '消退',
+		value: 'fade'
+	},
+	{
+		label: '滑动',
+		value: 'fade-slide'
+	},
+	{
+		label: '底部消退',
+		value: 'fade-bottom'
+	},
+	{
+		label: '缩放消退',
+		value: 'fade-scale'
+	},
+	{
+		label: '渐变',
+		value: 'zoom-fade'
+	},
+	{
+		label: '闪现',
+		value: 'zoom-out'
+	}
+]
+const {
+	isDark,
+	layout,
+	asideInverted,
+	headerInverted,
+	footerInverted,
+	fixedHeader,
+	footer,
+	breadcrumb,
+	breadcrumbIcon,
+	tagsView,
+	animate,
+	animateMode,
+	sidebarLogo
+} = storeToRefs(setting)
 
 function themeChange(val: any) {
-	setting.changeSetting({ key: 'theme', value: val })
+	setting.changeSetting('themeColor', val)
 }
 
-watch(
-	() => state.fixedHeader,
-	value => {
-		setting.changeSetting({ key: 'fixedHeader', value: value })
-	}
-)
+// 设置布局方式
+const setLayout = (val: LayoutType) => {
+	setting.changeSetting('layout', val)
+	setAsideTheme()
+}
 
-watch(
-	() => state.tagsView,
-	value => {
-		setting.changeSetting({ key: 'tagsView', value: value })
-	}
-)
+// watch(
+// 	() => state.fixedHeader,
+// 	value => {
+// 		setting.changeSetting('fixedHeader', value)
+// 	}
+// )
 
-watch(
-	() => state.sidebarLogo,
-	value => {
-		setting.changeSetting({ key: 'sidebarLogo', value: value })
-	}
-)
+// watch(
+// 	() => state.tagsView,
+// 	value => {
+// 		setting.changeSetting('tagsView', value)
+// 	}
+// )
+
+// watch(
+// 	() => state.sidebarLogo,
+// 	value => {
+// 		setting.changeSetting('sidebarLogo', value)
+// 	}
+// )
 </script>
 
 <style lang="scss" scoped>
 .drawer-container {
-	padding: 24px;
+	position: relative;
+	padding: 0 24px 16px 24px;
+	//padding-top: 0;
 	font-size: 14px;
 	line-height: 1.5;
 	word-wrap: break-word;
 
 	.drawer-title {
-		margin-bottom: 12px;
-		color: rgba(0, 0, 0, 0.85);
-		font-size: 14px;
-		line-height: 22px;
+		padding: 16px 24px;
+		margin: 0 -24px;
+		height: 55px;
+		//margin-bottom: 12px;
+		//color: rgba(0, 0, 0, 0.85);
+		font-size: 16px;
+		//line-height: 22px;
+		border-bottom: 1px solid var(--el-border-color-lighter);
+	}
+
+	.local-divider {
+		.el-icon {
+			position: relative;
+			top: 2px;
+			right: 5px;
+		}
 	}
 
 	.drawer-item {
-		color: rgba(0, 0, 0, 0.65);
+		//color: rgba(0, 0, 0, 0.65);
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		color: var(--el-text-color-primary);
 		font-size: 14px;
-		padding: 12px 0;
+		padding: 6px 0;
 	}
 
 	.drawer-switch {
-		float: right;
+		//float: right;
 	}
 
 	.job-link {
@@ -99,5 +264,117 @@ watch(
 		left: 0;
 		bottom: 0;
 	}
+
+	.layout-box {
+		position: relative;
+		display: flex;
+		flex-wrap: wrap;
+		justify-content: space-between;
+		padding: 12px 6px 0;
+		.layout-item {
+			position: relative;
+			box-sizing: border-box;
+			width: 100px;
+			height: 70px;
+			padding: 6px;
+			cursor: pointer;
+			border-radius: 5px;
+			box-shadow: 0 0 5px 1px var(--el-border-color-dark);
+			transition: all 0.2s;
+			.layout-dark {
+				background-color: var(--el-color-primary);
+				border-radius: 3px;
+			}
+			.layout-light {
+				background-color: var(--el-color-primary-light-5);
+				border-radius: 3px;
+			}
+			.layout-content {
+				background-color: var(--el-color-primary-light-8);
+				//border: 1px dashed var(--el-color-primary);
+				border: 1px solid var(--el-color-primary);
+				border-radius: 3px;
+			}
+			.el-icon {
+				position: absolute;
+				right: 10px;
+				bottom: 10px;
+				color: var(--el-color-primary);
+				transition: all 0.2s;
+			}
+			&:hover {
+				box-shadow: 0 0 5px 1px var(--el-text-color-secondary);
+			}
+		}
+		.is-active {
+			box-shadow: 0 0 0 2px var(--el-color-primary) !important;
+		}
+		.layout-vertical {
+			display: flex;
+			justify-content: space-between;
+			margin-bottom: 20px;
+			.layout-dark {
+				width: 20%;
+			}
+			.layout-container {
+				display: flex;
+				flex-direction: column;
+				justify-content: space-between;
+				width: 72%;
+				.layout-light {
+					height: 20%;
+				}
+				.layout-content {
+					height: 67%;
+				}
+			}
+		}
+		.layout-classic {
+			display: flex;
+			flex-direction: column;
+			justify-content: space-between;
+			margin-bottom: 20px;
+			.layout-dark {
+				height: 22%;
+			}
+			.layout-container {
+				display: flex;
+				justify-content: space-between;
+				height: 70%;
+				.layout-light {
+					width: 20%;
+				}
+				.layout-content {
+					width: 70%;
+				}
+			}
+		}
+		.layout-transverse {
+			display: flex;
+			flex-direction: column;
+			justify-content: space-between;
+			margin-bottom: 15px;
+			.layout-dark {
+				height: 20%;
+			}
+			.layout-content {
+				height: 67%;
+			}
+		}
+		.layout-columns {
+			display: flex;
+			justify-content: space-between;
+			margin-bottom: 15px;
+			.layout-dark {
+				width: 14%;
+			}
+			.layout-light {
+				width: 17%;
+			}
+			.layout-content {
+				width: 55%;
+			}
+		}
+	}
 }
 </style>

+ 0 - 45
src/layout/components/Sidebar/Link.vue

@@ -1,45 +0,0 @@
-<template>
-	<a v-if="isExternal(to)" :href="to" target="_blank" rel="noopener">
-		<slot />
-	</a>
-	<div v-else @click="push">
-		<slot />
-	</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent } from 'vue'
-import { isExternal } from '@/utils/validate'
-import { useRouter } from 'vue-router'
-
-import useStore from '@/store'
-
-const { app } = useStore()
-
-const sidebar = computed(() => app.sidebar)
-const device = computed(() => app.device)
-
-export default defineComponent({
-	props: {
-		to: {
-			type: String,
-			required: true
-		}
-	},
-	setup(props) {
-		const router = useRouter()
-		const push = () => {
-			if (device.value === 'mobile' && sidebar.value.opened == true) {
-				app.closeSideBar(false)
-			}
-			router.push(props.to).catch(err => {
-				console.log(err)
-			})
-		}
-		return {
-			push,
-			isExternal
-		}
-	}
-})
-</script>

+ 0 - 93
src/layout/components/Sidebar/Logo.vue

@@ -1,93 +0,0 @@
-<template>
-	<div class="sidebar-logo-container" :class="{ collapse: isCollapse }">
-		<transition name="sidebarLogoFade">
-			<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
-				<Svg-Icon class="sidebar-logo" icon-class="logo" />
-			</router-link>
-			<router-link v-else key="expand" class="sidebar-logo-link" to="/">
-				<SvgIcon class="sidebar-logo" icon-class="logo" />
-				<h1 class="sidebar-title">{{ title }}</h1>
-			</router-link>
-		</transition>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { ref, reactive, toRefs } from 'vue'
-
-const props = defineProps({
-	collapse: {
-		type: Boolean,
-		required: true
-	}
-})
-
-const state = reactive({
-	isCollapse: props.collapse
-})
-
-const { isCollapse } = toRefs(state)
-
-const title = ref('vue3_element_admin')
-</script>
-
-<style lang="scss" scoped>
-.sidebarLogoFade-enter-active {
-	transition: opacity 1.5s;
-}
-
-.sidebarLogoFade-enter,
-.sidebarLogoFade-leave-to {
-	opacity: 0;
-}
-
-.sidebar-logo-container {
-	position: relative;
-	width: 100%;
-	height: 50px;
-	line-height: 50px;
-	text-align: center;
-	overflow: hidden;
-	&::before {
-		z-index: auto;
-		content: '';
-		background-color: var(--el-menu-active-color);
-		opacity: 0.01;
-		position: absolute;
-		left: 0;
-		right: 0;
-		top: 0;
-		bottom: 0;
-		pointer-events: none;
-		transition: background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
-	}
-	& .sidebar-logo-link {
-		height: 100%;
-		width: 100%;
-
-		& .sidebar-logo {
-			margin-right: 0;
-			width: 36px;
-			height: 36px;
-			vertical-align: middle;
-		}
-
-		& .sidebar-title {
-			display: inline-block;
-			margin: 0;
-			color: var(--el-color-primary);
-			font-weight: 600;
-			line-height: 50px;
-			font-size: 14px;
-			font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
-			vertical-align: middle;
-		}
-	}
-
-	&.collapse {
-		.sidebar-logo {
-			margin-right: 0;
-		}
-	}
-}
-</style>

+ 0 - 101
src/layout/components/Sidebar/SidebarItem.vue

@@ -1,101 +0,0 @@
-<template>
-	<div v-if="!item.meta || !item.meta.hidden">
-		<template
-			v-if="
-				hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && (!item.meta || !item.meta.alwaysShow)
-			"
-		>
-			<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
-				<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
-					<svg-icon v-if="onlyOneChild.meta && onlyOneChild.meta.icon" :icon-class="onlyOneChild.meta.icon" />
-					<template #title>
-						{{ generateTitle(onlyOneChild.meta.title) }}
-					</template>
-				</el-menu-item>
-			</app-link>
-		</template>
-		<el-sub-menu v-else :index="resolvePath(item.path)" teleported>
-			<!-- popper-append-to-body -->
-			<template #title>
-				<svg-icon v-if="item.meta?.icon" :icon-class="item.meta.icon"></svg-icon>
-				<span v-if="item.meta?.title">{{ generateTitle(item.meta.title) }}</span>
-			</template>
-
-			<sidebar-item
-				v-for="child in item.children"
-				:key="child.path"
-				:item="child"
-				:is-nest="true"
-				:base-path="resolvePath(child.path)"
-				class="nest-menu"
-			/>
-		</el-sub-menu>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { ref } from 'vue'
-import { RouteRecordRaw } from 'vue-router'
-import path from 'path-browserify'
-import { isExternal } from '@/utils/validate'
-import AppLink from './Link.vue'
-
-import { generateTitle } from '@/utils/i18n'
-
-const props = defineProps({
-	item: {
-		type: Object as () => Partial<RouteRecordRaw>,
-		required: true
-	},
-	isNest: {
-		type: Boolean,
-		required: false
-	},
-	basePath: {
-		type: String,
-		required: true
-	}
-})
-
-const onlyOneChild = ref()
-
-function hasOneShowingChild(children = [] as any, parent: any) {
-	if (!children) {
-		children = []
-	}
-	const showingChildren = children.filter((item: any) => {
-		if (item.meta && item.meta.hidden) {
-			return false
-		} else {
-			// Temp set(will be used if only has one showing child)
-			onlyOneChild.value = item
-			return true
-		}
-	})
-
-	// When there is only one child router, the child router is displayed by default
-	if (showingChildren.length === 1) {
-		return true
-	}
-
-	// Show parent if there are no child router to display
-	if (showingChildren.length === 0) {
-		onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
-		return true
-	}
-
-	return false
-}
-
-function resolvePath(routePath: string) {
-	if (isExternal(routePath)) {
-		return routePath
-	}
-	if (isExternal(props.basePath as string)) {
-		return props.basePath
-	}
-	return path.resolve(props.basePath, routePath)
-}
-</script>
-
-<style lang="scss" scoped></style>

+ 0 - 36
src/layout/components/Sidebar/index.vue

@@ -1,36 +0,0 @@
-<template>
-	<div :class="{ 'has-logo': showLogo }">
-		<logo v-if="showLogo" :collapse="isCollapse" />
-		<el-scrollbar wrap-class="scrollbar-wrapper">
-			<el-menu :default-active="activeMenu" :collapse="isCollapse" :unique-opened="false" :collapse-transition="false" mode="vertical">
-				<sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" :is-collapse="isCollapse" />
-			</el-menu>
-		</el-scrollbar>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { computed } from 'vue'
-import { useRoute } from 'vue-router'
-
-import SidebarItem from './SidebarItem.vue'
-import Logo from './Logo.vue'
-import variables from '@/styles/variables.module.scss'
-import useStore from '@/store'
-
-const { permission, setting, app } = useStore()
-
-const route = useRoute()
-const routes = computed(() => permission.routes)
-const showLogo = computed(() => setting.sidebarLogo)
-const isCollapse = computed(() => !app.sidebar.opened)
-
-const activeMenu = computed(() => {
-	const { meta, path } = route
-	// if set path, the sidebar will highlight the path you set
-	if (meta.activeMenu) {
-		return meta.activeMenu as string
-	}
-	return path
-})
-</script>

+ 28 - 9
src/layout/components/TagsView/index.vue

@@ -268,21 +268,28 @@ onMounted(() => {
 
 <style lang="scss" scoped>
 .tags-view__container {
+	position: relative;
+	z-index: 1;
 	height: 34px;
 	width: 100%;
-	background: #fff;
-	border-bottom: 1px solid #d8dce5;
-	box-shadow: 0 1px 2px #00152914;
+	background-color: var(--el-bg-color);
+	//border-bottom: 1px solid #d8dce5;
+	border-bottom: 1px solid var(--el-border-color-light);
+	//box-shadow: 0 1px 2px #00152914;
 	.tags-view__wrapper {
 		.tags-view__item {
-			display: inline-block;
+			//display: inline-block;
+			display: inline-flex;
+			align-items: center;
 			position: relative;
 			cursor: pointer;
 			height: 26px;
 			line-height: 26px;
-			border: 1px solid #d8dce5;
+			//border: 1px solid #d8dce5;
+			border: 1px solid var(--el-border-color);
 			color: #495060;
-			background: #fff;
+			//background: #fff;
+			background-color: var(--el-bg-color);
 			padding: 0 8px;
 			font-size: 12px;
 			margin-left: 5px;
@@ -306,12 +313,24 @@ onMounted(() => {
 			}
 
 			.icon-close {
+				margin-left: 4px;
 				border-radius: 50%;
-				text-align: center;
+				display: inline-flex;
+				align-items: center;
+				justify-content: center;
+				border: 1px solid transparent;
+				width: 14px;
+				height: 14px;
+				//text-align: center;
 
 				&:hover {
-					background-color: #ccc;
-					color: #fff;
+					//background-color: #ccc;
+					//color: #fff;
+					border-color: var(--el-color-primary);
+					/*background-color: var(--el-button-hover-bg-color);
+					color: var(--el-button-hover-text-color);*/
+					/*background-color: var(--el-border-color-lighter);
+					color: var(--el-text-color-primary);*/
 				}
 			}
 		}

+ 0 - 1
src/layout/components/index.ts

@@ -1,4 +1,3 @@
-export { default as Navbar } from './Navbar.vue'
 export { default as AppMain } from './AppMain.vue'
 export { default as Settings } from './Settings/index.vue'
 export { default as TagsView } from './TagsView/index.vue'

+ 32 - 95
src/layout/index.vue

@@ -1,104 +1,41 @@
+<!-- 💥 这里是一次性加载 LayoutComponents -->
 <template>
-	<div :class="classObj" class="app-wrapper">
-		<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
-		<Sidebar class="sidebar-container" />
-		<div :class="{ hasTagsView: needTagsView }" class="main-container">
-			<div :class="{ 'fixed-header': fixedHeader }">
-				<navbar />
-				<tags-view v-if="needTagsView" />
-			</div>
-			<app-main />
-			<RightPanel v-if="showSettings">
-				<settings />
-			</RightPanel>
-		</div>
-	</div>
+	<component :is="LayoutComponents[layout]" />
+	<!--	<ThemeDrawer />-->
+	<RightPanel v-if="showSettings">
+		<Settings />
+	</RightPanel>
 </template>
 
-<script setup lang="ts">
-import { computed, watchEffect } from 'vue'
-import { useWindowSize } from '@vueuse/core'
-import { AppMain, Navbar, Settings, TagsView } from './components/index'
-import Sidebar from './components/Sidebar/index.vue'
-import RightPanel from '@/components/RightPanel/index.vue'
-
+<script setup lang="ts" name="layout">
+import { computed, type Component } from 'vue'
+import { LayoutType } from '@/store/interface'
+// import { AppMain, Settings, TagsView } from './components/index'
 import useStore from '@/store'
-
-const { width } = useWindowSize()
-const WIDTH = 992
-
-const { app, setting } = useStore()
-
-const sidebar = computed(() => app.sidebar)
-const device = computed(() => app.device)
-const needTagsView = computed(() => setting.tagsView)
-const fixedHeader = computed(() => setting.fixedHeader)
-const showSettings = computed(() => setting.showSettings)
-
-const classObj = computed(() => ({
-	hideSidebar: !sidebar.value.opened,
-	openSidebar: sidebar.value.opened,
-	withoutAnimation: sidebar.value.withoutAnimation,
-	mobile: device.value === 'mobile'
-}))
-
-watchEffect(() => {
-	if (width.value < WIDTH) {
-		app.toggleDevice('mobile')
-		app.closeSideBar(true)
-	} else {
-		app.toggleDevice('desktop')
-	}
-})
-
-function handleClickOutside() {
-	app.closeSideBar(false)
+// import { useGlobalStore } from "@/stores/modules/global";
+// import ThemeDrawer from './components/ThemeDrawer/index.vue'
+import RightPanel from '@/components/RightPanel/index.vue'
+import LayoutClassic from './LayoutClassic/index.vue'
+import LayoutTransverse from './LayoutTransverse/index.vue'
+import LayoutVertical from './LayoutVertical/index.vue'
+import LayoutColumns from './LayoutColumns/index.vue'
+import { Settings } from '@/layout/components'
+
+const LayoutComponents: Record<LayoutType, Component> = {
+	classic: LayoutClassic,
+	transverse: LayoutTransverse,
+	vertical: LayoutVertical,
+	columns: LayoutColumns
 }
+const { setting } = useStore()
+// const globalStore = useGlobalStore();
+// const layout = computed(() => setting.layout)
+const layout = computed(() => setting.layout)
+const showSettings = computed(() => setting.showSettings)
 </script>
 
-<style lang="scss" scoped>
-@import '@/styles/mixin.scss';
-@import '@/styles/variables.module.scss';
-
-.app-wrapper {
-	@include clearfix;
-	position: relative;
-	height: 100%;
-	width: 100%;
-
-	&.mobile.openSidebar {
-		position: fixed;
-		top: 0;
-	}
-}
-
-.drawer-bg {
-	background: #000;
-	opacity: 0.3;
-	width: 100%;
-	top: 0;
-	height: 100%;
-	position: absolute;
-	z-index: 999;
-}
-
-.fixed-header {
-	position: fixed;
-	top: 0;
-	right: 0;
-	z-index: 9;
-	width: calc(100% - #{$sideBarWidth});
-	transition: width 0.28s;
-}
-
-.hideSidebar .fixed-header {
-	width: calc(100% - 54px);
-}
-
-.mobile .fixed-header {
-	width: 100%;
-}
-.main-container {
-	background: #e5e5e5;
+<style scoped lang="scss">
+.layout {
+	min-width: 600px;
 }
 </style>

+ 1 - 0
src/layout/layout_common.scss

@@ -0,0 +1 @@
+$header_height: 55px;

+ 4 - 2
src/main.ts

@@ -4,6 +4,10 @@ import router from '@/router'
 // element plus
 import ElementPlus from 'element-plus'
 import 'element-plus/theme-chalk/index.css'
+// element dark css
+import 'element-plus/theme-chalk/dark/css-vars.css'
+// 自定义样式
+import '@/styles/index.scss'
 // 注册全局组件
 import { registerGlobComp } from '@/components/registerGlobComp'
 // 注册全局directive
@@ -40,8 +44,6 @@ import 'virtual:svg-icons-register'
 import i18n from '@/lang/index'
 // pinia store
 import pinia from '@/store/createPinia'
-// 自定义样式
-import '@/styles/index.scss'
 import { $log } from '@/utils'
 
 const app = createApp(App)

+ 16 - 3
src/permission.ts

@@ -34,16 +34,29 @@ router.beforeEach(async (to, from, next) => {
 			try {
 				await user.getUserInfo()
 				// const roles = user.roles
-				// const accessRoutes: any = await permission.generateRoutes(roles)
-				const accessRoutes: any = await permission.generateRoutes([])
+				// const accessRoutes: any = await permission.queryMenuList(roles)
+				const accessRoutes: any = await permission.queryMenuList([])
+				// 单独处理 菜单实体路径
 				accessRoutes.forEach((route: any) => {
 					router.addRoute(route)
 				})
+				/*router.addRoute('testLayout', {
+					// 管理员管理
+					path: 'adminManage1',
+					name: 'adminManage1',
+					// component: 'demo/adminManage/index',
+					// component: 'demo/adminManage/index',
+					component: modules[`/src/views/${'demo/adminManage/index'}.vue`],
+					// const  = modules[`/src/views/${tmp.component}.vue`] as any
+					// if (component) {
+					// 	tmp.component = component
+					meta: { title: 'demo_adminManage' }
+				})*/
 				next({ ...to, replace: true })
 			} catch (error) {
 				// 移除 token 并跳转登录页
 				await user.resetToken()
-				$log(error, 'getUserInfo&generateRoutes: error')
+				$log(error, 'getUserInfo&queryMenuList: error')
 				next(`/login?redirect=${to.path}`)
 			}
 			NProgress.done()

+ 133 - 13
src/router/index.ts

@@ -66,9 +66,10 @@ export const constantRoutes: Array<AppRouteRecordRaw> = [
 			}
 		]
 	},
-	// 首页
+	// 首页 	// 主入口
 	{
 		path: '/',
+		name: 'layout',
 		component: Layout,
 		redirect: '/dashboard',
 		children: [
@@ -76,7 +77,7 @@ export const constantRoutes: Array<AppRouteRecordRaw> = [
 				path: 'dashboard',
 				component: () => import('@/views/dashboard/index.vue'),
 				name: 'dashboard',
-				meta: { title: 'dashboard', icon: 'homepage', affix: true }
+				meta: { title: 'dashboard', icon: 'icon-homepage', affix: true }
 			}
 		]
 	},
@@ -92,7 +93,7 @@ export const constantRoutes: Array<AppRouteRecordRaw> = [
 							path: 'index',
 							component: () => import('@/views/components/index.vue'),
 							name: 'comps',
-							meta: { title: 'comps', icon: 'excel' }
+							meta: { title: 'comps', icon: 'icon-excel' }
 						}
 					]
 				},
@@ -100,14 +101,14 @@ export const constantRoutes: Array<AppRouteRecordRaw> = [
 				{
 					path: '/form',
 					component: Layout,
-					// meta: { title: 'Form', icon: 'guide' },
+					// meta: { title: 'Form', icon: 'icon-guide' },
 					redirect: '/default',
 					children: [
 						{
 							path: 'default',
 							component: () => import('@/views/form/default.vue'),
 							name: 'FormDefault',
-							meta: { title: 'form', icon: 'guide' }
+							meta: { title: 'form', icon: 'icon-guide' }
 						}
 					]
 				},
@@ -116,7 +117,7 @@ export const constantRoutes: Array<AppRouteRecordRaw> = [
 					path: '/table',
 					component: Layout,
 					redirect: '/default',
-					meta: { title: 'table', icon: 'table' },
+					meta: { title: 'table', icon: 'icon-table' },
 					children: [
 						{
 							path: 'default',
@@ -167,14 +168,14 @@ export const constantRoutes: Array<AppRouteRecordRaw> = [
 					path: '/demo',
 					component: Layout,
 					redirect: '/demo/adminManage',
-					meta: { title: 'demo', icon: 'peoples' },
+					meta: { title: 'demo', icon: 'icon-peoples' },
 					children: [
 						{
 							path: 'pageConfig',
 							component: () => import('@/views/demo/pageConfig/index'),
 							// component: 'demo/pageConfig/index',
 							name: 'pageConfig',
-							meta: { title: 'demo_pageConfig' }
+							meta: { title: 'demo_pageConfig', icon: 'le-fangda1' }
 						},
 						{
 							// 管理员管理
@@ -182,7 +183,7 @@ export const constantRoutes: Array<AppRouteRecordRaw> = [
 							name: 'adminManage',
 							component: () => import('@/views/demo/adminManage/index'),
 							// component: 'demo/adminManage/index',
-							meta: { title: 'demo_adminManage' }
+							meta: { title: 'demo_adminManage', icon: 'setting' }
 						}
 					]
 				}
@@ -218,12 +219,37 @@ export const constantRoutes: Array<AppRouteRecordRaw> = [
         children: [
             {
                 path: 'https://github.com/LanceJiang/vue3_element_admin',
-                meta: { title: '外部链接', icon: 'link' }
+                meta: { title: '外部链接', icon: 'icon-link' }
             }
         ]
     }*/
 ]
-
+const getFlatMenuList_1children = (menuList: AppRouteRecordRaw[]) => {
+	return menuList.reduce((res, v) => {
+		// 过滤掉隐藏
+		if (v.meta?.hidden) return res
+		const children = v.children
+		if (Array.isArray(children) && children.length) {
+			if (children.length === 1) {
+				const child0 = children[0]
+				// delete v.children
+				res.push({
+					...child0,
+					path: /\/.*/.test(child0.path) ? child0.path : v.name !== 'layout' ? `${v.path}/${child0.path}` : `/${child0.path}`
+				})
+			} else {
+				res.push({
+					...v,
+					children: getFlatMenuList_1children(v.children as AppRouteRecordRaw[])
+				})
+			}
+		} else {
+			res.push(v)
+		}
+		return res
+	}, [] as AppRouteRecordRaw[])
+}
+export const constantMenuList: Array<AppRouteRecordRaw> = getFlatMenuList_1children(constantRoutes)
 export const noFoundRouters = [
 	{
 		// redirect: '/404',
@@ -245,7 +271,7 @@ export const local_permissionsRoutes: Array<AppRouteRecordRaw> = [
 		path: '/setting',
 		// component: Layout,
 		component: 'Layout',
-		meta: { title: '设置', icon: 'guide' },
+		meta: { title: '设置', icon: 'icon-guide' },
 		redirect: '/setting/user',
 		children: [
 			{
@@ -319,7 +345,7 @@ export const local_permissionsRoutes: Array<AppRouteRecordRaw> = [
 		path: '/flow',
 		// component: Layout,
 		component: '',
-		meta: { title: '流程管理', icon: 'guide' },
+		meta: { title: '流程管理', icon: 'icon-guide' },
 		redirect: '/flow/group',
 		children: [
 			{
@@ -372,6 +398,100 @@ export const local_permissionsRoutes: Array<AppRouteRecordRaw> = [
 				meta: { title: '流程模型', icon: '' }
 			}
 		]
+	},
+	{
+		path: '/menu',
+		name: 'menu',
+		component: 'Layout',
+		redirect: '/menu/menu1',
+		meta: {
+			icon: 'List',
+			title: '菜单嵌套'
+		},
+		children: [
+			{
+				path: '/menu/menu1',
+				name: 'menu1',
+				component: 'menu/menu1/index',
+				meta: {
+					icon: 'Menu',
+					title: '菜单1'
+				}
+			},
+			{
+				path: '/menu/menu2',
+				name: 'menu2',
+				redirect: '/menu/menu2/menu21',
+				component: '',
+				meta: {
+					icon: 'Menu',
+					title: '菜单2'
+				},
+				children: [
+					{
+						path: '/menu/menu2/menu21',
+						// path: 'menu21',
+						name: 'menu21',
+						component: 'menu/menu2/menu21/index',
+						meta: {
+							icon: 'Menu',
+							title: '菜单2-1'
+						}
+					},
+					{
+						path: '/menu/menu2/menu22',
+						// path: 'menu22',
+						name: 'menu22',
+						redirect: '/menu/menu2/menu22/menu221',
+						meta: {
+							icon: 'Menu',
+							title: '菜单2-2'
+						},
+						children: [
+							{
+								path: '/menu/menu2/menu22/menu221',
+								// path: 'menu221',
+								name: 'menu221',
+								component: 'menu/menu2/menu22/menu221/index',
+								meta: {
+									icon: 'Menu',
+									title: '菜单2-2-1'
+								}
+							},
+							{
+								path: '/menu/menu2/menu22/menu222',
+								// path: 'menu222',
+								name: 'menu222',
+								component: 'menu/menu2/menu22/menu222/index',
+								meta: {
+									icon: 'Menu',
+									title: '菜单2-2-2'
+								}
+							}
+						]
+					},
+					{
+						path: '/menu/menu2/menu23',
+						// path: 'menu23',
+						name: 'menu23',
+						component: 'menu/menu2/menu23/index',
+						meta: {
+							icon: 'Menu',
+							title: '菜单2-3'
+						}
+					}
+				]
+			},
+			{
+				path: '/menu/menu3',
+				name: 'menu3',
+				component: 'menu/menu3/index',
+				meta: {
+					icon: 'Menu',
+					title: '菜单3'
+				}
+			}
+		]
 	}
 ]
 

+ 19 - 1
src/router/types.ts

@@ -4,10 +4,28 @@ import { defineComponent } from 'vue'
 
 export type Component<T = any> = ReturnType<typeof defineComponent> | (() => Promise<typeof import('*.vue')>) | (() => Promise<T>)
 
+export interface MetaProps {
+	// 标题
+	title?: string
+	// 图标
+	icon?: string
+	// 隐藏菜单
+	hidden?: boolean
+	// 是否固定
+	affix?: boolean
+	// 不缓存路由
+	noCache?: boolean
+
+	// todo???
+	activeMenu?: string
+	// // todo???
+	// roles?: string[]
+}
+// eslint-disable-next-line
 // @ts-ignore
 export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
 	name?: string
-	meta?: RouteMeta
+	meta?: MetaProps
 	component?: Component | string
 	components?: Component
 	children?: AppRouteRecordRaw[]

+ 40 - 11
src/settings.ts

@@ -1,18 +1,47 @@
-interface DefaultSettings {
-	title: string
-	showSettings: boolean
-	tagsView: boolean
-	fixedHeader: boolean
-	sidebarLogo: boolean
-}
+import { SettingState } from '@/types'
 
-const defaultSettings: DefaultSettings = {
-	title: 'vue3_admin',
+const defaultSettingState: SettingState = {
 	showSettings: true,
-	tagsView: true, // 标签展示
+	/** 主题模式 */
+	// 主题颜色
+	themeColor: '#409eff',
+	// 暗黑主题
+	isDark: false,
+	// 侧边栏深色
+	asideInverted: false,
+	// 头部深色
+	headerInverted: false,
+	// 底部深色
+	footerInverted: false,
+	// 展示底部
+	footer: true,
+	/** 布局模式 */
+	// 布局模式 (纵向:vertical | 经典:classic | 横向:transverse | 分栏:columns) // todo...
+	layout: 'classic',
+	/** 界面功能 */
+	// 面包屑
+	breadcrumb: true,
+	// 面包屑图标
+	breadcrumbIcon: true,
+	// 面包屑图标
+	animate: true,
+	// 面包屑图标
+	animateMode: 'fade-slide',
+	// 折叠菜单
+	isCollapse: false,
+	// 标签展示
+	tagsView: true,
+	// todo???
 	fixedHeader: true,
 	// 是否显示Logo
-	sidebarLogo: true
+	sidebarLogo: true,
+	// 手风琴
+	accordion: true
+}
+const defaultSettings = {
+	title: 'vue3_admin',
+
+	...defaultSettingState
 }
 
 export default defaultSettings

+ 63 - 0
src/store/interface/index.ts

@@ -0,0 +1,63 @@
+export type LayoutType = 'vertical' | 'classic' | 'transverse' | 'columns'
+
+export type AssemblySizeType = 'large' | 'default' | 'small'
+
+export type LanguageType = 'zh' | 'en' | null
+
+/* GlobalState */
+/*
+export interface GlobalState {
+	layout: LayoutType
+	assemblySize: AssemblySizeType
+	language: LanguageType
+	maximize: boolean
+	primary: string
+	isDark: boolean
+	isGrey: boolean
+	isWeak: boolean
+	asideInverted: boolean
+	headerInverted: boolean
+	isCollapse: boolean
+	accordion: boolean
+	breadcrumb: boolean
+	breadcrumbIcon: boolean
+	tabs: boolean
+	tabsIcon: boolean
+	footer: boolean
+}
+
+/!* UserState *!/
+export interface UserState {
+	token: string
+	userInfo: { name: string }
+}
+
+/!* tabsMenuProps *!/
+export interface TabsMenuProps {
+	icon: string
+	title: string
+	path: string
+	name: string
+	close: boolean
+	isKeepAlive: boolean
+}
+
+/!* TabsState *!/
+export interface TabsState {
+	tabsMenuList: TabsMenuProps[]
+}
+
+/!* AuthState *!/
+export interface AuthState {
+	routeName: string
+	authButtonList: {
+		[key: string]: string[]
+	}
+	authMenuList: Menu.MenuOptions[]
+}
+
+/!* KeepAliveState *!/
+export interface KeepAliveState {
+	keepAliveName: string[]
+}
+*/

+ 18 - 18
src/store/modules/app.ts

@@ -7,29 +7,29 @@ const useAppStore = defineStore({
 	id: 'app',
 	state: (): AppState => ({
 		device: 'desktop',
-		sidebar: {
-			opened: true,
-			withoutAnimation: false
-		},
+		// sidebar: {
+		// 	opened: true,
+		// 	withoutAnimation: false
+		// },
 		language: getLanguage(),
 		size: 'default'
 		// size: 'small'
 	}),
 	actions: {
-		toggleSidebar() {
-			this.sidebar.opened = !this.sidebar.opened
-			this.sidebar.withoutAnimation = false
-			// if (this.sidebar.opened) {
-			// 	ls.set('sidebarStatus', 1)
-			// } else {
-			// 	ls.set('sidebarStatus', 0)
-			// }
-		},
-		closeSideBar(withoutAnimation: any) {
-			// ls.set('sidebarStatus', 0)
-			this.sidebar.opened = false
-			this.sidebar.withoutAnimation = withoutAnimation
-		},
+		// toggleSidebar() {
+		// 	this.sidebar.opened = !this.sidebar.opened
+		// 	this.sidebar.withoutAnimation = false
+		// 	// if (this.sidebar.opened) {
+		// 	// 	ls.set('sidebarStatus', 1)
+		// 	// } else {
+		// 	// 	ls.set('sidebarStatus', 0)
+		// 	// }
+		// },
+		// closeSideBar(withoutAnimation: any) {
+		// 	// ls.set('sidebarStatus', 0)
+		// 	this.sidebar.opened = false
+		// 	this.sidebar.withoutAnimation = withoutAnimation
+		// },
 		toggleDevice(device: string) {
 			this.device = device
 		},

+ 71 - 31
src/store/modules/permission.ts

@@ -2,11 +2,14 @@ import { PermissionState } from '@/types'
 import { AppRouteRecordRaw } from '@/router/types'
 // import { RouteRecordRaw } from 'vue-router'
 import { defineStore } from 'pinia'
-import { constantRoutes, noFoundRouters } from '@/router'
-import { listRoutes } from '@/api/system/menu'
+import { constantRoutes, noFoundRouters, constantMenuList } from '@/router'
+import { getMenuList } from '@/api/system/menu'
 
-const modules = import.meta.glob('@/views/**/**.vue')
-export const Layout = () => import('@/layout/index.vue')
+const modules = import.meta.glob('@/views/**/*.vue')
+export const Layout = () => import('@/layout/index.vue') // todo...
+// export const Layout = () => import('@/layout/index_old.vue.vue')
+// export const Test = () => import('@/layout/test.vue')
+// export const Layout = () => import('@/layouts/index.vue')
 
 // const hasPermission = (roles: string[], route: AppRouteRecordRaw) => {
 // 	if (route.meta && route.meta.roles) {
@@ -26,62 +29,99 @@ export const filterAsyncRoutes = (routes: AppRouteRecordRaw[], roles: string[])
 	const res: AppRouteRecordRaw[] = []
 	routes.forEach(route => {
 		const tmp = { ...route } as any
-		tmp.meta = tmp.meta ? tmp.meta : {}
 		// if (hasPermission(roles, tmp)) {
 		// todo be delete
 		// }
+		// console.warn(tmp.component, 'tmp.component')
 		// 特殊Layout 配置 标识
-		if (!tmp.component || tmp.component === 'Layout') {
+		/*if (!tmp.component) {
+			tmp.component = Test
+		} else*/ if (!tmp.component || tmp.component === 'Layout') {
 			tmp.component = Layout
 		} else {
 			const component = modules[`/src/views/${tmp.component}.vue`] as any
-			// console.error(component, 'component....')
+			console.error(component, 'component')
 			if (component) {
 				tmp.component = component
 			} else {
 				tmp.component = modules[`/src/views/error-page/404.vue`]
 			}
 		}
-		/*if (tmp.component) {
-			const component = modules[`/src/views/${tmp.component}.vue`] as any
-			if (component) {
-				tmp.component = component
-			} else {
-				tmp.component = modules[`/src/views/error-page/404.vue`]
-			}
-		}*/
-		tmp.path && res.push(tmp)
+		res.push(tmp)
 
 		// 递归
 		if (tmp.children) {
 			tmp.children = filterAsyncRoutes(tmp.children, roles)
 		}
 	})
-	// console.log(res, 'res........')
 	return res
 }
 
+// 过滤隐藏
+const getShowMenuList = (menuList: AppRouteRecordRaw[], parentPath = '') => {
+	return menuList.filter(item => {
+		// console.error(item, 'item')
+		item.meta = item.meta ? item.meta : {}
+		// path全链 重组
+		item.path = /\/.*/.test(item.path) ? item.path : `${parentPath}/${item.path}`
+		if (!item.meta.hidden) {
+			item.children?.length && (item.children = getShowMenuList(item.children, item.path))
+			return true
+		}
+		return false
+	})
+}
+
 const usePermissionStore = defineStore({
 	id: 'permission',
 	state: (): PermissionState => ({
 		routes: [],
-		addRoutes: []
+		// 动态菜单
+		menuList: []
 	}),
+	getters: {
+		// 有效的 菜单列表
+		showMenuList: state => getShowMenuList(JSON.parse(JSON.stringify([...constantMenuList, ...state.menuList])))
+		/*getShowMenuList([
+				...JSON.parse(JSON.stringify(state.menuList)),
+				// 测试
+				{
+					path: 'dashboard',
+					// path: HOME_URL,
+					component: () => import('@/views/dashboard/index.vue'),
+					name: 'dashboard',
+					meta: { title: 'dashboard', icon: 'icon-homepage', affix: true }
+				}
+			])*/
+		// showMenuList: state => getShowMenuList(JSON.parse(JSON.stringify(state.routes)))
+	},
 	actions: {
-		setRoutes(routes: AppRouteRecordRaw[]) {
-			this.addRoutes = routes
-			this.routes = constantRoutes.concat(routes, noFoundRouters)
+		setRoutes(menuList: AppRouteRecordRaw[]) {
+			// 授权后的菜单列表
+			this.menuList = menuList
+			this.routes = constantRoutes.concat(
+				menuList,
+				noFoundRouters /*, {
+				// 管理员管理
+				path: '/adminManage1',
+				name: 'adminManage1',
+				// component: 'demo/adminManage/index',
+				// component: 'demo/adminManage/index',
+				component: modules[`/src/views/${'demo/adminManage/index'}.vue`],
+				// const  = modules[`/src/views/${tmp.component}.vue`] as any
+				// if (component) {
+				// 	tmp.component = component
+				meta: { title: 'demo_adminManage' }
+			}*/
+			)
 		},
-		generateRoutes(roles: string[]) {
-			return listRoutes().then((data: any) => {
-				// 过滤掉顶级菜单组 没有/path 的数据(避免报错)
-				const accessedRoutes = filterAsyncRoutes(
-					data.menu.filter((v: any) => /\/.*/.test(v.path)),
-					roles
-				)
-				// console.error(accessedRoutes, 'accessedRoutes')
-				this.setRoutes(accessedRoutes)
-				return accessedRoutes
+		queryMenuList(roles: string[]) {
+			return getMenuList().then((data: any) => {
+				console.error(data, 'menuList')
+				const menuList = filterAsyncRoutes(data!.menu.filter((v: any) => /\/.*/.test(v.path)), roles)
+				console.warn(menuList, data, 'xxxxxxxxxxx')
+				this.setRoutes(menuList)
+				return menuList
 			})
 		}
 	}

+ 35 - 29
src/store/modules/settings.ts

@@ -1,45 +1,51 @@
 import { defineStore } from 'pinia'
 import { SettingState } from '@/types'
 import defaultSettings from '@/settings'
-import { ls } from '@/utils'
+// import { ls } from '@/utils'
 
-const { showSettings, tagsView, fixedHeader, sidebarLogo } = defaultSettings
-const el = document.documentElement
+// const { layout, themeColor, isDark, showSettings, tagsView, fixedHeader, sidebarLogo } = defaultSettings
 
 export const useSettingStore = defineStore({
 	id: 'setting',
-	state: (): SettingState => ({
-		theme: ls.get('theme') || getComputedStyle(el).getPropertyValue(`--el-color-primary`),
-		showSettings: showSettings,
-		tagsView: ls.get('tagsView') != null ? ls.get('tagsView') : tagsView,
-		fixedHeader: fixedHeader,
-		sidebarLogo: sidebarLogo
-	}),
+	state: (): SettingState => defaultSettings,
+	// state: (): SettingState => ({
+	// 	layout,
+	// 	themeColor,
+	// 	isDark,
+	// 	showSettings,
+	// 	tagsView,
+	// 	fixedHeader,
+	// 	sidebarLogo
+	// }),
 	actions: {
-		async changeSetting(payload: { key: string; value: any }) {
-			const { key, value } = payload
+		async changeSetting(...payload: [string, any]) {
+			const [key, value] = payload
 			switch (key) {
-				case 'theme':
-					this.theme = value
-					break
-				case 'showSettings':
-					this.showSettings = value
-					break
-				case 'fixedHeader':
-					this.fixedHeader = value
-					break
-				case 'tagsView':
-					this.tagsView = value
-					ls.set('tagsView', value)
-					break
-				case 'sidebarLogo':
-					this.sidebarLogo = value
-					break
+				// case 'tagsView':
+				// 	this.tagsView = value
+				// 	// ls.set('tagsView', value)
+				// 	break
+				// case 'themeColor':
+				// 	this.themeColor = value
+				// 	break
+				// case 'showSettings':
+				// 	this.showSettings = value
+				// 	break
+				// case 'fixedHeader':
+				// 	this.fixedHeader = value
+				// 	break
+				// case 'sidebarLogo':
+				// 	this.sidebarLogo = value
+				// 	break
 				default:
+					// eslint-disable-next-line
+					// @ts-ignore
+					this[key] = value
 					break
 			}
 		}
-	}
+	},
+	persist: true
 })
 
 export default useSettingStore

+ 31 - 0
src/styles/element-plus-dark.scss

@@ -0,0 +1,31 @@
+/* 自定义 element 暗黑主题 */
+html.dark {
+  /* wangEditor */
+  --w-e-toolbar-color: #eeeeee;
+  --w-e-toolbar-bg-color: #141414;
+  --w-e-textarea-bg-color: #141414;
+  --w-e-textarea-color: #eeeeee;
+  --w-e-toolbar-active-bg-color: #464646;
+  --w-e-toolbar-border-color: var(--el-border-color-darker);
+  .w-e-bar-item button:hover,
+  .w-e-menu-tooltip-v5::before {
+    color: #eeeeee;
+  }
+
+  /* login */
+  /*.login-container {
+    background-color: #191919 !important;
+    .login-box {
+      background-color: rgb(0 0 0 / 80%) !important;
+      .login-form {
+        box-shadow: rgb(255 255 255 / 12%) 0 2px 10px 2px !important;
+        .logo-text {
+          color: var(--el-text-color-primary) !important;
+        }
+      }
+    }
+  }*/
+	.el-table {
+		//--el-table-header-bg-color: #f6faff;
+	}
+}

+ 14 - 5
src/styles/element-plus.scss

@@ -1,9 +1,10 @@
 :root {
-	// 这里可以设置你自定义的颜色变量
-	// 这个是element主要按钮:active的颜色,当主题更改后此变量的值也随之更改
-	--el-color-primary-dark: #0d84ff;
-	// element plus 2.1.0 禁用文本色值和正常文本色值无法区分问题
-	--el-text-color-disabled: #ccc;
+	//// 这里可以设置你自定义的颜色变量
+	//// 这个是element主要按钮:active的颜色,当主题更改后此变量的值也随之更改
+	//--el-color-primary-dark: #0d84ff;
+	//// element plus 2.1.0 禁用文本色值和正常文本色值无法区分问题
+	//--el-text-color-disabled: #ccc;
+	//--el-color-success: #03b497;
 }
 
 // 覆盖 element-plus 的样式
@@ -49,3 +50,11 @@
 .el-table__header col[name='gutter'] {
 	display: table-cell !important;
 }
+.el-table {
+	&.el-table--enable-row-transition .el-table__body td.el-table__cell {
+		//background-color: var(--el-bg-color);
+		transition: unset;
+	}
+	//--el-table-header-bg-color: #f6faff;
+	background-color: var(--el-bg-color);
+}

+ 5 - 4
src/styles/index.scss

@@ -1,4 +1,5 @@
 @import 'src/styles/variables.module';
+@import 'src/styles/element-plus-dark';
 @import './mixin.scss';
 @import './transition.scss';
 @import 'src/styles/element-plus';
@@ -9,8 +10,6 @@
 @import './lance-element-ui.scss';
 // 涉及lance-element-ui的全局组件样式
 @import './lance-element-vue.scss';
-// 加入布局样式
-@import "./flex.scss";
 body {
 	margin: 0;
 	padding: 0;
@@ -79,7 +78,7 @@ div:focus {
 }
 ::-webkit-scrollbar-track {
 	border-radius: 4px;
-	background: #fafafa;
+	background: $le-bg-color_1;
 }
 ::-webkit-scrollbar-track:hover {
 	background-color: rgba(0, 0, 0, 0.06);
@@ -106,7 +105,9 @@ div:focus {
 	flex: 1;
 	display: flex;
 	height: 100%;
+	min-height: 0;
 	flex-direction: column;
+	background-color: var(--el-bg-color-page);
 }
 // 文字超出隐藏
 .text-overflow_ellipsis {
@@ -130,4 +131,4 @@ div:focus {
 // table下 按钮集合的外壳样式
 .#{$prefix}button-wrap {
 	padding-bottom: 12px;
-}
+}

+ 131 - 130
src/styles/lance-element-ui.scss

@@ -3,137 +3,139 @@
 // 覆盖默认的 element-plus 样式
 
 .el-form-item__label {
-  color: $le-color_1;
+	color: $le-color_1;
+	//color: var(--el-text-color-regular);
 }
 
 // dialog覆盖样式
 // <el-dialog class="le-dialog"/>
 %el-dialog {
-  position: absolute;
-  margin: 0 !important;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
-  overflow: hidden;
-  border-radius: 6px;
-  //max-height: calc(100% - 48px);
-  max-height: calc(100% - 24px);
-  max-width: calc(100% - 24px);
+	position: absolute;
+	margin: 0 !important;
+	top: 50%;
+	left: 50%;
+	transform: translate(-50%, -50%);
+	overflow: hidden;
+	border-radius: 6px;
+	//max-height: calc(100% - 48px);
+	max-height: calc(100% - 24px);
+	max-width: calc(100% - 24px);
 }
 .#{$prefix}dialog {
-  &.el-dialog {
-    @extend %el-dialog;
-  }
+	&.el-dialog {
+		@extend %el-dialog;
+	}
 
-  .el-dialog__header {
-    display: flex;
-    padding: 16px 24px;
-    align-items: center;
-    justify-content: space-between;
-    background: #E3E9F1;
-    text-align: left;
+	.el-dialog__header {
+		display: flex;
+		padding: 16px 24px;
+		align-items: center;
+		justify-content: space-between;
+		//background: #E3E9F1;
+		background-color: var(--el-color-info-light-9);
+		text-align: left;
 		margin-right: 0;
-    .el-dialog__title {
-      //color: $le-color_1;
-    }
-  }
-  .el-dialog__headerbtn {
-    position: unset;
+		.el-dialog__title {
+			//color: $le-color_1;
+		}
+	}
+	.el-dialog__headerbtn {
+		position: unset;
 		width: 16px;
 		height: 16px;
-  }
+	}
 
-  .el-dialog__body {
-    //height: auto;
-    max-height: calc(100vh - 160px);
-    overflow-y: auto;
-    padding: 24px;
-    color: $le-color_1;
-  }
+	.el-dialog__body {
+		//height: auto;
+		max-height: calc(100vh - 160px);
+		overflow-y: auto;
+		padding: 24px;
+		color: $le-color_1;
+	}
 
-  .el-dialog__footer {
-    padding: 8px 24px 11px 24px;
-    border-top: 1px solid $le-border-color_3;
-  }
+	.el-dialog__footer {
+		padding: 8px 24px 11px 24px;
+		border-top: 1px solid $le-border-color_3;
+	}
 }
 
 // input-number 样式覆盖(右侧)
 .el-input-number {
-  &.is-controls-right {
-    &.is-disabled {
-      .el-input-number__decrease,
-      .el-input-number__increase {
-        &:hover {
-          color: $le-color_5;
-        }
-        color: $le-color_5;
-      }
-    }
-    .el-input-number__decrease,
-    .el-input-number__increase {
-      border: none !important;
-      background-color: transparent;
-      width: 17px;
-      color: $le-color_4;
-      &:hover {
-        color: $le-color-primary;
-      }
-      &.is-disabled {
-        color: $le-color_5;
-        cursor: not-allowed;
-      }
-    }
-    .el-input .el-input__wrapper {
-      padding: 0 20px 0 8px;
-    }
-    .el-input__inner {
+	&.is-controls-right {
+		&.is-disabled {
+			.el-input-number__decrease,
+			.el-input-number__increase {
+				&:hover {
+					color: $le-color_5;
+				}
+				color: $le-color_5;
+			}
+		}
+		.el-input-number__decrease,
+		.el-input-number__increase {
+			border: none !important;
+			background-color: transparent;
+			width: 17px;
+			color: $le-color_4;
+			&:hover {
+				color: $le-color-primary;
+			}
+			&.is-disabled {
+				color: $le-color_5;
+				cursor: not-allowed;
+			}
+		}
+		.el-input .el-input__wrapper {
+			padding: 0 20px 0 8px;
+		}
+		.el-input__inner {
 			--el-input-inner-height: calc(var(--el-input-height, 32px));
-      text-align: left;
-    }
-  }
+			text-align: left;
+		}
+	}
 }
 
 // 针对 el-button-group 无法兼容popover内嵌 el-button 的处理
 // eg: 内嵌 adPopover(el-popover) reference 为 Button  的 兼容
 .el-button-group {
-  > .#{$prefix}popover-box {
-    //& > .el-popover__reference-wrapper > .el-button {
-    //}
-    & > .el-popover__reference-wrapper > .el-button {
-      border-radius: 6px;
-      padding: 10px;
-    }
-    &:first-child {
-      .el-popover__reference-wrapper > .el-button {
-        border-top-right-radius: 0;
-        border-bottom-right-radius: 0;
-      }
-    }
+	> .#{$prefix}popover-box {
+		//& > .el-popover__reference-wrapper > .el-button {
+		//}
+		& > .el-popover__reference-wrapper > .el-button {
+			border-radius: 6px;
+			padding: 10px;
+		}
+		&:first-child {
+			.el-popover__reference-wrapper > .el-button {
+				border-top-right-radius: 0;
+				border-bottom-right-radius: 0;
+			}
+		}
 
-    &:last-child {
-      .el-popover__reference-wrapper > .el-button {
-        border-top-left-radius: 0;
-        border-bottom-left-radius: 0;
-      }
-    }
+		&:last-child {
+			.el-popover__reference-wrapper > .el-button {
+				border-top-left-radius: 0;
+				border-bottom-left-radius: 0;
+			}
+		}
 
-    &:first-child:last-child {
-      .el-popover__reference-wrapper > .el-button {
-        border-radius: 6px;
-      }
-    }
-    &:not(:first-child) {
-      .el-popover__reference-wrapper > .el-button {
-        margin-left: -1px;
-      }
-    }
-    &:not(:first-child):not(:last-child) {
-      .el-popover__reference-wrapper > .el-button {
-        margin-left: -1px;
-        border-radius: 0;
-      }
-    }
-  }
+		&:first-child:last-child {
+			.el-popover__reference-wrapper > .el-button {
+				border-radius: 6px;
+			}
+		}
+		&:not(:first-child) {
+			.el-popover__reference-wrapper > .el-button {
+				margin-left: -1px;
+			}
+		}
+		&:not(:first-child):not(:last-child) {
+			.el-popover__reference-wrapper > .el-button {
+				margin-left: -1px;
+				border-radius: 0;
+			}
+		}
+	}
 }
 
 // table表格样式覆盖
@@ -144,28 +146,28 @@
 }*/
 // 按钮样式覆盖
 .el-button {
-  font-family: $le-family;
-  font-weight: normal;
-  border-radius: 6px;
-  &--medium {
-    border-radius: 6px;
-  }
+	font-family: $le-family;
+	font-weight: normal;
+	border-radius: 6px;
+	&--medium {
+		border-radius: 6px;
+	}
 
-  &.is-disabled.is-plain {
-    border-color: #B3D5FE;
-    color: #B3D5FE;
-    &:hover, &:focus{
-      border-color: #B3D5FE;
-      color: #B3D5FE;
-    }
-  }
+	&.is-disabled.is-plain {
+		border-color: #B3D5FE;
+		color: #B3D5FE;
+		&:hover, &:focus{
+			border-color: #B3D5FE;
+			color: #B3D5FE;
+		}
+	}
 
-  // 若需要重置样式 todo...
-  //&:focus {
-  //  color: $le-color_1;
-  //  border-color: $le-border-color_1;
-  //  background-color: inherit;
-  //}
+	// 若需要重置样式 todo...
+	//&:focus {
+	//  color: $le-color_1;
+	//  border-color: $le-border-color_1;
+	//  background-color: inherit;
+	//}
 }
 // input 输入框
 /*.el-input__inner, .el-input .el-input__inner {
@@ -174,8 +176,7 @@
 
 // 文字提示
 .el-tooltip__popper {
-  max-width: 400px;
-  //max-width: 20%;
-  word-break: break-word;
+	max-width: 400px;
+	//max-width: 20%;
+	word-break: break-word;
 }
-

+ 76 - 75
src/styles/lance-element-vue.scss

@@ -26,101 +26,102 @@
 
 // 纯icon button 样式
 .#{$prefix}icon-button {
-  //font-size: 16px;
-  font-size: 14px;
-  padding: 0;
-  border-radius: 6px;
-  font-weight: 400;
-  height: 32px;
-  width: 32px;
-  display: inline-flex;
-  justify-content: center;
-  align-items: center;
-  border: none;
-  color: $le-color_3;
-  background: transparent;
-  &:hover {
-    background: $le-border-color_3;
-    color: $le-color_3;
-  }
+	//font-size: 16px;
+	font-size: 14px;
+	padding: 0;
+	border-radius: 6px;
+	font-weight: 400;
+	height: 32px;
+	width: 32px;
+	display: inline-flex;
+	justify-content: center;
+	align-items: center;
+	border: none;
+	color: $le-color_3;
+	background: transparent;
+	&:hover {
+		background: $le-border-color_3;
+		color: $le-color_3;
+	}
 }
 
 // 带搜索icon 的input
 .#{$prefix}input-search {
-  min-width: 60px;
-  flex: 1;
-  margin-right: 12px;
-  .el-input__suffix {
-    top: 1px;
-    height: calc(100% - 2px);
-  }
-  .el-input__icon {
-    cursor: pointer;
-    color: $le-color_1;
-    background: #fff;
-    &:hover {
-      color: $le-color-primary;
-    }
-  }
+	min-width: 60px;
+	flex: 1;
+	margin-right: 12px;
+	.el-input__suffix {
+		top: 1px;
+		height: calc(100% - 2px);
+	}
+	.el-input__icon {
+		cursor: pointer;
+		color: $le-color_1;
+		background: #fff;
+		&:hover {
+			color: $le-color-primary;
+		}
+	}
 }
 
 // 公用 触发 提示 的 tag(el-tag) 样式
 .#{$prefix}trigger-tag {
-  border: 0;
-  background: #f0f4f8;
-  height: 22px;
-  padding: 0 8px;
-  line-height: 22px;
-  //cursor: pointer;
+	border: 0;
+	background: #f0f4f8;
+	height: 22px;
+	padding: 0 8px;
+	line-height: 22px;
+	//cursor: pointer;
 }
 
 // card卡片样式
 .#{$prefix}card,
 .#{$prefix}card-bg {
-  display: flex;
-  flex-direction: column;
-  .el-card__header {
-    position: relative;
-    padding: 12px 24px;
-    line-height: 20px;
-    font-size: 14px;
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    border-radius: 6px 6px 0 0;
-    border-bottom: 1px solid $le-border-color_3;
+	display: flex;
+	flex-direction: column;
+	.el-card__header {
+		position: relative;
+		padding: 12px 24px;
+		line-height: 20px;
+		font-size: 14px;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		border-radius: 6px 6px 0 0;
+		border-bottom: 1px solid $le-border-color_3;
 
-    &::before {
-      display: none;
-      content: '';
-      position: absolute;
-      width: 4px;
-      height: 16px;
-      left: 0;
-      border-radius: 4px;
-      background-color: $le-color-primary;
-    }
-  }
-  .el-card__body {
-    padding: 16px 24px;
-    flex: 1;
-  }
+		&::before {
+			display: none;
+			content: '';
+			position: absolute;
+			width: 4px;
+			height: 16px;
+			left: 0;
+			border-radius: 4px;
+			background-color: $le-color-primary;
+		}
+	}
+	.el-card__body {
+		padding: 16px 24px;
+		flex: 1;
+	}
 }
 // header 带有背景色的 和 before 条样式
 .#{$prefix}card-bg {
-  .el-card__header {
-    background-color: $le-bg-color_3;
-    &::before {
-      display: block;
-    }
-  }
+	.el-card__header {
+		//background-color: $le-bg-color_3;
+		//background-color: $le-bg-color_3;
+		&::before {
+			display: block;
+		}
+	}
 }
 // $confirm 警示类型公用样式
 .#{$prefix}message-box--warn {
-  .el-message-box__btns button:last-child {
-    border-color: $le-color-warning;
-    background: $le-color-warning;
-  }
+	.el-message-box__btns button:last-child {
+		border-color: $le-color-warning;
+		background: $le-color-warning;
+	}
 }
 
 .#{$prefix}test {}

+ 91 - 90
src/styles/lance-element/draggableNest.scss

@@ -1,93 +1,94 @@
 @import "../variables.scss";
 .#{$prefix}draggable-nest {
-  .flip-list {
-    transition: all 0.3s ease-in-out;
-  }
-  .flip-list-move {
-    transition: transform 0.3s;
-  }
-  //.no-move {
-  //  transition: transform 0s;
-  //}
-  // 拖动时候的样式
-  .ghost {
-    //opacity: 0.5;
-    //background: rgba(87, 129, 244, .2);
-    //box-shadow: 1px 1px 5px 2px rgb(0 0 0 / 15%);
-    //cursor: move;
-    //box-shadow: #007bfc 0 0 6px -2px inset;
-    //background: #f00;
-    box-shadow: 1px 1px 5px 2px rgba(0,0,0,.15);
-    cursor: move;
-    transition: .18s ease;
-  }
-  .chosen {
-    box-shadow: 1px 1px 5px 2px rgba(0,0,0,.15);
-  }
-  flex: 1;
-  width: 100%;
-  overflow-y: auto;
-  &-item {
-    padding-left: 12px;
-    &:hover {
-      background: $le-hover-color_1;
-    }
-    .itemWrap {
-      display: flex;
-      align-items: center;
-      line-height: 34px;
-      padding-right: 4px;
-      color: #333;
-      font-size: 14px;
-      cursor: pointer;
-      //transition: .18s ease;
-      width: 100%;
-      // 不能拖动 不能删除
-      &.disabled {
-        color: rgba(0, 0, 0, 0.25);
-        cursor: not-allowed;
-        &.ghost {
-          background: unset;
-        }
-        & > .dragEl {
-          cursor: not-allowed;
-        }
-        opacity: unset;
-        box-shadow: none;
-        .disabled_fixed {
-          width: 28px;
-          margin-left: auto;
-          cursor: pointer;
-          color: #007bfc;
-        }
-      }
-      .dragEl {
-        font-size: 16px;
-        cursor: move;
-        margin-right: 8px;
-      }
-      .label_txt{
-        display: inline-block;
-        padding-left: 10px;
-        flex: 1;
-        //width: 80%;
-        white-space: nowrap;
-        overflow: hidden;
-        text-overflow: ellipsis;
-      }
-      & > .delEl {
-        display: none;
-        margin-left: auto;
-      }
-      &:not(.disabled):hover {
-        color: #007bfc;
-        & > .delEl {
-          display: inline-block;
-        }
-      }
-    }
-    .checkbox-hide {
-      visibility: hidden;
-    }
-  }
+	.flip-list {
+		transition: all 0.3s ease-in-out;
+	}
+	.flip-list-move {
+		transition: transform 0.3s;
+	}
+	//.no-move {
+	//  transition: transform 0s;
+	//}
+	// 拖动时候的样式
+	.ghost {
+		//opacity: 0.5;
+		//background: rgba(87, 129, 244, .2);
+		//box-shadow: 1px 1px 5px 2px rgb(0 0 0 / 15%);
+		//cursor: move;
+		//box-shadow: #007bfc 0 0 6px -2px inset;
+		//background: var(--el-color-danger);
+		box-shadow: 1px 1px 5px 2px rgba(0,0,0,.15);
+		cursor: move;
+		transition: .18s ease;
+	}
+	.chosen {
+		box-shadow: 1px 1px 5px 2px rgba(0,0,0,.15);
+	}
+	flex: 1;
+	width: 100%;
+	overflow-y: auto;
+	&-item {
+		padding-left: 12px;
+		&:hover {
+			background: $le-hover-color_1;
+		}
+		.itemWrap {
+			display: flex;
+			align-items: center;
+			line-height: 34px;
+			padding-right: 4px;
+			//color: #333;
+			color: var(--el-text-color-regular);
+			font-size: 14px;
+			cursor: pointer;
+			//transition: .18s ease;
+			width: 100%;
+			// 不能拖动 不能删除
+			&.disabled {
+				color: rgba(0, 0, 0, 0.25);
+				cursor: not-allowed;
+				&.ghost {
+					background: unset;
+				}
+				& > .dragEl {
+					cursor: not-allowed;
+				}
+				opacity: unset;
+				box-shadow: none;
+				.disabled_fixed {
+					width: 28px;
+					margin-left: auto;
+					cursor: pointer;
+					color: #007bfc;
+				}
+			}
+			.dragEl {
+				font-size: 16px;
+				cursor: move;
+				margin-right: 8px;
+			}
+			.label_txt{
+				display: inline-block;
+				padding-left: 10px;
+				flex: 1;
+				//width: 80%;
+				white-space: nowrap;
+				overflow: hidden;
+				text-overflow: ellipsis;
+			}
+			& > .delEl {
+				display: none;
+				margin-left: auto;
+			}
+			&:not(.disabled):hover {
+				color: #007bfc;
+				& > .delEl {
+					display: inline-block;
+				}
+			}
+		}
+		.checkbox-hide {
+			visibility: hidden;
+		}
+	}
 }

+ 53 - 51
src/styles/lance-element/formConfig.scss

@@ -1,65 +1,67 @@
 @import "../variables.scss";
 // FormConfig 样式
 .#{$prefix}form-config {
-  .form_wrap {
-    // 弹窗内有需要自己修改对应的样式控制
-    //max-height: 70vh;
-    //overflow-y: auto;
-    margin-bottom: 8px;
-  }
+	.form_wrap {
+		// 弹窗内有需要自己修改对应的样式控制
+		//max-height: 70vh;
+		//overflow-y: auto;
+		margin-bottom: 8px;
+	}
 
-  .el-form-item {
-    margin-bottom: 16px;
+	.el-form-item {
+		margin-bottom: 16px;
 		/* 隐藏formItem label */
 		&.hideLabel {
 			.el-form-item__label {
 				display: none;
 			}
 		}
-  }
-  .footer {
-    display: flex;
-    //justify-content: flex-end;
-    width: calc(100% + 48px);
-    margin-left: -24px;
-    padding: 10px 24px 0 24px;
-    border-top: 1px solid $le-border-color_3;
-    // 取消
-    .cancel-button {
-      //margin-right: auto;
-    }
-    // 重置
-    .reset-button {}
-    // 提交
-    .submit-button {}
-    .right-actions {
-      margin-left: auto;
-    }
-  }
+	}
+	.footer {
+		display: flex;
+		//justify-content: flex-end;
+		width: calc(100% + 48px);
+		margin-left: -24px;
+		padding: 10px 24px 0 24px;
+		//box-shadow: 1px 0 0 0 var(--el-input-border-color, var(--el-border-color));
+		//box-shadow: 0 -1px -1px 0 $le-border-color_3;
+		box-shadow: 0 -1px 0 -1px var(--el-border-color-lighter);
+		// 取消
+		.cancel-button {
+			//margin-right: auto;
+		}
+		// 重置
+		.reset-button {}
+		// 提交
+		.submit-button {}
+		.right-actions {
+			margin-left: auto;
+		}
+	}
 }
 // FormConfigDialog 样式 (区别于 le-dialog 样式以外的)
 .#{$prefix}form-config-dialog {
-  .el-dialog__body {
-    //height: auto;
-    padding-top: 10px;
-    padding-bottom: 10px;
-  }
-  .#{$prefix}form-config {
-    &--small {
-      .form_wrap {
-        // (el-dialog__body 的 maxHeight) - (el-dialog__body 的 padding-top)- (footer 的高度)
-        // small
-        max-height: calc(100vh - 160px - 53px - 20px);
-      }
-    }
-    // 默认 medium 尺寸
-    .form_wrap {
-      // 弹窗内有需要自己修改对应的样式控制
-      // (el-dialog__body 的 maxHeight) - (el-dialog__body 的 padding-top)- (footer 的高度)
-      // medium
-      max-height: calc(100vh - 160px - 57px - 20px);
-      overflow-y: auto;
-      overflow-x: hidden;
-    }
-  }
+	.el-dialog__body {
+		//height: auto;
+		padding-top: 10px;
+		padding-bottom: 10px;
+	}
+	.#{$prefix}form-config {
+		&--small {
+			.form_wrap {
+				// (el-dialog__body 的 maxHeight) - (el-dialog__body 的 padding-top)- (footer 的高度)
+				// small
+				max-height: calc(100vh - 160px - 53px - 20px);
+			}
+		}
+		// 默认 medium 尺寸
+		.form_wrap {
+			// 弹窗内有需要自己修改对应的样式控制
+			// (el-dialog__body 的 maxHeight) - (el-dialog__body 的 padding-top)- (footer 的高度)
+			// medium
+			max-height: calc(100vh - 160px - 57px - 20px);
+			overflow-y: auto;
+			overflow-x: hidden;
+		}
+	}
 }

+ 114 - 114
src/styles/lance-element/inputNumber.scss

@@ -1,131 +1,131 @@
 @import "../variables.scss";
 // InputNumber 样式
 .#{$prefix}input-number {
-  //position: relative;
-  //font-size: 14px;
-  //font-variant: tabular-nums;
-  //display: table;
-  display: flex;
-  align-items: stretch;
-  box-sizing: border-box;
-  color: $le-color_1;
-  width: 150px;
-  &.rate100 {
-    width: 100%;
-  }
-  &--prefix {
-    .el-input-number {
-      .el-input__wrapper {
-        border-top-left-radius: 0;
-        border-bottom-left-radius: 0;
-        //border: 1px solid $le-border-color_1;
-        //border-left-width: 0;
-      }
-    }
-  }
-  &--suffix {
-    .el-input-number {
-      .el-input__wrapper {
-        border-top-right-radius: 0;
-        border-bottom-right-radius: 0;
-        //border: 1px solid $le-border-color_1;
-        //border-right-width: 0;
-      }
-    }
-  }
-  .el-input-number {
-    //display: table-cell; // todo
-    width: 100%;
-    flex: 1;
+	//position: relative;
+	//font-size: 14px;
+	//font-variant: tabular-nums;
+	//display: table;
+	display: flex;
+	align-items: stretch;
+	box-sizing: border-box;
+	color: $le-color_1;
+	width: 150px;
+	&.rate100 {
+		width: 100%;
+	}
+	&--prefix {
+		.el-input-number {
+			.el-input__wrapper {
+				border-top-left-radius: 0;
+				border-bottom-left-radius: 0;
+				//border: 1px solid $le-border-color_1;
+				//border-left-width: 0;
+			}
+		}
+	}
+	&--suffix {
+		.el-input-number {
+			.el-input__wrapper {
+				border-top-right-radius: 0;
+				border-bottom-right-radius: 0;
+				//border: 1px solid $le-border-color_1;
+				//border-right-width: 0;
+			}
+		}
+	}
+	.el-input-number {
+		//display: table-cell; // todo
+		width: 100%;
+		flex: 1;
 		//height: fit-content;
-    /* float: left; */
-    //margin-bottom: 0;
-    //text-align: inherit;
-    /*.el-input__wrapper {
-      //border-radius: 6px;
-      //border: 1px solid $le-border-color_1;
-    }*/
+		/* float: left; */
+		//margin-bottom: 0;
+		//text-align: inherit;
+		/*.el-input__wrapper {
+			//border-radius: 6px;
+			//border: 1px solid $le-border-color_1;
+		}*/
 		&.is-controls-right {
 			[class*=decrease], [class*=increase] {
 				height: 50%;
 			}
 		}
-  }
-  .le-addon {
-    position: relative;
-    //display: table-cell;
-    display: flex;
-    align-items: center;
-    width: unset;
-    padding: 0 10px;
-    color: $le-color_1;
-    //font-weight: 400;
-    //font-size: 14px;
-    text-align: center;
-    //background-color: #fafafa;
-    //background-color: $le-border-color_3;
-    transition: all .3s;
+	}
+	.le-addon {
+		position: relative;
+		//display: table-cell;
+		display: flex;
+		align-items: center;
+		width: unset;
+		padding: 0 10px;
+		color: $le-color_1;
+		//font-weight: 400;
+		//font-size: 14px;
+		text-align: center;
+		//background-color: $le-bg-color_3;
+		//background-color: $le-border-color_3;
+		transition: all .3s;
 		line-height: initial; // todo
 
-    &.le-input-number__prefix {
-      border-top-left-radius: 4px;
-      border-bottom-left-radius: 4px;
-      border: 1px solid $le-border-color_1;
-      border-right-width: 0;
-    }
-    &.le-input-number__suffix {
-      border-top-right-radius: 4px;
-      border-bottom-right-radius: 4px;
-      border: 1px solid $le-border-color_1;
-      border-left-width: 0;
-    }
-  }
+		&.le-input-number__prefix {
+			border-top-left-radius: 4px;
+			border-bottom-left-radius: 4px;
+			border: 1px solid $le-border-color_1;
+			border-right-width: 0;
+		}
+		&.le-input-number__suffix {
+			border-top-right-radius: 4px;
+			border-bottom-right-radius: 4px;
+			border: 1px solid $le-border-color_1;
+			border-left-width: 0;
+		}
+	}
 
-  // size类型样式覆盖
-  &--mini {
-    .le-addon {
-      padding: 0 8px;
-    }
-  }
+	// size类型样式覆盖
+	&--mini {
+		.le-addon {
+			padding: 0 8px;
+		}
+	}
 }
 // InputNumberRange 样式(inputNumber 区间)
 .#{$prefix}input-number-range {
-  display: inline-flex;
-  //align-items: center;
-  &_addon {
-    position: relative;
-    display: flex;
-    align-items: center;
-    width: unset;
-    //padding: 0 10px;
-    color: $le-color_1;
-    text-align: center;
-    transition: all .3s;
-    padding-left: 4px;
-    padding-right: 4px;
+	display: inline-flex;
+	//align-items: center;
+	&_addon {
+		position: relative;
+		display: flex;
+		align-items: center;
+		width: unset;
+		//padding: 0 10px;
+		color: $le-color_1;
+		text-align: center;
+		transition: all .3s;
+		padding-left: 4px;
+		padding-right: 4px;
 
-    // 前置
-    &.prepend {
-      //padding-left:
-    }
-    // 后置
-    &.append {
-      padding-left: 4px;
-      padding-right: 8px;
-    }
-  }
-  &_line {
-    padding: 0 12px;
-    align-self: center;
-    //text-align: center;
-  }
-  // min
-  &_start {
-  }
-  // max
-  &_end {
-  }
-  .#{$prefix}input-number {
-    flex: 1;
-  }
+		// 前置
+		&.prepend {
+			//padding-left:
+		}
+		// 后置
+		&.append {
+			padding-left: 4px;
+			padding-right: 8px;
+		}
+	}
+	&_line {
+		padding: 0 12px;
+		align-self: center;
+		//text-align: center;
+	}
+	// min
+	&_start {
+	}
+	// max
+	&_end {
+	}
+	.#{$prefix}input-number {
+		flex: 1;
+	}
 }

+ 6 - 3
src/styles/lance-element/searchForm.scss

@@ -2,7 +2,8 @@
 // SearchForm 样式
 .#{$prefix}search-form {
 	&-container {
-		background-color: #fff;
+		//background-color: #fff;
+		background-color: var(--el-bg-color);;
 		margin-bottom: 12px;
 		padding: 10px 12px;
 		padding-top: 0;
@@ -18,8 +19,10 @@
 							border-radius: var(--el-input-border-radius,var(--el-border-radius-base)) 0 0 var(--el-input-border-radius,var(--el-border-radius-base));
 							/* border-right: 0; */
 							padding-left: 10px;
-							//background: #fafafa;
-							background: #fcfcfc;
+							//background: $le-bg-color_3;
+							//background: #fcfcfc;
+							//background: var(--el-border-color-lighter);
+							background: var(--el-border-color-extra-light);
 						}
 						.el-select-v2__wrapper, .el-input__wrapper {
 							border-radius: 0 var(--el-input-border-radius,var(--el-border-radius-base)) var(--el-input-border-radius,var(--el-border-radius-base)) 0;

+ 219 - 212
src/styles/lance-element/table.scss

@@ -3,80 +3,82 @@
 
 // table TableColumnsPopover 样式
 .#{$prefix}column-popover {
-  &.el-popper {
-    min-width: 250px;
-    line-height: 1;
-    padding: 12px 0 0;
-  }
-  .el-select-dropdown__item {
-    display: flex;
-    align-items: center;
-    width: 100%;
-    padding: 0 12px;
-    margin-right: 0;
-    .el-checkbox__label {
-      white-space: nowrap;
-      overflow: hidden;
-      text-overflow: ellipsis;
-      max-width: 300px;
-    }
-  }
-  // 分割线
-  .divider {
-    margin: 0 12px;
-    border-bottom:1px solid $le-border-color_3;
-  }
-  .el-scrollbar__wrap {
-    margin-bottom: 0 !important;
-    overflow-y: scroll;
-    overflow-x: hidden;
-    max-height: 24vh;
-  }
-  .el-select-dropdown__list {
-    padding: 0;
-  }
-  .columns-contents {
-    //min-width: 250px;
-    .title {
-      padding: 0 12px;
-      line-height: 22px;
-      color: $le-color_3;
-    }
-    .label {
-      padding: 0 12px;
-      height: 36px;
-      line-height: 36px;
-      color: $le-color_5;
-    }
-    //.draggableWrap {
-    //  //display: flex;
-    //  //flex-direction: column;
-    //  ////min-height: max-content;
-    //  //min-height: 20vh;
-    //}
-  }
-  .footer {
-    display: flex;
-    justify-content: space-between;
-    padding: 12px;
+	&.el-popper {
+		min-width: 250px;
+		line-height: 1;
+		padding: 12px 0 0;
+	}
+	.el-select-dropdown__item {
+		display: flex;
+		align-items: center;
+		width: 100%;
+		padding: 0 12px;
+		margin-right: 0;
+		.el-checkbox__label {
+			white-space: nowrap;
+			overflow: hidden;
+			text-overflow: ellipsis;
+			max-width: 300px;
+		}
+	}
+	// 分割线
+	.divider {
+		margin: 0 12px;
+		border-bottom:1px solid $le-border-color_3;
+	}
+	.el-scrollbar__wrap {
+		margin-bottom: 0 !important;
+		overflow-y: scroll;
+		overflow-x: hidden;
+		max-height: 24vh;
+	}
+	.el-select-dropdown__list {
+		padding: 0;
+	}
+	.columns-contents {
+		//min-width: 250px;
+		.title {
+			padding: 0 12px;
+			line-height: 22px;
+			//color: $le-color_3;
+			color: var(--el-text-color-primary);
+		}
+		.label {
+			padding: 0 12px;
+			height: 36px;
+			line-height: 36px;
+			color: $le-color_5;
+		}
+		//.draggableWrap {
+		//  //display: flex;
+		//  //flex-direction: column;
+		//  ////min-height: max-content;
+		//  //min-height: 20vh;
+		//}
+	}
+	.footer {
+		display: flex;
+		justify-content: space-between;
+		padding: 12px;
 		.el-button {
 			padding: 9px 15px;
 			font-size: 14px;
 			height: 32px;
 		}
-  }
+	}
 }
 // table 样式
 .#{$prefix}table-warp {
-  position: relative;
-  flex: 1;
-  display: flex;
-  flex-direction: column;
-  height: 100%;
-  width: 100%;
+	position: relative;
+	flex: 1;
+	display: flex;
+	flex-direction: column;
+	height: 100%;
+	width: 100%;
 	padding: 12px 12px 0 12px;
-  min-height: 0;
-  background: #fff;
+	min-height: 0;
+	//background: #fff;
+	background-color: var(--el-bg-color);
 	/*.tableLoading {
 		position: absolute;
 		left: 0;
@@ -99,94 +101,95 @@
 		background-color: #fff !important;
 		z-index: 2000;
 	}
-  .slot_title-wrap {
-    //width: 100%;
-    display: inline-flex;
-    align-items: center;
-    text-align: center;
-    overflow: hidden;
+	.slot_title-wrap {
+		//width: 100%;
+		display: inline-flex;
+		align-items: center;
+		text-align: center;
+		overflow: hidden;
 
-    .label {
-      overflow: hidden;
-      text-overflow: ellipsis;
-      white-space: nowrap;
-    }
+		.label {
+			overflow: hidden;
+			text-overflow: ellipsis;
+			white-space: nowrap;
+		}
 
-    .le-iconfont {
-      margin-left: .2em;
-      font-size: 14px;
-      cursor: pointer;
-      color: $le-color_3;
-      font-weight: normal;
-    }
-  }
-  .tableBody {
-    flex: 1;
-    min-height: 200px;
-    height: 100%;
-    display: flex;
-    flex-direction: column;
+		.le-iconfont {
+			margin-left: .2em;
+			font-size: 14px;
+			cursor: pointer;
+			color: $le-color_3;
+			font-weight: normal;
+		}
+	}
+	.tableBody {
+		flex: 1;
+		min-height: 200px;
+		height: 100%;
+		display: flex;
+		flex-direction: column;
 
-    .toolBarWrap {
-      margin-bottom: 12px;
-      height: auto;
-      display: flex;
-      //align-items: center;
-      align-items: flex-start;
-      justify-content: space-between;
-      .toolLeft {
-        flex: 1;
-        // 针对 searchGroup tags 设置的样式
-        .#{$prefix}search-group-tags {
-          margin-bottom: -4px;
-        }
-      }
+		.toolBarWrap {
+			margin-bottom: 12px;
+			height: auto;
+			display: flex;
+			//align-items: center;
+			align-items: flex-start;
+			justify-content: space-between;
+			.toolLeft {
+				flex: 1;
+				// 针对 searchGroup tags 设置的样式
+				.#{$prefix}search-group-tags {
+					margin-bottom: -4px;
+				}
+			}
 
-      .toolRight {
-        //align-self: flex-end;
-        // 兼容当搜索组件放置在 toolLeft 的时候
-        //min-height: 32px;
-        display: flex;
-        align-items: center;
-        // 仅带icon 的button 按钮样式
-        .icon-button {
-          @extend %icon-button;
+			.toolRight {
+				//align-self: flex-end;
+				// 兼容当搜索组件放置在 toolLeft 的时候
+				//min-height: 32px;
+				display: flex;
+				align-items: center;
+				// 仅带icon 的button 按钮样式
+				.icon-button {
+					@extend %icon-button;
 					margin-left: 12px;
 					font-size: 16px;
 					/*cursor: pointer;
 					&:hover {
 						color: var(--el-color-primary);
 					}*/
-        }
+				}
 				//.button-refresh{}
 				//.button-screen{}
-      }
-    }
+			}
+		}
 
-    .tableParentEl {
-      flex: 1;
-      overflow-y: hidden;
-    }
+		.tableParentEl {
+			flex: 1;
+			overflow-y: hidden;
+		}
 
-    //.le-table {}
-  }
-  .le-table {
-    font-size: 14px;
-    transform: translate(0, 0);
-		//$header-bg: #f5f7fa;
-		$header-bg: #f6faff;
+		//.le-table {}
+	}
+	.le-table {
+		font-size: 14px;
+		transform: translate(0, 0);
+		//$header-bg: var(--el-table-header-bg-color);
+		$header-bg: var(--el-fill-color-light);
+		//$header-bg: var(--el-fill-color-lighter);
 		// todo
-    /*&.el-table--border {
-      td:not(:last-child) {
-        border-right-color: transparent;
-      }
-    }
+		/*&.el-table--border {
+			td:not(:last-child) {
+				border-right-color: transparent;
+			}
+		}
 
-    &.el-table__body-wrapper {
-      .el-table--border.is-scrolling-left ~ .el-table__fixed:not(:last-child) {
-        border-right-color: transparent;
-      }
-    }*/
+		&.el-table__body-wrapper {
+			.el-table--border.is-scrolling-left ~ .el-table__fixed:not(:last-child) {
+				border-right-color: transparent;
+			}
+		}*/
 
 		.el-table__header-wrapper tr th {
 			&.el-table-fixed-column--left,
@@ -195,87 +198,91 @@
 				background-color: $header-bg;
 			}
 		}
-    th {
-      height: 41px;
+		th {
+			height: 41px;
 			//background-color: var(--el-table-header-bg-color);
 			background-color: $header-bg;
-      &.is-right {
-        .cell {
-          justify-content: end;
-        }
-      }
-      &.is-center {
-        .cell {
-          justify-content: center;
-        }
-      }
-      .cell {
-        //font-size: 13px;
-        font-weight: normal;
-        color: $le-color_1;
-        line-height: 17px;
-        padding-left: 12px;
-        padding-right: 12px;
-        display: flex;
-        align-items: center;
+			&.is-right {
+				.cell {
+					justify-content: end;
+				}
+			}
+			&.is-center {
+				.cell {
+					justify-content: center;
+				}
+			}
+			.cell {
+				//font-size: 13px;
+				font-weight: normal;
+				color: $le-color_1;
+				line-height: 17px;
+				padding-left: 12px;
+				padding-right: 12px;
+				display: flex;
+				align-items: center;
 				/*text-align: center;
 				justify-content: center;
 				overflow: hidden;*/
-      }
-    }
-    .el-table-column--selection .cell {
-      padding-left: 12px;
-      padding-right: 12px;
-    }
+			}
+		}
+		.el-table-column--selection .cell {
+			padding-left: 12px;
+			padding-right: 12px;
+		}
 
-    &.el-table--striped {
-      .el-table__body {
-        tr.el-table__row--striped td {
-          background: $le-hover-color_1;
-        }
-      }
-    }
-    .el-table__body {
-      //td {
-      //  color: $le-color_2;
-      //  font-size: 12px;
-      //}
+		&.el-table--striped {
+			.el-table__body {
+				tr.el-table__row--striped td {
+					background: $le-hover-color_1;
+				}
+			}
+		}
+		.el-table__body {
+			//td {
+			//  color: $le-color_2;
+			//  font-size: 12px;
+			//}
 
-      tr.hover-row.current-row > td,
-      tr.hover-row.el-table__row--striped.current-row > td,
-      tr.hover-row.el-table__row--striped > td,
-      tr.hover-row > td {
-        background-color: #e8f2fe;
-      }
-    }
-    &.el-table--enable-row-hover {
-      .el-table__body tr:hover > td {
-        background-color: #e8f2fe;
-      }
-    }
-    .el-table__body tr.hover-row > td {
-      background: $le-hover-color_1;
-    }
-    // 排序类型样式调整
-    .caret-wrapper {
-      height: 17px;
-      vertical-align: unset;
+			tr.hover-row.current-row > td,
+			tr.hover-row.el-table__row--striped.current-row > td,
+			tr.hover-row.el-table__row--striped > td,
+			tr.hover-row > td {
+				//background-color: #e8f2fe;
+				background-color: var(--el-fill-color-lighter);
+				//background-color: var(--el-color-danger);
+			}
+		}
+		&.el-table--enable-row-hover {
+			.el-table__body tr:hover > td {
+				//background-color: #e8f2fe;
+				background-color: var(--el-fill-color-lighter);
+				//background-color: var(--el-color-danger);
+			}
+		}
+		.el-table__body tr.hover-row > td {
+			background: $le-hover-color_1;
+		}
+		// 排序类型样式调整
+		.caret-wrapper {
+			height: 17px;
+			vertical-align: unset;
 			min-width: 14px;
-      //transform: translateY(3px);
-    }
-    .sort-caret {
-      &.ascending {
-        top: -3px;
-      }
-      &.descending {
-        bottom: -3px;
-      }
-    }
-  }
-  .el-pagination {
-    display: flex;
-    //padding: 16px 12px;
-    padding: 12px 0;
-    justify-content: flex-end;
-  }
+			//transform: translateY(3px);
+		}
+		.sort-caret {
+			&.ascending {
+				top: -3px;
+			}
+			&.descending {
+				bottom: -3px;
+			}
+		}
+	}
+	.el-pagination {
+		display: flex;
+		//padding: 16px 12px;
+		padding: 12px 0;
+		justify-content: flex-end;
+	}
 }

+ 15 - 13
src/styles/project_normal.scss

@@ -4,19 +4,19 @@ body {
 }
 // 页面公用样式 flex column 外壳
 .flex-column-page-wrap {
-  flex: 1;
-  display: flex;
-  height: 100%;
-  flex-direction: column;
+	flex: 1;
+	display: flex;
+	height: 100%;
+	flex-direction: column;
 }
 // 单行超出隐藏
 .text-overflow-hidden {
-  white-space: normal;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  display: -webkit-box;
-  -webkit-line-clamp: 1;
-  -webkit-box-orient: vertical;
+	white-space: normal;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	display: -webkit-box;
+	-webkit-line-clamp: 1;
+	-webkit-box-orient: vertical;
 }
 
 .common_title {
@@ -26,19 +26,21 @@ body {
 	//line-height: 22px;
 	padding: 12px 0;
 	font-size: 14px;
-	color: rgba(0, 0, 0, 0.85);
+	//color: rgba(0, 0, 0, 0.85);
+	color: var(--el-text-color-primary);
+	//background-color: var(--el-fill-color);
 	&:before {
 		content: '';
 		width: 3px;
 		height: 16px;
 		margin-right: 6px;
-		background: #4097fd;
+		background: $le-color-primary;
 	}
 }
 
 // 全局滚动条样式
 ::-webkit-scrollbar{width:6px;height:6px}
-::-webkit-scrollbar-track{border-radius:6px;background: #FAFAFA;}
+::-webkit-scrollbar-track{border-radius:6px;background: $le-bg-color_1;}
 ::-webkit-scrollbar-track:hover{background-color:rgba(0,0,0,0.06);box-shadow:0 0 0 #fff inset,0 0 0 rgba(255,255,255,0.9) inset,0 0 0 rgba(255,255,255,0.9) inset,0 0 0 rgba(255,255,255,0.9) inset}
 ::-webkit-scrollbar-track:active{background-color:rgba(0,0,0,0.1)}
 ::-webkit-scrollbar-thumb{border-radius:6px;background-color:rgba(0,0,0,0.1);box-shadow:0 0 0 #fff inset,0 0 0 #fff inset,0 0 0 rgba(255,255,255,0.9) inset,0 0 0 rgba(255,255,255,0.9) inset}

+ 82 - 28
src/styles/sidebar.scss

@@ -1,7 +1,67 @@
-#app {
+:root {
 	--el-menu-item-height: 42px;
 	--el-menu-sub-item-height: 42px;
 	--el-menu-hover-bg-color: transparent;
+}
+$active_bg: var(--el-color-primary);
+// 公用的 菜单样式
+.layout-menu-wrap, .layout-menu-popper-wrap {
+	// 折叠情况
+	&.el-menu--collapse {
+		.menu-icon {
+			margin-right: 0;
+		}
+		// 折叠后无子菜单 样式类
+		.el-menu-tooltip__trigger {
+			justify-content: center;
+		}
+	}
+	.menu-icon {
+		margin-right: 4px;
+		font-size: 18px;
+	}
+	.el-sub-menu {
+		&.is-active {
+			& > .el-sub-menu__title {
+				color: var(--el-menu-active-color);
+			}
+		}
+	}
+	.el-menu-item,
+	.el-sub-menu__title {
+		margin-top: 5px;
+	}
+	.el-menu-item.is-active::before {
+		background-color: $active_bg;
+	}
+	.el-menu-item:hover {
+		&::before {
+			background-color: $active_bg;
+		}
+	}
+	.el-sub-menu__title:hover {
+		&::before {
+			background-color: $active_bg;
+		}
+	}
+	.el-menu-item::before,
+	.el-sub-menu__title::before {
+		z-index: auto;
+		content: "";
+		background-color: #0000;
+		opacity: 0.08;
+		position: absolute;
+		left: 8px;
+		right: 8px;
+		top: 0;
+		bottom: 0;
+		pointer-events: none;
+		border-radius: 3px;
+		transition: background-color .3s cubic-bezier(.4, 0, .2, 1);
+	}
+}
+/*#app {
+	// rgba(243, 243, 245);
 	.main-container {
 		min-height: 100%;
 		height: 100%;
@@ -57,7 +117,7 @@
 			overflow: hidden;
 		}
 
-		.svg-icon {
+		.menu-icon {
 			margin-right: 16px;
 		}
 
@@ -77,14 +137,14 @@
 					}
 				}
 			}
-			/*$active_1: var(--el-menu-active-color);
+			!*$active_1: var(--el-menu-active-color);
 			$active_bg: desaturate(rgba(0, 0, 0, .90), 1%);
 			//$active_bg: desaturate($active_1, 50%);
-			//$active_bg: draken($active_1, 50%);*/
+			//$active_bg: draken($active_1, 50%);*!
 			$active_bg: var(--el-color-primary);
 			.el-menu-item,
 			.el-sub-menu__title {
-				margin-top: 6px;
+				margin-top: 5px;
 			}
 			.el-menu-item.is-active::before {
 				background-color: $active_bg;
@@ -117,16 +177,16 @@
 		}
 
 		// menu hover
-		/*.submenu-title-noDropdown,
+		!*.submenu-title-noDropdown,
 		.el-sub-menu__title {
 			&:hover {
 				//background-color: $menuHover !important;
 			}
-		}*/
+		}*!
 
-		/*.is-active > .el-sub-menu__title {
+		!*.is-active > .el-sub-menu__title {
 			color: // $subMenuActiveText !important;
-		}*/
+		}*!
 
 		& .nest-menu .el-sub-menu > .el-sub-menu__title,
 		& .el-sub-menu .el-menu-item {
@@ -174,9 +234,9 @@
 			& > .el-sub-menu__title {
 				padding: 0 !important;
 
-				.svg-icon {
+				!*.svg-icon {
 					margin-left: 20px;
-				}
+				}*!
 
 				.sub-el-icon {
 					margin-left: 19px;
@@ -233,14 +293,18 @@
 			transition: none;
 		}
 	}
-}
+}*/
 
 // when menu collapsed
-.el-menu--vertical {
+// 由于 vertical collapsed 出现样式问题 进行隐藏
+.layout-menu-popper-wrap.el-menu--vertical,
+.layout-menu-popper-wrap.el-menu--horizontal {
+	// el-menu--horizontal: layout-transverse 模式
+	// el-menu--vertical: layout-vertical 模式
 	& > .el-menu {
-		.svg-icon {
+		/*.svg-icon {
 			margin-right: 16px;
-		}
+		}*/
 		.sub-el-icon {
 			margin-right: 12px;
 			margin-left: -2px;
@@ -258,19 +322,9 @@
 	// the scroll bar appears when the subMenu is too long
 	> .el-menu--popup {
 		max-height: 100vh;
-		overflow-y: auto;
-
-		&::-webkit-scrollbar-track-piece {
-			background: #d3dce6;
-		}
-
-		&::-webkit-scrollbar {
-			width: 6px;
-		}
-
-		&::-webkit-scrollbar-thumb {
-			background: #99a9bf;
-			border-radius: 20px;
+		//overflow-y: auto;
+		& > li:first-child {
+			margin-top: 0;
 		}
 	}
 }

+ 16 - 0
src/styles/theme/aside.ts

@@ -0,0 +1,16 @@
+import { Theme } from '@/hooks/interface'
+
+export const asideTheme: Record<Theme.ThemeType, { [key: string]: string }> = {
+	light: {
+		'--el-aside-logo-text-color': '#303133',
+		'--el-aside-border-color': '#e4e7ed'
+	},
+	inverted: {
+		'--el-aside-logo-text-color': '#dadada',
+		'--el-aside-border-color': '#414243'
+	},
+	dark: {
+		'--el-aside-logo-text-color': '#dadada',
+		'--el-aside-border-color': '#414243'
+	}
+}

+ 25 - 0
src/styles/theme/footer.ts

@@ -0,0 +1,25 @@
+import { Theme } from '@/hooks/interface'
+
+export const footerTheme: Record<Theme.ThemeType, { [key: string]: string }> = {
+	light: {
+		// '--el-footer-logo-text-color': '#303133',
+		'--el-footer-bg-color': '#ffffff',
+		'--el-footer-text-color': '#303133',
+		'--el-footer-text-color-regular': '#606266',
+		'--el-footer-border-color': '#e4e7ed'
+	},
+	inverted: {
+		// '--el-footer-logo-text-color': '#dadada',
+		'--el-footer-bg-color': '#191a20',
+		'--el-footer-text-color': '#e5eaf3',
+		'--el-footer-text-color-regular': '#cfd3dc',
+		'--el-footer-border-color': '#414243'
+	},
+	dark: {
+		// '--el-footer-logo-text-color': '#dadada',
+		'--el-footer-bg-color': '#141414',
+		'--el-footer-text-color': '#e5eaf3',
+		'--el-footer-text-color-regular': '#cfd3dc',
+		'--el-footer-border-color': '#414243'
+	}
+}

+ 25 - 0
src/styles/theme/header.ts

@@ -0,0 +1,25 @@
+import { Theme } from '@/hooks/interface'
+
+export const headerTheme: Record<Theme.ThemeType, { [key: string]: string }> = {
+	light: {
+		'--el-header-logo-text-color': '#303133',
+		'--el-header-bg-color': '#ffffff',
+		'--el-header-text-color': '#303133',
+		'--el-header-text-color-regular': '#606266',
+		'--el-header-border-color': '#e4e7ed'
+	},
+	inverted: {
+		'--el-header-logo-text-color': '#dadada',
+		'--el-header-bg-color': '#191a20',
+		'--el-header-text-color': '#e5eaf3',
+		'--el-header-text-color-regular': '#cfd3dc',
+		'--el-header-border-color': '#414243'
+	},
+	dark: {
+		'--el-header-logo-text-color': '#dadada',
+		'--el-header-bg-color': '#141414',
+		'--el-header-text-color': '#e5eaf3',
+		'--el-header-text-color-regular': '#cfd3dc',
+		'--el-header-border-color': '#414243'
+	}
+}

+ 31 - 0
src/styles/theme/menu.ts

@@ -0,0 +1,31 @@
+import { Theme } from '@/hooks/interface'
+
+export const menuTheme: Record<Theme.ThemeType, { [key: string]: string }> = {
+	light: {
+		'--el-menu-bg-color': '#ffffff',
+		// '--el-menu-hover-bg-color': '#cccccc',
+		'--el-menu-active-bg-color': 'var(--el-color-primary-light-9)',
+		'--el-menu-text-color': '#333333',
+		'--el-menu-active-color': 'var(--el-color-primary)',
+		'--el-menu-hover-text-color': '#333333',
+		'--el-menu-horizontal-sub-item-height': '50px'
+	},
+	inverted: {
+		'--el-menu-bg-color': '#191a20',
+		// '--el-menu-hover-bg-color': '#000000',
+		'--el-menu-active-bg-color': '#000000',
+		'--el-menu-text-color': '#bdbdc0',
+		'--el-menu-active-color': '#ffffff',
+		'--el-menu-hover-text-color': '#ffffff',
+		'--el-menu-horizontal-sub-item-height': '50px'
+	},
+	dark: {
+		'--el-menu-bg-color': '#141414',
+		// '--el-menu-hover-bg-color': '#000000',
+		'--el-menu-active-bg-color': '#000000',
+		'--el-menu-text-color': '#bdbdc0',
+		'--el-menu-active-color': '#ffffff',
+		'--el-menu-hover-text-color': '#ffffff',
+		'--el-menu-horizontal-sub-item-height': '50px'
+	}
+}

+ 69 - 9
src/styles/transition.scss

@@ -11,26 +11,86 @@
 	opacity: 0;
 }
 
-/* fade-transform */
-.fade-transform-leave-active,
-.fade-transform-enter-active {
-	transition: all 0.5s;
+/* fade-slide */
+.fade-slide-leave-active,
+.fade-slide-enter-active {
+	transition: all 0.3s;
 }
-
-.fade-transform-enter {
+.fade-slide-enter-from {
 	opacity: 0;
 	transform: translateX(-30px);
 }
-
-.fade-transform-leave-to {
+.fade-slide-leave-to {
 	opacity: 0;
 	transform: translateX(30px);
 }
 
+/* fade-bottom */
+.fade-bottom-enter-active,
+.fade-bottom-leave-active {
+	transition:
+		opacity 0.25s,
+		transform 0.3s;
+}
+.fade-bottom-enter-from {
+	opacity: 0;
+	transform: translateY(-10%);
+}
+.fade-bottom-leave-to {
+	opacity: 0;
+	transform: translateY(10%);
+}
+
+/* fade-scale */
+.fade-scale-leave-active,
+.fade-scale-enter-active {
+	transition: all 0.28s;
+}
+.fade-scale-enter-from {
+	opacity: 0;
+	transform: scale(1.2);
+}
+.fade-scale-leave-to {
+	opacity: 0;
+	transform: scale(0.8);
+}
+
+/* zoom-fade */
+.zoom-fade-enter-active,
+.zoom-fade-leave-active {
+	transition:
+		transform 0.2s,
+		opacity 0.3s ease-out;
+}
+.zoom-fade-enter-from {
+	opacity: 0;
+	transform: scale(0.92);
+}
+.zoom-fade-leave-to {
+	opacity: 0;
+	transform: scale(1.06);
+}
+
+
+/* zoom-out */
+.zoom-out-enter-active,
+.zoom-out-leave-active {
+	transition:
+		opacity 0.1s ease-in-out,
+		transform 0.15s ease-out;
+}
+.zoom-out-enter-from,
+.zoom-out-leave-to {
+	opacity: 0;
+	transform: scale(0);
+}
+
+
+
 /* breadcrumb transition */
 .breadcrumb-enter-active,
 .breadcrumb-leave-active {
-	transition: all 0.5s;
+	transition: all 0.3s;
 }
 
 .breadcrumb-enter,

+ 37 - 21
src/styles/variables.scss

@@ -3,36 +3,52 @@ $prefix: 'le-';
 $le-family: Helvetica Neue, Helvetica, sans-serif;
 
 // 主题类型
-$le-color-primary: #4097fd;
-$le-color-success: #03B497;
-$le-color-warning: #F4A508;
-$le-color-danger: #F47367;
-$le-color-info: #67809F;
+//$le-color-primary: #4097fd;
+$le-color-primary: var(--el-color-primary);
+//$le-color-success: #03b497;
+$le-color-success: var(--el-color-success);
+//$le-color-warning: #f4a508;
+$le-color-warning: var(--el-color-warning);
+//$le-color-danger: #f47367;
+$le-color-danger: var(--el-color-danger);
+//$le-color-info: #67809f;
+$le-color-info: var(--el-color-info);
 
 // 文字
-$le-color_1: #32363B;
-$le-color_2: #5C6570;
-$le-color_3: #7C8794;
-$le-color_4: #A0AAB7;
-$le-color_5: #BDC3C9;
+//$le-color_1: #32363b;
+$le-color_1: var(--el-text-color-primary);
+//$le-color_2: #5c6570;
+//$le-color_3: #7c8794;
+//$le-color_3: var(--el-fill-color);
+$le-color_3: var(--el-text-color-primary);
+//$le-color_4: #a0aab7;
+$le-color_4: var(--el-text-color-regular);
+//$le-color_5: #bdc3c9;
+$le-color_5: var(--el-text-color-secondary);
 
 // 线条
-$le-border-color_1: #C6CDD5;
-$le-border-color_2: #D5DBE1;
-$le-border-color_3: #EAEDF0;
-$le-border-color_4: #F5F7FA;
+//$le-border-color_1: #c6cdd5;
+//$le-border-color_2: #d5dbe1;
+//$le-border-color_3: #eaedf0;
+//$le-border-color_4: #f5f7fa;
+$le-border-color_1: var(--el-border-color);
+$le-border-color_2: var(--el-border-color-light);
+$le-border-color_3: var(--el-border-color-lighter);
+$le-border-color_4: var(--el-border-color-extra-light);
 
 // 背景
-$le-bg-color_1: #F5F6F7;
-$le-bg-color_2: #F5F7FA;
-$le-bg-color_3: #FAFAFA;
+//$le-bg-color_1: #f5f6f7;
+$le-bg-color_1: var(--el-bg-color);
+$le-bg-color_2: #f5f7fa;
+//$le-bg-color_3: #fafafa;
 
 // hover
-$le-hover-color_1: #F6F8FA;
-$le-hover-color_2: #F0F7FF;
+//$le-hover-color_1: #f6f8fa;
+$le-hover-color_1: var(--el-fill-color-lighter);
+$le-hover-color_2: #f0f7ff;
 
 //$le-color-header: #7c8794;
 //$le-color-black: #32363b;
-//$le-bg-color-gray: #f5f6f7;
+//$le-bg-color-gray: $le-bg-color_1;
 
-//$le-link-background-gray: #4097fd;
+//$le-link-background-gray: $le-color-primary;

+ 4 - 0
src/types/global.d.ts

@@ -9,6 +9,10 @@ declare namespace Menu {
 		children?: MenuOptions[]
 	}
 	interface MetaProps {
+		// 关于icon 描述:
+		// 1.来自本地src/assets/icons 的svg: 'icon-[dir]-[name]'
+		// 2.iconfont svg 链接: 'le-[name]'
+		// 3. 匹配不到icon- & le- 默认element
 		icon: string
 		title: string
 		activeMenu?: string

+ 42 - 4
src/types/store.d.ts

@@ -19,20 +19,57 @@ export interface AppState {
  */
 export interface PermissionState {
 	routes: AppRouteRecordRaw[]
-	addRoutes: AppRouteRecordRaw[]
+	menuList: AppRouteRecordRaw[]
 }
 
 /**
  * 设置状态类型声明
  */
 export interface SettingState {
-	theme: string
+	// 展示设置
+	showSettings: boolean
+	// 展示底部
+	footer: boolean
+	// 折叠菜单
+	isCollapse: boolean
+	// 布局模式
+	layout: string
+	// 主题颜色
+	themeColor: string
+	// 暗黑主题
+	isDark: boolean
+	// 侧边栏深色
+	asideInverted: boolean
+	// 头部深色
+	headerInverted: boolean
+	// 底部深色
+	footerInverted: boolean
+	// 多页签
 	tagsView: boolean
+	// 固定 Header
 	fixedHeader: boolean
-	showSettings: boolean
+	// 侧边栏 Logo
 	sidebarLogo: boolean
+	// 手风琴
+	accordion: boolean
+	// 面包屑
+	breadcrumb: boolean
+	// 面包屑图标
+	breadcrumbIcon: boolean
+	// 页面切换动画
+	animate: boolean
+	// 页面切换动画类型
+	/**
+	 * 过渡动画类型
+	 * - fade: 消退
+	 * - fade-slide: 滑动
+	 * - fade-bottom: 底部消退
+	 * - fade-scale: 缩放消退
+	 * - zoom-fade: 渐变
+	 * - zoom-out: 闪现
+	 */
+	animateMode: 'fade' | 'fade-slide' | 'fade-bottom' | 'fade-scale' | 'zoom-fade' | 'zoom-out'
 }
-
 /**
  * 标签状态类型声明
  */
@@ -54,6 +91,7 @@ export interface UserState {
 		userId: string
 		userName: string
 	}
+	cur_userInfo: Recordable
 	// nickname: string
 	// avatar: string
 	roles: string[]

+ 59 - 0
src/utils/color.ts

@@ -0,0 +1,59 @@
+import { ElMessage } from 'element-plus'
+
+/**
+ * @description hex颜色转rgb颜色
+ * @param {String} str 颜色值字符串
+ * @returns {String} 返回处理后的颜色值
+ */
+export function hexToRgb(str: any) {
+	let hexs: any = ''
+	const reg = /^\#?[0-9A-Fa-f]{6}$/
+	if (!reg.test(str)) return ElMessage.warning('输入错误的hex')
+	str = str.replace('#', '')
+	hexs = str.match(/../g)
+	for (let i = 0; i < 3; i++) hexs[i] = parseInt(hexs[i], 16)
+	return hexs
+}
+
+/**
+ * @description rgb颜色转Hex颜色
+ * @param {*} r 代表红色
+ * @param {*} g 代表绿色
+ * @param {*} b 代表蓝色
+ * @returns {String} 返回处理后的颜色值
+ */
+export function rgbToHex(r: any, g: any, b: any) {
+	const reg = /^\d{1,3}$/
+	if (!reg.test(r) || !reg.test(g) || !reg.test(b)) return ElMessage.warning('输入错误的rgb颜色值')
+	const hexs = [r.toString(16), g.toString(16), b.toString(16)]
+	for (let i = 0; i < 3; i++) if (hexs[i].length == 1) hexs[i] = `0${hexs[i]}`
+	return `#${hexs.join('')}`
+}
+
+/**
+ * @description 加深颜色值
+ * @param {String} color 颜色值字符串
+ * @param {Number} level 加深的程度,限0-1之间
+ * @returns {String} 返回处理后的颜色值
+ */
+export function getDarkColor(color: string, level: number) {
+	const reg = /^\#?[0-9A-Fa-f]{6}$/
+	if (!reg.test(color)) return ElMessage.warning('输入错误的hex颜色值')
+	const rgb = hexToRgb(color)
+	for (let i = 0; i < 3; i++) rgb[i] = Math.round(20.5 * level + rgb[i] * (1 - level))
+	return rgbToHex(rgb[0], rgb[1], rgb[2])
+}
+
+/**
+ * @description 变浅颜色值
+ * @param {String} color 颜色值字符串
+ * @param {Number} level 加深的程度,限0-1之间
+ * @returns {String} 返回处理后的颜色值
+ */
+export function getLightColor(color: string, level: number) {
+	const reg = /^\#?[0-9A-Fa-f]{6}$/
+	if (!reg.test(color)) return ElMessage.warning('输入错误的hex颜色值')
+	const rgb = hexToRgb(color)
+	for (let i = 0; i < 3; i++) rgb[i] = Math.round(255 * level + rgb[i] * (1 - level))
+	return rgbToHex(rgb[0], rgb[1], rgb[2])
+}

+ 3 - 3
src/views/components/components/InputNumberDemo.vue

@@ -9,13 +9,13 @@
 		<LeInputNumber size="small" v-model="testNumber" :min="0" prefixIcon="#" suffixIcon="¥" _controlsPosition=""></LeInputNumber>
 		<LeInputNumber v-model="testNumber" :min="0" style="width: 300px" controlsPosition="">
 			<template slot="prefix"><span class="le-addon le-input-number__prefix" style="background: #0f0">prefix</span></template>
-			<template slot="suffix"><span class="le-addon le-input-number__suffix" style="background: #f00">suffix</span></template>
+			<template slot="suffix"><span class="le-addon le-input-number__suffix" style="background: var(--el-color-danger)">suffix</span></template>
 		</LeInputNumber>
 		<div>-------LeInputNumberRange--------</div>
 		{{ numberRangeParams }}<br />
 		<LeInputNumberRange v-model="numberRangeParams" v-bind="numberRangeForm" itemStyle="width: 400px">
-<!--			<template #prepend><span class="le-input-number-range_addon local_prepend" style="background: #0f0;">prepend</span></template>-->
-			<template #append><span class="le-input-number-range_addon local_append" style="background: #f00">append</span></template>
+<!--			<template #prepend><span class="le-input-number-range_addon local_prepend" style="background: var(--el-color-success);">prepend</span></template>-->
+			<template #append><span class="le-input-number-range_addon local_append" style="background: var(--el-color-danger)">append</span></template>
 		</LeInputNumberRange>
 	</div>
 </template>

+ 3 - 3
src/views/components/components/LeSelectDemo.vue

@@ -1,7 +1,7 @@
 <template>
 	<div class="common_title">LeSelect 使用</div>
 	<div class="content">
-		<div style="color: #f00">testValue:{{ testValue }}</div>
+		<div style="color: var(--el-color-danger)">testValue:{{ testValue }}</div>
 <!--		el-select
 		<ElSelect v-model="testValue" filterable multiple collapseTags isPopover>
 			&lt;!&ndash;				<template #prefix>
@@ -15,7 +15,7 @@
 								<LeIcon icon-class="icon-logo"/>
 							</template>-->
 		</el-select-v2>
-		LeSelect(cust_label 多语言)<span style="color: #f00;">(测试valueKey 变更)</span>
+		LeSelect(cust_label 多语言)<span style="color: var(--el-color-danger);">(测试valueKey 变更)</span>
 		<!--			3labelKey="obj.label"-->
 		<LeSelect
 			v-model="testValue"
@@ -45,7 +45,7 @@
 			style="width: 200px" filterable isPopover>
 			<template #default="{ item, index, disabled }"> <LeIcon icon-class="icon-logo" /> {{ item.le_label }} </template>
 		</LeSelect>
-		valueKey(obj.value) <span style="color: #00f;">(测试labelKey 变更)</span>
+		valueKey(obj.value) <span style="color: var(--el-color-primary);">(测试labelKey 变更)</span>
 		<!-- 读取option 非value 的数据 作为value 储蓄 -->
 		<LeSelect
 			v-model="testValue"

+ 1 - 1
src/views/components/components/SearchGroup2Popover.vue

@@ -170,7 +170,7 @@
 		<div>-------分割--------</div>
 		<!--   模拟 查看更多下拉列表 类型 le-popover--list   -->
 		<!--      <LePopover trigger="click" :popperClass="`le-popover&#45;&#45;list ${'popperClass'}`" placement="right">
-			<div class="labelWrap" style="width: 400px;background: #f00;" slot="reference">
+			<div class="labelWrap" style="width: 400px;background: var(--el-color-danger);" slot="reference">
 				可售:666(le-popover默认 click 触发le-popover-list展示)
 			</div>
 			<div class="more-item_title">

+ 1 - 1
src/views/components/index.vue

@@ -75,7 +75,7 @@
     </div>-->
 		<div class="common_title">暂无数据 LeNoData</div>
 		<div class="content">
-			<LeNoData :message="`<div style='background: #f00;'>test: lang: ${$i18n.locale}</div>`" @click="$log('test....')">
+			<LeNoData :message="`<div style='background: var(--el-color-danger);'>test: lang: ${$i18n.locale}</div>`" @click="$log('test....')">
 				<template #extraContent>no data</template>
 			</LeNoData>
 		</div>

+ 1 - 1
src/views/dashboard/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div class="dashboard-container">
+	<div class="dashboard-container column-page-wrap">
 		<!-- Echarts 图表 -->
 		<el-row :gutter="40" style="margin-top: 20px">
 			<el-col :sm="24" :lg="8" class="card-panel__col">

+ 8 - 8
src/views/demo/pageConfig/index.vue

@@ -8,8 +8,8 @@
 			:loading="tableOpts.options.loading"
 		>
 			<template #slot_label_test="{label}">
-				<div style="background: #f00; color:#fff;display: flex">
-					label custom: string<span style="margin-left: auto; background: #00f">{{label}}</span>
+				<div style="background: var(--el-color-danger); color:#fff;display: flex">
+					label custom: string<span style="margin-left: auto; background: var(--el-color-primary)">{{label}}</span>
 				</div>
 			</template>
 		</LeSearchForm>
@@ -108,7 +108,7 @@ const _forms = [
 	{
 		prop: 'leSelect多选',
 		label: 'leSelect多选',
-		// itemStyle: 'background: #f00; width: 500px',
+		// itemStyle: 'background: var(--el-color-danger); width: 500px',
 		// itemWidth: '300px',
 		itemType: 'leSelect',
 		multiple: true,
@@ -139,7 +139,7 @@ const _forms = [
 			return <el-input v-model={params[form.prop]} placeholder="placeholder test... 666" />
 			// return <el-input onChange={form.onInput} v-model={params[form.prop]} placeholder="placeholder test... 666" />
 			// return <el-input onChange={form.onChange} v-model={params[form.prop]} placeholder="placeholder test... 666" />
-			/*return <div style="background: #f0f; display: flex; padding: 0 10px;">
+			/*return <div style="background: var(--el-color-danger-light-3); display: flex; padding: 0 10px;">
 				<el-input modelValue={params[form.prop]} onInput={(e) => (params[form.prop] = e)} placeholder="placeholder render"/>
 				<el-input modelValue={params.others} onInput={(e) => (params.others = e)} placeholder="placeholder others"/>
 			</div>*/
@@ -322,14 +322,14 @@ const _forms = [
 			/*label({ label }) {
 				console.error(label, 'label...')
 				return (
-					<span style="background: #f00;display: flex">
+					<span style="background: var(--el-color-danger);display: flex">
 						label custom: fn<span style="margin-left: auto; background: #0f0">{label}</span>
 					</span>
 				)
 			},*/
 			label: 'slot_label_test',
 		},
-		// itemStyle: 'background: #f00',
+		// itemStyle: 'background: var(--el-color-danger)',
 		span: 8,
 		// inlinePrompt: true,
 		activeText: '是',
@@ -416,7 +416,7 @@ const columns = [
 		formatter: (row: any, column: any) => {
 			// console.log(row, column, 'row, column');
 			/** 若在内部用到jsx写法 需要在 script 新增 lang 为 jsx || tsx */
-			return <div style="background: #f0f;">${row.name || '- 66666 -'}</div>
+			return <div style="background: var(--el-color-danger-light-3);">${row.name || '- 66666 -'}</div>
 			// return row.name || '- 66666 -'
 		}
 	},
@@ -489,7 +489,7 @@ nextTick(() => {
 		projectType: '类型one'
 	}
 	// debugger
-	searchForm.value.forceUpdateInitParams(tableOpts.searchParams)
+	searchForm.value?.forceUpdateInitParams(tableOpts.searchParams)
 	window.searchForm = searchForm
 })*/
 // 选中的columns

+ 9 - 5
src/views/error-page/404.vue

@@ -59,10 +59,14 @@ export default {
 
 <style lang="scss" scoped>
 .wscn-http404-container {
-	transform: translate(-50%, -50%);
-	position: absolute;
-	top: 40%;
-	left: 50%;
+	display: flex;
+	flex-direction: column;
+	height: 100%;
+	justify-content: center;
+	//transform: translate(-50%, -50%);
+	//position: absolute;
+	//top: 40%;
+	//left: 50%;
 }
 .wscn-http404 {
 	position: relative;
@@ -200,7 +204,7 @@ export default {
 		&__headline {
 			font-size: 20px;
 			line-height: 24px;
-			color: #222;
+			color: var(--el-text-color-primary);
 			font-weight: bold;
 			opacity: 0;
 			margin-bottom: 10px;

+ 2 - 2
src/views/flow/create/components/BasicInfo.vue

@@ -92,10 +92,10 @@ defineExpose({
 
 <style scoped>
 .basic-info-wrap {
-	//background: #f00;
+	//background: var(--el-color-danger);
 }
 .flow-name {
 	padding-right: 4px;
-	color: #f00;
+	color: var(--el-color-danger);
 }
 </style>

+ 13 - 13
src/views/form/default.vue

@@ -17,9 +17,9 @@
 					<div style="background: #a0aab7">{{ label }} + {{ option.value_1 }}</div>
 				</template>
 				<template #leSelect_label="{label}">
-					<span style="background: #f00; display: flex">
+					<span style="background: var(--el-color-danger); display: flex">
 						label custom: template
-						<span style="margin-left: auto; background: #00f; color: #fff">{{ label }}</span>
+						<span style="margin-left: auto; background: var(--el-color-primary); color: #fff">{{ label }}</span>
 					</span>
 				</template>
 			</LeFormConfig>
@@ -50,7 +50,7 @@
 					@cancel="changeVisible"
 				>
 					<template #leSelectSlot="{ option, label }">
-						<div style="background: #00f">{{ label }} + {{ option.value_1 }}</div>
+						<div style="background: var(--el-color-primary)">{{ label }} + {{ option.value_1 }}</div>
 					</template>
 					<template #extraContent>
 <!--						若有需要可以塞入额外内容-->
@@ -76,7 +76,7 @@
 				@submit="formSubmit"
 			>
 				<template #leSelectSlot="{ option, label }">
-					<div style="background: #f00">{{ label }} + {{ option.value_1 }}</div>
+					<div style="background: var(--el-color-danger)">{{ label }} + {{ option.value_1 }}</div>
 				</template>
 			</LeFormConfigDialog>
 		</div>
@@ -121,12 +121,12 @@ export default defineComponent({
 					// label: 'leSelect_label',
 					// label render 支持
 					label({ label }) {
-						return <span style='background: #f00;display: flex'>label custom: fn<span style='margin-left: auto; background: #0f0'>{ label }</span></span>
+						return <span style='background: var(--el-color-danger);display: flex'>label custom: fn<span style='margin-left: auto; background: #0f0'>{ label }</span></span>
 					},
 					// option: 'leSelectSlot',
 					// option({ value, label, disabled }) { // select 类型处理
 					option({ item, index, disabled }){ // leSelect (基于el-select-v2 二开)
-						const style = `color: #fff; background: #00f`
+						const style = `color: #fff; background: var(--el-color-primary)`
 						return <div style={style}>{item.le_label} ttt</div>
 					},
 				},
@@ -166,7 +166,7 @@ export default defineComponent({
 				render: (extendsParams) => {
 					const { form, params } = extendsParams
 					// console.error(form, params, '///////////')
-					return <div style="background: #f0f; display: flex; width: 100%;">
+					return <div style="background: var(--el-color-danger-light-3); display: flex; width: 100%;">
 						<el-input modelValue={params[form.prop]} onInput={e => (params[form.prop] = e)} placeholder="placeholder render" />
 						<span style="min-width: 130px;padding: 0 10px; text-align: center;"> -render, others -</span>
 						<el-input modelValue={params.others} onInput={e => (params.others = e)} placeholder="placeholder others" />
@@ -187,7 +187,7 @@ export default defineComponent({
 					/*label({ label }) {
 						// console.log(label, '带 {label} 参数')
 						return (
-							<span style="background: #f00;display: flex">
+							<span style="background: var(--el-color-danger);display: flex">
 								label custom: fn<span style="margin-left: auto; background: #0f0">{label}</span>
 							</span>
 						)
@@ -310,7 +310,7 @@ export default defineComponent({
 				slots: {
 					// // option: 'cascaderSelectSlot',
 					option: ({node, data}) => {
-						return <div style="color: #f0f;"><le-icon iconClass="icon-logo"/>{data.label}</div>
+						return <div style="color: var(--el-color-danger-light-3);"><le-icon iconClass="icon-logo"/>{data.label}</div>
 					},
 				},
 				change(...args) {
@@ -340,7 +340,7 @@ export default defineComponent({
 				itemStyle: 'min-width: 200px',
 				class: 'inputNumber_class_class',
 			  slots: {
-					prefix: () => <span class="le-addon le-input-number__prefix" style="background: #0f0;">prefix</span>,
+					prefix: () => <span class="le-addon le-input-number__prefix" style="background: var(--el-color-success);">prefix</span>,
 					suffix: () => <span class="le-addon le-input-number__suffix" style="background: red; /*height: 45px;*/">suffix</span>
 					// onlyTest: () => <span class="le-addon le-input-number__suffix" style="background: red; /*height: 45px;*/">onlyTest</span>
 				},
@@ -385,7 +385,7 @@ export default defineComponent({
 				slots: {
 					prepend: () => <span class="le-addon le-input-number__prefix" style="background: yellow;">prepend</span>,
 					append: () => <span class="le-addon le-input-number__prefix" style="background: yellow;">append</span>,
-					prefix: () => <span class="le-addon le-input-number__prefix" style="background: #0f0;">prefix</span>,
+					prefix: () => <span class="le-addon le-input-number__prefix" style="background: var(--el-color-success);">prefix</span>,
 					suffix: () => <span class="le-addon le-input-number__suffix" style="background: red; /*height: 45px;*/">suffix</span>
 				},
 				itemType: 'inputNumberRange',
@@ -554,12 +554,12 @@ export default defineComponent({
 						// label: 'leSelect_label',
 						// label render 支持
 						label({ label }) {
-							return <span style='background: #f00;display: flex'>label custom: fn<span style='margin-left: auto; background: #0f0'>{ label }</span></span>
+							return <span style='background: var(--el-color-danger);display: flex'>label custom: fn<span style='margin-left: auto; background: #0f0'>{ label }</span></span>
 						},
 						// option: 'leSelectSlot',
 						// option({ value, label, disabled }) { // select 类型处理
 						option({ item, index, disabled }){ // leSelect (基于el-select-v2 二开)
-							const style = `color: #fff; background: #00f`
+							const style = `color: #fff; background: var(--el-color-primary)`
 							return <div style={style}>{item.le_label} ttt</div>
 						},
 					},

+ 2 - 3
src/views/table/default.vue

@@ -121,16 +121,15 @@ export default {
 	//padding-top: 12px;
 	padding: 12px 12px 0 12px;
 	overflow: auto;
-	background-color: #f5f6f7;
+	background-color: $le-bg-color_1;
 }
 
 // 其他样式
 .local_table {
 	//padding: 0 12px;
 	box-shadow: 0 0 6px 4px rgb(145 159 175 / 6%);
-	border-top: 1px solid #eaedf0;
+	border-top: 1px solid $le-border-color_3;
 	border-radius: 6px 6px 0 0;
-	background-color: #fff;
 	//padding: 12px 12px 0 12px;
 	&.tabs_content-wrap {
 		border-top: 0;

+ 4 - 4
src/views/table/default_config.jsx

@@ -18,7 +18,7 @@ const slot_user = (scope) => {
 	return [
 		<div style={'background: #0ff;'}>
 			slot:default <br />
-			<div style={'color: #00f'}>{row[column.property]}</div>
+			<div style={'color: var(--el-color-primary)'}>{row[column.property]}</div>
 		</div>
 	]
 }
@@ -64,7 +64,7 @@ export const columns = [
 		// 用户提示配置
 		titleHelp: {
 			icon: 'le-iconfont le-check_1', // todo 自定义icon
-			message: `<span style='background: #f00'>wo的 <br/>sssssssssss</span>`
+			message: `<span style='background: var(--el-color-danger)'>wo的 <br/>sssssssssss</span>`
 		},
 		slots: {
 			/**
@@ -84,7 +84,7 @@ export const columns = [
 			 */
 			// console.error(maybeRow, 'maybeRow ', others, 'others')
 			// const row = maybeRow.row || maybeRow
-			// return <span style="background: #f00;">{'orderNo: formatter: ' + row.orderNo}</span>
+			// return <span style="background: var(--el-color-danger);">{'orderNo: formatter: ' + row.orderNo}</span>
 			return others[1]// cellValue
 		}
 	},
@@ -406,7 +406,7 @@ export const get_tabs_filterForms = () => [
 			const { form, params } = extendsParams
 			// console.error(params, 'params')
 			return (
-				<div style="background: #f0f;">
+				<div style="background: var(--el-color-danger-light-3);">
 					<el-input value={params[form.prop]} on-input={e => (params[form.prop] = e)} placeholder="placeholder render" />
 					<el-input value={params.others} on-input={e => (params.others = e)} placeholder="placeholder others" />
 				</div>

+ 5 - 6
src/views/table/expandTable.vue

@@ -14,7 +14,7 @@
 				<h3 style="line-height: 36px">展开行表格示例</h3>
 			</template>
 			<template #expand="{ row }">
-				<p style="background: #00f;">名称: {{ row.name }}</p>
+				<p style="background: var(--el-color-primary);">名称: {{ row.name }}</p>
 				<p>日期: {{ row.date }}</p>
 				<p>金额1: {{ row.amount1 }}</p>
 				<p>金额2: {{ row.amount2 }}</p>
@@ -48,12 +48,12 @@ const columns = [
 		type: 'expand',
 		slots: {
 			header: () => {
-				return <div style="color: #f00;">展开行</div>
+				return <div style="color: var(--el-color-danger);">展开行</div>
 			},
 			// 两种渲染方式
 			default: 'expand',
 			/*default: ({row, column}) => {
-				return <><p style="background: #f00;">名称: { row.name }</p>
+				return <><p style="background: var(--el-color-danger);">名称: { row.name }</p>
 					<p>日期: { row.date }</p>
 					<p>金额1: { row.amount1 }</p>
 					<p>金额2: { row.amount2 }</p></>
@@ -162,16 +162,15 @@ onMounted(() => {
 	//padding-top: 12px;
 	padding: 12px 12px 0 12px;
 	overflow: auto;
-	background-color: #f5f6f7;
+	background-color: $le-bg-color_1;
 }
 
 // 其他样式
 .local_table {
 	//padding: 0 12px;
 	box-shadow: 0 0 6px 4px rgb(145 159 175 / 6%);
-	border-top: 1px solid #eaedf0;
+	border-top: 1px solid $le-border-color_3;
 	border-radius: 6px 6px 0 0;
-	background-color: #fff;
 	//padding: 12px 12px 0 12px;
 	&.tabs_content-wrap {
 		border-top: 0;

+ 3 - 3
src/views/table/footerSummary.vue

@@ -48,7 +48,7 @@ const options = ref({
 					curPageTotal: {data.length}
 				</div>)
 			} else {
-				sums[index] = <div style="color: #f00">- 空 -</div>
+				sums[index] = <div style="color: var(--el-color-danger)">- 空 -</div>
 			}
 		})
 		return sums
@@ -152,14 +152,14 @@ onMounted(() => {
 	//padding-top: 12px;
 	padding: 12px 12px 0 12px;
 	overflow: auto;
-	background-color: #f5f6f7;
+	background-color: $le-bg-color_1;
 }
 
 // 其他样式
 .local_table {
 	//padding: 0 12px;
 	box-shadow: 0 0 6px 4px rgb(145 159 175 / 6%);
-	border-top: 1px solid #eaedf0;
+	border-top: 1px solid $le-border-color_3;
 	border-radius: 6px 6px 0 0;
 	background-color: #fff;
 	//padding: 12px 12px 0 12px;

+ 2 - 3
src/views/table/mergeCells.vue

@@ -149,16 +149,15 @@ onMounted(() => {
 	//padding-top: 12px;
 	padding: 12px 12px 0 12px;
 	overflow: auto;
-	background-color: #f5f6f7;
+	background-color: $le-bg-color_1;
 }
 
 // 其他样式
 .local_table {
 	//padding: 0 12px;
 	box-shadow: 0 0 6px 4px rgb(145 159 175 / 6%);
-	border-top: 1px solid #eaedf0;
+	border-top: 1px solid $le-border-color_3;
 	border-radius: 6px 6px 0 0;
-	background-color: #fff;
 	//padding: 12px 12px 0 12px;
 	&.tabs_content-wrap {
 		border-top: 0;

+ 2 - 2
src/views/table/multipleHeader.vue

@@ -351,14 +351,14 @@ onMounted(() => {
 	//padding-top: 12px;
 	padding: 12px 12px 0 12px;
 	overflow: auto;
-	background-color: #f5f6f7;
+	background-color: $le-bg-color_1;
 }
 
 // 其他样式
 .local_table {
 	//padding: 0 12px;
 	box-shadow: 0 0 6px 4px rgb(145 159 175 / 6%);
-	border-top: 1px solid #eaedf0;
+	border-top: 1px solid $le-border-color_3;
 	border-radius: 6px 6px 0 0;
 	background-color: #fff;
 	//padding: 12px 12px 0 12px;

+ 3 - 2
src/views/table/resizeParentHeightTable.vue

@@ -14,6 +14,7 @@ import MergeCells from '@/views/table/mergeCells.vue'
 	//padding-top: 12px;
 	//padding: 12px 12px 0 12px;
 	overflow: auto;
-	background-color: #f5f6f7;
+	//background-color: $le-bg-color_1;
+	background: var(--el-bg-color);
 }
-</style>
+</style>

+ 2 - 2
src/views/table/treeTable.vue

@@ -176,14 +176,14 @@ onMounted(() => {
 	//padding-top: 12px;
 	padding: 12px 12px 0 12px;
 	overflow: auto;
-	background-color: #f5f6f7;
+	background-color: $le-bg-color_1;
 }
 
 // 其他样式
 .local_table {
 	//padding: 0 12px;
 	box-shadow: 0 0 6px 4px rgb(145 159 175 / 6%);
-	border-top: 1px solid #eaedf0;
+	border-top: 1px solid $le-border-color_3;
 	border-radius: 6px 6px 0 0;
 	background-color: #fff;
 	//padding: 12px 12px 0 12px;

+ 1 - 1
src/views/test/componentCommunication/components/Comp2.vue

@@ -21,7 +21,7 @@ export default {
 		}
 	}
 	// render() {
-	//   return <div style='background: #f0f;'>我是 component 2</div>
+	//   return <div style='background: var(--el-color-danger-light-3);'>我是 component 2</div>
 	// }
 }
 </script>