前端后台管理系统梳理
再梳理一遍

一、商品后台管理系统
1. 功能
1.1 服务端情况
- 开启了CORS跨域支持
- 需要授权的 API ,必须在请求头中使用
Authorization字段提供token令牌(axios拦截器) baseUrl,接口地址:http://localhost:8888/api/private/v1/
token令牌在服务端生成,当登录成功时,post请求返回的用户信息里包含了这一元素:

1.1.1 axios发ajax请求
Axios:通过promise实现对ajax技术的一种封装,ajax只能访问同源的请求。axios并没有install 方法,所以是不能使用vue.use()方法的。为了不在每个文件都引用一次axios,将它改造成vue插件。
- npm安装
- axios发请求
1)使用
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
2) 改造为vue插件
plugins/http.js
import axios from 'axios'
const httpHelper = {}
// 配置Vue插件
httpHelper.install = function fn (Vue) {
axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1'
Vue.prototype.$http = axios
}
export default httpHelper
main.js里安装插件
import http from '@/plugins/http.js'
Vue.use(http)
1.1.2 拦截器 – 向服务端发请求需要token
httpAxios.install = function (Vue) {
const instance = axios.create({
baseURL: 'http://127.0.0.1:8888/api/private/v1'
})
instance.interceptors.request.use(function (config) {
if (config.url.toLowerCase() !== 'login') {
const token = sessionStorage.getItem('token')
config.headers.Authorization = token
}
return config
}, function (error) {
return Promise.reject(error)
})
Vue.prototype.$http = instance
}
1.1.3 路由导航守卫 – 权限控制
路由的前置守卫 router.js
router.beforeEach((to, from, next) => {
console.log(to, from)
if (to.name === 'login') {
next()
} else {
const token = sessionStorage.getItem('token')
if (!token) {
router.push({ 'name': 'login' })
Message.warning('请先登录')
return
}
next()
}
})
1.1.4 使用async和await
handleLogin () {
this.$http.post('login', this.formData)
.then((res) => {
const data = res.data
const {meta: {status, msg}} = data
if (status === 200) {
const token = data.data.token
sessionStorage.setItem('token', token)
this.$message.success(msg)
} else {
this.$message.error(msg)
}
})
}
async handleLogin () {
const res = await this.$http.post('login', this.formData)
const data = res.data
const { meta: { status, msg } } = data
if (status === 200) {
const token = data.data.token
sessionStorage.setItem('token', token)
this.$message.success(msg)
} else {
this.$message.error(msg)
}
}
外层方法使用async关键字(await最近的外层函数要加上async),发请求代码前加 await,省略.then

