597 行
14 KiB
Plaintext
597 行
14 KiB
Plaintext
<template>
|
||
<!-- #ifdef APP -->
|
||
<scroll-view style="flex: 1; padding: 15px;">
|
||
<!-- #endif -->
|
||
<!-- #ifndef APP -->
|
||
<view style="padding: 15px;">
|
||
<!-- #endif -->
|
||
<text class="page-title">drag-cell 拖拽排序组件</text>
|
||
|
||
<view class="section">
|
||
<text class="section-title">示例 1:标签拖拽排序</text>
|
||
<text class="section-desc">长按标签后可拖拽排序,点击标签可拿到 item 和 index</text>
|
||
<uni-drag-cell v-model="data.tagList" @change="onTagChange">
|
||
<template #default="{ item, index }">
|
||
<view class="tag-slot">
|
||
<view class="tag-item" @click="clickItem(item as string, index as number)">
|
||
<text class="tag-text">{{ item as string }}</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
</uni-drag-cell>
|
||
<text class="result-text">当前顺序:{{ data.tagList.join(', ') }}</text>
|
||
</view>
|
||
|
||
<view class="section">
|
||
<text class="section-title">示例 2:宫格拖拽排序(3 列)</text>
|
||
<text class="section-desc">长按宫格项后可拖拽排序</text>
|
||
<uni-drag-cell v-model="data.gridList3" :column="3" @change="onGridChange">
|
||
<template #default="{ item }">
|
||
<view class="grid-slot">
|
||
<view class="grid-item">
|
||
<text class="grid-text">{{ (item as UTSJSONObject)['name'] }}</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
</uni-drag-cell>
|
||
<text class="result-text">当前顺序:{{ getGridNames(data.gridList3) }}</text>
|
||
</view>
|
||
|
||
<view class="section">
|
||
<text class="section-title">示例 3:列表拖拽排序(1 列)</text>
|
||
<text class="section-desc">长按列表项后可拖拽排序</text>
|
||
<uni-drag-cell v-model="data.gridList4" :column="1" @change="onGridChange">
|
||
<template #default="{ item }">
|
||
<view class="grid-slot-small">
|
||
<view class="grid-item-small">
|
||
<text class="grid-text-small">{{ (item as UTSJSONObject)['name'] }}</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
</uni-drag-cell>
|
||
<text class="result-text">当前顺序:{{ getGridNames(data.gridList4) }}</text>
|
||
</view>
|
||
|
||
<view class="section">
|
||
<text class="section-title">示例 4:可新增并删除的标签</text>
|
||
<text class="section-desc">长按标签可排序,拖到底部可删除,点击 + 可输入并新增标签</text>
|
||
<uni-drag-cell v-model="data.deleteList" :show-close="true" :show-append="true" @change="onDeleteChange">
|
||
<template #default="{ item }">
|
||
<view class="tag-slot">
|
||
<view class="tag-item-delete">
|
||
<text class="tag-text">{{ item as string }}</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
<template #append>
|
||
<view class="tag-slot">
|
||
<view class="tag-item-add" @click="promptAddDeleteTag">
|
||
<text class="tag-plus-text">+</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
</uni-drag-cell>
|
||
<text class="result-text">当前顺序:{{ data.deleteList.join(', ') }}</text>
|
||
<text class="result-text">剩余:{{ data.deleteList.length }} 项</text>
|
||
</view>
|
||
|
||
<view class="section">
|
||
<text class="section-title">示例 5:带删除功能的图标宫格</text>
|
||
<text class="section-desc">长按后可拖动图标宫格项,并支持拖到底部删除</text>
|
||
<uni-drag-cell v-model="data.iconGridList" :column="3" :show-close="true" @change="onIconGridChange">
|
||
<template #default="{ item }">
|
||
<view class="icon-grid-slot">
|
||
<view class="icon-grid-item">
|
||
<view class="icon-box">
|
||
<text class="icon-emoji">{{ (item as UTSJSONObject)['icon'] }}</text>
|
||
</view>
|
||
<text class="icon-label">{{ (item as UTSJSONObject)['label'] }}</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
</uni-drag-cell>
|
||
<text class="result-text">当前顺序:{{ getIconNames(data.iconGridList) }}</text>
|
||
</view>
|
||
|
||
<view class="section">
|
||
<text class="section-title">示例 6:九宫格图片选择</text>
|
||
<text class="section-desc">点击 + 选择图片,支持拖拽排序,也支持拖到底部删除</text>
|
||
<uni-drag-cell v-model="data.imageList" :column="3" :show-close="true"
|
||
:show-append="data.imageList.length < maxImageCount" @change="onImageChange">
|
||
<template #default="{ item, index }">
|
||
<view class="image-grid-slot">
|
||
<view class="image-grid-item" @click="previewImages(index as number)">
|
||
<image class="image-grid-img" :src="item" mode="aspectFill"></image>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
<template #append>
|
||
<view class="image-grid-slot">
|
||
<view class="image-grid-plus" @click="chooseImages">
|
||
<text class="image-grid-plus-icon">+</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
</uni-drag-cell>
|
||
<text class="result-text">已选择:{{ data.imageList.length }}/{{ maxImageCount }}</text>
|
||
</view>
|
||
|
||
<view class="section">
|
||
<text class="section-title">示例 7:手柄拖拽排序</text>
|
||
<text class="section-desc">开启手柄模式后,不需要长按,只能拖动右侧手柄进行排序</text>
|
||
<uni-drag-cell v-model="data.handleList" :column="1" :handle-mode="true" @change="onHandleListChange">
|
||
<template #default="{ item, index }">
|
||
<view class="handle-demo-slot">
|
||
<view class="handle-demo-item">
|
||
<text class="handle-demo-index">{{ (index as number) + 1 }}</text>
|
||
<text class="handle-demo-text">{{ item as string }}</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
<template #handle="{ item }">
|
||
<view class="handle-demo-grip">
|
||
<text class="handle-demo-grip-text">≡</text>
|
||
</view>
|
||
</template>
|
||
</uni-drag-cell>
|
||
<text class="result-text">当前顺序:{{ data.handleList.join(', ') }}</text>
|
||
</view>
|
||
|
||
<view style="height: 50px;"></view>
|
||
<!-- #ifdef APP -->
|
||
</scroll-view>
|
||
<!-- #endif -->
|
||
<!-- #ifndef APP -->
|
||
</view>
|
||
<!-- #endif -->
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
const maxImageCount = 9
|
||
|
||
type Data = {
|
||
tagList: string[],
|
||
gridList3: UTSJSONObject[],
|
||
gridList4: UTSJSONObject[],
|
||
deleteList: string[],
|
||
iconGridList: UTSJSONObject[],
|
||
imageList: string[],
|
||
handleList: string[]
|
||
}
|
||
|
||
const data = reactive<Data>({
|
||
tagList: ['标签1', '标签2', '标签3', '标签4'],
|
||
gridList3: [
|
||
{ name: '宫格1' },
|
||
{ name: '宫格2' },
|
||
{ name: '宫格3' },
|
||
{ name: '宫格4' },
|
||
{ name: '宫格5' },
|
||
{ name: '宫格6' },
|
||
{ name: '宫格7' },
|
||
{ name: '宫格8' },
|
||
{ name: '宫格9' }
|
||
],
|
||
gridList4: [
|
||
{ name: '1' },
|
||
{ name: '2' },
|
||
{ name: '3' },
|
||
{ name: '4' },
|
||
{ name: '5' }
|
||
],
|
||
deleteList: ['标签A', '标签B', '标签C', '标签D', '标签E'],
|
||
iconGridList: [
|
||
{ icon: 'H', label: '首页' },
|
||
{ icon: 'M', label: '消息' },
|
||
{ icon: 'P', label: '我的' },
|
||
{ icon: 'S', label: '设置' },
|
||
{ icon: 'A', label: '相册' },
|
||
{ icon: 'Y', label: '音乐' }
|
||
],
|
||
imageList: [],
|
||
handleList: ['微信支付', '支付宝', 'Apple Pay']
|
||
})
|
||
|
||
const clickItem = (item : string, index : number) => {
|
||
uni.showToast({
|
||
title: '点击了第 ' + (index + 1) + ' 项:' + item,
|
||
icon: 'none'
|
||
})
|
||
}
|
||
|
||
const onTagChange = (list : string[]) => {
|
||
console.log('tag list changed:', list)
|
||
}
|
||
|
||
const onGridChange = (list : UTSJSONObject[]) => {
|
||
console.log('grid list changed:', list)
|
||
}
|
||
|
||
const onDeleteChange = (list : string[]) => {
|
||
console.log('delete list changed:', list)
|
||
}
|
||
|
||
const onIconGridChange = (list : UTSJSONObject[]) => {
|
||
console.log('icon grid list changed:', list)
|
||
}
|
||
|
||
const onImageChange = (list : string[]) => {
|
||
console.log('image list changed:', list)
|
||
}
|
||
|
||
const onHandleListChange = (list : string[]) => {
|
||
console.log('handle list changed:', list)
|
||
}
|
||
|
||
const promptAddDeleteTag = () => {
|
||
uni.showModal({
|
||
title: '新增标签',
|
||
editable: true,
|
||
placeholderText: '请输入标签内容',
|
||
confirmText: '确定',
|
||
cancelText: '取消',
|
||
success: (res) => {
|
||
if (res.confirm != true) {
|
||
return
|
||
}
|
||
const value = res.content != null ? res.content.trim() : ''
|
||
if (value.length == 0) {
|
||
uni.showToast({
|
||
title: '标签内容不能为空',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
const nextList = data.deleteList.slice()
|
||
nextList.push(value)
|
||
data.deleteList = nextList
|
||
onDeleteChange(data.deleteList)
|
||
}
|
||
})
|
||
}
|
||
|
||
const extractChooseImagePaths = (res : ChooseImageSuccess) : string[] => {
|
||
const paths : string[] = []
|
||
for (let i = 0; i < res.tempFilePaths.length; i++) {
|
||
const path = res.tempFilePaths[i]
|
||
if (path.length > 0) {
|
||
paths.push(path)
|
||
}
|
||
}
|
||
if (paths.length > 0) {
|
||
return paths
|
||
}
|
||
for (let i = 0; i < res.tempFiles.length; i++) {
|
||
const path = res.tempFiles[i].path
|
||
if (path.length > 0) {
|
||
paths.push(path)
|
||
}
|
||
}
|
||
return paths
|
||
}
|
||
|
||
const chooseImages = () => {
|
||
const remainCount = maxImageCount - data.imageList.length
|
||
if (remainCount <= 0) {
|
||
return
|
||
}
|
||
uni.chooseImage({
|
||
count: remainCount,
|
||
success: (res : ChooseImageSuccess) => {
|
||
const selectedPaths = extractChooseImagePaths(res)
|
||
if (selectedPaths.length == 0) {
|
||
return
|
||
}
|
||
const nextList = data.imageList.slice()
|
||
for (let i = 0; i < selectedPaths.length; i++) {
|
||
if (nextList.length >= maxImageCount) {
|
||
break
|
||
}
|
||
nextList.push(selectedPaths[i])
|
||
}
|
||
data.imageList = nextList
|
||
onImageChange(data.imageList)
|
||
}
|
||
})
|
||
}
|
||
|
||
const previewImages = (index : number) => {
|
||
if (index < 0 || index >= data.imageList.length) {
|
||
return
|
||
}
|
||
uni.previewImage({
|
||
urls: data.imageList,
|
||
current: data.imageList[index]
|
||
})
|
||
}
|
||
|
||
const getGridNames = (list : UTSJSONObject[]) : string => {
|
||
const names : string[] = []
|
||
for (let i = 0; i < list.length; i++) {
|
||
const value = list[i]['name']
|
||
if (value != null) {
|
||
names.push(value as string)
|
||
}
|
||
}
|
||
return names.join(', ')
|
||
}
|
||
|
||
const getIconNames = (list : UTSJSONObject[]) : string => {
|
||
const names : string[] = []
|
||
for (let i = 0; i < list.length; i++) {
|
||
const value = list[i]['label']
|
||
if (value != null) {
|
||
names.push(value as string)
|
||
}
|
||
}
|
||
return names.join(', ')
|
||
}
|
||
|
||
defineExpose({
|
||
data,
|
||
clickItem,
|
||
promptAddDeleteTag,
|
||
chooseImages,
|
||
previewImages
|
||
})
|
||
</script>
|
||
|
||
<style>
|
||
.page-title {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: var(--text-color, #333333);
|
||
margin-bottom: 20px;
|
||
text-align: center;
|
||
}
|
||
|
||
.section {
|
||
margin-bottom: 30px;
|
||
background-color: var(--list-background-color, #ffffff);
|
||
border-radius: 8px;
|
||
padding: 15px;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #007aff;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.section-desc {
|
||
font-size: 12px;
|
||
color: var(--text-color, #333333);
|
||
opacity: 0.7;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.tag-slot {
|
||
padding-right: 10px;
|
||
padding-bottom: 10px;
|
||
}
|
||
|
||
.tag-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
background-color: #007aff;
|
||
border-radius: 4px;
|
||
padding: 8px 16px;
|
||
}
|
||
|
||
.tag-text {
|
||
color: #ffffff;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.tag-item-delete {
|
||
display: flex;
|
||
flex-direction: column;
|
||
background-color: #ff6b6b;
|
||
border-radius: 4px;
|
||
padding: 4px 16px;
|
||
height: 32px;
|
||
}
|
||
|
||
.tag-item-add {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #fff5f5;
|
||
border-width: 1px;
|
||
border-style: dashed;
|
||
border-color: #ff6b6b;
|
||
border-radius: 4px;
|
||
padding: 4px 16px;
|
||
height: 32px;
|
||
}
|
||
|
||
.tag-plus-text {
|
||
color: #ff6b6b;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.grid-slot {
|
||
width: 100%;
|
||
padding: 5px;
|
||
}
|
||
|
||
.grid-item {
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #28a745;
|
||
border-radius: 8px;
|
||
height: 80px;
|
||
}
|
||
|
||
.grid-text {
|
||
color: #ffffff;
|
||
font-size: 14px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.grid-slot-small {
|
||
width: 90%;
|
||
/* 避免scale放大1.12倍时被裁剪*/
|
||
padding: 3px;
|
||
align-self: center;
|
||
}
|
||
|
||
.grid-item-small {
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #ffc107;
|
||
border-radius: 8px;
|
||
height: 60px;
|
||
}
|
||
|
||
.grid-text-small {
|
||
color: #333333;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.icon-grid-slot {
|
||
width: 100%;
|
||
padding: 5px;
|
||
}
|
||
|
||
.icon-grid-item {
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #f8f9fa;
|
||
border-radius: 8px;
|
||
padding: 10px;
|
||
}
|
||
|
||
.icon-box {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 50px;
|
||
height: 50px;
|
||
background-color: #e9ecef;
|
||
border-radius: 25px;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.icon-emoji {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
}
|
||
|
||
.icon-label {
|
||
font-size: 12px;
|
||
color: #333333;
|
||
}
|
||
|
||
.image-grid-slot {
|
||
width: 100%;
|
||
padding: 5px;
|
||
}
|
||
|
||
.image-grid-item {
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 104px;
|
||
background-color: #f5f5f5;
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.image-grid-img {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.image-grid-plus {
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 104px;
|
||
background-color: #fafafa;
|
||
border-width: 1px;
|
||
border-style: dashed;
|
||
border-color: #d9d9d9;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.image-grid-plus-icon {
|
||
font-size: 36px;
|
||
font-weight: bold;
|
||
color: #999999;
|
||
}
|
||
|
||
.handle-demo-slot {
|
||
width: 100%;
|
||
padding: 4px 0;
|
||
}
|
||
|
||
.handle-demo-item {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
background-color: #f7f8fa;
|
||
border-width: 1px;
|
||
border-style: solid;
|
||
border-color: #e5e6eb;
|
||
border-radius: 10px;
|
||
padding: 16px 52px 16px 14px;
|
||
}
|
||
|
||
.handle-demo-index {
|
||
width: 24px;
|
||
margin-right: 12px;
|
||
color: #007aff;
|
||
font-size: 15px;
|
||
font-weight: bold;
|
||
text-align: center;
|
||
}
|
||
|
||
.handle-demo-text {
|
||
color: #333333;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.handle-demo-grip {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 40px;
|
||
height: 56px;
|
||
}
|
||
|
||
.handle-demo-grip-text {
|
||
color: #999999;
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.result-text {
|
||
font-size: 12px;
|
||
color: var(--text-color, #333333);
|
||
opacity: 0.7;
|
||
margin-top: 10px;
|
||
}
|
||
</style>
|