Vue-admin-template项目

一、关于Vue-admin-template

1.1 介绍

vue-element-admin 是一个后台前端解决方案,它基于 vueelement-ui实现。

可以把 vue-element-admin当做工具箱或者集成方案仓库,在 vue-admin-template 的基础上进行二次开发,想要什么功能或者组件就去 vue-element-admin 那里复制过来。

vue-element-admin官方

首先前端目录结构

├── build                      // 构建相关  
├── bin                        // 执行脚本
├── public                     // 公共文件
│   ├── favicon.ico            // favicon图标
│   └── index.html             // html模板
├── src                        // 源代码
│   ├── api                    // 所有请求
│   ├── assets                 // 主题 字体等静态资源
│   ├── components             // 全局公用组件
│   ├── directive              // 全局指令
│   ├── layout                 // 布局
│   ├── router                 // 路由
│   ├── store                  // 全局 store管理
│   ├── styles                 // css样式
│   ├── utils                  // 全局公用方法
│   ├── views                  // view
│   ├── App.vue                // 入口页面
│   ├── main.js                // 入口 加载组件 初始化等
│   ├── permission.js          // 权限管理
│   └── settings.js            // 系统配置
├── .editorconfig              // 编码格式
├── .env.development           // 开发环境配置
├── .env.production            // 生产环境配置
├── .env.staging               // 测试环境配置
├── .eslintignore              // 忽略语法检查
├── .eslintrc.js               // eslint 配置项
├── .gitignore                 // git 忽略项
├── .travis.yml                // travis.yml
├── babel.config.js            // babel.config.js
├── package.json               // package.json
└── vue.config.js              // vue.config.js
1.2 项目安装
# 项目地址
https://github.com/PanJiaChen/vue-admin-template

# 进入项目目录
cd vue-admin-template

# 安装依赖
npm install

# 本地开发 启动项目
npm run dev

二、登录模块

2.1 运行流程
  1. login页面进行表单验证,(login/index.vue)

  2. 然后验证成功点击按钮,将数据发送到vuex,有actions的方法(store/modules/user.js)

  3. 发送请求login,发送请求时会进行一个请求拦截,会在请求头header里加入X-Token,

  4. (utils/request.js)返回res以及其他数据如权限等并存储在vuex和cookie,login会返回一个Promise对象,方便login页面调用then或catch操作。

流程图
在这里插入图片描述

2.2 功能实现

首先是页面的搭建html部分

<template>
  <div class="login-container">
    <el-form
      ref="loginForm"
      :model="loginForm"
      :rules="loginRules"
      class="login-form"
      auto-complete="on"
      label-position="left"
    >
      <div class="title-container">
        <h3 class="title">电商管理后台</h3>
      </div>

      <el-form-item prop="username">
        <span class="svg-container">
          <svg-icon icon-class="user" />
        </span>
        <el-input
          ref="username"
          v-model="loginForm.username"
          placeholder="Username"
          name="username"
          type="text"
          tabindex="1"
          auto-complete="on"
          v-focus
        />
      </el-form-item>

      <el-form-item prop="password">
        <span class="svg-container">
          <svg-icon icon-class="password" />
        </span>
        <el-input
          :key="passwordType"
          ref="password"
          v-model="loginForm.password"
          :type="passwordType"
          placeholder="Password"
          name="password"
          tabindex="2"
          auto-complete="on"
          @keyup.enter.native="handleLogin"
        />
        <span class="show-pwd" @click="showPwd">
          <svg-icon
            :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'"
          />
        </span>
      </el-form-item>

      <el-button
        :loading="loading"
        type="primary"
        style="width: 100%; margin-bottom: 30px"
        @click.native.prevent="handleLogin"
        >Login</el-button
      >

      <div class="tips">
        <span style="margin-right: 20px">username: admin</span>
        <span> password: any</span>
      </div>
    </el-form>
  </div>
</template>
2.3 使用的form实现表单的数据绑定

在vue中,要用:model属性来绑定表单,在

data函数中规定返回的值的格式。

