day-110-one-hundred-and-ten-20230711-组件缓存-图片上传-富文编辑器-鉴权处理
组件缓存
keep-alive组件的使用
keep-alive组件的原理
图片上传
图片上传流程
- 点击上传图片后,把图片发送给服务器。
- 服务器返回一个相对或绝对地址。
- 提交时,把图片地址与其余表单数据一起传递给服务器。
el-upload组件
- 图片上传分为两步:
- 选取图片
- 规则限制:类型、大小…
- multiple 是否支持多图片上传
- limit 最大上传的个数
- on-exceed 超出最大限制触发的函数
- accept 格式限制
- before-upload 上传之前的钩子
- auto-upload 默认true
- 把图片上传到服务器,服务器返回上传后的绝对地址
- 图片流程中常用的属性:
- action 上传的接口地址
- headers 上传请求的请求头设置
- data 上传请求的请求主体设置
- name 上传文件的字段名「默认 file」
- with-credentials 是否允许携带资源凭证
- on-success 上传成功「response, file, fileList」
- on-error 上传失败 「err, file, fileList」
- on-progress 上传中「event, file, fileList」
- http-request 覆盖默认的上传行为
- 其余文件常用的属性:
- show-file-list
- file-list [{name,url},…]
- drag
- on-preview
- on-remove
- 内置上传机制说明:
- element-ui / antd 等组件库中的上传组件
- 除了正常的文件选取和常规操作外,最主要的是:组件内部,内置了Ajax请求
- 用的是原生的 XMLHttpRequest 「不是我们的axios,所以我们axios的二次封装等操作,对其内置的ajax不生效」
- 基于POST,向action指定的接口地址发送请求,我们可以基于headers设置请求头信息,请求主体中,其默认带了一个信息
字段名 | 值 |
file | 选取的文件对象 |
- 而且是基于 FormData 格式,把信息传递给服务器的!!
- 如果需要设置除file以外的,额外的请求主体信息,可以基于 data 设置
- 都指定好之后,开始上传
- 可以基于 on-progress 监听上传进度,处理进度条
- 成功触发 on-success,其中 response 就是服务器返回的响应主体信息
- 失败触发 on-error
- …
- 当然除了这些内置的设置外,我们还可以基于 http-request 覆盖内置的上传行为,转而走自己单独的上传请求
内置上传
<script>
import ut from "@/assets/utils";
export default {
data() {
return {
// imageURL: "https://iot.fastbee.cn/prod-api/profile/avatar/2023/07/07/blob_20230707165813A006.png",
imageURL: "",
token: ut.storage.get("TK"),
};
},
methods: {
uploadSuccess(response) {
console.log(`response-->`, response);
let {code,msg,url}=response
if (+code===200) {
this.imageURL=url
this.$message.success("上传成功");
return false;
}
this.$message.error(`网络繁忙请稍后再试`)
},
uploadError(err) {
console.log(`err-->`, err);
this.$message.error(msg)
},
//限制大小;
beforeUpload(file) {
console.log(`file-->`, file);
let { size } = file;
if (size > 1000 * 1024) {
this.$message.warning("上传的图片不能超过1000kb");
return false;
}
return true;
},
},
};
</script>
<template>
<div class="category-box">
<el-upload
class="avatar-uploader"
:show-file-list="false"
accept="image/*"
:on-success="uploadSuccess"
:on-error="uploadError"
:before-upload="beforeUpload"
action="/api/iot/tool/upload"
:headers="{
Authorization: token,
}"
>
<img v-if="imageURL" :src="mixinPrefixAdd(imageURL)" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</div>
</template>
<style lang="less" scoped>
.category-box {
min-height: 300px;
background-color: #fff;
}
.avatar-uploader {
box-sizing: border-box;
width: 200px;
height: 200px;
border: 1px solid #ddd;
display: flex;
justify-content: center;
align-items: center;
.avatar {
width: 100px;
height: 100px;
}
.avatar-uploader-icon {
font-size: 20px;
}
}
</style>
自定义上传
- fang/f20230711/ManageSystem/src/api/index.js
import http from "./http";
//图片上传
/* // https://iot.fastbee.cn/prod-api/iot/tool/upload
/iot/tool/upload - POST
Authorization 需要传递Token
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryalInqpHWOwZhUZyQ
请求主体
file:文件对象
返回值:
code: 200
fileName: "/profile/iot/6/2023-0711-105411.jpg"
msg: "操作成功"
url: "/profile/iot/6/2023-0711-105411.jpg" */
const uploadImage = (file) => {
let theFormData = new FormData();
theFormData.append("file", file);
return http.post("/iot/tool/upload", theFormData);
};
/* 暴露API */
const API = {
uploadImage,
};
export default API;
- fang/f20230711/ManageSystem/src/views/iot/Category.vue
<script>
import ut from "@/assets/utils";
export default {
data() {
return {
// imageURL: "https://iot.fastbee.cn/prod-api/profile/avatar/2023/07/07/blob_20230707165813A006.png",
imageURL: "",
token: ut.storage.get("TK"),
};
},
methods: {
//限制大小;
beforeUpload(file) {
console.log(`file-->`, file);
let { size } = file;
if (size > 1000 * 1024) {
this.$message.warning("上传的图片不能超过1000kb");
return false;
}
return true;
},
// ============
// 自定义请求
async request(data) {
let { file } = data;
console.log(`file-->`, file);
try {
let { code, msg, url } = await this.$API.uploadImage(file);
if (+code === 200) {
this.imageURL = url;
this.$message.success("自定义上传-上传成功");
return;
}
this.$message.error(`自定义上传-上传失败:${msg}`);
} catch (error) {
console.log(`error:-->`, error);
}
},
},
};
</script>
<template>
<div class="category-box">
<el-upload
class="avatar-uploader"
:show-file-list="false"
accept="image/*"
:before-upload="beforeUpload"
action=""
:http-request="request"
>
<img v-if="imageURL" :src="mixinPrefixAdd(imageURL)" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</div>
</template>
<style lang="less" scoped>
.category-box {
min-height: 300px;
background-color: #fff;
}
.avatar-uploader {
box-sizing: border-box;
width: 200px;
height: 200px;
border: 1px solid #ddd;
display: flex;
justify-content: center;
align-items: center;
.avatar {
width: 100px;
height: 100px;
}
.avatar-uploader-icon {
font-size: 20px;
}
}
</style>
富文编辑器
- 一般来说,全局导入与局部导入都差不多,不过个人感觉还是局部导入来得好。
- 不过目前演示的是全局导入。
关于源码
- fang/f20230711/ManageSystem/node_modules/vue-quill-editor/src/index.js
- fang/f20230711/ManageSystem/node_modules/vue-quill-editor/src/editor.vue
代码示例
- Vue2进阶/ManageSystem/src/global.js
import Vue from "vue"
// 富文本编辑器
import VueQuillEditor from 'vue-quill-editor'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
Vue.use(VueQuillEditor, {
placeholder: '请输入正文信息'
})
- Vue2进阶/ManageSystem/src/views/iot/Product.vue
<script>
export default {
data() {
return {
content: "",
};
},
methods: {
handle() {
console.log(this.content);
},
},
};
</script>
<template>
<div>
<quill-editor v-model="content" />
<el-button type="primary" @click="handle">按钮</el-button>
</div>
</template>
<style lang="less" scoped>
.quill-editor {
width: 800px;
}
</style>
鉴权处理
权限校验定义
- 什么是权限校验?
- 根据不同的登录者(具备不同的权限),呈现不同的内容,或者限制相关的操作;例如:一个管理系统,公司全员可登录,管理员、经理、业务员肯定具备不同的权限!!
- 登录校验也算是权限校验的一种!只不过登录校验只是用来判断一个大类,不能具体到路由与页面与按钮。
权限表现形式
- 权限表现的形式:
- 具备相关的权限,我们再展示渲染出对应的菜单;如果不具备,就不给用户看到!!
- 例如:分类菜单、删除按钮…
- 不论是否具备权限,我们都渲染出来,但是在操作的时候,给予没有相关操作权限的提示!!
- 例如:没会员不能看、没会员只能看5分钟、引导充会员…
- 有权限和没权限看到不同的内容!!
- 例如:客户列表,经理看到的是全部客户,业务员只能看到自己的客户…
- …
权限校验机制的构建流程
- 权限是如何在系统中构建的:
- 开发者根据需求,构建出对应的权限代码,例如:
- 我们发现哪些地方未来要做权限处理,则都设置对应的编号代码
设备管理
iot:template:list -> 物模型列表
iot:template:query -> 物模型查询筛选
iot:template:add -> 新增物模型信息
iot:template:update -> 修改物模型信息
iot:template:delete -> 删除物模型信息
iot:category:list -> 产品分类管理
iot:product:list -> 产品管理
系统管理
system:user:query -> 用户管理
system:menu:query -> 菜单管路
...
- 职务/角色管理
- 在创建职务的时候,我们会为当前职务勾选对应的权限;这样职务就具备对应的权限代码了!
- 员工管理
- 在管理员工的时候,我们会前为其分配职务,这样员工就有自己所属的权限「具备的权限代码」!
权限校验具体实现逻辑
- 前端开发者如何实现权限校验?
- 从表现形式上来看上述的1~3:
- 在用户登录成功后,我们从服务器获取登录者信息的时候,一般后台会返回当前用户具备的权限代码「即便没有返回,也会有对应的接口,可以让我们获取到其权限代码」;我们把获取的权限代码存储起来:
- vuex中「推荐:安全、页面刷新就会从服务器获取最新的权限…」
- localStorage中「不安全、更新度较慢…」
- 情况3:有权限和没权限看到不同的内容!
- 这种情况一般都是服务器处理{为了保障安全},服务器会根据当前登陆者的权限,返回所拥有的信息!!
- 同样的请求接口,同样的参数设置,业务员登录只能获取自己的客户,经理登录,服务器返回的就是全部客户!!
- 情况2:不论是否具备权限,我们都渲染出来,但是在操作的时候,给予没有相关操作权限的提示!
- 在我们进行相关操作的时候,我们首先看vuex中是否具备对应的权限代码,不具备就给与对应的提示和引导即可!
- 一般是做一个全局minix或在原型上挂载一个方法,在用户点击对应的按钮时就执行一下那个方法,看该方法的返回结果。如果是拒绝的Promise或false,就不做任何操作或者提示用户没权限。如果是正确的Promise或true,就进行后续操作。
- 情况1:具备相关的权限,我们再展示渲染出对应的菜单。
- 实现方案有两种:
- 客户端在渲染内容的时候,根据权限代码进行判断,具备相关权限的,则渲染出来,不具备的则不渲染!!
- v-if
- 自定义指令处理「推荐」
- 问题:路由表中包含了,项目中所有的页面的路由配置(不管是否拥有对应的权限);这样即便没有权限,我们把菜单隐藏了,但是“高级玩家”还是可以自己去改变地址栏中的路由地址,从而进入到指定的路由中!
- 解决:如果还是前端解决,则我们需要在每一次路由跳转(导航守卫中)的时候,做权限校验!
在操作的时候给予没有相关操作权限的提示的流程
- fang/f20230711/ManageSystem/src/global.js
import Vue from "vue";
// 权限校验-第二种情况:不论是否具备权限,都先渲染出来,只不过操作的时候再校验权限。
Vue.prototype.$handlePermiseCheck = function $handlePermiseCheck(checkFlag) {
//先获取登录者具备的全部权限标识:
let permissions = this.$store.state.profile.permissions || [];
//验证传递的标识是否存在
if (permissions.includes(checkFlag)) {
return Promise.resolve('');
}
//没有权限:
this.$notify({
title: "权限警告",
message: "你不具备此操作权限,请先联系管理员!",
type: "warning",
duration: 2000,
});
return Promise.reject('');
};
- fang/f20230711/ManageSystem/src/views/iot/Template.vue
<script>
export default {
methods: {
// 触发删除多项的按钮
async triggerDeleteAll() {
try {
await this.$handlePermiseCheck("iot:template:delete");
// 执行校验通过相关的操作。
//....
} catch (_) {
// 执行校验失败相关的操作。
//....
}
},
// 触发下载按钮
async triggerDownLoad() {
try {
await this.$handlePermiseCheck("iot:template:download");
// 执行校验通过相关的操作。
//....
} catch (error) {
console.log(`error:-->`, error);
// 执行校验失败相关的操作。
//....
}
},
// 触发新增
async triggerAdd() {
try {
await this.$handlePermiseCheck("iot:template:add");
// 执行校验通过相关的操作。
//....
} catch (error) {
console.log(`error:-->`, error);
// 执行校验失败相关的操作。
//....
}
},
},
};
</script>
<template>
<div class="template-box">
<!-- 筛选/操作区域 -->
<div class="filter-box">
<div class="handler">
<el-button type="primary" ghost icon="el-icon-plus" @click="triggerAdd">
新增
</el-button>
<el-button
type="danger"
ghost
icon="el-icon-minus"
@click="triggerDeleteAll"
>
删除
</el-button>
<el-button
type="success"
ghost
icon="el-icon-download"
@click="triggerDownLoad"
>
下载
</el-button>
</div>
</div>
</div>
</template>
动态路由
- 动态路由 -> 权限校验交给后台,客户端只需要在登录成功后,从服务器获取对应的路由表即可
- 我们事先按照最高权限编写路由表的配置;
- 把写好的路由表直接给后台(不是ajax传输,而是直接把代码给后台),后台提供一个接口,客户端可基于ajax,从服务器端进行获取;
- 获取到的路由表是经过服务器端权限校验完成的,例如:登陆者不具备产品管理的权限,那么返回的路由表中是没有这个路由的配置的!!
- 客户端基于router.addRoutes/addRoute来动态增加路由配置!!
- 页面左侧的Menu菜单,也是基于服务器返回的路由表信息,循环动态绑定的!
进阶参考
vue富文本编辑器vue-quill-editor的使用和介绍