Layout 布局组件快速搭建

文章目录

  • 设置主题样式变量
  • 封装公共布局组件
    • 封装 Logo 组件
    • 封装 Menu 菜单组件
    • 封装 Breadcrumb 面包屑组件
    • 封装 TabBar 标签栏组件
    • 封装 Main 内容区组件
    • 封装 Footer 底部组件
    • 封装 Theme 主题组件
  • 经典布局
  • 水平布局
  • 响应式布局
  • 搭建 Layout 布局组件
  • 添加 Layout 路由配置
  • 启动项目

设置主题样式变量

  1. 创建主题变量文件:src/assets/styles/theme.scss

  2. 添加页面布局组件用到的变量:

    :root {
      --os-layout-aside-width: 224px; // 侧边栏宽度
      --os-layout-logo-height: 56px; // 头部高度
      --os-layout-header-height: 56px; // 头部高度
      --os-layout-header-menu-width: 300px; // 头部菜单宽度
      --os-layout-footer-height: 40px; // 底部高度
      --os-layout-tab-height: 40px; // 底部高度
    }
    
  3. 设置全局滚动条样式:

    /* scroll bar */
    ::-webkit-scrollbar {
        width: 6px;
        height: 6px;
    }
    
    ::-webkit-scrollbar-thumb {
        background-color: var(--el-border-color-darker);
        border-radius: 20px;
    }
    
  4. 在统一管理和维护项目的样式文件src/assets/styles/index.scss中导入主题样式文件:

    @import "theme.scss";
    

注:统一管理和维护项目的样式文件src/assets/styles/index.scss需在mian.ts中导入,注意导入顺序。

封装公共布局组件

封装 Logo 组件

  1. 创建 Logo 组件:src/layouts/components/Logo/index.vue

  2. 通过import.meta.env获取.env文件中定义的环境变量,环境变量使用详情跳转认识和使用 Vite 环境变量配置:

    <script setup lang="ts">
    const app_title = import.meta.env.VITE_APP_TITLE
    </script>
    
  3. Logo 组件内容,添加 Logo 图标和项目标题:

    <template>
      <!-- logo 容器 -->
      <div class="logo-container">
        <!--  logo 图标 -->
        <svg-icon width="32px" height="32px" name="logo" />
    
        <!-- 应用标题,只在中等及以上屏幕尺寸上可见 -->
        <span class="logo-title hidden md:block">{{ app_title }}</span>
      </div>
    </template>
    
  4. 添加样式:

    <style scoped lang="postcss">
    .logo-container {
      @apply flex items-center justify-center;
      height: var(--os-layout-logo-height);
      width: var(--os-layout-aside-width);
    
      .logo-title {
        @apply text-lg font-bold subpixel-antialiased;
        @apply bg-clip-text text-transparent bg-gradient-to-r from-pink-500 to-violet-500;
      }
    }
    </style>
    

封装 Menu 菜单组件

  1. 创建 Menu 菜单组件:src/layouts/components/Menu/index.vue

  2. el-menu 组件中使用 props 来控制 mode 属性,可以实现菜单的不同显示模式(如 verticalhorizontal):

    <script setup lang="ts">
    const props = defineProps<{
      mode?: 'vertical' | 'horizontal'
    }>()
    </script>
    
  3. 使用 ElementPlus 的 el-menu 菜单组件搭建垂直菜单:

    <template>
      <!-- 菜单容器 -->
      <div class="menu-container">
        <!-- Element UI 菜单组件 -->
        <el-menu
          class="os-menu"
          :mode="props.mode"
        >
          <!-- 一级菜单 -->
          <el-sub-menu index="1">
            <!-- 一级菜单标题 -->
            <template #title>
              <span>Navigator One</span>
            </template>
              
            <!-- 二级菜单 -->
            <el-menu-item index="1-1">item one</el-menu-item>
            <el-menu-item index="1-2">item two</el-menu-item>
          </el-sub-menu>
            
          <!-- 一级菜单 -->
          <el-menu-item index="2">
            <span>Navigator Two</span>
          </el-menu-item>
        </el-menu>
      </div>
    </template>
    
  4. 添加样式:

    <style scoped lang="postcss">
    .menu-container{
      background-color: var(--el-menu-bg-color);
    
      .os-menu{
        @apply border-0;
    
        &.el-menu--horizontal{
          height: calc(var(--os-layout-logo-height) - 1px);
        }
      }
    }
    </style>
    

封装 Breadcrumb 面包屑组件

  1. 创建 src/layouts/components/Breadcrumb/index.vue

  2. 使用 ElementPlus el-breadcrumb 面包屑组件:

    <template>
      <el-breadcrumb separator="/" class="breadcrumb-container">
        <el-breadcrumb-item :to="{ path: '/' }">homepage</el-breadcrumb-item>
        <el-breadcrumb-item>management</el-breadcrumb-item>
        <el-breadcrumb-item>list</el-breadcrumb-item>
        <el-breadcrumb-item>detail</el-breadcrumb-item>
      </el-breadcrumb>
    </template>
    
  3. 添加样式:

    <style scoped lang="postcss">
    .breadcrumb-container {
      display: flex;
      flex-wrap: nowrap; /* 防止换行 */
      overflow: hidden; /* 横向滚动溢出处理 */
      padding-right: 12px;
    }
    </style>
    

