888 lines
26 KiB
Vue
888 lines
26 KiB
Vue
<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>
|