8 changed files with 451 additions and 607 deletions
@ -1 +1,2 @@ |
|||
export { default as PreferencesWidget } from './preferences-widget.vue'; |
|||
export { default as Preferences } from './preferences.vue'; |
|||
export * from './use-open-preferences'; |
|||
|
|||
@ -0,0 +1,405 @@ |
|||
<script setup lang="ts"> |
|||
import type { |
|||
BuiltinThemeType, |
|||
ContentCompactType, |
|||
LayoutHeaderModeType, |
|||
LayoutType, |
|||
SupportedLanguagesType, |
|||
ThemeModeType, |
|||
} from '@vben/types'; |
|||
import type { |
|||
BreadcrumbStyleType, |
|||
NavigationStyleType, |
|||
} from '@vben-core/preferences'; |
|||
import type { SegmentedItem } from '@vben-core/shadcn-ui'; |
|||
|
|||
import { computed, ref } from 'vue'; |
|||
|
|||
import { IcRoundFolderCopy, IcRoundRestartAlt } from '@vben-core/iconify'; |
|||
import { $t, loadLocaleMessages } from '@vben-core/locales'; |
|||
import { |
|||
clearPreferencesCache, |
|||
preferences, |
|||
resetPreferences, |
|||
usePreferences, |
|||
} from '@vben-core/preferences'; |
|||
import { |
|||
VbenButton, |
|||
VbenIconButton, |
|||
VbenSegmented, |
|||
VbenSheet, |
|||
useToast, |
|||
} from '@vben-core/shadcn-ui'; |
|||
|
|||
import { useClipboard } from '@vueuse/core'; |
|||
|
|||
import { |
|||
Animation, |
|||
Block, |
|||
Breadcrumb, |
|||
BuiltinTheme, |
|||
ColorMode, |
|||
Content, |
|||
Copyright, |
|||
Footer, |
|||
General, |
|||
GlobalShortcutKeys, |
|||
Header, |
|||
Layout, |
|||
Navigation, |
|||
Radius, |
|||
Sidebar, |
|||
Tabbar, |
|||
Theme, |
|||
} from './blocks'; |
|||
import IconSetting from './icons/setting.vue'; |
|||
import { useOpenPreferences } from './use-open-preferences'; |
|||
|
|||
const emit = defineEmits<{ clearPreferencesAndLogout: [] }>(); |
|||
const { toast } = useToast(); |
|||
const appLocale = defineModel<SupportedLanguagesType>('appLocale'); |
|||
const appDynamicTitle = defineModel<boolean>('appDynamicTitle'); |
|||
const appAiAssistant = defineModel<boolean>('appAiAssistant'); |
|||
const appLayout = defineModel<LayoutType>('appLayout'); |
|||
const appColorGrayMode = defineModel<boolean>('appColorGrayMode'); |
|||
const appColorWeakMode = defineModel<boolean>('appColorWeakMode'); |
|||
const appSemiDarkMenu = defineModel<boolean>('appSemiDarkMenu'); |
|||
const appContentCompact = defineModel<ContentCompactType>('appContentCompact'); |
|||
|
|||
const transitionProgress = defineModel<boolean>('transitionProgress'); |
|||
const transitionName = defineModel<string>('transitionName'); |
|||
const transitionLoading = defineModel<boolean>('transitionLoading'); |
|||
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 sidebarWidth = defineModel<number>('sidebarWidth'); |
|||
const sidebarCollapsed = defineModel<boolean>('sidebarCollapsed'); |
|||
const sidebarCollapsedShowTitle = defineModel<boolean>( |
|||
'sidebarCollapsedShowTitle', |
|||
); |
|||
|
|||
const headerEnable = defineModel<boolean>('headerEnable'); |
|||
const headerMode = defineModel<LayoutHeaderModeType>('headerMode'); |
|||
|
|||
const breadcrumbEnable = defineModel<boolean>('breadcrumbEnable'); |
|||
const breadcrumbShowIcon = defineModel<boolean>('breadcrumbShowIcon'); |
|||
const breadcrumbShowHome = defineModel<boolean>('breadcrumbShowHome'); |
|||
const breadcrumbStyleType = defineModel<BreadcrumbStyleType>( |
|||
'breadcrumbStyleType', |
|||
); |
|||
const breadcrumbHideOnlyOne = defineModel<boolean>('breadcrumbHideOnlyOne'); |
|||
|
|||
const tabbarEnable = defineModel<boolean>('tabbarEnable'); |
|||
const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon'); |
|||
|
|||
const navigationStyleType = defineModel<NavigationStyleType>( |
|||
'navigationStyleType', |
|||
); |
|||
const navigationSplit = defineModel<boolean>('navigationSplit'); |
|||
const navigationAccordion = defineModel<boolean>('navigationAccordion'); |
|||
|
|||
// const logoVisible = defineModel<boolean>('logoVisible'); |
|||
|
|||
const footerEnable = defineModel<boolean>('footerEnable'); |
|||
const footerFixed = defineModel<boolean>('footerFixed'); |
|||
|
|||
const copyrightEnable = defineModel<boolean>('copyrightEnable'); |
|||
const copyrightCompanyName = defineModel<string>('copyrightCompanyName'); |
|||
const copyrightCompanySiteLink = defineModel<string>( |
|||
'copyrightCompanySiteLink', |
|||
); |
|||
const copyrightDate = defineModel<string>('copyrightDate'); |
|||
const copyrightIcp = defineModel<string>('copyrightIcp'); |
|||
const copyrightIcpLink = defineModel<string>('copyrightIcpLink'); |
|||
|
|||
const shortcutKeysEnable = defineModel<boolean>('shortcutKeysEnable'); |
|||
const shortcutKeysGlobalSearch = defineModel<boolean>( |
|||
'shortcutKeysGlobalSearch', |
|||
); |
|||
const shortcutKeysGlobalLogout = defineModel<boolean>( |
|||
'shortcutKeysGlobalLogout', |
|||
); |
|||
const shortcutKeysGlobalPreferences = defineModel<boolean>( |
|||
'shortcutKeysGlobalPreferences', |
|||
); |
|||
|
|||
const { |
|||
diffPreference, |
|||
isDark, |
|||
isFullContent, |
|||
isHeaderNav, |
|||
isMixedNav, |
|||
isSideMixedNav, |
|||
isSideMode, |
|||
isSideNav, |
|||
} = usePreferences(); |
|||
const { copy } = useClipboard(); |
|||
|
|||
const activeTab = ref('appearance'); |
|||
|
|||
const tabs = computed((): SegmentedItem[] => { |
|||
return [ |
|||
{ |
|||
label: $t('preferences.appearance'), |
|||
value: 'appearance', |
|||
}, |
|||
{ |
|||
label: $t('preferences.layout'), |
|||
value: 'layout', |
|||
}, |
|||
{ |
|||
label: $t('preferences.shortcut-keys.title'), |
|||
value: 'shortcutKey', |
|||
}, |
|||
{ |
|||
label: $t('preferences.general'), |
|||
value: 'general', |
|||
}, |
|||
]; |
|||
}); |
|||
|
|||
const showBreadcrumbConfig = computed(() => { |
|||
return ( |
|||
!isFullContent.value && |
|||
!isMixedNav.value && |
|||
!isHeaderNav.value && |
|||
preferences.header.enable |
|||
); |
|||
}); |
|||
|
|||
const { openPreferences } = useOpenPreferences(); |
|||
|
|||
async function handleCopy() { |
|||
await copy(JSON.stringify(diffPreference.value, null, 2)); |
|||
|
|||
toast({ |
|||
description: $t('preferences.copy'), |
|||
title: $t('preferences.copy-success'), |
|||
}); |
|||
} |
|||
|
|||
async function handleClearCache() { |
|||
resetPreferences(); |
|||
clearPreferencesCache(); |
|||
emit('clearPreferencesAndLogout'); |
|||
} |
|||
|
|||
async function handleReset() { |
|||
if (!diffPreference.value) { |
|||
return; |
|||
} |
|||
resetPreferences(); |
|||
await loadLocaleMessages(preferences.app.locale); |
|||
toast({ |
|||
description: $t('preferences.reset-title'), |
|||
title: $t('preferences.reset-success'), |
|||
}); |
|||
toast({ |
|||
description: $t('preferences.reset-title'), |
|||
title: $t('preferences.reset-success'), |
|||
}); |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="z-100 fixed right-0 top-1/2"> |
|||
<VbenSheet |
|||
v-model:open="openPreferences" |
|||
:description="$t('preferences.subtitle')" |
|||
:title="$t('preferences.title')" |
|||
> |
|||
<template #trigger> |
|||
<VbenButton |
|||
:title="$t('preferences.title')" |
|||
class="bg-primary flex-col-center h-12 w-12 cursor-pointer rounded-l-lg rounded-r-none border-none" |
|||
> |
|||
<IconSetting |
|||
class="duration-3000 fill-primary-foreground animate-spin text-2xl" |
|||
/> |
|||
</VbenButton> |
|||
</template> |
|||
<template #extra> |
|||
<div class="flex items-center"> |
|||
<VbenIconButton |
|||
:disabled="!diffPreference" |
|||
:tooltip="$t('preferences.reset-tip')" |
|||
class="relative" |
|||
> |
|||
<span |
|||
v-if="diffPreference" |
|||
class="bg-primary absolute right-0.5 top-0.5 h-2 w-2 rounded" |
|||
></span> |
|||
<IcRoundRestartAlt class="size-5" @click="handleReset" /> |
|||
</VbenIconButton> |
|||
</div> |
|||
</template> |
|||
|
|||
<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.title')"> |
|||
<Animation |
|||
v-model:transition-enable="transitionEnable" |
|||
v-model:transition-loading="transitionLoading" |
|||
v-model:transition-name="transitionName" |
|||
v-model:transition-progress="transitionProgress" |
|||
/> |
|||
</Block> |
|||
</template> |
|||
<template #appearance> |
|||
<Block :title="$t('preferences.theme.title')"> |
|||
<Theme |
|||
v-model="themeMode" |
|||
v-model:app-semi-dark-menu="appSemiDarkMenu" |
|||
/> |
|||
</Block> |
|||
<!-- <Block :title="$t('preferences.theme-color')"> |
|||
<ThemeColor |
|||
v-model="themeColorPrimary" |
|||
:color-primary-presets="colorPrimaryPresets" |
|||
/> |
|||
</Block> --> |
|||
<Block :title="$t('preferences.theme.builtin.title')"> |
|||
<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 |
|||
v-model:app-color-gray-mode="appColorGrayMode" |
|||
v-model:app-color-weak-mode="appColorWeakMode" |
|||
/> |
|||
</Block> |
|||
</template> |
|||
<template #layout> |
|||
<Block :title="$t('preferences.layout')"> |
|||
<Layout v-model="appLayout" /> |
|||
</Block> |
|||
<Block :title="$t('preferences.content')"> |
|||
<Content v-model="appContentCompact" /> |
|||
</Block> |
|||
|
|||
<Block :title="$t('preferences.sidebar.title')"> |
|||
<Sidebar |
|||
v-model:sidebar-collapsed="sidebarCollapsed" |
|||
v-model:sidebar-collapsed-show-title="sidebarCollapsedShowTitle" |
|||
v-model:sidebar-enable="sidebarEnable" |
|||
v-model:sidebar-width="sidebarWidth" |
|||
:disabled="!isSideMode" |
|||
/> |
|||
</Block> |
|||
|
|||
<Block :title="$t('preferences.header.title')"> |
|||
<Header |
|||
v-model:headerEnable="headerEnable" |
|||
v-model:headerMode="headerMode" |
|||
:disabled="isFullContent" |
|||
/> |
|||
</Block> |
|||
|
|||
<Block :title="$t('preferences.navigation-menu.title')"> |
|||
<Navigation |
|||
v-model:navigation-accordion="navigationAccordion" |
|||
v-model:navigation-split="navigationSplit" |
|||
v-model:navigation-style-type="navigationStyleType" |
|||
:disabled="isFullContent" |
|||
:disabled-navigation-split="!isMixedNav" |
|||
/> |
|||
</Block> |
|||
|
|||
<Block :title="$t('preferences.breadcrumb.title')"> |
|||
<Breadcrumb |
|||
v-model:breadcrumb-enable="breadcrumbEnable" |
|||
v-model:breadcrumb-hide-only-one="breadcrumbHideOnlyOne" |
|||
v-model:breadcrumb-show-home="breadcrumbShowHome" |
|||
v-model:breadcrumb-show-icon="breadcrumbShowIcon" |
|||
v-model:breadcrumb-style-type="breadcrumbStyleType" |
|||
:disabled=" |
|||
!showBreadcrumbConfig || !(isSideNav || isSideMixedNav) |
|||
" |
|||
/> |
|||
</Block> |
|||
|
|||
<Block :title="$t('preferences.tabbar.title')"> |
|||
<Tabbar |
|||
v-model:tabbar-enable="tabbarEnable" |
|||
v-model:tabbar-show-icon="tabbarShowIcon" |
|||
/> |
|||
</Block> |
|||
<Block :title="$t('preferences.footer.title')"> |
|||
<Footer |
|||
v-model:footer-enable="footerEnable" |
|||
v-model:footer-fixed="footerFixed" |
|||
/> |
|||
</Block> |
|||
<Block :title="$t('preferences.copyright.title')"> |
|||
<Copyright |
|||
v-model:copyright-company-name="copyrightCompanyName" |
|||
v-model:copyright-company-site-link="copyrightCompanySiteLink" |
|||
v-model:copyright-date="copyrightDate" |
|||
v-model:copyright-enable="copyrightEnable" |
|||
v-model:copyright-icp="copyrightIcp" |
|||
v-model:copyright-icp-link="copyrightIcpLink" |
|||
:disabled="!footerEnable" |
|||
/> |
|||
</Block> |
|||
</template> |
|||
|
|||
<template #shortcutKey> |
|||
<Block :title="$t('preferences.shortcut-keys.global')"> |
|||
<GlobalShortcutKeys |
|||
v-model:shortcut-keys-enable="shortcutKeysEnable" |
|||
v-model:shortcut-keys-global-search="shortcutKeysGlobalSearch" |
|||
v-model:shortcut-keys-logout="shortcutKeysGlobalLogout" |
|||
v-model:shortcut-keys-preferences=" |
|||
shortcutKeysGlobalPreferences |
|||
" |
|||
/> |
|||
</Block> |
|||
</template> |
|||
</VbenSegmented> |
|||
</div> |
|||
|
|||
<template #footer> |
|||
<VbenButton |
|||
:disabled="!diffPreference" |
|||
class="mx-4 w-full" |
|||
size="sm" |
|||
variant="outline" |
|||
@click="handleClearCache" |
|||
> |
|||
<IcRoundRestartAlt class="mr-2 size-4" /> |
|||
{{ $t('preferences.clear-and-logout') }} |
|||
</VbenButton> |
|||
<VbenButton |
|||
:disabled="!diffPreference" |
|||
class="mr-4 w-full" |
|||
size="sm" |
|||
variant="default" |
|||
@click="handleCopy" |
|||
> |
|||
<IcRoundFolderCopy class="mr-2 size-3" /> |
|||
{{ $t('preferences.copy') }} |
|||
</VbenButton> |
|||
</template> |
|||
</VbenSheet> |
|||
</div> |
|||
</template> |
|||
@ -1,185 +0,0 @@ |
|||
<script lang="ts" setup> |
|||
import { loadLocaleMessages } from '@vben-core/locales'; |
|||
import { preferences, updatePreferences } from '@vben-core/preferences'; |
|||
|
|||
import Preferences from './preferences.vue'; |
|||
</script> |
|||
<template> |
|||
<Preferences |
|||
:app-ai-assistant="preferences.app.aiAssistant" |
|||
:app-color-gray-mode="preferences.app.colorGrayMode" |
|||
:app-color-weak-mode="preferences.app.colorWeakMode" |
|||
:app-content-compact="preferences.app.contentCompact" |
|||
:app-dynamic-title="preferences.app.dynamicTitle" |
|||
:app-layout="preferences.app.layout" |
|||
:app-locale="preferences.app.locale" |
|||
:app-semi-dark-menu="preferences.app.semiDarkMenu" |
|||
:breadcrumb-enable="preferences.breadcrumb.enable" |
|||
:breadcrumb-hide-only-one="preferences.breadcrumb.hideOnlyOne" |
|||
:breadcrumb-show-home="preferences.breadcrumb.showHome" |
|||
:breadcrumb-show-icon="preferences.breadcrumb.showIcon" |
|||
:breadcrumb-style-type="preferences.breadcrumb.styleType" |
|||
:copyright-company-name="preferences.copyright.companyName" |
|||
:copyright-company-site-link="preferences.copyright.companySiteLink" |
|||
:copyright-date="preferences.copyright.date" |
|||
:copyright-enable="preferences.copyright.enable" |
|||
:copyright-icp="preferences.copyright.icp" |
|||
:copyright-icp-link="preferences.copyright.icpLink" |
|||
:footer-enable="preferences.footer.enable" |
|||
:footer-fixed="preferences.footer.fixed" |
|||
:header-enable="preferences.header.enable" |
|||
:header-mode="preferences.header.mode" |
|||
:navigation-accordion="preferences.navigation.accordion" |
|||
:navigation-split="preferences.navigation.split" |
|||
:navigation-style-type="preferences.navigation.styleType" |
|||
:shortcut-keys-enable="preferences.shortcutKeys.enable" |
|||
:shortcut-keys-global-logout="preferences.shortcutKeys.globalLogout" |
|||
:shortcut-keys-global-preferences=" |
|||
preferences.shortcutKeys.globalPreferences |
|||
" |
|||
:shortcut-keys-global-search="preferences.shortcutKeys.globalSearch" |
|||
:sidebar-collapsed="preferences.sidebar.collapsed" |
|||
:sidebar-collapsed-show-title="preferences.sidebar.collapsedShowTitle" |
|||
:sidebar-enable="preferences.sidebar.enable" |
|||
:sidebar-width="preferences.sidebar.width" |
|||
: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-loading="preferences.transition.loading" |
|||
:transition-name="preferences.transition.name" |
|||
:transition-progress="preferences.transition.progress" |
|||
@update:app-ai-assistant=" |
|||
(val) => updatePreferences({ app: { aiAssistant: val } }) |
|||
" |
|||
@update:app-color-gray-mode=" |
|||
(val) => updatePreferences({ app: { colorGrayMode: val } }) |
|||
" |
|||
@update:app-color-weak-mode=" |
|||
(val) => updatePreferences({ app: { colorWeakMode: val } }) |
|||
" |
|||
@update:app-content-compact=" |
|||
(val) => updatePreferences({ app: { contentCompact: val } }) |
|||
" |
|||
@update:app-dynamic-title=" |
|||
(val) => updatePreferences({ app: { dynamicTitle: val } }) |
|||
" |
|||
@update:app-layout="(val) => updatePreferences({ app: { layout: val } })" |
|||
@update:app-locale=" |
|||
(val) => { |
|||
updatePreferences({ app: { locale: val } }); |
|||
loadLocaleMessages(val); |
|||
} |
|||
" |
|||
@update:app-semi-dark-menu=" |
|||
(val) => updatePreferences({ app: { semiDarkMenu: val } }) |
|||
" |
|||
@update:breadcrumb-enable=" |
|||
(val) => updatePreferences({ breadcrumb: { enable: val } }) |
|||
" |
|||
@update:breadcrumb-hide-only-one=" |
|||
(val) => updatePreferences({ breadcrumb: { hideOnlyOne: val } }) |
|||
" |
|||
@update:breadcrumb-show-home=" |
|||
(val) => updatePreferences({ breadcrumb: { showHome: val } }) |
|||
" |
|||
@update:breadcrumb-show-icon=" |
|||
(val) => updatePreferences({ breadcrumb: { showIcon: val } }) |
|||
" |
|||
@update:breadcrumb-style-type=" |
|||
(val) => updatePreferences({ breadcrumb: { styleType: val } }) |
|||
" |
|||
@update:copyright-company-name=" |
|||
(val) => updatePreferences({ copyright: { companyName: val } }) |
|||
" |
|||
@update:copyright-company-site-link=" |
|||
(val) => updatePreferences({ copyright: { companySiteLink: val } }) |
|||
" |
|||
@update:copyright-date=" |
|||
(val) => updatePreferences({ copyright: { date: val } }) |
|||
" |
|||
@update:copyright-enable=" |
|||
(val) => updatePreferences({ copyright: { enable: val } }) |
|||
" |
|||
@update:copyright-icp=" |
|||
(val) => updatePreferences({ copyright: { icp: val } }) |
|||
" |
|||
@update:copyright-icp-link=" |
|||
(val) => updatePreferences({ copyright: { icpLink: val } }) |
|||
" |
|||
@update:footer-enable=" |
|||
(val) => updatePreferences({ footer: { enable: val } }) |
|||
" |
|||
@update:footer-fixed=" |
|||
(val) => updatePreferences({ footer: { fixed: val } }) |
|||
" |
|||
@update:header-enable=" |
|||
(val) => updatePreferences({ header: { enable: val } }) |
|||
" |
|||
@update:header-mode="(val) => updatePreferences({ header: { mode: val } })" |
|||
@update:navigation-accordion=" |
|||
(val) => updatePreferences({ navigation: { accordion: val } }) |
|||
" |
|||
@update:navigation-split=" |
|||
(val) => updatePreferences({ navigation: { split: val } }) |
|||
" |
|||
@update:navigation-style-type=" |
|||
(val) => updatePreferences({ navigation: { styleType: val } }) |
|||
" |
|||
@update:shortcut-keys-enable=" |
|||
(val) => updatePreferences({ shortcutKeys: { enable: val } }) |
|||
" |
|||
@update:shortcut-keys-global-logout=" |
|||
(val) => updatePreferences({ shortcutKeys: { globalLogout: val } }) |
|||
" |
|||
@update:shortcut-keys-global-preferences=" |
|||
(val) => updatePreferences({ shortcutKeys: { globalPreferences: val } }) |
|||
" |
|||
@update:shortcut-keys-global-search=" |
|||
(val) => updatePreferences({ shortcutKeys: { globalSearch: val } }) |
|||
" |
|||
@update:sidebar-collapsed=" |
|||
(val) => updatePreferences({ sidebar: { collapsed: val } }) |
|||
" |
|||
@update:sidebar-collapsed-show-title=" |
|||
(val) => updatePreferences({ sidebar: { collapsedShowTitle: val } }) |
|||
" |
|||
@update:sidebar-enable=" |
|||
(val) => updatePreferences({ sidebar: { enable: val } }) |
|||
" |
|||
@update:sidebar-width=" |
|||
(val) => updatePreferences({ sidebar: { width: val } }) |
|||
" |
|||
@update:tabbar-enable=" |
|||
(val) => updatePreferences({ tabbar: { enable: val } }) |
|||
" |
|||
@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 } }) |
|||
" |
|||
@update:transition-loading=" |
|||
(val) => updatePreferences({ transition: { loading: val } }) |
|||
" |
|||
@update:transition-name=" |
|||
(val) => updatePreferences({ transition: { name: val } }) |
|||
" |
|||
@update:transition-progress=" |
|||
(val) => updatePreferences({ transition: { progress: val } }) |
|||
" |
|||
/> |
|||
</template> |
|||
@ -1,398 +1,44 @@ |
|||
<script setup lang="ts"> |
|||
import type { |
|||
BuiltinThemeType, |
|||
ContentCompactType, |
|||
LayoutHeaderModeType, |
|||
LayoutType, |
|||
SupportedLanguagesType, |
|||
ThemeModeType, |
|||
} from '@vben/types'; |
|||
import type { |
|||
BreadcrumbStyleType, |
|||
NavigationStyleType, |
|||
} from '@vben-core/preferences'; |
|||
import type { SegmentedItem } from '@vben-core/shadcn-ui'; |
|||
|
|||
import { computed, ref } from 'vue'; |
|||
|
|||
import { IcRoundFolderCopy, IcRoundRestartAlt } from '@vben-core/iconify'; |
|||
import { $t, loadLocaleMessages } from '@vben-core/locales'; |
|||
import { |
|||
clearPreferencesCache, |
|||
preferences, |
|||
resetPreferences, |
|||
usePreferences, |
|||
} from '@vben-core/preferences'; |
|||
import { |
|||
VbenButton, |
|||
VbenIconButton, |
|||
VbenSegmented, |
|||
VbenSheet, |
|||
useToast, |
|||
} from '@vben-core/shadcn-ui'; |
|||
|
|||
import { useClipboard } from '@vueuse/core'; |
|||
|
|||
import { |
|||
Animation, |
|||
Block, |
|||
Breadcrumb, |
|||
BuiltinTheme, |
|||
ColorMode, |
|||
Content, |
|||
Copyright, |
|||
Footer, |
|||
General, |
|||
GlobalShortcutKeys, |
|||
Header, |
|||
Layout, |
|||
Navigation, |
|||
Radius, |
|||
Sidebar, |
|||
Tabbar, |
|||
Theme, |
|||
} from './blocks'; |
|||
import Trigger from './trigger.vue'; |
|||
import { useOpenPreferences } from './use-open-preferences'; |
|||
|
|||
const emit = defineEmits<{ clearPreferencesAndLogout: [] }>(); |
|||
const { toast } = useToast(); |
|||
const appLocale = defineModel<SupportedLanguagesType>('appLocale'); |
|||
const appDynamicTitle = defineModel<boolean>('appDynamicTitle'); |
|||
const appAiAssistant = defineModel<boolean>('appAiAssistant'); |
|||
const appLayout = defineModel<LayoutType>('appLayout'); |
|||
const appColorGrayMode = defineModel<boolean>('appColorGrayMode'); |
|||
const appColorWeakMode = defineModel<boolean>('appColorWeakMode'); |
|||
const appSemiDarkMenu = defineModel<boolean>('appSemiDarkMenu'); |
|||
const appContentCompact = defineModel<ContentCompactType>('appContentCompact'); |
|||
|
|||
const transitionProgress = defineModel<boolean>('transitionProgress'); |
|||
const transitionName = defineModel<string>('transitionName'); |
|||
const transitionLoading = defineModel<boolean>('transitionLoading'); |
|||
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 sidebarWidth = defineModel<number>('sidebarWidth'); |
|||
const sidebarCollapsed = defineModel<boolean>('sidebarCollapsed'); |
|||
const sidebarCollapsedShowTitle = defineModel<boolean>( |
|||
'sidebarCollapsedShowTitle', |
|||
); |
|||
|
|||
const headerEnable = defineModel<boolean>('headerEnable'); |
|||
const headerMode = defineModel<LayoutHeaderModeType>('headerMode'); |
|||
|
|||
const breadcrumbEnable = defineModel<boolean>('breadcrumbEnable'); |
|||
const breadcrumbShowIcon = defineModel<boolean>('breadcrumbShowIcon'); |
|||
const breadcrumbShowHome = defineModel<boolean>('breadcrumbShowHome'); |
|||
const breadcrumbStyleType = defineModel<BreadcrumbStyleType>( |
|||
'breadcrumbStyleType', |
|||
); |
|||
const breadcrumbHideOnlyOne = defineModel<boolean>('breadcrumbHideOnlyOne'); |
|||
|
|||
const tabbarEnable = defineModel<boolean>('tabbarEnable'); |
|||
const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon'); |
|||
|
|||
const navigationStyleType = defineModel<NavigationStyleType>( |
|||
'navigationStyleType', |
|||
); |
|||
const navigationSplit = defineModel<boolean>('navigationSplit'); |
|||
const navigationAccordion = defineModel<boolean>('navigationAccordion'); |
|||
|
|||
// const logoVisible = defineModel<boolean>('logoVisible'); |
|||
|
|||
const footerEnable = defineModel<boolean>('footerEnable'); |
|||
const footerFixed = defineModel<boolean>('footerFixed'); |
|||
|
|||
const copyrightEnable = defineModel<boolean>('copyrightEnable'); |
|||
const copyrightCompanyName = defineModel<string>('copyrightCompanyName'); |
|||
const copyrightCompanySiteLink = defineModel<string>( |
|||
'copyrightCompanySiteLink', |
|||
); |
|||
const copyrightDate = defineModel<string>('copyrightDate'); |
|||
const copyrightIcp = defineModel<string>('copyrightIcp'); |
|||
const copyrightIcpLink = defineModel<string>('copyrightIcpLink'); |
|||
|
|||
const shortcutKeysEnable = defineModel<boolean>('shortcutKeysEnable'); |
|||
const shortcutKeysGlobalSearch = defineModel<boolean>( |
|||
'shortcutKeysGlobalSearch', |
|||
); |
|||
const shortcutKeysGlobalLogout = defineModel<boolean>( |
|||
'shortcutKeysGlobalLogout', |
|||
); |
|||
const shortcutKeysGlobalPreferences = defineModel<boolean>( |
|||
'shortcutKeysGlobalPreferences', |
|||
); |
|||
|
|||
const { |
|||
diffPreference, |
|||
isDark, |
|||
isFullContent, |
|||
isHeaderNav, |
|||
isMixedNav, |
|||
isSideMixedNav, |
|||
isSideMode, |
|||
isSideNav, |
|||
} = usePreferences(); |
|||
const { copy } = useClipboard(); |
|||
|
|||
const activeTab = ref('appearance'); |
|||
|
|||
const tabs = computed((): SegmentedItem[] => { |
|||
return [ |
|||
{ |
|||
label: $t('preferences.appearance'), |
|||
value: 'appearance', |
|||
}, |
|||
{ |
|||
label: $t('preferences.layout'), |
|||
value: 'layout', |
|||
}, |
|||
{ |
|||
label: $t('preferences.shortcut-keys.title'), |
|||
value: 'shortcutKey', |
|||
}, |
|||
{ |
|||
label: $t('preferences.general'), |
|||
value: 'general', |
|||
}, |
|||
]; |
|||
}); |
|||
|
|||
const showBreadcrumbConfig = computed(() => { |
|||
return ( |
|||
!isFullContent.value && |
|||
!isMixedNav.value && |
|||
!isHeaderNav.value && |
|||
preferences.header.enable |
|||
); |
|||
<script lang="ts" setup> |
|||
import { computed } from 'vue'; |
|||
|
|||
import { preferences, updatePreferences } from '@vben-core/preferences'; |
|||
import { capitalizeFirstLetter } from '@vben-core/toolkit'; |
|||
|
|||
import Preferences from './preferences-sheet.vue'; |
|||
|
|||
/** |
|||
* preferences 转成 vue props |
|||
* preferences.app.aiAssistant=>appAiAssistant |
|||
*/ |
|||
const attrs = computed(() => { |
|||
const result: Record<string, any> = {}; |
|||
for (const [key, value] of Object.entries(preferences)) { |
|||
for (const [subKey, subValue] of Object.entries(value)) { |
|||
result[`${key}${capitalizeFirstLetter(subKey)}`] = subValue; |
|||
} |
|||
} |
|||
return result; |
|||
}); |
|||
|
|||
const { openPreferences } = useOpenPreferences(); |
|||
|
|||
async function handleCopy() { |
|||
await copy(JSON.stringify(diffPreference.value, null, 2)); |
|||
|
|||
toast({ |
|||
description: $t('preferences.copy'), |
|||
title: $t('preferences.copy-success'), |
|||
}); |
|||
} |
|||
|
|||
async function handleClearCache() { |
|||
resetPreferences(); |
|||
clearPreferencesCache(); |
|||
emit('clearPreferencesAndLogout'); |
|||
} |
|||
|
|||
async function handleReset() { |
|||
if (!diffPreference.value) { |
|||
return; |
|||
/** |
|||
* preferences 转成 vue listener |
|||
* preferences.app.aiAssistant=>@update:appAiAssistant |
|||
*/ |
|||
const listen = computed(() => { |
|||
const result: Record<string, any> = {}; |
|||
for (const [key, value] of Object.entries(preferences)) { |
|||
if (typeof value === 'object') { |
|||
for (const subKey of Object.keys(value)) { |
|||
result[`update:${key}${capitalizeFirstLetter(subKey)}`] = (val: any) => |
|||
updatePreferences({ [key]: { [subKey]: val } }); |
|||
} |
|||
} else { |
|||
result[key] = value; |
|||
} |
|||
} |
|||
resetPreferences(); |
|||
await loadLocaleMessages(preferences.app.locale); |
|||
toast({ |
|||
description: $t('preferences.reset-title'), |
|||
title: $t('preferences.reset-success'), |
|||
}); |
|||
toast({ |
|||
description: $t('preferences.reset-title'), |
|||
title: $t('preferences.reset-success'), |
|||
}); |
|||
} |
|||
return result; |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="z-100 fixed right-0 top-1/2"> |
|||
<VbenSheet |
|||
v-model:open="openPreferences" |
|||
:description="$t('preferences.subtitle')" |
|||
:title="$t('preferences.title')" |
|||
> |
|||
<template #trigger> |
|||
<Trigger /> |
|||
</template> |
|||
<template #extra> |
|||
<div class="flex items-center"> |
|||
<VbenIconButton |
|||
:disabled="!diffPreference" |
|||
:tooltip="$t('preferences.reset-tip')" |
|||
class="relative" |
|||
> |
|||
<span |
|||
v-if="diffPreference" |
|||
class="bg-primary absolute right-0.5 top-0.5 h-2 w-2 rounded" |
|||
></span> |
|||
<IcRoundRestartAlt class="size-5" @click="handleReset" /> |
|||
</VbenIconButton> |
|||
</div> |
|||
</template> |
|||
|
|||
<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.title')"> |
|||
<Animation |
|||
v-model:transition-enable="transitionEnable" |
|||
v-model:transition-loading="transitionLoading" |
|||
v-model:transition-name="transitionName" |
|||
v-model:transition-progress="transitionProgress" |
|||
/> |
|||
</Block> |
|||
</template> |
|||
<template #appearance> |
|||
<Block :title="$t('preferences.theme.title')"> |
|||
<Theme |
|||
v-model="themeMode" |
|||
v-model:app-semi-dark-menu="appSemiDarkMenu" |
|||
/> |
|||
</Block> |
|||
<!-- <Block :title="$t('preferences.theme-color')"> |
|||
<ThemeColor |
|||
v-model="themeColorPrimary" |
|||
:color-primary-presets="colorPrimaryPresets" |
|||
/> |
|||
</Block> --> |
|||
<Block :title="$t('preferences.theme.builtin.title')"> |
|||
<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 |
|||
v-model:app-color-gray-mode="appColorGrayMode" |
|||
v-model:app-color-weak-mode="appColorWeakMode" |
|||
/> |
|||
</Block> |
|||
</template> |
|||
<template #layout> |
|||
<Block :title="$t('preferences.layout')"> |
|||
<Layout v-model="appLayout" /> |
|||
</Block> |
|||
<Block :title="$t('preferences.content')"> |
|||
<Content v-model="appContentCompact" /> |
|||
</Block> |
|||
|
|||
<Block :title="$t('preferences.sidebar.title')"> |
|||
<Sidebar |
|||
v-model:sidebar-collapsed="sidebarCollapsed" |
|||
v-model:sidebar-collapsed-show-title="sidebarCollapsedShowTitle" |
|||
v-model:sidebar-enable="sidebarEnable" |
|||
v-model:sidebar-width="sidebarWidth" |
|||
:disabled="!isSideMode" |
|||
/> |
|||
</Block> |
|||
|
|||
<Block :title="$t('preferences.header.title')"> |
|||
<Header |
|||
v-model:headerEnable="headerEnable" |
|||
v-model:headerMode="headerMode" |
|||
:disabled="isFullContent" |
|||
/> |
|||
</Block> |
|||
|
|||
<Block :title="$t('preferences.navigation-menu.title')"> |
|||
<Navigation |
|||
v-model:navigation-accordion="navigationAccordion" |
|||
v-model:navigation-split="navigationSplit" |
|||
v-model:navigation-style-type="navigationStyleType" |
|||
:disabled="isFullContent" |
|||
:disabled-navigation-split="!isMixedNav" |
|||
/> |
|||
</Block> |
|||
|
|||
<Block :title="$t('preferences.breadcrumb.title')"> |
|||
<Breadcrumb |
|||
v-model:breadcrumb-enable="breadcrumbEnable" |
|||
v-model:breadcrumb-hide-only-one="breadcrumbHideOnlyOne" |
|||
v-model:breadcrumb-show-home="breadcrumbShowHome" |
|||
v-model:breadcrumb-show-icon="breadcrumbShowIcon" |
|||
v-model:breadcrumb-style-type="breadcrumbStyleType" |
|||
:disabled=" |
|||
!showBreadcrumbConfig || !(isSideNav || isSideMixedNav) |
|||
" |
|||
/> |
|||
</Block> |
|||
|
|||
<Block :title="$t('preferences.tabbar.title')"> |
|||
<Tabbar |
|||
v-model:tabbar-enable="tabbarEnable" |
|||
v-model:tabbar-show-icon="tabbarShowIcon" |
|||
/> |
|||
</Block> |
|||
<Block :title="$t('preferences.footer.title')"> |
|||
<Footer |
|||
v-model:footer-enable="footerEnable" |
|||
v-model:footer-fixed="footerFixed" |
|||
/> |
|||
</Block> |
|||
<Block :title="$t('preferences.copyright.title')"> |
|||
<Copyright |
|||
v-model:copyright-company-name="copyrightCompanyName" |
|||
v-model:copyright-company-site-link="copyrightCompanySiteLink" |
|||
v-model:copyright-date="copyrightDate" |
|||
v-model:copyright-enable="copyrightEnable" |
|||
v-model:copyright-icp="copyrightIcp" |
|||
v-model:copyright-icp-link="copyrightIcpLink" |
|||
:disabled="!footerEnable" |
|||
/> |
|||
</Block> |
|||
</template> |
|||
|
|||
<template #shortcutKey> |
|||
<Block :title="$t('preferences.shortcut-keys.global')"> |
|||
<GlobalShortcutKeys |
|||
v-model:shortcut-keys-enable="shortcutKeysEnable" |
|||
v-model:shortcut-keys-global-search="shortcutKeysGlobalSearch" |
|||
v-model:shortcut-keys-logout="shortcutKeysGlobalLogout" |
|||
v-model:shortcut-keys-preferences=" |
|||
shortcutKeysGlobalPreferences |
|||
" |
|||
/> |
|||
</Block> |
|||
</template> |
|||
</VbenSegmented> |
|||
</div> |
|||
|
|||
<template #footer> |
|||
<VbenButton |
|||
:disabled="!diffPreference" |
|||
class="mx-4 w-full" |
|||
size="sm" |
|||
variant="outline" |
|||
@click="handleClearCache" |
|||
> |
|||
<IcRoundRestartAlt class="mr-2 size-4" /> |
|||
{{ $t('preferences.clear-and-logout') }} |
|||
</VbenButton> |
|||
<VbenButton |
|||
:disabled="!diffPreference" |
|||
class="mr-4 w-full" |
|||
size="sm" |
|||
variant="default" |
|||
@click="handleCopy" |
|||
> |
|||
<IcRoundFolderCopy class="mr-2 size-3" /> |
|||
{{ $t('preferences.copy') }} |
|||
</VbenButton> |
|||
</template> |
|||
</VbenSheet> |
|||
</div> |
|||
<Preferences v-bind="attrs" v-on="listen" /> |
|||
</template> |
|||
|
|||
@ -1,21 +0,0 @@ |
|||
<script setup lang="ts"> |
|||
import { $t } from '@vben-core/locales'; |
|||
import { VbenButton } from '@vben-core/shadcn-ui'; |
|||
|
|||
import IconSetting from './icons/setting.vue'; |
|||
|
|||
defineOptions({ |
|||
name: 'PreferenceTrigger', |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<VbenButton |
|||
:title="$t('preferences.title')" |
|||
class="bg-primary flex-col-center h-12 w-12 cursor-pointer rounded-l-lg rounded-r-none border-none" |
|||
> |
|||
<IconSetting |
|||
class="duration-3000 fill-primary-foreground animate-spin text-2xl" |
|||
/> |
|||
</VbenButton> |
|||
</template> |
|||
Loading…
Reference in new issue