From 8fb4efdca0c5fc6b307ab38a974cd92b94099602 Mon Sep 17 00:00:00 2001 From: zelmazhou <72006584+zuiaiwanqian@users.noreply.github.com> Date: Tue, 16 Jan 2024 12:00:45 +0800 Subject: [PATCH] =?UTF-8?q?[Upload=20=E4=B8=8A=E4=BC=A0]=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=8B=96=E6=8B=BD=E6=8E=92=E5=BA=8F=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=20(#2527)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 初始交互完成 * feat: 交互功能完成 * feat: 删除添加配合排序完成 * feat: 完成交互 * feat: 完成拖拽排序 * feat: 完成示例 * feat: 完成拖拽排序 * chore: 优化注释 * fix: 修复优化 * fix: 修复bug * test: update snapshots --------- Co-authored-by: anlyyao --- src/upload/README.en-US.md | 6 + src/upload/README.md | 9 + .../__test__/__snapshots__/demo.test.js.snap | 3 +- src/upload/_example/drag/index.js | 55 ++++ src/upload/_example/drag/index.json | 6 + src/upload/_example/drag/index.wxml | 13 + src/upload/_example/drag/index.wxss | 0 src/upload/_example/multiple/index.js | 2 +- src/upload/_example/multiple/index.wxml | 1 + src/upload/_example/upload.json | 3 +- src/upload/_example/upload.wxml | 6 + src/upload/drag.wxs | 238 +++++++++++++++ src/upload/props.ts | 9 + src/upload/type.ts | 26 ++ src/upload/upload.less | 28 ++ src/upload/upload.ts | 135 ++++++++- src/upload/upload.wxml | 279 +++++++++++++----- 17 files changed, 736 insertions(+), 83 deletions(-) create mode 100644 src/upload/_example/drag/index.js create mode 100644 src/upload/_example/drag/index.json create mode 100644 src/upload/_example/drag/index.wxml create mode 100644 src/upload/_example/drag/index.wxss create mode 100644 src/upload/drag.wxs diff --git a/src/upload/README.en-US.md b/src/upload/README.en-US.md index d05d9ea7e..74e953622 100644 --- a/src/upload/README.en-US.md +++ b/src/upload/README.en-US.md @@ -20,6 +20,8 @@ media-type | Array | ['image', 'video'] | Typescript:`Array` `type request-method | Function | - | \- | N size-limit | Number / Object | - | Typescript:`number \| SizeLimitObj` `interface SizeLimitObj { size: number; unit: SizeUnit ; message?: string }` `type SizeUnitArray = ['B', 'KB', 'MB', 'GB']` `type SizeUnit = SizeUnitArray[number]`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/tree/develop/src/upload/type.ts) | N source | String | media | options:media/messageFile | N +draggable | Boolean / Object | - | Does it support drag and drop sorting. Whether it vibrates during long periods of time and whether it vibrates during collisions。。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/tree/develop/src/upload/type.ts) | N +transition | Object | `{ backTransition: true, duration: 300, timingFunction: 'ease' }` | Transition parameters when dragging and moving positions。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/tree/develop/src/upload/type.ts) | N ### Upload Events @@ -31,6 +33,9 @@ fail | \- | \- remove | `(index: number; file: UploadFile)` | \- select-change | `(currentSelectedFiles: MediaContext[])` | \- success | `(files: MediaContext)` | [see more ts definition](https://github.com/Tencent/tdesign-miniprogram/tree/develop/src/upload/type.ts)。
`type MediaContext = VideoContext[] \| ImageContext[]`

`interface VideoContext { name?: string; type?: string; url?: string; duration?: number; size?: number; width?: number; height?: number; thumb: string; progress: number }`

`interface ImageContext { name: string; type: string; url: string; size: number; width: number; height: number; progress: number }`
+click | `(file: VideoContext \| ImageContext)` | - +drop | `(files: MediaContext)` | [see more ts definition](https://github.com/Tencent/tdesign-miniprogram/tree/develop/src/upload/type.ts)。
`type MediaContext = VideoContext[] \| ImageContext[]`

`interface VideoContext { name?: string; type?: string; url?: string; duration?: number; size?: number; width?: number; height?: number; thumb: string; progress: number }`

`interface +ImageContext { name: string; type: string; url: string; size: number; width: number; height: number; progress: number }`
### CSS Variables @@ -44,3 +49,4 @@ Name | Default Value | Description --td-upload-add-icon-font-size | 56rpx | - --td-upload-disabled-mask | rgba(255, 255, 255, 0.55) | - --td-upload-radius | @radius-default | - +--td-upload-drag-z-index | 999 | - diff --git a/src/upload/README.md b/src/upload/README.md index 42c4ff73d..8bc650abf 100644 --- a/src/upload/README.md +++ b/src/upload/README.md @@ -32,6 +32,10 @@ isComponent: true {{ multiple }} +### 长按拖拽排序图片 + +{{ drag }} + ### 加载状态 支持多种状态:`loading`、`reload`、`failed`; @@ -66,6 +70,9 @@ media-type | Array | ['image', 'video'] | 支持上传的文件类型,图片 request-method | Function | - | 自定义上传方法 | N size-limit | Number / Object | - | 图片文件大小限制,单位 KB。可选单位有:`'B' \| 'KB' \| 'MB' \| 'GB'`。示例一:`1000`。示例二:`{ size: 2, unit: 'MB', message: '图片大小不超过 {sizeLimit} MB' }`。TS 类型:`number \| SizeLimitObj` `interface SizeLimitObj { size: number; unit: SizeUnit ; message?: string }` `type SizeUnitArray = ['B', 'KB', 'MB', 'GB']` `type SizeUnit = SizeUnitArray[number]`。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/src/upload/type.ts) | N source | String | media | 来源。可选项:media/messageFile | N +draggable | Boolean / Object | - | 是否支持拖拽排序。长按时是否振动,碰撞时是否振动。示例一:`true`。示例二:`{ vibrate: true, collisionVibrate: true }`。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/src/upload/type.ts) | N +transition | Object | `{ backTransition: true, duration: 300, timingFunction: 'ease' }` | 拖拽位置移动时的过渡参数。TS 类型:`{ backTransition: boolean, duration: number, timingFunction: string }`,`duration`单位为ms。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/src/upload/type.ts) | N + ### Upload Events @@ -78,6 +85,7 @@ remove | `(index: number; file: UploadFile)` | 移除文件时触发 select-change | `(files: MediaContext[]; currentSelectedFiles: MediaContext[])` | 选择文件或图片之后,上传之前,触发该事件。
`files` 表示之前已经上传完成的文件列表。
`currentSelectedFiles` 表示本次上传选中的文件列表 success | `(files: MediaContext)` | 上传成功后触发,包含所有上传的文件;`url` 表示选定视频的临时文件路径 (本地路径)。`duration` 表示选定视频的时间长度。`size`选定视频的数据量大小。更多描述参考 wx.chooseMedia 小程序官网描述。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/src/upload/type.ts)。
`type MediaContext = VideoContext[] \| ImageContext[]`

`interface VideoContext { name?: string; type?: string; url?: string; duration?: number; size?: number; width?: number; height?: number; thumb: string; progress: number }`

`interface ImageContext { name: string; type: string; url: string; size: number; width: number; height: number; progress: number }`
click | `(file: VideoContext \| ImageContext)` | 点击已选文件时触发;常用于重新上传 +drop | `(files: MediaContext)` | 拖拽结束后触发,包含所有上传的文件(拖拽后的文件顺序);`url` 表示选定视频的临时文件路径 (本地路径)。`duration` 表示选定视频的时间长度。`size`选定视频的数据量大小。更多描述参考 wx.chooseMedia 小程序官网描述。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/src/upload/type.ts)。
`type MediaContext = VideoContext[] \| ImageContext[]`

