硅谷甄选三(layout)

一、组件的静态页面

1、组件的静态页面

//src\layout\index.vue
<template>
  <div class="layout_container">
    <!-- 左侧菜单 -->
    <div class="layout_slider"></div>
    <!-- 顶部导航 -->
    <div class="layout_tabbar"></div>
    <!-- 内容展示区域 -->
    <div class="layout_main">
      <p style="height: 1000000px"></p>
    </div>
  </div>
</template>

<script setup lang="ts"></script>

<style lang="scss" scoped>
.layout_container {
  width: 100%;
  height: 100vh;
  .layout_slider {
    width: $base-menu-width;
    height: 100vh;
    background: $base-menu-background;
  }
  .layout_tabbar {
    position: fixed;
    width: calc(100% - $base-menu-width);
    height: $base-tabbar-height;
    background: cyan;
    top: 0;
    left: $base-menu-width;
  }
  .layout_main {
    position: absolute;
    width: calc(100% - $base-menu-width);
    height: calc(100vh - $base-tabbar-height);
    background-color: yellowgreen;
    left: $base-menu-width;
    top: $base-tabbar-height;
    padding: 20px;
    overflow: auto;
  }
}
</style>

2、定义部分全局变量&滚动条

scss全局变量

//src\styles\variable.scss
//左侧菜单宽度
$base-menu-width :260px;
//左侧菜单背景颜色
$base-menu-background: #001529;

//顶部导航的高度
$base-tabbar-height:50px;

 滚动条

//src\styles\index.scss
//滚动条外观设置

::-webkit-scrollbar{
  width: 10px;
}

::-webkit-scrollbar-track{
  background: $base-menu-background;
}

::-webkit-scrollbar-thumb{
  width: 10px;
  background-color: yellowgreen;
  border-radius: 10px;
}

二、 Logo子组件的搭建

页面左上角的这部分,我们将它做成子组件,并且封装方便维护以及修改。

1、Logo子组件

在这里我们引用了封装好的setting

//src\layout\logo\index.vue
<template>
  <div class="logo" v-if="setting.logoHidden">
    <img :src="setting.logo" alt="" />
    <p>{{ setting.title }}</p>
  </div>
</template>

<script setup lang="ts">
  //引入设置标题与logo配置文件
  import setting from '@/setting'
</script>

<style lang="scss" scoped>
  .logo {
    width: 100%;
    height: $base-menu-logo-height;
    color: white;
    display: flex;
    align-items: center;
    padding: 20px;
    img {
      width: 40px;
      height: 40px;
    }
    p {
      font-size: $base-logo-title-fontSize;
      margin-left: 10px;
    }
  }
</style>

2 、封装setting

为了方便我们以后对logo以及标题的修改。

//src\setting.ts
//用于项目logo|标题配置
export default {
  title: '硅谷甄选运营平台', //项目的标题
  logo: '/public/logo.png', //项目logo设置
  logoHidden: true, //logo组件是否隐藏
}

3、 使用

在layout组件中引入并使用

<logo></logo>

//引入logo组件
import Logo from './logo/index.vue';

三、左侧菜单组件

1、静态页面(未封装)

主要使用到了element-plus的menu组件。附带使用了滚动组件

//src\layout\index.vue
<!-- 左侧菜单 -->
<div class="layout_slider">
  <Logo></Logo>
  <!-- 展示菜单 -->
  <!-- 滚动组件 -->
  <el-scrollbar class="scrollbar">
    <!-- 菜单组件 -->
    <el-menu background-color="#001529" text-color="white">
      <el-menu-item index="1">首页</el-menu-item>
      <el-menu-item index="2">数据大屏</el-menu-item>
      <!-- 折叠菜单 -->
      <el-sub-menu index="3">
        <template #title>
          <span>权限管理</span>
        </template>
        <el-menu-item index="3-1">用户管理</el-menu-item>
        <el-menu-item index="3-2">角色管理</el-menu-item>
        <el-menu-item index="3-3">菜单管理</el-menu-item>
      </el-sub-menu>
    </el-menu>
  </el-scrollbar>
</div>

 2、递归组件生成动态菜单

在这一部分,我们要根据路由生成左侧的菜单栏