封装 TabBar 标签栏组件

  1. 创建 src/layouts/components/TabBar/index.vue

  2. 生成 TabBar 标签数据:

    <script setup lang="ts">
    const tabs = Array(1).fill({
      label: 'About',
      name: 'about',
      icon: 'About',
      path: '/about'
    })
    </script>
    
  3. 使用 ElementPlus el-tabs 标签组件,Scrollbar 组件替换浏览器原生滚动条:

    <template>
      <!-- Tab Bar -->
      <div class="tab-bar-container">
        <div class="tab-bar-box">
          <el-scrollbar>
            <el-tabs
              tab-position="top"
              type="card"
              closable
            >
              <el-tab-pane
                v-for="(item, index) in tabs"
                :key="index"
                :name="item.name"
              >
                <template #label>
                    <span class="tab-title">
                      <svg-icon :name="item.icon" />
                      <span>{{ item.label }}</span>
                    </span>
                </template>
              </el-tab-pane>
            </el-tabs>
          </el-scrollbar>
        </div>
        <div class="more-button">
          <el-dropdown trigger="click">
            <span>
              <svg-icon name="More" />
            </span>
            <template #dropdown>
              <el-dropdown-menu>
                <el-dropdown-item>关闭</el-dropdown-item>
              </el-dropdown-menu>
            </template>
          </el-dropdown>
        </div>
      </div>
    </template>
    
  4. 添加样式:

    <style scoped lang="scss">
    .tab-bar-container {
      display: flex;
      align-items: center;
      gap: 8px;
      border-bottom: 1px solid var(--el-border-color-light);
    
      .tab-bar-box {
        display: flex;
        flex-grow: 1;
        margin-left: 8px;
        width: calc(100% - 60px);
    
        .tab-title{
          display: flex;
          align-items: center;
          gap: 8px;
        }
      }
    
      .more-button {
        display: flex;
        align-items: center;
        justify-content: center;
        width: var(--os-layout-tab-height);
        height: var(--os-layout-tab-height);
        border-left: 1px solid var(--el-border-color-light);
    
      }
    }
    
    :deep(.el-tabs) {
      .el-tabs__header {
        height: var(--os-layout-tab-height);
        padding: 0;
        margin: 0;
        border-bottom: none;
    
        .el-tabs__nav-wrap {
          .el-tabs__nav-scroll {
            .el-tabs__nav {
              border: none;
    
              .el-tabs__item {
                height: calc(var(--os-layout-tab-height) - 2px);
                line-height: calc(var(--os-layout-tab-height) - 2px);
                border: none;
                position: relative; // 确保父元素是相对定位的
    
                &::before {
                  content: '';
                  position: absolute;
                  bottom: 0;
                  width: 100%;
                  height: 2px;
                  background-color: transparent;
                }
    
                &.is-active {
                  color: var(--el-color-primary);
                  fill: var(--el-color-primary);
    
                  &::before {
                    background-color: var(--el-color-primary); // 改变背景色以显示下划线
                  }
                }
              }
            }
          }
        }
      }
    }
    </style>
    

封装 Main 内容区组件

  1. 创建 src/layouts/components/Main/index.vue

    <script setup lang="ts">
    
    </script>
    
    <template>
      <div>Main</div>
    </template>
    
    <style scoped lang="scss">
    
    </style>
    
  2. 后续补充内容…

封装 Footer 底部组件

  1. 创建 src/layouts/components/Footer/index.vue

  2. 通过import.meta.env获取.env文件中定义的环境变量

    <script setup lang="ts">
    const app_title = import.meta.env.VITE_APP_TITLE
    </script>
    
  3. 添加内容:

    <template>
      <div class="footer-container">
        <!-- 左侧:公司名称或标志 -->
        <div class="flex-content">
          <svg-icon name="logo" />
          <span class="logo-title">{{ app_title }}</span>
        </div>
        <!-- 中间:导航链接 -->
        <div class="flex-content">
          <a href="about">关于我们</a>
          <a href="#contact">联系我们</a>
          <a href="#privacy-policy">隐私政策</a>
          <a href="#terms-of-service">服务条款</a>
        </div>
        <!-- 右侧:版权信息 -->
        <div class="text-right">&copy; 2024 Octopus. 保留所有权利.</div>
      </div>
    </template>
    
  4. 添加样式:

    <style scoped lang="postcss">
    .footer-container{
      @apply flex items-center w-full;
      @apply select-none text-xs;
      height: var(--os-layout-footer-height);
      @apply justify-center lg:justify-between;
    
      .flex-content{
        @apply items-center gap-2;
        @apply flex justify-center gap-1 text-gray-400;
        @apply hidden lg:flex;
    
        .logo-title {
          @apply font-bold subpixel-antialiased text-sm;
          @apply bg-clip-text text-transparent bg-gradient-to-r from-pink-500 to-violet-500;
        }
      }
    }
    </style>
    

