mirror of
				https://github.com/soybeanjs/soybean-admin.git
				synced 2025-11-04 07:43:42 +08:00 
			
		
		
		
	feat(projects): 添加multiTab标签页
This commit is contained in:
		@@ -72,7 +72,7 @@ soybean-admin
 | 
			
		||||
│   │   ├── dark-mode.ts       //windicss暗黑模式插件
 | 
			
		||||
│   │   └── smooth-scroll.ts   //滚动平滑插件
 | 
			
		||||
│   ├── router                 //vue路由
 | 
			
		||||
│   │   ├── menus.ts      //菜单
 | 
			
		||||
│   │   ├── menus.ts           //菜单
 | 
			
		||||
│   │   ├── permission.ts      //路由守卫相关函数
 | 
			
		||||
│   │   └── routes.ts          //声明的路由
 | 
			
		||||
│   ├── service                //网络请求
 | 
			
		||||
@@ -93,7 +93,6 @@ soybean-admin
 | 
			
		||||
│   └── views                  //页面
 | 
			
		||||
│       ├── dashboard
 | 
			
		||||
│       └── system
 | 
			
		||||
├── tree.md
 | 
			
		||||
├── tsconfig.json              //TS配置
 | 
			
		||||
├── vite.config.ts             //vite配置
 | 
			
		||||
