Vue2 + element ui 后台管理系统(项目总结)

1、准备所需框架,插件,组件库等。


axios的封装

axios 的封装,对请求拦截器和响应拦截器进行处理:

创建 axios ,在里面配置请求基地址和请求超时时间。在请求拦截器里面设置请求头,用户登录后,后台会返回token,请求其他数据是需要用到这个token,用来判断我们是否登录成功;在响应拦截器里面我们可以对 token 进行过期处理,还可以对后台返回给我们的状态码,比如成功的状态码200,和失败的状态码404 进行对应的处理,还有就是没有网络的时候,响应拦截器会返回给我们失败的状态,根据这些状态就可以给用户进行提示。

// 引入 axios 网路请求
import axios from 'axios'
// 引入 element ui 里消息提示框,对用户进行提示操作
import {Message, MessageBox} from "element-ui";
// 引入 router
import router from "@/router";
// 创建 axios 
const server = axios.create({
    // 请求基地址
    baseURL: '请求基地址',
    // 请求超时时间
    timeout: 5000
})
// 请求拦截器
server.interceptors.request.use(config => {
    // 获取 本地存储的 token
    let token = localStorage.getItem('token')
    // 判断本地是否存在 token ,登录成功后,后台会返回
    if (token) {
        // 添加请求头,请求数据的时候需要用
        config.headers.Authorization = token
    }
    return config
}, error => {
    Promise.reject(error)
})
// 响应拦截器
server.interceptors.response.use(res => {
    // 状态码
    let codeStatus = res.data.meta.status;  // 状态码
    // token 无效的过期处理
    if (res.data.meta.msg == "无效token") {
        MessageBox.confirm('登录失效, 是否重新登录?', '提示', {
            confirmButtonText: '重新登录',
            cancelButtonText: '取消',
            type: 'warning'
        }).then(() => {
            // 删除本地存储的 token 
            localStorage.removeItem('token')
            // 跳转到登录页面
            router.push('/login')
        }).catch(() => {
            this.$message({
                type: 'info',
                message: '已取消重新登录'
            });
        });
    }
    // 失败的状态码处理,给用户进行提示
    if (codeStatus === 400 || codeStatus === 401 || 
        codeStatus === 403 || codeStatus === 404 ||
        codeStatus === 422 || codeStatus === 500) {
        Message.error(res.data.meta.msg);
    }
    return res.data
}, error => {
    // 没有网络的时候进行处理,提示用户
    let {message} = error
    // Network Error 这个是固定的
    if (message == 'Network Error') {
        message = "后端接口连接异常"
    // 请求超时处理
    } else if (message.includes('timeout')) {
        message = '系统接口亲请求超时'
    } else {
        return
    }
    Message.error(message);
})
export default server

二次处理:

这次的处理可以使页面的网络请求代码更加简洁,也方便后期的维护

// 引入 axios 的封装
import server from "@/api/server";

// 导出请求
export function getLogin(data) {
    // 请求方式,请求地址,请求参数
    return server.post('login', data)
}

// 如果是 GET 请求
// export function getLogin(params) {
    // 请求方式,请求地址,请求参数
    // return server.get('login', {params})
// }

2、登录(核心:登录的鉴权)

  1. 将后端接口所需的数据,前端进行验证表单(element ui 表单验证)。
  2. 将验证后的数据用 axios 发送给后端,
  3. 根据后端响应给我们的数据,状态,对应的给用户进行提示。
  4. 把 token 存储到本地,跳转到首页

用登录鉴权功能,防止用户在没有登录的情况下跳转页面

登录的鉴权我们需要用到 Vue 的路由守卫(分为全局路由守卫,组件路由守卫和路由独享守卫,这里用到了全局路由守卫的前置路由守卫)

在 router :

// 注意:全局路由守卫是在实例化路由后写的,不能在写在实例化之前
// to 是要进⼊的路由,from 离开之前的路由,next 是进入下一个路由
router.beforeEach((to, from, next) => {
    // 获取本地存储的 token 判断我们是否登录
    let token = localStorage.getItem('token')
    // 如果 token 不存在,并且当前页面不是 login 页面
    if (!token && to.path != '/login') {
        // 跳转的登录页面
        next('/login');
        return
    }
    // 如果 token 存在,并且当前页面是 login 页面
    if (token && to.path == '/login') {
        // 跳转到主页
        next('/')
        return;
    }
    next()
})

// 也可以设置路由白名单,可以让用户在不登陆的情况下,也可以跳转到的页面

