From 44c1079f87201207e53b9d808ba785a6045ec510 Mon Sep 17 00:00:00 2001 From: JEECG <445654970@qq.com> Date: Sun, 14 Sep 2025 11:59:09 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90v3.8.3=E3=80=91=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E9=A1=B6=E9=83=A8=E5=AF=BC=E8=88=AA=E9=A3=8E=E6=A0=BC=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E7=9A=84=E6=A0=B7=E5=BC=8F=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=A4=96=E9=83=A8=E9=93=BE=E6=8E=A5=E6=89=93=E5=BC=80=E5=8F=8A?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E9=87=8D=E5=AE=9A=E5=90=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Menu/src/BasicMenu.vue | 30 +++- .../src/layouts/default/menu/useLayoutMenu.ts | 20 ++- .../src/layouts/default/setting/handler.ts | 12 +- jeecgboot-vue3/src/layouts/page/index.vue | 145 +++++++++++++++++- jeecgboot-vue3/src/layouts/page/useEmpty.ts | 87 +++++++++++ .../src/router/helper/menuHelper.ts | 1 + .../src/router/helper/routeHelper.ts | 1 + jeecgboot-vue3/src/router/types.ts | 1 + 8 files changed, 280 insertions(+), 17 deletions(-) create mode 100644 jeecgboot-vue3/src/layouts/page/useEmpty.ts diff --git a/jeecgboot-vue3/src/components/Menu/src/BasicMenu.vue b/jeecgboot-vue3/src/components/Menu/src/BasicMenu.vue index 328d6c1d7..6bc3365d2 100644 --- a/jeecgboot-vue3/src/components/Menu/src/BasicMenu.vue +++ b/jeecgboot-vue3/src/components/Menu/src/BasicMenu.vue @@ -124,8 +124,10 @@ return; } // update-begin--author:liaozhiyang---date:20250114---for:【issues/7706】顶部栏导航内部路由也可以支持采用新浏览器tab打开 - const findItem = getMatchingMenu(props.items, key); - if (findItem?.internalOrExternal == true) { + const menus = await getMenus(); + const findItem = getMatchingPath(menus, key); + if (findItem?.internalOrExternal == true && !findItem?.children?.length) { + // 一级菜单当设置了外部打开,只有没有子菜单时才生效 window.open(location.origin + key); return; } @@ -137,18 +139,32 @@ } // update-begin--author:liaozhiyang---date:20240418---for:【QQYUN-8773】顶部混合导航(顶部左侧组合菜单)一级菜单没有配置redirect默认跳子菜单的第一个 if (props.type === MenuTypeEnum.MIX) { - const menus = await getMenus(); const menuItem = getMatchingPath(menus, key); - if (menuItem && !menuItem.redirect && menuItem.children?.length) { + // 没有重定向且originComponent不是系统默认的就当做是组件,否则就跳子菜单的第一个 + if (menuItem && !menuItem.redirect && menuItem.originComponent == '/layouts/default/index' && menuItem.children?.length) { const subMenuItem = getSubMenu(menuItem.children); if (subMenuItem?.path) { const path = subMenuItem.redirect ?? subMenuItem.path; let _key = path; if (isUrl(path)) { - window.open(path); + // window.open(path); // 外部打开emit出去的key不能是url,否则左侧菜单出不来 _key = key; } + + // update-begin--author:liaozhiyang---date:20250825---for:【QQYUN-13593】敲敲云首页菜单外部打开 + // ===================================================================== + // TODO: 临时代码 - 需要删除!!! + // 这是针对敲敲云首页菜单的特殊处理,后续需要重构或删除 + // ===================================================================== + // 是外部打开且是白名单内的菜单,则直接打开 + if (subMenuItem?.internalOrExternal == true && ['/myapps/index'].includes(path)) { + window.open(location.origin + path); + return; + } + // ===================================================================== + // update-end--author:liaozhiyang---date:20250825---for:【QQYUN-13593】敲敲云首页菜单外部打开 + emit('menuClick', _key, { title: subMenuItem.title }); } else { emit('menuClick', key, item); @@ -189,12 +205,12 @@ /** * liaozhiyang * 2024-05-18 - * 获取指定菜单下的第一个菜单 + * 获取指定菜单下的第一个菜单(忽略隐藏路由) */ function getSubMenu(menus) { for (let i = 0, len = menus.length; i < len; i++) { const item = menus[i]; - if (item.path && !item.children?.length) { + if (item.path && !item.hideMenu && !item.children?.length) { return item; } else if (item.children?.length) { const result = getSubMenu(item.children); diff --git a/jeecgboot-vue3/src/layouts/default/menu/useLayoutMenu.ts b/jeecgboot-vue3/src/layouts/default/menu/useLayoutMenu.ts index 8375d3b89..83d6ee7e4 100644 --- a/jeecgboot-vue3/src/layouts/default/menu/useLayoutMenu.ts +++ b/jeecgboot-vue3/src/layouts/default/menu/useLayoutMenu.ts @@ -2,12 +2,13 @@ import type { Menu } from '/@/router/types'; import type { Ref } from 'vue'; import { watch, unref, ref, computed } from 'vue'; import { useRouter } from 'vue-router'; -import { MenuSplitTyeEnum } from '/@/enums/menuEnum'; +import { MenuSplitTyeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; import { useThrottleFn } from '@vueuse/core'; import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; import { getChildrenMenus, getCurrentParentPath, getMenus, getShallowMenus } from '/@/router/menus'; import { usePermissionStore } from '/@/store/modules/permission'; import { useAppInject } from '/@/hooks/web/useAppInject'; +import { PAGE_NOT_FOUND_NAME_404 } from '/@/router/constant'; export function useSplitMenu(splitType: Ref) { // Menu array @@ -15,7 +16,7 @@ export function useSplitMenu(splitType: Ref) { const { currentRoute } = useRouter(); const { getIsMobile } = useAppInject(); const permissionStore = usePermissionStore(); - const { setMenuSetting, getIsHorizontal, getSplit } = useMenuSetting(); + const { setMenuSetting, getIsHorizontal, getSplit, getMenuType } = useMenuSetting(); const throttleHandleSplitLeftMenu = useThrottleFn(handleSplitLeftMenu, 50); @@ -33,9 +34,22 @@ export function useSplitMenu(splitType: Ref) { [() => unref(currentRoute).path, () => unref(splitType)], async ([path]: [string, MenuSplitTyeEnum]) => { if (unref(splitNotLeft) || unref(getIsMobile)) return; - const { meta } = unref(currentRoute); const currentActiveMenu = meta.currentActiveMenu as string; + // update-begin--author:liaozhiyang---date:20250908---for:【QQYUN-13718】一级菜单默认重定向到子菜单,但子菜单未授权,导致点击一级菜单加载不出子菜单 + // 顶部混合模式且顶部左侧组合菜单开始时 + if (unref(getMenuType) === MenuTypeEnum.MIX && unref(getSplit)) { + // 404页面时,跳转到重定向的路径 + if (unref(currentRoute).name === PAGE_NOT_FOUND_NAME_404 && unref(currentRoute)?.redirectedFrom?.path) { + const menus = await getMenus(); + const findItem = menus.find((item:any) => item.redirect === unref(currentRoute).path); + if (findItem) { + // 说明是从一级菜单重定向过来的 + path = findItem.path; + } + } + } + // update-end--author:liaozhiyang---date:20250908---for:【QQYUN-13718】一级菜单默认重定向到子菜单,但子菜单未授权,导致点击一级菜单加载不出子菜单 let parentPath = await getCurrentParentPath(path); if (!parentPath) { parentPath = await getCurrentParentPath(currentActiveMenu); diff --git a/jeecgboot-vue3/src/layouts/default/setting/handler.ts b/jeecgboot-vue3/src/layouts/default/setting/handler.ts index 05c909def..c6314cf64 100644 --- a/jeecgboot-vue3/src/layouts/default/setting/handler.ts +++ b/jeecgboot-vue3/src/layouts/default/setting/handler.ts @@ -36,13 +36,14 @@ export function layoutHandler(event: HandlerEnum, value: any) { baseHandler(HandlerEnum.TABS_THEME, tabsThemeOptions[1].value); } else if (isMixMenu) { baseHandler(event, value); - baseHandler(HandlerEnum.HEADER_THEME, HEADER_PRESET_BG_COLOR_LIST[4]); + baseHandler(HandlerEnum.HEADER_THEME, HEADER_PRESET_BG_COLOR_LIST[2]); baseHandler(HandlerEnum.MENU_THEME, SIDE_BAR_BG_COLOR_LIST[3]); if (darkMode) { updateHeaderBgColor(); updateSidebarBgColor(); } - baseHandler(HandlerEnum.CHANGE_THEME_COLOR, APP_PRESET_COLOR_LIST[1]); + // 顶部混合导航模式主题色改成绿色 + baseHandler(HandlerEnum.CHANGE_THEME_COLOR, APP_PRESET_COLOR_LIST[2]); baseHandler(HandlerEnum.TABS_THEME, tabsThemeOptions[1].value); } else if (isMixSidebarMenu) { baseHandler(event, value); @@ -65,6 +66,13 @@ export function layoutHandler(event: HandlerEnum, value: any) { baseHandler(HandlerEnum.CHANGE_THEME_COLOR, APP_PRESET_COLOR_LIST[1]); baseHandler(HandlerEnum.TABS_THEME, tabsThemeOptions[1].value); } + // update-begin--author:liaozhiyang---date:20250825---for:【QQYUN-13600】默认顶部混合导航模式且启用顶部左侧导航,切换到其他模式时导航刷新后菜单样式混乱 + if (isMixMenu) { + baseHandler(HandlerEnum.MENU_SPLIT, true); + } else { + baseHandler(HandlerEnum.MENU_SPLIT, false); + } + // update-end--author:liaozhiyang---date:20250825---for:【QQYUN-13600】默认顶部混合导航模式且启用顶部左侧导航,切换到其他模式时导航刷新后菜单样式混乱 } export function baseHandler(event: HandlerEnum, value: any) { diff --git a/jeecgboot-vue3/src/layouts/page/index.vue b/jeecgboot-vue3/src/layouts/page/index.vue index dc1d4fc71..27cdde51a 100644 --- a/jeecgboot-vue3/src/layouts/page/index.vue +++ b/jeecgboot-vue3/src/layouts/page/index.vue @@ -14,10 +14,26 @@ - - - - + + @@ -36,6 +52,7 @@ import { getTransitionName } from './transition'; import { useMultipleTabStore } from '/@/store/modules/multipleTab'; + import { useEmpty } from './useEmpty'; export default defineComponent({ name: 'PageLayout', @@ -56,7 +73,9 @@ } return tabStore.getCachedTabList; }); - + // update-begin--author:liaozhiyang---date:20250826---for:【QQYUN-13593】空白页美化 + const { pageTip, getPageTip, effectVars } = useEmpty(); + // update-end--author:liaozhiyang---date:20250826---for:【QQYUN-13593】空白页美化 return { getTransitionName, openCache, @@ -64,7 +83,123 @@ getBasicTransition, getCaches, getCanEmbedIFramePage, + pageTip, + getPageTip, + effectVars, }; }, }); + diff --git a/jeecgboot-vue3/src/layouts/page/useEmpty.ts b/jeecgboot-vue3/src/layouts/page/useEmpty.ts new file mode 100644 index 000000000..41e7d4cf1 --- /dev/null +++ b/jeecgboot-vue3/src/layouts/page/useEmpty.ts @@ -0,0 +1,87 @@ +import { computed, unref, ref, watch } from 'vue'; +import { getMenus } from '/@/router/menus'; +import { useRoute } from 'vue-router'; +import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; +import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; +import { useRootSetting } from '/@/hooks/setting/useRootSetting'; +import { lighten, darken } from '/@/utils/color'; +export const useEmpty = () => { + const { getThemeColor, getDarkMode } = useRootSetting(); + const route = useRoute(); + const { getHeaderBgColor } = useHeaderSetting(); + const { getMenuBgColor } = useMenuSetting(); + const pageTip = ref(''); + const effectVars = computed(() => { + const primary = unref(getThemeColor) || '#1890ff'; + const menuBg = unref(getMenuBgColor) || '#ffffff'; + const headerBg = unref(getHeaderBgColor); + const isDark = unref(getDarkMode) === 'dark'; + // 以主题色为基色,派生三组渐变色 + const a1 = lighten(primary, 25); + const a2 = primary; + const b1 = lighten(headerBg, 45); + const b2 = lighten(headerBg, 10); + const c1 = lighten(menuBg, 35); + const c2 = darken(primary, 5); + const bg1 = isDark ? '#0f172a' : '#f7f8fa'; + const bg2 = isDark ? '#111827' : '#f2f5f9'; + const grid = isDark ? 'rgba(255,255,255,0.04)' : 'rgba(60,70,90,0.06)'; + const tipColor = isDark ? '#626262' : '#b9b9b9'; + const tipBg = isDark ? 'rgba(17,24,39,0.6)' : 'rgba(255,255,255,0.6)'; + const tipBorder = isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)'; + return { + '--blob-a-1': a1, + '--blob-a-2': a2, + '--blob-b-1': b1, + '--blob-b-2': b2, + '--blob-c-1': c1, + '--blob-c-2': c2, + '--bg-1': bg1, + '--bg-2': bg2, + '--grid-color': grid, + '--tip-color': tipColor, + '--tip-bg': tipBg, + '--tip-border': tipBorder, + } as Record; + }); + + const getPageTip = async (route) => { + const menus = await getMenus(); + const menu = getMatchingPath(menus, route.path); + if (menu) { + if (['/layouts/default/index'].includes(menu.originComponent)) { + pageTip.value = '点击子菜单跳转到对应外部链接!'; + } else { + pageTip.value = '查看组件引用是否正确'; + } + } + }; + watch( + route, + () => { + getPageTip({ path: window.location.pathname }); + }, + { immediate: true } + ); + + function getMatchingPath(menus, path) { + for (let i = 0, len = menus.length; i < len; i++) { + const item = menus[i]; + if (item.path === path) { + return item; + } else if (item.children?.length) { + const result = getMatchingPath(item.children, path); + if (result) { + return result; + } + } + } + return null; + } + + return { + pageTip, + getPageTip, + effectVars, + }; +}; diff --git a/jeecgboot-vue3/src/router/helper/menuHelper.ts b/jeecgboot-vue3/src/router/helper/menuHelper.ts index 0cab74690..ac8efc9b7 100644 --- a/jeecgboot-vue3/src/router/helper/menuHelper.ts +++ b/jeecgboot-vue3/src/router/helper/menuHelper.ts @@ -98,6 +98,7 @@ export function transformRouteToMenu(routeModList: AppRouteModule[], routerMappi hideMenu, alwaysShow:node.alwaysShow||false, path: node.path, + originComponent: node.originComponent, ...(node.redirect ? { redirect: node.redirect } : {}), }; }, diff --git a/jeecgboot-vue3/src/router/helper/routeHelper.ts b/jeecgboot-vue3/src/router/helper/routeHelper.ts index a7834c1aa..1c6eba9de 100644 --- a/jeecgboot-vue3/src/router/helper/routeHelper.ts +++ b/jeecgboot-vue3/src/router/helper/routeHelper.ts @@ -134,6 +134,7 @@ export function transformObjToRoute(routeList: AppRouteModul routeList.forEach((route) => { const component = route.component as string; if (component) { + route.originComponent = component; if (component.toUpperCase() === 'LAYOUT') { route.component = LayoutMap.get(component.toUpperCase()); } else { diff --git a/jeecgboot-vue3/src/router/types.ts b/jeecgboot-vue3/src/router/types.ts index 5be6ce6dc..995dd79d6 100644 --- a/jeecgboot-vue3/src/router/types.ts +++ b/jeecgboot-vue3/src/router/types.ts @@ -9,6 +9,7 @@ export interface AppRouteRecordRaw extends Omit { name: string; meta: RouteMeta; component?: Component | string; + originComponent?: string; components?: Component; children?: AppRouteRecordRaw[]; props?: Recordable;