我的第一个vue项目记录——电商后台管理

5,508 阅读31分钟

项目概述

项目并不是我独立开发出来的,毕竟这是我的第一个vue项目,我只是菜鸡一只, 在B站上看到了黑马的这个项目,通过做这个项目,然后记录下来,让自己成长,收获的更多一些。

  • 感兴趣的同学可以在我的github上下载项目的源代码:github.com/Angus2333/v…

  • 功能:电商后台管理系统用于管理用户账号、商品分类、商品信息、订单、数据统计等业务功能

  • 开发模式:电商后台管理系统整体采用前后端分离的开发模式,其中前端项目是基于 Vue 技术栈的 SPA 项目

  • 电商后台管理系统的技术选型

    • 前端项目技术栈
      • Vue
      • Vue-router
      • Element-UI
      • Axios
      • Echarts
    • 后端项目技术栈
      • Node.js
      • Express
      • Jwt
      • Mysql
      • Sequelize

登录/退出功能

一、登录概述

1、登录业务流程

  • 在登录页面输入用户名和密码
  • 点击登录按钮,调用后台接口进行验证
  • 通过验证后,根据后台的响应状态跳转到项目主页

2、登录业务相关技术点

  • http是无状态的
    • 如果前端和后台接口之间不存在跨域问题,推荐使用cookie和session记录登录状态
    • 如果存在跨域问题,则推荐使用token方式维持状态

二、登录 — token 原理分析

三、登录功能实现

1、准备

  • 将项目文件夹在VS Code中打开,先新建一个终端输入git status查看工作区是否干净
  • 如果工作区显示是干净的,继续输入git checkout -b login创建一个分支,并且切换到了该分支上(不同的功能尽量在不同的分支上完成)
  • 打开Vue的可视化面板,选中任务栏-->server-->启动,等项目编译完成后,即可点击启动app
  • 打开代码编辑器,选中src文件夹-->main.js,该文件时整个项目的入口文件
  • 梳理App.vue文件,删除项目创建时默认生成的一些HTML标签,样式,和js代码
  • 删除项目文件创建时默认生成的一些没用的组件

2、创建登录组件

  • 在component文件夹中创建一个登录组件Login.vue

    //支持less语法,需要安装less-loader和less开发依赖,scoped控制组件样式生效的区间

  • 在路由文件中导入登录组件,并添加路由规则

    import Login from '../components/Login.vue' const routes = [ 
    //重定向路由(当用户访问/时,会自动定向到登录路由)
    { path: '/', redirect: '/login' }, //登录组件路由 
    { path: '/login', component: Login } 
    ]
    
  • 在根组件中添加占位符

3、登录页面基本布局

  • 需要在assets文件夹下新建一个css文件夹

  • 在css文件夹中创建一个全局样式文件global.css,并书写:

    html, body, #app { height: 100%; margin: 0; padding: 0; }
    
  • 将全局样式导入到入口文件main.js中

    import './assets/css/global.css'
    
  • 在Element官网选择需要的组件代码粘贴到登录组件的相应位置,再更具需要修改

    登录 重置

  • 因为在安装Element插件时配置的是按需导入,因此需要在plugins文件夹中的element.js文件中导入需要用到的组件,并注册为全局可用

  • 给输入框添加小图标,方便用户区分文本框和密码框

    • Element官网中给输入框添加icon的方法有两种,这里选择通过prefix-icon属性在 input 组件首部和尾部增加显示图标

    • 再到Element官网提供的icon图标库中找需要的图标

    • 还可以到第三方icon图标库中寻找需要的图标,前提是需将第三方图标库导入到入口文件main.js中

4、登录表单数据绑定

根据在Element官网我们所选择的表单组件中绑定数据的方式去绑定我们需要的数据 给密码框添加type='password'属性,将密码隐藏

<el-form class="form_box" :model="loginForm">
    <el-form-item>
      <el-input v-model="loginForm.username" prefix-icon="iconfont icon-user"></el-input>
    </el-form-item>
    <el-form-item>
      <el-input v-model="loginForm.password" prefix-icon="iconfont icon-3702mima" type="password"></el-input>
    </el-form-item>
  <el-form-item class="btns">
    <el-button type="primary">登录</el-button>
    <el-button type="info">重置</el-button>
  </el-form-item>
</el-form>

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

5、登录表单数据验证

  • 根据在Element官网中Form 组件提供了表单验证的功能,只需给表单绑定 rules 属性,并传入约定的验证规则,并将 Form-Item 的 prop 属性设置为需校验的字段名即

    登录 重置

    data () { 
    return { // 登录表单数据绑定对象 
    loginForm: { 
    username: '', password: '' 
    }, // 登录表单数据验证 
    loginFormRules: { 
    username: [ { required: true, message: '请输入用户名', trigger: 'blur' }, 
    { min: 3, max: 10, message: '长度在 3 到 10 个字符', trigger: 'blur' } ], 
    password: [ { required: true, message: '请输入密码', trigger: 'blur' }, 
    { min: 6, max: 15, message: '长度在 6 到 15 个字符', trigger: 'blur' } ]
     } 
    } 
    }
    