<script>
export default {
    data(){
        return{
            //这是登录表单的数据绑定
            loginForm:{
                username:'',
                password:''
            }
        };
    }
};
</script>

不要忘记给el-input 添加属性type="password"来隐藏密码。

2.4 使用rules对表单做一些验证

在data中先定义一些规则

<script>
export default {
    data(){
        return{
            //这是登录表单的验证规则
         loginRules: {
        username: [{ required: true, trigger: 'blur' },
        { min: 5, max: 12, trigger: 'blur', message: '用户名5到12位' }],
        password: [{ required: true, trigger: 'blur' },
        { min: 5, max: 12, trigger: 'blur', message: '密码5到12位' }]
      },
        };
    }
};
</script>

然后表单使用定义好的规则

注意: prop:与规则中的name属性相同

  <el-form
      ref="loginForm"
      :model="loginForm"
      :rules="loginRules"  // 绑定已定义好的规则
      class="login-form"
      auto-complete="on"
      label-position="left"
    >
    
2.5 提交表单进行表单验证及http请求
    async handleLogin () {
      try {
        await this.$refs.loginForm.validate() // 提交前做一次表单验证
        this.loading = true
        await this.$store.dispatch('user/login', this.loginForm) // http请求
        this.$router.push({ path: '/' }) // 验证成功后进行页面跳转
        this.loading = false
      } catch (err) {
        this.loading = false
      }
    }

三、主页模块

3.1 主页的token拦截处理
3.1.1 权限拦截的流程图

已经完成了登录的过程,并且存储了token,但是此时主页并没有因为token的有无而被控制访问权限

在这里插入图片描述

在基础框架阶段,**src/permission.js**是专门处理路由权限的,所以在这里处理

3.1.2 代码实现
import router from './router'
import store from './store'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import getPageTitle from '@/utils/get-page-title'

NProgress.configure({ showSpinner: false }) // NProgress Configuration

const whiteList = ['/login', '/404'] // no redirect whitelist

router.beforeEach((to, from, next) => {
  // start progress bar
  NProgress.start()

  // set page title
  document.title = getPageTitle(to.meta.title)
  console.log(store.getters.token)
  if (store.getters.token) {
    // 如果有 token,判断是否请求的是登录页
    if (to.path === '/login') {
      // 如果请求的是登录页,因为具有 token ,表明已经登录了,就直接跳转主页即可
      next('/')
    } else {
      // 如果请求的不是登录页,那就直接放行
      next()
    }
  } else {
    // 如果没有 token,说明没有登录,则判断用户请求的页面是否在白名单之中
    if (whiteList.indexOf(to.path) > -1) {
      // 直接放行
      next()
    } else {
      next('/login')
    }
  }
  NProgress.done() // 手动强制关闭一次  为了解决 手动切换地址时  进度条的不关闭的问题
})

router.afterEach(() => {
  // finish progress bar
  NProgress.done()
})

在导航守卫的位置,添加了NProgress的插件,可以完成进入时的进度条效果

3.2 主页搭建
3.2.1 设置左侧导航

左侧导航组件的样式文件 styles/siderbar.scss

设置左侧导航图片

.scrollbar-wrapper { 
    background: url('~@/assets/common/leftnavBg.png') no-repeat 0 100%;
}

显示左侧logo图片 src/setttings.js

module.exports = {
  title: '小优电商后台管理系统',
  fixedHeader: false,
  sidebarLogo: true // 显示logo
  }

设置头部图片结构 src/layout/components/Sidebar/Logo.vue

<div class="sidebar-logo-container" :class="{'collapse':collapse}">
    <transition name="sidebarLogoFade">
      <router-link key="collapse" class="sidebar-logo-link" to="/">
        <img src="@/assets/common/logo.png" class="sidebar-logo  ">
      </router-link>
    </transition>
  </div>

设置大图和小图的样式

  &.collapse {
    .sidebar-logo {
      margin-right: 0px;
      width: 32px;
      height: 32px;
    }
  }
