feat: supports specifying the position of the preference button (#4154)

pull/48/MERGE
Vben 2024-08-14 23:02:39 +08:00 committed by GitHub
parent 9c6e059aac
commit 30223f18db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 121 additions and 21 deletions

View File

@ -189,6 +189,7 @@ const defaultPreferences: Preferences = {
locale: 'zh-CN', locale: 'zh-CN',
loginExpiredMode: 'modal', loginExpiredMode: 'modal',
name: 'Vben Admin', name: 'Vben Admin',
preferencesButtonPosition: 'fixed',
watermark: false, watermark: false,
}, },
breadcrumb: { breadcrumb: {
@ -319,6 +320,8 @@ interface AppPreferences {
loginExpiredMode: LoginExpiredModeType; loginExpiredMode: LoginExpiredModeType;
/** 应用名 */ /** 应用名 */
name: string; name: string;
/** 偏好设置按钮位置 */
preferencesButtonPosition: PreferencesButtonPositionType;
/** /**
* @zh_CN 是否开启水印 * @zh_CN 是否开启水印
*/ */

View File

@ -38,7 +38,7 @@ export {
RotateCw, RotateCw,
Search, Search,
SearchX, SearchX,
Settings2, Settings,
Sun, Sun,
SunMoon, SunMoon,
SwatchBook, SwatchBook,

View File

@ -7,6 +7,13 @@ type LayoutType =
type ThemeModeType = 'auto' | 'dark' | 'light'; type ThemeModeType = 'auto' | 'dark' | 'light';
/**
*
* fixed
* header
*/
type PreferencesButtonPositionType = 'fixed' | 'header';
type BuiltinThemeType = type BuiltinThemeType =
| 'custom' | 'custom'
| 'deep-blue' | 'deep-blue'
@ -92,6 +99,7 @@ export type {
LoginExpiredModeType, LoginExpiredModeType,
NavigationStyleType, NavigationStyleType,
PageTransitionType, PageTransitionType,
PreferencesButtonPositionType,
TabsStyleType, TabsStyleType,
ThemeModeType, ThemeModeType,
}; };

View File

@ -19,6 +19,7 @@ const defaultPreferences: Preferences = {
locale: 'zh-CN', locale: 'zh-CN',
loginExpiredMode: 'modal', loginExpiredMode: 'modal',
name: 'Vben Admin', name: 'Vben Admin',
preferencesButtonPosition: 'fixed',
watermark: false, watermark: false,
}, },
breadcrumb: { breadcrumb: {

View File

@ -10,6 +10,7 @@ import type {
LoginExpiredModeType, LoginExpiredModeType,
NavigationStyleType, NavigationStyleType,
PageTransitionType, PageTransitionType,
PreferencesButtonPositionType,
TabsStyleType, TabsStyleType,
ThemeModeType, ThemeModeType,
} from '@vben-core/typings'; } from '@vben-core/typings';
@ -49,6 +50,8 @@ interface AppPreferences {
loginExpiredMode: LoginExpiredModeType; loginExpiredMode: LoginExpiredModeType;
/** 应用名 */ /** 应用名 */
name: string; name: string;
/** 偏好设置按钮位置 */
preferencesButtonPosition: PreferencesButtonPositionType;
/** /**
* @zh_CN * @zh_CN
*/ */

View File

@ -5,7 +5,12 @@ import { preferences, usePreferences } from '@vben/preferences';
import { useAccessStore } from '@vben/stores'; import { useAccessStore } from '@vben/stores';
import { VbenFullScreen } from '@vben-core/shadcn-ui'; import { VbenFullScreen } from '@vben-core/shadcn-ui';
import { GlobalSearch, LanguageToggle, ThemeToggle } from '../../widgets'; import {
GlobalSearch,
LanguageToggle,
PreferencesButton,
ThemeToggle,
} from '../../widgets';
interface Props { interface Props {
/** /**
@ -26,34 +31,44 @@ const accessStore = useAccessStore();
const { globalSearchShortcutKey } = usePreferences(); const { globalSearchShortcutKey } = usePreferences();
const slots = useSlots(); const slots = useSlots();
const rightSlots = computed(() => { const rightSlots = computed(() => {
const list = [{ index: 30, name: 'user-dropdown' }]; const list = [{ index: 100, name: 'user-dropdown' }];
if (preferences.widget.globalSearch) { if (preferences.widget.globalSearch) {
list.push({ list.push({
index: 5, index: 5,
name: 'global-search', name: 'global-search',
}); });
} }
if (preferences.widget.themeToggle) {
if (
preferences.app.enablePreferences &&
preferences.app.preferencesButtonPosition === 'header'
) {
list.push({ list.push({
index: 10, index: 10,
name: 'preferences',
});
}
if (preferences.widget.themeToggle) {
list.push({
index: 15,
name: 'theme-toggle', name: 'theme-toggle',
}); });
} }
if (preferences.widget.languageToggle) { if (preferences.widget.languageToggle) {
list.push({ list.push({
index: 15, index: 20,
name: 'language-toggle', name: 'language-toggle',
}); });
} }
if (preferences.widget.fullscreen) { if (preferences.widget.fullscreen) {
list.push({ list.push({
index: 20, index: 25,
name: 'fullscreen', name: 'fullscreen',
}); });
} }
if (preferences.widget.notification) { if (preferences.widget.notification) {
list.push({ list.push({
index: 25, index: 30,
name: 'notification', name: 'notification',
}); });
} }
@ -66,6 +81,7 @@ const rightSlots = computed(() => {
}); });
return list.sort((a, b) => a.index - b.index); return list.sort((a, b) => a.index - b.index);
}); });
const leftSlots = computed(() => { const leftSlots = computed(() => {
const list: any[] = []; const list: any[] = [];
@ -108,8 +124,12 @@ const leftSlots = computed(() => {
class="mr-4" class="mr-4"
/> />
</template> </template>
<template v-else-if="slot.name === 'preferences'">
<PreferencesButton class="mr-2" />
</template>
<template v-else-if="slot.name === 'theme-toggle'"> <template v-else-if="slot.name === 'theme-toggle'">
<ThemeToggle class="mr-2" /> <ThemeToggle class="mr-2 mt-[2px]" />
</template> </template>
<template v-else-if="slot.name === 'language-toggle'"> <template v-else-if="slot.name === 'language-toggle'">
<LanguageToggle class="mr-2" /> <LanguageToggle class="mr-2" />

View File

@ -320,8 +320,14 @@ const headerSlots = computed(() => {
<slot v-if="lockStore.isLockScreen" name="lock-screen"></slot> <slot v-if="lockStore.isLockScreen" name="lock-screen"></slot>
</Transition> </Transition>
<template v-if="preferences.app.enablePreferences"> <template
v-if="
preferences.app.enablePreferences &&
preferences.app.preferencesButtonPosition === 'fixed'
"
>
<Preferences <Preferences
class="z-100 fixed bottom-20 right-0"
@clear-preferences-and-logout="clearPreferencesAndLogout" @clear-preferences-and-logout="clearPreferencesAndLogout"
/> />
</template> </template>

View File

@ -54,9 +54,6 @@ const styleItems = computed((): SelectOption[] => [
<SwitchItem v-model="tabbarDragable" :disabled="!tabbarEnable"> <SwitchItem v-model="tabbarDragable" :disabled="!tabbarEnable">
{{ $t('preferences.tabbar.dragable') }} {{ $t('preferences.tabbar.dragable') }}
</SwitchItem> </SwitchItem>
<SelectItem v-model="tabbarStyleType" :items="styleItems">
{{ $t('preferences.tabbar.styleType.title') }}
</SelectItem>
<SwitchItem v-model="tabbarShowIcon" :disabled="!tabbarEnable"> <SwitchItem v-model="tabbarShowIcon" :disabled="!tabbarEnable">
{{ $t('preferences.tabbar.icon') }} {{ $t('preferences.tabbar.icon') }}
</SwitchItem> </SwitchItem>
@ -69,4 +66,7 @@ const styleItems = computed((): SelectOption[] => [
<SwitchItem v-model="tabbarShowMaximize" :disabled="!tabbarEnable"> <SwitchItem v-model="tabbarShowMaximize" :disabled="!tabbarEnable">
{{ $t('preferences.tabbar.showMaximize') }} {{ $t('preferences.tabbar.showMaximize') }}
</SwitchItem> </SwitchItem>
<SelectItem v-model="tabbarStyleType" :items="styleItems">
{{ $t('preferences.tabbar.styleType.title') }}
</SelectItem>
</template> </template>

View File

@ -1,6 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import type { SelectOption } from '@vben/types';
import { computed } from 'vue';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import SelectItem from '../select-item.vue';
import SwitchItem from '../switch-item.vue'; import SwitchItem from '../switch-item.vue';
defineOptions({ defineOptions({
@ -14,6 +19,20 @@ const widgetNotification = defineModel<boolean>('widgetNotification');
const widgetThemeToggle = defineModel<boolean>('widgetThemeToggle'); const widgetThemeToggle = defineModel<boolean>('widgetThemeToggle');
const widgetSidebarToggle = defineModel<boolean>('widgetSidebarToggle'); const widgetSidebarToggle = defineModel<boolean>('widgetSidebarToggle');
const widgetLockScreen = defineModel<boolean>('widgetLockScreen'); const widgetLockScreen = defineModel<boolean>('widgetLockScreen');
const appPreferencesButtonPosition = defineModel<string>(
'appPreferencesButtonPosition',
);
const positionItems = computed((): SelectOption[] => [
{
label: $t('preferences.position.header'),
value: 'header',
},
{
label: $t('preferences.position.fixed'),
value: 'fixed',
},
]);
</script> </script>
<template> <template>
@ -38,4 +57,7 @@ const widgetLockScreen = defineModel<boolean>('widgetLockScreen');
<SwitchItem v-model="widgetSidebarToggle"> <SwitchItem v-model="widgetSidebarToggle">
{{ $t('preferences.widget.sidebarToggle') }} {{ $t('preferences.widget.sidebarToggle') }}
</SwitchItem> </SwitchItem>
<SelectItem v-model="appPreferencesButtonPosition" :items="positionItems">
{{ $t('preferences.position.title') }}
</SelectItem>
</template> </template>

View File

@ -1,2 +1,3 @@
export { default as Preferences } from './preferences.vue'; export { default as Preferences } from './preferences.vue';
export { default as PreferencesButton } from './preferences-button.vue';
export * from './use-open-preferences'; export * from './use-open-preferences';

View File

@ -0,0 +1,13 @@
<script lang="ts" setup>
import { Settings } from '@vben/icons';
import { VbenIconButton } from '@vben-core/shadcn-ui';
import Preferences from './preferences.vue';
</script>
<template>
<Preferences>
<VbenIconButton>
<Settings class="size-4" />
</VbenIconButton>
</Preferences>
</template>

View File

@ -7,13 +7,14 @@ import type {
LayoutHeaderModeType, LayoutHeaderModeType,
LayoutType, LayoutType,
NavigationStyleType, NavigationStyleType,
PreferencesButtonPositionType,
ThemeModeType, ThemeModeType,
} from '@vben/types'; } from '@vben/types';
import type { SegmentedItem } from '@vben-core/shadcn-ui'; import type { SegmentedItem } from '@vben-core/shadcn-ui';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { Copy, RotateCw, Settings2 } from '@vben/icons'; import { Copy, RotateCw, Settings } from '@vben/icons';
import { $t, loadLocaleMessages } from '@vben/locales'; import { $t, loadLocaleMessages } from '@vben/locales';
import { import {
clearPreferencesCache, clearPreferencesCache,
@ -63,6 +64,9 @@ const appColorWeakMode = defineModel<boolean>('appColorWeakMode');
const appContentCompact = defineModel<ContentCompactType>('appContentCompact'); const appContentCompact = defineModel<ContentCompactType>('appContentCompact');
const appWatermark = defineModel<boolean>('appWatermark'); const appWatermark = defineModel<boolean>('appWatermark');
const appEnableCheckUpdates = defineModel<boolean>('appEnableCheckUpdates'); const appEnableCheckUpdates = defineModel<boolean>('appEnableCheckUpdates');
const appPreferencesButtonPosition = defineModel<PreferencesButtonPositionType>(
'appPreferencesButtonPosition',
);
const transitionProgress = defineModel<boolean>('transitionProgress'); const transitionProgress = defineModel<boolean>('transitionProgress');
const transitionName = defineModel<string>('transitionName'); const transitionName = defineModel<string>('transitionName');
@ -220,19 +224,21 @@ async function handleReset() {
</script> </script>
<template> <template>
<div class="z-100 fixed right-0 top-1/2"> <div>
<VbenSheet <VbenSheet
v-model:open="openPreferences" v-model:open="openPreferences"
:description="$t('preferences.subtitle')" :description="$t('preferences.subtitle')"
:title="$t('preferences.title')" :title="$t('preferences.title')"
> >
<template #trigger> <template #trigger>
<VbenButton <slot name="trigger">
:title="$t('preferences.title')" <VbenButton
class="bg-primary flex-col-center h-10 w-10 cursor-pointer rounded-l-lg rounded-r-none border-none" :title="$t('preferences.title')"
> class="bg-primary flex-col-center size-10 cursor-pointer rounded-l-lg rounded-r-none border-none"
<Settings2 class="size-5" /> >
</VbenButton> <Settings class="size-5" />
</VbenButton>
</slot>
</template> </template>
<template #extra> <template #extra>
<div class="flex items-center"> <div class="flex items-center">
@ -358,6 +364,9 @@ async function handleReset() {
</Block> </Block>
<Block :title="$t('preferences.widget.title')"> <Block :title="$t('preferences.widget.title')">
<Widget <Widget
v-model:app-preferences-button-position="
appPreferencesButtonPosition
"
v-model:widget-fullscreen="widgetFullscreen" v-model:widget-fullscreen="widgetFullscreen"
v-model:widget-global-search="widgetGlobalSearch" v-model:widget-global-search="widgetGlobalSearch"
v-model:widget-language-toggle="widgetLanguageToggle" v-model:widget-language-toggle="widgetLanguageToggle"

View File

@ -47,5 +47,9 @@ const listen = computed(() => {
}); });
</script> </script>
<template> <template>
<PreferencesSheet v-bind="attrs" v-on="listen" /> <PreferencesSheet v-bind="attrs" v-on="listen">
<template #trigger>
<slot></slot>
</template>
</PreferencesSheet>
</template> </template>

View File

@ -172,6 +172,11 @@
"dynamicTitle": "Dynamic Title", "dynamicTitle": "Dynamic Title",
"watermark": "Watermark", "watermark": "Watermark",
"checkUpdates": "Periodic update check", "checkUpdates": "Periodic update check",
"position": {
"title": "Preferences Postion",
"header": "Header",
"fixed": "Fixed"
},
"sidebar": { "sidebar": {
"title": "Sidebar", "title": "Sidebar",
"width": "Width", "width": "Width",

View File

@ -172,6 +172,11 @@
"dynamicTitle": "动态标题", "dynamicTitle": "动态标题",
"watermark": "水印", "watermark": "水印",
"checkUpdates": "定时检查更新", "checkUpdates": "定时检查更新",
"position": {
"title": "偏好设置位置",
"header": "顶栏",
"fixed": "固定"
},
"sidebar": { "sidebar": {
"title": "侧边栏", "title": "侧边栏",
"width": "宽度", "width": "宽度",