mirror of
				https://github.com/soybeanjs/soybean-admin.git
				synced 2025-11-04 07:43:42 +08:00 
			
		
		
		
	feat(projects): 添加头部折叠按钮
This commit is contained in:
		
							
								
								
									
										10
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								package.json
									
									
									
									
									
								
							@@ -36,14 +36,14 @@
 | 
			
		||||
    "vue-router": "^4.0.12"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@commitlint/cli": "^16.0.1",
 | 
			
		||||
    "@commitlint/cli": "^16.0.2",
 | 
			
		||||
    "@commitlint/config-conventional": "^16.0.0",
 | 
			
		||||
    "@iconify/json": "^1.1.453",
 | 
			
		||||
    "@iconify/vue": "^3.1.1",
 | 
			
		||||
    "@types/crypto-js": "^4.1.0",
 | 
			
		||||
    "@types/node": "^17.0.8",
 | 
			
		||||
    "@types/qs": "^6.9.7",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^5.8.1",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^5.9.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^5.9.0",
 | 
			
		||||
    "@vitejs/plugin-vue": "^2.0.1",
 | 
			
		||||
    "@vue/eslint-config-prettier": "^7.0.0",
 | 
			
		||||
@@ -59,20 +59,20 @@
 | 
			
		||||
    "eslint-plugin-prettier": "^4.0.0",
 | 
			
		||||
    "eslint-plugin-vue": "^8.2.0",
 | 
			
		||||
    "husky": "^7.0.4",
 | 
			
		||||
    "lint-staged": "^12.1.5",
 | 
			
		||||
    "lint-staged": "^12.1.7",
 | 
			
		||||
    "mockjs": "^1.1.0",
 | 
			
		||||
    "patch-package": "^6.4.7",
 | 
			
		||||
    "postinstall-postinstall": "^2.1.0",
 | 
			
		||||
    "prettier": "^2.5.1",
 | 
			
		||||
    "rollup-plugin-visualizer": "^5.5.2",
 | 
			
		||||
    "sass": "^1.46.0",
 | 
			
		||||
    "sass": "^1.47.0",
 | 
			
		||||
    "typescript": "^4.5.4",
 | 
			
		||||
    "unplugin-icons": "^0.13.0",
 | 
			
		||||
    "unplugin-vue-components": "^0.17.11",
 | 
			
		||||
    "vite": "^2.7.10",
 | 
			
		||||
    "vite-plugin-html": "^2.1.2",
 | 
			
		||||
    "vite-plugin-mock": "^2.9.6",
 | 
			
		||||
    "vite-plugin-windicss": "^1.6.1",
 | 
			
		||||
    "vite-plugin-windicss": "^1.6.2",
 | 
			
		||||
    "vue-tsc": "^0.30.2",
 | 
			
		||||
    "vueuc": "^0.4.19",
 | 
			
		||||
    "windicss": "^3.4.2"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										704
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										704
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										38
									
								
								src/components/common/HoverContainer/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/components/common/HoverContainer/index.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div v-if="showTooltip">
 | 
			
		||||
    <n-tooltip :placement="placement" trigger="hover">
 | 
			
		||||
      <template #trigger>
 | 
			
		||||
        <div class="flex-center h-full cursor-pointer hover:bg-[#f6f6f6] dark:hover:bg-[#333]" :class="contentClass">
 | 
			
		||||
          <slot></slot>
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
      {{ tooltipContent }}
 | 
			
		||||
    </n-tooltip>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div v-else class="flex-center cursor-pointer hover:bg-[#f6f6f6] dark:hover:bg-[#333]" :class="contentClass">
 | 
			
		||||
    <slot></slot>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed } from 'vue';
 | 
			
		||||
import { NTooltip } from 'naive-ui';
 | 
			
		||||
import type { FollowerPlacement } from 'vueuc';
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  /** tooltip显示文本 */
 | 
			
		||||
  tooltipContent?: string;
 | 
			
		||||
  /** tooltip的位置 */
 | 
			
		||||
  placement?: FollowerPlacement;
 | 
			
		||||
  /** class类 */
 | 
			
		||||
  contentClass?: string;
 | 
			
		||||
}
 | 
			
		||||
