types(Uploader): use tsx (#8192)

This commit is contained in:
neverland 2021-02-21 17:04:56 +08:00 committed by GitHub
parent da09a6d871
commit 4c0120a183
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 120 additions and 72 deletions

View File

@ -1,23 +1,28 @@
import { bem } from './shared'; import { defineComponent, PropType } from 'vue';
import { isImageFile } from './utils';
// Utils
import { bem, isImageFile, FileListItem } from './utils';
import { isDef, getSizeStyle } from '../utils'; import { isDef, getSizeStyle } from '../utils';
import { callInterceptor } from '../utils/interceptor'; import { callInterceptor, Interceptor } from '../utils/interceptor';
// Components // Components
import Icon from '../icon'; import Icon from '../icon';
import Image from '../image'; import Image, { ImageFit } from '../image';
import Loading from '../loading'; import Loading from '../loading';
export default { export default defineComponent({
props: { props: {
name: String, name: [String, Number],
item: Object,
index: Number, index: Number,
imageFit: String, imageFit: String as PropType<ImageFit>,
lazyLoad: Boolean, lazyLoad: Boolean,
deletable: Boolean, deletable: Boolean,
previewSize: [Number, String], previewSize: [Number, String],
beforeDelete: Function, beforeDelete: Function as PropType<Interceptor>,
item: {
type: Object as PropType<FileListItem>,
required: true,
},
}, },
emits: ['delete', 'preview'], emits: ['delete', 'preview'],
@ -45,7 +50,7 @@ export default {
} }
}; };
const onDelete = (event) => { const onDelete = (event: MouseEvent) => {
const { name, item, index, beforeDelete } = props; const { name, item, index, beforeDelete } = props;
event.stopPropagation(); event.stopPropagation();
callInterceptor({ callInterceptor({
@ -120,4 +125,4 @@ export default {
</div> </div>
); );
}, },
}; });

View File

@ -1,14 +1,17 @@
import { ref, reactive } from 'vue'; import { ref, reactive, PropType } from 'vue';
// Utils // Utils
import { bem, createComponent } from './shared'; import { pick, isPromise, getSizeStyle, ComponentInstance } from '../utils';
import { isPromise, getSizeStyle, pick } from '../utils';
import { import {
bem,
toArray, toArray,
isOversize, isOversize,
filterFiles, filterFiles,
isImageFile, isImageFile,
FileListItem,
readFileContent, readFileContent,
createComponent,
UploaderResultType,
} from './utils'; } from './utils';
// Composition // Composition
@ -18,7 +21,29 @@ import { useLinkField } from '../composables/use-link-field';
// Components // Components
import Icon from '../icon'; import Icon from '../icon';
import PreviewItem from './PreviewItem'; import PreviewItem from './PreviewItem';
import ImagePreview from '../image-preview'; import ImagePreview, { ImagePreviewOptions } from '../image-preview';
// Types
import type { ImageFit } from '../image';
import type { Interceptor } from '../utils/interceptor';
type PromiseOrNot<T> = T | Promise<T>;
export type UploaderBeforeRead = (
file: File | File[],
detail: {
name: string | number;
index: number;
}
) => PromiseOrNot<File | File[] | undefined>;
export type UploaderAfterRead = (
items: FileListItem | FileListItem[],
detail: {
name: string | number;
index: number;
}
) => void;
export default createComponent({ export default createComponent({
props: { props: {
@ -27,11 +52,11 @@ export default createComponent({
disabled: Boolean, disabled: Boolean,
lazyLoad: Boolean, lazyLoad: Boolean,
uploadText: String, uploadText: String,
afterRead: Function, afterRead: Function as PropType<UploaderAfterRead>,
beforeRead: Function, beforeRead: Function as PropType<UploaderBeforeRead>,
beforeDelete: Function, beforeDelete: Function as PropType<Interceptor>,
previewSize: [Number, String], previewSize: [Number, String],
previewOptions: Object, previewOptions: Object as PropType<ImagePreviewOptions>,
name: { name: {
type: [Number, String], type: [Number, String],
default: '', default: '',
@ -41,7 +66,7 @@ export default createComponent({
default: 'image/*', default: 'image/*',
}, },
modelValue: { modelValue: {
type: Array, type: Array as PropType<FileListItem[]>,
default: () => [], default: () => [],
}, },
maxSize: { maxSize: {
@ -69,11 +94,11 @@ export default createComponent({
default: true, default: true,
}, },
imageFit: { imageFit: {
type: String, type: String as PropType<ImageFit>,
default: 'cover', default: 'cover',
}, },
resultType: { resultType: {
type: String, type: String as PropType<UploaderResultType>,
default: 'dataUrl', default: 'dataUrl',
}, },
uploadIcon: { uploadIcon: {
@ -104,7 +129,7 @@ export default createComponent({
} }
}; };
const onAfterRead = (items) => { const onAfterRead = (items: FileListItem | FileListItem[]) => {
resetInput(); resetInput();
if (isOversize(items, props.maxSize)) { if (isOversize(items, props.maxSize)) {
@ -129,11 +154,11 @@ export default createComponent({
} }
}; };
const readFile = (files) => { const readFile = (files: File | File[]) => {
const { maxCount, modelValue, resultType } = props; const { maxCount, modelValue, resultType } = props;
if (Array.isArray(files)) { if (Array.isArray(files)) {
const remainCount = maxCount - modelValue.length; const remainCount = +maxCount - modelValue.length;
if (files.length > remainCount) { if (files.length > remainCount) {
files = files.slice(0, remainCount); files = files.slice(0, remainCount);
@ -142,11 +167,11 @@ export default createComponent({
Promise.all( Promise.all(
files.map((file) => readFileContent(file, resultType)) files.map((file) => readFileContent(file, resultType))
).then((contents) => { ).then((contents) => {
const fileList = files.map((file, index) => { const fileList = (files as File[]).map((file, index) => {
const result = { file, status: '', message: '' }; const result: FileListItem = { file, status: '', message: '' };
if (contents[index]) { if (contents[index]) {
result.content = contents[index]; result.content = contents[index] as string;
} }
return result; return result;
@ -156,7 +181,11 @@ export default createComponent({
}); });
} else { } else {
readFileContent(files, resultType).then((content) => { readFileContent(files, resultType).then((content) => {
const result = { file: files, status: '', message: '' }; const result: FileListItem = {
file: files as File,
status: '',
message: '',
};
if (content) { if (content) {
result.content = content; result.content = content;
@ -167,17 +196,18 @@ export default createComponent({
} }
}; };
const onChange = (event) => { const onChange = (event: Event) => {
let { files } = event.target; const { files } = event.target as HTMLInputElement;
if (props.disabled || !files.length) { if (props.disabled || !files || !files.length) {
return; return;
} }
files = files.length === 1 ? files[0] : [].slice.call(files); const file =
files.length === 1 ? files[0] : ([].slice.call(files) as File[]);
if (props.beforeRead) { if (props.beforeRead) {
const response = props.beforeRead(files, getDetail()); const response = props.beforeRead(file, getDetail());
if (!response) { if (!response) {
resetInput(); resetInput();
@ -190,7 +220,7 @@ export default createComponent({
if (data) { if (data) {
readFile(data); readFile(data);
} else { } else {
readFile(files); readFile(file);
} }
}) })
.catch(resetInput); .catch(resetInput);
@ -198,19 +228,21 @@ export default createComponent({
} }
} }
readFile(files); readFile(file);
}; };
let imagePreview; let imagePreview: ComponentInstance | undefined;
const onClosePreview = () => { const onClosePreview = () => {
emit('close-preview'); emit('close-preview');
}; };
const previewImage = (item) => { const previewImage = (item: FileListItem) => {
if (props.previewFullImage) { if (props.previewFullImage) {
const imageFiles = props.modelValue.filter(isImageFile); const imageFiles = props.modelValue.filter(isImageFile);
const images = imageFiles.map((item) => item.content || item.url); const images = imageFiles
.map((item) => item.content || item.url)
.filter((item) => !!item) as string[];
imagePreview = ImagePreview({ imagePreview = ImagePreview({
images, images,
@ -227,7 +259,7 @@ export default createComponent({
} }
}; };
const deleteFile = (item, index) => { const deleteFile = (item: FileListItem, index: number) => {
const fileList = props.modelValue.slice(0); const fileList = props.modelValue.slice(0);
fileList.splice(index, 1); fileList.splice(index, 1);
@ -235,22 +267,18 @@ export default createComponent({
emit('delete', item, getDetail(index)); emit('delete', item, getDetail(index));
}; };
const renderPreviewItem = (item, index) => { const renderPreviewItem = (item: FileListItem, index: number) => {
const needPickData = [ const needPickData = [
'imageFit', 'imageFit',
'deletable', 'deletable',
'previewSize', 'previewSize',
'beforeDelete', 'beforeDelete',
]; ] as const;
const previewData = pick(props, needPickData); const previewData = {
const previewProp = pick(item, needPickData); ...pick(props, needPickData),
...pick(item, needPickData, true),
Object.keys(previewProp).forEach((item) => { };
if (previewProp[item] !== undefined) {
previewData[item] = previewProp[item];
}
});
return ( return (
<PreviewItem <PreviewItem
@ -289,7 +317,7 @@ export default createComponent({
type="file" type="file"
class={bem('input')} class={bem('input')}
accept={props.accept} accept={props.accept}
capture={props.capture} capture={props.capture as any}
multiple={props.multiple} multiple={props.multiple}
disabled={props.disabled} disabled={props.disabled}
onChange={onChange} onChange={onChange}

View File

@ -1,5 +0,0 @@
import { createNamespace } from '../utils';
const [createComponent, bem] = createNamespace('uploader');
export { bem, createComponent };

View File

@ -1,4 +1,25 @@
export type ResultType = 'dataUrl' | 'text' | 'file'; import { createNamespace } from '../utils';
import type { ImageFit } from '../image';
import type { Interceptor } from '../utils/interceptor';
const [createComponent, bem] = createNamespace('uploader');
export { bem, createComponent };
export type UploaderResultType = 'dataUrl' | 'text' | 'file';
export type FileListItem = {
url?: string;
file?: File;
content?: string;
isImage?: boolean;
status?: '' | 'uploading' | 'done' | 'failed';
message?: string;
imageFit?: ImageFit;
deletable?: boolean;
previewSize?: number | string;
beforeDelete?: Interceptor;
};
export function toArray<T>(item: T | T[]): T[] { export function toArray<T>(item: T | T[]): T[] {
if (Array.isArray(item)) { if (Array.isArray(item)) {
@ -8,8 +29,8 @@ export function toArray<T>(item: T | T[]): T[] {
return [item]; return [item];
} }
export function readFileContent(file: File, resultType: ResultType) { export function readFileContent(file: File, resultType: UploaderResultType) {
return new Promise<string | ArrayBuffer | null | void>((resolve) => { return new Promise<string | void>((resolve) => {
if (resultType === 'file') { if (resultType === 'file') {
resolve(); resolve();
return; return;
@ -18,7 +39,7 @@ export function readFileContent(file: File, resultType: ResultType) {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (event) => { reader.onload = (event) => {
resolve((event.target as FileReader).result); resolve((event.target as FileReader).result as string);
}; };
if (resultType === 'dataUrl') { if (resultType === 'dataUrl') {
@ -29,15 +50,6 @@ export function readFileContent(file: File, resultType: ResultType) {
}); });
} }
export type FileListItem = {
url?: string;
file?: File;
content?: string; // dataUrl
isImage?: boolean;
status?: '' | 'uploading' | 'done' | 'failed';
message?: string;
};
export function isOversize( export function isOversize(
items: FileListItem | FileListItem[], items: FileListItem | FileListItem[],
maxSize: number | string maxSize: number | string
@ -81,7 +93,7 @@ export function isImageFile(item: FileListItem): boolean {
return isImageUrl(item.url); return isImageUrl(item.url);
} }
if (item.content) { if (typeof item.content === 'string') {
return item.content.indexOf('data:image') === 0; return item.content.indexOf('data:image') === 0;
} }

View File

@ -38,9 +38,15 @@ export function get(object: any, path: string): any {
return result; return result;
} }
export function pick<T, U extends keyof T>(obj: T, keys: ReadonlyArray<U>) { export function pick<T, U extends keyof T>(
obj: T,
keys: ReadonlyArray<U>,
ignoreUndefined?: boolean
) {
return keys.reduce((ret, key) => { return keys.reduce((ret, key) => {
if (!ignoreUndefined || obj[key] !== undefined) {
ret[key] = obj[key]; ret[key] = obj[key];
}
return ret; return ret;
}, {} as Pick<T, U>); }, {} as Pick<T, U>);
} }

View File

@ -22,12 +22,14 @@ declare module 'vue' {
onCancel?: EventHandler; onCancel?: EventHandler;
onClosed?: EventHandler; onClosed?: EventHandler;
onChange?: EventHandler; onChange?: EventHandler;
onDelete?: EventHandler;
onOpened?: EventHandler; onOpened?: EventHandler;
onScroll?: EventHandler; onScroll?: EventHandler;
onSubmit?: EventHandler; onSubmit?: EventHandler;
onSelect?: EventHandler; onSelect?: EventHandler;
onToggle?: EventHandler; onToggle?: EventHandler;
onConfirm?: EventHandler; onConfirm?: EventHandler;
onPreview?: EventHandler;
onKeypress?: EventHandler; onKeypress?: EventHandler;
onTouchend?: EventHandler; onTouchend?: EventHandler;
onClickStep?: EventHandler; onClickStep?: EventHandler;