├── windi.config.ts            //windicss框架配置
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,10 @@ export interface ThemeSettings {
 | 
			
		||||
  crumbsStyle: CrumbsStyle;
 | 
			
		||||
  /** 页面样式 */
 | 
			
		||||
  pageStyle: PageStyle;
 | 
			
		||||
  /** 固定头部和多标签 */
 | 
			
		||||
  fixedHeaderAndTab: boolean;
 | 
			
		||||
  /** 显示重载按钮 */
 | 
			
		||||
  showReload: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface OtherColor {
 | 
			
		||||
@@ -50,10 +54,6 @@ interface HeaderStyle {
 | 
			
		||||
  height: number;
 | 
			
		||||
  /** 背景颜色 */
 | 
			
		||||
  bgColor: string;
 | 
			
		||||
  /** 固定顶部 */
 | 
			
		||||
  fixed: boolean;
 | 
			
		||||
  /** 显示重载按钮 */
 | 
			
		||||
  showReload: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface MenuStyle {
 | 
			
		||||
@@ -70,6 +70,8 @@ interface MenuStyle {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface MultiTabStyle {
 | 
			
		||||
  /** 多标签高度 */
 | 
			
		||||
  height: number;
 | 
			
		||||
  /** 多标签可见 */
 | 
			
		||||
  visible: boolean;
 | 
			
		||||
  /** 背景颜色 */
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <header v-if="fixedHeader && theme.navStyle.mode !== 'horizontal-mix'" class="w-full header-height"></header>
 | 
			
		||||
  <header v-if="fixedHeaderAndTab && theme.navStyle.mode !== 'horizontal-mix'" class="header-height w-full"></header>
 | 
			
		||||
  <n-layout-header :inverted="headerInverted" :position="position" :style="{ zIndex }">
 | 
			
		||||
    <div class="global-header header-height flex-y-center w-full">
 | 
			
		||||
      <div v-if="!theme.isVerticalNav" class="menu-width h-full">
 | 
			
		||||
@@ -38,8 +38,8 @@ const theme = useThemeStore();
 | 
			
		||||
const inverted = computed(() => {
 | 
			
		||||
  return theme.navStyle.theme !== 'light';
 | 
			
		||||
});
 | 
			
		||||
const fixedHeader = computed(() => theme.headerStyle.fixed || theme.navStyle.mode === 'horizontal-mix');
 | 
			
		||||
const position = computed(() => (fixedHeader.value ? 'absolute' : 'static'));
 | 
			
		||||
const fixedHeaderAndTab = computed(() => theme.fixedHeaderAndTab || theme.navStyle.mode === 'horizontal-mix');
 | 
			
		||||
const position = computed(() => (fixedHeaderAndTab.value ? 'absolute' : 'static'));
 | 
			
		||||
const headerInverted = computed(() => {
 | 
			
		||||
  return theme.navStyle.theme !== 'dark' ? inverted.value : !inverted.value;
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,50 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div></div>
 | 
			
		||||
  <div v-if="fixedHeaderAndTab && theme.navStyle.mode !== 'horizontal-mix'" class="multi-tab-height w-full"></div>
 | 
			
		||||
  <div
 | 
			
		||||
    class="multi-tab-height flex-y-center w-full px-10px"
 | 
			
		||||
    :class="{ 'multi-tab-top absolute': fixedHeaderAndTab, 'bg-[#f5f7f9]': !theme.darkMode }"
 | 
			
		||||
    :style="{ zIndex }"
 | 
			
		||||
  >
 | 
			
		||||
    <n-space :align="'center'">
 | 
			
		||||
      <n-tag>爱在西元前</n-tag>
 | 
			
		||||
      <n-tag type="success">不该</n-tag>
 | 
			
		||||
      <n-tag type="warning">超人不会飞</n-tag>
 | 
			
		||||
      <n-tag type="error">手写的从前</n-tag>
 | 
			
		||||
      <n-tag type="info">哪里都是你</n-tag>
 | 
			
		||||
      <n-gradient-text size="24">这是MultiTab组件</n-gradient-text>
 | 
			
		||||
    </n-space>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup></script>
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed } from 'vue';
 | 
			
		||||
import { NSpace, NTag, NGradientText } from 'naive-ui';
 | 
			
		||||
import { useThemeStore } from '@/store';
 | 
			
		||||
 | 
			
		||||
defineProps({
 | 
			
		||||
  zIndex: {
 | 
			
		||||
    type: Number,
 | 
			
		||||
    default: 0
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const theme = useThemeStore();
 | 
			
		||||
 | 
			
		||||
const fixedHeaderAndTab = computed(() => theme.fixedHeaderAndTab || theme.navStyle.mode === 'horizontal-mix');
 | 
			
		||||
const multiTabHeight = computed(() => {
 | 
			
		||||
  const { height } = theme.multiTabStyle;
 | 
			
		||||
  return `${height}px`;
 | 
			
		||||
});
 | 
			
		||||
const headerHeight = computed(() => {
 | 
			
		||||
  const { height } = theme.headerStyle;
 | 
			
		||||
  return `${height}px`;
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped>
 | 
			
		||||
.multi-tab-height {
 | 
			
		||||
  height: v-bind(multiTabHeight);
 | 
			
		||||
}
 | 
			
		||||
.multi-tab-top {
 | 
			
		||||
  top: v-bind(headerHeight);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,8 @@
 | 
			
		||||
    <setting-menu-item label="分割菜单">
 | 
			
		||||
      <n-switch :value="theme.menuStyle.splitMenu" @update:value="handleSplitMenu" />
 | 
			
		||||
    </setting-menu-item>
 | 
			
		||||
    <setting-menu-item label="固定头部">
 | 
			
		||||
      <n-switch :value="splitMenu" :disabled="disabledSplitMenu" @update:value="handleFixedHeader" />
 | 
			
		||||
    <setting-menu-item label="固定头部和多标签">
 | 
			
		||||
      <n-switch :value="splitMenu" :disabled="disabledSplitMenu" @update:value="handleFixedHeaderAndTab" />
 | 
			
		||||
    </setting-menu-item>
 | 
			
		||||
    <setting-menu-item label="头部高度">
 | 
			
		||||
      <n-input-number
 | 
			
		||||
@@ -16,6 +16,15 @@
 | 
			
		||||
        @update:value="handleHeaderHeight"
 | 
			
		||||
      />
 | 
			
		||||
    </setting-menu-item>
 | 
			
		||||
    <setting-menu-item label="多标签高度">
 | 
			
		||||
      <n-input-number
 | 
			
		||||
        class="w-120px"
 | 
			
		||||
        size="small"
 | 
			
		||||
        :value="theme.multiTabStyle.height"
 | 
			
		||||
        :step="1"
 | 
			
		||||
        @update:value="handleMultiTabHeight"
 | 
			
		||||
      />
 | 
			
		||||
    </setting-menu-item>
 | 
			
		||||
    <setting-menu-item label="菜单展开宽度">
 | 
			
		||||
      <n-input-number
 | 
			
		||||
        class="w-120px"
 | 
			
		||||
@@ -46,10 +55,17 @@ import { useThemeStore } from '@/store';
 | 
			
		||||
import { SettingMenuItem } from '../common';
 | 
			
		||||
 | 
			
		||||
const theme = useThemeStore();
 | 
			
		||||
const { handleSplitMenu, handleFixedHeader, handleHeaderHeight, handleMenuWidth, handleMixMenuWidth } = useThemeStore();
 | 
			
		||||
const {
 | 
			
		||||
  handleSplitMenu,
 | 
			
		||||
  handleFixedHeaderAndTab,
 | 
			
		||||
  handleHeaderHeight,
 | 
			
		||||
  handleMultiTabHeight,
 | 
			
		||||
  handleMenuWidth,
 | 
			
		||||
  handleMixMenuWidth
 | 
			
		||||
} = useThemeStore();
 | 
			
		||||
 | 
			
		||||
const disabledSplitMenu = computed(() => theme.navStyle.mode === 'horizontal-mix');
 | 
			
		||||
const splitMenu = computed(() => theme.headerStyle.fixed || disabledSplitMenu.value);
 | 
			
		||||
const splitMenu = computed(() => theme.fixedHeaderAndTab || disabledSplitMenu.value);
 | 
			
		||||
const disabledMenuWidth = computed(() => {
 | 
			
		||||
  const { mode } = theme.navStyle;
 | 
			
		||||
  return mode !== 'vertical' && mode !== 'horizontal-mix';
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import GlobalSider from './GlobalSider/index.vue';
 | 
			
		||||
import GlobalHeader from './GlobalHeader/index.vue';
 | 
			
		||||
import GlobalTab from './GlobalTab/index.vue';
 | 
			
		||||
import GlobalFooter from './GlobalFooter/index.vue';
 | 
			
		||||
import SettingDrawer from './SettingDrawer/index.vue';
 | 
			
		||||
 | 
			
		||||
export { GlobalSider, GlobalHeader, GlobalFooter, SettingDrawer };
 | 
			
		||||
export { GlobalSider, GlobalHeader, GlobalTab, GlobalFooter, SettingDrawer };
 | 
			
		||||
 
 | 
			
		||||
@@ -10,8 +10,14 @@
 | 
			
		||||
          :class="[{ 'content-padding': isHorizontalMix }, fullPage ? 'h-full' : 'min-h-100vh']"
 | 
			
		||||
        >
 | 
			
		||||
          <global-header v-if="!isHorizontalMix" :z-index="1" />
 | 
			
		||||
          <global-tab :z-index="1" />
 | 
			
		||||
          <n-layout-content class="flex-auto" :class="{ 'bg-[#f5f7f9]': !theme.darkMode }">
 | 
			
		||||
            <router-view />
 | 
			
		||||
            <router-view v-slot="{ Component }">
 | 
			
		||||
              <keep-alive v-if="keepAlive">
 | 
			
		||||
                <component :is="Component" />
 | 
			
		||||
              </keep-alive>
 | 
			
		||||
              <component :is="Component" v-else />
 | 
			
		||||
            </router-view>
 | 
			
		||||
          </n-layout-content>
 | 
			
		||||
          <global-footer />
 | 
			
		||||
        </div>
 | 
			
		||||
@@ -27,17 +33,24 @@ import { useRoute } from 'vue-router';
 | 
			
		||||
import { NLayout, NScrollbar, NLayoutContent } from 'naive-ui';
 | 
			
		||||
import { useThemeStore } from '@/store';
 | 
			
		||||
import { useScrollBehavior } from '@/hooks';
 | 
			
		||||
import { GlobalSider, GlobalHeader, GlobalFooter, SettingDrawer } from './components';
 | 
			
		||||
import { GlobalSider, GlobalHeader, GlobalTab, GlobalFooter, SettingDrawer } from './components';
 | 
			
		||||
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
const theme = useThemeStore();
 | 
			
		||||
const { scrollbar, resetScrollWatcher } = useScrollBehavior();
 | 
			
		||||
 | 
			
		||||
const isHorizontalMix = computed(() => theme.navStyle.mode === 'horizontal-mix');
 | 
			
		||||
const headerHeight = computed(() => {
 | 
			
		||||
  const { height } = theme.headerStyle;
 | 
			
		||||
  return `${height}px`;
 | 
			
		||||
const headerAndMultiTabHeight = computed(() => {
 | 
			
		||||
  const {
 | 
			
		||||
    headerStyle: { height: hHeight },
 | 
			
		||||
    multiTabStyle: { height: mHeight }
 | 
			
		||||
  } = theme;
 | 
			
		||||
  return `${hHeight + mHeight}px`;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/** 缓存页面 */
 | 
			
		||||
const keepAlive = computed(() => Boolean(route.meta?.keepAlive));
 | 
			
		||||
 | 
			
		||||
/** 100%视高 */
 | 
			
		||||
const fullPage = computed(() => Boolean(route.meta?.fullPage));
 | 
			
		||||
 | 
			
		||||
@@ -49,9 +62,9 @@ resetScrollWatcher();
 | 
			
		||||
  z-index: 11;
 | 
			
		||||
}
 | 
			
		||||
.sider-margin {
 | 
			
		||||
  margin-top: v-bind(headerHeight);
 | 
			
		||||
  margin-top: v-bind(headerAndMultiTabHeight);
 | 
			
		||||
}
 | 
			
		||||
.content-padding {
 | 
			
		||||
  padding-top: v-bind(headerHeight);
 | 
			
		||||
  padding-top: v-bind(headerAndMultiTabHeight);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,12 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <n-scrollbar ref="scrollbar" class="h-full" :x-scrollable="true" :content-class="fullPage ? 'h-full' : ''">
 | 
			
		||||
    <div class="inline-block w-full" :class="[fullPage ? 'h-full' : 'min-h-100vh']">
 | 
			
		||||
      <router-view />
 | 
			
		||||
      <router-view v-slot="{ Component }">
 | 
			
		||||
        <keep-alive v-if="keepAlive">
 | 
			
		||||
          <component :is="Component" />
 | 
			
		||||
        </keep-alive>
 | 
			
		||||
        <component :is="Component" v-else />
 | 
			
		||||
      </router-view>
 | 
			
		||||
    </div>
 | 
			
		||||
  </n-scrollbar>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -15,6 +20,9 @@ import { useScrollBehavior } from '@/hooks';
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
const { scrollbar, resetScrollWatcher } = useScrollBehavior();
 | 
			
		||||
 | 
			
		||||
/** 缓存页面 */
 | 
			
		||||
const keepAlive = computed(() => Boolean(route.meta?.keepAlive));
 | 
			
		||||
 | 
			
		||||
/** 100%视高 */
 | 
			
		||||
const fullPage = computed(() => Boolean(route.meta?.fullPage));
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -101,6 +101,7 @@ export const customRoutes: CustomRoute[] = [
 | 
			
		||||
    redirect: { name: RouteNameMap.get('dashboard-analysis') },
 | 
			
		||||
    meta: {
 | 
			
		||||
      title: EnumRouteTitle.dashboard,
 | 
			
		||||
      keepAlive: true,
 | 
			
		||||
      icon: Dashboard
 | 
			
		||||
    },
 | 
			
		||||
    children: [
 | 
			
		||||
 
 | 
			
		||||
@@ -45,11 +45,10 @@ const themeSettings: ThemeSettings = {
 | 
			
		||||
  },
 | 
			
		||||
  headerStyle: {
 | 
			
		||||
    height: 64,
 | 
			
		||||
    bgColor: '#fff',
 | 
			
		||||
    fixed: true,
 | 
			
		||||
    showReload: true
 | 
			
		||||
    bgColor: '#fff'
 | 
			
		||||
  },
 | 
			
		||||
  multiTabStyle: {
 | 
			
		||||
    height: 48,
 | 
			
		||||
    visible: true,
 | 
			
		||||
    bgColor: '#fff'
 | 
			
		||||
  },
 | 
			
		||||
@@ -68,7 +67,9 @@ const themeSettings: ThemeSettings = {
 | 
			
		||||
      { value: 'fade-bottom', label: EnumAnimate['fade-bottom'] },
 | 
			
		||||
      { value: 'fade-scale', label: EnumAnimate['fade-scale'] }
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
  },
 | 
			
		||||
  fixedHeaderAndTab: true,
 | 
			
		||||
  showReload: true
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default themeSettings;
 | 
			
		||||
 
 | 
			
		||||
@@ -47,8 +47,10 @@ const appStore = defineStore({
 | 
			
		||||
    toggleMenu() {
 | 
			
		||||
      this.menu.collapsed = !this.menu.collapsed;
 | 
			
		||||
    },
 | 
			
		||||
    /** 初始化多tab的数据 */
 | 
			
		||||
    initMultiTab() {},
 | 
			
		||||
    /** 添加多tab的数据 */
 | 
			
		||||
    addMultiTab(route: RouteLocationNormalizedLoaded) {
 | 
			
		||||
      this.multiTab.routes.push(route);
 | 
			
		||||
    },
 | 
			
		||||
    /** 打开配置抽屉 */
 | 
			
		||||
    openSettingDrawer() {
 | 
			
		||||
      this.settingDrawer.visible = true;
 | 
			
		||||
 
 | 
			
		||||
@@ -85,15 +85,17 @@ const themeStore = defineStore({
 | 
			
		||||
        this.menuStyle.mixWidth = width;
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    /** 更改头部的高度(不包含tab标签) */
 | 
			
		||||
    /** 更改头部的高度 */
 | 
			
		||||
    handleHeaderHeight(height: number | null) {
 | 
			
		||||
      if (height !== null) {
 | 
			
		||||
        this.headerStyle.height = height;
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    /** 固定头部 */
 | 
			
		||||
    handleFixedHeader(isFixed: boolean) {
 | 
			
		||||
      this.headerStyle.fixed = isFixed;
 | 
			
		||||
    /** 更改Tab标签的高度 */
 | 
			
		||||
    handleMultiTabHeight(height: number | null) {
 | 
			
		||||
      if (height !== null) {
 | 
			
		||||
        this.multiTabStyle.height = height;
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    /** 设置多标签的显示 */
 | 
			
		||||
    handleMultiTabVisible(visible: boolean) {
 | 
			
		||||
@@ -116,6 +118,10 @@ const themeStore = defineStore({
 | 
			
		||||
      if (this.pageStyle.animate) {
 | 
			
		||||
        this.pageStyle.animateType = animateType;
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    /** 固定头部 */
 | 
			
		||||
    handleFixedHeaderAndTab(isFixed: boolean) {
 | 
			
		||||
      this.fixedHeaderAndTab = isFixed;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="p-10px">
 | 
			
		||||
  <div class="px-10px">
 | 
			
		||||
    <data-card :loading="loading" />
 | 
			
		||||
    <nav-card :loading="loading" />
 | 
			
		||||
  </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="p-10px">
 | 
			
		||||
  <div class="px-10px">
 | 
			
		||||
    <div class="flex-y-center flex-col h-500px bg-white">
 | 
			
		||||
      <n-gradient-text type="primary" size="32">工作台</n-gradient-text>
 | 
			
		||||
      <n-space>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user