Vue实战——后台管理系统(一)

3,536 阅读8分钟

引言

相信大家都已经对Vue掌握的比较透彻了,在接下来的几篇文章中,我们将用Vue且使用一些插件如Element-PlusPinia 等去实现一个较为常用的后台管理系统,在做项目的同时会带大家回顾一些重要的知识点。在这篇文章中,我们将介绍五星路由和如何使用 Vue 和 ElementPlus 构建一个后台管理系统的登录界面。我们将重点介绍 ElementPlus 的表单组件及其在登录表单中的应用。

初始化项目

首先,我们需要创建一个新的 Vue 项目并安装 ElementPlus 和相关依赖:

npm init vite
npm install element-plus
npm install @element-plus/icons-vue

配置ElementPlus

main.js 中引入并配置 ElementPlus:

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'

// 按需引入 Element Plus 的组件
import { 
    ElButton, 
    ElForm,
    ElFormItem,
    ElInput,
    ElCheckbox,
    ElLink,
    ElIcon,
    ElAvatar,
    ElDropdown,
    ElDropdownMenu,
    ElDropdownItem,
    ElMenu,
    ElMenuItem,
    ElSubMenu
} from 'element-plus'
import 'element-plus/dist/index.css' // 引入 Element Plus 的样式
import * as ElementPlusIconsVue from '@element-plus/icons-vue' // 引入 Element Plus 图标库

// 创建 Vue 应用实例
const app = createApp(App)

// 注册按需引入的组件
app.use(createPinia())
app.use(router)

// 注册组件
app
   .use(ElButton)
   .use(ElForm)
   .use(ElFormItem)
   .use(ElInput)
   .use(ElCheckbox)
   .use(ElLink)
   .use(ElIcon)
   .use(ElAvatar)
   .use(ElDropdown)
   .use(ElDropdownMenu)
   .use(ElDropdownItem)
   .use(ElMenu)
   .use(ElMenuItem)
   .use(ElSubMenu)

// 注册 Element Plus 图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
}

// 挂载应用
app.mount('#app')

这里需要注意的是,我们不是引入整个ElementPlus库,而是按需引入 Element Plus 的组件,以避免引入整个库,从而减少打包体积,如果不按需引入,整个 Element Plus 库可能会非常庞大,这会导致页面加载速度变慢。且通过仅引入使用到的组件和图标,可以避免引入不必要的代码,从而提高应用的性能和响应速度。更多 ElementPluss 的使用可以参考一个 Vue 3 UI 框架 | Element Plus (element-plus.org)

配置路由

首先分析,该选择哪种路由,hash 兼容好但形式不好,url中带有#,一般在(用户端 + PC 产品);history 兼容性差但是和后端路由一致 ,结合项目分析, 这是一个后台管理系统,是给公司的内部员工和老板使用的,所以可以放心使用history路由。

router 文件夹下创建 index.js 文件,内容如下:

import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { usePermissStore } from '../store/permiss'

const routes = [
    {
        path: '/',
        redirect: '/dashboard'
    },
    {
        path: '/',
        name: 'Home',
        component: Home,
        children: [
            {
                path: '/dashboard',
                name: 'dashboard',
                meta: {
                    title: '系统首页',
                    permiss: '11'
                },
                component: () => import('../views/dashboard.vue')
            },
            {
                path: '/system-user',
                name: 'system-user',
                meta: {
                    title: '用户管理',
                    permiss: '11'
                },
                component: () => import('../views/system-user.vue')
            },
            {
                path: '/system-role',
                name: 'system-role',
                meta: {
                    title: '角色管理',
                    permiss: '12'
                },
                component: () => import('../views/system-role.vue')
            }
        ]
    },
    {
        path: '/login',
        meta: {
            title: '登录',
            noAuth: true
        },
        component: () => import('../views/login.vue')
    },
    {
        path: '/403',
        meta: {
            title: '403',
            noAuth: true
        },
        component: () => import('../views/403.vue')
    },
    {
        path: '/404',
        name: '404',
        meta: {
            title: '404',
            noAuth: true
        },
        component: () => import('../views/404.vue')
    },
    {
        path: '/:path(.*)',
        redirect: '/404'
    }
]

const router = createRouter({
    history: createWebHistory(),
    routes
})

router.beforeEach((to, from, next) => {
    NProgress.start()
    document.title = to.meta.title

    const role = localStorage.getItem('ms_name')
    const permissStore = usePermissStore()

    if (!role && !to.meta.noAuth) {
        next('/login')
    } else if (typeof to.meta.permiss == 'string' && !permissStore.key.includes(to.meta.permiss)) {
        next('/403')
    } else {
        next()
    }
})

