从零到一:搭建Vue3后台管理系统

简介: 本文将在《从零到一:搭建一个Vue3开发框架》基础上搭建Vue3后台管理系统,所以如果您没有看过上篇文章或者想了解如何从零到一搭建Vue3开发框架,可以先看一下上篇文章。

网络异常,图片无法展示
|


本文将在《从零到一:搭建一个Vue3开发框架》基础上搭建Vue3后台管理系统,所以如果您没有看过上篇文章或者想了解如何从零到一搭建Vue3开发框架,可以先看一下上篇文章。


2022.3.13 更新

前端技术变化很快,当初写这篇文章时 ElementPlus 和 Pinia 还没成熟,但是时至今日,它们已经是后台管理系统组件库和状态管理库更好的选择,所以我又搭了一个新的开发框架,如果你想了解搭建开发框架的过程,本文依然是一个不错的选择,如果你需要一个开发框架,请来这里 vue3-element-admin2


因为之前的开发框架已经把所有需要用到的库都集成完成,所以在此基础上,我们需要做的就是把后台管理系统相关的主体框架及组件编写完成即可,《项目地址》 下面就让我们开始吧!


1. 路由权限控制


通常,如果用户未登录的情况下访问后台管理系统且目标页面不是登录页面,需要将用户路由指向登录页面进行登录,这一步可以通过vue-router的路由前置守卫实现,代码如下:


// 路由前置守卫
router.beforeEach((to) => {
  // 如果to需要鉴权
  if (!to.meta.notNeedAuth) {
    // 获取userInfo
    const userInfo = store.getters.userInfo;
    // 如果未登录
    if (!userInfo.name || !userInfo.roles.length) {
      return { name: "Login" };
    }
  }
})
复制代码


这里我们用到了 store.getters.userInfo.userId 判断用户是否登录,实际项目中,大家可以根据实际项目需求进行该判断,比如判断 cookie,seesion 等。 并且在登录成功后,在 src/store/modules/permission.ts 中进行了动态路由的添加,模拟登录成功后,动态添加该用户的权限路由。


2. 封装页面主体框架


后台管理系统的整理内容框架一般如下图所示:


网络异常,图片无法展示
|


左侧为 logo 及菜单区,右侧顶部为控制菜单展开收起的按钮,当前页面的面包屑导航,用户头像,点击下拉一般会有用户信息,修改密码,退出登录等快捷入口及操作,以及当前已打开页面的页签列表。


在系统内点击左侧菜单切换页面的时候,页面主体框架是不变的,只有中间内容区切换展示对应业务页面,很自然的想到这一部分可以抽离出一个组件进行维护,业务页面通过 router-view 标签嵌套在主体框架中。


在项目中,以上功能可以通过路由嵌套实现,调整后的首页路由如下:


{
    path: "/",
    name: "Root",
    component: Layout,
    redirect: "/home",
    children: [
      {
        path: "home",
        name: "Home",
        meta: {
          title: "首页",
          icon: "el-icon-s-home",
          needCache: true,
          fixed: true,
        },
        component: () => import("@/views/Home.vue"),
      },
    ],
  }
复制代码


上面的 Layout 即为封装好的主体框架组件,Home.vue 代码如下:


<template>
  <div class="box t_center">
    <img src="@/assets/img/avatar.jpg" alt="" />
    <HelloWorld
      msg="Vite2 + Vue3 + Element3 + TS + SASS + jest + cypress buy wzy!"
    />
  </div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import HelloWorld from "@/components/HelloWorld.vue";
export default defineComponent({
  name: "Home",
  components: {
    HelloWorld,
  },
  setup() {
    return {};
  },
});
</script>
<style lang="scss" scoped>
.box {
  padding-top: 100px;
  > img {
    width: 100px;
    height: auto;
  }
}
</style>
复制代码


HelloWorld.vue


