mirror of
https://gitee.com/vant-contrib/vant-weapp.git
synced 2025-04-06 03:58:05 +08:00
feat(uploader): add new key thumb & standardization file-list (#3673)
fix #3606, fix #3661
This commit is contained in:
parent
8f21b5ed17
commit
36256cd28f
@ -5,7 +5,7 @@ Page({
|
||||
fileList1: [],
|
||||
fileList2: [
|
||||
{ url: 'https://img.yzcdn.cn/vant/leaf.jpg' },
|
||||
{ url: 'https://img.yzcdn.cn/vant/tree.jpg' }
|
||||
{ url: 'https://img.yzcdn.cn/vant/tree.jpg' },
|
||||
],
|
||||
fileList3: [{ url: 'https://img.yzcdn.cn/vant/sand.jpg' }],
|
||||
fileList4: [],
|
||||
@ -17,14 +17,14 @@ Page({
|
||||
{
|
||||
url: 'https://img.yzcdn.cn/vant/leaf.jpg',
|
||||
status: 'uploading',
|
||||
message: '上传中'
|
||||
message: '上传中',
|
||||
},
|
||||
{
|
||||
url: 'https://img.yzcdn.cn/vant/tree.jpg',
|
||||
status: 'failed',
|
||||
message: '上传失败'
|
||||
}
|
||||
]
|
||||
message: '上传失败',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
beforeRead(event) {
|
||||
@ -39,6 +39,7 @@ Page({
|
||||
|
||||
afterRead(event) {
|
||||
const { file, name } = event.detail;
|
||||
console.log(JSON.stringify(file, null, 2));
|
||||
const fileList = this.data[`fileList${name}`];
|
||||
|
||||
this.setData({ [`fileList${name}`]: fileList.concat(file) });
|
||||
@ -67,12 +68,12 @@ Page({
|
||||
this.uploadFilePromise(`my-photo${index}.png`, file)
|
||||
);
|
||||
Promise.all(uploadTasks)
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
wx.showToast({ title: '上传成功', icon: 'none' });
|
||||
const fileList = data.map(item => ({ url: item.fileID }));
|
||||
const fileList = data.map((item) => ({ url: item.fileID }));
|
||||
this.setData({ cloudPath: data, fileList6: fileList });
|
||||
})
|
||||
.catch(e => {
|
||||
.catch((e) => {
|
||||
wx.showToast({ title: '上传失败', icon: 'none' });
|
||||
console.log(e);
|
||||
});
|
||||
@ -82,7 +83,7 @@ Page({
|
||||
uploadFilePromise(fileName, chooseResult) {
|
||||
return wx.cloud.uploadFile({
|
||||
cloudPath: fileName,
|
||||
filePath: chooseResult.path
|
||||
filePath: chooseResult.path,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { isNumber, isPlainObject } from './validator';
|
||||
|
||||
export function isDef(value: any): boolean {
|
||||
return value !== undefined && value !== null;
|
||||
}
|
||||
@ -7,10 +9,6 @@ export function isObj(x: any): boolean {
|
||||
return x !== null && (type === 'object' || type === 'function');
|
||||
}
|
||||
|
||||
export function isNumber(value) {
|
||||
return /^\d+(\.\d+)?$/.test(value);
|
||||
}
|
||||
|
||||
export function range(num: number, min: number, max: number) {
|
||||
return Math.min(Math.max(num, min), max);
|
||||
}
|
||||
@ -55,6 +53,20 @@ export function requestAnimationFrame(cb: Function) {
|
||||
});
|
||||
}
|
||||
|
||||
export function pickExclude(obj: unknown, keys: string[]) {
|
||||
if (!isPlainObject(obj)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return Object.keys(obj).reduce((prev, key) => {
|
||||
if (!keys.includes(key)) {
|
||||
prev[key] = obj[key];
|
||||
}
|
||||
|
||||
return prev;
|
||||
}, {});
|
||||
}
|
||||
|
||||
export function getRect(
|
||||
this: WechatMiniprogram.Component.TrivialInstance,
|
||||
selector: string
|
||||
|
39
packages/common/validator.ts
Normal file
39
packages/common/validator.ts
Normal file
@ -0,0 +1,39 @@
|
||||
export function isFunction(val: unknown): val is Function {
|
||||
return typeof val === 'function';
|
||||
}
|
||||
|
||||
export function isPlainObject(val: unknown): val is Record<string, unknown> {
|
||||
return val !== null && typeof val === 'object' && !Array.isArray(val);
|
||||
}
|
||||
|
||||
export function isPromise<T = unknown>(val: unknown): val is Promise<T> {
|
||||
return isPlainObject(val) && isFunction(val.then) && isFunction(val.catch);
|
||||
}
|
||||
|
||||
export function isDef(value: any): boolean {
|
||||
return value !== undefined && value !== null;
|
||||
}
|
||||
|
||||
export function isObj(x: unknown): x is Record<string, unknown> {
|
||||
const type = typeof x;
|
||||
return x !== null && (type === 'object' || type === 'function');
|
||||
}
|
||||
|
||||
export function isNumber(value: string) {
|
||||
return /^\d+(\.\d+)?$/.test(value);
|
||||
}
|
||||
|
||||
export function isBoolean(value: unknown): value is boolean {
|
||||
return typeof value === 'boolean';
|
||||
}
|
||||
|
||||
const IMAGE_REGEXP = /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i;
|
||||
const VIDEO_REGEXP = /\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv)/i;
|
||||
|
||||
export function isImageUrl(url: string): boolean {
|
||||
return IMAGE_REGEXP.test(url);
|
||||
}
|
||||
|
||||
export function isVideoUrl(url: string): boolean {
|
||||
return VIDEO_REGEXP.test(url);
|
||||
}
|
@ -27,6 +27,7 @@ Page({
|
||||
data: {
|
||||
fileList: [],
|
||||
},
|
||||
|
||||
afterRead(event) {
|
||||
const { file } = event.detail;
|
||||
// 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式
|
||||
@ -48,7 +49,7 @@ Page({
|
||||
|
||||
### 图片预览
|
||||
|
||||
通过向组件传入`file-list`属性,可以绑定已经上传的图片列表,并展示图片列表的预览图
|
||||
通过向组件传入`file-list`属性,可以绑定已经上传的图片列表,并展示图片列表的预览图。file-list 的详细结构可见下方。
|
||||
|
||||
```html
|
||||
<van-uploader file-list="{{ fileList }}" />
|
||||
@ -58,10 +59,14 @@ Page({
|
||||
Page({
|
||||
data: {
|
||||
fileList: [
|
||||
{ url: 'https://img.yzcdn.cn/vant/leaf.jpg', name: '图片1' },
|
||||
{
|
||||
url: 'https://img.yzcdn.cn/vant/leaf.jpg',
|
||||
name: '图片1',
|
||||
},
|
||||
// Uploader 根据文件后缀来判断是否为图片文件
|
||||
// 如果图片 URL 中不包含类型信息,可以添加 isImage 标记来声明
|
||||
{
|
||||
url: 'http://iph.href.lu?text=default',
|
||||
url: 'http://iph.href.lu/60x60?text=default',
|
||||
name: '图片2',
|
||||
isImage: true,
|
||||
@ -74,7 +79,7 @@ Page({
|
||||
|
||||
### 图片编辑状态
|
||||
|
||||
通过`deletable `可以标识所有图片或者单张图片是否可删除。如果`Props `的全局`deletable `为`false`,则所有图片都不展示删除按钮;如果`Props `的全局`deletable `为`true`,则可通过设置每一个图片对象里的`deletable `来控制每一张图片是否显示删除按钮,如果图片对象里不设置则默认为`true`。
|
||||
通过`deletable`可以标识所有图片或者单张图片是否可删除。如果`Props`的全局`deletable`为`false`,则所有图片都不展示删除按钮;如果`Props`的全局`deletable`为`true`,则可通过设置每一个图片对象里的`deletable`来控制每一张图片是否显示删除按钮,如果图片对象里不设置则默认为`true`。
|
||||
|
||||
```html
|
||||
<van-uploader file-list="{{ fileList }}" deletable="{{ true }}" />
|
||||
@ -153,6 +158,7 @@ Page({
|
||||
```html
|
||||
<van-uploader
|
||||
file-list="{{ fileList }}"
|
||||
accept="media"
|
||||
use-before-read
|
||||
bind:before-read="beforeRead"
|
||||
bind:after-read="afterRead"
|
||||
@ -237,13 +243,26 @@ uploadFilePromise(fileName, chooseResult) {
|
||||
|
||||
#### accept 的合法值
|
||||
|
||||
| 参数 | 说明 |
|
||||
| ------- | ------------------------------ |
|
||||
| `media` | 图片和视频 |
|
||||
| `image` | 图片 |
|
||||
| `video` | 视频 |
|
||||
| `file` | 除了图片和视频之外的其它的文件 |
|
||||
| `all` | 所有文件 |
|
||||
| 参数 | 说明 |
|
||||
| ------- | ------------------------------------ |
|
||||
| `media` | 图片和视频 |
|
||||
| `image` | 图片 |
|
||||
| `video` | 视频 |
|
||||
| `file` | 从客户端会话选择图片和视频以外的文件 |
|
||||
| `all` | 从客户端会话选择所有文件 |
|
||||
|
||||
### FileList
|
||||
|
||||
`file-list` 为一个对象数组,数组中的每一个对象包含以下 `key`
|
||||
|
||||
| 参数 | 说明 |
|
||||
| --------- | ------------------------------------------------------ |
|
||||
| `url` | 图片和视频的网络资源地址 |
|
||||
| `name` | 文件名称,视频将在全屏预览时作为标题显示 |
|
||||
| `thumb` | 图片缩略图或视频封面的网络资源地址,仅对图片和视频有效 |
|
||||
| `type` | 文件类型,可选值`image` `video` `file` |
|
||||
| `isImage` | 手动标记图片资源 |
|
||||
| `isVideo` | 手动标记视频资源 |
|
||||
|
||||
### Slot
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { VantComponent } from '../common/component';
|
||||
import { isImageFile, isVideo, chooseFile, isPromise } from './utils';
|
||||
import { isImageFile, chooseFile, isVideoFile } from './utils';
|
||||
import { chooseImageProps, chooseVideoProps } from './shared';
|
||||
import { isBoolean, isPromise } from '../common/validator';
|
||||
|
||||
VantComponent({
|
||||
props: {
|
||||
@ -73,13 +74,13 @@ VantComponent({
|
||||
const { fileList = [], maxCount } = this.data;
|
||||
const lists = fileList.map((item) => ({
|
||||
...item,
|
||||
isImage:
|
||||
typeof item.isImage === 'undefined'
|
||||
? isImageFile(item)
|
||||
: item.isImage,
|
||||
deletable:
|
||||
typeof item.deletable === 'undefined' ? true : item.deletable,
|
||||
isImage: isImageFile(item),
|
||||
isVideo: isVideoFile(item),
|
||||
deletable: isBoolean(item.deletable) ? item.deletable : true,
|
||||
}));
|
||||
|
||||
console.log(lists);
|
||||
|
||||
this.setData({ lists, isInCount: lists.length < maxCount });
|
||||
},
|
||||
|
||||
@ -100,18 +101,9 @@ VantComponent({
|
||||
maxCount: maxCount - lists.length,
|
||||
})
|
||||
.then((res) => {
|
||||
let file = null;
|
||||
console.log(res);
|
||||
|
||||
if (isVideo(res, accept)) {
|
||||
file = {
|
||||
path: res.tempFilePath,
|
||||
...res,
|
||||
};
|
||||
} else {
|
||||
file = multiple ? res.tempFiles : res.tempFiles[0];
|
||||
}
|
||||
|
||||
this.onBeforeRead(file);
|
||||
this.onBeforeRead(multiple ? res : res[0]);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$emit('error', error);
|
||||
@ -120,14 +112,14 @@ VantComponent({
|
||||
|
||||
onBeforeRead(file) {
|
||||
const { beforeRead, useBeforeRead } = this.data;
|
||||
let res: boolean | Promise<any> = true;
|
||||
let res: boolean | Promise<void> = true;
|
||||
|
||||
if (typeof beforeRead === 'function') {
|
||||
res = beforeRead(file, this.getDetail());
|
||||
}
|
||||
|
||||
if (useBeforeRead) {
|
||||
res = new Promise((resolve, reject) => {
|
||||
res = new Promise<void>((resolve, reject) => {
|
||||
this.$emit('before-read', {
|
||||
file,
|
||||
...this.getDetail(),
|
||||
@ -150,7 +142,7 @@ VantComponent({
|
||||
},
|
||||
|
||||
onAfterRead(file) {
|
||||
const { maxSize } = this.data;
|
||||
const { maxSize, afterRead } = this.data;
|
||||
const oversize = Array.isArray(file)
|
||||
? file.some((item) => item.size > maxSize)
|
||||
: file.size > maxSize;
|
||||
@ -160,8 +152,8 @@ VantComponent({
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof this.data.afterRead === 'function') {
|
||||
this.data.afterRead(file, this.getDetail());
|
||||
if (typeof afterRead === 'function') {
|
||||
afterRead(file, this.getDetail());
|
||||
}
|
||||
|
||||
this.$emit('after-read', { file, ...this.getDetail() });
|
||||
@ -184,32 +176,28 @@ VantComponent({
|
||||
const item = lists[index];
|
||||
|
||||
wx.previewImage({
|
||||
urls: lists
|
||||
.filter((item) => item.isImage)
|
||||
.map((item) => item.url || item.path),
|
||||
current: item.url || item.path,
|
||||
urls: lists.filter((item) => isImageFile(item)).map((item) => item.url),
|
||||
current: item.url,
|
||||
fail() {
|
||||
wx.showToast({ title: '预览图片失败', icon: 'none' });
|
||||
},
|
||||
});
|
||||
},
|
||||
// fix: accept 为 video 时不能展示视频
|
||||
onPreviewVideo: function (event) {
|
||||
|
||||
onPreviewVideo(event) {
|
||||
if (!this.data.previewFullImage) return;
|
||||
var index = event.currentTarget.dataset.index;
|
||||
var lists = this.data.lists;
|
||||
const { index } = event.currentTarget.dataset;
|
||||
const { lists } = this.data;
|
||||
|
||||
wx.previewMedia({
|
||||
sources: lists
|
||||
.filter(function (item) {
|
||||
return item.isVideo;
|
||||
})
|
||||
.map(function (item) {
|
||||
item.type = 'video';
|
||||
item.url = item.url || item.path;
|
||||
return item;
|
||||
}),
|
||||
.filter((item) => isVideoFile(item))
|
||||
.map((item) => ({
|
||||
...item,
|
||||
type: 'video',
|
||||
})),
|
||||
current: index,
|
||||
fail: function () {
|
||||
fail() {
|
||||
wx.showToast({ title: '预览视频失败', icon: 'none' });
|
||||
},
|
||||
});
|
||||
|
@ -14,7 +14,7 @@
|
||||
<image
|
||||
wx:if="{{ item.isImage }}"
|
||||
mode="{{ imageFit }}"
|
||||
src="{{ item.url || item.path }}"
|
||||
src="{{ item.thumb || item.url }}"
|
||||
alt="{{ item.name || ('图片' + index) }}"
|
||||
class="van-uploader__preview-image"
|
||||
style="width: {{ utils.addUnit(previewSize) }}; height: {{ utils.addUnit(previewSize) }};"
|
||||
@ -23,21 +23,23 @@
|
||||
/>
|
||||
<video
|
||||
wx:elif="{{ item.isVideo }}"
|
||||
src="{{item.url || item.path}}"
|
||||
autoplay="{{item.autoplay}}"
|
||||
src="{{ item.url }}"
|
||||
title="{{ item.name || ('视频' + index) }}"
|
||||
poster="{{ item.thumb }}"
|
||||
autoplay="{{ item.autoplay }}"
|
||||
class="van-uploader__preview-image"
|
||||
style="width: {{ utils.addUnit(previewSize) }}; height: {{ utils.addUnit(previewSize) }};"
|
||||
data-index="{{ index }}"
|
||||
bind:tap="onPreviewVideo"
|
||||
>
|
||||
</video>
|
||||
</video>
|
||||
<view
|
||||
wx:else
|
||||
class="van-uploader__file"
|
||||
style="width: {{ utils.addUnit(previewSize) }}; height: {{ utils.addUnit(previewSize) }};"
|
||||
>
|
||||
<van-icon name="description" class="van-uploader__file-icon" />
|
||||
<view class="van-uploader__file-name van-ellipsis">{{ item.name || item.url || item.path }}</view>
|
||||
<view class="van-uploader__file-name van-ellipsis">{{ item.name || item.url }}</view>
|
||||
</view>
|
||||
<view
|
||||
wx:if="{{ item.status === 'uploading' || item.status === 'failed' }}"
|
||||
@ -59,7 +61,7 @@
|
||||
|
||||
<!-- 上传样式 -->
|
||||
<block wx:if="{{ isInCount }}">
|
||||
<view class="van-uploader__slot" bind:tap="startUpload">
|
||||
<view class="van-uploader__slot" bindtap="startUpload">
|
||||
<slot />
|
||||
</view>
|
||||
|
||||
|
@ -1,26 +1,24 @@
|
||||
import { pickExclude } from '../common/utils';
|
||||
import { isImageUrl, isVideoUrl } from '../common/validator';
|
||||
|
||||
interface File {
|
||||
path: string; // 上传临时地址
|
||||
url: string; // 上传临时地址
|
||||
size: number; // 上传大小
|
||||
name: string; // 上传文件名称,accept="image" 不存在
|
||||
type: string; // 上传类型,accept="image" 不存在
|
||||
time: number; // 上传时间,accept="image" 不存在
|
||||
image: boolean; // 是否为图片
|
||||
}
|
||||
|
||||
const IMAGE_REGEXP = /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i;
|
||||
|
||||
function isImageUrl(url: string): boolean {
|
||||
return IMAGE_REGEXP.test(url);
|
||||
size?: number; // 上传大小
|
||||
name?: string;
|
||||
type: string; // 上传类型
|
||||
duration?: number; // 上传时间
|
||||
time?: number; // 消息文件时间
|
||||
isImage?: boolean;
|
||||
isVideo?: boolean;
|
||||
}
|
||||
|
||||
export function isImageFile(item: File): boolean {
|
||||
if (item.type) {
|
||||
return item.type.indexOf('image') === 0;
|
||||
if (item.isImage != null) {
|
||||
return item.isImage;
|
||||
}
|
||||
|
||||
if (item.path) {
|
||||
return isImageUrl(item.path);
|
||||
if (item.type) {
|
||||
return item.type === 'image';
|
||||
}
|
||||
|
||||
if (item.url) {
|
||||
@ -30,11 +28,62 @@ export function isImageFile(item: File): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isVideo(
|
||||
res: any,
|
||||
accept: string
|
||||
): res is WechatMiniprogram.ChooseVideoSuccessCallbackResult {
|
||||
return accept === 'video';
|
||||
export function isVideoFile(item: File): boolean {
|
||||
if (item.isVideo != null) {
|
||||
return item.isVideo;
|
||||
}
|
||||
|
||||
if (item.type) {
|
||||
return item.type === 'video';
|
||||
}
|
||||
|
||||
if (item.url) {
|
||||
return isVideoUrl(item.url);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function formatImage(
|
||||
res: WechatMiniprogram.ChooseImageSuccessCallbackResult
|
||||
): File[] {
|
||||
return res.tempFiles.map((item) => ({
|
||||
...pickExclude(item, ['path']),
|
||||
type: 'image',
|
||||
url: item.path,
|
||||
thumb: item.path,
|
||||
}));
|
||||
}
|
||||
|
||||
function formatVideo(
|
||||
res: WechatMiniprogram.ChooseVideoSuccessCallbackResult & Record<string, any>
|
||||
) {
|
||||
return [
|
||||
{
|
||||
...pickExclude(res, ['tempFilePath', 'thumbTempFilePath', 'errMsg']),
|
||||
type: 'video',
|
||||
url: res.tempFilePath,
|
||||
thumb: res.thumbTempFilePath,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function formatMedia(res: WechatMiniprogram.ChooseMediaSuccessCallbackResult) {
|
||||
return res.tempFiles.map((item) => ({
|
||||
...pickExclude(item, ['fileType', 'thumbTempFilePath', 'tempFilePath']),
|
||||
type: res.type,
|
||||
url: item.tempFilePath,
|
||||
thumb: res.type === 'video' ? item.thumbTempFilePath : item.tempFilePath,
|
||||
}));
|
||||
}
|
||||
|
||||
function formatFile(
|
||||
res: WechatMiniprogram.ChooseMessageFileSuccessCallbackResult
|
||||
) {
|
||||
return res.tempFiles.map((item) => ({
|
||||
...pickExclude(item, ['path']),
|
||||
url: item.path,
|
||||
}));
|
||||
}
|
||||
|
||||
export function chooseFile({
|
||||
@ -46,66 +95,47 @@ export function chooseFile({
|
||||
sizeType,
|
||||
camera,
|
||||
maxCount,
|
||||
}): Promise<
|
||||
| WechatMiniprogram.ChooseImageSuccessCallbackResult
|
||||
| WechatMiniprogram.ChooseMediaSuccessCallbackResult
|
||||
| WechatMiniprogram.ChooseVideoSuccessCallbackResult
|
||||
| WechatMiniprogram.ChooseMessageFileSuccessCallbackResult
|
||||
> {
|
||||
switch (accept) {
|
||||
case 'image':
|
||||
return new Promise((resolve, reject) => {
|
||||
}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
switch (accept) {
|
||||
case 'image':
|
||||
wx.chooseImage({
|
||||
count: multiple ? Math.min(maxCount, 9) : 1, // 最多可以选择的数量,如果不支持多选则数量为1
|
||||
sourceType: capture, // 选择图片的来源,相册还是相机
|
||||
count: multiple ? Math.min(maxCount, 9) : 1,
|
||||
sourceType: capture,
|
||||
sizeType,
|
||||
success: resolve,
|
||||
success: (res) => resolve(formatImage(res)),
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
case 'media':
|
||||
return new Promise((resolve, reject) => {
|
||||
break;
|
||||
case 'media':
|
||||
wx.chooseMedia({
|
||||
count: multiple ? Math.min(maxCount, 9) : 1, // 最多可以选择的数量,如果不支持多选则数量为1
|
||||
sourceType: capture, // 选择图片的来源,相册还是相机
|
||||
count: multiple ? Math.min(maxCount, 9) : 1,
|
||||
sourceType: capture,
|
||||
maxDuration,
|
||||
sizeType,
|
||||
camera,
|
||||
success: resolve,
|
||||
success: (res) => resolve(formatMedia(res)),
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
case 'video':
|
||||
return new Promise((resolve, reject) => {
|
||||
break;
|
||||
case 'video':
|
||||
wx.chooseVideo({
|
||||
sourceType: capture,
|
||||
compressed,
|
||||
maxDuration,
|
||||
camera,
|
||||
success: resolve,
|
||||
success: (res) => resolve(formatVideo(res)),
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
default:
|
||||
return new Promise((resolve, reject) => {
|
||||
break;
|
||||
default:
|
||||
wx.chooseMessageFile({
|
||||
count: multiple ? maxCount : 1, // 最多可以选择的数量,如果不支持多选则数量为1
|
||||
type: 'file',
|
||||
success: resolve,
|
||||
count: multiple ? maxCount : 1,
|
||||
type: accept,
|
||||
success: (res) => resolve(formatFile(res)),
|
||||
fail: reject,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function isFunction(val: unknown): val is Function {
|
||||
return typeof val === 'function';
|
||||
}
|
||||
|
||||
export function isObject(val: any): val is Record<any, any> {
|
||||
return val !== null && typeof val === 'object';
|
||||
}
|
||||
|
||||
export function isPromise<T = any>(val: unknown): val is Promise<T> {
|
||||
return isObject(val) && isFunction(val.then) && isFunction(val.catch);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user