封装 Theme 主题组件

  1. 创建 src/stores/modules/theme/types.ts 定义主题类型:

    export type LayoutMode = 'classic' | 'horizontal' | 'responsive'
    // 主题配置
    export interface ThemeConfig {
      showThemeConfig: boolean,
      darkThemeEnabled: boolean,
      grayThemeEnabled: boolean,
      layoutMode: LayoutMode,
      themePrimaryColor: string,
      predefineColors: string[]
    }
    
  2. 创建 src/stores/modules/theme/index.ts 仓库存储主题状态:

    import { defineStore } from 'pinia'
    import { reactive, watch } from 'vue'
    import { ThemeConfig } from './types'
    import { ElMessage } from 'element-plus'
    import { getDarkColor, getLightColor } from '@/utils/Color'
    
    // theme store
    export const useThemeStore = defineStore(
      'theme',
      () => {
        const themeConfig = reactive<ThemeConfig>({
          showThemeConfig: false,
          darkThemeEnabled: false,
          grayThemeEnabled: false,
          layoutMode: 'classic',
          themePrimaryColor: '#409eff',
          predefineColors: ['#409eff']
        })
    
        // 切换显示状态的方法
        const toggleThemeConfig = () => {
          themeConfig.showThemeConfig = !themeConfig.showThemeConfig
        }
    
        // 黑暗主题切换
        watch(() => themeConfig.darkThemeEnabled, () => {
          const html = document.documentElement as HTMLElement
          if (themeConfig.darkThemeEnabled) {
            html.setAttribute('class', 'dark')
          } else {
            html.removeAttribute('class')
          }
          changeThemeColor(themeConfig.themePrimaryColor)
        })
    
        watch(() => themeConfig.grayThemeEnabled,() => {
          if (themeConfig.grayThemeEnabled) {
            document.documentElement.dataset.theme = 'gray'
          } else {
            document.documentElement.dataset.theme = ''
          }
        })
    
        // 添加预定义颜色
        function addPredefineColor(color:string) {
          console.log('color', color)
          const predefineColors = themeConfig.predefineColors
          // 查找元素的索引
          const index = predefineColors.indexOf(color)
    
          if (index !== -1) {
            // 使用 splice 方法删除该元素
            predefineColors.splice(index, 1)
          }
          if (themeConfig.predefineColors.length === 10) {
            themeConfig.predefineColors.pop() // 删除队列中的第一个元素
          }
          themeConfig.predefineColors.unshift(color)
        }
    
        // 修改主题颜色
        const changeThemeColor = (color:string ) => {
          if (!color) {
            color = '#409eff'
            ElMessage({ type: 'success', message: '主题颜色已重置' })
          }
          addPredefineColor(color)
          // 计算主题颜色变化
          document.documentElement.style.setProperty('--el-color-primary', color)
          document.documentElement.style.setProperty(
            '--el-color-primary-dark-2',
            themeConfig.darkThemeEnabled ? `${getLightColor(color, 0.2)}` : `${getDarkColor(color, 0.3)}`
          )
          for (let i = 1; i <= 9; i++) {
            const primaryColor = themeConfig.darkThemeEnabled ? `${getDarkColor(color, i / 10)}` : `${getLightColor(color, i / 10)}`
            document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, primaryColor)
          }
        }
    
        return {
          themeConfig,
          toggleThemeConfig,
          changeThemeColor
        }
      },
      {
        persist: true
      })
    
  3. 创建 src/layouts/components/Theme/index.vue 主题组件并引入主题仓库:

    <script setup lang="ts">
    import { useThemeStore } from '@/stores/modules/theme'
    import { onMounted } from 'vue'
    
    const { themeConfig, changeThemeColor } = useThemeStore()
    
    onMounted(() => {
      // 在组件挂载到 DOM 后更新主题颜色
      changeThemeColor(themeConfig.themePrimaryColor)
    })
    </script>
    
  4. 使用 el-drawer 抽屉组件进行主题布局:

    <template>
      <div>
        <el-drawer v-model="themeConfig.showThemeConfig" size="300">
          <template #header="{ titleId }">
            <span :id="titleId" class="theme-header"> 主题配置 </span>
          </template>
    
          <el-scrollbar>
            <div class="mr-4">
              <el-divider>布局样式</el-divider>
              <div class="layout-box">
                <el-tooltip effect="dark" content="经典布局" placement="top" :show-after="200">
                  <div
                    :class="['layout-item layout-classic', { 'is-active': themeConfig.layoutMode == 'classic' }]"
                    @click="themeConfig.layoutMode='classic'"
                  >
                    <div class="layout-dark"></div>
                    <div class="layout-container">
                      <div class="layout-light"></div>
                      <div class="layout-content"></div>
                    </div>
                    <svg-icon class="select-layout" name="About" v-if="themeConfig.layoutMode == 'classic'" />
                  </div>
                </el-tooltip>
                <el-tooltip
                  effect="dark"
                  content="横向布局"
                  placement="top"
                  :show-after="200"
                >
                  <div
                    :class="['layout-item layout-transverse', { 'is-active': themeConfig.layoutMode == 'horizontal' }]"
                    @click="themeConfig.layoutMode='horizontal'"
                  >
                    <div class="layout-dark"></div>
                    <div class="layout-content"></div>
                    <svg-icon class="select-layout" name="About" v-if=" themeConfig.layoutMode == 'horizontal'" />
                  </div>
                </el-tooltip>
              </div>
    
              <el-divider>主题风格</el-divider>
              <div class="switch-container">
                <div class="switch-box">
                  <div class="switch-title">
                    <span>主题颜色</span>
                    <el-tooltip content="主题颜色" placement="top">
                      <svg-icon class="size-4" name="About"></svg-icon>
                    </el-tooltip>
                  </div>
                  <el-color-picker
                    color-format="hex"
                    v-model="themeConfig.themePrimaryColor"
                    :predefine="themeConfig.predefineColors"
                    @change="changeThemeColor"
                  />
                </div>
                <div class="switch-box">
                  <div class="switch-title">
                    <span>暗黑主题</span>
                    <el-tooltip content="暗黑主题" placement="top">
                      <svg-icon class="size-4" name="About"></svg-icon>
                    </el-tooltip>
                  </div>
                  <el-switch v-model="themeConfig.darkThemeEnabled" />
                </div>
                <div class="switch-box">
                  <div class="switch-title">
                    <span>灰色模式</span>
                    <el-tooltip content="灰色模式" placement="top">
                      <svg-icon class="size-4" name="About"></svg-icon>
                    </el-tooltip>
                  </div>
                  <el-switch v-model="themeConfig.grayThemeEnabled" />
                </div>
              </div>
    
              <el-divider>界面设置</el-divider>
              <div class="switch-container">
                <div class="switch-box">
                  <div class="switch-title">
                    <span>theme</span>
                  </div>
                  <el-switch inline-prompt />
                </div>
              </div>
            </div>
          </el-scrollbar>
        </el-drawer>
      </div>
    </template>
    
  5. 为组件添加样式:

    <style scoped lang="postcss">
    .theme-header {
      @apply text-base font-bold flex items-center;
    }
    
    .switch-container {
      @apply flex-grow space-y-2;
    }
    
    .switch-box {
      @apply flex justify-between items-center w-full;
    }
    
    .switch-title {
      @apply flex gap-2 items-center;
    }
    
    :deep(.el-drawer__header) {
      @apply px-5 py-0 h-12 border-b-2 border-solid m-0;
      color: var(--el-text-color-regular);
      border-color: var(--el-border-color-light);
    }
    
    :deep(.el-drawer__title) {
      font-size: 20px;
    }
    
    :deep(.el-drawer__body) {
      @apply py-0 pr-0;
    }
    
    .layout-box {
      position: relative;
      display: flex;
      flex-wrap: wrap;
      justify-content: space-between;
      padding: 15px 15px 0;
    
      .layout-item {
        position: relative;
        box-sizing: border-box;
        width: 100px;
        height: 70px;
        padding: 6px;
        cursor: pointer;
        border-radius: 5px;
        box-shadow: 0 0 5px 1px var(--el-border-color-dark);
        transition: all 0.2s;
    
        .layout-dark {
          background-color: var(--el-color-primary);
          border-radius: 3px;
        }
    
        .layout-light {
          background-color: var(--el-color-primary-light-5);
          border-radius: 3px;
        }
    
        .layout-content {
          background-color: var(--el-color-primary-light-8);
          border: 1px dashed var(--el-color-primary);
          border-radius: 3px;
        }
    
        .select-layout {
          position: absolute;
          right: 10px;
          bottom: 10px;
          fill: var(--el-color-primary);
          transition: all 0.2s;
        }
    
        &:hover {
          box-shadow: 0 0 5px 1px var(--el-text-color-secondary);
        }
      }
    
      .is-active {
        box-shadow: 0 0 0 2px var(--el-color-primary) !important;
      }
    
      .layout-classic {
        display: flex;
        justify-content: space-between;
        margin-bottom: 20px;
    
        .layout-dark {
          width: 20%;
        }
    
        .layout-container {
          display: flex;
          flex-direction: column;
          justify-content: space-between;
          width: 72%;
    
          .layout-light {
            height: 20%;
          }
    
          .layout-content {
            height: 67%;
          }
        }
      }
    
      .layout-transverse {
        display: flex;
        flex-direction: column;
        justify-content: space-between;
        margin-bottom: 15px;
    
        .layout-dark {
          height: 20%;
        }
    
        .layout-content {
          height: 67%;
        }
      }
    }
    </style>
    