<template>
  <h1 class="c_3477F2">{{ msg }}</h1>
  <p>{{ count }}</p>
  <el-button class="plus" type="primary" @click="count++"> plus </el-button>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
  name: "HelloWorld",
  props: {
    msg: {
      type: String,
      required: true,
    },
  },
  setup() {
    const count = ref(0);
    return { count };
  },
});
</script>
<style lang="scss" scoped>
p {
  padding: 20px 0;
  font-size: 20px;
  color: red;
}
</style>
复制代码


此时打开首页,即可看到如上图的首页内容。


接下来,我们一步步编写 Layout 组件


2-1. 左侧 logo


网络异常,图片无法展示
|


这里很简单,不需要多说,对应文件


src/layout/components/SliderBar/Logo.vue


2-2. 左侧菜单


网络异常,图片无法展示
|


这一部分相对复杂一些,src/layout/components/SliderBar/index.vue 为主体文件,使用 el-menu 组件,内部遍历 routes,根据是否有子路由,选择使用 el-submenu 或者 el-munu-item 组件生成菜单,需要注意的是这里的 Item 组件需要给定 name,以便 Item 组件递归自调用时使用。


src/layout/components/SliderBar/index.vue


<template>
  <el-aside :class="{ collapse: isCollapse }">
    <Logo />
    <el-menu :default-active="activeMenu" :collapse="isCollapse">
      <template v-for="item in routes" :key="item.path">
        <SlideBarItem :item="item" />
      </template>
    </el-menu>
  </el-aside>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { useStore } from "vuex";
import { useRoute } from "vue-router";
import Logo from "./Logo.vue";
import SlideBarItem from "./Item.vue";
export default defineComponent({
  name: "",
  components: { Logo, SlideBarItem },
  setup() {
    const store = useStore();
    const routes = store.getters.routes;
    return {
      routes,
    };
  },
  computed: {
    activeMenu() {
      return useRoute().name;
    },
    isCollapse() {
      return useStore().getters.isCollapse;
    },
  },
});
</script>
复制代码


src/layout/components/SliderBar/Item.vue


<template>
  <template v-if="!item.hidden">
    <template v-if="item.children">
      <template v-if="isOnlyChild(item)">
        <SliderBarItem
          :item="
            Object.assign(
              {},
              {
                ...item.children[0],
                meta: { ...item.meta, ...item.children[0].meta },
              }
            )
          "
        />
      </template>
      <el-submenu v-else :index="item.name">
        <template #title>
          <i v-if="item.meta && item.meta.icon" :class="item.meta.icon"></i>
          <span>{{ (item.meta && item.meta.title) || item.name }}</span>
        </template>
        <template v-for="child in item.children" :key="child.name">
          <SliderBarItem :item="child" />
        </template>
      </el-submenu>
    </template>
    <el-menu-item v-else :index="item.name">
      <router-link :to="item">
        <i v-if="item.meta && item.meta.icon" :class="item.meta.icon"></i>
        <span>{{ (item.meta && item.meta.title) || item.name }}</span>
      </router-link>
    </el-menu-item>
  </template>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
  name: "SliderBarItem",
  components: {},
  props: {
    item: {
      type: Object,
      required: true,
    },
  },
  setup() {
    const isOnlyChild = (item) => {
      return item.children && item.children.length === 1;
    };
    return {
      isOnlyChild,
    };
  }
})
</script>
复制代码


这里要说一下 el-menu 组件有一个 collapse 属性,控制菜单的展开收起,我们在 storesetting.js 模块中维护了这个状态。


2-3. 右侧 Collapse 组件


该组件控制左侧菜单展开收起 src/layout/components/NavBar/Collapse.vue


<template>
  <div class="collapse_box pr_20 pointer" @click="changeCollapse">
    <span v-if="isCollapse" class="fontsize_20 el-icon-s-unfold"></span>
    <span v-else class="fontsize_20 el-icon-s-fold"></span>
  </div>
</template>
<script>
import { defineComponent } from "vue";
import { useStore } from "vuex";
export default defineComponent({
  name: "",
  setup() {
    const store = useStore();
    const changeCollapse = () => {
      store.commit("setting/SET_COLLAPSE", !store.getters.isCollapse);
    };
    return {
      changeCollapse,
    };
  },
  computed: {
    isCollapse() {
      return useStore().getters.isCollapse;
    },
  }
})
</script>
复制代码


