修改vant-ui的van-uploader组件在APP下也能唤起相册

背景

近期有个小H5项目需要接入自有APP中,这个项目中涉及多个图片上传的功能,且使用的是vant 的van-uploader,但发布到APP进行测试时发现上传组件全都没有反应,这可咋整?按vant官方的建议改APP是一种方法,但APP是外包的,改是比较费劲了。那就只能自己动手修改vant了。

目标

在vant 对应的组件中增加nativeHandle属性,传入APP 提供的JSAPI,使之能适配APP。

方法

1、在gitee上找到vant的源码,clone 2.x分支,git clone -b 2.x --single-branch https://gitee.com/vant-contrib/vant.git

2、找到uploader增加nativeHandle的属性;

3、增加从APP回调的图片base64信息,再转换为File,加入组件原有的处理逻辑。

4、修改后,编译。

直接放代码,(base64转File的方法抄的网上的),直接上代码

代码中中文注释部分为修改过的部分。

// Utils
import { createNamespace, addUnit, noop, isPromise, isDef } from '../utils';
import { toArray, readFile, isOversize, isImageFile } from './utils';

// Mixins
import { FieldMixin } from '../mixins/field';

// Components
import Icon from '../icon';
import Image from '../image';
import Loading from '../loading';
import ImagePreview from '../image-preview';

const [createComponent, bem] = createNamespace('uploader');