3、首页(核心:动态路由,左侧导航菜单刷新后还是对应的选中状态)

首页要实现的功能:

退出功能:

退出功能使用 element ui 的 MessageBox 弹框 对用户进行提示,让用户确定是否要退出,确定退出后,删除本地存储的 token 并且返回到登录页面。

左侧导航菜单功能:

  1. 使用 axios 发起网络请求,获取左侧菜单栏的数据
  2. 使用element ui 的 NavMenu 导航菜单 实现页面的布局
  3. 实现刷新还是对应选中的状态
    // 注意事项:我这利用的的是获取当前路由的方式,不用去特意的存储路由
    // 不过需要配置路由的时候,路由的 path 和 name 完全一样才可以,path 前面带 / 没事
    // NavMenu 导航里 SubMenu Attribute  的 index 需要绑定 path 
    // :default-active="$route.name" 就可以实现了
    
     <el-menu
     :collapse="isCollapse"
     :collapse-transition="false"
     :default-active="$route.name"
     active-text-color="#409EFF"
     background-color="#333744"
     class="el-menu-vertical-demo"
     text-color="#fff"
     unique-opened>
     <el-submenu v-for="(item,index) in menusData" :key="item.id" :index="item.path">
         <template slot="title">
            <i v-show="index == 0" class="el-icon-s-custom"></i>
            <i v-show="index == 1" class="el-icon-s-help"></i>
            <i v-show="index == 2" class="el-icon-star-on"></i>
            <i v-show="index == 3" class="el-icon-s-order"></i>
            <i v-show="index == 4" class="el-icon-s-flag"></i>
            <span>{{ item.authName }}</span>
         </template>
         <el-menu-item-group>
                <template slot="title"><span v-show="isCollapse">{{ item.authName }}</span></template>
                <el-menu-item v-for="ele in item.children" :key="ele.id" :index="ele.path"
                 @click="$router.push('/'+ele.path)">{{ ele.authName }}
                </el-menu-item>
            </el-menu-item-group>
        </el-submenu>
    </el-menu>
  4. 实现动态路由
    // 实现动态路由,我们在获取左侧菜单的时候就要对其进行处理,
    // 如果获取的是树状结构的数据,我们需要对其转换为列表结构的数据,方便我们对其循环添加
    // 在添加到对应的路由里 使用的router 的方法 addRoute 
    
    // 因为浏览器刷新的时候,路由会初始化,所以我们在 router 里面也需要在做一次动态路由的添加,
    // 防止刷新后找不到对应的路由
    // 所以 树状转列表 的递归方法需要封装一下,可以重用
    

    树状结构转换为列表结构方法:

    export function list(data) {
        // 声明一个空数组,用来添加动态路由的数据的
        let array = [];
        // 使用后递归循环的方式将树形结构数据转化为列表结构
        function listReplacement(data) {
            data.forEach(item => {
                // 判断每一项的children有没有数据,
                // 因为后台给我们返回的数据里,就算children里面没有数据也会把children以空数组的方式返回给我们
                // 所以我们通过children数组的长度来判断
                if (item.children.length) {
                    // 调用函数自身,实现递归
                    listReplacement(item.children)
                } else {
                    // 如果没有就将这一项数据添加到一个新的数组,达成列表结构
                    // 注意:如果你的路由文件需要和这个导航菜单的path需要一致。
                    // 如果不一致(比如首字母大写,再进行添加到compoent里面时需要将数据的path里面的第一个字母也转换成大写,具体还需要看你的数据写法)
                    // console.log(item)
                    // 我的因为path和对应文件名一致,就不需要在转换
                    // 指定文件的component 里,有的可能需要在路径末尾添加 .vue 后缀
                    array.push({
                        path: '/' + item.path,
                        name: item.path,
                        component: () => import(`@/views/page/${item.path}.vue`)
                    })
                }
            })
        }
        listReplacement(data)
        // 返回通过递归循环转换成列表数据的数组
        return array
    }
    

    获取左侧菜单后的处理:

    // 先引入递归方法和axios对应的请求
    import {list} from "@/utils/listReplacement";
    import {getMenus} from "@/api/api";
    
    getMenus().then(res => {
        this.menusData = res.data
        // 将获取到的左侧菜单存到本地,方便在 router 里面的处理
        localStorage.setItem('menus', JSON.stringify(res.data))
        // 树状转列表
        let menusList = list(res.data)
        // 列表数据循环
        menusList.forEach(item => {
            // 使用router 的方法 addRoute进行添加
            // 第一个参数是需要添加的路由的name属性,第二个参数是要添加的路由参数
            this.$router.addRoute('home', item)
        })
    })

    在 router :

    // 注意:此操作是在实例化路由后写的,不能在写在实例化之前
    function dynamic() {
        // 获取本地存储的 token 用来判断我们是否登录
        let token = localStorage.getItem('token')
        let menus = JSON.parse(localStorage.getItem('menus'))
        // 判断我们是否登录,是否有左侧菜单的数据
        if (token && menus) {
            // 如果有,menus给的数据不是列表结构而是树状结构,就需要我们把树状结构转换为列表结构
            //我们完成动态路由需要的是列表结构的导航菜单
            let menusData = list(menus)
            // 使用router 的方法 addRoute进行添加
            // 第一个参数是需要添加的路由的name,第二个参数是要添加的路由
            menusData.forEach(item => {
                router.addRoute("home", item)
            })
        }
    }
    // 调用一次,不然会出现白屏
    dynamic()
    
  5. 以上操作完成就可以跳转对应的页面了

