vue3+Ant Design Vue Pro +typeScript 封装上传组件以及上传hooks (useImport)
后台管理基本上都会有导入导出功能,由于每个页面基本上都会有,而且写的时候都会写很多重复的代码,所以我们需要将导入导出按钮封装成一个组件去使用
2.1:用过上传组件的基本都知道,上传无非就是包括了直接使用action上传或者使用自定义函数上传,比如后端需要formData格式,我们此时需要new fromData去传参,由于业务需求我们公司使用的直接用action上传,也就意味着直接传URL给a-upload的action属性即可
2.2:需要注意的是,上传时必须在请求头里携带上token,同时必须要在上传过程中做一些必要的校验(例如:只能上传office等),类似导入之类的业务需求一般都是上传excel,所以我们必须要限制文件上传的类型以及文件的大小(大小一般由产品决定)
2.3:导入需求一般需要点击导入按钮,弹出弹窗,然后由用户下载模板,然后填写完成后点击上传,然后会触发上传的before-upload钩子,如果通过校验,则为done状态,如果未通过,则为error状态,这时我们需要toast提示用户格式不正确XXX之类的提示语
2.4:用户在上传后会有成功失败2种状态,如果失败需要查看失败原因等需求(视自己需求决定)
导入模板:
下载
共上传{{ importNum }}条,成功{{ successUploadData }}条,
失败{{ failUploadData }}条
下载文件
3.1:上述代码可以看出,在上传过程中可能会有额外的参数传给a-upload组件的data属性即可,如果需要修改,可以对外暴漏函数去调用传值修改即可。
3.2: 上传文件路径以及下载模板等一般需要后端配合,我们这里的做法是路径会传一个字符串到子组件里,下载模板需要后端返回URL,前端直接下载文件即可,所以父组件需要传文件上传路径以及下载文件的请求到子组件
3.3: 上传结束后,如果有失败状态的文件,也需要调用后端接口下载excel文件
4.1:父组件代码
import templateImport from '@/components/template-import/index.vue'
const goodsImportData = () => init.getConfig().api + 'baseservice/goods/importData' // 商品文件上传地址
const importData = reactive({
data: {
// 控制导入弹窗的打开
importShow: false,
// 上传文件地址
upLoadUrl: '',
},
})
/**
* @method 导入
*/
const handleImport = () => {
// 给子组件传递上传地址
importData.data.upLoadUrl = goodsImportData()
//打开导入弹窗
importData.data.importShow = true
}
/**
* @method 导入关闭
*/
const importClosed = (is: boolean) => {
importData.data.importShow = is
// 刷新列表数据
setHttpTableData()
}
/**
* @method 下载模板
*/
const onDownload = () => {
// 后端接口返回模板URL,前端直接下载excel
goodsImportTemplate()
.then((res) => {
if (res.ResultCode === 200 && res.Success) {
uploadFile(res.Tag)
}
})
.catch((err) => {
console.log(err)
})
}
/**
* @method 上传成功后下载的失败/成功的文件
*/
const onDownloadFile = (url: string) => {
// 子组件发射事件,传出文件URL,上传失败后下载文件
uploadFile(url)
}
/**
* @method 下载文件
*/
const uploadFile = (file: string) => {
window.open(encodeURI(file), 'foo', 'noopener=yes,noreferrer=yes')
}
4.2: 父组件为每一个页面,但是后台管理中父组件有无数个页面,就意味着上述重复的代码要写无数次,那我们直接封装成hook调用即可
5.1: 新建useImport.ts文件
5.2:代码演示
import { reactive, getCurrentInstance, type ComponentInternalInstance } from 'vue'
import { axiosResponse } from '@/type/interface'
type CallBackType = ((...args: any[]) => string) | string
export default function useImport() {
const { proxy } = getCurrentInstance() as ComponentInternalInstance
/**导入参数 */
const importData = reactive({
data: {
importShow: false,
upLoadUrl: '',
},
})
/**
* @method 打开导入弹窗
* @param callBack 获取导入地址函数 / 导入地址
*/
function handleImport(callBack: T extends CallBackType ? T : never) {
importData.data.upLoadUrl = typeof callBack === 'function' ? callBack() : callBack
importData.data.importShow = true
}
/**
* @method 下载模板
*/
async function onDownload(callBack: () => Promise) {
try {
const { Success, Tag, ResultCode } = await callBack()
if (ResultCode === 200 && Success) {
proxy?.$_u.uploadFile(Tag)
}
} catch (error) {
console.log('下载模板error', error)
}
}
/**
* @method 导入弹窗关闭事件
* @param is 是否关闭
* @param callBack 关闭后回调(一般为重新请求)
*/
function importClosed(is: boolean, callBack: (...args: any[]) => void) {
importData.data.importShow = is
callBack()
}
return {
importData,
importClosed,
onDownload,
handleImport,
}
}
5.3:父组件使用
导入
importClosed(is,setHttpTableData)"
@download="onDownload(workAreaImportTemplate)"
@downloadFile="(url:string)=>proxy?.$_u.uploadFile(url)"
>
import useImport from '@/hooks/useImport'
const { importData, importClosed, onDownload, handleImport } = useImport()
/**
* 操作按钮
* @param type 操作类型 add:新增 on:启用 off:禁用 import:导入 export:导出 print:打印
*/
const handleOpera = (type: operaType) => {
switch (type) {
case 'add':
router.push({ name: 'workSpaceAdd' })
break
case 'on':
case 'off':
handleEnable(type)
break
case 'import':
handleImport(proxy!.$api.workSpaceList_api.workAreaImport)
break
case 'export':
handleExport('工作区', workAreaExportWorkArea, queryInfo)
break
default:
break
}
}
6.1:导出功能较为简单,一般是根据条件筛选导出,前端只需要把条件传给后端以及调用导出接口,后端返回URL后前端下载excel即可
6.2:导出hook代码演示
import { getCurrentInstance, type ComponentInternalInstance } from 'vue'
import { axiosResponse } from '@/type/interface'
export default function useExport() {
const { proxy } = getCurrentInstance() as ComponentInternalInstance
/**
* @method 导出
* @param from 单据来源
* @param callBack 请求回调
* @param exportInfo 导出参数
*/
async function handleExport(from: string, callBack: (exportInfo: Record) => Promise, exportInfo: Record) {
try {
const { Success, Tag, ResultCode } = await callBack(exportInfo)
if (ResultCode === 200 && Success) {
Tag ? proxy!.$_u.uploadFile(Tag) : proxy!.$message.error(`暂无${from}信息导出数据`)
}
} catch (error) {
console.log(`${from}导出error`, error)
}
}
return {
handleExport,
}
}
6.2:代码演示
导出
import useExport from '@/hooks/useExport'
const { handleExport } = useExport()
const queryInfo = {
code: '',
name: '',
state: '',
warehouseId: getTopMenu.value ? '' : activeWareHouse.value.warehouseId,
warehouseRegionId: '',
warehouseCodeOrName: '',
workCodeOrName: '',
}
/**
* 操作按钮
* @param type 操作类型 add:新增 on:启用 off:禁用 import:导入 export:导出 print:打印
*/
const handleOpera = (type: operaType) => {
switch (type) {
case 'add':
router.push({ name: 'workSpaceAdd' })
break
case 'on':
case 'off':
handleEnable(type)
break
case 'import':
handleImport(proxy!.$api.workSpaceList_api.workAreaImport)
break
case 'export':
handleExport('工作区', workAreaExportWorkArea, queryInfo)
break
default:
break
}
}