mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-27 14:49:43 +08:00
chore: Optimize multi-theme switching
This commit is contained in:
@@ -17,9 +17,7 @@ const appName = computed(() => preferences.app.name);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="bg-background flex min-h-full flex-1 select-none overflow-x-hidden"
|
||||
>
|
||||
<div class="flex min-h-full flex-1 select-none overflow-x-hidden">
|
||||
<AuthenticationFromView
|
||||
v-if="authPanelLeft"
|
||||
class="-enter-x min-h-full w-2/5"
|
||||
@@ -47,9 +45,7 @@ const appName = computed(() => preferences.app.name);
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!authPanelCenter" class="relative hidden w-0 flex-1 lg:block">
|
||||
<div
|
||||
class="absolute inset-0 h-full w-full bg-[var(--color-authentication)]"
|
||||
>
|
||||
<div class="bg-authentication absolute inset-0 h-full w-full">
|
||||
<div class="flex-col-center -enter-x mr-20 h-full">
|
||||
<SloganIcon :alt="appName" class="animate-float h-64 w-2/5" />
|
||||
<div class="text-1xl mt-6 font-sans text-white lg:text-2xl">
|
||||
@@ -61,10 +57,7 @@ const appName = computed(() => preferences.app.name);
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="authPanelCenter"
|
||||
class="flex-center w-full dark:bg-[var(--color-authentication)]"
|
||||
>
|
||||
<div v-if="authPanelCenter" class="flex-center bg-authentication w-full">
|
||||
<AuthenticationFromView
|
||||
class="enter-y md:bg-background w-full rounded-3xl pb-20 shadow-2xl md:w-2/3 lg:w-1/2 xl:w-2/5"
|
||||
>
|
||||
|
@@ -12,7 +12,7 @@ defineOptions({
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="flex-center bg-background absolute right-2 top-4 rounded-3xl px-3 py-1 dark:bg-[var(--color-authentication)]"
|
||||
class="flex-center bg-accent absolute right-2 top-4 rounded-3xl px-3 py-1"
|
||||
>
|
||||
<div class="hidden md:flex">
|
||||
<AuthenticationColorToggle />
|
||||
|
@@ -40,6 +40,7 @@
|
||||
"@vben-core/design": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vben-core/colorful": "workspace:*",
|
||||
"@vben-core/design": "workspace:*",
|
||||
"@vben-core/iconify": "workspace:*",
|
||||
"@vben-core/preferences": "workspace:*",
|
||||
@@ -49,7 +50,6 @@
|
||||
"@vben/locales": "workspace:*",
|
||||
"@vueuse/core": "^10.11.0",
|
||||
"@vueuse/integrations": "^10.11.0",
|
||||
"@vben/chart-ui": "workspace:*",
|
||||
"qrcode": "^1.5.3",
|
||||
"vue": "^3.4.30",
|
||||
"vue-router": "^4.4.0"
|
||||
|
@@ -189,11 +189,12 @@ function handleGo(path: string) {
|
||||
|
||||
<span
|
||||
v-if="showForgetPassword"
|
||||
class="text-primary hover:text-primary/80 cursor-pointer text-sm font-normal"
|
||||
class="text-primary hover:text-primary-hover active:text-primary-active cursor-pointer text-sm font-normal"
|
||||
@click="handleGo(forgetPasswordPath)"
|
||||
>
|
||||
{{ $t('authentication.forget-password') }}
|
||||
</span>
|
||||
|
||||
<!-- <VbenButton variant="ghost" @click="handleGo('/auth/forget-password')">
|
||||
忘记密码?
|
||||
</VbenButton> -->
|
||||
@@ -235,7 +236,7 @@ function handleGo(path: string) {
|
||||
<div v-if="showRegister" class="text-center text-sm">
|
||||
{{ $t('authentication.account-tip') }}
|
||||
<span
|
||||
class="text-primary hover:text-primary/80 cursor-pointer text-sm font-normal"
|
||||
class="text-primary hover:text-primary-hover active:text-primary-active cursor-pointer text-sm font-normal"
|
||||
@click="handleGo(registerPath)"
|
||||
>
|
||||
{{ $t('authentication.create-account') }}
|
||||
|
@@ -132,11 +132,11 @@ function goLogin() {
|
||||
name="agreePolicy"
|
||||
>
|
||||
{{ $t('authentication.sign-up-agree') }}
|
||||
<span class="text-primary hover:text-primary/80">{{
|
||||
<span class="text-primary hover:text-primary-hover">{{
|
||||
$t('authentication.sign-up-privacy-policy')
|
||||
}}</span>
|
||||
&
|
||||
<span class="text-primary hover:text-primary/80">
|
||||
<span class="text-primary hover:text-primary-hover">
|
||||
{{ $t('authentication.sign-up-terms') }}
|
||||
</span>
|
||||
</VbenCheckbox>
|
||||
@@ -158,7 +158,7 @@ function goLogin() {
|
||||
<div class="mt-4 text-center text-sm">
|
||||
{{ $t('authentication.already-account') }}
|
||||
<span
|
||||
class="text-primary hover:text-primary/80 cursor-pointer text-sm font-normal"
|
||||
class="text-primary hover:text-primary-hover cursor-pointer text-sm font-normal"
|
||||
@click="goLogin()"
|
||||
>
|
||||
{{ $t('authentication.go-login') }}
|
||||
|
@@ -1,7 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import type { BuiltinThemeType } from '@vben/types';
|
||||
|
||||
import { IcRoundColorLens } from '@vben-core/iconify';
|
||||
import {
|
||||
COLOR_PRIMARY_RESETS,
|
||||
COLOR_PRESETS,
|
||||
preferences,
|
||||
updatePreferences,
|
||||
} from '@vben-core/preferences';
|
||||
@@ -11,10 +13,10 @@ defineOptions({
|
||||
name: 'AuthenticationColorToggle',
|
||||
});
|
||||
|
||||
function handleUpdate(value: string) {
|
||||
function handleUpdate(value: BuiltinThemeType) {
|
||||
updatePreferences({
|
||||
theme: {
|
||||
colorPrimary: value,
|
||||
builtinType: value,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -23,22 +25,32 @@ function handleUpdate(value: string) {
|
||||
<template>
|
||||
<div class="group relative flex items-center overflow-hidden">
|
||||
<div
|
||||
class="ease-ou flex w-0 overflow-hidden transition-all duration-500 group-hover:w-48"
|
||||
class="ease-ou flex w-0 overflow-hidden transition-all duration-500 group-hover:w-60"
|
||||
>
|
||||
<template v-for="color in COLOR_PRIMARY_RESETS" :key="color">
|
||||
<template v-for="preset in COLOR_PRESETS" :key="preset.color">
|
||||
<VbenIconButton
|
||||
class="flex-center flex-shrink-0"
|
||||
@click="handleUpdate(color)"
|
||||
@click="handleUpdate(preset.type)"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
preferences.theme.colorPrimary === color
|
||||
? `before:opacity-100`
|
||||
: '',
|
||||
]"
|
||||
:style="{ backgroundColor: color }"
|
||||
class="relative h-3.5 w-3.5 rounded-[2px] before:absolute before:left-0.5 before:top-0.5 before:h-2.5 before:w-2.5 before:rounded-[2px] before:border before:border-gray-900 before:opacity-0 before:transition-all before:duration-150 before:content-[''] hover:scale-110"
|
||||
></div>
|
||||
:style="{ backgroundColor: preset.color }"
|
||||
class="flex-center relative size-5 rounded-full hover:scale-110"
|
||||
>
|
||||
<svg
|
||||
v-if="preferences.theme.builtinType === preset.type"
|
||||
class="h-3.5 w-3.5 text-white"
|
||||
height="1em"
|
||||
viewBox="0 0 15 15"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M11.467 3.727c.289.189.37.576.181.865l-4.25 6.5a.625.625 0 0 1-.944.12l-2.75-2.5a.625.625 0 0 1 .841-.925l2.208 2.007l3.849-5.886a.625.625 0 0 1 .865-.181"
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</VbenIconButton>
|
||||
</template>
|
||||
</div>
|
||||
|
@@ -1,5 +1,6 @@
|
||||
export * from './authentication';
|
||||
export * from './coze-assistant';
|
||||
export * from './dashboard';
|
||||
export * from './fallback';
|
||||
export * from './global-provider';
|
||||
export * from './global-search';
|
||||
@@ -8,4 +9,3 @@ export * from './notification';
|
||||
export * from './preferences';
|
||||
export * from './theme-toggle';
|
||||
export * from './user-dropdown';
|
||||
export * from './dashboard';
|
||||
|
@@ -11,6 +11,7 @@ export { default as Sidebar } from './layout/sidebar.vue';
|
||||
export { default as Tabbar } from './layout/tabbar.vue';
|
||||
export { default as GlobalShortcutKeys } from './shortcut-keys/global.vue';
|
||||
export { default as SwitchItem } from './switch-item.vue';
|
||||
export { default as ThemeColor } from './theme/color.vue';
|
||||
export { default as BuiltinTheme } from './theme/builtin.vue';
|
||||
export { default as ColorMode } from './theme/color-mode.vue';
|
||||
export { default as Radius } from './theme/radius.vue';
|
||||
export { default as Theme } from './theme/theme.vue';
|
||||
|
@@ -0,0 +1,137 @@
|
||||
<script setup lang="ts">
|
||||
import type { BuiltinThemeType } from '@vben/types';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { TinyColor, convertToHsl } from '@vben-core/colorful';
|
||||
import { MdiEditBoxOutline } from '@vben-core/iconify';
|
||||
import {
|
||||
BUILT_IN_THEME_PRESETS,
|
||||
type BuiltinThemePreset,
|
||||
} from '@vben-core/preferences';
|
||||
|
||||
defineOptions({
|
||||
name: 'PreferenceBuiltinTheme',
|
||||
});
|
||||
|
||||
const props = defineProps<{ isDark: boolean }>();
|
||||
|
||||
const colorInput = ref();
|
||||
const modelValue = defineModel<BuiltinThemeType>({ default: 'default' });
|
||||
const themeColorPrimary = defineModel<string>('themeColorPrimary');
|
||||
|
||||
const inputValue = computed(() => {
|
||||
return new TinyColor(themeColorPrimary.value).toHexString();
|
||||
});
|
||||
|
||||
function typeView(name: BuiltinThemeType) {
|
||||
switch (name) {
|
||||
case 'default': {
|
||||
return $t('preferences.theme.default');
|
||||
}
|
||||
case 'violet': {
|
||||
return $t('preferences.theme.violet');
|
||||
}
|
||||
case 'pink': {
|
||||
return $t('preferences.theme.pink');
|
||||
}
|
||||
case 'rose': {
|
||||
return $t('preferences.theme.rose');
|
||||
}
|
||||
case 'sky-blue': {
|
||||
return $t('preferences.theme.sky-blue');
|
||||
}
|
||||
case 'deep-blue': {
|
||||
return $t('preferences.theme.deep-blue');
|
||||
}
|
||||
|
||||
case 'green': {
|
||||
return $t('preferences.theme.green');
|
||||
}
|
||||
case 'deep-green': {
|
||||
return $t('preferences.theme.deep-green');
|
||||
}
|
||||
case 'orange': {
|
||||
return $t('preferences.theme.orange');
|
||||
}
|
||||
case 'yellow': {
|
||||
return $t('preferences.theme.yellow');
|
||||
}
|
||||
case 'zinc': {
|
||||
return $t('preferences.theme.zinc');
|
||||
}
|
||||
case 'neutral': {
|
||||
return $t('preferences.theme.neutral');
|
||||
}
|
||||
case 'slate': {
|
||||
return $t('preferences.theme.slate');
|
||||
}
|
||||
case 'gray': {
|
||||
return $t('preferences.theme.gray');
|
||||
}
|
||||
case 'custom': {
|
||||
return $t('preferences.theme.custom');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleSelect(theme: BuiltinThemePreset) {
|
||||
modelValue.value = theme.type;
|
||||
const primaryColor = props.isDark
|
||||
? theme.darkPrimaryColor || theme.primaryColor
|
||||
: theme.primaryColor;
|
||||
|
||||
themeColorPrimary.value = primaryColor || theme.color;
|
||||
}
|
||||
|
||||
function handleInputChange(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
themeColorPrimary.value = convertToHsl(target.value);
|
||||
}
|
||||
|
||||
function selectColor() {
|
||||
colorInput.value?.[0]?.click?.();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full flex-wrap justify-between">
|
||||
<template v-for="theme in BUILT_IN_THEME_PRESETS" :key="theme.type">
|
||||
<div class="flex cursor-pointer flex-col" @click="handleSelect(theme)">
|
||||
<div
|
||||
:class="{
|
||||
'outline-box-active': theme.type === modelValue,
|
||||
}"
|
||||
class="outline-box flex-center group cursor-pointer"
|
||||
>
|
||||
<template v-if="theme.type !== 'custom'">
|
||||
<div
|
||||
:style="{ backgroundColor: theme.color }"
|
||||
class="mx-10 my-2 size-5 rounded-md"
|
||||
></div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="size-full px-10 py-2" @click.stop="selectColor">
|
||||
<div class="flex-center relative size-5 rounded-sm">
|
||||
<MdiEditBoxOutline
|
||||
class="absolute z-10 size-5 opacity-60 group-hover:opacity-100"
|
||||
/>
|
||||
<input
|
||||
ref="colorInput"
|
||||
:value="inputValue"
|
||||
class="absolute inset-0 opacity-0"
|
||||
type="color"
|
||||
@input="handleInputChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="text-muted-foreground my-2 text-center text-xs">
|
||||
{{ typeView(theme.type) }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
@@ -1,90 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { computed, ref, watch, watchEffect } from 'vue';
|
||||
|
||||
import { MdiEditBoxOutline } from '@vben-core/iconify';
|
||||
import { TinyColor, convertToHsl } from '@vben-core/toolkit';
|
||||
|
||||
defineOptions({
|
||||
name: 'PreferenceColor',
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<{ colorPrimaryPresets: string[] }>(), {
|
||||
colorPrimaryPresets: () => [],
|
||||
});
|
||||
|
||||
const colorInput = ref();
|
||||
const currentColor = ref(props.colorPrimaryPresets?.[0]);
|
||||
|
||||
const modelValue = defineModel<string>();
|
||||
|
||||
const activeColor = computed((): CSSProperties => {
|
||||
return {
|
||||
outlineColor: currentColor.value,
|
||||
outlineWidth: '2px',
|
||||
};
|
||||
});
|
||||
|
||||
function isActive(color: string): string[] {
|
||||
return color === currentColor.value ? ['outline-box-active'] : [];
|
||||
}
|
||||
|
||||
const inputStyle = computed((): CSSProperties => {
|
||||
return props.colorPrimaryPresets.includes(currentColor.value)
|
||||
? {}
|
||||
: activeColor.value;
|
||||
});
|
||||
|
||||
const inputValue = computed(() => {
|
||||
return new TinyColor(modelValue.value).toHexString();
|
||||
});
|
||||
|
||||
function selectColor() {
|
||||
colorInput.value.click();
|
||||
}
|
||||
|
||||
function handleInputChange(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
modelValue.value = convertToHsl(target.value);
|
||||
}
|
||||
|
||||
// 监听颜色变化,转成系统可识别的 hsl 格式
|
||||
watch(currentColor, (val) => {
|
||||
modelValue.value = convertToHsl(val);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (modelValue.value) {
|
||||
currentColor.value = modelValue.value;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full flex-wrap justify-between">
|
||||
<template v-for="color in colorPrimaryPresets" :key="color">
|
||||
<div
|
||||
:class="isActive(color)"
|
||||
class="outline-box p-2"
|
||||
@click="currentColor = color"
|
||||
>
|
||||
<div
|
||||
:style="{ backgroundColor: color }"
|
||||
class="h-5 w-5 rounded-md"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
<div :style="inputStyle" class="outline-box p-2" @click="selectColor">
|
||||
<div class="flex-center bg-accent relative h-5 w-5 rounded-md">
|
||||
<MdiEditBoxOutline class="absolute z-10" />
|
||||
<input
|
||||
ref="colorInput"
|
||||
:value="inputValue"
|
||||
class="absolute inset-0 opacity-0"
|
||||
type="color"
|
||||
@input="handleInputChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import { ToggleGroup, ToggleGroupItem } from '@vben-core/shadcn-ui';
|
||||
|
||||
defineOptions({
|
||||
name: 'PreferenceColorMode',
|
||||
});
|
||||
|
||||
const modelValue = defineModel<string | undefined>('themeRadius', {
|
||||
default: '0.5',
|
||||
});
|
||||
|
||||
const items = [
|
||||
{ label: '0', value: '0' },
|
||||
{ label: '0.25', value: '0.25' },
|
||||
{ label: '0.5', value: '0.5' },
|
||||
{ label: '0.75', value: '0.75' },
|
||||
{ label: '1', value: '1' },
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ToggleGroup
|
||||
v-model="modelValue"
|
||||
class="gap-2"
|
||||
size="sm"
|
||||
type="single"
|
||||
variant="outline"
|
||||
>
|
||||
<template v-for="item in items" :key="item.value">
|
||||
<ToggleGroupItem
|
||||
:value="item.value"
|
||||
class="data-[state=on]:bg-primary data-[state=on]:text-primary-foreground h-7 w-16 rounded-sm"
|
||||
>
|
||||
{{ item.label }}
|
||||
</ToggleGroupItem>
|
||||
</template>
|
||||
</ToggleGroup>
|
||||
</template>
|
@@ -1,4 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import type { ThemeModeType } from '@vben-core/preferences';
|
||||
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import {
|
||||
IcRoundMotionPhotosAuto,
|
||||
@@ -17,7 +21,7 @@ const appSemiDarkMenu = defineModel<boolean>('appSemiDarkMenu', {
|
||||
default: true,
|
||||
});
|
||||
|
||||
const THEME_PRESET = [
|
||||
const THEME_PRESET: Array<{ icon: Component; name: ThemeModeType }> = [
|
||||
{
|
||||
icon: IcRoundWbSunny,
|
||||
name: 'light',
|
||||
|
@@ -7,7 +7,6 @@
|
||||
>
|
||||
<path
|
||||
d="M19.9 12.66a1 1 0 0 1 0-1.32l1.28-1.44a1 1 0 0 0 .12-1.17l-2-3.46a1 1 0 0 0-1.07-.48l-1.88.38a1 1 0 0 1-1.15-.66l-.61-1.83a1 1 0 0 0-.95-.68h-4a1 1 0 0 0-1 .68l-.56 1.83a1 1 0 0 1-1.15.66L5 4.79a1 1 0 0 0-1 .48L2 8.73a1 1 0 0 0 .1 1.17l1.27 1.44a1 1 0 0 1 0 1.32L2.1 14.1a1 1 0 0 0-.1 1.17l2 3.46a1 1 0 0 0 1.07.48l1.88-.38a1 1 0 0 1 1.15.66l.61 1.83a1 1 0 0 0 1 .68h4a1 1 0 0 0 .95-.68l.61-1.83a1 1 0 0 1 1.15-.66l1.88.38a1 1 0 0 0 1.07-.48l2-3.46a1 1 0 0 0-.12-1.17ZM18.41 14l.8.9l-1.28 2.22l-1.18-.24a3 3 0 0 0-3.45 2L12.92 20h-2.56L10 18.86a3 3 0 0 0-3.45-2l-1.18.24l-1.3-2.21l.8-.9a3 3 0 0 0 0-4l-.8-.9l1.28-2.2l1.18.24a3 3 0 0 0 3.45-2L10.36 4h2.56l.38 1.14a3 3 0 0 0 3.45 2l1.18-.24l1.28 2.22l-.8.9a3 3 0 0 0 0 3.98m-6.77-6a4 4 0 1 0 4 4a4 4 0 0 0-4-4m0 6a2 2 0 1 1 2-2a2 2 0 0 1-2 2"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
@@ -1,10 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { loadLocaleMessages } from '@vben/locales';
|
||||
import {
|
||||
COLOR_PRIMARY_RESETS,
|
||||
preferences,
|
||||
updatePreferences,
|
||||
} from '@vben-core/preferences';
|
||||
import { preferences, updatePreferences } from '@vben-core/preferences';
|
||||
|
||||
import Preferences from './preferences.vue';
|
||||
</script>
|
||||
@@ -18,13 +14,11 @@ import Preferences from './preferences.vue';
|
||||
:app-layout="preferences.app.layout"
|
||||
:app-locale="preferences.app.locale"
|
||||
:app-semi-dark-menu="preferences.app.semiDarkMenu"
|
||||
:app-theme-mode="preferences.app.themeMode"
|
||||
:breadcrumb-enable="preferences.breadcrumb.enable"
|
||||
:breadcrumb-hide-only-one="preferences.breadcrumb.hideOnlyOne"
|
||||
:breadcrumb-home="preferences.breadcrumb.showHome"
|
||||
:breadcrumb-icon="preferences.breadcrumb.showIcon"
|
||||
:breadcrumb-style-type="preferences.breadcrumb.styleType"
|
||||
:color-primary-presets="COLOR_PRIMARY_RESETS"
|
||||
:footer-enable="preferences.footer.enable"
|
||||
:footer-fixed="preferences.footer.fixed"
|
||||
:header-enable="preferences.header.enable"
|
||||
@@ -43,7 +37,10 @@ import Preferences from './preferences.vue';
|
||||
:sidebar-enable="preferences.sidebar.enable"
|
||||
:tabbar-enable="preferences.tabbar.enable"
|
||||
:tabbar-show-icon="preferences.tabbar.showIcon"
|
||||
:theme-builtin-type="preferences.theme.builtinType"
|
||||
:theme-color-primary="preferences.theme.colorPrimary"
|
||||
:theme-mode="preferences.theme.mode"
|
||||
:theme-radius="preferences.theme.radius"
|
||||
:transition-enable="preferences.transition.enable"
|
||||
:transition-name="preferences.transition.name"
|
||||
:transition-progress="preferences.transition.progress"
|
||||
@@ -72,9 +69,6 @@ import Preferences from './preferences.vue';
|
||||
@update:app-semi-dark-menu="
|
||||
(val) => updatePreferences({ app: { semiDarkMenu: val } })
|
||||
"
|
||||
@update:app-theme-mode="
|
||||
(val) => updatePreferences({ app: { themeMode: val } })
|
||||
"
|
||||
@update:breadcrumb-enable="
|
||||
(val) => updatePreferences({ breadcrumb: { enable: val } })
|
||||
"
|
||||
@@ -136,9 +130,16 @@ import Preferences from './preferences.vue';
|
||||
@update:tabbar-show-icon="
|
||||
(val) => updatePreferences({ tabbar: { showIcon: val } })
|
||||
"
|
||||
@update:theme-builtin-type="
|
||||
(val) => updatePreferences({ theme: { builtinType: val } })
|
||||
"
|
||||
@update:theme-color-primary="
|
||||
(val) => updatePreferences({ theme: { colorPrimary: val } })
|
||||
"
|
||||
@update:theme-mode="(val) => updatePreferences({ theme: { mode: val } })"
|
||||
@update:theme-radius="
|
||||
(val) => updatePreferences({ theme: { radius: val } })
|
||||
"
|
||||
@update:transition-enable="
|
||||
(val) => updatePreferences({ transition: { enable: val } })
|
||||
"
|
||||
|
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
BuiltinThemeType,
|
||||
ContentCompactType,
|
||||
LayoutHeaderModeType,
|
||||
LayoutType,
|
||||
@@ -35,6 +36,7 @@ import {
|
||||
Animation,
|
||||
Block,
|
||||
Breadcrumb,
|
||||
BuiltinTheme,
|
||||
ColorMode,
|
||||
Content,
|
||||
Footer,
|
||||
@@ -43,19 +45,14 @@ import {
|
||||
Header,
|
||||
Layout,
|
||||
Navigation,
|
||||
Radius,
|
||||
Sidebar,
|
||||
Tabbar,
|
||||
Theme,
|
||||
ThemeColor,
|
||||
} from './blocks';
|
||||
import Trigger from './trigger.vue';
|
||||
import { useOpenPreferences } from './use-open-preferences';
|
||||
|
||||
withDefaults(defineProps<{ colorPrimaryPresets: string[] }>(), {
|
||||
colorPrimaryPresets: () => [],
|
||||
});
|
||||
|
||||
const appThemeMode = defineModel<ThemeModeType>('appThemeMode');
|
||||
const appLocale = defineModel<SupportedLanguagesType>('appLocale');
|
||||
const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
|
||||
const appAiAssistant = defineModel<boolean>('appAiAssistant');
|
||||
@@ -70,6 +67,9 @@ const transitionName = defineModel<string>('transitionName');
|
||||
const transitionEnable = defineModel<boolean>('transitionEnable');
|
||||
|
||||
const themeColorPrimary = defineModel<string>('themeColorPrimary');
|
||||
const themeBuiltinType = defineModel<BuiltinThemeType>('themeBuiltinType');
|
||||
const themeMode = defineModel<ThemeModeType>('themeMode');
|
||||
const themeRadius = defineModel<string>('themeRadius');
|
||||
|
||||
const sidebarEnable = defineModel<boolean>('sidebarEnable');
|
||||
const sidebarCollapsed = defineModel<boolean>('sidebarCollapsed');
|
||||
@@ -115,6 +115,7 @@ const shortcutKeysGlobalPreferences = defineModel<boolean>(
|
||||
|
||||
const {
|
||||
diffPreference,
|
||||
isDark,
|
||||
isFullContent,
|
||||
isHeaderNav,
|
||||
isMixedNav,
|
||||
@@ -124,14 +125,10 @@ const {
|
||||
} = usePreferences();
|
||||
const { copy } = useClipboard();
|
||||
|
||||
const activeTab = ref('general');
|
||||
const activeTab = ref('appearance');
|
||||
|
||||
const tabs = computed((): SegmentedItem[] => {
|
||||
return [
|
||||
{
|
||||
label: $t('preferences.general'),
|
||||
value: 'general',
|
||||
},
|
||||
{
|
||||
label: $t('preferences.appearance'),
|
||||
value: 'appearance',
|
||||
@@ -140,11 +137,14 @@ const tabs = computed((): SegmentedItem[] => {
|
||||
label: $t('preferences.layout'),
|
||||
value: 'layout',
|
||||
},
|
||||
|
||||
{
|
||||
label: $t('preferences.shortcut-keys.title'),
|
||||
value: 'shortcutKey',
|
||||
},
|
||||
{
|
||||
label: $t('preferences.general'),
|
||||
value: 'general',
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
@@ -200,18 +200,45 @@ function handleReset() {
|
||||
|
||||
<div class="p-4 pt-4">
|
||||
<VbenSegmented v-model="activeTab" :tabs="tabs">
|
||||
<template #general>
|
||||
<Block :title="$t('preferences.general')">
|
||||
<General
|
||||
v-model:app-ai-assistant="appAiAssistant"
|
||||
v-model:app-dynamic-title="appDynamicTitle"
|
||||
v-model:app-locale="appLocale"
|
||||
/>
|
||||
</Block>
|
||||
|
||||
<Block :title="$t('preferences.animation')">
|
||||
<Animation
|
||||
v-model:transition-enable="transitionEnable"
|
||||
v-model:transition-name="transitionName"
|
||||
v-model:transition-progress="transitionProgress"
|
||||
/>
|
||||
</Block>
|
||||
</template>
|
||||
<template #appearance>
|
||||
<Block :title="$t('preferences.theme')">
|
||||
<Block :title="$t('preferences.theme.name')">
|
||||
<Theme
|
||||
v-model="appThemeMode"
|
||||
v-model="themeMode"
|
||||
v-model:app-semi-dark-menu="appSemiDarkMenu"
|
||||
/>
|
||||
</Block>
|
||||
<Block :title="$t('preferences.theme-color')">
|
||||
<!-- <Block :title="$t('preferences.theme-color')">
|
||||
<ThemeColor
|
||||
v-model="themeColorPrimary"
|
||||
:color-primary-presets="colorPrimaryPresets"
|
||||
/>
|
||||
</Block> -->
|
||||
<Block :title="$t('preferences.theme.builtin')">
|
||||
<BuiltinTheme
|
||||
v-model="themeBuiltinType"
|
||||
v-model:theme-color-primary="themeColorPrimary"
|
||||
:is-dark="isDark"
|
||||
/>
|
||||
</Block>
|
||||
<Block :title="$t('preferences.theme.radius')">
|
||||
<Radius v-model="themeRadius" />
|
||||
</Block>
|
||||
<Block :title="$t('preferences.other')">
|
||||
<ColorMode
|
||||
@@ -281,23 +308,7 @@ function handleReset() {
|
||||
/>
|
||||
</Block>
|
||||
</template>
|
||||
<template #general>
|
||||
<Block :title="$t('preferences.general')">
|
||||
<General
|
||||
v-model:app-ai-assistant="appAiAssistant"
|
||||
v-model:app-dynamic-title="appDynamicTitle"
|
||||
v-model:app-locale="appLocale"
|
||||
/>
|
||||
</Block>
|
||||
|
||||
<Block :title="$t('preferences.animation')">
|
||||
<Animation
|
||||
v-model:transition-enable="transitionEnable"
|
||||
v-model:transition-name="transitionName"
|
||||
v-model:transition-progress="transitionProgress"
|
||||
/>
|
||||
</Block>
|
||||
</template>
|
||||
<template #shortcutKey>
|
||||
<Block :title="$t('preferences.shortcut-keys.global')">
|
||||
<GlobalShortcutKeys
|
||||
|
@@ -14,6 +14,8 @@ defineOptions({
|
||||
:title="$t('preferences.name')"
|
||||
class="bg-primary flex-col-center h-12 w-12 cursor-pointer rounded-l-lg rounded-r-none border-none"
|
||||
>
|
||||
<IconSetting class="duration-3000 animate-spin text-2xl" />
|
||||
<IconSetting
|
||||
class="duration-3000 fill-primary-foreground animate-spin text-2xl"
|
||||
/>
|
||||
</VbenButton>
|
||||
</template>
|
||||
|
@@ -29,7 +29,7 @@ withDefaults(defineProps<{ shouldOnHover?: boolean }>(), {
|
||||
|
||||
function handleChange(isDark: boolean) {
|
||||
updatePreferences({
|
||||
app: { themeMode: isDark ? 'dark' : 'light' },
|
||||
theme: { mode: isDark ? 'dark' : 'light' },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,13 +64,12 @@ const PRESETS = [
|
||||
/>
|
||||
</template>
|
||||
<ToggleGroup
|
||||
:model-value="preferences.app.themeMode"
|
||||
:model-value="preferences.theme.mode"
|
||||
class="gap-2"
|
||||
type="single"
|
||||
variant="outline"
|
||||
@update:model-value="
|
||||
(val) =>
|
||||
updatePreferences({ app: { themeMode: val as ThemeModeType } })
|
||||
(val) => updatePreferences({ theme: { mode: val as ThemeModeType } })
|
||||
"
|
||||
>
|
||||
<ToggleGroupItem
|
||||
|
Reference in New Issue
Block a user