1.2 自定义组件
以自定义面包屑组件(子组件)为例,父组件(使用到面包屑的组件)向子组件传值。
子组件:
<template>
<!-- 面包屑 -->
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>{{this.level1}}</el-breadcrumb-item>
<el-breadcrumb-item>{{this.level2}}</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script>
export default {
name: 'my-bread',
props: ['level1', 'level2']
}
</script>
父组件:
<my-bread level1='权限管理' level2='角色列表'></my-bread>
1.3 登录
- 组件el-form 表单
- :rules=“rules” 表单验证,
show-password - 登录按钮的点击事件:
1)表单验证是否通过
2)通过设置token,跳转到主页 - 头像:css设置水平居中
- 全局监听enter键(把监听事件绑定到document上),当(that.$route.path === ‘/login’) && (e.keyCode === 13),触发登录方法
1.4 主页
- 布局
- 头部+左侧导航菜单+主体
1.4.1 头部,退出
- 删除token
sessionStorage.clear() - 跳转到登录页
this.$router.push({name: 'login'})
1.4.2 左侧菜单 – 权限控制
- el-aside -> el-menu
- :router 开启路由模式,设置el-menu-item的index值 为路由标识
- 动态渲染:不同身份登录,权限控制
一级菜单和子菜单之间是并列关系,在知道有几级菜单的情况下,思考:如果不知道有几级菜单?
<el-menu :unique-opened=true :router=true>
<el-submenu :index="item1.order.toString()" v-for="(item1,i) in menus" :key="item1.id">
<template slot="title">
<i :class="iconlist[i]"></i>
<span>{{item1.authName}}</span>
</template>
<el-menu-item :index="item2.path" v-for="item2 in item1.children" :key="item2.id">
<i class="el-icon-menu"></i>
<span>{{item2.authName}}</span>
</el-menu-item>
</el-submenu>
</el-menu>
1.5 系统功能
1.5.1 用户管理 - 用户列表 - 增删改查
- 展示:现有用户数量
GET - 增加:用户名、密码、邮箱、手机,新增后重新加载页面
POST - 删除:删除后重新加载页面
PUT - 修改:
DELETE
1)改基本信息:手机、邮箱
2)改角色:关系到权限 - 组件
面包屑、搜索框、表格、分页组件、switch状态转换
1.5.1.1 显示、搜索的处理
显示:
在created的时候,调用this.getUserList(),拿到res的数据,赋值给data里的userlist
const res = await this.$http.get(`users?query=${this.query}&pagenum=${this.pagenum}&pagesize=${this.pagesize}`)
搜索:
<el-input @clear="getUserList" v-model="query" clearable @keyup.enter.native="getUserList">
键盘抬起时/输入框清空时,依然是触发getUserList,该方法内部通过双向数据绑定的query进行传参查询,接口文档中,query参数可以为空,为空时显示全量信息。
1.5.1.2 slot-scope=“scope” 参数传递
<el-table-column label="用户状态">
<template slot-scope="scope">
<!-- scope.row就是当前绑定的数据对象 -->
<el-switch @change="handleSwitchChange(scope.row)" v-model="scope.row.mg_state" active-color="#13ce66"
inactive-color="#ff4949">
</el-switch>
</template>
</el-table-column>
1.5.1.3 添加完成,清空文本框
for (const key in this.formData) {
this.formData[key] = ''
}
1.5.1.4 编辑用户,显示已有信息
编辑按钮所在行,通过slot-scope="scope"将被编辑用户信息传给方法editUser,通过初始化form(显示用户信息的el-form所绑定的内容)展示
editUser(user) {
this.dialogFormVisibleEdit = true
this.form = user
}
<el-dialog title="编辑用户" :visible.sync="dialogFormVisibleEdit">
<el-form :model="form">
<el-form-item label="用户名" >
<el-input v-model="form.username" disabled></el-input>
</el-form-item>
</el-form>
</el-dialog>
1.5.1.5 下拉框-显示当前用户角色
- select标签有多个option,有
selected属性的option,会默认选中 - 被选中的option的value值就是select的value值,当选择了其他角色时,select的value值也会随之改变 => 获取当前编辑用户的角色id,双向绑定给select的value,既处理了显示,又处理了角色分配。

1.5.2 权限管理 - 权限列表、角色列表
1.5.2.1 权限列表
一到三级权限的名词展示,可以作为后台的文档数据,不必作为一项功能
1.5.2.2 角色列表
- 添加角色(简单提交)
- 编辑、删除(简单slot-scope传参、delete请求)
- 分配角色(tag)
- 权限展示(表格展开行)
【难点1】 三级权限展示,树形结构,row、col的结构
请求角色列表的产物,因此数据包含在getRoleList的返回值里。
理解el-row和el-col的关系,两者包含成为一维。

<el-row :gutter="20">
<el-col :span="6"><div class="grid-content bg-purple"></div></el-col>
<el-col :span="6"><div class="grid-content bg-purple"></div></el-col>
<el-col :span="6"><div class="grid-content bg-purple"></div></el-col>
<el-col :span="6"><div class="grid-content bg-purple"></div></el-col>
</el-row>
{
"data": [
{
"id": 30,
"roleName": "主管",
"roleDesc": "技术负责人",
"children": [
{
"id": 101,
"authName": "商品管理",
"path": null,
"children": [
{
"id": 104,
"authName": "商品列表",
"path": null,
"children": [
{
"id": 105,
"authName": "添加商品",
"path": null
}
]
}
]
}
]
}
],
}