6、点击重置按钮实现表单重置

  • 想要重置表单,首先要获取表单的实例对象,根据Element官网给Form表单组件提供的方法,首先通过ref给表单添加一个引用对象,之后只要获取到这个引用对象(可以任意命名,只要合法),就可以获取到这个表单的实例对象

  • 给重置按钮添加单机事件,并命名事件处理函数

    <el-button type="info" @click="loginFormReset">重置
    
  • 在行为区定义事件处理函数,事件处理函数中的this就是这个组件的对象,通过打印这个this可以看到里面有个$ref属性,它的值是一个对象,这个对象中就包含之前给表单添加的ref引用对象,获取这个引用对象,就是获取到了登录表单的实例对象。

  • 使用Element官网在Form表单组件中提供的resetFields方法(对整个表单进行重置,将所有字段值重置为初始值并移除校验结果)表单实例方法就可以重置表单

    methods: { 
    // 重置表单事件 loginFormReset () {
    // console.log(this);
    this.$refs.loginFormRef.resetFields() 
    } 
    }
    

7、点击登录,对表单数据进行预验证(在发送请求之前)

  • 添加表单引用对象

  • 给登录按钮添加单机事件,并命名事件处理函数

    <el-button type="primary" @click="login">登录
    
  • 定义事件处理函数

  • 使用Element官网在Form表单组件中提供的validate(对整个表单进行校验的方法,参数为一个回调函数。该回调函数会在校验结束后被调用,并传入两个参数:是否校验成功和未通过校验的字段。若不传入回调函数,则会返回一个 promise)表单实例方法就可以对表单进行预验证

    login () { 
    this.$refs.loginFormRef.validate(bool => { 
    console.log(bool) 
    }) 
    }
    

8、发送登录请求(通过预验证后)

  • 在入口文件导入axios,将其挂载到Vue的原型对象上,并配置请求根路径

    // 引入
    axios import axios from 'axios' 
    // 配置请求根路径 
    axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/' 
    // 将axios挂在到Vue原型对象上
     Vue.prototype.$http = axios
    
  • 在登录事件处理函数中发送请求(异步),使用async/await关键字处理异步代码,返回值是一个对象,其中只有data属性中包含的是有用的服务器返回数据,所以将其解构赋值到一个变量

    login () {
     this.refs.loginFormRef.validate(async bool => { 
    if (!bool) return 
    // this.loginForm就是需要传递的表单数据 
    const { data: res } = await this.http.post('login', this.loginForm) 
    console.log(res)
     }) 
    }
    

    9、告诉用户登录结果

  • Element官网提供了Message 消息提示的组件,在element.js文件中导入需要用到的组件,该组件需要进行全局挂载,而不是全局注册

    import { Message } from 'element-ui' Vue.prototype.$message = Message
    
  • 根据接收到的登录请求返回的状态码来提示用户是否登录成功

    login () {
     this.refs.loginFormRef.validate(async bool => { 
    if (!bool) return 
    // this.loginForm就是需要传递的表单数据 
    const { data: res } = await this.http.post('login', this.loginForm) 
    if (res.meta.status !== 200) return this.message.error('登录失败!') 
    return this.message.error(′登录失败!′)
    return this.message.success('登录成功!') }) }
    

