fix: fix a series of known problems,fixed #54

pull/48/MERGE
vince 2024-07-18 21:59:18 +08:00
parent 01e95e029f
commit 276ef2ebc3
36 changed files with 314 additions and 293 deletions

View File

@ -43,7 +43,7 @@
"@vben/utils": "workspace:*", "@vben/utils": "workspace:*",
"@vueuse/core": "^10.11.0", "@vueuse/core": "^10.11.0",
"ant-design-vue": "^4.2.3", "ant-design-vue": "^4.2.3",
"dayjs": "^1.11.11", "dayjs": "^1.11.12",
"pinia": "2.1.7", "pinia": "2.1.7",
"vue": "^3.4.32", "vue": "^3.4.32",
"vue-router": "^4.4.0" "vue-router": "^4.4.0"

View File

@ -27,6 +27,12 @@
"embedded": "Embedded", "embedded": "Embedded",
"externalLink": "External Link" "externalLink": "External Link"
}, },
"badge": {
"title": "Menu Badge",
"dot": "Dot Badge",
"text": "Text Badge",
"color": "Badge Color"
},
"fallback": { "title": "Fallback Page" }, "fallback": { "title": "Fallback Page" },
"features": { "features": {
"title": "Features", "title": "Features",

View File

@ -27,6 +27,12 @@
"embedded": "内嵌", "embedded": "内嵌",
"externalLink": "外链" "externalLink": "外链"
}, },
"badge": {
"title": "菜单徽标",
"dot": "点徽标",
"text": "文本徽标",
"color": "徽标颜色"
},
"fallback": { "fallback": {
"title": "缺省页" "title": "缺省页"
}, },

View File

