diff --git a/example/pages/uploader/index.js b/example/pages/uploader/index.js index 282f38b7..a584e2dd 100644 --- a/example/pages/uploader/index.js +++ b/example/pages/uploader/index.js @@ -2,7 +2,7 @@ import Page from '../../common/page'; Page({ data: { - fileList: [], + fileList1: [], fileList2: [ { url: 'https://img.yzcdn.cn/vant/leaf.jpg' }, { url: 'https://img.yzcdn.cn/vant/tree.jpg' } diff --git a/packages/common/style/var.less b/packages/common/style/var.less index 76e96d4f..1d373f6c 100644 --- a/packages/common/style/var.less +++ b/packages/common/style/var.less @@ -522,22 +522,29 @@ // Uploader @uploader-size: 80px; @uploader-icon-size: 24px; -@uploader-icon-color: @gray-6; +@uploader-icon-color: @gray-4; @uploader-text-color: @gray-6; @uploader-text-font-size: @font-size-sm; -@uploader-upload-border-color: @gray-3; -@uploader-upload-border-radius: 4px; -@uploader-upload-background-color: @white; +@uploader-upload-border-radius: 8px; +@uploader-upload-background-color: @gray-1; +@uploader-upload-active-color: @active-color; @uploader-delete-color: @gray-6; @uploader-delete-icon-size: 18px; @uploader-delete-background-color: @white; @uploader-file-background-color: @background-color; @uploader-file-icon-size: 20px; @uploader-file-icon-color: @gray-7; +@uploader-file-name-padding: 0 @padding-base; +@uploader-file-name-margin-top: @padding-xs; @uploader-file-name-font-size: @font-size-sm; @uploader-file-name-text-color: @gray-7; +@uploader-mask-background-color: fade(@gray-8, 88%); +@uploader-mask-icon-size: 22px; +@uploader-mask-message-font-size: @font-size-sm; +@uploader-mask-message-line-height: 14px; +@uploader-loading-icon-size: 22px; +@uploader-loading-icon-color: @white; @uploader-disabled-opacity: @disabled-opacity; - // DropdownMenu @dropdown-menu-height: 50px; @dropdown-menu-background-color: @white; diff --git a/packages/uploader/index.less b/packages/uploader/index.less index 0413c7cb..fee171e1 100644 --- a/packages/uploader/index.less +++ b/packages/uploader/index.less @@ -29,13 +29,13 @@ height: @uploader-size; margin: 0 @padding-xs @padding-xs 0; background-color: @uploader-upload-background-color; - border: 1px dashed @uploader-upload-border-color; border-radius: @uploader-upload-border-radius; + &:active { + background-color: @uploader-upload-active-color; + } + &-icon { - display: inline-block; - width: 24px; - height: 24px; color: @uploader-icon-color; font-size: @uploader-icon-size; } @@ -54,11 +54,13 @@ &__preview { position: relative; margin: 0 @padding-xs @padding-xs 0; + cursor: pointer; &-image { display: block; width: @uploader-size; height: @uploader-size; + overflow: hidden; border-radius: @uploader-upload-border-radius; } @@ -84,9 +86,6 @@ border-radius: @uploader-upload-border-radius; &-icon { - display: inline-block; - width: 20px; - height: 20px; color: @uploader-file-icon-color; font-size: @uploader-file-icon-size; } @@ -94,8 +93,8 @@ &-name { box-sizing: border-box; width: 100%; - margin-top: @padding-xs; - padding: 0 5px; + margin-top: @uploader-file-name-margin-top; + padding: @uploader-file-name-padding; color: @uploader-file-name-text-color; font-size: @uploader-file-name-font-size; text-align: center; diff --git a/packages/uploader/index.ts b/packages/uploader/index.ts index 21bbdbf2..a5f16512 100644 --- a/packages/uploader/index.ts +++ b/packages/uploader/index.ts @@ -1,5 +1,6 @@ import { VantComponent } from '../common/component'; -import { isImageFile, isVideo } from './utils'; +import { isImageFile, isVideo, chooseFile, isPromise } from './utils'; +import { chooseImageProps, chooseVideoProps } from './shared'; VantComponent({ props: { @@ -7,6 +8,8 @@ VantComponent({ multiple: Boolean, uploadText: String, useBeforeRead: Boolean, + afterRead: null, + beforeRead: null, previewSize: { type: null, value: 90 @@ -19,14 +22,6 @@ VantComponent({ type: String, value: 'image' }, - sizeType: { - type: Array, - value: ['original', 'compressed'] - }, - capture: { - type: Array, - value: ['album', 'camera'] - }, fileList: { type: Array, value: [], @@ -60,27 +55,16 @@ VantComponent({ type: String, value: 'scaleToFill' }, - camera: { - type: String, - value: 'back' - }, - compressed: { - type: Boolean, - value: true - }, - maxDuration: { - type: Number, - value: 60 - }, uploadIcon: { type: String, - value: 'plus' - } + value: 'photograph' + }, + ...chooseImageProps, + ...chooseVideoProps }, data: { lists: [], - computedPreviewSize: '', isInCount: true }, @@ -95,128 +79,116 @@ VantComponent({ this.setData({ lists, isInCount: lists.length < maxCount }); }, + getDetail(index) { + return { + name: this.data.name, + index: index == null ? this.data.fileList.length : index + }; + }, + startUpload() { - if (this.data.disabled) return; - const { - name = '', - capture, - maxCount, - multiple, - maxSize, - accept, - sizeType, - lists, - camera, - compressed, - maxDuration, - useBeforeRead = false // 是否定义了 beforeRead - } = this.data; + const { maxCount, multiple, accept, lists, disabled } = this.data; - let chooseFile = null; - const newMaxCount = maxCount - lists.length; - // 设置为只选择图片的时候使用 chooseImage 来实现 - if (accept === 'image') { - chooseFile = new Promise((resolve, reject) => { - wx.chooseImage({ - count: multiple ? (newMaxCount > 9 ? 9 : newMaxCount) : 1, // 最多可以选择的数量,如果不支持多选则数量为1 - sourceType: capture, // 选择图片的来源,相册还是相机 - sizeType, - success: resolve, - fail: reject - }); - }); - } else if (accept === 'video') { - chooseFile = new Promise((resolve, reject) => { - wx.chooseVideo({ - sourceType: capture, - compressed, - maxDuration, - camera, - success: resolve, - fail: reject - }); - }); - } else { - chooseFile = new Promise((resolve, reject) => { - wx.chooseMessageFile({ - count: multiple ? newMaxCount : 1, // 最多可以选择的数量,如果不支持多选则数量为1 - type: 'file', - success: resolve, - fail: reject - }); - }); - } + if (disabled) return; - chooseFile - .then( - ( - res: - | WechatMiniprogram.ChooseImageSuccessCallbackResult - | WechatMiniprogram.ChooseMessageFileSuccessCallbackResult - | WechatMiniprogram.ChooseVideoSuccessCallbackResult - ) => { - let file = null; + chooseFile({ + ...this.data, + maxCount: maxCount - lists.length + }) + .then(res => { + let file = null; - if (isVideo(res, accept)) { - file = { - path: res.tempFilePath, - ...res - }; - } else { - file = multiple ? res.tempFiles : res.tempFiles[0]; - } - - // 检查文件大小 - if (file instanceof Array) { - const sizeEnable = file.every(item => item.size <= maxSize); - if (!sizeEnable) { - this.$emit('oversize', { name }); - return; - } - } else if (file.size > maxSize) { - this.$emit('oversize', { name }); - return; - } - - // 触发上传之前的钩子函数 - if (useBeforeRead) { - this.$emit('before-read', { - file, - name, - callback: (result: boolean) => { - if (result) { - // 开始上传 - this.$emit('after-read', { file, name }); - } - } - }); - } else { - this.$emit('after-read', { file, name }); - } + if (isVideo(res, accept)) { + file = { + path: res.tempFilePath, + ...res + }; + } else { + file = multiple ? res.tempFiles : res.tempFiles[0]; } - ) + + this.onBeforeRead(file); + }) .catch(error => { this.$emit('error', error); }); }, - deleteItem(event) { - const { index } = event.currentTarget.dataset; - this.$emit('delete', { index, name: this.data.name }); + onBeforeRead(file) { + const { beforeRead, useBeforeRead } = this.data; + let res: boolean | Promise = true; + + if (typeof beforeRead === 'function') { + res = beforeRead(file, this.getDetail()); + } + + if (useBeforeRead) { + res = new Promise((resolve, reject) => { + this.$emit('before-read', { + file, + ...this.getDetail(), + callback: (ok: boolean) => { + ok ? resolve() : reject(); + } + }); + }); + } + + if (!res) { + return; + } + + if (isPromise(res)) { + res.then((data: any) => this.onAfterRead(data || file)); + } else { + this.onAfterRead(file); + } }, - doPreviewImage(event) { - if (!this.data.previewFullImage) return; - const curUrl = event.currentTarget.dataset.url; - const images = this.data.lists - .filter(item => item.isImage) - .map(item => item.url || item.path); + onAfterRead(file) { + const { maxSize } = this.data; + const oversize = Array.isArray(file) + ? file.some(item => item.size > maxSize) + : file.size > maxSize; - this.$emit('click-preview', { url: curUrl, name: this.data.name }); + if (oversize) { + this.$emit('oversize', { file, ...this.getDetail() }); + return; + } + + if (typeof this.data.afterRead === 'function') { + this.data.afterRead(file, this.getDetail()); + } + + this.$emit('after-read', { file, ...this.getDetail() }); + }, + + deleteItem(event) { + const { index } = event.currentTarget.dataset; + + this.$emit('delete', { + ...this.getDetail(index), + file: this.data.fileList[index] + }); + }, + + onPreviewImage(event) { + const { index } = event.currentTarget.dataset; + const { lists } = this.data; + const item = lists[index]; + + this.$emit('click-preview', { + url: item.url || item.path, + ...this.getDetail(index) + }); + + if (!this.data.previewFullImage) return; wx.previewImage({ - urls: images, - current: curUrl, + urls: lists + .filter(item => item.isImage) + .map(item => item.url || item.path), + current: item.url || item.path, fail() { wx.showToast({ title: '预览图片失败', icon: 'none' }); } diff --git a/packages/uploader/index.wxml b/packages/uploader/index.wxml index 2185c216..dabbf5fd 100644 --- a/packages/uploader/index.wxml +++ b/packages/uploader/index.wxml @@ -16,8 +16,8 @@ alt="{{ item.name || ('图片' + index) }}" class="van-uploader__preview-image" style="width: {{ utils.addUnit(previewSize) }}; height: {{ utils.addUnit(previewSize) }};" - data-url="{{ item.url || item.path }}" - bind:tap="doPreviewImage" + data-index="{{ index }}" + bind:tap="onPreviewImage" /> { + if (accept === 'image') { + return new Promise((resolve, reject) => { + wx.chooseImage({ + count: multiple ? Math.min(maxCount, 9) : 1, // 最多可以选择的数量,如果不支持多选则数量为1 + sourceType: capture, // 选择图片的来源,相册还是相机 + sizeType, + success: resolve, + fail: reject + }); + }); + } + if (accept === 'video') { + return new Promise((resolve, reject) => { + wx.chooseVideo({ + sourceType: capture, + compressed, + maxDuration, + camera, + success: resolve, + fail: reject + }); + }); + } + return new Promise((resolve, reject) => { + wx.chooseMessageFile({ + count: multiple ? maxCount : 1, // 最多可以选择的数量,如果不支持多选则数量为1 + type: 'file', + success: resolve, + fail: reject + }); + }); +} + +export function isFunction(val: unknown): val is Function { + return typeof val === 'function'; +} + +export function isObject(val: any): val is Record { + return val !== null && typeof val === 'object'; +} + +export function isPromise(val: unknown): val is Promise { + return isObject(val) && isFunction(val.then) && isFunction(val.catch); +}