代码怎么写:
- 先纵向看,有几个一级权限就应当v-for循环出几行
- 再横向看,每个el-row里,拿一个el-col现实出当级权限,另一个el-col再作为一个整体来看(再看成一行两列)
<el-row v-for="(item1,i) in scope.row.children" :key="i" class="expand">
<!-- 第一列中放一级标签 -->
<el-col :span="4">
<!-- 可移除标签,这里展示一级标签,绑定取消权限方法 -->
<el-tag class="expand" closable @close="delRight(item1.id,scope.row)">{{item1.authName}}</el-tag>
</el-col>
<el-col :span="20">
<!-- 第二列也是一行两列 -->
<el-row v-for="(item2,index) in item1.children" :key="index">
<!-- 第二行第一列中放二级标签 -->
<el-col :span="4">
<el-tag class="expand" type="success" closable @close="delRight(item2.id,scope.row)">{{item2.authName}}
</el-tag>
</el-col>
<el-col :span="20">
<el-tag class="expand" @close="delRight(item3.id,scope.row)" closable type="warning"
v-for="(item3,indexInner) in item2.children" :key="indexInner">{{item3.authName}}</el-tag>
</el-col>
</el-row>
</el-col>
</el-row>
【难点2】 分配权限,树形结构el-tree 全选、半选(如何展示)

- 展开所有的权限(树形结构自带的属性:
default-expand-all) - 显示:先勾选上拥有的三级权限(三层forEach,获得所有三级权限的集合,赋值给
checklistArr)
:default-checked-keys="checklistArr" - 提交:需要获取所有的一、二、三级权限的集合(全选+半选),传参修改
显示:
editRight(role) {
// checklist权限的集合,是个树形结构
this.checklist = role.children
this.currentRoleId = role.id
var tmpArr = []
this.checklist.forEach(item1 => {
var item2 = item1.children
item2.forEach(item2 => {
var item3 = item2.children
item3.forEach(item3 => {
tmpArr.push(item3.id)
})
})
})
this.checklistArr = tmpArr
}
提交:
async confirmRole() {
let arr1 = this.$refs.mytree.getCheckedKeys()
let arr2 = this.$refs.mytree.getHalfCheckedKeys()
let arr = [...arr1, ...arr2]
const res = await this.$http.post(`roles/${this.currentRoleId}/rights`, { rids: arr.join(',') })
}

1.5.3 商品管理 - 列表、分类参数、商品分类
1.5.3.1 商品分类
分类接口设计:
重要参数type,type=2(返回一层、二层分类),type=3(返回前三层分类)

1.【难点3】树形组件element-tree-grid在表格中的运用(组件选择、配置)
- 表格样式:树形结构 +【是否有效】【级别】【操作】字段;
- 组件选择:由于第一列的el-table-column要展示为树形结构,因此使用组件element-tree-grid,all props of el-table-column are supported;
- 在获取到三级商品分类后,element-tree-grid只需要绑定好对应的属性,即可显示。

<el-tree-grid
treeKey="cat_id"
parentKey="cat_pid"
levelKey="cat_level"
childKey="children"
prop="cat_name"
label="分类名称">
</el-tree-grid>
2.【难点4】级联选择器-添加分类(理解接口type参数)
- 由于最细添加的是三级分类(或者添加一级、二级分类),因此级联选择器只用展示一、二级的父类,请求分类接口,type=2
- value / v-model: 选中项绑定值,是一个数组
- 级联选择器的
handleChange方法,能获取到最接近一层的父类参数分类id,配置级别和分类名称,组成一个obj作为参数发POST请求
handleChange(value) {
console.log(value);
}

