diff --git a/.editorconfig b/.editorconfig index dd765724..00ee2de4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,7 +5,8 @@ root = true [*] charset = utf-8 # 设置文件字符集为 utf-8 end_of_line = lf # 控制换行类型(lf | cr | crlf) -indent_style = tab # 缩进风格(tab | space) +indent_style = space # 缩进风格(tab | space) +indent_size = 2 # 缩进大小 insert_final_newline = true # 始终在文件末尾插入一个新行 # 表示仅 md 文件适用以下规则 diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 3300699c..69c2fc75 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -64,6 +64,12 @@ module.exports = { }, ], "vue/multi-word-component-names": "off", + "prettier/prettier": [ + "error", + { + useTabs: false, // 不使用制表符 + }, + ], }, // eslint不能对html文件生效 overrides: [ diff --git a/src/layout/components/Sidebar/LeftMenu.vue b/src/layout/components/Sidebar/LeftMenu.vue new file mode 100644 index 00000000..16d13282 --- /dev/null +++ b/src/layout/components/Sidebar/LeftMenu.vue @@ -0,0 +1,66 @@ + + diff --git a/src/layout/components/Sidebar/SidebarItem.vue b/src/layout/components/Sidebar/SidebarItem.vue index 06ebf71d..d932eeb1 100644 --- a/src/layout/components/Sidebar/SidebarItem.vue +++ b/src/layout/components/Sidebar/SidebarItem.vue @@ -54,6 +54,7 @@ function hasOneShowingChild(children = [], parent: any) { // 2:如果无子路由, 复制当前路由信息作为其子路由,满足只拥有一个子路由的条件,所以返回 true if (showingChildren.length === 0) { onlyOneChild.value = { ...parent, path: "", noShowingChildren: true }; + return true; } return false; @@ -71,6 +72,7 @@ function resolvePath(routePath: string) { if (isExternal(props.basePath)) { return props.basePath; } + // 完整路径 = 父级路径(/level/level_3) + 路由路径 const fullPath = path.resolve(props.basePath, routePath); // 相对路径 → 绝对路径 return fullPath; diff --git a/src/layout/components/Sidebar/TopMenu.vue b/src/layout/components/Sidebar/TopMenu.vue new file mode 100644 index 00000000..39c68628 --- /dev/null +++ b/src/layout/components/Sidebar/TopMenu.vue @@ -0,0 +1,78 @@ + + + diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue index 3eb734ae..fc499bcf 100644 --- a/src/layout/components/Sidebar/index.vue +++ b/src/layout/components/Sidebar/index.vue @@ -1,18 +1,15 @@ diff --git a/src/layout/components/TagsView/index.vue b/src/layout/components/TagsView/index.vue index 717c6c91..1348fd44 100644 --- a/src/layout/components/TagsView/index.vue +++ b/src/layout/components/TagsView/index.vue @@ -10,6 +10,9 @@ import { translateRouteTitleI18n } from "@/utils/i18n"; import { usePermissionStore } from "@/store/modules/permission"; import { useTagsViewStore, TagView } from "@/store/modules/tagsView"; +import { useSettingsStore } from "@/store/modules/settings"; +import { useAppStore } from "@/store/modules/app"; + import ScrollPane from "./ScrollPane.vue"; const { proxy } = getCurrentInstance() as ComponentInternalInstance; @@ -18,8 +21,11 @@ const route = useRoute(); const permissionStore = usePermissionStore(); const tagsViewStore = useTagsViewStore(); +const appStore = useAppStore(); const { visitedViews } = storeToRefs(tagsViewStore); +const settingsStore = useSettingsStore(); +const layout = computed(() => settingsStore.layout); const selectedTag = ref({}); const scrollPaneRef = ref(); @@ -226,7 +232,52 @@ function closeTagMenu() { function handleScroll() { 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(() => { initTags(); }); diff --git a/src/layout/index.vue b/src/layout/index.vue index 0d2e4589..fdaab020 100644 --- a/src/layout/index.vue +++ b/src/layout/index.vue @@ -1,15 +1,15 @@ + diff --git a/src/router/index.ts b/src/router/index.ts index 6df76e4a..02028beb 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -24,6 +24,7 @@ export const constantRoutes: RouteRecordRaw[] = [ { path: "/", + name: "/", component: Layout, redirect: "/dashboard", children: [ diff --git a/src/store/modules/app.ts b/src/store/modules/app.ts index cd1c1860..b538d0cf 100644 --- a/src/store/modules/app.ts +++ b/src/store/modules/app.ts @@ -14,11 +14,12 @@ export const useAppStore = defineStore("app", () => { const language = useStorage("language", defaultSettings.language); const sidebarStatus = useStorage("sidebarStatus", "closed"); + const sidebar = reactive({ opened: sidebarStatus.value !== "closed", withoutAnimation: false, }); - + const activeTopMenu = useStorage("activeTop", ""); /** * 根据语言标识读取对应的语言包 */ @@ -68,18 +69,25 @@ export const useAppStore = defineStore("app", () => { function changeLanguage(val: string) { language.value = val; } - + /** + * 混合模式顶部切换 + */ + function changeTopActive(val: string) { + activeTopMenu.value = val; + } return { device, sidebar, language, locale, size, + activeTopMenu, toggleDevice, changeSize, changeLanguage, toggleSidebar, closeSideBar, openSideBar, + changeTopActive, }; }); diff --git a/src/store/modules/permission.ts b/src/store/modules/permission.ts index 8a69655d..63e7c973 100644 --- a/src/store/modules/permission.ts +++ b/src/store/modules/permission.ts @@ -1,4 +1,4 @@ -import { RouteRecordRaw } from "vue-router"; +import { RouteRecordRaw, useRouter } from "vue-router"; import { defineStore } from "pinia"; import { constantRoutes } from "@/router"; import { store } from "@/store"; @@ -41,12 +41,13 @@ const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => { routes.forEach((route) => { const tmpRoute = { ...route }; // ES6扩展运算符复制新对象 - + if (!route.name) { + tmpRoute.name = route.path; + } // 判断用户(角色)是否有该路由的访问权限 if (hasPermission(roles, tmpRoute)) { if (tmpRoute.component?.toString() == "Layout") { tmpRoute.component = Layout; - console.log(); } else { const component = modules[`../../views/${tmpRoute.component}.vue`]; if (component) { @@ -97,7 +98,19 @@ export const usePermissionStore = defineStore("permission", () => { }); }); } - return { routes, setRoutes, generateRoutes }; + + /** + * 混合模式左侧菜单 + */ + const mixLeftMenu = ref([]); + function getMixLeftMenu(activeTop: string) { + routes.value.forEach((item) => { + if (item.path === activeTop) { + mixLeftMenu.value = item.children || []; + } + }); + } + return { routes, setRoutes, generateRoutes, getMixLeftMenu, mixLeftMenu }; }); // 非setup diff --git a/src/styles/sidebar.scss b/src/styles/sidebar.scss index eb7e679e..402d3e42 100644 --- a/src/styles/sidebar.scss +++ b/src/styles/sidebar.scss @@ -144,15 +144,46 @@ .sidebar-container { width: $sideBarWidth !important; transition: transform 0.28s; + + .header { + .logo-wrap { + width: 63px !important; + transition: transform 0.28s; + } + } } - &.hideSidebar { + &.hideSidebar:not(.isMix) { .sidebar-container { pointer-events: none; transition-duration: 0.3s; 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 {