经典布局

  1. 创建经典页面布局组件:src/layouts/LayoutClassic/index.vue

  2. 导入布局组件:

    <script setup lang="ts">
    import Footer from '@/layouts/components/Footer/index.vue'; // 引入页脚组件
    import Menu from '@/layouts/components/Menu/index.vue'; // 引入菜单组件
    import Logo from '@/layouts/components/Logo/index.vue'; // 引入Logo组件
    import Main from '@/layouts/components/Main/index.vue'; // 引入主要内容组件
    import TabBar from '@/layouts/components/TabBar/index.vue'; // 引入标签栏组件
    import Breadcrumb from '@/layouts/components/Breadcrumb/index.vue'; // 引入面包屑导航组件
    
    import { useThemeStore } from '@/stores/modules/theme' // 引入主题仓库
    const { toggleThemeConfig } = useThemeStore()
    
  3. 使用 ElementPlus 提供的 Container 布局容器快速搭建页面的基本结构:

  4. 在组件中使用布局组件划分出页面结构:侧边菜单栏、顶部、Tab 标签栏、主体和底部:

    <el-container>:外层容器。 当子元素中包含 <el-header><el-footer> 时,全部子元素会垂直上下排列, 否则会水平左右排列。

    <el-header>:顶栏容器。

    <el-aside>:侧边栏容器。

    <el-main>:主要区域容器。

    <el-footer>:底栏容器。

    <template>
      <div class="layout-container">
        <el-container>
          <!-- 左侧边栏 -->
          <el-aside>
            <!-- Logo 组件 -->
            <Logo class="os-logo"/>
            <!-- 菜单组件 -->
            <Menu class="menu-classic" />
          </el-aside>
    
          <el-container>
            <!-- 页面顶部区域 -->
            <el-header>
              <!-- 头部内容区域 -->
              <div class="header-content">
                <!-- 折叠按钮 -->
                <svg-icon class="fold-expand-button" name="Fold" />
                <!-- 面包屑导航 -->
                <Breadcrumb />
              </div>
    
              <!-- 头部菜单区域 -->
              <div class="header-menu">
                <!-- 语言切换下拉菜单 -->
                <el-dropdown :hide-on-click="false">
                  <svg-icon name="Earth" />
                  <template #dropdown>
                    <el-dropdown-menu>
                      <el-dropdown-item>简体中文</el-dropdown-item>
                    </el-dropdown-menu>
                  </template>
                </el-dropdown>
    
                <!-- 搜索按钮 -->
                <svg-icon class="cursor-pointer" name="Search" />
    
                <!-- 主题配置按钮 -->
                <el-tooltip content="主题配置" placement="bottom">
                  <svg-icon class="cursor-pointer" name="Theme" @click="toggleThemeConfig" />
                </el-tooltip>
    
                <!-- 锁屏按钮 -->
                <el-tooltip content="锁屏" placement="bottom">
                  <svg-icon class="cursor-pointer" name="Lock" />
                </el-tooltip>
    
                <!-- 全屏按钮 -->
                <el-tooltip content="全屏" placement="bottom">
                  <svg-icon class="cursor-pointer" name="FullScreen" />
                </el-tooltip>
    
                <!-- 用户信息下拉菜单 -->
                <el-dropdown>
                  <div class="flex items-center gap-1">
                    <el-tag type="primary">Admin</el-tag>
                    <el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png" />
                  </div>
                  <template #dropdown>
                    <el-dropdown-menu>
                      <el-dropdown-item command="profile">
                        <template #default>
                          <div class="flex items-center gap-1">
                            <svg-icon name="User" />
                            <span>个人中心</span>
                          </div>
                        </template>
                      </el-dropdown-item>
                      <el-dropdown-item command="logout">
                        <template #default>
                          <div class="flex items-center gap-1">
                            <svg-icon name="Logout" />
                            <span>退出登录</span>
                          </div>
                        </template>
                      </el-dropdown-item>
                    </el-dropdown-menu>
                  </template>
                </el-dropdown>
              </div>
    
            </el-header>
    
            <!-- 标签栏 -->
            <tab-bar class="tab-container" />
    
            <!-- 主内容区域 -->
            <el-main>
              <Main />
            </el-main>
    
            <!-- 底部页脚 -->
            <el-footer>
              <Footer />
            </el-footer>
          </el-container>
        </el-container>
      </div>
    </template>
    
  5. 为组件添加样式:

    <style scoped lang="postcss">
    .layout-container {
      @apply w-screen h-screen;
    
      .el-aside {
        @apply h-full;
        @apply border-r border-solid overflow-hidden;
        border-color: var(--el-border-color-light);
        width: var(--os-layout-aside-width);
    
        .os-logo {
          @apply overflow-hidden pl-2;
        }
    
        .menu-classic {
          height: calc(100vh - var(--os-layout-logo-height));
        }
      }
    
      .el-container {
        @apply h-full w-full;
    
        .tab-container {
          height: var(--os-layout-tab-height);
        }
    
        .el-header {
          @apply flex items-center justify-between;
          @apply border-b border-solid overflow-hidden;
          border-color: var(--el-border-color-light);
          height: var(--os-layout-header-height);
    
          .header-content {
            @apply flex items-center gap-5;
    
            .fold-expand-button {
              @apply cursor-pointer;
              @apply size-6;
            }
          }
    
          .header-menu {
            @apply flex items-center justify-between gap-3;
          }
        }
    
        .el-main {
          @apply h-full p-3;
          background-color: var(--el-bg-color-page);
        }
    
        .el-footer {
          height: var(--os-layout-footer-height);
        }
      }
    }
    </style>
    