4、实现对应的页面功能

4-1、用户列表:

  1. axios 获取用户列表数据
  2. 用element ui 的 table 表格 ,Button 按钮 和 Pagination 分页 实现页面的布局
    // 分页功能:
    // 主要就是修改我们的请求的 页码,每页多少条,然后再次发起请求,对页面进行渲染
    <el-pagination
        :current-page="pagenum"
        :page-size="pagesize"
        :page-sizes="[5, 10, 25, 50]"
        :total="total"
        layout="total, sizes, prev, pager, next, jumper"
        style="margin-top: 20px"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange">
    </el-pagination>
    
    
    // methods
    handleSizeChange(val) {
        this.pagenum = 1
        this.pagesize = val
        this.getUsers()
    },
    handleCurrentChange(val) {
        this.pagenum = val
        this.getUsers()
    },
  3. 添加,编辑,删除,状态,分配角色使用element ui 的 Dialog 对话框和表单验证
  4. 分配角色功能:
    // 分配角色功能需要用到角色列表的数据,
    // 把角色列表的数据用下拉菜单渲染到分配角色的弹框中
    // 将角色 id 和 用户 id 用 axios 发送给后端,完成分配角色功能
    
     // 分配角色的确定按钮
        setUpUser() {
          if (!this.setUpForm.rid) {
            this.$message({
              message: '请选择分配的角色',
              type: 'warning'
            })
            return;
          }
          getSetUpUsers(this.setUpForm).then(res => {
            if (res.meta.status === 200) {
              this.$message({
                message: res.meta.msg,
                type: 'success'
              })
              this.getUsers()
            }
            this.setUpUserVisible = false
          })
        },

 添加用户:

<el-button type="primary" @click="addUserVisible = true">添加用户</el-button>

addUser() {
      getAddUsers(this.userForm).then(res => {
        if (res.meta.status === 201) {
          this.$message({
            message: res.meta.msg,
            type: 'success'
          })
          this.getUsers()
        }
        this.userForm = {}
        this.addUserVisible = false
      })
    },

编辑:

    // 编辑
    edit(val) {
      this.editUserForm = val
      this.editUserVisible = true
    },


// 编辑用户的确定按钮
    editUser() {
      getEditUsers(this.editUserForm).then(res => {
        console.log(res)
        if (res.meta.status === 200) {
          this.$message({
            message: res.meta.msg,
            type: 'success'
          })
          this.getUsers()
        }
        this.editUserVisible = false
      })
    },

删除:

// 删除
    del(val) {
      this.$confirm('此操作将永久删除该数据, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        getDeleteUsers(val).then(res => {
          console.log(res)
          if (res.meta.status == 200) {
            this.$message({
              type: 'success',
              message: '删除成功!'
            });
            this.getUsers()
          }
        })
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消删除'
        });
      });
    },

 修改状态:

  // 修改状态
    stateChange(val) {
      getStateUsers(val).then(res => {
        if (res.meta.status === 200) {
          this.$message({
            message: res.meta.msg,
            type: 'success'
          })
        } else {
          val.mg_state = !val.mg_state
        }
      })
    },

 4-2、角色列表:

渲染页面,带有展开:

      <!--角色列表-->
      <el-table :data="rolesData" border style="width: 100%">
        <!--每一行展开数据-->
        <el-table-column type="expand">
          <template slot-scope="props">
            <el-form class="demo-table-expand" inline label-position="left">
              <el-row v-for="item in props.row.children" :key="item.id" :gutter="24" class="oneRow">
                <el-col :span="5">
                  <el-tag closable effect="dark" @close="delRolesRights(props.row,item)">{{ item.authName }}</el-tag>
                  <i class="el-icon-caret-right" style="font-size: 18px"></i>
                </el-col>
                <el-col :span="19">
                  <el-row v-for="ele in item.children" :key="ele.id" :gutter="24" class="twoRow">
                    <el-col :span="6">
                      <el-tag closable effect="dark" type="success" @close="delRolesRights(props.row,ele)">
                        {{ ele.authName }}
                      </el-tag>
                      <i class="el-icon-caret-right" style="font-size: 18px"></i>
                    </el-col>
                    <el-col :span="18">
                      <el-row :gutter="24" class="rightBox">
                        <el-tag v-for="items in ele.children.slice(0,4)" :key="items.id"
                                closable effect="dark" type="warning"
                                @close="delRolesRights(props.row,items)">
                          {{ items.authName }}
                        </el-tag>
                      </el-row>
                    </el-col>
                  </el-row>
                </el-col>
              </el-row>
            </el-form>
          </template>
        </el-table-column>
        <el-table-column label="#" type="index">
        </el-table-column>
        <el-table-column label="角色名称" prop="roleName">
        </el-table-column>
        <el-table-column label="角色描述" prop="roleDesc">
        </el-table-column>
        <el-table-column fixed="right" label="操作">
          <template slot-scope="scope">
            <el-button icon="el-icon-edit" size="mini" type="primary" @click="editRole(scope.row)">编辑</el-button>
            <el-button icon="el-icon-delete" size="mini" type="danger" @click="deleteRole(scope.row)">删除</el-button>
            <el-button icon="el-icon-setting" size="mini" type="warning" @click="assignmentRights(scope.row)">分配权限
            </el-button>
          </template>
        </el-table-column>
      </el-table>

核心:就是分配权限功能:

// 分配权限功能思路就是获取树状权限列表的所有数据
// 利用递归获取当前角色拥有权限的id
// 根据角色拥有权限的id 选中对应权限
// 请求修改的时候需要使用 element ui 的tree树形控件,将半选中的和选中的id 用asiox 请求接口发送给后端
// 我这个写法有闪烁问题
// 需用在关闭弹框和打开弹框的时候都条用一次清空数据
//只用在关闭的时候使用setCheckedKeys 经行再次更新 就可以解决


  // 关闭分配权限弹框的函数回调
    assignmentRightsDown() {
      this.assignmentRightsVisible = false
      this.assignmentRightsKeys = []
      // this.$nextTick(() => {
      this.$refs.tree.setCheckedKeys(this.assignmentRightsKeys)
      // })
    },
    // 分配权限弹框的确定按钮
    async assignment() {
      let getCheckedKeys = this.$refs.tree.getCheckedKeys()
      let getHalfCheckedKeys = this.$refs.tree.getHalfCheckedKeys()
      let rids = [...getCheckedKeys, ...getHalfCheckedKeys].join(',')
      const assignmentright = await getAssignmentRights(this.assignmentRightFrom, rids)
      if (assignmentright.meta.status === 200) {
        this.$message({
          type: 'success',
          message: assignmentright.meta.msg
        });
        await this.getRoles()
      }
      this.assignmentRightsVisible = false
    },
    // 分配权限按钮
    assignmentRights(val) {
      this.assignmentRightsKeys = []
      // this.$nextTick(() => {
      // this.$refs.tree.setCheckedKeys(this.assignmentRightsKeys)
      // })
      this.assignmentRightFrom = val
      this.assignmentRightsVisible = true
      this.getLeafKeys(val.children, this.assignmentRightsKeys)
    },
    // 循环递归,拿到角色拥有权限的id
    getLeafKeys(arrays, arr) {
      arrays.forEach(item => {
        if (!item.children) {
          return arr.push(item.id)
        }
        this.getLeafKeys(item.children, arr)
        // this.$nextTick(() => {
        //   this.$refs.tree.setCheckedKeys(arr)
        // })
      })
    },

4-3、商品列表

核心:就是添加商品的图片上传功能和富文本

图片上传功能:

// 思路
// 使用element ui 的Upload 上传;
// 上传图片需要我们设置请求头发送token,
// 设置完毕后在上成功触发的函数里面,
// 后台会返回临时路径和正式地址 
// 将图片信息添加到表单图片pics数组中
// 删除也是通过循环遍历得到对应的存储临时路径的下标进行删除的
// 图片预览就是点击图片,将正式地址用弹出框加img标签渲染就可以了

