feat: support custom background colors for sidebar and header (#4151)

* feat: support custom background colors for sidebar and header

* fix: type error
This commit is contained in:
Vben 2024-08-14 21:47:37 +08:00 committed by GitHub
parent 83fcdec37c
commit 8f40d5107c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 148 additions and 47 deletions

View File

@ -2,7 +2,7 @@
## Description
Vben Admin 数据 mock 服务,没有对接任何的数据库,所有数据都是模拟的,用于前端开发时提供数据支持。由于 sqlite 安装需要在本地进行编译,所以这里接口是直接返回的。线上环境不再提供mock集成可自行部署服务或者对接真实数据同步 mock.js等工具有一些限制比如上传文件不行、无法模拟复杂的逻辑等所以这里使用了 真是的后端服务来实现。唯一麻烦的是本地需要同时启动后端服务和前端服务,但是这样可以更好的模拟真实环境。
Vben Admin 数据 mock 服务没有对接任何的数据库所有数据都是模拟的用于前端开发时提供数据支持。线上环境不再提供mock集成可自行部署服务或者对接真实数据mock.js 等工具有一些限制,比如上传文件不行、无法模拟复杂的逻辑等,所以这里使用了真实的后端服务来实现。唯一麻烦的是本地需要同时启动后端服务和前端服务,但是这样可以更好的模拟真实环境。
## Running the app

View File

@ -260,6 +260,7 @@ const defaultPreferences: Preferences = {
colorWarning: 'hsl(42 84% 61%)',
mode: 'dark',
radius: '0.5',
semiDarkHeader: false,
semiDarkMenu: true,
},
transition: {
@ -452,6 +453,8 @@ interface ThemePreferences {
mode: ThemeModeType;
/** 圆角 */
radius: string;
/** 是否开启半深色header只在theme='light'时生效) */
semiDarkHeader: boolean;
/** 是否开启半深色菜单只在theme='light'时生效) */
semiDarkMenu: boolean;
}

View File

@ -107,9 +107,6 @@ css 变量内的颜色,必须使用 `hsl` 格式,如 `0 0% 100%`,不需要
/* 基本文字大小 */
--font-size-base: 16px;
/* 主体内容背景色 */
--content: 240 11% 96%;
/* =============component & UI============= */
/* menu */
@ -117,6 +114,9 @@ css 变量内的颜色,必须使用 `hsl` 格式,如 `0 0% 100%`,不需要
--sidebar-deep: 210 11.11% 96.47%;
--menu: var(--sidebar);
/* header */
--header: 0 0% 100%;
accent-color: var(--primary);
color-scheme: light;
}
@ -206,14 +206,12 @@ css 变量内的颜色,必须使用 `hsl` 格式,如 `0 0% 100%`,不需要
/* 基本文字大小 */
--font-size-base: 16px;
/* 主体内容背景色 */
--content: 240 11% 96%;
/* =============component & UI============= */
--sidebar: 222.34deg 10.43% 12.27%;
--sidebar-deep: 220deg 13.06% 9%;
--menu: var(--sidebar);
--header: 222.34deg 10.43% 12.27%;
color-scheme: dark;
}
@ -228,7 +226,6 @@ css 变量内的颜色,必须使用 `hsl` 格式,如 `0 0% 100%`,不需要
### 默认主题下:
```css
/* */
:root {
/* Background color for <Card /> */
--card: 0 0% 30%;
@ -238,7 +235,6 @@ css 变量内的颜色,必须使用 `hsl` 格式,如 `0 0% 100%`,不需要
### 黑暗模式下
```css
/* */
.dark,
.dark[data-theme='custom'],
.dark[data-theme='default'] {
@ -335,7 +331,7 @@ type BuiltinThemeType =
/* 主体区域背景色 */
--background-deep: 210 11.11% 96.47%;
--foreground: 210 6% 21%;
--foreground: 222 84% 5%;
/* Background color for <Card /> */
--card: 0 0% 100%;
@ -346,8 +342,12 @@ type BuiltinThemeType =
--popover-foreground: 222.2 84% 4.9%;
/* Muted backgrounds such as <TabsList />, <Skeleton /> and <Switch /> */
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
/* --muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%; */
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
/* 主题颜色 */
@ -405,16 +405,16 @@ type BuiltinThemeType =
/* 基本文字大小 */
--font-size-base: 16px;
/* 主体内容背景色 */
--content: 240 11% 96%;
/* =============component & UI============= */
/* menu */
--sidebar: 0 0% 100%;
--sidebar-deep: 210 11.11% 96.47%;
--sidebar-deep: 0 0% 100%;
--menu: var(--sidebar);
/* header */
--header: 0 0% 100%;
accent-color: var(--primary);
color-scheme: light;
}
@ -719,8 +719,13 @@ type BuiltinThemeType =
--popover-foreground: 210 40% 98%;
/* Muted backgrounds such as <TabsList />, <Skeleton /> and <Switch /> */
--muted: 220deg 6.82% 17.25%;
--muted-foreground: 215 20.2% 65.1%;
/* --muted: 220deg 6.82% 17.25%; */
/* --muted-foreground: 215 20.2% 65.1%; */
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
/* 主题颜色 */
@ -747,16 +752,16 @@ type BuiltinThemeType =
--secondary-foreground: 0 0% 98%;
/* Used for accents such as hover effects on <DropdownMenuItem>, <SelectItem>...etc */
--accent: 0deg 0% 100% / 8%;
--accent-hover: 0deg 0% 100% / 12%;
--accent: 216 5% 19%;
--accent-hover: 216 5% 24%;
--accent-foreground: 0 0% 98%;
/* Darker color */
--heavy: 0deg 0% 100% / 12%;
--heavy: 216 5% 24%;
--heavy-foreground: var(--accent-foreground);
/* Default border color */
--border: 240 3.7% 15.9%;
--border: 240 3.7% 22%;
/* Border color for inputs such as <Input />, <Select />, <Textarea /> */
--input: 0deg 0% 100% / 10%;
@ -777,15 +782,15 @@ type BuiltinThemeType =
/* 基本文字大小 */
--font-size-base: 16px;
/* 主体内容背景色 */
--content: 240 11% 96%;
/* =============component & UI============= */
--sidebar: 222.34deg 10.43% 12.27%;
--sidebar-deep: 220deg 13.06% 9%;
--menu: var(--sidebar);
/* header */
--header: 222.34deg 10.43% 12.27%;
color-scheme: dark;
}
@ -812,6 +817,7 @@ type BuiltinThemeType =
--ring: 263.4 70% 50.4%;
--sidebar: 224 71.4% 4.1%;
--sidebar-deep: 224 71.4% 4.1%;
--header: 224 71.4% 4.1%;
}
.dark[data-theme='pink'],
@ -837,6 +843,7 @@ type BuiltinThemeType =
--ring: 346.8 77.2% 49.8%;
--sidebar: 20 14.3% 4.1%;
--sidebar-deep: 20 14.3% 4.1%;
--header: 20 14.3% 4.1%;
}
.dark[data-theme='rose'],
@ -862,6 +869,7 @@ type BuiltinThemeType =
--ring: 0 72.2% 50.6%;
--sidebar: 0 0% 3.9%;
--sidebar-deep: 0 0% 3.9%;
--header: 0 0% 3.9%;
}
.dark[data-theme='sky-blue'],
@ -887,6 +895,7 @@ type BuiltinThemeType =
--ring: 224.3 76.3% 48%;
--sidebar: 222.2 84% 4.9%;
--sidebar-deep: 222.2 84% 4.9%;
--header: 222.2 84% 4.9%;
}
.dark[data-theme='deep-blue'],
@ -912,6 +921,7 @@ type BuiltinThemeType =
--ring: 224.3 76.3% 48%;
--sidebar: 222.2 84% 4.9%;
--sidebar-deep: 222.2 84% 4.9%;
--header: 222.2 84% 4.9%;
}
.dark[data-theme='green'],
@ -937,6 +947,7 @@ type BuiltinThemeType =
--ring: 142.4 71.8% 29.2%;
--sidebar: 20 14.3% 4.1%;
--sidebar-deep: 20 14.3% 4.1%;
--header: 20 14.3% 4.1%;
}
.dark[data-theme='deep-green'],
@ -962,6 +973,7 @@ type BuiltinThemeType =
--ring: 142.4 71.8% 29.2%;
--sidebar: 20 14.3% 4.1%;
--sidebar-deep: 20 14.3% 4.1%;
--header: 20 14.3% 4.1%;
}
.dark[data-theme='orange'],
@ -987,6 +999,7 @@ type BuiltinThemeType =
--ring: 20.5 90.2% 48.2%;
--sidebar: 20 14.3% 4.1%;
--sidebar-deep: 20 14.3% 4.1%;
--header: 20 14.3% 4.1%;
}
.dark[data-theme='yellow'],
@ -1012,6 +1025,7 @@ type BuiltinThemeType =
--ring: 35.5 91.7% 32.9%;
--sidebar: 20 14.3% 4.1%;
--sidebar-deep: 20 14.3% 4.1%;
--header: 20 14.3% 4.1%;
}
.dark[data-theme='zinc'],
@ -1037,6 +1051,7 @@ type BuiltinThemeType =
--ring: 240 4.9% 83.9%;
--sidebar: 240 10% 3.9%;
--sidebar-deep: 240 10% 3.9%;
--header: 240 4.9% 83.9%;
}
.dark[data-theme='neutral'],
@ -1062,6 +1077,7 @@ type BuiltinThemeType =
--ring: 0 0% 83.1%;
--sidebar: 0 0% 3.9%;
--sidebar-deep: 0 0% 3.9%;
--header: 0 0% 3.9%;
}
.dark[data-theme='slate'],
@ -1087,6 +1103,7 @@ type BuiltinThemeType =
--ring: 212.7 26.8% 83.9;
--sidebar: 222.2 84% 4.9%;
--sidebar-deep: 222.2 84% 4.9%;
--header: 222.2 84% 4.9%;
}
.dark[data-theme='gray'],
@ -1112,6 +1129,7 @@ type BuiltinThemeType =
--ring: 216 12.2% 83.9%;
--sidebar: 224 71.4% 4.1%;
--sidebar-deep: 224 71.4% 4.1%;
--header: 224 71.4% 4.1%;
}
```
@ -1200,6 +1218,50 @@ export const overridesPreferences = defineOverridesPreferences({
});
```
## 自定义侧边栏颜色
侧边栏颜色通过`--sidebar`变量来配置
### 默认主题下:
```css
:root {
--sidebar: 0 0% 100%;
}
```
### 黑暗模式下
```css
.dark,
.dark[data-theme='custom'],
.dark[data-theme='default'] {
--sidebar: 222.34deg 10.43% 12.27%;
}
```
## 自定义顶栏颜色
侧边栏颜色通过`--header`变量来配置
### 默认主题下:
```css
:root {
--header: 0 0% 100%;
}
```
### 黑暗模式下
```css
.dark,
.dark[data-theme='custom'],
.dark[data-theme='default'] {
--header: 222.34deg 10.43% 12.27%;
}
```
## 色弱模式
一般用于特殊场景,将设置为色弱模式,你可以在`preferences.ts`中进行配置:

View File

@ -80,6 +80,9 @@ const customColors = {
...createColorsPalette('green'),
foreground: 'hsl(var(--success-foreground))',
},
header: {
DEFAULT: 'hsl(var(--header))',
},
heavy: {
DEFAULT: 'hsl(var(--heavy))',
foreground: 'hsl(var(--heavy-foreground))',

View File

@ -82,15 +82,15 @@
/* 基本文字大小 */
--font-size-base: 16px;
/* 主体内容背景色 */
--content: 240 11% 96%;
/* =============component & UI============= */
--sidebar: 222.34deg 10.43% 12.27%;
--sidebar-deep: 220deg 13.06% 9%;
--menu: var(--sidebar);
/* header */
--header: 222.34deg 10.43% 12.27%;
color-scheme: dark;
}
@ -117,6 +117,7 @@
--ring: 263.4 70% 50.4%;
--sidebar: 224 71.4% 4.1%;
--sidebar-deep: 224 71.4% 4.1%;
--header: 224 71.4% 4.1%;
}
.dark[data-theme='pink'],
@ -142,6 +143,7 @@
--ring: 346.8 77.2% 49.8%;
--sidebar: 20 14.3% 4.1%;
--sidebar-deep: 20 14.3% 4.1%;
--header: 20 14.3% 4.1%;
}
.dark[data-theme='rose'],
@ -167,6 +169,7 @@
--ring: 0 72.2% 50.6%;
--sidebar: 0 0% 3.9%;
--sidebar-deep: 0 0% 3.9%;
--header: 0 0% 3.9%;
}
.dark[data-theme='sky-blue'],
@ -192,6 +195,7 @@
--ring: 224.3 76.3% 48%;
--sidebar: 222.2 84% 4.9%;
--sidebar-deep: 222.2 84% 4.9%;
--header: 222.2 84% 4.9%;
}
.dark[data-theme='deep-blue'],
@ -217,6 +221,7 @@
--ring: 224.3 76.3% 48%;
--sidebar: 222.2 84% 4.9%;
--sidebar-deep: 222.2 84% 4.9%;
--header: 222.2 84% 4.9%;
}
.dark[data-theme='green'],
@ -242,6 +247,7 @@
--ring: 142.4 71.8% 29.2%;
--sidebar: 20 14.3% 4.1%;
--sidebar-deep: 20 14.3% 4.1%;
--header: 20 14.3% 4.1%;
}
.dark[data-theme='deep-green'],
@ -267,6 +273,7 @@
--ring: 142.4 71.8% 29.2%;
--sidebar: 20 14.3% 4.1%;
--sidebar-deep: 20 14.3% 4.1%;
--header: 20 14.3% 4.1%;
}
.dark[data-theme='orange'],
@ -292,6 +299,7 @@
--ring: 20.5 90.2% 48.2%;
--sidebar: 20 14.3% 4.1%;
--sidebar-deep: 20 14.3% 4.1%;
--header: 20 14.3% 4.1%;
}
.dark[data-theme='yellow'],
@ -317,6 +325,7 @@
--ring: 35.5 91.7% 32.9%;
--sidebar: 20 14.3% 4.1%;
--sidebar-deep: 20 14.3% 4.1%;
--header: 20 14.3% 4.1%;
}
.dark[data-theme='zinc'],
@ -342,6 +351,7 @@
--ring: 240 4.9% 83.9%;
--sidebar: 240 10% 3.9%;
--sidebar-deep: 240 10% 3.9%;
--header: 240 4.9% 83.9%;
}
.dark[data-theme='neutral'],
@ -367,6 +377,7 @@
--ring: 0 0% 83.1%;
--sidebar: 0 0% 3.9%;
--sidebar-deep: 0 0% 3.9%;
--header: 0 0% 3.9%;
}
.dark[data-theme='slate'],
@ -392,6 +403,7 @@
--ring: 212.7 26.8% 83.9;
--sidebar: 222.2 84% 4.9%;
--sidebar-deep: 222.2 84% 4.9%;
--header: 222.2 84% 4.9%;
}
.dark[data-theme='gray'],
@ -417,4 +429,5 @@
--ring: 216 12.2% 83.9%;
--sidebar: 224 71.4% 4.1%;
--sidebar-deep: 224 71.4% 4.1%;
--header: 224 71.4% 4.1%;
}