针对页面 resize 展开收起的逻辑写在了 src/layout/mixin/resize.js 中,在 src/layout/index.vue 引入并进行混入。


2-4. 右侧面包屑导航


src/layout/components/Navbar/Breadcrumb.vue 该组件动态展示当前页面路由,且当前页面的祖先路由可以点击进行跳转。


2-5. 右侧头像下拉


src/layout/components/Navbar/AvatarDropDown.vue 该组件展示登录用户的头像,点击展开下拉,显示用户信息,修改密码,退出登录等功能。


2-6. 右侧已访问路由列表


网络异常,图片无法展示
|


src/layout/components/Navbar/VisitedViews.vue 改组件展示用户已经访问的路由,并且右键显示刷新,关闭,关闭其他,关闭所有菜单,功能相对复杂一些,对应数据状态存和修改数据方法放在 src/store/modules/tagsView.ts 中进行维护,当路由跳转时,通过路由后置守卫,将访问过的路由添加到 store/tagsView 中。


至此,lauyout 相关组件封装完成,为了便于以后扩展,我们在

src/layout/components 下新建 AppMain.vue 对所有组件做引入及 业务组件切换的动画封装


<template>
  <section class="app_main flex">
    <SliderBar />
    <el-container>
      <el-header height="80px">
        <NavBar />
      </el-header>
      <el-main>
        <router-view v-slot="{ Component }">
          <template v-if="Component">
            <transition name="fade-transform" mode="out-in">
              <keep-alive :include="cachedViews">
                <component :is="Component" :key="key" />
              </keep-alive>
            </transition>
          </template>
        </router-view>
      </el-main>
    </el-container>
  </section>
</template>
<script lang="ts">
import { defineComponent, ref, watchEffect } from "vue";
import { useStore } from "vuex";
import { useRoute } from "vue-router";
import SliderBar from "./SliderBar/index.vue";
import NavBar from "./NavBar/index.vue";
export default defineComponent({
  components: { SliderBar, NavBar },
  setup() {
    const store = useStore();
    const cachedViews = store.getters.cachedViews;
    const route = useRoute();
    const key = ref(route.fullPath);
    watchEffect(() => (key.value = route.fullPath));
    return {
      cachedViews,
      key,
    };
  },
});
</script>
复制代码


最后 src/layout/index.vue 如下:


<template>
  <AppMain />
</template>
<script lang="ts">
import { defineComponent } from "vue";
import AppMain from "./components/AppMain.vue";
import ResizeMixin from "./mixin/resize";
export default defineComponent({
  components: { AppMain },
  mixins: [ResizeMixin],
});
</script>
复制代码


3. 解决页面刷新丢失 store 数据问题


这是一个 Vue 项目老生常谈的问题了,因为我们搭建的是一个 Vue 单页应用,共享状态 通过 Vuex 放在了 store 中进行维护,刷新页面后 store 中的数据进行了初始化,之前保存的状态就丢失了,解决这个问题,我们需要在 store 的引入过程中封装一下。 新建文件 src/utils/cacheStore.ts


import store from "@/store";
//在页面加载时读取sessionStorage里的状态信息
const sessionStore = sessionStorage.getItem("store");
if (sessionStore) {
  store.replaceState(Object.assign({}, store.state, JSON.parse(sessionStore)));
  store.dispatch("permission/handleRoutes", null, { root: true });
}
//在页面刷新时将vuex里的信息保存到sessionStorage里
window.addEventListener("beforeunload", () => {
  sessionStorage.setItem("store", JSON.stringify(store.state));
});
export default store;
复制代码

main.tsstore 引入同时做调整


import store from "@/utils/cacheStore";
复制代码


End


至此,本文就结束了,项目中其他的细节还是需要在项目代码中具体查看,本文只是把主体思路及节点讲述一遍,如有任何问题或建议,欢迎留言讨论!