router.afterEach(() => {
    NProgress.done()
})

export default router

主要路由配置

  • 根路径 / 被重定向到 /dashboard

  • Home 组件下的子路由包括 /dashboard, /system-user, 和 /system-role,使用了路由懒加载来优化性能。

  • 登录、403 和 404 页面路径都被配置了 meta 属性以便于后续的路由守卫判断。

  • :path(.*) 用于匹配所有未定义的路径,并重定向到 404 页面。

这里有些亮点:

  • 路由懒加载

    • 使用动态 import() 函数来实现懒加载,只有在实际访问路由时才加载对应的组件,减少初始加载时间。
  • 二级路由

    • 子路由配置在 Home 组件下,通过嵌套路由实现页面的嵌套结构。
  • meta 属性

    • title: 用于设置页面标题。
    • noAuth: 标记无需认证的路由。
    • permiss: 权限标识,用于权限控制。
  • NProgress 进度条

    • 安装依赖: npm i NProgress
    • router.beforeEach: 开始显示进度条。
    • router.afterEach: 隐藏进度条,路由切换完成后执行。

这里我们设置了全局前置守卫

  • NProgress.start() :开始显示进度条,提升用户体验,提示用户路由切换的进度。

  • document.title = to.meta.title:更新页面标题,基于目标路由的 meta 信息。

  • const role = localStorage.getItem('ms_name') :从本地存储中获取用户角色信息。通常用于检查用户是否已登录。

  • const permissStore = usePermissStore() :获取权限存储,用于权限控制。

  • if (!role && !to.meta.noAuth)

    • 如果用户未登录且目标路由没有标记 noAuth(表示需要认证),则重定向到登录页面。
  • else if (typeof to.meta.permiss === 'string' && !permissStore.key.includes(to.meta.permiss))

    • 如果用户权限不足(未包含目标路由所需的 permiss),则重定向到 403 页面。
  • else { next() }

    • 其他情况,允许路由切换。

且使用了后置全局守卫 NProgress.done() :结束进度条,表示路由切换完成。用于恢复 UI 状态,提供良好的用户体验。

权限存储

Pinia

这个项目的数据流管理我们使用的是Pinia,Pinia 是 Vue 3 的一个状态管理库,用于替代 Vuex(不了解Vuex的可以看看这篇文章浅析Vuex——快速入门复杂项目的数据流管理 - 掘金 (juejin.cn))。它比 Vuex 更加现代化和易用,设计简洁,提供了更好的 TypeScript 支持和更简单的 API。Pinia 允许你在 Vue 应用中管理全局状态,并通过 store 来集中管理应用的状态。

使用

  • defineStore 用于定义 store。

  • state 用于定义状态。

  • actions 用于定义修改状态的方法。

想更深入了解可以查阅Pinia | The intuitive store for Vue.js (vuejs.org)

回到项目中

权限管理

在main.js中

import {createPinia}  from 'pinia'
import { usePermissStore } from './store/permiss'
   const permissStore = usePermissStore();

store 文件夹下创建 permiss.js 文件,内容如下:

import { defineStore } from 'pinia';

export const usePermissStore = defineStore('permiss', {
    state: () => {
        const defaultList = {
            // 代表某个菜单下面的一级或二级菜单
            admin: [
                '0',
                '1',
                '11',
                '12',
                '13',
                '2',
                '21',
                '22',
                '23',
                '24',
                '25',
                '26',
                '27',
                '28',
                '29',
                '291',
                '292',
                '3',
                '31',
                '32',
                '33',
                '34',
                '4',
                '41',
                '42',
                '5',
                '7',
                '6',
                '61',
                '62',
                '63',
                '64',
                '65',
                '66',
            ],
            user: ['0', '1', '11', '12', '13'],
        };
        const username = localStorage.getItem('ms_name');


        return {
            key: (username == 'admin' ? defaultList.admin : defaultList.user),
            defaultList
        }
    },
    actions: {
        handleSet(val) {
            this.key = val
        }
    }
})

这里 state 定义了默认权限列表 defaultList 和当前用户的权限 keyactions 定义了 handleSet 方法,用于设置用户权限。相比于VuexPinia不用使用mutations去修改状态,而是直接通过actions简洁了许多!

创建登录界面

views 文件夹下创建 Login.vue 文件,内容如下:

<template>
    <div class="login-bg"> 
        <div class="login-container">
            <header class="login-header">
                <img src="../assets/images/logo.svg" alt="" class="logo mr10">
                <div class="login-title">后台管理系统</div>
            </header>
                
            <el-form
                 size="large" 
                 :model="param" 
                 :rules="rules" 
                 ref="login"
                 >
                <el-form-item label="用户名" prop="username">
                    <el-input 
                        v-model="param.username"
                        placeholder="请输入用户名"></el-input>
                </el-form-item>
                <el-form-item label="密码" prop="password">
                    <el-input
                        v-model="param.password" 
                        type="password" 
                        placeholder="请输入密码"></el-input>
                </el-form-item> 
                <div class="pwd-tips">
                    <el-checkbox class="pwd-checkbox" 
                        v-model="checked"
                        label="记住密码"
                        />
                    <el-link type="primary">忘记密码</el-link>
                </div>
                <el-button
                    class="login-btn" 
                    type="primary" 
                    size="large"
                    @click="submitForm(login)"
                    >登入</el-button>
                    <p class="login-tips">Tips:用户名admin 密码123456</p>
                    <p class="login-text">
                        没有账号? <el-link type="primary">立即注册</el-link>
                    </p>
            </el-form>

        </div>
    </div>
</template>

<script setup>
    import { ElMessage } from 'element-plus';
    import  { reactive,ref }  from 'vue';
    import { useRouter } from 'vue-router';
    import { usePermissStore } from '../store/permiss';

    const router = useRouter();  

    const permissStore = usePermissStore();


    const login = ref(null)  // 绑定form 元素

    const param = reactive({
        username:'',
        password:''

    })

    const rules = {
        username: [
            {
                required: true,
                message: '请输入用户名',
                trigger: 'blur'
            }
        ],
        password:[
            {
                required: true,
                message: '请输入密码',
                trigger: 'blur'
            },
            {
                min:6,
                messages: '密码长度不低于6位',
                trigger: 'blur'

            }
        ]
    }
    const checked = ref(false)

    const submitForm = (formEl) => {
        console.log(formEl);
        formEl.validate((valid) =>{
            if(valid){
                ElMessage.success('登入成功')
                localStorage.setItem('ms_name',param.username)
                const keys = permissStore.defaultList[param.username];
                permissStore.handleSet(keys);
                router.push('/');
                // console.log('表单验证成功')
            }else{
                ElMessage.error('登入失败')
                // console.log('表单验证失败');
            }
        })
    }


</script>

<style  scoped>
.login-bg {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100vh;
    background: url(../../assets/img/login-bg.jpg) center/cover no-repeat;
}

.login-header {
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 40px;
}

.logo {
    width: 35px;
}

.login-title {
    font-size: 22px;
    color: #333;
    font-weight: bold;
}

.login-container {
    width: 450px;
    border-radius: 5px;
    background: #fff;
    padding: 40px 50px 50px;
    box-sizing: border-box;
}

.pwd-tips {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 14px;
    margin: -10px 0 10px;
    color: #787878;
}

.pwd-checkbox {
    height: auto;
}

.login-btn {
    display: block;
    width: 100%;
}

.login-tips {
    font-size: 12px;
    color: #999;
}

.login-text {
    display: flex;
    align-items: center;
    margin-top: 20px;
    font-size: 14px;
    color: #787878;
}
</style>

<template> 开始讲解。我们用了:model="param" 绑定表单数据模型,:rules="rules" 绑定表单验证规则,ref="login" 绑定 Form 实例用于后续操作。v-model="param.username"v-model="param.password" :双向绑定表单输入值到 param 对象。 <el-button> :提交按钮,点击时调用 submitForm(login) 方法。

<script> 部分。用了

  • const login = ref(null) :用于绑定表单实例,以便后续调用 validate 方法。
  • const param = reactive({ username: '', password: '' }) :响应式对象,用于绑定表单数据。
  • const rules:表单验证规则。usernamepassword 有不同的验证要求,比如必填项、密码长度不低于6位。且使用了 blur鼠标离开输入框就会发生校验。
  • formEl.validate:调用 Element Plus 表单实例的 validate 方法来进行表单验证。
  • ElMessage.successElMessage.error:根据验证结果显示成功或失败的消息。
  • localStorage.setItem('ms_name', param.username) :将用户名存储在 localStorage 中,以便在后续的请求中使用。
  • const keys = permissStore.defaultList[param.username] :获取用户的权限列表。
  • permissStore.handleSet(keys) :更新 Pinia store 中的权限列表。
  • router.push('/') :登录成功后,重定向到首页。

由此登入界面便完成了。

qstef-zvenx.gif

总结

通过以上步骤,我们使用VueElementPlusPinia 创建了一个基础的后台管理系统,包含登录界面,权限管理等。实现后续功能的文章还在更新中,如果觉得对你有帮助的话可以点个赞哦。