SettingDrawer.tsx 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. import { defineComponent, computed, unref, ref } from 'vue';
  2. import { BasicDrawer } from '/@/components/Drawer/index';
  3. import { Divider, Switch, Tooltip, InputNumber, Select } from 'ant-design-vue';
  4. import Button from '/@/components/Button/index.vue';
  5. import { MenuModeEnum, MenuTypeEnum, MenuThemeEnum, TopMenuAlignEnum } from '/@/enums/menuEnum';
  6. import { ContentEnum, RouterTransitionEnum } from '/@/enums/appEnum';
  7. import { CopyOutlined, RedoOutlined, CheckOutlined } from '@ant-design/icons-vue';
  8. import { appStore } from '/@/store/modules/app';
  9. import { userStore } from '/@/store/modules/user';
  10. import { ProjectConfig } from '/@/types/config';
  11. import { useMessage } from '/@/hooks/web/useMessage';
  12. import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
  13. import defaultSetting from '/@/settings/projectSetting';
  14. import mixImg from '/@/assets/images/layout/menu-mix.svg';
  15. import sidebarImg from '/@/assets/images/layout/menu-sidebar.svg';
  16. import menuTopImg from '/@/assets/images/layout/menu-top.svg';
  17. import { updateColorWeak, updateGrayMode } from '/@/setup/theme';
  18. const themeOptions = [
  19. {
  20. value: MenuThemeEnum.LIGHT,
  21. label: '亮色',
  22. key: MenuThemeEnum.LIGHT,
  23. },
  24. {
  25. value: MenuThemeEnum.DARK,
  26. label: '暗色',
  27. key: MenuThemeEnum.DARK,
  28. },
  29. ];
  30. const contentModeOptions = [
  31. {
  32. value: ContentEnum.FULL,
  33. label: '流式',
  34. key: ContentEnum.FULL,
  35. },
  36. {
  37. value: ContentEnum.FIXED,
  38. label: '定宽',
  39. key: ContentEnum.FIXED,
  40. },
  41. ];
  42. const topMenuAlignOptions = [
  43. {
  44. value: TopMenuAlignEnum.CENTER,
  45. label: '居中',
  46. key: TopMenuAlignEnum.CENTER,
  47. },
  48. {
  49. value: TopMenuAlignEnum.START,
  50. label: '居左',
  51. key: TopMenuAlignEnum.START,
  52. },
  53. {
  54. value: TopMenuAlignEnum.END,
  55. label: '居右',
  56. key: TopMenuAlignEnum.END,
  57. },
  58. ];
  59. const routerTransitionOptions = [
  60. RouterTransitionEnum.ZOOM_FADE,
  61. RouterTransitionEnum.FADE,
  62. RouterTransitionEnum.ZOOM_OUT,
  63. RouterTransitionEnum.SIDE_FADE,
  64. RouterTransitionEnum.FADE_BOTTOM,
  65. ].map((item) => {
  66. return {
  67. label: item,
  68. value: item,
  69. key: item,
  70. };
  71. });
  72. interface SwitchOptions {
  73. config?: DeepPartial<ProjectConfig>;
  74. def?: any;
  75. disabled?: boolean;
  76. handler?: Fn;
  77. }
  78. interface SelectConfig {
  79. options?: SelectOptions;
  80. def?: any;
  81. disabled?: boolean;
  82. handler?: Fn;
  83. }
  84. export default defineComponent({
  85. name: 'SettingDrawer',
  86. setup(_, { attrs }) {
  87. const { createSuccessModal, createMessage } = useMessage();
  88. const getProjectConfigRef = computed(() => {
  89. return appStore.getProjectConfig;
  90. });
  91. const getIsHorizontalRef = computed(() => {
  92. return unref(getProjectConfigRef).menuSetting.mode === MenuModeEnum.HORIZONTAL;
  93. });
  94. const getShowHeaderRef = computed(() => {
  95. return unref(getProjectConfigRef).headerSetting.show;
  96. });
  97. const getShowMenuRef = computed(() => {
  98. return unref(getProjectConfigRef).menuSetting.show && !unref(getIsHorizontalRef);
  99. });
  100. const getShowTabsRef = computed(() => {
  101. return unref(getProjectConfigRef).multiTabsSetting.show;
  102. });
  103. function handleCopy() {
  104. const { isSuccessRef } = useCopyToClipboard(
  105. JSON.stringify(unref(getProjectConfigRef), null, 2)
  106. );
  107. unref(isSuccessRef) &&
  108. createSuccessModal({
  109. title: '操作成功',
  110. content: '复制成功,请到 src/settings/projectSetting.ts 中修改配置!',
  111. });
  112. }
  113. function renderSidebar() {
  114. const {
  115. headerSetting: { theme: headerTheme },
  116. menuSetting: { type, theme: menuTheme, split },
  117. } = unref(getProjectConfigRef);
  118. const typeList = ref([
  119. {
  120. title: '左侧菜单模式',
  121. mode: MenuModeEnum.INLINE,
  122. type: MenuTypeEnum.SIDEBAR,
  123. src: sidebarImg,
  124. },
  125. {
  126. title: '混合模式',
  127. mode: MenuModeEnum.INLINE,
  128. type: MenuTypeEnum.MIX,
  129. src: mixImg,
  130. },
  131. {
  132. title: '顶部菜单模式',
  133. mode: MenuModeEnum.HORIZONTAL,
  134. type: MenuTypeEnum.TOP_MENU,
  135. src: menuTopImg,
  136. },
  137. ]);
  138. return [
  139. <div class={`setting-drawer__siderbar`}>
  140. {unref(typeList).map((item) => {
  141. const { title, type: ItemType, mode, src } = item;
  142. return (
  143. <Tooltip title={title} placement="bottom" key={title}>
  144. {{
  145. default: () => (
  146. <div
  147. onClick={baseHandler.bind(null, 'layout', {
  148. mode: mode,
  149. type: ItemType,
  150. split: unref(getIsHorizontalRef) ? false : undefined,
  151. })}
  152. >
  153. <CheckOutlined class={['check-icon', type === ItemType ? 'active' : '']} />
  154. <img src={src} />
  155. </div>
  156. ),
  157. }}
  158. </Tooltip>
  159. );
  160. })}
  161. </div>,
  162. renderSwitchItem('分割菜单', {
  163. handler: (e) => {
  164. baseHandler('splitMenu', e);
  165. },
  166. def: split,
  167. disabled: !unref(getShowMenuRef),
  168. }),
  169. renderSelectItem('顶栏主题', {
  170. handler: (e) => {
  171. baseHandler('headerMenu', e);
  172. },
  173. def: headerTheme,
  174. options: themeOptions,
  175. disabled: !unref(getShowHeaderRef),
  176. }),
  177. renderSelectItem('菜单主题', {
  178. handler: (e) => {
  179. baseHandler('menuTheme', e);
  180. },
  181. def: menuTheme,
  182. options: themeOptions,
  183. disabled: !unref(getShowMenuRef),
  184. }),
  185. ];
  186. }
  187. /**
  188. * @description:
  189. */
  190. function renderFeatures() {
  191. const {
  192. contentMode,
  193. headerSetting: { fixed },
  194. menuSetting: {
  195. hasDrag,
  196. collapsed,
  197. showSearch,
  198. menuWidth,
  199. topMenuAlign,
  200. collapsedShowTitle,
  201. } = {},
  202. } = appStore.getProjectConfig;
  203. return [
  204. renderSwitchItem('侧边菜单拖拽', {
  205. handler: (e) => {
  206. baseHandler('hasDrag', e);
  207. },
  208. def: hasDrag,
  209. disabled: !unref(getShowMenuRef),
  210. }),
  211. renderSwitchItem('侧边菜单搜索', {
  212. handler: (e) => {
  213. baseHandler('showSearch', e);
  214. },
  215. def: showSearch,
  216. disabled: !unref(getShowMenuRef),
  217. }),
  218. renderSwitchItem('折叠菜单', {
  219. handler: (e) => {
  220. baseHandler('collapsed', e);
  221. },
  222. def: collapsed,
  223. disabled: !unref(getShowMenuRef),
  224. }),
  225. renderSwitchItem('折叠菜单显示名称', {
  226. handler: (e) => {
  227. baseHandler('collapsedShowTitle', e);
  228. },
  229. def: collapsedShowTitle,
  230. disabled: !unref(getShowMenuRef) || !collapsed,
  231. }),
  232. renderSwitchItem('固定header', {
  233. handler: (e) => {
  234. baseHandler('headerFixed', e);
  235. },
  236. def: fixed,
  237. disabled: !unref(getShowHeaderRef),
  238. }),
  239. renderSelectItem('顶部菜单布局', {
  240. handler: (e) => {
  241. baseHandler('topMenuAlign', e);
  242. },
  243. def: topMenuAlign,
  244. options: topMenuAlignOptions,
  245. disabled: !unref(getShowHeaderRef),
  246. }),
  247. renderSelectItem('内容区域宽度', {
  248. handler: (e) => {
  249. baseHandler('contentMode', e);
  250. },
  251. def: contentMode,
  252. options: contentModeOptions,
  253. }),
  254. <div class={`setting-drawer__cell-item`}>
  255. <span>自动锁屏</span>
  256. <InputNumber
  257. style="width:120px"
  258. size="small"
  259. min={0}
  260. onChange={(e) => {
  261. baseHandler('lockTime', e);
  262. }}
  263. defaultValue={appStore.getProjectConfig.lockTime}
  264. formatter={(value: string) => {
  265. if (parseInt(value) === 0) {
  266. return '0(不自动锁屏)';
  267. }
  268. return `${value}分钟`;
  269. }}
  270. />
  271. </div>,
  272. <div class={`setting-drawer__cell-item`}>
  273. <span>菜单展开宽度</span>
  274. <InputNumber
  275. style="width:120px"
  276. size="small"
  277. max={600}
  278. min={100}
  279. step={10}
  280. disabled={!unref(getShowMenuRef)}
  281. defaultValue={menuWidth}
  282. formatter={(value: string) => `${parseInt(value)}px`}
  283. onChange={(e) => {
  284. baseHandler('menuWidth', e);
  285. }}
  286. />
  287. </div>,
  288. ];
  289. }
  290. function renderTransition() {
  291. const { routerTransition, openRouterTransition, openPageLoading } = appStore.getProjectConfig;
  292. return (
  293. <>
  294. {renderSwitchItem('页面切换loading', {
  295. handler: (e) => {
  296. baseHandler('openPageLoading', e);
  297. },
  298. def: openPageLoading,
  299. })}
  300. {renderSwitchItem('切换动画', {
  301. handler: (e) => {
  302. baseHandler('openRouterTransition', e);
  303. },
  304. def: openRouterTransition,
  305. })}
  306. {renderSelectItem('路由动画', {
  307. handler: (e) => {
  308. baseHandler('routerTransition', e);
  309. },
  310. def: routerTransition,
  311. options: routerTransitionOptions,
  312. disabled: !openRouterTransition,
  313. })}
  314. </>
  315. );
  316. }
  317. function renderContent() {
  318. const {
  319. grayMode,
  320. colorWeak,
  321. fullContent,
  322. showLogo,
  323. headerSetting: { show: showHeader },
  324. menuSetting: { show: showMenu },
  325. multiTabsSetting: { show: showMultiple, showQuick, showIcon: showTabIcon },
  326. showBreadCrumb,
  327. showBreadCrumbIcon,
  328. } = unref(getProjectConfigRef);
  329. return [
  330. renderSwitchItem('面包屑', {
  331. handler: (e) => {
  332. baseHandler('showBreadCrumb', e);
  333. },
  334. def: showBreadCrumb,
  335. disabled: !unref(getShowHeaderRef),
  336. }),
  337. renderSwitchItem('面包屑图标', {
  338. handler: (e) => {
  339. baseHandler('showBreadCrumbIcon', e);
  340. },
  341. def: showBreadCrumbIcon,
  342. disabled: !unref(getShowHeaderRef),
  343. }),
  344. renderSwitchItem('标签页', {
  345. handler: (e) => {
  346. baseHandler('showMultiple', e);
  347. },
  348. def: showMultiple,
  349. }),
  350. renderSwitchItem('标签页快捷按钮', {
  351. handler: (e) => {
  352. baseHandler('showQuick', e);
  353. },
  354. def: showQuick,
  355. disabled: !unref(getShowTabsRef),
  356. }),
  357. renderSwitchItem('标签页图标', {
  358. handler: (e) => {
  359. baseHandler('showTabIcon', e);
  360. },
  361. def: showTabIcon,
  362. disabled: !unref(getShowTabsRef),
  363. }),
  364. renderSwitchItem('左侧菜单', {
  365. handler: (e) => {
  366. baseHandler('showSidebar', e);
  367. },
  368. def: showMenu,
  369. disabled: unref(getIsHorizontalRef),
  370. }),
  371. renderSwitchItem('顶栏', {
  372. handler: (e) => {
  373. baseHandler('showHeader', e);
  374. },
  375. def: showHeader,
  376. }),
  377. renderSwitchItem('Logo', {
  378. handler: (e) => {
  379. baseHandler('showLogo', e);
  380. },
  381. def: showLogo,
  382. }),
  383. renderSwitchItem('全屏内容', {
  384. handler: (e) => {
  385. baseHandler('fullContent', e);
  386. },
  387. def: fullContent,
  388. }),
  389. renderSwitchItem('灰色模式', {
  390. handler: (e) => {
  391. baseHandler('grayMode', e);
  392. },
  393. def: grayMode,
  394. }),
  395. renderSwitchItem('色弱模式', {
  396. handler: (e) => {
  397. baseHandler('colorWeak', e);
  398. },
  399. def: colorWeak,
  400. }),
  401. ];
  402. }
  403. function baseHandler(event: string, value: any) {
  404. let config: DeepPartial<ProjectConfig> = {};
  405. if (event === 'layout') {
  406. const { mode, type, split } = value;
  407. const splitOpt = split === undefined ? { split } : {};
  408. config = {
  409. menuSetting: {
  410. mode,
  411. type,
  412. collapsed: false,
  413. ...splitOpt,
  414. },
  415. };
  416. }
  417. if (event === 'hasDrag') {
  418. config = {
  419. menuSetting: {
  420. hasDrag: value,
  421. },
  422. };
  423. }
  424. if (event === 'openPageLoading') {
  425. config = {
  426. openPageLoading: value,
  427. };
  428. }
  429. if (event === 'topMenuAlign') {
  430. config = {
  431. menuSetting: {
  432. topMenuAlign: value,
  433. },
  434. };
  435. }
  436. if (event === 'showBreadCrumb') {
  437. config = {
  438. showBreadCrumb: value,
  439. };
  440. }
  441. if (event === 'showBreadCrumbIcon') {
  442. config = {
  443. showBreadCrumbIcon: value,
  444. };
  445. }
  446. if (event === 'collapsed') {
  447. config = {
  448. menuSetting: {
  449. collapsed: value,
  450. },
  451. };
  452. }
  453. if (event === 'menuWidth') {
  454. config = {
  455. menuSetting: {
  456. menuWidth: value,
  457. },
  458. };
  459. }
  460. if (event === 'collapsedShowTitle') {
  461. config = {
  462. menuSetting: {
  463. collapsedShowTitle: value,
  464. },
  465. };
  466. }
  467. if (event === 'lockTime') {
  468. config = {
  469. lockTime: value,
  470. };
  471. }
  472. if (event === 'showQuick') {
  473. config = {
  474. multiTabsSetting: {
  475. showQuick: value,
  476. },
  477. };
  478. }
  479. if (event === 'showTabIcon') {
  480. config = {
  481. multiTabsSetting: {
  482. showIcon: value,
  483. },
  484. };
  485. }
  486. if (event === 'contentMode') {
  487. config = {
  488. contentMode: value,
  489. };
  490. }
  491. if (event === 'menuTheme') {
  492. config = {
  493. menuSetting: {
  494. theme: value,
  495. },
  496. };
  497. }
  498. if (event === 'splitMenu') {
  499. config = {
  500. menuSetting: {
  501. split: value,
  502. },
  503. };
  504. }
  505. if (event === 'showMultiple') {
  506. config = {
  507. multiTabsSetting: {
  508. show: value,
  509. },
  510. };
  511. }
  512. if (event === 'headerMenu') {
  513. config = {
  514. headerSetting: {
  515. theme: value,
  516. },
  517. };
  518. }
  519. if (event === 'grayMode') {
  520. config = {
  521. grayMode: value,
  522. };
  523. updateGrayMode(value);
  524. }
  525. if (event === 'colorWeak') {
  526. config = {
  527. colorWeak: value,
  528. };
  529. updateColorWeak(value);
  530. }
  531. if (event === 'showLogo') {
  532. config = {
  533. showLogo: value,
  534. };
  535. }
  536. if (event === 'showSearch') {
  537. config = {
  538. menuSetting: {
  539. showSearch: value,
  540. },
  541. };
  542. }
  543. if (event === 'showSidebar') {
  544. config = {
  545. menuSetting: {
  546. show: value,
  547. },
  548. };
  549. }
  550. if (event === 'openRouterTransition') {
  551. config = {
  552. openRouterTransition: value,
  553. };
  554. }
  555. if (event === 'routerTransition') {
  556. config = {
  557. routerTransition: value,
  558. };
  559. }
  560. if (event === 'headerFixed') {
  561. config = {
  562. headerSetting: {
  563. fixed: value,
  564. },
  565. };
  566. }
  567. if (event === 'fullContent') {
  568. config = {
  569. fullContent: value,
  570. };
  571. }
  572. if (event === 'showHeader') {
  573. config = {
  574. headerSetting: {
  575. show: value,
  576. },
  577. };
  578. }
  579. appStore.commitProjectConfigState(config);
  580. }
  581. function handleResetSetting() {
  582. try {
  583. appStore.commitProjectConfigState(defaultSetting);
  584. const { colorWeak, grayMode } = defaultSetting;
  585. // updateTheme(themeColor);
  586. updateColorWeak(colorWeak);
  587. updateGrayMode(grayMode);
  588. createMessage.success('重置成功!');
  589. } catch (error) {
  590. createMessage.error(error);
  591. }
  592. }
  593. function handleClearAndRedo() {
  594. localStorage.clear();
  595. userStore.resumeAllState();
  596. location.reload();
  597. }
  598. function renderSelectItem(text: string, config?: SelectConfig) {
  599. const { handler, def, disabled = false, options } = config || {};
  600. const opt = def ? { value: def, defaultValue: def } : {};
  601. return (
  602. <div class={`setting-drawer__cell-item`}>
  603. <span>{text}</span>
  604. {/* @ts-ignore */}
  605. <Select
  606. {...opt}
  607. disabled={disabled}
  608. size="small"
  609. style={{ width: '120px' }}
  610. onChange={(e) => {
  611. handler && handler(e);
  612. }}
  613. options={options}
  614. />
  615. </div>
  616. );
  617. }
  618. function renderSwitchItem(text: string, options?: SwitchOptions) {
  619. const { handler, def, disabled = false } = options || {};
  620. const opt = def ? { checked: def } : {};
  621. return (
  622. <div class={`setting-drawer__cell-item`}>
  623. <span>{text}</span>
  624. <Switch
  625. {...opt}
  626. disabled={disabled}
  627. onChange={(e) => {
  628. handler && handler(e);
  629. }}
  630. checkedChildren="开"
  631. unCheckedChildren="关"
  632. />
  633. </div>
  634. );
  635. }
  636. return () => (
  637. <BasicDrawer {...attrs} title="项目配置" width={300} wrapClassName="setting-drawer">
  638. {{
  639. default: () => (
  640. <>
  641. <Divider>{() => '导航栏模式'}</Divider>
  642. {renderSidebar()}
  643. <Divider>{() => '界面功能'}</Divider>
  644. {renderFeatures()}
  645. <Divider>{() => '界面显示'}</Divider>
  646. {renderContent()}
  647. <Divider>{() => '切换动画'}</Divider>
  648. {renderTransition()}
  649. <Divider />
  650. <div class="setting-drawer__footer">
  651. <Button type="primary" block onClick={handleCopy}>
  652. {() => (
  653. <>
  654. <CopyOutlined class="mr-2" />
  655. 拷贝
  656. </>
  657. )}
  658. </Button>
  659. <Button block class="mt-2" onClick={handleResetSetting} color="warning">
  660. {() => (
  661. <>
  662. <RedoOutlined class="mr-2" />
  663. 重置
  664. </>
  665. )}
  666. </Button>
  667. <Button block class="mt-2" onClick={handleClearAndRedo} color="error">
  668. {() => (
  669. <>
  670. <RedoOutlined class="mr-2" />
  671. 清空缓存并返回登录页
  672. </>
  673. )}
  674. </Button>
  675. </div>
  676. </>
  677. ),
  678. }}
  679. </BasicDrawer>
  680. );
  681. },
  682. });