Ant Design Vue 中a-upload組件通過axios實現文件列表上傳與更新回顯的前後端處理方案

前言

在企業應用的快速開發中,我們需要儘快的完成一些功能。如果您使用了Ant Design Vue,在進行表單的文件上傳相關功能開發的時候,您肯定迫不及待地需要找到一篇包治百病的文章,正是如此,纔有了該文的誕生,願以此文解君憂。

方案設計

前端方案設計

  • 重寫a-upload的文件上傳方式,使用axios來進行上傳
  • 選擇一個文件後立即進行上傳,前端記錄上傳成功後的name和uid,並構建一個File實例,用於a-upload組件的已上傳文件列表的回顯
  • 提交前讓文件列表轉化爲要後端要處理的uid列表

後端方案設計

  • 提供一個統一上傳單個文件的接口,每個文件選擇後自動上傳都將上傳到該接口,並寫入到數據庫的file數據表中
  • 對錶單數據新建完成後,對上傳的文件列表進行當前實體記錄的綁定
  • 對錶單數據更新完成後,檢測該實體記錄的所有文件列表,對沒有在該列表中的uid的文件列表進行刪除,然後對該列表中所有的uid文件記錄進行當前實體記錄的綁定

新建與更新的一致性處理方案

  • 因爲更新表單在讀取舊數據後,需要與新選擇文件進行同樣的格式化處理,這裏的處理流程一樣,進行回顯的數據是一樣的,提交表單也都是提交file表中已經存在的uid列表,所以這裏的數據結構是一致的,處理起來將會更加簡潔明瞭。

讓代碼說話

爲了讓各位看官老爺們便於理解,直接上代碼,希望能將整件事說明白。

構建表單

  
<a-form :form="form">  
    <a-form-item label="名稱" style="margin-bottom: 0;">  
        <a-input v-decorator="['name', {rules: [{required: true, message: '請輸入名稱!'}]}]" />  
    </a-form-item>
    <a-form-item>  
      <a-upload  
      :multiple="true"  
      :fileList="downloadFiles"  
      :remove="handleDownloadFileRemove"  
      :customRequest="downloadFilesCustomRequest"  
      >  
        <a-button class="upload-btn"> <a-icon type="upload" > 相關下載 </a-button>  
      </a-upload>  
    </a-form-item>
</a-form>

編寫js代碼

  • 請求後端接口的token、header以及baseUrl等我已默認您已經在axios的統一設置中已經配置好了
  • 爲了簡化axios相關操作,我們將axios進行了如下封裝(您也可以按此完全使用axios來直接對數據進行提交等):
const dibootApi = {
    get (url, params) {  
      return axios.get(url, {  
        params  
      })  
    },  
    upload(url, formData) {  
      return service({  
        url,  
        method: 'POST',  
        data: formData  
      })  
    }
}
export default dibootApi
  • 我們默認爲demo實體中需要上傳一些文件列表
export default {
    name: 'demoForm',
    data () {
        title: '新建',                            // 該表單的功能標題
        form: this.$form.createForm(this),        // 表單數據初始化,沒什麼好說的
        model: {},                                // 如果是更新表單,之前的數據放到這裏,來做數據初始化顯示之用
        downloadFiles: []                         // 已經上傳的文件列表
    },
    methods: {
        // 初始打開的表單時的處理(如果是更新表單,這裏首先需要讀取出相關數據)
        async open (id) {  
          if (id === undefined) {  
            // 沒有id數據則認爲是新建  
            this.model = {}  
            this.afterOpen()  
          } else {  
            // 否則作爲更新處理  
            const res = await dibootApi.get(`/${this.name}/${id}`)  
            if (res.code === 0) {  
                this.model = res.data  
                this.title = '編輯'  
                this.afterOpen(id)  
            } else {  
                this.$notification.error({  
                    message: '獲取數據失敗',  
                    description: res.msg  
                })  
            }  
          }  
        },
        // 更新表單在讀取數據完成後的操作
        afterOpen (id) {  
            // 獲取該記錄信息後,回顯文件列表相關操作
            dibootApi.post(`/demo/getFiles/${id}`).then(res => {  
                if (res.code === 0){  
                    if (res.data.downloadFile !== undefined){  
                        res.data.downloadFile.forEach(data => {  
                            this.downloadFiles.push(this.fileFormatter(data))  
                        })  
                    }
                }  
            })  
        },
        // 重寫a-upload的文件上傳處理方式
        downloadFilesCustomRequest (data) {  
            this.saveFile(data)  
        },  
        // 上傳並保存文件
        saveFile (data){  
            const formData = new FormData()  
            formData.append('file', data.file)  
            dibootApi.upload('/demo/upload', formData).then((res) => {  
                if (res.code === 0){  
                    let file = this.fileFormatter(res.data) 
                    // 上傳單個文件後,將該文件會先到a-upload組件的已上傳文件列表中的操作
                    this.downloadFiles.push(file) 
                } else {  
                    this.$message.error(res.msg)  
                }  
            })  
        },
        // 對上傳成功返回的數據進行格式化處理,格式化a-upload能顯示在已上傳列表中的格式(這個格式官方文檔有給出的)
        fileFormatter(data) {  
            let file = {  
                uid: data.uuid,    // 文件唯一標識,建議設置爲負數,防止和內部產生的 id 衝突  
                name: data.name,   // 文件名  
                status: 'done', // 狀態有:uploading done error removed  
                response: '{"status": "success"}', // 服務端響應內容  
            }  
            return file  
        },
        // 沒錯,刪除某個已上傳的文件的時候,就是調用的這裏
        handleDownloadFileRemove (file) {  
            const index = this.downloadFiles.indexOf(file)  
            const newFileList = this.downloadFiles.slice()  
            newFileList.splice(index, 1)  
            this.downloadFiles = newFileList  
        },
        // 表單校驗就靠他了,不過這裏面還是可以對需要提交的一些數據做些手腳的
        validate () {  
            return new Promise((resolve, reject) => {  
                this.form.validateFields((err, fieldsValue) => {  
                    if (!err) {  
                        // 設置上傳文件列表  
                        const downloadFiles = this.downloadFiles.map(o => {  
                            return o.uid  
                        })  
                        const values = {  
                            ...fieldsValue,  
                            'downloadFiles': downloadFiles
                        }  
                        resolve(values)  
                    } else {  
                        reject(err)  
                    }
                })  
            })  
        },
        // 表單提交的相關操作
        async onSubmit () {  
            const values = await this.validate()  
            try {  
                let result = {}  
                if (this.model.id === undefined) {  
                    // 新增該記錄  
                    result = await this.add(values)  
                } else {  
                    // 更新該記錄  
                    values['id'] = this.model.id  
                    result = await this.update(values)  
                }  

                // 執行提交成功的一系列後續操作  
                this.submitSuccess(result)  
            } catch (e) {  
                // 執行提交失敗的一系列後續操作  
                this.submitFailed(e)  
            }  
        },
        // 新增數據的操作
        async add (values) {
            ....
        },
        // 更新數據的操作
        async update (values) {
            ...
        }
    }
}