View File

@ -8,7 +8,7 @@
/* 主体区域背景色 */
--background-deep: 210 11.11% 96.47%;
--foreground: 222 84% 5%;
--foreground: 210 6% 21%;
/* Background color for <Card /> */
--card: 0 0% 100%;
@ -82,9 +82,6 @@
/* 基本文字大小 */
--font-size-base: 16px;
/* 主体内容背景色 */
--content: 240 11% 96%;
/* =============component & UI============= */
/* menu */
@ -92,6 +89,9 @@
--sidebar-deep: 0 0% 100%;
--menu: var(--sidebar);
/* header */
--header: 0 0% 100%;
accent-color: var(--primary);
color-scheme: light;
}

View File

@ -90,6 +90,7 @@ const defaultPreferences: Preferences = {
colorWarning: 'hsl(42 84% 61%)',
mode: 'dark',
radius: '0.5',
semiDarkHeader: false,
semiDarkMenu: true,
},
transition: {

View File

@ -183,6 +183,8 @@ interface ThemePreferences {
mode: ThemeModeType;
/** 圆角 */
radius: string;
/** 是否开启半深色header只在theme='light'时生效) */
semiDarkHeader: boolean;
/** 是否开启半深色菜单只在theme='light'时生效) */
semiDarkMenu: boolean;
}

View File

@ -36,6 +36,10 @@ interface Props {
* 侧边菜单宽度
*/
sidebarWidth: number;
/**
* 主题
*/
theme: string | undefined;
/**
* 宽度
*/
@ -76,8 +80,9 @@ function handleToggleMenu() {
<template>
<header
:class="theme"
:style="style"
class="border-border bg-background top-0 flex w-full flex-[0_0_auto] items-center border-b transition-[margin-top] duration-200"
class="border-border bg-header top-0 flex w-full flex-[0_0_auto] items-center border-b transition-[margin-top] duration-200"
>
<div v-if="slots.logo" :style="logoStyle">
<slot name="logo"></slot>

View File

@ -72,6 +72,10 @@ interface VbenLayoutProps {
* @default 'fixed'
*/
headerMode?: LayoutHeaderModeType;
/**
* header
*/
headerTheme?: ThemeModeType;
/**
* header切换侧边栏按钮
* @default
@ -127,7 +131,6 @@ interface VbenLayoutProps {
* @default 80
*/
sidebarMixedWidth?: number;
/**
*
* @default dark

View File

@ -485,6 +485,7 @@ function handleOpenMenu() {
:show="!isFullContent && !headerHidden"
:show-toggle-btn="showHeaderToggleButton"
:sidebar-width="sidebarWidth"
:theme="headerTheme"
:width="mainStyle.width"
:z-index="headerZIndex"
@open-menu="handleOpenMenu"

View File

@ -776,7 +776,7 @@ $namespace: vben;
&.is-active {
div[data-state='open'] > .#{$namespace}-sub-menu-content,
> .#{$namespace}-sub-menu-content {
font-weight: 500;
// font-weight: 500;
color: var(--menu-submenu-active-color);
text-decoration: none;
cursor: pointer;

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, type HTMLAttributes } from 'vue';
import { computed } from 'vue';
import { cn } from '@vben-core/shared';
@ -11,7 +11,7 @@ import {
} from 'radix-vue';
const props = withDefaults(
defineProps<{ class?: HTMLAttributes['class'] } & HoverCardContentProps>(),
defineProps<{ class?: any } & HoverCardContentProps>(),
{
sideOffset: 4,
},

View File

@ -109,7 +109,7 @@ function scrollIntoView() {
<!-- divider -->
<div
v-if="i !== 0 && tab.key !== active"
class="tabs-chrome__divider bg-foreground/80 absolute left-[var(--gap)] top-1/2 z-0 h-4 w-[1px] translate-y-[-50%] transition-all"
class="tabs-chrome__divider bg-foreground/60 absolute left-[var(--gap)] top-1/2 z-0 h-4 w-[1px] translate-y-[-50%] transition-all"
></div>
<!-- background -->
<div

View File

@ -47,15 +47,16 @@ const userStore = useUserStore();
const { updateWatermark } = useWatermark();
const lockStore = useLockStore();
const headerMenuTheme = computed(() => {
return isDark.value ? 'dark' : 'light';
});
const sidebarTheme = computed(() => {
const dark = isDark.value || preferences.theme.semiDarkMenu;
return dark ? 'dark' : 'light';
});
const headerTheme = computed(() => {
const dark = isDark.value || preferences.theme.semiDarkHeader;
return dark ? 'dark' : 'light';
});
const logoClass = computed(() => {
const { collapsedShowTitle } = preferences.sidebar;
const classes: string[] = [];
@ -161,6 +162,7 @@ const headerSlots = computed(() => {
:footer-fixed="preferences.footer.fixed"
:header-hidden="preferences.header.hidden"
:header-mode="preferences.header.mode"
:header-theme="headerTheme"
:header-toggle-sidebar-button="preferences.widget.sidebarToggle"
:header-visible="preferences.header.enable"
:is-mobile="preferences.app.isMobile"
@ -200,7 +202,7 @@ const headerSlots = computed(() => {
:collapsed="logoCollapsed"
:src="preferences.logo.source"
:text="preferences.app.name"
:theme="showHeaderNav ? headerMenuTheme : theme"
:theme="showHeaderNav ? headerTheme : theme"
/>
</template>
<!-- 头部区域 -->
@ -222,7 +224,7 @@ const headerSlots = computed(() => {
:default-active="headerActive"
:menus="wrapperMenus(headerMenus)"
:rounded="isMenuRounded"
:theme="headerMenuTheme"
:theme="headerTheme"
class="w-full"
mode="horizontal"
@select="handleMenuSelect"

View File

@ -13,9 +13,8 @@ defineOptions({
});
const modelValue = defineModel<string>({ default: 'auto' });
const themeSemiDarkMenu = defineModel<boolean>('themeSemiDarkMenu', {
default: true,
});
const themeSemiDarkMenu = defineModel<boolean>('themeSemiDarkMenu');
const themeSemiDarkHeader = defineModel<boolean>('themeSemiDarkHeader');
const THEME_PRESET: Array<{ icon: Component; name: ThemeModeType }> = [
{
@ -77,5 +76,8 @@ function nameView(name: string) {
>
{{ $t('preferences.theme.darkMenu') }}
</SwitchItem>
<SwitchItem v-model="themeSemiDarkHeader" :disabled="modelValue === 'dark'">
{{ $t('preferences.theme.darkHeader') }}
</SwitchItem>
</div>
</template>

View File

@ -74,6 +74,7 @@ const themeBuiltinType = defineModel<BuiltinThemeType>('themeBuiltinType');
const themeMode = defineModel<ThemeModeType>('themeMode');
const themeRadius = defineModel<string>('themeRadius');
const themeSemiDarkMenu = defineModel<boolean>('themeSemiDarkMenu');
const themeSemiDarkHeader = defineModel<boolean>('themeSemiDarkHeader');
const sidebarEnable = defineModel<boolean>('sidebarEnable');
const sidebarWidth = defineModel<number>('sidebarWidth');
@ -274,6 +275,7 @@ async function handleReset() {
<Block :title="$t('preferences.theme.title')">
<Theme
v-model="themeMode"
v-model:theme-semi-dark-header="themeSemiDarkHeader"
v-model:theme-semi-dark-menu="themeSemiDarkMenu"
/>
</Block>

View File

@ -237,6 +237,7 @@
"light": "Light",
"dark": "Dark",
"darkMenu": "Semi Dark Menu",
"darkHeader": "Semi Dark Header",
"weakMode": "Weak Mode",
"grayMode": "Gray Mode",
"builtin": {

View File

@ -237,6 +237,7 @@
"light": "浅色",
"dark": "深色",
"darkMenu": "深色菜单",
"darkHeader": "深色顶栏",
"weakMode": "色弱模式",
"grayMode": "灰色模式",
"builtin": {