1)将父组件中写好的子组件结构提取出去

//src\layout\index.vue
      <!-- 展示菜单 -->
      <!-- 滚动组件 -->
      <el-scrollbar class="scrollbar">
        <!-- 菜单组件 -->
        <el-menu background-color="#001529" text-color="white">
          <!-- 更具路由动态生成菜单 -->
          <Menu></Menu>
        </el-menu>
      </el-scrollbar>

2)动态菜单子组件:src\layout\menu\index.vue

3)处理路由

因为我们要根据路由以及其子路由作为我们菜单的一级|二级标题。因此我们要获取路由信息。

给路由中加入了路由元信息meta:它包含了2个属性:title以及hidden。

//src\router\routes.ts
{
  //登录路由
  path: '/login',
    component: () => import('@/views/login/index.vue'),
    name: 'login', //命名路由
    meta: {
    	title: '登录', //菜单标题
      hidden: true, //路由的标题在菜单中是否隐藏
      },
      }


//加入子路由
{
    //登录成功以后展示数据的路由
    path: '/',
    component: () => import('@/layout/index.vue'),
    name: 'layout',
    children: [
      {
        path: '/home',
        component: () => import('@/views/home/index.vue')
      }
    ]

  },

4)仓库引入路由并对路由信息类型声明(vue-router有对应函数)

//src\store\modules\user.ts
//引入路由(常量路由)
import { constantRoute } from '@/router/routes'
。。。。。
//小仓库存储数据地方
state: (): UserState => {
  return {
    token: GET_TOKEN(), //用户唯一标识token
    menuRoutes: constantRoute, //仓库存储生成菜单需要数组(路由)
}
//src/store/moudles/types/type.ts
import type { RouteRecordRaw } from "vue-router"
//定义小仓库数据state类型
export interface UserState {
    token: string | null,
    menuRoutes: RouteRecordRaw[]
  }

 5)父组件拿到仓库路由信息并传递给子组件

//src\layout\index.vue
<el-scrollbar class="scrollbar">
          <el-menu
            class="el-menu-vertical-demo"
            >
            <Menu :menuList="userStore.menuRoutes"></Menu>
          </el-menu>
        </el-scrollbar>
//获取用户小仓库
import useUserStore from '@/store/moudles/user';
let userStore = useUserStore();

6)子组件prps接收并且处理结构

//src/layout/menu/index.vue
<template>
    <template v-for="(item, index) in menuList" :key="item.path">
        <!-- 没有子路由 -->
        <template v-if="!item.children">
            <el-menu-item v-if="!item.meta.hidden" :index="item.path" @click="goRoute">
                <el-icon>
                  <component :is="item.meta.icon"></component>
                </el-icon>
                <template #title>
                <span>{{ item.meta.title }}</span>
                </template>
            </el-menu-item>
        </template>
        <!-- 有且只有一个子路由 -->
        <template v-if="item.children && item.children.length == 1">
        <el-menu-item
            :index="item.children[0].path"
            v-if="!item.children[0].meta.hidden"
            @click="goRoute"
        >
            <el-icon>
              <component :is="item.children[0].meta.icon"></component>
            </el-icon>
            <template #title>
              <span>{{ item.children[0].meta.title }}</span>
            </template>
        </el-menu-item>
        </template>
        <!-- 有子路由且个数大于一个 -->
        <el-sub-menu
        :index="item.path"
        v-if="item.children && item.children.length >= 2"
        >
        
        <template #title>
          <el-icon>
           <component :is="item.meta.icon"></component>
          </el-icon>
            <span>{{ item.meta.title }}</span>
        </template>
        <Menu :menuList="item.children"></Menu>
        </el-sub-menu>
    </template>
</template>

<script setup lang="ts">
import { useRouter } from 'vue-router';
//获取父组件传递过来的全部路由数组
defineProps(['menuList']);
//获取路由器对象
let $router = useRouter();
//点击菜单的回调
const goRoute = (vc: any) => {
    //路由跳转
    $router.push(vc.index);
}

</script>
<script lang="ts">
export default {
  name: 'Menu',
}
</script>
<style lang="scss" scoped></style>

注意:

1:因为每一个项我们要判断俩次(是否要隐藏,以及子组件个数),所以在el-menu-item外面又套了一层模板

