Vue3上传下载的样式模版 | 附Demo(Vue3 + Java)

前言

基本的Vue3知识推荐阅读:Vue 目录

1. 基本知识

对于下述Demo涉及以下知识点:

  1. Vue 组件基础

使用 defineComponent 定义一个 Vue 组件
script setup 是一种新的 <script> 语法糖,用于简化组件的定义
Dialog 组件用于创建一个弹出对话框,包含 v-model 双向绑定来控制对话框的显示和隐藏
el-upload 组件用于文件上传,提供多种属性和事件来处理文件上传逻辑
template #footer 是一个具名插槽,用于定义对话框底部的按钮

  1. Vue 3 Composition API

ref 和 reactive
ref 用于创建响应式的数据对象,如 dialogVisible、formLoading、fileList 等
reactive 可以创建一个响应式对象,用于更复杂的状态管理

方法和事件
通过 ref 和 defineExpose 提供方法给父组件调用,如 open 方法
详细分析Vue3中的defineExpose(附Demo)
使用 defineEmits 定义组件触发的事件,如 success 事件
详细分析Vue3中的defineEmits基本知识(子传父)

  1. Element Plus 组件库

Dialog 对话框
Dialog 组件用于创建弹出对话框,使用 v-model 绑定显示状态
width 属性设置对话框的宽度,title 属性设置对话框的标题

el-upload 文件上传
el-upload 组件用于文件上传,支持拖拽上传和点击上传
action 属性指定上传文件的服务器地址
headers 属性用于设置上传请求的头部信息

  1. 异步操作和 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-upload
      ref="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>
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 = true
  updateSupport.value = 0
  fileList.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 = true
  uploadRef.value!.submit()
}

// 文件上传成功的处理
const emits = defineEmits(['success'])
const submitFormSuccess = (response: any) => {
  if (response.code !== 0) {
    message.error(response.msg)
    formLoading.value = false
    return
  }
  // 构建成功提示信息
  const data = response.data
  let 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 = false
  dialogVisible.value = false
  emits('success')
}

// 文件上传失败的处理
const submitFormError = (): void => {
  message.error('上传失败,请您重新上传!')
  formLoading.value = false
}

// 重置表单
const resetForm = async (): Promise<void> => {
  formLoading.value = false
  uploadRef.value?.clearFiles()
}

// 文件超出数量限制的处理
const handleExceed = (): void => {
  message.error('最多只能上传一个文件!')
}

// 下载模板
const importTemplate = async () => {
  const res = await AppointmentCommissionApi.importUserTemplate()
  download.excel(res, '危品预约委托模版.xls')
}
</script>

按钮触发对话框:(以下为抽取实战中的Demo)

<template>
  <el-button
    type="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>
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 = true
  try {
    const data = await AppointmentCommissionApi.getAppointmentCommissionPage(queryParams)
    list.value = data.list
    total.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 下载

其模版接口如下:

@GetMapping("/get-import-template")
@Operation(summary = "获得导入危品委托管理的模板")
public void importTemplate(HttpServletResponse response) throws IOException {
    // 手动创建导出 demo
    List<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);
}

对应的导入类如下:

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 上传

@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));
}

对应的实现类如下:

@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;
}

对应的类如下:

@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;
}
  • 38
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农研究僧

你的鼓励将是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值