// 小图样式
.sidebar-logo {
      width: 140px;
      vertical-align: middle;
      margin-right: 12px;
}
// 大图样式
3.2.2 设置头部内容的布局和样式

头部组件位置 layout/components/Navbar.vue

添加公司名称,注释面包屑

  <div class="app-breadcrumb">
      北京小优智慧城市科技优先公司
      <span class="breadBtn">V1.0</span>
    </div>
 <!-- <breadcrumb class="breadcrumb-container" /> -->

右侧下拉菜单设置

   <div class="right-menu">
      <el-dropdown class="avatar-container" trigger="click">
        <div class="avatar-wrapper">
          <img src="@/assets/common/bigUserHeader.png" class="user-avatar">
          <span class="name">管理员</span>
          <i class="el-icon-caret-bottom" style="color:#fff" />
        </div>
        <el-dropdown-menu slot="dropdown" class="user-dropdown">
          <router-link to="/">
            <el-dropdown-item>
              首页
            </el-dropdown-item>
          </router-link>
          <a target="_blank" href="https://github.com/PanJiaChen/vue-admin-template/">
            <el-dropdown-item>邮箱</el-dropdown-item>
          </a>
          <el-dropdown-item divided @click.native="logout">
            <span style="display:block;">退出登录</span>
          </el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>

四、路由模块

4.1 静态路由
{
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },

  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },
  {
    path: '/import',
    component: Layout,
    hidden: true,//隐藏在左侧菜单中
    children: [{
      path: '',//二级路由path什么都不写表示二级默认路由
      component: () => import('@/views/import')
    }]
  },
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
      path: 'dashboard',
      name: 'dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: { title: 'Dashboard', icon: 'dashboard' }
    }]
  },
4.2 动态路由封装成了组件先引入
// 引入动态路由
import user from './modules/user'
import role from './modules/role'
import rights from './modules/rights'
import goods from './modules/goods'
import category from './modules/category'
import report from './modules/report'

// 动态路由
export const permissionRouter = [ user, role, rights, goods, category, report]

4.3 改变路由模式
const createRouter = () => new Router({
  mode: 'history', // 可以改路由hash和history模式
  base:'/hr', //配置项目的基础地址
  scrollBehavior: () => ({ y: 0 }),
  // routes: [...constantRoutes, ...permissionRouter]
  routes: [...constantRoutes]
})
4.4 左侧菜单路由的显示和隐藏

设置hidden为 true就不会在左侧显示了

 {
    path: 'detail/:id', // query传参 动态路由传参
    component: () => import('@/views/user/detail'),
    hidden: true, // 不在左侧菜单显示
    meta: {
      title: '用户详情' // 标记当前路由规则的中文名称 后续在做左侧菜单时 使用
    }
  },

五、用户管理

5.1 页面结构搭建
<template>
  <div class="dashboard-container">
    <div class="app-container">
      <common-tools :show-before="true">
        <span slot="before">共166条记录</span>
        <template slot="after">
          <el-button size="small" type="warning">导入</el-button>
          <el-button size="small" type="danger">导出</el-button>
          <el-button size="small" type="primary">新增用户</el-button>
        </template>
      </common-tools>
      <!-- 放置表格和分页 -->
      <el-card>
        <el-table border>
          <el-table-column label="序号" sortable="" />
          <el-table-column label="用户名" sortable="" />
          <el-table-column label="工号" sortable="" />
          <el-table-column label="手机" sortable="" />
          <el-table-column label="角色" sortable="" />
          <el-table-column label="创建时间" sortable="" />
          <el-table-column label="账户状态" sortable="" />
          <el-table-column label="操作" sortable="" fixed="right" width="280">
            <template>
              <el-button type="text" size="small">查看</el-button>
              <el-button type="text" size="small">角色</el-button>
              <el-button type="text" size="small">编辑</el-button>
              <el-button type="text" size="small">删除</el-button>
            </template>
          </el-table-column>
        </el-table>
        <!-- 分页组件 -->
        <el-row
          type="flex"
          justify="center"
          align="middle"
          style="height: 60px"
        >
          <el-pagination layout="prev, pager, next" />
        </el-row>
      </el-card>
    </div>
  </div>