export default createComponent({
  inheritAttrs: false,

  mixins: [FieldMixin],

  model: {
    prop: 'fileList',
  },

  props: {
    disabled: Boolean,
    readonly: Boolean,
    lazyLoad: Boolean,
    uploadText: String,
    afterRead: Function,
    beforeRead: Function,
    beforeDelete: Function,
    previewSize: [Number, String],
    previewOptions: Object,
    nativeHandle: Function, //增加组件属性
    name: {
      type: [Number, String],
      default: '',
    },
    accept: {
      type: String,
      default: 'image/*',
    },
    fileList: {
      type: Array,
      default: () => [],
    },
    maxSize: {
      type: [Number, String, Function],
      default: Number.MAX_VALUE,
    },
    maxCount: {
      type: [Number, String],
      default: Number.MAX_VALUE,
    },
    deletable: {
      type: Boolean,
      default: true,
    },
    showUpload: {
      type: Boolean,
      default: true,
    },
    previewImage: {
      type: Boolean,
      default: true,
    },
    previewFullImage: {
      type: Boolean,
      default: true,
    },
    imageFit: {
      type: String,
      default: 'cover',
    },
    resultType: {
      type: String,
      default: 'dataUrl',
    },
    uploadIcon: {
      type: String,
      default: 'photograph',
    },
  },

  computed: {
    previewSizeWithUnit() {
      return addUnit(this.previewSize);
    },

    // for form
    value() {
      return this.fileList;
    },
  },

  created() {
    this.urls = [];
  },

  beforeDestroy() {
    this.urls.forEach((url) => URL.revokeObjectURL(url));
  },

  methods: {
    getDetail(index = this.fileList.length) {
      return {
        name: this.name,
        index,
      };
    },

    //增加图片转File流
    dataURLtoBlob(dataurl, name, mime) {
      const bstr = atob(dataurl);
      let n = bstr.length;
      const u8arr = new Uint8Array(n);
      while (n--) {
          u8arr[n] = bstr.charCodeAt(n)
      }
      return new File([u8arr], name, {
          type: mime,
      })
    },

    //增加添加处理方法,
    addFile(dataurl, name, mime){
      const files = [];
      files.push(this.dataURLtoBlob(dataurl,name, mime));
      const event = {"target":{"files":files}};
      this.onChange(event);
    },
    onChange(event) {
      let { files } = event.target;
      if (this.disabled || !files.length) {
        return;
      }

      files = files.length === 1 ? files[0] : [].slice.call(files);

      if (this.beforeRead) {
        const response = this.beforeRead(files, this.getDetail());

        if (!response) {
          this.resetInput();
          return;
        }

        if (isPromise(response)) {
          response
            .then((data) => {
              if (data) {
                this.readFile(data);
              } else {
                this.readFile(files);
              }
            })
            .catch(this.resetInput);

          return;
        }
      }

      this.readFile(files);
    },

    readFile(files) {
      const oversize = isOversize(files, this.maxSize);

      if (Array.isArray(files)) {
        const maxCount = this.maxCount - this.fileList.length;

        if (files.length > maxCount) {
          files = files.slice(0, maxCount);
        }

        Promise.all(files.map((file) => readFile(file, this.resultType))).then(
          (contents) => {
            const fileList = files.map((file, index) => {
              const result = { file, status: '', message: '' };

              if (contents[index]) {
                result.content = contents[index];
              }

              return result;
            });

            this.onAfterRead(fileList, oversize);
          }
        );
      } else {
        readFile(files, this.resultType).then((content) => {
          const result = { file: files, status: '', message: '' };

          if (content) {
            result.content = content;
          }

          this.onAfterRead(result, oversize);
        });
      }
    },

    onAfterRead(files, oversize) {
      this.resetInput();

      let validFiles = files;

      if (oversize) {
        let oversizeFiles = files;
        if (Array.isArray(files)) {
          oversizeFiles = [];
          validFiles = [];
          files.forEach((item) => {
            if (item.file) {
              if (isOversize(item.file, this.maxSize)) {
                oversizeFiles.push(item);
              } else {
                validFiles.push(item);
              }
            }
          });
        } else {
          validFiles = null;
        }
        this.$emit('oversize', oversizeFiles, this.getDetail());
      }

      const isValidFiles = Array.isArray(validFiles)
        ? Boolean(validFiles.length)
        : Boolean(validFiles);

      if (isValidFiles) {
        this.$emit('input', [...this.fileList, ...toArray(validFiles)]);

        if (this.afterRead) {
          this.afterRead(validFiles, this.getDetail());
        }
      }
    },

    onDelete(file, index) {
      const beforeDelete = file.beforeDelete ?? this.beforeDelete;
      if (beforeDelete) {
        const response = beforeDelete(file, this.getDetail(index));

        if (!response) {
          return;
        }

        if (isPromise(response)) {
          response
            .then(() => {
              this.deleteFile(file, index);
            })
            .catch(noop);
          return;
        }
      }

      this.deleteFile(file, index);
    },

    deleteFile(file, index) {
      const fileList = this.fileList.slice(0);
      fileList.splice(index, 1);

      this.$emit('input', fileList);
      this.$emit('delete', file, this.getDetail(index));
    },

    resetInput() {
      /* istanbul ignore else */
      if (this.$refs.input) {
        this.$refs.input.value = '';
      }
    },

    onClickUpload(event) {
      this.$emit('click-upload', event);
    },

    onPreviewImage(item) {
      if (!this.previewFullImage) {
        return;
      }

      const imageFiles = this.fileList.filter((item) => isImageFile(item));
      const imageContents = imageFiles.map((item) => {
        if (item.file && !item.url) {
          item.url = URL.createObjectURL(item.file);
          this.urls.push(item.url);
        }
        return item.url;
      });

      this.imagePreview = ImagePreview({
        images: imageContents,
        startPosition: imageFiles.indexOf(item),
        onClose: () => {
          this.$emit('close-preview');
        },
        ...this.previewOptions,
      });
    },

    // @exposed-api
    closeImagePreview() {
      if (this.imagePreview) {
        this.imagePreview.close();
      }
    },

    // @exposed-api
    chooseFile() {
      if (this.disabled) {
        return;
      }
      /* istanbul ignore else */
      if (this.$refs.input) {
        this.$refs.input.click();
      }
    },

    genPreviewMask(item) {
      const { status, message } = item;

      if (status === 'uploading' || status === 'failed') {
        const MaskIcon =
          status === 'failed' ? (
            <Icon name="close" class={bem('mask-icon')} />
          ) : (
            <Loading class={bem('loading')} />
          );

        const showMessage = isDef(message) && message !== '';

        return (
          <div class={bem('mask')}>
            {MaskIcon}
            {showMessage && <div class={bem('mask-message')}>{message}</div>}
          </div>
        );
      }
    },

    genPreviewItem(item, index) {
      const deleteAble = item.deletable ?? this.deletable;
      const showDelete = item.status !== 'uploading' && deleteAble;

      const DeleteIcon = showDelete && (
        <div
          class={bem('preview-delete')}
          onClick={(event) => {
            event.stopPropagation();
            this.onDelete(item, index);
          }}
        >
          <Icon name="cross" class={bem('preview-delete-icon')} />
        </div>
      );

      const PreviewCoverContent = this.slots('preview-cover', {
        index,
        ...item,
      });

      const PreviewCover = PreviewCoverContent && (
        <div class={bem('preview-cover')}>{PreviewCoverContent}</div>
      );

      const previewSize = item.previewSize ?? this.previewSize;
      const imageFit = item.imageFit ?? this.imageFit;

      const Preview = isImageFile(item) ? (
        <Image
          fit={imageFit}
          src={item.content || item.url}
          class={bem('preview-image')}
          width={previewSize}
          height={previewSize}
          lazyLoad={this.lazyLoad}
          onClick={() => {
            this.onPreviewImage(item);
          }}
        >
          {PreviewCover}
        </Image>
      ) : (
        <div
          class={bem('file')}
          style={{
            width: this.previewSizeWithUnit,
            height: this.previewSizeWithUnit,
          }}
        >
          <Icon class={bem('file-icon')} name="description" />
          <div class={[bem('file-name'), 'van-ellipsis']}>
            {item.file ? item.file.name : item.url}
          </div>
          {PreviewCover}
        </div>
      );

      return (
        <div
          class={bem('preview')}
          onClick={() => {
            this.$emit('click-preview', item, this.getDetail(index));
          }}
        >
          {Preview}
          {this.genPreviewMask(item)}
          {DeleteIcon}
        </div>
      );
    },

    genPreviewList() {
      if (this.previewImage) {
        return this.fileList.map(this.genPreviewItem);
      }
    },

    genUpload() {
      if (this.fileList.length >= this.maxCount || !this.showUpload) {
        return;
      }

      const slot = this.slots();

      //当有nativeHanle时替换上传按钮的处理方法
      const Input = this.readonly ? null : (
        this.nativeHandle==null?
        <input
          {...{ attrs: this.$attrs }}
          ref="input"
          type="file"
          accept={this.accept}
          class={bem('input')}
          disabled={this.disabled}
          onChange={this.onChange}
        />:
        <input 
          {...{ attrs: this.$attrs }}
          ref="input"
          type="button"
          accept={this.accept}
          class={bem('input')}
          disabled={this.disabled}
          onClick = {()=>{this.nativeHandle(this);}}
        />
      );

      if (slot) {
        return (
          <div
            class={bem('input-wrapper')}
            key="input-wrapper"
            onClick={this.onClickUpload}
          >
            {slot}
            {Input}
          </div>
        );
      }

      let style;
      if (this.previewSize) {
        const size = this.previewSizeWithUnit;
        style = {
          width: size,
          height: size,
        };
      }

      return (
        <div
          class={bem('upload', { readonly: this.readonly })}
          style={style}
          onClick={this.onClickUpload}
        >
          <Icon name={this.uploadIcon} class={bem('upload-icon')} />
          {this.uploadText && (
            <span class={bem('upload-text')}>{this.uploadText}</span>
          )}
          {Input}
        </div>
      );
    },
  },

  render() {
    return (
      <div class={bem()}>
        <div class={bem('wrapper', { disabled: this.disabled })}>
          {this.genPreviewList()}
          {this.genUpload()}
        </div>
      </div>
    );
  },
});

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Vue Vant-UI是Vue.js的一个移动端UI组件库,是一个轻量级的、高效的组件库,非常适合用于移动端前端开发。其中,Van-UploaderVant-UI的上传文件组件,允许用户将文件上传到服务器或第三方存储库。 在实现头像图片上传时,我们可以采用如下步骤: 1. 首先需要安装Vant-UI组件库。可以通过npm命令进行安装,输入如下代码:npm install vant --save 2. 在Vue项目中引入Vant-UI组件库。在main.js文件中写入如下代码:import Vant from 'vant' import 'vant/lib/vant-css/index.css' Vue.use(Vant) 3. 在需要使用上传头像的组件中引入Van-Uploader组件,并编写如下代码: <template> <van-uploader :show-upload="false" :before-read="beforeRead" :after-read="afterRead" > <van-icon name="photograph" /> </van-uploader> </template> <script> export default { data() { return { file: '' } }, methods: { beforeRead(file) { if (file.type !== 'image/jpeg' && file.type !== 'image/png') { this.$toast('请上传 JPG/PNG 格式的图片'); return false; } if (file.size > 500 * 1024) { this.$toast('图片大小不能超过 500KB'); return false; } }, afterRead(file) { this.file = URL.createObjectURL(file.file); } } } </script> 4. 上面的代码中,我们主要使用了Van-Uploader组件的before-read和after-read两个事件回调函数。before-read为上传文件之前的校验函数,例如判断文件类型和文件大小是否符合要求,这里我们限制了文件类型为JPG/PNG并且大小不能超过500KB。after-read则表示读取文件后的回调函数,我们将上传的文件读取为本地链接并保存到file属性中,以便进行后续处理。 5. 最后,将file属性传递给后端进行处理,例如将该链接保存到服务器或者上传到第三方存储库中。 总之,使用Van-Uploader组件可以轻松实现头像图片上传功能,同时也可以根据需求进行个性化的定制和扩展,是一个非常实用且易于使用的组件
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值