Vue+element ui导入组件封装——超级优雅版

Vue.js 关于表格合并篇

    • [1. 需求说明](#1. 需求说明)
    • [2. 第一个版本](#2. 第一个版本)
    • [3. 让人脑壳疼的地方](#3. 让人脑壳疼的地方)
    • [4. 程序思维至关重要](#4. 程序思维至关重要)
    • [5. 有必要提一下面向对象编程](#5. 有必要提一下面向对象编程)

1. 需求说明

  • 目前是开发管理系统,那肯定免不了有各种导入文件
  • 所以封装一个全项目通用的是十分有必要的
  • 本文由浅入深,通俗易懂

2. 第一个版本

  • 封装组件这玩意对于大家来说其实不是什么难事
  • 所以第一个版本一下就写出来了,是这个样子的
  • 不同的场景会有一个唯一的sign值,在下载模板以及导入的时候传给后端
  • 并且提供了其它参数供选择:otherParams,有时候传文件也需要附带参数
  • 代码如下(注释也尽可能详尽了)
javascript 复制代码
<template>
  <div>
    <el-dialog
      :title="dialogTitle"
      :visible.sync="visible"
      width="620px"
      :close-on-click-modal="false"
      @opened="Opened()"
      @closed="Closed('ruleForm')"
    >
      <div>
        <!-- 导入说明 -->
        <div class="import-explain">
          <h3 class="import_item_title">·&nbsp;&nbsp;导入说明</h3>
          <div class="tipContent">
            <span
              v-for="(item, index) in htmlList"
              :key="index"
              @click="textTap"
              v-html="index + 1 + '.' + item"
            ></span>
          </div>
        </div>
        <div class="import-file">
          <h3 class="import_item_title">
            ·&nbsp;&nbsp;<slot name="importTitle">导入文件</slot>
          </h3>
        </div>
        <div class="select-file">
          <el-upload
            ref="upload"
            accept=".xls,.xlsx"
            :action="uploadUrl"
            :data="{ ...queryData, ...params, ...otherParams }"
            :on-preview="handlePreview"
            :on-remove="handleRemove"
            :before-remove="beforeRemove"
            :on-exceed="handleExceed"
            :on-change="handleChang"
            :on-success="handleSuccess"
            :headers="headers"
            :auto-upload="false"
            :file-list="fileList"
            multiple
            :limit="limit > 1 ? limit : undefined"
          >
            <el-button size="small" type="primary">选择文件</el-button>
            <span>&nbsp;&nbsp;文件格式支持xls,xlsx</span>
          </el-upload>
        </div>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button @click="visible = false">取 消</el-button>
        <el-button type="primary" @click="handleConfirm('ruleForm')">
          确 定 导 入
        </el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
import { download } from "@/api/data.js";
import { downloadFileUrl } from "@/utils/index.js";
import { getToken } from "@/utils/auth";
export default {
  name: "importFile",
  props: {
    dialogTitle: {
      //弹窗标题
      type: String,
      default: "导入文件",
    },
    uploadUrl: {
      //导入地址
      type: String,
      default: "",
    },
    limit: {
      //上传最大文件数
      type: Number,
      default: 1,
    },
    headers: {
      //文件上传自定义请求头信息
      type: Object,
      default: function () {
        return {
          Authorization: getToken(),
        };
      },
    },
    htmlList: {
      //提示信息列表
      type: Array,
      default: Array,
    },
    queryData: {
      //导入接口所需的数据
      type: Object,
      default: Object,
    },
    otherParams: {
      type: Object,
    },
  },
  data() {
    return {
      visible: false,
      params: {
        otherParams: undefined, //{}其他参数
      }, //导入参数
      fileList: [],
      selectFile: false, //false 未上传文件  true 已上传文件
    };
  },
  created() {
    console.log("create", this.headers);
  },
  mounted() {},
  methods: {
    //弹框打开
    Opened() {},
    //弹框关闭
    Closed() {
      this.fileList = []; //上传的导入文件置空
      this.selectFile = false;
    },
    //确定导入
    handleConfirm() {
      if (this.selectFile) {
        this.$refs.upload.submit();
      } else {
        this.$message.error("请选择需要上传的文件!");
      }
    },
    //点击文件列表中已上传的文件时的钩子
    handlePreview(file) {
      this.$emit("handlePreview", {
        file: file,
      });
    },
    //文件列表移除文件时的钩子
    handleRemove(file, fileList) {
      this.selectFile = false;
      this.$emit("handleRemove", {
        file: file,
        fileList: fileList,
      });
    },
    //删除文件之前的钩子,参数为上传的文件和文件列表,若返回 false 或者返回 Promise 且被 reject,则停止删除。
    beforeRemove(file, fileList) {
      this.$emit("beforeRemove", {
        file: file,
        fileList: fileList,
      });
    },
    //文件超出个数限制时的钩子
    handleExceed(files, fileList) {
      this.$emit("handleExceed", {
        file: files[0],
        fileList: fileList,
      });
    },

    //文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用
    handleChang(file, fileList) {
      // 仅上传一个文件 多次选择文件时 覆盖之前的文件
      if (this.limit == 1) {
        if (fileList.length > 0) {
          this.fileList = [fileList[fileList.length - 1]]; //这一步,是 展示最后一次选择文件
        }
      }
      this.selectFile = true;
    },
    //文件上传成功时的钩子
    handleSuccess(response, file, fileList) {
      if (response.code != 200) {
        this.$message.error(response.message);
      } else {
        this.$refs.upload.clearFiles();
        this.$emit("handleSuccess", {
          response: response,
          file: file,
          fileList: fileList,
        });
      }
    },
    textTap(e) {
      if (e.target.dataset.type == "download") {
        e.preventDefault();
        const url = e.target.dataset.url;
        const sign = e.target.dataset.sign;
        const title = e.target.innerText;
        let params = {};
        if (sign) {
          params.sign = sign;
        }
        download(url, params).then((res) => {
          downloadFileUrl(res, "application/vnd.ms-excel", title, "xlsx");
        });
      }
    },
  },
};
</script>

<style lang="scss" scoped>
.import-file {
  margin-top: 15px;
  margin-bottom: 15px;
}

.select-file,
.tipContent {
  margin-left: 16px;
}

//导入说明
.import-explain {
  // margin-top: 15px;
}

:deep(.import_item) {
  margin-top: 15px;
}

:deep(.import_item_title) {
  display: block;
  margin: 0;
  width: 100%;
  font-size: 18px;
  color: #333;
  line-height: 2;
}

:deep(.import_item_content) {
  margin-left: 16px;
  margin-top: 15px;
}

.tipContent {
  display: flex;
  justify-content: flex-start;
  align-items: flex-start;
  flex-direction: column;
  margin-top: 15px;
}

.tipContent span {
  line-height: 1.8;
}

.tipContent a {
  color: #46a6ff;
}
</style>
  • 这个是低耦合的,并且非常通用,大家可以直接CV,该有的各种函数以及检测都到位了,样式也提供在这里
  • 调用的时候,也很简单
javascript 复制代码
<DialogUploadFile 
	:dialogTitle="'导入'" 
	:dialogVisible="dialogVisibleImport" 
   :htmlList="importHtmlList" 
   :uploadUrl="uploadUrl" 
   :queryData="{ sign: signTest }" 
   @handleSuccess="uploadSuccess"
   @close="dialogBeforeClose">
</DialogUploadFile>
  • importHtmlList,这个就是那个文本了,导入说明。

3. 让人脑壳疼的地方

  • 这个组件封装一两年了,也没发现有啥问题,能够正常使用,直到最近...
  • 在产品需求这块,发现会有一些情况,加了一些输入框,需要收集其他信息,如下:
  • 还有些逻辑:比如切换以及验证

4. 程序思维至关重要

  • 那么,各位朋友们,这种问题要怎么解决呢?由基础版升级到这种复杂的
  • 难点在于:不能影响之前的逻辑及业务,又要进行功能升级
  • 首先想到的应该是加各种 if 了,也就是直接在公用组件里面修改,外部传值进来,如下:
  • 这样确实能解决问题,但是吧,以后每次新增功能,都要新增各种 if ,但是十分不建议的:
  • 这是公用组件,整个项目都在用,万一哪天被某个人改错了,风险是非常大的,等着挨骂吧
  • 甚至于,以后代码量干到几百行,谁还看得懂,请神吧(工具组件应该尽量简洁)

5. 有必要提一下面向对象编程

  • 这玩意确实比较抽象,很多人做了好几年程序员也搞不懂
  • 首先,肯定不是把你对象请到公司,坐你对面,然后你看着她写代码
  • 简而言之:各个组件 / 方法,都应该保持独立,有自己的私有的方法与属性,提供接口给外部调用。你只能通过调用我的方法改变我的属性,不能乱来,就跟打狗还得看主人一样。
  • 放在上传组件上来说,这些业务判断是不能放到公用组件的,甚至于说"玷污了"它,只能写到调用它的组件里面去:公用方法或组件尽量不涉及具体业务逻辑
  • 所以,其实一个插槽就能搞定
  • 这些新的参数(下拉框及时间区间),以及验证必填的逻辑,通通放到父组件去,如下:
javascript 复制代码
<DialogUploadFile 
   :dialogTitle="'导入'" 
   :dialogVisible="dialogVisibleImport" 
   :checkChildData="checkChildData"
   :htmlList="importHtmlList" 
   :uploadUrl="uploadUrl" 
   :queryData="{ sign: signTest }" 
   @handleSuccess="uploadSuccess"
   @close="dialogBeforeClose">
   
   <template v-slot:otherDataSlot>
   	<!-- 通通放在这里 -->
   </template>
</DialogUploadFile>
  • 验证方法:checkChildData,加一个参数即可,也在调用上传组件的地方定义好
  • 在确认导入之前,调用该方法,符合条件返回 true,反之返回false即可

1. 希望本文能对大家有所帮助,如有错误,敬请指出
2. 原创不易,还请各位客官动动发财的小手支持一波(关注、评论、点赞、收藏)
3. 拜谢各位!后续将继续奉献优质好文
4. 如果存在疑问,可以私信我(文底有V)

相关推荐
清风徐来QCQ3 小时前
css总结
前端
天***88964 小时前
js封装一个双精度算法实现
开发语言·前端·javascript
Algebraaaaa5 小时前
什么是前端、后端与全栈开发,Qt属于什么?
开发语言·前端·qt
胡斌附体5 小时前
使用Electron创建helloworld程序
前端·javascript·electron·nodejs·pc
toobeloong5 小时前
Electron 从低版本升级到高版本 - webview通信的改造
前端·javascript·electron
im_AMBER5 小时前
React 01
前端·javascript·笔记·react.js·前端框架·web
@大迁世界5 小时前
React 19.2.0 有哪些新变化
前端·javascript·react.js·前端框架·ecmascript
华仔啊6 小时前
用 Vue3 + Canvas 做了个超实用的水印工具,同事都在抢着用
前端·vue.js·canvas