2:当子路由个数大于等于一个时,并且或许子路由还有后代路由时。这里我们使用了递归组件。递归组件需要命名(另外使用一个script标签,vue2格式)。

3、菜单图标

1)注册图标组件

因为我们要根据路由配置对应的图标,也要为了后续方便更改。因此我们将所有的图标注册为全局组件。(使用之前将分页器以及矢量图注册全局组件的自定义插件)(所有图标全局注册的方法element-plus文档中已给出)

//src\components\index.ts
。。。。。。
//引入element-plus提供全部图标组件
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
。。。。。。

//对外暴露插件对象
export default {
  //必须叫做install方法
  //会接收我们的app
 。。。。。。
  //将element-plus提供全部图标注册为全局组件 
    for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
      app.component(key, component)
    }
  },
}

2)给路由元信息添加属性:icon

以laytou和其子组件为例首先在element-puls找到你要使用的图标的名字。将它添加到路由元信息的icon属性

//src\router\routes.ts
  {
    //登录成功以后展示数据的路由
    path: '/',
    component: () => import('@/layout/index.vue'),
    name: 'layout',
    meta: {
      title: 'layout',
      hidden: false,
      icon: 'Avatar',
    },
    children: [
      {
        path: '/home',
        component: () => import('@/views/home/index.vue'),
        meta: {
          title: '首页',
          hidden: false,
          icon: 'HomeFilled',
        },
      },
    ],
  },

3)菜单组件使用

以只有一个子路由的组件为例:

//src\layout\menu\index.vue
<!-- 有且只有一个子路由 -->
<template v-if="item.children && item.children.length == 1">
  <el-menu-item
    index="item.children[0].path"
    v-if="!item.children[0].meta.hidden"
    >
    <template #title>
      <el-icon>
        <component :is="item.children[0].meta.icon"></component>
      </el-icon>
      <span>{{ item.children[0].meta.title }}</span>
    </template>
  </el-menu-item>
</template>

4)项目全部路由配置 

全部路由配置(以权限管理为例)

//src/router/routers.ts
//全部路由配置(以权限管理为例)
{
  path: '/acl',
    component: () => import('@/layout/index.vue'),
    name: 'Acl',
    meta: {
    hidden: false,
      title: '权限管理',
      icon: 'Lock',
      },
  children: [
    {
      path: '/acl/user',
      component: () => import('@/views/acl/user/index.vue'),
      name: 'User',
      meta: {
        hidden: false,
        title: '用户管理',
        icon: 'User',
      },
    },
    {
      path: '/acl/role',
      component: () => import('@/views/acl/role/index.vue'),
      name: 'Role',
      meta: {
        hidden: false,
        title: '角色管理',
        icon: 'UserFilled',
      },
    },
    {
      path: '/acl/permission',
      component: () => import('@/views/acl/permission/index.vue'),
      name: 'Permission',
      meta: {
        hidden: false,
        title: '菜单管理',
        icon: 'Monitor',
      },
    },
  ],
    },

5)main组件 

//src/layout/main/index.vue
<template>
    <router-view></router-view>
</template>

5)动画 && 自动展示 

  • 将router-link封装成单独的文件并且添加一些动画
//src/layout/main/index.vue
<template>
  <!-- 路由组件出口的位置 -->
  <router-view v-slot="{ Component }">
    <transition name="fade">
      <!-- 渲染layout一级路由的子路由 -->
      <component :is="Component" />
    </transition>
  </router-view>
</template>

<script setup lang="ts"></script>

<style lang="scss" scoped>
  .fade-enter-from {
    opacity: 0;
  }
  .fade-enter-active {
    transition: all 0.3s;
  }
  .fade-enter-to {
    opacity: 1;
  }
</style>
  • 自动展示

当页面刷新时,菜单会自动收起。我们使用element-plus的default-active 处理。$router.path为当前路由。

//src\layout\index.vue
<el-menu
    class="el-menu-vertical-demo"
    :default-active = "$route.path"
    >
import { useRoute } from 'vue-router';
//获取路由器对象
let $route = useRoute();

 四、顶部tabbar组件

1、静态页面 

element-plus:breadcrumb el-button el-dropdown

