This commit is contained in:
dap 2024-10-23 07:49:10 +08:00
commit e5625efcf7
13 changed files with 95 additions and 28 deletions

View File

@ -384,6 +384,10 @@ interface RouteMeta {
* The menu is visible, but access will be redirected to 403 * The menu is visible, but access will be redirected to 403
*/ */
menuVisibleWithForbidden?: boolean; menuVisibleWithForbidden?: boolean;
/**
* Open in a new window
*/
openInNewWindow?: boolean;
/** /**
* Used for route->menu sorting * Used for route->menu sorting
*/ */

View File

@ -382,6 +382,10 @@ interface RouteMeta {
* 菜单可以看到但是访问会被重定向到403 * 菜单可以看到但是访问会被重定向到403
*/ */
menuVisibleWithForbidden?: boolean; menuVisibleWithForbidden?: boolean;
/**
* 在新窗口打开
*/
openInNewWindow?: boolean;
/** /**
* 用于路由->菜单排序 * 用于路由->菜单排序
*/ */
@ -539,6 +543,13 @@ interface RouteMeta {
用于配置页面在菜单可以看到但是访问会被重定向到403。 用于配置页面在菜单可以看到但是访问会被重定向到403。
### openInNewWindow
- 类型:`boolean`
- 默认值:`false`
设置为 `true` 时,会在新窗口打开页面。
### order ### order
- 类型:`number` - 类型:`number`

View File

@ -81,7 +81,7 @@ getCurrentInstance().ctx.xxxx;
## 依赖安装问题 ## 依赖安装问题
- 如果依赖安装不了或者启动报错可以尝试执行`pnpm run resintall`。 - 如果依赖安装不了或者启动报错可以尝试执行`pnpm run reinstall`。
- 如果依赖安装不了或者报错,可以尝试切换手机热点来进行依赖安装。 - 如果依赖安装不了或者报错,可以尝试切换手机热点来进行依赖安装。
- 如果还是不行,可以自行配置国内镜像安装。 - 如果还是不行,可以自行配置国内镜像安装。
- 也可以在项目根目录创建 `.npmrc` 文件,内容如下 - 也可以在项目根目录创建 `.npmrc` 文件,内容如下

View File

@ -23,4 +23,15 @@ function openWindow(url: string, options: OpenWindowOptions = {}): void {
window.open(url, target, features); 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 };

View File

@ -98,6 +98,10 @@ interface RouteMeta {
* 访403 * 访403
*/ */
menuVisibleWithForbidden?: boolean; menuVisibleWithForbidden?: boolean;
/**
*
*/
openInNewWindow?: boolean;
/** /**
* -> * ->
*/ */

View File

@ -6,14 +6,12 @@ import { VbenIcon } from '../icon';
interface Props extends BreadcrumbProps {} interface Props extends BreadcrumbProps {}
defineOptions({ name: 'Breadcrumb' }); defineOptions({ name: 'Breadcrumb' });
withDefaults(defineProps<Props>(), { const { breadcrumbs, showIcon } = defineProps<Props>();
showIcon: false,
});
const emit = defineEmits<{ select: [string] }>(); const emit = defineEmits<{ select: [string] }>();
function handleClick(path?: string) { function handleClick(index: number, path?: string) {
if (!path) { if (!path || index === breadcrumbs.length - 1) {
return; return;
} }
emit('select', path); emit('select', path);
@ -27,7 +25,10 @@ function handleClick(path?: string) {
:key="`${item.path}-${item.title}-${index}`" :key="`${item.path}-${item.title}-${index}`"
> >
<li> <li>
<a href="javascript:void 0" @click.stop="handleClick(item.path)"> <a
href="javascript:void 0"
@click.stop="handleClick(index, item.path)"
>
<span class="flex-center z-10 h-full"> <span class="flex-center z-10 h-full">
<VbenIcon <VbenIcon
v-if="showIcon" v-if="showIcon"

View File

@ -1,23 +1,25 @@
import { useRouter } from 'vue-router'; import { type RouteRecordNormalized, useRouter } from 'vue-router';
import { isHttpUrl, openWindow } from '@vben/utils'; import { isHttpUrl, openRouteInNewWindow, openWindow } from '@vben/utils';
function useNavigation() { function useNavigation() {
const router = useRouter(); const router = useRouter();
const routes = router.getRoutes(); const routes = router.getRoutes();
const routeMetaMap = new Map<string, any>(); const routeMetaMap = new Map<string, RouteRecordNormalized>();
routes.forEach((route) => { routes.forEach((route) => {
routeMetaMap.set(route.path, route.meta); routeMetaMap.set(route.path, route);
}); });
const navigation = async (path: string) => { const navigation = async (path: string) => {
const route = routeMetaMap.get(path);
const { openInNewWindow = false, query = {} } = route?.meta ?? {};
if (isHttpUrl(path)) { if (isHttpUrl(path)) {
openWindow(path, { target: '_blank' }); openWindow(path, { target: '_blank' });
} else if (openInNewWindow) {
openRouteInNewWindow(path);
} else { } else {
const meta = routeMetaMap.get(path);
const query = meta?.query ?? {};
await router.push({ await router.push({
path, path,
query, query,

View File

@ -4,7 +4,7 @@ import type { Router, RouteRecordNormalized } from 'vue-router';
import { toRaw } from 'vue'; import { toRaw } from 'vue';
import { import {
openWindow, openRouteInNewWindow,
startProgress, startProgress,
stopProgress, stopProgress,
} from '@vben-core/shared/utils'; } from '@vben-core/shared/utils';
@ -290,11 +290,7 @@ export const useTabbarStore = defineStore('core-tabbar', {
* @param tab * @param tab
*/ */
async openTabInNewWindow(tab: TabDefinition) { async openTabInNewWindow(tab: TabDefinition) {
const { hash, origin } = location; openRouteInNewWindow(tab.fullPath || tab.path);
const path = tab.fullPath || tab.path;
const fullPath = path.startsWith('/') ? path : `/${path}`;
const url = `${origin}${hash ? '/#' : ''}${fullPath}`;
openWindow(url, { target: '_blank' });
}, },
/** /**
@ -312,6 +308,14 @@ export const useTabbarStore = defineStore('core-tabbar', {
// this.addTab(tab); // this.addTab(tab);
this.tabs.splice(index, 1, 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.addTab(tab);
this.tabs.splice(index, 1, 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);
}, },
/** /**

View File

@ -48,7 +48,8 @@
"tabDetail": "Tab Detail Page", "tabDetail": "Tab Detail Page",
"fullScreen": "FullScreen", "fullScreen": "FullScreen",
"clipboard": "Clipboard", "clipboard": "Clipboard",
"menuWithQuery": "Menu With Query" "menuWithQuery": "Menu With Query",
"openInNewWindow": "Open in New Window"
}, },
"breadcrumb": { "breadcrumb": {
"navigation": "Breadcrumb Navigation", "navigation": "Breadcrumb Navigation",

View File

@ -48,7 +48,8 @@
"tabDetail": "标签详情页", "tabDetail": "标签详情页",
"fullScreen": "全屏", "fullScreen": "全屏",
"clipboard": "剪贴板", "clipboard": "剪贴板",
"menuWithQuery": "带参菜单" "menuWithQuery": "带参菜单",
"openInNewWindow": "新窗口打开"
}, },
"breadcrumb": { "breadcrumb": {
"navigation": "面包屑导航", "navigation": "面包屑导航",

View File

@ -174,7 +174,7 @@ const routes: RouteRecordRaw[] = [
import('#/views/demos/features/full-screen/index.vue'), import('#/views/demos/features/full-screen/index.vue'),
meta: { meta: {
icon: 'lucide:fullscreen', 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'), 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', name: 'VueQueryDemo',
path: '/demos/features/vue-query', path: '/demos/features/vue-query',

View File

@ -0,0 +1,11 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
</script>
<template>
<Fallback
description="当前页面已在新窗口内打开"
status="coming-soon"
title="新窗口打开页面"
/>
</template>

View File

@ -43,13 +43,13 @@ catalog:
'@types/html-minifier-terser': ^7.0.2 '@types/html-minifier-terser': ^7.0.2
'@types/jsonwebtoken': ^9.0.7 '@types/jsonwebtoken': ^9.0.7
'@types/lodash.clonedeep': ^4.5.9 '@types/lodash.clonedeep': ^4.5.9
'@types/node': ^22.7.7 '@types/node': ^22.7.8
'@types/nprogress': ^0.2.3 '@types/nprogress': ^0.2.3
'@types/postcss-import': ^14.0.3 '@types/postcss-import': ^14.0.3
'@types/qrcode': ^1.5.5 '@types/qrcode': ^1.5.5
'@types/sortablejs': ^1.15.8 '@types/sortablejs': ^1.15.8
'@typescript-eslint/eslint-plugin': ^8.10.0 '@typescript-eslint/eslint-plugin': ^8.11.0
'@typescript-eslint/parser': ^8.10.0 '@typescript-eslint/parser': ^8.11.0
'@vee-validate/zod': ^4.14.3 '@vee-validate/zod': ^4.14.3
'@vite-pwa/vitepress': ^0.5.3 '@vite-pwa/vitepress': ^0.5.3
'@vitejs/plugin-vue': ^5.1.4 '@vitejs/plugin-vue': ^5.1.4
@ -84,7 +84,7 @@ catalog:
echarts: ^5.5.1 echarts: ^5.5.1
element-plus: ^2.8.6 element-plus: ^2.8.6
eslint: ^9.13.0 eslint: ^9.13.0
eslint-config-turbo: ^2.2.1 eslint-config-turbo: ^2.2.3
eslint-plugin-command: ^0.2.6 eslint-plugin-command: ^0.2.6
eslint-plugin-eslint-comments: ^3.2.0 eslint-plugin-eslint-comments: ^3.2.0
eslint-plugin-import-x: ^4.3.1 eslint-plugin-import-x: ^4.3.1
@ -153,7 +153,7 @@ catalog:
tailwindcss: ^3.4.14 tailwindcss: ^3.4.14
tailwindcss-animate: ^1.0.7 tailwindcss-animate: ^1.0.7
theme-colors: ^0.1.0 theme-colors: ^0.1.0
turbo: ^2.2.1 turbo: ^2.2.3
typescript: ^5.6.3 typescript: ^5.6.3
unbuild: ^2.0.0 unbuild: ^2.0.0
unplugin-element-plus: ^0.8.0 unplugin-element-plus: ^0.8.0