preferences.vue 11 KB

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