目录
- 前言
- 1. 基本知识
- 2. Demo
- 3. 彩蛋
- 3.1 下载
- 3.2 上传
前言
1. 基本知识
对于下述Demo涉及以下知识点:
- Vue 组件基础
使用 defineComponent 定义一个 Vue 组件
script setup 是一种新的 <script>
语法糖,用于简化组件的定义
Dialog 组件用于创建一个弹出对话框,包含 v-model 双向绑定来控制对话框的显示和隐藏
el-upload 组件用于文件上传,提供多种属性和事件来处理文件上传逻辑
template #footer
是一个具名插槽,用于定义对话框底部的按钮
- Vue 3 Composition API
ref 和 reactive
ref 用于创建响应式的数据对象,如 dialogVisible、formLoading、fileList 等
reactive 可以创建一个响应式对象,用于更复杂的状态管理
方法和事件
通过 ref 和 defineExpose 提供方法给父组件调用,如 open 方法
详细分析Vue3中的defineExpose(附Demo)
使用 defineEmits 定义组件触发的事件,如 success 事件
详细分析Vue3中的defineEmits基本知识(子传父)
- Element Plus 组件库
Dialog 对话框
Dialog 组件用于创建弹出对话框,使用 v-model 绑定显示状态
width 属性设置对话框的宽度,title 属性设置对话框的标题
el-upload 文件上传
el-upload
组件用于文件上传,支持拖拽上传和点击上传
action
属性指定上传文件的服务器地址
headers
属性用于设置上传请求的头部信息
- 异步操作和 API 调用
API 调用
使用 axios 或其他 HTTP 客户端库进行 API 调用,如 AppointmentCommissionApi.importUserTemplate()
处理文件下载时,使用 download 工具函数下载模板文件
4.2. 异步函数
使用 async/await 语法处理异步操作,确保操作按预期顺序执行
2. Demo
基本的语法知识只是点睛一下
如下Demo:
AppointmentImportForm(负责渲染一个对话框,允许用户上传文件以导入预约委托)
<template><Dialog v-model="dialogVisible" title="危品预约委托导入" width="400"><!-- 文件上传组件 --><el-uploadref="uploadRef"v-model:file-list="fileList":action="importUrl + '?updateSupport=' + updateSupport":auto-upload="false":disabled="formLoading":headers="uploadHeaders":limit="1":on-error="submitFormError":on-exceed="handleExceed":on-success="submitFormSuccess"accept=".xlsx, .xls"drag><Icon icon="ep:upload" /><div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div><!-- 上传提示信息 --><template #tip><div class="el-upload__tip text-center"><div class="el-upload__tip"><el-checkbox v-model="updateSupport" />是否更新已经存在的委托数据</div><span>仅允许导入 xls、xlsx 格式文件。</span><el-link:underline="false"style="font-size: 12px; vertical-align: baseline"type="primary"@click="importTemplate">下载模板</el-link></div></template></el-upload><!-- 对话框底部的按钮 --><template #footer><el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button><el-button @click="dialogVisible = false">取 消</el-button></template></Dialog>
</template><script lang="ts" setup>javascript">
import * as AppointmentCommissionApi from '@/api/dangerous/appointmentcommission'
import { getAccessToken, getTenantId } from '@/utils/auth'
import download from '@/utils/download'defineOptions({ name: 'AppointmentImportForm' })// 消息弹窗
const message = useMessage()// 对话框的显示控制
const dialogVisible = ref(false)
const formLoading = ref(false)
const uploadRef = ref()
const importUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/dangerous/appointment-commission/import'
const uploadHeaders = ref()
const fileList = ref([])
const updateSupport = ref(0)// 打开对话框
const open = () => {dialogVisible.value = trueupdateSupport.value = 0fileList.value = []resetForm()
}
defineExpose({ open })// 提交表单
const submitForm = async () => {if (fileList.value.length == 0) {message.error('请上传文件')return}// 设置上传请求头uploadHeaders.value = {Authorization: 'Bearer ' + getAccessToken(),'tenant-id': getTenantId()}formLoading.value = trueuploadRef.value!.submit()
}// 文件上传成功的处理
const emits = defineEmits(['success'])
const submitFormSuccess = (response: any) => {if (response.code !== 0) {message.error(response.msg)formLoading.value = falsereturn}// 构建成功提示信息const data = response.datalet text = '上传成功数量:' + data.createUsernames.length + ';'for (let username of data.createUsernames) {text += '< ' + username + ' >'}text += '更新成功数量:' + data.updateUsernames.length + ';'for (const username of data.updateUsernames) {text += '< ' + username + ' >'}text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';'for (const username in data.failureUsernames) {text += '< ' + username + ': ' + data.failureUsernames[username] + ' >'}message.alert(text)formLoading.value = falsedialogVisible.value = falseemits('success')
}// 文件上传失败的处理
const submitFormError = (): void => {message.error('上传失败,请您重新上传!')formLoading.value = false
}// 重置表单
const resetForm = async (): Promise<void> => {formLoading.value = falseuploadRef.value?.clearFiles()
}// 文件超出数量限制的处理
const handleExceed = (): void => {message.error('最多只能上传一个文件!')
}// 下载模板
const importTemplate = async () => {const res = await AppointmentCommissionApi.importUserTemplate()download.excel(res, '危品预约委托模版.xls')
}
</script>
按钮触发对话框:(以下为抽取实战中的Demo)
<template><el-buttontype="warning"plain@click="handleImport"v-hasPermi="['dangerous:appointment-commission:import']"><Icon icon="ep:upload" class="mr-5px" /> 导入</el-button><!-- 用户导入对话框 --><AppointmentImportForm ref="importFormRef" @success="getList" />
</template><script lang="ts" setup>javascript">
import AppointmentImportForm from '@/components/AppointmentImportForm.vue'
import { ref } from 'vue'
import * as AppointmentCommissionApi from '@/api/dangerous/appointmentcommission'// 引用导入表单组件
const importFormRef = ref()// 触发导入对话框
const handleImport = () => {importFormRef.value.open()
}// 获取列表数据
const getList = async () => {loading.value = truetry {const data = await AppointmentCommissionApi.getAppointmentCommissionPage(queryParams)list.value = data.listtotal.value = data.total} finally {loading.value = false}
}
</script>
以及接口文件:
// 下载危品导入模板
export const importUserTemplate = () => {return request.download({ url: '/dangerous/appointment-commission/get-import-template' })
}
Demo如下:
3. 彩蛋
由于是Java作为后端,此处补充Java的基本接口:(只提供思路,后端代码给的不全)
对应的上传下载推荐阅读:【Java项目】实战CRUD的功能整理(持续更新)
3.1 下载
其模版接口如下:
java">@GetMapping("/get-import-template")
@Operation(summary = "获得导入危品委托管理的模板")
public void importTemplate(HttpServletResponse response) throws IOException {// 手动创建导出 demoList<AppointmentCommissionImportExcelVO> list = Arrays.asList(AppointmentCommissionImportExcelVO.builder().chineseShipName("xx").shipVoyage("xx").appointmentCompany("xx").appointmentType("装船").appointmentEntryTime(LocalDate.parse("2024-05-29").atStartOfDay()).build(),AppointmentCommissionImportExcelVO.builder().chineseShipName("xx").shipVoyage("xx").appointmentCompany("xx").appointmentType("卸船").appointmentEntryTime(LocalDate.parse("2024-05-29").atStartOfDay()).build());// 输出ExcelUtils.write(response, "危品预约委托管理导入模板.xls", "危品预约", AppointmentCommissionImportExcelVO.class, list);
}
对应的导入类如下:
java">package cn.iocoder.yudao.module.dangerous.controller.admin.appointmentcommission.vo;import com.alibaba.excel.annotation.ExcelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;import java.time.LocalDateTime;@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = false) // 设置 chain = false,避免用户导入有问题
public class AppointmentCommissionImportExcelVO {@ExcelProperty("中文船名")private String chineseShipName;@ExcelProperty("船舶航次")private String shipVoyage;@ExcelProperty("预约公司")private String appointmentCompany;@ExcelProperty("预约类型")private String appointmentType;@ExcelProperty("预约进场时间")private LocalDateTime appointmentEntryTime;
}
3.2 上传
java">@PostMapping("/import")
@Operation(summary = "导入危品预约委托")
@Parameters({@Parameter(name = "file", description = "Excel 文件", required = true),@Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true")
})
@PreAuthorize("@ss.hasPermission('dangerous:appointment-commission:import')")
public CommonResult<AppointmentCommissionImportResqVO> importExcel(@RequestParam("file") MultipartFile file,@RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception {List<AppointmentCommissionImportExcelVO> list = ExcelUtils.read(file, AppointmentCommissionImportExcelVO.class);return success(appointmentCommissionService.importAppointmentCommissionList(list, updateSupport));
}
对应的实现类如下:
java">@Override
@Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
public AppointmentCommissionImportResqVO importAppointmentCommissionList(List<AppointmentCommissionImportExcelVO> importAppointmentCommissionDatas, boolean isUpdateSupport) {if(CollUtil.isEmpty(importAppointmentCommissionDatas)){throw exception(APPOINTMENT_COMMISSION_IMPORT_LIST_IS_EMPTY);}AppointmentCommissionImportResqVO respVO = AppointmentCommissionImportResqVO.builder().createChineseShipName(new ArrayList<>()).updateChineseShipName(new ArrayList<>()).failureChineseShipName(new LinkedHashMap<>()).build();importAppointmentCommissionDatas.forEach(importAppointmentCommissionData ->{try {AppointmentCommissionDO insertDo = BeanUtils.toBean(importAppointmentCommissionData, AppointmentCommissionDO.class);String orderNo = redisIdGeneratorService.generatorOrderNo("SQ");insertDo.setAppointmentId(orderNo);insertDo.setAppointmentStatus("未提交");appointmentCommissionMapper.insert(insertDo);respVO.getCreateChineseShipName().add(importAppointmentCommissionData.getChineseShipName());} catch (Exception e) {respVO.getFailureChineseShipName().put(importAppointmentCommissionData.getChineseShipName(), e.getMessage());}});return respVO;
}
对应的类如下:
java">@Schema(description = "危品导入 Response VO")
@Data
@Builder
public class AppointmentCommissionImportResqVO {@Schema(description = "创建成功中文船名", requiredMode = Schema.RequiredMode.REQUIRED)private List<String> createChineseShipName;@Schema(description = "更新成功的中文船名", requiredMode = Schema.RequiredMode.REQUIRED)private List<String> updateChineseShipName;@Schema(description = "导入失败的中文船名,key 为用户名,value 为失败原因", requiredMode = Schema.RequiredMode.REQUIRED)private Map<String, String> failureChineseShipName;
}