//src/layout/tabbar/index.vue
<template>
  <div class="tabbar">
    <div class="tabbar_left">
      <!-- 顶部左侧的图标 -->
      <el-icon style="margin-right: 10px">
        <Expand></Expand>
      </el-icon>
      <!-- 左侧的面包屑 -->
      <el-breadcrumb separator-icon="ArrowRight">
        <el-breadcrumb-item>权限挂历</el-breadcrumb-item>
        <el-breadcrumb-item>用户管理</el-breadcrumb-item>
      </el-breadcrumb>
    </div>
    <div class="tabbar_right">
      <el-button size="small" icon="Refresh" circle></el-button>
      <el-button size="small" icon="FullScreen" circle></el-button>
      <el-button size="small" icon="Setting" circle></el-button>
      <img
        src="../../../public/logo.png"
        style="width: 24px; height: 24px; margin: 0px 10px"
      />
      <!-- 下拉菜单 -->
      <el-dropdown>
        <span class="el-dropdown-link">
          admin
          <el-icon class="el-icon--right">
            <arrow-down />
          </el-icon>
        </span>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item>退出登陆</el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </div>
  </div>
</template>

<script setup lang="ts"></script>

<style lang="scss" scoped>
.tabbar {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: space-between;
  background-image: linear-gradient(
    to right,
    rgb(236, 229, 229),
    rgb(151, 136, 136),
    rgb(240, 234, 234)
  );
  .tabbar_left {
    display: flex;
    align-items: center;
    margin-left: 20px;
  }
  .tabbar_right {
    display: flex;
    align-items: center;
  }
}
</style>

2、菜单折叠

1)折叠变量

定义一个折叠变量来判断现在的状态是否折叠。因为这个变量同时给breadcrumb组件以及祖先组件layout使用,因此将这个变量定义在pinia中。

//src/store/modules/setting.ts
//小仓库:layout组件相关配置仓库
import { defineStore } from 'pinia'

let useLayOutSettingStore = defineStore('SettingStore', {
  state: () => {
    return {
      fold: false, //用户控制菜单折叠还是收起的控制
    }
  },
})

export default useLayOutSettingStore

2)面包屑组件点击图标切换状态

//src/layout/tabbar/breadcrumb/index.vue
<template>
    <!-- 顶部左侧静态 -->
    <el-icon style="margin-right:10px" @click="changeIcon">
        <component :is="LayOutSettingStore.fold ? 'Fold' : 'Expand'"></component>
    </el-icon>
    <!-- 左侧面包屑 -->
    <el-breadcrumb separator-icon="ArrowRight">
        <!-- 面包动态展示路由名字与标题 -->
        <el-breadcrumb-item v-for="(item, index) in $route.matched" :key="index" v-show="item.meta.title" :to="item.path">
            <!-- 图标 -->
            <el-icon>
                <component :is="item.meta.icon"></component>
            </el-icon>
            <!-- 面包屑展示匹配路由的标题 -->
            <span>{{ item.meta.title }}</span>
        </el-breadcrumb-item>
    </el-breadcrumb>
</template>

<script setup lang="ts">
import { useRoute } from 'vue-router';
 import useLayOutSettingStore from '@/store/modules/setting';
 //获取layout配置相关的仓库
 let LayOutSettingStore = useLayOutSettingStore();
//获取路由对象
let $route = useRoute();
//点击图标的方法
const changeIcon = () => {
    //图标进行切换
    LayOutSettingStore.fold = !LayOutSettingStore.fold
}

</script>
<script lang="ts">
export default {
    name: "Breadcrumb"
}
</script>

<style scoped></style>

3)layout组件根据fold状态来修改个子组件的样式(以左侧菜单为例)

 

//src/layout/index.vue
<!-- 左侧菜单 -->
    <div class="layout_slider" :class="{ flod: LayOutSettingStore.fold ? true : false }">
      <Logo></Logo>
      <el-scrollbar class="scrollbar">
        <el-menu :default-active="$route.path" background-color="#001529" text-color="white"
          active-text-color="yellowgreen">
          <!--根据路由动态生成菜单-->
          <Menu :menuList="userStore.menuRoutes"></Menu>
        </el-menu>
      </el-scrollbar>
    </div>

 

 绑定动态样式修改scss