10、登录成功之后的行为

  • 将登录成功后,服务器返回的token保存到客户端的sessionStorge中,保持登录状态(sessionStorge和localStorge保存期限不同,sessionStorge在窗口关闭后会删除保存的数据,localStorge没有保存期限);所有API接口除了登录接口外,都需要登录之后才能访问

  • 通过编程式导航跳转到后台主页,路由地址是/home(在 Vue 实例内部,你可以通过$router 访问路由实例。因此你可以调用 this.$router.push

    login () { 
    this.refs.loginFormRef.validate(async bool => { 
    if (!bool) return
     // this.loginForm就是需要传递的表单数据 
    const { data: res } = await this.http.post('login', this.loginForm) 
    if (res.meta.status !== 200) 
    return this.message.error('登录失败!') 
    this.message.error(′登录失败!′)
    this.message.success('登录成功!') 
    window.sessionStorage.setItem('token', res.data.token) 
    this.$router.push('/home')
     })
     }
    
  • 创建home组件,在路由文件中导入组件,并添加路由规则

11、路由导航守卫

  • 在路由文件中添加导航守卫:如果用户没有登录,用户可以通过直接输入URL地址访问页面,这是不合理的,因此需要重新导航到登录页面

    import Vue from 'vue' 
    import VueRouter from 'vue-router' 
    import Login from '../components/Login.vue'
     import Home from '../components/Home.vue'
    Vue.use(VueRouter)const routes = [ { path: '/', redirect: '/login' },
     { path: '/login', component: Login }, { path: '/home', component: Home } 
    ]
    const router = new VueRouter({ routes })
    
    // 路由导航守卫 
    router.beforeEach((to, from, next) => { 
    // to 将要访问的路径
     // from 正要离开的路径 
    // next() 
    允许跳转 next('/home')
     强制跳转的路径
     if (to.path === '/login') 
    return next()
    // 获取token 
    const tokenStr = window.sessionStorage.getItem('token') 
    if (!tokenStr) return next('/login') 
    next() 
    })
    export default router
    

12、实现退出功能

  • 在Home组件中暂时添加一个退出按钮

  • 给按钮绑定单机事件,并命名事件处理函数

  • 定义事件处理函数,基于token实现退出功能只需销毁本地token即可

13、提交登录功能代码

  • 在login分支上将所有改动过的文件添加到暂存区git add .
  • 将暂存区的文件都提交的本地仓库git commit -m '提示信息'
  • 切换到master主分支合并login分支代码git merge login
  • 将最新的本地代码推送到码云git push
  • 将本地的login分支推送到码云
    • 先切换到login分支git checkout login
    • 首次推送分支git push -u origin login

主页布局

1.整体布局

  • 在Elment官网中找到符合要求的布局容器组件,,将代码复制到Home.vue中,根据具体需求进行梳理
  • 在在element.js文件中导入需要用到的组件,并注册为全局组件
  • 完善css样式

2、头部布局

  • 更具要求完善HTML布局
  • 完善css样式

3、左边栏基本布局

  • 在Elment官网找到符合要求的菜单组件,将代码复制到Home.vue中
  • 在element.js文件中导入需要用到的组件,并注册为全局组件
  • 根据具体需求将结构进行梳理,在element.js文件中将没用到的组件删除

4、通过接口获取菜单数据渲染到页面

  • 根据API接口文档中的要求,需要在入口文件main.js中,利用axios拦截器为所有API请求在请求头中添加一个 Authorization 字段提供 token 令牌

    // 给请求头添加字段 
    axios.interceptors.request.use((config) => { 
    // console.log(config) 
    config.headers.Authorization = window.sessionStorage.getItem('token') 
    return config 
    })
    
  • 页面开始加载前就要调用API接口获取所有菜单,所以在行为区域调用生命周期函数created()

  • methods中定义一个向服务器端发送请求以获取菜单数据的函数(注意需要处理请求失败的情况),使其在生命周期函数created()中立马被调用

  • 将获取到的数据立即挂载到data中,在data中定义一个数组(根据API接口返回的数据格式),用来存储返回的数据

    export default { 
    data () { 
    return {
     menuList: [] } }, 
     created () {
     this.getMenuList()
     }, 
    methods: { 
    async getMenuList () { 
      const { data: res } = await this.http.get('menus') 
      // console.log(res) 
      if (res.meta.status !== 200) 
      return this.http.get(′menus′)
      //console.log(res)
      if(res.meta.status!==200)
      returnthis.message.error('数据获取失败!')
      this.menuList = res.data 
     // console.log(this.menuList) 
     }
     }
     }
    
  • 根据返回的数据结构,将菜单数据渲染到主页左侧菜单栏(控制菜单打开关闭的是属性index,因此注意给index动态绑定唯一的值)

5、左侧菜单栏样式和功能优化

  • 给一级菜单添加字体图标时需要用到第三方图标库,采用的是动态添加类名的方式添加字体图标,以下是解决方案:

    • 在行为区域data中创建一个对象用来保存需要用到的第三方字体图标,键是菜单数据中一级菜单的id,值就是对应的字体图标的类名

      data () { return { menuList: [], iconsObj: { 125: 'iconfont icon-users', 103: 'iconfont icon-tijikongjian', 101: 'iconfont icon-shangpin', 102: 'iconfont icon-danju', 145: 'iconfont icon-baobiao' } } },
      
    • 在模板中动态绑定class

  • 左侧菜单栏只允许一个一级菜单是展开状态,Elemnet官网导航菜单组件中提供了一个属性unique-opened

  • 解决菜单栏边框线对不齐的问题,打开页面调试工具可以发现菜单是有边框的,去掉即可

6、左侧菜单栏的折叠与展开功能

  • 添加功能结构

  • 完善样式

  • 绑定单击事件,并命名事件函数

  • 定义事件函数,实现折叠与展开功能

    • Elemnet官网导航菜单组件中提供了一个属性collapse控制菜单的折叠与展开

    • data中定义一个变量存放布尔值:isCollapse: false,再与collapse动态绑定::collapse="isCollapse"

    • 在事件函数中控制这个变量的布尔值切换即可

      toggloBtn () { this.isCollapse = !this.isCollapse }

    • Elemnet官网导航菜单组件中提供了一个属性collapse-transition可以控制是否开启折叠与展开的动画

    • 根据折叠与展开的状态,利用三元表达式动态绑定侧边栏的宽度

      <el-aside :width="isCollapse ? '64px' : '200px'">
        <div class="togglo_button" @click="toggloBtn">|||</div>
      

7、首页路由重定向

  • 新建一个Welcome.vue组件

  • 在路由文件引入该组件

  • 添加路由规则/welcome,该路由规则是/home路由的子路由,同时将/home重定向到该路由规则

    const routes = [ { path: '/', redirect: '/login' }, { path: '/login', component: Login }, { path: '/home', component: Home, redirect: '/welcome', children: [ { path: '/welcome', component: Welcome } ] } ]
    
  • 在Home页面的右侧主体区添加路由占位符

8、左侧菜单改造为路由连接

  • Elemnet官网导航菜单组件中提供了一个属性router,可以控制是否启用vue-router的模式(启用该模式会在激活导航时以 index 作为 path 进行路由跳转)
  • 前面获取的菜单数据中有一个path关键字,里面存放的值就可以作为路由,将其作为二级菜单的index属性的值

用户管理开发

1、将用户列表页定义到Home页的子路由

  • 在component文件夹中新建一个user文件夹,并在其中创建一个User.vue组件

  • 在路由文件引入User.vue组件

  • /home路由的子路由中新增一个/user子路由规则

  • Elemnet官网导航菜单组件中提供了一个属性default-active,将当前激活的菜单的index属性的值赋值给default-active,就可以高亮显示当前激活的菜单

    • 给当前激活的菜单绑定单击事件,将index的值作为实参传递给事件处理函数

    • 定义处理事件函数:将index的值保存到sessionStorage中,同时data中的activePath变量也要被动态赋值

      saveClkStatus (activePath) { window.sessionStorage.setItem('activePath', activePath) this.activePath = activePath }

    • data中定义一个变量activePath用来保存被激活的链接,默认值为空字符串:activePath: ''

    • 在生命周期函数created()中给变量量activePath赋值,这个值就是sessionStorage中存储的index的值(这样即使刷新页面,default-active属性依然可以获得当前激活菜单的index值,以保持该菜单的激活状态)

      created () { this.getMenuList() this.activePath = window.sessionStorage.getItem('activePath') },

2、绘制用户列表页UI结构

  • Elemnet官网中找到合适的面包屑导航组件,并梳理
  • 在element.js中按需导入需要的组件,并全局引用
  • Elemnet官网中找到合适的卡片视图组件,梳理并按需导入
  • 如果需要覆盖默认样式,可以将相关样式写到全局样式文件中
  • Elemnet官网中找到合适的搜索组件,同时可以用 Elemnet官网中提供的布局组件进行布局,梳理并按需导入

3、获取用户列表数据

  • 在页面加载时就要获取用户列表数据,因此在生命周期函数created()中调用一个获取用户列表数据的函数
  • methods中定义获取用户列表数据的函数
  • data中定义一个变量,用来保存获取用户列表数据时需要传递的参数(根据API文档的要求),用async/await关键字优化异步请求(需要处理数据请求成功与否返回的状态)
  • 根据返回的用户列表数据,在data中定义一些变量,用来保存可能需要用到的数据

4、用户列表数据渲染

  • Elemnet官网中找到合适的表格组件,梳理并按需导入

  • 根据需求,按照Elemnet官网提供的表格组件属性来将表格进行优化

  • 自定义列状态栏的渲染,利用作用域插槽:根据Elemnet官网表格组件中提供的自定义列的显示内容,可组合其他组件使用(通过 Scoped slot 可以获取到 row, column, $index 和 store(table组件内部的状态管理)的数据)

  • 自定义列操作栏的渲染,利用作用域插槽

    • 添加三个图标按钮组件,更具需求梳理

    • 给按钮添加文字提示组件

  • 实现数据分页效果

    • Elemnet官网中找到合适的分页组件,梳理并按需导入
    • 根据组件的各项方法和属性实现分页功能
    • 完善样式

5、实现用户状态的修改

  • switch开关组件提供一个change事件,switch状态发生变化时出发这个函数,给switch开关绑定这个change事件,并命名传递一个实参scope.row

    <el-table-column prop="mg_state" label="状态">
          <template slot-scope="scope">
            <!-- {{scope.row}} -->
            <el-switch v-model="scope.row.mg_state" active-color="#13ce66" inactive-color="#ff4949" @change="usersStatus(scope.row)"></el-switch>
          </template>
        </el-table-column>
    复制代码
    
  • 定义事件处理函数,并接受参数

  • 在处理函数中向服务器发送更改状态的请求,并用async/await关键字处理异步请求函数,解构接收返回值(注意处理请求失败的情况,如果失败,switch开关要恢复原来的状态,就是参数中保存的状态值取反)

    async usersStatus (userSta) {
      console.log(userSta)
      const { data: res } = await this.$http.put(`users/${userSta.id}/state/${userSta.mg_state}`)
      // console.log(res)
      if (res.meta.status !== 200) {
        userSta.mg_state = !userSta.mg_state
        return this.$message.error('更新用户状态失败!')
      }
      this.$message.success('更新用户状态成功!')
    }
    复制代码
    

6、实现用户搜索功能

  • 给搜索框进行双向数据绑定,绑定的数据对象就是reqParams.query

  • 给搜索按钮绑定单击事件,事件的处理函数就是之前定义过的获取用户列表数据的函数getUsersList()

  • Elemnet官网中给input输入框组件提供了clearable属性,可以清空输入框

  • Elemnet官网中给input输入框组件提供了clear事件,给输入框绑定这个事件并且调用getUsersList()函数,就可以实现在搜索框清空的状态下重新显示所有数据

    <el-input placeholder="请输入内容" v-model="reqParams.query"  clearable @clear="getUsersList">
              <el-button slot="append" icon="el-icon-search" @click="getUsersList"></el-button>
            </el-input>
    复制代码
    

7、实现添加用户的功能

  • 添加用户对话框的显示与隐藏

    • 在Elemnet官网中找到Dialog对话框组件,梳理并按需导入

    • data中定义一个变量用来控制对话框的显示和隐藏addDialogVisible: false,并将该变量动态绑定到对话框组件的visible.sync属性和按钮的单击事件上,在按钮的单击事件中给该变量赋值取反

      这是一段信息

      取 消 确 定 复制代码

    • 给之前添加用户的按钮绑定单击事件控制对话框的显示,在单击事件中给该变量赋值取反

      <el-button type="primary" @click="addDialogVisible = !addDialogVisible">添加用户
      
  • 在添加用户的对话框中渲染一个添加用户的表单

    • 更改对话框的头部提示
    • 在Elemnet官网中找到Form表单框组件,按照需求梳理,并按需导入需要的组件
    • 将表单进行数据绑定,并在data中根据需求和API接口要求定义这个需要绑定的数据
    • 将表单进行验证规则绑定,并在data中根据需求定义表单验证规则
    • ref属性给表单添加一个引用对象
  • 实现自定义验证规则

    • 根据Elemnet官网中提供自定义验证规则的组件实例,在data中自定义需要的验证规则

      data () { //
       自定义手机号验证规则 var checkMobile = (rules, val, callBack) => { 
      const regMobile = /^[1][3,4,5,7,8][0-9]{9}$/ 
      if (regMobile.test(val)) { 
      return callBack() 
      }
       callBack('请输入正确的手机号') }
      
    • 在添加验证规则区域引用自定义的验证规则

      { validator: checkMobile, trigger: ['blur', 'change'] }
      
  • 实现添加用户表单重置功能(表单关闭时重置)

    • 给对话框绑定关闭事件(Elemnet官网提供的close事件),并命名事件处理函数

      <el-dialog title="添加用户" :visible.sync="addDialogVisible" width="50%" @close="addFormClose"> 复制代码
      
    • 定义事件处理函数,使用表单的引用对象来重置表单

      // 添加用户对话框关闭时重置表单 addFormClose () { this.$refs.addFormRef.resetFields() }
      
  • 添加用户的预验证功能,给添加按钮的单击事件命名事件处理函数,并定义这个处理函数,同时通过Elemnet官网表单组件提供的validate()方法提供添加预验证功能

  • 预验证成功后发起添加用户的请求,用async/await关键字处理请求,解构接收有用的返回数据

  • 根据返回的状态码提示用户是否添加用户成功

  • 同时实现关闭添加用户的对话框,调用获取用户列表数据的函数重新渲染用户列表页

    // 添加用户提交 addFormSub () {
     this.refs.addFormRef.validate(async bol => { 
    if (!bol) return const { data: res } = await this.http.post('users', this.addForm) 
    if (res.meta.status !== 201) {
     return this.message.error(res.meta.msg) }
     this.message.success('添加用户成功!')
     // 关闭对话框
     this.addDialogVisible = !this.addDialogVisible
     // 重新获取用户列表数据 
    this.getUsersList() }) }
    

8、实现修改用户的功能

  • 修改用户的对话框的显示与隐藏
    • 点击修改按钮弹出修改用户信息的对话框,给修改按钮绑定单击事件,并命名
    • 定义事件处理函数,控制修改用户信息的对话框的显示和查询需要修改的用户信息
    • 在页面结构区域添加一个修改用户信息的对话框
  • 根据id查询用户的旧数据
    • 编辑按钮所在的插槽作用域中的scope.row。id中存储的就是用户的id,可以将其作为实参传递到控制修改用户信息对话框的显示的函数中
    • 根据该id发送查询用户信息的请求
    • 根据返回的用户信息数据格式,在data中定义相同数据格式的变量用来保存获取到的用户数据
  • 在修改用户的对话框中渲染一个修改用户的表单
    • 在页面结构中引入合适的表单组件,根据需求梳理
    • 根据需求,将获取的数据渲染到表单中(用户名不能修改)
    • 对需要的数据添加和定义验证规则
  • 实现修改用户对话框关闭时的表单重置功能
  • 实现根据API接口文档提交修改表单和提交前的预验证

9、实现删除用户的功能

  • 给删除按钮绑定单击事件,定义事件处理函数,传递需要被删除的用户的id作为实参
  • 当梳理函数执行时首先弹出提示框,提示用户是否继续执行删除操作
  • 在Elemnet官网中MessageBox弹框组件有实现弹窗的使用方法,按需导入用到的组件,并需要全局挂载要用的方法
  • 当用户点击确定时,根据传递的用户id向服务器发送删除该用户的请求,并提示用户删除成功,并重新获取用户列表数据
  • 当用户点击取消时,提示用户删除操作取消

10、提交用户列表功能代码

  • 在Git中新建一个user分支,将实现用户列表功能的代码都提交到暂存区
  • 将完成的代码提交到user分支
  • 将新建的user分支推送到云端
  • 切换到主分支,将user中代码合并到主分支
  • 将最新的本地代码提交到云端中

权限管理开发

一、创建一个新的分支rights并上传到云端,接下来在这个分支上开发权限管理的相关功能

二、权限列表页

1、新建权限列表页

  • 在component文件夹中新建一个power文件夹,在该文件夹中新建权限列表页组件
  • 将该组件导入路由文件中,并在/home路由的子路由中创建权限列表页的路由规则

2、权限列表页基本布局

  • 面包屑导航组件
  • 卡片组件

3、获取权限列表数据

  • API接口文档中提示的获取到的数据类型有两种list和tree,根据需求发送合适的请求

4、将权限列表数据渲染到页面中

  • 用作用域插槽的形式美化权限等级列(在Elemnet官网中有合适的tag组件)

三、角色列表页

1、权限管理业务分析

  • 给用户分配具备不同权限的角色,以达到给不同用户有不同权限的目的

2、新建角色列表组件

  • 在power文件夹中新建角色列表组件
  • 将该组件导入路由文件中,并在/home路由的子路由中创建权限列表页的路由规则

3、权限列表页基本布局

4、获取权限列表数据

5、将权限列表数据渲染到页面中

  • 用作用域插槽来渲染角色所拥有的权限详情
  • 使用Layout布局中的组件来布局权限详情展示
  • 样式完善,同时给页面加最小宽度,以防浏览器宽度变小时,页面样式变形
  • 给三级权限的关闭事件绑定一个事件处理函数,该事件用来控制删除当前权限,为防止每删除一次权限,浏览器就重新获取一次数据,可以直接将删除请求的返回值反向赋值给当前角色的权限数据

6、分配权限按钮功能

  • 分配权限对话框的显示与隐藏

  • 获取所有权限列表,这是需要的数据形式时tree状的(在对话框显示的时候)

  • 将获取的数据渲染到对话框中(Element官网中有树形组件)

  • 根据Element官网中有树形组件提供的属性default-checked-keys,它需要动态绑定一个需要勾选的权限的数组,这个数组中的元素都是需要被勾选的权限对应的id值

  • 通过递归函数来获取所有需要默认勾选的权限的id

  • 每次关闭权限分配对话框时需要清空用来保存权限id的数组

  • 实现给角色添加和减少权限的功能

    • 给对话框的确定按钮绑定单击事件

    • 定义事件处理函数,给服务器发送修改角色权限的请求

      • 根据API接口的要求,传递的参数应该是用逗号分割的包含半选中和被选中权限的id组成的字符串
      • Element官网在树形控件中提供了获取含半选中和被选中项id的方法getCheckedKeysgetHalfCheckedKeys(获取引用实例可以调用这些方法)
    • 请求成功后刷新页面和关闭对话框

      // 提交需要添加和删除的角色权限 
      async rightsAllotSub () {
       // 被选中和半选中项的id组成的数组 ...为展开运算符
       const keysArr = [ ...this.refs.getKeysRef.getCheckedKeys(), ...this.refs.getKeysRef.getCheckedKeys(),...this.refs.getKeysRef.getHalfCheckedKeys() ]
       // 转换成字符串
       const idStr = keysArr.join(',') 
      // 发送请求
       const { data: res } = await this.http.post(`roles/http.post(‘roles/{this.rolesId}/rights`, { rids: idStr }) 
      if (res.meta.status !== 200) { 
       return this.message.error('分配角色权限失败!') }
       this.message.success('分配角色权限成功!')
       this.getRolesList() 
       this.rightAllotShow = false }
      

7、在用户列表中完善角色分配的功能

8、完善角色列表的添加角色和删除编辑等功能

9、提交代码

商品管理开发

一、商品分类页

1、基础页面布局

2、获取商品分类数据

3、渲染商品分类数据到页面

  • 安装第三方依赖插件vue-table-with-tree-gird到运行依赖
  • 参照插件文档渲染数据

4、添加分类功能

  • 完成对话框的显示和隐藏,完善表单结构
  • 获取父级分类数据,不许要的第三级(单独定义该请求)
  • 利用Element官网提供的级联选择器组件(props属性时必须配置的),渲染父级分类数据到表单
  • 按照API要求发送添加请求

5、编辑分类功能

6、删除分类功能

7、代码提交

二、商品参数页

1、创建组件并导入路由文件,创建路由规则

2、基础页面布局,同时完成商品分类级联选择器数据渲染

3、根据级联选择器是否有选项来判定添加参数和添加属性的按钮启用和禁用

  • 利用Vue的计算属性来监听级联选择器所产生的选中项生成的数组是否有元素,来决定按钮的禁用和启用

4、根据级联选择器中选中的商品分类获取参数数据列表和属性列表

  • 用计算属性来监听选择器是否有选中的分类,如果有返回该分类id,如果没有则返回null
  • 请求中需要的sel参数可以时默认选中的标签页所对应的值
  • 获取参数列表的函数可以放在一个方法中,防止多次要调用
  • 发送请求的函数中需要判断接收的函数时动态参数的,还是静态属性的,他们要分别存放在不同的变量中,因为他们要渲染在不同的标签页下

5、数据渲染到标签页

  • 渲染参数的可选项和静态属性的值时,需要将返回的数据先转换成数组的形式方便循环渲染

    • 解决在渲染目标值为空字符串时的情况
  • 完成点击添加标签按钮可以添加参数可选项和属性值

    • 因为数据时模板渲染的,所以要解决点击添加标签按钮所有参数和属性都会同步添加的问题

    • 让添加的文本框自动获得焦点可以用:

      this.nextTick(_ => { this.refs.saveTagInput.$refs.input.focus(); });

    $nextTick方法的作用就是当页面上元素被重新渲染之后,才会执行回调函数中的代码,在这就是在文本框被渲染完成后执行回调函数中的代码

    • 发送请求向服务器端保存添加的参数可选项和属性的值
    • 完成删除参数可选项和属性的值的功能

6、添加参数和属性按钮功能(共用对话框)

7、修改参数或属性按钮功能(共用对话框)

8、删除参数或属性按钮功能

9、提交代码

三、商品列表页

1、创建一个新的分支,并同步到云仓库

2、创建组件并导入路由文件,创建路由规则

3、基础页面布局

4、获取商品列表数据

5、数据列表渲染

  • 在入口文件中全局定义一个时间处理函数,来转换时间显示样式(Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 )

6、分页功能

7、商品搜索功能

8、商品添加功能

  • 点击添加商品按钮跳转到添加商品页
  • 新建添加商品页组件,并导入路由文件,创建路由规则
  • 添加商品页布局
  • 步骤条和标签页同步(数据联动)
  • 注意表单组件的放置位置
  • 阻止标签页的切换,在必要的时候(利用标签页组件中提供的before-leave属性)
  • 获取服务端参数列表数据,渲染到商品参数面板和商品属性面板,样式优化
  • 利用上传组件上传图片(注意使用绝对路径,上传图片也是需要登录权限的,所以要添加请求头,并根据添加商品请求中参数的格式要求,将图片的临时路径同步到添加商品表单的数据中)
  • 完善图片上传后需要删除的功能
  • 完善图片预览功能
  • 在商品内容区渲染一个富文本编辑器(需要安装运行依赖vue-quill-editor
  • 点击商品内容区最后添加商品按钮发送添加商品请求
    • 预验证
    • 在按照服务器端请求数据的格式转换商品分类id组成的数组时,如果在原数据中将其格式进行转换的话,运行代码会报错,因为在基本信息区域中选择商品分类的级联选择器绑定的数据形数组,改成字符串后会报错(解决方案就是安装一个运行依赖lodash,器中提供了对数据进行深拷贝的方法cloneDeep()
    • 处理动态参数和静态属性
    • 发送请求

9、商品删除功能

10、提交代码

订单管理开发

1、新建分支,并同步到云端

2、新建组件,导入,创建路由

3、基础布局,获取订单数据并渲染

4、修改地址对话框显示隐藏功能和省市区三级地址联动(素材中有省市区地址数据)

5、物流进度对话框显示隐藏功能(时间线组件不在安装的element-ui插件中,需要手动导入新版本的组件)

6、提交代码

数据统计开发

1、新建分支,并同步到云端report

2、新建组件,导入,创建路由

3、基础布局,获取报表数据并渲染

  • 安装运行依赖echats
  • 根据echats官方文档提供的基础步骤渲染报表
    • 发送请求获取数据
    • 根据服务器接口文档的提示,将获取到的报表数据先和文档中的数据合并后再渲染(可以lodash中提供的merge()方法合并数据)

4、提交代码

项目优化

1、可视化UI面板控制台中的项目警告处理和功能优化

  • 安装nprogress依赖,在入口文件中导入插件和里面的样式,参照官方文档给页面顶部加上进度条(利用请求拦截器和响应拦截器)
  • 在生产环境中将代码中所有的console.log代码都移除(在依赖中安装开发依赖插件babel-plugin-transform-remove-console最后在项目根目录中的babel.config.js文件中的plugins中配置"transform-remove-console"
    • 需解决的问题时babel.config.js文件中的配置是全局生效的,这样会影响到在开发环境中我们调试代码
    • 解决方法就是在babel.config.js文件的头部定义一个保存"transform-remove-console"该项配置的变量,该变量先默认为空数组,接下来判断当前所处的环境是开发环境还是生产环境,如果是生产环境就给该数组添加"transform-remove-console"配置项,否则数组任为空,将该变量用展开运算符添加到plugins的配置中

2、生成打包报告

  • 在可视化的UI面板中查看控制台和分析面板,看到项目中所存在的问题,资源文件和依赖项文件等文件大小是否需要优化

  • 通过vue-cil3.0工具生成的项目,默认隐藏了所有webpack的配置项,如果需要修改webpack默认的配置项,可以在项目根目录中按需创建vue.config.js配置文件(具体配置参考 cli.vuejs.org/zh/config/#…

  • 为开发模式和发布模式指定不同的打包入口:默认情况下,Vue项目的开发模式与发布模式,共用同一个打包的入口文件(即 src/main.js)。为了将项目的开发过程与发布过程分离,我们可以为两种模式,各自指定打包的入口文件

    • 开发模式的入口文件为 src/main-dev.js
    • 发布模式的入口文件为 src/main-prod.js
  • configureWebpack 和 chainWebpack:在 vue.config.js 导出的配置对象中,新增 configureWebpack 或 chainWebpack 节点,来自定义 webpack 的打包配置,configureWebpack 和 chainWebpack 的作用相同,唯一的区别就是它们修改 webpack 配置的方 式不同

    • chainWebpack 通过链式编程的形式,来修改默认的 webpack 配置
    • configureWebpack 通过操作对象的形式,来修改默认的 webpack 配置(两者具体的使用差异,可参考如下网址:cli.vuejs.org/zh/guide/we…
  • 通过 chainWebpack 自定义打包入口,先将入口文件名该成main-dev.js的开发模式入口文件,复制其中的代码,创建一个发布模式的入口文件main-prod.js粘贴开发模式入口文件中的代码

    • 在vue.config.js文件中配置如下代码

      chainWebpack: config => { // 发布模式 config.when(process.env.NODE_ENV === 'production', config => { config.entry('app').clear().add('./src/main-prod.js') }) // 开发模式 config.when(process.env.NODE_ENV === 'development', config => { config.entry('app').clear().add('./src/main-dev.js') }) } 复制代码

3、第三方库启用CDN

  • 通过 externals 加载外部 CDN 资源:默认情况下,通过import语法导入的第三方依赖包,最终会被打包合并到同一个文件中,从而导致打包成功后,单文件体积过大的问题,可以通过 webpack 的 externals 节点,来配置并加载外部的 CDN 资源。凡是声明在 externals 中的第三方依赖包,都不会被打包

    • 在vue.config.js文件中的发布模式具体配置代码如下

      config.set('externals', { vue: 'Vue', 'vue-router': 'VueRouter', axios: 'axios', lodash: '_', echarts: 'echarts', nprogress: 'NProgress', 'vue-quill-editor': 'VueQuillEditor' })
      

      同时,需要在 public/index.html 文件的头部,添加如下的 CDN 资源引用:

    • 同时,需要在 public/index.html 文件的头部,添加如下的 CDN 资源引用:

4、Element-UI组件按需加载

  • 通过 CDN 优化 ElementUI 的打包:虽然在开发阶段,我们启用了element-ui组件的按需加载,尽可能的减少了打包的体积,但是那些被按需加载的组件,还是占用了较大的文件体积。此时,我们可以将element-ui中的组件,也通过CDN的形式来加载,这样能够进一步减小打包后的文件体积

    • 在 main-prod.js 中,注释掉 element-ui 按需加载的代码

    • 在 index.html 的头部区域中,通过 CDN 加载 element-ui 的 js 和 css 样式(版本号要与package.json中一致)

5、路由懒加载

  • 当打包构建项目时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成 不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了

    • 安装 @babel/plugin-syntax-dynamic-import 包

    • 在 babel.config.js 配置文件中声明该插件

    • 将路由改为按需加载的形式,示例代码如下

      const Foo = () => import(/ webpackChunkName: "group-foo" / './Foo.vue') const Bar = () => import(/ webpackChunkName: "group-foo" / './Bar.vue') const Baz = () => import(/ webpackChunkName: "group-boo" / './Baz.vue') 复制代码
      
    • 关于路由懒加载的详细文档,可参考如下链接router.vuejs.org/zh/guide/ad…

6、首页内容定制

  • 不同的打包环境下,首页内容可能会有所不同。我们可以通过插件的方式进行定制,插件配置如下:

    chainWebpack: config => { 
    config.when(process.env.NODE_ENV === 'production', 
    config => { config.plugin('html').tap(args => { args[0].isProd = true return args }) })
    config.when(process.env.NODE_ENV === 'development', config => { config.plugin('html').tap(args => { args[0].isProd = false return args }) }) } 复制代码
    
  • 在 public/index.html 首页中,可以根据 isProd 的值,来决定如何渲染页面结构:

    <!– 按需渲染页面的标题 -->
    <%= htmlWebpackPlugin.options.isProd ? '' : 'dev - ' %>电商后台管理系统 <% if(htmlWebpackPlugin.options.isProd) { %> <% } %> 
    

项目上线

1. 通过 node 创建 web 服务器

  • 创建 node 项目,并安装 express,通过 express 快速创建 web 服务器,将 vue 打包生成的 dist 文件夹, 托管为静态资源即可,关键代码如下:

    const express = require('express')
     // 创建 web 服务器 
    const app = express() 
    // 托管静态资源 
    app.use(express.static('./dist')) 
    // 启动 web 服务器 
    app.listen(80, () => {
     console.log('web server running at http://127.0.0.1') 
    }) 
    

2. 开启 gzip 配置

  • 使用 gzip 可以减小文件体积,使传输速度更快。
    • 安装相应包npm install compression -S
    • 导入包const compression = require('compression');
    • 启用中间件app.use(compression());

3. 配置 https 服务(后端)

  • 传统的 HTTP 协议传输的数据都是明文,不安全

  • 采用 HTTPS 协议对传输的数据进行了加密处理,可以防止数据被中间人窃取,使用更安全

  • 申请 SSL 证书(freessl.org

    • 进入 freessl.cn/ 官网,输入要申请的域名并选择品牌
    • 输入自己的邮箱并选择相关选项
    • 验证 DNS(在域名管理后台添加 TXT 记录)
    • 验证通过之后,下载 SSL 证书( full_chain.pem 公钥;private.key 私钥)
  • 配置 HTTPS 服务:在后台项目中导入证书

    const https = require('https'); 
    const fs = require('fs');
     const options = { 
    cert: fs.readFileSync('./full_chain.pem'),
     key: fs.readFileSync('./private.key')
     } https.createServer(options, app).listen(443);
    

文章部分转载于:jujin.im/post/684490…