</template>

5.2 页面数据的获取及渲染
  // 获取页面列表数据
    async getUserList () {
      this.loading = true
      const params = {
        query: '',
        pagenum: this.pagenum,
        pagesize: this.pageSize,
        total: 0
      }
      const { data: { data: { pagenum, total, users } } } = await getUserList(params)
      this.userList = users
      this.total = total
      this.pagenum = pagenum
      this.loading = false
    }
5.3 过滤器解决时间格式问题

使用moment解决时间格式问题

import moment from 'moment'
export default {
  filterTime: function (value) {
    return moment(value * 1000).format('YYYY-MM-DD HH:mm:ss')
  }
}
5.4 展示部门信息
5.4.1 编写api
import request from '@/utils/request'

/**
 * 获取部门列表
 */
export function getDepartMent() {
  return request({
    url: `department`,
    method: 'get'
  })
}
5.4.2 获取部门数据,转化成需要的格式
import { getDepartMent } from '@/api/department'
import { tranListToTreeData } from '@/utils'
// 获取所有部门
async getAllDepartment() {
    this.showTree = true
    this.loading = true
    const res = await getDepartMent()
    this.treeData = tranListToTreeData(res, 0)
    this.loading = false
}

data 中新增3个变量

treeData: [], // 存储部门的树形数据
showTree: false, // 部门文本框获取焦点时,设置为true,展示部门信息
loading: false, // 显示或隐藏进度
5.4.3 部门获取焦点时,展示数据
  <el-form-item label="部门" prop="department_title">
        <el-input
          v-model="userForm.department_title"
          @focus="getAllDepartment"
        />

        <el-tree
          v-if="showTree"
          v-loading="loading"
          :data="treeData"
          :props="{ label: 'department_title' }"
          @node-click="handleNodeClick"
        />
      </el-form-item>

点击部门赋值表单数据

选择部门触发

 handleNodeClick(node) {
      console.log(node)
      this.userForm.department_title = node.department_title
      this.userForm.department_id = node.department_id
      this.showTree = false
    },

六、 权限管理

6.1 RBAC权限设计

RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联。简单地说,一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系。

如下图

在这里插入图片描述

6.2 权限模块

// vuex的权限模块
import { permissionRouter, constantRoutes } from '@/router'
// vuex 中的permission模块用来存放当前 静态路由 + 当前用户 的权限路由
const state = {
  routes: constantRoutes // 所有人默认拥有静态路由
}

const mutations = {
  // newRoutes可以认为是 用户登录 通过权限多得到的动态路由的部分
  setRoutes (state, newRoutes) {
    state.routes = [...constantRoutes, ...newRoutes]
  }
}

const actions = {
  filterRoutes (context, menus) {
    const routes = []
    menus.forEach(key => {
      routes.push(... permissionRouter.filter(item => item.name === key))
    })
    routes.push( { path: '*', redirect: '/404', hidden: true })
    context.commit('setRoutes',routes)
    return routes
  }
}
export default {
  namespaced: true,
  state,
  mutations,
  actions
}

// vuex 中的permission模块用来存放当前 静态路由 + 当前用户 的权限路由
const state = {
routes: constantRoutes // 所有人默认拥有静态路由
}

const mutations = {
// newRoutes可以认为是 用户登录 通过权限多得到的动态路由的部分
setRoutes (state, newRoutes) {
state.routes = […constantRoutes, …newRoutes]
}
}

const actions = {
filterRoutes (context, menus) {
const routes = []
menus.forEach(key => {
routes.push(… permissionRouter.filter(item => item.name === key))
})
routes.push( { path: ‘*’, redirect: ‘/404’, hidden: true })
context.commit(‘setRoutes’,routes)
return routes
}
}
export default {
namespaced: true,
state,
mutations,
actions
}


Logo

前往低代码交流专区

更多推荐