day-110-one-hundred-and-ten-20230711-组件缓存-图片上传-富文编辑器-鉴权处理

组件缓存

keep-alive组件的使用

keep-alive组件的原理

图片上传

图片上传流程

  1. 点击上传图片后,把图片发送给服务器。
  2. 服务器返回一个相对或绝对地址。
  3. 提交时,把图片地址与其余表单数据一起传递给服务器。
el-upload组件
  • 图片上传分为两步:
  1. 选取图片
  • 规则限制:类型、大小…
  • multiple 是否支持多图片上传
  • limit 最大上传的个数
  • on-exceed 超出最大限制触发的函数
  • accept 格式限制
  • before-upload 上传之前的钩子
  • auto-upload 默认true
  1. 把图片上传到服务器,服务器返回上传后的绝对地址
  • 图片流程中常用的属性:
  • 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>

富文编辑器

  1. vue富文本编辑器vue-quill-editor的使用和介绍
  • 一般来说,全局导入与局部导入都差不多,不过个人感觉还是局部导入来得好。
  • 不过目前演示的是全局导入。

关于源码

  • 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>

鉴权处理

权限校验定义

  • 什么是权限校验?
  • 根据不同的登录者(具备不同的权限),呈现不同的内容,或者限制相关的操作;例如:一个管理系统,公司全员可登录,管理员、经理、业务员肯定具备不同的权限!!
  • 登录校验也算是权限校验的一种!只不过登录校验只是用来判断一个大类,不能具体到路由与页面与按钮。

权限表现形式

  • 权限表现的形式:
  1. 具备相关的权限,我们再展示渲染出对应的菜单;如果不具备,就不给用户看到!!
  • 例如:分类菜单、删除按钮…
  1. 不论是否具备权限,我们都渲染出来,但是在操作的时候,给予没有相关操作权限的提示!!
  • 例如:没会员不能看、没会员只能看5分钟、引导充会员…
  1. 有权限和没权限看到不同的内容!!
  • 例如:客户列表,经理看到的是全部客户,业务员只能看到自己的客户…

权限校验机制的构建流程

  • 权限是如何在系统中构建的:
  • 开发者根据需求,构建出对应的权限代码,例如:
  • 我们发现哪些地方未来要做权限处理,则都设置对应的编号代码
设备管理
  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的使用和介绍