Vue+element ui导入组件封装——超级优雅版
Vue.js 关于表格合并篇
- 1. 需求说明
- 2. 第一个版本
- 3. 让人脑壳疼的地方
- 4. 程序思维至关重要
- 5. 有必要提一下面向对象编程
1. 需求说明
- 目前是开发管理系统,那肯定免不了有各种导入文件
- 所以封装一个全项目通用的是十分有必要的
- 本文由浅入深,通俗易懂
2. 第一个版本
- 封装组件这玩意对于大家来说其实不是什么难事
- 所以第一个版本一下就写出来了,是这个样子的
- 不同的场景会有一个唯一的sign值,在下载模板以及导入的时候传给后端
- 并且提供了其它参数供选择:otherParams,有时候传文件也需要附带参数
- 代码如下(注释也尽可能详尽了)
<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">· 导入说明</h3><div class="tipContent"><spanv-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">· <slot name="importTitle">导入文件</slot></h3></div><div class="select-file"><el-uploadref="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> 文件格式支持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,该有的各种函数以及检测都到位了,样式也提供在这里
- 调用的时候,也很简单
<DialogUploadFile :dialogTitle="'导入'" :dialogVisible="dialogVisibleImport" :htmlList="importHtmlList" :uploadUrl="uploadUrl" :queryData="{ sign: signTest }" @handleSuccess="uploadSuccess"@close="dialogBeforeClose">
</DialogUploadFile>
- importHtmlList,这个就是那个文本了,导入说明。
3. 让人脑壳疼的地方
- 这个组件封装一两年了,也没发现有啥问题,能够正常使用,直到最近…
- 在产品需求这块,发现会有一些情况,加了一些输入框,需要收集其他信息,如下:
- 还有些逻辑:比如切换以及验证
4. 程序思维至关重要
- 那么,各位朋友们,这种问题要怎么解决呢?由基础版升级到这种复杂的
- 难点在于:不能影响之前的逻辑及业务,又要进行功能升级
- 首先想到的应该是加各种 if 了,也就是直接在公用组件里面修改,外部传值进来,如下:
- 这样确实能解决问题,但是吧,以后每次新增功能,都要新增各种 if ,但是十分不建议的:
- 这是公用组件,整个项目都在用,万一哪天被某个人改错了,风险是非常大的,等着挨骂吧
- 甚至于,以后代码量干到几百行,谁还看得懂,请神吧(工具组件应该尽量简洁)
5. 有必要提一下面向对象编程
- 这玩意确实比较抽象,很多人做了好几年程序员也搞不懂
- 首先,肯定不是把你对象请到公司,坐你对面,然后你看着她写代码
- 简而言之:各个组件 / 方法,都应该保持独立,有自己的私有的方法与属性,提供接口给外部调用。你只能通过调用我的方法改变我的属性,不能乱来,就跟打狗还得看主人一样。
- 放在上传组件上来说,这些业务判断是不能放到公用组件的,甚至于说“玷污了”它,只能写到调用它的组件里面去:公用方法或组件尽量不涉及具体业务逻辑
- 所以,其实一个插槽就能搞定
- 这些新的参数(下拉框及时间区间),以及验证必填的逻辑,通通放到父组件去,如下:
<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)