Vue3.0教程 (十一)layout 组件

layout 组件

一、 src/layout/index.vue

<template>
  <el-container>
    <sidebar />
    <el-main style="margin:0;padding:0;">
      <el-container class="main-container">
        <el-header class="bg-blue-600">头部</el-header>
        <el-main style="margin:0;padding:0;">
          <AppMain />
        </el-main>
        <el-footer class="bg-blue-800">底部</el-footer>
      </el-container>
    </el-main>
  </el-container>
</template>

<script setup>
import { Sidebar, AppMain } from './components'
</script>

二、创建动态组件

components/AppMain.vue

<template>
    <router-view v-slot="{ Component, route }">
      <transition name="fade-transform" mode="out-in">
        <keep-alive >
          <component :is="Component" :key="route.path" />
        </keep-alive>
      </transition>
    </router-view>
</template>
<script setup>
const route = useRoute()
</script>

<style lang="scss" scoped>
// .app-main {
//   /* 50= navbar  50  */
//   // min-height: calc(100vh - 230px);
//   width: 100%;
//   position: relative;
//   overflow: hidden;
//   height:calc( 100% - 52px );
// }
.app-main {
  flex: auto;
  min-height: 0;
  height: 100%;
}
</style>

三、router 使用动态组件

import { createWebHashHistory, createRouter } from 'vue-router'
import Layout from '@/layout'
// createWebHistory  相当于router3的 'history'  '/index'
// createWebHashHistory 相当于router3的 'hash'   '/#/index'
 
// 公共路由
export const constantRoutes = [
  {
    path: '/login',
    component: () => import('../views/login.vue'),
    hidden: true
  },
  {
    path: '',
    component: Layout,
    redirect: '/index',
    children: [
      {
        path: '/index',
        component: () => import('@/views/dashboard'),
        name: 'Index',
        meta: { title: '首页', icon: 'home', affix: true }
      }
    ]
  },
  {
    path: "/:pathMatch(.*)*",
    component: () => import('../views/error/404.vue'),
    hidden: true
  },
  {
    path: '/401',
    component: () => import('../views/error/401.vue'),
    hidden: true
  },
];
 
const router = createRouter({
  history: createWebHashHistory(),
  routes: constantRoutes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition
    } else {
      return { top: 0 }
    }
  },
});
 
export default router;

四、创建首页dashboard.vue

五、左侧菜单组件

1. Sidebar

// 需要创建  index.vue
<template>
  <el-aside :width="variables.sideBarWidth" >
    <el-menu default-active="2" class="el-menu-vertical-demo" :collapse="isCollapse" @open="handleOpen" @close="handleClose">
      <sidebar-item v-for="(route, index) in sidebarRouters" :key="route.path + index" :item="route" :base-path="route.path" />
    </el-menu>
  </el-aside>
</template>

<script setup>
import variables from '@/assets/styles/variables.module.scss' // scss 公共样式
import usePermissionStore from '@/store/modules/permission' 
import SidebarItem from './SidebarItem'

const permissionStore = usePermissionStore()
const sidebarRouters = computed(() => permissionStore.sidebarRouters);

const isCollapse = ref(false)
</script>
<style lang="scss" scoped>
</style>

2.SidebarItem 子组件

// 创建 SidebarItem.vue
<template>
  <div v-if="!item.hidden">
    <template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children)">
      <!-- 一级菜单 -->
      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
          <el-icon>
            <setting />
          </el-icon>
          <template #title>{{ onlyOneChild.meta.title }}</template>
        </el-menu-item>
      </app-link>
    </template>
    <!-- 二级菜单 -->
    <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
      <template #title>
        <el-icon>
          <location />
        </el-icon>
        <span>{{ item.meta.title }}</span>
      </template>
      <!-- 三级菜单 -->
      <sidebar-item v-for="child in item.children" :key="child.path" :is-nest="true" :item="child" :base-path="resolvePath(child.path)" class="nest-menu" />
    </el-sub-menu>
  </div>
</template>

<script setup>
import { isExternal, getNormalPath } from '@/utils/validate'
import AppLink from './Link'
const props = defineProps({
  // route object
  item: {
    type: Object,
    required: true
  },
  isNest: {
    type: Boolean,
    default: false
  },
  basePath: {
    type: String,
    default: ''
  }
})
console.log(props);
const onlyOneChild = ref({});

function hasOneShowingChild (children = [], parent) {
  if (!children) {
    children = [];
  }
  const showingChildren = children.filter(item => {
    if (item.hidden) {
      return false
    } else {
      // Temp set(will be used if only has one showing child)
      onlyOneChild.value = item
      return true
    }
  })

  // When there is only one child router, the child router is displayed by default
  if (showingChildren.length === 1) {
    return true
  }

  // Show parent if there are no child router to display
  if (showingChildren.length === 0) {
    onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
    return true
  }

  return false
};



function resolvePath (routePath, routeQuery) {
  if (isExternal(routePath)) {
    return routePath
  }
  if (isExternal(props.basePath)) {
    return props.basePath
  }
  if (routeQuery) {
    let query = JSON.parse(routeQuery);
    return { path: getNormalPath(props.basePath + '/' + routePath), query: query }
  }
  return getNormalPath(props.basePath + '/' + routePath)
}

</script>

<style lang="scss" scoped>
</style>

3.AppLink 组件(点击菜单直接跳转)

<template>
  <component :is="type" v-bind="linkProps()">
    <slot />
  </component>
</template>

<script setup>
import { isExternal } from '@/utils/validate'

const props = defineProps({
  to: {
    type: [String, Object],
    required: true
  }
})

const isExt = computed(() => {
  return isExternal(props.to)
})

const type = computed(() => {
  if (isExt.value) {
    return 'a'
  }
  return 'router-link'
})

function linkProps() {
  if (isExt.value) {
    return {
      href: props.to,
      target: '_blank',
      rel: 'noopener'
    }
  }
  return {
    to: props.to
  }
}
</script>

4.validate.js 引用的方法

/**
 * 判断url是否是http或https 
 * @param {string} path
 * @returns {Boolean}
 */
 export function isHttp(url) {
  return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1
}

/**
 * @param {string} path
 * @returns {Boolean}
 */
 export function isExternal(path) {
  return /^(https?:|mailto:|tel:)/.test(path)
}

// 返回项目路径
export function getNormalPath(p) {
  if (p.length === 0 || !p || p == 'undefined') {
    return p
  };
  let res = p.replace('//', '/')
  if (res[res.length - 1] === '/') {
    return res.slice(0, res.length - 1)
  }
  return res;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值