Vue3+Vite+Ts project combat 04 build Layout layout

Layout layout container

<!-- src\layout\AppLayout.vue -->
<template>
  <el-container>
    <el-aside width="200px">
      Aside
    </el-aside>
    <el-container>
      <el-header>Header</el-header>
      <el-main>
        <!-- 子路由出口 -->
        <router-view />
      </el-main>
    </el-container>
  </el-container>
</template>

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

<style scoped lang="scss">
.el-container {
      
      
  height: 100vh;
}
.el-header {
      
      
  background-color: #B3C0D1;
}
.el-aside {
      
      
  width: auto;
  background-color: #304156;
}
.el-main {
      
      
  background-color: #E9EEF3;
}
</style>

// src\styles\common.scss
* {
    
    
  margin: 0;
  padding: 0;
}

// src\router\index.ts
import {
    
     createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import AppLayout from '@/layout/AppLayout.vue'

const routes:RouteRecordRaw[] = [
  {
    
    
    path: '/',
    component: AppLayout,
    children: [
      {
    
    
        path: '/',
        name: 'home',
        component: () => import('@/views/home/index.vue')
      }
    ]
  },
  {
    
    
    path: '/login',
    name: 'login',
    component: () => import('@/views/login/index.vue')
  }
]

const router = createRouter({
    
    
  // history: createWebHashHistory(), // hash 路由模式
  history: createWebHistory(), // history 路由模式
  routes // 路由规则
})

export default router

Configure page routing navigation

Initialize routing directory

Create several other page files (may be added later):

└─ product # 商品相关
    ├─ attr # 商品规格
    │   └─ index.vue
    ├─ category # 商品分类
    │   └─ index.vue
    └─ list # 商品列表
        └─ index.vue

Configure routing:

// src\router\modules\products.ts
import {
    
     RouteRecordRaw, RouterView } from 'vue-router'

const routes:RouteRecordRaw = {
    
    
  path: 'product',
  component: RouterView,
  children: [
    {
    
    
      path: 'list',
      name: 'product_list',
      component: () => import('@/views/product/list/index.vue')
    },
    {
    
    
      path: 'category',
      name: 'product_category',
      component: () => import('@/views/product/category/index.vue')
    },
    {
    
    
      path: 'attr',
      name: 'product_attr',
      component: () => import('@/views/product/attr/index.vue')
    },
    {
    
    
      path: 'reply',
      name: 'product_reply',
      component: () => import('@/views/product/reply/index.vue')
    }
  ]
}

export default routes

// src\router\index.ts
import {
    
     createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import AppLayout from '@/layout/AppLayout.vue'
import productRoutes from './modules/product'

const routes:RouteRecordRaw[] = [
  {
    
    
    path: '/',
    component: AppLayout,
    children: [
      {
    
    
        path: '/',
        name: 'home',
        component: () => import('@/views/home/index.vue')
      },
      productRoutes
    ]
  },
  {
    
    
    path: '/login',
    name: 'login',
    component: () => import('@/views/login/index.vue')
  }
]

const router = createRouter({
    
    
  // history: createWebHashHistory(), // hash 路由模式
  history: createWebHistory(), // history 路由模式
  routes // 路由规则
})

export default router

menu navigation

Temporarily write several menu contents statically:

<!-- src\layout\components\AppMenu.vue -->
<template>
  <el-menu
    active-text-color="#ffd04b"
    background-color="#304156"
    class="el-menu-vertical-demo"
    default-active="2"
    text-color="#fff"
    router
  >
    <el-menu-item index="/">
      <!-- <Menu> 首字母要大写,否则会和浏览器原生的 <menu> 冲突 -->
      <el-icon><Menu /></el-icon>
      <span>首页</span>
    </el-menu-item>
    <el-sub-menu index="1">
      <template #title>
        <el-icon><location /></el-icon>
        <span>商品</span>
      </template>
      <el-menu-item index="/product/list">
        <el-icon><Menu /></el-icon>
        <span>商品列表</span>
      </el-menu-item>
      <el-menu-item index="/product/category">
        <el-icon><Menu /></el-icon>
        <span>商品分类</span>
      </el-menu-item>
      <el-menu-item index="/product/attr">
        <el-icon><Menu /></el-icon>
        <span>商品规格</span>
      </el-menu-item>
    </el-sub-menu>
  </el-menu>
</template>

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

<style scoped></style>

<!-- src\layout\AppLayout.vue -->
<template>
  <el-container>
    <el-aside width="200px">
      <AppMenu />
    </el-aside>
    <el-container>
      <el-header>Header</el-header>
      <el-main>
        <!-- 子路由出口 -->
        <router-view />
      </el-main>
    </el-container>
  </el-container>
</template>

<script setup lang="ts">
import AppMenu from './AppMenu/index.vue'
</script>

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

Toggle sidebar expand collapse

Store sidebar expanded state:

// src\store\index.ts
import {
    
     defineStore } from 'pinia'

const useStore = defineStore('main', {
    
    
  state: () => ({
    
    
    count: 0,
    isCollapse: false
  }),
  getters: {
    
    
    doubleCount(state) {
    
    
      return state.count * 2
    }
  },
  actions: {
    
    
    increment() {
    
    
      this.count++
    }
  }
})

export default useStore

Create a Header layout component and write a sidebar control button:

<!-- src\layout\AppHeader\index.vue -->
<template>
  <ToggleSidebar />

  <!-- 面包屑 -->
</template>

<script setup lang="ts">
import ToggleSidebar from './ToggleSidebar.vue'
</script>

<style scoped lang="scss" >
i {
      
      
  font-size: 19px;
  cursor: pointer;
}
</style>

<!-- src\layout\AppHeader\ToggleSidebar.vue -->
<template>
  <el-icon>
    <component
      :is="store.isCollapse ? 'expand' : 'fold'"
      @click="handleCollapse"
    />
  </el-icon>
</template>

<script setup lang="ts">
import useStore from '@/store'

const store = useStore()

// 因为没有其他地方可以修改侧边栏状态
// 所以这里直接修改
const handleCollapse = () => {
      
      
  store.isCollapse = !store.isCollapse
}
</script>

<style scoped></style>

Bind the sidebar status, load the Header component, and modify the el-header style:

<!-- src\layout\AppLayout.vue -->
<template>
  <el-container>
    <el-aside width="200px">
      <AppMenu />
    </el-aside>
    <el-container>
      <el-header>
        <AppHeader />
      </el-header>
      <el-main>
        <!-- 子路由出口 -->
        <router-view />
      </el-main>
    </el-container>
  </el-container>
</template>

<script setup lang="ts">
import AppMenu from './AppMenu/index.vue'
import AppHeader from './AppHeader/index.vue'
</script>

<style scoped lang="scss">
.el-container {
      
      
  height: 100vh;
}
.el-header {
      
      
  background-color: #fff;
  color: #333;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.el-aside {
      
      
  width: auto;
  background-color: #304156;
}
.el-main {
      
      
  background-color: #E9EEF3;
}
</style>

Breadcrumbs

Configure routing headers through routing meta information

// src\router\modules\products.ts
import {
    
     RouteRecordRaw, RouterView } from 'vue-router'

const routes:RouteRecordRaw = {
    
    
  path: 'product',
  component: RouterView,
  meta: {
    
    
    title: '商品'
  },
  children: [
    {
    
    
      path: 'list',
      name: 'product_list',
      component: () => import('@/views/product/list/index.vue'),
      meta: {
    
    
        title: '商品列表'
      }
    },
    {
    
    
      path: 'category',
      name: 'product_category',
      component: () => import('@/views/product/category/index.vue'),
      meta: {
    
    
        title: '商品分类'
      }
    },
    {
    
    
      path: 'attr',
      name: 'product_attr',
      component: () => import('@/views/product/attr/index.vue'),
      meta: {
    
    
        title: '商品规格'
      }
    }
  ]
}

export default routes

// src\router\index.ts
...

const routes:RouteRecordRaw[] = [
  {
    
    
    path: '/',
    component: AppLayout,
    children: [
      {
    
    
        path: '/',
        name: 'home',
        component: () => import('@/views/home/index.vue'),
        meta: {
    
    
          title: '首页'
        }
      },
      ...
    ]
  },
  ...
]

...

breadcrumb component

<!-- src\layout\AppHeader\Breadcrumb.vue -->
<template>
  <el-breadcrumb separator-icon="arrow-right">
    <el-breadcrumb-item
      v-for="item in routes"
      :key="item.path"
    >
      {
   
   { item.meta.title }}
    </el-breadcrumb-item>
  </el-breadcrumb>
</template>

<script setup lang="ts">
import {
      
       useRouter } from 'vue-router'
import {
      
       computed } from 'vue'

// 获取路由,类似 Vue2 的 this.$router
const router = useRouter()

// 获取当前路由的匹配记录
const routes = computed(() => {
      
      
  return router.currentRoute.value.matched.filter(item => item.meta.title)
})

</script>

<style scoped></style>

Load the breadcrumb component

<!-- src\layout\AppHeader\index.vue -->
<template>
  <el-space size="large">
    <ToggleSidebar />
    <Breadcrumb />
  </el-space>
</template>

<script setup lang="ts">
import ToggleSidebar from './ToggleSidebar.vue'
import Breadcrumb from './Breadcrumb.vue'
</script>

<style scoped lang="scss" >
i {
      
      
  font-size: 19px;
  cursor: pointer;
}
</style>

Configure routing meta-information TypeScript support , in order to facilitate the creation of custom-created type declaration files in src/typesthe directory:

// src\types\vue-router.d.ts
import 'vue-router'

declare module 'vue-router' {
    
    
  // eslint-disable-next-line no-unused-vars
  interface RouteMeta {
    
    
    title?: string
  }
}

other

The page title can be set using nuxt/vue-meta (next branch) .

full screen function

Fullscreen API - Web API interface reference | MDN

Create a fullscreen button component:

<!-- src\layout\AppHeader\FullScreen.vue -->
<template>
  <el-icon><full-screen @click="toggleFullScreen" /></el-icon>
</template>

<script setup lang="ts">
const toggleFullScreen = () => {
      
      
  if (!document.fullscreenElement) {
      
      
    document.documentElement.requestFullscreen()
  } else {
      
      
    if (document.exitFullscreen) {
      
      
      document.exitFullscreen()
    }
  }
}
</script>

<style scoped></style>

load component

<!-- src\layout\AppHeader\index.vue -->
<template>
  <el-space size="large">
    <ToggleSidebar />
    <Breadcrumb />
  </el-space>
  <el-space size="large">
    <FullScreen />
  </el-space>
</template>

<script setup lang="ts">
import ToggleSidebar from './ToggleSidebar.vue'
import Breadcrumb from './Breadcrumb.vue'
import FullScreen from './FullScreen.vue'
</script>

<style scoped lang="scss" >
i {
      
      
  font-size: 19px;
  cursor: pointer;
}
</style>

page loading progress bar

Use nprogress to achieve page loading progress bar effect.

npm i --save nprogress
# TS 类型补充模块
npm i --save-dev @types/nprogress
// src\router\index.ts
...
import nprogress from 'nprogress'
import 'nprogress/nprogress.css'

// 关闭 loading 图标
nprogress.configure({
    
     showSpinner: false })

...

router.beforeEach(() => {
    
    
  // 开始加载进度条
  nprogress.start()
})

router.afterEach(() => {
    
    
  // 结束加载进度条
  nprogress.done()
})

export default router

Note: Starting from Vue Router v4.x, it is not recommended to use it in the navigation guard to next()call the next navigation guard, instead use returnto control, return falsewill cancel the current navigation; return a routing address, it will jump to this route; the default route will call.

Guess you like

Origin blog.csdn.net/u012961419/article/details/124300171