存客宝 React
This commit is contained in:
509
Cunkebao/app/workspace/auto-like/components/audience-tags.tsx
Normal file
509
Cunkebao/app/workspace/auto-like/components/audience-tags.tsx
Normal file
@@ -0,0 +1,509 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Search, Plus, Trash2, LucideTag, Users } from "lucide-react"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
|
||||
|
||||
interface TagGroup {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
type: "profession" | "interest" | "age" | "consumption" | "interaction" | "custom"
|
||||
tags: Tag[]
|
||||
}
|
||||
|
||||
interface Tag {
|
||||
id: string
|
||||
name: string
|
||||
count: number
|
||||
}
|
||||
|
||||
interface UserProfile {
|
||||
id: string
|
||||
name: string
|
||||
avatar: string
|
||||
tags: string[]
|
||||
profession?: string
|
||||
interest?: string
|
||||
region?: string
|
||||
lastActive?: string
|
||||
}
|
||||
|
||||
export interface AudienceTagsData {
|
||||
selectedTags: string[]
|
||||
tagOperator: "and" | "or"
|
||||
}
|
||||
|
||||
interface AudienceTagsProps {
|
||||
initialData?: Partial<AudienceTagsData>
|
||||
onSave: (data: AudienceTagsData) => void
|
||||
onBack: () => void
|
||||
}
|
||||
|
||||
export function AudienceTags({ initialData, onSave, onBack }: AudienceTagsProps) {
|
||||
const [tagGroups, setTagGroups] = useState<TagGroup[]>([])
|
||||
const [users, setUsers] = useState<UserProfile[]>([])
|
||||
const [selectedTags, setSelectedTags] = useState<string[]>(initialData?.selectedTags || [])
|
||||
const [tagOperator, setTagOperator] = useState<"and" | "or">(initialData?.tagOperator || "or")
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [activeTab, setActiveTab] = useState("all")
|
||||
const [newTagName, setNewTagName] = useState("")
|
||||
const [newTagDescription, setNewTagDescription] = useState("")
|
||||
const [newTagType, setNewTagType] = useState<TagGroup["type"]>("custom")
|
||||
const [isCreateTagDialogOpen, setIsCreateTagDialogOpen] = useState(false)
|
||||
|
||||
// 模拟获取标签组和用户数据
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
|
||||
// 模拟标签组数据
|
||||
const mockTagGroups: TagGroup[] = [
|
||||
{
|
||||
id: "profession",
|
||||
name: "职业",
|
||||
description: "按照好友的职业分类",
|
||||
type: "profession",
|
||||
tags: [
|
||||
{ id: "teacher", name: "教师", count: 15 },
|
||||
{ id: "doctor", name: "医生", count: 8 },
|
||||
{ id: "engineer", name: "工程师", count: 22 },
|
||||
{ id: "business", name: "企业白领", count: 30 },
|
||||
{ id: "freelancer", name: "自由职业", count: 12 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "interest",
|
||||
name: "兴趣爱好",
|
||||
description: "按照好友的兴趣爱好分类",
|
||||
type: "interest",
|
||||
tags: [
|
||||
{ id: "photography", name: "摄影爱好者", count: 18 },
|
||||
{ id: "sports", name: "运动达人", count: 25 },
|
||||
{ id: "food", name: "美食爱好者", count: 32 },
|
||||
{ id: "travel", name: "旅行达人", count: 20 },
|
||||
{ id: "tech", name: "科技发烧友", count: 15 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "age",
|
||||
name: "年龄范围",
|
||||
description: "按照好友的年龄范围分类",
|
||||
type: "age",
|
||||
tags: [
|
||||
{ id: "18-25", name: "18-25岁", count: 22 },
|
||||
{ id: "26-35", name: "26-35岁", count: 45 },
|
||||
{ id: "36-45", name: "36-45岁", count: 30 },
|
||||
{ id: "46-55", name: "46-55岁", count: 15 },
|
||||
{ id: "56+", name: "56岁以上", count: 8 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "consumption",
|
||||
name: "消费能力",
|
||||
description: "按照好友的消费能力分类",
|
||||
type: "consumption",
|
||||
tags: [
|
||||
{ id: "high", name: "高消费", count: 12 },
|
||||
{ id: "medium", name: "中等消费", count: 48 },
|
||||
{ id: "low", name: "低消费", count: 30 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "interaction",
|
||||
name: "互动频率",
|
||||
description: "按照与好友的互动频率分类",
|
||||
type: "interaction",
|
||||
tags: [
|
||||
{ id: "high-interaction", name: "高频互动", count: 15 },
|
||||
{ id: "medium-interaction", name: "中频互动", count: 35 },
|
||||
{ id: "low-interaction", name: "低频互动", count: 40 },
|
||||
{ id: "new-friend", name: "近期新添加", count: 10 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "custom",
|
||||
name: "自定义标签",
|
||||
description: "自定义创建的标签",
|
||||
type: "custom",
|
||||
tags: [
|
||||
{ id: "potential-customer", name: "潜在客户", count: 28 },
|
||||
{ id: "vip", name: "VIP客户", count: 10 },
|
||||
{ id: "partner", name: "合作伙伴", count: 5 },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
setTagGroups(mockTagGroups)
|
||||
|
||||
// 模拟用户数据
|
||||
const mockUsers: UserProfile[] = Array.from({ length: 50 }, (_, i) => {
|
||||
const professionTag = mockTagGroups[0].tags[Math.floor(Math.random() * mockTagGroups[0].tags.length)]
|
||||
const interestTag = mockTagGroups[1].tags[Math.floor(Math.random() * mockTagGroups[1].tags.length)]
|
||||
const ageTag = mockTagGroups[2].tags[Math.floor(Math.random() * mockTagGroups[2].tags.length)]
|
||||
const consumptionTag = mockTagGroups[3].tags[Math.floor(Math.random() * mockTagGroups[3].tags.length)]
|
||||
const interactionTag = mockTagGroups[4].tags[Math.floor(Math.random() * mockTagGroups[4].tags.length)]
|
||||
|
||||
// 随机选择一些标签
|
||||
const userTags = [
|
||||
professionTag.id,
|
||||
interestTag.id,
|
||||
Math.random() > 0.5 ? ageTag.id : null,
|
||||
Math.random() > 0.5 ? consumptionTag.id : null,
|
||||
Math.random() > 0.5 ? interactionTag.id : null,
|
||||
].filter(Boolean) as string[]
|
||||
|
||||
// 随机添加一些自定义标签
|
||||
if (Math.random() > 0.7) {
|
||||
const customTag = mockTagGroups[5].tags[Math.floor(Math.random() * mockTagGroups[5].tags.length)]
|
||||
userTags.push(customTag.id)
|
||||
}
|
||||
|
||||
return {
|
||||
id: `user-${i + 1}`,
|
||||
name: `用户${i + 1}`,
|
||||
avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${i}`,
|
||||
tags: userTags,
|
||||
profession: professionTag.name,
|
||||
interest: interestTag.name,
|
||||
region: ["北京", "上海", "广州", "深圳", "杭州"][Math.floor(Math.random() * 5)],
|
||||
lastActive: `${Math.floor(Math.random() * 24)}小时前`,
|
||||
}
|
||||
})
|
||||
|
||||
setUsers(mockUsers)
|
||||
}
|
||||
|
||||
fetchData()
|
||||
}, [])
|
||||
|
||||
// 获取所有标签
|
||||
const allTags = tagGroups.flatMap((group) => group.tags)
|
||||
|
||||
// 根据选中的标签过滤用户
|
||||
const filteredUsers = users.filter((user) => {
|
||||
if (selectedTags.length === 0) return true
|
||||
|
||||
if (tagOperator === "and") {
|
||||
return selectedTags.every((tagId) => user.tags.includes(tagId))
|
||||
} else {
|
||||
return selectedTags.some((tagId) => user.tags.includes(tagId))
|
||||
}
|
||||
})
|
||||
|
||||
// 根据搜索查询过滤标签
|
||||
const filteredTagGroups = tagGroups
|
||||
.map((group) => ({
|
||||
...group,
|
||||
tags: group.tags.filter((tag) => tag.name.toLowerCase().includes(searchQuery.toLowerCase())),
|
||||
}))
|
||||
.filter((group) => group.tags.length > 0)
|
||||
|
||||
// 根据标签类型过滤标签组
|
||||
const tabFilteredTagGroups =
|
||||
activeTab === "all" ? filteredTagGroups : filteredTagGroups.filter((group) => group.id === activeTab)
|
||||
|
||||
// 切换标签选择
|
||||
const toggleTag = (tagId: string) => {
|
||||
setSelectedTags(selectedTags.includes(tagId) ? selectedTags.filter((id) => id !== tagId) : [...selectedTags, tagId])
|
||||
}
|
||||
|
||||
// 创建新标签
|
||||
const handleCreateTag = () => {
|
||||
if (newTagName.trim()) {
|
||||
const newTag: Tag = {
|
||||
id: `custom-${Date.now()}`,
|
||||
name: newTagName.trim(),
|
||||
count: 0,
|
||||
}
|
||||
|
||||
setTagGroups(
|
||||
tagGroups.map((group) => (group.id === "custom" ? { ...group, tags: [...group.tags, newTag] } : group)),
|
||||
)
|
||||
|
||||
setNewTagName("")
|
||||
setNewTagDescription("")
|
||||
setIsCreateTagDialogOpen(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存选择的标签
|
||||
const handleSave = () => {
|
||||
onSave({
|
||||
selectedTags,
|
||||
tagOperator,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>指定点赞的人群标签</CardTitle>
|
||||
<CardDescription>选择特定标签,只对带有这些标签的好友朋友圈进行点赞</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* 标签选择逻辑 */}
|
||||
<div className="flex items-center space-x-4">
|
||||
<Label className="font-medium">标签匹配逻辑:</Label>
|
||||
<RadioGroup
|
||||
value={tagOperator}
|
||||
onValueChange={(value) => setTagOperator(value as "and" | "or")}
|
||||
className="flex space-x-4"
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="and" id="and" />
|
||||
<Label htmlFor="and">同时满足所有标签</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="or" id="or" />
|
||||
<Label htmlFor="or">满足任一标签</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
{/* 已选标签展示 */}
|
||||
<div className="space-y-2">
|
||||
<Label className="font-medium">已选标签:</Label>
|
||||
<div className="flex flex-wrap gap-2 min-h-10 p-2 border rounded-md">
|
||||
{selectedTags.length === 0 ? (
|
||||
<span className="text-sm text-muted-foreground">未选择任何标签,将对所有好友点赞</span>
|
||||
) : (
|
||||
selectedTags.map((tagId) => {
|
||||
const tag = allTags.find((t) => t.id === tagId)
|
||||
return tag ? (
|
||||
<Badge key={tagId} className="flex items-center gap-1">
|
||||
{tag.name}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-4 w-4 p-0 hover:bg-transparent"
|
||||
onClick={() => toggleTag(tagId)}
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
<span className="sr-only">Remove</span>
|
||||
</Button>
|
||||
</Badge>
|
||||
) : null
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 标签搜索和分类 */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="搜索标签"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-9"
|
||||
/>
|
||||
</div>
|
||||
<Dialog open={isCreateTagDialogOpen} onOpenChange={setIsCreateTagDialogOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" className="ml-2">
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
创建标签
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>创建新标签</DialogTitle>
|
||||
<DialogDescription>创建一个新的标签来分类您的好友</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="tagName">标签名称</Label>
|
||||
<Input
|
||||
id="tagName"
|
||||
placeholder="输入标签名称"
|
||||
value={newTagName}
|
||||
onChange={(e) => setNewTagName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="tagDescription">标签描述(可选)</Label>
|
||||
<Textarea
|
||||
id="tagDescription"
|
||||
placeholder="输入标签描述"
|
||||
value={newTagDescription}
|
||||
onChange={(e) => setNewTagDescription(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="tagType">标签类型</Label>
|
||||
<RadioGroup
|
||||
value={newTagType}
|
||||
onValueChange={(value) => setNewTagType(value as TagGroup["type"])}
|
||||
className="grid grid-cols-2 gap-2"
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="profession" id="profession" />
|
||||
<Label htmlFor="profession">职业</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="interest" id="interest" />
|
||||
<Label htmlFor="interest">兴趣爱好</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="age" id="age" />
|
||||
<Label htmlFor="age">年龄范围</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="consumption" id="consumption" />
|
||||
<Label htmlFor="consumption">消费能力</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="interaction" id="interaction" />
|
||||
<Label htmlFor="interaction">互动频率</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="custom" id="custom" />
|
||||
<Label htmlFor="custom">自定义</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsCreateTagDialogOpen(false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleCreateTag} disabled={!newTagName.trim()}>
|
||||
创建
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||
<TabsList className="grid grid-cols-7">
|
||||
<TabsTrigger value="all">全部</TabsTrigger>
|
||||
<TabsTrigger value="profession">职业</TabsTrigger>
|
||||
<TabsTrigger value="interest">兴趣</TabsTrigger>
|
||||
<TabsTrigger value="age">年龄</TabsTrigger>
|
||||
<TabsTrigger value="consumption">消费</TabsTrigger>
|
||||
<TabsTrigger value="interaction">互动</TabsTrigger>
|
||||
<TabsTrigger value="custom">自定义</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
<div className="space-y-6">
|
||||
{tabFilteredTagGroups.map((group) => (
|
||||
<div key={group.id} className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-medium flex items-center">
|
||||
<LucideTag className="h-4 w-4 mr-1 text-muted-foreground" />
|
||||
{group.name}
|
||||
</h3>
|
||||
<span className="text-xs text-muted-foreground">{group.description}</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{group.tags.map((tag) => (
|
||||
<Badge
|
||||
key={tag.id}
|
||||
variant={selectedTags.includes(tag.id) ? "default" : "outline"}
|
||||
className="cursor-pointer flex items-center gap-1"
|
||||
onClick={() => toggleTag(tag.id)}
|
||||
>
|
||||
{tag.name}
|
||||
<span className="text-xs opacity-70">({tag.count})</span>
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{tabFilteredTagGroups.length === 0 && (
|
||||
<div className="text-center py-8 text-muted-foreground">未找到符合条件的标签</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 预览匹配的用户 */}
|
||||
<div className="space-y-4 pt-4 border-t">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-medium flex items-center">
|
||||
<Users className="h-4 w-4 mr-1 text-muted-foreground" />
|
||||
匹配的好友预览
|
||||
</h3>
|
||||
<Badge variant="outline">共 {filteredUsers.length} 人</Badge>
|
||||
</div>
|
||||
<ScrollArea className="h-64 border rounded-md">
|
||||
<div className="p-2 space-y-2">
|
||||
{filteredUsers.slice(0, 20).map((user) => (
|
||||
<div key={user.id} className="flex items-center justify-between p-2 rounded-md hover:bg-muted/50">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Avatar>
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback>{user.name.substring(0, 2)}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<p className="font-medium">{user.name}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{user.profession} · {user.region} · 最近活跃: {user.lastActive}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1 max-w-[200px] justify-end">
|
||||
{user.tags.slice(0, 2).map((tagId) => {
|
||||
const tag = allTags.find((t) => t.id === tagId)
|
||||
return tag ? (
|
||||
<Badge key={tagId} variant="outline" className="text-xs">
|
||||
{tag.name}
|
||||
</Badge>
|
||||
) : null
|
||||
})}
|
||||
{user.tags.length > 2 && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
+{user.tags.length - 2}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{filteredUsers.length === 0 && (
|
||||
<div className="text-center py-4 text-muted-foreground">未找到匹配的好友</div>
|
||||
)}
|
||||
{filteredUsers.length > 20 && (
|
||||
<div className="text-center py-2 text-muted-foreground text-sm">
|
||||
显示前 20 位好友,共 {filteredUsers.length} 位匹配
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<Button variant="outline" onClick={onBack}>
|
||||
返回上一步
|
||||
</Button>
|
||||
<Button onClick={handleSave}>完成设置</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
294
Cunkebao/app/workspace/auto-like/components/device-selection.tsx
Normal file
294
Cunkebao/app/workspace/auto-like/components/device-selection.tsx
Normal file
@@ -0,0 +1,294 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { Search, RefreshCw, Smartphone, Database, Users } from "lucide-react"
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
|
||||
|
||||
interface Device {
|
||||
id: string
|
||||
name: string
|
||||
status: "online" | "offline"
|
||||
wechatId: string
|
||||
}
|
||||
|
||||
interface DatabaseItem {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
count: number
|
||||
}
|
||||
|
||||
interface AudienceGroup {
|
||||
id: string
|
||||
name: string
|
||||
count: number
|
||||
description: string
|
||||
}
|
||||
|
||||
export interface DeviceSelectionData {
|
||||
selectedDevices: string[]
|
||||
selectedDatabase: string
|
||||
selectedAudience: string
|
||||
}
|
||||
|
||||
interface DeviceSelectionProps {
|
||||
initialData?: Partial<DeviceSelectionData>
|
||||
onSave: (data: DeviceSelectionData) => void
|
||||
onBack: () => void
|
||||
}
|
||||
|
||||
export function DeviceSelection({ initialData, onSave, onBack }: DeviceSelectionProps) {
|
||||
const [devices, setDevices] = useState<Device[]>([])
|
||||
const [databases, setDatabases] = useState<DatabaseItem[]>([])
|
||||
const [audienceGroups, setAudienceGroups] = useState<AudienceGroup[]>([])
|
||||
const [selectedDevices, setSelectedDevices] = useState<string[]>(initialData?.selectedDevices || [])
|
||||
const [selectedDatabase, setSelectedDatabase] = useState<string>(initialData?.selectedDatabase || "")
|
||||
const [selectedAudience, setSelectedAudience] = useState<string>(initialData?.selectedAudience || "")
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [activeTab, setActiveTab] = useState("all")
|
||||
|
||||
// 模拟获取设备数据
|
||||
useEffect(() => {
|
||||
// 模拟设备数据
|
||||
const mockDevices: Device[] = Array.from({ length: 10 }, (_, i) => ({
|
||||
id: `device-${i + 1}`,
|
||||
name: `设备 ${i + 1}`,
|
||||
status: Math.random() > 0.3 ? "online" : "offline",
|
||||
wechatId: `wxid_${Math.random().toString(36).substr(2, 8)}`,
|
||||
}))
|
||||
setDevices(mockDevices)
|
||||
|
||||
// 模拟数据库数据
|
||||
const mockDatabases: DatabaseItem[] = [
|
||||
{
|
||||
id: "db-1",
|
||||
name: "默认数据库",
|
||||
description: "系统默认的数据库",
|
||||
count: 1250,
|
||||
},
|
||||
{
|
||||
id: "db-2",
|
||||
name: "高净值客户",
|
||||
description: "高消费能力的客户群体",
|
||||
count: 450,
|
||||
},
|
||||
{
|
||||
id: "db-3",
|
||||
name: "潜在客户",
|
||||
description: "有购买意向的潜在客户",
|
||||
count: 780,
|
||||
},
|
||||
]
|
||||
setDatabases(mockDatabases)
|
||||
|
||||
// 模拟目标人群数据
|
||||
const mockAudienceGroups: AudienceGroup[] = [
|
||||
{
|
||||
id: "audience-1",
|
||||
name: "全部好友",
|
||||
count: 1250,
|
||||
description: "所有微信好友",
|
||||
},
|
||||
{
|
||||
id: "audience-2",
|
||||
name: "高频互动好友",
|
||||
count: 320,
|
||||
description: "经常互动的好友",
|
||||
},
|
||||
{
|
||||
id: "audience-3",
|
||||
name: "潜在客户",
|
||||
count: 450,
|
||||
description: "有购买意向的好友",
|
||||
},
|
||||
{
|
||||
id: "audience-4",
|
||||
name: "VIP客户",
|
||||
description: "已成交的VIP客户",
|
||||
},
|
||||
]
|
||||
setAudienceGroups(mockAudienceGroups)
|
||||
|
||||
// 设置默认选中的数据库和目标人群
|
||||
if (!initialData?.selectedDatabase) {
|
||||
setSelectedDatabase("db-1")
|
||||
}
|
||||
if (!initialData?.selectedAudience) {
|
||||
setSelectedAudience("audience-1")
|
||||
}
|
||||
}, [initialData?.selectedDatabase, initialData?.selectedAudience])
|
||||
|
||||
// 过滤设备
|
||||
const filteredDevices = devices.filter((device) => {
|
||||
const matchesSearch =
|
||||
device.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
device.wechatId.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
|
||||
const matchesTab =
|
||||
activeTab === "all" ||
|
||||
(activeTab === "selected" && selectedDevices.includes(device.id)) ||
|
||||
(activeTab === "online" && device.status === "online") ||
|
||||
(activeTab === "offline" && device.status === "offline")
|
||||
|
||||
return matchesSearch && matchesTab
|
||||
})
|
||||
|
||||
// 选择/取消选择单个设备
|
||||
const handleDeviceSelect = (deviceId: string) => {
|
||||
setSelectedDevices(
|
||||
selectedDevices.includes(deviceId)
|
||||
? selectedDevices.filter((id) => id !== deviceId)
|
||||
: [...selectedDevices, deviceId],
|
||||
)
|
||||
}
|
||||
|
||||
// 保存选择的设备
|
||||
const handleSave = () => {
|
||||
onSave({
|
||||
selectedDevices,
|
||||
selectedDatabase,
|
||||
selectedAudience,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>选择执行点赞任务的设备</CardTitle>
|
||||
<CardDescription>选择要执行自动点赞任务的设备、数据库和目标人群</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* 设备筛选和搜索 */}
|
||||
<div className="space-y-4">
|
||||
<Label className="font-medium">选择设备</Label>
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="搜索设备名称/微信号"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-9"
|
||||
/>
|
||||
</div>
|
||||
<Button variant="outline" size="icon">
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 设备分类标签页 */}
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||
<TabsList className="grid grid-cols-4">
|
||||
<TabsTrigger value="all">全部设备</TabsTrigger>
|
||||
<TabsTrigger value="selected">已选择 ({selectedDevices.length})</TabsTrigger>
|
||||
<TabsTrigger value="online">在线设备</TabsTrigger>
|
||||
<TabsTrigger value="offline">离线设备</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
{/* 设备列表 */}
|
||||
<div className="space-y-3">
|
||||
{filteredDevices.map((device) => (
|
||||
<Card
|
||||
key={device.id}
|
||||
className={`p-4 hover:shadow-md transition-shadow ${
|
||||
selectedDevices.includes(device.id) ? "ring-2 ring-primary" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<Checkbox
|
||||
checked={selectedDevices.includes(device.id)}
|
||||
onCheckedChange={() => handleDeviceSelect(device.id)}
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="font-medium truncate flex items-center">
|
||||
<Smartphone className="h-4 w-4 mr-1 text-muted-foreground" />
|
||||
{device.name}
|
||||
</div>
|
||||
<Badge variant={device.status === "online" ? "success" : "secondary"} className="text-xs">
|
||||
{device.status === "online" ? "在线" : "离线"}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">微信号: {device.wechatId}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
|
||||
{filteredDevices.length === 0 && (
|
||||
<div className="text-center py-8 text-muted-foreground">未找到符合条件的设备</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 数据库选择 */}
|
||||
<div className="space-y-4 pt-4 border-t">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Database className="h-5 w-5 text-muted-foreground" />
|
||||
<Label className="font-medium">选择数据库</Label>
|
||||
</div>
|
||||
<RadioGroup value={selectedDatabase} onValueChange={setSelectedDatabase} className="space-y-3">
|
||||
{databases.map((db) => (
|
||||
<div key={db.id} className="flex items-start space-x-3">
|
||||
<RadioGroupItem value={db.id} id={db.id} />
|
||||
<div className="flex-1">
|
||||
<Label htmlFor={db.id} className="font-medium">
|
||||
{db.name}
|
||||
<Badge variant="outline" className="ml-2">
|
||||
{db.count}条数据
|
||||
</Badge>
|
||||
</Label>
|
||||
<p className="text-sm text-muted-foreground">{db.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
{/* 目标人群选择 */}
|
||||
<div className="space-y-4 pt-4 border-t">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Users className="h-5 w-5 text-muted-foreground" />
|
||||
<Label className="font-medium">选择目标人群</Label>
|
||||
</div>
|
||||
<RadioGroup value={selectedAudience} onValueChange={setSelectedAudience} className="space-y-3">
|
||||
{audienceGroups.map((group) => (
|
||||
<div key={group.id} className="flex items-start space-x-3">
|
||||
<RadioGroupItem value={group.id} id={group.id} />
|
||||
<div className="flex-1">
|
||||
<Label htmlFor={group.id} className="font-medium">
|
||||
{group.name}
|
||||
<Badge variant="outline" className="ml-2">
|
||||
{group.count}人
|
||||
</Badge>
|
||||
</Label>
|
||||
<p className="text-sm text-muted-foreground">{group.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<Button variant="outline" onClick={onBack}>
|
||||
返回上一步
|
||||
</Button>
|
||||
<Button onClick={handleSave} disabled={selectedDevices.length === 0}>
|
||||
完成设置
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
263
Cunkebao/app/workspace/auto-like/components/like-config.tsx
Normal file
263
Cunkebao/app/workspace/auto-like/components/like-config.tsx
Normal file
@@ -0,0 +1,263 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Slider } from "@/components/ui/slider"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
|
||||
import { Info, Trash2 } from "lucide-react"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
|
||||
export interface LikeConfigData {
|
||||
taskName: string
|
||||
maxLikesPerDay: number
|
||||
likeOldContent: boolean
|
||||
contentTypes: string[]
|
||||
keywordFilters: string[]
|
||||
startImmediately: boolean
|
||||
likeFirstPageOnly: boolean
|
||||
}
|
||||
|
||||
interface LikeConfigProps {
|
||||
initialData?: Partial<LikeConfigData>
|
||||
onSave: (data: LikeConfigData) => void
|
||||
onBack: () => void
|
||||
}
|
||||
|
||||
export function LikeConfig({ initialData, onSave, onBack }: LikeConfigProps) {
|
||||
const [formData, setFormData] = useState<LikeConfigData>({
|
||||
taskName: initialData?.taskName ?? "朋友圈自动点赞任务",
|
||||
maxLikesPerDay: initialData?.maxLikesPerDay ?? 50,
|
||||
likeOldContent: initialData?.likeOldContent ?? false,
|
||||
contentTypes: initialData?.contentTypes ?? ["text", "image", "video"],
|
||||
keywordFilters: initialData?.keywordFilters ?? [],
|
||||
startImmediately: initialData?.startImmediately ?? true,
|
||||
likeFirstPageOnly: initialData?.likeFirstPageOnly ?? false,
|
||||
})
|
||||
|
||||
const [newKeyword, setNewKeyword] = useState("")
|
||||
|
||||
// 内容类型选项
|
||||
const contentTypeOptions = [
|
||||
{ id: "text", label: "纯文字动态" },
|
||||
{ id: "image", label: "图片动态" },
|
||||
{ id: "video", label: "视频动态" },
|
||||
{ id: "link", label: "链接分享" },
|
||||
{ id: "original", label: "仅原创内容" },
|
||||
]
|
||||
|
||||
// 添加关键词
|
||||
const addKeyword = () => {
|
||||
if (newKeyword.trim() && !formData.keywordFilters.includes(newKeyword.trim())) {
|
||||
setFormData({
|
||||
...formData,
|
||||
keywordFilters: [...formData.keywordFilters, newKeyword.trim()],
|
||||
})
|
||||
setNewKeyword("")
|
||||
}
|
||||
}
|
||||
|
||||
// 删除关键词
|
||||
const removeKeyword = (keyword: string) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
keywordFilters: formData.keywordFilters.filter((k) => k !== keyword),
|
||||
})
|
||||
}
|
||||
|
||||
// 切换内容类型
|
||||
const toggleContentType = (typeId: string) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
contentTypes: formData.contentTypes.includes(typeId)
|
||||
? formData.contentTypes.filter((id) => id !== typeId)
|
||||
: [...formData.contentTypes, typeId],
|
||||
})
|
||||
}
|
||||
|
||||
// 处理表单提交
|
||||
const handleSubmit = () => {
|
||||
onSave(formData)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>点赞配置设置</CardTitle>
|
||||
<CardDescription>配置点赞任务的名称、内容和数量</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* 任务名称 */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="taskName" className="font-medium">
|
||||
任务名称
|
||||
</Label>
|
||||
<Input
|
||||
id="taskName"
|
||||
value={formData.taskName}
|
||||
onChange={(e) => setFormData({ ...formData, taskName: e.target.value })}
|
||||
placeholder="输入任务名称"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 每日点赞数量 */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="maxLikesPerDay" className="font-medium">
|
||||
每日最大点赞数
|
||||
</Label>
|
||||
<span className="text-sm text-muted-foreground">{formData.maxLikesPerDay}个</span>
|
||||
</div>
|
||||
<Slider
|
||||
id="maxLikesPerDay"
|
||||
min={10}
|
||||
max={200}
|
||||
step={10}
|
||||
value={[formData.maxLikesPerDay]}
|
||||
onValueChange={(value) => setFormData({ ...formData, maxLikesPerDay: value[0] })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 内容类型设置 */}
|
||||
<div className="space-y-3 pt-4 border-t">
|
||||
<Label className="font-medium">点赞内容类型</Label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||
{contentTypeOptions.map((type) => (
|
||||
<div key={type.id} className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={`content-${type.id}`}
|
||||
checked={formData.contentTypes.includes(type.id)}
|
||||
onCheckedChange={() => toggleContentType(type.id)}
|
||||
/>
|
||||
<Label htmlFor={`content-${type.id}`} className="text-sm">
|
||||
{type.label}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 关键词过滤 */}
|
||||
<div className="space-y-3 pt-4 border-t">
|
||||
<Label className="font-medium">关键词过滤</Label>
|
||||
<div className="flex space-x-2">
|
||||
<Input
|
||||
placeholder="输入关键词"
|
||||
value={newKeyword}
|
||||
onChange={(e) => setNewKeyword(e.target.value)}
|
||||
className="flex-1"
|
||||
/>
|
||||
<Button type="button" onClick={addKeyword} disabled={!newKeyword.trim()}>
|
||||
添加
|
||||
</Button>
|
||||
</div>
|
||||
{formData.keywordFilters.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{formData.keywordFilters.map((keyword) => (
|
||||
<Badge key={keyword} variant="secondary" className="flex items-center gap-1">
|
||||
{keyword}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-4 w-4 p-0 hover:bg-transparent"
|
||||
onClick={() => removeKeyword(keyword)}
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
<span className="sr-only">Remove</span>
|
||||
</Button>
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<p className="text-xs text-muted-foreground">添加关键词后,系统将只对包含这些关键词的内容进行点赞</p>
|
||||
</div>
|
||||
|
||||
{/* 其他选项 */}
|
||||
<div className="space-y-4 pt-4 border-t">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label htmlFor="likeOldContent" className="font-medium">
|
||||
点赞历史内容
|
||||
</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-4 w-4 text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>开启后系统将点赞好友的历史朋友圈内容</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<Switch
|
||||
id="likeOldContent"
|
||||
checked={formData.likeOldContent}
|
||||
onCheckedChange={(checked) => setFormData({ ...formData, likeOldContent: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label htmlFor="startImmediately" className="font-medium">
|
||||
立即开始点赞
|
||||
</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-4 w-4 text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>开启后任务创建完成将立即开始执行</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<Switch
|
||||
id="startImmediately"
|
||||
checked={formData.startImmediately}
|
||||
onCheckedChange={(checked) => setFormData({ ...formData, startImmediately: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label htmlFor="likeFirstPageOnly" className="font-medium">
|
||||
仅点赞第一页
|
||||
</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-4 w-4 text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>开启后系统将只点赞朋友圈第一页的内容</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<Switch
|
||||
id="likeFirstPageOnly"
|
||||
checked={formData.likeFirstPageOnly}
|
||||
onCheckedChange={(checked) => setFormData({ ...formData, likeFirstPageOnly: checked })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<Button variant="outline" onClick={onBack}>
|
||||
返回上一步
|
||||
</Button>
|
||||
<Button onClick={handleSubmit}>保存并继续</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
488
Cunkebao/app/workspace/auto-like/components/like-rules.tsx
Normal file
488
Cunkebao/app/workspace/auto-like/components/like-rules.tsx
Normal file
@@ -0,0 +1,488 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Slider } from "@/components/ui/slider"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
|
||||
import { Info, Plus, Trash2 } from "lucide-react"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
|
||||
export interface TimeRange {
|
||||
id: string
|
||||
start: string
|
||||
end: string
|
||||
}
|
||||
|
||||
export interface LikeRulesData {
|
||||
enableAutoLike: boolean
|
||||
likeInterval: number
|
||||
maxLikesPerDay: number
|
||||
likeOldContent: boolean
|
||||
contentTypes: string[]
|
||||
keywordFilters: string[]
|
||||
friendGroups: string[]
|
||||
excludedGroups: string[]
|
||||
timeRanges: TimeRange[]
|
||||
randomizeInterval: boolean
|
||||
minInterval?: number
|
||||
maxInterval?: number
|
||||
}
|
||||
|
||||
interface LikeRulesProps {
|
||||
initialData?: Partial<LikeRulesData>
|
||||
onSave: (data: LikeRulesData) => void
|
||||
}
|
||||
|
||||
export function LikeRules({ initialData, onSave }: LikeRulesProps) {
|
||||
const [formData, setFormData] = useState<LikeRulesData>({
|
||||
enableAutoLike: initialData?.enableAutoLike ?? true,
|
||||
likeInterval: initialData?.likeInterval ?? 15,
|
||||
maxLikesPerDay: initialData?.maxLikesPerDay ?? 50,
|
||||
likeOldContent: initialData?.likeOldContent ?? false,
|
||||
contentTypes: initialData?.contentTypes ?? ["text", "image", "video"],
|
||||
keywordFilters: initialData?.keywordFilters ?? [],
|
||||
friendGroups: initialData?.friendGroups ?? ["all"],
|
||||
excludedGroups: initialData?.excludedGroups ?? [],
|
||||
timeRanges: initialData?.timeRanges ?? [{ id: "1", start: "09:00", end: "11:00" }],
|
||||
randomizeInterval: initialData?.randomizeInterval ?? false,
|
||||
minInterval: initialData?.minInterval ?? 5,
|
||||
maxInterval: initialData?.maxInterval ?? 30,
|
||||
})
|
||||
|
||||
const [newKeyword, setNewKeyword] = useState("")
|
||||
|
||||
// 内容类型选项
|
||||
const contentTypeOptions = [
|
||||
{ id: "text", label: "纯文字动态" },
|
||||
{ id: "image", label: "图片动态" },
|
||||
{ id: "video", label: "视频动态" },
|
||||
{ id: "link", label: "链接分享" },
|
||||
{ id: "original", label: "仅原创内容" },
|
||||
]
|
||||
|
||||
// 好友分组选项(模拟数据)
|
||||
const friendGroupOptions = [
|
||||
{ id: "all", label: "所有好友" },
|
||||
{ id: "work", label: "工作相关" },
|
||||
{ id: "family", label: "亲友" },
|
||||
{ id: "clients", label: "客户" },
|
||||
{ id: "potential", label: "潜在客户" },
|
||||
]
|
||||
|
||||
// 添加时间范围
|
||||
const addTimeRange = () => {
|
||||
const newId = String(formData.timeRanges.length + 1)
|
||||
setFormData({
|
||||
...formData,
|
||||
timeRanges: [...formData.timeRanges, { id: newId, start: "12:00", end: "14:00" }],
|
||||
})
|
||||
}
|
||||
|
||||
// 删除时间范围
|
||||
const removeTimeRange = (id: string) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
timeRanges: formData.timeRanges.filter((range) => range.id !== id),
|
||||
})
|
||||
}
|
||||
|
||||
// 更新时间范围
|
||||
const updateTimeRange = (id: string, field: "start" | "end", value: string) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
timeRanges: formData.timeRanges.map((range) => (range.id === id ? { ...range, [field]: value } : range)),
|
||||
})
|
||||
}
|
||||
|
||||
// 添加关键词
|
||||
const addKeyword = () => {
|
||||
if (newKeyword.trim() && !formData.keywordFilters.includes(newKeyword.trim())) {
|
||||
setFormData({
|
||||
...formData,
|
||||
keywordFilters: [...formData.keywordFilters, newKeyword.trim()],
|
||||
})
|
||||
setNewKeyword("")
|
||||
}
|
||||
}
|
||||
|
||||
// 删除关键词
|
||||
const removeKeyword = (keyword: string) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
keywordFilters: formData.keywordFilters.filter((k) => k !== keyword),
|
||||
})
|
||||
}
|
||||
|
||||
// 切换内容类型
|
||||
const toggleContentType = (typeId: string) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
contentTypes: formData.contentTypes.includes(typeId)
|
||||
? formData.contentTypes.filter((id) => id !== typeId)
|
||||
: [...formData.contentTypes, typeId],
|
||||
})
|
||||
}
|
||||
|
||||
// 切换好友分组
|
||||
const toggleFriendGroup = (groupId: string) => {
|
||||
if (groupId === "all") {
|
||||
setFormData({
|
||||
...formData,
|
||||
friendGroups: ["all"],
|
||||
excludedGroups: [],
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 如果当前包含"all",则移除它
|
||||
let newGroups = formData.friendGroups.filter((id) => id !== "all")
|
||||
|
||||
if (formData.friendGroups.includes(groupId)) {
|
||||
newGroups = newGroups.filter((id) => id !== groupId)
|
||||
// 如果没有选择任何组,默认回到"all"
|
||||
if (newGroups.length === 0) {
|
||||
newGroups = ["all"]
|
||||
}
|
||||
} else {
|
||||
newGroups.push(groupId)
|
||||
}
|
||||
|
||||
setFormData({
|
||||
...formData,
|
||||
friendGroups: newGroups,
|
||||
})
|
||||
}
|
||||
|
||||
// 切换排除分组
|
||||
const toggleExcludedGroup = (groupId: string) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
excludedGroups: formData.excludedGroups.includes(groupId)
|
||||
? formData.excludedGroups.filter((id) => id !== groupId)
|
||||
: [...formData.excludedGroups, groupId],
|
||||
})
|
||||
}
|
||||
|
||||
// 处理表单提交
|
||||
const handleSubmit = () => {
|
||||
onSave(formData)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>点赞规则设置</CardTitle>
|
||||
<CardDescription>设定自动点赞的规则和时间间隔</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* 基本设置 */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label htmlFor="enableAutoLike" className="font-medium">
|
||||
启用自动点赞
|
||||
</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-4 w-4 text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>开启后系统将根据设置自动为朋友圈点赞</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<Switch
|
||||
id="enableAutoLike"
|
||||
checked={formData.enableAutoLike}
|
||||
onCheckedChange={(checked) => setFormData({ ...formData, enableAutoLike: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="intervalType" className="font-medium">
|
||||
点赞间隔设置
|
||||
</Label>
|
||||
<Select
|
||||
value={formData.randomizeInterval ? "random" : "fixed"}
|
||||
onValueChange={(value) => setFormData({ ...formData, randomizeInterval: value === "random" })}
|
||||
disabled={!formData.enableAutoLike}
|
||||
>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="选择间隔类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="fixed">固定间隔</SelectItem>
|
||||
<SelectItem value="random">随机间隔</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{formData.randomizeInterval ? (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="minInterval" className="text-sm">
|
||||
最小间隔(分钟)
|
||||
</Label>
|
||||
<span className="text-sm text-muted-foreground">{formData.minInterval}分钟</span>
|
||||
</div>
|
||||
<Slider
|
||||
id="minInterval"
|
||||
min={1}
|
||||
max={30}
|
||||
step={1}
|
||||
value={[formData.minInterval || 5]}
|
||||
onValueChange={(value) => setFormData({ ...formData, minInterval: value[0] })}
|
||||
disabled={!formData.enableAutoLike}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="maxInterval" className="text-sm">
|
||||
最大间隔(分钟)
|
||||
</Label>
|
||||
<span className="text-sm text-muted-foreground">{formData.maxInterval}分钟</span>
|
||||
</div>
|
||||
<Slider
|
||||
id="maxInterval"
|
||||
min={formData.minInterval || 5}
|
||||
max={120}
|
||||
step={1}
|
||||
value={[formData.maxInterval || 30]}
|
||||
onValueChange={(value) => setFormData({ ...formData, maxInterval: value[0] })}
|
||||
disabled={!formData.enableAutoLike}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="likeInterval" className="text-sm">
|
||||
点赞间隔(分钟)
|
||||
</Label>
|
||||
<span className="text-sm text-muted-foreground">{formData.likeInterval}分钟</span>
|
||||
</div>
|
||||
<Slider
|
||||
id="likeInterval"
|
||||
min={1}
|
||||
max={60}
|
||||
step={1}
|
||||
value={[formData.likeInterval]}
|
||||
onValueChange={(value) => setFormData({ ...formData, likeInterval: value[0] })}
|
||||
disabled={!formData.enableAutoLike}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="maxLikesPerDay" className="font-medium">
|
||||
每日最大点赞数
|
||||
</Label>
|
||||
<span className="text-sm text-muted-foreground">{formData.maxLikesPerDay}个</span>
|
||||
</div>
|
||||
<Slider
|
||||
id="maxLikesPerDay"
|
||||
min={10}
|
||||
max={200}
|
||||
step={10}
|
||||
value={[formData.maxLikesPerDay]}
|
||||
onValueChange={(value) => setFormData({ ...formData, maxLikesPerDay: value[0] })}
|
||||
disabled={!formData.enableAutoLike}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label htmlFor="likeOldContent" className="font-medium">
|
||||
点赞历史内容
|
||||
</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-4 w-4 text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>开启后系统将点赞好友的历史朋友圈内容</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<Switch
|
||||
id="likeOldContent"
|
||||
checked={formData.likeOldContent}
|
||||
onCheckedChange={(checked) => setFormData({ ...formData, likeOldContent: checked })}
|
||||
disabled={!formData.enableAutoLike}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 内容类型设置 */}
|
||||
<div className="space-y-3 pt-4 border-t">
|
||||
<Label className="font-medium">点赞内容类型</Label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||
{contentTypeOptions.map((type) => (
|
||||
<div key={type.id} className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={`content-${type.id}`}
|
||||
checked={formData.contentTypes.includes(type.id)}
|
||||
onCheckedChange={() => toggleContentType(type.id)}
|
||||
disabled={!formData.enableAutoLike}
|
||||
/>
|
||||
<Label htmlFor={`content-${type.id}`} className="text-sm">
|
||||
{type.label}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 关键词过滤 */}
|
||||
<div className="space-y-3 pt-4 border-t">
|
||||
<Label className="font-medium">关键词过滤</Label>
|
||||
<div className="flex space-x-2">
|
||||
<Input
|
||||
placeholder="输入关键词"
|
||||
value={newKeyword}
|
||||
onChange={(e) => setNewKeyword(e.target.value)}
|
||||
className="flex-1"
|
||||
disabled={!formData.enableAutoLike}
|
||||
/>
|
||||
<Button type="button" onClick={addKeyword} disabled={!formData.enableAutoLike || !newKeyword.trim()}>
|
||||
添加
|
||||
</Button>
|
||||
</div>
|
||||
{formData.keywordFilters.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{formData.keywordFilters.map((keyword) => (
|
||||
<Badge key={keyword} variant="secondary" className="flex items-center gap-1">
|
||||
{keyword}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-4 w-4 p-0 hover:bg-transparent"
|
||||
onClick={() => removeKeyword(keyword)}
|
||||
disabled={!formData.enableAutoLike}
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
<span className="sr-only">Remove</span>
|
||||
</Button>
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<p className="text-xs text-muted-foreground">添加关键词后,系统将只对包含这些关键词的内容进行点赞</p>
|
||||
</div>
|
||||
|
||||
{/* 好友分组设置 */}
|
||||
<div className="space-y-3 pt-4 border-t">
|
||||
<Label className="font-medium">好友分组筛选</Label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||
{friendGroupOptions.map((group) => (
|
||||
<div key={group.id} className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={`group-${group.id}`}
|
||||
checked={formData.friendGroups.includes(group.id)}
|
||||
onCheckedChange={() => toggleFriendGroup(group.id)}
|
||||
disabled={!formData.enableAutoLike || (group.id !== "all" && formData.friendGroups.includes("all"))}
|
||||
/>
|
||||
<Label htmlFor={`group-${group.id}`} className="text-sm">
|
||||
{group.label}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{!formData.friendGroups.includes("all") && (
|
||||
<div className="mt-4">
|
||||
<Label className="font-medium text-sm">排除分组</Label>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2 mt-2">
|
||||
{friendGroupOptions
|
||||
.filter((group) => group.id !== "all" && !formData.friendGroups.includes(group.id))
|
||||
.map((group) => (
|
||||
<div key={`exclude-${group.id}`} className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={`exclude-${group.id}`}
|
||||
checked={formData.excludedGroups.includes(group.id)}
|
||||
onCheckedChange={() => toggleExcludedGroup(group.id)}
|
||||
disabled={!formData.enableAutoLike}
|
||||
/>
|
||||
<Label htmlFor={`exclude-${group.id}`} className="text-sm">
|
||||
{group.label}
|
||||
</Label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 时间范围设置 */}
|
||||
<div className="space-y-3 pt-4 border-t">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="font-medium">点赞时间段</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={addTimeRange}
|
||||
disabled={!formData.enableAutoLike || formData.timeRanges.length >= 5}
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
添加时间段
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{formData.timeRanges.map((range) => (
|
||||
<div key={range.id} className="flex items-center space-x-2">
|
||||
<Input
|
||||
type="time"
|
||||
value={range.start}
|
||||
onChange={(e) => updateTimeRange(range.id, "start", e.target.value)}
|
||||
className="w-32"
|
||||
disabled={!formData.enableAutoLike}
|
||||
/>
|
||||
<span>至</span>
|
||||
<Input
|
||||
type="time"
|
||||
value={range.end}
|
||||
onChange={(e) => updateTimeRange(range.id, "end", e.target.value)}
|
||||
className="w-32"
|
||||
disabled={!formData.enableAutoLike}
|
||||
/>
|
||||
{formData.timeRanges.length > 1 && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => removeTimeRange(range.id)}
|
||||
disabled={!formData.enableAutoLike}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 text-destructive" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">设置点赞的时间段,系统将在这些时间段内执行点赞任务</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button onClick={handleSubmit}>保存并继续</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
"use client"
|
||||
|
||||
import { CheckIcon } from "lucide-react"
|
||||
import { cn } from "@/app/lib/utils"
|
||||
|
||||
interface Step {
|
||||
id: string
|
||||
name: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
interface StepIndicatorProps {
|
||||
steps: Step[]
|
||||
currentStep: number
|
||||
onStepClick?: (index: number) => void
|
||||
}
|
||||
|
||||
export function StepIndicator({ steps, currentStep, onStepClick }: StepIndicatorProps) {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<ol className="flex items-center w-full">
|
||||
{steps.map((step, index) => {
|
||||
const isCompleted = index < currentStep
|
||||
const isCurrent = index === currentStep
|
||||
const isClickable = onStepClick && index <= currentStep
|
||||
|
||||
return (
|
||||
<li
|
||||
key={step.id}
|
||||
className={cn("flex items-center space-x-2.5 flex-1", index !== steps.length - 1 ? "relative" : "")}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"flex items-center justify-center w-8 h-8 rounded-full shrink-0 text-sm font-medium",
|
||||
isCompleted
|
||||
? "bg-primary text-primary-foreground"
|
||||
: isCurrent
|
||||
? "bg-primary/20 text-primary border border-primary"
|
||||
: "bg-muted text-muted-foreground",
|
||||
isClickable ? "cursor-pointer" : "",
|
||||
)}
|
||||
onClick={() => isClickable && onStepClick(index)}
|
||||
>
|
||||
{isCompleted ? <CheckIcon className="w-5 h-5" /> : index + 1}
|
||||
</span>
|
||||
<span>
|
||||
<h3
|
||||
className={cn(
|
||||
"font-medium leading-tight",
|
||||
isCompleted || isCurrent ? "text-primary" : "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{step.name}
|
||||
</h3>
|
||||
{step.description && (
|
||||
<p className="text-sm text-muted-foreground hidden md:block">{step.description}</p>
|
||||
)}
|
||||
</span>
|
||||
{index !== steps.length - 1 && (
|
||||
<div
|
||||
className={cn(
|
||||
"absolute top-4 left-8 -translate-y-1/2 w-full h-0.5",
|
||||
isCompleted ? "bg-primary" : "bg-muted",
|
||||
)}
|
||||
style={{ width: "calc(100% - 2rem)" }}
|
||||
></div>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ol>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
243
Cunkebao/app/workspace/auto-like/components/time-settings.tsx
Normal file
243
Cunkebao/app/workspace/auto-like/components/time-settings.tsx
Normal file
@@ -0,0 +1,243 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { Slider } from "@/components/ui/slider"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
|
||||
import { Info, Plus, Trash2 } from "lucide-react"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
|
||||
export interface TimeRange {
|
||||
id: string
|
||||
start: string
|
||||
end: string
|
||||
}
|
||||
|
||||
export interface TimeSettingsData {
|
||||
enableAutoLike: boolean
|
||||
timeRanges: TimeRange[]
|
||||
likeInterval: number
|
||||
randomizeInterval: boolean
|
||||
minInterval?: number
|
||||
maxInterval?: number
|
||||
}
|
||||
|
||||
interface TimeSettingsProps {
|
||||
initialData?: Partial<TimeSettingsData>
|
||||
onSave: (data: TimeSettingsData) => void
|
||||
}
|
||||
|
||||
export function TimeSettings({ initialData, onSave }: TimeSettingsProps) {
|
||||
const [formData, setFormData] = useState<TimeSettingsData>({
|
||||
enableAutoLike: initialData?.enableAutoLike ?? true,
|
||||
timeRanges: initialData?.timeRanges ?? [{ id: "1", start: "06:00", end: "08:00" }],
|
||||
likeInterval: initialData?.likeInterval ?? 15,
|
||||
randomizeInterval: initialData?.randomizeInterval ?? false,
|
||||
minInterval: initialData?.minInterval ?? 5,
|
||||
maxInterval: initialData?.maxInterval ?? 30,
|
||||
})
|
||||
|
||||
// 添加时间范围
|
||||
const addTimeRange = () => {
|
||||
const newId = String(formData.timeRanges.length + 1)
|
||||
setFormData({
|
||||
...formData,
|
||||
timeRanges: [...formData.timeRanges, { id: newId, start: "12:00", end: "14:00" }],
|
||||
})
|
||||
}
|
||||
|
||||
// 删除时间范围
|
||||
const removeTimeRange = (id: string) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
timeRanges: formData.timeRanges.filter((range) => range.id !== id),
|
||||
})
|
||||
}
|
||||
|
||||
// 更新时间范围
|
||||
const updateTimeRange = (id: string, field: "start" | "end", value: string) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
timeRanges: formData.timeRanges.map((range) => (range.id === id ? { ...range, [field]: value } : range)),
|
||||
})
|
||||
}
|
||||
|
||||
// 处理表单提交
|
||||
const handleSubmit = () => {
|
||||
onSave(formData)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>点赞时间设置</CardTitle>
|
||||
<CardDescription>设置自动点赞的时间段和间隔</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* 基本设置 */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label htmlFor="enableAutoLike" className="font-medium">
|
||||
启用自动点赞
|
||||
</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-4 w-4 text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>开启后系统将根据设置自动为朋友圈点赞</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
<Switch
|
||||
id="enableAutoLike"
|
||||
checked={formData.enableAutoLike}
|
||||
onCheckedChange={(checked) => setFormData({ ...formData, enableAutoLike: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 时间范围设置 */}
|
||||
<div className="space-y-3 pt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="font-medium">点赞时间段</Label>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={addTimeRange}
|
||||
disabled={!formData.enableAutoLike || formData.timeRanges.length >= 5}
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
添加时间段
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{formData.timeRanges.map((range) => (
|
||||
<div key={range.id} className="flex items-center space-x-2">
|
||||
<Input
|
||||
type="time"
|
||||
value={range.start}
|
||||
onChange={(e) => updateTimeRange(range.id, "start", e.target.value)}
|
||||
className="w-32"
|
||||
disabled={!formData.enableAutoLike}
|
||||
/>
|
||||
<span>至</span>
|
||||
<Input
|
||||
type="time"
|
||||
value={range.end}
|
||||
onChange={(e) => updateTimeRange(range.id, "end", e.target.value)}
|
||||
className="w-32"
|
||||
disabled={!formData.enableAutoLike}
|
||||
/>
|
||||
{formData.timeRanges.length > 1 && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => removeTimeRange(range.id)}
|
||||
disabled={!formData.enableAutoLike}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 text-destructive" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">设置点赞的时间段,系统将在这些时间段内执行点赞任务</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 pt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="intervalType" className="font-medium">
|
||||
点赞间隔设置
|
||||
</Label>
|
||||
<Select
|
||||
value={formData.randomizeInterval ? "random" : "fixed"}
|
||||
onValueChange={(value) => setFormData({ ...formData, randomizeInterval: value === "random" })}
|
||||
disabled={!formData.enableAutoLike}
|
||||
>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue placeholder="选择间隔类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="fixed">固定间隔</SelectItem>
|
||||
<SelectItem value="random">随机间隔</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{formData.randomizeInterval ? (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="minInterval" className="text-sm">
|
||||
最小间隔(分钟)
|
||||
</Label>
|
||||
<span className="text-sm text-muted-foreground">{formData.minInterval}分钟</span>
|
||||
</div>
|
||||
<Slider
|
||||
id="minInterval"
|
||||
min={1}
|
||||
max={30}
|
||||
step={1}
|
||||
value={[formData.minInterval || 5]}
|
||||
onValueChange={(value) => setFormData({ ...formData, minInterval: value[0] })}
|
||||
disabled={!formData.enableAutoLike}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="maxInterval" className="text-sm">
|
||||
最大间隔(分钟)
|
||||
</Label>
|
||||
<span className="text-sm text-muted-foreground">{formData.maxInterval}分钟</span>
|
||||
</div>
|
||||
<Slider
|
||||
id="maxInterval"
|
||||
min={formData.minInterval || 5}
|
||||
max={120}
|
||||
step={1}
|
||||
value={[formData.maxInterval || 30]}
|
||||
onValueChange={(value) => setFormData({ ...formData, maxInterval: value[0] })}
|
||||
disabled={!formData.enableAutoLike}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="likeInterval" className="text-sm">
|
||||
点赞间隔(分钟)
|
||||
</Label>
|
||||
<span className="text-sm text-muted-foreground">{formData.likeInterval}分钟</span>
|
||||
</div>
|
||||
<Slider
|
||||
id="likeInterval"
|
||||
min={1}
|
||||
max={60}
|
||||
step={1}
|
||||
value={[formData.likeInterval]}
|
||||
onValueChange={(value) => setFormData({ ...formData, likeInterval: value[0] })}
|
||||
disabled={!formData.enableAutoLike}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button onClick={handleSubmit}>下一步</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user