preferences-drawer.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  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, Pin, PinOff, RotateCw } from '@vben/icons';
  17. import { $t, loadLocaleMessages } from '@vben/locales';
  18. import {
  19. clearCache,
  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. FontSize,
  41. Footer,
  42. General,
  43. GlobalShortcutKeys,
  44. Header,
  45. Layout,
  46. Navigation,
  47. Radius,
  48. Sidebar,
  49. Tabbar,
  50. Theme,
  51. Widget,
  52. } from './blocks';
  53. const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
  54. const message = globalShareState.getMessage();
  55. const appLocale = defineModel<SupportedLanguagesType>('appLocale');
  56. const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
  57. const appLayout = defineModel<LayoutType>('appLayout');
  58. const appColorGrayMode = defineModel<boolean>('appColorGrayMode');
  59. const appColorWeakMode = defineModel<boolean>('appColorWeakMode');
  60. const appContentCompact = defineModel<ContentCompactType>('appContentCompact');
  61. const appWatermark = defineModel<boolean>('appWatermark');
  62. const appWatermarkContent = defineModel<string>('appWatermarkContent');
  63. const appEnableCheckUpdates = defineModel<boolean>('appEnableCheckUpdates');
  64. const appEnableCopyPreferences = defineModel<boolean>(
  65. 'appEnableCopyPreferences',
  66. );
  67. const appEnableStickyPreferencesNavigationBar = defineModel<boolean>(
  68. 'appEnableStickyPreferencesNavigationBar',
  69. );
  70. const appPreferencesButtonPosition = defineModel<PreferencesButtonPositionType>(
  71. 'appPreferencesButtonPosition',
  72. );
  73. const transitionProgress = defineModel<boolean>('transitionProgress');
  74. const transitionName = defineModel<string>('transitionName');
  75. const transitionLoading = defineModel<boolean>('transitionLoading');
  76. const transitionEnable = defineModel<boolean>('transitionEnable');
  77. const themeColorPrimary = defineModel<string>('themeColorPrimary');
  78. const themeBuiltinType = defineModel<BuiltinThemeType>('themeBuiltinType');
  79. const themeMode = defineModel<ThemeModeType>('themeMode');
  80. const themeRadius = defineModel<string>('themeRadius');
  81. const themeFontSize = defineModel<number>('themeFontSize');
  82. const themeSemiDarkSidebar = defineModel<boolean>('themeSemiDarkSidebar');
  83. const themeSemiDarkSidebarSub = defineModel<boolean>('themeSemiDarkSidebarSub');
  84. const themeSemiDarkHeader = defineModel<boolean>('themeSemiDarkHeader');
  85. const sidebarEnable = defineModel<boolean>('sidebarEnable');
  86. const sidebarWidth = defineModel<number>('sidebarWidth');
  87. const sidebarDraggable = defineModel<boolean>('sidebarDraggable');
  88. const sidebarCollapsed = defineModel<boolean>('sidebarCollapsed');
  89. const sidebarCollapsedShowTitle = defineModel<boolean>(
  90. 'sidebarCollapsedShowTitle',
  91. );
  92. const sidebarAutoActivateChild = defineModel<boolean>(
  93. 'sidebarAutoActivateChild',
  94. );
  95. const sidebarExpandOnHover = defineModel<boolean>('sidebarExpandOnHover');
  96. const sidebarCollapsedButton = defineModel<boolean>('sidebarCollapsedButton');
  97. const sidebarFixedButton = defineModel<boolean>('sidebarFixedButton');
  98. const headerEnable = defineModel<boolean>('headerEnable');
  99. const headerMode = defineModel<LayoutHeaderModeType>('headerMode');
  100. const headerMenuAlign =
  101. defineModel<LayoutHeaderMenuAlignType>('headerMenuAlign');
  102. const breadcrumbEnable = defineModel<boolean>('breadcrumbEnable');
  103. const breadcrumbShowIcon = defineModel<boolean>('breadcrumbShowIcon');
  104. const breadcrumbShowHome = defineModel<boolean>('breadcrumbShowHome');
  105. const breadcrumbStyleType = defineModel<BreadcrumbStyleType>(
  106. 'breadcrumbStyleType',
  107. );
  108. const breadcrumbHideOnlyOne = defineModel<boolean>('breadcrumbHideOnlyOne');
  109. const tabbarEnable = defineModel<boolean>('tabbarEnable');
  110. const tabbarShowIcon = defineModel<boolean>('tabbarShowIcon');
  111. const tabbarShowMore = defineModel<boolean>('tabbarShowMore');
  112. const tabbarShowMaximize = defineModel<boolean>('tabbarShowMaximize');
  113. const tabbarPersist = defineModel<boolean>('tabbarPersist');
  114. const tabbarVisitHistory = defineModel<boolean>('tabbarVisitHistory');
  115. const tabbarDraggable = defineModel<boolean>('tabbarDraggable');
  116. const tabbarWheelable = defineModel<boolean>('tabbarWheelable');
  117. const tabbarStyleType = defineModel<string>('tabbarStyleType');
  118. const tabbarMaxCount = defineModel<number>('tabbarMaxCount');
  119. const tabbarMiddleClickToClose = defineModel<boolean>(
  120. 'tabbarMiddleClickToClose',
  121. );
  122. const navigationStyleType = defineModel<NavigationStyleType>(
  123. 'navigationStyleType',
  124. );
  125. const navigationSplit = defineModel<boolean>('navigationSplit');
  126. const navigationAccordion = defineModel<boolean>('navigationAccordion');
  127. // const logoVisible = defineModel<boolean>('logoVisible');
  128. const footerEnable = defineModel<boolean>('footerEnable');
  129. const footerFixed = defineModel<boolean>('footerFixed');
  130. const copyrightSettingShow = defineModel<boolean>('copyrightSettingShow');
  131. const copyrightEnable = defineModel<boolean>('copyrightEnable');
  132. const copyrightCompanyName = defineModel<string>('copyrightCompanyName');
  133. const copyrightCompanySiteLink = defineModel<string>(
  134. 'copyrightCompanySiteLink',
  135. );
  136. const copyrightDate = defineModel<string>('copyrightDate');
  137. const copyrightIcp = defineModel<string>('copyrightIcp');
  138. const copyrightIcpLink = defineModel<string>('copyrightIcpLink');
  139. const shortcutKeysEnable = defineModel<boolean>('shortcutKeysEnable');
  140. const shortcutKeysGlobalSearch = defineModel<boolean>(
  141. 'shortcutKeysGlobalSearch',
  142. );
  143. const shortcutKeysGlobalLogout = defineModel<boolean>(
  144. 'shortcutKeysGlobalLogout',
  145. );
  146. const shortcutKeysGlobalLockScreen = defineModel<boolean>(
  147. 'shortcutKeysGlobalLockScreen',
  148. );
  149. const widgetGlobalSearch = defineModel<boolean>('widgetGlobalSearch');
  150. const widgetFullscreen = defineModel<boolean>('widgetFullscreen');
  151. const widgetLanguageToggle = defineModel<boolean>('widgetLanguageToggle');
  152. const widgetNotification = defineModel<boolean>('widgetNotification');
  153. const widgetThemeToggle = defineModel<boolean>('widgetThemeToggle');
  154. const widgetSidebarToggle = defineModel<boolean>('widgetSidebarToggle');
  155. const widgetLockScreen = defineModel<boolean>('widgetLockScreen');
  156. const widgetRefresh = defineModel<boolean>('widgetRefresh');
  157. const {
  158. diffPreference,
  159. isDark,
  160. isFullContent,
  161. isHeaderNav,
  162. isHeaderSidebarNav,
  163. isMixedNav,
  164. isSideMixedNav,
  165. isSideMode,
  166. isSideNav,
  167. } = usePreferences();
  168. const { copy } = useClipboard({ legacy: true });
  169. const [Drawer] = useVbenDrawer();
  170. const activeTab = ref('appearance');
  171. const tabs = computed((): SegmentedItem[] => {
  172. return [
  173. {
  174. label: $t('preferences.appearance'),
  175. value: 'appearance',
  176. },
  177. {
  178. label: $t('preferences.layout'),
  179. value: 'layout',
  180. },
  181. {
  182. label: $t('preferences.shortcutKeys.title'),
  183. value: 'shortcutKey',
  184. },
  185. {
  186. label: $t('preferences.general'),
  187. value: 'general',
  188. },
  189. ];
  190. });
  191. const showBreadcrumbConfig = computed(() => {
  192. return (
  193. !isFullContent.value &&
  194. !isMixedNav.value &&
  195. !isHeaderNav.value &&
  196. preferences.header.enable
  197. );
  198. });
  199. async function handleCopy() {
  200. await copy(JSON.stringify(diffPreference.value, null, 2));
  201. message.copyPreferencesSuccess?.(
  202. $t('preferences.copyPreferencesSuccessTitle'),
  203. $t('preferences.copyPreferencesSuccess'),
  204. );
  205. }
  206. async function handleClearCache() {
  207. resetPreferences();
  208. clearCache();
  209. emit('clearPreferencesAndLogout');
  210. }
  211. async function handleReset() {
  212. if (!diffPreference.value) {
  213. return;
  214. }
  215. resetPreferences();
  216. await loadLocaleMessages(preferences.app.locale);
  217. }
  218. </script>
  219. <template>
  220. <div>
  221. <Drawer
  222. :description="$t('preferences.subtitle')"
  223. :title="$t('preferences.title')"
  224. class="border-0! sm:max-w-sm"
  225. >
  226. <template #extra>
  227. <div class="flex items-center">
  228. <VbenIconButton
  229. :disabled="!diffPreference"
  230. :tooltip="$t('preferences.resetTip')"
  231. class="relative"
  232. @click="handleReset"
  233. >
  234. <span
  235. v-if="diffPreference"
  236. class="absolute top-0.5 right-0.5 size-2 rounded-sm bg-primary"
  237. ></span>
  238. <RotateCw class="size-4" />
  239. </VbenIconButton>
  240. <VbenIconButton
  241. :tooltip="
  242. appEnableStickyPreferencesNavigationBar
  243. ? $t('preferences.disableStickyPreferencesNavigationBar')
  244. : $t('preferences.enableStickyPreferencesNavigationBar')
  245. "
  246. class="relative"
  247. @click="
  248. () =>
  249. (appEnableStickyPreferencesNavigationBar =
  250. !appEnableStickyPreferencesNavigationBar)
  251. "
  252. >
  253. <PinOff
  254. v-if="appEnableStickyPreferencesNavigationBar"
  255. class="size-4"
  256. />
  257. <Pin v-else class="size-4" />
  258. </VbenIconButton>
  259. </div>
  260. </template>
  261. <div>
  262. <VbenSegmented
  263. v-model="activeTab"
  264. :tabs="tabs"
  265. :class="{
  266. 'sticky-tabs-header': appEnableStickyPreferencesNavigationBar,
  267. }"
  268. >
  269. <template #general>
  270. <Block :title="$t('preferences.general')">
  271. <General
  272. v-model:app-dynamic-title="appDynamicTitle"
  273. v-model:app-enable-check-updates="appEnableCheckUpdates"
  274. v-model:app-enable-copy-preferences="appEnableCopyPreferences"
  275. v-model:app-locale="appLocale"
  276. v-model:app-watermark="appWatermark"
  277. v-model:app-watermark-content="appWatermarkContent"
  278. />
  279. </Block>
  280. <Block :title="$t('preferences.animation.title')">
  281. <Animation
  282. v-model:transition-enable="transitionEnable"
  283. v-model:transition-loading="transitionLoading"
  284. v-model:transition-name="transitionName"
  285. v-model:transition-progress="transitionProgress"
  286. />
  287. </Block>
  288. </template>
  289. <template #appearance>
  290. <Block :title="$t('preferences.theme.title')">
  291. <Theme
  292. v-model="themeMode"
  293. v-model:theme-semi-dark-header="themeSemiDarkHeader"
  294. v-model:theme-semi-dark-sidebar="themeSemiDarkSidebar"
  295. v-model:theme-semi-dark-sidebar-sub="themeSemiDarkSidebarSub"
  296. />
  297. </Block>
  298. <Block :title="$t('preferences.theme.builtin.title')">
  299. <BuiltinTheme
  300. v-model="themeBuiltinType"
  301. v-model:theme-color-primary="themeColorPrimary"
  302. :is-dark="isDark"
  303. />
  304. </Block>
  305. <Block :title="$t('preferences.theme.radius')">
  306. <Radius v-model="themeRadius" />
  307. </Block>
  308. <Block :title="$t('preferences.theme.fontSize')">
  309. <FontSize v-model="themeFontSize" />
  310. </Block>
  311. <Block :title="$t('preferences.other')">
  312. <ColorMode
  313. v-model:app-color-gray-mode="appColorGrayMode"
  314. v-model:app-color-weak-mode="appColorWeakMode"
  315. />
  316. </Block>
  317. </template>
  318. <template #layout>
  319. <Block :title="$t('preferences.layout')">
  320. <Layout v-model="appLayout" />
  321. </Block>
  322. <Block :title="$t('preferences.content')">
  323. <Content v-model="appContentCompact" />
  324. </Block>
  325. <Block :title="$t('preferences.sidebar.title')">
  326. <Sidebar
  327. v-model:sidebar-auto-activate-child="sidebarAutoActivateChild"
  328. v-model:sidebar-draggable="sidebarDraggable"
  329. v-model:sidebar-collapsed="sidebarCollapsed"
  330. v-model:sidebar-collapsed-show-title="sidebarCollapsedShowTitle"
  331. v-model:sidebar-enable="sidebarEnable"
  332. v-model:sidebar-expand-on-hover="sidebarExpandOnHover"
  333. v-model:sidebar-width="sidebarWidth"
  334. v-model:sidebar-collapsed-button="sidebarCollapsedButton"
  335. v-model:sidebar-fixed-button="sidebarFixedButton"
  336. :current-layout="appLayout"
  337. :disabled="!isSideMode"
  338. />
  339. </Block>
  340. <Block :title="$t('preferences.header.title')">
  341. <Header
  342. v-model:header-enable="headerEnable"
  343. v-model:header-menu-align="headerMenuAlign"
  344. v-model:header-mode="headerMode"
  345. :disabled="isFullContent"
  346. />
  347. </Block>
  348. <Block :title="$t('preferences.navigationMenu.title')">
  349. <Navigation
  350. v-model:navigation-accordion="navigationAccordion"
  351. v-model:navigation-split="navigationSplit"
  352. v-model:navigation-style-type="navigationStyleType"
  353. :disabled="isFullContent"
  354. :disabled-navigation-split="!isMixedNav"
  355. />
  356. </Block>
  357. <Block :title="$t('preferences.breadcrumb.title')">
  358. <Breadcrumb
  359. v-model:breadcrumb-enable="breadcrumbEnable"
  360. v-model:breadcrumb-hide-only-one="breadcrumbHideOnlyOne"
  361. v-model:breadcrumb-show-home="breadcrumbShowHome"
  362. v-model:breadcrumb-show-icon="breadcrumbShowIcon"
  363. v-model:breadcrumb-style-type="breadcrumbStyleType"
  364. :disabled="
  365. !showBreadcrumbConfig ||
  366. !(isSideNav || isSideMixedNav || isHeaderSidebarNav)
  367. "
  368. />
  369. </Block>
  370. <Block :title="$t('preferences.tabbar.title')">
  371. <Tabbar
  372. v-model:tabbar-draggable="tabbarDraggable"
  373. v-model:tabbar-enable="tabbarEnable"
  374. v-model:tabbar-persist="tabbarPersist"
  375. v-model:tabbar-visit-history="tabbarVisitHistory"
  376. v-model:tabbar-show-icon="tabbarShowIcon"
  377. v-model:tabbar-show-maximize="tabbarShowMaximize"
  378. v-model:tabbar-show-more="tabbarShowMore"
  379. v-model:tabbar-style-type="tabbarStyleType"
  380. v-model:tabbar-wheelable="tabbarWheelable"
  381. v-model:tabbar-max-count="tabbarMaxCount"
  382. v-model:tabbar-middle-click-to-close="tabbarMiddleClickToClose"
  383. />
  384. </Block>
  385. <Block :title="$t('preferences.widget.title')">
  386. <Widget
  387. v-model:app-preferences-button-position="
  388. appPreferencesButtonPosition
  389. "
  390. v-model:widget-fullscreen="widgetFullscreen"
  391. v-model:widget-global-search="widgetGlobalSearch"
  392. v-model:widget-language-toggle="widgetLanguageToggle"
  393. v-model:widget-lock-screen="widgetLockScreen"
  394. v-model:widget-notification="widgetNotification"
  395. v-model:widget-refresh="widgetRefresh"
  396. v-model:widget-sidebar-toggle="widgetSidebarToggle"
  397. v-model:widget-theme-toggle="widgetThemeToggle"
  398. />
  399. </Block>
  400. <Block :title="$t('preferences.footer.title')">
  401. <Footer
  402. v-model:footer-enable="footerEnable"
  403. v-model:footer-fixed="footerFixed"
  404. />
  405. </Block>
  406. <Block
  407. v-if="copyrightSettingShow"
  408. :title="$t('preferences.copyright.title')"
  409. >
  410. <Copyright
  411. v-model:copyright-company-name="copyrightCompanyName"
  412. v-model:copyright-company-site-link="copyrightCompanySiteLink"
  413. v-model:copyright-date="copyrightDate"
  414. v-model:copyright-enable="copyrightEnable"
  415. v-model:copyright-icp="copyrightIcp"
  416. v-model:copyright-icp-link="copyrightIcpLink"
  417. :disabled="!footerEnable"
  418. />
  419. </Block>
  420. </template>
  421. <template #shortcutKey>
  422. <Block :title="$t('preferences.shortcutKeys.global')">
  423. <GlobalShortcutKeys
  424. v-model:shortcut-keys-enable="shortcutKeysEnable"
  425. v-model:shortcut-keys-global-search="shortcutKeysGlobalSearch"
  426. v-model:shortcut-keys-lock-screen="shortcutKeysGlobalLockScreen"
  427. v-model:shortcut-keys-logout="shortcutKeysGlobalLogout"
  428. />
  429. </Block>
  430. </template>
  431. </VbenSegmented>
  432. </div>
  433. <template #footer>
  434. <VbenButton
  435. v-if="appEnableCopyPreferences"
  436. :disabled="!diffPreference"
  437. class="mx-4 w-full"
  438. size="sm"
  439. variant="default"
  440. @click="handleCopy"
  441. >
  442. <Copy class="mr-2 size-3" />
  443. {{ $t('preferences.copyPreferences') }}
  444. </VbenButton>
  445. <VbenButton
  446. :disabled="!diffPreference"
  447. class="mr-4 w-full"
  448. size="sm"
  449. variant="ghost"
  450. @click="handleClearCache"
  451. >
  452. {{ $t('preferences.clearAndLogout') }}
  453. </VbenButton>
  454. </template>
  455. </Drawer>
  456. </div>
  457. </template>
  458. <style scoped>
  459. :deep(.sticky-tabs-header [role='tablist']) {
  460. position: sticky;
  461. @apply -top-3;
  462. z-index: 9999;
  463. }
  464. </style>