release: update 5.1.0

This commit is contained in:
xiaoxian521
2024-03-09 22:45:04 +08:00
parent d543b57e34
commit 70772864ac
152 changed files with 8006 additions and 8599 deletions

View File

@@ -1,10 +1,10 @@
<script setup lang="ts">
import { isEqual } from "@pureadmin/utils";
import { transformI18n } from "@/plugins/i18n";
import { useRoute, useRouter } from "vue-router";
import { ref, watch, onMounted, toRaw } from "vue";
import { getParentPaths, findRouteByPath } from "@/router/utils";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { useRoute, useRouter, RouteLocationMatched } from "vue-router";
const route = useRoute();
const levelList = ref([]);
@@ -64,12 +64,28 @@ const getBreadcrumb = (): void => {
);
};
const handleLink = (item: RouteLocationMatched): void => {
const { redirect, path } = item;
const handleLink = item => {
const { redirect, name, path } = item;
if (redirect) {
router.push(redirect as any);
} else {
router.push(path);
if (name) {
if (item.query) {
router.push({
name,
query: item.query
});
} else if (item.params) {
router.push({
name,
params: item.params
});
} else {
router.push({ name });
}
} else {
router.push({ path });
}
}
};
@@ -92,9 +108,9 @@ watch(
<el-breadcrumb class="!leading-[50px] select-none" separator="/">
<transition-group name="breadcrumb">
<el-breadcrumb-item
class="!inline !items-stretch"
v-for="item in levelList"
:key="item.path"
class="!inline !items-stretch"
>
<a @click.prevent="handleLink(item)">
{{ transformI18n(item.meta.title) }}

View File

@@ -21,6 +21,7 @@ const {
logout,
backTopMenu,
onPanel,
getLogo,
username,
userAvatar,
avatarsStyle,
@@ -43,13 +44,14 @@ nextTick(() => {
class="horizontal-header"
>
<div class="horizontal-header-left" @click="backTopMenu">
<img src="/logo.svg" alt="logo" />
<img :src="getLogo()" alt="logo" />
<span>{{ title }}</span>
</div>
<el-menu
router
ref="menuRef"
router
mode="horizontal"
popper-class="pure-scrollbar"
class="horizontal-header-menu"
:default-active="defaultActive"
>
@@ -62,7 +64,7 @@ nextTick(() => {
</el-menu>
<div class="horizontal-header-right">
<!-- 菜单搜索 -->
<Search />
<Search id="header-search" />
<!-- 通知 -->
<Notice id="header-notice" />
<!-- 国际化 -->
@@ -77,7 +79,7 @@ nextTick(() => {
:class="['dark:!text-white', getDropdownItemClass(locale, 'zh')]"
@click="translationCh"
>
<span class="check-zh" v-show="locale === 'zh'">
<span v-show="locale === 'zh'" class="check-zh">
<IconifyIconOffline :icon="Check" />
</span>
简体中文
@@ -87,7 +89,7 @@ nextTick(() => {
:class="['dark:!text-white', getDropdownItemClass(locale, 'en')]"
@click="translationEn"
>
<span class="check-en" v-show="locale === 'en'">
<span v-show="locale === 'en'" class="check-en">
<IconifyIconOffline :icon="Check" />
</span>
English

View File

@@ -1,6 +1,8 @@
<script setup lang="ts">
import { ref, computed } from "vue";
import { computed } from "vue";
import { useGlobal } from "@pureadmin/utils";
import { useNav } from "@/layout/hooks/useNav";
import MenuFold from "@iconify-icons/ri/menu-fold-fill";
interface Props {
@@ -11,7 +13,6 @@ const props = withDefaults(defineProps<Props>(), {
isActive: false
});
const visible = ref(false);
const { tooltipEffect } = useNav();
const iconClass = computed(() => {
@@ -22,14 +23,14 @@ const iconClass = computed(() => {
"h-[16px]",
"inline-block",
"align-middle",
"text-primary",
"cursor-pointer",
"duration-[100ms]",
"hover:text-primary",
"dark:hover:!text-white"
"duration-[100ms]"
];
});
const { $storage } = useGlobal<GlobalPropertiesApi>();
const themeColor = computed(() => $storage.layout?.themeColor);
const emit = defineEmits<{
(e: "toggleClick"): void;
}>();
@@ -40,32 +41,29 @@ const toggleClick = () => {
</script>
<template>
<div class="container">
<el-tooltip
placement="right"
:visible="visible"
:effect="tooltipEffect"
:content="props.isActive ? '点击折叠' : '点击展开'"
>
<IconifyIconOffline
:icon="MenuFold"
:class="iconClass"
:style="{ transform: props.isActive ? 'none' : 'rotateY(180deg)' }"
@click="toggleClick"
@mouseenter="visible = true"
@mouseleave="visible = false"
/>
</el-tooltip>
<div class="collapse-container">
<IconifyIconOffline
v-tippy="{
content: props.isActive ? '点击折叠' : '点击展开',
theme: tooltipEffect,
hideOnClick: 'toggle',
placement: 'right'
}"
:icon="MenuFold"
:class="[iconClass, themeColor === 'light' ? '' : 'text-primary']"
:style="{ transform: props.isActive ? 'none' : 'rotateY(180deg)' }"
@click="toggleClick"
/>
</div>
</template>
<style lang="scss" scoped>
.container {
.collapse-container {
position: absolute;
bottom: 0;
width: 100%;
height: 40px;
line-height: 40px;
box-shadow: 0 0 6px -2px var(--el-color-primary);
box-shadow: 0 0 6px -3px var(--el-color-primary);
}
</style>

View File

@@ -0,0 +1,36 @@
<script setup lang="ts">
import { computed } from "vue";
import { isUrl } from "@pureadmin/utils";
import { menuType } from "@/layout/types";
defineOptions({
name: "LinkItem"
});
const props = defineProps<{
to: menuType;
}>();
const isExternalLink = computed(() => isUrl(props.to.name));
const getLinkProps = (item: menuType) => {
if (isExternalLink.value) {
return {
href: item.name,
target: "_blank",
rel: "noopener"
};
}
return {
to: item
};
};
</script>
<template>
<component
:is="isExternalLink ? 'a' : 'router-link'"
v-bind="getLinkProps(to)"
>
<slot />
</component>
</template>

View File

@@ -6,7 +6,7 @@ const props = defineProps({
collapse: Boolean
});
const { title } = useNav();
const { title, getLogo } = useNav();
</script>
<template>
@@ -19,7 +19,7 @@ const { title } = useNav();
class="sidebar-logo-link"
:to="getTopMenu()?.path ?? '/'"
>
<img src="/logo.svg" alt="logo" />
<img :src="getLogo()" alt="logo" />
<span class="sidebar-title">{{ title }}</span>
</router-link>
<router-link
@@ -29,7 +29,7 @@ const { title } = useNav();
class="sidebar-logo-link"
:to="getTopMenu()?.path ?? '/'"
>
<img src="/logo.svg" alt="logo" />
<img :src="getLogo()" alt="logo" />
<span class="sidebar-title">{{ title }}</span>
</router-link>
</transition>
@@ -48,6 +48,7 @@ const { title } = useNav();
flex-wrap: nowrap;
align-items: center;
height: 100%;
padding-left: 10px;
img {
display: inline-block;

View File

@@ -61,13 +61,14 @@ watch(
<template>
<div
v-if="device !== 'mobile'"
class="horizontal-header"
v-loading="usePermissionStoreHook().wholeMenus.length === 0"
class="horizontal-header"
>
<el-menu
router
ref="menuRef"
router
mode="horizontal"
popper-class="pure-scrollbar"
class="horizontal-header-menu"
:default-active="defaultActive"
>
@@ -96,7 +97,7 @@ watch(
</el-menu>
<div class="horizontal-header-right">
<!-- 菜单搜索 -->
<Search />
<Search id="header-search" />
<!-- 通知 -->
<Notice id="header-notice" />
<!-- 国际化 -->
@@ -111,7 +112,7 @@ watch(
:class="['dark:!text-white', getDropdownItemClass(locale, 'zh')]"
@click="translationCh"
>
<span class="check-zh" v-show="locale === 'zh'">
<span v-show="locale === 'zh'" class="check-zh">
<IconifyIconOffline :icon="Check" />
</span>
简体中文
@@ -121,7 +122,7 @@ watch(
:class="['dark:!text-white', getDropdownItemClass(locale, 'en')]"
@click="translationEn"
>
<span class="check-en" v-show="locale === 'en'">
<span v-show="locale === 'en'" class="check-en">
<IconifyIconOffline :icon="Check" />
</span>
English

View File

@@ -1,18 +1,28 @@
<script setup lang="ts">
import path from "path";
import { getConfig } from "@/config";
import LinkItem from "./linkItem.vue";
import { menuType } from "../../types";
import extraIcon from "./extraIcon.vue";
import { ReText } from "@/components/ReText";
import { useNav } from "@/layout/hooks/useNav";
import { transformI18n } from "@/plugins/i18n";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { ref, toRaw, PropType, nextTick, computed, CSSProperties } from "vue";
import {
type PropType,
type CSSProperties,
ref,
toRaw,
computed,
useAttrs
} from "vue";
import ArrowUp from "@iconify-icons/ep/arrow-up-bold";
import EpArrowDown from "@iconify-icons/ep/arrow-down-bold";
import ArrowLeft from "@iconify-icons/ep/arrow-left-bold";
import ArrowRight from "@iconify-icons/ep/arrow-right-bold";
const attrs = useAttrs();
const { layout, isCollapse, tooltipEffect, getDivStyle } = useNav();
const props = defineProps({
@@ -29,29 +39,15 @@ const props = defineProps({
}
});
const getSpanStyle = computed((): CSSProperties => {
return {
width: "100%",
textAlign: "center"
};
});
const getNoDropdownStyle = computed((): CSSProperties => {
return {
width: "100%",
display: "flex",
alignItems: "center"
};
});
const getMenuTextStyle = computed(() => {
return {
overflow: "hidden",
textOverflow: "ellipsis",
outline: "none"
};
});
const getsubMenuIconStyle = computed((): CSSProperties => {
const getSubMenuIconStyle = computed((): CSSProperties => {
return {
display: "flex",
justifyContent: "center",
@@ -60,45 +56,8 @@ const getsubMenuIconStyle = computed((): CSSProperties => {
layout.value === "horizontal"
? "0 5px 0 0"
: isCollapse.value
? "0 auto"
: "0 5px 0 0"
};
});
const getSubTextStyle = computed((): CSSProperties => {
if (!isCollapse.value) {
return {
width: "210px",
display: "inline-block",
overflow: "hidden",
textOverflow: "ellipsis"
};
} else {
return {
width: ""
};
}
});
const getSubMenuDivStyle = computed((): any => {
return item => {
return !isCollapse.value
? {
width: "100%",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
overflow: "hidden"
}
: {
width: "100%",
textAlign:
item?.parentId === null
? "center"
: layout.value === "mix" && item?.pathList?.length === 2
? "center"
: ""
};
? "0 auto"
: "0 5px 0 0"
};
});
@@ -113,41 +72,6 @@ const expandCloseIcon = computed(() => {
});
const onlyOneChild: menuType = ref(null);
// 存放菜单是否存在showTooltip属性标识
const hoverMenuMap = new WeakMap();
// 存储菜单文本dom元素
const menuTextRef = ref(null);
function hoverMenu(key) {
// 如果当前菜单showTooltip属性已存在退出计算
if (hoverMenuMap.get(key)) return;
nextTick(() => {
// 如果文本内容的整体宽度大于其可视宽度,则文本溢出
menuTextRef.value?.scrollWidth > menuTextRef.value?.clientWidth
? Object.assign(key, {
showTooltip: true
})
: Object.assign(key, {
showTooltip: false
});
hoverMenuMap.set(key, true);
});
}
// 左侧菜单折叠后,当菜单没有图标时只显示第一个文字并加上省略号
function overflowSlice(text, item?: any) {
const newText =
(text?.length > 1 ? text.toString().slice(0, 1) : text) + "...";
if (item && !(isCollapse.value && item?.parentId === null)) {
return layout.value === "mix" &&
item?.pathList?.length === 2 &&
isCollapse.value
? newText
: text;
}
return newText;
}
function hasOneShowingChild(children: menuType[] = [], parent: menuType) {
const showingChildren = children.filter((item: any) => {
@@ -182,131 +106,110 @@ function resolvePath(routePath) {
</script>
<template>
<el-menu-item
<link-item
v-if="
hasOneShowingChild(props.item.children, props.item) &&
(!onlyOneChild.children || onlyOneChild.noShowingChildren)
"
:index="resolvePath(onlyOneChild.path)"
:class="{ 'submenu-title-noDropdown': !isNest }"
:style="getNoDropdownStyle"
:to="item"
>
<div
v-if="toRaw(props.item.meta.icon)"
class="sub-menu-icon"
:style="getsubMenuIconStyle"
<el-menu-item
:index="resolvePath(onlyOneChild.path)"
:class="{ 'submenu-title-noDropdown': !isNest }"
:style="getNoDropdownStyle"
v-bind="attrs"
>
<component
:is="
useRenderIcon(
toRaw(onlyOneChild.meta.icon) ||
(props.item.meta && toRaw(props.item.meta.icon))
)
<div
v-if="toRaw(props.item.meta.icon)"
class="sub-menu-icon"
:style="getSubMenuIconStyle"
>
<component
:is="
useRenderIcon(
toRaw(onlyOneChild.meta.icon) ||
(props.item.meta && toRaw(props.item.meta.icon))
)
"
/>
</div>
<el-text
v-if="
(!props.item?.meta.icon &&
isCollapse &&
layout === 'vertical' &&
props.item?.pathList?.length === 1) ||
(!onlyOneChild.meta.icon &&
isCollapse &&
layout === 'mix' &&
props.item?.pathList?.length === 2)
"
/>
</div>
<span
v-if="
!props.item?.meta.icon &&
isCollapse &&
layout === 'vertical' &&
props.item?.pathList?.length === 1
"
:style="getSpanStyle"
>
{{ overflowSlice(transformI18n(onlyOneChild.meta.title)) }}
</span>
<span
v-if="
!onlyOneChild.meta.icon &&
isCollapse &&
layout === 'mix' &&
props.item?.pathList?.length === 2
"
:style="getSpanStyle"
>
{{ overflowSlice(transformI18n(onlyOneChild.meta.title)) }}
</span>
<template #title>
<div :style="getDivStyle">
<span v-if="layout === 'horizontal'">
{{ transformI18n(onlyOneChild.meta.title) }}
</span>
<el-tooltip
v-else
placement="top"
:effect="tooltipEffect"
:offset="-10"
:disabled="!onlyOneChild.showTooltip"
>
<template #content>
{{ transformI18n(onlyOneChild.meta.title) }}
</template>
<span
ref="menuTextRef"
:style="getMenuTextStyle"
@mouseover="hoverMenu(onlyOneChild)"
truncated
class="!px-4 !text-inherit"
>
{{ transformI18n(onlyOneChild.meta.title) }}
</el-text>
<template #title>
<div :style="getDivStyle">
<ReText
:tippyProps="{
offset: [0, -10],
theme: tooltipEffect
}"
class="!text-inherit"
>
{{ transformI18n(onlyOneChild.meta.title) }}
</span>
</el-tooltip>
<extraIcon :extraIcon="onlyOneChild.meta.extraIcon" />
</div>
</template>
</el-menu-item>
</ReText>
<extraIcon :extraIcon="onlyOneChild.meta.extraIcon" />
</div>
</template>
</el-menu-item>
</link-item>
<el-sub-menu
v-else
ref="subMenu"
v-bind="expandCloseIcon"
teleported
:index="resolvePath(props.item.path)"
v-bind="expandCloseIcon"
>
<template #title>
<div
v-if="toRaw(props.item.meta.icon)"
:style="getsubMenuIconStyle"
:style="getSubMenuIconStyle"
class="sub-menu-icon"
>
<component
:is="useRenderIcon(props.item.meta && toRaw(props.item.meta.icon))"
/>
</div>
<span v-if="layout === 'horizontal'">
{{ transformI18n(props.item.meta.title) }}
</span>
<div
:style="getSubMenuDivStyle(props.item)"
<ReText
v-if="
!(
layout === 'vertical' &&
isCollapse &&
toRaw(props.item.meta.icon) &&
props.item.parentId === null
)
"
:tippyProps="{
offset: [0, -10],
theme: tooltipEffect
}"
:class="{
'!text-inherit': true,
'!px-4':
layout !== 'horizontal' &&
isCollapse &&
!toRaw(props.item.meta.icon) &&
props.item.parentId === null
}"
>
<el-tooltip
v-if="layout !== 'horizontal'"
placement="top"
:effect="tooltipEffect"
:offset="-10"
:disabled="!props.item.showTooltip"
>
<template #content>
{{ transformI18n(props.item.meta.title) }}
</template>
<span
ref="menuTextRef"
:style="getSubTextStyle"
@mouseover="hoverMenu(props.item)"
>
{{
overflowSlice(transformI18n(props.item.meta.title), props.item)
}}
</span>
</el-tooltip>
<extraIcon v-if="!isCollapse" :extraIcon="props.item.meta.extraIcon" />
</div>
{{ transformI18n(props.item.meta.title) }}
</ReText>
<extraIcon v-if="!isCollapse" :extraIcon="props.item.meta.extraIcon" />
</template>
<sidebar-item
v-for="child in props.item.children"
:key="child.path"

View File

@@ -18,7 +18,14 @@ const showLogo = ref(
)?.showLogo ?? true
);
const { device, pureApp, isCollapse, menuSelect, toggleSideBar } = useNav();
const {
device,
pureApp,
isCollapse,
tooltipEffect,
menuSelect,
toggleSideBar
} = useNav();
const subMenuData = ref([]);
@@ -80,7 +87,7 @@ onBeforeUnmount(() => {
<template>
<div
v-loading="loading"
:class="['sidebar-container', showLogo ? 'has-logo' : '']"
:class="['sidebar-container', showLogo ? 'has-logo' : 'no-logo']"
>
<Logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar
@@ -91,10 +98,12 @@ onBeforeUnmount(() => {
router
unique-opened
mode="vertical"
popper-class="pure-scrollbar"
class="outer-most select-none"
:collapse="isCollapse"
:default-active="defaultActive"
:collapse-transition="false"
:popper-effect="tooltipEffect"
:default-active="defaultActive"
>
<sidebar-item
v-for="routes in menuData"