feat: supports specifying the position of the preference button (#4154)
parent
9c6e059aac
commit
30223f18db
|
@ -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 是否开启水印
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -38,7 +38,7 @@ export {
|
||||||
RotateCw,
|
RotateCw,
|
||||||
Search,
|
Search,
|
||||||
SearchX,
|
SearchX,
|
||||||
Settings2,
|
Settings,
|
||||||
Sun,
|
Sun,
|
||||||
SunMoon,
|
SunMoon,
|
||||||
SwatchBook,
|
SwatchBook,
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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 是否开启水印
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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>
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -172,6 +172,11 @@
|
||||||
"dynamicTitle": "动态标题",
|
"dynamicTitle": "动态标题",
|
||||||
"watermark": "水印",
|
"watermark": "水印",
|
||||||
"checkUpdates": "定时检查更新",
|
"checkUpdates": "定时检查更新",
|
||||||
|
"position": {
|
||||||
|
"title": "偏好设置位置",
|
||||||
|
"header": "顶栏",
|
||||||
|
"fixed": "固定"
|
||||||
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"title": "侧边栏",
|
"title": "侧边栏",
|
||||||
"width": "宽度",
|
"width": "宽度",
|
||||||
|
|
Loading…
Reference in New Issue