feat(tabs): added tab folding
| | |
| | | - 新增`mixSideFixed`配置。用于固定左侧混合模式菜单 |
| | | - modal 组件新增`height`和`min-height`属性 |
| | | - 新增`PageWrapper`组件。并应用于示例页面 |
| | | - 新增标签页折叠功能 |
| | | |
| | | ### 🐛 Bug Fixes |
| | | |
| | |
| | | import userMock from './sys/user'; |
| | | import menuMock from './sys/menu'; |
| | | import tableDemoMock from './demo/table-demo'; |
| | | import accountDemoMock from './demo/account'; |
| | | import selectDemoMock from './demo/select-demo'; |
| | | |
| | | /** |
| | | * Used in a production environment. Need to manually import all modules |
| | | */ |
| | | export function setupProdMockServer() { |
| | | createProdMockServer([...userMock, ...menuMock, ...tableDemoMock]); |
| | | createProdMockServer([ |
| | | ...userMock, |
| | | ...menuMock, |
| | | ...tableDemoMock, |
| | | ...accountDemoMock, |
| | | ...selectDemoMock, |
| | | ]); |
| | | } |
| | |
| | | } |
| | | ); |
| | | |
| | | // watch(() => props.icon, update, { flush: 'post' }); |
| | | watch(() => props.icon, update, { flush: 'post' }); |
| | | |
| | | onMounted(update); |
| | | |
| | |
| | | mode: Ref<MenuModeEnum>, |
| | | accordion: Ref<boolean> |
| | | ) { |
| | | const { getCollapsed, getIsMixSidebar, getMixSideFixed } = useMenuSetting(); |
| | | const { getCollapsed, getIsMixSidebar } = useMenuSetting(); |
| | | |
| | | async function setOpenKeys(path: string) { |
| | | if (mode.value === MenuModeEnum.HORIZONTAL) { |
| | | return; |
| | | } |
| | | const native = unref(getIsMixSidebar) && unref(getMixSideFixed); |
| | | const native = unref(getIsMixSidebar); |
| | | useTimeoutFn( |
| | | () => { |
| | | const menuList = toRaw(menus.value); |
| | | if (menuList?.length === 0) { |
| | | menuState.openKeys = []; |
| | | return; |
| | | } |
| | | if (!unref(accordion)) { |
| | | menuState.openKeys = es6Unique([ |
| | | ...menuState.openKeys, |
| | |
| | | watchEffect, |
| | | toRef, |
| | | getCurrentInstance, |
| | | nextTick, |
| | | } from 'vue'; |
| | | |
| | | import Modal from './components/Modal'; |
| | |
| | | visible: unref(visibleRef), |
| | | title: undefined, |
| | | }; |
| | | |
| | | return { |
| | | ...opt, |
| | | wrapClassName: unref(getWrapClassName), |
| | |
| | | <template> |
| | | <ScrollContainer ref="wrapperRef" :style="wrapStyle"> |
| | | <ScrollContainer ref="wrapperRef"> |
| | | <div ref="spinRef" :style="spinStyle" v-loading="loading" :loading-tip="loadingTip"> |
| | | <slot /> |
| | | </div> |
| | |
| | | redoModalHeight: setModalHeight, |
| | | }); |
| | | |
| | | const wrapStyle = computed( |
| | | (): CSSProperties => { |
| | | return { |
| | | minHeight: `${props.minHeight}px`, |
| | | height: `${unref(realHeightRef)}px`, |
| | | // overflow: 'auto', |
| | | }; |
| | | } |
| | | ); |
| | | |
| | | const spinStyle = computed( |
| | | (): CSSProperties => { |
| | | return { |
| | | minHeight: `${props.minHeight}px`, |
| | | // padding 28 |
| | | height: `${unref(realHeightRef) - 28}px`, |
| | | }; |
| | |
| | | } |
| | | } |
| | | |
| | | return { wrapStyle, wrapperRef, spinRef, spinStyle }; |
| | | return { wrapperRef, spinRef, spinStyle }; |
| | | }, |
| | | }); |
| | | </script> |
| | |
| | | width: 520px; |
| | | padding-bottom: 0; |
| | | |
| | | .scroll-container { |
| | | .scrollbar { |
| | | padding: 14px; |
| | | } |
| | | // .ant-spin-nested-loading { |
| | | // padding: 16px; |
| | | // } |
| | | |
| | | &-title { |
| | | font-size: 16px; |
| | |
| | | </template> |
| | | <script lang="ts"> |
| | | import { addResizeListener, removeResizeListener } from '/@/utils/event/resizeEvent'; |
| | | |
| | | import componentSetting from '/@/settings/componentSetting'; |
| | | const { scrollbar } = componentSetting; |
| | | import { toObject } from './util'; |
| | | import { |
| | | defineComponent, |
| | |
| | | props: { |
| | | native: { |
| | | type: Boolean, |
| | | default: false, |
| | | default: scrollbar?.native ?? false, |
| | | }, |
| | | wrapStyle: { |
| | | type: [String, Array], |
| | |
| | | // flex-wrap: wrap; |
| | | } |
| | | |
| | | .scroll-container { |
| | | .scrollbar { |
| | | height: 220px; |
| | | } |
| | | } |
| | |
| | | import type { SorterResult } from './types/table'; |
| | | import componentSetting from '/@/settings/componentSetting'; |
| | | |
| | | const { table } = componentSetting; |
| | | |
| | | const { pageSizeOptions, defaultPageSize, fetchSetting, defaultSortFn, defaultFilterFn } = table; |
| | | |
| | | export const ROW_KEY = 'key'; |
| | | |
| | | // 可选的每页显示条数; |
| | | export const PAGE_SIZE_OPTIONS = ['10', '50', '80', '100']; |
| | | export const PAGE_SIZE_OPTIONS = pageSizeOptions; |
| | | |
| | | // 每页显示条数 |
| | | export const PAGE_SIZE = ~~PAGE_SIZE_OPTIONS[0]; |
| | | export const PAGE_SIZE = defaultPageSize; |
| | | |
| | | // 通用接口字段设置 |
| | | // 支持 xxx.xxx.xxx格式 |
| | | export const FETCH_SETTING = { |
| | | // 传给后台的当前页字段名 |
| | | pageField: 'page', |
| | | // 传给后台的每页显示记录数字段名 |
| | | sizeField: 'pageSize', |
| | | // 接口返回的表格数据字段名 |
| | | listField: 'items', |
| | | // 接口返回的表格总数字段名 |
| | | totalField: 'total', |
| | | }; |
| | | export const FETCH_SETTING = fetchSetting; |
| | | |
| | | // 配置通用排序函数 |
| | | export function DEFAULT_SORT_FN(sortInfo: SorterResult) { |
| | | const { field, order } = sortInfo; |
| | | return { |
| | | // 传给后台的排序字段你 |
| | | field, |
| | | // 传给后台的排序方式 asc/desc |
| | | order, |
| | | }; |
| | | } |
| | | export const DEFAULT_SORT_FN = defaultSortFn; |
| | | |
| | | export function DEFAULT_FILTER_FN(data: Partial<Recordable<string[]>>) { |
| | | return data; |
| | | } |
| | | export const DEFAULT_FILTER_FN = defaultFilterFn; |
| | | |
| | | // 表格单元格默认布局 |
| | | export const DEFAULT_ALIGN = 'center'; |
| | |
| | | }); |
| | | |
| | | const getRealWidth = computed(() => { |
| | | return unref(getCollapsed) ? unref(getMiniWidthNumber) : unref(getMenuWidth); |
| | | return unref(getCollapsed) && !unref(getMixSideFixed) |
| | | ? unref(getMiniWidthNumber) |
| | | : unref(getMenuWidth); |
| | | }); |
| | | |
| | | const getMiniWidthNumber = computed(() => { |
| | |
| | | ? SIDE_BAR_SHOW_TIT_MINI_WIDTH + |
| | | (unref(getMixSideFixed) && unref(mixSideHasChildren) ? unref(getRealWidth) : 0) |
| | | : unref(getRealWidth); |
| | | |
| | | return `calc(100% - ${unref(width)}px)`; |
| | | }); |
| | | |
| | |
| | | |
| | | const getShowRedo = computed(() => unref(getMultipleTabSetting).showRedo); |
| | | |
| | | const getShowFold = computed(() => unref(getMultipleTabSetting).showFold); |
| | | |
| | | function setMultipleTabSetting(multiTabsSetting: Partial<MultiTabsSetting>) { |
| | | appStore.commitProjectConfigState({ multiTabsSetting }); |
| | | } |
| | |
| | | getShowMultipleTab, |
| | | getShowQuick, |
| | | getShowRedo, |
| | | getShowFold, |
| | | }; |
| | | } |
| | |
| | | import { tabStore } from '/@/store/modules/tab'; |
| | | import { appStore } from '/@/store/modules/app'; |
| | | import type { RouteLocationNormalized } from 'vue-router'; |
| | | |
| | | export function useTabs() { |
| | | function canIUseFn(): boolean { |
| | |
| | | closeRight: () => canIUseFn() && tabStore.closeRightTabAction(tabStore.getCurrentTab), |
| | | closeOther: () => canIUseFn() && tabStore.closeOtherTabAction(tabStore.getCurrentTab), |
| | | closeCurrent: () => canIUseFn() && tabStore.closeTabAction(tabStore.getCurrentTab), |
| | | close: (tab?: RouteLocationNormalized) => |
| | | canIUseFn() && tabStore.closeTabAction(tab || tabStore.getCurrentTab), |
| | | }; |
| | | } |
| | |
| | | getShowSearch, |
| | | } = useHeaderSetting(); |
| | | |
| | | const { getShowMultipleTab, getShowQuick, getShowRedo } = useMultipleTabSetting(); |
| | | const { getShowMultipleTab, getShowQuick, getShowRedo, getShowFold } = useMultipleTabSetting(); |
| | | |
| | | const getShowMenuRef = computed(() => { |
| | | return unref(getShowMenu) && !unref(getIsHorizontal); |
| | |
| | | }); |
| | | }} |
| | | def={unref(getMenuType)} |
| | | /> |
| | | <SwitchItem |
| | | title={t('layout.setting.splitMenu')} |
| | | event={HandlerEnum.MENU_SPLIT} |
| | | def={unref(getSplit)} |
| | | disabled={!unref(getShowMenuRef) || unref(getMenuType) !== MenuTypeEnum.MIX} |
| | | /> |
| | | <SwitchItem |
| | | title={t('layout.setting.mixSidebarFixed')} |
| | | event={HandlerEnum.MENU_FIXED_MIX_SIDEBAR} |
| | | def={unref(getMixSideFixed)} |
| | | disabled={!unref(getIsMixSidebar)} |
| | | /> |
| | | |
| | | <SwitchItem |
| | | title={t('layout.setting.closeMixSidebarOnChange')} |
| | | event={HandlerEnum.MENU_CLOSE_MIX_SIDEBAR_ON_CHANGE} |
| | | def={unref(getCloseMixSidebarOnChange)} |
| | | disabled={!unref(getIsMixSidebar)} |
| | | /> |
| | | |
| | | <SelectItem |
| | | title={t('layout.setting.mixSidebarTrigger')} |
| | | event={HandlerEnum.MENU_TRIGGER_MIX_SIDEBAR} |
| | | def={unref(getMixSideTrigger)} |
| | | options={mixSidebarTriggerOptions} |
| | | disabled={!unref(getIsMixSidebar)} |
| | | /> |
| | | </> |
| | | ); |
| | |
| | | return ( |
| | | <> |
| | | <SwitchItem |
| | | title={t('layout.setting.splitMenu')} |
| | | event={HandlerEnum.MENU_SPLIT} |
| | | def={unref(getSplit)} |
| | | disabled={!unref(getShowMenuRef) || unref(getMenuType) !== MenuTypeEnum.MIX} |
| | | /> |
| | | <SwitchItem |
| | | title={t('layout.setting.mixSidebarFixed')} |
| | | event={HandlerEnum.MENU_FIXED_MIX_SIDEBAR} |
| | | def={unref(getMixSideFixed)} |
| | | disabled={!unref(getIsMixSidebar)} |
| | | /> |
| | | |
| | | <SwitchItem |
| | | title={t('layout.setting.closeMixSidebarOnChange')} |
| | | event={HandlerEnum.MENU_CLOSE_MIX_SIDEBAR_ON_CHANGE} |
| | | def={unref(getCloseMixSidebarOnChange)} |
| | | disabled={!unref(getIsMixSidebar)} |
| | | /> |
| | | <SwitchItem |
| | | title={t('layout.setting.menuCollapse')} |
| | | event={HandlerEnum.MENU_COLLAPSED} |
| | | def={unref(getCollapsed)} |
| | | disabled={!unref(getShowMenuRef)} |
| | | /> |
| | | |
| | | <SwitchItem |
| | | title={t('layout.setting.menuDrag')} |
| | | event={HandlerEnum.MENU_HAS_DRAG} |
| | | def={unref(getCanDrag)} |
| | |
| | | def={unref(getAccordion)} |
| | | disabled={!unref(getShowMenuRef)} |
| | | /> |
| | | <SwitchItem |
| | | title={t('layout.setting.menuCollapse')} |
| | | event={HandlerEnum.MENU_COLLAPSED} |
| | | def={unref(getCollapsed)} |
| | | disabled={!unref(getShowMenuRef) || unref(getIsMixSidebar)} |
| | | /> |
| | | |
| | | <SwitchItem |
| | | title={t('layout.setting.collapseMenuDisplayName')} |
| | | event={HandlerEnum.MENU_COLLAPSED_SHOW_TITLE} |
| | | def={unref(getCollapsedShowTitle)} |
| | | disabled={!unref(getShowMenuRef) || !unref(getCollapsed)} |
| | | disabled={!unref(getShowMenuRef) || !unref(getCollapsed) || unref(getIsMixSidebar)} |
| | | /> |
| | | |
| | | <SwitchItem |
| | |
| | | event={HandlerEnum.MENU_FIXED} |
| | | def={unref(getMenuFixed)} |
| | | disabled={!unref(getShowMenuRef) || unref(getIsMixSidebar)} |
| | | /> |
| | | <SelectItem |
| | | title={t('layout.setting.mixSidebarTrigger')} |
| | | event={HandlerEnum.MENU_TRIGGER_MIX_SIDEBAR} |
| | | def={unref(getMixSideTrigger)} |
| | | options={mixSidebarTriggerOptions} |
| | | disabled={!unref(getIsMixSidebar)} |
| | | /> |
| | | <SelectItem |
| | | title={t('layout.setting.topMenuLayout')} |
| | |
| | | def={unref(getShowQuick)} |
| | | disabled={!unref(getShowMultipleTab)} |
| | | /> |
| | | <SwitchItem |
| | | title={t('layout.setting.tabsFoldBtn')} |
| | | event={HandlerEnum.TABS_SHOW_FOLD} |
| | | def={unref(getShowFold)} |
| | | disabled={!unref(getShowMultipleTab)} |
| | | /> |
| | | |
| | | <SwitchItem |
| | | title={t('layout.setting.sidebar')} |
| | |
| | | TABS_SHOW_QUICK, |
| | | TABS_SHOW_REDO, |
| | | TABS_SHOW, |
| | | TABS_SHOW_FOLD, |
| | | |
| | | LOCK_TIME, |
| | | FULL_CONTENT, |
| | |
| | | return { menuSetting: { mixSideTrigger: value } }; |
| | | |
| | | case HandlerEnum.MENU_FIXED_MIX_SIDEBAR: |
| | | return { menuSetting: { mixSideTrigger: value } }; |
| | | return { menuSetting: { mixSideFixed: value } }; |
| | | |
| | | // ============transition================== |
| | | case HandlerEnum.OPEN_PAGE_LOADING: |
| | |
| | | |
| | | case HandlerEnum.TABS_SHOW: |
| | | return { multiTabsSetting: { show: value } }; |
| | | |
| | | case HandlerEnum.TABS_SHOW_REDO: |
| | | return { multiTabsSetting: { showRedo: value } }; |
| | | |
| | | case HandlerEnum.TABS_SHOW_FOLD: |
| | | return { multiTabsSetting: { showFold: value } }; |
| | | |
| | | // ============header================== |
| | | case HandlerEnum.HEADER_THEME: |
| | | updateHeaderBgColor(value); |
| | |
| | | |
| | | <div |
| | | v-click-outside="handleClickOutside" |
| | | :style="getWrapStyle" |
| | | :class="[ |
| | | prefixCls, |
| | | getMenuTheme, |
| | | { |
| | | open: openMenu, |
| | | mini: getCollapsed, |
| | | }, |
| | | ]" |
| | | v-bind="getMenuEvents" |
| | |
| | | <MenuTag :item="item" :showTitle="false" :isHorizontal="false" /> |
| | | <Icon |
| | | :class="`${prefixCls}-module__icon`" |
| | | :size="22" |
| | | :size="getCollapsed ? 16 : 20" |
| | | :icon="item.meta && item.meta.icon" |
| | | /> |
| | | <p :class="`${prefixCls}-module__name`">{{ t(item.name) }}</p> |
| | |
| | | <span class="text"> {{ title }}</span> |
| | | <Icon |
| | | :size="16" |
| | | v-if="getMixSideFixed" |
| | | icon="ri:pushpin-2-fill" |
| | | :icon="getMixSideFixed ? 'ri:pushpin-2-fill' : 'ri:pushpin-2-line'" |
| | | class="pushpin" |
| | | @click="handleFixedMenu" |
| | | /> |
| | | <Icon :size="16" v-else icon="ri:pushpin-2-line" class="pushpin" @click="handleFixedMenu" /> |
| | | </div> |
| | | <ScrollContainer :class="`${prefixCls}-menu-list__content`"> |
| | | <BasicMenu |
| | |
| | | import { useDragLine } from './useLayoutSider'; |
| | | import { useGlobSetting } from '/@/hooks/setting'; |
| | | |
| | | import { SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum'; |
| | | import { SIDE_BAR_SHOW_TIT_MINI_WIDTH, SIDE_BAR_MINI_WIDTH } from '/@/enums/appEnum'; |
| | | |
| | | import clickOutside from '/@/directives/clickOutside'; |
| | | |
| | |
| | | getMixSideFixed, |
| | | mixSideHasChildren, |
| | | setMenuSetting, |
| | | getIsMixSidebar, |
| | | getCollapsed, |
| | | } = useMenuSetting(); |
| | | |
| | | const { title } = useGlobSetting(); |
| | |
| | | (): CSSProperties => { |
| | | return { |
| | | width: unref(openMenu) ? `${unref(getMenuWidth)}px` : 0, |
| | | left: `${unref(getMixSideWidth)}px`, |
| | | }; |
| | | } |
| | | ); |
| | |
| | | return isFixed; |
| | | }); |
| | | |
| | | const getMixSideWidth = computed(() => { |
| | | return unref(getCollapsed) ? SIDE_BAR_MINI_WIDTH : SIDE_BAR_SHOW_TIT_MINI_WIDTH; |
| | | }); |
| | | |
| | | const getDomStyle = computed( |
| | | (): CSSProperties => { |
| | | const fixedWidth = unref(getIsFixed) ? unref(getRealWidth) : 0; |
| | | const width = `${SIDE_BAR_SHOW_TIT_MINI_WIDTH + fixedWidth}px`; |
| | | return { |
| | | width, |
| | | maxWidth: width, |
| | | minWidth: width, |
| | | flex: `0 0 ${width}`, |
| | | }; |
| | | const width = `${unref(getMixSideWidth) + fixedWidth}px`; |
| | | return getWrapCommonStyle(width); |
| | | } |
| | | ); |
| | | |
| | | const getWrapStyle = computed( |
| | | (): CSSProperties => { |
| | | const width = `${unref(getMixSideWidth)}px`; |
| | | return getWrapCommonStyle(width); |
| | | } |
| | | ); |
| | | |
| | | const getMenuEvents = computed(() => { |
| | | // return unref(getMixSideTrigger) === 'hover' |
| | | // ? { |
| | | // onMouseleave: () => { |
| | | // closeMenu(); |
| | | // }, |
| | | // } |
| | | // : {}; |
| | | return { |
| | | onMouseleave: () => { |
| | | closeMenu(); |
| | | }, |
| | | }; |
| | | return !unref(getMixSideFixed) |
| | | ? { |
| | | onMouseleave: () => { |
| | | closeMenu(); |
| | | }, |
| | | } |
| | | : {}; |
| | | }); |
| | | |
| | | const getShowDragBar = computed(() => unref(getCanDrag)); |
| | |
| | | } |
| | | }); |
| | | |
| | | function getWrapCommonStyle(width: string): CSSProperties { |
| | | return { |
| | | width, |
| | | maxWidth: width, |
| | | minWidth: width, |
| | | flex: `0 0 ${width}`, |
| | | }; |
| | | } |
| | | |
| | | // Process module menu click |
| | | async function hanldeModuleClick(path: string, hover = false) { |
| | | const children = await getChildrenMenus(path); |
| | | |
| | |
| | | chilrenMenus.value = children; |
| | | } |
| | | |
| | | // Set the currently active menu and submenu |
| | | async function setActive(setChildren = false) { |
| | | const path = currentRoute.value?.path; |
| | | if (!path) return; |
| | | const parentPath = await getCurrentParentPath(path); |
| | | activePath.value = parentPath; |
| | | // hanldeModuleClick(parentPath); |
| | | if (unref(getMixSideFixed)) { |
| | | if (unref(getIsMixSidebar)) { |
| | | const activeMenu = unref(menuModules).find((item) => item.path === unref(activePath)); |
| | | const p = activeMenu?.path; |
| | | if (p) { |
| | | const children = await getChildrenMenus(p); |
| | | if (setChildren) { |
| | | chilrenMenus.value = children; |
| | | openMenu.value = children.length > 0; |
| | | |
| | | if (unref(getMixSideFixed)) { |
| | | openMenu.value = children.length > 0; |
| | | } |
| | | } |
| | | if (children.length === 0) { |
| | | chilrenMenus.value = []; |
| | |
| | | }); |
| | | } |
| | | |
| | | // Close menu |
| | | function closeMenu() { |
| | | if (!unref(getIsFixed)) { |
| | | openMenu.value = false; |
| | |
| | | getDomStyle, |
| | | handleFixedMenu, |
| | | getMixSideFixed, |
| | | getWrapStyle, |
| | | getCollapsed, |
| | | }; |
| | | }, |
| | | }); |
| | |
| | | top: 0; |
| | | left: 0; |
| | | z-index: @layout-mix-sider-fixed-z-index; |
| | | width: @width; |
| | | height: 100%; |
| | | max-width: @width; |
| | | min-width: @width; |
| | | overflow: hidden; |
| | | background: @sider-dark-bg-color; |
| | | transition: all 0.3s ease 0s; |
| | | flex: 0 0 @width; |
| | | transition: all 0.2s ease 0s; |
| | | .@{tag-prefix-cls} { |
| | | position: absolute; |
| | | top: 6px; |
| | |
| | | } |
| | | |
| | | &-dom { |
| | | width: @width; |
| | | height: 100%; |
| | | max-width: @width; |
| | | min-width: @width; |
| | | overflow: hidden; |
| | | transition: all 0.2s ease 0s; |
| | | flex: 0 0 @width; |
| | | } |
| | | |
| | | &-logo { |
| | |
| | | } |
| | | |
| | | &.open { |
| | | > .scroll-container { |
| | | > .scrollbar { |
| | | border-right: 1px solid rgb(238, 238, 238); |
| | | } |
| | | } |
| | |
| | | border-bottom: 1px solid @border-color; |
| | | } |
| | | |
| | | > .scroll-container { |
| | | > .scrollbar { |
| | | border-right: 1px solid @border-color; |
| | | } |
| | | } |
| | |
| | | |
| | | > .scrollbar { |
| | | height: calc(100% - @header-height) !important; |
| | | } |
| | | |
| | | &.mini &-module { |
| | | &__name { |
| | | display: none; |
| | | } |
| | | |
| | | &__icon { |
| | | margin-bottom: 0; |
| | | } |
| | | } |
| | | |
| | | &-module { |
| | |
| | | &-menu-list { |
| | | position: fixed; |
| | | top: 0; |
| | | left: 80px; |
| | | width: 0; |
| | | width: 200px; |
| | | height: calc(100%); |
New file |
| | |
| | | <template> |
| | | <span :class="`${prefixCls}__extra-fold`" @click="handleFold"> |
| | | <Icon :icon="getIcon" /> |
| | | </span> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { defineComponent, unref, computed } from 'vue'; |
| | | import { RedoOutlined } from '@ant-design/icons-vue'; |
| | | import { useDesign } from '/@/hooks/web/useDesign'; |
| | | import { Tooltip } from 'ant-design-vue'; |
| | | import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; |
| | | import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
| | | |
| | | import Icon from '/@/components/Icon'; |
| | | |
| | | export default defineComponent({ |
| | | name: 'FoldButton', |
| | | components: { RedoOutlined, Tooltip, Icon }, |
| | | |
| | | setup() { |
| | | const { prefixCls } = useDesign('multiple-tabs-content'); |
| | | const { getShowMenu, setMenuSetting } = useMenuSetting(); |
| | | const { getShowHeader, setHeaderSetting } = useHeaderSetting(); |
| | | |
| | | const getIsUnFold = computed(() => { |
| | | return !unref(getShowMenu) && !unref(getShowHeader); |
| | | }); |
| | | |
| | | const getIcon = computed(() => { |
| | | return unref(getIsUnFold) ? 'codicon:screen-normal' : 'codicon:screen-full'; |
| | | }); |
| | | |
| | | function handleFold() { |
| | | const isScale = !unref(getShowMenu) && !unref(getShowHeader); |
| | | setMenuSetting({ |
| | | show: isScale, |
| | | hidden: !isScale, |
| | | }); |
| | | setHeaderSetting({ |
| | | show: isScale, |
| | | }); |
| | | } |
| | | |
| | | return { prefixCls, getIcon, handleFold }; |
| | | }, |
| | | }); |
| | | </script> |
| | |
| | | |
| | | &-content { |
| | | &__extra-quick, |
| | | &__extra-redo { |
| | | &__extra-redo, |
| | | &__extra-fold { |
| | | display: inline-block; |
| | | width: 36px; |
| | | height: @multiple-height; |
| | |
| | | <template #tabBarExtraContent v-if="getShowRedo || getShowQuick"> |
| | | <TabRedo v-if="getShowRedo" /> |
| | | <QuickButton v-if="getShowQuick" /> |
| | | <FoldButton v-if="getShowFold" /> |
| | | </template> |
| | | </Tabs> |
| | | </div> |
| | |
| | | components: { |
| | | QuickButton: createAsyncComponent(() => import('./components/QuickButton.vue')), |
| | | TabRedo: createAsyncComponent(() => import('./components/TabRedo.vue')), |
| | | FoldButton: createAsyncComponent(() => import('./components/FoldButton.vue')), |
| | | Tabs, |
| | | TabPane: Tabs.TabPane, |
| | | TabContent, |
| | |
| | | useTabsDrag(affixTextList); |
| | | const { prefixCls } = useDesign('multiple-tabs'); |
| | | const go = useGo(); |
| | | const { getShowQuick, getShowRedo } = useMultipleTabSetting(); |
| | | const { getShowQuick, getShowRedo, getShowFold } = useMultipleTabSetting(); |
| | | |
| | | const getTabsState = computed(() => { |
| | | return tabStore.getTabsState.filter((item) => !item.meta?.hideTab); |
| | |
| | | getTabsState, |
| | | getShowQuick, |
| | | getShowRedo, |
| | | getShowFold, |
| | | }; |
| | | }, |
| | | }); |
| | |
| | | import { RouteLocationNormalized } from 'vue-router'; |
| | | import { useTabs } from '/@/hooks/web/useTabs'; |
| | | import { useI18n } from '/@/hooks/web/useI18n'; |
| | | import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; |
| | | import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; |
| | | |
| | | const { t } = useI18n(); |
| | | |
| | |
| | | |
| | | const { currentRoute } = router; |
| | | |
| | | const { getShowMenu, setMenuSetting } = useMenuSetting(); |
| | | const { getShowHeader, setHeaderSetting } = useHeaderSetting(); |
| | | |
| | | const isTabs = computed(() => tabContentProps.type === TabContentEnum.TAB_TYPE); |
| | | |
| | | const getCurrentTab = computed( |
| | |
| | | return unref(isTabs) ? tabContentProps.tabItem : unref(currentRoute); |
| | | } |
| | | ); |
| | | |
| | | const getIsScale = computed(() => { |
| | | return !unref(getShowMenu) && !unref(getShowHeader); |
| | | }); |
| | | |
| | | /** |
| | | * @description: drop-down list |
| | |
| | | }, |
| | | ]; |
| | | |
| | | if (!unref(isTabs)) { |
| | | const isScale = unref(getIsScale); |
| | | dropMenuList.unshift({ |
| | | icon: isScale ? 'codicon:screen-normal' : 'codicon:screen-full', |
| | | event: MenuEventEnum.SCALE, |
| | | text: isScale ? t('layout.multipleTab.putAway') : t('layout.multipleTab.unfold'), |
| | | disabled: false, |
| | | }); |
| | | } |
| | | |
| | | return dropMenuList; |
| | | }); |
| | | |
| | |
| | | }; |
| | | } |
| | | |
| | | function scaleScreen() { |
| | | const isScale = !unref(getShowMenu) && !unref(getShowHeader); |
| | | setMenuSetting({ |
| | | show: isScale, |
| | | hidden: !isScale, |
| | | }); |
| | | setHeaderSetting({ |
| | | show: isScale, |
| | | }); |
| | | } |
| | | |
| | | // Handle right click event |
| | | function handleMenuEvent(menu: DropMenu): void { |
| | | const { refreshPage, closeAll, closeCurrent, closeLeft, closeOther, closeRight } = useTabs(); |
| | | const { refreshPage, closeAll, close, closeLeft, closeOther, closeRight } = useTabs(); |
| | | const { event } = menu; |
| | | switch (event) { |
| | | case MenuEventEnum.SCALE: |
| | |
| | | break; |
| | | // Close current |
| | | case MenuEventEnum.CLOSE_CURRENT: |
| | | closeCurrent(); |
| | | close(tabContentProps.tabItem); |
| | | break; |
| | | // Close left |
| | | case MenuEventEnum.CLOSE_LEFT: |
| | |
| | | closeRight: 'Close Right', |
| | | closeOther: 'Close Other', |
| | | closeAll: 'Close All', |
| | | putAway: 'PutAway', |
| | | unfold: 'Unfold', |
| | | tooltipRedo: 'Refresh', |
| | | }; |
| | |
| | | tabs: 'Tabs', |
| | | tabsQuickBtn: 'Tabs quick button', |
| | | tabsRedoBtn: 'Tabs redo button', |
| | | tabsFoldBtn: 'Tabs flod button', |
| | | sidebar: 'Sidebar', |
| | | header: 'Header', |
| | | footer: 'Footer', |
| | |
| | | export default { |
| | | redo: '刷新当前', |
| | | close: '关闭当前', |
| | | closeLeft: '关闭左侧', |
| | | closeRight: '关闭右侧', |
| | | closeOther: '关闭其他', |
| | | closeAll: '关闭全部', |
| | | putAway: '收起', |
| | | unfold: '展开', |
| | | redo: '重新加载', |
| | | close: '关闭标签页', |
| | | closeLeft: '关闭左侧标签页', |
| | | closeRight: '关闭右侧标签页', |
| | | closeOther: '关闭其它标签页', |
| | | closeAll: '关闭全部标签页', |
| | | tooltipRedo: '刷新', |
| | | }; |
| | |
| | | tabs: '标签页', |
| | | tabsQuickBtn: '标签页快捷按钮', |
| | | tabsRedoBtn: '标签页刷新按钮', |
| | | tabsFoldBtn: '标签页折叠按钮', |
| | | sidebar: '左侧菜单', |
| | | header: '顶栏', |
| | | footer: '页脚', |
New file |
| | |
| | | // Used to configure the general configuration of some components without modifying the components |
| | | |
| | | import type { SorterResult } from '../components/Table'; |
| | | |
| | | export default { |
| | | // basic-table setting |
| | | table: { |
| | | // Form interface request general configuration |
| | | // support xxx.xxx.xxx |
| | | fetchSetting: { |
| | | // The field name of the current page passed to the background |
| | | pageField: 'page', |
| | | // The number field name of each page displayed in the background |
| | | sizeField: 'pageSize', |
| | | // Field name of the form data returned by the interface |
| | | listField: 'items', |
| | | // Total number of tables returned by the interface field name |
| | | totalField: 'total', |
| | | }, |
| | | // Number of pages that can be selected |
| | | pageSizeOptions: ['10', '50', '80', '100'], |
| | | // Default display quantity on one page |
| | | defaultPageSize: 10, |
| | | // Custom general sort function |
| | | defaultSortFn: (sortInfo: SorterResult) => { |
| | | const { field, order } = sortInfo; |
| | | return { |
| | | // The sort field passed to the backend you |
| | | field, |
| | | // Sorting method passed to the background asc/desc |
| | | order, |
| | | }; |
| | | }, |
| | | // Custom general filter function |
| | | defaultFilterFn: (data: Partial<Recordable<string[]>>) => { |
| | | return data; |
| | | }, |
| | | }, |
| | | // scrollbar setting |
| | | scrollbar: { |
| | | // Whether to use native scroll bar |
| | | // After opening, the menu, modal, drawer will change the pop-up scroll bar to native |
| | | native: false, |
| | | }, |
| | | }; |
| | |
| | | |
| | | // aes encryption key |
| | | export const cacheCipher = { |
| | | key: '_12345678901234@', |
| | | iv: '@12345678901234_', |
| | | key: '_11111000001111@', |
| | | iv: '@11111000001111_', |
| | | }; |
| | | |
| | | // Whether the system cache is encrypted using aes |
| | |
| | | |
| | | // Whether to show the refresh button |
| | | showRedo: true, |
| | | // Whether to show the collapse button |
| | | showFold: true, |
| | | }, |
| | | |
| | | // Transition Setting |
| | |
| | | |
| | | // 显示刷新按钮 |
| | | showRedo: boolean; |
| | | |
| | | // 显示折叠按钮 |
| | | showFold: boolean; |
| | | } |
| | | |
| | | export interface HeaderSetting { |
| | |
| | | <template> |
| | | <PageWrapper title="单号:234231029431" contentBackgrond> |
| | | <template #extra> |
| | | <a-button key="3"> 操作一 </a-button> |
| | | <a-button key="2"> 操作二 </a-button> |
| | | <a-button key="1" type="primary"> 主操作 </a-button> |
| | | <a-button> 操作一 </a-button> |
| | | <a-button> 操作二 </a-button> |
| | | <a-button type="primary"> 主操作 </a-button> |
| | | </template> |
| | | |
| | | <template #footer> |