preferences-sheet.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. <script setup lang="ts">
  2. import type { SegmentedItem } from '@vben-core/shadcn-ui';
  3. import type {
  4. BreadcrumbStyleType,
  5. BuiltinThemeType,
  6. ContentCompactType,
  7. LayoutHeaderModeType,
  8. LayoutType,
  9. NavigationStyleType,
  10. SupportedLanguagesType,
  11. ThemeModeType,
  12. } from '@vben-core/typings';
  13. import { computed, ref } from 'vue';
  14. import { Copy, RotateCw, SwatchBook } from '@vben-core/icons';
  15. import { $t, loadLocaleMessages } from '@vben-core/locales';
  16. import {
  17. clearPreferencesCache,
  18. preferences,
  19. resetPreferences,
  20. usePreferences,
  21. } from '@vben-core/preferences';
  22. import {
  23. VbenButton,
  24. VbenIconButton,
  25. VbenSegmented,
  26. VbenSheet,
  27. useToast,
  28. } from '@vben-core/shadcn-ui';
  29. import { useClipboard } from '@vueuse/core';
  30. import {
  31. Animation,
  32. Block,
  33. Breadcrumb,
  34. BuiltinTheme,
  35. ColorMode,
  36. Content,
  37. Copyright,
  38. Footer,
  39. General,
  40. GlobalShortcutKeys,
  41. Header,
  42. Layout,
  43. Navigation,
  44. Radius,
  45. Sidebar,
  46. Tabbar,
  47. Theme,
  48. Widget,
  49. } from './blocks';
  50. import { useOpenPreferences } from './use-open-preferences';
  51. const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
  52. const { toast } = useToast();
  53. const appLocale = defineModel<SupportedLanguagesType>('appLocale');
  54. const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
  55. const appLayout = defineModel<LayoutType>('appLayout');
  56. const appColorGrayMode = defineModel<boolean>('appColorGrayMode');
  57. const appColorWeakMode = defineModel<boolean>('appColorWeakMode');
  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 themeSemiDarkMenu = defineModel<boolean>('themeSemiDarkMenu');
  68. const sidebarEnable = defineModel<boolean>('sidebarEnable');
  69. const sidebarWidth = defineModel<number>('sidebarWidth');
  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 tabbarPersist = defineModel<boolean>('tabbarPersist');
  86. const tabbarDragable = defineModel<boolean>('tabbarDragable');
  87. const tabbarStyleType = defineModel<string>('tabbarStyleType');
  88. const navigationStyleType = defineModel<NavigationStyleType>(
  89. 'navigationStyleType',
  90. );
  91. const navigationSplit = defineModel<boolean>('navigationSplit');
  92. const navigationAccordion = defineModel<boolean>('navigationAccordion');
  93. // const logoVisible = defineModel<boolean>('logoVisible');
  94. const footerEnable = defineModel<boolean>('footerEnable');
  95. const footerFixed = defineModel<boolean>('footerFixed');
  96. const copyrightEnable = defineModel<boolean>('copyrightEnable');
  97. const copyrightCompanyName = defineModel<string>('copyrightCompanyName');
  98. const copyrightCompanySiteLink = defineModel<string>(
  99. 'copyrightCompanySiteLink',
  100. );
  101. const copyrightDate = defineModel<string>('copyrightDate');
  102. const copyrightIcp = defineModel<string>('copyrightIcp');
  103. const copyrightIcpLink = defineModel<string>('copyrightIcpLink');
  104. const shortcutKeysEnable = defineModel<boolean>('shortcutKeysEnable');
  105. const shortcutKeysGlobalSearch = defineModel<boolean>(
  106. 'shortcutKeysGlobalSearch',
  107. );
  108. const shortcutKeysGlobalLogout = defineModel<boolean>(
  109. 'shortcutKeysGlobalLogout',
  110. );
  111. const shortcutKeysGlobalPreferences = defineModel<boolean>(
  112. 'shortcutKeysGlobalPreferences',
  113. );
  114. const shortcutKeysGlobalLockScreen = defineModel<boolean>(
  115. 'shortcutKeysGlobalLockScreen',
  116. );
  117. const widgetGlobalSearch = defineModel<boolean>('widgetGlobalSearch');
  118. const widgetFullscreen = defineModel<boolean>('widgetFullscreen');
  119. const widgetLanguageToggle = defineModel<boolean>('widgetLanguageToggle');
  120. const widgetNotification = defineModel<boolean>('widgetNotification');
  121. const widgetThemeToggle = defineModel<boolean>('widgetThemeToggle');
  122. const widgetAiAssistant = defineModel<boolean>('widgetAiAssistant');
  123. const widgetSidebarToggle = defineModel<boolean>('widgetSidebarToggle');
  124. const widgetLockScreen = defineModel<boolean>('widgetLockScreen');
  125. const {
  126. diffPreference,
  127. isDark,
  128. isFullContent,
  129. isHeaderNav,
  130. isMixedNav,
  131. isSideMixedNav,
  132. isSideMode,
  133. isSideNav,
  134. } = usePreferences();
  135. const { copy } = useClipboard();
  136. const activeTab = ref('appearance');
  137. const tabs = computed((): SegmentedItem[] => {
  138. return [
  139. {
  140. label: $t('preferences.appearance'),
  141. value: 'appearance',
  142. },
  143. {
  144. label: $t('preferences.layout'),
  145. value: 'layout',
  146. },
  147. {
  148. label: $t('preferences.shortcutKeys.title'),
  149. value: 'shortcutKey',
  150. },
  151. {
  152. label: $t('preferences.general'),
  153. value: 'general',
  154. },
  155. ];
  156. });
  157. const showBreadcrumbConfig = computed(() => {
  158. return (
  159. !isFullContent.value &&
  160. !isMixedNav.value &&
  161. !isHeaderNav.value &&
  162. preferences.header.enable
  163. );
  164. });
  165. const { openPreferences } = useOpenPreferences();
  166. async function handleCopy() {
  167. await copy(JSON.stringify(diffPreference.value, null, 2));
  168. toast({
  169. description: $t('preferences.copyPreferences'),
  170. title: $t('preferences.copyPreferencesSuccess'),
  171. });
  172. }
  173. async function handleClearCache() {
  174. resetPreferences();
  175. clearPreferencesCache();
  176. emit('clearPreferencesAndLogout');
  177. }
  178. async function handleReset() {
  179. if (!diffPreference.value) {
  180. return;
  181. }
  182. resetPreferences();
  183. await loadLocaleMessages(preferences.app.locale);
  184. toast({
  185. description: $t('preferences.resetTitle'),
  186. title: $t('preferences.resetSuccess'),
  187. });
  188. }
  189. </script>
  190. <template>
  191. <div class="z-100 fixed right-0 top-1/2">
  192. <VbenSheet
  193. v-model:open="openPreferences"
  194. :description="$t('preferences.subtitle')"
  195. :title="$t('preferences.title')"
  196. >
  197. <template #trigger>
  198. <VbenButton
  199. :title="$t('preferences.title')"
  200. class="bg-primary flex-col-center h-10 w-10 cursor-pointer rounded-l-lg rounded-r-none border-none"
  201. >
  202. <SwatchBook class="size-5" />
  203. </VbenButton>
  204. </template>
  205. <template #extra>
  206. <div class="flex items-center">
  207. <VbenIconButton
  208. :disabled="!diffPreference"
  209. :tooltip="$t('preferences.resetTip')"
  210. class="relative"
  211. >
  212. <span
  213. v-if="diffPreference"
  214. class="bg-primary absolute right-0.5 top-0.5 h-2 w-2 rounded"
  215. ></span>
  216. <RotateCw class="size-4" @click="handleReset" />
  217. </VbenIconButton>
  218. </div>
  219. </template>
  220. <div class="p-4 pt-4">
  221. <VbenSegmented v-model="activeTab" :tabs="tabs">
  222. <template #general>
  223. <Block :title="$t('preferences.general')">
  224. <General
  225. v-model:app-dynamic-title="appDynamicTitle"
  226. v-model:app-locale="appLocale"
  227. />
  228. </Block>
  229. <Block :title="$t('preferences.animation.title')">
  230. <Animation
  231. v-model:transition-enable="transitionEnable"
  232. v-model:transition-loading="transitionLoading"
  233. v-model:transition-name="transitionName"
  234. v-model:transition-progress="transitionProgress"
  235. />
  236. </Block>
  237. </template>
  238. <template #appearance>
  239. <Block :title="$t('preferences.theme.title')">
  240. <Theme
  241. v-model="themeMode"
  242. v-model:theme-semi-dark-menu="themeSemiDarkMenu"
  243. />
  244. </Block>
  245. <!-- <Block :title="$t('preferences.theme-color')">
  246. <ThemeColor
  247. v-model="themeColorPrimary"
  248. :color-primary-presets="colorPrimaryPresets"
  249. />
  250. </Block> -->
  251. <Block :title="$t('preferences.theme.builtin.title')">
  252. <BuiltinTheme
  253. v-model="themeBuiltinType"
  254. v-model:theme-color-primary="themeColorPrimary"
  255. :is-dark="isDark"
  256. />
  257. </Block>
  258. <Block :title="$t('preferences.theme.radius')">
  259. <Radius v-model="themeRadius" />
  260. </Block>
  261. <Block :title="$t('preferences.other')">
  262. <ColorMode
  263. v-model:app-color-gray-mode="appColorGrayMode"
  264. v-model:app-color-weak-mode="appColorWeakMode"
  265. />
  266. </Block>
  267. </template>
  268. <template #layout>
  269. <Block :title="$t('preferences.layout')">
  270. <Layout v-model="appLayout" />
  271. </Block>
  272. <Block :title="$t('preferences.content')">
  273. <Content v-model="appContentCompact" />
  274. </Block>
  275. <Block :title="$t('preferences.sidebar.title')">
  276. <Sidebar
  277. v-model:sidebar-collapsed="sidebarCollapsed"
  278. v-model:sidebar-collapsed-show-title="sidebarCollapsedShowTitle"
  279. v-model:sidebar-enable="sidebarEnable"
  280. v-model:sidebar-width="sidebarWidth"
  281. :disabled="!isSideMode"
  282. />
  283. </Block>
  284. <Block :title="$t('preferences.header.title')">
  285. <Header
  286. v-model:headerEnable="headerEnable"
  287. v-model:headerMode="headerMode"
  288. :disabled="isFullContent"
  289. />
  290. </Block>
  291. <Block :title="$t('preferences.navigationMenu.title')">
  292. <Navigation
  293. v-model:navigation-accordion="navigationAccordion"
  294. v-model:navigation-split="navigationSplit"
  295. v-model:navigation-style-type="navigationStyleType"
  296. :disabled="isFullContent"
  297. :disabled-navigation-split="!isMixedNav"
  298. />
  299. </Block>
  300. <Block :title="$t('preferences.breadcrumb.title')">
  301. <Breadcrumb
  302. v-model:breadcrumb-enable="breadcrumbEnable"
  303. v-model:breadcrumb-hide-only-one="breadcrumbHideOnlyOne"
  304. v-model:breadcrumb-show-home="breadcrumbShowHome"
  305. v-model:breadcrumb-show-icon="breadcrumbShowIcon"
  306. v-model:breadcrumb-style-type="breadcrumbStyleType"
  307. :disabled="
  308. !showBreadcrumbConfig || !(isSideNav || isSideMixedNav)
  309. "
  310. />
  311. </Block>
  312. <Block :title="$t('preferences.tabbar.title')">
  313. <Tabbar
  314. v-model:tabbar-dragable="tabbarDragable"
  315. v-model:tabbar-enable="tabbarEnable"
  316. v-model:tabbar-persist="tabbarPersist"
  317. v-model:tabbar-show-icon="tabbarShowIcon"
  318. v-model:tabbar-style-type="tabbarStyleType"
  319. />
  320. </Block>
  321. <Block :title="$t('preferences.widget.title')">
  322. <Widget
  323. v-model:widget-ai-assistant="widgetAiAssistant"
  324. v-model:widget-fullscreen="widgetFullscreen"
  325. v-model:widget-global-search="widgetGlobalSearch"
  326. v-model:widget-language-toggle="widgetLanguageToggle"
  327. v-model:widget-lock-screen="widgetLockScreen"
  328. v-model:widget-notification="widgetNotification"
  329. v-model:widget-sidebar-toggle="widgetSidebarToggle"
  330. v-model:widget-theme-toggle="widgetThemeToggle"
  331. />
  332. </Block>
  333. <Block :title="$t('preferences.footer.title')">
  334. <Footer
  335. v-model:footer-enable="footerEnable"
  336. v-model:footer-fixed="footerFixed"
  337. />
  338. </Block>
  339. <Block :title="$t('preferences.copyright.title')">
  340. <Copyright
  341. v-model:copyright-company-name="copyrightCompanyName"
  342. v-model:copyright-company-site-link="copyrightCompanySiteLink"
  343. v-model:copyright-date="copyrightDate"
  344. v-model:copyright-enable="copyrightEnable"
  345. v-model:copyright-icp="copyrightIcp"
  346. v-model:copyright-icp-link="copyrightIcpLink"
  347. :disabled="!footerEnable"
  348. />
  349. </Block>
  350. </template>
  351. <template #shortcutKey>
  352. <Block :title="$t('preferences.shortcutKeys.global')">
  353. <GlobalShortcutKeys
  354. v-model:shortcut-keys-enable="shortcutKeysEnable"
  355. v-model:shortcut-keys-global-search="shortcutKeysGlobalSearch"
  356. v-model:shortcut-keys-lock-screen="shortcutKeysGlobalLockScreen"
  357. v-model:shortcut-keys-logout="shortcutKeysGlobalLogout"
  358. v-model:shortcut-keys-preferences="
  359. shortcutKeysGlobalPreferences
  360. "
  361. />
  362. </Block>
  363. </template>
  364. </VbenSegmented>
  365. </div>
  366. <template #footer>
  367. <VbenButton
  368. :disabled="!diffPreference"
  369. class="mx-4 w-full"
  370. size="sm"
  371. variant="default"
  372. @click="handleCopy"
  373. >
  374. <Copy class="mr-2 size-3" />
  375. {{ $t('preferences.copyPreferences') }}
  376. </VbenButton>
  377. <VbenButton
  378. :disabled="!diffPreference"
  379. class="mr-4 w-full"
  380. size="sm"
  381. variant="ghost"
  382. @click="handleClearCache"
  383. >
  384. <!-- <RotateCw class="mr-2 size-4" /> -->
  385. {{ $t('preferences.clearAndLogout') }}
  386. </VbenButton>
  387. </template>
  388. </VbenSheet>
  389. </div>
  390. </template>