Forráskód Böngészése

feat: add theme-aware logo support via optional sourceDark configuration (#6866)

* Initial plan

* Initial exploration and setup

Co-authored-by: aonoa <32682251+aonoa@users.noreply.github.com>

* Add support for separate light and dark theme logos

Co-authored-by: aonoa <32682251+aonoa@users.noreply.github.com>

* Update documentation with dark theme logo configuration

Co-authored-by: aonoa <32682251+aonoa@users.noreply.github.com>

* feat: Add theme-aware logo support for authentication/login page

Co-authored-by: aonoa <32682251+aonoa@users.noreply.github.com>

* revert: .npmrc

Signed-off-by: aonoa <1991849113@qq.com>

---------

Signed-off-by: aonoa <1991849113@qq.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: aonoa <32682251+aonoa@users.noreply.github.com>
Co-authored-by: Li Kui <90845831+likui628@users.noreply.github.com>
Co-authored-by: aonoa <1991849113@qq.com>
Copilot 7 hónapja
szülő
commit
c7c39047de

+ 1 - 1
.npmrc

@@ -1,4 +1,4 @@
-registry = "https://registry.npmmirror.com"
+registry=https://registry.npmmirror.com
 public-hoist-pattern[]=lefthook
 public-hoist-pattern[]=eslint
 public-hoist-pattern[]=prettier

+ 2 - 0
apps/web-antd/src/layouts/auth.vue

@@ -8,12 +8,14 @@ import { $t } from '#/locales';
 
 const appName = computed(() => preferences.app.name);
 const logo = computed(() => preferences.logo.source);
+const logoDark = computed(() => preferences.logo.sourceDark);
 </script>
 
 <template>
   <AuthPageLayout
     :app-name="appName"
     :logo="logo"
+    :logo-dark="logoDark"
     :page-description="$t('authentication.pageDesc')"
     :page-title="$t('authentication.pageTitle')"
   >

+ 2 - 0
apps/web-ele/src/layouts/auth.vue

@@ -8,12 +8,14 @@ import { $t } from '#/locales';
 
 const appName = computed(() => preferences.app.name);
 const logo = computed(() => preferences.logo.source);
+const logoDark = computed(() => preferences.logo.sourceDark);
 </script>
 
 <template>
   <AuthPageLayout
     :app-name="appName"
     :logo="logo"
+    :logo-dark="logoDark"
     :page-description="$t('authentication.pageDesc')"
     :page-title="$t('authentication.pageTitle')"
   >

+ 2 - 0
apps/web-naive/src/layouts/auth.vue

@@ -8,12 +8,14 @@ import { $t } from '#/locales';
 
 const appName = computed(() => preferences.app.name);
 const logo = computed(() => preferences.logo.source);
+const logoDark = computed(() => preferences.logo.sourceDark);
 </script>
 
 <template>
   <AuthPageLayout
     :app-name="appName"
     :logo="logo"
+    :logo-dark="logoDark"
     :page-description="$t('authentication.pageDesc')"
     :page-title="$t('authentication.pageTitle')"
   >

+ 3 - 0
docs/src/en/guide/essentials/settings.md

@@ -261,6 +261,7 @@ const defaultPreferences: Preferences = {
     enable: true,
     fit: 'contain',
     source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
+    // sourceDark: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-dark.webp', // Optional: Dark theme logo
   },
   navigation: {
     accordion: true,
@@ -457,6 +458,8 @@ interface LogoPreferences {
   fit: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
   /** Logo URL */
   source: string;
+  /** Dark theme logo URL (optional, if not set, use source) */
+  sourceDark?: string;
 }
 
 interface NavigationPreferences {

+ 3 - 0
docs/src/guide/essentials/settings.md

@@ -260,6 +260,7 @@ const defaultPreferences: Preferences = {
     enable: true,
     fit: 'contain',
     source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
+    // sourceDark: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-dark.webp', // 可选:暗色主题logo
   },
   navigation: {
     accordion: true,
@@ -457,6 +458,8 @@ interface LogoPreferences {
   fit: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
   /** logo地址 */
   source: string;
+  /** 暗色主题logo地址 (可选,若不设置则使用 source) */
+  sourceDark?: string;
 }
 
 interface NavigationPreferences {

+ 3 - 3
packages/@core/base/shared/src/utils/date.ts

@@ -1,6 +1,6 @@
-import dayjs from "dayjs";
-import timezone from "dayjs/plugin/timezone";
-import utc from "dayjs/plugin/utc";
+import dayjs from 'dayjs';
+import timezone from 'dayjs/plugin/timezone';
+import utc from 'dayjs/plugin/utc';
 
 dayjs.extend(utc);
 dayjs.extend(timezone);

+ 2 - 0
packages/@core/preferences/src/types.ts

@@ -146,6 +146,8 @@ interface LogoPreferences {
   fit: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';
   /** logo地址 */
   source: string;
+  /** 暗色主题logo地址 (可选,若不设置则使用 source) */
+  sourceDark?: string;
 }
 
 interface NavigationPreferences {

+ 22 - 3
packages/@core/ui-kit/shadcn-ui/src/components/logo/logo.vue

@@ -1,4 +1,6 @@
 <script setup lang="ts">
+import { computed } from 'vue';
+
 import { VbenAvatar } from '../avatar';
 
 interface Props {
@@ -22,6 +24,10 @@ interface Props {
    * @zh_CN Logo 图标
    */
   src?: string;
+  /**
+   * @zh_CN 暗色主题 Logo 图标 (可选,若不设置则使用 src)
+   */
+  srcDark?: string;
   /**
    * @zh_CN Logo 文本
    */
@@ -36,14 +42,27 @@ defineOptions({
   name: 'VbenLogo',
 });
 
-withDefaults(defineProps<Props>(), {
+const props = withDefaults(defineProps<Props>(), {
   collapsed: false,
   href: 'javascript:void 0',
   logoSize: 32,
   src: '',
+  srcDark: '',
   theme: 'light',
   fit: 'cover',
 });
+
+/**
+ * @zh_CN 根据主题选择合适的 logo 图标
+ */
+const logoSrc = computed(() => {
+  // 如果是暗色主题且提供了 srcDark,则使用暗色主题的 logo
+  if (props.theme === 'dark' && props.srcDark) {
+    return props.srcDark;
+  }
+  // 否则使用默认的 src
+  return props.src;
+});
 </script>
 
 <template>
@@ -54,9 +73,9 @@ withDefaults(defineProps<Props>(), {
       class="flex h-full items-center gap-2 overflow-hidden px-3 text-lg leading-normal transition-all duration-500"
     >
       <VbenAvatar
-        v-if="src"
+        v-if="logoSrc"
         :alt="text"
-        :src="src"
+        :src="logoSrc"
         :size="logoSize"
         :fit="fit"
         class="relative rounded-none bg-transparent"

+ 26 - 3
packages/effects/layouts/src/authentication/authentication.vue

@@ -1,6 +1,8 @@
 <script setup lang="ts">
 import type { ToolbarType } from './types';
 
+import { computed } from 'vue';
+
 import { preferences, usePreferences } from '@vben/preferences';
 
 import { Copyright } from '../basic/copyright';
@@ -11,6 +13,7 @@ import Toolbar from './toolbar.vue';
 interface Props {
   appName?: string;
   logo?: string;
+  logoDark?: string;
   pageTitle?: string;
   pageDescription?: string;
   sloganImage?: string;
@@ -20,10 +23,11 @@ interface Props {
   clickLogo?: () => void;
 }
 
-withDefaults(defineProps<Props>(), {
+const props = withDefaults(defineProps<Props>(), {
   appName: '',
   copyright: true,
   logo: '',
+  logoDark: '',
   pageDescription: '',
   pageTitle: '',
   sloganImage: '',
@@ -34,6 +38,18 @@ withDefaults(defineProps<Props>(), {
 
 const { authPanelCenter, authPanelLeft, authPanelRight, isDark } =
   usePreferences();
+
+/**
+ * @zh_CN 根据主题选择合适的 logo 图标
+ */
+const logoSrc = computed(() => {
+  // 如果是暗色主题且提供了 logoDark,则使用暗色主题的 logo
+  if (isDark.value && props.logoDark) {
+    return props.logoDark;
+  }
+  // 否则使用默认的 logo
+  return props.logo;
+});
 </script>
 
 <template>
@@ -65,14 +81,21 @@ const { authPanelCenter, authPanelLeft, authPanelRight, isDark } =
     <slot name="logo">
       <!-- 头部 Logo 和应用名称 -->
       <div
-        v-if="logo || appName"
+        v-if="logoSrc || appName"
         class="absolute left-0 top-0 z-10 flex flex-1"
         @click="clickLogo"
       >
         <div
           class="text-foreground lg:text-foreground ml-4 mt-4 flex flex-1 items-center sm:left-6 sm:top-6"
         >
-          <img v-if="logo" :alt="appName" :src="logo" class="mr-2" width="42" />
+          <img
+            v-if="logoSrc"
+            :key="logoSrc"
+            :alt="appName"
+            :src="logoSrc"
+            class="mr-2"
+            width="42"
+          />
           <p v-if="appName" class="m-0 text-xl font-medium">
             {{ appName }}
           </p>

+ 3 - 0
packages/effects/layouts/src/basic/layout.vue

@@ -259,6 +259,7 @@ const headerSlots = computed(() => {
         :class="logoClass"
         :collapsed="logoCollapsed"
         :src="preferences.logo.source"
+        :src-dark="preferences.logo.sourceDark"
         :text="preferences.app.name"
         :theme="showHeaderNav ? headerTheme : theme"
         @click="clickLogo"
@@ -350,6 +351,8 @@ const headerSlots = computed(() => {
       <VbenLogo
         v-if="preferences.logo.enable"
         :fit="preferences.logo.fit"
+        :src="preferences.logo.source"
+        :src-dark="preferences.logo.sourceDark"
         :text="preferences.app.name"
         :theme="theme"
       >

+ 7 - 4
packages/stores/src/modules/timezone.ts

@@ -1,9 +1,12 @@
-import { ref, unref } from "vue";
+import { ref, unref } from 'vue';
 
-import { DEFAULT_TIME_ZONE_OPTIONS } from "@vben-core/preferences";
-import { getCurrentTimezone, setCurrentTimezone } from "@vben-core/shared/utils";
+import { DEFAULT_TIME_ZONE_OPTIONS } from '@vben-core/preferences';
+import {
+  getCurrentTimezone,
+  setCurrentTimezone,
+} from '@vben-core/shared/utils';
 
-import { acceptHMRUpdate, defineStore } from "pinia";
+import { acceptHMRUpdate, defineStore } from 'pinia';
 
 interface TimezoneHandler {
   getTimezone?: () => Promise<null | string | undefined>;