651 lines
26 KiB
TypeScript
651 lines
26 KiB
TypeScript
"use client"
|
||
|
||
import { useState, useRef, useEffect } from "react"
|
||
import { Card } from "@/components/ui/card"
|
||
import { Button } from "@/components/ui/button"
|
||
import { Input } from "@/components/ui/input"
|
||
import { Label } from "@/components/ui/label"
|
||
import { Textarea } from "@/components/ui/textarea"
|
||
import {
|
||
MessageSquare,
|
||
ImageIcon,
|
||
Video,
|
||
FileText,
|
||
Link2,
|
||
Users,
|
||
AppWindowIcon as Window,
|
||
Plus,
|
||
X,
|
||
Upload,
|
||
Clock,
|
||
UploadCloud,
|
||
} from "lucide-react"
|
||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"
|
||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||
import { toast } from "@/components/ui/use-toast"
|
||
|
||
interface FileContent {
|
||
url: string
|
||
name: string
|
||
}
|
||
|
||
interface MessageContent {
|
||
id: string
|
||
type: "text" | "image" | "video" | "file" | "miniprogram" | "link" | "group"
|
||
content: any // 暂时使用 any 类型来解决类型问题
|
||
sendInterval?: number
|
||
intervalUnit?: "seconds" | "minutes"
|
||
scheduledTime?: {
|
||
hour: number
|
||
minute: number
|
||
second: number
|
||
}
|
||
title?: string
|
||
description?: string
|
||
address?: string
|
||
coverImage?: string
|
||
groupId?: string
|
||
linkUrl?: string
|
||
cover?: string
|
||
}
|
||
|
||
interface DayPlan {
|
||
day: number
|
||
messages: MessageContent[]
|
||
}
|
||
|
||
interface MessageSettingsProps {
|
||
formData: any
|
||
onChange: (data: any) => void
|
||
onNext: () => void
|
||
onPrev: () => void
|
||
}
|
||
|
||
// 消息类型配置
|
||
const messageTypes = [
|
||
{ id: "text", icon: MessageSquare, label: "文本" },
|
||
{ id: "image", icon: ImageIcon, label: "图片" },
|
||
{ id: "video", icon: Video, label: "视频" },
|
||
{ id: "file", icon: FileText, label: "文件" },
|
||
{ id: "miniprogram", icon: Window, label: "小程序" },
|
||
{ id: "link", icon: Link2, label: "链接" },
|
||
{ id: "group", icon: Users, label: "邀请入群" },
|
||
]
|
||
|
||
// 模拟群组数据
|
||
const mockGroups = [
|
||
{ id: "1", name: "产品交流群1", memberCount: 156 },
|
||
{ id: "2", name: "产品交流群2", memberCount: 234 },
|
||
{ id: "3", name: "产品交流群3", memberCount: 89 },
|
||
]
|
||
|
||
export function MessageSettings({ formData, onChange, onNext, onPrev }: MessageSettingsProps) {
|
||
const [dayPlans, setDayPlans] = useState<DayPlan[]>([
|
||
{
|
||
day: 0,
|
||
messages: [
|
||
{
|
||
id: "1",
|
||
type: "text",
|
||
content: "",
|
||
sendInterval: 5,
|
||
intervalUnit: "seconds",
|
||
},
|
||
],
|
||
},
|
||
])
|
||
|
||
// 添加 useEffect 来初始化消息计划数据
|
||
useEffect(() => {
|
||
if (formData.messagePlans && Array.isArray(formData.messagePlans)) {
|
||
setDayPlans(formData.messagePlans)
|
||
}
|
||
}, [formData.messagePlans])
|
||
|
||
const [isAddDayPlanOpen, setIsAddDayPlanOpen] = useState(false)
|
||
const [isGroupSelectOpen, setIsGroupSelectOpen] = useState(false)
|
||
const [selectedGroupId, setSelectedGroupId] = useState("")
|
||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||
const [uploading, setUploading] = useState(false)
|
||
const [uploadTarget, setUploadTarget] = useState<{dayIndex: number, messageIndex: number, type: string} | null>(null)
|
||
const coverInputRef = useRef<HTMLInputElement>(null)
|
||
const [uploadingCover, setUploadingCover] = useState(false)
|
||
const [coverTarget, setCoverTarget] = useState<{dayIndex: number, messageIndex: number} | null>(null)
|
||
|
||
// 添加新消息
|
||
const handleAddMessage = (dayIndex: number, type = "text") => {
|
||
const updatedPlans = [...dayPlans]
|
||
const newMessage: MessageContent = {
|
||
id: Date.now().toString(),
|
||
type: type as MessageContent["type"],
|
||
content: "",
|
||
}
|
||
|
||
if (dayPlans[dayIndex].day === 0) {
|
||
// 即时消息使用间隔设置
|
||
newMessage.sendInterval = 5
|
||
newMessage.intervalUnit = "seconds" // 默认改为秒
|
||
} else {
|
||
// 非即时消息使用具体时间设置
|
||
newMessage.scheduledTime = {
|
||
hour: 9,
|
||
minute: 0,
|
||
second: 0,
|
||
}
|
||
}
|
||
|
||
updatedPlans[dayIndex].messages.push(newMessage)
|
||
setDayPlans(updatedPlans)
|
||
onChange({ ...formData, messagePlans: updatedPlans })
|
||
}
|
||
|
||
// 更新消息内容
|
||
const handleUpdateMessage = (dayIndex: number, messageIndex: number, updates: Partial<MessageContent>) => {
|
||
const updatedPlans = [...dayPlans]
|
||
updatedPlans[dayIndex].messages[messageIndex] = {
|
||
...updatedPlans[dayIndex].messages[messageIndex],
|
||
...updates,
|
||
}
|
||
setDayPlans(updatedPlans)
|
||
onChange({ ...formData, messagePlans: updatedPlans })
|
||
}
|
||
|
||
// 删除消息
|
||
const handleRemoveMessage = (dayIndex: number, messageIndex: number) => {
|
||
const updatedPlans = [...dayPlans]
|
||
updatedPlans[dayIndex].messages.splice(messageIndex, 1)
|
||
setDayPlans(updatedPlans)
|
||
onChange({ ...formData, messagePlans: updatedPlans })
|
||
}
|
||
|
||
// 切换时间单位
|
||
const toggleIntervalUnit = (dayIndex: number, messageIndex: number) => {
|
||
const message = dayPlans[dayIndex].messages[messageIndex]
|
||
const newUnit = message.intervalUnit === "minutes" ? "seconds" : "minutes"
|
||
handleUpdateMessage(dayIndex, messageIndex, { intervalUnit: newUnit })
|
||
}
|
||
|
||
// 添加新的天数计划
|
||
const handleAddDayPlan = () => {
|
||
const newDay = dayPlans.length
|
||
setDayPlans([
|
||
...dayPlans,
|
||
{
|
||
day: newDay,
|
||
messages: [
|
||
{
|
||
id: Date.now().toString(),
|
||
type: "text",
|
||
content: "",
|
||
scheduledTime: {
|
||
hour: 9,
|
||
minute: 0,
|
||
second: 0,
|
||
},
|
||
},
|
||
],
|
||
},
|
||
])
|
||
setIsAddDayPlanOpen(false)
|
||
toast({
|
||
title: "添加成功",
|
||
description: `已添加第${newDay}天的消息计划`,
|
||
})
|
||
}
|
||
|
||
// 选择群组
|
||
const handleSelectGroup = (groupId: string) => {
|
||
setSelectedGroupId(groupId)
|
||
setIsGroupSelectOpen(false)
|
||
toast({
|
||
title: "选择成功",
|
||
description: `已选择群组:${mockGroups.find((g) => g.id === groupId)?.name}`,
|
||
})
|
||
}
|
||
|
||
// 处理文件上传
|
||
const handleFileUpload = (dayIndex: number, messageIndex: number, type: "image" | "video" | "file") => {
|
||
setUploadTarget({ dayIndex, messageIndex, type })
|
||
fileInputRef.current?.setAttribute('accept', type === 'image' ? 'image/*' : type === 'video' ? 'video/*' : '*')
|
||
fileInputRef.current?.click()
|
||
}
|
||
|
||
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||
const file = event.target.files?.[0]
|
||
if (!file || !uploadTarget) return
|
||
setUploading(true)
|
||
const formData = new FormData()
|
||
formData.append("file", file)
|
||
try {
|
||
const token = localStorage.getItem('token');
|
||
const headers: HeadersInit = {}
|
||
if (token) headers['Authorization'] = `Bearer ${token}`
|
||
const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/attachment/upload`, {
|
||
method: 'POST',
|
||
headers,
|
||
body: formData,
|
||
})
|
||
const result = await response.json()
|
||
if (result.code === 200 && result.data?.url) {
|
||
if (uploadTarget.type === 'file') {
|
||
// 多文件,存对象
|
||
const prevFiles = Array.isArray(dayPlans[uploadTarget.dayIndex].messages[uploadTarget.messageIndex].content)
|
||
? dayPlans[uploadTarget.dayIndex].messages[uploadTarget.messageIndex].content
|
||
: []
|
||
handleUpdateMessage(uploadTarget.dayIndex, uploadTarget.messageIndex, { content: [...prevFiles, { url: result.data.url, name: result.data.name || result.data.url.split('/').pop() }] })
|
||
} else {
|
||
handleUpdateMessage(uploadTarget.dayIndex, uploadTarget.messageIndex, { content: result.data.url })
|
||
}
|
||
toast({ title: '上传成功', description: `${uploadTarget.type === 'image' ? '图片' : uploadTarget.type === 'video' ? '视频' : '文件'}上传成功` })
|
||
} else {
|
||
toast({ title: '上传失败', description: result.msg || '请重试', variant: 'destructive' })
|
||
}
|
||
} catch (e: any) {
|
||
toast({ title: '上传失败', description: e?.message || '请重试', variant: 'destructive' })
|
||
} finally {
|
||
setUploading(false)
|
||
setUploadTarget(null)
|
||
if (fileInputRef.current) fileInputRef.current.value = ''
|
||
}
|
||
}
|
||
|
||
const handleUploadCover = (dayIndex: number, messageIndex: number) => {
|
||
setCoverTarget({ dayIndex, messageIndex })
|
||
coverInputRef.current?.click()
|
||
}
|
||
|
||
const handleCoverFileChange = async (
|
||
event: React.ChangeEvent<HTMLInputElement>,
|
||
dayIndex?: number,
|
||
messageIndex?: number
|
||
) => {
|
||
const file = event.target.files?.[0]
|
||
if (!file || !coverTarget) return
|
||
setUploadingCover(true)
|
||
const formData = new FormData()
|
||
formData.append("file", file)
|
||
try {
|
||
const token = localStorage.getItem('token')
|
||
const headers: HeadersInit = {}
|
||
if (token) headers['Authorization'] = `Bearer ${token}`
|
||
const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/attachment/upload`, {
|
||
method: 'POST', headers, body: formData,
|
||
})
|
||
const result = await response.json()
|
||
if (result.code === 200 && result.data?.url) {
|
||
handleUpdateMessage(coverTarget.dayIndex, coverTarget.messageIndex, { cover: result.data.url })
|
||
toast({ title: '上传成功', description: '封面已添加' })
|
||
} else {
|
||
toast({ title: '上传失败', description: result.msg || '请重试', variant: 'destructive' })
|
||
}
|
||
} catch (e: any) {
|
||
toast({ title: '上传失败', description: e?.message || '请重试', variant: 'destructive' })
|
||
} finally {
|
||
setUploadingCover(false)
|
||
setCoverTarget(null)
|
||
if (coverInputRef.current) coverInputRef.current.value = ''
|
||
}
|
||
}
|
||
|
||
const handleRemoveCover = (dayIndex: number, messageIndex: number) => {
|
||
handleUpdateMessage(dayIndex, messageIndex, { cover: "" })
|
||
}
|
||
|
||
return (
|
||
<Card className="p-6">
|
||
<div className="space-y-6">
|
||
<div className="flex items-center justify-between">
|
||
<h2 className="text-lg font-semibold">消息设置</h2>
|
||
<Button variant="outline" size="icon" onClick={() => setIsAddDayPlanOpen(true)}>
|
||
<Plus className="h-4 w-4" />
|
||
</Button>
|
||
</div>
|
||
|
||
<Tabs defaultValue="0" className="w-full">
|
||
<TabsList className="w-full">
|
||
{dayPlans.map((plan) => (
|
||
<TabsTrigger key={plan.day} value={plan.day.toString()} className="flex-1">
|
||
{plan.day === 0 ? "即时消息" : `第${plan.day}天`}
|
||
</TabsTrigger>
|
||
))}
|
||
</TabsList>
|
||
|
||
{dayPlans.map((plan, dayIndex) => (
|
||
<TabsContent key={plan.day} value={plan.day.toString()}>
|
||
<div className="space-y-4">
|
||
{plan.messages.map((message, messageIndex) => (
|
||
<div key={message.id} className="space-y-4 p-4 bg-gray-50 rounded-lg">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center space-x-2">
|
||
{plan.day === 0 ? (
|
||
<>
|
||
<Label>间隔</Label>
|
||
<Input
|
||
type="number"
|
||
value={message.sendInterval}
|
||
onChange={(e) =>
|
||
handleUpdateMessage(dayIndex, messageIndex, { sendInterval: Number(e.target.value) })
|
||
}
|
||
className="w-20"
|
||
/>
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
onClick={() => toggleIntervalUnit(dayIndex, messageIndex)}
|
||
className="flex items-center space-x-1"
|
||
>
|
||
<Clock className="h-3 w-3" />
|
||
<span>{message.intervalUnit === "minutes" ? "分钟" : "秒"}</span>
|
||
</Button>
|
||
</>
|
||
) : (
|
||
<>
|
||
<Label>发送时间</Label>
|
||
<div className="flex items-center space-x-1">
|
||
<Input
|
||
type="number"
|
||
min="0"
|
||
max="23"
|
||
value={message.scheduledTime?.hour || 0}
|
||
onChange={(e) =>
|
||
handleUpdateMessage(dayIndex, messageIndex, {
|
||
scheduledTime: {
|
||
...(message.scheduledTime || { hour: 0, minute: 0, second: 0 }),
|
||
hour: Number(e.target.value),
|
||
},
|
||
})
|
||
}
|
||
className="w-16"
|
||
/>
|
||
<span>:</span>
|
||
<Input
|
||
type="number"
|
||
min="0"
|
||
max="59"
|
||
value={message.scheduledTime?.minute || 0}
|
||
onChange={(e) =>
|
||
handleUpdateMessage(dayIndex, messageIndex, {
|
||
scheduledTime: {
|
||
...(message.scheduledTime || { hour: 0, minute: 0, second: 0 }),
|
||
minute: Number(e.target.value),
|
||
},
|
||
})
|
||
}
|
||
className="w-16"
|
||
/>
|
||
<span>:</span>
|
||
<Input
|
||
type="number"
|
||
min="0"
|
||
max="59"
|
||
value={message.scheduledTime?.second || 0}
|
||
onChange={(e) =>
|
||
handleUpdateMessage(dayIndex, messageIndex, {
|
||
scheduledTime: {
|
||
...(message.scheduledTime || { hour: 0, minute: 0, second: 0 }),
|
||
second: Number(e.target.value),
|
||
},
|
||
})
|
||
}
|
||
className="w-16"
|
||
/>
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
<Button variant="ghost" size="sm" onClick={() => handleRemoveMessage(dayIndex, messageIndex)}>
|
||
<X className="h-4 w-4" />
|
||
</Button>
|
||
</div>
|
||
|
||
<div className="flex items-center space-x-2 bg-white p-2 rounded-lg">
|
||
{messageTypes.map((type) => (
|
||
<Button
|
||
key={type.id}
|
||
variant={message.type === type.id ? "default" : "outline"}
|
||
size="sm"
|
||
onClick={() => handleUpdateMessage(dayIndex, messageIndex, { type: type.id as any })}
|
||
className="flex flex-col items-center p-2 h-auto"
|
||
>
|
||
<type.icon className="h-4 w-4" />
|
||
</Button>
|
||
))}
|
||
</div>
|
||
|
||
{message.type === "text" && (
|
||
<Textarea
|
||
value={message.content}
|
||
onChange={(e) => handleUpdateMessage(dayIndex, messageIndex, { content: e.target.value })}
|
||
placeholder="请输入消息内容"
|
||
className="min-h-[100px]"
|
||
/>
|
||
)}
|
||
|
||
{message.type === "miniprogram" && (
|
||
<div className="space-y-4">
|
||
<div className="space-y-2">
|
||
<Label>
|
||
标题<span className="text-red-500">*</span>
|
||
</Label>
|
||
<Input
|
||
value={message.title}
|
||
onChange={(e) => handleUpdateMessage(dayIndex, messageIndex, { title: e.target.value })}
|
||
placeholder="请输入小程序标题"
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label>描述</Label>
|
||
<Input
|
||
value={message.description}
|
||
onChange={(e) =>
|
||
handleUpdateMessage(dayIndex, messageIndex, { description: e.target.value })
|
||
}
|
||
placeholder="请输入小程序描述"
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label>
|
||
链接<span className="text-red-500">*</span>
|
||
</Label>
|
||
<Input
|
||
value={message.address}
|
||
onChange={(e) => handleUpdateMessage(dayIndex, messageIndex, { address: e.target.value })}
|
||
placeholder="请输入小程序路径"
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{message.type === "link" && (
|
||
<div className="space-y-4">
|
||
<div className="space-y-2">
|
||
<Label>
|
||
标题<span className="text-red-500">*</span>
|
||
</Label>
|
||
<Input
|
||
value={message.title}
|
||
onChange={(e) => handleUpdateMessage(dayIndex, messageIndex, { title: e.target.value })}
|
||
placeholder="请输入链接标题"
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label>描述</Label>
|
||
<Input
|
||
value={message.description}
|
||
onChange={(e) =>
|
||
handleUpdateMessage(dayIndex, messageIndex, { description: e.target.value })
|
||
}
|
||
placeholder="请输入链接描述"
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<Label>
|
||
链接<span className="text-red-500">*</span>
|
||
</Label>
|
||
<Input
|
||
value={message.linkUrl}
|
||
onChange={(e) => handleUpdateMessage(dayIndex, messageIndex, { linkUrl: e.target.value })}
|
||
placeholder="请输入链接地址"
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{message.type === "group" && (
|
||
<div className="space-y-2">
|
||
<Label>
|
||
选择群聊<span className="text-red-500">*</span>
|
||
</Label>
|
||
<Button
|
||
variant="outline"
|
||
className="w-full justify-start"
|
||
onClick={() => setIsGroupSelectOpen(true)}
|
||
>
|
||
{selectedGroupId ? mockGroups.find((g) => g.id === selectedGroupId)?.name : "选择邀请入的群"}
|
||
</Button>
|
||
</div>
|
||
)}
|
||
|
||
{(message.type === "image" || message.type === "video" || message.type === "file") && (
|
||
<div className="border-2 border-dashed rounded-lg p-4 text-center">
|
||
<Button
|
||
variant="outline"
|
||
className="w-full h-[120px]"
|
||
onClick={() => handleFileUpload(dayIndex, messageIndex, message.type as any)}
|
||
disabled={uploading}
|
||
>
|
||
<Upload className="h-4 w-4 mr-2" />
|
||
{uploading && uploadTarget && uploadTarget.dayIndex === dayIndex && uploadTarget.messageIndex === messageIndex ? '上传中...' : `上传${message.type === "image" ? "图片" : message.type === "video" ? "视频" : "文件"}`}
|
||
</Button>
|
||
<input
|
||
type="file"
|
||
ref={fileInputRef}
|
||
onChange={handleFileChange}
|
||
className="hidden"
|
||
/>
|
||
{/* 文件预览 */}
|
||
{message.type === 'image' && message.content && (
|
||
<div className="mt-4">
|
||
<img src={message.content} alt="图片预览" className="max-h-32 mx-auto rounded-lg border" />
|
||
</div>
|
||
)}
|
||
{message.type === 'video' && message.content && (
|
||
<div className="mt-4">
|
||
<video src={message.content} controls className="max-h-32 mx-auto rounded-lg border" />
|
||
</div>
|
||
)}
|
||
{message.type === 'file' && Array.isArray(message.content) && message.content.length > 0 && (
|
||
<ul className="mt-4 space-y-2 text-left">
|
||
{message.content.map((fileObj: {url: string, name: string}, idx: number) => (
|
||
<li key={fileObj.url} className="flex items-center gap-2">
|
||
<a href={fileObj.url} target="_blank" rel="noopener noreferrer" className="text-blue-600 underline break-all flex-1">{fileObj.name || fileObj.url.split('/').pop()}</a>
|
||
<Button size="icon" variant="ghost" onClick={() => {
|
||
const newFiles = message.content.filter((_: any, i: number) => i !== idx)
|
||
handleUpdateMessage(dayIndex, messageIndex, { content: newFiles })
|
||
}}><X className="h-4 w-4" /></Button>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{(message.type === "miniprogram" || message.type === "link") && (
|
||
<div className="mt-4">
|
||
<Label>封面</Label>
|
||
<div className="border-2 border-dashed rounded-lg p-4 text-center">
|
||
{message.cover ? (
|
||
<div className="flex flex-col items-center">
|
||
<img src={message.cover} alt="封面" className="h-24 rounded mb-2" />
|
||
<Button size="sm" onClick={() => handleRemoveCover(dayIndex, messageIndex)}>移除封面</Button>
|
||
</div>
|
||
) : (
|
||
<Button
|
||
variant="outline"
|
||
className="w-full h-[100px] flex flex-col items-center justify-center"
|
||
onClick={() => handleUploadCover(dayIndex, messageIndex)}
|
||
disabled={uploadingCover}
|
||
>
|
||
<UploadCloud className="h-8 w-8 mb-2" />
|
||
上传封面
|
||
</Button>
|
||
)}
|
||
<input
|
||
type="file"
|
||
ref={coverInputRef}
|
||
onChange={(e) => handleCoverFileChange(e, dayIndex, messageIndex)}
|
||
className="hidden"
|
||
accept="image/*"
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
))}
|
||
|
||
<Button variant="outline" onClick={() => handleAddMessage(dayIndex)} className="w-full">
|
||
<Plus className="w-4 h-4 mr-2" />
|
||
添加消息
|
||
</Button>
|
||
</div>
|
||
</TabsContent>
|
||
))}
|
||
</Tabs>
|
||
|
||
<div className="flex justify-between pt-4">
|
||
<Button variant="outline" onClick={onPrev}>
|
||
上一步
|
||
</Button>
|
||
<Button onClick={onNext}>提交</Button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 添加天数计划弹窗 */}
|
||
<Dialog open={isAddDayPlanOpen} onOpenChange={setIsAddDayPlanOpen}>
|
||
<DialogContent>
|
||
<DialogHeader>
|
||
<DialogTitle>添加消息计划</DialogTitle>
|
||
</DialogHeader>
|
||
<div className="py-4">
|
||
<p className="text-sm text-gray-500 mb-4">选择要添加的消息计划类型</p>
|
||
<Button onClick={handleAddDayPlan} className="w-full">
|
||
添加第 {dayPlans.length} 天计划
|
||
</Button>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
{/* 选择群聊弹窗 */}
|
||
<Dialog open={isGroupSelectOpen} onOpenChange={setIsGroupSelectOpen}>
|
||
<DialogContent>
|
||
<DialogHeader>
|
||
<DialogTitle>选择群聊</DialogTitle>
|
||
</DialogHeader>
|
||
<div className="py-4">
|
||
<div className="space-y-2">
|
||
{mockGroups.map((group) => (
|
||
<div
|
||
key={group.id}
|
||
className={`p-4 rounded-lg cursor-pointer hover:bg-gray-100 ${
|
||
selectedGroupId === group.id ? "bg-blue-50 border border-blue-200" : ""
|
||
}`}
|
||
onClick={() => handleSelectGroup(group.id)}
|
||
>
|
||
<div className="font-medium">{group.name}</div>
|
||
<div className="text-sm text-gray-500">成员数:{group.memberCount}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
<DialogFooter>
|
||
<Button variant="outline" onClick={() => setIsGroupSelectOpen(false)}>
|
||
取消
|
||
</Button>
|
||
<Button onClick={() => setIsGroupSelectOpen(false)}>确定</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
</Card>
|
||
)
|
||
}
|