`interface VideoContext { name?: string; type?: string; url?: string; duration?: number; size?: number; width?: number; height?: number; thumb: string; progress: number }`

`interface ImageContext { name: string; type: string; url: string; size: number; width: number; height: number; progress: number }`
### CSS 变量 @@ -91,3 +99,4 @@ click | `(file: VideoContext \| ImageContext)` | 点击已选文件时触发; --td-upload-add-icon-font-size | 56rpx | - --td-upload-disabled-mask | rgba(255, 255, 255, 0.55) | - --td-upload-radius | @radius-default | - +--td-upload-drag-z-index | 999 | - diff --git a/src/upload/__test__/__snapshots__/demo.test.js.snap b/src/upload/__test__/__snapshots__/demo.test.js.snap index 1b051c464..4cac05150 100644 --- a/src/upload/__test__/__snapshots__/demo.test.js.snap +++ b/src/upload/__test__/__snapshots__/demo.test.js.snap @@ -62,7 +62,7 @@ exports[`Upload Upload multiple demo works fine 1`] = ` "url": "https://tdesign.gtimg.com/miniprogram/images/example6.png", }, Object { - "name": "uploaded1.png", + "name": "uploaded3.png", "type": "image", "url": "https://tdesign.gtimg.com/miniprogram/images/example5.png", }, @@ -83,6 +83,7 @@ exports[`Upload Upload multiple demo works fine 1`] = ` }}" bind:click="handleClick" bind:remove="handleRemove" + bind:sort-end="handleSortEnd" bind:success="handleSuccess" /> diff --git a/src/upload/_example/drag/index.js b/src/upload/_example/drag/index.js new file mode 100644 index 000000000..1febcdcef --- /dev/null +++ b/src/upload/_example/drag/index.js @@ -0,0 +1,55 @@ +Component({ + data: { + originFiles: [ + { + url: 'https://tdesign.gtimg.com/miniprogram/images/example4.png', + name: 'uploaded1.png', + type: 'image', + }, + { + url: 'https://tdesign.gtimg.com/miniprogram/images/example6.png', + name: 'uploaded2.png', + type: 'image', + }, + { + url: 'https://tdesign.gtimg.com/miniprogram/images/example5.png', + name: 'uploaded3.png', + type: 'image', + }, + ], + gridConfig: { + column: 4, + width: 160, + height: 160, + }, + config: { + count: 1, + }, + }, + methods: { + handleSuccess(e) { + const { files } = e.detail; + this.setData({ + originFiles: files, + }); + }, + handleRemove(e) { + const { index } = e.detail; + const { originFiles } = this.data; + originFiles.splice(index, 1); + this.setData({ + originFiles, + }); + }, + handleClick(e) { + console.log(e.detail.file); + }, + + handleDrop(e) { + const { files } = e.detail; + this.setData({ + originFiles: files, + }); + }, + }, +}); diff --git a/src/upload/_example/drag/index.json b/src/upload/_example/drag/index.json new file mode 100644 index 000000000..b2b7fb8bb --- /dev/null +++ b/src/upload/_example/drag/index.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "t-upload": "tdesign-miniprogram/upload/upload" + } +} diff --git a/src/upload/_example/drag/index.wxml b/src/upload/_example/drag/index.wxml new file mode 100644 index 000000000..b96ecd745 --- /dev/null +++ b/src/upload/_example/drag/index.wxml @@ -0,0 +1,13 @@ + + + diff --git a/src/upload/_example/drag/index.wxss b/src/upload/_example/drag/index.wxss new file mode 100644 index 000000000..e69de29bb diff --git a/src/upload/_example/multiple/index.js b/src/upload/_example/multiple/index.js index 20452ab1a..c9ac75157 100644 --- a/src/upload/_example/multiple/index.js +++ b/src/upload/_example/multiple/index.js @@ -13,7 +13,7 @@ Component({ }, { url: 'https://tdesign.gtimg.com/miniprogram/images/example5.png', - name: 'uploaded1.png', + name: 'uploaded3.png', type: 'image', }, ], diff --git a/src/upload/_example/multiple/index.wxml b/src/upload/_example/multiple/index.wxml index 971dfc319..8900227fa 100644 --- a/src/upload/_example/multiple/index.wxml +++ b/src/upload/_example/multiple/index.wxml @@ -7,5 +7,6 @@ bind:success="handleSuccess" bind:remove="handleRemove" bind:click="handleClick" + bind:sort-end="handleSortEnd" /> diff --git a/src/upload/_example/upload.json b/src/upload/_example/upload.json index f64e2ee69..984e6304c 100644 --- a/src/upload/_example/upload.json +++ b/src/upload/_example/upload.json @@ -4,6 +4,7 @@ "single": "./single", "multiple": "./multiple", "status": "./status", - "messageFile": "./messageFile" + "messageFile": "./messageFile", + "drag": "./drag" } } diff --git a/src/upload/_example/upload.wxml b/src/upload/_example/upload.wxml index 04145110d..3a38078bc 100644 --- a/src/upload/_example/upload.wxml +++ b/src/upload/_example/upload.wxml @@ -15,6 +15,12 @@ + + + 上传图片 + + + 上传图片 diff --git a/src/upload/drag.wxs b/src/upload/drag.wxs new file mode 100644 index 000000000..a136b58a3 --- /dev/null +++ b/src/upload/drag.wxs @@ -0,0 +1,238 @@ +var classPrefix = ''; +var startIndex = 0; +var endIndex = 0; +var dragCollisionList = []; + +var isOutRange = function (x1, y1, x2, y2, x3, y3) { + return x1 < 0 || x1 >= y1 || x2 < 0 || x2 >= y2 || x3 < 0 || x3 >= y3; +}; + +var sortCore = function (sKey, eKey, st) { + var _ = st.dragBaseData; + + var excludeFix = function (cKey, type) { + if (st.list[cKey].fixed) { + // fixed 元素位置不会变化, 这里直接用 cKey(sortKey) 获取, 更加快捷 + type ? --cKey : ++cKey; + return excludeFix(cKey, type); + } + return cKey; + }; + + // 先获取到 endKey 对应的 realKey, 防止下面排序过程中该 realKey 被修改 + var endRealKey = -1; + st.list.forEach(function (item) { + if (item.sortKey === eKey) endRealKey = item.realKey; + }); + + return st.list.map(function (item) { + if (item.fixed) return item; + var cKey = item.sortKey; + var rKey = item.realKey; + + if (sKey < eKey) { + // 正序拖动 + if (cKey > sKey && cKey <= eKey) { + --rKey; + cKey = excludeFix(--cKey, true); + } else if (cKey === sKey) { + rKey = endRealKey; + cKey = eKey; + } + } else if (sKey > eKey) { + // 倒序拖动 + if (cKey >= eKey && cKey < sKey) { + ++rKey; + cKey = excludeFix(++cKey, false); + } else if (cKey === sKey) { + rKey = endRealKey; + cKey = eKey; + } + } + + if (item.sortKey !== cKey) { + item.tranX = (cKey % _.columns) * 100 + '%'; + item.tranY = Math.floor(cKey / _.columns) * 100 + '%'; + item.sortKey = cKey; + item.realKey = rKey; + } + return item; + }); +}; + +var triggerCustomEvent = function (list, type, ins) { + var _list = [], + listData = []; + + list.forEach(function (item) { + _list[item.sortKey] = item; + }); + + _list.forEach(function (item) { + if (!item.extraNode) { + listData.push(item.data); + } + }); + + ins.triggerEvent(type, { listData: listData }); +}; + +var longPress = function (event, ownerInstance) { + var ins = event.instance; + var st = ownerInstance.getState(); + var _ = st.dragBaseData; + + var sTouch = event.changedTouches[0]; + if (!sTouch) return; + + st.cur = ins.getDataset().index; + longPressIndex = st.cur; + + // 初始项是固定项则返回 + var item = st.list[st.cur]; + if (item && item.fixed) return; + + // 如果已经在 drag 中则返回, 防止多指触发 drag 动作, touchstart 事件中有效果 + if (st.dragging) return; + st.dragging = true; + ownerInstance.callMethod('dragStatusChange', { dragging: true }); + + // 计算X,Y轴初始位移, 使 item 中心移动到点击处, 单列时候X轴初始不做位移 + st.tranX = _.columns === 1 ? 0 : sTouch.pageX - (_.itemWidth / 2 + _.wrapLeft); + st.tranY = sTouch.pageY - (_.itemHeight / 2 + _.wrapTop); + st.sId = sTouch.identifier; + ins.setStyle({ + transform: 'translate3d(' + st.tranX + 'px, ' + st.tranY + 'px, 0)', + }); + st.itemsInstance.forEach(function (item, index) { + item.removeClass(classPrefix + '__drag--tran').removeClass(classPrefix + '__drag--cur'); + item.addClass(index === st.cur ? classPrefix + '__drag--cur' : classPrefix + '__drag--tran'); + }); + ownerInstance.callMethod('dragVibrate', { vibrateType: 'longPress' }); +}; + +var touchMove = function (event, ownerInstance) { + var ins = event.instance; + var st = ownerInstance.getState(); + var _ = st.dragBaseData; + + var mTouch = event.changedTouches[0]; + if (!mTouch) return; + + if (!st.dragging) return; + + // 如果不是同一个触发点则返回 + if (st.sId !== mTouch.identifier) return; + + // 计算X,Y轴位移, 单列时候X轴初始不做位移 + var tranX = _.columns === 1 ? 0 : mTouch.pageX - (_.itemWidth / 2 + _.wrapLeft); + var tranY = mTouch.pageY - (_.itemHeight / 2 + _.wrapTop); + + // 到顶到底自动滑动 + if (mTouch.clientY > _.windowHeight - _.itemHeight - _.realBottomSize) { + // 当前触摸点pageY + item高度 - (屏幕高度 - 底部固定区域高度) + ownerInstance.callMethod('pageScroll', { + scrollTop: mTouch.pageY + _.itemHeight - (_.windowHeight - _.realBottomSize), + }); + } else if (mTouch.clientY < _.itemHeight + _.realTopSize) { + // 当前触摸点pageY - item高度 - 顶部固定区域高度 + ownerInstance.callMethod('pageScroll', { + scrollTop: mTouch.pageY - _.itemHeight - _.realTopSize, + }); + } + + // 设置当前激活元素偏移量 + ins.setStyle({ + transform: 'translate3d(' + tranX + 'px, ' + tranY + 'px, 0)', + }); + + var startKey = st.list[st.cur].sortKey; + var curX = Math.round(tranX / _.itemWidth); + var curY = Math.round(tranY / _.itemHeight); + var endKey = curX + _.columns * curY; + + // 目标项是固定项则返回 + var item = st.list[endKey]; + if (item && item.fixed) return; + + // X轴或Y轴超出范围则返回 + if (isOutRange(curX, _.columns, curY, _.rows, endKey, st.list.length)) return; + + // 防止拖拽过程中发生乱序问题 + if (startKey === endKey || startKey === st.preStartKey) return; + st.preStartKey = startKey; + + dragCollisionList = sortCore(startKey, endKey, st); + startIndex = startKey; + endIndex = endKey; + st.itemsInstance.forEach(function (itemIns, index) { + var item = dragCollisionList[index]; + if (index !== st.cur) { + itemIns.setStyle({ + transform: 'translate3d(' + item.tranX + ',' + item.tranY + ', 0)', + }); + } + }); + + ownerInstance.callMethod('dragVibrate', { vibrateType: 'touchMove' }); + ownerInstance.callMethod('dragCollision', { + dragCollisionList: dragCollisionList, + startIndex: startIndex, + endIndex: endIndex, + }); + triggerCustomEvent(dragCollisionList, 'change', ownerInstance); +}; + +var touchEnd = function (event, ownerInstance) { + var ins = event.instance; + var st = ownerInstance.getState(); + + if (!st.dragging) return; + triggerCustomEvent(st.list, 'sortend', ownerInstance); + ins.addClass(classPrefix + '__drag--tran'); + ins.setStyle({ + transform: 'translate3d(' + st.list[st.cur].tranX + ',' + st.list[st.cur].tranY + ', 0)', + }); + st.preStartKey = -1; + st.dragging = false; + ownerInstance.callMethod('dragStatusChange', { dragging: false }); + ownerInstance.callMethod('dragEnd', { + dragCollisionList: dragCollisionList, + startIndex: startIndex, + endIndex: endIndex, + }); + st.cur = -1; + st.tranX = 0; + st.tranY = 0; +}; + +var baseDataObserver = function (newVal, oldVal, ownerInstance, ins) { + var st = ownerInstance.getState(); + st.dragBaseData = newVal; + classPrefix = newVal.classPrefix; +}; + +var listObserver = function (newVal, oldVal, ownerInstance, ins) { + var st = ownerInstance.getState(); + st.itemsInstance = ownerInstance.selectAllComponents('.' + classPrefix + '__drag-item'); + st.list = newVal || []; + st.list.forEach(function (item, index) { + var itemIns = st.itemsInstance[index]; + if (item && itemIns) { + itemIns.removeClass(classPrefix + '__drag--tran'); + itemIns.setStyle({ + transform: 'translate3d(' + item.tranX + ',' + item.tranY + ', 0)', + }); + if (item.fixed) itemIns.addClass(classPrefix + '__drag--fixed'); + } + }); + dragCollisionList = []; +}; + +module.exports = { + longPress: longPress, + touchMove: touchMove, + touchEnd: touchEnd, + baseDataObserver: baseDataObserver, + listObserver: listObserver, +}; diff --git a/src/upload/props.ts b/src/upload/props.ts index 130794c6c..82aeba03f 100644 --- a/src/upload/props.ts +++ b/src/upload/props.ts @@ -69,6 +69,15 @@ const props: TdUploadProps = { type: String, value: 'media', }, + /** 是否支持拖拽排序 */ + draggable: { + type: null, + }, + /** 是否使用动画过渡 */ + transition: { + type: Object, + value: { backTransition: true, duration: 300, timingFunction: 'ease' }, + }, }; export default props; diff --git a/src/upload/type.ts b/src/upload/type.ts index dae02eee0..904c263e2 100644 --- a/src/upload/type.ts +++ b/src/upload/type.ts @@ -119,6 +119,21 @@ export interface TdUploadProps { type: StringConstructor; value?: 'media' | 'messageFile'; }; + /** + * 是否支持拖拽排序 + */ + draggable?: { + type: null; + value?: boolean | Draggable; + }; + + /** + * 过渡参数 + */ + transition?: { + type: ObjectConstructor; + value: Transition; + }; } export type UploadMpConfig = ImageConfig | VideoConfig; @@ -167,3 +182,14 @@ export interface SizeLimitObj { export type SizeUnitArray = ['B', 'KB', 'MB', 'GB']; export type SizeUnit = SizeUnitArray[number]; + +export interface Draggable { + vibrate?: boolean; + collisionVibrate?: boolean; +} + +export interface Transition { + backTransition?: boolean; + duration?: number; + timingFunction?: string; +} diff --git a/src/upload/upload.less b/src/upload/upload.less index b626b66db..cf57393a8 100644 --- a/src/upload/upload.less +++ b/src/upload/upload.less @@ -7,8 +7,10 @@ @upload-add-icon-font-size: var(--td-upload-add-icon-font-size, 56rpx); @upload-add-icon-disabled-color: var(--td-upload-add-icon-disabled-color, @text-color-disabled); @upload-disabled-mask: var(--td-upload-disabled-mask, rgba(255, 255, 255, 0.55)); +@upload-drag-z-index: var(--td-upload-drag-z-index, 999); .@{prefix}-upload { + position: relative; &__grid { &-content { padding: 0; @@ -104,6 +106,32 @@ animation: spin infinite linear 0.6s; } } + + &__drag { + position: relative; + width: 100%; + --td-grid-item-bg-color: transparent; + + &-item { + position: absolute; + z-index: 1; + top: 0px; + left: 0px; + height: auto; + width: 100%; + } + &--fixed { + z-index: 0; + } + &--tran { + transition-property: transform; + transition-duration: var(--td-upload-drag-transition-duration); + transition-timing-function: var(--td-upload-drag-transition-timing-function); + } + &--cur { + z-index: @upload-drag-z-index; + } + } } @keyframes spin { diff --git a/src/upload/upload.ts b/src/upload/upload.ts index 2de89d2ec..1657a9d4a 100644 --- a/src/upload/upload.ts +++ b/src/upload/upload.ts @@ -22,6 +22,12 @@ export default class Upload extends SuperComponent { customFiles: [] as UploadFile[], // 内部动态修改的files customLimit: 0, // 内部动态修改的limit column: 4, + dragBaseData: {}, // 拖拽所需要页面数据 + rows: 0, // 行数 + dragWrapStyle: '', // 拖拽容器的样式 + dragList: [], // 拖拽的数据列 + dragging: true, // 是否开始拖拽 + dragLayout: false, // 是否开启拖拽布局 }; properties = props; @@ -34,7 +40,7 @@ export default class Upload extends SuperComponent { ]; observers = { - 'files, max'(files: UploadFile, max: number) { + 'files, max, draggable'(files: UploadFile, max: number) { this.handleLimit(files, max); }, gridConfig() { @@ -62,11 +68,12 @@ export default class Upload extends SuperComponent { if (max === 0) { max = 20; } - this.setData({ customFiles: customFiles.length > max ? customFiles.slice(0, max) : customFiles, customLimit: max - customFiles.length, + dragging: true, }); + this.initDragLayout(); } triggerSuccessEvent(files) { @@ -133,6 +140,81 @@ export default class Upload extends SuperComponent { }); } + initDragLayout() { + const { draggable, disabled } = this.properties; + if (!draggable || disabled) return; + this.initDragList(); + this.initDragBaseData(); + } + + initDragList() { + let i = 0; + const { column, customFiles, customLimit } = this.data; + 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.data.rows = Math.ceil(dragList.length / column); + this.setData({ + dragList, + }); + } + + initDragBaseData() { + const { classPrefix, rows, column, customFiles } = this.data; + if (customFiles.length === 0) return; + const query = this.createSelectorQuery(); + const selectorGridItem = `.${classPrefix} >>> .t-grid-item`; + const 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.setData( + { + dragBaseData, + dragWrapStyle, + dragLayout: true, + }, + () => { + // 为了给拖拽元素加上拖拽方法,同时控制不拖拽时不取消穿透 + const timer = setTimeout(() => { + this.setData({ dragging: false }); + clearTimeout(timer); + }, 0); + }, + ); + }); + } + methods = { uploadFiles(files: UploadFile[]) { return new Promise((resolve) => { @@ -170,7 +252,6 @@ export default class Upload extends SuperComponent { onAddTap() { const { disabled, mediaType, source } = this.properties; if (disabled) return; - if (source === 'media') { this.chooseMedia(mediaType); } else { @@ -275,5 +356,53 @@ export default class Upload extends SuperComponent { this._trigger('add', { files }); this.startUpload(files); }, + + dragVibrate(e) { + const { vibrateType } = e; + const { draggable } = this.data; + const dragVibrate = draggable?.vibrate ?? true; + const dragCollisionVibrate = draggable?.collisionVibrate; + if ((dragVibrate && vibrateType === 'longPress') || (dragCollisionVibrate && vibrateType === 'touchMove')) { + wx.vibrateShort({ + type: 'light', + }); + } + }, + + dragStatusChange(e) { + const { dragging } = e; + this.setData({ dragging }); + }, + + dragEnd(e) { + const { dragCollisionList } = e; + let files = []; + if (dragCollisionList.length === 0) { + files = this.data.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.properties; + if (transition.backTransition) { + const timer = setTimeout(() => { + this.triggerEvent('drop', { files }); + clearTimeout(timer); + }, transition.duration); + } else { + this.triggerEvent('drop', { files }); + } + }, }; } diff --git a/src/upload/upload.wxml b/src/upload/upload.wxml index 34542b1f6..2f6162cd9 100644 --- a/src/upload/upload.wxml +++ b/src/upload/upload.wxml @@ -1,95 +1,220 @@ + - - - - + + + - - - + + + + + {{addContent}} + + + + + + + + - - - - + + + + + + + + + + {{addContent}} + + + + + - +