preferences.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. <script setup lang="ts">
  2. import type {
  3. BuiltinThemeType,
  4. ContentCompactType,
  5. LayoutHeaderModeType,
  6. LayoutType,
  7. SupportedLanguagesType,
  8. ThemeModeType,
  9. } from '@vben/types';
  10. import type {
  11. BreadcrumbStyleType,
  12. NavigationStyleType,
  13. } from '@vben-core/preferences';
  14. import type { SegmentedItem } from '@vben-core/shadcn-ui';
  15. import { computed, ref } from 'vue';
  16. import { $t, loadLocaleMessages } from '@vben/locales';
  17. import { IcRoundFolderCopy, IcRoundRestartAlt } from '@vben-core/iconify';
  18. import {
  19. clearPreferencesCache,
  20. preferences,
  21. resetPreferences,
  22. usePreferences,
  23. } from '@vben-core/preferences';
  24. import {
  25. VbenButton,
  26. VbenIconButton,
  27. VbenSegmented,
  28. VbenSheet,
  29. toast,
  30. } from '@vben-core/shadcn-ui';
  31. import { useClipboard } from '@vueuse/core';
  32. import {
  33. Animation,
  34. Block,
  35. Breadcrumb,
  36. BuiltinTheme,
  37. ColorMode,
  38. Content,
  39. Footer,
  40. General,
  41. GlobalShortcutKeys,
  42. Header,
  43. Layout,
  44. Navigation,
  45. Radius,
  46. Sidebar,
  47. Tabbar,
  48. Theme,
  49. } from './blocks';
  50. import Trigger from './trigger.vue';
  51. import { useOpenPreferences } from './use-open-preferences';
  52. const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
  53. const appLocale = defineModel<SupportedLanguagesType>('appLocale');
  54. const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
  55. const appAiAssistant = defineModel<boolean>('appAiAssistant');
  56. const appLayout = defineModel<LayoutType>('appLayout');
  57. const appColorGrayMode = defineModel<boolean>('appColorGrayMode');
  58. const appColorWeakMode = defineModel<boolean>('appColorWeakMode');
  59. const appSemiDarkMenu = defineModel<boolean>('appSemiDarkMenu');
  60. const appContentCompact = defineModel<ContentCompactType>('appContentCompact');
  61. const transitionProgress = defineModel<boolean>('transitionProgress');
  62. const transitionName = defineModel<string>('transitionName');
  63. const transitionLoading = defineModel<boolean>('transitionLoading');
  64. const transitionEnable = defineModel<boolean>('transitionEnable');
  65. const themeColorPrimary = defineModel<string>('themeColorPrimary');
  66. const themeBuiltinType = defineModel<BuiltinThemeType>('themeBuiltinType');
  67. const themeMode = defineModel<ThemeModeType>('themeMode');
  68. const themeRadius = defineModel<string>('themeRadius');
  69. const sidebarEnable = defineModel<boolean>('sidebarEnable');
  70. const sidebarCollapsed = defineModel<boolean>('sidebarCollapsed');
  71. const sidebarCollapsedShowTitle = defineModel<boolean>(
  72. 'sidebarCollapsedShowTitle',
  73. );
  74. const headerEnable = defineModel<boolean>('headerEnable');
  75. const headerMode = defineModel<LayoutHeaderModeType>('headerMode');
  76. const breadcrumbEnable = defineModel<boolean>('breadcrumbEnable');
  77. const breadcrumbShowIcon = defineModel<boolean>('breadcrumbShowIcon');
  78. const breadcrumbShowHome = defineModel<boolean>('breadcrumbShowHome');
  79. const breadcrumbStyleType = defineModel<BreadcrumbStyleType>(
  80. 'breadcrumbStyleType',
  81. );
  82. const breadcrumbHideOnlyOne = defineModel<boolean>('breadcrumbHideOnlyOne');
  83. const tabbarEnable = defineModel<boolean>('tabbarEnable');
  84. const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon');
  85. const navigationStyleType = defineModel<NavigationStyleType>(
  86. 'navigationStyleType',
  87. );
  88. const navigationSplit = defineModel<boolean>('navigationSplit');
  89. const navigationAccordion = defineModel<boolean>('navigationAccordion');
  90. // const logoVisible = defineModel<boolean>('logoVisible');
  91. const footerEnable = defineModel<boolean>('footerEnable');
  92. const footerFixed = defineModel<boolean>('footerFixed');
  93. const shortcutKeysEnable = defineModel<boolean>('shortcutKeysEnable');
  94. const shortcutKeysGlobalSearch = defineModel<boolean>(
  95. 'shortcutKeysGlobalSearch',
  96. );
  97. const shortcutKeysGlobalLogout = defineModel<boolean>(
  98. 'shortcutKeysGlobalLogout',
  99. );
  100. const shortcutKeysGlobalPreferences = defineModel<boolean>(
  101. 'shortcutKeysGlobalPreferences',
  102. );
  103. const {
  104. diffPreference,
  105. isDark,
  106. isFullContent,
  107. isHeaderNav,
  108. isMixedNav,
  109. isSideMixedNav,
  110. isSideMode,
  111. isSideNav,
  112. } = usePreferences();
  113. const { copy } = useClipboard();
  114. const activeTab = ref('appearance');
  115. const tabs = computed((): SegmentedItem[] => {
  116. return [
  117. {
  118. label: $t('preferences.appearance'),
  119. value: 'appearance',
  120. },
  121. {
  122. label: $t('preferences.layout'),
  123. value: 'layout',
  124. },
  125. {
  126. label: $t('preferences.shortcut-keys.title'),
  127. value: 'shortcutKey',
  128. },
  129. {
  130. label: $t('preferences.general'),
  131. value: 'general',
  132. },
  133. ];
  134. });
  135. const showBreadcrumbConfig = computed(() => {
  136. return (
  137. !isFullContent.value &&
  138. !isMixedNav.value &&
  139. !isHeaderNav.value &&
  140. preferences.header.enable
  141. );
  142. });
  143. const { openPreferences } = useOpenPreferences();
  144. async function handleCopy() {
  145. await copy(JSON.stringify(diffPreference.value, null, 2));
  146. toast($t('preferences.copy-success'));
  147. }
  148. async function handleClearCache() {
  149. resetPreferences();
  150. clearPreferencesCache();
  151. emit('clearPreferencesAndLogout');
  152. }
  153. async function handleReset() {
  154. if (!diffPreference.value) {
  155. return;
  156. }
  157. resetPreferences();
  158. await loadLocaleMessages(preferences.app.locale);
  159. toast($t('preferences.reset-success'));
  160. }
  161. </script>
  162. <template>
  163. <div class="z-100 fixed right-0 top-1/2">
  164. <VbenSheet
  165. v-model:open="openPreferences"
  166. :description="$t('preferences.subtitle')"
  167. :title="$t('preferences.title')"
  168. >
  169. <template #trigger>
  170. <Trigger />
  171. </template>
  172. <template #extra>
  173. <div class="flex items-center">
  174. <VbenIconButton
  175. :disabled="!diffPreference"
  176. :tooltip="$t('preferences.reset-tip')"
  177. class="relative"
  178. >
  179. <span
  180. v-if="diffPreference"
  181. class="bg-primary absolute right-0.5 top-0.5 h-2 w-2 rounded"
  182. ></span>
  183. <IcRoundRestartAlt class="size-5" @click="handleReset" />
  184. </VbenIconButton>
  185. </div>
  186. </template>
  187. <div class="p-4 pt-4">
  188. <VbenSegmented v-model="activeTab" :tabs="tabs">
  189. <template #general>
  190. <Block :title="$t('preferences.general')">
  191. <General
  192. v-model:app-ai-assistant="appAiAssistant"
  193. v-model:app-dynamic-title="appDynamicTitle"
  194. v-model:app-locale="appLocale"
  195. />
  196. </Block>
  197. <Block :title="$t('preferences.animation.title')">
  198. <Animation
  199. v-model:transition-enable="transitionEnable"
  200. v-model:transition-loading="transitionLoading"
  201. v-model:transition-name="transitionName"
  202. v-model:transition-progress="transitionProgress"
  203. />
  204. </Block>
  205. </template>
  206. <template #appearance>
  207. <Block :title="$t('preferences.theme.title')">
  208. <Theme
  209. v-model="themeMode"
  210. v-model:app-semi-dark-menu="appSemiDarkMenu"
  211. />
  212. </Block>
  213. <!-- <Block :title="$t('preferences.theme-color')">
  214. <ThemeColor
  215. v-model="themeColorPrimary"
  216. :color-primary-presets="colorPrimaryPresets"
  217. />
  218. </Block> -->
  219. <Block :title="$t('preferences.theme.builtin.title')">
  220. <BuiltinTheme
  221. v-model="themeBuiltinType"
  222. v-model:theme-color-primary="themeColorPrimary"
  223. :is-dark="isDark"
  224. />
  225. </Block>
  226. <Block :title="$t('preferences.theme.radius')">
  227. <Radius v-model="themeRadius" />
  228. </Block>
  229. <Block :title="$t('preferences.other')">
  230. <ColorMode
  231. v-model:app-color-gray-mode="appColorGrayMode"
  232. v-model:app-color-weak-mode="appColorWeakMode"
  233. />
  234. </Block>
  235. </template>
  236. <template #layout>
  237. <Block :title="$t('preferences.layout')">
  238. <Layout v-model="appLayout" />
  239. </Block>
  240. <Block :title="$t('preferences.content')">
  241. <Content v-model="appContentCompact" />
  242. </Block>
  243. <Block :title="$t('preferences.sidebar')">
  244. <Sidebar
  245. v-model:sidebar-collapsed="sidebarCollapsed"
  246. v-model:sidebar-collapsed-show-title="sidebarCollapsedShowTitle"
  247. v-model:sidebar-enable="sidebarEnable"
  248. :disabled="!isSideMode"
  249. />
  250. </Block>
  251. <Block :title="$t('preferences.header.title')">
  252. <Header
  253. v-model:headerEnable="headerEnable"
  254. v-model:headerMode="headerMode"
  255. :disabled="isFullContent"
  256. />
  257. </Block>
  258. <Block :title="$t('preferences.navigation-menu.title')">
  259. <Navigation
  260. v-model:navigation-accordion="navigationAccordion"
  261. v-model:navigation-split="navigationSplit"
  262. v-model:navigation-style-type="navigationStyleType"
  263. :disabled="isFullContent"
  264. :disabled-navigation-split="!isMixedNav"
  265. />
  266. </Block>
  267. <Block :title="$t('preferences.breadcrumb.title')">
  268. <Breadcrumb
  269. v-model:breadcrumb-enable="breadcrumbEnable"
  270. v-model:breadcrumb-hide-only-one="breadcrumbHideOnlyOne"
  271. v-model:breadcrumb-show-home="breadcrumbShowHome"
  272. v-model:breadcrumb-show-icon="breadcrumbShowIcon"
  273. v-model:breadcrumb-style-type="breadcrumbStyleType"
  274. :disabled="
  275. !showBreadcrumbConfig || !(isSideNav || isSideMixedNav)
  276. "
  277. />
  278. </Block>
  279. <Block :title="$t('preferences.tabbar.title')">
  280. <Tabbar
  281. v-model:tabbar-enable="tabbarEnable"
  282. v-model:tabbar-show-icon="tabbarShowIcon"
  283. />
  284. </Block>
  285. <Block :title="$t('preferences.footer.title')">
  286. <Footer
  287. v-model:footer-enable="footerEnable"
  288. v-model:footer-fixed="footerFixed"
  289. />
  290. </Block>
  291. </template>
  292. <template #shortcutKey>
  293. <Block :title="$t('preferences.shortcut-keys.global')">
  294. <GlobalShortcutKeys
  295. v-model:shortcut-keys-enable="shortcutKeysEnable"
  296. v-model:shortcut-keys-global-search="shortcutKeysGlobalSearch"
  297. v-model:shortcut-keys-logout="shortcutKeysGlobalLogout"
  298. v-model:shortcut-keys-preferences="
  299. shortcutKeysGlobalPreferences
  300. "
  301. />
  302. </Block>
  303. </template>
  304. </VbenSegmented>
  305. </div>
  306. <template #footer>
  307. <VbenButton
  308. :disabled="!diffPreference"
  309. class="mx-4 w-full"
  310. size="sm"
  311. variant="outline"
  312. @click="handleClearCache"
  313. >
  314. <IcRoundRestartAlt class="mr-2 size-4" />
  315. {{ $t('preferences.clear-and-logout') }}
  316. </VbenButton>
  317. <VbenButton
  318. :disabled="!diffPreference"
  319. class="mr-4 w-full"
  320. size="sm"
  321. variant="default"
  322. @click="handleCopy"
  323. >
  324. <IcRoundFolderCopy class="mr-2 size-3" />
  325. {{ $t('preferences.copy') }}
  326. </VbenButton>
  327. </template>
  328. </VbenSheet>
  329. </div>
  330. </template>