diff --git a/docs/src/en/guide/essentials/route.md b/docs/src/en/guide/essentials/route.md index 8592056f..501cba38 100644 --- a/docs/src/en/guide/essentials/route.md +++ b/docs/src/en/guide/essentials/route.md @@ -384,6 +384,10 @@ interface RouteMeta { * The menu is visible, but access will be redirected to 403 */ menuVisibleWithForbidden?: boolean; + /** + * Open in a new window + */ + openInNewWindow?: boolean; /** * Used for route->menu sorting */ diff --git a/docs/src/guide/essentials/route.md b/docs/src/guide/essentials/route.md index d73f2be0..06ddba01 100644 --- a/docs/src/guide/essentials/route.md +++ b/docs/src/guide/essentials/route.md @@ -382,6 +382,10 @@ interface RouteMeta { * 菜单可以看到,但是访问会被重定向到403 */ menuVisibleWithForbidden?: boolean; + /** + * 在新窗口打开 + */ + openInNewWindow?: boolean; /** * 用于路由->菜单排序 */ @@ -539,6 +543,13 @@ interface RouteMeta { 用于配置页面在菜单可以看到,但是访问会被重定向到403。 +### openInNewWindow + +- 类型:`boolean` +- 默认值:`false` + +设置为 `true` 时,会在新窗口打开页面。 + ### order - 类型:`number` diff --git a/docs/src/guide/other/faq.md b/docs/src/guide/other/faq.md index 54a74588..02bbe8b0 100644 --- a/docs/src/guide/other/faq.md +++ b/docs/src/guide/other/faq.md @@ -81,7 +81,7 @@ getCurrentInstance().ctx.xxxx; ## 依赖安装问题 -- 如果依赖安装不了或者启动报错可以尝试执行`pnpm run resintall`。 +- 如果依赖安装不了或者启动报错可以尝试执行`pnpm run reinstall`。 - 如果依赖安装不了或者报错,可以尝试切换手机热点来进行依赖安装。 - 如果还是不行,可以自行配置国内镜像安装。 - 也可以在项目根目录创建 `.npmrc` 文件,内容如下 diff --git a/packages/@core/base/shared/src/utils/window.ts b/packages/@core/base/shared/src/utils/window.ts index 1dc991f2..4608f4be 100644 --- a/packages/@core/base/shared/src/utils/window.ts +++ b/packages/@core/base/shared/src/utils/window.ts @@ -23,4 +23,15 @@ function openWindow(url: string, options: OpenWindowOptions = {}): void { window.open(url, target, features); } -export { openWindow }; +/** + * 在新窗口中打开路由。 + * @param path + */ +function openRouteInNewWindow(path: string) { + const { hash, origin } = location; + const fullPath = path.startsWith('/') ? path : `/${path}`; + const url = `${origin}${hash ? '/#' : ''}${fullPath}`; + openWindow(url, { target: '_blank' }); +} + +export { openRouteInNewWindow, openWindow }; diff --git a/packages/@core/base/typings/src/vue-router.d.ts b/packages/@core/base/typings/src/vue-router.d.ts index 534358cc..b95bb33b 100644 --- a/packages/@core/base/typings/src/vue-router.d.ts +++ b/packages/@core/base/typings/src/vue-router.d.ts @@ -98,6 +98,10 @@ interface RouteMeta { * 菜单可以看到,但是访问会被重定向到403 */ menuVisibleWithForbidden?: boolean; + /** + * 在新窗口打开 + */ + openInNewWindow?: boolean; /** * 用于路由->菜单排序 */ diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb-background.vue b/packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb-background.vue index 7015e453..cfed25b3 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb-background.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb-background.vue @@ -6,14 +6,12 @@ import { VbenIcon } from '../icon'; interface Props extends BreadcrumbProps {} defineOptions({ name: 'Breadcrumb' }); -withDefaults(defineProps(), { - showIcon: false, -}); +const { breadcrumbs, showIcon } = defineProps(); const emit = defineEmits<{ select: [string] }>(); -function handleClick(path?: string) { - if (!path) { +function handleClick(index: number, path?: string) { + if (!path || index === breadcrumbs.length - 1) { return; } emit('select', path); @@ -27,7 +25,10 @@ function handleClick(path?: string) { :key="`${item.path}-${item.title}-${index}`" >
  • - + (); + const routeMetaMap = new Map(); routes.forEach((route) => { - routeMetaMap.set(route.path, route.meta); + routeMetaMap.set(route.path, route); }); const navigation = async (path: string) => { + const route = routeMetaMap.get(path); + const { openInNewWindow = false, query = {} } = route?.meta ?? {}; if (isHttpUrl(path)) { openWindow(path, { target: '_blank' }); + } else if (openInNewWindow) { + openRouteInNewWindow(path); } else { - const meta = routeMetaMap.get(path); - const query = meta?.query ?? {}; await router.push({ path, query, diff --git a/packages/stores/src/modules/tabbar.ts b/packages/stores/src/modules/tabbar.ts index f24d7907..e4799bfd 100644 --- a/packages/stores/src/modules/tabbar.ts +++ b/packages/stores/src/modules/tabbar.ts @@ -4,7 +4,7 @@ import type { Router, RouteRecordNormalized } from 'vue-router'; import { toRaw } from 'vue'; import { - openWindow, + openRouteInNewWindow, startProgress, stopProgress, } from '@vben-core/shared/utils'; @@ -290,11 +290,7 @@ export const useTabbarStore = defineStore('core-tabbar', { * @param tab */ async openTabInNewWindow(tab: TabDefinition) { - const { hash, origin } = location; - const path = tab.fullPath || tab.path; - const fullPath = path.startsWith('/') ? path : `/${path}`; - const url = `${origin}${hash ? '/#' : ''}${fullPath}`; - openWindow(url, { target: '_blank' }); + openRouteInNewWindow(tab.fullPath || tab.path); }, /** @@ -312,6 +308,14 @@ export const useTabbarStore = defineStore('core-tabbar', { // this.addTab(tab); this.tabs.splice(index, 1, tab); } + // 过滤固定tabs,后面更改affixTabOrder的值的话可能会有问题,目前行464排序affixTabs没有设置值 + const affixTabs = this.tabs.filter((tab) => isAffixTab(tab)); + // 获得固定tabs的index + const newIndex = affixTabs.findIndex( + (item) => getTabPath(item) === getTabPath(tab), + ); + // 交换位置重新排序 + await this.sortTabs(index, newIndex); }, /** @@ -419,6 +423,12 @@ export const useTabbarStore = defineStore('core-tabbar', { // this.addTab(tab); this.tabs.splice(index, 1, tab); } + // 过滤固定tabs,后面更改affixTabOrder的值的话可能会有问题,目前行464排序affixTabs没有设置值 + const affixTabs = this.tabs.filter((tab) => isAffixTab(tab)); + // 获得固定tabs的index,使用固定tabs的下一个位置也就是活动tabs的第一个位置 + const newIndex = affixTabs.length; + // 交换位置重新排序 + await this.sortTabs(index, newIndex); }, /** diff --git a/playground/src/locales/langs/en-US/demos.json b/playground/src/locales/langs/en-US/demos.json index 0e185ec2..86dafca5 100644 --- a/playground/src/locales/langs/en-US/demos.json +++ b/playground/src/locales/langs/en-US/demos.json @@ -48,7 +48,8 @@ "tabDetail": "Tab Detail Page", "fullScreen": "FullScreen", "clipboard": "Clipboard", - "menuWithQuery": "Menu With Query" + "menuWithQuery": "Menu With Query", + "openInNewWindow": "Open in New Window" }, "breadcrumb": { "navigation": "Breadcrumb Navigation", diff --git a/playground/src/locales/langs/zh-CN/demos.json b/playground/src/locales/langs/zh-CN/demos.json index dffc2f3d..908449c6 100644 --- a/playground/src/locales/langs/zh-CN/demos.json +++ b/playground/src/locales/langs/zh-CN/demos.json @@ -48,7 +48,8 @@ "tabDetail": "标签详情页", "fullScreen": "全屏", "clipboard": "剪贴板", - "menuWithQuery": "带参菜单" + "menuWithQuery": "带参菜单", + "openInNewWindow": "新窗口打开" }, "breadcrumb": { "navigation": "面包屑导航", diff --git a/playground/src/router/routes/modules/demos.ts b/playground/src/router/routes/modules/demos.ts index 8149ab00..f587e0be 100644 --- a/playground/src/router/routes/modules/demos.ts +++ b/playground/src/router/routes/modules/demos.ts @@ -174,7 +174,7 @@ const routes: RouteRecordRaw[] = [ import('#/views/demos/features/full-screen/index.vue'), meta: { icon: 'lucide:fullscreen', - title: $t('demos.features.title'), + title: $t('demos.features.fullScreen'), }, }, { @@ -200,6 +200,17 @@ const routes: RouteRecordRaw[] = [ title: $t('demos.features.menuWithQuery'), }, }, + { + name: 'NewWindowDemo', + path: '/demos/new-window', + component: () => + import('#/views/demos/features/new-window/index.vue'), + meta: { + icon: 'lucide:app-window', + openInNewWindow: true, + title: $t('demos.features.openInNewWindow'), + }, + }, { name: 'VueQueryDemo', path: '/demos/features/vue-query', diff --git a/playground/src/views/demos/features/new-window/index.vue b/playground/src/views/demos/features/new-window/index.vue new file mode 100644 index 00000000..68f89d0d --- /dev/null +++ b/playground/src/views/demos/features/new-window/index.vue @@ -0,0 +1,11 @@ + + + diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index fb5d4524..d1c2b03d 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -43,13 +43,13 @@ catalog: '@types/html-minifier-terser': ^7.0.2 '@types/jsonwebtoken': ^9.0.7 '@types/lodash.clonedeep': ^4.5.9 - '@types/node': ^22.7.7 + '@types/node': ^22.7.8 '@types/nprogress': ^0.2.3 '@types/postcss-import': ^14.0.3 '@types/qrcode': ^1.5.5 '@types/sortablejs': ^1.15.8 - '@typescript-eslint/eslint-plugin': ^8.10.0 - '@typescript-eslint/parser': ^8.10.0 + '@typescript-eslint/eslint-plugin': ^8.11.0 + '@typescript-eslint/parser': ^8.11.0 '@vee-validate/zod': ^4.14.3 '@vite-pwa/vitepress': ^0.5.3 '@vitejs/plugin-vue': ^5.1.4 @@ -84,7 +84,7 @@ catalog: echarts: ^5.5.1 element-plus: ^2.8.6 eslint: ^9.13.0 - eslint-config-turbo: ^2.2.1 + eslint-config-turbo: ^2.2.3 eslint-plugin-command: ^0.2.6 eslint-plugin-eslint-comments: ^3.2.0 eslint-plugin-import-x: ^4.3.1 @@ -153,7 +153,7 @@ catalog: tailwindcss: ^3.4.14 tailwindcss-animate: ^1.0.7 theme-colors: ^0.1.0 - turbo: ^2.2.1 + turbo: ^2.2.3 typescript: ^5.6.3 unbuild: ^2.0.0 unplugin-element-plus: ^0.8.0