水平布局

  1. 创建经典页面布局组件:src/layouts/LayoutClassic/index.vue

  2. 导入布局组件:

    <script setup lang="ts">
    import Footer from '@/layouts/components/Footer/index.vue'; // 引入页脚组件
    import Menu from '@/layouts/components/Menu/index.vue'; // 引入菜单组件
    import Logo from '@/layouts/components/Logo/index.vue'; // 引入Logo组件
    import Main from '@/layouts/components/Main/index.vue'; // 引入主要内容组件
    import TabBar from '@/layouts/components/TabBar/index.vue'; // 引入标签栏组件
    
    import { useThemeStore } from '@/stores/modules/theme' // 引入主题仓库
    const { toggleThemeConfig } = useThemeStore()
    </script>
    
  3. 在组件中使用 Element UI 的布局组件划分出页面结构:顶部、Tab 标签栏、主体和底部:

    <template>
      <div class="layout-container">
        <el-container>
          <el-container>
            <el-header>
              <div class="flex">
                <Logo class="os-logo" />
                <Menu mode="horizontal" class="menu-horizontal " />
              </div>
              <div class="header-menu">
                <!-- 语言转换 -->
                <el-dropdown :hide-on-click="false">
                  <svg-icon name="Earth" />
                  <template #dropdown>
                    <el-dropdown-menu>
                      <el-dropdown-item>简体中文</el-dropdown-item>
                    </el-dropdown-menu>
                  </template>
                </el-dropdown>
    
                <!-- 搜索按钮 -->
                <svg-icon class="cursor-pointer" name="Search" />
    
                <!-- 皮肤按钮 -->
                <el-tooltip content="主题配置" placement="bottom">
                  <svg-icon class="cursor-pointer" name="Theme" @click="toggleThemeConfig"/>
                </el-tooltip>
    
                <!-- 锁屏按钮 -->
                <el-tooltip content="锁屏" placement="bottom">
                  <svg-icon class="cursor-pointer" name="Lock" />
                </el-tooltip>
    
                <!-- 全屏按钮 -->
                <el-tooltip content="全屏" placement="bottom">
                  <svg-icon class="cursor-pointer" name="FullScreen" />
                </el-tooltip>
    
                <!-- 用户信息 -->
                <el-dropdown>
                  <div class="flex items-center gap-1">
                    <el-tag type="primary">Admin</el-tag>
                    <el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png" />
                  </div>
                  <template #dropdown>
                    <el-dropdown-menu>
                      <el-dropdown-item command="profile">
                        <template #default>
                          <div class="flex items-center gap-1">
                            <svg-icon name="User" />
                            <span>个人中心</span>
                          </div>
                        </template>
                      </el-dropdown-item>
                      <el-dropdown-item command="logout">
                        <template #default>
                          <div class="flex items-center gap-1">
                            <svg-icon name="Logout" />
                            <span>退出登录</span>
                          </div>
                        </template>
                      </el-dropdown-item>
                    </el-dropdown-menu>
                  </template>
                </el-dropdown>
              </div>
            </el-header>
            <tab-bar class="tab-container" />
            <el-main>
              <Main />
            </el-main>
            <el-footer>
              <Footer />
            </el-footer>
          </el-container>
        </el-container>
      </div>
    </template>
    
  4. 添加样式:

    <style scoped lang="postcss">
    .layout-container {
      @apply w-screen h-screen;
    
      .el-aside {
        @apply bg-amber-300 h-full;
        width: var(--os-layout-aside-width);
      }
    
      .el-container {
        @apply h-full w-full;
    
        .tab-container {
          height: var(--os-layout-tab-height);
        }
    
        .el-header {
          @apply w-full pl-2 pr-2 flex justify-between;
          height: var(--os-layout-header-height);
          @apply border-b border-solid overflow-hidden;
          border-color: var(--el-border-color-light);
    
          .menu-horizontal{
            width: calc(100vw - var(--os-layout-aside-width) - var(--os-layout-header-menu-width));
          }
    
          .header-menu{
            @apply flex items-center justify-between gap-3;
          }
        }
    
        .el-main {
          @apply h-full p-3;
          background-color:var(--el-bg-color-page);
        }
    
        .el-footer {
          height: var(--os-layout-footer-height);
        }
      }
    }
    </style>
    

