common-components/upload-image/upload-image.js

/**
 * @module upload-image
 * @description 上传图片组件
 * @property  {number}   maxCount    最大上传数,默认为6张
 * @property  {number}   lineCount   单行显示的图片数,默认为3张
 * @property  {boolean}  canEdit     是否支持编辑,默认为true
 * @property  {Array}    files       图片列表,结构形如{url: string, fileId: string},url表示可访问路径,fileId表示文件标识
 * @property  {boolean}  auto        是否自动上传,默认为true
 * @property  {boolean}  isBtnShownBefore 上传按钮显示在前还是在后,默认为false,显示在后
 * @property  {string}   apiUrl      上传api url
 * @property  {Object}   header      上传api header
 * @fires done 上传完成分发事件
 */

class FileItem {

  // 图片路径
  url = '';
  // 图片目前上传状态 0:上传失败 1:已上传 2:未上传 3:上传中
  // 图片目前上传状态 fail:上传失败 done:已上传 wait:未上传 ing:上传中
  state = '';
  // 文件id
  fileId = '';
  // 已上传数据的id
  id = '';
  // 上传进度 范围:0~100
  loadProgress = 0;

  constructor(url, state, fileId) {
    this.url = url;
    this.state = state;
    this.fileId = fileId;
  }

}

