智慧园区后台
智慧园区后台
kilito总结
vue-admin
场景: 二次迭代
vue-admin-template 下载项目 找到核心内容 (自己分到的业务模块)
项目目录
固定的抽象模式 语义化 + 增加可维护性
- src - 业务代码 最终浏览器中运行 (目录划分相似 语义化 模块化 - 维护方便)
- src之外 - 开发阶段配置文件
关键文件
package.json 包管理文件
- scripts 可执行的命令 可以自定义
- dependencies 生产依赖 这里面的包参与业务开发最终打包上线 npm i axios 参与打包 运行浏览器
- devDependencies 开发依赖 开发阶段生效 不参与打包 npm i sass -D 不参与打包 开发阶段生效
团队开发模式下 整个团队要保证每个人在本地依赖的包都是一样的
main.js
不写业务代码 全局初始化的事情 初始化三方组件/样式初始化/store/router全局注册elementui 挂载vuerouter 挂载vuex 渲染app根组件
组件化的开发模式 组件树
vueRouter
- 如果左侧菜单只有一项 如何配置
- 如果左侧菜单有嵌套 如何配置
vuex
解决的问题:把应用中很多地方都会用到的状态统一管理
模块化的开发模式:1. 定义模块(state - mutation - action) 2. 把子模块在modules选项中注册好
使用vuex中的数据或者方法:加上模块名- 模块化 namespaced modules: {子模块}
- 和数据相关的所有操作都放到vuex中维护 + 组件只做一个事儿 触发action
request.js + apis
- request 产生一个实例对象给每一个api函数使用 一对多的关系 request配置一次 所有的api函数都会跟着生效
- asiox基础封装
- 实例化 axios.create({}) baseURL + timeout
- 请求拦截器 拦截请求处理请求参数再交给后端 headers中注入token
- 响应拦截器 拦截返回数据做处理再交给客户端 401 return response.data 401状态 数据剥离
- 拓展
- axios.create() 可以执行多次
- 拦截器可以有多个(串联参数的模式 前一个拦截器处理完 会把处理之后的数据 下发给下一个拦截器继续处理)
- 拦截器函数中千万别弄丢 return
1 | import axios from 'axios' |
登录
业务模式
表单基础校验
表单统一校验
1
2
3
4
5
6
7
8
9
10
11
12
13
141. 按照业务要求编写校验规则对象(rules)
2. el-form组件绑定表单对象(model)和规则对象(rules)
3. el-form-item组件通过prop属性指定要使用的校验规则
<!--
基础校验
el-form :model="表单对象" :rules="规则对象"
el-form-item prop属性指定一下要使用哪条规则
el-input v-model双向绑定
统一校验
1. 获取表单的实例对象
2. 调用validate方法
-->后续的业务处理(调用接口 - token - 存入token - 跳转首页 - 提示用户)
表单校验
- 先通过 devtools查看双向啊绑定是否成功
- 对照接口文档查看接口参数是否一致(拼写+类型)
- 校验时机实时校验和统一校验都需要
token管理
业务背景:Token的有效期会持续一段时间,在这段时间内没有必要重复请求token,但是Vuex本身是基于内存的管理方式,刷新浏览器Token会丢失,为了避免丢失需要配置持久化进行缓存
基础思路:- 存Token数据时,一份存入vuex,一份存入cookie
- vuex中初始化Token时,优先从本地cookie取,取不到再初始化为空串儿
- 多模块共享 - vuex维护
- 管理方式 vuex + cookie(localstorage)
为什么要使用Vuex+Cookies
俩种存储方式的优势都想要
- vuex 基于内存 存取快 但是刷新就丢失
- ls/cookie 基于磁盘 存取速度稍慢 刷新不丢失(持久化)
因为我们既可以享受vuex速度优势封装优势 同时保持持久化
cookie vs ls
- 存数据的空间大小
ls 5M cookie kb
- 是否允许后端操作
ls 纯前端操作 cookie 前端可操作 后端也可操作(占多数)
- 是否跟随接口发送
cookie
token持久化
为什么要进行持久化
基于vuex的存储页面刷新token丢失如何来做
- 在获取到token之后 一式两份 vuex + cookie(ls)
- Vuex初始化state的时候 优先从本地取 取不到才初始化为空
vuex vs cookie
vuex - 基于内存 快 刷新就丢
cookie - 基于磁盘 稍慢 持久化
请求头添加token(请求拦截器)
为什么要做
接口鉴权怎么做
请求拦截器中统一配置 headers.Authorization = token(格式以后端要求的为主)
记住我优化
- 基础实现逻辑
如果当前用户选中了记住,在登录时把用户的信息存入本地 在组件初始化的时候去取数据 回填
如果当前没有选中,在登录时把数据清空
接口错误统一处理
为什么统一处理
很多个接口都需要做这个事儿
而且报错的提示位置和字段和后端协商好的如何来做
axios响应拦截器来做 判断错误信息存在 弹框提示
token是否存在控制路由权限跳转
- 根据流程图 -> js分支语句
- permission.js
- 权限相关的事儿都放到这里 模块化的思想
- 路由权限前置守卫
- main.js引入立刻执行
数据基础渲染
- 基础实现逻辑
- 准备静态模版(elementUI)
- 解决初始报错 在data中把模版绑定的数据都声明一遍
- 封装接口(url/ method / 参数[名称 + 类型 + 参数数量])
- 组件中封装一个独立的方法 在方法中调用接口函数 (复用的好处 调用之前做一些额外的参数处理)
- 选择一个合适生命周期钩子函数调用独立的方法 (created / mounted 都可以)
- 使用数据渲染模版 (数据驱动视图)
列表基础渲染
实现步骤
- 按照接口文档请求列表接口封装一下(url/method/参数)[拼写/类型]
- data准备响应式的数据(以后端接口实际返回为主)
- 在methods封装一个方法(参数的二次处理 + 调用接口 + 数据赋值)
- 生命周期钩子函数调用这个方法(created / mounted)
- 把响应式数据绑定组件身上(文档组件要求通过什么属性绑定就通过生命属性)
分页功能
分页的逻辑
页数 = 总条数 / 单页的条数组件分好页
传入总数 :total=”100”
单页条数 :pageSize=”2” 默认101
2
3
4
5
6
7
8
9<!--
1. 页数分出来 (页数 = 总条数 / 每页条数)
2. 点击每页的时候获取当前页的数据重新渲染到table上
-->
<el-pagination
layout="total, prev, pager, next"
:page-size="params.pageSize"
:total="total"
/>点击分页交互的实现
点击时拿到当前点击的页数(父组件从子组件获取内部的数据 子传父)
@current-change=”pageChange”1
2
3
4
5
6
7pageChange(page) {
// console.log(page) // 回调参数 拿到的是当前页
// 把点击的页数赋值给请求参数页数
this.params.page = page
// 使用最新的请求参数获取列表数据
this.getCardList()
}使用当前的页数去后端要当前页的数据重新渲染到table
1
2this.params.page = page
this.getList()
搜索功能
思路分析:把各种搜索条件当作请求参数发给后端 后端会根据字段对数据库数据做过滤筛选拿到符合条件返回
1. 表单组件的双向绑定收集到当前的请求数据
2. 把收集到的表单参数发送接口给后端那符合条件的数据
3. 把拿到的数据关系显示在列表中
下拉组件
1 | <el-select v-model="params.cardStatus"> |
状态适配
场景
后端返回的数据无法直接显示到页面中 0/1 男女转化的状态码数量只有两个
解决方案:三元表达式 status === 0? ‘女’ :’男’转化的状态码比较多
解决方案:映射的方案1
2
3
4
5
6
7
8function format(status) {
const MAP = {
0:'女',
1:'男'
}
return MAP[status]
}
新增功能
1. 步骤
1. 点击跳转到新的路由页面/在当前页面打开一个弹框
2. 准备表单项(通常只要有表单就会有校验 单独校验 + 统一校验)
3. 收集表单数据(打开devtools 检测双向绑定是否ok)
4. 提交 (接口字段不多不少 字段名称完全对应 类型完全匹配)
5. 后续的逻辑处理 (提供用户 + 重新拉取列表 + 表单的重置 + 路由的重置)
表单校验
简单校验
使用elementUI默认的配置项就可以完成1
2
3
4
5<!--
el-form :model :rules
el-form-item prop指定要用哪条规则
el-input v-model双向绑定
-->自定义校验
1
2
3
4
5
6
7
8
9
10
11data() {
const validateMobile = (rule,value,cb) => {
// 默认的配置 声明式配置
// 自定义校验逻辑 命令式的校验 写的式逻辑代码
// value: 输入框的数据 校验的那个数据
// cb:校验放行函数 不管在通过还是未通过都需要调用它
}
}
{
validator: validateMobile
}对某些字段单独校验
- 场景:默认的表单校验管控不到 出现在自定义组件中 上传
- 调用单独校验实例方法 this.$refs.form.validateField(‘校验的字段’)
表单功能的时候要提前做数据验证 devtools 双向绑定是否生效 生效之后再调用接口
梳理新增和编辑
新增 点击新增按钮 - 跳转到新增路由 - 准备表单- 二次参数处理 - 提交新增接口 - 后续处理(提示/回跳)
编辑 点击编辑按钮 - 携带id跳转到新增路由 - 准备表单(回填) - 表单校验 - 二次参数处理 - 提交的更新接口 - 提示回跳
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<!--
scope 作用域插槽
scope.row -> 当前行的数据对象
如果我们只是想使用插槽渲染模板 #default
如果我们除了想要使用插槽渲染模板 而且还想要拿到它内部的数据 #default="scope"
scope 类似于函数的形参
组件内部会把当前行数数据对象当成一个实参传到scope的位置
在内部传递实参的时候 实参的格式
{
row: 当前行的对象数据
}
因为本来传下来的就是一个对象 所以通过解构赋值的方式去取row参数 #default="{row}"
-->
<el-button size="mini" type="text" @click="editCard(scope.row.id)">编辑</el-button>
重点:
新增和编辑状态 始终用的就是 id 有id代表当前是编辑状态 没有id代表就是新增状态
- 有id 才获取详情接口
- 有id 调用更新接口 没有id 调用新增接口
- 有id 显示编辑 没有id 显示新增
点击确定调用接口时区分状态之外 还需区分参数
- 如果是新增接口调用 没有任何id
- 如果是更新接口调用 请求参数中附加id[相关id数据在数据回填的时候加进去] 通知后端要更新谁
上传图片
上传的流程
点击上传按钮 -> el-upload [打开本地文件选择框 + 上传前的文件校验] -> File ->
new FormData()[接口要求传递一个formdata类型的数据] -> 往formData的对象中append字段
-> append(‘file’,file) append(‘type’,’business…’) -> 使用完整的formData对象提交接口完成 上传细节问题
- el-upload
- 非常简单 不需要做任何的自定义配置 默认的配置项完成上传就行了
- 需要自定义场景 :http-request=’function’
- 如果添加了上传前的校验 流程:先执行上传前的校验函数 函数返回值为true 再执行upload上传函数
- 函数参数 :上传前的函数 file对象 [size / type做校验] 上传函数 res对象 { file:File }
- 上传接口接口参数
- 常规的接口 contentType application/json
- 上传接口 contentType application/form-data
- 前后端校验逻辑要保持一致
文件大小 小于5m / 文件类型 jpeg 前端要以这个为主
[因为接口不只是可以通过浏览器提交 也可以通过其他方式提交 基于界面操作类校验会失败]
- el-upload
1 | <!-- |
表单校验
- 基础校验 [按照配置]
- 统一校验 [validate 把所有需要校验的表单项都校验一便 把校验的结果布尔值返回valid]
- 自定义校验规则 [{ validator:校验方法 rule value cb } 不管是成功还是失败都必须调用cb]
- 单独校验某个表单字段
- 场景:表单中有一些特殊的字段 没有直接和el-form表单系统进行绑定 有值之后不会通知校验系统
- 解决办法:手动触发校验逻辑 调用el-form组件实例对象的单独校验方法 validateField(‘prop指定的那 个规则’)
vue2的响应式缺点
对象的属性动态添加 不是响应式的
Object.defineProperty
解决办法:
this.$set(要修改的对象,要添加的属性名,新属性的值)
特俗情况: v-model 视图修改的时候同样可以被收集到本来不存在的属性身上
下拉列表的数据
- 单独有一个下拉列表接口
- 没有单独下拉 复用table表格的列表接口(不需要判断状态 没有状态的所有数据)
知道一个事情:业务数据是由状态的 而且状态可以通过用户操作进行切换
表单的清空
调用表单组件的实例方法 resetFields
手动做数据的清除
1
2
3
4this.form = {
name= ""
}
网络请求的优化
- 场景: 限制了请求个数 保证只有打开时才请求
- 怎么做到?
- 判断第一个参数row是否能在第二个参数rows中找到 如果能找到代表打开了
- find 通过匹配找到符合条件的第一项 然后把找到的项返回
- findIndex 通过匹配找到符合条件的第一项 然后把找到项的下标值返回 splice(index,1)
1 | // 只有展开时获取数据并绑定 |
带有模板的状态格式化
直接可以把后端的数据渲染出来 prop指定要渲染的字段
不能直接渲染 需要格式化 格式化出来的内容一九是一个文本 string
- :formatter=”formatStatus” 不需要手动传参 自动传入
- 插槽 + 插值表达式 [渲染出来的是函数的返回值]
1
2
3
4<template default="{row}">
{{ formatStatus(row.status) }}
</template>
// 格式化status
formatStatus(type) {
const TYPEMAP = {
0: ‘待生效’,
1: ‘生效中’,
2: ‘已到期’,
3: ‘已退租’
}
return TYPEMAP[type]
}
1 |
|
对象动态添加响应式属性
vue2 Object.defineProperty 无法监听到对象属性的添加 / 数组通过下标直接修改 也监听不到
Vue.set / this.$set
1
2Vue.set(this.form,'age',18) // 就会变成响应式
Vue.set(this.list,'3','new data')
后端返回的数据字段不够 需要前端自定义字段
默认的返回的企业列表数据中没有一个存放合同列表的数据的数组
1 | this.list = res.data.rows.map(item => { |
word 文档的预览
解决方案:通过一个固定的预览地址 拼接 自己的合同url地址 然后通过a链接在新窗口打开
拼接方式:prewViewURL?src=url
业务数据状态变化
合同列表
- 当前时间还没有合同的起止时间 未开始
- 当前时间已经超过了合同的起止时间 已结束
- 当前时间正好式合同期限内,且用户没有退租 生效中
不同的业务数据状态影响显示及操作区域按钮的控制
实际开发时如何判断某块业务已经跑通了:接口通了并且对应的状态已经确认被修改了
excel导出
实际开发过程中的导出
前端主导(xlsx)
流程:调用列表接口把要导出的数据拿到 -> 数据的二次转化 -> [excel 工作簿 - 工作表 - 单元格数据] - 使用xlsx创建一个工作簿 - 使用xlsx方法创建一个工作表 - 把工作表添加到工作簿 – [使用中文替换中文表头] 调用xlsx的导出方法
工作中遇到了需求,参考代码 换接口 数据二次处理 处理表头
后端主导(最常见)
流程:前端直接调用导出接口 - 后端会把数据转换成excel文件流当成返回值返回 - 直接触发浏览器的下载功能
两种方案的本质区别:
把数据转化成excel的过程发生在哪里?如果发生在浏览器 前端主导 如果发生在后端服务器 后端主导
前端主导 - 处理数据量不能太大
后端主导 - 适合处理量大或量小都可
账单支付金额的接口计算
思想:
操作表现流程:选中了楼宇 + 选中时间 => 调用接口 -> 计算之后的数据 -> 显示到input框内
1. 通过事件分别监听一下楼宇什么时候选中 时间什么时候选中 [通过事件绑定回调函数 两个组件分别绑定同一个回调函数]
2. 在事件的回调中判断一下接口必须传参是否都具备了 [非空判断]
3. 如果接口参数都不是空 正常发接口请求 获取计算金额
4. 通过 v-model 进行正常的回填 [通过属性控制它不可编辑]
tab切换类交互
- 场景:角色列表切换 / 菜单列表切换 / tabs组件点击切换
- 通用实现方法
- 点击谁把谁index/id(唯一的标识)记录下来
- 准备一个激活的类名样式 active
- 通过vue中的动态类名:class = {acitve: index(当前项index) === curIndex(激活记录下标) }
树状组件
树:嵌套的数据结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18const treeList = [
{
id:'1',
title:'第一项',
children: [
{
id:'1-1',
title:'第二项',
children: [
{
id:'1-1-1',
title:'第三项'
}
]
},
]
}
]如何把一个平铺的数组处理成树形的数组?
大多是情况下 都需要前端自己处理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const treeList = [
{
id: '1',
title:'第一项',
pid: null
},
{
id:'1-1',
title:'第二项',
pid: '1'
},
{
id:'1-1-1',
title:'第三项',
pid:'1-1'
}
]
前端同步异步的问题
1 | async mounted() { |
RBAC指的是什么?
基于角色的权限控制思想
员工 - 角色 - 权限点
先把权限点交给某个角色 然后再把角色交给某个员工 这个员工自动有了角色下所有的权限点
前端菜单路由权限控制
- 权限数据的生成(RBAC)
- 新增一个角色 给角色配置权限数据
- 新增一个员工 给员工分配这个角色 员工就有了当前角色下所有的权限数据
- 完整的实现流程
调用接口获取当前员工的权限数据 permission [‘park:building:add_edit’]
- 在Vuex中编写逻辑,user/state里存放个人用户信息
- action里调用时,把permissions目标数据return出去 给另一个js模块使用
- permission文件中触发action,获取用户信息(有token时)
对权限数据做格式化处理 产生两个权限数据 一级路由权限数组 + 二级路由权限数组
把路由表拆分成两部分 动态路由表[需要加权限控制] + 静态路由表[不需要加权限控制]
- 拆分动态路由表导出使用 asyncRoutes
- 初始化时候只处理静态路由表 routes: […routes]
以一级和二级权限数组作为对主动态路由表做过滤筛选处理 -> 有资格加入到路由系统中的动态路由表
- 使用一级权限点过滤一级路由 使用二级权限点过滤二级路由 最终得到显示左侧的路由表
- 调用函数获取最终的动态路由
调用router的addRoute方法把动态路由表依次添加到路由系统中 访问url可以渲染对应的组件
使用动态路由表数据通过存入Vuex然后利用它响应式的特性 渲染到左侧菜单中
- vuex新增一个模块,menu模块,先以静态的路由表作为初始值
- 在得到过滤之后的动态路由表之后,和之前的静态做一个结合
- 在sidebar组件中结合v-for指令做使用Vuex中的数据做渲染
解决切换用户有缓存的bug 方案:在用户退出登录时
调用清空路由的reset方法
手动把Vuex中的数据也清空
用户信息也清空
- 每一个独立的小功能封装成一个独立的小函数 维护方便
1 | if (!store.state.user.profile.id) { |