@ -177,6 +177,48 @@ const routes: RouteRecordRaw[] = [
}, },
], ],
}, },
{
meta: {
icon: 'lucide:circle-dot',
title: $t('page.demos.badge.title'),
},
name: 'BadgeDemo',
path: 'badge',
redirect: '/demos/badge/dot',
children: [
{
name: 'BadgeDotDemo',
component: () => import('#/views/demos/badge/index.vue'),
path: 'dot',
meta: {
badgeType: 'dot',
icon: 'lucide:square-dot',
title: $t('page.demos.badge.dot'),
},
},
{
name: 'BadgeTextDemo',
component: () => import('#/views/demos/badge/index.vue'),
path: 'text',
meta: {
badge: 'New',
icon: 'lucide:square-dot',
title: $t('page.demos.badge.text'),
},
},
{
name: 'BadgeColorDemo',
component: () => import('#/views/demos/badge/index.vue'),
path: 'color',
meta: {
badge: 'Hot',
badgeVariants: 'destructive',
icon: 'lucide:square-dot',
title: $t('page.demos.badge.color'),
},
},
],
},
{ {
meta: { meta: {
icon: 'ic:round-settings-input-composite', icon: 'ic:round-settings-input-composite',

View File

@ -0,0 +1,9 @@
<script lang="ts" setup>
import { Fallback } from '@vben/common-ui';
defineOptions({ name: 'Menu321' });
</script>
<template>
<Fallback description="用于徽标示例" status="coming-soon" title="徽标示例" />
</template>

View File

@ -31,7 +31,7 @@
"@changesets/git": "^3.0.0", "@changesets/git": "^3.0.0",
"@manypkg/get-packages": "^2.2.2", "@manypkg/get-packages": "^2.2.2",
"consola": "^3.2.3", "consola": "^3.2.3",
"dayjs": "^1.11.11", "dayjs": "^1.11.12",
"find-up": "^7.0.0", "find-up": "^7.0.0",
"nanoid": "^5.0.7", "nanoid": "^5.0.7",
"pkg-types": "^1.1.3", "pkg-types": "^1.1.3",

View File

@ -47,7 +47,7 @@
"tailwindcss": "^3.4.3" "tailwindcss": "^3.4.3"
}, },
"dependencies": { "dependencies": {
"@iconify/json": "^2.2.228", "@iconify/json": "^2.2.229",
"@iconify/tailwind": "^1.1.1", "@iconify/tailwind": "^1.1.1",
"@tailwindcss/forms": "^0.5.7", "@tailwindcss/forms": "^0.5.7",
"@tailwindcss/nesting": "0.0.0-insiders.565cd3e", "@tailwindcss/nesting": "0.0.0-insiders.565cd3e",

View File

@ -32,7 +32,7 @@ const shadcnUiColors = {
}, },
background: { background: {
DEFAULT: 'hsl(var(--background))', DEFAULT: 'hsl(var(--background))',
content: 'hsl(var(--background-content))', deep: 'hsl(var(--background-deep))',
}, },
border: { border: {
DEFAULT: 'hsl(var(--border))', DEFAULT: 'hsl(var(--border))',

View File

@ -41,7 +41,7 @@
"@vben/node-utils": "workspace:*", "@vben/node-utils": "workspace:*",
"@vitejs/plugin-vue": "^5.0.5", "@vitejs/plugin-vue": "^5.0.5",
"@vitejs/plugin-vue-jsx": "^4.0.0", "@vitejs/plugin-vue-jsx": "^4.0.0",
"dayjs": "^1.11.11", "dayjs": "^1.11.12",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"rollup": "^4.18.1", "rollup": "^4.18.1",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",

View File

@ -66,7 +66,7 @@
"@vue/test-utils": "^2.4.6", "@vue/test-utils": "^2.4.6",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"cspell": "^8.11.0", "cspell": "^8.11.0",
"husky": "^9.1.0", "husky": "^9.1.1",
"is-ci": "^3.0.1", "is-ci": "^3.0.1",
"jsdom": "^24.1.0", "jsdom": "^24.1.0",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",

View File

@ -61,7 +61,7 @@ const defaultPreferences: Preferences = {
}, },
sidebar: { sidebar: {
collapsed: false, collapsed: false,
collapsedShowTitle: true, collapsedShowTitle: false,
enable: true, enable: true,
expandOnHover: true, expandOnHover: true,
extraCollapse: true, extraCollapse: true,

View File

@ -84,6 +84,10 @@ function usePreferences() {
return isMixedNav.value || isSideMixedNav.value || isSideNav.value; return isMixedNav.value || isSideMixedNav.value || isSideNav.value;
}); });
const sidebarCollapsed = computed(() => {
return preferences.sidebar.collapsed;
});
/** /**
* @zh_CN keep-alive * @zh_CN keep-alive
* tabskeep-alive * tabskeep-alive
@ -172,6 +176,7 @@ function usePreferences() {
isSideNav, isSideNav,
keepAlive, keepAlive,
layout, layout,
sidebarCollapsed,
theme, theme,
}; };
} }

View File

@ -23,14 +23,14 @@
scroll-behavior: smooth; scroll-behavior: smooth;
text-rendering: optimizelegibility; text-rendering: optimizelegibility;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
}
html.invert-mode { &.invert-mode {
@apply invert; @apply invert;
} }
html.grayscale-mode { &.grayscale-mode {
@apply grayscale; @apply grayscale;
}
} }
#app, #app,
@ -41,7 +41,8 @@
body { body {
min-height: 100vh; min-height: 100vh;
overflow: overlay;
/* overflow: overlay; */
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
} }

View File

@ -5,7 +5,7 @@
--background: 222.34deg 10.43% 12.27%; --background: 222.34deg 10.43% 12.27%;
/* 主体区域背景色 */ /* 主体区域背景色 */
--background-content: 220deg 13.06% 9%; --background-deep: 220deg 13.06% 9%;
--foreground: 220 13% 91%; --foreground: 220 13% 91%;
/* Background color for <Card /> */ /* Background color for <Card /> */
@ -95,7 +95,7 @@
.dark[data-theme='violet'], .dark[data-theme='violet'],
[data-theme='violet'] .dark { [data-theme='violet'] .dark {
--background: 224 71.4% 4.1%; --background: 224 71.4% 4.1%;
--background-content: var(--background); --background-deep: var(--background);
--foreground: 210 20% 98%; --foreground: 210 20% 98%;
--card: 224 71.4% 4.1%; --card: 224 71.4% 4.1%;
--card-foreground: 210 20% 98%; --card-foreground: 210 20% 98%;
@ -120,7 +120,7 @@
.dark[data-theme='pink'], .dark[data-theme='pink'],
[data-theme='pink'] .dark { [data-theme='pink'] .dark {
--background: 20 14.3% 4.1%; --background: 20 14.3% 4.1%;
--background-content: var(--background); --background-deep: var(--background);
--foreground: 0 0% 95%; --foreground: 0 0% 95%;
--card: 24 9.8% 10%; --card: 24 9.8% 10%;
--card-foreground: 0 0% 95%; --card-foreground: 0 0% 95%;
@ -145,7 +145,7 @@
.dark[data-theme='rose'], .dark[data-theme='rose'],
[data-theme='rose'] .dark { [data-theme='rose'] .dark {
--background: 0 0% 3.9%; --background: 0 0% 3.9%;
--background-content: var(--background); --background-deep: var(--background);
--foreground: 0 0% 98%; --foreground: 0 0% 98%;
--card: 0 0% 3.9%; --card: 0 0% 3.9%;
--card-foreground: 0 0% 98%; --card-foreground: 0 0% 98%;
@ -170,7 +170,7 @@
.dark[data-theme='sky-blue'], .dark[data-theme='sky-blue'],
[data-theme='sky-blue'] .dark { [data-theme='sky-blue'] .dark {
--background: 222.2 84% 4.9%; --background: 222.2 84% 4.9%;
--background-content: var(--background); --background-deep: var(--background);
--foreground: 210 40% 98%; --foreground: 210 40% 98%;
--card: 222.2 84% 4.9%; --card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%; --card-foreground: 210 40% 98%;
@ -195,7 +195,7 @@
.dark[data-theme='deep-blue'], .dark[data-theme='deep-blue'],
[data-theme='deep-blue'] .dark { [data-theme='deep-blue'] .dark {
--background: 222.2 84% 4.9%; --background: 222.2 84% 4.9%;
--background-content: var(--background); --background-deep: var(--background);
--foreground: 210 40% 98%; --foreground: 210 40% 98%;
--card: 222.2 84% 4.9%; --card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%; --card-foreground: 210 40% 98%;
@ -220,7 +220,7 @@
.dark[data-theme='green'], .dark[data-theme='green'],
[data-theme='green'] .dark { [data-theme='green'] .dark {
--background: 20 14.3% 4.1%; --background: 20 14.3% 4.1%;
--background-content: var(--background); --background-deep: var(--background);
--foreground: 0 0% 95%; --foreground: 0 0% 95%;
--card: 24 9.8% 10%; --card: 24 9.8% 10%;
--card-foreground: 0 0% 95%; --card-foreground: 0 0% 95%;
@ -245,7 +245,7 @@
.dark[data-theme='deep-green'], .dark[data-theme='deep-green'],
[data-theme='deep-green'] .dark { [data-theme='deep-green'] .dark {
--background: 20 14.3% 4.1%; --background: 20 14.3% 4.1%;
--background-content: var(--background); --background-deep: var(--background);
--foreground: 0 0% 95%; --foreground: 0 0% 95%;
--card: 24 9.8% 10%; --card: 24 9.8% 10%;
--card-foreground: 0 0% 95%; --card-foreground: 0 0% 95%;
@ -270,7 +270,7 @@
.dark[data-theme='orange'], .dark[data-theme='orange'],
[data-theme='orange'] .dark { [data-theme='orange'] .dark {
--background: 20 14.3% 4.1%; --background: 20 14.3% 4.1%;
--background-content: var(--background); --background-deep: var(--background);
--foreground: 60 9.1% 97.8%; --foreground: 60 9.1% 97.8%;
--card: 20 14.3% 4.1%; --card: 20 14.3% 4.1%;
--card-foreground: 60 9.1% 97.8%; --card-foreground: 60 9.1% 97.8%;
@ -295,7 +295,7 @@
.dark[data-theme='yellow'], .dark[data-theme='yellow'],
[data-theme='yellow'] .dark { [data-theme='yellow'] .dark {
--background: 20 14.3% 4.1%; --background: 20 14.3% 4.1%;
--background-content: var(--background); --background-deep: var(--background);
--foreground: 60 9.1% 97.8%; --foreground: 60 9.1% 97.8%;
--card: 20 14.3% 4.1%; --card: 20 14.3% 4.1%;
--card-foreground: 60 9.1% 97.8%; --card-foreground: 60 9.1% 97.8%;
@ -320,7 +320,7 @@
.dark[data-theme='zinc'], .dark[data-theme='zinc'],
[data-theme='zinc'] .dark { [data-theme='zinc'] .dark {
--background: 240 10% 3.9%; --background: 240 10% 3.9%;
--background-content: var(--background); --background-deep: var(--background);
--foreground: 0 0% 98%; --foreground: 0 0% 98%;
--card: 240 10% 3.9%; --card: 240 10% 3.9%;
--card-foreground: 0 0% 98%; --card-foreground: 0 0% 98%;
@ -345,7 +345,7 @@
.dark[data-theme='neutral'], .dark[data-theme='neutral'],
[data-theme='neutral'] .dark { [data-theme='neutral'] .dark {
--background: 0 0% 3.9%; --background: 0 0% 3.9%;
--background-content: var(--background); --background-deep: var(--background);
--foreground: 0 0% 98%; --foreground: 0 0% 98%;
--card: 0 0% 3.9%; --card: 0 0% 3.9%;
--card-foreground: 0 0% 98%; --card-foreground: 0 0% 98%;
@ -370,7 +370,7 @@
.dark[data-theme='slate'], .dark[data-theme='slate'],
[data-theme='slate'] .dark { [data-theme='slate'] .dark {
--background: 222.2 84% 4.9%; --background: 222.2 84% 4.9%;
--background-content: var(--background); --background-deep: var(--background);
--foreground: 210 40% 98%; --foreground: 210 40% 98%;
--card: 222.2 84% 4.9%; --card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%; --card-foreground: 210 40% 98%;
@ -395,7 +395,7 @@
.dark[data-theme='gray'], .dark[data-theme='gray'],
[data-theme='gray'] .dark { [data-theme='gray'] .dark {
--background: 224 71.4% 4.1%; --background: 224 71.4% 4.1%;
--background-content: var(--background); --background-deep: var(--background);
--foreground: 210 20% 98%; --foreground: 210 20% 98%;
--card: 224 71.4% 4.1%; --card: 224 71.4% 4.1%;
--card-foreground: 210 20% 98%; --card-foreground: 210 20% 98%;

View File

@ -8,7 +8,7 @@
--background: 0 0 100%; --background: 0 0 100%;
/* 主体区域背景色 */ /* 主体区域背景色 */
--background-content: 210 11.11% 96.47%; --background-deep: 210 11.11% 96.47%;
--foreground: 210 6% 21%; --foreground: 210 6% 21%;
/* Background color for <Card /> */ /* Background color for <Card /> */
@ -74,7 +74,7 @@
/* ============= custom ============= */ /* ============= custom ============= */
/* 遮罩颜色 */ /* 遮罩颜色 */
--overlay: 0deg 0% 0% / 40%; --overlay: 0deg 0% 0% / 30%;
/* 基本文字大小 */ /* 基本文字大小 */
--font-size-base: 16px; --font-size-base: 16px;

View File

@ -9,50 +9,20 @@ import { useContentHeightListener } from '@vben-core/hooks';
interface Props { interface Props {
/** /**
* 内容区域定宽 * 内容区域定宽
* @default 'wide'
*/ */
contentCompact?: ContentCompactType; contentCompact: ContentCompactType;
/** /**
* 定宽布局宽度 * 定宽布局宽度
* @default 1200
*/ */
contentCompactWidth?: number; contentCompactWidth: number;
/** padding: number;
* padding paddingBottom: number;
* @default 16 paddingLeft: number;
*/ paddingRight: number;
padding?: number; paddingTop: number;
/**
* paddingBottom
* @default 16
*/
paddingBottom?: number;
/**
* paddingLeft
* @default 16
*/
paddingLeft?: number;
/**
* paddingRight
* @default 16
*/
paddingRight?: number;
/**
* paddingTop
* @default 16
*/
paddingTop?: number;
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {});
contentCompact: 'wide',
contentCompactWidth: 1200,
padding: 16,
paddingBottom: 16,
paddingLeft: 16,
paddingRight: 16,
paddingTop: 16,
});
const { contentElement } = useContentHeightListener(); const { contentElement } = useContentHeightListener();
@ -83,7 +53,7 @@ const style = computed((): CSSProperties => {
</script> </script>
<template> <template>
<main ref="contentElement" :style="style" class="bg-background-content"> <main ref="contentElement" :style="style" class="bg-background-deep">
<slot></slot> <slot></slot>
</main> </main>
</template> </template>

View File

@ -4,38 +4,21 @@ import { computed } from 'vue';
interface Props { interface Props {
/** /**
* 是否固定在顶部 * 是否固定在底部
* @default true
*/ */
fixed?: boolean; fixed?: boolean;
/** height: number;
* 高度
* @default 32
*/
height?: number;
/** /**
* 是否显示 * 是否显示
* @default true * @default true
*/ */
show?: boolean; show?: boolean;
/** width: string;
* 高度 zIndex: number;
* @default 100%
*/
width?: string;
/**
* zIndex
* @default 0
*/
zIndex?: number;
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
fixed: true,
height: 32,
show: true, show: true,
width: '100%',
zIndex: 0,
}); });
const style = computed((): CSSProperties => { const style = computed((): CSSProperties => {
@ -53,7 +36,7 @@ const style = computed((): CSSProperties => {
<template> <template>
<footer <footer
:style="style" :style="style"
class="bg-background-content bottom-0 w-full transition-all duration-200" class="bg-background-deep bottom-0 w-full transition-all duration-200"
> >
<slot></slot> <slot></slot>
</footer> </footer>

View File

@ -8,61 +8,45 @@ import { VbenIconButton } from '@vben-core/shadcn-ui';
interface Props { interface Props {
/** /**
* 横屏 * 横屏
* @default false
*/ */
fullWidth?: boolean; fullWidth: boolean;
/** /**
* 高度 * 高度
* @default 60
*/ */
height?: number; height: number;
/** /**
* 是否混合导航 * 是否混合导航
* @default false * @default false
*/ */
isMixedNav?: boolean; isMixedNav: boolean;
/** /**
* 是否移动端 * 是否移动端
* @default false
*/ */
isMobile?: boolean; isMobile: boolean;
/** /**
* 是否显示 * 是否显示
* @default true
*/ */
show?: boolean; show: boolean;
/** /**
* 是否显示关闭菜单按钮 * 是否显示关闭菜单按钮
* @default true
*/ */
showToggleBtn?: boolean; showToggleBtn: boolean;
/** /**
* 侧边菜单宽度 * 侧边菜单宽度
* @default 0
*/ */
sidebarWidth?: number; sidebarWidth: number;
/** /**
* 宽度 * 宽度
* @default 100%
*/ */
width?: string; width: string;
/** /**
* zIndex * zIndex
* @default 0
*/ */
zIndex?: number; zIndex: number;
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {});
height: 60,
isMixedNav: false,
show: true,
showToggleBtn: false,
sidebarWidth: 0,
width: '100%',
zIndex: 0,
});
const emit = defineEmits<{ openMenu: []; toggleSidebar: [] }>(); const emit = defineEmits<{ openMenu: []; toggleSidebar: [] }>();
@ -73,7 +57,6 @@ const style = computed((): CSSProperties => {
const right = !show || !fullWidth ? undefined : 0; const right = !show || !fullWidth ? undefined : 0;
return { return {
// ...(props.isMixedNav ? { left: 0, position: `fixed` } : {}),
height: `${height}px`, height: `${height}px`,
marginTop: show ? 0 : `-${height}px`, marginTop: show ? 0 : `-${height}px`,
right, right,
@ -87,11 +70,7 @@ const logoStyle = computed((): CSSProperties => {
}); });
function handleToggleMenu() { function handleToggleMenu() {
if (props.isMobile) { props.isMobile ? emit('openMenu') : emit('toggleSidebar');
emit('openMenu');
} else {
emit('toggleSidebar');
}
} }
</script> </script>

View File

@ -9,7 +9,7 @@ import { SidebarCollapseButton, SidebarFixedButton } from './widgets';
interface Props { interface Props {
/** /**
* 折叠区域高度 * 折叠区域高度
* @default 32 * @default 42
*/ */
collapseHeight?: number; collapseHeight?: number;
/** /**
@ -41,6 +41,11 @@ interface Props {
* @default false * @default false
*/ */
isSidebarMixed?: boolean; isSidebarMixed?: boolean;
/**
* 顶部margin
* @default 60
*/
marginTop?: number;
/** /**
* 混合菜单宽度 * 混合菜单宽度
* @default 80 * @default 80
@ -85,8 +90,9 @@ const props = withDefaults(defineProps<Props>(), {
extraWidth: 180, extraWidth: 180,
fixedExtra: false, fixedExtra: false,
isSidebarMixed: false, isSidebarMixed: false,
mixedWidth: 80, marginTop: 0,
paddingTop: 60, mixedWidth: 70,
paddingTop: 0,
show: true, show: true,
showCollapseButton: true, showCollapseButton: true,
theme: 'dark', theme: 'dark',
@ -108,11 +114,13 @@ const asideRef = shallowRef<HTMLDivElement | null>();
const hiddenSideStyle = computed((): CSSProperties => calcMenuWidthStyle(true)); const hiddenSideStyle = computed((): CSSProperties => calcMenuWidthStyle(true));
const style = computed((): CSSProperties => { const style = computed((): CSSProperties => {
const { isSidebarMixed, paddingTop, zIndex } = props; const { isSidebarMixed, marginTop, paddingTop, zIndex } = props;
return { return {
'--scroll-shadow': 'var(--sidebar)', '--scroll-shadow': 'var(--sidebar)',
...calcMenuWidthStyle(false), ...calcMenuWidthStyle(false),
height: `calc(100% - ${marginTop}px)`,
marginTop: `${marginTop}px`,
paddingTop: `${paddingTop}px`, paddingTop: `${paddingTop}px`,
zIndex, zIndex,
...(isSidebarMixed && extraVisible.value ? { transition: 'none' } : {}), ...(isSidebarMixed && extraVisible.value ? { transition: 'none' } : {}),
@ -222,6 +230,7 @@ function handleMouseleave() {
if (expandOnHover.value) { if (expandOnHover.value) {
return; return;
} }
expandOnHovering.value = false; expandOnHovering.value = false;
collapse.value = true; collapse.value = true;
extraVisible.value = false; extraVisible.value = false;
@ -233,7 +242,7 @@ function handleMouseleave() {
v-if="domVisible" v-if="domVisible"
:class="theme" :class="theme"
:style="hiddenSideStyle" :style="hiddenSideStyle"
class="h-full transition-all duration-200" class="h-full transition-all duration-150"
></div> ></div>
<aside <aside
:class="[ :class="[
@ -244,7 +253,7 @@ function handleMouseleave() {
}, },
]" ]"
:style="style" :style="style"
class="border-border fixed left-0 top-0 h-full border-r transition-all duration-200" class="border-border fixed left-0 top-0 h-full border-r transition-all duration-150"
@mouseenter="handleMouseenter" @mouseenter="handleMouseenter"
@mouseleave="handleMouseleave" @mouseleave="handleMouseleave"
> >
@ -255,7 +264,7 @@ function handleMouseleave() {
<div v-if="slots.logo" :style="headerStyle"> <div v-if="slots.logo" :style="headerStyle">
<slot name="logo"></slot> <slot name="logo"></slot>
</div> </div>
<VbenScrollbar :style="contentStyle" shadow> <VbenScrollbar :style="contentStyle" shadow shadow-border>
<slot></slot> <slot></slot>
</VbenScrollbar> </VbenScrollbar>
@ -267,8 +276,11 @@ function handleMouseleave() {
<div <div
v-if="isSidebarMixed" v-if="isSidebarMixed"
ref="asideRef" ref="asideRef"
:class="{
'border-r': extraVisible,
}"
:style="extraStyle" :style="extraStyle"
class="border-border bg-sidebar fixed top-0 h-full overflow-hidden border-x transition-all duration-200" class="border-border bg-sidebar fixed top-0 h-full overflow-hidden transition-all duration-200"
> >
<SidebarCollapseButton <SidebarCollapseButton
v-if="isSidebarMixed && expandOnHover" v-if="isSidebarMixed && expandOnHover"
@ -286,6 +298,7 @@ function handleMouseleave() {
:style="extraContentStyle" :style="extraContentStyle"
class="border-border border-t py-2" class="border-border border-t py-2"
shadow shadow
shadow-border
> >
<slot name="extra"></slot> <slot name="extra"></slot>
</VbenScrollbar> </VbenScrollbar>

View File

@ -5,15 +5,11 @@ import { computed } from 'vue';
interface Props { interface Props {
/** /**
* 高度 * 高度
* @default 30
*/ */
height?: number; height: number;
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {});
fixed: true,
height: 38,
});
const style = computed((): CSSProperties => { const style = computed((): CSSProperties => {
const { height } = props; const { height } = props;

View File

@ -123,6 +123,11 @@ interface VbenLayoutProps {
* @default true * @default true
*/ */
sidebarEnable?: boolean; sidebarEnable?: boolean;
/**
*
* @default 48
*/
sidebarExtraCollapsedWidth?: number;
/** /**
* *
* @default false * @default false

View File

@ -21,6 +21,7 @@ defineOptions({
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
contentCompact: 'wide', contentCompact: 'wide',
contentCompactWidth: 1200,
contentPadding: 0, contentPadding: 0,
contentPaddingBottom: 0, contentPaddingBottom: 0,
contentPaddingLeft: 0, contentPaddingLeft: 0,
@ -39,6 +40,7 @@ const props = withDefaults(defineProps<Props>(), {
layout: 'sidebar-nav', layout: 'sidebar-nav',
sideCollapseWidth: 60, sideCollapseWidth: 60,
sidebarCollapseShowTitle: false, sidebarCollapseShowTitle: false,
sidebarExtraCollapsedWidth: 60,
sidebarHidden: false, sidebarHidden: false,
sidebarMixedWidth: 80, sidebarMixedWidth: 80,
sidebarSemiDark: true, sidebarSemiDark: true,
@ -62,16 +64,16 @@ const {
isScrolling, isScrolling,
y: scrollY, y: scrollY,
} = useScroll(document); } = useScroll(document);
const { y: mouseY } = useMouse({ type: 'client' }); const { y: mouseY } = useMouse({ type: 'client' });
// sidehover // sidehover
const sidebarExpandOnHovering = ref(false); const sidebarExpandOnHovering = ref(false);
// const sideHidden = ref(false);
const headerIsHidden = ref(false); const headerIsHidden = ref(false);
const realLayout = computed(() => { const realLayout = computed(() =>
return props.isMobile ? 'sidebar-nav' : props.layout; props.isMobile ? 'sidebar-nav' : props.layout,
}); );
/** /**
* 是否全屏显示content不需要侧边底部顶部tab区域 * 是否全屏显示content不需要侧边底部顶部tab区域
@ -98,7 +100,7 @@ const isMixedNav = computed(() => realLayout.value === 'mixed-nav');
/** /**
* 顶栏是否自动隐藏 * 顶栏是否自动隐藏
*/ */
const isHeaderAuto = computed(() => props.headerMode === 'auto'); const isHeaderAutoMode = computed(() => props.headerMode === 'auto');
/** /**
* header区域高度 * header区域高度
@ -146,7 +148,7 @@ const sidebarEnableState = computed(() => {
/** /**
* 侧边区域离顶部高度 * 侧边区域离顶部高度
*/ */
const sidePaddingTop = computed(() => { const sidebarMarginTop = computed(() => {
const { isMobile } = props; const { isMobile } = props;
return isMixedNav.value && !isMobile ? getHeaderHeight.value : 0; return isMixedNav.value && !isMobile ? getHeaderHeight.value : 0;
}); });
@ -182,10 +184,10 @@ const getSidebarWidth = computed(() => {
/** /**
* 获取扩展区域宽度 * 获取扩展区域宽度
*/ */
const getExtraWidth = computed(() => { const sidebarExtraWidth = computed(() => {
const { sidebarWidth } = props; const { sidebarExtraCollapsedWidth, sidebarWidth } = props;
return sidebarExtraCollapse.value ? getSideCollapseWidth.value : sidebarWidth; return sidebarExtraCollapse.value ? sidebarExtraCollapsedWidth : sidebarWidth;
}); });
/** /**
@ -269,19 +271,29 @@ const mainStyle = computed(() => {
}; };
}); });
// tabbar
const tabbarStyle = computed((): CSSProperties => { const tabbarStyle = computed((): CSSProperties => {
let width = ''; let width = '';
let marginLeft = 0; let marginLeft = 0;
// tabbar 100%
if (!isMixedNav.value) { if (!isMixedNav.value) {
width = '100%'; width = '100%';
} else if (sidebarEnable.value) { } else if (sidebarEnable.value) {
//
const onHoveringWidth = sidebarExpandOnHover.value
? props.sidebarWidth
: getSideCollapseWidth.value;
// marginLeft
marginLeft = sidebarCollapse.value marginLeft = sidebarCollapse.value
? getSideCollapseWidth.value ? getSideCollapseWidth.value
: props.sidebarWidth; : onHoveringWidth;
width = `calc(100% - ${getSidebarWidth.value}px)`; // tabbar 100%
width = `calc(100% - ${sidebarCollapse.value ? getSidebarWidth.value : onHoveringWidth}px)`;
} else { } else {
// tabbar 100%
width = '100%'; width = '100%';
} }
@ -300,7 +312,7 @@ const contentStyle = computed((): CSSProperties => {
fixed && fixed &&
!fullContent.value && !fullContent.value &&
!headerIsHidden.value && !headerIsHidden.value &&
(!isHeaderAuto.value || scrollY.value < headerWrapperHeight.value) (!isHeaderAutoMode.value || scrollY.value < headerWrapperHeight.value)
? `${headerWrapperHeight.value}px` ? `${headerWrapperHeight.value}px`
: 0, : 0,
paddingBottom: `${footerEnable && footerFixed ? footerHeight : 0}px`, paddingBottom: `${footerEnable && footerFixed ? footerHeight : 0}px`,
@ -333,7 +345,12 @@ const headerWrapperStyle = computed((): CSSProperties => {
*/ */
const sidebarZIndex = computed(() => { const sidebarZIndex = computed(() => {
const { isMobile, zIndex } = props; const { isMobile, zIndex } = props;
const offset = isMobile || isSideMode.value ? 1 : -1; let offset = isMobile || isSideMode.value ? 1 : -1;
if (isMixedNav.value) {
offset += 1;
}
return zIndex + offset; return zIndex + offset;
}); });
@ -366,7 +383,12 @@ const showHeaderLogo = computed(() => {
watch( watch(
() => props.isMobile, () => props.isMobile,
(val) => { (val) => {
sidebarCollapse.value = val; if (val) {
sidebarCollapse.value = true;
}
},
{
immediate: true,
}, },
); );
@ -379,7 +401,7 @@ watch(
watch( watch(
[() => props.headerMode, () => mouseY.value], [() => props.headerMode, () => mouseY.value],
() => { () => {
if (!isHeaderAuto.value || isMixedNav.value || fullContent.value) { if (!isHeaderAutoMode.value || isMixedNav.value || fullContent.value) {
return; return;
} }
headerIsHidden.value = true; headerIsHidden.value = true;
@ -434,10 +456,6 @@ function handleClickMask() {
sidebarCollapse.value = true; sidebarCollapse.value = true;
} }
function handleToggleSidebar() {
emit('toggleSidebar');
}
function handleOpenMenu() { function handleOpenMenu() {
sidebarCollapse.value = false; sidebarCollapse.value = false;
} }
@ -456,12 +474,12 @@ function handleOpenMenu() {
v-model:extra-visible="sidebarExtraVisible" v-model:extra-visible="sidebarExtraVisible"
:collapse-width="getSideCollapseWidth" :collapse-width="getSideCollapseWidth"
:dom-visible="!isMobile" :dom-visible="!isMobile"
:extra-width="getExtraWidth" :extra-width="sidebarExtraWidth"
:fixed-extra="sidebarExpandOnHover" :fixed-extra="sidebarExpandOnHover"
:header-height="isMixedNav ? 0 : getHeaderHeight" :header-height="isMixedNav ? 0 : getHeaderHeight"
:is-sidebar-mixed="isSidebarMixedNav" :is-sidebar-mixed="isSidebarMixedNav"
:margin-top="sidebarMarginTop"
:mixed-width="sidebarMixedWidth" :mixed-width="sidebarMixedWidth"
:padding-top="sidePaddingTop"
:show="showSidebar" :show="showSidebar"
:theme="sidebarFace.theme" :theme="sidebarFace.theme"
:width="getSidebarWidth" :width="getSidebarWidth"
@ -506,7 +524,7 @@ function handleOpenMenu() {
:width="mainStyle.width" :width="mainStyle.width"
:z-index="headerZIndex" :z-index="headerZIndex"
@open-menu="handleOpenMenu" @open-menu="handleOpenMenu"
@toggle-sidebar="handleToggleSidebar" @toggle-sidebar="() => emit('toggleSidebar')"
> >
<template v-if="showHeaderLogo" #logo> <template v-if="showHeaderLogo" #logo>
<slot name="logo"></slot> <slot name="logo"></slot>

View File

@ -397,7 +397,7 @@ $namespace: vben;
&:hover { &:hover {
.#{$namespace}-menu__icon { .#{$namespace}-menu__icon {
transform: scale(1.3); transform: scale(1.2);
} }
} }
@ -422,7 +422,7 @@ $namespace: vben;
.#{$namespace}-menu__popup-container, .#{$namespace}-menu__popup-container,
.#{$namespace}-menu { .#{$namespace}-menu {
--menu-title-width: 140px; --menu-title-width: 140px;
--menu-item-icon-width: 20px; --menu-item-icon-size: 16px;
--menu-item-height: 38px; --menu-item-height: 38px;
--menu-item-padding-y: 22px; --menu-item-padding-y: 22px;
--menu-item-padding-x: 12px; --menu-item-padding-x: 12px;
@ -430,7 +430,7 @@ $namespace: vben;
--menu-item-popup-padding-x: 12px; --menu-item-popup-padding-x: 12px;
--menu-item-margin-y: 3px; --menu-item-margin-y: 3px;
--menu-item-margin-x: 0px; --menu-item-margin-x: 0px;
--menu-item-collapse-padding-y: 25px; --menu-item-collapse-padding-y: 23.5px;
--menu-item-collapse-padding-x: 0px; --menu-item-collapse-padding-x: 0px;
--menu-item-collapse-margin-y: 4px; --menu-item-collapse-margin-y: 4px;
--menu-item-collapse-margin-x: 0px; --menu-item-collapse-margin-x: 0px;
@ -701,10 +701,9 @@ $namespace: vben;
&__icon { &__icon {
flex-shrink: 0; flex-shrink: 0;
// width: var(--menu-item-icon-width); width: var(--menu-item-icon-size);
max-height: var(--menu-item-icon-width); height: var(--menu-item-icon-size);
margin-right: 12px; margin-right: 8px;
font-size: 20px;
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
} }
@ -810,7 +809,7 @@ $namespace: vben;
width: inherit; width: inherit;
margin-top: -8px; margin-top: -8px;
margin-right: 0; margin-right: 0;
font-size: 16px; // font-size: 16px;
font-weight: normal; font-weight: normal;
opacity: 1; opacity: 1;
transition: transform 0.25s ease; transition: transform 0.25s ease;

View File

@ -25,14 +25,6 @@ const emit = defineEmits<{
}>(); }>();
const { b, e, is } = useNamespace('normal-menu'); const { b, e, is } = useNamespace('normal-menu');
function handleClick(menu: MenuRecordRaw) {
emit('select', menu);
}
function handleMouseenter(menu: MenuRecordRaw) {
emit('enter', menu);
}
</script> </script>
<template> <template>
@ -49,8 +41,8 @@ function handleMouseenter(menu: MenuRecordRaw) {
<template v-for="menu in menus" :key="menu.path"> <template v-for="menu in menus" :key="menu.path">
<li <li
:class="[e('item'), is('active', activePath === menu.path)]" :class="[e('item'), is('active', activePath === menu.path)]"
@click="handleClick(menu)" @click="() => emit('select', menu)"
@mouseenter="handleMouseenter(menu)" @mouseenter="() => emit('enter', menu)"
> >
<VbenIcon :class="e('icon')" :icon="menu.icon" fallback /> <VbenIcon :class="e('icon')" :icon="menu.icon" fallback />
<span :class="e('name')" class="truncate"> {{ menu.name }}</span> <span :class="e('name')" class="truncate"> {{ menu.name }}</span>
@ -64,10 +56,9 @@ $namespace: vben;
.#{$namespace}-normal-menu { .#{$namespace}-normal-menu {
--menu-item-margin-y: 4px; --menu-item-margin-y: 4px;
--menu-item-margin-x: 0px; --menu-item-margin-x: 0px;
--menu-item-padding-y: 8px; --menu-item-padding-y: 9px;
--menu-item-padding-x: 0px; --menu-item-padding-x: 0px;
--menu-item-radius: 0px; --menu-item-radius: 0px;
--menu-dark-background: 0deg 0% 100% / 10%;
height: calc(100% - 4px); height: calc(100% - 4px);
@ -82,12 +73,9 @@ $namespace: vben;
&:not(.is-active):hover { &:not(.is-active):hover {
color: hsl(var(--primary-foreground)); color: hsl(var(--primary-foreground));
background-color: hsl(var(--menu-dark-background));
} }
&.is-active { &.is-active {
background-color: hsl(var(--menu-dark-background));
.#{$namespace}-normal-menu__name, .#{$namespace}-normal-menu__name,
.#{$namespace}-normal-menu__icon { .#{$namespace}-normal-menu__icon {
color: hsl(var(--primary-foreground)); color: hsl(var(--primary-foreground));
@ -129,7 +117,7 @@ $namespace: vben;
border-color 0.15s ease; border-color 0.15s ease;
&.is-active { &.is-active {
@apply text-primary bg-primary/20; @apply text-primary bg-primary/15 dark:bg-accent;
.#{$namespace}-normal-menu__name, .#{$namespace}-normal-menu__name,
.#{$namespace}-normal-menu__icon { .#{$namespace}-normal-menu__icon {
@ -138,14 +126,12 @@ $namespace: vben;
} }
&:not(.is-active):hover { &:not(.is-active):hover {
@apply text-foreground; @apply dark:bg-accent text-primary bg-heavy dark:text-foreground;
background-color: hsl(var(--menu-dark-background));
} }
&:hover { &:hover {
.#{$namespace}-normal-menu__icon { .#{$namespace}-normal-menu__icon {
transform: scale(1.3); transform: scale(1.2);
} }
} }
} }

View File

@ -35,7 +35,7 @@ function handleClick(path?: string) {
<VbenIcon <VbenIcon
v-if="item.icon && showIcon" v-if="item.icon && showIcon"
:icon="item.icon" :icon="item.icon"
class="mr-1 size-5 flex-shrink-0" class="mr-1 size-4 flex-shrink-0"
/> />
<span <span
:class="{ :class="{

View File

@ -1,15 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue';
interface Props { interface Props {
/**
* Logo 图标 alt
*/
alt?: string;
/** /**
* 是否收起文本 * 是否收起文本
*/ */
collapse?: boolean; collapsed?: boolean;
/** /**
* Logo 跳转地址 * Logo 跳转地址
*/ */
@ -25,7 +19,7 @@ interface Props {
/** /**
* Logo 文本 * Logo 文本
*/ */
text?: string; text: string;
/** /**
* Logo 主题 * Logo 主题
*/ */
@ -33,39 +27,34 @@ interface Props {
} }
defineOptions({ defineOptions({
name: 'Logo', name: 'VbenLogo',
}); });
const props = withDefaults(defineProps<Props>(), { withDefaults(defineProps<Props>(), {
alt: 'Vben', collapsed: false,
collapse: false,
href: 'javascript:void 0', href: 'javascript:void 0',
logoSize: 36, logoSize: 32,
src: '', src: '',
text: '',
theme: 'light', theme: 'light',
}); });
const logoClass = computed(() => {
return [props.theme, props.collapse ? 'collapsed' : ''];
});
</script> </script>
<template> <template>
<div :class="logoClass" class="flex h-full items-center text-lg"> <div :class="theme" class="flex h-full items-center text-lg">
<a <a
:class="$attrs.class" :class="$attrs.class"
:href="href" :href="href"
class="flex h-full items-center gap-2 overflow-hidden px-3 font-semibold leading-normal transition-all duration-500" class="flex h-full items-center gap-2 overflow-hidden px-3 text-lg font-semibold leading-normal transition-all duration-500"
> >
<img <img
v-if="src" v-if="src"
:alt="alt" :alt="text"
:src="src" :src="src"
:width="logoSize" :width="logoSize"
class="relative rounded-none bg-transparent" class="relative rounded-none bg-transparent"
/> />
<span <span
v-if="!collapse" v-if="!collapsed"
class="text-primary dark:text-foreground truncate text-nowrap" class="text-primary dark:text-foreground truncate text-nowrap"
> >
{{ text }} {{ text }}

View File

@ -49,7 +49,7 @@ const badgeStyle = computed(() => {
v-else v-else
:class="badgeClass" :class="badgeClass"
:style="badgeStyle" :style="badgeStyle"
class="rounded-md px-1.5 py-0.5 text-xs" class="text-primary-foreground rounded-xl px-1.5 py-0.5 text-xs"
> >
{{ badge }} {{ badge }}
</div> </div>

View File

@ -12,12 +12,14 @@ interface Props {
class?: HTMLAttributes['class']; class?: HTMLAttributes['class'];
horizontal?: boolean; horizontal?: boolean;
shadow?: boolean; shadow?: boolean;
shadowBorder?: boolean;
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
class: '', class: '',
horizontal: false, horizontal: false,
shadow: false, shadow: false,
shadowBorder: false,
}); });
const isAtTop = ref(true); const isAtTop = ref(true);
@ -42,7 +44,8 @@ function handleScroll(event: Event) {
<div <div
v-if="shadow" v-if="shadow"
:class="{ :class="{
'border-border border-t opacity-100': !isAtTop, 'opacity-100': !isAtTop,
'border-border border-t': shadowBorder && !isAtTop,
}" }"
class="scrollbar-top-shadow pointer-events-none absolute top-0 z-10 h-12 w-full opacity-0 transition-opacity duration-300 ease-in-out will-change-[opacity]" class="scrollbar-top-shadow pointer-events-none absolute top-0 z-10 h-12 w-full opacity-0 transition-opacity duration-300 ease-in-out will-change-[opacity]"
></div> ></div>
@ -50,7 +53,8 @@ function handleScroll(event: Event) {
<div <div
v-if="shadow" v-if="shadow"
:class="{ :class="{
'border-border border-b opacity-100': !isAtTop && !isAtBottom, 'opacity-100': !isAtTop && !isAtBottom,
'border-border border-t': shadowBorder && !isAtTop && !isAtBottom,
}" }"
class="scrollbar-bottom-shadow pointer-events-none absolute bottom-0 z-10 h-12 w-full opacity-0 transition-opacity duration-300 ease-in-out will-change-[opacity]" class="scrollbar-bottom-shadow pointer-events-none absolute bottom-0 z-10 h-12 w-full opacity-0 transition-opacity duration-300 ease-in-out will-change-[opacity]"
></div> ></div>

View File

@ -58,14 +58,6 @@ watch(active, () => {
scrollIntoView(); scrollIntoView();
}); });
function handleClose(key: string) {
emit('close', key);
}
function handleUnpinTab(tab: TabConfig) {
emit('unpin', tab);
}
function scrollIntoView() { function scrollIntoView() {
setTimeout(() => { setTimeout(() => {
const element = document.querySelector(`.tabs-chrome__item.is-active`); const element = document.querySelector(`.tabs-chrome__item.is-active`);
@ -142,21 +134,18 @@ function scrollIntoView() {
<div <div
class="tabs-chrome__extra absolute right-[calc(var(--gap)*1.5)] top-1/2 z-[3] size-4 translate-y-[-50%]" class="tabs-chrome__extra absolute right-[calc(var(--gap)*1.5)] top-1/2 z-[3] size-4 translate-y-[-50%]"
> >
<!-- <div
class="tabs-chrome__extra absolute right-[calc(var(--gap)*2)] top-1/2 z-[3] size-4 translate-y-[-50%] opacity-0 transition-opacity group-hover:opacity-100"
> -->
<!-- close-icon --> <!-- close-icon -->
<X <X
v-show=" v-show="
!tab.affixTab && tabsView.length > 1 && tab.closable !tab.affixTab && tabsView.length > 1 && tab.closable
" "
class="hover:bg-accent stroke-accent-foreground/80 hover:stroke-accent-foreground dark:group-[.is-active]:text-accent-foreground group-[.is-active]:text-primary mt-[2px] size-3 cursor-pointer rounded-full transition-all" class="hover:bg-accent stroke-accent-foreground/80 hover:stroke-accent-foreground dark:group-[.is-active]:text-accent-foreground group-[.is-active]:text-primary mt-[2px] size-3 cursor-pointer rounded-full transition-all"
@click.stop="handleClose(tab.key)" @click.stop="() => emit('close', tab.key)"
/> />
<MdiPin <MdiPin
v-show="tab.affixTab && tabsView.length > 1 && tab.closable" v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
class="hover:bg-accent hover:stroke-accent-foreground group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground mt-[2px] size-3.5 cursor-pointer rounded-full transition-all" class="hover:bg-accent hover:stroke-accent-foreground group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground mt-[2px] size-3.5 cursor-pointer rounded-full transition-all"
@click.stop="handleUnpinTab(tab)" @click.stop="() => emit('unpin', tab)"
/> />
</div> </div>

View File

@ -59,14 +59,6 @@ watch(active, () => {
scrollIntoView(); scrollIntoView();
}); });
function handleClose(key: string) {
emit('close', key);
}
function handleUnpinTab(tab: TabConfig) {
emit('unpin', tab);
}
function scrollIntoView() { function scrollIntoView() {
setTimeout(() => { setTimeout(() => {
const element = document.querySelector(`.tabs-chrome__item.is-active`); const element = document.querySelector(`.tabs-chrome__item.is-active`);
@ -120,12 +112,12 @@ function scrollIntoView() {
!tab.affixTab && tabsView.length > 1 && tab.closable !tab.affixTab && tabsView.length > 1 && tab.closable
" "
class="hover:bg-accent stroke-accent-foreground/80 hover:stroke-accent-foreground dark:group-[.is-active]:text-accent-foreground group-[.is-active]:text-primary size-3 cursor-pointer rounded-full transition-all" class="hover:bg-accent stroke-accent-foreground/80 hover:stroke-accent-foreground dark:group-[.is-active]:text-accent-foreground group-[.is-active]:text-primary size-3 cursor-pointer rounded-full transition-all"
@click.stop="handleClose(tab.key)" @click.stop="() => emit('close', tab.key)"
/> />
<MdiPin <MdiPin
v-show="tab.affixTab && tabsView.length > 1 && tab.closable" v-show="tab.affixTab && tabsView.length > 1 && tab.closable"
class="hover:bg-accent hover:stroke-accent-foreground group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground mt-[2px] size-3.5 cursor-pointer rounded-full transition-all" class="hover:bg-accent hover:stroke-accent-foreground group-[.is-active]:text-primary dark:group-[.is-active]:text-accent-foreground mt-[2px] size-3.5 cursor-pointer rounded-full transition-all"
@click.stop="handleUnpinTab(tab)" @click.stop="() => emit('unpin', tab)"
/> />
</div> </div>

View File

@ -11,7 +11,7 @@ defineOptions({
<template> <template>
<div <div
class="flex-col-center bg-background-content relative px-6 py-10 lg:flex-initial lg:px-8" class="flex-col-center bg-background-deep relative px-6 py-10 lg:flex-initial lg:px-8"
> >
<!-- Toolbar Slot --> <!-- Toolbar Slot -->
<slot name="toolbar"> <slot name="toolbar">

View File

@ -30,8 +30,15 @@ defineOptions({ name: 'BasicLayout' });
const emit = defineEmits<{ clearPreferencesAndLogout: [] }>(); const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
const { isDark, isHeaderNav, isMixedNav, isMobile, isSideMixedNav, layout } = const {
usePreferences(); isDark,
isHeaderNav,
isMixedNav,
isMobile,
isSideMixedNav,
layout,
sidebarCollapsed,
} = usePreferences();
const headerMenuTheme = computed(() => { const headerMenuTheme = computed(() => {
return isDark.value ? 'dark' : 'light'; return isDark.value ? 'dark' : 'light';
@ -43,38 +50,45 @@ const theme = computed(() => {
}); });
const logoClass = computed(() => { const logoClass = computed(() => {
let cls = ''; const { collapsedShowTitle } = preferences.sidebar;
const { collapsed, collapsedShowTitle } = preferences.sidebar; const classes: string[] = [];
if (collapsedShowTitle && collapsed && !isMixedNav.value) {
cls += ' mx-auto'; if (collapsedShowTitle && sidebarCollapsed.value && !isMixedNav.value) {
classes.push('mx-auto');
} }
if (isSideMixedNav.value) { if (isSideMixedNav.value) {
cls += ' flex-center'; classes.push('flex-center');
} }
return cls;
return classes.join(' ');
}); });
const isMenuRounded = computed(() => { const isMenuRounded = computed(() => {
return preferences.navigation.styleType === 'rounded'; return preferences.navigation.styleType === 'rounded';
}); });
const logoCollapse = computed(() => { const logoCollapsed = computed(() => {
if (isHeaderNav.value || isMixedNav.value) { const shouldCollapse = isHeaderNav.value || isMixedNav.value;
if (shouldCollapse) {
return false; return false;
} }
const { collapsed } = preferences.sidebar; const shouldExpandOnMobile = !sidebarCollapsed.value && isMobile.value;
if (!collapsed && isMobile) { if (shouldExpandOnMobile) {
return false; return false;
} }
return collapsed || isSideMixedNav.value;
return sidebarCollapsed.value || isSideMixedNav.value;
}); });
const showHeaderNav = computed(() => { const showHeaderNav = computed(() => {
return isHeaderNav.value || isMixedNav.value; return isHeaderNav.value || isMixedNav.value;
}); });
//
const { const {
extraActiveMenu, extraActiveMenu,
extraMenus, extraMenus,
@ -89,9 +103,9 @@ const {
handleMenuSelect, handleMenuSelect,
headerActive, headerActive,
headerMenus, headerMenus,
sideActive, sidebarActive,
sideMenus, sidebarMenus,
sideVisible, sidebarVisible,
} = useMixedMenu(); } = useMixedMenu();
function wrapperMenus(menus: MenuRecordRaw[]) { function wrapperMenus(menus: MenuRecordRaw[]) {
@ -127,7 +141,7 @@ function clearPreferencesAndLogout() {
:layout="layout" :layout="layout"
:sidebar-collapse="preferences.sidebar.collapsed" :sidebar-collapse="preferences.sidebar.collapsed"
:sidebar-collapse-show-title="preferences.sidebar.collapsedShowTitle" :sidebar-collapse-show-title="preferences.sidebar.collapsedShowTitle"
:sidebar-enable="sideVisible" :sidebar-enable="sidebarVisible"
:sidebar-expand-on-hover="preferences.sidebar.expandOnHover" :sidebar-expand-on-hover="preferences.sidebar.expandOnHover"
:sidebar-extra-collapse="preferences.sidebar.extraCollapse" :sidebar-extra-collapse="preferences.sidebar.extraCollapse"
:sidebar-hidden="preferences.sidebar.hidden" :sidebar-hidden="preferences.sidebar.hidden"
@ -168,9 +182,9 @@ function clearPreferencesAndLogout() {
<!-- logo --> <!-- logo -->
<template #logo> <template #logo>
<VbenLogo <VbenLogo
:alt="preferences.app.name" v-if="preferences.logo.enable"
:class="logoClass" :class="logoClass"
:collapse="logoCollapse" :collapsed="logoCollapsed"
:src="preferences.logo.source" :src="preferences.logo.source"
:text="preferences.app.name" :text="preferences.app.name"
:theme="showHeaderNav ? headerMenuTheme : theme" :theme="showHeaderNav ? headerMenuTheme : theme"
@ -215,8 +229,8 @@ function clearPreferencesAndLogout() {
:accordion="preferences.navigation.accordion" :accordion="preferences.navigation.accordion"
:collapse="preferences.sidebar.collapsed" :collapse="preferences.sidebar.collapsed"
:collapse-show-title="preferences.sidebar.collapsedShowTitle" :collapse-show-title="preferences.sidebar.collapsedShowTitle"
:default-active="sideActive" :default-active="sidebarActive"
:menus="wrapperMenus(sideMenus)" :menus="wrapperMenus(sidebarMenus)"
:rounded="isMenuRounded" :rounded="isMenuRounded"
:theme="theme" :theme="theme"
mode="vertical" mode="vertical"
@ -224,9 +238,9 @@ function clearPreferencesAndLogout() {
/> />
</template> </template>
<template #mixed-menu> <template #mixed-menu>
<!-- :collapse="!preferences.sidebar.collapsedShowTitle" -->
<LayoutMixedMenu <LayoutMixedMenu
:active-path="extraActiveMenu" :active-path="extraActiveMenu"
:collapse="!preferences.sidebar.collapsedShowTitle"
:menus="wrapperMenus(headerMenus)" :menus="wrapperMenus(headerMenus)"
:rounded="isMenuRounded" :rounded="isMenuRounded"
:theme="theme" :theme="theme"
@ -248,7 +262,6 @@ function clearPreferencesAndLogout() {
<template #side-extra-title> <template #side-extra-title>
<VbenLogo <VbenLogo
v-if="preferences.logo.enable" v-if="preferences.logo.enable"
:alt="preferences.app.name"
:text="preferences.app.name" :text="preferences.app.name"
:theme="theme" :theme="theme"
/> />
@ -266,6 +279,7 @@ function clearPreferencesAndLogout() {
<template #content> <template #content>
<LayoutContent /> <LayoutContent />
</template> </template>
<!-- 页脚 --> <!-- 页脚 -->
<template v-if="preferences.footer.enable" #footer> <template v-if="preferences.footer.enable" #footer>
<LayoutFooter> <LayoutFooter>

View File

@ -20,10 +20,6 @@ const emit = defineEmits<{
const route = useRoute(); const route = useRoute();
function handleSelect(menu: MenuRecordRaw) {
emit('select', menu);
}
onBeforeMount(() => { onBeforeMount(() => {
const menu = findMenuByPath(props.menus || [], route.path); const menu = findMenuByPath(props.menus || [], route.path);
if (menu) { if (menu) {
@ -43,6 +39,6 @@ onBeforeMount(() => {
:rounded="rounded" :rounded="rounded"
:theme="theme" :theme="theme"
@enter="(menu) => emit('enter', menu)" @enter="(menu) => emit('enter', menu)"
@select="handleSelect" @select="(menu) => emit('select', menu)"
/> />
</template> </template>

View File

@ -1,6 +1,6 @@
import type { MenuRecordRaw } from '@vben-core/typings'; import type { MenuRecordRaw } from '@vben-core/typings';
import { computed, ref } from 'vue'; import { computed, ref, watch } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { findRootMenuByPath } from '@vben-core/helpers'; import { findRootMenuByPath } from '@vben-core/helpers';
@ -78,6 +78,18 @@ function useExtraMenu() {
} }
}; };
watch(
() => route.path,
() => {
const { findMenu, rootMenu, rootMenuPath } = findRootMenuByPath(
menus.value,
route.path,
);
extraActiveMenu.value = rootMenuPath ?? findMenu?.path ?? '';
extraMenus.value = rootMenu?.children ?? [];
},
);
return { return {
extraActiveMenu, extraActiveMenu,
extraMenus, extraMenus,

View File

@ -1,6 +1,6 @@
import type { MenuRecordRaw } from '@vben-core/typings'; import type { MenuRecordRaw } from '@vben-core/typings';
import { computed, onBeforeMount, ref } from 'vue'; import { computed, onBeforeMount, ref, watch } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { findRootMenuByPath } from '@vben-core/helpers'; import { findRootMenuByPath } from '@vben-core/helpers';
@ -10,8 +10,8 @@ import { useCoreAccessStore } from '@vben-core/stores';
import { useNavigation } from './use-navigation'; import { useNavigation } from './use-navigation';
function useMixedMenu() { function useMixedMenu() {
const accessStore = useCoreAccessStore();
const { navigation } = useNavigation(); const { navigation } = useNavigation();
const accessStore = useCoreAccessStore();
const route = useRoute(); const route = useRoute();
const splitSideMenus = ref<MenuRecordRaw[]>([]); const splitSideMenus = ref<MenuRecordRaw[]>([]);
const rootMenuPath = ref<string>(''); const rootMenuPath = ref<string>('');
@ -22,7 +22,7 @@ function useMixedMenu() {
() => preferences.navigation.split && isMixedNav.value, () => preferences.navigation.split && isMixedNav.value,
); );
const sideVisible = computed(() => { const sidebarVisible = computed(() => {
const enableSidebar = preferences.sidebar.enable; const enableSidebar = preferences.sidebar.enable;
if (needSplit.value) { if (needSplit.value) {
return enableSidebar && splitSideMenus.value.length > 0; return enableSidebar && splitSideMenus.value.length > 0;
@ -49,14 +49,14 @@ function useMixedMenu() {
/** /**
* *
*/ */
const sideMenus = computed(() => { const sidebarMenus = computed(() => {
return needSplit.value ? splitSideMenus.value : menus.value; return needSplit.value ? splitSideMenus.value : menus.value;
}); });
/** /**
* *
*/ */
const sideActive = computed(() => { const sidebarActive = computed(() => {
return route.path; return route.path;
}); });
@ -102,6 +102,13 @@ function useMixedMenu() {
splitSideMenus.value = rootMenu?.children ?? []; splitSideMenus.value = rootMenu?.children ?? [];
} }
watch(
() => route.path,
(path: string) => {
calcSideMenus(path);
},
);
// 初始化计算侧边菜单 // 初始化计算侧边菜单
onBeforeMount(() => { onBeforeMount(() => {
calcSideMenus(); calcSideMenus();
@ -111,9 +118,9 @@ function useMixedMenu() {
handleMenuSelect, handleMenuSelect,
headerActive, headerActive,
headerMenus, headerMenus,
sideActive, sidebarActive,
sideMenus, sidebarMenus,
sideVisible, sidebarVisible,
}; };
} }

View File

@ -67,8 +67,8 @@ importers:
specifier: ^8.11.0 specifier: ^8.11.0
version: 8.11.0 version: 8.11.0
husky: husky:
specifier: ^9.1.0 specifier: ^9.1.1
version: 9.1.0 version: 9.1.1
is-ci: is-ci:
specifier: ^3.0.1 specifier: ^3.0.1
version: 3.0.1 version: 3.0.1
@ -236,8 +236,8 @@ importers:
specifier: ^4.2.3 specifier: ^4.2.3
version: 4.2.3(vue@3.4.32(typescript@5.5.3)) version: 4.2.3(vue@3.4.32(typescript@5.5.3))
dayjs: dayjs:
specifier: ^1.11.11 specifier: ^1.11.12
version: 1.11.11 version: 1.11.12
pinia: pinia:
specifier: 2.1.7 specifier: 2.1.7
version: 2.1.7(typescript@5.5.3)(vue@3.4.32(typescript@5.5.3)) version: 2.1.7(typescript@5.5.3)(vue@3.4.32(typescript@5.5.3))
@ -418,8 +418,8 @@ importers:
specifier: ^3.2.3 specifier: ^3.2.3
version: 3.2.3 version: 3.2.3
dayjs: dayjs:
specifier: ^1.11.11 specifier: ^1.11.12
version: 1.11.11 version: 1.11.12
find-up: find-up:
specifier: ^7.0.0 specifier: ^7.0.0
version: 7.0.0 version: 7.0.0
@ -442,8 +442,8 @@ importers:
internal/tailwind-config: internal/tailwind-config:
dependencies: dependencies:
'@iconify/json': '@iconify/json':
specifier: ^2.2.228 specifier: ^2.2.229
version: 2.2.228 version: 2.2.229
'@iconify/tailwind': '@iconify/tailwind':
specifier: ^1.1.1 specifier: ^1.1.1
version: 1.1.1 version: 1.1.1
@ -537,8 +537,8 @@ importers:
specifier: ^4.0.0 specifier: ^4.0.0
version: 4.0.0(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)(terser@5.31.3))(vue@3.4.32(typescript@5.5.3)) version: 4.0.0(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)(terser@5.31.3))(vue@3.4.32(typescript@5.5.3))
dayjs: dayjs:
specifier: ^1.11.11 specifier: ^1.11.12
version: 1.11.11 version: 1.11.12
dotenv: dotenv:
specifier: ^16.4.5 specifier: ^16.4.5
version: 16.4.5 version: 16.4.5
@ -2944,8 +2944,8 @@ packages:
resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
deprecated: Use @eslint/object-schema instead deprecated: Use @eslint/object-schema instead
'@iconify/json@2.2.228': '@iconify/json@2.2.229':
resolution: {integrity: sha512-Xd1CgQ1bCFLrp4t+J2TU+AXM+kVHAFLfhK9FcZD54aMPlzENdQMJ5JhfFzCgnsLBAIW1PbSY2Edbm4tt2Tw9Lg==} resolution: {integrity: sha512-DD1k97sjm87n+C15Ey1dVX1cBKCawfms6N0d+1vvAon5P3yurpPEO9OyU88f53+9Chpo+CuIp3+TihvsghlfQQ==}
'@iconify/tailwind@1.1.1': '@iconify/tailwind@1.1.1':
resolution: {integrity: sha512-4mmA//qjZigv7D4KlqcVSYTqfRIJzyts2/lSCAJfCL0rVMIE76+ifJnaE5jxCo1+nYGBF8FsFo0qFOs+sX4EnA==} resolution: {integrity: sha512-4mmA//qjZigv7D4KlqcVSYTqfRIJzyts2/lSCAJfCL0rVMIE76+ifJnaE5jxCo1+nYGBF8FsFo0qFOs+sX4EnA==}
@ -4794,8 +4794,8 @@ packages:
dataloader@1.4.0: dataloader@1.4.0:
resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==}
dayjs@1.11.11: dayjs@1.11.12:
resolution: {integrity: sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==} resolution: {integrity: sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==}
de-indent@1.0.2: de-indent@1.0.2:
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
@ -5847,8 +5847,8 @@ packages:
humanize-ms@1.2.1: humanize-ms@1.2.1:
resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
husky@9.1.0: husky@9.1.1:
resolution: {integrity: sha512-8XCjbomYTGdNF2h50dio3T3zghmZ9f/ZNzr99YwSkvDdhEjJGs5qzy8tbFx+SG8yCx2wn9nMVfZxVrr/yT8gNQ==} resolution: {integrity: sha512-fCqlqLXcBnXa/TJXmT93/A36tJsjdJkibQ1MuIiFyCCYUlpYpIaj2mv1w+3KR6Rzu1IC3slFTje5f6DUp2A2rg==}
engines: {node: '>=18'} engines: {node: '>=18'}
hasBin: true hasBin: true
@ -11402,7 +11402,7 @@ snapshots:
'@humanwhocodes/object-schema@2.0.3': {} '@humanwhocodes/object-schema@2.0.3': {}
'@iconify/json@2.2.228': '@iconify/json@2.2.229':
dependencies: dependencies:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
pathe: 1.1.2 pathe: 1.1.2
@ -12772,7 +12772,7 @@ snapshots:
array-tree-filter: 2.1.0 array-tree-filter: 2.1.0
async-validator: 4.2.5 async-validator: 4.2.5
csstype: 3.1.3 csstype: 3.1.3
dayjs: 1.11.11 dayjs: 1.11.12
dom-align: 1.12.4 dom-align: 1.12.4
dom-scroll-into-view: 2.0.1 dom-scroll-into-view: 2.0.1
lodash: 4.17.21 lodash: 4.17.21
@ -13652,7 +13652,7 @@ snapshots:
dataloader@1.4.0: {} dataloader@1.4.0: {}
dayjs@1.11.11: {} dayjs@1.11.12: {}
de-indent@1.0.2: {} de-indent@1.0.2: {}
@ -14948,7 +14948,7 @@ snapshots:
dependencies: dependencies:
ms: 2.1.3 ms: 2.1.3
husky@9.1.0: {} husky@9.1.1: {}
iconv-lite@0.4.24: iconv-lite@0.4.24:
dependencies: dependencies: