| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- import { useState } from 'react'
- import { View, Text, Image } from '@tarojs/components'
- import Taro from '@tarojs/taro'
- import { cn } from '@/utils/cn'
- import { Button } from '@/components/ui/button'
- import { uploadFromSelect, type UploadResult } from '@/utils/minio'
- interface AvatarUploadProps {
- currentAvatar?: string
- onUploadSuccess?: (result: UploadResult) => void
- onUploadError?: (error: Error) => void
- size?: number
- editable?: boolean
- }
- export function AvatarUpload({
- currentAvatar,
- onUploadSuccess,
- onUploadError,
- size = 96,
- editable = true
- }: AvatarUploadProps) {
- const [uploading, setUploading] = useState(false)
- const [progress, setProgress] = useState(0)
- const handleChooseImage = async () => {
- if (!editable || uploading) return
- try {
- setUploading(true)
- setProgress(0)
- const result = await uploadFromSelect(
- 'avatars',
- {
- sourceType: ['album', 'camera'],
- count: 1
- },
- {
- onProgress: (event) => {
- setProgress(event.progress)
- if (event.stage === 'uploading') {
- Taro.showLoading({
- title: `上传中...${event.progress}%`
- })
- }
- },
- onComplete: () => {
- Taro.hideLoading()
- Taro.showToast({
- title: '上传成功',
- icon: 'success'
- })
- },
- onError: (error) => {
- Taro.hideLoading()
- onUploadError?.(error)
- Taro.showToast({
- title: '上传失败',
- icon: 'none'
- })
- }
- }
- )
- onUploadSuccess?.(result)
- } catch (error) {
- console.error('头像上传失败:', error)
- onUploadError?.(error as Error)
- } finally {
- setUploading(false)
- setProgress(0)
- }
- }
- const avatarSize = size
- const iconSize = Math.floor(size / 4)
- return (
- <View
- className="relative inline-block"
- onClick={handleChooseImage}
- >
- <View
- className={cn(
- "relative overflow-hidden rounded-full",
- "border-4 border-white shadow-lg",
- editable && "cursor-pointer active:scale-95 transition-transform duration-150",
- uploading && "opacity-75"
- )}
- style={{ width: avatarSize, height: avatarSize }}
- >
- <Image
- src={currentAvatar || 'https://images.unsplash.com/photo-1494790108755-2616b612b786?w=160&h=160&fit=crop&crop=face'}
- mode="aspectFill"
- className="w-full h-full"
- />
-
- {uploading && (
- <View className="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center">
- <View className="text-white text-xs">{progress}%</View>
- </View>
- )}
- </View>
- {editable && !uploading && (
- <View
- className={cn(
- "absolute -bottom-1 -right-1",
- "w-8 h-8 bg-blue-500 rounded-full",
- "flex items-center justify-center shadow-md",
- "border-2 border-white"
- )}
- >
- <View className="i-heroicons-camera-20-solid w-4 h-4 text-white" />
- </View>
- )}
- {uploading && (
- <View
- className={cn(
- "absolute -bottom-1 -right-1",
- "w-8 h-8 bg-gray-500 rounded-full",
- "flex items-center justify-center shadow-md",
- "border-2 border-white"
- )}
- >
- <View className="i-heroicons-arrow-path-20-solid w-4 h-4 text-white animate-spin" />
- </View>
- )}
- </View>
- )
- }
|