Commit 4f6b65b8a1b7e694718b4aa42aced1e59e90ec9e
1 parent
58f988a7
feat(trigger): add trigger config
Showing
22 changed files
with
255 additions
and
68 deletions
CHANGELOG.zh_CN.md
1 | ## Wip | 1 | ## Wip |
2 | 2 | ||
3 | +### ✨ Features | ||
4 | + | ||
5 | +- 菜单 trigger 可以选择位置 | ||
6 | +- 增加富文本嵌入表单的示例 | ||
7 | +- 表单组件 schema 增加 `required`属性。简化配置 | ||
8 | +- openModal 和 openDrawer 第二个参数可以代替`transferModalData`传参到内部 | ||
9 | + | ||
3 | ### ⚡ Performance Improvements | 10 | ### ⚡ Performance Improvements |
4 | 11 | ||
5 | - 菜单性能继续优化,更流畅 | 12 | - 菜单性能继续优化,更流畅 |
6 | - 优化懒加载组件及示例 | 13 | - 优化懒加载组件及示例 |
14 | +- layout 样式微调 | ||
7 | 15 | ||
8 | ### 🎫 Chores | 16 | ### 🎫 Chores |
9 | 17 |
package.json
@@ -28,6 +28,7 @@ | @@ -28,6 +28,7 @@ | ||
28 | "echarts": "^4.9.0", | 28 | "echarts": "^4.9.0", |
29 | "lodash-es": "^4.17.15", | 29 | "lodash-es": "^4.17.15", |
30 | "mockjs": "^1.1.0", | 30 | "mockjs": "^1.1.0", |
31 | + "moment": "^2.29.1", | ||
31 | "nprogress": "^0.2.0", | 32 | "nprogress": "^0.2.0", |
32 | "path-to-regexp": "^6.2.0", | 33 | "path-to-regexp": "^6.2.0", |
33 | "qrcode": "^1.4.4", | 34 | "qrcode": "^1.4.4", |
src/App.vue
@@ -11,7 +11,7 @@ | @@ -11,7 +11,7 @@ | ||
11 | 11 | ||
12 | import zhCN from 'ant-design-vue/es/locale/zh_CN'; | 12 | import zhCN from 'ant-design-vue/es/locale/zh_CN'; |
13 | import moment from 'moment'; | 13 | import moment from 'moment'; |
14 | - import 'moment/locale/zh-cn'; | 14 | + import 'moment/dist/locale/zh-cn'; |
15 | 15 | ||
16 | import { useConfigProvider, useInitAppConfigStore, useListenerNetWork } from './useApp'; | 16 | import { useConfigProvider, useInitAppConfigStore, useListenerNetWork } from './useApp'; |
17 | import { useLockPage } from '/@/hooks/web/useLockPage'; | 17 | import { useLockPage } from '/@/hooks/web/useLockPage'; |
src/components/Menu/src/BasicMenu.tsx
@@ -73,7 +73,7 @@ export default defineComponent({ | @@ -73,7 +73,7 @@ export default defineComponent({ | ||
73 | offset += 46; | 73 | offset += 46; |
74 | } | 74 | } |
75 | return { | 75 | return { |
76 | - height: `calc(100% - ${offset - 10}px)`, | 76 | + height: `calc(100% - ${offset - 12}px)`, |
77 | position: 'relative', | 77 | position: 'relative', |
78 | overflowY: 'auto', | 78 | overflowY: 'auto', |
79 | }; | 79 | }; |
src/components/Menu/src/MenuContent.tsx
@@ -32,7 +32,7 @@ export default defineComponent({ | @@ -32,7 +32,7 @@ export default defineComponent({ | ||
32 | * @description: 渲染图标 | 32 | * @description: 渲染图标 |
33 | */ | 33 | */ |
34 | function renderIcon(icon: string) { | 34 | function renderIcon(icon: string) { |
35 | - return icon ? <Icon icon={icon} size={18} class="mr-1 menu-item-icon" /> : null; | 35 | + return icon ? <Icon icon={icon} size={18} class="menu-item-icon" /> : null; |
36 | } | 36 | } |
37 | 37 | ||
38 | return () => { | 38 | return () => { |
src/components/Menu/src/index.less
@@ -41,6 +41,7 @@ | @@ -41,6 +41,7 @@ | ||
41 | font-size: 12px; | 41 | font-size: 12px; |
42 | flex-direction: column; | 42 | flex-direction: column; |
43 | align-items: center; | 43 | align-items: center; |
44 | + line-height: 24px; | ||
44 | } | 45 | } |
45 | 46 | ||
46 | & > li > .ant-menu-submenu-title { | 47 | & > li > .ant-menu-submenu-title { |
@@ -183,9 +184,17 @@ | @@ -183,9 +184,17 @@ | ||
183 | transition: unset; | 184 | transition: unset; |
184 | } | 185 | } |
185 | 186 | ||
187 | + &:not(.basic-menu__sidebar-hor).ant-menu-inline-collapsed { | ||
188 | + .basic-menu-item__level1 { | ||
189 | + > div { | ||
190 | + align-items: center; | ||
191 | + } | ||
192 | + } | ||
193 | + } | ||
194 | + | ||
186 | &.ant-menu-dark:not(.basic-menu__sidebar-hor):not(.basic-menu__second) { | 195 | &.ant-menu-dark:not(.basic-menu__sidebar-hor):not(.basic-menu__second) { |
187 | // Reset menu item row height | 196 | // Reset menu item row height |
188 | - .ant-menu-item, | 197 | + .ant-menu-item:not(.basic-menu-item__level1), |
189 | .ant-menu-sub.ant-menu-inline > .ant-menu-item, | 198 | .ant-menu-sub.ant-menu-inline > .ant-menu-item, |
190 | .ant-menu-sub.ant-menu-inline > .ant-menu-submenu > .ant-menu-submenu-title { | 199 | .ant-menu-sub.ant-menu-inline > .ant-menu-submenu > .ant-menu-submenu-title { |
191 | height: @app-menu-item-height; | 200 | height: @app-menu-item-height; |
src/enums/menuEnum.ts
@@ -17,6 +17,16 @@ export enum MenuThemeEnum { | @@ -17,6 +17,16 @@ export enum MenuThemeEnum { | ||
17 | LIGHT = 'light', | 17 | LIGHT = 'light', |
18 | } | 18 | } |
19 | 19 | ||
20 | +// 折叠触发器位置 | ||
21 | +export enum TriggerEnum { | ||
22 | + // 不显示 | ||
23 | + NONE = 'NONE', | ||
24 | + // 菜单底部 | ||
25 | + FOOTER = 'FOOTER', | ||
26 | + // 头部 | ||
27 | + HEADER = 'HEADER', | ||
28 | +} | ||
29 | + | ||
20 | export type Mode = 'vertical' | 'vertical-right' | 'horizontal' | 'inline'; | 30 | export type Mode = 'vertical' | 'vertical-right' | 'horizontal' | 'inline'; |
21 | 31 | ||
22 | // menu mode | 32 | // menu mode |
src/layouts/Logo.vue
1 | <template> | 1 | <template> |
2 | - <div class="app-logo anticon" @click="handleGoHome" :style="wrapStyle"> | 2 | + <div class="app-logo anticon" :class="theme" @click="handleGoHome" :style="wrapStyle"> |
3 | <img :src="logo" /> | 3 | <img :src="logo" /> |
4 | <div v-if="show" class="logo-title ml-2 ellipsis">{{ globSetting.title }}</div> | 4 | <div v-if="show" class="logo-title ml-2 ellipsis">{{ globSetting.title }}</div> |
5 | </div> | 5 | </div> |
@@ -26,6 +26,9 @@ | @@ -26,6 +26,9 @@ | ||
26 | type: Boolean as PropType<boolean>, | 26 | type: Boolean as PropType<boolean>, |
27 | default: true, | 27 | default: true, |
28 | }, | 28 | }, |
29 | + theme: { | ||
30 | + type: String, | ||
31 | + }, | ||
29 | }, | 32 | }, |
30 | setup(props) { | 33 | setup(props) { |
31 | const showRef = ref<boolean>(!!props.showTitle); | 34 | const showRef = ref<boolean>(!!props.showTitle); |
@@ -80,6 +83,9 @@ | @@ -80,6 +83,9 @@ | ||
80 | padding-left: 16px; | 83 | padding-left: 16px; |
81 | cursor: pointer; | 84 | cursor: pointer; |
82 | // justify-content: center; | 85 | // justify-content: center; |
86 | + &.light { | ||
87 | + border-bottom: 1px solid @border-color-base; | ||
88 | + } | ||
83 | 89 | ||
84 | .logo-title { | 90 | .logo-title { |
85 | font-size: 18px; | 91 | font-size: 18px; |
src/layouts/default/LayoutContent.tsx
1 | import { defineComponent } from 'vue'; | 1 | import { defineComponent } from 'vue'; |
2 | -import { Layout } from 'ant-design-vue'; | 2 | +// import { Layout } from 'ant-design-vue'; |
3 | // hooks | 3 | // hooks |
4 | 4 | ||
5 | import { ContentEnum } from '/@/enums/appEnum'; | 5 | import { ContentEnum } from '/@/enums/appEnum'; |
@@ -13,9 +13,9 @@ export default defineComponent({ | @@ -13,9 +13,9 @@ export default defineComponent({ | ||
13 | const { contentMode } = getProjectConfig; | 13 | const { contentMode } = getProjectConfig; |
14 | const wrapClass = contentMode === ContentEnum.FULL ? 'full' : 'fixed'; | 14 | const wrapClass = contentMode === ContentEnum.FULL ? 'full' : 'fixed'; |
15 | return ( | 15 | return ( |
16 | - <Layout.Content class={`layout-content ${wrapClass} `}> | ||
17 | - {() => <PageLayout />} | ||
18 | - </Layout.Content> | 16 | + // <Layout.Content class={`layout-content ${wrapClass} `}> |
17 | + <PageLayout class={`layout-content ${wrapClass} `} /> | ||
18 | + // </Layout.Content> | ||
19 | ); | 19 | ); |
20 | }; | 20 | }; |
21 | }, | 21 | }, |
src/layouts/default/LayoutHeader.tsx
@@ -6,6 +6,7 @@ import UserDropdown from './UserDropdown'; | @@ -6,6 +6,7 @@ import UserDropdown from './UserDropdown'; | ||
6 | import LayoutMenu from './LayoutMenu'; | 6 | import LayoutMenu from './LayoutMenu'; |
7 | import LayoutBreadcrumb from './LayoutBreadcrumb'; | 7 | import LayoutBreadcrumb from './LayoutBreadcrumb'; |
8 | import LockAction from './actions/LockActionItem'; | 8 | import LockAction from './actions/LockActionItem'; |
9 | +import LayoutTrigger from './LayoutTrigger'; | ||
9 | import NoticeAction from './actions/notice/NoticeActionItem.vue'; | 10 | import NoticeAction from './actions/notice/NoticeActionItem.vue'; |
10 | import { | 11 | import { |
11 | RedoOutlined, | 12 | RedoOutlined, |
@@ -25,7 +26,7 @@ import { useModal } from '/@/components/Modal/index'; | @@ -25,7 +26,7 @@ import { useModal } from '/@/components/Modal/index'; | ||
25 | import { appStore } from '/@/store/modules/app'; | 26 | import { appStore } from '/@/store/modules/app'; |
26 | import { errorStore } from '/@/store/modules/error'; | 27 | import { errorStore } from '/@/store/modules/error'; |
27 | 28 | ||
28 | -import { MenuModeEnum, MenuSplitTyeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; | 29 | +import { MenuModeEnum, MenuSplitTyeEnum, MenuTypeEnum, TriggerEnum } from '/@/enums/menuEnum'; |
29 | import { GITHUB_URL } from '/@/settings/siteSetting'; | 30 | import { GITHUB_URL } from '/@/settings/siteSetting'; |
30 | export default defineComponent({ | 31 | export default defineComponent({ |
31 | name: 'DefaultLayoutHeader', | 32 | name: 'DefaultLayoutHeader', |
@@ -75,6 +76,13 @@ export default defineComponent({ | @@ -75,6 +76,13 @@ export default defineComponent({ | ||
75 | return theme ? `layout-header__header--${theme}` : ''; | 76 | return theme ? `layout-header__header--${theme}` : ''; |
76 | }); | 77 | }); |
77 | 78 | ||
79 | + const showHeaderTrigger = computed(() => { | ||
80 | + const { show, trigger, hidden } = unref(getProjectConfigRef).menuSetting; | ||
81 | + | ||
82 | + if (!show || !hidden) return false; | ||
83 | + return trigger === TriggerEnum.HEADER; | ||
84 | + }); | ||
85 | + | ||
78 | function handleToErrorList() { | 86 | function handleToErrorList() { |
79 | errorStore.commitErrorListCountState(0); | 87 | errorStore.commitErrorListCountState(0); |
80 | push('/exception/error-log'); | 88 | push('/exception/error-log'); |
@@ -92,6 +100,7 @@ export default defineComponent({ | @@ -92,6 +100,7 @@ export default defineComponent({ | ||
92 | const { | 100 | const { |
93 | useErrorHandle, | 101 | useErrorHandle, |
94 | showLogo, | 102 | showLogo, |
103 | + multiTabsSetting: { show: showTab }, | ||
95 | headerSetting: { | 104 | headerSetting: { |
96 | theme: headerTheme, | 105 | theme: headerTheme, |
97 | useLockPage, | 106 | useLockPage, |
@@ -114,11 +123,17 @@ export default defineComponent({ | @@ -114,11 +123,17 @@ export default defineComponent({ | ||
114 | {() => ( | 123 | {() => ( |
115 | <> | 124 | <> |
116 | <div class="layout-header__content "> | 125 | <div class="layout-header__content "> |
117 | - {showLogo && !isSidebarType && <Logo class={`layout-header__logo`} />} | ||
118 | - | ||
119 | - {mode !== MenuModeEnum.HORIZONTAL && showBreadCrumb && !splitMenu && ( | ||
120 | - <LayoutBreadcrumb showIcon={showBreadCrumbIcon} /> | 126 | + {showLogo && !isSidebarType && ( |
127 | + <Logo class={`layout-header__logo`} theme={headerTheme} /> | ||
121 | )} | 128 | )} |
129 | + | ||
130 | + <div class="layout-header__left"> | ||
131 | + {unref(showHeaderTrigger) && <LayoutTrigger theme={headerTheme} sider={false} />} | ||
132 | + {mode !== MenuModeEnum.HORIZONTAL && showBreadCrumb && !splitMenu && ( | ||
133 | + <LayoutBreadcrumb showIcon={showBreadCrumbIcon} /> | ||
134 | + )} | ||
135 | + </div> | ||
136 | + | ||
122 | {unref(showTopMenu) && ( | 137 | {unref(showTopMenu) && ( |
123 | <div | 138 | <div |
124 | class={[`layout-header__menu `]} | 139 | class={[`layout-header__menu `]} |
@@ -193,7 +208,7 @@ export default defineComponent({ | @@ -193,7 +208,7 @@ export default defineComponent({ | ||
193 | </Tooltip> | 208 | </Tooltip> |
194 | </div> | 209 | </div> |
195 | )} | 210 | )} |
196 | - {showRedo && ( | 211 | + {showRedo && showTab && ( |
197 | <Tooltip> | 212 | <Tooltip> |
198 | {{ | 213 | {{ |
199 | title: () => '刷新', | 214 | title: () => '刷新', |
src/layouts/default/LayoutMenu.tsx
@@ -68,9 +68,6 @@ export default defineComponent({ | @@ -68,9 +68,6 @@ export default defineComponent({ | ||
68 | return unref(getProjectConfigRef).menuSetting.mode === MenuModeEnum.HORIZONTAL; | 68 | return unref(getProjectConfigRef).menuSetting.mode === MenuModeEnum.HORIZONTAL; |
69 | }); | 69 | }); |
70 | 70 | ||
71 | - onMounted(() => { | ||
72 | - genMenus(); | ||
73 | - }); | ||
74 | const [throttleHandleSplitLeftMenu] = useThrottle(handleSplitLeftMenu, 50); | 71 | const [throttleHandleSplitLeftMenu] = useThrottle(handleSplitLeftMenu, 50); |
75 | 72 | ||
76 | // watch( | 73 | // watch( |
@@ -90,6 +87,7 @@ export default defineComponent({ | @@ -90,6 +87,7 @@ export default defineComponent({ | ||
90 | immediate: true, | 87 | immediate: true, |
91 | } | 88 | } |
92 | ); | 89 | ); |
90 | + | ||
93 | watch( | 91 | watch( |
94 | [() => permissionStore.getLastBuildMenuTimeState, permissionStore.getBackMenuListState], | 92 | [() => permissionStore.getLastBuildMenuTimeState, permissionStore.getBackMenuListState], |
95 | () => { | 93 | () => { |
@@ -112,7 +110,7 @@ export default defineComponent({ | @@ -112,7 +110,7 @@ export default defineComponent({ | ||
112 | if (!children) { | 110 | if (!children) { |
113 | appStore.commitProjectConfigState({ | 111 | appStore.commitProjectConfigState({ |
114 | menuSetting: { | 112 | menuSetting: { |
115 | - show: false, | 113 | + hidden: false, |
116 | }, | 114 | }, |
117 | }); | 115 | }); |
118 | flatMenusRef.value = []; | 116 | flatMenusRef.value = []; |
@@ -122,7 +120,7 @@ export default defineComponent({ | @@ -122,7 +120,7 @@ export default defineComponent({ | ||
122 | const flatChildren = await getFlatChildrenMenus(children); | 120 | const flatChildren = await getFlatChildrenMenus(children); |
123 | appStore.commitProjectConfigState({ | 121 | appStore.commitProjectConfigState({ |
124 | menuSetting: { | 122 | menuSetting: { |
125 | - show: true, | 123 | + hidden: true, |
126 | }, | 124 | }, |
127 | }); | 125 | }); |
128 | flatMenusRef.value = flatChildren; | 126 | flatMenusRef.value = flatChildren; |
@@ -193,6 +191,10 @@ export default defineComponent({ | @@ -193,6 +191,10 @@ export default defineComponent({ | ||
193 | ); | 191 | ); |
194 | }); | 192 | }); |
195 | 193 | ||
194 | + onMounted(() => { | ||
195 | + genMenus(); | ||
196 | + }); | ||
197 | + | ||
196 | return () => { | 198 | return () => { |
197 | const { | 199 | const { |
198 | showLogo, | 200 | showLogo, |
@@ -229,7 +231,11 @@ export default defineComponent({ | @@ -229,7 +231,11 @@ export default defineComponent({ | ||
229 | {{ | 231 | {{ |
230 | header: () => | 232 | header: () => |
231 | isShowLogo && ( | 233 | isShowLogo && ( |
232 | - <Logo showTitle={!collapsed} class={[`layout-menu__logo`, themeData]} /> | 234 | + <Logo |
235 | + showTitle={!collapsed} | ||
236 | + class={[`layout-menu__logo`, themeData]} | ||
237 | + theme={themeData} | ||
238 | + /> | ||
233 | ), | 239 | ), |
234 | }} | 240 | }} |
235 | </BasicMenu> | 241 | </BasicMenu> |
src/layouts/default/LayoutSideBar.tsx
1 | import { computed, defineComponent, nextTick, onMounted, ref, unref } from 'vue'; | 1 | import { computed, defineComponent, nextTick, onMounted, ref, unref } from 'vue'; |
2 | 2 | ||
3 | import { Layout } from 'ant-design-vue'; | 3 | import { Layout } from 'ant-design-vue'; |
4 | -import SideBarTrigger from './SideBarTrigger'; | 4 | +import LayoutTrigger from './LayoutTrigger'; |
5 | import { menuStore } from '/@/store/modules/menu'; | 5 | import { menuStore } from '/@/store/modules/menu'; |
6 | 6 | ||
7 | // import darkMiniIMg from '/@/assets/images/sidebar/dark-mini.png'; | 7 | // import darkMiniIMg from '/@/assets/images/sidebar/dark-mini.png'; |
8 | // import lightMiniImg from '/@/assets/images/sidebar/light-mini.png'; | 8 | // import lightMiniImg from '/@/assets/images/sidebar/light-mini.png'; |
9 | // import lightImg from '/@/assets/images/sidebar/light.png'; | 9 | // import lightImg from '/@/assets/images/sidebar/light.png'; |
10 | import { appStore } from '/@/store/modules/app'; | 10 | import { appStore } from '/@/store/modules/app'; |
11 | -import { MenuModeEnum, MenuSplitTyeEnum } from '/@/enums/menuEnum'; | 11 | +import { MenuModeEnum, MenuSplitTyeEnum, TriggerEnum } from '/@/enums/menuEnum'; |
12 | import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum'; | 12 | import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum'; |
13 | import { useDebounce } from '/@/hooks/core/useDebounce'; | 13 | import { useDebounce } from '/@/hooks/core/useDebounce'; |
14 | import LayoutMenu from './LayoutMenu'; | 14 | import LayoutMenu from './LayoutMenu'; |
@@ -133,6 +133,25 @@ export default defineComponent({ | @@ -133,6 +133,25 @@ export default defineComponent({ | ||
133 | return unref(brokenRef) ? 0 : unref(getMiniWidth); | 133 | return unref(brokenRef) ? 0 : unref(getMiniWidth); |
134 | }); | 134 | }); |
135 | 135 | ||
136 | + const showTrigger = computed(() => { | ||
137 | + const { | ||
138 | + menuSetting: { trigger }, | ||
139 | + } = unref(getProjectConfigRef); | ||
140 | + return trigger !== TriggerEnum.NONE && trigger === TriggerEnum.FOOTER; | ||
141 | + }); | ||
142 | + | ||
143 | + function handleSiderClick(e: ChangeEvent) { | ||
144 | + if (!e || !e.target || e.target.className !== 'basic-menu__content') return; | ||
145 | + | ||
146 | + const { collapsed, show } = appStore.getProjectConfig.menuSetting; | ||
147 | + if (!collapsed || !show) return; | ||
148 | + appStore.commitProjectConfigState({ | ||
149 | + menuSetting: { | ||
150 | + collapsed: false, | ||
151 | + }, | ||
152 | + }); | ||
153 | + } | ||
154 | + | ||
136 | function renderDragLine() { | 155 | function renderDragLine() { |
137 | const { menuSetting: { hasDrag = true } = {} } = unref(getProjectConfigRef); | 156 | const { menuSetting: { hasDrag = true } = {} } = unref(getProjectConfigRef); |
138 | return ( | 157 | return ( |
@@ -149,8 +168,22 @@ export default defineComponent({ | @@ -149,8 +168,22 @@ export default defineComponent({ | ||
149 | menuSetting: { theme, split: splitMenu }, | 168 | menuSetting: { theme, split: splitMenu }, |
150 | } = unref(getProjectConfigRef); | 169 | } = unref(getProjectConfigRef); |
151 | const { getCollapsedState, getMenuWidthState } = menuStore; | 170 | const { getCollapsedState, getMenuWidthState } = menuStore; |
171 | + | ||
172 | + const triggerDom = unref(showTrigger) | ||
173 | + ? { | ||
174 | + trigger: () => <LayoutTrigger />, | ||
175 | + } | ||
176 | + : {}; | ||
177 | + | ||
178 | + const triggerAttr = unref(showTrigger) | ||
179 | + ? {} | ||
180 | + : { | ||
181 | + trigger: null, | ||
182 | + }; | ||
183 | + | ||
152 | return ( | 184 | return ( |
153 | <Layout.Sider | 185 | <Layout.Sider |
186 | + onClick={handleSiderClick} | ||
154 | onCollapse={onCollapseChange} | 187 | onCollapse={onCollapseChange} |
155 | breakpoint="md" | 188 | breakpoint="md" |
156 | width={getMenuWidthState} | 189 | width={getMenuWidthState} |
@@ -161,9 +194,10 @@ export default defineComponent({ | @@ -161,9 +194,10 @@ export default defineComponent({ | ||
161 | class="layout-sidebar" | 194 | class="layout-sidebar" |
162 | ref={sideRef} | 195 | ref={sideRef} |
163 | onBreakpoint={handleBreakpoint} | 196 | onBreakpoint={handleBreakpoint} |
197 | + {...triggerAttr} | ||
164 | > | 198 | > |
165 | {{ | 199 | {{ |
166 | - trigger: () => <SideBarTrigger />, | 200 | + ...triggerDom, |
167 | default: () => ( | 201 | default: () => ( |
168 | <> | 202 | <> |
169 | <LayoutMenu | 203 | <LayoutMenu |
src/layouts/default/LayoutTrigger.tsx
0 → 100644
1 | +import { | ||
2 | + DoubleRightOutlined, | ||
3 | + DoubleLeftOutlined, | ||
4 | + MenuUnfoldOutlined, | ||
5 | + MenuFoldOutlined, | ||
6 | +} from '@ant-design/icons-vue'; | ||
7 | +import { defineComponent } from 'vue'; | ||
8 | + | ||
9 | +// store | ||
10 | +import { menuStore } from '/@/store/modules/menu'; | ||
11 | + | ||
12 | +export default defineComponent({ | ||
13 | + name: 'LayoutTrigger', | ||
14 | + props: { | ||
15 | + sider: { | ||
16 | + type: Boolean, | ||
17 | + default: true, | ||
18 | + }, | ||
19 | + theme: { | ||
20 | + type: String, | ||
21 | + }, | ||
22 | + }, | ||
23 | + setup(props) { | ||
24 | + function toggleMenu() { | ||
25 | + menuStore.commitCollapsedState(!menuStore.getCollapsedState); | ||
26 | + } | ||
27 | + | ||
28 | + return () => { | ||
29 | + const siderTrigger = menuStore.getCollapsedState ? ( | ||
30 | + <DoubleRightOutlined /> | ||
31 | + ) : ( | ||
32 | + <DoubleLeftOutlined /> | ||
33 | + ); | ||
34 | + if (props.sider) return siderTrigger; | ||
35 | + | ||
36 | + return ( | ||
37 | + <span class={['layout-trigger', props.theme]} onClick={toggleMenu}> | ||
38 | + {menuStore.getCollapsedState ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />} | ||
39 | + </span> | ||
40 | + ); | ||
41 | + }; | ||
42 | + }, | ||
43 | +}); |
src/layouts/default/SideBarTrigger.tsx deleted
100644 → 0
1 | -import { DoubleRightOutlined, DoubleLeftOutlined } from '@ant-design/icons-vue'; | ||
2 | -import { defineComponent } from 'vue'; | ||
3 | - | ||
4 | -// store | ||
5 | -import { menuStore } from '/@/store/modules/menu'; | ||
6 | - | ||
7 | -export default defineComponent({ | ||
8 | - name: 'SideBarTrigger', | ||
9 | - setup() { | ||
10 | - return () => (menuStore.getCollapsedState ? <DoubleRightOutlined /> : <DoubleLeftOutlined />); | ||
11 | - }, | ||
12 | -}); |
src/layouts/default/index.less
@@ -17,11 +17,10 @@ | @@ -17,11 +17,10 @@ | ||
17 | &__main { | 17 | &__main { |
18 | position: relative; | 18 | position: relative; |
19 | height: 100%; | 19 | height: 100%; |
20 | - // overflow: hidden; | ||
21 | - // overflow: auto; | ||
22 | 20 | ||
23 | &.fixed { | 21 | &.fixed { |
24 | - overflow: auto; | 22 | + overflow-x: hidden; |
23 | + overflow-y: auto; | ||
25 | } | 24 | } |
26 | 25 | ||
27 | &.fixed.lock { | 26 | &.fixed.lock { |
@@ -373,9 +372,39 @@ | @@ -373,9 +372,39 @@ | ||
373 | } | 372 | } |
374 | } | 373 | } |
375 | 374 | ||
376 | -.layout-breadcrumb { | ||
377 | - padding: 0 16px; | 375 | +.layout-header__left { |
378 | flex-grow: 1; | 376 | flex-grow: 1; |
377 | + display: flex; | ||
378 | + align-items: center; | ||
379 | + | ||
380 | + .layout-trigger { | ||
381 | + padding: 4px 10px 0 16px; | ||
382 | + cursor: pointer; | ||
383 | + | ||
384 | + .anticon { | ||
385 | + font-size: 17px; | ||
386 | + } | ||
387 | + | ||
388 | + &.light { | ||
389 | + &:hover { | ||
390 | + background: @header-light-bg-hover-color; | ||
391 | + } | ||
392 | + | ||
393 | + svg { | ||
394 | + fill: #000; | ||
395 | + } | ||
396 | + } | ||
397 | + | ||
398 | + &.dark { | ||
399 | + &:hover { | ||
400 | + background: @header-dark-bg-hover-color; | ||
401 | + } | ||
402 | + } | ||
403 | + } | ||
404 | + | ||
405 | + .layout-breadcrumb { | ||
406 | + padding: 0 8px; | ||
407 | + } | ||
379 | } | 408 | } |
380 | 409 | ||
381 | .ant-layout-sider-trigger { | 410 | .ant-layout-sider-trigger { |
src/layouts/default/index.tsx
@@ -73,7 +73,7 @@ export default defineComponent({ | @@ -73,7 +73,7 @@ export default defineComponent({ | ||
73 | showSettingButton, | 73 | showSettingButton, |
74 | multiTabsSetting: { show: showTabs }, | 74 | multiTabsSetting: { show: showTabs }, |
75 | headerSetting: { fixed }, | 75 | headerSetting: { fixed }, |
76 | - menuSetting: { split, show }, | 76 | + menuSetting: { split, hidden }, |
77 | } = unref(getProjectConfigRef); | 77 | } = unref(getProjectConfigRef); |
78 | 78 | ||
79 | const fixedHeaderCls = fixed | 79 | const fixedHeaderCls = fixed |
@@ -82,7 +82,7 @@ export default defineComponent({ | @@ -82,7 +82,7 @@ export default defineComponent({ | ||
82 | 82 | ||
83 | const { isLock } = getLockInfo; | 83 | const { isLock } = getLockInfo; |
84 | 84 | ||
85 | - const showSideBar = split ? show : true; | 85 | + const showSideBar = split ? hidden : true; |
86 | return ( | 86 | return ( |
87 | <Layout class="default-layout relative"> | 87 | <Layout class="default-layout relative"> |
88 | {() => ( | 88 | {() => ( |
@@ -107,9 +107,7 @@ export default defineComponent({ | @@ -107,9 +107,7 @@ export default defineComponent({ | ||
107 | unref(showHeaderRef) && <LayoutHeader />} | 107 | unref(showHeaderRef) && <LayoutHeader />} |
108 | 108 | ||
109 | {showTabs && !unref(getFullContent) && ( | 109 | {showTabs && !unref(getFullContent) && ( |
110 | - <Layout.Header class={`default-layout__tabs`}> | ||
111 | - {() => <MultipleTabs />} | ||
112 | - </Layout.Header> | 110 | + <MultipleTabs class={`default-layout__tabs`} /> |
113 | )} | 111 | )} |
114 | 112 | ||
115 | {useOpenBackTop && <BackTop target={getTarget} />} | 113 | {useOpenBackTop && <BackTop target={getTarget} />} |
src/layouts/default/setting/SettingDrawer.tsx
@@ -2,7 +2,13 @@ import { defineComponent, computed, unref, ref } from 'vue'; | @@ -2,7 +2,13 @@ import { defineComponent, computed, unref, ref } from 'vue'; | ||
2 | import { BasicDrawer } from '/@/components/Drawer/index'; | 2 | import { BasicDrawer } from '/@/components/Drawer/index'; |
3 | import { Divider, Switch, Tooltip, InputNumber, Select } from 'ant-design-vue'; | 3 | import { Divider, Switch, Tooltip, InputNumber, Select } from 'ant-design-vue'; |
4 | import Button from '/@/components/Button/index.vue'; | 4 | import Button from '/@/components/Button/index.vue'; |
5 | -import { MenuModeEnum, MenuTypeEnum, MenuThemeEnum, TopMenuAlignEnum } from '/@/enums/menuEnum'; | 5 | +import { |
6 | + MenuModeEnum, | ||
7 | + MenuTypeEnum, | ||
8 | + MenuThemeEnum, | ||
9 | + TopMenuAlignEnum, | ||
10 | + TriggerEnum, | ||
11 | +} from '/@/enums/menuEnum'; | ||
6 | import { ContentEnum, RouterTransitionEnum } from '/@/enums/appEnum'; | 12 | import { ContentEnum, RouterTransitionEnum } from '/@/enums/appEnum'; |
7 | import { CopyOutlined, RedoOutlined, CheckOutlined } from '@ant-design/icons-vue'; | 13 | import { CopyOutlined, RedoOutlined, CheckOutlined } from '@ant-design/icons-vue'; |
8 | import { appStore } from '/@/store/modules/app'; | 14 | import { appStore } from '/@/store/modules/app'; |
@@ -23,41 +29,49 @@ const themeOptions = [ | @@ -23,41 +29,49 @@ const themeOptions = [ | ||
23 | { | 29 | { |
24 | value: MenuThemeEnum.LIGHT, | 30 | value: MenuThemeEnum.LIGHT, |
25 | label: '亮色', | 31 | label: '亮色', |
26 | - key: MenuThemeEnum.LIGHT, | ||
27 | }, | 32 | }, |
28 | { | 33 | { |
29 | value: MenuThemeEnum.DARK, | 34 | value: MenuThemeEnum.DARK, |
30 | label: '暗色', | 35 | label: '暗色', |
31 | - key: MenuThemeEnum.DARK, | ||
32 | }, | 36 | }, |
33 | ]; | 37 | ]; |
34 | const contentModeOptions = [ | 38 | const contentModeOptions = [ |
35 | { | 39 | { |
36 | value: ContentEnum.FULL, | 40 | value: ContentEnum.FULL, |
37 | label: '流式', | 41 | label: '流式', |
38 | - key: ContentEnum.FULL, | ||
39 | }, | 42 | }, |
40 | { | 43 | { |
41 | value: ContentEnum.FIXED, | 44 | value: ContentEnum.FIXED, |
42 | label: '定宽', | 45 | label: '定宽', |
43 | - key: ContentEnum.FIXED, | ||
44 | }, | 46 | }, |
45 | ]; | 47 | ]; |
46 | const topMenuAlignOptions = [ | 48 | const topMenuAlignOptions = [ |
47 | { | 49 | { |
48 | value: TopMenuAlignEnum.CENTER, | 50 | value: TopMenuAlignEnum.CENTER, |
49 | label: '居中', | 51 | label: '居中', |
50 | - key: TopMenuAlignEnum.CENTER, | ||
51 | }, | 52 | }, |
52 | { | 53 | { |
53 | value: TopMenuAlignEnum.START, | 54 | value: TopMenuAlignEnum.START, |
54 | label: '居左', | 55 | label: '居左', |
55 | - key: TopMenuAlignEnum.START, | ||
56 | }, | 56 | }, |
57 | { | 57 | { |
58 | value: TopMenuAlignEnum.END, | 58 | value: TopMenuAlignEnum.END, |
59 | label: '居右', | 59 | label: '居右', |
60 | - key: TopMenuAlignEnum.END, | 60 | + }, |
61 | +]; | ||
62 | + | ||
63 | +const menuTriggerOptions = [ | ||
64 | + { | ||
65 | + value: TriggerEnum.NONE, | ||
66 | + label: '不显示', | ||
67 | + }, | ||
68 | + { | ||
69 | + value: TriggerEnum.FOOTER, | ||
70 | + label: '底部', | ||
71 | + }, | ||
72 | + { | ||
73 | + value: TriggerEnum.HEADER, | ||
74 | + label: '顶部', | ||
61 | }, | 75 | }, |
62 | ]; | 76 | ]; |
63 | 77 | ||
@@ -181,7 +195,7 @@ export default defineComponent({ | @@ -181,7 +195,7 @@ export default defineComponent({ | ||
181 | baseHandler('splitMenu', e); | 195 | baseHandler('splitMenu', e); |
182 | }, | 196 | }, |
183 | def: split, | 197 | def: split, |
184 | - disabled: !unref(getShowMenuRef), | 198 | + disabled: !unref(getShowMenuRef) || type !== MenuTypeEnum.MIX, |
185 | }), | 199 | }), |
186 | renderSelectItem('顶栏主题', { | 200 | renderSelectItem('顶栏主题', { |
187 | handler: (e) => { | 201 | handler: (e) => { |
@@ -215,6 +229,7 @@ export default defineComponent({ | @@ -215,6 +229,7 @@ export default defineComponent({ | ||
215 | menuWidth, | 229 | menuWidth, |
216 | topMenuAlign, | 230 | topMenuAlign, |
217 | collapsedShowTitle, | 231 | collapsedShowTitle, |
232 | + trigger, | ||
218 | } = {}, | 233 | } = {}, |
219 | } = appStore.getProjectConfig; | 234 | } = appStore.getProjectConfig; |
220 | return [ | 235 | return [ |
@@ -262,6 +277,13 @@ export default defineComponent({ | @@ -262,6 +277,13 @@ export default defineComponent({ | ||
262 | options: topMenuAlignOptions, | 277 | options: topMenuAlignOptions, |
263 | disabled: !unref(getShowHeaderRef), | 278 | disabled: !unref(getShowHeaderRef), |
264 | }), | 279 | }), |
280 | + renderSelectItem('菜单折叠按钮', { | ||
281 | + handler: (e) => { | ||
282 | + baseHandler('menuTrigger', e); | ||
283 | + }, | ||
284 | + def: trigger, | ||
285 | + options: menuTriggerOptions, | ||
286 | + }), | ||
265 | renderSelectItem('内容区域宽度', { | 287 | renderSelectItem('内容区域宽度', { |
266 | handler: (e) => { | 288 | handler: (e) => { |
267 | baseHandler('contentMode', e); | 289 | baseHandler('contentMode', e); |
@@ -298,7 +320,7 @@ export default defineComponent({ | @@ -298,7 +320,7 @@ export default defineComponent({ | ||
298 | disabled={!unref(getShowMenuRef)} | 320 | disabled={!unref(getShowMenuRef)} |
299 | defaultValue={menuWidth} | 321 | defaultValue={menuWidth} |
300 | formatter={(value: string) => `${parseInt(value)}px`} | 322 | formatter={(value: string) => `${parseInt(value)}px`} |
301 | - onChange={(e) => { | 323 | + onChange={(e: any) => { |
302 | baseHandler('menuWidth', e); | 324 | baseHandler('menuWidth', e); |
303 | }} | 325 | }} |
304 | /> | 326 | /> |
@@ -424,13 +446,21 @@ export default defineComponent({ | @@ -424,13 +446,21 @@ export default defineComponent({ | ||
424 | if (event === 'layout') { | 446 | if (event === 'layout') { |
425 | const { mode, type, split } = value; | 447 | const { mode, type, split } = value; |
426 | const splitOpt = split === undefined ? { split } : {}; | 448 | const splitOpt = split === undefined ? { split } : {}; |
449 | + let headerSetting = {}; | ||
450 | + if (type === MenuTypeEnum.TOP_MENU) { | ||
451 | + headerSetting = { | ||
452 | + theme: MenuThemeEnum.DARK, | ||
453 | + }; | ||
454 | + } | ||
427 | config = { | 455 | config = { |
428 | menuSetting: { | 456 | menuSetting: { |
429 | mode, | 457 | mode, |
430 | type, | 458 | type, |
431 | collapsed: false, | 459 | collapsed: false, |
460 | + show: true, | ||
432 | ...splitOpt, | 461 | ...splitOpt, |
433 | }, | 462 | }, |
463 | + headerSetting, | ||
434 | }; | 464 | }; |
435 | } | 465 | } |
436 | if (event === 'hasDrag') { | 466 | if (event === 'hasDrag') { |
@@ -440,6 +470,13 @@ export default defineComponent({ | @@ -440,6 +470,13 @@ export default defineComponent({ | ||
440 | }, | 470 | }, |
441 | }; | 471 | }; |
442 | } | 472 | } |
473 | + if (event === 'menuTrigger') { | ||
474 | + config = { | ||
475 | + menuSetting: { | ||
476 | + trigger: value, | ||
477 | + }, | ||
478 | + }; | ||
479 | + } | ||
443 | if (event === 'openPageLoading') { | 480 | if (event === 'openPageLoading') { |
444 | config = { | 481 | config = { |
445 | openPageLoading: value, | 482 | openPageLoading: value, |
@@ -647,7 +684,7 @@ export default defineComponent({ | @@ -647,7 +684,7 @@ export default defineComponent({ | ||
647 | <Switch | 684 | <Switch |
648 | {...opt} | 685 | {...opt} |
649 | disabled={disabled} | 686 | disabled={disabled} |
650 | - onChange={(e) => { | 687 | + onChange={(e: any) => { |
651 | handler && handler(e); | 688 | handler && handler(e); |
652 | }} | 689 | }} |
653 | checkedChildren="开" | 690 | checkedChildren="开" |
src/settings/projectSetting.ts
1 | import type { ProjectConfig } from '/@/types/config'; | 1 | import type { ProjectConfig } from '/@/types/config'; |
2 | 2 | ||
3 | -import { MenuTypeEnum, MenuThemeEnum, MenuModeEnum } from '/@/enums/menuEnum'; | 3 | +import { MenuTypeEnum, MenuThemeEnum, MenuModeEnum, TriggerEnum } from '/@/enums/menuEnum'; |
4 | import { ContentEnum, PermissionModeEnum, RouterTransitionEnum } from '/@/enums/appEnum'; | 4 | import { ContentEnum, PermissionModeEnum, RouterTransitionEnum } from '/@/enums/appEnum'; |
5 | import { primaryColor } from '../../build/config/lessModifyVars'; | 5 | import { primaryColor } from '../../build/config/lessModifyVars'; |
6 | import { isProdMode } from '/@/utils/env'; | 6 | import { isProdMode } from '/@/utils/env'; |
@@ -23,6 +23,7 @@ const setting: ProjectConfig = { | @@ -23,6 +23,7 @@ const setting: ProjectConfig = { | ||
23 | // 是否显示logo | 23 | // 是否显示logo |
24 | showLogo: true, | 24 | showLogo: true, |
25 | 25 | ||
26 | + // 头部配置 | ||
26 | headerSetting: { | 27 | headerSetting: { |
27 | fixed: true, | 28 | fixed: true, |
28 | // 是否显示顶部 | 29 | // 是否显示顶部 |
@@ -50,8 +51,10 @@ const setting: ProjectConfig = { | @@ -50,8 +51,10 @@ const setting: ProjectConfig = { | ||
50 | collapsedShowTitle: false, | 51 | collapsedShowTitle: false, |
51 | // 是否可拖拽 | 52 | // 是否可拖拽 |
52 | hasDrag: false, | 53 | hasDrag: false, |
53 | - // 是否显示 | 54 | + // 是否显示 没有dom |
54 | show: true, | 55 | show: true, |
56 | + // 是否显示 有dom | ||
57 | + hidden: false, | ||
55 | // 是否显示搜索框 | 58 | // 是否显示搜索框 |
56 | showSearch: true, | 59 | showSearch: true, |
57 | // 菜单宽度 | 60 | // 菜单宽度 |
@@ -67,7 +70,9 @@ const setting: ProjectConfig = { | @@ -67,7 +70,9 @@ const setting: ProjectConfig = { | ||
67 | // 顶部菜单布局 | 70 | // 顶部菜单布局 |
68 | topMenuAlign: 'center', | 71 | topMenuAlign: 'center', |
69 | // 折叠菜单时候隐藏搜索框 | 72 | // 折叠菜单时候隐藏搜索框 |
70 | - collapsedShowSearch: true, | 73 | + collapsedShowSearch: false, |
74 | + // 折叠触发器的位置 | ||
75 | + trigger: TriggerEnum.HEADER, | ||
71 | }, | 76 | }, |
72 | // 消息配置 | 77 | // 消息配置 |
73 | messageSetting: { | 78 | messageSetting: { |
src/types/config.d.ts
1 | // 左侧菜单, 顶部菜单 | 1 | // 左侧菜单, 顶部菜单 |
2 | -import { MenuTypeEnum, MenuModeEnum, MenuThemeEnum } from '/@/enums/menuEnum'; | 2 | +import { MenuTypeEnum, MenuModeEnum, MenuThemeEnum, TriggerEnum } from '/@/enums/menuEnum'; |
3 | import { ContentEnum, PermissionModeEnum, RouterTransitionEnum } from '/@/enums/appEnum'; | 3 | import { ContentEnum, PermissionModeEnum, RouterTransitionEnum } from '/@/enums/appEnum'; |
4 | 4 | ||
5 | export interface MessageSetting { | 5 | export interface MessageSetting { |
@@ -15,6 +15,7 @@ export interface MenuSetting { | @@ -15,6 +15,7 @@ export interface MenuSetting { | ||
15 | hasDrag: boolean; | 15 | hasDrag: boolean; |
16 | showSearch: boolean; | 16 | showSearch: boolean; |
17 | show: boolean; | 17 | show: boolean; |
18 | + hidden: boolean; | ||
18 | split: boolean; | 19 | split: boolean; |
19 | menuWidth: number; | 20 | menuWidth: number; |
20 | mode: MenuModeEnum; | 21 | mode: MenuModeEnum; |
@@ -22,6 +23,7 @@ export interface MenuSetting { | @@ -22,6 +23,7 @@ export interface MenuSetting { | ||
22 | theme: MenuThemeEnum; | 23 | theme: MenuThemeEnum; |
23 | topMenuAlign: 'start' | 'center' | 'end'; | 24 | topMenuAlign: 'start' | 'center' | 'end'; |
24 | collapsedShowSearch: boolean; | 25 | collapsedShowSearch: boolean; |
26 | + trigger: TriggerEnum; | ||
25 | } | 27 | } |
26 | 28 | ||
27 | export interface MultiTabsSetting { | 29 | export interface MultiTabsSetting { |
src/utils/helper/routeHelper.ts
@@ -42,6 +42,7 @@ export function genRouteModule(moduleList: AppRouteModule[]) { | @@ -42,6 +42,7 @@ export function genRouteModule(moduleList: AppRouteModule[]) { | ||
42 | } | 42 | } |
43 | 43 | ||
44 | // 动态引入 | 44 | // 动态引入 |
45 | +// TODO 错误写法 | ||
45 | function asyncImportRoute(routes: AppRouteRecordRaw[]) { | 46 | function asyncImportRoute(routes: AppRouteRecordRaw[]) { |
46 | routes.forEach((item) => { | 47 | routes.forEach((item) => { |
47 | let { component } = item; | 48 | let { component } = item; |
vite.config.ts
@@ -119,12 +119,7 @@ const viteConfig: UserConfig = { | @@ -119,12 +119,7 @@ const viteConfig: UserConfig = { | ||
119 | }, | 119 | }, |
120 | // The package will be recompiled using rollup, and the new package compiled into the esm module specification will be put into node_modules/.vite_opt_cache | 120 | // The package will be recompiled using rollup, and the new package compiled into the esm module specification will be put into node_modules/.vite_opt_cache |
121 | optimizeDeps: { | 121 | optimizeDeps: { |
122 | - include: [ | ||
123 | - 'echarts/map/js/china', | ||
124 | - 'ant-design-vue/es/locale/zh_CN', | ||
125 | - '@ant-design/icons-vue', | ||
126 | - 'moment/locale/zh-cn', | ||
127 | - ], | 122 | + include: ['echarts/map/js/china', 'ant-design-vue/es/locale/zh_CN', '@ant-design/icons-vue'], |
128 | }, | 123 | }, |
129 | 124 | ||
130 | // Local cross-domain proxy | 125 | // Local cross-domain proxy |
yarn.lock
@@ -5513,7 +5513,7 @@ modify-values@^1.0.0: | @@ -5513,7 +5513,7 @@ modify-values@^1.0.0: | ||
5513 | resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" | 5513 | resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" |
5514 | integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== | 5514 | integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== |
5515 | 5515 | ||
5516 | -moment@^2.27.0: | 5516 | +moment@^2.27.0, moment@^2.29.1: |
5517 | version "2.29.1" | 5517 | version "2.29.1" |
5518 | resolved "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" | 5518 | resolved "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" |
5519 | integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== | 5519 | integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== |