.layout_slider {
    width: $base-menu-width;
    height: 100vh;
    background: $base-menu-background;
    transition: all 0.3s;
    .scrollbar {
      width: 100%;
      height: calc(100vh - $base-menu-logo-height);
    }
    &.fold {
      width: $base-menu-min-width;
    }
  }

 4)左侧菜单使用element-plus折叠collapse属性

<el-menu :default-active="$route.path" 
        :collapse="LayOutSettingStore.fold"
        background-color="#001529" text-color="white"
          active-text-color="yellowgreen">
          <!--根据路由动态生成菜单-->
          <Menu :menuList="userStore.menuRoutes"></Menu>
        </el-menu>

效果图:

注意:折叠文字的时候会把图标也折叠起来。在menu组件中吧图标放到template外面就可以。

 3、顶部面包屑动态展示

//src/layout/tabbar/breadcrumb/index.vue
<template>
    <!-- 左侧面包屑 -->
    <el-breadcrumb separator-icon="ArrowRight">
        <!-- 面包动态展示路由名字与标题 -->
        <el-breadcrumb-item v-for="(item, index) in $route.matched" :key="index" v-show="item.meta.title" :to="item.path">
            <!-- 图标 -->
            <el-icon>
                <component :is="item.meta.icon"></component>
            </el-icon>
            <!-- 面包屑展示匹配路由的标题 -->
            <span>{{ item.meta.title }}</span>
        </el-breadcrumb-item>
    </el-breadcrumb>
</template>

<script setup lang="ts">
import { useRoute } from 'vue-router';
//获取路由对象
let $route = useRoute();

</script>
<script lang="ts">
export default {
    name: "Breadcrumb"
}
</script>

<style scoped></style>

 

 4、刷新业务的实现

1)使用pinia定义一个变量作为标记

//src/store/modules/setting.ts
//小仓库:layout组件相关配置仓库
import { Refresh } from '@element-plus/icons-vue'
import { defineStore } from 'pinia'

let useLayOutSettingStore = defineStore('SettingStore', {
  state: () => {
    return {
      fold: false, //用户控制菜单折叠还是收起的控制
      refsh: false//刷新
    }
  },
})

export default useLayOutSettingStore

 

2)点击刷新按钮,修改标记

//src/layout/tabbar/setting/index.vue
//获取骨架的小仓库
import useLayOutSettingStore from '@/store/modules/setting';
let layoutSettingStore = useLayOutSettingStore();
//刷新按钮点击回调
const updateRefsh = () => {
    layoutSettingStore.refsh = !layoutSettingStore.refsh;
};

3)main组件检测标记销毁&重加载组件(nextTick

//src/layout/main/index.vue
<template>
    <!-- 路由组件出口的位置 -->
    <router-view v-slot="{ Component }">
      <transition name="fade">
        <!-- 渲染layout一级路由的子路由 -->
        <component :is="Component" v-if="flag"/>
      </transition>
    </router-view>
  </template>
  
  <script setup lang="ts">
  import { watch, ref, nextTick } from 'vue'
  //使用layout的小仓库
  import useLayOutSettingStore from '@/store/modules/setting'
  let layOutSettingStore = useLayOutSettingStore()
  //控制当前组件是否销毁重建
  let flag = ref(true)
  //监听仓库内部的数据是否发生变化,如果发生变化,说明用户点击过刷新按钮
  watch(
    () => layOutSettingStore.refsh,
    () => {
      //点击刷新按钮:路由组件销毁
      flag.value = false
      nextTick(() => {
        flag.value = true
      })
    },
  )
  </script>

4、全屏模式的实现

(利用docment根节点的方法)

//src/layout/tabbar/setting/index.vue
//全屏按钮点击的回调
const fullScreen = () => {
    //DOM对象的一个属性:可以用来判断当前是不是全屏模式[全屏:true,不是全屏:false]
    let full = document.fullscreenElement;
    //切换为全屏模式
    if (!full) {
        //文档根节点的方法requestFullscreen,实现全屏模式
        document.documentElement.requestFullscreen();
    } else {
        //变为不是全屏模式->退出全屏模式
        document.exitFullscreen();
    }
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值