first commit
This commit is contained in:
887
uni_modules/tdesign-uniapp/components/upload/upload.vue
Normal file
887
uni_modules/tdesign-uniapp/components/upload/upload.vue
Normal file
@@ -0,0 +1,887 @@
|
||||
<template>
|
||||
<view
|
||||
:style="tools._style([customStyle])"
|
||||
:class="classPrefix + ' ' + tClass"
|
||||
>
|
||||
<t-grid
|
||||
:gutter="gutter"
|
||||
:border="false"
|
||||
align="center"
|
||||
:column="column"
|
||||
:custom-style="draggable ? 'overflow: visible' : ''"
|
||||
>
|
||||
<block v-if="!dragLayout">
|
||||
<t-grid-item
|
||||
v-for="(file, index) in customFiles"
|
||||
:key="index"
|
||||
:t-class="classPrefix + '__grid ' + classPrefix + '__grid-file'"
|
||||
:t-class-content="classPrefix + '__grid-content'"
|
||||
aria-role="presentation"
|
||||
>
|
||||
<view
|
||||
:class="classPrefix + '__wrapper ' + (disabled ? classPrefix + '__wrapper--disabled' : '')"
|
||||
:style="gridItemStyle"
|
||||
:aria-role="ariaRole || getWrapperAriaRole(file)"
|
||||
:aria-label="ariaLabel || getWrapperAriaLabel(file)"
|
||||
>
|
||||
<t-image
|
||||
v-if="file.type !== 'video'"
|
||||
:data-file="file"
|
||||
:data-index="index"
|
||||
:t-class="classPrefix + '__thumbnail'"
|
||||
:custom-style="(imageProps && imageProps.style) || ''"
|
||||
:src="file.thumb || file.url"
|
||||
:mode="(imageProps && imageProps.mode) || 'aspectFill'"
|
||||
:error="(imageProps && imageProps.error) || 'default'"
|
||||
:lazy="(imageProps && imageProps.lazy) || false"
|
||||
:loading="(imageProps && imageProps.loading) || 'default'"
|
||||
:shape="(imageProps && imageProps.shape) || 'round'"
|
||||
:webp="(imageProps && imageProps.webp) || false"
|
||||
:show-menu-by-longpress="(imageProps && imageProps.showMenuByLongpress) || false"
|
||||
@click="onPreview($event, { file, index })"
|
||||
/>
|
||||
<video
|
||||
v-if="file.type === 'video'"
|
||||
:class="classPrefix + '__thumbnail'"
|
||||
:src="file.url"
|
||||
:poster="file.thumb"
|
||||
controls
|
||||
:autoplay="false"
|
||||
objectFit="contain"
|
||||
:data-file="file"
|
||||
@click.stop="onFileClick"
|
||||
/>
|
||||
<view
|
||||
v-if="file.status && file.status != 'done'"
|
||||
:class="classPrefix + '__progress-mask'"
|
||||
:data-index="index"
|
||||
:data-file="file"
|
||||
@click.stop="onFileClick"
|
||||
>
|
||||
<block v-if="file.status == 'loading'">
|
||||
<t-icon
|
||||
:t-class="classPrefix + '__progress-loading'"
|
||||
name="loading"
|
||||
size="48rpx"
|
||||
aria-hidden
|
||||
/>
|
||||
<view :class="classPrefix + '__progress-text'">
|
||||
{{ file.percent ? file.percent + '%' : '上传中...' }}
|
||||
</view>
|
||||
</block>
|
||||
<t-icon
|
||||
v-else
|
||||
:name="file.status == 'reload' ? 'refresh' : 'close-circle'"
|
||||
size="48rpx"
|
||||
aria-hidden
|
||||
/>
|
||||
<view
|
||||
v-if="file.status == 'reload' || file.status == 'failed'"
|
||||
:class="classPrefix + '__progress-text'"
|
||||
>
|
||||
{{ file.status == 'reload' ? '重新上传' : '上传失败' }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="tools.isBoolean(file.removeBtn) ? file.removeBtn : removeBtn"
|
||||
:class="classPrefix + '__close-btn hotspot-expanded'"
|
||||
:data-index="index"
|
||||
aria-role="button"
|
||||
aria-label="删除"
|
||||
@click.stop="onDelete"
|
||||
>
|
||||
<t-icon
|
||||
name="close"
|
||||
size="32rpx"
|
||||
color="#fff"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</t-grid-item>
|
||||
<t-grid-item
|
||||
v-if="addBtn && customLimit > 0"
|
||||
:t-class="classPrefix + '__grid'"
|
||||
:t-class-content="classPrefix + '__grid-content'"
|
||||
aria-label="上传"
|
||||
@click="onAddTap"
|
||||
>
|
||||
<view
|
||||
:class="classPrefix + '__wrapper'"
|
||||
:style="gridItemStyle"
|
||||
>
|
||||
<slot name="add-content" />
|
||||
<block v-if="addContent">
|
||||
{{ addContent }}
|
||||
</block>
|
||||
<view
|
||||
v-else
|
||||
:class="classPrefix + '__add-icon ' + (disabled ? classPrefix + '__add-icon--disabled' : '')"
|
||||
>
|
||||
<t-icon name="add" />
|
||||
</view>
|
||||
</view>
|
||||
</t-grid-item>
|
||||
</block>
|
||||
<block v-else>
|
||||
<view
|
||||
:class="classPrefix + '__drag'"
|
||||
:list="dragList"
|
||||
:style="dragWrapStyle + ';'"
|
||||
:drag-base-data="dragBaseData"
|
||||
>
|
||||
<view
|
||||
v-for="(file, index) in customFiles"
|
||||
:key="index"
|
||||
:ref="classPrefix + '__drag-item'"
|
||||
:class="getDragItemClass(index)"
|
||||
:style="getDragItemStyle(index)"
|
||||
:data-index="index"
|
||||
@longpress="parseEventDynamicCode($event, 'longPress', index)"
|
||||
@touchmove.stop.prevent="parseEventDynamicCode($event, dragging ? 'touchMove' : '', index)"
|
||||
@touchend.stop.prevent="parseEventDynamicCode($event, dragging ? 'touchEnd' : '', index)"
|
||||
>
|
||||
<t-grid-item
|
||||
:t-class="classPrefix + '__grid ' + classPrefix + '__grid-file'"
|
||||
:t-class-content="classPrefix + '__grid-content'"
|
||||
aria-role="presentation"
|
||||
custom-style="width: 100%"
|
||||
>
|
||||
<view
|
||||
:class="classPrefix + '__wrapper ' + (disabled ? classPrefix + '__wrapper--disabled' : '')"
|
||||
:style="gridItemStyle + ';'"
|
||||
:aria-role="ariaRole || getWrapperAriaRole(file)"
|
||||
:aria-label="ariaLabel || getWrapperAriaLabel(file)"
|
||||
>
|
||||
<t-image
|
||||
v-if="file.type !== 'video'"
|
||||
:data-file="file"
|
||||
:data-index="index"
|
||||
:t-class="classPrefix + '__thumbnail'"
|
||||
:custom-style="(imageProps && imageProps.style) || ''"
|
||||
:src="file.thumb || file.url"
|
||||
:mode="(imageProps && imageProps.mode) || 'aspectFill'"
|
||||
:error="(imageProps && imageProps.error) || 'default'"
|
||||
:lazy="(imageProps && imageProps.lazy) || false"
|
||||
:loading="(imageProps && imageProps.loading) || 'default'"
|
||||
:shape="(imageProps && imageProps.shape) || 'round'"
|
||||
:webp="(imageProps && imageProps.webp) || false"
|
||||
:show-menu-by-longpress="(imageProps && imageProps.showMenuByLongpress) || false"
|
||||
@click="onPreview($event, { file, index })"
|
||||
/>
|
||||
<video
|
||||
v-if="file.type === 'video'"
|
||||
:class="classPrefix + '__thumbnail'"
|
||||
:src="file.url"
|
||||
:poster="file.thumb"
|
||||
controls
|
||||
:autoplay="false"
|
||||
objectFit="contain"
|
||||
:data-file="file"
|
||||
@click.stop="onFileClick"
|
||||
/>
|
||||
<view
|
||||
v-if="file.status && file.status != 'done'"
|
||||
:class="classPrefix + '__progress-mask'"
|
||||
:data-index="index"
|
||||
:data-file="file"
|
||||
@click.stop="onFileClick"
|
||||
>
|
||||
<block v-if="file.status == 'loading'">
|
||||
<t-icon
|
||||
:t-class="classPrefix + '__progress-loading'"
|
||||
name="loading"
|
||||
size="48rpx"
|
||||
aria-hidden
|
||||
/>
|
||||
<view :class="classPrefix + '__progress-text'">
|
||||
{{ file.percent ? file.percent + '%' : '上传中...' }}
|
||||
</view>
|
||||
</block>
|
||||
<t-icon
|
||||
v-else
|
||||
:name="file.status == 'reload' ? 'refresh' : 'close-circle'"
|
||||
size="48rpx"
|
||||
aria-hidden
|
||||
/>
|
||||
<view
|
||||
v-if="file.status == 'reload' || file.status == 'failed'"
|
||||
:class="classPrefix + '__progress-text'"
|
||||
>
|
||||
{{ file.status == 'reload' ? '重新上传' : '上传失败' }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="tools.isBoolean(file.removeBtn) ? file.removeBtn : removeBtn"
|
||||
:class="classPrefix + '__close-btn hotspot-expanded'"
|
||||
:data-index="index"
|
||||
:data-url="file.url"
|
||||
aria-role="button"
|
||||
aria-label="删除"
|
||||
@click.stop="onDelete"
|
||||
>
|
||||
<t-icon
|
||||
name="close"
|
||||
size="32rpx"
|
||||
color="#fff"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</t-grid-item>
|
||||
</view>
|
||||
<view
|
||||
v-if="addBtn && customLimit > 0"
|
||||
:ref="classPrefix + '__drag-item'"
|
||||
:class="getDragItemClass(customFiles.length)"
|
||||
:style="getDragItemStyle(customFiles.length)"
|
||||
>
|
||||
<t-grid-item
|
||||
:t-class="classPrefix + '__grid'"
|
||||
:t-class-content="classPrefix + '__grid-content'"
|
||||
aria-label="上传"
|
||||
custom-style="width: 100%"
|
||||
@click="onAddTap"
|
||||
>
|
||||
<view
|
||||
:class="classPrefix + '__wrapper'"
|
||||
:style="gridItemStyle"
|
||||
>
|
||||
<slot name="add-content" />
|
||||
<block v-if="addContent">
|
||||
{{ addContent }}
|
||||
</block>
|
||||
<view
|
||||
v-else
|
||||
:class="classPrefix + '__add-icon ' + (disabled ? classPrefix + '__add-icon--disabled' : '')"
|
||||
>
|
||||
<t-icon name="add" />
|
||||
</view>
|
||||
</view>
|
||||
</t-grid-item>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</t-grid>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import TGrid from '../grid/grid';
|
||||
import TGridItem from '../grid-item/grid-item';
|
||||
import TIcon from '../icon/icon';
|
||||
import TImage from '../image/image';
|
||||
import { uniComponent } from '../common/src/index';
|
||||
import props from './props';
|
||||
import { prefix } from '../common/config';
|
||||
import { isOverSize, coalesce, isWxWork, isPC } from '../common/utils';
|
||||
import { isObject } from '../common/validator';
|
||||
import tools from '../common/utils.wxs';
|
||||
import {
|
||||
getWrapperAriaRole,
|
||||
getWrapperAriaLabel,
|
||||
} from './upload.computed.js';
|
||||
import {
|
||||
longPress,
|
||||
touchMove,
|
||||
touchEnd,
|
||||
baseDataObserver,
|
||||
listObserver,
|
||||
} from './drag.computed.js';
|
||||
import { parseEventDynamicCode } from '../common/event/dynamic';
|
||||
|
||||
|
||||
const name = `${prefix}-upload`;
|
||||
|
||||
const makeMethods = () => [
|
||||
[longPress, 'longPress'],
|
||||
[touchMove, 'touchMove'],
|
||||
[touchEnd, 'touchEnd'],
|
||||
[baseDataObserver, 'baseDataObserver'],
|
||||
[listObserver, 'listObserver'],
|
||||
].reduce((acc, item) => {
|
||||
const func = item[0];
|
||||
return {
|
||||
...acc,
|
||||
[item[1]](...args) {
|
||||
func.call(this, ...args);
|
||||
},
|
||||
};
|
||||
}, {});
|
||||
|
||||
|
||||
export default uniComponent({
|
||||
name,
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
controlledProps: [
|
||||
{
|
||||
key: 'files',
|
||||
event: 'success',
|
||||
},
|
||||
],
|
||||
externalClasses: [`${prefix}-class`],
|
||||
components: {
|
||||
TGrid,
|
||||
TGridItem,
|
||||
TIcon,
|
||||
TImage,
|
||||
},
|
||||
props: {
|
||||
...props,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
classPrefix: name,
|
||||
prefix,
|
||||
current: false,
|
||||
proofs: [],
|
||||
customFiles: [], // 内部动态修改的files
|
||||
customLimit: 0, // 内部动态修改的limit
|
||||
column: 4,
|
||||
dragBaseData: {}, // 拖拽所需要页面数据
|
||||
rows: 0, // 行数
|
||||
dragWrapStyle: '', // 拖拽容器的样式
|
||||
dragList: [], // 拖拽的数据列
|
||||
dragging: true, // 是否开始拖拽
|
||||
dragLayout: false, // 是否开启拖拽布局
|
||||
tools,
|
||||
|
||||
gridItemStyle: '',
|
||||
|
||||
fakeState: {},
|
||||
|
||||
dragItemClassList: [],
|
||||
dragItemStyleList: [],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
files: {
|
||||
handler() {
|
||||
this.onWatchFilesLimit();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
max: 'onWatchFilesLimit',
|
||||
draggable: {
|
||||
handler() {
|
||||
this.onWatchFilesLimit();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
|
||||
gridConfig: {
|
||||
handler() {
|
||||
this.updateGrid();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
dragList: {
|
||||
handler(val) {
|
||||
setTimeout(() => {
|
||||
this.listObserver(val);
|
||||
}, 33);
|
||||
},
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
dragBaseData: {
|
||||
handler(val) {
|
||||
this.baseDataObserver(val);
|
||||
},
|
||||
deep: true,
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.handleLimit(this.files, this.max);
|
||||
this.updateGrid();
|
||||
},
|
||||
methods: {
|
||||
getWrapperAriaRole,
|
||||
getWrapperAriaLabel,
|
||||
|
||||
...makeMethods(),
|
||||
|
||||
handleLimit(customFiles, max) {
|
||||
if (max === 0) {
|
||||
max = Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
this.customFiles = customFiles.length > max ? customFiles.slice(0, max) : customFiles;
|
||||
this.customLimit = max - customFiles.length;
|
||||
this.dragging = true;
|
||||
|
||||
this.initDragLayout();
|
||||
},
|
||||
|
||||
triggerSuccessEvent(files) {
|
||||
this._trigger('success', { files: [...this.customFiles, ...files] });
|
||||
},
|
||||
|
||||
triggerFailEvent(err) {
|
||||
this.$emit('fail', err);
|
||||
},
|
||||
|
||||
onFileClick(e) {
|
||||
const { file, index } = e.currentTarget.dataset;
|
||||
this.$emit('click', { index, file });
|
||||
},
|
||||
|
||||
/**
|
||||
* 由于小程序暂时在ios上不支持返回上传文件的fileType,这里用文件的后缀来判断
|
||||
* @param mediaType
|
||||
* @param tempFilePath
|
||||
* @returns string
|
||||
* @link https://developers.weixin.qq.com/community/develop/doc/00042820b28ee8fb41fc4d0c254c00
|
||||
*/
|
||||
getFileType(mediaType, tempFilePath, fileType) {
|
||||
if (fileType) return fileType; // 如果有返回fileType就直接用
|
||||
if (mediaType.length === 1) {
|
||||
// 在单选媒体类型的时候直接使用单选媒体类型
|
||||
return mediaType[0];
|
||||
}
|
||||
// 否则根据文件后缀进行判读
|
||||
const videoType = ['avi', 'wmv', 'mkv', 'mp4', 'mov', 'rm', '3gp', 'flv', 'mpg', 'rmvb'];
|
||||
const temp = tempFilePath.split('.');
|
||||
const postfix = temp[temp.length - 1];
|
||||
if (videoType.includes(postfix.toLocaleLowerCase())) {
|
||||
return 'video';
|
||||
}
|
||||
return 'image';
|
||||
},
|
||||
|
||||
// 选中文件之后,计算一个随机的短文件名
|
||||
getRandFileName(filePath) {
|
||||
const extIndex = filePath.lastIndexOf('.');
|
||||
const extName = extIndex === -1 ? '' : filePath.substr(extIndex);
|
||||
return parseInt(`${Date.now()}${Math.floor(Math.random() * 900 + 100)}`, 10).toString(36) + extName;
|
||||
},
|
||||
|
||||
checkFileSize(size, sizeLimit, fileType) {
|
||||
if (isOverSize(size, sizeLimit)) {
|
||||
let title = `${fileType === 'video' ? '视频' : '图片'}大小超过限制`;
|
||||
|
||||
if (isObject(sizeLimit)) {
|
||||
const { size: limitSize, message: limitMessage } = sizeLimit;
|
||||
title = limitMessage?.replace('{sizeLimit}', String(limitSize));
|
||||
}
|
||||
uni.showToast({ icon: 'none', title });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
onDelete(e) {
|
||||
const { index } = e.currentTarget.dataset;
|
||||
this.deleteHandle(index);
|
||||
},
|
||||
|
||||
deleteHandle(index) {
|
||||
const { customFiles } = this;
|
||||
const delFile = customFiles[index];
|
||||
this.$emit('remove', { index, file: delFile });
|
||||
},
|
||||
|
||||
updateGrid() {
|
||||
let { gridConfig = {} } = this;
|
||||
if (!isObject(gridConfig)) gridConfig = {};
|
||||
const { column = 4, width = 160, height = 160 } = gridConfig;
|
||||
|
||||
this.gridItemStyle = `width:${width}rpx;height:${height}rpx`;
|
||||
this.column = column;
|
||||
},
|
||||
|
||||
resetDragLayout() {
|
||||
this.dragBaseData = {};
|
||||
this.dragWrapStyle = '';
|
||||
this.dragLayout = false;
|
||||
},
|
||||
|
||||
initDragLayout() {
|
||||
const { draggable, disabled, customFiles } = this;
|
||||
if (!draggable || disabled || customFiles.length === 0) {
|
||||
this.resetDragLayout();
|
||||
return;
|
||||
}
|
||||
this.initDragList();
|
||||
setTimeout(() => {
|
||||
this.initDragBaseData();
|
||||
}, 33)
|
||||
;
|
||||
},
|
||||
|
||||
initDragList() {
|
||||
let i = 0;
|
||||
const { column, customFiles, customLimit } = this;
|
||||
const dragList = [];
|
||||
customFiles.forEach((item, index) => {
|
||||
dragList.push({
|
||||
realKey: i, // 真实顺序
|
||||
sortKey: index, // 整体顺序
|
||||
tranX: `${(index % column) * 100}%`,
|
||||
tranY: `${Math.floor(index / column) * 100}%`,
|
||||
data: { ...item },
|
||||
});
|
||||
i += 1;
|
||||
});
|
||||
if (customLimit > 0) {
|
||||
const listLength = dragList.length;
|
||||
dragList.push({
|
||||
realKey: listLength, // 真实顺序
|
||||
sortKey: listLength, // 整体顺序
|
||||
tranX: `${(listLength % column) * 100}%`,
|
||||
tranY: `${Math.floor(listLength / column) * 100}%`,
|
||||
fixed: true,
|
||||
});
|
||||
}
|
||||
this.rows = Math.ceil(dragList.length / column);
|
||||
|
||||
this.dragList = dragList;
|
||||
},
|
||||
|
||||
initDragBaseData() {
|
||||
const { classPrefix, rows, column } = this;
|
||||
|
||||
let query;
|
||||
// #ifdef H5 || APP-PLUS
|
||||
query = uni.createSelectorQuery().in(this);
|
||||
// #endif
|
||||
if (!query) {
|
||||
query = this.createSelectorQuery();
|
||||
}
|
||||
|
||||
|
||||
let selectorGridItem;
|
||||
let selectorGrid;
|
||||
// #ifdef H5 || APP-PLUS
|
||||
selectorGridItem = '.t-grid-item';
|
||||
selectorGrid = '.t-grid';
|
||||
// #endif
|
||||
|
||||
if (!selectorGridItem) {
|
||||
selectorGridItem = `.${classPrefix} >>> .t-grid-item`;
|
||||
selectorGrid = `.${classPrefix} >>> .t-grid`;
|
||||
}
|
||||
|
||||
query.select(selectorGridItem).boundingClientRect();
|
||||
query.select(selectorGrid).boundingClientRect();
|
||||
query.selectViewport().scrollOffset();
|
||||
query.exec((res) => {
|
||||
const [{ width, height }, { left, top }, { scrollTop }] = res;
|
||||
const dragBaseData = {
|
||||
rows,
|
||||
classPrefix,
|
||||
itemWidth: width,
|
||||
itemHeight: height,
|
||||
wrapLeft: left,
|
||||
wrapTop: top + scrollTop,
|
||||
columns: column,
|
||||
};
|
||||
const dragWrapStyle = `height: ${rows * height}px`;
|
||||
|
||||
this.dragBaseData = dragBaseData;
|
||||
this.dragWrapStyle = dragWrapStyle;
|
||||
this.dragLayout = true;
|
||||
|
||||
|
||||
// 为了给拖拽元素加上拖拽方法,同时控制不拖拽时不取消穿透
|
||||
const timer = setTimeout(() => {
|
||||
this.dragging = false;
|
||||
clearTimeout(timer);
|
||||
}, 0);
|
||||
});
|
||||
},
|
||||
|
||||
getPreviewMediaSources() {
|
||||
const previewMediaSources = [];
|
||||
this.customFiles.forEach((ele) => {
|
||||
const mediaSource = {
|
||||
url: ele.url,
|
||||
type: ele.type,
|
||||
poster: ele.thumb || undefined,
|
||||
};
|
||||
previewMediaSources.push(mediaSource);
|
||||
});
|
||||
|
||||
return previewMediaSources;
|
||||
},
|
||||
|
||||
onPreview(e) {
|
||||
this.onFileClick(e);
|
||||
const { preview } = this;
|
||||
|
||||
if (!preview) return;
|
||||
|
||||
const usePreviewMedia = this.customFiles.some(file => file.type === 'video');
|
||||
if (usePreviewMedia) {
|
||||
this.onPreviewMedia(e);
|
||||
} else {
|
||||
this.onPreviewImage(e);
|
||||
}
|
||||
},
|
||||
|
||||
onPreviewImage(e) {
|
||||
const { index } = e.currentTarget.dataset;
|
||||
const urls = this.customFiles.filter(file => file.percent !== -1).map(file => file.url);
|
||||
const current = this.customFiles[index]?.url;
|
||||
uni.previewImage({
|
||||
urls,
|
||||
current,
|
||||
fail() {
|
||||
uni.showToast({ title: '预览图片失败', icon: 'none' });
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
onPreviewMedia(e) {
|
||||
const { index: current } = e.currentTarget.dataset;
|
||||
const sources = this.getPreviewMediaSources();
|
||||
uni.previewMedia({
|
||||
sources,
|
||||
current,
|
||||
fail() {
|
||||
uni.showToast({ title: '预览视频失败', icon: 'none' });
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
uploadFiles(files) {
|
||||
return Promise.resolve().then(() => {
|
||||
// 开始调用上传函数
|
||||
const task = this.data.requestMethod(files);
|
||||
if (task instanceof Promise) {
|
||||
return task;
|
||||
}
|
||||
return Promise.resolve({});
|
||||
});
|
||||
},
|
||||
|
||||
startUpload(files) {
|
||||
// 如果传入了上传函数,则进度设为0并开始上传,否则跳过上传
|
||||
if (typeof this.requestMethod === 'function') {
|
||||
return this.uploadFiles(files)
|
||||
.then(() => {
|
||||
files.forEach((file) => {
|
||||
file.percent = 100;
|
||||
});
|
||||
this.triggerSuccessEvent(files);
|
||||
})
|
||||
.catch((err) => {
|
||||
this.triggerFailEvent(err);
|
||||
});
|
||||
}
|
||||
|
||||
// 如果没有上传函数,success事件与微信api上传成功关联
|
||||
this.triggerSuccessEvent(files);
|
||||
|
||||
this.handleLimit(this.customFiles, this.max);
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
onWatchFilesLimit() {
|
||||
this.handleLimit(this.files, this.max);
|
||||
},
|
||||
|
||||
onAddTap() {
|
||||
const { disabled, mediaType, source } = this;
|
||||
if (disabled) return;
|
||||
if (source === 'media') {
|
||||
this.chooseMedia(mediaType);
|
||||
} else {
|
||||
this.chooseMessageFile(mediaType);
|
||||
}
|
||||
},
|
||||
|
||||
chooseMedia(mediaType) {
|
||||
const { customLimit } = this;
|
||||
const { config, sizeLimit } = this;
|
||||
let func = 'chooseMedia';
|
||||
// #ifdef H5 || MP-ALIPAY
|
||||
func = 'chooseImage';
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
if (isPC || isWxWork) {
|
||||
func = 'chooseImage';
|
||||
}
|
||||
// #endif
|
||||
uni[func]({
|
||||
count: Math.min(20, customLimit),
|
||||
mediaType,
|
||||
...config,
|
||||
success: (res) => {
|
||||
const files = [];
|
||||
|
||||
// 支持单/多文件
|
||||
res.tempFiles.forEach((temp) => {
|
||||
const { size, fileType, tempFilePath, width, height, duration, thumbTempFilePath, ...res } = temp;
|
||||
|
||||
if (this.checkFileSize(size, sizeLimit, fileType)) return;
|
||||
|
||||
|
||||
const name = temp.name || this.getRandFileName(tempFilePath);
|
||||
files.push({
|
||||
name,
|
||||
type: this.getFileType(mediaType, temp.name || tempFilePath, fileType),
|
||||
url: tempFilePath,
|
||||
size,
|
||||
width,
|
||||
height,
|
||||
duration,
|
||||
thumb: thumbTempFilePath,
|
||||
percent: 0,
|
||||
...res,
|
||||
});
|
||||
});
|
||||
this.afterSelect(files);
|
||||
},
|
||||
fail: (err) => {
|
||||
this.triggerFailEvent(err);
|
||||
},
|
||||
complete: (res) => {
|
||||
this.$emit('complete', res);
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
chooseMessageFile(mediaType) {
|
||||
const { customLimit } = this;
|
||||
const { config, sizeLimit } = this;
|
||||
uni.chooseMessageFile({
|
||||
count: Math.min(100, customLimit),
|
||||
type: Array.isArray(mediaType) ? 'all' : mediaType,
|
||||
...config,
|
||||
success: (res) => {
|
||||
const files = [];
|
||||
|
||||
// 支持单/多文件
|
||||
res.tempFiles.forEach((temp) => {
|
||||
const { size, type: fileType, path: tempFilePath, ...res } = temp;
|
||||
|
||||
if (this.checkFileSize(size, sizeLimit, fileType)) return;
|
||||
|
||||
const name = this.getRandFileName(tempFilePath);
|
||||
files.push({
|
||||
name,
|
||||
type: this.getFileType(mediaType, tempFilePath, fileType),
|
||||
url: tempFilePath,
|
||||
size,
|
||||
percent: 0,
|
||||
...res,
|
||||
});
|
||||
});
|
||||
this.afterSelect(files);
|
||||
},
|
||||
fail: err => this.triggerFailEvent(err),
|
||||
complete: res => this.$emit('complete', res),
|
||||
});
|
||||
},
|
||||
|
||||
afterSelect(files) {
|
||||
this._trigger('select-change', {
|
||||
files: [...this.customFiles],
|
||||
currentSelectedFiles: [files],
|
||||
});
|
||||
this._trigger('add', { files });
|
||||
this.startUpload(files);
|
||||
},
|
||||
|
||||
dragVibrate(e) {
|
||||
const { vibrateType } = e;
|
||||
const { draggable } = this;
|
||||
const dragVibrate = coalesce(draggable?.vibrate, true);
|
||||
const dragCollisionVibrate = draggable?.collisionVibrate;
|
||||
if ((dragVibrate && vibrateType === 'longPress') || (dragCollisionVibrate && vibrateType === 'touchMove')) {
|
||||
uni.vibrateShort({
|
||||
type: 'light',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
dragStatusChange(e) {
|
||||
const { dragging } = e;
|
||||
this.dragging = dragging;
|
||||
},
|
||||
|
||||
dragEnd(e) {
|
||||
const { dragCollisionList } = e;
|
||||
let files = [];
|
||||
if (dragCollisionList.length === 0) {
|
||||
files = this.customFiles;
|
||||
} else {
|
||||
files = dragCollisionList.reduce((list, item) => {
|
||||
const { realKey, data, fixed } = item;
|
||||
if (!fixed) {
|
||||
list[realKey] = {
|
||||
...data,
|
||||
};
|
||||
}
|
||||
return list;
|
||||
}, []);
|
||||
}
|
||||
this.triggerDropEvent(files);
|
||||
},
|
||||
|
||||
triggerDropEvent(files) {
|
||||
const { transition } = this;
|
||||
if (transition.backTransition) {
|
||||
const timer = setTimeout(() => {
|
||||
this.$emit('drop', { files });
|
||||
clearTimeout(timer);
|
||||
}, transition.duration);
|
||||
} else {
|
||||
this.$emit('drop', { files });
|
||||
}
|
||||
},
|
||||
getState() {
|
||||
return this.fakeState || {};
|
||||
},
|
||||
callMethod(...args) {
|
||||
return this[args[0]]?.(...args.slice(1));
|
||||
},
|
||||
parseEventDynamicCode,
|
||||
setDragItemClass(index, operation, val) {
|
||||
if (!this.dragItemClassList[index]) {
|
||||
this.dragItemClassList[index] = [];
|
||||
}
|
||||
const valList = Array.isArray(val) ? val : [val];
|
||||
if (operation === 'add') {
|
||||
this.dragItemClassList[index].push(...valList);
|
||||
return;
|
||||
}
|
||||
if (operation === 'remove') {
|
||||
this.dragItemClassList[index] = this.dragItemClassList[index].filter(item => !valList.includes(item));
|
||||
}
|
||||
},
|
||||
getDragItemClass(index) {
|
||||
const { classPrefix } = this;
|
||||
const base = [
|
||||
`${classPrefix}__drag-item`,
|
||||
];
|
||||
return [
|
||||
...base,
|
||||
...(this.dragItemClassList[index] || []),
|
||||
].join(' ');
|
||||
},
|
||||
setDragItemStyle(index, val) {
|
||||
if (!this.dragItemStyleList[index]) {
|
||||
this.dragItemStyleList[index] = [];
|
||||
}
|
||||
this.dragItemStyleList[index].push(val);
|
||||
},
|
||||
getDragItemStyle(index) {
|
||||
const { column, transition } = this;
|
||||
const base = [
|
||||
`width: ${100 / column}%`,
|
||||
`--td-upload-drag-transition-duration: ${transition.duration}ms`,
|
||||
`--td-upload-drag-transition-timing-function: ${transition.timingFunction}`,
|
||||
];
|
||||
|
||||
return [
|
||||
...base,
|
||||
...(this.dragItemStyleList[index] || []),
|
||||
].join(';');
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
@import './upload.css';
|
||||
</style>
|
||||
Reference in New Issue
Block a user