編寫SpringBoot相關的接口代碼

  • DemoController

/***  
 * 獲取文件信息列表  
 * @param id  
 * @return  
 * @throws Exception  
 */
@PostMapping("/getFiles/{id}")  
public JsonResult getFilesMap(@PathVariable("id") Serializable id) throws Exception{  
    List<File> files = fileService.getEntityList(  
            Wrappers.<File>lambdaQuery()  
                    .eq(File::getRelObjType, Demo.class.getSimpleName())  
                    .eq(File::getRelObjId, id)  
    );  
    return new JsonResult(Status.OK, files);  
}

/***  
 * 上傳文件  
 * @param file  
 * @param request  
 * @return  
 * @throws Exception  
 */
@PostMapping("/upload")  
public JsonResult upload(@RequestParam("file") MultipartFile file) throws Exception {  
    File fileEntity = demoService.uploadFile(file);  
    return new JsonResult(Status.OK, fileEntity, "上傳文件成功");  
}

/***
* 創建成功後的相關處理
* @param entity
* @return
*/
@Override
protected String afterCreated(BaseEntity entity) throws Exception {
    DemoDTO demoDTO = (DemoDTO) entity;
    // 更新文件關聯信息
    demoService.updateFiles(new ArrayList<String>(){{
        addAll(demoDTO.getDownloadFiles());
    }}, demoDTO.getId(), true);
    return null;
}

/***
* 更新成功後的相關處理
* @param entity
* @return
*/
@Override
protected String afterUpdated(BaseEntity entity) throws Exception {
    DemoDTO demoDTO = (DemoDTO) entity;
    // 更新文件關聯信息
    demoService.updateFiles(new ArrayList<String>(){{
        addAll(demoDTO.getDownloadFiles());
    }}, demoDTO.getId(), false);
    return null;
}
  • DemoService
@Override  
public File uploadFile(MultipartFile file) {  
    if(V.isEmpty(file)){  
        throw new BusinessException(Status.FAIL_OPERATION, "請上傳圖片");  
    }  
    String fileName = file.getOriginalFilename();  
    String ext = fileName.substring(fileName.lastIndexOf(".")+1);  
    String newFileName = S.newUuid() + "." + ext;  
    //TODO: 需要對合法的文件類型進行驗證  
    if(FileHelper.isImage(ext)){  
        throw new BusinessException(Status.FAIL_OPERATION, "請上傳合法的文件類型");  
    };  
    
    // 說明:此處爲我們的處理流程,看官們需要根據自己的需求來對文件進行保存及處理(之後我們的File組件開源之後也可以按照此處的處理)
    String filePath = FileHelper.saveFile(file, newFileName);  
    if(V.isEmpty(filePath)){  
        throw new BusinessException(Status.FAIL_OPERATION, "圖片上傳失敗");  
    }  
  
    File fileEntity = new File();  
    fileEntity.setRelObjType(Demo.class.getSimpleName());  
    fileEntity.setFileType(ext);  
    fileEntity.setName(fileName);  
    fileEntity.setPath(filePath);  
    String link = "/file/download/" + D.getYearMonth() + "_" + newFileName;  
    fileEntity.setLink(link);  
  
    boolean success = fileService.createEntity(fileEntity);  
    if (!success){  
        throw new BusinessException(Status.FAIL_OPERATION, "上傳文件失敗");  
    }  
    return fileEntity;  
}  
  
@Override  
public void updateFiles(List<String> uuids, Long currentId, boolean isCreate) {  
    // 如果不是創建,需要刪除不在列表中的file記錄  
    if (!isCreate){  
        fileService.deleteEntities(Wrappers.<File>lambdaQuery().notIn(File::getUuid, uuids));  
    }  
    // 進行相關更新  
    boolean success = fileService.updateEntity(  
            Wrappers.<File>lambdaUpdate()  
                    .in(File::getUuid, uuids)  
                    .set(File::getRelObjType, Demo.class.getSimpleName())  
                    .set(File::getRelObjId, currentId));  
    if (!success){  
        throw new BusinessException(Status.FAIL_OPERATION, "更新文件信息失敗");  
    }  
}

提示

  • 該文章所有代碼皆爲方案示例,其中有些許刪減,不確保能直接運行,如果各位有好的想法,歡迎一起探討。

diboot 簡單高效的輕代碼開發框架 (求star)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章