mirror of
https://github.com/pure-admin/vue-pure-admin.git
synced 2025-06-04 07:27:41 +08:00
feat: 路由添加可配置的fixedTag
属性,作用为当前菜单名称是否固定显示在标签页且不可关闭 (#1047)
This commit is contained in:
parent
c0a1b918aa
commit
296db3789b
@ -90,6 +90,10 @@
|
||||
padding: 0 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.fixed-tag {
|
||||
padding: 0 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import { onClickOutside } from "@vueuse/core";
|
||||
import { handleAliveRoute, getTopMenu } from "@/router/utils";
|
||||
import { useSettingStoreHook } from "@/store/modules/settings";
|
||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
import { ref, watch, unref, toRaw, nextTick, onBeforeUnmount } from "vue";
|
||||
import {
|
||||
delay,
|
||||
@ -59,6 +60,10 @@ const contextmenuRef = ref();
|
||||
const isShowArrow = ref(false);
|
||||
const topPath = getTopMenu()?.path;
|
||||
const { VITE_HIDE_HOME } = import.meta.env;
|
||||
const fixedTags = [
|
||||
...routerArrays,
|
||||
...usePermissionStoreHook().flatteningRoutes.filter(v => v?.meta?.fixedTag)
|
||||
];
|
||||
|
||||
const dynamicTagView = async () => {
|
||||
await nextTick();
|
||||
@ -228,10 +233,13 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
|
||||
other?: boolean
|
||||
): void => {
|
||||
if (other) {
|
||||
useMultiTagsStoreHook().handleTags("equal", [
|
||||
VITE_HIDE_HOME === "false" ? routerArrays[0] : toRaw(getTopMenu()),
|
||||
obj
|
||||
]);
|
||||
useMultiTagsStoreHook().handleTags(
|
||||
"equal",
|
||||
[
|
||||
VITE_HIDE_HOME === "false" ? fixedTags : toRaw(getTopMenu()),
|
||||
obj
|
||||
].flat()
|
||||
);
|
||||
} else {
|
||||
useMultiTagsStoreHook().handleTags("splice", "", {
|
||||
startIndex,
|
||||
@ -244,7 +252,7 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
|
||||
if (tag === "other") {
|
||||
spliceRoute(1, 1, true);
|
||||
} else if (tag === "left") {
|
||||
spliceRoute(1, valueIndex - 1);
|
||||
spliceRoute(fixedTags.length, valueIndex - 1, true);
|
||||
} else if (tag === "right") {
|
||||
spliceRoute(valueIndex + 1, multiTags.value.length);
|
||||
} else {
|
||||
@ -321,10 +329,11 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
|
||||
case 5:
|
||||
// 关闭全部标签页
|
||||
useMultiTagsStoreHook().handleTags("splice", "", {
|
||||
startIndex: 1,
|
||||
startIndex: fixedTags.length,
|
||||
length: multiTags.value.length
|
||||
});
|
||||
router.push(topPath);
|
||||
// router.push(fixedTags[fixedTags.length - 1]?.path);
|
||||
handleAliveRoute(route as ToRouteType);
|
||||
break;
|
||||
case 6:
|
||||
@ -363,10 +372,14 @@ function showMenus(value: boolean) {
|
||||
});
|
||||
}
|
||||
|
||||
function disabledMenus(value: boolean) {
|
||||
function disabledMenus(value: boolean, fixedTag = false) {
|
||||
Array.of(1, 2, 3, 4, 5).forEach(v => {
|
||||
tagsViews[v].disabled = value;
|
||||
});
|
||||
if (fixedTag) {
|
||||
tagsViews[2].show = false;
|
||||
tagsViews[2].disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** 检查当前右键的菜单两边是否存在别的菜单,如果左侧的菜单是顶级菜单,则不显示关闭左侧标签页,如果右侧没有菜单,则不显示关闭右侧标签页 */
|
||||
@ -383,6 +396,13 @@ function showMenuModel(
|
||||
} else {
|
||||
currentIndex = allRoute.findIndex(v => isEqual(v.query, query));
|
||||
}
|
||||
function fixedTagDisabled() {
|
||||
if (allRoute[currentIndex]?.meta?.fixedTag) {
|
||||
Array.of(1, 2, 3, 4, 5).forEach(v => {
|
||||
tagsViews[v].disabled = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showMenus(true);
|
||||
|
||||
@ -401,6 +421,7 @@ function showMenuModel(
|
||||
tagsViews[v].disabled = false;
|
||||
});
|
||||
tagsViews[2].disabled = true;
|
||||
fixedTagDisabled();
|
||||
} else if (currentIndex === 1 && routeLength === 2) {
|
||||
disabledMenus(false);
|
||||
// 左侧的菜单是顶级菜单,右侧不存在别的菜单
|
||||
@ -408,6 +429,7 @@ function showMenuModel(
|
||||
tagsViews[v].show = false;
|
||||
tagsViews[v].disabled = true;
|
||||
});
|
||||
fixedTagDisabled();
|
||||
} else if (routeLength - 1 === currentIndex && currentIndex !== 0) {
|
||||
// 当前路由是所有路由中的最后一个
|
||||
tagsViews[3].show = false;
|
||||
@ -415,18 +437,24 @@ function showMenuModel(
|
||||
tagsViews[v].disabled = false;
|
||||
});
|
||||
tagsViews[3].disabled = true;
|
||||
if (allRoute[currentIndex - 1]?.meta?.fixedTag) {
|
||||
tagsViews[2].show = false;
|
||||
tagsViews[2].disabled = true;
|
||||
}
|
||||
fixedTagDisabled();
|
||||
} else if (currentIndex === 0 || currentPath === `/redirect${topPath}`) {
|
||||
// 当前路由为顶级菜单
|
||||
disabledMenus(true);
|
||||
} else {
|
||||
disabledMenus(false);
|
||||
disabledMenus(false, allRoute[currentIndex - 1]?.meta?.fixedTag);
|
||||
fixedTagDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
function openMenu(tag, e) {
|
||||
closeMenu();
|
||||
if (tag.path === topPath) {
|
||||
// 右键菜单为顶级菜单,只显示刷新
|
||||
if (tag.path === topPath || tag?.meta?.fixedTag) {
|
||||
// 右键菜单为顶级菜单或拥有 fixedTag 属性,只显示刷新
|
||||
showMenus(false);
|
||||
tagsViews[0].show = true;
|
||||
} else if (route.path !== tag.path && route.name !== tag.name) {
|
||||
@ -485,7 +513,6 @@ function tagOnClick(item) {
|
||||
} else {
|
||||
router.push({ path });
|
||||
}
|
||||
// showMenuModel(item?.path, item?.query);
|
||||
}
|
||||
|
||||
onClickOutside(contextmenuRef, closeMenu, {
|
||||
@ -549,7 +576,11 @@ onBeforeUnmount(() => {
|
||||
v-for="(item, index) in multiTags"
|
||||
:ref="'dynamic' + index"
|
||||
:key="index"
|
||||
:class="['scroll-item is-closable', linkIsActive(item)]"
|
||||
:class="[
|
||||
'scroll-item is-closable',
|
||||
linkIsActive(item),
|
||||
!isAllEmpty(item?.meta?.fixedTag) && 'fixed-tag'
|
||||
]"
|
||||
@contextmenu.prevent="openMenu(item, $event)"
|
||||
@mouseenter.prevent="onMouseenter(index)"
|
||||
@mouseleave.prevent="onMouseleave(index)"
|
||||
@ -562,8 +593,10 @@ onBeforeUnmount(() => {
|
||||
</span>
|
||||
<span
|
||||
v-if="
|
||||
iconIsActive(item, index) ||
|
||||
(index === activeIndex && index !== 0)
|
||||
isAllEmpty(item?.meta?.fixedTag)
|
||||
? iconIsActive(item, index) ||
|
||||
(index === activeIndex && index !== 0)
|
||||
: false
|
||||
"
|
||||
class="el-icon-close"
|
||||
@click.stop="deleteMenu(item)"
|
||||
|
@ -17,9 +17,9 @@ import {
|
||||
isIncludeAllChildren
|
||||
} from "@pureadmin/utils";
|
||||
import { getConfig } from "@/config";
|
||||
import type { menuType } from "@/layout/types";
|
||||
import { buildHierarchyTree } from "@/utils/tree";
|
||||
import { userKey, type DataInfo } from "@/utils/auth";
|
||||
import { type menuType, routerArrays } from "@/layout/types";
|
||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
const IFrame = () => import("@/layout/frameView.vue");
|
||||
@ -178,6 +178,14 @@ function handleAsyncRoutes(routeList) {
|
||||
);
|
||||
usePermissionStoreHook().handleWholeMenus(routeList);
|
||||
}
|
||||
if (!useMultiTagsStoreHook().getMultiTagsCache) {
|
||||
useMultiTagsStoreHook().handleTags("equal", [
|
||||
...routerArrays,
|
||||
...usePermissionStoreHook().flatteningRoutes.filter(
|
||||
v => v?.meta?.fixedTag
|
||||
)
|
||||
]);
|
||||
}
|
||||
addPathMatch();
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { store } from "@/store";
|
||||
import { routerArrays } from "@/layout/types";
|
||||
import { usePermissionStoreHook } from "./permission";
|
||||
import { responsiveStorageNameSpace } from "@/config";
|
||||
import type { multiType, positionType } from "./types";
|
||||
import { isEqual, isBoolean, isUrl, storageLocal } from "@pureadmin/utils";
|
||||
@ -15,7 +16,12 @@ export const useMultiTagsStore = defineStore({
|
||||
? storageLocal().getItem<StorageConfigs>(
|
||||
`${responsiveStorageNameSpace()}tags`
|
||||
)
|
||||
: [...routerArrays],
|
||||
: [
|
||||
...routerArrays,
|
||||
...usePermissionStoreHook().flatteningRoutes.filter(
|
||||
v => v?.meta?.fixedTag
|
||||
)
|
||||
],
|
||||
multiTagsCache: storageLocal().getItem<StorageConfigs>(
|
||||
`${responsiveStorageNameSpace()}configure`
|
||||
)?.multiTagsCache
|
||||
|
@ -4,7 +4,12 @@ import type { cacheType } from "./types";
|
||||
import { constantMenus } from "@/router";
|
||||
import { useMultiTagsStoreHook } from "./multiTags";
|
||||
import { debounce, getKeyList } from "@pureadmin/utils";
|
||||
import { ascending, filterTree, filterNoPermissionTree } from "@/router/utils";
|
||||
import {
|
||||
ascending,
|
||||
filterTree,
|
||||
filterNoPermissionTree,
|
||||
formatFlatteningRoutes
|
||||
} from "@/router/utils";
|
||||
|
||||
export const usePermissionStore = defineStore({
|
||||
id: "pure-permission",
|
||||
@ -13,6 +18,8 @@ export const usePermissionStore = defineStore({
|
||||
constantMenus,
|
||||
// 整体路由生成的菜单(静态、动态)
|
||||
wholeMenus: [],
|
||||
// 整体路由(一维数组格式)
|
||||
flatteningRoutes: [],
|
||||
// 缓存页面keepAlive
|
||||
cachePageList: []
|
||||
}),
|
||||
@ -22,6 +29,9 @@ export const usePermissionStore = defineStore({
|
||||
this.wholeMenus = filterNoPermissionTree(
|
||||
filterTree(ascending(this.constantMenus.concat(routes)))
|
||||
);
|
||||
this.flatteningRoutes = formatFlatteningRoutes(
|
||||
this.constantMenus.concat(routes)
|
||||
);
|
||||
},
|
||||
cacheOperate({ mode, name }: cacheType) {
|
||||
const delIndex = this.cachePageList.findIndex(v => v === name);
|
||||
|
@ -20,6 +20,7 @@
|
||||
| `frameLoading` | 加载动画(内嵌的`iframe`页面是否开启首次加载动画) |
|
||||
| `keepAlive` | 缓存页面(是否缓存该路由页面,开启后会保存该页面的整体状态,刷新后会清空状态) |
|
||||
| `hiddenTag` | 标签页(当前菜单名称或自定义信息禁止添加到标签页) |
|
||||
| `fixedTag` | 固定标签页(当前菜单名称是否固定显示在标签页且不可关闭) |
|
||||
| `showLink` | 菜单(是否显示该菜单) |
|
||||
| `showParent` | 父级菜单(是否显示父级菜单 [点击查看更多](https://yiming_chang.gitee.io/pure-admin-doc/pages/routerMenu/#%E7%AC%AC%E4%B8%80%E7%A7%8D-%E8%AF%A5%E6%A8%A1%E5%BC%8F%E9%92%88%E5%AF%B9%E7%88%B6%E7%BA%A7%E8%8F%9C%E5%8D%95%E4%B8%8B%E5%8F%AA%E6%9C%89%E4%B8%80%E4%B8%AA%E5%AD%90%E8%8F%9C%E5%8D%95%E7%9A%84%E6%83%85%E5%86%B5-%E5%9C%A8%E5%AD%90%E8%8F%9C%E5%8D%95%E7%9A%84-meta-%E5%B1%9E%E6%80%A7%E4%B8%AD%E5%8A%A0%E4%B8%8A-showparent-true-%E5%8D%B3%E5%8F%AF)) |
|
||||
|
||||
|
@ -10,6 +10,7 @@ import ReAnimateSelector from "@/components/ReAnimateSelector";
|
||||
import {
|
||||
menuTypeOptions,
|
||||
showLinkOptions,
|
||||
fixedTagOptions,
|
||||
keepAliveOptions,
|
||||
hiddenTagOptions,
|
||||
showParentOptions,
|
||||
@ -37,6 +38,7 @@ const props = withDefaults(defineProps<FormProps>(), {
|
||||
frameLoading: true,
|
||||
keepAlive: false,
|
||||
hiddenTag: false,
|
||||
fixedTag: false,
|
||||
showLink: true,
|
||||
showParent: false
|
||||
})
|
||||
@ -258,33 +260,6 @@ defineExpose({ getRef });
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="缓存页面">
|
||||
<Segmented
|
||||
:modelValue="newFormInline.keepAlive ? 0 : 1"
|
||||
:options="keepAliveOptions"
|
||||
@change="
|
||||
({ option: { value } }) => {
|
||||
newFormInline.keepAlive = value;
|
||||
}
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="标签页">
|
||||
<Segmented
|
||||
:modelValue="newFormInline.hiddenTag ? 1 : 0"
|
||||
:options="hiddenTagOptions"
|
||||
@change="
|
||||
({ option: { value } }) => {
|
||||
newFormInline.hiddenTag = value;
|
||||
}
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col
|
||||
v-show="newFormInline.menuType !== 3"
|
||||
:value="12"
|
||||
@ -305,7 +280,7 @@ defineExpose({ getRef });
|
||||
</re-col>
|
||||
<re-col
|
||||
v-show="newFormInline.menuType !== 3"
|
||||
:value="8"
|
||||
:value="12"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
>
|
||||
@ -321,6 +296,47 @@ defineExpose({ getRef });
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="缓存页面">
|
||||
<Segmented
|
||||
:modelValue="newFormInline.keepAlive ? 0 : 1"
|
||||
:options="keepAliveOptions"
|
||||
@change="
|
||||
({ option: { value } }) => {
|
||||
newFormInline.keepAlive = value;
|
||||
}
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="标签页">
|
||||
<Segmented
|
||||
:modelValue="newFormInline.hiddenTag ? 1 : 0"
|
||||
:options="hiddenTagOptions"
|
||||
@change="
|
||||
({ option: { value } }) => {
|
||||
newFormInline.hiddenTag = value;
|
||||
}
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
|
||||
<el-form-item label="固定标签页">
|
||||
<Segmented
|
||||
:modelValue="newFormInline.fixedTag ? 0 : 1"
|
||||
:options="fixedTagOptions"
|
||||
@change="
|
||||
({ option: { value } }) => {
|
||||
newFormInline.fixedTag = value;
|
||||
}
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
@ -32,6 +32,19 @@ const showLinkOptions: Array<OptionsType> = [
|
||||
}
|
||||
];
|
||||
|
||||
const fixedTagOptions: Array<OptionsType> = [
|
||||
{
|
||||
label: "固定",
|
||||
tip: "当前菜单名称固定显示在标签页且不可关闭",
|
||||
value: true
|
||||
},
|
||||
{
|
||||
label: "不固定",
|
||||
tip: "当前菜单名称不固定显示在标签页且可关闭",
|
||||
value: false
|
||||
}
|
||||
];
|
||||
|
||||
const keepAliveOptions: Array<OptionsType> = [
|
||||
{
|
||||
label: "缓存",
|
||||
@ -87,6 +100,7 @@ const frameLoadingOptions: Array<OptionsType> = [
|
||||
export {
|
||||
menuTypeOptions,
|
||||
showLinkOptions,
|
||||
fixedTagOptions,
|
||||
keepAliveOptions,
|
||||
hiddenTagOptions,
|
||||
showParentOptions,
|
||||
|
@ -155,6 +155,7 @@ export function useMenu() {
|
||||
frameLoading: row?.frameLoading ?? true,
|
||||
keepAlive: row?.keepAlive ?? false,
|
||||
hiddenTag: row?.hiddenTag ?? false,
|
||||
fixedTag: row?.fixedTag ?? false,
|
||||
showLink: row?.showLink ?? true,
|
||||
showParent: row?.showParent ?? false
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ interface FormItemProps {
|
||||
frameLoading: boolean;
|
||||
keepAlive: boolean;
|
||||
hiddenTag: boolean;
|
||||
fixedTag: boolean;
|
||||
showLink: boolean;
|
||||
showParent: boolean;
|
||||
}
|
||||
|
4
types/router.d.ts
vendored
4
types/router.d.ts
vendored
@ -45,8 +45,10 @@ declare global {
|
||||
/** 离场动画 */
|
||||
leaveTransition?: string;
|
||||
};
|
||||
// 是否不添加信息到标签页,(默认`false`)
|
||||
/** 当前菜单名称或自定义信息禁止添加到标签页(默认`false`) */
|
||||
hiddenTag?: boolean;
|
||||
/** 当前菜单名称是否固定显示在标签页且不可关闭(默认`false`) */
|
||||
fixedTag?: boolean;
|
||||
/** 动态路由可打开的最大数量 `可选` */
|
||||
dynamicLevel?: number;
|
||||
/** 将某个菜单激活
|
||||
|
Loading…
x
Reference in New Issue
Block a user