preferences.vue 13 KB

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