const props = withDefaults(defineProps<Props>(), {
 | 
			
		||||
  tooltipContent: '',
 | 
			
		||||
  placement: 'bottom',
 | 
			
		||||
  contentClass: ''
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const showTooltip = computed(() => Boolean(props.tooltipContent));
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
@@ -2,5 +2,6 @@ import NaiveProvider from './NaiveProvider/index.vue';
 | 
			
		||||
import SystemLogo from './SystemLogo/index.vue';
 | 
			
		||||
import DarkModeSwitch from './DarkModeSwitch/index.vue';
 | 
			
		||||
import DarkModeContainer from './DarkModeContainer/index.vue';
 | 
			
		||||
import HoverContainer from './HoverContainer/index.vue';
 | 
			
		||||
 | 
			
		||||
export { NaiveProvider, SystemLogo, DarkModeSwitch, DarkModeContainer };
 | 
			
		||||
export { NaiveProvider, SystemLogo, DarkModeSwitch, DarkModeContainer, HoverContainer };
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +1,3 @@
 | 
			
		||||
export * from './system';
 | 
			
		||||
export * from './router';
 | 
			
		||||
export * from './layout';
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										70
									
								
								src/composables/common/layout.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/composables/common/layout.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
import { computed } from 'vue';
 | 
			
		||||
import { useAppStore, useThemeStore } from '@/store';
 | 
			
		||||
import type { ThemeLayoutMode, GlobalHeaderProps } from '@/interface';
 | 
			
		||||
 | 
			
		||||
type LayoutHeaderProps = Record<ThemeLayoutMode, GlobalHeaderProps>;
 | 
			
		||||
 | 
			
		||||
export function useBasicLayout() {
 | 
			
		||||
  const app = useAppStore();
 | 
			
		||||
  const theme = useThemeStore();
 | 
			
		||||
 | 
			
		||||
  type LayoutMode = 'vertical' | 'horizontal';
 | 
			
		||||
  const mode = computed(() => {
 | 
			
		||||
    const vertical: LayoutMode = 'vertical';
 | 
			
		||||
    const horizontal: LayoutMode = 'horizontal';
 | 
			
		||||
    return theme.layout.mode.includes(vertical) ? vertical : horizontal;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const layoutHeaderProps: LayoutHeaderProps = {
 | 
			
		||||
    vertical: {
 | 
			
		||||
      showLogo: false,
 | 
			
		||||
      showHeaderMenu: false,
 | 
			
		||||
      showMenuCollape: true
 | 
			
		||||
    },
 | 
			
		||||
    'vertical-mix': {
 | 
			
		||||
      showLogo: false,
 | 
			
		||||
      showHeaderMenu: false,
 | 
			
		||||
      showMenuCollape: false
 | 
			
		||||
    },
 | 
			
		||||
    horizontal: {
 | 
			
		||||
      showLogo: true,
 | 
			
		||||
      showHeaderMenu: true,
 | 
			
		||||
      showMenuCollape: false
 | 
			
		||||
    },
 | 
			
		||||
    'horizontal-mix': {
 | 
			
		||||
      showLogo: true,
 | 
			
		||||
      showHeaderMenu: false,
 | 
			
		||||
      showMenuCollape: true
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const headerProps = computed(() => layoutHeaderProps[theme.layout.mode]);
 | 
			
		||||
 | 
			
		||||
  const siderVisible = computed(() => theme.layout.mode !== 'horizontal');
 | 
			
		||||
  const siderWidth = computed(() => {
 | 
			
		||||
    const { width, mixWidth, mixChildMenuWidth } = theme.sider;
 | 
			
		||||
    const isVerticalMix = theme.layout.mode === 'vertical-mix';
 | 
			
		||||
    let w = isVerticalMix ? mixWidth : width;
 | 
			
		||||
    if (isVerticalMix && app.mixSiderFixed) {
 | 
			
		||||
      w += mixChildMenuWidth;
 | 
			
		||||
    }
 | 
			
		||||
    return w;
 | 
			
		||||
  });
 | 
			
		||||
  const siderCollapsedWidth = computed(() => {
 | 
			
		||||
    const { collapsedWidth, mixCollapsedWidth, mixChildMenuWidth } = theme.sider;
 | 
			
		||||
    const isVerticalMix = theme.layout.mode === 'vertical-mix';
 | 
			
		||||
    let w = isVerticalMix ? mixCollapsedWidth : collapsedWidth;
 | 
			
		||||
    if (isVerticalMix && app.mixSiderFixed) {
 | 
			
		||||
      w += mixChildMenuWidth;
 | 
			
		||||
    }
 | 
			
		||||
    return w;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    mode,
 | 
			
		||||
    headerProps,
 | 
			
		||||
    siderVisible,
 | 
			
		||||
    siderWidth,
 | 
			
		||||
    siderCollapsedWidth
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
export * from './enum';
 | 
			
		||||
export * from './theme';
 | 
			
		||||
export * from './system';
 | 
			
		||||
export * from './layout';
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								src/interface/layout.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/interface/layout.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
/** 全局头部属性 */
 | 
			
		||||
export interface GlobalHeaderProps {
 | 
			
		||||
  /** 显示logo */
 | 
			
		||||
  showLogo: boolean;
 | 
			
		||||
  /** 显示头部菜单 */
 | 
			
		||||
  showHeaderMenu: boolean;
 | 
			
		||||
  /** 显示菜单折叠按钮 */
 | 
			
		||||
  showMenuCollape: boolean;
 | 
			
		||||
}
 | 
			
		||||
@@ -8,11 +8,11 @@
 | 
			
		||||
    :sider-visible="siderVisible"
 | 
			
		||||
    :sider-width="siderWidth"
 | 
			
		||||
    :sider-collapsed-width="siderCollapsedWidth"
 | 
			
		||||
    :sider-collapse="false"
 | 
			
		||||
    :sider-collapse="app.siderCollapse"
 | 
			
		||||
    :fixed-footer="theme.footer.fixed"
 | 
			
		||||
  >
 | 
			
		||||
    <template #header>
 | 
			
		||||
      <global-header />
 | 
			
		||||
      <global-header v-bind="headerProps" />
 | 
			
		||||
    </template>
 | 
			
		||||
    <template #tab>
 | 
			
		||||
      <global-tab />
 | 
			
		||||
@@ -29,41 +29,14 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { computed } from 'vue';
 | 
			
		||||
import { useAppStore, useThemeStore } from '@/store';
 | 
			
		||||
import { useBasicLayout } from '@/composables';
 | 
			
		||||
import { SoybeanLayout } from '@/package';
 | 
			
		||||
import { SettingDrawer, GlobalHeader, GlobalTab, GlobalSider, GlobalContent, GlobalFooter } from '../common';
 | 
			
		||||
 | 
			
		||||
const app = useAppStore();
 | 
			
		||||
const theme = useThemeStore();
 | 
			
		||||
 | 
			
		||||
const siderVisible = computed(() => theme.layout.mode !== 'horizontal');
 | 
			
		||||
 | 
			
		||||
type LayoutMode = 'vertical' | 'horizontal';
 | 
			
		||||
const mode = computed(() => {
 | 
			
		||||
  const vertical: LayoutMode = 'vertical';
 | 
			
		||||
  const horizontal: LayoutMode = 'horizontal';
 | 
			
		||||
  return theme.layout.mode.includes(vertical) ? vertical : horizontal;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const siderWidth = computed(() => {
 | 
			
		||||
  const { width, mixWidth, mixChildMenuWidth } = theme.sider;
 | 
			
		||||
  const isVerticalMix = theme.layout.mode === 'vertical-mix';
 | 
			
		||||
  let w = isVerticalMix ? mixWidth : width;
 | 
			
		||||
  if (isVerticalMix && app.mixSiderFixed) {
 | 
			
		||||
    w += mixChildMenuWidth;
 | 
			
		||||
  }
 | 
			
		||||
  return w;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const siderCollapsedWidth = computed(() => {
 | 
			
		||||
  const { collapsedWidth, mixCollapsedWidth, mixChildMenuWidth } = theme.sider;
 | 
			
		||||
  const isVerticalMix = theme.layout.mode === 'vertical-mix';
 | 
			
		||||
  let w = isVerticalMix ? mixCollapsedWidth : collapsedWidth;
 | 
			
		||||
  if (isVerticalMix && app.mixSiderFixed) {
 | 
			
		||||
    w += mixChildMenuWidth;
 | 
			
		||||
  }
 | 
			
		||||
  return w;
 | 
			
		||||
});
 | 
			
		||||
const { mode, headerProps, siderVisible, siderWidth, siderCollapsedWidth } = useBasicLayout();
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								src/layouts/common/GlobalHeader/components/MenuCollapse.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/layouts/common/GlobalHeader/components/MenuCollapse.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <hover-container class="w-40px h-full" @click="app.toggleSiderCollapse">
 | 
			
		||||
    <icon-line-md-menu-unfold-left v-if="app.siderCollapse" class="text-16px" />
 | 
			
		||||
    <icon-line-md-menu-fold-left v-else class="text-16px" />
 | 
			
		||||
  </hover-container>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { HoverContainer } from '@/components';
 | 
			
		||||
import { useAppStore } from '@/store';
 | 
			
		||||
 | 
			
		||||
const app = useAppStore();
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
							
								
								
									
										3
									
								
								src/layouts/common/GlobalHeader/components/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/layouts/common/GlobalHeader/components/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
import MenuCollapse from './MenuCollapse.vue';
 | 
			
		||||
 | 
			
		||||
export { MenuCollapse };
 | 
			
		||||
@@ -1,9 +1,32 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <dark-mode-container class="global-header flex-y-center h-full"></dark-mode-container>
 | 
			
		||||
  <dark-mode-container class="global-header flex-y-center h-full">
 | 
			
		||||
    <global-logo v-if="showLogo" :show-title="true" class="h-full" :style="{ width: theme.sider.width + 'px' }" />
 | 
			
		||||
    <div v-if="!showHeaderMenu" class="flex-1-hidden flex-y-center h-full">
 | 
			
		||||
      <menu-collapse v-if="showMenuCollape" />
 | 
			
		||||
      <!-- <global-breadcrumb v-if="theme.header.crumb.visible" /> -->
 | 
			
		||||
    </div>
 | 
			
		||||
  </dark-mode-container>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { DarkModeContainer } from '@/components';
 | 
			
		||||
import { useThemeStore } from '@/store';
 | 
			
		||||
import type { GlobalHeaderProps } from '@/interface';
 | 
			
		||||
import GlobalLogo from '../GlobalLogo/index.vue';
 | 
			
		||||
import { MenuCollapse } from './components';
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  /** 显示logo */
 | 
			
		||||
  showLogo: GlobalHeaderProps['showLogo'];
 | 
			
		||||
  /** 显示头部菜单 */
 | 
			
		||||
  showHeaderMenu: GlobalHeaderProps['showHeaderMenu'];
 | 
			
		||||
  /** 显示菜单折叠按钮 */
 | 
			
		||||
  showMenuCollape: GlobalHeaderProps['showMenuCollape'];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineProps<Props>();
 | 
			
		||||
 | 
			
		||||
const theme = useThemeStore();
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped>
 | 
			
		||||
.global-header {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								src/layouts/common/GlobalLogo/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/layouts/common/GlobalLogo/index.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <router-link :to="routeHomePath" class="flex-center w-full nowrap-hidden">
 | 
			
		||||
    <system-logo class="w-32px h-32px text-primary" />
 | 
			
		||||
    <h2 v-if="showTitle" class="pl-8px text-16px font-bold text-primary">{{ title }}</h2>
 | 
			
		||||
  </router-link>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { SystemLogo } from '@/components';
 | 
			
		||||
import { routePath } from '@/router';
 | 
			
		||||
import { useAppInfo } from '@/composables';
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  /** 显示名字 */
 | 
			
		||||
  showTitle: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineProps<Props>();
 | 
			
		||||
 | 
			
		||||
const { title } = useAppInfo();
 | 
			
		||||
const routeHomePath = routePath('root');
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
@@ -4,5 +4,6 @@ import GlobalTab from './GlobalTab/index.vue';
 | 
			
		||||
import GlobalSider from './GlobalSider/index.vue';
 | 
			
		||||
import GlobalContent from './GlobalContent/index.vue';
 | 
			
		||||
import GlobalFooter from './GlobalFooter/index.vue';
 | 
			
		||||
import GlobalLogo from './GlobalLogo/index.vue';
 | 
			
		||||
 | 
			
		||||
export { SettingDrawer, GlobalHeader, GlobalTab, GlobalSider, GlobalContent, GlobalFooter };
 | 
			
		||||
export { SettingDrawer, GlobalHeader, GlobalTab, GlobalSider, GlobalContent, GlobalFooter, GlobalLogo };
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,8 @@ interface AppStore {
 | 
			
		||||
  toggleSettingdrawerVisible(): void;
 | 
			
		||||
  /** 侧边栏折叠状态 */
 | 
			
		||||
  siderCollapse: Ref<boolean>;
 | 
			
		||||
  /** 折叠/展开 侧边栏折叠状态 */
 | 
			
		||||
  toggleSiderCollapse(): void;
 | 
			
		||||
  /** 设置侧边栏折叠状态 */
 | 
			
		||||
  setSiderCollapse(collapse: boolean): void;
 | 
			
		||||
  /** vertical-mix模式下 侧边栏的固定状态 */
 | 
			
		||||
@@ -41,7 +43,7 @@ export const useAppStore = defineStore('app-store', () => {
 | 
			
		||||
  } = useModalVisible();
 | 
			
		||||
 | 
			
		||||
  // 侧边栏的折叠状态
 | 
			
		||||
  const { bool: siderCollapse, setBool: setSiderCollapse } = useBoolean();
 | 
			
		||||
  const { bool: siderCollapse, setBool: setSiderCollapse, toggle: toggleSiderCollapse } = useBoolean();
 | 
			
		||||
 | 
			
		||||
  // vertical-mix模式下 侧边栏的固定状态
 | 
			
		||||
  const { bool: mixSiderFixed, setBool: setMixSiderIsFixed } = useBoolean();
 | 
			
		||||
@@ -55,6 +57,7 @@ export const useAppStore = defineStore('app-store', () => {
 | 
			
		||||
    toggleSettingdrawerVisible,
 | 
			
		||||
    siderCollapse,
 | 
			
		||||
    setSiderCollapse,
 | 
			
		||||
    toggleSiderCollapse,
 | 
			
		||||
    mixSiderFixed,
 | 
			
		||||
    setMixSiderIsFixed
 | 
			
		||||
  };
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user