响应式布局

  1. 创建src/layouts/LayoutResponsive/index.vue响应式布局组件

  2. 添加公共组件:

    <script setup lang="ts">
    import Logo from '@/layouts/components/Logo/index.vue'
    import Main from '@/layouts/components/Main/index.vue'
    import Menu from '@/layouts/components/Menu/index.vue'
    import { ref } from 'vue'
    
    import { useThemeStore } from '@/stores/modules/theme'
    const { themeConfig } = useThemeStore()
    
    const navDrawer = ref(false)
    const menuDrawer = ref(false)
    const translateShow = ref(false)
    </script>
    
  3. 页面布局:

    <template>
      <div class="layout-responsive">
        <el-container>
          <el-header class="flex items-center justify-between">
            <div class="h-full flex items-center gap-2">
              <svg-icon width="30px" height="30px" name="menu" @click="menuDrawer = !menuDrawer" />
              <Logo />
            </div>
            <div class="header-menu">
              <!-- 搜索按钮 -->
              <svg-icon width="28px" height="28px" class="cursor-pointer" name="Search" />
    
              <!-- 用户信息 -->
              <el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
                         @click="navDrawer = !navDrawer" />
    
              <!-- 导航信息 -->  
              <el-drawer v-model="navDrawer" size="100%">
                <template #header>
                  <Logo />
                </template>
                <div class="nav-container ">
                  <a href="#">个人中心</a>
                  <el-divider />
                  <div>
                      <span class="flex items-center justify-between" @click="translateShow = !translateShow">
                        翻译
                        <svg-icon name="Earth"></svg-icon>
                      </span>
                    
                    <!-- 语言列表 -->
                    <transition name="fade">
                      <div v-show="translateShow" id="language-list" class="language-list mt-2">
                        <ul>
                          <li class="p-2">英语</li>
                          <li class="p-2">中文</li>
                          <li class="p-2">西班牙语</li>
                        </ul>
                      </div>
                    </transition>
                  </div>
                  <el-divider />
                  <div class="nav-theme-switch">
                    <span>暗黑主题</span>
                    <el-switch v-model="themeConfig.darkThemeEnabled" size="large" />
                  </div>
                  <el-divider />
                  <span>退出登录</span>
                </div>
              </el-drawer>
            </div>
          </el-header>
          <el-main>
            <el-backtop target=".el-main" />
            <Main />
          </el-main>
        </el-container>
    
        <!-- 菜单组件 -->
        <el-drawer
          v-model="menuDrawer"
          direction="ltr"
          size="320px"
        >
          <Menu />
        </el-drawer>
      </div>
    </template>
    
  4. 添加样式:

    <style scoped lang="postcss">
    .layout-responsive {
      @apply w-screen h-screen;
    
      .el-container {
        @apply h-full w-full;
    
        .el-header {
          @apply w-full flex pl-2;
          height: var(--os-layout-header-height);
          @apply border-b border-solid overflow-hidden;
          border-color: var(--el-border-color-light);
    
          .header-menu {
            @apply flex items-center justify-between gap-3;
          }
        }
    
        .tab-container {
          background-color: var(--el-bg-color);
    
        }
    
        .el-main {
          @apply h-full;
          background-color: var(--el-bg-color-page);
        }
      }
    }
    
    :deep(.el-drawer__header) {
      @apply px-3 py-0 h-14 border-solid m-2;
      color: var(--el-text-color-regular);
      border-color: var(--el-border-color-light);
    }
    
    :deep(.el-drawer__title) {
      font-size: 20px;
    }
    
    :deep(.el-drawer__body) {
      @apply py-0 px-8;
    }
    
    :deep(.el-drawer__close-btn) {
      @apply text-3xl;
    }
    
    .nav-container {
      margin: 0 auto;
      padding: 24px 0 96px;
      max-width: 18rem;
    }
    
    .nav-theme-switch {
      @apply flex items-center justify-between rounded-md;
    }
    
    /* 添加动画效果 */
    .fade-enter-active {
      transition: opacity 0.5s ease;
    }
    
    .fade-leave-active {
      transition: opacity 0.2s ease;
    }
    
    .fade-enter-from, .fade-leave-to {
      opacity: 0;
    }
    
    .logo-title {
      @apply text-lg font-bold subpixel-antialiased;
      @apply bg-clip-text text-transparent bg-gradient-to-r from-pink-500 to-violet-500;
    }
    </style>
    

