Merge branch 'main' into fix/lint
commit
00990d9453
|
|
@ -226,6 +226,7 @@ watch(
|
||||||
description="ann.vben@gmail.com"
|
description="ann.vben@gmail.com"
|
||||||
tag-text="Pro"
|
tag-text="Pro"
|
||||||
@logout="handleLogout"
|
@logout="handleLogout"
|
||||||
|
@clear-preferences-and-logout="handleLogout"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #notification>
|
<template #notification>
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,7 @@ watch(
|
||||||
description="ann.vben@gmail.com"
|
description="ann.vben@gmail.com"
|
||||||
tag-text="Pro"
|
tag-text="Pro"
|
||||||
@logout="handleLogout"
|
@logout="handleLogout"
|
||||||
|
@clear-preferences-and-logout="handleLogout"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #notification>
|
<template #notification>
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,7 @@ watch(
|
||||||
description="ann.vben@gmail.com"
|
description="ann.vben@gmail.com"
|
||||||
tag-text="Pro"
|
tag-text="Pro"
|
||||||
@logout="handleLogout"
|
@logout="handleLogout"
|
||||||
|
@clear-preferences-and-logout="handleLogout"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #notification>
|
<template #notification>
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,7 @@ watch(
|
||||||
description="ann.vben@gmail.com"
|
description="ann.vben@gmail.com"
|
||||||
tag-text="Pro"
|
tag-text="Pro"
|
||||||
@logout="handleLogout"
|
@logout="handleLogout"
|
||||||
|
@clear-preferences-and-logout="handleLogout"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #notification>
|
<template #notification>
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,7 @@ watch(
|
||||||
description="ann.vben@gmail.com"
|
description="ann.vben@gmail.com"
|
||||||
tag-text="Pro"
|
tag-text="Pro"
|
||||||
@logout="handleLogout"
|
@logout="handleLogout"
|
||||||
|
@clear-preferences-and-logout="handleLogout"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #notification>
|
<template #notification>
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,17 @@ type LayoutType =
|
||||||
type ThemeModeType = 'auto' | 'dark' | 'light';
|
type ThemeModeType = 'auto' | 'dark' | 'light';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 偏好设置按钮位置
|
* 按钮位置
|
||||||
|
* user-dropdown 用户的下拉弹出框中
|
||||||
* fixed 固定在右侧
|
* fixed 固定在右侧
|
||||||
* header 顶栏
|
* header 顶栏
|
||||||
* auto 自动
|
* auto 自动
|
||||||
*/
|
*/
|
||||||
type PreferencesButtonPositionType = 'auto' | 'fixed' | 'header';
|
type PreferencesButtonPositionType =
|
||||||
|
| 'auto'
|
||||||
|
| 'fixed'
|
||||||
|
| 'header'
|
||||||
|
| 'user-dropdown';
|
||||||
|
|
||||||
type BuiltinThemeType =
|
type BuiltinThemeType =
|
||||||
| 'custom'
|
| 'custom'
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj
|
||||||
"loginExpiredMode": "page",
|
"loginExpiredMode": "page",
|
||||||
"name": "Vben Admin",
|
"name": "Vben Admin",
|
||||||
"preferencesButtonPosition": "auto",
|
"preferencesButtonPosition": "auto",
|
||||||
|
"timezone": "Asia/Shanghai",
|
||||||
"watermark": false,
|
"watermark": false,
|
||||||
"watermarkContent": "",
|
"watermarkContent": "",
|
||||||
"zIndex": 200,
|
"zIndex": 200,
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ const defaultPreferences: Preferences = {
|
||||||
loginExpiredMode: 'page',
|
loginExpiredMode: 'page',
|
||||||
name: 'Vben Admin',
|
name: 'Vben Admin',
|
||||||
preferencesButtonPosition: 'auto',
|
preferencesButtonPosition: 'auto',
|
||||||
|
timezone: 'Asia/Shanghai',
|
||||||
watermark: false,
|
watermark: false,
|
||||||
watermarkContent: '',
|
watermarkContent: '',
|
||||||
zIndex: 200,
|
zIndex: 200,
|
||||||
|
|
|
||||||
|
|
@ -124,19 +124,19 @@ class PreferenceManager {
|
||||||
// 使用命名空间初始化存储管理器
|
// 使用命名空间初始化存储管理器
|
||||||
this.cache = new StorageManager({ prefix: namespace });
|
this.cache = new StorageManager({ prefix: namespace });
|
||||||
|
|
||||||
// 合并初始偏好设置
|
// 合并初始偏好设置:前面的对象优先,后面的对象仅补齐缺失字段
|
||||||
this.initialPreferences = merge({}, overrides, defaultPreferences);
|
this.initialPreferences = merge({}, overrides, defaultPreferences);
|
||||||
this.customPreferencesExtension = extension ?? null;
|
this.customPreferencesExtension = extension ?? null;
|
||||||
this.initialCustomPreferences = this.resolveCustomPreferencesDefaults(
|
this.initialCustomPreferences = this.resolveCustomPreferencesDefaults(
|
||||||
this.customPreferencesExtension,
|
this.customPreferencesExtension,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 加载缓存的偏好设置并与初始配置合并
|
// 加载缓存的偏好设置,并仅用缓存补齐初始化配置中未显式设置的字段
|
||||||
const cachedPreferences = (await this.loadFromCache()) || {};
|
const cachedPreferences = (await this.loadFromCache()) || {};
|
||||||
const mergedPreference = merge(
|
const mergedPreference = merge(
|
||||||
{},
|
{},
|
||||||
|
this.initialPreferences, // 初始化配置优先,缓存仅补齐缺失字段
|
||||||
cachedPreferences,
|
cachedPreferences,
|
||||||
this.initialPreferences,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// 更新偏好设置
|
// 更新偏好设置
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,10 @@ interface AppPreferences {
|
||||||
name: string;
|
name: string;
|
||||||
/** 偏好设置按钮位置 */
|
/** 偏好设置按钮位置 */
|
||||||
preferencesButtonPosition: PreferencesButtonPositionType;
|
preferencesButtonPosition: PreferencesButtonPositionType;
|
||||||
|
/**
|
||||||
|
* @zh_CN 应用时区
|
||||||
|
*/
|
||||||
|
timezone: string;
|
||||||
/**
|
/**
|
||||||
* @zh_CN 是否开启水印
|
* @zh_CN 是否开启水印
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -195,12 +195,12 @@ function usePreferences() {
|
||||||
*/
|
*/
|
||||||
const preferencesButtonPosition = computed(() => {
|
const preferencesButtonPosition = computed(() => {
|
||||||
const { enablePreferences, preferencesButtonPosition } = preferences.app;
|
const { enablePreferences, preferencesButtonPosition } = preferences.app;
|
||||||
|
|
||||||
// 如果没有启用偏好设置按钮
|
// 如果没有启用偏好设置按钮
|
||||||
if (!enablePreferences) {
|
if (!enablePreferences) {
|
||||||
return {
|
return {
|
||||||
fixed: false,
|
fixed: false,
|
||||||
header: false,
|
header: false,
|
||||||
|
userDropdown: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,12 +211,15 @@ function usePreferences() {
|
||||||
const contentIsMaximize = headerHidden && sidebarHidden;
|
const contentIsMaximize = headerHidden && sidebarHidden;
|
||||||
|
|
||||||
const isHeaderPosition = preferencesButtonPosition === 'header';
|
const isHeaderPosition = preferencesButtonPosition === 'header';
|
||||||
|
const isUserDropdownPosition =
|
||||||
|
preferencesButtonPosition === 'user-dropdown';
|
||||||
|
|
||||||
// 如果设置了固定位置
|
// 如果设置了固定位置
|
||||||
if (preferencesButtonPosition !== 'auto') {
|
if (preferencesButtonPosition !== 'auto') {
|
||||||
return {
|
return {
|
||||||
fixed: preferencesButtonPosition === 'fixed',
|
fixed: preferencesButtonPosition === 'fixed',
|
||||||
header: isHeaderPosition,
|
header: isHeaderPosition,
|
||||||
|
userDropdown: isUserDropdownPosition,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -230,6 +233,7 @@ function usePreferences() {
|
||||||
return {
|
return {
|
||||||
fixed,
|
fixed,
|
||||||
header: !fixed,
|
header: !fixed,
|
||||||
|
userDropdown: !fixed && isUserDropdownPosition,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,52 +33,61 @@ withDefaults(defineProps<Props>(), {
|
||||||
|
|
||||||
const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
|
const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
|
||||||
|
|
||||||
const REFERENCE_VALUE = 50;
|
const REFERENCE_VALUE = 100;
|
||||||
|
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
const { globalSearchShortcutKey, preferencesButtonPosition } = usePreferences();
|
const { globalSearchShortcutKey, preferencesButtonPosition } = usePreferences();
|
||||||
const slots = useSlots();
|
const slots = useSlots();
|
||||||
const { refresh } = useRefresh();
|
const { refresh } = useRefresh();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插槽列表类型
|
||||||
|
*/
|
||||||
|
type SlotItem = { index: number; name: string };
|
||||||
|
|
||||||
const rightSlots = computed(() => {
|
const rightSlots = computed(() => {
|
||||||
const list = [{ index: REFERENCE_VALUE + 100, name: 'user-dropdown' }];
|
const list: Array<SlotItem> = [];
|
||||||
|
// 全局搜索
|
||||||
if (preferences.widget.globalSearch) {
|
if (preferences.widget.globalSearch) {
|
||||||
list.push({
|
list.push({
|
||||||
index: REFERENCE_VALUE,
|
index: REFERENCE_VALUE,
|
||||||
name: 'global-search',
|
name: 'global-search',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 偏好设置快捷功能
|
||||||
if (preferencesButtonPosition.value.header) {
|
if (preferencesButtonPosition.value.header) {
|
||||||
list.push({
|
list.push({
|
||||||
index: REFERENCE_VALUE + 10,
|
index: REFERENCE_VALUE + 10,
|
||||||
name: 'preferences',
|
name: 'preferences',
|
||||||
});
|
});
|
||||||
|
// 将偏好设置中的子功能分组到同一个按钮位置控制逻辑下
|
||||||
|
if (preferences.widget.themeToggle) {
|
||||||
|
list.push({
|
||||||
|
index: REFERENCE_VALUE + 20,
|
||||||
|
name: 'theme-toggle',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (preferences.widget.languageToggle) {
|
||||||
|
list.push({
|
||||||
|
index: REFERENCE_VALUE + 30,
|
||||||
|
name: 'language-toggle',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (preferences.widget.timezone) {
|
||||||
|
list.push({
|
||||||
|
index: REFERENCE_VALUE + 40,
|
||||||
|
name: 'timezone',
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (preferences.widget.themeToggle) {
|
// 全屏
|
||||||
list.push({
|
|
||||||
index: REFERENCE_VALUE + 20,
|
|
||||||
name: 'theme-toggle',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (preferences.widget.languageToggle) {
|
|
||||||
list.push({
|
|
||||||
index: REFERENCE_VALUE + 30,
|
|
||||||
name: 'language-toggle',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (preferences.widget.timezone) {
|
|
||||||
list.push({
|
|
||||||
index: REFERENCE_VALUE + 40,
|
|
||||||
name: 'timezone',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (preferences.widget.fullscreen) {
|
if (preferences.widget.fullscreen) {
|
||||||
list.push({
|
list.push({
|
||||||
index: REFERENCE_VALUE + 50,
|
index: REFERENCE_VALUE + 50,
|
||||||
name: 'fullscreen',
|
name: 'fullscreen',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 消息通知
|
||||||
if (preferences.widget.notification) {
|
if (preferences.widget.notification) {
|
||||||
list.push({
|
list.push({
|
||||||
index: REFERENCE_VALUE + 60,
|
index: REFERENCE_VALUE + 60,
|
||||||
|
|
@ -87,17 +96,24 @@ const rightSlots = computed(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(slots).forEach((key) => {
|
Object.keys(slots).forEach((key) => {
|
||||||
const name = key.split('-');
|
// 适配插槽名称,例如第一个插槽名:header-right-1
|
||||||
if (key.startsWith('header-right')) {
|
if (key.startsWith('header-right')) {
|
||||||
list.push({ index: Number(name[2]), name: key });
|
// 取第三个占位的数字,若是第三个占位不是数字,则自动分配排序索引
|
||||||
|
const slotIndex = Number(key.split('-')[2]);
|
||||||
|
const index = Number.isNaN(slotIndex) ? nextIndex(list) : slotIndex;
|
||||||
|
list.push({ index, name: key });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// 最后追加用户下拉框,若是索引值超过1000时则固定在1000(适配用户按钮不在最后的场景)
|
||||||
|
const userDropdownIndex = Math.min(1000, nextIndex(list));
|
||||||
|
list.push({ index: userDropdownIndex, name: 'user-dropdown' });
|
||||||
|
// 按照索引排序,保证插槽顺序
|
||||||
return list.toSorted((a, b) => a.index - b.index);
|
return list.toSorted((a, b) => a.index - b.index);
|
||||||
});
|
});
|
||||||
|
|
||||||
const leftSlots = computed(() => {
|
const leftSlots = computed(() => {
|
||||||
const list: Array<{ index: number; name: string }> = [];
|
const list: Array<SlotItem> = [];
|
||||||
|
// 刷新
|
||||||
if (preferences.widget.refresh) {
|
if (preferences.widget.refresh) {
|
||||||
list.push({
|
list.push({
|
||||||
index: 0,
|
index: 0,
|
||||||
|
|
@ -106,14 +122,28 @@ const leftSlots = computed(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(slots).forEach((key) => {
|
Object.keys(slots).forEach((key) => {
|
||||||
const name = key.split('-');
|
// 适配插槽名称,例如第一个插槽名:header-left-1
|
||||||
if (key.startsWith('header-left')) {
|
if (key.startsWith('header-left')) {
|
||||||
list.push({ index: Number(name[2]), name: key });
|
// 取第三个占位的数字,若是第三个占位不是数字,则自动分配排序索引
|
||||||
|
const slotIndex = Number(key.split('-')[2]);
|
||||||
|
const index = Number.isNaN(slotIndex) ? nextIndex(list) : slotIndex;
|
||||||
|
list.push({ index, name: key });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// 按照索引排序,保证插槽顺序
|
||||||
return list.toSorted((a, b) => a.index - b.index);
|
return list.toSorted((a, b) => a.index - b.index);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取列表下一个索引值(用于排序)
|
||||||
|
* @param list 列表
|
||||||
|
*/
|
||||||
|
function nextIndex(list: Array<SlotItem>) {
|
||||||
|
const index =
|
||||||
|
list.length > 0 ? Math.max(...list.map((item) => item.index)) : 0;
|
||||||
|
return index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
function clearPreferencesAndLogout() {
|
function clearPreferencesAndLogout() {
|
||||||
emit('clearPreferencesAndLogout');
|
emit('clearPreferencesAndLogout');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref, unref } from 'vue';
|
||||||
|
|
||||||
import { SUPPORT_LANGUAGES } from '@vben/constants';
|
import { SUPPORT_LANGUAGES } from '@vben/constants';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
import { useTimezoneStore } from '@vben/stores';
|
||||||
|
|
||||||
import InputItem from '../input-item.vue';
|
import InputItem from '../input-item.vue';
|
||||||
import SelectItem from '../select-item.vue';
|
import SelectItem from '../select-item.vue';
|
||||||
|
|
@ -11,6 +14,7 @@ defineOptions({
|
||||||
});
|
});
|
||||||
|
|
||||||
const appLocale = defineModel<string>('appLocale');
|
const appLocale = defineModel<string>('appLocale');
|
||||||
|
const appTimezone = defineModel<string>('appTimezone');
|
||||||
const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
|
const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
|
||||||
const appWatermark = defineModel<boolean>('appWatermark');
|
const appWatermark = defineModel<boolean>('appWatermark');
|
||||||
const appWatermarkContent = defineModel<string>('appWatermarkContent');
|
const appWatermarkContent = defineModel<string>('appWatermarkContent');
|
||||||
|
|
@ -18,12 +22,32 @@ const appEnableCheckUpdates = defineModel<boolean>('appEnableCheckUpdates');
|
||||||
const appEnableCopyPreferences = defineModel<boolean>(
|
const appEnableCopyPreferences = defineModel<boolean>(
|
||||||
'appEnableCopyPreferences',
|
'appEnableCopyPreferences',
|
||||||
);
|
);
|
||||||
|
const timezoneStore = useTimezoneStore();
|
||||||
|
|
||||||
|
const timezoneOptionsRef = ref<
|
||||||
|
{
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
timezoneOptionsRef.value = await timezoneStore.getTimezoneOptions();
|
||||||
|
// 获取当前时区,例如:Asia/Shanghai
|
||||||
|
const timezoneValue = unref(timezoneStore.timezone);
|
||||||
|
if (timezoneValue) {
|
||||||
|
appTimezone.value = timezoneValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<SelectItem v-model="appLocale" :items="SUPPORT_LANGUAGES">
|
<SelectItem v-model="appLocale" :items="SUPPORT_LANGUAGES">
|
||||||
{{ $t('preferences.language') }}
|
{{ $t('preferences.language') }}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
|
<SelectItem v-model="appTimezone" :items="timezoneOptionsRef">
|
||||||
|
{{ $t('preferences.timezone') }}
|
||||||
|
</SelectItem>
|
||||||
<SwitchItem v-model="appDynamicTitle">
|
<SwitchItem v-model="appDynamicTitle">
|
||||||
{{ $t('preferences.dynamicTitle') }}
|
{{ $t('preferences.dynamicTitle') }}
|
||||||
</SwitchItem>
|
</SwitchItem>
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,10 @@ const positionItems = computed((): SelectOption[] => [
|
||||||
label: $t('preferences.position.fixed'),
|
label: $t('preferences.position.fixed'),
|
||||||
value: 'fixed',
|
value: 'fixed',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: $t('preferences.position.userDropdown'),
|
||||||
|
value: 'user-dropdown',
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
|
||||||
const message = globalShareState.getMessage();
|
const message = globalShareState.getMessage();
|
||||||
|
|
||||||
const appLocale = defineModel<SupportedLanguagesType>('appLocale');
|
const appLocale = defineModel<SupportedLanguagesType>('appLocale');
|
||||||
|
const appTimezone = defineModel<string>('appTimezone');
|
||||||
const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
|
const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
|
||||||
const appLayout = defineModel<LayoutType>('appLayout');
|
const appLayout = defineModel<LayoutType>('appLayout');
|
||||||
const appColorGrayMode = defineModel<boolean>('appColorGrayMode');
|
const appColorGrayMode = defineModel<boolean>('appColorGrayMode');
|
||||||
|
|
@ -359,6 +360,7 @@ function handleCustomPreferencesUpdate(updates: CustomPreferencesRecord) {
|
||||||
v-model:app-enable-check-updates="appEnableCheckUpdates"
|
v-model:app-enable-check-updates="appEnableCheckUpdates"
|
||||||
v-model:app-enable-copy-preferences="appEnableCopyPreferences"
|
v-model:app-enable-copy-preferences="appEnableCopyPreferences"
|
||||||
v-model:app-locale="appLocale"
|
v-model:app-locale="appLocale"
|
||||||
|
v-model:app-timezone="appTimezone"
|
||||||
v-model:app-watermark="appWatermark"
|
v-model:app-watermark="appWatermark"
|
||||||
v-model:app-watermark-content="appWatermarkContent"
|
v-model:app-watermark-content="appWatermarkContent"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,26 @@ import { VbenButton } from '@vben-core/shadcn-ui';
|
||||||
|
|
||||||
import PreferencesDrawer from './preferences-drawer.vue';
|
import PreferencesDrawer from './preferences-drawer.vue';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
/** 是否显示按钮 */
|
||||||
|
showButton?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
showButton: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
|
||||||
|
|
||||||
const [Drawer, drawerApi] = useVbenDrawer({
|
const [Drawer, drawerApi] = useVbenDrawer({
|
||||||
connectedComponent: PreferencesDrawer,
|
connectedComponent: PreferencesDrawer,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 暴露打开抽屉的方法
|
||||||
|
defineExpose({
|
||||||
|
open: () => drawerApi.open(),
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* preferences 转成 vue props
|
* preferences 转成 vue props
|
||||||
* preferences.widget.fullscreen=>widgetFullscreen
|
* preferences.widget.fullscreen=>widgetFullscreen
|
||||||
|
|
@ -56,17 +72,22 @@ const listen = computed(() => {
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<Drawer v-bind="{ ...$attrs, ...attrs }" v-on="listen" />
|
<Drawer
|
||||||
|
v-bind="{ ...$attrs, ...attrs }"
|
||||||
|
v-on="listen"
|
||||||
|
@clear-preferences-and-logout="emit('clearPreferencesAndLogout')"
|
||||||
|
/>
|
||||||
|
|
||||||
<div @click="() => drawerApi.open()">
|
<!-- 触发打开抽屉的按钮(可覆盖) -->
|
||||||
<slot>
|
<slot>
|
||||||
<VbenButton
|
<VbenButton
|
||||||
:title="$t('preferences.title')"
|
v-if="props.showButton"
|
||||||
class="flex-col-center size-10 cursor-pointer rounded-l-lg rounded-r-none border-none bg-primary"
|
:title="$t('preferences.title')"
|
||||||
>
|
class="flex-col-center size-10 cursor-pointer rounded-l-lg rounded-r-none border-none bg-primary"
|
||||||
<Settings class="size-5" />
|
@click="() => drawerApi.open()"
|
||||||
</VbenButton>
|
>
|
||||||
</slot>
|
<Settings class="size-5" />
|
||||||
</div>
|
</VbenButton>
|
||||||
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import type { AnyFunction } from '@vben/types';
|
||||||
import { computed, useTemplateRef, watch } from 'vue';
|
import { computed, useTemplateRef, watch } from 'vue';
|
||||||
|
|
||||||
import { useHoverToggle } from '@vben/hooks';
|
import { useHoverToggle } from '@vben/hooks';
|
||||||
import { LockKeyhole, LogOut } from '@vben/icons';
|
import { LockKeyhole, LogOut, Settings } from '@vben/icons';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
import { preferences, usePreferences } from '@vben/preferences';
|
import { preferences, usePreferences } from '@vben/preferences';
|
||||||
import { useAccessStore } from '@vben/stores';
|
import { useAccessStore } from '@vben/stores';
|
||||||
|
|
@ -29,6 +29,7 @@ import {
|
||||||
import { useMagicKeys, whenever } from '@vueuse/core';
|
import { useMagicKeys, whenever } from '@vueuse/core';
|
||||||
|
|
||||||
import { LockScreenModal } from '../lock-screen';
|
import { LockScreenModal } from '../lock-screen';
|
||||||
|
import { Preferences } from '../preferences';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/**
|
/**
|
||||||
|
|
@ -82,10 +83,13 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
hoverDelay: 500,
|
hoverDelay: 500,
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{ logout: [] }>();
|
const emit = defineEmits<{ clearPreferencesAndLogout: []; logout: [] }>();
|
||||||
|
|
||||||
const { globalLockScreenShortcutKey, globalLogoutShortcutKey } =
|
const {
|
||||||
usePreferences();
|
globalLockScreenShortcutKey,
|
||||||
|
globalLogoutShortcutKey,
|
||||||
|
preferencesButtonPosition,
|
||||||
|
} = usePreferences();
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
const [LockModal, lockModalApi] = useVbenModal({
|
const [LockModal, lockModalApi] = useVbenModal({
|
||||||
connectedComponent: LockScreenModal,
|
connectedComponent: LockScreenModal,
|
||||||
|
|
@ -98,6 +102,7 @@ const [LogoutModal, logoutModalApi] = useVbenModal({
|
||||||
|
|
||||||
const refTrigger = useTemplateRef('refTrigger');
|
const refTrigger = useTemplateRef('refTrigger');
|
||||||
const refContent = useTemplateRef('refContent');
|
const refContent = useTemplateRef('refContent');
|
||||||
|
const refPreferences = useTemplateRef('refPreferences');
|
||||||
const [openPopover, hoverWatcher] = useHoverToggle(
|
const [openPopover, hoverWatcher] = useHoverToggle(
|
||||||
[refTrigger, refContent],
|
[refTrigger, refContent],
|
||||||
() => props.hoverDelay,
|
() => props.hoverDelay,
|
||||||
|
|
@ -151,6 +156,11 @@ function handleSubmitLogout() {
|
||||||
logoutModalApi.close();
|
logoutModalApi.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置 - 打开偏好设置抽屉
|
||||||
|
function handleOpenSettings() {
|
||||||
|
refPreferences.value?.open();
|
||||||
|
}
|
||||||
|
|
||||||
if (enableShortcutKey.value) {
|
if (enableShortcutKey.value) {
|
||||||
const keys = useMagicKeys();
|
const keys = useMagicKeys();
|
||||||
const logoutKey = keys['Alt+KeyQ'];
|
const logoutKey = keys['Alt+KeyQ'];
|
||||||
|
|
@ -195,6 +205,13 @@ if (enableShortcutKey.value) {
|
||||||
{{ $t('ui.widgets.logoutTip') }}
|
{{ $t('ui.widgets.logoutTip') }}
|
||||||
</LogoutModal>
|
</LogoutModal>
|
||||||
|
|
||||||
|
<Preferences
|
||||||
|
v-if="preferencesButtonPosition.userDropdown"
|
||||||
|
ref="refPreferences"
|
||||||
|
:show-button="false"
|
||||||
|
@clear-preferences-and-logout="emit('clearPreferencesAndLogout')"
|
||||||
|
/>
|
||||||
|
|
||||||
<DropdownMenu v-model:open="openPopover">
|
<DropdownMenu v-model:open="openPopover">
|
||||||
<DropdownMenuTrigger ref="refTrigger" :disabled="props.trigger === 'hover'">
|
<DropdownMenuTrigger ref="refTrigger" :disabled="props.trigger === 'hover'">
|
||||||
<div class="mr-2 ml-1 cursor-pointer rounded-full p-1.5 hover:bg-accent">
|
<div class="mr-2 ml-1 cursor-pointer rounded-full p-1.5 hover:bg-accent">
|
||||||
|
|
@ -241,6 +258,14 @@ if (enableShortcutKey.value) {
|
||||||
{{ menu.text }}
|
{{ menu.text }}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem
|
||||||
|
v-if="preferencesButtonPosition.userDropdown"
|
||||||
|
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"
|
||||||
|
@click="handleOpenSettings"
|
||||||
|
>
|
||||||
|
<Settings class="mr-2 size-4" />
|
||||||
|
{{ $t('preferences.title') }}
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
v-if="preferences.widget.lockScreen"
|
v-if="preferences.widget.lockScreen"
|
||||||
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"
|
class="mx-1 flex cursor-pointer items-center rounded-sm py-1 leading-8"
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@
|
||||||
"mode": "Mode",
|
"mode": "Mode",
|
||||||
"general": "General",
|
"general": "General",
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
|
"timezone": "Timezone",
|
||||||
"dynamicTitle": "Dynamic Title",
|
"dynamicTitle": "Dynamic Title",
|
||||||
"watermark": "Watermark",
|
"watermark": "Watermark",
|
||||||
"watermarkContent": "Please input Watermark content",
|
"watermarkContent": "Please input Watermark content",
|
||||||
|
|
@ -46,7 +47,8 @@
|
||||||
"title": "Preferences Postion",
|
"title": "Preferences Postion",
|
||||||
"header": "Header",
|
"header": "Header",
|
||||||
"auto": "Auto",
|
"auto": "Auto",
|
||||||
"fixed": "Fixed"
|
"fixed": "Fixed",
|
||||||
|
"userDropdown": "User Dropdown"
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"buttons": "Show Buttons",
|
"buttons": "Show Buttons",
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@
|
||||||
},
|
},
|
||||||
"lockScreen": {
|
"lockScreen": {
|
||||||
"title": "Lock Screen",
|
"title": "Lock Screen",
|
||||||
"screenButton": "Locking",
|
"screenButton": "Unlock",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"placeholder": "Please enter password",
|
"placeholder": "Please enter password",
|
||||||
"unlock": "Click to unlock",
|
"unlock": "Click to unlock",
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@
|
||||||
"mode": "模式",
|
"mode": "模式",
|
||||||
"general": "通用",
|
"general": "通用",
|
||||||
"language": "语言",
|
"language": "语言",
|
||||||
|
"timezone": "时区",
|
||||||
"dynamicTitle": "动态标题",
|
"dynamicTitle": "动态标题",
|
||||||
"watermark": "水印",
|
"watermark": "水印",
|
||||||
"watermarkContent": "请输入水印文案",
|
"watermarkContent": "请输入水印文案",
|
||||||
|
|
@ -46,7 +47,8 @@
|
||||||
"title": "偏好设置位置",
|
"title": "偏好设置位置",
|
||||||
"header": "顶栏",
|
"header": "顶栏",
|
||||||
"auto": "自动",
|
"auto": "自动",
|
||||||
"fixed": "固定"
|
"fixed": "固定",
|
||||||
|
"userDropdown": "用户下拉窗"
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"buttons": "显示按钮",
|
"buttons": "显示按钮",
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@
|
||||||
},
|
},
|
||||||
"lockScreen": {
|
"lockScreen": {
|
||||||
"title": "锁定屏幕",
|
"title": "锁定屏幕",
|
||||||
"screenButton": "锁定",
|
"screenButton": "解锁",
|
||||||
"password": "密码",
|
"password": "密码",
|
||||||
"placeholder": "请输入锁屏密码",
|
"placeholder": "请输入锁屏密码",
|
||||||
"unlock": "点击解锁",
|
"unlock": "点击解锁",
|
||||||
|
|
|
||||||
|
|
@ -251,6 +251,7 @@ onBeforeMount(() => {
|
||||||
tag-text="Pro"
|
tag-text="Pro"
|
||||||
trigger="both"
|
trigger="both"
|
||||||
@logout="handleLogout"
|
@logout="handleLogout"
|
||||||
|
@clear-preferences-and-logout="handleLogout"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #notification>
|
<template #notification>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue