Leon 1 rok pred
rodič
commit
845a87e9e4
2 zmenil súbory, kde vykonal 343 pridanie a 4 odobranie
  1. 85 0
      src/_moment/pages/message.scss
  2. 258 4
      src/_moment/pages/message.tsx

+ 85 - 0
src/_moment/pages/message.scss

@@ -0,0 +1,85 @@
+.grid-wrapper {
+    width: 100%;
+    position: relative;
+    background-color: #f5f5f5;
+  }
+  
+  .grid-container {
+    width: 100%;
+    height: 100%;
+    position: relative;
+    overflow: hidden;
+  }
+  
+  .grid-item {
+    width: 33.33%;
+    height: 33.33vw;
+    position: absolute;
+    will-change: transform;
+    
+    &.active {
+      z-index: 100;
+      
+      .grid-image {
+        transform: scale(1.05);
+        opacity: 0.8;
+      }
+    }
+  }
+  
+  .grid-image {
+    width: 100%;
+    height: 100%;
+    padding: 4px;
+    box-sizing: border-box;
+    transition: all 0.2s ease;
+  }
+  
+  .delete-btn {
+    position: absolute;
+    right: 4px;
+    top: 4px;
+    width: 20px;
+    height: 20px;
+    background-color: rgba(0, 0, 0, 0.5);
+    border-radius: 50%;
+    z-index: 1;
+    
+    &::before,
+    &::after {
+      content: '';
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      width: 12px;
+      height: 2px;
+      background-color: #fff;
+      transform: translate(-50%, -50%) rotate(45deg);
+    }
+    
+    &::after {
+      transform: translate(-50%, -50%) rotate(-45deg);
+    }
+  }
+  
+  .upload-item {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background-color: #fff;
+    border: 1px dashed #ddd;
+    box-sizing: border-box;
+    position: absolute;
+    margin: 4px;
+    width: calc(33.33% - 8px);
+    height: calc(33.33vw - 8px);
+    will-change: transform;
+  }
+  
+  .upload-icon {
+    font-size: 32px;
+    color: #999;
+    font-weight: lighter;
+  }
+  
+  

+ 258 - 4
src/_moment/pages/message.tsx

@@ -1,5 +1,259 @@
-import { View } from "@tarojs/components";
+import { useState, useRef, useEffect } from 'react'
+import { View, MovableArea, MovableView, Image } from '@tarojs/components'
+import { createSelectorQuery, chooseImage, showToast } from '@tarojs/taro'
+import './message.scss'
+
+interface ImageItem {
+  id: string
+  url: string
+}
+
+interface Position {
+  x: number
+  y: number
+}
+
+interface Props {
+  maxCount?: number
+  value?: ImageItem[]
+  onChange?: (images: ImageItem[]) => void
+}
+
+export default function SortableGrid({ 
+  maxCount = 9,
+  value = [],
+  onChange
+}: Props) {
+  const [images, setImages] = useState<ImageItem[]>(value)
+  const [currentItem, setCurrentItem] = useState<ImageItem | null>(null)
+  const [positions, setPositions] = useState<{[key: string]: Position}>({})
+  const [itemSize, setItemSize] = useState({ width: 0, height: 0 })
+  const [isDragging, setIsDragging] = useState(false)
+  const [draggedIndex, setDraggedIndex] = useState<number>(-1)
+  const [targetIndex, setTargetIndex] = useState<number>(-1)
+  const gridRef = useRef<any>(null)
+
+  // 更新图片列表
+  const updateImages = (newImages: ImageItem[]) => {
+    setImages(newImages)
+    onChange?.(newImages)
+  }
+
+  // 处理图片上传
+  const handleUpload = async () => {
+    if (images.length >= maxCount) {
+      showToast({
+        title: `最多只能上传${maxCount}张图片`,
+        icon: 'none'
+      })
+      return
+    }
+
+    try {
+      const res = await chooseImage({
+        count: maxCount - images.length,
+        sizeType: ['compressed'],
+        sourceType: ['album', 'camera']
+      })
+
+      const newImages = [
+        ...images,
+        ...res.tempFilePaths.map((url, index) => ({
+          id: Date.now() + index + '',
+          url
+        }))
+      ]
+
+      updateImages(newImages)
+    } catch (error) {
+      console.error('选择图片失败:', error)
+    }
+  }
+
+  // 删除图片
+  const handleDelete = (item: ImageItem) => {
+    const newImages = images.filter(img => img.id !== item.id)
+    updateImages(newImages)
+  }
+
+  // 计算网格项尺寸和位置
+  const calculateLayout = () => {
+    return new Promise<void>((resolve) => {
+      const query = createSelectorQuery()
+      query.select('.grid-container').boundingClientRect((rect: any) => {
+        if (rect) {
+          const width = rect.width / 3
+          setItemSize({ width, height: width })
+          
+          const newPositions: {[key: string]: Position} = {}
+          const totalItems = images.length + (images.length < maxCount ? 1 : 0)
+          
+          for (let i = 0; i < totalItems; i++) {
+            const row = Math.floor(i / 3)
+            const col = i % 3
+            newPositions[i] = {
+              x: col * width,
+              y: row * width
+            }
+          }
+          
+          setPositions(newPositions)
+          resolve()
+        }
+      }).exec()
+    })
+  }
+
+  // 获取目标索引
+  const getTargetIndex = (x: number, y: number): number => {
+    if (!itemSize.width) return -1
+    
+    const col = Math.floor((x + itemSize.width / 2) / itemSize.width)
+    const row = Math.floor((y + itemSize.height / 2) / itemSize.height)
+    
+    if (col < 0 || col >= 3 || row < 0) return -1
+    const index = row * 3 + col
+    return index >= images.length ? -1 : index
+  }
+
+  // 长按开始拖动
+  const handleLongPress = (index: number) => {
+    setIsDragging(true)
+    setDraggedIndex(index)
+    setCurrentItem(images[index])
+  }
+
+  // 拖动中
+  const handleMove = (e: any) => {
+    if (!isDragging || draggedIndex === -1) return
+
+    const { x, y } = e.detail
+    const newTargetIndex = getTargetIndex(x, y)
+    
+    if (newTargetIndex !== -1 && newTargetIndex !== draggedIndex && newTargetIndex < images.length) {
+      setTargetIndex(newTargetIndex)
+    } else {
+      setTargetIndex(-1)
+    }
+  }
+
+  // 拖动结束
+  const handleTouchEnd = () => {
+    if (isDragging && targetIndex !== -1 && draggedIndex !== -1) {
+      const newImages = [...images]
+      const [draggedItem] = newImages.splice(draggedIndex, 1)
+      newImages.splice(targetIndex, 0, draggedItem)
+      updateImages(newImages)
+    }
+    
+    setIsDragging(false)
+    setDraggedIndex(-1)
+    setTargetIndex(-1)
+    setCurrentItem(null)
+  }
+
+  // 获取元素样式
+  const getItemStyle = (index: number) => {
+    if (!positions[index]) return {}
+    
+    const baseStyle = {
+      transform: `translate3d(${positions[index].x}px, ${positions[index].y}px, 0)`,
+      transition: isDragging ? 'transform 0.3s ease' : 'transform 0.3s ease'
+    }
+
+    if (index === draggedIndex) {
+      return {
+        ...baseStyle,
+        transition: 'none',
+        zIndex: 100
+      }
+    }
+
+    if (targetIndex !== -1) {
+      if (index === targetIndex) {
+        const targetPos = positions[draggedIndex]
+        return {
+          ...baseStyle,
+          transform: `translate3d(${targetPos.x}px, ${targetPos.y}px, 0)`
+        }
+      }
+      
+      if (index > targetIndex && index <= draggedIndex) {
+        const nextPos = positions[index - 1]
+        return {
+          ...baseStyle,
+          transform: `translate3d(${nextPos.x}px, ${nextPos.y}px, 0)`
+        }
+      }
+      
+      if (index < targetIndex && index >= draggedIndex) {
+        const prevPos = positions[index + 1]
+        return {
+          ...baseStyle,
+          transform: `translate3d(${prevPos.x}px, ${prevPos.y}px, 0)`
+        }
+      }
+    }
+
+    return baseStyle
+  }
+
+  // 计算容器高度
+  const getContainerHeight = () => {
+    const totalItems = images.length + (images.length < maxCount ? 1 : 0)
+    const rows = Math.ceil(totalItems / 3)
+    return `${rows * 33.33}vw`
+  }
+
+  useEffect(() => {
+    calculateLayout()
+  }, [images.length])
+
+  return (
+    <View className="grid-wrapper" style={{ height: getContainerHeight() }}>
+      <MovableArea className="grid-container" ref={gridRef}>
+        {images.map((item, index) => (
+          <MovableView
+            key={item.id}
+            className={`grid-item ${draggedIndex === index ? 'active' : ''}`}
+            direction="all"
+            x={positions[index]?.x || 0}
+            y={positions[index]?.y || 0}
+            onLongPress={() => handleLongPress(index)}
+            onChange={handleMove}
+            onTouchEnd={handleTouchEnd}
+            damping={50}
+            friction={2}
+          >
+            <Image
+              className="grid-image"
+              src={item.url}
+              mode="aspectFill"
+            />
+            <View 
+              className="delete-btn"
+              onClick={(e) => {
+                e.stopPropagation()
+                handleDelete(item)
+              }}
+            />
+          </MovableView>
+        ))}
+        
+        {images.length < maxCount && (
+          <View 
+            className="grid-item upload-item" 
+            onClick={handleUpload}
+            style={{
+              transform: `translate3d(${positions[images.length]?.x}px, ${positions[images.length]?.y}px, 0)`,
+              transition: 'transform 0.3s ease'
+            }}
+          >
+            <View className="upload-icon">+</View>
+          </View>
+        )}
+      </MovableArea>
+    </View>
+  )
+}
 
-export default function Message(){
-    return <View></View>
-}