Component({

  options: {
    addGlobalClass: true,
  },

  properties: {
    // 最多可上传数,默认为6张
    maxCount: {
      type: Number,
      value: 6
    },
    // 一行最多显示数
    lineCount: {
      type: Number,
      value: 3
    },
    // 上传图片接口路径
    uploadUrl: {
      type: String,
      value: ''
    },
    // 是否能编辑
    canEdit: {
      type: Boolean,
      value: true
    },
    files: {
      type: Array,
      value: [],
    },
    auto: {
      type: Boolean,
      value: true,
    },
    // 上传按钮显示在前还是在后
    isBtnShownBefore: {
      type: Boolean,
      value: false,
    },
    // 上传api url
    apiUrl: {
      type: String,
      value: '',
    },
    // 上传api header
    header: {
      type: Object,
      value: {},
    }
  },

  observers: {
    'files': function(newVal) {
      this.handleInputFilesChange(newVal);
    },
    'auto': function(newVal) {
      if (newVal) {
        this.startUpload();
      }
    }
  },

  data: {
    /**
     * item: {url: String, state: String, fileId: String, loadProgress: Number}
     *    url: 图片路径
     *    state: 目前状态 fail:上传失败 done:已上传 wait:未上传 ing:上传中
     *    fileId: 文件id
     *    loadProgress: 上传进度
     */
    // 展示图片列表
    showFiles: [],
    // 等待上传图片列表
    waitFiles: [],
    // 已上传图片列表
    doneFiles: [],
    /**
     * 上传状态
     *    'wait': 待上传上传文件队列刚增加但未开始上传
     *    'uploading': 有上传任务正在上传中
     *    'done': 已完成(待上传队列为空)
     */
    uploadState: 'done',
  },

  methods: {

    /**
     * 选择图片接口
     */
    chooseImage() {
      wx.chooseImage({
        count: this.data.maxCount - this.data.showFiles.length,
        sizeType: ['compressed'],
        sourceType: ['album', 'camera'],
        success: (res) => {

          console.log('选择图片api调用成功', res);

          let files = res.tempFilePaths;
          let state = 'wait';

          for (let item of files) {
            let data = new FileItem(item, state, '');
            this.data.showFiles.push(data);
            this.data.waitFiles.push(data)
          }

          this.setData({
            showFiles: this.data.showFiles,
            waitFiles: this.data.waitFiles
          }, () => {
            let uploadState = 'wait';
            if (this.data.auto) {
              uploadState = 'uploading';
              this.startUpload();
            }
            this.setData({
              uploadState: uploadState
            })
          })

        },
        fail: (err) => {
          console.log('选择图片api出错', err)
        },
        complete: (cp) => {
          console.log('选择图片api完成', cp)
        }
      })
    },

    /**
     * 预览图片
     */
    previewImage(e) {
      let list = this.data.showFiles.map(item => item.url)
      let currentUrl = e.currentTarget.dataset.url;
      wx.previewImage({
        urls: list,
        current: currentUrl,
        success: (res) => {
          console.log('预览图片api调用成功', res)
        },
        fail: (err) => {
          console.log('预览图片api调用失败', err)
        },
        complete: (cp) => {
          console.log('预览图片api调用完成', cp)
        },
      })
    },

    /**
     * 删除图片
     */
    del(e) {
      let url = e.currentTarget.dataset.url;
      let showIndex = e.currentTarget.dataset.index,
        waitIndex = this.data.waitFiles.indexOf(this.data.waitFiles.find(item => item.url === url)),
        doneIndex = this.data.doneFiles.indexOf(this.data.doneFiles.find(item => item.url === url));
      this.data.showFiles.splice(showIndex, 1);
      this.data.waitFiles.splice(waitIndex, 1);
      this.data.doneFiles.splice(doneIndex, 1);
      console.log('doneFiles', this.data.doneFiles)
      this.setData({
        showFiles: this.data.showFiles,
        waitFiles: this.data.waitFiles,
        doneFiles: this.data.doneFiles
      })
    },

    /**
     * 处理传入的files变化
     */
    handleInputFilesChange(newVal) {
      let _files = this.handleInputFiles(newVal);
      this.data.uploadTask && this.data.uploadTask.abort();
      this.setData({
        showFiles: [..._files],
        doneFiles: [..._files]
      })
    },

    /**
     * 传入的文件列表数据转换为组件数据列表
     */
    handleInputFiles(data) {
      let arr = [],
        state = 'done';
      if (data instanceof Array) {
        for (let item of data) {
          let file = new FileItem(item.url, state, item.fileId);
          file.id = item.id || '';
          arr.push(file);
        }
      } else {
        arr.push(new FileItem(data.url, state, data.fileId));
      }
      return arr;
    },

    /**
     * 开始上传
     */
    startUpload() {

      if (!this.data.waitFiles || this.data.waitFiles.length <= 0) {
        this.setData({
          uploadState: 'done'
        }, () => {
          // 抛出完成上传事件
          this.triggerEvent('done', {
            files: this.data.doneFiles
          });
        });
        this.data.uploadTask = null;
        return;
      }

      this.setData({
        uploadState: 'uploading'
      })

      let current = this.data.waitFiles.pop();
      let showItem = this.data.showFiles.find(item => item.url === current.url);
      let showItemIndex = this.data.showFiles.indexOf(showItem);
      showItem.state = 'ing';
      this.setData({
        [`showFiles[${ showItemIndex }]`]: showItem
      });

      this._upload(current.url, showItem, showItemIndex);

    },

    _upload(filePath, ...otherParams) {

      let {
        showItem,
        showItemIndex,
      } = otherParams;

      if (!this.data.apiUrl) return;

      let task = wx.uploadFile({

        url: this.data.apiUrl,
        filePath: filePath,
        name: 'file',
        header: this.data.header,

        success: (res) => {
          let data = res.data;
          showItem.state = 'done';
          if (this.data.showFiles.find(item => item.url === showItem.url)) {
            showItem.fileId = JSON.parse(data).fileId;
            this.data.doneFiles.push(showItem);
            this.setData({
              [`showFiles[${ showItemIndex }]`]: showItem,
              doneFiles: this.data.doneFiles
            }, () => {
              this.startUpload();
            })
          } else {
            this.startUpload();
          }
        },

        fail: (err) => {
          // 上传失败的图片只能先删除然后在选择上传
          console.log(err);
          showItem.state = 'fail';
          this.setData({
            [`showFiles[${ showItemIndex }]`]: showItem
          }, () => {
            this.startUpload();
          })
        },

        complete: (cp) => {

        }
      });

      task.onProgressUpdate((progress, totalBytesSent, totalBytesExpectedToSend) => {
        showItem.loadProgress = progress.progress;
        this.setData({
          [`showFiles[${ showItemIndex }]`]: showItem
        });
      });

    },

  }
});