【需求】多文件一起上传时携带不同的参数

69 阅读2分钟

首先我这边得到的需求是系统升级的固件上传,即多个固件上传的同时携带不同的参数,并在后端绑定分组+升级顺序。

(ps:本人是个纯正的后端java开发,只是意外来做了一部分前端的内容,各位大佬要是有更好的方法,可以评论告诉我,轻喷,栓q。) 比如A固件是A厂商、A品牌、A产品型号、mesh模块,升级顺序是1,B固件是A厂商、B品牌、C产品型号、gateway模块、升级顺序2.这时候我们要把这两个固件同时上传到后端,并匹配其前端选择的数据,进行分组和存储。

前端实现

用到了element组件库里的el-tabs组件,官网实现如下图:

image.png

但是我们这里的需求仅靠这个无法实现,因为我们这里的上传需要依靠多表单的提交才能实现,所以在el-tabs的基础上需要加上一个表单的循环。

image.png

具体前端实现如下

<el-dialog :title="$t('common.addSystemUpdate')" :visible.sync="systemUpgradeDialogVisible" width="900px"
      append-to-body>
      <div style="margin-bottom: 20px;">
        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="addTab(editableTabsValue)">{{
          $t('common.add')
        }}</el-button>
      </div>
      <el-tabs v-model="editableTabsValue" type="card" closable @tab-remove="removeTab" @tab-click="clickTab">
        <el-tab-pane v-for="(item, index) in editableTabs" :key="item.name" :label="item.title" :name="item.name">
          <el-form ref="form" :model="form" 
          label-width="auto">
            <el-row>
              <el-col :span="12">
                <el-form-item :label="$t('manufacturer.manufacturerName')" prop="manufacturerName">
                  <el-select v-model="item.form.manufacturerName" :placeholder="$t('brand.manufacturerHolder')"
                    @change=selectChanged(item.form)>
                    <el-option v-for="dict in manufacturerList" :key="dict.manufacturerName"
                      :label="dict.manufacturerName" :value="dict.manufacturerName"></el-option>
                  </el-select>
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item :label="$t('brand.brandName')" prop="brandName">
                  <el-select v-model="item.form.brandName" :placeholder="$t('category.brandHolder')"
                    @change=brandSelectChanged(item.form)>
                    <el-option v-for="dict in brandList" :key="dict.brandName" :label="dict.brandName"
                      :value="dict.brandName"></el-option>
                  </el-select>
                </el-form-item>
              </el-col>
            </el-row>
            <el-row>
              <el-col :span="12">
                <el-form-item :label="$t('firmware.firmwareNumberCode')" prop="firmwareNumberCode">
                  <el-select v-model="item.form.firmwareId" :placeholder="$t('firmware.firmwareNumberCodeHolder')"
                    @change=firmwareSelectChanged(item.form)>
                    <el-option v-for="dict in firmwareNumberList" :key="dict.id" :label="dict.firmwareNumberName"
                      :value="dict.id"></el-option>
                  </el-select>
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item :label="$t('model.module')" prop="type">
                  <el-select v-model="item.form.moduleId" :placeholder="$t('model.moduleHolder')"
                  @change=moduleSelectChanged(item.form)>
                    <el-option v-for="dict in moduleList" :key="dict.moduleId" :label="dict.moduleName"
                      :value="dict.moduleId"></el-option>
                  </el-select>
                </el-form-item>
              </el-col>
            </el-row>
            <el-row>
              <el-col :span="12">
                <el-form-item :label="$t('device.firmware.upgradeType')">
                  <el-select v-model="item.form.upgradeType" :placeholder="$t('device.firmware.upgradeTypeHolder')">
                    <el-option v-for="dict in dict.type.firmware_upgrade_type" :key="dict.value" :label="dict.label"
                      :value="dict.value"></el-option>
                  </el-select>
                </el-form-item>
              </el-col>
              <el-col :span="8">
                <el-form-item :label="$t('device.firmware.upOrder')">
                  <el-input-number v-model="item.form.upOrder" :min="1" :max="10" controls-position="right"></el-input-number>
                </el-form-item>
              </el-col>
            </el-row>
            <el-row>
              <el-col :span="24">
                <el-form-item :label="$t('device.firmware.content')">
                  <el-input v-model="item.form.content" type="textarea"
                    :placeholder="$t('device.firmware.contentHolder')"></el-input>
                </el-form-item>
              </el-col>
            </el-row>
            <el-row>
              <el-col :span="24">
                <el-form-item :label="$t('device.firmware.head')">
                  <el-transfer :titles="['Source', 'Target']" v-model="item.form.headList" :data="data"></el-transfer>
                </el-form-item>
              </el-col>
            </el-row>
            <el-row>
              <el-col :span="24">
                <el-form-item :label="$t('device.firmware.uploadFirmware')">
                  <el-upload ref="systemUpload" name="file" class="upload-demo" drag action="#" :auto-upload="true" :multiple="false"
                    :http-request="systemHttpRequest" :on-change="changeUpload">
                    <i class="el-icon-upload"></i>
                    <div class="el-upload__text">{{ $t('device.firmware.uploadTip') }}</div>
                    <div class="el-upload__tip" slot="tip" style="color:red">{{ $t('device.firmware.uploadText') }}</div>
                  </el-upload>
                </el-form-item>
              </el-col>
            </el-row>
          </el-form>
        </el-tab-pane>
      </el-tabs>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitSystemForm" v-loading.fullscreen.lock="fullscreenLoading"
          :element-loading-text="$t('common.uploading')">{{ $t('tooltip.ok') }}</el-button>
        <el-button @click="systemCancel">{{ $t('tooltip.cancel') }}</el-button>
      </div>
    </el-dialog>

