离线上传方案

发表于:2021-06-08 16:56技术,浏览器热度:60喜欢:0

有个需求需要断网环境下上传文件,在有网环境下提交
附件需要提交到单独的文件服务器,返回文件服务器的id
框架是element-ui

设计思路

  1. 利用浏览器自带的indexDB,存储blob数据
  2. 利用localStorage缓存表单数据,indexDB的key
  3. 在有网的环境下,从indexDB获取数据,附件上传完成后,拿到id,拼接到表单的附件对象里面

封装indexDB

if (!window.indexedDB) {
  window.alert('不支持indexDB数据库');
  return false;
}
const debug = true;
const log = debug ? window.console.log : () => { }

class HrIndexDB {
  db = null;
  dbName = null;
  storeName = null;
  constructor(storeName = 'storeName', dbName = "HR_INDEX_DB") {
    let request = window.indexedDB.open(dbName);
    this.storeName = storeName;
    this.dbName = dbName;
    request.onupgradeneeded = (event) => {
      this.db = event.target.result;
      // 不存在objectStore实例,创建实例
      if (!this.db.objectStoreNames.contains(storeName)) {
        const objectStore = this.db.createObjectStore(storeName, { keyPath: 'id' });
        // 创建索引
        objectStore.createIndex('id', 'id', { unique: false })
      }
    }

    request.onerror = (event) => {
      // 错误
    }

    request.onsuccess = (event) => {
      this.db = event.target.result;
    }
  }
  insert = (id, value) => {
    const transcation = this.db.transcation([this.storeName], 'readwrite');
    transcation.objectStore(this.storeName).add({ id, value });

    transcation.onerror = (event) => {
      log('insert error', event)
    }
    transcation.onsuccess = () => {
      log('insert success')
    }
  }
  select(id, cb) {
    const objectStore = this.db.transcation([this.storeName]).objectStore(this.storeName);
    const request = objectStore.get(id);

    request.onsuccess = (event) => {
      cb(request.result, event)
    }
  }
  selectAsync(id) {
    return new Promise((resolve, reject) => {
      const objectStore = this.db.transcation([this.storeName]).objectStore(this.storeName);
      const request = objectStore.get(id);

      request.onsuccess = event => {
        resolve(request.result, event)
      }
      request.onerror = (err) => {
        reject(err);
        console.log('indexDB selectError', err)
      }
    })
  }
  del(id) {
    const objectStore = this.db.transcation([this.storeName], 'readwrite').objectStore(this.storeName, 'readwrite');
    objectStore.delete(id);
  }
  listAll(cb) {
    const objectStore = this.db.transcation([this.storeName], 'readwrite').objectStore(this.storeName, 'readwrite');
    objectStore.openCursor().onsuccess = event => {
      let cursor = event.target.result;
      if (cursor) {
        cb(cursor)
        cursor.continue()
      } else {

      }
    }
  }

  clearAll() {
    const objectStore = this.db.transcation([this.storeName], 'readwrite').objectStore(this.storeName, 'readwrite');
    objectStore.openCursor().onsuccess = event => {
      let cursor = event.target.result;
      if (cursor) {
        cb(cursor)
        this.del(cursor.key)
        cursor.continue()
      } else {
        console.log('清空完成')
      }
    }
  }
}
export default HrIndexDB;

拦截element-ui上传

  1. 改写httpRequest方法
    <el-form>
      <el-form-item v-for="item in itemList" :key="item.key" :label="item.name" :prop="item.key">
       <el-upload :httpRequest="customHttpRequest" :data="{item,standard}" />
      </el-form-item>
    </el-form>
    
    const hrIndexDB = new IndexDB()
    {
      data(){
        return{
          itemList:[
            {name:"表单1",key:"formItem1",}
         ]
        }
      }
      methods:{
        customHttpRequest(option){
          const fileName = option.file.name;
          const {item,standard} = option.data.item;
          //每个表单项可以存在多个上传的附件,用于计数
          const count = item.attachmentList? item.attachmentList.length:0;
          const key = `attachment_${item.key}`;
          //将上传的文件转换成blob
          const blob = new Blob([option,file],{type:option.file.type});
          const data = {
            key,
            blob,
            fileName,
            type:option.file.type,
            inputTime
          };
    
          //将附件的所有数据缓存到
          hrIndexDB.insert(key,data);
    
          if(item.attachmentList){
            item.attachmentList.push(data)
          }else{
            item.attachmentList = [data];
          }
    
        }
      }
    }
    

存储到localStorage

{
  data(){
    return{
      itemList:[
        {name:"表单1",key:"formItem1"}
    ]
    }
  }
  methods:{
    handleSave(){
      this.itemList.forEach(item=>{
        const key = `attachment_${item.key}`;
        localStorage.setItem(key,JSON.stringify(item))
      });
    }
  }
}

提交


{
  methods:{
    handleUpload(formData){
      return new Promise((resolve,reject)=>{
        axios({
          url:'',
          method:'post'
          data:formData
        })
      })
    },
    // 提交
    async submit(){
      const taskList = [];//批量上传的表单队列
      const attachmentList = [];//附件队列
      this.itemList.forEach(item=>{
        const data = item;
        data.proofList = [];//上传完成后的
        // 在拦截阶段,上传的存储到indexDB的数据
        if(item.attachmentList){
          item.attachmentList.forEach(attachment=>{
            // 利用引用类型,把item传递过去,改变taskList里面 proofList字段
            attachmentList.push({item,key:attachment.key})
          })
        }
      })
      await this.handleUploadRemoteBatch(attachmentList);
      
      axios({
        url:'',
        data:taskList,
        method:'post'
      })

    }
    // 单线程上传队列
    async handleUploadRemoteBatch(attachmentList){
      const list  = attachmentList;
      let index = 0;
      const Length = list.length;

      while(index<Length){
        const {key,item} = list[index];
        const indexDBItem = await hrIndexDB.selectAsync(key);
        const {value} = indexDBItem;
        const {blob,fileName,type} = value;
        const file = new File([blob],fileName,{type});
        const forData = new FormData();
        formData.append('file',file);
        formData.append('fileName',fileName);

        const {status,data} = await this.handleUpload(formData);
        const {id} = data;
        item.proofList.push({name:fileName,id});
        index++
      }
    }
  }
}