Movable
组件类型:UxMovableComponentPublicInstance
支持宫格和列表布局,内置照片墙上传场景,支持多选、编辑、滑动删除、禁用
平台兼容性
UniApp X
Android | iOS | web | 鸿蒙 Next | 小程序 |
---|---|---|---|---|
√ | √ | √ | x | √ |
UniApp Vue Nvue
Android | iOS | web | 鸿蒙 Next | 小程序 |
---|---|---|---|---|
x | x | √ | x | x |
Props
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
layout | String | grid | 布局 |
column | Number | 4 | 列数 |
spacing | Number | 8 | 间隔 |
aspectRatio | Number | 1 | item宽高比仅grid有效 |
multiple | Boolean | false | 多选 |
editable | Boolean | false | 可编辑 |
swipe | Boolean | false | 滑动删除 |
disabled | Boolean | false | 禁用 |
layout
值 | 说明 |
---|---|
grid宫格 | |
list列表 | |
Events
事件名 | 说明 | 参数 |
---|---|---|
init | 初始化时触发 | |
change | 值改变时触发 |
示例代码
html
<template>
<ux-page :stack="showDoc">
<ux-navbar :title="title" :bold="true">
<template v-slot:right>
<!-- #ifndef MP -->
<ux-button theme="text" icon="/static/tip.png" :icon-size="22" @click="onDoc()"></ux-button>
<!-- #endif -->
</template>
</ux-navbar>
<ux-scroll>
<ux-card direction="column" icon="flag-filled" title="原生拖拽组件" :bold="true">
<ux-text text="支持宫格和列表布局,内置照片墙上传场景,支持多选、编辑、滑动删除、禁用等功能"></ux-text>
</ux-card>
<!-- #ifndef MP -->
<ux-card direction="column" icon="arrowright" title="照片墙" :bold="true">
<ux-text text="内置照片墙上传场景,支持多选、删除" :mb="15"></ux-text>
<view style="height: 180px;">
<ux-movable ref="movableGridRef"
layout="grid"
:spacing="6"
:aspect-ratio="0.75"
:column="column"
:multiple="multiple"
:disabled="disabled"
@init="initGrid"
@change="gridChange">
</ux-movable>
</view>
<ux-row>
<ux-button text="列数 + 1" @click="onColumn(1)"></ux-button>
<ux-button text="列数 - 1" :ml="10" @click="onColumn(-1)"></ux-button>
<ux-button text="多选" :ml="10" @click="onMultiple()"></ux-button>
</ux-row>
</ux-card>
<ux-card direction="column" icon="arrowright" title="列表式" :bold="true">
<ux-text text="支持滑动删除" :mb="15"></ux-text>
<view style="height: 180px;">
<ux-movable ref="movableListRef"
layout="list"
:swipe="swipe"
:disabled="disabled"
@init="initList"
@change="listChange">
</ux-movable>
</view>
</ux-card>
<ux-placeholder :height="300">
<ux-row justify="center" align="center" style="height: 100%;">
<ux-text prefix-icon="soapbubble-filled" text="真的没有了~"></ux-text>
</ux-row>
</ux-placeholder>
<!-- #endif -->
<!-- #ifdef MP -->
<ux-placeholder :height="200">
<ux-row justify="center" align="center" style="height: 100%;">
<ux-text prefix-icon="soapbubble-filled" text="小程序和Web不支持"></ux-text>
</ux-row>
</ux-placeholder>
<!-- #endif -->
</ux-scroll>
</ux-page>
</template>
<template>
<ux-page :stack="showDoc">
<ux-navbar :title="title" :bold="true">
<template v-slot:right>
<!-- #ifndef MP -->
<ux-button theme="text" icon="/static/tip.png" :icon-size="22" @click="onDoc()"></ux-button>
<!-- #endif -->
</template>
</ux-navbar>
<ux-scroll>
<ux-card direction="column" icon="flag-filled" title="原生拖拽组件" :bold="true">
<ux-text text="支持宫格和列表布局,内置照片墙上传场景,支持多选、编辑、滑动删除、禁用等功能"></ux-text>
</ux-card>
<!-- #ifndef MP -->
<ux-card direction="column" icon="arrowright" title="照片墙" :bold="true">
<ux-text text="内置照片墙上传场景,支持多选、删除" :mb="15"></ux-text>
<view style="height: 180px;">
<ux-movable ref="movableGridRef"
layout="grid"
:spacing="6"
:aspect-ratio="0.75"
:column="column"
:multiple="multiple"
:disabled="disabled"
@init="initGrid"
@change="gridChange">
</ux-movable>
</view>
<ux-row>
<ux-button text="列数 + 1" @click="onColumn(1)"></ux-button>
<ux-button text="列数 - 1" :ml="10" @click="onColumn(-1)"></ux-button>
<ux-button text="多选" :ml="10" @click="onMultiple()"></ux-button>
</ux-row>
</ux-card>
<ux-card direction="column" icon="arrowright" title="列表式" :bold="true">
<ux-text text="支持滑动删除" :mb="15"></ux-text>
<view style="height: 180px;">
<ux-movable ref="movableListRef"
layout="list"
:swipe="swipe"
:disabled="disabled"
@init="initList"
@change="listChange">
</ux-movable>
</view>
</ux-card>
<ux-placeholder :height="300">
<ux-row justify="center" align="center" style="height: 100%;">
<ux-text prefix-icon="soapbubble-filled" text="真的没有了~"></ux-text>
</ux-row>
</ux-placeholder>
<!-- #endif -->
<!-- #ifdef MP -->
<ux-placeholder :height="200">
<ux-row justify="center" align="center" style="height: 100%;">
<ux-text prefix-icon="soapbubble-filled" text="小程序和Web不支持"></ux-text>
</ux-row>
</ux-placeholder>
<!-- #endif -->
</ux-scroll>
</ux-page>
</template>
ts
<script setup>
import { UxMovableItem, UxMovablePlaceholder } from '@/uni_modules/ux-movable'
import * as plus from '@/uni_modules/ux-plus'
const column = ref(4)
const disabled = ref(false)
const multiple = ref(false)
const swipe = ref(true)
const movableGridRef = ref<ComponentPublicInstance | null>(null)
const movableListRef = ref<ComponentPublicInstance | null>(null)
const gridDatas = ref<UxMovableItem[]>([])
const listDatas = ref<UxMovableItem[]>([])
function getData(i: number, url: string): UxMovableItem {
return {
id: i,
index: i + 1,
label: 'Label' + i,
url: url,
radius: 6,
rightTop: {
url: '/static/close.png',
width: 20,
height: 20,
margin: 4,
click: (data: UxMovableItem) => {
movableGridRef.value?.$callMethod('delData', data)
uni.showToast({
title: '删除成功',
icon: 'none'
})
}
},
selected: {
checked: false,
selectedUrl: '/static/checked.png',
unselectedUrl: '/static/unchecked.png',
width: 20,
height: 20,
margin: 4,
},
loading: {
show: true,
progress: 0,
waitingLabel: '等待上传...'
},
border: {
type: 'solid',
width: 1,
color: '#f0f0f0'
},
click: (data: UxMovableItem) => {
// 不要直接用data,ios事件会丢失
let index = gridDatas.value.findIndex(e => e.id == data.id)
if(multiple.value) {
gridDatas.value[index].selected!.checked = !gridDatas.value[index].selected!.checked
movableGridRef.value?.$callMethod('selectData', gridDatas.value[index])
} else {
uni.previewImage({
urls: gridDatas.value.filter((e): boolean => (e.url ?? '') != '').map((e): string => e.url!),
current: gridDatas.value[index].url
})
}
}
} as UxMovableItem
}
function startUpload() {
// 上传时不能滑动
disabled.value = true
let index = 0
let fun = () => {}
fun = () => {
if(index >= gridDatas.value.length) {
disabled.value = false
return
}
let e = gridDatas.value[index]
if(e.upload != null) {
// 忽略上传按钮
index++
fun()
return
}
if(e.loading == null) {
// 参数未配置
index++
fun()
return
}
if(e.loading!.show == false) {
// 已上传
index++
fun()
return
}
e.loading!.show = true
index++
let up = () => {}
up = () => {
if(e.loading!.progress >= 100) {
e.loading!.show = false
movableGridRef.value?.$callMethod('upload', e)
fun()
return
}
e.loading!.progress += 5
e.loading!.progress = Math.min(100, e.loading!.progress)
requestAnimationFrame(() => {
movableGridRef.value?.$callMethod('upload', e)
up()
})
}
up()
}
fun()
}
function onUpload(paths: string[]) {
console.log(paths);
// 新增等待上传
for (let i = 0; i < paths.length; i++) {
gridDatas.value.push(getData(gridDatas.value.length + i, paths[i]))
}
movableGridRef.value?.$callMethod('setData', gridDatas.value.map((e: UxMovableItem) => e))
startUpload()
}
function getGridDatas() {
let urls = [
'https://q9.itc.cn/q_70/images03/20240607/100a8219bc9044e5a712e464525577c3.jpeg',
'https://iknow-pic.cdn.bcebos.com/8cb1cb1349540923d860dce08058d109b3de4926',
'https://iknow-pic.cdn.bcebos.com/0823dd54564e9258d93d39708e82d158ccbf4e26',
'https://iknow-pic.cdn.bcebos.com/18d8bc3eb13533fa91bf2cb0bad3fd1f41345b26',
'https://iknow-pic.cdn.bcebos.com/960a304e251f95cacdfc2d28db177f3e67095226',
]
let items = [{
id: -1,
index: 0,
radius: 6,
upload: {
url: '/static/add.png',
backgroundColor: '#f0f0f0',
},
click: (data: UxMovableItem) => {
uni.chooseImage({
count: 30,
success: (res) => {
onUpload(res.tempFilePaths)
}
})
}
}] as UxMovableItem[]
for (var i = 0; i < urls.length; i++) {
items.push(getData(i, urls[i]))
}
return items
}
function getListDatas() {
let urls = [
'https://q9.itc.cn/q_70/images03/20240607/100a8219bc9044e5a712e464525577c3.jpeg',
'https://iknow-pic.cdn.bcebos.com/8cb1cb1349540923d860dce08058d109b3de4926',
'https://iknow-pic.cdn.bcebos.com/0823dd54564e9258d93d39708e82d158ccbf4e26',
'https://iknow-pic.cdn.bcebos.com/18d8bc3eb13533fa91bf2cb0bad3fd1f41345b26',
'https://iknow-pic.cdn.bcebos.com/960a304e251f95cacdfc2d28db177f3e67095226',
]
let items = [] as UxMovableItem[]
for (var i = 0; i < urls.length; i++) {
items.push({
id: i,
index: i + 1,
label: 'Label' + i,
url: urls[i],
radius: 6,
border: {
type: 'solid',
width: 1,
color: '#f0f0f0'
}
} as UxMovableItem)
}
return items
}
function initGrid() {
gridDatas.value = getGridDatas()
gridDatas.value.forEach((e, i) => {
e.index = i
})
movableGridRef.value?.$callMethod('setData', gridDatas.value.map((e: UxMovableItem) => e))
startUpload()
}
function initList() {
listDatas.value = getListDatas()
listDatas.value.forEach((e, i) => {
e.index = i
})
movableListRef.value?.$callMethod('setData', listDatas.value.map((e: UxMovableItem) => e))
}
function gridChange(datas: UxMovableItem[]) {
gridDatas.value = datas
console.log(gridDatas.value);
}
function listChange(datas: UxMovableItem[]) {
listDatas.value = datas
console.log(listDatas.value);
}
function onColumn(n: number) {
column.value += n
column.value = Math.max(2, column.value)
column.value = Math.min(5, column.value)
}
function onMultiple() {
multiple.value = !multiple.value
}
function onSwipe() {
swipe.value= !swipe.value
}
function onDisabled() {
disabled.value = !disabled.value
}
const showDoc = ref(false)
function onDoc() {
plus.openWeb({
title: '在线文档',
url: 'https://www.uxframe.cn/component/blur.html',
// blur: 1,
success: () => {
showDoc.value = true
},
complete: () => {
showDoc.value = false
}
})
}
const title = ref('')
onLoad((e: OnLoadOptions) => {
title.value = e['title'] ?? ''
})
</script>
<script setup>
import { UxMovableItem, UxMovablePlaceholder } from '@/uni_modules/ux-movable'
import * as plus from '@/uni_modules/ux-plus'
const column = ref(4)
const disabled = ref(false)
const multiple = ref(false)
const swipe = ref(true)
const movableGridRef = ref<ComponentPublicInstance | null>(null)
const movableListRef = ref<ComponentPublicInstance | null>(null)
const gridDatas = ref<UxMovableItem[]>([])
const listDatas = ref<UxMovableItem[]>([])
function getData(i: number, url: string): UxMovableItem {
return {
id: i,
index: i + 1,
label: 'Label' + i,
url: url,
radius: 6,
rightTop: {
url: '/static/close.png',
width: 20,
height: 20,
margin: 4,
click: (data: UxMovableItem) => {
movableGridRef.value?.$callMethod('delData', data)
uni.showToast({
title: '删除成功',
icon: 'none'
})
}
},
selected: {
checked: false,
selectedUrl: '/static/checked.png',
unselectedUrl: '/static/unchecked.png',
width: 20,
height: 20,
margin: 4,
},
loading: {
show: true,
progress: 0,
waitingLabel: '等待上传...'
},
border: {
type: 'solid',
width: 1,
color: '#f0f0f0'
},
click: (data: UxMovableItem) => {
// 不要直接用data,ios事件会丢失
let index = gridDatas.value.findIndex(e => e.id == data.id)
if(multiple.value) {
gridDatas.value[index].selected!.checked = !gridDatas.value[index].selected!.checked
movableGridRef.value?.$callMethod('selectData', gridDatas.value[index])
} else {
uni.previewImage({
urls: gridDatas.value.filter((e): boolean => (e.url ?? '') != '').map((e): string => e.url!),
current: gridDatas.value[index].url
})
}
}
} as UxMovableItem
}
function startUpload() {
// 上传时不能滑动
disabled.value = true
let index = 0
let fun = () => {}
fun = () => {
if(index >= gridDatas.value.length) {
disabled.value = false
return
}
let e = gridDatas.value[index]
if(e.upload != null) {
// 忽略上传按钮
index++
fun()
return
}
if(e.loading == null) {
// 参数未配置
index++
fun()
return
}
if(e.loading!.show == false) {
// 已上传
index++
fun()
return
}
e.loading!.show = true
index++
let up = () => {}
up = () => {
if(e.loading!.progress >= 100) {
e.loading!.show = false
movableGridRef.value?.$callMethod('upload', e)
fun()
return
}
e.loading!.progress += 5
e.loading!.progress = Math.min(100, e.loading!.progress)
requestAnimationFrame(() => {
movableGridRef.value?.$callMethod('upload', e)
up()
})
}
up()
}
fun()
}
function onUpload(paths: string[]) {
console.log(paths);
// 新增等待上传
for (let i = 0; i < paths.length; i++) {
gridDatas.value.push(getData(gridDatas.value.length + i, paths[i]))
}
movableGridRef.value?.$callMethod('setData', gridDatas.value.map((e: UxMovableItem) => e))
startUpload()
}
function getGridDatas() {
let urls = [
'https://q9.itc.cn/q_70/images03/20240607/100a8219bc9044e5a712e464525577c3.jpeg',
'https://iknow-pic.cdn.bcebos.com/8cb1cb1349540923d860dce08058d109b3de4926',
'https://iknow-pic.cdn.bcebos.com/0823dd54564e9258d93d39708e82d158ccbf4e26',
'https://iknow-pic.cdn.bcebos.com/18d8bc3eb13533fa91bf2cb0bad3fd1f41345b26',
'https://iknow-pic.cdn.bcebos.com/960a304e251f95cacdfc2d28db177f3e67095226',
]
let items = [{
id: -1,
index: 0,
radius: 6,
upload: {
url: '/static/add.png',
backgroundColor: '#f0f0f0',
},
click: (data: UxMovableItem) => {
uni.chooseImage({
count: 30,
success: (res) => {
onUpload(res.tempFilePaths)
}
})
}
}] as UxMovableItem[]
for (var i = 0; i < urls.length; i++) {
items.push(getData(i, urls[i]))
}
return items
}
function getListDatas() {
let urls = [
'https://q9.itc.cn/q_70/images03/20240607/100a8219bc9044e5a712e464525577c3.jpeg',
'https://iknow-pic.cdn.bcebos.com/8cb1cb1349540923d860dce08058d109b3de4926',
'https://iknow-pic.cdn.bcebos.com/0823dd54564e9258d93d39708e82d158ccbf4e26',
'https://iknow-pic.cdn.bcebos.com/18d8bc3eb13533fa91bf2cb0bad3fd1f41345b26',
'https://iknow-pic.cdn.bcebos.com/960a304e251f95cacdfc2d28db177f3e67095226',
]
let items = [] as UxMovableItem[]
for (var i = 0; i < urls.length; i++) {
items.push({
id: i,
index: i + 1,
label: 'Label' + i,
url: urls[i],
radius: 6,
border: {
type: 'solid',
width: 1,
color: '#f0f0f0'
}
} as UxMovableItem)
}
return items
}
function initGrid() {
gridDatas.value = getGridDatas()
gridDatas.value.forEach((e, i) => {
e.index = i
})
movableGridRef.value?.$callMethod('setData', gridDatas.value.map((e: UxMovableItem) => e))
startUpload()
}
function initList() {
listDatas.value = getListDatas()
listDatas.value.forEach((e, i) => {
e.index = i
})
movableListRef.value?.$callMethod('setData', listDatas.value.map((e: UxMovableItem) => e))
}
function gridChange(datas: UxMovableItem[]) {
gridDatas.value = datas
console.log(gridDatas.value);
}
function listChange(datas: UxMovableItem[]) {
listDatas.value = datas
console.log(listDatas.value);
}
function onColumn(n: number) {
column.value += n
column.value = Math.max(2, column.value)
column.value = Math.min(5, column.value)
}
function onMultiple() {
multiple.value = !multiple.value
}
function onSwipe() {
swipe.value= !swipe.value
}
function onDisabled() {
disabled.value = !disabled.value
}
const showDoc = ref(false)
function onDoc() {
plus.openWeb({
title: '在线文档',
url: 'https://www.uxframe.cn/component/blur.html',
// blur: 1,
success: () => {
showDoc.value = true
},
complete: () => {
showDoc.value = false
}
})
}
const title = ref('')
onLoad((e: OnLoadOptions) => {
title.value = e['title'] ?? ''
})
</script>
css
<style lang="scss">
.blur {
position: absolute;
top: 200px;
width: 100%;
height: 120px;
}
</style>
<style lang="scss">
.blur {
position: absolute;
top: 200px;
width: 100%;
height: 120px;
}
</style>