250 lines
5.4 KiB
JavaScript

import { createNamespace, suffixPx } from '../utils';
import { toArray, readFile, isOversize } from './utils';
import Icon from '../icon';
import Image from '../image';
import ImagePreview from '../image-preview';
const [createComponent, bem] = createNamespace('uploader');
function isImageDataUrl(dataUrl) {
return dataUrl.indexOf('data:image') === 0;
}
export default createComponent({
inheritAttrs: false,
model: {
prop: 'fileList'
},
props: {
fileList: Array,
disabled: Boolean,
uploadText: String,
afterRead: Function,
beforeRead: Function,
previewSize: [Number, String],
previewImage: {
type: Boolean,
default: true
},
accept: {
type: String,
default: 'image/*'
},
resultType: {
type: String,
default: 'dataUrl'
},
maxSize: {
type: Number,
default: Number.MAX_VALUE
},
maxCount: {
type: Number,
default: Number.MAX_VALUE
}
},
computed: {
detail() {
return {
name: this.$attrs.name || ''
};
}
},
methods: {
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.detail);
if (!response) {
this.resetInput();
return;
}
if (response.then) {
response
.then(() => {
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) => ({
file,
content: contents[index]
}));
this.onAfterRead(fileList, oversize);
});
} else {
readFile(files, this.resultType).then(content => {
this.onAfterRead({ file: files, content }, oversize);
});
}
},
onAfterRead(files, oversize) {
if (oversize) {
this.$emit('oversize', files, this.detail);
return;
}
this.resetInput();
this.$emit('input', [...this.fileList, ...toArray(files)]);
if (this.afterRead) {
this.afterRead(files, this.detail);
}
},
onDelete(file, index) {
const fileList = this.fileList.slice(0);
fileList.splice(index, 1);
this.$emit('input', fileList);
this.$emit('delete', file);
},
resetInput() {
/* istanbul ignore else */
if (this.$refs.input) {
this.$refs.input.value = '';
}
},
onPreviewImage(item) {
const imageFiles = this.fileList
.map(item => item.content)
.filter(content => isImageDataUrl(content));
ImagePreview({
images: imageFiles,
startPosition: imageFiles.indexOf(item.content)
});
},
renderPreview() {
if (!this.previewImage) {
return;
}
return this.fileList.map((item, index) => (
<div class={bem('preview')}>
<Image
fit="cover"
src={item.content}
class={bem('preview-image')}
width={this.previewSize}
height={this.previewSize}
scopedSlots={{
error() {
return [
<Icon class={bem('file-icon')} name="description" />,
<div class={[bem('file-name'), 'van-ellipsis']}>{item.file.name}</div>
];
}
}}
onClick={() => {
if (isImageDataUrl(item.content)) {
this.onPreviewImage(item);
}
}}
/>
<Icon
name="delete"
class={bem('preview-delete')}
onClick={() => {
this.onDelete(item, index);
}}
/>
</div>
));
},
renderUpload() {
if (this.fileList.length >= this.maxCount) {
return;
}
const slot = this.slots();
const Input = (
<input
{...{ attrs: this.$attrs }}
ref="input"
type="file"
accept={this.accept}
class={bem('input')}
disabled={this.disabled}
onChange={this.onChange}
/>
);
if (slot) {
return (
<div class={bem('input-wrapper')}>
{slot}
{Input}
</div>
);
}
let style;
if (this.previewSize) {
const size = suffixPx(this.previewSize);
style = {
width: size,
height: size
};
}
return (
<div class={bem('upload')} style={style}>
<Icon name="plus" class={bem('upload-icon')} />
{this.uploadText && <span class={bem('upload-text')}>{this.uploadText}</span>}
{Input}
</div>
);
}
},
render(h) {
return (
<div class={bem()}>
<div class={bem('wrapper')}>
{this.renderPreview()}
{this.renderUpload()}
</div>
</div>
);
}
});