搭建 Layout 布局组件

  1. 创建 Layout 布局组件 src/layouts/index.vue

  2. 导入经典布局组件 LayoutClassic 和水平布局组件 LayoutHorizontal,创建一个 layoutMode 对象来将布局模式字符串映射到相应的组件,使用 computed 函数根据 mode 动态选择布局模式。

    <script setup lang="ts">
    import { type Component, computed, onMounted, onUnmounted, ref, watch } from 'vue'
    import LayoutClassic from '@/layouts/LayoutClassic/index.vue'
    import LayoutHorizontal from '@/layouts/LayoutHorizontal/index.vue'
    import LayoutResponsive from '@/layouts/LayoutResponsive/index.vue'
    import Theme from '@/layouts/components/Theme/index.vue'
    
    import { useThemeStore } from '@/stores/modules/theme'
    const { themeConfig } = useThemeStore()
    
    // 定义布局模式与对应的组件类型
    const layoutMode: Record<string, Component> = {
      classic: LayoutClassic,   // 经典布局
      horizontal: LayoutHorizontal,  // 横向布局
      responsive: LayoutResponsive  // 响应式布局
    }
    
    // 布局模式
    const layMode = ref(themeConfig.layoutMode)
    
    // 监听主题配置模式
    watch(() => themeConfig.layoutMode, (newMode) => {
      layMode.value = newMode
    })
    
    // 计算属性,根据具体逻辑返回所选布局的组件
    const mode = computed(() => layoutMode[layMode.value])
    
    // 响应式布局,切换布局模式
    const updateMode = () => {
      if (window.innerWidth <= 768) {
        layMode.value = 'responsive'
      } else {
        layMode.value = themeConfig.layoutMode
      }
    }
    
    onMounted(() => {
      // 初始更新
      updateMode()
      window.addEventListener('resize', updateMode)
    })
    
    onUnmounted(() => {
      window.removeEventListener('resize', updateMode)
    })
    </script>
    
    <template>
      <!-- 根据 mode 的值渲染不同的布局样式 -->
      <component :is="mode" />
      <Theme />
    </template>
    
    <style scoped lang="postcss">
    </style>
    

添加 Layout 路由配置

  1. src/App.vue 中添加代码,当访问路由路径时,Vue Router 会根据路由配置将对应的组件渲染到 <router-view> 中,实现单页应用的页面切换效果:

    <template>
      <router-view />
    </template>
    
  2. src/router/index.ts 中添加路由:

    const router = createRouter({
      routes: [
        {
          path: '/',
          name: 'layout',
          component: () => import('@/layouts/index.vue')
        }
      ]
    })
    

启动项目

  1. 终端执行命令启动项目:

    pnpm run dev
    
  2. 浏览器访问:http://localhost:8080/,默认为经典布局样式:

    image-20240805095808149

  3. src/layouts/index.vue 中切换为水平布局样式,查看页面:

    const mode = computed(() => layoutMode['horizontal'])
    

    image-20240805095901012

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/881736.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

连续数组问题

目录 一题目&#xff1a; 二思路&#xff1a; 三代码&#xff1a; 一题目&#xff1a; leetcode链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 二思路&#xff1a; 思路&#xff1a;前缀和&#xff08;第二种&#xff09;化0为-1hash&#xff1a; 这样可以把…

SQL server学习01-SQL server环境配置

目录 一&#xff0c;手动下载及安装 microsoft .net framework 3.5 1&#xff0c;下载 2&#xff0c;安装 二&#xff0c;安装SQL server2014 1&#xff0c;下载 2&#xff0c;安装 3&#xff0c;启动SQL server服务 三&#xff0c;下载及安装Microsoft SQL Server…

高效编程的利器 Jupyter Notebook

目录 前言1. Jupyter Notebook简介1.1 功能特点1.2 使用场景 2. 不同编程工具的对比与效率提升2.1 VS Code&#xff1a;灵活且轻量的代码编辑器2.2 PyCharm&#xff1a;面向专业开发者的集成开发环境2.3 Git&#xff1a;高效协作的版本控制工具2.4 Jupyter Notebook 和 VS Code…

【AI学习笔记】初学机器学习西瓜书概要记录(一)机器学习基础知识篇

初学机器学习西瓜书的概要记录&#xff08;一&#xff09;机器学习基础知识篇(已完结) 初学机器学习西瓜书的概要记录&#xff08;二&#xff09;常用的机器学习方法篇(持续更新) 初学机器学习西瓜书的概要记录&#xff08;三&#xff09;进阶知识篇(待更) 文字公式撰写不易&am…

【爱给网-注册安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

virtualbox中的网络模式,网络设置,固定IP

virtualbox关于网络设置的文档&#xff1a;https://www.virtualbox.org/manual/topics/networkingdetails.html#networkingdetails DHCP Dynamic Host Configuration Protocol&#xff1a;动态主机配置协议&#xff0c;是专门用来给网络中的节点分发IP地址&#xff0c;确保每…

用友U8二次开发工具KK-FULL-*****-EFWeb使用方法