相关文章
|
5月前
|
JavaScript 前端开发 安全
Vue 3
Vue 3以组合式API、Proxy响应式系统和全面TypeScript支持,重构前端开发范式。性能优化与生态协同并进,兼顾易用性与工程化,引领Web开发迈向高效、可维护的新纪元。(238字)
821 139
|
10月前
|
缓存 JavaScript PHP
斩获开发者口碑!SnowAdmin:基于 Vue3 的高颜值后台管理系统,3 步极速上手!
SnowAdmin 是一款基于 Vue3/TypeScript/Arco Design 的开源后台管理框架,以“清新优雅、开箱即用”为核心设计理念。提供角色权限精细化管理、多主题与暗黑模式切换、动态路由与页面缓存等功能,支持代码规范自动化校验及丰富组件库。通过模块化设计与前沿技术栈(Vite5/Pinia),显著提升开发效率,适合团队协作与长期维护。项目地址:[GitHub](https://github.com/WANG-Fan0912/SnowAdmin)。
1229 5
|
5月前
|
缓存 JavaScript 算法
Vue 3性能优化
Vue 3 通过 Proxy 和编译优化提升性能,但仍需遵循最佳实践。合理使用 v-if、key、computed,避免深度监听,利用懒加载与虚拟列表,结合打包优化,方可充分发挥其性能优势。(239字)
443 1
|
6月前
|
开发工具 iOS开发 MacOS
基于Vite7.1+Vue3+Pinia3+ArcoDesign网页版webos后台模板
最新版研发vite7+vue3.5+pinia3+arco-design仿macos/windows风格网页版OS系统Vite-Vue3-WebOS。
722 11
|
5月前
|
JavaScript 安全
vue3使用ts传参教程
Vue 3结合TypeScript实现组件传参,提升类型安全与开发效率。涵盖Props、Emits、v-model双向绑定及useAttrs透传属性,建议明确声明类型,保障代码质量。
505 0
|
7月前
|
缓存 前端开发 大数据
虚拟列表在Vue3中的具体应用场景有哪些?
虚拟列表在 Vue3 中通过仅渲染可视区域内容,显著提升大数据列表性能,适用于 ERP 表格、聊天界面、社交媒体、阅读器、日历及树形结构等场景,结合 `vue-virtual-scroller` 等工具可实现高效滚动与交互体验。
750 1
|
7月前
|
缓存 JavaScript UED
除了循环引用,Vue3还有哪些常见的性能优化技巧?
除了循环引用,Vue3还有哪些常见的性能优化技巧?
418 0
|
8月前
|
JavaScript
vue3循环引用自已实现
当渲染大量数据列表时,使用虚拟列表只渲染可视区域的内容,显著减少 DOM 节点数量。
200 0
|
10月前
|
JavaScript 前端开发 API
Vue 2 与 Vue 3 的区别:深度对比与迁移指南
Vue.js 是一个用于构建用户界面的渐进式 JavaScript 框架,在过去的几年里,Vue 2 一直是前端开发中的重要工具。而 Vue 3 作为其升级版本,带来了许多显著的改进和新特性。在本文中,我们将深入比较 Vue 2 和 Vue 3 的主要区别,帮助开发者更好地理解这两个版本之间的变化,并提供迁移建议。 1. Vue 3 的新特性概述 Vue 3 引入了许多新特性,使得开发体验更加流畅、灵活。以下是 Vue 3 的一些关键改进: 1.1 Composition API Composition API 是 Vue 3 的核心新特性之一。它改变了 Vue 组件的代码结构,使得逻辑组
2205 0
|
6月前
|
JavaScript
Vue中如何实现兄弟组件之间的通信
在Vue中,兄弟组件可通过父组件中转、事件总线、Vuex/Pinia或provide/inject实现通信。小型项目推荐父组件中转或事件总线,大型项目建议使用Pinia等状态管理工具,确保数据流清晰可控,避免内存泄漏。
585 2

热门文章

最新文章