SettingDrawer.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. import type { ProjectConfig } from '/@/types/config';
  2. import defaultSetting from '/@/settings/projectSetting';
  3. import { defineComponent, computed, unref, FunctionalComponent } from 'vue';
  4. import { BasicDrawer } from '/@/components/Drawer/index';
  5. import { Divider, Switch, Tooltip, InputNumber, Select } from 'ant-design-vue';
  6. import Button from '/@/components/Button/index.vue';
  7. import { CopyOutlined, RedoOutlined, CheckOutlined } from '@ant-design/icons-vue';
  8. import { MenuTypeEnum } from '/@/enums/menuEnum';
  9. import { appStore } from '/@/store/modules/app';
  10. import { useMessage } from '/@/hooks/web/useMessage';
  11. import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
  12. import { useRootSetting } from '/@/hooks/setting/useRootSetting';
  13. import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
  14. import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
  15. import { useMultipleTabSetting } from '/@/hooks/setting/useMultipleTabSetting';
  16. import { updateColorWeak, updateGrayMode } from '/@/setup/theme';
  17. import { baseHandler } from './handler';
  18. import {
  19. HandlerEnum,
  20. contentModeOptions,
  21. topMenuAlignOptions,
  22. menuTriggerOptions,
  23. routerTransitionOptions,
  24. menuTypeList,
  25. } from './enum';
  26. import { HEADER_PRESET_BG_COLOR_LIST, SIDE_BAR_BG_COLOR_LIST } from '/@/settings/colorSetting';
  27. interface SwitchOptions {
  28. config?: DeepPartial<ProjectConfig>;
  29. def?: any;
  30. disabled?: boolean;
  31. handler?: Fn;
  32. }
  33. interface SelectConfig {
  34. options?: LabelValueOptions;
  35. def?: any;
  36. disabled?: boolean;
  37. handler?: Fn;
  38. }
  39. interface ThemePickerProps {
  40. colorList: string[];
  41. handler: Fn;
  42. def: string;
  43. }
  44. const { createSuccessModal, createMessage } = useMessage();
  45. /**
  46. * Menu type Picker comp
  47. */
  48. const MenuTypePicker: FunctionalComponent = () => {
  49. const { getIsHorizontal, getMenuType } = useMenuSetting();
  50. return (
  51. <div class={`setting-drawer__siderbar`}>
  52. {menuTypeList.map((item) => {
  53. const { title, type: ItemType, mode, src } = item;
  54. return (
  55. <Tooltip title={title} placement="bottom" key={title}>
  56. {{
  57. default: () => (
  58. <div
  59. onClick={baseHandler.bind(null, HandlerEnum.CHANGE_LAYOUT, {
  60. mode: mode,
  61. type: ItemType,
  62. split: unref(getIsHorizontal) ? false : undefined,
  63. })}
  64. >
  65. <CheckOutlined
  66. class={['check-icon', unref(getMenuType) === ItemType ? 'active' : '']}
  67. />
  68. <img src={src} />
  69. </div>
  70. ),
  71. }}
  72. </Tooltip>
  73. );
  74. })}
  75. </div>
  76. );
  77. };
  78. const ThemePicker: FunctionalComponent<ThemePickerProps> = (props) => {
  79. return (
  80. <div class={`setting-drawer__theme-item`}>
  81. {props.colorList.map((color) => {
  82. return (
  83. <span
  84. onClick={() => props.handler?.(color)}
  85. key={color}
  86. class={[props.def === color ? 'active' : '']}
  87. style={{
  88. background: color,
  89. }}
  90. >
  91. <CheckOutlined class="icon" />
  92. </span>
  93. );
  94. })}
  95. </div>
  96. );
  97. };
  98. /**
  99. * FooterButton component
  100. */
  101. const FooterButton: FunctionalComponent = () => {
  102. const { getRootSetting } = useRootSetting();
  103. function handleCopy() {
  104. const { isSuccessRef } = useCopyToClipboard(JSON.stringify(unref(getRootSetting), null, 2));
  105. unref(isSuccessRef) &&
  106. createSuccessModal({
  107. title: '操作成功',
  108. content: '复制成功,请到 src/settings/projectSetting.ts 中修改配置!',
  109. });
  110. }
  111. function handleResetSetting() {
  112. try {
  113. appStore.commitProjectConfigState(defaultSetting);
  114. const { colorWeak, grayMode } = defaultSetting;
  115. // updateTheme(themeColor);
  116. updateColorWeak(colorWeak);
  117. updateGrayMode(grayMode);
  118. createMessage.success('重置成功!');
  119. } catch (error) {
  120. createMessage.error(error);
  121. }
  122. }
  123. function handleClearAndRedo() {
  124. localStorage.clear();
  125. appStore.resumeAllState();
  126. location.reload();
  127. }
  128. return (
  129. <div class="setting-drawer__footer">
  130. <Button type="primary" block onClick={handleCopy}>
  131. {() => (
  132. <>
  133. <CopyOutlined class="mr-2" />
  134. 拷贝
  135. </>
  136. )}
  137. </Button>
  138. <Button block class="mt-2" onClick={handleResetSetting} color="warning">
  139. {() => (
  140. <>
  141. <RedoOutlined class="mr-2" />
  142. 重置
  143. </>
  144. )}
  145. </Button>
  146. <Button block class="mt-2" onClick={handleClearAndRedo} color="error">
  147. {() => (
  148. <>
  149. <RedoOutlined class="mr-2" />
  150. 清空缓存并返回登录页
  151. </>
  152. )}
  153. </Button>
  154. </div>
  155. );
  156. };
  157. export default defineComponent({
  158. name: 'SettingDrawer',
  159. setup(_, { attrs }) {
  160. const {
  161. getContentMode,
  162. getRouterTransition,
  163. getOpenRouterTransition,
  164. getOpenPageLoading,
  165. getShowFooter,
  166. getShowBreadCrumb,
  167. getShowBreadCrumbIcon,
  168. getShowLogo,
  169. getFullContent,
  170. getColorWeak,
  171. getGrayMode,
  172. } = useRootSetting();
  173. const {
  174. getIsHorizontal,
  175. getShowMenu,
  176. getMenuType,
  177. getTrigger,
  178. getCollapsedShowTitle,
  179. getMenuFixed,
  180. getCollapsed,
  181. getShowSearch,
  182. getHasDrag,
  183. getTopMenuAlign,
  184. getAccordion,
  185. getMenuWidth,
  186. getMenuBgColor,
  187. getIsTopMenu,
  188. getSplit,
  189. } = useMenuSetting();
  190. const { getShowHeader, getFixed: getHeaderFixed, getHeaderBgColor } = useHeaderSetting();
  191. const { getShowMultipleTab, getShowQuick } = useMultipleTabSetting();
  192. const getShowMenuRef = computed(() => {
  193. return unref(getShowMenu) && !unref(getIsHorizontal);
  194. });
  195. function renderSidebar() {
  196. return (
  197. <>
  198. <MenuTypePicker />
  199. {renderSwitchItem('分割菜单', {
  200. handler: (e) => {
  201. baseHandler(HandlerEnum.MENU_SPLIT, e);
  202. },
  203. def: unref(getSplit),
  204. disabled: !unref(getShowMenuRef) || unref(getMenuType) !== MenuTypeEnum.MIX,
  205. })}
  206. </>
  207. );
  208. }
  209. function renderTheme() {
  210. return (
  211. <>
  212. <Divider>{() => '顶栏主题'}</Divider>
  213. <ThemePicker
  214. colorList={HEADER_PRESET_BG_COLOR_LIST}
  215. def={unref(getHeaderBgColor)}
  216. handler={(e) => {
  217. baseHandler(HandlerEnum.HEADER_THEME, e);
  218. }}
  219. />
  220. <Divider>{() => '菜单主题'}</Divider>
  221. <ThemePicker
  222. colorList={SIDE_BAR_BG_COLOR_LIST}
  223. def={unref(getMenuBgColor)}
  224. handler={(e) => {
  225. baseHandler(HandlerEnum.MENU_THEME, e);
  226. }}
  227. />
  228. </>
  229. );
  230. }
  231. /**
  232. * @description:
  233. */
  234. function renderFeatures() {
  235. return [
  236. renderSwitchItem('侧边菜单拖拽', {
  237. handler: (e) => {
  238. baseHandler(HandlerEnum.MENU_HAS_DRAG, e);
  239. },
  240. def: unref(getHasDrag),
  241. disabled: !unref(getShowMenuRef),
  242. }),
  243. renderSwitchItem('侧边菜单搜索', {
  244. handler: (e) => {
  245. baseHandler(HandlerEnum.MENU_SHOW_SEARCH, e);
  246. },
  247. def: unref(getShowSearch),
  248. disabled: !unref(getShowMenuRef),
  249. }),
  250. renderSwitchItem('侧边菜单手风琴模式', {
  251. handler: (e) => {
  252. baseHandler(HandlerEnum.MENU_ACCORDION, e);
  253. },
  254. def: unref(getAccordion),
  255. disabled: !unref(getShowMenuRef),
  256. }),
  257. renderSwitchItem('折叠菜单', {
  258. handler: (e) => {
  259. baseHandler(HandlerEnum.MENU_COLLAPSED, e);
  260. },
  261. def: unref(getCollapsed),
  262. disabled: !unref(getShowMenuRef),
  263. }),
  264. renderSwitchItem('折叠菜单显示名称', {
  265. handler: (e) => {
  266. baseHandler(HandlerEnum.MENU_COLLAPSED_SHOW_TITLE, e);
  267. },
  268. def: unref(getCollapsedShowTitle),
  269. disabled: !unref(getShowMenuRef) || !unref(getCollapsed),
  270. }),
  271. renderSwitchItem('固定header', {
  272. handler: (e) => {
  273. baseHandler(HandlerEnum.HEADER_FIXED, e);
  274. },
  275. def: unref(getHeaderFixed),
  276. disabled: !unref(getShowHeader),
  277. }),
  278. renderSwitchItem('固定Siderbar', {
  279. handler: (e) => {
  280. baseHandler(HandlerEnum.MENU_FIXED, e);
  281. },
  282. def: unref(getMenuFixed),
  283. disabled: !unref(getShowMenuRef),
  284. }),
  285. renderSelectItem('顶部菜单布局', {
  286. handler: (e) => {
  287. baseHandler(HandlerEnum.MENU_TOP_ALIGN, e);
  288. },
  289. def: unref(getTopMenuAlign),
  290. options: topMenuAlignOptions,
  291. disabled: !unref(getShowHeader) || (!unref(getIsTopMenu) && !unref(getSplit)),
  292. }),
  293. renderSelectItem('菜单折叠按钮', {
  294. handler: (e) => {
  295. baseHandler(HandlerEnum.MENU_TRIGGER, e);
  296. },
  297. disabled: !unref(getShowMenuRef),
  298. def: unref(getTrigger),
  299. options: menuTriggerOptions,
  300. }),
  301. renderSelectItem('内容区域宽度', {
  302. handler: (e) => {
  303. baseHandler(HandlerEnum.CONTENT_MODE, e);
  304. },
  305. def: unref(getContentMode),
  306. options: contentModeOptions,
  307. }),
  308. <div class={`setting-drawer__cell-item`}>
  309. <span>自动锁屏</span>
  310. <InputNumber
  311. style="width:120px"
  312. size="small"
  313. min={0}
  314. onChange={(e: any) => {
  315. baseHandler(HandlerEnum.LOCK_TIME, e);
  316. }}
  317. defaultValue={appStore.getProjectConfig.lockTime}
  318. formatter={(value: string) => {
  319. if (parseInt(value) === 0) {
  320. return '0(不自动锁屏)';
  321. }
  322. return `${value}分钟`;
  323. }}
  324. />
  325. </div>,
  326. <div class={`setting-drawer__cell-item`}>
  327. <span>菜单展开宽度</span>
  328. <InputNumber
  329. style="width:120px"
  330. size="small"
  331. max={600}
  332. min={100}
  333. step={10}
  334. disabled={!unref(getShowMenuRef)}
  335. defaultValue={unref(getMenuWidth)}
  336. formatter={(value: string) => `${parseInt(value)}px`}
  337. onChange={(e: any) => {
  338. baseHandler(HandlerEnum.MENU_WIDTH, e);
  339. }}
  340. />
  341. </div>,
  342. ];
  343. }
  344. function renderContent() {
  345. return [
  346. renderSwitchItem('面包屑', {
  347. handler: (e) => {
  348. baseHandler(HandlerEnum.SHOW_BREADCRUMB, e);
  349. },
  350. def: unref(getShowBreadCrumb),
  351. disabled: !unref(getShowHeader),
  352. }),
  353. renderSwitchItem('面包屑图标', {
  354. handler: (e) => {
  355. baseHandler(HandlerEnum.SHOW_BREADCRUMB_ICON, e);
  356. },
  357. def: unref(getShowBreadCrumbIcon),
  358. disabled: !unref(getShowHeader),
  359. }),
  360. renderSwitchItem('标签页', {
  361. handler: (e) => {
  362. baseHandler(HandlerEnum.TABS_SHOW, e);
  363. },
  364. def: unref(getShowMultipleTab),
  365. }),
  366. renderSwitchItem('标签页快捷按钮', {
  367. handler: (e) => {
  368. baseHandler(HandlerEnum.TABS_SHOW_QUICK, e);
  369. },
  370. def: unref(getShowQuick),
  371. disabled: !unref(getShowMultipleTab),
  372. }),
  373. renderSwitchItem('左侧菜单', {
  374. handler: (e) => {
  375. baseHandler(HandlerEnum.MENU_SHOW_SIDEBAR, e);
  376. },
  377. def: unref(getShowMenu),
  378. disabled: unref(getIsHorizontal),
  379. }),
  380. renderSwitchItem('顶栏', {
  381. handler: (e) => {
  382. baseHandler(HandlerEnum.HEADER_SHOW, e);
  383. },
  384. def: unref(getShowHeader),
  385. }),
  386. renderSwitchItem('Logo', {
  387. handler: (e) => {
  388. baseHandler(HandlerEnum.SHOW_LOGO, e);
  389. },
  390. def: unref(getShowLogo),
  391. }),
  392. renderSwitchItem('页脚', {
  393. handler: (e) => {
  394. baseHandler(HandlerEnum.SHOW_FOOTER, e);
  395. },
  396. def: unref(getShowFooter),
  397. }),
  398. renderSwitchItem('全屏内容', {
  399. handler: (e) => {
  400. baseHandler(HandlerEnum.FULL_CONTENT, e);
  401. },
  402. def: unref(getFullContent),
  403. }),
  404. renderSwitchItem('灰色模式', {
  405. handler: (e) => {
  406. baseHandler(HandlerEnum.GRAY_MODE, e);
  407. },
  408. def: unref(getGrayMode),
  409. }),
  410. renderSwitchItem('色弱模式', {
  411. handler: (e) => {
  412. baseHandler(HandlerEnum.COLOR_WEAK, e);
  413. },
  414. def: unref(getColorWeak),
  415. }),
  416. ];
  417. }
  418. function renderTransition() {
  419. return (
  420. <>
  421. {renderSwitchItem('页面切换loading', {
  422. handler: (e) => {
  423. baseHandler(HandlerEnum.OPEN_PAGE_LOADING, e);
  424. },
  425. def: unref(getOpenPageLoading),
  426. })}
  427. {renderSwitchItem('切换动画', {
  428. handler: (e) => {
  429. baseHandler(HandlerEnum.OPEN_ROUTE_TRANSITION, e);
  430. },
  431. def: unref(getOpenRouterTransition),
  432. })}
  433. {renderSelectItem('路由动画', {
  434. handler: (e) => {
  435. baseHandler(HandlerEnum.ROUTER_TRANSITION, e);
  436. },
  437. def: unref(getRouterTransition),
  438. options: routerTransitionOptions,
  439. disabled: !unref(getOpenRouterTransition),
  440. })}
  441. </>
  442. );
  443. }
  444. function renderSelectItem(text: string, config?: SelectConfig) {
  445. const { handler, def, disabled = false, options } = config || {};
  446. const opt = def ? { value: def, defaultValue: def } : {};
  447. return (
  448. <div class={`setting-drawer__cell-item`}>
  449. <span>{text}</span>
  450. <Select
  451. {...opt}
  452. disabled={disabled}
  453. size="small"
  454. style={{ width: '120px' }}
  455. onChange={(e) => {
  456. handler && handler(e);
  457. }}
  458. options={options}
  459. />
  460. </div>
  461. );
  462. }
  463. function renderSwitchItem(text: string, options?: SwitchOptions) {
  464. const { handler, def, disabled = false } = options || {};
  465. const opt = def ? { checked: def } : {};
  466. return (
  467. <div class={`setting-drawer__cell-item`}>
  468. <span>{text}</span>
  469. <Switch
  470. {...opt}
  471. disabled={disabled}
  472. onChange={(e: any) => {
  473. handler && handler(e);
  474. }}
  475. checkedChildren="开"
  476. unCheckedChildren="关"
  477. />
  478. </div>
  479. );
  480. }
  481. return () => (
  482. <BasicDrawer {...attrs} title="项目配置" width={300} wrapClassName="setting-drawer">
  483. {{
  484. default: () => (
  485. <>
  486. <Divider>{() => '导航栏模式'}</Divider>
  487. {renderSidebar()}
  488. {renderTheme()}
  489. <Divider>{() => '界面功能'}</Divider>
  490. {renderFeatures()}
  491. <Divider>{() => '界面显示'}</Divider>
  492. {renderContent()}
  493. <Divider>{() => '切换动画'}</Divider>
  494. {renderTransition()}
  495. <Divider />
  496. <FooterButton />
  497. </>
  498. ),
  499. }}
  500. </BasicDrawer>
  501. );
  502. },
  503. });