1、安装: 下一步&#xff0c;下一步即可。弹出黑框不要关闭&#xff0c;让其自动执行并关闭。 2、服务配置&#xff1a; 输入服务器IP地址&#xff0c;选择U8数据源&#xff0c;输入U8用户名及账号&#xff0c;U8登录日期勾选系统日期。测试参数有效性&#xff0c;提示测试通过…

【Unity-UGUI组件拓展】| Image 组件拓展,支持FIlled和Slice功能并存

🎬【Unity-UGUI组件拓展】| Image 组件拓展,支持FIlled和Slice功能并存一、组件介绍二、组件拓展方法三、完整代码💯总结🎬 博客主页:https://xiaoy.blog.csdn.net 🎥 本文由 呆呆敲代码的小Y 原创,首发于 CSDN🙉 🎄 学习专栏推荐:Unity系统学习专栏 🌲 游戏…

esp32 wifi 联网后,用http 发送hello 用pc 浏览器查看网页

参考chatgpt Esp32可以配置为http服务器&#xff0c;可以socket编程。为了免除编写针对各种操作系统的app。完全可以用浏览器仿问esp32服务器&#xff0c;获取esp32的各种数据&#xff0c;甚至esp的音频&#xff0c;视频。也可以利用浏览器对esp进行各种操作。但esp不能主动仿…

golang学习笔记1-go程序执行流程

声明&#xff1a;本人已有C&#xff0c;C,Python基础&#xff0c;只写本人认为的重点&#xff0c;方便自己回顾。 命令行执行go程序有两种方式&#xff0c;其流程如下图 注意第一种方式会得到可执行文件&#xff0c;第二种不会。 例1 在当前目录下编译hello.go go build hel…

Matplotlib绘图基础

1、散点图 绘制散点图是数据可视化中非常常见的操作&#xff0c;它用于显示两组数据之间的关系。Matplotlib 提供了 plt.scatter() 函数&#xff0c;可以轻松绘制散点图。以下是一个基础的散点图示例代码&#xff0c;并包含了一些优化可视化呈现的技巧。 import matplotlib.p…

istio中如何使用serviceentry引入外部服务

假设需要引入一个外部服务&#xff0c;外部服务ip为10.10.102.90&#xff0c;端口为32033. 引入到istio中后&#xff0c;我想通过域名gindemo.test.ch:9090来访问这个服务。 serviceentry yaml内容如下&#xff1a; apiVersion: networking.istio.io/v1beta1 kind: ServiceEn…

53 语言模型(和之后用来训练语言模型的数据集)_by《李沐:动手学深度学习v2》pytorch版

系列文章目录 文章目录 系列文章目录理论部分使用计数来建模N元语法总结 代码读取长序列数据随机采样顺序分区 小结练习 理论部分 在上一部分中&#xff0c;我们了解了如何将文本数据映射为词元&#xff0c;以及将这些词元可以视为一系列离散的观测&#xff0c;例如单词或字符…

构建与优化自定义进程池

1. 什么是进程池&#xff1f; 简单来说&#xff0c;进程池就是预先创建固定数量的工作进程&#xff0c;通过设计任务队列或调度算法来分配任务给空闲的进程 —— 实现“负载均衡”。 2. 进程池框架设计 枚举错误返回值&#xff1a; enum {UsageError 1,ArgError,PipeError };…

基于51单片机的汽车倒车防撞报警器系统

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 本课题基于微控制器控制器&#xff0c; 设计一款汽车倒车防撞报警器系统。 要求&#xff1a; 要求&#xff1a;1.配有距离&#xff0c; 用于把车和障碍物之间的距离信号送入控制器。 2.配有报警系…

如何安装和注册 GitLab Runner

如何安装和注册 GitLab Runner GitLab Runner 是一个用于运行 GitLab CI/CD (Continuous Integration/Continuous Deployment) 作业。它是一个与 GitLab 配合使用的应用程序&#xff0c;可以在本地或云中运行。Runner 可以执行不同类型的作业&#xff0c;例如编译代码、运行测…

传统软件应用技术的价值转换率越来越低

为什么感觉到卷&#xff1f;可能的一个原因是大家都在进步&#xff0c;用户和竞争对手也在进步&#xff0c;而自己却没有进步&#xff0c;也谈不上思维模式的改变。 我们不谈理论、不谈理想、不谈市场环境不好&#xff0c;就谈与用户接触过程的案例&#xff0c;这是最有说服力的…

传输层协议(TCP和UDP)

目录 一、UDP 1、UDPAPI 2、UDPAPI的使用 二、TCP 1、TCPAPI 2、TCP的相关特性 2.1 确认应答 2.2 超时重传 2.3 连接管理&#xff08;三次握手&#xff0c;四次挥手&#xff09; 2.4 滑动窗口 2.5 流量控制 2.6 拥塞控制 2.7 延时应答 2.8 捎带应答 2.9 面向字节…

1.3 计算机网络的分类

欢迎大家订阅【计算机网络】学习专栏&#xff0c;开启你的计算机网络学习之旅&#xff01; 文章目录 前言一、按分布范围分类二、按传输技术分类三、按拓扑结构分类四、按使用者分类五、按传输介质分类 前言 计算机网络根据不同的标准可以被分为多种类型&#xff0c;本章从分布…

SqlSugar的where条件中使用可空类型报语法错误

SQLServer数据表中有两列可空列&#xff0c;均为数值类型&#xff0c;同时在数据库中录入测试数据&#xff0c;Age和Height列均部分有值。   使用SqlSugar的DbFirst功能生成数据库表类&#xff0c;其中Age、Height属性均为可空类型。   当Where函数中的检索条件较多时&a…