Files
cunkebao_v3/Cunkebao/app/workspace/group-push/components/message-editor.tsx
2025-04-09 09:31:09 +08:00

231 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState, useRef, type ChangeEvent } from "react"
import { Button } from "@/components/ui/button"
import { Textarea } from "@/components/ui/textarea"
import { Input } from "@/components/ui/input"
import { Image, Video, Link, X } from "lucide-react"
interface MessageEditorProps {
onMessageChange: (message: {
text: string
images: File[]
video: File | null
link: string
}) => void
defaultValues?: {
text: string
images: string[]
video: string
link: string
}
}
export function MessageEditor({ onMessageChange, defaultValues }: MessageEditorProps) {
const [text, setText] = useState(defaultValues?.text || "")
const [images, setImages] = useState<File[]>([])
const [imageUrls, setImageUrls] = useState<string[]>(defaultValues?.images || [])
const [video, setVideo] = useState<File | null>(null)
const [videoUrl, setVideoUrl] = useState<string>(defaultValues?.video || "")
const [link, setLink] = useState(defaultValues?.link || "")
const imageInputRef = useRef<HTMLInputElement>(null)
const videoInputRef = useRef<HTMLInputElement>(null)
const handleTextChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
const newText = e.target.value
if (newText.length <= 800) {
setText(newText)
onMessageChange({
text: newText,
images,
video,
link,
})
}
}
const handleImageUpload = (e: ChangeEvent<HTMLInputElement>) => {
const files = Array.from(e.target.files || [])
if (files.length > 0) {
// 检查文件大小和数量限制
const validFiles = files.filter((file) => file.size <= 20 * 1024 * 1024) // 20MB
const newImages = [...images, ...validFiles].slice(0, 9) // 最多9张图片
setImages(newImages)
// 创建临时URL用于预览
const newImageUrls = newImages.map((file) => URL.createObjectURL(file))
setImageUrls(newImageUrls)
onMessageChange({
text,
images: newImages,
video,
link,
})
}
}
const handleVideoUpload = (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (file && file.size <= 100 * 1024 * 1024) {
// 100MB
setVideo(file)
setVideoUrl(URL.createObjectURL(file))
onMessageChange({
text,
images,
video: file,
link,
})
}
}
const handleLinkChange = (e: ChangeEvent<HTMLInputElement>) => {
const newLink = e.target.value
setLink(newLink)
onMessageChange({
text,
images,
video,
link: newLink,
})
}
const removeImage = (index: number) => {
const newImages = [...images]
newImages.splice(index, 1)
setImages(newImages)
const newImageUrls = [...imageUrls]
newImageUrls.splice(index, 1)
setImageUrls(newImageUrls)
onMessageChange({
text,
images: newImages,
video,
link,
})
}
const removeVideo = () => {
setVideo(null)
setVideoUrl("")
onMessageChange({
text,
images,
video: null,
link,
})
}
return (
<div className="space-y-4 border rounded-md p-4">
<div>
<Textarea
placeholder="请输入消息内容最多800字"
value={text}
onChange={handleTextChange}
className="min-h-[120px]"
/>
<div className="text-xs text-gray-500 mt-1 text-right">{text.length}/800</div>
</div>
{/* 图片预览区域 */}
{imageUrls.length > 0 && (
<div className="grid grid-cols-3 gap-2">
{imageUrls.map((url, index) => (
<div key={index} className="relative group">
<img
src={url || "/placeholder.svg"}
alt={`上传的图片 ${index + 1}`}
className="h-24 w-full object-cover rounded-md"
/>
<button
type="button"
onClick={() => removeImage(index)}
className="absolute top-1 right-1 bg-black bg-opacity-50 rounded-full p-1 text-white opacity-0 group-hover:opacity-100 transition-opacity"
>
<X className="h-4 w-4" />
</button>
</div>
))}
</div>
)}
{/* 视频预览区域 */}
{videoUrl && (
<div className="relative group">
<video src={videoUrl} controls className="w-full h-48 object-cover rounded-md" />
<button
type="button"
onClick={removeVideo}
className="absolute top-2 right-2 bg-black bg-opacity-50 rounded-full p-1 text-white opacity-0 group-hover:opacity-100 transition-opacity"
>
<X className="h-4 w-4" />
</button>
</div>
)}
{/* 链接输入区域 */}
{link && (
<div className="flex items-center gap-2 p-2 bg-gray-50 rounded-md">
<Link className="h-4 w-4 text-blue-500" />
<a href={link} target="_blank" rel="noopener noreferrer" className="text-blue-500 text-sm flex-1 truncate">
{link}
</a>
</div>
)}
{/* 工具栏 */}
<div className="flex items-center gap-2 pt-2 border-t">
<input
type="file"
ref={imageInputRef}
onChange={handleImageUpload}
accept="image/*"
multiple
className="hidden"
/>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => imageInputRef.current?.click()}
disabled={images.length >= 9}
>
<Image className="h-4 w-4 mr-1" />
</Button>
<input type="file" ref={videoInputRef} onChange={handleVideoUpload} accept="video/*" className="hidden" />
<Button
type="button"
variant="outline"
size="sm"
onClick={() => videoInputRef.current?.click()}
disabled={!!video}
>
<Video className="h-4 w-4 mr-1" />
</Button>
<div className="flex-1">
<Input type="url" placeholder="输入链接地址" value={link} onChange={handleLinkChange} className="h-9" />
</div>
</div>
<div className="text-xs text-gray-500">
<p>920MB</p>
<p>1100MB</p>
</div>
</div>
)
}