Merge branch 'dev' of github.com:youlaitech/vue3-element-admin
Former-commit-id: 852c9b4d01bdc17d5670f30348db3099899344b9
This commit is contained in:
@@ -5,7 +5,8 @@ root = true
|
|||||||
[*]
|
[*]
|
||||||
charset = utf-8 # 设置文件字符集为 utf-8
|
charset = utf-8 # 设置文件字符集为 utf-8
|
||||||
end_of_line = lf # 控制换行类型(lf | cr | crlf)
|
end_of_line = lf # 控制换行类型(lf | cr | crlf)
|
||||||
indent_style = tab # 缩进风格(tab | space)
|
indent_style = space # 缩进风格(tab | space)
|
||||||
|
indent_size = 2 # 缩进大小
|
||||||
insert_final_newline = true # 始终在文件末尾插入一个新行
|
insert_final_newline = true # 始终在文件末尾插入一个新行
|
||||||
|
|
||||||
# 表示仅 md 文件适用以下规则
|
# 表示仅 md 文件适用以下规则
|
||||||
|
|||||||
@@ -64,6 +64,12 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
"vue/multi-word-component-names": "off",
|
"vue/multi-word-component-names": "off",
|
||||||
|
"prettier/prettier": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
useTabs: false, // 不使用制表符
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
// eslint不能对html文件生效
|
// eslint不能对html文件生效
|
||||||
overrides: [
|
overrides: [
|
||||||
|
|||||||
66
src/layout/components/Sidebar/LeftMenu.vue
Normal file
66
src/layout/components/Sidebar/LeftMenu.vue
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import SidebarItem from "./SidebarItem.vue";
|
||||||
|
import { useSettingsStore } from "@/store/modules/settings";
|
||||||
|
import { useAppStore } from "@/store/modules/app";
|
||||||
|
import variables from "@/styles/variables.module.scss";
|
||||||
|
|
||||||
|
import path from "path-browserify";
|
||||||
|
import { isExternal } from "@/utils/index";
|
||||||
|
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const currRoute = useRoute();
|
||||||
|
const layout = computed(() => settingsStore.layout);
|
||||||
|
const props = defineProps({
|
||||||
|
menuList: {
|
||||||
|
required: true,
|
||||||
|
default: () => {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
type: Array<any>,
|
||||||
|
},
|
||||||
|
basePath: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析路径
|
||||||
|
*
|
||||||
|
* @param routePath 路由路径
|
||||||
|
*/
|
||||||
|
function resolvePath(routePath: string) {
|
||||||
|
if (isExternal(routePath)) {
|
||||||
|
return routePath;
|
||||||
|
}
|
||||||
|
if (isExternal(props.basePath)) {
|
||||||
|
return props.basePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完整路径 = 父级路径(/level/level_3) + 路由路径
|
||||||
|
const fullPath = path.resolve(props.basePath, routePath); // 相对路径 → 绝对路径
|
||||||
|
return fullPath;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<el-menu
|
||||||
|
:default-active="layout === 'top' ? '-' : currRoute.path"
|
||||||
|
:collapse="!appStore.sidebar.opened"
|
||||||
|
:background-color="variables.menuBg"
|
||||||
|
:text-color="variables.menuText"
|
||||||
|
:active-text-color="variables.menuActiveText"
|
||||||
|
:unique-opened="false"
|
||||||
|
:collapse-transition="false"
|
||||||
|
:mode="layout === 'top' ? 'horizontal' : 'vertical'"
|
||||||
|
>
|
||||||
|
<sidebar-item
|
||||||
|
v-for="route in menuList"
|
||||||
|
:key="route.path"
|
||||||
|
:item="route"
|
||||||
|
:base-path="resolvePath(route.path)"
|
||||||
|
:is-collapse="!appStore.sidebar.opened"
|
||||||
|
/>
|
||||||
|
</el-menu>
|
||||||
|
</template>
|
||||||
@@ -54,6 +54,7 @@ function hasOneShowingChild(children = [], parent: any) {
|
|||||||
// 2:如果无子路由, 复制当前路由信息作为其子路由,满足只拥有一个子路由的条件,所以返回 true
|
// 2:如果无子路由, 复制当前路由信息作为其子路由,满足只拥有一个子路由的条件,所以返回 true
|
||||||
if (showingChildren.length === 0) {
|
if (showingChildren.length === 0) {
|
||||||
onlyOneChild.value = { ...parent, path: "", noShowingChildren: true };
|
onlyOneChild.value = { ...parent, path: "", noShowingChildren: true };
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -71,6 +72,7 @@ function resolvePath(routePath: string) {
|
|||||||
if (isExternal(props.basePath)) {
|
if (isExternal(props.basePath)) {
|
||||||
return props.basePath;
|
return props.basePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 完整路径 = 父级路径(/level/level_3) + 路由路径
|
// 完整路径 = 父级路径(/level/level_3) + 路由路径
|
||||||
const fullPath = path.resolve(props.basePath, routePath); // 相对路径 → 绝对路径
|
const fullPath = path.resolve(props.basePath, routePath); // 相对路径 → 绝对路径
|
||||||
return fullPath;
|
return fullPath;
|
||||||
|
|||||||
78
src/layout/components/Sidebar/TopMenu.vue
Normal file
78
src/layout/components/Sidebar/TopMenu.vue
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { usePermissionStore } from "@/store/modules/permission";
|
||||||
|
import variables from "@/styles/variables.module.scss";
|
||||||
|
import { useAppStore } from "@/store/modules/app";
|
||||||
|
import { translateRouteTitleI18n } from "@/utils/i18n";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const activePath = computed(() => appStore.activeTopMenu);
|
||||||
|
const router = useRouter();
|
||||||
|
// 递归跳转
|
||||||
|
const goFirst = (menu: any[]) => {
|
||||||
|
if (!menu.length) return;
|
||||||
|
const [first] = menu;
|
||||||
|
if (first.children) {
|
||||||
|
goFirst(first.children);
|
||||||
|
} else {
|
||||||
|
router.push({
|
||||||
|
name: first.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const selectMenu = (index: string) => {
|
||||||
|
appStore.changeTopActive(index);
|
||||||
|
permissionStore.getMixLeftMenu(index);
|
||||||
|
const { mixLeftMenu } = permissionStore;
|
||||||
|
goFirst(mixLeftMenu);
|
||||||
|
};
|
||||||
|
const permissionStore = usePermissionStore();
|
||||||
|
const topMenu = ref<any[]>([]);
|
||||||
|
onMounted(() => {
|
||||||
|
topMenu.value = permissionStore.routes.filter(
|
||||||
|
(item) => !item.meta || !item.meta.hidden
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<el-scrollbar>
|
||||||
|
<el-menu
|
||||||
|
mode="horizontal"
|
||||||
|
:default-active="activePath"
|
||||||
|
:background-color="variables.menuBg"
|
||||||
|
:text-color="variables.menuText"
|
||||||
|
:active-text-color="variables.menuActiveText"
|
||||||
|
@select="selectMenu"
|
||||||
|
>
|
||||||
|
<el-menu-item
|
||||||
|
v-for="route in topMenu"
|
||||||
|
:key="route.path"
|
||||||
|
:index="route.path"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<svg-icon
|
||||||
|
v-if="route.meta && route.meta.icon"
|
||||||
|
:icon-class="route.meta.icon"
|
||||||
|
/>
|
||||||
|
<span v-if="route.path === '/'"> 首页 </span>
|
||||||
|
<template v-else>
|
||||||
|
<span v-if="route.meta && route.meta.title">
|
||||||
|
{{ translateRouteTitleI18n(route.meta.title) }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-menu-item>
|
||||||
|
<!-- <sidebar-item
|
||||||
|
v-for="route in topMenu"
|
||||||
|
:key="route.path"
|
||||||
|
:item="route"
|
||||||
|
:base-path="route.path"
|
||||||
|
:is-collapse="false"
|
||||||
|
/> -->
|
||||||
|
</el-menu>
|
||||||
|
</el-scrollbar>
|
||||||
|
</template>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.el-menu {
|
||||||
|
height: 50px !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,18 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRoute } from "vue-router";
|
import TopMenu from "./TopMenu.vue";
|
||||||
import SidebarItem from "./SidebarItem.vue";
|
import LeftMenu from "./LeftMenu.vue";
|
||||||
import Logo from "./Logo.vue";
|
import Logo from "./Logo.vue";
|
||||||
|
|
||||||
import { useSettingsStore } from "@/store/modules/settings";
|
import { useSettingsStore } from "@/store/modules/settings";
|
||||||
import { usePermissionStore } from "@/store/modules/permission";
|
import { usePermissionStore } from "@/store/modules/permission";
|
||||||
import { useAppStore } from "@/store/modules/app";
|
import { useAppStore } from "@/store/modules/app";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import variables from "@/styles/variables.module.scss";
|
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const permissionStore = usePermissionStore();
|
const permissionStore = usePermissionStore();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const currRoute = useRoute();
|
|
||||||
const { sidebarLogo } = storeToRefs(settingsStore);
|
const { sidebarLogo } = storeToRefs(settingsStore);
|
||||||
const layout = computed(() => settingsStore.layout);
|
const layout = computed(() => settingsStore.layout);
|
||||||
const showContent = ref(true);
|
const showContent = ref(true);
|
||||||
@@ -28,41 +25,86 @@ watch(
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="{ 'has-logo': sidebarLogo }" class="menu-wrap">
|
<div
|
||||||
|
:class="{ 'has-logo': sidebarLogo }"
|
||||||
|
class="menu-wrap"
|
||||||
|
v-if="layout !== 'mix'"
|
||||||
|
>
|
||||||
<logo v-if="sidebarLogo" :collapse="!appStore.sidebar.opened" />
|
<logo v-if="sidebarLogo" :collapse="!appStore.sidebar.opened" />
|
||||||
<el-scrollbar v-if="showContent">
|
<el-scrollbar v-if="showContent">
|
||||||
<el-menu
|
<LeftMenu :menu-list="permissionStore.routes" base-path="" />
|
||||||
:default-active="layout === 'top' ? '-' : currRoute.path"
|
|
||||||
:collapse="!appStore.sidebar.opened"
|
|
||||||
:background-color="variables.menuBg"
|
|
||||||
:text-color="variables.menuText"
|
|
||||||
:active-text-color="variables.menuActiveText"
|
|
||||||
:unique-opened="false"
|
|
||||||
:collapse-transition="false"
|
|
||||||
:mode="layout === 'top' ? 'horizontal' : 'vertical'"
|
|
||||||
>
|
|
||||||
<sidebar-item
|
|
||||||
v-for="route in permissionStore.routes"
|
|
||||||
:key="route.path"
|
|
||||||
:item="route"
|
|
||||||
:base-path="route.path"
|
|
||||||
:is-collapse="!appStore.sidebar.opened"
|
|
||||||
/>
|
|
||||||
</el-menu>
|
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
<NavRight v-if="layout === 'top'" />
|
<NavRight v-if="layout === 'top'" />
|
||||||
</div>
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<div :class="{ 'has-logo': sidebarLogo }" class="menu-wrap">
|
||||||
|
<div class="header">
|
||||||
|
<logo v-if="sidebarLogo" :collapse="!appStore.sidebar.opened" />
|
||||||
|
<TopMenu />
|
||||||
|
<NavRight />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
:deep(.setting-container) {
|
:deep(.setting-container) {
|
||||||
.setting-item {
|
.setting-item {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
||||||
.svg-icon {
|
.svg-icon {
|
||||||
margin-right: 0px;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.isMix {
|
||||||
|
.menu-wrap {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 50px;
|
||||||
|
background-color: $menuBg;
|
||||||
|
|
||||||
|
:deep(.header) {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
// 顶部模式全局变量修改
|
||||||
|
--el-menu-item-height: 50px;
|
||||||
|
|
||||||
|
.logo-wrap {
|
||||||
|
width: $sideBarWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu {
|
||||||
|
background-color: $menuBg;
|
||||||
|
|
||||||
|
.el-menu-item {
|
||||||
|
color: $menuText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-scrollbar {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-menu {
|
||||||
|
display: inline-block;
|
||||||
|
width: $sideBarWidth;
|
||||||
|
background-color: $menuBg;
|
||||||
|
|
||||||
|
:deep(.el-menu) {
|
||||||
|
background-color: $menuBg;
|
||||||
|
|
||||||
|
.el-menu-item {
|
||||||
|
color: $menuText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import { translateRouteTitleI18n } from "@/utils/i18n";
|
|||||||
|
|
||||||
import { usePermissionStore } from "@/store/modules/permission";
|
import { usePermissionStore } from "@/store/modules/permission";
|
||||||
import { useTagsViewStore, TagView } from "@/store/modules/tagsView";
|
import { useTagsViewStore, TagView } from "@/store/modules/tagsView";
|
||||||
|
import { useSettingsStore } from "@/store/modules/settings";
|
||||||
|
import { useAppStore } from "@/store/modules/app";
|
||||||
|
|
||||||
import ScrollPane from "./ScrollPane.vue";
|
import ScrollPane from "./ScrollPane.vue";
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
@@ -18,8 +21,11 @@ const route = useRoute();
|
|||||||
|
|
||||||
const permissionStore = usePermissionStore();
|
const permissionStore = usePermissionStore();
|
||||||
const tagsViewStore = useTagsViewStore();
|
const tagsViewStore = useTagsViewStore();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
|
||||||
const { visitedViews } = storeToRefs(tagsViewStore);
|
const { visitedViews } = storeToRefs(tagsViewStore);
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
const layout = computed(() => settingsStore.layout);
|
||||||
|
|
||||||
const selectedTag = ref({});
|
const selectedTag = ref({});
|
||||||
const scrollPaneRef = ref();
|
const scrollPaneRef = ref();
|
||||||
@@ -226,7 +232,52 @@ function closeTagMenu() {
|
|||||||
function handleScroll() {
|
function handleScroll() {
|
||||||
closeTagMenu();
|
closeTagMenu();
|
||||||
}
|
}
|
||||||
|
function findOutermostParent(tree: any[], findName: string) {
|
||||||
|
let parentMap: any = {};
|
||||||
|
|
||||||
|
function buildParentMap(node: any, parent: any) {
|
||||||
|
parentMap[node.name] = parent;
|
||||||
|
|
||||||
|
if (node.children) {
|
||||||
|
for (let i = 0; i < node.children.length; i++) {
|
||||||
|
buildParentMap(node.children[i], node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < tree.length; i++) {
|
||||||
|
buildParentMap(tree[i], null);
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentNode = parentMap[findName];
|
||||||
|
while (currentNode) {
|
||||||
|
if (!parentMap[currentNode.name]) {
|
||||||
|
return currentNode;
|
||||||
|
}
|
||||||
|
currentNode = parentMap[currentNode.name];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const againActiveTop = (newVal: string) => {
|
||||||
|
if (layout.value !== "mix") return;
|
||||||
|
const parent = findOutermostParent(permissionStore.routes, newVal);
|
||||||
|
if (appStore.activeTopMenu !== parent.path) {
|
||||||
|
appStore.changeTopActive(parent.path);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 如果是混合模式,更改selectedTag,需要对应高亮的activeTop
|
||||||
|
watch(
|
||||||
|
() => route.name,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
againActiveTop(newVal as string);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initTags();
|
initTags();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import Main from "./main.vue";
|
||||||
import { computed, watchEffect } from "vue";
|
import { computed, watchEffect } from "vue";
|
||||||
import { useWindowSize } from "@vueuse/core";
|
import { useWindowSize } from "@vueuse/core";
|
||||||
import { AppMain, Navbar, Settings, TagsView } from "./components/index";
|
|
||||||
import Sidebar from "./components/Sidebar/index.vue";
|
import Sidebar from "./components/Sidebar/index.vue";
|
||||||
import RightPanel from "@/components/RightPanel/index.vue";
|
import LeftMenu from "./components/Sidebar/LeftMenu.vue";
|
||||||
|
|
||||||
import { useAppStore } from "@/store/modules/app";
|
import { useAppStore } from "@/store/modules/app";
|
||||||
import { useSettingsStore } from "@/store/modules/settings";
|
import { useSettingsStore } from "@/store/modules/settings";
|
||||||
|
import { usePermissionStore } from "@/store/modules/permission";
|
||||||
|
const permissionStore = usePermissionStore();
|
||||||
const { width } = useWindowSize();
|
const { width } = useWindowSize();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 响应式布局容器固定宽度
|
* 响应式布局容器固定宽度
|
||||||
*
|
*
|
||||||
@@ -22,10 +22,25 @@ const WIDTH = 992;
|
|||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
const fixedHeader = computed(() => settingsStore.fixedHeader);
|
const activeTopMenu = computed(() => {
|
||||||
const showTagsView = computed(() => settingsStore.tagsView);
|
return appStore.activeTopMenu;
|
||||||
const showSettings = computed(() => settingsStore.showSettings);
|
});
|
||||||
|
// 混合模式左侧菜单
|
||||||
|
const mixLeftMenu = computed(() => {
|
||||||
|
return permissionStore.mixLeftMenu;
|
||||||
|
});
|
||||||
const layout = computed(() => settingsStore.layout);
|
const layout = computed(() => settingsStore.layout);
|
||||||
|
watch(
|
||||||
|
() => activeTopMenu.value,
|
||||||
|
(newVal) => {
|
||||||
|
if (layout.value !== "mix") return;
|
||||||
|
permissionStore.getMixLeftMenu(newVal);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const classObj = computed(() => ({
|
const classObj = computed(() => ({
|
||||||
hideSidebar: !appStore.sidebar.opened,
|
hideSidebar: !appStore.sidebar.opened,
|
||||||
@@ -33,6 +48,7 @@ const classObj = computed(() => ({
|
|||||||
withoutAnimation: appStore.sidebar.withoutAnimation,
|
withoutAnimation: appStore.sidebar.withoutAnimation,
|
||||||
mobile: appStore.device === "mobile",
|
mobile: appStore.device === "mobile",
|
||||||
isTop: layout.value === "top",
|
isTop: layout.value === "top",
|
||||||
|
isMix: layout.value === "mix",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
@@ -66,21 +82,15 @@ function handleOutsideClick() {
|
|||||||
></div>
|
></div>
|
||||||
|
|
||||||
<Sidebar class="sidebar-container" />
|
<Sidebar class="sidebar-container" />
|
||||||
|
<template v-if="layout === 'mix'">
|
||||||
<div :class="{ hasTagsView: showTagsView }" class="main-container">
|
<div class="mix-wrap">
|
||||||
<div :class="{ 'fixed-header': fixedHeader }">
|
<!-- :menu-list="mixLeftMenu -->
|
||||||
<navbar v-if="layout !== 'top'" />
|
<!-- :menu-list="permissionStore.routes -->
|
||||||
<tags-view v-if="showTagsView" />
|
<LeftMenu :menu-list="mixLeftMenu" :base-path="activeTopMenu" />
|
||||||
</div>
|
<Main />
|
||||||
|
|
||||||
<!--主页面-->
|
|
||||||
<app-main />
|
|
||||||
|
|
||||||
<!-- 设置面板 -->
|
|
||||||
<RightPanel v-if="showSettings">
|
|
||||||
<settings />
|
|
||||||
</RightPanel>
|
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
<Main v-else />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -137,7 +147,7 @@ function handleOutsideClick() {
|
|||||||
height: 50px;
|
height: 50px;
|
||||||
|
|
||||||
:deep(.logo-wrap) {
|
:deep(.logo-wrap) {
|
||||||
width: 210px;
|
width: $sideBarWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-scrollbar) {
|
:deep(.el-scrollbar) {
|
||||||
@@ -155,4 +165,30 @@ function handleOutsideClick() {
|
|||||||
// 顶部模式全局变量修改
|
// 顶部模式全局变量修改
|
||||||
--el-menu-item-height: 50px;
|
--el-menu-item-height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.isMix {
|
||||||
|
:deep(.main-container) {
|
||||||
|
display: inline-block;
|
||||||
|
width: calc(100% - #{$sideBarWidth});
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mix-wrap {
|
||||||
|
display: flex;
|
||||||
|
padding-top: 50px;
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.openSidebar {
|
||||||
|
.mix-wrap {
|
||||||
|
.el-menu {
|
||||||
|
width: $sideBarWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
57
src/layout/main.vue
Normal file
57
src/layout/main.vue
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, watchEffect } from "vue";
|
||||||
|
import { useWindowSize } from "@vueuse/core";
|
||||||
|
import { AppMain, Navbar, Settings, TagsView } from "./components/index";
|
||||||
|
import RightPanel from "@/components/RightPanel/index.vue";
|
||||||
|
|
||||||
|
import { useAppStore } from "@/store/modules/app";
|
||||||
|
import { useSettingsStore } from "@/store/modules/settings";
|
||||||
|
const { width } = useWindowSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应式布局容器固定宽度
|
||||||
|
*
|
||||||
|
* 大屏(>=1200px)
|
||||||
|
* 中屏(>=992px)
|
||||||
|
* 小屏(>=768px)
|
||||||
|
*/
|
||||||
|
const WIDTH = 992;
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
|
const fixedHeader = computed(() => settingsStore.fixedHeader);
|
||||||
|
const showTagsView = computed(() => settingsStore.tagsView);
|
||||||
|
const showSettings = computed(() => settingsStore.showSettings);
|
||||||
|
const layout = computed(() => settingsStore.layout);
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (width.value < WIDTH) {
|
||||||
|
appStore.toggleDevice("mobile");
|
||||||
|
appStore.closeSideBar(true);
|
||||||
|
} else {
|
||||||
|
appStore.toggleDevice("desktop");
|
||||||
|
|
||||||
|
if (width.value >= 1200) {
|
||||||
|
//大屏
|
||||||
|
appStore.openSideBar(true);
|
||||||
|
} else {
|
||||||
|
appStore.closeSideBar(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div :class="{ hasTagsView: showTagsView }" class="main-container">
|
||||||
|
<div :class="{ 'fixed-header': fixedHeader }">
|
||||||
|
<navbar v-if="layout === 'left'" />
|
||||||
|
<tags-view v-if="showTagsView" />
|
||||||
|
</div>
|
||||||
|
<!--主页面-->
|
||||||
|
<app-main />
|
||||||
|
<!-- 设置面板 -->
|
||||||
|
<RightPanel v-if="showSettings">
|
||||||
|
<settings />
|
||||||
|
</RightPanel>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -24,6 +24,7 @@ export const constantRoutes: RouteRecordRaw[] = [
|
|||||||
|
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
|
name: "/",
|
||||||
component: Layout,
|
component: Layout,
|
||||||
redirect: "/dashboard",
|
redirect: "/dashboard",
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -14,11 +14,12 @@ export const useAppStore = defineStore("app", () => {
|
|||||||
const language = useStorage("language", defaultSettings.language);
|
const language = useStorage("language", defaultSettings.language);
|
||||||
|
|
||||||
const sidebarStatus = useStorage("sidebarStatus", "closed");
|
const sidebarStatus = useStorage("sidebarStatus", "closed");
|
||||||
|
|
||||||
const sidebar = reactive({
|
const sidebar = reactive({
|
||||||
opened: sidebarStatus.value !== "closed",
|
opened: sidebarStatus.value !== "closed",
|
||||||
withoutAnimation: false,
|
withoutAnimation: false,
|
||||||
});
|
});
|
||||||
|
const activeTopMenu = useStorage("activeTop", "");
|
||||||
/**
|
/**
|
||||||
* 根据语言标识读取对应的语言包
|
* 根据语言标识读取对应的语言包
|
||||||
*/
|
*/
|
||||||
@@ -68,18 +69,25 @@ export const useAppStore = defineStore("app", () => {
|
|||||||
function changeLanguage(val: string) {
|
function changeLanguage(val: string) {
|
||||||
language.value = val;
|
language.value = val;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 混合模式顶部切换
|
||||||
|
*/
|
||||||
|
function changeTopActive(val: string) {
|
||||||
|
activeTopMenu.value = val;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
device,
|
device,
|
||||||
sidebar,
|
sidebar,
|
||||||
language,
|
language,
|
||||||
locale,
|
locale,
|
||||||
size,
|
size,
|
||||||
|
activeTopMenu,
|
||||||
toggleDevice,
|
toggleDevice,
|
||||||
changeSize,
|
changeSize,
|
||||||
changeLanguage,
|
changeLanguage,
|
||||||
toggleSidebar,
|
toggleSidebar,
|
||||||
closeSideBar,
|
closeSideBar,
|
||||||
openSideBar,
|
openSideBar,
|
||||||
|
changeTopActive,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { RouteRecordRaw } from "vue-router";
|
import { RouteRecordRaw, useRouter } from "vue-router";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { constantRoutes } from "@/router";
|
import { constantRoutes } from "@/router";
|
||||||
import { store } from "@/store";
|
import { store } from "@/store";
|
||||||
@@ -41,12 +41,13 @@ const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
|
|||||||
|
|
||||||
routes.forEach((route) => {
|
routes.forEach((route) => {
|
||||||
const tmpRoute = { ...route }; // ES6扩展运算符复制新对象
|
const tmpRoute = { ...route }; // ES6扩展运算符复制新对象
|
||||||
|
if (!route.name) {
|
||||||
|
tmpRoute.name = route.path;
|
||||||
|
}
|
||||||
// 判断用户(角色)是否有该路由的访问权限
|
// 判断用户(角色)是否有该路由的访问权限
|
||||||
if (hasPermission(roles, tmpRoute)) {
|
if (hasPermission(roles, tmpRoute)) {
|
||||||
if (tmpRoute.component?.toString() == "Layout") {
|
if (tmpRoute.component?.toString() == "Layout") {
|
||||||
tmpRoute.component = Layout;
|
tmpRoute.component = Layout;
|
||||||
console.log();
|
|
||||||
} else {
|
} else {
|
||||||
const component = modules[`../../views/${tmpRoute.component}.vue`];
|
const component = modules[`../../views/${tmpRoute.component}.vue`];
|
||||||
if (component) {
|
if (component) {
|
||||||
@@ -97,7 +98,19 @@ export const usePermissionStore = defineStore("permission", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { routes, setRoutes, generateRoutes };
|
|
||||||
|
/**
|
||||||
|
* 混合模式左侧菜单
|
||||||
|
*/
|
||||||
|
const mixLeftMenu = ref<RouteRecordRaw[]>([]);
|
||||||
|
function getMixLeftMenu(activeTop: string) {
|
||||||
|
routes.value.forEach((item) => {
|
||||||
|
if (item.path === activeTop) {
|
||||||
|
mixLeftMenu.value = item.children || [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return { routes, setRoutes, generateRoutes, getMixLeftMenu, mixLeftMenu };
|
||||||
});
|
});
|
||||||
|
|
||||||
// 非setup
|
// 非setup
|
||||||
|
|||||||
@@ -144,15 +144,46 @@
|
|||||||
.sidebar-container {
|
.sidebar-container {
|
||||||
width: $sideBarWidth !important;
|
width: $sideBarWidth !important;
|
||||||
transition: transform 0.28s;
|
transition: transform 0.28s;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
.logo-wrap {
|
||||||
|
width: 63px !important;
|
||||||
|
transition: transform 0.28s;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.hideSidebar {
|
&.hideSidebar:not(.isMix) {
|
||||||
.sidebar-container {
|
.sidebar-container {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition-duration: 0.3s;
|
transition-duration: 0.3s;
|
||||||
transform: translate3d(-$sideBarWidth, 0, 0);
|
transform: translate3d(-$sideBarWidth, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.hideSidebar {
|
||||||
|
.isMix {
|
||||||
|
:deep(.sidebar-container) {
|
||||||
|
.header {
|
||||||
|
.logo-wrap {
|
||||||
|
width: 64px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.openSidebar {
|
||||||
|
.isMix {
|
||||||
|
:deep(.sidebar-container) {
|
||||||
|
.header {
|
||||||
|
.logo-wrap {
|
||||||
|
width: 210px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.withoutAnimation {
|
.withoutAnimation {
|
||||||
|
|||||||
Reference in New Issue
Block a user