data部分

data() {
    return {
      // 遮罩层
      loading: true,
      fullscreenLoading: false,
      systemUpgradeDialogVisible: false,
      editableTabsValue: '1',
      editableTabs: [{
        title: 标题,
        name: '1',
        form: {
          //表单字段
        }
      }],
      tabIndex: 1
    };

js部分关键方法 这里我们的处理方法是把所有携带的字段属性拼接成一个json,然后作为file的key赋值,然后存进filelist传给后端。

// 新增系统升级相关方法
    handleSystemAdd() {
      this.editableTabs = []
      this.editableTabs.push({
        title: this.$t('device.firmware.addFirmware'),
        name: "1",
        form:{
          firmwareName: undefined,
          versionCode: undefined,
          versionName: undefined,
          status: "0",
          upOrder: 1,
          content: undefined,
          firmwareId: undefined,
          file: [],
          headList:[1, 2, 3, 6, 8, 9, 10]
        }
      });
      list(this.query).then(res => {
        this.manufacturerList = res.data.list
      })
      this.data = []
      this.dict.type.firmware_upload_title.forEach(dict => {
        this.data.push({
          key: parseInt(dict.value),
          label: dict.label,
          disabled: this.headList.indexOf(parseInt(dict.value)) >= 0
        })
      })
      this.systemUpgradeDialogVisible = true;
      this.title = this.$t('common.addSystemUpdate');
    },
    addTab(targetName) {
      let newTabName = ++this.tabIndex + '';
      this.editableTabs.push({
        title: this.$t('device.firmware.addFirmware'),
        name: newTabName,
        form:{
          firmwareName: undefined,
          versionCode: undefined,
          versionName: undefined,
          status: "0",
          upOrder: 1,
          content: undefined,
          firmwareId: undefined,
          file: [],
          headList:[1, 2, 3, 6, 8, 9, 10]
        }
      });
      this.editableTabsValue = newTabName;
    },
    clickTab(tab,event) {
      this.editableTabsValue = tab.name;
    },
    removeTab(targetName) {
      let tabs = this.editableTabs;
      let activeName = this.editableTabsValue;
      if (activeName === targetName) {
        tabs.forEach((tab, index) => {
          if (tab.name === targetName) {
            let nextTab = tabs[index + 1] || tabs[index - 1];
            if (nextTab) {
              activeName = nextTab.name;
            }
          }
        });
      }
      this.editableTabsValue = activeName;
      this.editableTabs = tabs.filter(tab => tab.name !== targetName);
    },
    submitSystemForm() {
      this.fullscreenLoading = true
      const params = new FormData()
      var upOrderArr = []
      // 判断你上传固件顺序是否有重复
      upOrderArr = this.editableTabs.map(tab => {
        return tab.form.upOrder
      })
      for (var i = 0; i < upOrderArr.length; i++) {
        if (upOrderArr.indexOf(upOrderArr[i]) !== upOrderArr.lastIndexOf(upOrderArr[i])) {
          this.fullscreenLoading = false
          this.$modal.msgError(this.$t('device.firmware.msg1'));
          return
        }
      }
      // 处理标签列表数据
      this.editableTabs.forEach((tab, index) => {
        let form = tab.form
        var data = {}
        if (form.moduleId) {
          data.moduleId = form.moduleId
          data.moduleName = form.moduleName
        }
        data.firmwareId = form.firmwareId
        data.upgradeType = form.upgradeType
        data.headPayloads = form.headList
        data.content = form.content
        data.upOrder = form.upOrder
        params.append(JSON.stringify(data),tab.form.file[0].file)
      })
      let config = {
        headers: {
          "Content-Type": "multipart/form-data",
          "repeatSubmit": false,
          "Authorization": 'Bearer ' + getToken()
        }
      }
      axios.post(process.env.VUE_APP_BASE_API + "你的上传接口的路径", params, config)
        .then(response => {
          if (response.data.code === 200) {
            this.$modal.msgSuccess(response.data.msg === "" ? "操作成功" : response.data.msg);
          } else {
            this.$modal.msgError(response.data.msg === "" ? "操作失败" : response.data.msg);
          }
          //上传完成后刷新页面
          this.systemUpgradeDialogVisible = false
          this.getList()
          this.fullscreenLoading = false
          this.systemCancel()
        })
    },
    systemCancel() {
      this.tabIndex = 1
      this.editableTabsValue = '1',
      this.editableTabs = []
      this.systemUpgradeDialogVisible = false;
      this.data = []
    }

后端接收处理

因为后端接收的时候我们接收的是一个List,所以这里需要做一些额外的处理。 大佬同事帮我写了一个文件注解过滤器。作用是处理前端传递过来的文件集合。主要方法如下:

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);

    if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
        MultipartRequest multipartRequest = webRequest.getNativeRequest(MultipartRequest.class);
        Collection<Part> parts = servletRequest.getParts();
        if (Map.class.isAssignableFrom(parameter.getParameterType())) {
            Map<String, MultipartFile> result = new HashMap<>();
            for (Part part : parts) {
                List<MultipartFile> files = multipartRequest.getFiles(part.getName());
                result.put(part.getName(), files.get(0));
            }
            return result;
        }else if (List.class.isAssignableFrom(parameter.getParameterType())){
            List<MultipartFile> result = new ArrayList<>();
            for (Part part : parts) {
                List<MultipartFile> files = multipartRequest.getFiles(part.getName());
                result.addAll(files);
            }
            return result;
        }
    }
    return null;
}

然后就可以对接收到的文件列表进行处理了


@Override
@Transactional
public AjaxResult systemAdd(List<MultipartFile> files) throws IOException {
    for (MultipartFile file : files) {
        try {
            String data = URLDecoder.decode(file.getName(),"utf-8");
            // 取出来的data就是你前端传递的form表单的数据,然后可以进行具体的业务逻辑了
            // ps:这里的数据需要进行编码,不然就会出现乱码
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
    ......
    return AjaxResult.success();
}