//  action:指定图片上传api接口
//  :on-preview : 当点击图片时会触发该事件进行预览操作,处理图片预览
//  :on-remove : 当用户点击图片右上角的X号时触发执行
//  :on-success:当用户点击上传图片并成功上传时触发
//  list-type :设置预览图片的方式
//  :headers :设置上传图片的请求头

    // 请求路径
   actionURL: 'http://127.0.0.1:8888/api/private/v1/upload',
    // 设置请求头
      headerToken: {
        Authorization: localStorage.getItem('token')
      },

   // 预览图片
    previewImg(file) {
      // console.log(file)
      this.previewVisible = true
      this.previewURL = file.response.data.url
    },
    // 删除图片
    removeImg(file) {
      // console.log(file)
      // console.log(this.goodsForm)
      let path = file.response.data.tmp_path
      let index = this.goodsForm.pics.findIndex(item => item.pic == path)
      this.goodsForm.pics.splice(index, 1)
    },
    // 上传成功
    successImg(file) {
      // console.log(file)
      let path = file.data.tmp_path
      this.goodsForm.pics.push({pic: path})
    },

富文本:


富文本步骤:
1. 安装vue-quill-editor
npm install vue-quill-editor -S

2. 安装quill
npm install quill -S


// 导入富文本编辑器 在mian.js引入
import VueQuillEditor from 'vue-quill-editor'
// require styles 导入富文本编辑器对应的样式
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
Vue.use(VueQuillEditor)

 <!-- 富文本编辑器组件 -->
<quill-editor v-model="addForm.goods_introduce"></quill-editor>


4-4、数据报表

用的是echarts 数据可视化工具

// 安装echarts,建议不要装最新的,最好安装4版本的,不要安装5版本的
// 因为5版本的引入方式需要在加 *as ,打包的时候难处理
// 指定版本安装 echarts
cnpm i echarts@4.9.0 -S

// 引入
import echarts from 'echarts';

// 使用的时候注意事项
// 需要给echarts 容器给上宽高,不然是出不来效果的
// 因为用的是dom元素,所以在mounted() 里面写
// 因为没有自适应,但是官方给我们提供了方法


// 初始化dom元素
 var myEchaets = echarts.init(document.getElementById('main'))
    // 获取数据报表数据
    const reports = await getReports() 
    this.option.legend.data = reports.data.legend.data
    this.option.series = reports.data.series
    this.option.xAxis = reports.data.xAxis
    this.option.yAxis = reports.data.yAxis
    myEchaets.setOption(this.option)
    // 根据窗口自适应
    window.onresize = function () {
      myEchaets.resize();
    };

 5、项目打包优化

入口:Vue 项目上线优化_٨ـﮩﮩ٨ـ玖玥ﮩ٨ـﮩ٨的博客-CSDN博客

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Vue + ElementUI 是一个用于构建后台管理系统的前端项目。它结合了Vue框架和ElementUI组件库,提供了丰富的UI组件和开发工具,使开发者能够快速构建出美观、易用的后台管理界面。\[1\] 在开始使用Vue + ElementUI项目之前,你需要先安装ElementUI。你可以通过在项目路径下的终端中输入以下命令来安装ElementUI:npm i element-ui -S。然后,在项目的main.js文件中,通过import导入ElementUI,并使用Vue.use(ElementUI)来全局使用ElementUI组件。如果你只想按需引入某个组件,你可以使用import {ComponentName} from 'element-ui'来导入指定的组件,然后使用Vue.use(ComponentName)来使用该组件。\[2\] 在Vue + ElementUI项目中,你可以使用computed属性来定义一个函数来渲染组件。例如,你可以在computed中定义一个名为noChildren的函数,用于过滤出没有子级的一级菜单。然后,你可以使用v-for指令来遍历过滤出来的一级菜单,并在相应位置进行呈现。例如,你可以使用<el-menu-item>来呈现每个一级菜单项,并使用相应的数据来设置index、key、图标和标题等属性。\[3\] 总结起来,Vue + ElementUI是一个用于构建后台管理系统的前端项目,它结合了Vue框架和ElementUI组件库。你可以通过安装ElementUI并在项目中使用它的组件来构建出美观、易用的后台管理界面。在渲染组件方面,你可以使用computed属性来定义函数来过滤和呈现数据。 #### 引用[.reference_title] - *1* *2* *3* [Vue + Element-UI —— 项目实战(一)](https://blog.csdn.net/qq_45902692/article/details/125079634)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值