preferences-drawer.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. <script setup lang="ts">
  2. import type { SupportedLanguagesType } from '@vben/locales';
  3. import type {
  4. BreadcrumbStyleType,
  5. BuiltinThemeType,
  6. ContentCompactType,
  7. LayoutHeaderMenuAlignType,
  8. LayoutHeaderModeType,
  9. LayoutType,
  10. NavigationStyleType,
  11. PreferencesButtonPositionType,
  12. ThemeModeType,
  13. } from '@vben/types';
  14. import type { SegmentedItem } from '@vben-core/shadcn-ui';
  15. import { computed, ref } from 'vue';
  16. import { Copy, RotateCw } from '@vben/icons';
  17. import { $t, loadLocaleMessages } from '@vben/locales';
  18. import {
  19. clearPreferencesCache,
  20. preferences,
  21. resetPreferences,
  22. usePreferences,
  23. } from '@vben/preferences';
  24. import { useVbenDrawer } from '@vben-core/popup-ui';
  25. import {
  26. VbenButton,
  27. VbenIconButton,
  28. VbenSegmented,
  29. } from '@vben-core/shadcn-ui';
  30. import { globalShareState } from '@vben-core/shared/global-state';
  31. import { useClipboard } from '@vueuse/core';
  32. import {
  33. Animation,
  34. Block,
  35. Breadcrumb,
  36. BuiltinTheme,
  37. ColorMode,
  38. Content,
  39. Copyright,
  40. Footer,
  41. General,
  42. GlobalShortcutKeys,
  43. Header,
  44. Layout,
  45. Navigation,
  46. Radius,
  47. Sidebar,
  48. Tabbar,
  49. Theme,
  50. Widget,
  51. } from './blocks';
  52. const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
  53. const message = globalShareState.getMessage();
  54. const appLocale = defineModel<SupportedLanguagesType>('appLocale');
  55. const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
  56. const appLayout = defineModel<LayoutType>('appLayout');
  57. const appColorGrayMode = defineModel<boolean>('appColorGrayMode');
  58. const appColorWeakMode = defineModel<boolean>('appColorWeakMode');
  59. const appContentCompact = defineModel<ContentCompactType>('appContentCompact');
  60. const appWatermark = defineModel<boolean>('appWatermark');
  61. const appEnableCheckUpdates = defineModel<boolean>('appEnableCheckUpdates');
  62. const appPreferencesButtonPosition = defineModel<PreferencesButtonPositionType>(
  63. 'appPreferencesButtonPosition',
  64. );
  65. const transitionProgress = defineModel<boolean>('transitionProgress');
  66. const transitionName = defineModel<string>('transitionName');
  67. const transitionLoading = defineModel<boolean>('transitionLoading');
  68. const transitionEnable = defineModel<boolean>('transitionEnable');
  69. const themeColorPrimary = defineModel<string>('themeColorPrimary');
  70. const themeBuiltinType = defineModel<BuiltinThemeType>('themeBuiltinType');
  71. const themeMode = defineModel<ThemeModeType>('themeMode');
  72. const themeRadius = defineModel<string>('themeRadius');
  73. const themeSemiDarkSidebar = defineModel<boolean>('themeSemiDarkSidebar');
  74. const themeSemiDarkHeader = defineModel<boolean>('themeSemiDarkHeader');
  75. const sidebarEnable = defineModel<boolean>('sidebarEnable');
  76. const sidebarWidth = defineModel<number>('sidebarWidth');
  77. const sidebarCollapsed = defineModel<boolean>('sidebarCollapsed');
  78. const sidebarCollapsedShowTitle = defineModel<boolean>(
  79. 'sidebarCollapsedShowTitle',
  80. );
  81. const sidebarAutoActivateChild = defineModel<boolean>(
  82. 'sidebarAutoActivateChild',
  83. );
  84. const SidebarExpandOnHover = defineModel<boolean>('sidebarExpandOnHover');
  85. const headerEnable = defineModel<boolean>('headerEnable');
  86. const headerMode = defineModel<LayoutHeaderModeType>('headerMode');
  87. const headerMenuAlign =
  88. defineModel<LayoutHeaderMenuAlignType>('headerMenuAlign');
  89. const breadcrumbEnable = defineModel<boolean>('breadcrumbEnable');
  90. const breadcrumbShowIcon = defineModel<boolean>('breadcrumbShowIcon');
  91. const breadcrumbShowHome = defineModel<boolean>('breadcrumbShowHome');
  92. const breadcrumbStyleType = defineModel<BreadcrumbStyleType>(
  93. 'breadcrumbStyleType',
  94. );
  95. const breadcrumbHideOnlyOne = defineModel<boolean>('breadcrumbHideOnlyOne');
  96. const tabbarEnable = defineModel<boolean>('tabbarEnable');
  97. const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon');
  98. const tabbarShowMore = defineModel<boolean>('tabbarShowMore');
  99. const tabbarShowMaximize = defineModel<boolean>('tabbarShowMaximize');
  100. const tabbarPersist = defineModel<boolean>('tabbarPersist');
  101. const tabbarDraggable = defineModel<boolean>('tabbarDraggable');
  102. const tabbarWheelable = defineModel<boolean>('tabbarWheelable');
  103. const tabbarStyleType = defineModel<string>('tabbarStyleType');
  104. const navigationStyleType = defineModel<NavigationStyleType>(
  105. 'navigationStyleType',
  106. );
  107. const navigationSplit = defineModel<boolean>('navigationSplit');
  108. const navigationAccordion = defineModel<boolean>('navigationAccordion');
  109. // const logoVisible = defineModel<boolean>('logoVisible');
  110. const footerEnable = defineModel<boolean>('footerEnable');
  111. const footerFixed = defineModel<boolean>('footerFixed');
  112. const copyrightSettingShow = defineModel<boolean>('copyrightSettingShow');
  113. const copyrightEnable = defineModel<boolean>('copyrightEnable');
  114. const copyrightCompanyName = defineModel<string>('copyrightCompanyName');
  115. const copyrightCompanySiteLink = defineModel<string>(
  116. 'copyrightCompanySiteLink',
  117. );
  118. const copyrightDate = defineModel<string>('copyrightDate');
  119. const copyrightIcp = defineModel<string>('copyrightIcp');
  120. const copyrightIcpLink = defineModel<string>('copyrightIcpLink');
  121. const shortcutKeysEnable = defineModel<boolean>('shortcutKeysEnable');
  122. const shortcutKeysGlobalSearch = defineModel<boolean>(
  123. 'shortcutKeysGlobalSearch',
  124. );
  125. const shortcutKeysGlobalLogout = defineModel<boolean>(
  126. 'shortcutKeysGlobalLogout',
  127. );
  128. const shortcutKeysGlobalLockScreen = defineModel<boolean>(
  129. 'shortcutKeysGlobalLockScreen',
  130. );
  131. const widgetGlobalSearch = defineModel<boolean>('widgetGlobalSearch');
  132. const widgetFullscreen = defineModel<boolean>('widgetFullscreen');
  133. const widgetLanguageToggle = defineModel<boolean>('widgetLanguageToggle');
  134. const widgetNotification = defineModel<boolean>('widgetNotification');
  135. const widgetThemeToggle = defineModel<boolean>('widgetThemeToggle');
  136. const widgetSidebarToggle = defineModel<boolean>('widgetSidebarToggle');
  137. const widgetLockScreen = defineModel<boolean>('widgetLockScreen');
  138. const widgetRefresh = defineModel<boolean>('widgetRefresh');
  139. const {
  140. diffPreference,
  141. isDark,
  142. isFullContent,
  143. isHeaderNav,
  144. isHeaderSidebarNav,
  145. isMixedNav,
  146. isSideMixedNav,
  147. isSideMode,
  148. isSideNav,
  149. } = usePreferences();
  150. const { copy } = useClipboard({ legacy: true });
  151. const [Drawer] = useVbenDrawer();
  152. const activeTab = ref('appearance');
  153. const tabs = computed((): SegmentedItem[] => {
  154. return [
  155. {
  156. label: $t('preferences.appearance'),
  157. value: 'appearance',
  158. },
  159. {
  160. label: $t('preferences.layout'),
  161. value: 'layout',
  162. },
  163. {
  164. label: $t('preferences.shortcutKeys.title'),
  165. value: 'shortcutKey',
  166. },
  167. {
  168. label: $t('preferences.general'),
  169. value: 'general',
  170. },
  171. ];
  172. });
  173. const showBreadcrumbConfig = computed(() => {
  174. return (
  175. !isFullContent.value &&
  176. !isMixedNav.value &&
  177. !isHeaderNav.value &&
  178. preferences.header.enable
  179. );
  180. });
  181. async function handleCopy() {
  182. await copy(JSON.stringify(diffPreference.value, null, 2));
  183. message.copyPreferencesSuccess?.(
  184. $t('preferences.copyPreferencesSuccessTitle'),
  185. $t('preferences.copyPreferencesSuccess'),
  186. );
  187. }
  188. async function handleClearCache() {
  189. resetPreferences();
  190. clearPreferencesCache();
  191. emit('clearPreferencesAndLogout');
  192. }
  193. async function handleReset() {
  194. if (!diffPreference.value) {
  195. return;
  196. }
  197. resetPreferences();
  198. await loadLocaleMessages(preferences.app.locale);
  199. }
  200. </script>
  201. <template>
  202. <div>
  203. <Drawer
  204. :description="$t('preferences.subtitle')"
  205. :title="$t('preferences.title')"
  206. class="sm:max-w-sm"
  207. >
  208. <template #extra>
  209. <div class="flex items-center">
  210. <VbenIconButton
  211. :disabled="!diffPreference"
  212. :tooltip="$t('preferences.resetTip')"
  213. class="relative"
  214. >
  215. <span
  216. v-if="diffPreference"
  217. class="bg-primary absolute right-0.5 top-0.5 h-2 w-2 rounded"
  218. ></span>
  219. <RotateCw class="size-4" @click="handleReset" />
  220. </VbenIconButton>
  221. </div>
  222. </template>
  223. <div class="p-1">
  224. <VbenSegmented v-model="activeTab" :tabs="tabs">
  225. <template #general>
  226. <Block :title="$t('preferences.general')">
  227. <General
  228. v-model:app-dynamic-title="appDynamicTitle"
  229. v-model:app-enable-check-updates="appEnableCheckUpdates"
  230. v-model:app-locale="appLocale"
  231. v-model:app-watermark="appWatermark"
  232. />
  233. </Block>
  234. <Block :title="$t('preferences.animation.title')">
  235. <Animation
  236. v-model:transition-enable="transitionEnable"
  237. v-model:transition-loading="transitionLoading"
  238. v-model:transition-name="transitionName"
  239. v-model:transition-progress="transitionProgress"
  240. />
  241. </Block>
  242. </template>
  243. <template #appearance>
  244. <Block :title="$t('preferences.theme.title')">
  245. <Theme
  246. v-model="themeMode"
  247. v-model:theme-semi-dark-header="themeSemiDarkHeader"
  248. v-model:theme-semi-dark-sidebar="themeSemiDarkSidebar"
  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-auto-activate-child="sidebarAutoActivateChild"
  278. v-model:sidebar-collapsed="sidebarCollapsed"
  279. v-model:sidebar-collapsed-show-title="sidebarCollapsedShowTitle"
  280. v-model:sidebar-enable="sidebarEnable"
  281. v-model:sidebar-expand-on-hover="SidebarExpandOnHover"
  282. v-model:sidebar-width="sidebarWidth"
  283. :current-layout="appLayout"
  284. :disabled="!isSideMode"
  285. />
  286. </Block>
  287. <Block :title="$t('preferences.header.title')">
  288. <Header
  289. v-model:header-enable="headerEnable"
  290. v-model:header-menu-align="headerMenuAlign"
  291. v-model:header-mode="headerMode"
  292. :disabled="isFullContent"
  293. />
  294. </Block>
  295. <Block :title="$t('preferences.navigationMenu.title')">
  296. <Navigation
  297. v-model:navigation-accordion="navigationAccordion"
  298. v-model:navigation-split="navigationSplit"
  299. v-model:navigation-style-type="navigationStyleType"
  300. :disabled="isFullContent"
  301. :disabled-navigation-split="!isMixedNav"
  302. />
  303. </Block>
  304. <Block :title="$t('preferences.breadcrumb.title')">
  305. <Breadcrumb
  306. v-model:breadcrumb-enable="breadcrumbEnable"
  307. v-model:breadcrumb-hide-only-one="breadcrumbHideOnlyOne"
  308. v-model:breadcrumb-show-home="breadcrumbShowHome"
  309. v-model:breadcrumb-show-icon="breadcrumbShowIcon"
  310. v-model:breadcrumb-style-type="breadcrumbStyleType"
  311. :disabled="
  312. !showBreadcrumbConfig ||
  313. !(isSideNav || isSideMixedNav || isHeaderSidebarNav)
  314. "
  315. />
  316. </Block>
  317. <Block :title="$t('preferences.tabbar.title')">
  318. <Tabbar
  319. v-model:tabbar-draggable="tabbarDraggable"
  320. v-model:tabbar-enable="tabbarEnable"
  321. v-model:tabbar-persist="tabbarPersist"
  322. v-model:tabbar-show-icon="tabbarShowIcon"
  323. v-model:tabbar-show-maximize="tabbarShowMaximize"
  324. v-model:tabbar-show-more="tabbarShowMore"
  325. v-model:tabbar-style-type="tabbarStyleType"
  326. v-model:tabbar-wheelable="tabbarWheelable"
  327. />
  328. </Block>
  329. <Block :title="$t('preferences.widget.title')">
  330. <Widget
  331. v-model:app-preferences-button-position="
  332. appPreferencesButtonPosition
  333. "
  334. v-model:widget-fullscreen="widgetFullscreen"
  335. v-model:widget-global-search="widgetGlobalSearch"
  336. v-model:widget-language-toggle="widgetLanguageToggle"
  337. v-model:widget-lock-screen="widgetLockScreen"
  338. v-model:widget-notification="widgetNotification"
  339. v-model:widget-refresh="widgetRefresh"
  340. v-model:widget-sidebar-toggle="widgetSidebarToggle"
  341. v-model:widget-theme-toggle="widgetThemeToggle"
  342. />
  343. </Block>
  344. <Block :title="$t('preferences.footer.title')">
  345. <Footer
  346. v-model:footer-enable="footerEnable"
  347. v-model:footer-fixed="footerFixed"
  348. />
  349. </Block>
  350. <Block
  351. v-if="copyrightSettingShow"
  352. :title="$t('preferences.copyright.title')"
  353. >
  354. <Copyright
  355. v-model:copyright-company-name="copyrightCompanyName"
  356. v-model:copyright-company-site-link="copyrightCompanySiteLink"
  357. v-model:copyright-date="copyrightDate"
  358. v-model:copyright-enable="copyrightEnable"
  359. v-model:copyright-icp="copyrightIcp"
  360. v-model:copyright-icp-link="copyrightIcpLink"
  361. :disabled="!footerEnable"
  362. />
  363. </Block>
  364. </template>
  365. <template #shortcutKey>
  366. <Block :title="$t('preferences.shortcutKeys.global')">
  367. <GlobalShortcutKeys
  368. v-model:shortcut-keys-enable="shortcutKeysEnable"
  369. v-model:shortcut-keys-global-search="shortcutKeysGlobalSearch"
  370. v-model:shortcut-keys-lock-screen="shortcutKeysGlobalLockScreen"
  371. v-model:shortcut-keys-logout="shortcutKeysGlobalLogout"
  372. />
  373. </Block>
  374. </template>
  375. </VbenSegmented>
  376. </div>
  377. <template #footer>
  378. <VbenButton
  379. :disabled="!diffPreference"
  380. class="mx-4 w-full"
  381. size="sm"
  382. variant="default"
  383. @click="handleCopy"
  384. >
  385. <Copy class="mr-2 size-3" />
  386. {{ $t('preferences.copyPreferences') }}
  387. </VbenButton>
  388. <VbenButton
  389. :disabled="!diffPreference"
  390. class="mr-4 w-full"
  391. size="sm"
  392. variant="ghost"
  393. @click="handleClearCache"
  394. >
  395. {{ $t('preferences.clearAndLogout') }}
  396. </VbenButton>
  397. </template>
  398. </Drawer>
  399. </div>
  400. </template>