vue3导入导出
vue3+ts封装导入、导出、上传文件组件以及hooks自动化函数
vue3+Ant Design Vue Pro +typeScript 封装上传组件以及上传hooks (useImport)
后台管理常见的导入导出功能页面展示
后台管理基本上都会有导入导出功能,由于每个页面基本上都会有,而且写的时候都会写很多重复的代码,所以我们需要将导入导出按钮封装成一个组件去使用
使用a-upload注意事项
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种状态,如果失败需要查看失败原因等需求(视自己需求决定)
3.子组件代码演示
<template>
<!-- 导入弹框 -->
<tempateModal :modalOption="modalOption" title="导入" @modalCloseHandle="addUserModalClose">
<template #modalContent>
<div class="import-template">
导入模板:
<a-button ghost type="primary" @click="download"> 下载</a-button>
</div>
<div class="upload-result" v-if="uploadSuccess">
共上传{{ importNum }}条,成功{{ successUploadData }}条,
<span style="color: red">失败{{ failUploadData }}条</span>
<span style="color: #1890ff; cursor: pointer" @click="downloadFile"> 下载文件</span>
</div>
</template>
<template #footer>
<div class="upload-footer">
<a-upload v-model:file-list="fileList" name="file" :data="uploadData.data" :action="upLoadUrl" :headers="{ Authorization: token }" @change="handleChange" :before-upload="beforeUpload">
<a-button type="primary"> 上传 </a-button>
</a-upload>
<a-button type="addUserModalClose" class="cancel-btn" @click="addUserModalClose"> 取消 </a-button>
</div>
</template>
</tempateModal>
</template>
<script setup lang="ts">
import { ref, reactive, getCurrentInstance, watch, computed } from 'vue'
import tempateModal from '@/components/template-modal/index.vue'
import type { UploadChangeParam, UploadProps } from 'ant-design-vue'
import { useStore } from 'vuex'
import * as TYPES from '@/type/mutation-types'
import storage from '@/utlis/storage'
const getTopMenu = computed(() => store.state.app.activeTopMenu === TYPES['JSLX_01']) // 企业级
const activeWareHouse = computed(() => store.state.app.activeWareHouse) // 当前活跃仓库
// const token = computed(() => store.state.app.token)
const token = storage.get('session', TYPES['ACCESS_TOKEN'])
const store = useStore()
/**额外携带参数 */
const uploadData = reactive({
data: {
warehouseId: getTopMenu.value ? '' : activeWareHouse.value.warehouseId,
warehouseCode: getTopMenu.value ? '' : activeWareHouse.value.warehouseCode,
roleTypeCode: store.state.app.activeTopMenu,
systemType: 'WMS',
parkCode: '',
id: activeWareHouse.value.warehouseId,
},
})
/**图片上传接口 */
const upLoadUrl = ref('')
/**上传数量 */
const importNum = ref()
/**下载文件路径 */
const importUrl = ref()
/**上传结果 */
const uploadSuccess = ref<boolean>(false)
/**上传成功数据 */
const successUploadData = ref<any[]>([])
/**上传失败数据 */
const failUploadData = ref<any[]>([])
const fileList = ref([])
const _props = defineProps(['importData'])
const emit = defineEmits(['closeImport', 'download', 'downloadFile', 'uploadSuccess'])
const {
appContext: {
config: { globalProperties },
},
}: any = getCurrentInstance()
let uploadList = reactive({
default: [],
})
// 导入弹窗对象
const modalOption = reactive({
data: {
id: 0,
visible: false,
width: 650,
title: '弹窗',
confirmLoading: false,
footer: true,
wrapClassName: 'test3', // 存在多级必传
},
})
watch(
/**
* @method 初始化
*/
() => _props.importData,
(now) => {
modalOption.data.visible = now.data.importShow
upLoadUrl.value = now.data.upLoadUrl
},
{ deep: true }
)
const addUserModalClose = () => {
emit('closeImport', false)
uploadSuccess.value = false
fileList.value = []
}
const beforeUpload: UploadProps['beforeUpload'] = async (file: any) => {
/**
* @method 上传前操作
*/
if (file.type.indexOf('application/') >= 0) {
const isJpgOrPng = file.type === 'application/vnd.ms-excel' || file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
if (!isJpgOrPng) {
file.status = 'error'
globalProperties.$message.error('请上传正确的文件')
return Promise.reject()
}
}
// const isLtSize = file.size / 1024 < 2000
// if (!isLtSize) {
// file.status = 'error'
// globalProperties.$message.error(`上传限制2000KB`)
// return Promise.reject()
// }
}
const download = () => {
/**
* @method 下载模板
*/
emit('download')
}
const handleChange = (info: UploadChangeParam) => {
/**
* @method 上传中、完成、失败都会调用这个函数
*/
if (info.file.status === 'done') {
if (info.file.response.ResultCode === 200) {
uploadSuccess.value = true
successUploadData.value = info.file.response.Tag.successNum
failUploadData.value = info.file.response.Tag.errorNum
importNum.value = info.file.response.Tag.importNum
importUrl.value = info.file.response.Tag.url
emit('uploadSuccess', info.file.response)
} else {
globalProperties.$message.error(info.file.response.Tag)
}
}
}
const downloadFile = () => {
/**
* @method 下载文件
*/
emit('downloadFile', importUrl.value)
}
/**
* 设置上传数量
* @param importDataNums 上传数量/成功数量/失败数量
*/
const setImportNums = (importDataNums: any) => {
uploadSuccess.value = true
importNum.value = importDataNums.importNum
successUploadData.value = importDataNums.successNum
failUploadData.value = importDataNums.errorNum
importUrl.value = importDataNums.url
}
/**
* 设置上传参数
* @param data 上传参数
*/
const setUploadData = (data: any) => {
uploadData.data = data
}
defineExpose({ setImportNums, setUploadData })
</script>
<style scoped lang="less">
.import-template {
padding: 15px;
height: 60px;
}
.upload-result {
border-top: 1px solid #eee;
padding: 15px;
height: 60px;
line-height: 30px;
}
.upload-footer {
display: flex;
justify-content: flex-end;
align-items: center;
}
.upload-footer span:first-child {
width: 100%;
margin-right: 80px;
}
.cancel-btn {
align-self: baseline;
margin-left: 10px;
position: absolute;
}
</style>
3.1:上述代码可以看出,在上传过程中可能会有额外的参数传给a-upload组件的data属性即可,如果需要修改,可以对外暴漏函数去调用传值修改即可。
3.2: 上传文件路径以及下载模板等一般需要后端配合,我们这里的做法是路径会传一个字符串到子组件里,下载模板需要后端返回URL,前端直接下载文件即可,所以父组件需要传文件上传路径以及下载文件的请求到子组件
3.3: 上传结束后,如果有失败状态的文件,也需要调用后端接口下载excel文件
4.父组件使用
4.1:父组件代码
import templateImport from '@/components/template-import/index.vue'
<templateImport
:importData="importData"
@closeImport="importClosed"
@download="onDownload"
@downloadFile="onDownloadFile"
>
</templateImport>
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.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<T>(callBack: T extends CallBackType ? T : never) {
importData.data.upLoadUrl = typeof callBack === 'function' ? callBack() : callBack
importData.data.importShow = true
}
/**
* @method 下载模板
*/
async function onDownload(callBack: () => Promise<axiosResponse>) {
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:父组件使用
<a-button :style="{ marginLeft: '10px' }" @click="handleOpera('import')" v-permission="'CD00102'"> 导入</a-button>
<templateImport
:importData="importData"
@closeImport="(is:boolean)=>importClosed(is,setHttpTableData)"
@download="onDownload(workAreaImportTemplate)"
@downloadFile="(url:string)=>proxy?.$_u.uploadFile(url)"
>
</templateImport>
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.导出hooks封装
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<string, any>) => Promise<axiosResponse>, exportInfo: Record<string, any>) {
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:代码演示
<a-button :style="{ marginLeft: '10px' }" @click="handleOpera('export')" v-permission="'CD00103'"> 导出</a-button>
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
}
}
更多推荐
所有评论(0)