| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674 | <template>  <view class="uni-file-picker">    <view v-if="title" class="uni-file-picker__header">      <text class="file-title">{{ title }}</text>      <text class="file-count">{{ filesList.length }}/{{ limitLength }}</text>    </view>    <view v-if="subtitle" class="file-subtitle">      <view>{{ subtitle }}</view>    </view>    <upload-image      v-if="fileMediatype === 'image' && showType === 'grid'"      :readonly="readonly"      :image-styles="imageStyles"      :files-list="url"      :limit="limitLength"      :disablePreview="disablePreview"      :delIcon="delIcon"      @uploadFiles="uploadFiles"      @choose="choose"      @delFile="delFile"    >      <slot>        <view class="is-add">          <image :src="imgsrc" class="add-icon"></image>        </view>      </slot>    </upload-image>    <upload-file      v-if="fileMediatype !== 'image' || showType !== 'grid'"      :readonly="readonly"      :list-styles="listStyles"      :files-list="filesList"      :showType="showType"      :delIcon="delIcon"      @uploadFiles="uploadFiles"      @choose="choose"      @delFile="delFile"    >      <slot><button type="primary" size="mini">选择文件</button></slot>    </upload-file>  </view></template><script>  import { chooseAndUploadFile, uploadCloudFiles } from './choose-and-upload-file.js';  import {    get_file_ext,    get_extname,    get_files_and_is_max,    get_file_info,    get_file_data,  } from './utils.js';  import uploadImage from './upload-image.vue';  import uploadFile from './upload-file.vue';  import sheep from '@/sheep';  let fileInput = null;  /**   * FilePicker 文件选择上传   * @description 文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间   * @tutorial https://ext.dcloud.net.cn/plugin?id=4079   * @property {Object|Array}	value	组件数据,通常用来回显 ,类型由return-type属性决定   * @property {String|Array}	url	  url数据   * @property {Boolean}	disabled = [true|false]	组件禁用   * 	@value true 	禁用   * 	@value false 	取消禁用   * @property {Boolean}	readonly = [true|false]	组件只读,不可选择,不显示进度,不显示删除按钮   * 	@value true 	只读   * 	@value false 	取消只读   * @property {Boolean}	disable-preview = [true|false]	禁用图片预览,仅 mode:grid 时生效   * 	@value true 	禁用图片预览   * 	@value false 	取消禁用图片预览   * @property {Boolean}	del-icon = [true|false]	是否显示删除按钮   * 	@value true 	显示删除按钮   * 	@value false 	不显示删除按钮   * @property {Boolean}	auto-upload = [true|false]	是否自动上传,值为true则只触发@select,可自行上传   * 	@value true 	自动上传   * 	@value false 	取消自动上传   * @property {Number|String}	limit	最大选择个数 ,h5 会自动忽略多选的部分   * @property {String}	title	组件标题,右侧显示上传计数   * @property {String}	mode = [list|grid]	选择文件后的文件列表样式   * 	@value list 	列表显示   * 	@value grid 	宫格显示   * @property {String}	file-mediatype = [image|video|all]	选择文件类型   * 	@value image	只选择图片   * 	@value video	只选择视频   * 	@value all		选择所有文件   * @property {Array}	file-extname	选择文件后缀,根据 file-mediatype 属性而不同   * @property {Object}	list-style	mode:list 时的样式   * @property {Object}	image-styles	选择文件后缀,根据 file-mediatype 属性而不同   * @event {Function} select 	选择文件后触发   * @event {Function} progress 文件上传时触发   * @event {Function} success 	上传成功触发   * @event {Function} fail 		上传失败触发   * @event {Function} delete 	文件从列表移除时触发   */  export default {    name: 'sUploader',    components: {      uploadImage,      uploadFile,    },    options: {      virtualHost: true,    },    emits: ['select', 'success', 'fail', 'progress', 'delete', 'update:modelValue', 'update:url'],    props: {      modelValue: {        type: [Array, Object],        default() {          return [];        },      },      url: {        type: [Array, String],        default() {          return [];        },      },      disabled: {        type: Boolean,        default: false,      },      disablePreview: {        type: Boolean,        default: false,      },      delIcon: {        type: Boolean,        default: true,      },      // 自动上传      autoUpload: {        type: Boolean,        default: true,      },      // 最大选择个数 ,h5只能限制单选或是多选      limit: {        type: [Number, String],        default: 9,      },      // 列表样式 grid | list | list-card      mode: {        type: String,        default: 'grid',      },      // 选择文件类型  image/video/all      fileMediatype: {        type: String,        default: 'image',      },      // 文件类型筛选      fileExtname: {        type: [Array, String],        default() {          return [];        },      },      title: {        type: String,        default: '',      },      listStyles: {        type: Object,        default() {          return {            // 是否显示边框            border: true,            // 是否显示分隔线            dividline: true,            // 线条样式            borderStyle: {},          };        },      },      imageStyles: {        type: Object,        default() {          return {            width: 'auto',            height: 'auto',          };        },      },      readonly: {        type: Boolean,        default: false,      },      sizeType: {        type: Array,        default() {          return ['original', 'compressed'];        },      },      driver: {        type: String,        default: 'local', // local=本地 | oss | unicloud      },      subtitle: {        type: String,        default: '',      },    },    data() {      return {        files: [],        localValue: [],        imgsrc: sheep.$url.static('/static/img/shop/upload-camera.png'),      };    },    watch: {      modelValue: {        handler(newVal, oldVal) {          this.setValue(newVal, oldVal);        },        immediate: true,      },    },    computed: {      returnType() {        if (this.limit > 1) {          return 'array';        }        return 'object';      },      filesList() {        let files = [];        this.files.forEach((v) => {          files.push(v);        });        return files;      },      showType() {        if (this.fileMediatype === 'image') {          return this.mode;        }        return 'list';      },      limitLength() {        if (this.returnType === 'object') {          return 1;        }        if (!this.limit) {          return 1;        }        if (this.limit >= 9) {          return 9;        }        return this.limit;      },    },    created() {      if (this.driver === 'local') {        uniCloud.chooseAndUploadFile = chooseAndUploadFile;      }      this.form = this.getForm('uniForms');      this.formItem = this.getForm('uniFormsItem');      if (this.form && this.formItem) {        if (this.formItem.name) {          this.rename = this.formItem.name;          this.form.inputChildrens.push(this);        }      }    },    methods: {      /**       * 公开用户使用,清空文件       * @param {Object} index       */      clearFiles(index) {        if (index !== 0 && !index) {          this.files = [];          this.$nextTick(() => {            this.setEmit();          });        } else {          this.files.splice(index, 1);        }        this.$nextTick(() => {          this.setEmit();        });      },      /**       * 公开用户使用,继续上传       */      upload() {        let files = [];        this.files.forEach((v, index) => {          if (v.status === 'ready' || v.status === 'error') {            files.push(Object.assign({}, v));          }        });        return this.uploadFiles(files);      },      async setValue(newVal, oldVal) {        const newData = async (v) => {          const reg = /cloud:\/\/([\w.]+\/?)\S*/;          let url = '';          if (v.fileID) {            url = v.fileID;          } else {            url = v.url;          }          if (reg.test(url)) {            v.fileID = url;            v.url = await this.getTempFileURL(url);          }          if (v.url) v.path = v.url;          return v;        };        if (this.returnType === 'object') {          if (newVal) {            await newData(newVal);          } else {            newVal = {};          }        } else {          if (!newVal) newVal = [];          for (let i = 0; i < newVal.length; i++) {            let v = newVal[i];            await newData(v);          }        }        this.localValue = newVal;        if (this.form && this.formItem && !this.is_reset) {          this.is_reset = false;          this.formItem.setValue(this.localValue);        }        let filesData = Object.keys(newVal).length > 0 ? newVal : [];        this.files = [].concat(filesData);      },      /**       * 选择文件       */      choose() {        if (this.disabled) return;        if (          this.files.length >= Number(this.limitLength) &&          this.showType !== 'grid' &&          this.returnType === 'array'        ) {          uni.showToast({            title: `您最多选择 ${this.limitLength} 个文件`,            icon: 'none',          });          return;        }        this.chooseFiles();      },      /**       * 选择文件并上传       */      chooseFiles() {        const _extname = get_extname(this.fileExtname);        // 获取后缀        uniCloud          .chooseAndUploadFile({            type: this.fileMediatype,            compressed: false,            sizeType: this.sizeType,            // TODO 如果为空,video 有问题            extension: _extname.length > 0 ? _extname : undefined,            count: this.limitLength - this.files.length, //默认9            onChooseFile: this.chooseFileCallback,            onUploadProgress: (progressEvent) => {              this.setProgress(progressEvent, progressEvent.index);            },          })          .then((result) => {            this.setSuccessAndError(result.tempFiles);          })          .catch((err) => {            console.log('选择失败', err);          });      },      /**       * 选择文件回调       * @param {Object} res       */      async chooseFileCallback(res) {        const _extname = get_extname(this.fileExtname);        const is_one =          (Number(this.limitLength) === 1 && this.disablePreview && !this.disabled) ||          this.returnType === 'object';        // 如果这有一个文件 ,需要清空本地缓存数据        if (is_one) {          this.files = [];        }        let { filePaths, files } = get_files_and_is_max(res, _extname);        if (!(_extname && _extname.length > 0)) {          filePaths = res.tempFilePaths;          files = res.tempFiles;        }        let currentData = [];        for (let i = 0; i < files.length; i++) {          if (this.limitLength - this.files.length <= 0) break;          files[i].uuid = Date.now();          let filedata = await get_file_data(files[i], this.fileMediatype);          filedata.progress = 0;          filedata.status = 'ready';          this.files.push(filedata);          currentData.push({            ...filedata,            file: files[i],          });        }        this.$emit('select', {          tempFiles: currentData,          tempFilePaths: filePaths,        });        res.tempFiles = files;        // 停止自动上传        if (!this.autoUpload) {          res.tempFiles = [];        }      },      /**       * 批传       * @param {Object} e       */      uploadFiles(files) {        files = [].concat(files);        return uploadCloudFiles          .call(this, files, 5, (res) => {            this.setProgress(res, res.index, true);          })          .then((result) => {            this.setSuccessAndError(result);            return result;          })          .catch((err) => {            console.log(err);          });      },      /**       * 成功或失败       */      async setSuccessAndError(res, fn) {        let successData = [];        let errorData = [];        let tempFilePath = [];        let errorTempFilePath = [];        for (let i = 0; i < res.length; i++) {          const item = res[i];          const index = item.uuid ? this.files.findIndex((p) => p.uuid === item.uuid) : item.index;          if (index === -1 || !this.files) break;          if (item.errMsg === 'request:fail') {            this.files[index].url = item.path;            this.files[index].status = 'error';            this.files[index].errMsg = item.errMsg;            // this.files[index].progress = -1            errorData.push(this.files[index]);            errorTempFilePath.push(this.files[index].url);          } else {            this.files[index].errMsg = '';            this.files[index].fileID = item.url;            const reg = /cloud:\/\/([\w.]+\/?)\S*/;            if (reg.test(item.url)) {              this.files[index].url = await this.getTempFileURL(item.url);            } else {              this.files[index].url = item.url;            }            this.files[index].status = 'success';            this.files[index].progress += 1;            successData.push(this.files[index]);            tempFilePath.push(this.files[index].fileID);          }        }        if (successData.length > 0) {          this.setEmit();          // 状态改变返回          this.$emit('success', {            tempFiles: this.backObject(successData),            tempFilePaths: tempFilePath,          });        }        if (errorData.length > 0) {          this.$emit('fail', {            tempFiles: this.backObject(errorData),            tempFilePaths: errorTempFilePath,          });        }      },      /**       * 获取进度       * @param {Object} progressEvent       * @param {Object} index       * @param {Object} type       */      setProgress(progressEvent, index, type) {        const fileLenth = this.files.length;        const percentNum = (index / fileLenth) * 100;        const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);        let idx = index;        if (!type) {          idx = this.files.findIndex((p) => p.uuid === progressEvent.tempFile.uuid);        }        if (idx === -1 || !this.files[idx]) return;        // fix by mehaotian 100 就会消失,-1 是为了让进度条消失        this.files[idx].progress = percentCompleted - 1;        // 上传中        this.$emit('progress', {          index: idx,          progress: parseInt(percentCompleted),          tempFile: this.files[idx],        });      },      /**       * 删除文件       * @param {Object} index       */      delFile(index) {        this.$emit('delete', {          tempFile: this.files[index],          tempFilePath: this.files[index].url,        });        this.files.splice(index, 1);        this.$nextTick(() => {          this.setEmit();        });      },      /**       * 获取文件名和后缀       * @param {Object} name       */      getFileExt(name) {        const last_len = name.lastIndexOf('.');        const len = name.length;        return {          name: name.substring(0, last_len),          ext: name.substring(last_len + 1, len),        };      },      /**       * 处理返回事件       */      setEmit() {        let data = [];        let updateUrl = [];        if (this.returnType === 'object') {          data = this.backObject(this.files)[0];          this.localValue = data ? data : null;          updateUrl = data ? data.url : '';        } else {          data = this.backObject(this.files);          if (!this.localValue) {            this.localValue = [];          }          this.localValue = [...data];          if (this.localValue.length > 0) {            this.localValue.forEach((item) => {              updateUrl.push(item.url);            });          }        }        this.$emit('update:modelValue', this.localValue);        this.$emit('update:url', updateUrl);      },      /**       * 处理返回参数       * @param {Object} files       */      backObject(files) {        let newFilesData = [];        files.forEach((v) => {          newFilesData.push({            extname: v.extname,            fileType: v.fileType,            image: v.image,            name: v.name,            path: v.path,            size: v.size,            fileID: v.fileID,            url: v.url,          });        });        return newFilesData;      },      async getTempFileURL(fileList) {        fileList = {          fileList: [].concat(fileList),        };        const urls = await uniCloud.getTempFileURL(fileList);        return urls.fileList[0].tempFileURL || '';      },      /**       * 获取父元素实例       */      getForm(name = 'uniForms') {        let parent = this.$parent;        let parentName = parent.$options.name;        while (parentName !== name) {          parent = parent.$parent;          if (!parent) return false;          parentName = parent.$options.name;        }        return parent;      },    },  };</script><style lang="scss" scoped>  .uni-file-picker {    /* #ifndef APP-NVUE */    box-sizing: border-box;    overflow: hidden;    /* width: 100%; */    /* #endif */    /* flex: 1; */    position: relative;  }  .uni-file-picker__header {    padding-top: 5px;    padding-bottom: 10px;    /* #ifndef APP-NVUE */    display: flex;    /* #endif */    justify-content: space-between;  }  .file-title {    font-size: 14px;    color: #333;  }  .file-count {    font-size: 14px;    color: #999;  }  .is-add {    /* #ifndef APP-NVUE */    display: flex;    /* #endif */    align-items: center;    justify-content: center;  }  .add-icon {    width: 57rpx;    height: 49rpx;  }  .file-subtitle {    position: absolute;    left: 50%;    transform: translateX(-50%);    bottom: 0;    width: 140rpx;    height: 36rpx;    z-index: 1;    display: flex;    justify-content: center;    color: #fff;    font-weight: 500;    background: rgba(#000, 0.3);    font-size: 24rpx;  }</style>
 |