1.5.3.2 【难点5】分类参数 – 只允许为三级分类设置参数
- 级联选择器 type=3 展示出三级分类
- 动态(涉及列表展开)、静态参数
如何限定只让第三级被选中:
handleChange () {
if (this.selectedOptions.length !== 3) {
this.$message.warning('商品只能添加到三级分类')
this.selectedOptions.length = 0
}
}
1.5.3.3 【难点6】什么时候渲染出参数,tabs标签页改造
在el-tab-pane里含了el-table–绑定dynamicAttrs el-button
在点击了级联选择器器之后,渲染出动、静态参数
async handleChange() {
if (this.value.length === 3) {
if (this.active === '1') {
// 动态参数
const res = await this.$http.get(`categories/${this.value[2]}/attributes?sel=many`)
this.dynamicAttrs = res.data.data
this.dynamicAttrs.forEach((item) => {
// 把attr_vals转为数组类型
item.attr_vals = item.attr_vals.split(',')
})
} else if (this.active === '2') {
const res1 = await this.$http.get(`categories/${this.value[2]}/attributes?sel=only`)
this.staticAttrs = res1.data.data
}
}
}
1.5.3.4 【体现功能】商品列表 - 1000条数据

商品参数数据格式:
参数名–参数值(可能有多个)
"data": [
{
"attr_id": 1,
"attr_name": "cpu",
"cat_id": 22,
"attr_sel": "only",
"attr_write": "manual",
"attr_vals": "2G","4G","8G"
}
],
- 查询名称、价格、重量、参数管理
- 添加商品详细描述(名称、价格、重点、数量、分类、参数、属性、图片)
- 步骤条组件+纵向tabs组件(需要包裹在el-form里)(并列关系)
- 上传图片(饿了么Upload组件、百度Web Uploader)
- 富文本编辑器
- 功能上的bug,商品列表不能显示图片(接口没返回这个字段)
- bug2,数据库表设计时,时间字段长度太短,导致时间不正确,令人汗颜


改了表字段的数据类型,可能不是最合适,但是时间显示正常了。创建时间的时候,还是以1970位默认值。

1. 上传图片 – 拓展,检测文件格式;性能
请求上传接口,需要设置token(这里不是发axios请求)
<el-upload
action="http://47.xxx.1xx.7:8888/api/private/v1/upload"
:headers="header"
:on-remove="handleRemove"
:on-success="handleSuccess"
:file-list="fileList"
list-type="picture">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
header: {
Authorization: sessionStorage.getItem('token')
}
2. 富文本编辑器组件 vue-quill-editor
<quill-editor v-model="form.goods_introduce"></quill-editor>
1.5.4 订单管理
1.5.5 数据统计
2. 框架选择 – vue
- 容易上手:react(
JSX,学习成本),vue(模板):任何合乎规范的 HTML 都是合法的 Vue 模板。可以在单文件组件里完全控制 CSS,将其作为组件代码的一部分。 - vue-cli(核心插件)供了各种用途的模板。
- 体积:一个包含了 Vuex + Vue Router 的 Vue 项目 (gzip 之后 30kB) 相比使用了这些优化的 angular-cli 生成的默认项目尺寸 (~65KB) 还是要小得多。
- 当某个组件的状态发生变化时,系统能精确知晓哪个组件确实需要被重渲染。(组件的依赖是在渲染过程中自动追踪的)
3. 组件库
3.1 elementUI
3.1.1 vue-cli安装插件
vue create mypro
cd mypro
vue add element
这里选择的是按需引入:

观察项目文件夹中多了plugins-element.js
默认按需引入只引入了{Button组件} (可以减小体积),修改代码

3.1.2 echarts
4. 项目结构

一般脚手架都应当有以下的几个功能:
- 自动化构建代码,比如打包、压缩、上传等功能
- 本地开发与调试,并有热替换与热加载等功能
- 本地接口数据模拟
- css 模块化:拓展语言、预处理器saas、less(css 不是编程语言,所以不能声明变量、函数,不能做判断、循环和计算,也不能嵌套)
- 检查并自动矫正不符合规范的代码,并优化代码格式
定义好项目的目录结构
- 解耦:代码尽量去耦合,这样代码逻辑清晰,也容易扩展
- 分块:按照功能对代码进行分块、分组,并能快捷的添加分块、分组
- 编辑器友好:需要更新功能时,可以很快的定位到相关文件,并且这些文件应该是很靠近的,而不至于到处找文件
5. 亮点
- 权限、商品分类涉及到多层级的树形结构
- 权限控制