From e3d29f0935d50155caea9f4cb9030c998ec2e1a0 Mon Sep 17 00:00:00 2001
From: Ghost <106998207@qq.com>
Date: Thu, 10 Apr 2025 16:40:30 +0800
Subject: [PATCH] =?UTF-8?q?=E3=80=90=E6=93=8D=E7=9B=98=E6=89=8B=E7=AB=AF?=
=?UTF-8?q?=E3=80=91=E8=87=AA=E5=8A=A8=E7=82=B9=E8=B5=9E=E6=8F=90=E4=BA=A4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Cunkebao/app/workspace/auto-group/loading.tsx | 3 +
Cunkebao/app/workspace/auto-group/page.tsx | 331 ++++------
.../auto-like/components/audience-tags.tsx | 580 ++++-------------
.../auto-like/components/basic-settings.tsx | 201 ++++++
.../components/device-selection-dialog.tsx | 187 ++++++
.../auto-like/components/device-selection.tsx | 452 ++++++-------
.../auto-like/components/like-rules.tsx | 603 +++++++-----------
.../auto-like/components/step-indicator.tsx | 104 ++-
.../auto-like/components/tag-selector.tsx | 243 +++++++
Cunkebao/app/workspace/auto-like/loading.tsx | 3 +
.../app/workspace/auto-like/new/loading.tsx | 3 +
Cunkebao/app/workspace/auto-like/new/page.tsx | 160 +++++
Cunkebao/app/workspace/auto-like/page.tsx | 484 +++++++++++---
.../group-sync/components/basic-settings.tsx | 17 +-
.../components/basic-settings.tsx | 242 +++----
.../components/device-selection-dialog.tsx | 9 +-
.../moments-sync/new/steps/BasicSettings.tsx | 5 +-
.../moments-sync/new/steps/DeviceSelector.tsx | 72 +--
.../traffic-distribution/new/loading.tsx | 4 +-
.../traffic-distribution/new/page.tsx | 58 +-
Cunkebao/lib/toast.ts | 31 +
Server/application/cunkebao/config/route.php | 9 +
.../controller/WorkbenchController.php | 469 ++++++++++++++
.../application/cunkebao/model/Workbench.php | 58 ++
.../cunkebao/model/WorkbenchAutoLike.php | 25 +
.../cunkebao/model/WorkbenchGroupCreate.php | 22 +
.../cunkebao/model/WorkbenchGroupPush.php | 22 +
.../cunkebao/model/WorkbenchMomentsSync.php | 22 +
.../cunkebao/validate/Workbench.php | 128 ++++
29 files changed, 2863 insertions(+), 1684 deletions(-)
create mode 100644 Cunkebao/app/workspace/auto-group/loading.tsx
create mode 100644 Cunkebao/app/workspace/auto-like/components/basic-settings.tsx
create mode 100644 Cunkebao/app/workspace/auto-like/components/device-selection-dialog.tsx
create mode 100644 Cunkebao/app/workspace/auto-like/components/tag-selector.tsx
create mode 100644 Cunkebao/app/workspace/auto-like/loading.tsx
create mode 100644 Cunkebao/app/workspace/auto-like/new/loading.tsx
create mode 100644 Cunkebao/app/workspace/auto-like/new/page.tsx
create mode 100644 Cunkebao/lib/toast.ts
create mode 100644 Server/application/cunkebao/controller/WorkbenchController.php
create mode 100644 Server/application/cunkebao/model/Workbench.php
create mode 100644 Server/application/cunkebao/model/WorkbenchAutoLike.php
create mode 100644 Server/application/cunkebao/model/WorkbenchGroupCreate.php
create mode 100644 Server/application/cunkebao/model/WorkbenchGroupPush.php
create mode 100644 Server/application/cunkebao/model/WorkbenchMomentsSync.php
create mode 100644 Server/application/cunkebao/validate/Workbench.php
diff --git a/Cunkebao/app/workspace/auto-group/loading.tsx b/Cunkebao/app/workspace/auto-group/loading.tsx
new file mode 100644
index 00000000..f15322a8
--- /dev/null
+++ b/Cunkebao/app/workspace/auto-group/loading.tsx
@@ -0,0 +1,3 @@
+export default function Loading() {
+ return null
+}
diff --git a/Cunkebao/app/workspace/auto-group/page.tsx b/Cunkebao/app/workspace/auto-group/page.tsx
index 9ea241ec..bba756c8 100644
--- a/Cunkebao/app/workspace/auto-group/page.tsx
+++ b/Cunkebao/app/workspace/auto-group/page.tsx
@@ -1,19 +1,16 @@
"use client"
-import { useState, useCallback } from "react"
+import { useState } from "react"
import { Button } from "@/components/ui/button"
-import { PlusCircle, ArrowLeft } from "lucide-react"
-import { StepIndicator } from "./components/step-indicator"
-import { GroupSettings } from "./components/group-settings"
-import { DeviceSelection } from "./components/device-selection"
-import { TagSelection } from "./components/tag-selection"
-import { useToast } from "@/components/ui/use-toast"
-
-// 保留原有的卡片列表视图
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { ChevronLeft, Plus, Filter, Search, RefreshCw, MoreVertical, Clock, Edit, Trash2, Eye } from "lucide-react"
+import { Card } from "@/components/ui/card"
+import { Input } from "@/components/ui/input"
+import Link from "next/link"
import { Badge } from "@/components/ui/badge"
-import { Users, Settings, RefreshCcw } from "lucide-react"
+import { useRouter } from "next/navigation"
+import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
+import { Switch } from "@/components/ui/switch"
+import { Users, Settings } from "lucide-react"
interface Plan {
id: string
@@ -49,97 +46,30 @@ const mockPlans: Plan[] = [
},
]
-const steps = [
- { title: "群配置", description: "设置群人数与组数" },
- { title: "设备选择", description: "选择执行设备" },
- { title: "人群标签", description: "选择目标人群" },
-]
-
export default function AutoGroupPage() {
+ const router = useRouter()
const [isCreating, setIsCreating] = useState(false)
- const [currentStep, setCurrentStep] = useState(0)
- const { toast } = useToast()
+ const [plans, setPlans] = useState(mockPlans)
- const [formData, setFormData] = useState({
- name: "新建群计划",
- fixedWechatIds: [] as string[],
- groupingOption: "all" as "all" | "fixed",
- fixedGroupCount: 5,
- selectedDevices: [] as string[],
- audienceTags: [] as string[],
- trafficTags: [] as string[],
- matchLogic: "or" as "and" | "or",
- excludeTags: ["已拉群"] as string[],
- })
+ const handleDelete = (planId: string) => {
+ setPlans(plans.filter((plan) => plan.id !== planId))
+ }
- const handleStepClick = useCallback((step: number) => {
- setCurrentStep(step)
- }, [])
+ const handleEdit = (planId: string) => {
+ router.push(`/workspace/auto-group/${planId}/edit`)
+ }
- const handleNext = useCallback(() => {
- setCurrentStep((prev) => prev + 1)
- }, [])
+ const handleView = (planId: string) => {
+ router.push(`/workspace/auto-group/${planId}`)
+ }
- const handlePrevious = useCallback(() => {
- setCurrentStep((prev) => prev - 1)
- }, [])
-
- const handleComplete = useCallback(() => {
- // 这里可以添加表单提交逻辑
- console.log("Form submitted:", formData)
- toast({
- title: "计划创建成功",
- description: `已成功创建"${formData.name}"计划`,
- })
- setIsCreating(false)
- setCurrentStep(0)
- // 重置表单数据
- setFormData({
- name: "新建群计划",
- fixedWechatIds: [],
- groupingOption: "all",
- fixedGroupCount: 5,
- selectedDevices: [],
- audienceTags: [],
- trafficTags: [],
- matchLogic: "or",
- excludeTags: ["已拉群"],
- })
- }, [formData, toast])
-
- const handleCancel = useCallback(() => {
- setIsCreating(false)
- setCurrentStep(0)
- }, [])
-
- // 使用useCallback包装回调函数,避免不必要的重新创建
- const handleGroupSettingsChange = useCallback(
- (values: {
- name: string
- fixedWechatIds: string[]
- groupingOption: "all" | "fixed"
- fixedGroupCount: number
- }) => {
- setFormData((prev) => ({ ...prev, ...values }))
- },
- [],
- )
-
- const handleDevicesChange = useCallback((devices: string[]) => {
- setFormData((prev) => ({ ...prev, selectedDevices: devices }))
- }, [])
-
- const handleTagsChange = useCallback(
- (values: {
- audienceTags: string[]
- trafficTags: string[]
- matchLogic: "and" | "or"
- excludeTags: string[]
- }) => {
- setFormData((prev) => ({ ...prev, ...values }))
- },
- [],
- )
+ const togglePlanStatus = (planId: string) => {
+ setPlans(
+ plans.map((plan) =>
+ plan.id === planId ? { ...plan, status: plan.status === "running" ? "stopped" : "running" } : plan,
+ ),
+ )
+ }
const getStatusColor = (status: Plan["status"]) => {
switch (status) {
@@ -167,129 +97,114 @@ export default function AutoGroupPage() {
}
}
- if (isCreating) {
- return (
-
-
-
-
-
-
-
- {currentStep === 0 && (
-
- )}
-
- {currentStep === 1 && (
-
- )}
-
- {currentStep === 2 && (
-
- )}
-
- )
- }
-
return (
-
-
-
-
微信自动拉群
-
+
+
-
-
- 进行中
- 已完成
-
+
+
+
+
-
-
- {mockPlans.map((plan) => (
-
-
-
- {plan.name}
- {getStatusText(plan.status)}
-
-
-
-
-
-
- 已建群数:{plan.groupCount}
-
-
-
- 群规模:{plan.groupSize}
-
-
-
- 更新时间:{plan.lastUpdated}
-
-
-
+
+ {plans.map((plan) => (
+
+
+
+
+
+
{plan.name}
+
+ {getStatusText(plan.status)}
+
+
+
+ togglePlanStatus(plan.id)} />
+
+
+
+
+
+ handleView(plan.id)}>
+
+ 查看
+
+ handleEdit(plan.id)}>
+
+ 编辑
+
+ handleDelete(plan.id)}>
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+ 已建群数:{plan.groupCount}
+
+
+
+ 群规模:{plan.groupSize}人/群
+
+
+
+
总人数:{plan.totalFriends}人
+
+
{plan.tags.map((tag) => (
{tag}
))}
-
-
-
-
-
-
- ))}
-
-
+
+
+
-
- 暂无已完成的计划
-
-
+
+
+
+ 更新时间:{plan.lastUpdated}
+
+
+
+ ))}
+
)
}
-
diff --git a/Cunkebao/app/workspace/auto-like/components/audience-tags.tsx b/Cunkebao/app/workspace/auto-like/components/audience-tags.tsx
index ce3b134b..93c8bc37 100644
--- a/Cunkebao/app/workspace/auto-like/components/audience-tags.tsx
+++ b/Cunkebao/app/workspace/auto-like/components/audience-tags.tsx
@@ -1,51 +1,13 @@
"use client"
-import { useState, useEffect } from "react"
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { useState } from "react"
+import { Card, CardContent } 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
-}
+import { Check, Plus, Tag, X } from "lucide-react"
+import { Input } from "@/components/ui/input"
+import { Badge } from "@/components/ui/badge"
export interface AudienceTagsData {
selectedTags: string[]
@@ -53,457 +15,147 @@ export interface AudienceTagsData {
}
interface AudienceTagsProps {
- initialData?: Partial
+ initialData: AudienceTagsData
onSave: (data: AudienceTagsData) => void
onBack: () => void
}
+// 模拟标签数据
+const predefinedTags = [
+ "高意向",
+ "中意向",
+ "低意向",
+ "新客户",
+ "老客户",
+ "VIP客户",
+ "男性",
+ "女性",
+ "年轻人",
+ "中年人",
+ "老年人",
+ "城市",
+ "农村",
+ "高收入",
+ "中等收入",
+ "低收入",
+]
+
export function AudienceTags({ initialData, onSave, onBack }: AudienceTagsProps) {
- const [tagGroups, setTagGroups] = useState([])
- const [users, setUsers] = useState([])
- const [selectedTags, setSelectedTags] = useState(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("custom")
- const [isCreateTagDialogOpen, setIsCreateTagDialogOpen] = useState(false)
+ const [formData, setFormData] = useState(initialData)
+ const [newTag, setNewTag] = useState("")
- // 模拟获取标签组和用户数据
- useEffect(() => {
- const fetchData = async () => {
- await new Promise((resolve) => setTimeout(resolve, 500))
+ const toggleTag = (tag: string) => {
+ const newSelectedTags = formData.selectedTags.includes(tag)
+ ? formData.selectedTags.filter((t) => t !== tag)
+ : [...formData.selectedTags, tag]
- // 模拟标签组数据
- 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 },
- ],
- },
- ]
+ setFormData({ ...formData, selectedTags: newSelectedTags })
+ }
- 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)}小时前`,
- }
+ const addCustomTag = () => {
+ if (newTag.trim() && !predefinedTags.includes(newTag) && !formData.selectedTags.includes(newTag)) {
+ setFormData({
+ ...formData,
+ selectedTags: [...formData.selectedTags, newTag.trim()],
})
-
- setUsers(mockUsers)
+ setNewTag("")
}
-
- 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 (
-
-
-
- 指定点赞的人群标签
- 选择特定标签,只对带有这些标签的好友朋友圈进行点赞
-
-
- {/* 标签选择逻辑 */}
-
-
+
+
+
+
+
+
选择需要点赞的人群标签
+
+
+ {predefinedTags.map((tag) => (
+ toggleTag(tag)}
+ >
+ {formData.selectedTags.includes(tag) && }
+ {tag}
+
+ ))}
+
+
+
+
+
+ setNewTag(e.target.value)}
+ className="pl-9"
+ placeholder="添加自定义标签"
+ onKeyDown={(e) => e.key === "Enter" && addCustomTag()}
+ />
+
+
+
+
+
+
+
+
选择多个标签之间的匹配关系
+
setTagOperator(value as "and" | "or")}
- className="flex space-x-4"
+ value={formData.tagOperator}
+ onValueChange={(value) => setFormData({ ...formData, tagOperator: value as "and" | "or" })}
+ className="flex flex-col space-y-2"
>
-
-
+
+
-
-
+
+
- {/* 已选标签展示 */}
-
-
-
- {selectedTags.length === 0 ? (
-
未选择任何标签,将对所有好友点赞
+
+
+
+ {formData.selectedTags.length === 0 ? (
+
未选择任何标签
) : (
- selectedTags.map((tagId) => {
- const tag = allTags.find((t) => t.id === tagId)
- return tag ? (
-
- {tag.name}
-
-
- {/* 标签搜索和分类 */}
-
-
-
-
- setSearchQuery(e.target.value)}
- className="pl-9"
- />
-
-
-
-
-
-
- 全部
- 职业
- 兴趣
- 年龄
- 消费
- 互动
- 自定义
-
-
-
-
- {tabFilteredTagGroups.map((group) => (
-
-
-
-
- {group.name}
-
- {group.description}
-
-
- {group.tags.map((tag) => (
- toggleTag(tag.id)}
- >
- {tag.name}
- ({tag.count})
-
- ))}
-
+ ))}
- ))}
-
- {tabFilteredTagGroups.length === 0 && (
-
未找到符合条件的标签
)}
- {/* 预览匹配的用户 */}
-
-
-
-
- 匹配的好友预览
-
- 共 {filteredUsers.length} 人
-
-
-
- {filteredUsers.slice(0, 20).map((user) => (
-
-
-
-
- {user.name.substring(0, 2)}
-
-
-
{user.name}
-
- {user.profession} · {user.region} · 最近活跃: {user.lastActive}
-
-
-
-
- {user.tags.slice(0, 2).map((tagId) => {
- const tag = allTags.find((t) => t.id === tagId)
- return tag ? (
-
- {tag.name}
-
- ) : null
- })}
- {user.tags.length > 2 && (
-
- +{user.tags.length - 2}
-
- )}
-
-
- ))}
- {filteredUsers.length === 0 && (
-
未找到匹配的好友
- )}
- {filteredUsers.length > 20 && (
-
- 显示前 20 位好友,共 {filteredUsers.length} 位匹配
-
- )}
-
-
+
+
+ 上一步
+
+ onSave(formData)} disabled={formData.selectedTags.length === 0}>
+ 完成设置
+
-
-
-
-
-
- 返回上一步
-
- 完成设置
-
-
+
+
+
)
}
-
diff --git a/Cunkebao/app/workspace/auto-like/components/basic-settings.tsx b/Cunkebao/app/workspace/auto-like/components/basic-settings.tsx
new file mode 100644
index 00000000..6ec9063e
--- /dev/null
+++ b/Cunkebao/app/workspace/auto-like/components/basic-settings.tsx
@@ -0,0 +1,201 @@
+"use client"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import { Switch } from "@/components/ui/switch"
+import { Minus, Plus } from "lucide-react"
+
+interface BasicSettingsProps {
+ formData: {
+ taskName: string
+ likeInterval: number
+ maxLikesPerDay: number
+ timeRange: { start: string; end: string }
+ contentTypes: string[]
+ enabled: boolean
+ }
+ onChange: (data: Partial
) => void
+ onNext: () => void
+}
+
+export function BasicSettings({ formData, onChange, onNext }: BasicSettingsProps) {
+ const handleContentTypeChange = (type: string) => {
+ const currentTypes = [...formData.contentTypes]
+ if (currentTypes.includes(type)) {
+ onChange({ contentTypes: currentTypes.filter((t) => t !== type) })
+ } else {
+ onChange({ contentTypes: [...currentTypes, type] })
+ }
+ }
+
+ const incrementInterval = () => {
+ onChange({ likeInterval: Math.min(formData.likeInterval + 5, 60) })
+ }
+
+ const decrementInterval = () => {
+ onChange({ likeInterval: Math.max(formData.likeInterval - 5, 5) })
+ }
+
+ const incrementMaxLikes = () => {
+ onChange({ maxLikesPerDay: Math.min(formData.maxLikesPerDay + 10, 200) })
+ }
+
+ const decrementMaxLikes = () => {
+ onChange({ maxLikesPerDay: Math.max(formData.maxLikesPerDay - 10, 10) })
+ }
+
+ return (
+
+
+
+ onChange({ taskName: e.target.value })}
+ className="h-12 rounded-xl border-gray-200"
+ />
+
+
+
+
+
+
+
+
+
+
onChange({ likeInterval: Number.parseInt(e.target.value) || 5 })}
+ className="h-12 rounded-none border-x-0 border-gray-200 text-center"
+ />
+
+ 分钟
+
+
+
+
+
+
+
设置两次点赞之间的最小时间间隔
+
+
+
+
+
+
+
+
+
+
onChange({ maxLikesPerDay: Number.parseInt(e.target.value) || 10 })}
+ className="h-12 rounded-none border-x-0 border-gray-200 text-center"
+ />
+
+ 次/天
+
+
+
+
+
+
+
设置每天最多点赞的次数
+
+
+
+
+
+
设置每天可以点赞的时间段
+
+
+
+
+
+ {[
+ { id: "text", label: "文字" },
+ { id: "image", label: "图片" },
+ { id: "video", label: "视频" },
+ ].map((type) => (
+
handleContentTypeChange(type.id)}
+ >
+ {type.label}
+
+ ))}
+
+
选择要点赞的内容类型
+
+
+
+
+ onChange({ enabled: checked })}
+ />
+
+
+
+ 下一步
+
+
+ )
+}
diff --git a/Cunkebao/app/workspace/auto-like/components/device-selection-dialog.tsx b/Cunkebao/app/workspace/auto-like/components/device-selection-dialog.tsx
new file mode 100644
index 00000000..407654ed
--- /dev/null
+++ b/Cunkebao/app/workspace/auto-like/components/device-selection-dialog.tsx
@@ -0,0 +1,187 @@
+"use client"
+
+import { useState, useEffect } from "react"
+import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"
+import { Input } from "@/components/ui/input"
+import { Button } from "@/components/ui/button"
+import { Badge } from "@/components/ui/badge"
+import { Search, RefreshCw, Loader2 } from "lucide-react"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+import { ScrollArea } from "@/components/ui/scroll-area"
+import { Checkbox } from "@/components/ui/checkbox"
+import { api } from "@/lib/api"
+
+interface ServerDevice {
+ id: number
+ imei: string
+ memo: string
+ wechatId: string
+ alive: number
+ totalFriend: number
+}
+
+interface Device {
+ id: number
+ name: string
+ imei: string
+ wxid: string
+ status: "online" | "offline"
+ totalFriend: number
+}
+
+interface DeviceSelectionDialogProps {
+ open: boolean
+ onOpenChange: (open: boolean) => void
+ selectedDevices: number[]
+ onSelect: (devices: number[]) => void
+}
+
+export function DeviceSelectionDialog({ open, onOpenChange, selectedDevices, onSelect }: DeviceSelectionDialogProps) {
+ const [searchQuery, setSearchQuery] = useState("")
+ const [statusFilter, setStatusFilter] = useState("all")
+ const [devices, setDevices] = useState([])
+ const [loading, setLoading] = useState(false)
+ const [tempSelectedDevices, setTempSelectedDevices] = useState(selectedDevices)
+
+ useEffect(() => {
+ if (open) {
+ setTempSelectedDevices(selectedDevices)
+ fetchDevices()
+ }
+ }, [open, selectedDevices])
+
+ const fetchDevices = async () => {
+ try {
+ setLoading(true)
+ const response = await api.get<{code: number, msg: string, data: {list: ServerDevice[], total: number}}>('/v1/devices?page=1&limit=100')
+
+ if (response.code === 200 && response.data.list) {
+ const transformedDevices: Device[] = response.data.list.map(device => ({
+ id: device.id,
+ name: device.memo || device.imei || '',
+ imei: device.imei || '',
+ wxid: device.wechatId || '',
+ status: device.alive === 1 ? "online" : "offline",
+ totalFriend: device.totalFriend || 0
+ }))
+ setDevices(transformedDevices)
+ }
+ } catch (error) {
+ console.error('获取设备列表失败:', error)
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ const handleRefresh = () => {
+ fetchDevices()
+ }
+
+ const handleDeviceToggle = (deviceId: number, checked: boolean) => {
+ if (checked) {
+ setTempSelectedDevices(prev => [...prev, deviceId])
+ } else {
+ setTempSelectedDevices(prev => prev.filter(id => id !== deviceId))
+ }
+ }
+
+ const handleConfirm = () => {
+ onSelect(tempSelectedDevices)
+ onOpenChange(false)
+ }
+
+ const handleCancel = () => {
+ setTempSelectedDevices(selectedDevices)
+ onOpenChange(false)
+ }
+
+ const filteredDevices = devices.filter((device) => {
+ const searchLower = searchQuery.toLowerCase()
+ const matchesSearch =
+ (device.name || '').toLowerCase().includes(searchLower) ||
+ (device.imei || '').toLowerCase().includes(searchLower) ||
+ (device.wxid || '').toLowerCase().includes(searchLower)
+
+ const matchesStatus =
+ statusFilter === "all" ||
+ (statusFilter === "online" && device.status === "online") ||
+ (statusFilter === "offline" && device.status === "offline")
+
+ return matchesSearch && matchesStatus
+ })
+
+ return (
+
+ )
+}
diff --git a/Cunkebao/app/workspace/auto-like/components/device-selection.tsx b/Cunkebao/app/workspace/auto-like/components/device-selection.tsx
index dbb80f70..6f89531e 100644
--- a/Cunkebao/app/workspace/auto-like/components/device-selection.tsx
+++ b/Cunkebao/app/workspace/auto-like/components/device-selection.tsx
@@ -1,36 +1,13 @@
"use client"
-import { useState, useEffect } from "react"
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { useState } from "react"
+import { Card, CardContent } 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 { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from "@/components/ui/dropdown-menu"
+import { CheckCircle2, ChevronDown, ChevronUp, Smartphone } from "lucide-react"
+import { ScrollArea } from "@/components/ui/scroll-area"
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[]
@@ -39,256 +16,213 @@ export interface DeviceSelectionData {
}
interface DeviceSelectionProps {
- initialData?: Partial
+ initialData: DeviceSelectionData
onSave: (data: DeviceSelectionData) => void
onBack: () => void
}
+// 模拟设备数据
+const mockDevices = [
+ { id: "1", name: "iPhone 13", status: "online", lastActive: "刚刚" },
+ { id: "2", name: "华为 P40", status: "offline", lastActive: "3小时前" },
+ { id: "3", name: "小米 11", status: "online", lastActive: "1小时前" },
+ { id: "4", name: "OPPO Find X3", status: "offline", lastActive: "昨天" },
+ { id: "5", name: "vivo X60", status: "online", lastActive: "刚刚" },
+]
+
+// 模拟数据库选项
+const databaseOptions = [
+ { id: "all", name: "全部客户" },
+ { id: "new", name: "新客户" },
+ { id: "vip", name: "VIP客户" },
+]
+
+// 模拟用户群体选项
+const audienceOptions = [
+ { id: "all", name: "全部好友" },
+ { id: "active", name: "活跃好友" },
+ { id: "inactive", name: "不活跃好友" },
+ { id: "recent", name: "最近添加" },
+]
+
export function DeviceSelection({ initialData, onSave, onBack }: DeviceSelectionProps) {
- const [devices, setDevices] = useState([])
- const [databases, setDatabases] = useState([])
- const [audienceGroups, setAudienceGroups] = useState([])
- const [selectedDevices, setSelectedDevices] = useState(initialData?.selectedDevices || [])
- const [selectedDatabase, setSelectedDatabase] = useState(initialData?.selectedDatabase || "")
- const [selectedAudience, setSelectedAudience] = useState(initialData?.selectedAudience || "")
- const [searchQuery, setSearchQuery] = useState("")
- const [activeTab, setActiveTab] = useState("all")
+ const [formData, setFormData] = useState(initialData)
+ const [devices, setDevices] = useState(mockDevices)
+ const [showAllDevices, setShowAllDevices] = useState(false)
- // 模拟获取设备数据
- 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 toggleDevice = (deviceId: string) => {
+ const newSelectedDevices = formData.selectedDevices.includes(deviceId)
+ ? formData.selectedDevices.filter((id) => id !== deviceId)
+ : [...formData.selectedDevices, deviceId]
- // 模拟数据库数据
- 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],
- )
+ setFormData({ ...formData, selectedDevices: newSelectedDevices })
}
- // 保存选择的设备
- const handleSave = () => {
- onSave({
- selectedDevices,
- selectedDatabase,
- selectedAudience,
- })
+ const selectAllDevices = () => {
+ const allDeviceIds = devices.map((device) => device.id)
+ setFormData({ ...formData, selectedDevices: allDeviceIds })
}
+ const clearDeviceSelection = () => {
+ setFormData({ ...formData, selectedDevices: [] })
+ }
+
+ // 用于显示的设备
+ const displayedDevices = showAllDevices ? devices : devices.slice(0, 3)
+
return (
-
-
- 选择执行点赞任务的设备
- 选择要执行自动点赞任务的设备、数据库和目标人群
-
-
- {/* 设备筛选和搜索 */}
-
-
-
-
-
-
setSearchQuery(e.target.value)}
- className="pl-9"
- />
+
+
+
+
+
+
+
+
+ 全选
+
+
+ 清空
+
+
-
-
+
+
+ {displayedDevices.map((device) => (
+
toggleDevice(device.id)}
+ >
+
+
+
+
+
+
{device.name}
+
+
+ {device.status === "online" ? "在线" : "离线"}
+
+ {device.lastActive}
+
+
+
+
+ {formData.selectedDevices.includes(device.id) &&
}
+
+ ))}
+
+ {devices.length > 3 && (
+
setShowAllDevices(!showAllDevices)}
+ >
+ {showAllDevices ? (
+ <>
+
+ 收起
+ >
+ ) : (
+ <>
+
+ 展开更多({devices.length - 3}台)
+ >
+ )}
+
+ )}
+
+
+
+
+
+
+
选择需要点赞的目标客户群体
+
+
+
+
+ {databaseOptions.find((option) => option.id === formData.selectedDatabase)?.name ||
+ "选择客户数据库"}
+
+
+
+
+
+ {databaseOptions.map((option) => (
+ setFormData({ ...formData, selectedDatabase: option.id })}
+ className="flex items-center justify-between cursor-pointer"
+ >
+ {option.name}
+ {formData.selectedDatabase === option.id && }
+
+ ))}
+
+
+
+
+
+
+
+
选择需要点赞的好友范围
+
+
+
+
+ {audienceOptions.find((option) => option.id === formData.selectedAudience)?.name ||
+ "选择好友范围"}
+
+
+
+
+
+ {audienceOptions.map((option) => (
+ setFormData({ ...formData, selectedAudience: option.id })}
+ className="flex items-center justify-between cursor-pointer"
+ >
+ {option.name}
+ {formData.selectedAudience === option.id && }
+
+ ))}
+
+
+
+
+
+
+
+
+ 上一步
+
+ onSave(formData)}
+ disabled={
+ formData.selectedDevices.length === 0 || !formData.selectedDatabase || !formData.selectedAudience
+ }
+ >
+ 下一步
-
- {/* 设备分类标签页 */}
-
-
- 全部设备
- 已选择 ({selectedDevices.length})
- 在线设备
- 离线设备
-
-
-
- {/* 设备列表 */}
-
- {filteredDevices.map((device) => (
-
-
-
handleDeviceSelect(device.id)}
- />
-
-
-
-
- {device.name}
-
-
- {device.status === "online" ? "在线" : "离线"}
-
-
-
微信号: {device.wechatId}
-
-
-
- ))}
-
- {filteredDevices.length === 0 && (
-
未找到符合条件的设备
- )}
-
-
-
- {/* 数据库选择 */}
-
-
-
-
-
-
- {databases.map((db) => (
-
-
-
-
-
{db.description}
-
-
- ))}
-
-
-
- {/* 目标人群选择 */}
-
-
-
-
-
-
- {audienceGroups.map((group) => (
-
-
-
-
-
{group.description}
-
-
- ))}
-
-
-
-
- 返回上一步
-
-
- 完成设置
-
-
)
}
-
diff --git a/Cunkebao/app/workspace/auto-like/components/like-rules.tsx b/Cunkebao/app/workspace/auto-like/components/like-rules.tsx
index b5c84a47..cd01bbc3 100644
--- a/Cunkebao/app/workspace/auto-like/components/like-rules.tsx
+++ b/Cunkebao/app/workspace/auto-like/components/like-rules.tsx
@@ -1,23 +1,16 @@
"use client"
import { useState } from "react"
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Card, CardContent } from "@/components/ui/card"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
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 { Plus, Trash2, Clock } from "lucide-react"
import { Badge } from "@/components/ui/badge"
-
-export interface TimeRange {
- id: string
- start: string
- end: string
-}
+import { useViewMode } from "@/app/components/LayoutWrapper"
export interface LikeRulesData {
enableAutoLike: boolean
@@ -28,80 +21,30 @@ export interface LikeRulesData {
keywordFilters: string[]
friendGroups: string[]
excludedGroups: string[]
- timeRanges: TimeRange[]
+ timeRanges: { id: string; start: string; end: string }[]
randomizeInterval: boolean
- minInterval?: number
- maxInterval?: number
+ minInterval: number
+ maxInterval: number
}
interface LikeRulesProps {
- initialData?: Partial
+ initialData: LikeRulesData
onSave: (data: LikeRulesData) => void
}
export function LikeRules({ initialData, onSave }: LikeRulesProps) {
- const [formData, setFormData] = useState({
- 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 [formData, setFormData] = useState(initialData)
const [newKeyword, setNewKeyword] = useState("")
+ const { viewMode } = useViewMode()
- // 内容类型选项
- 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 handleContentTypeToggle = (type: string) => {
+ const updatedTypes = formData.contentTypes.includes(type)
+ ? formData.contentTypes.filter((t) => t !== type)
+ : [...formData.contentTypes, type]
+ setFormData({ ...formData, contentTypes: updatedTypes })
}
- // 删除时间范围
- 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 = () => {
+ const addKeywordFilter = () => {
if (newKeyword.trim() && !formData.keywordFilters.includes(newKeyword.trim())) {
setFormData({
...formData,
@@ -111,378 +54,276 @@ export function LikeRules({ initialData, onSave }: LikeRulesProps) {
}
}
- // 删除关键词
- const removeKeyword = (keyword: string) => {
+ const removeKeywordFilter = (keyword: string) => {
setFormData({
...formData,
keywordFilters: formData.keywordFilters.filter((k) => k !== keyword),
})
}
- // 切换内容类型
- const toggleContentType = (typeId: string) => {
+ const addTimeRange = () => {
+ const newId = String(formData.timeRanges.length + 1)
setFormData({
...formData,
- contentTypes: formData.contentTypes.includes(typeId)
- ? formData.contentTypes.filter((id) => id !== typeId)
- : [...formData.contentTypes, typeId],
+ timeRanges: [...formData.timeRanges, { id: newId, start: "09:00", end: "18:00" }],
})
}
- // 切换好友分组
- const toggleFriendGroup = (groupId: string) => {
- if (groupId === "all") {
+ const updateTimeRange = (id: string, field: "start" | "end", value: string) => {
+ setFormData({
+ ...formData,
+ timeRanges: formData.timeRanges.map((range) => (range.id === id ? { ...range, [field]: value } : range)),
+ })
+ }
+
+ const removeTimeRange = (id: string) => {
+ if (formData.timeRanges.length > 1) {
setFormData({
...formData,
- friendGroups: ["all"],
- excludedGroups: [],
+ timeRanges: formData.timeRanges.filter((range) => range.id !== id),
})
- 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 (
-
-
-
- 点赞规则设置
- 设定自动点赞的规则和时间间隔
-
-
- {/* 基本设置 */}
-
+
+
+
+
-
-
+
+
内容类型
+
选择需要自动点赞的内容类型
+
+
+ handleContentTypeToggle("text")}
+ />
+
+ 文字
+
+
+
+ handleContentTypeToggle("image")}
+ />
+
+ 图片
+
+
+
+ handleContentTypeToggle("video")}
+ />
+
+ 视频
+
+
+
+
+
+
+
+ 每日最大点赞数
+
+
设置每日最多点赞次数,建议不超过100次
+
+
setFormData({ ...formData, maxLikesPerDay: value[0] })}
+ className="flex-1"
+ />
+
+ {formData.maxLikesPerDay}次
+
+
+
+
+
+
+ 点赞间隔
+
+
设置点赞之间的时间间隔(分钟)
+
+
setFormData({ ...formData, likeInterval: value[0] })}
+ className="flex-1"
+ />
+
+ {formData.likeInterval}分钟
+
+
+
+
-
- 点赞间隔设置
-
-
+
+
+ 随机化间隔
+
+
开启后,系统将在设定范围内随机选择点赞间隔
+
+
setFormData({ ...formData, randomizeInterval: checked })}
+ />
- {formData.randomizeInterval ? (
-
-
-
-
- 最小间隔(分钟)
-
- {formData.minInterval}分钟
-
-
+
+ 最小间隔(分钟)
+ setFormData({ ...formData, minInterval: Number.parseInt(e.target.value) || 1 })}
min={1}
- max={30}
- step={1}
- value={[formData.minInterval || 5]}
- onValueChange={(value) => setFormData({ ...formData, minInterval: value[0] })}
- disabled={!formData.enableAutoLike}
+ className="mt-1"
/>
-
-
-
- 最大间隔(分钟)
-
- {formData.maxInterval}分钟
-
-
setFormData({ ...formData, maxInterval: value[0] })}
- disabled={!formData.enableAutoLike}
+
+ 最大间隔(分钟)
+ setFormData({ ...formData, maxInterval: Number.parseInt(e.target.value) || 1 })}
+ min={formData.minInterval + 1}
+ className="mt-1"
/>
- ) : (
-
-
-
- 点赞间隔(分钟)
-
- {formData.likeInterval}分钟
-
-
setFormData({ ...formData, likeInterval: value[0] })}
- disabled={!formData.enableAutoLike}
- />
-
)}
-
-
- 每日最大点赞数
-
-
{formData.maxLikesPerDay}个
+
时间范围
+
设置自动点赞的时间段
+
+
-
setFormData({ ...formData, maxLikesPerDay: value[0] })}
- disabled={!formData.enableAutoLike}
- />
-
-
-
- 点赞历史内容
-
-
-
-
-
-
-
- 开启后系统将点赞好友的历史朋友圈内容
-
-
-
+
+
关键词过滤
+
添加包含特定关键词的内容才会被点赞
+
+
+ setNewKeyword(e.target.value)}
+ placeholder="输入关键词"
+ className="flex-1"
+ onKeyDown={(e) => e.key === "Enter" && addKeywordFilter()}
+ />
+
+ 添加
+
-
setFormData({ ...formData, likeOldContent: checked })}
- disabled={!formData.enableAutoLike}
- />
-
-
- {/* 内容类型设置 */}
-
-
点赞内容类型
-
- {contentTypeOptions.map((type) => (
-
- toggleContentType(type.id)}
- disabled={!formData.enableAutoLike}
- />
-
- {type.label}
-
-
- ))}
-
-
-
- {/* 关键词过滤 */}
-
-
关键词过滤
-
- setNewKeyword(e.target.value)}
- className="flex-1"
- disabled={!formData.enableAutoLike}
- />
-
- 添加
-
-
- {formData.keywordFilters.length > 0 && (
+ {formData.keywordFilters.length === 0 && (
+ 未设置关键词过滤
+ )}
+
{formData.keywordFilters.map((keyword) => (
{keyword}
removeKeyword(keyword)}
- disabled={!formData.enableAutoLike}
+ className="h-4 w-4 p-0 ml-1"
+ onClick={() => removeKeywordFilter(keyword)}
>
- Remove
))}
- )}
-
添加关键词后,系统将只对包含这些关键词的内容进行点赞
-
-
- {/* 好友分组设置 */}
-
-
好友分组筛选
-
- {friendGroupOptions.map((group) => (
-
- toggleFriendGroup(group.id)}
- disabled={!formData.enableAutoLike || (group.id !== "all" && formData.friendGroups.includes("all"))}
- />
-
- {group.label}
-
-
- ))}
- {!formData.friendGroups.includes("all") && (
-
-
排除分组
-
- {friendGroupOptions
- .filter((group) => group.id !== "all" && !formData.friendGroups.includes(group.id))
- .map((group) => (
-
- toggleExcludedGroup(group.id)}
- disabled={!formData.enableAutoLike}
- />
-
- {group.label}
-
-
- ))}
-
-
- )}
-
-
- {/* 时间范围设置 */}
-
-
点赞时间段
-
= 5}
- >
-
- 添加时间段
-
+
+
+ 点赞历史内容
+
+
开启后,系统会点赞朋友圈中较旧的内容
+
+
setFormData({ ...formData, likeOldContent: checked })}
+ />
-
-
-
设置点赞的时间段,系统将在这些时间段内执行点赞任务
-
-
-
- 保存并继续
-
-
+
onSave(formData)}>
+ 下一步
+
+
+
+
)
}
-
diff --git a/Cunkebao/app/workspace/auto-like/components/step-indicator.tsx b/Cunkebao/app/workspace/auto-like/components/step-indicator.tsx
index b14baec1..4a2a0ce9 100644
--- a/Cunkebao/app/workspace/auto-like/components/step-indicator.tsx
+++ b/Cunkebao/app/workspace/auto-like/components/step-indicator.tsx
@@ -1,75 +1,51 @@
"use client"
-import { CheckIcon } from "lucide-react"
-import { cn } from "@/app/lib/utils"
+import { Check } from "lucide-react"
-interface Step {
- id: string
- name: string
- description?: string
-}
-
-interface StepIndicatorProps {
- steps: Step[]
+interface StepProps {
currentStep: number
- onStepClick?: (index: number) => void
}
-export function StepIndicator({ steps, currentStep, onStepClick }: StepIndicatorProps) {
- return (
-
-
- {steps.map((step, index) => {
- const isCompleted = index < currentStep
- const isCurrent = index === currentStep
- const isClickable = onStepClick && index <= currentStep
+export function StepIndicator({ currentStep }: StepProps) {
+ const steps = [
+ { title: "基础设置", description: "设置点赞规则" },
+ { title: "设备选择", description: "选择执行设备" },
+ { title: "人群选择", description: "选择目标人群" },
+ ]
- return (
- -
- isClickable && onStepClick(index)}
+ return (
+
+
+
+ {steps.map((step, index) => (
+
+
- {isCompleted ?
: index + 1}
-
-
-
- {step.name}
-
- {step.description && (
- {step.description}
- )}
-
- {index !== steps.length - 1 && (
-
- )}
-
- )
- })}
-
+ {index < currentStep ?
: index + 1}
+
+
+
+ {step.title}
+
+
{step.description}
+
+
+ ))}
+
+
+
)
}
-
diff --git a/Cunkebao/app/workspace/auto-like/components/tag-selector.tsx b/Cunkebao/app/workspace/auto-like/components/tag-selector.tsx
new file mode 100644
index 00000000..564f5afb
--- /dev/null
+++ b/Cunkebao/app/workspace/auto-like/components/tag-selector.tsx
@@ -0,0 +1,243 @@
+"use client"
+
+import { useState } from "react"
+import { Search, Tag, Check, X } from "lucide-react"
+import { Input } from "@/components/ui/input"
+import { Button } from "@/components/ui/button"
+import { Badge } from "@/components/ui/badge"
+import { Card, CardContent } from "@/components/ui/card"
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { ScrollArea } from "@/components/ui/scroll-area"
+import { Checkbox } from "@/components/ui/checkbox"
+import { Label } from "@/components/ui/label"
+import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
+
+interface TagGroup {
+ id: string
+ name: string
+ tags: string[]
+}
+
+interface TagSelectorProps {
+ selectedTags: string[]
+ tagOperator: "and" | "or"
+ onTagsChange: (tags: string[]) => void
+ onOperatorChange: (operator: "and" | "or") => void
+ onBack: () => void
+ onComplete: () => void
+}
+
+export function TagSelector({
+ selectedTags,
+ tagOperator,
+ onTagsChange,
+ onOperatorChange,
+ onBack,
+ onComplete,
+}: TagSelectorProps) {
+ const [searchQuery, setSearchQuery] = useState("")
+ const [tagGroups, setTagGroups] = useState([
+ {
+ id: "intention",
+ name: "意向度",
+ tags: ["高意向", "中意向", "低意向"],
+ },
+ {
+ id: "customer",
+ name: "客户类型",
+ tags: ["新客户", "老客户", "VIP客户"],
+ },
+ {
+ id: "gender",
+ name: "性别",
+ tags: ["男性", "女性"],
+ },
+ {
+ id: "age",
+ name: "年龄段",
+ tags: ["年轻人", "中年人", "老年人"],
+ },
+ {
+ id: "location",
+ name: "地区",
+ tags: ["城市", "农村"],
+ },
+ {
+ id: "income",
+ name: "收入",
+ tags: ["高收入", "中等收入", "低收入"],
+ },
+ {
+ id: "interaction",
+ name: "互动频率",
+ tags: ["高频互动", "中频互动", "低频互动"],
+ },
+ ])
+ const [customTag, setCustomTag] = useState("")
+
+ const toggleTag = (tag: string) => {
+ if (selectedTags.includes(tag)) {
+ onTagsChange(selectedTags.filter((t) => t !== tag))
+ } else {
+ onTagsChange([...selectedTags, tag])
+ }
+ }
+
+ const addCustomTag = () => {
+ if (customTag.trim() && !selectedTags.includes(customTag.trim())) {
+ onTagsChange([...selectedTags, customTag.trim()])
+ setCustomTag("")
+ }
+ }
+
+ const removeTag = (tag: string) => {
+ onTagsChange(selectedTags.filter((t) => t !== tag))
+ }
+
+ const filteredTagGroups = tagGroups
+ .map((group) => ({
+ ...group,
+ tags: group.tags.filter((tag) => tag.toLowerCase().includes(searchQuery.toLowerCase())),
+ }))
+ .filter((group) => group.tags.length > 0)
+
+ return (
+
+
+
+
+
+
选择目标人群标签
+
+
+
+
+ setSearchQuery(e.target.value)}
+ />
+
+
+
+
+ {tagGroups.slice(0, 4).map((group) => (
+
+ {group.name}
+
+ ))}
+
+ {tagGroups.map((group) => (
+
+
+ {group.tags.map((tag) => (
+ toggleTag(tag)}
+ >
+ {selectedTags.includes(tag) && }
+ {tag}
+
+ ))}
+
+
+ ))}
+
+
+
+
+ {filteredTagGroups.length > 0 ? (
+ filteredTagGroups.map((group) => (
+
+
{group.name}
+
+ {group.tags.map((tag) => (
+
+ toggleTag(tag)}
+ />
+
+ {tag}
+
+
+ ))}
+
+
+ ))
+ ) : (
+
没有找到匹配的标签
+ )}
+
+
+
+
+
+
+ setCustomTag(e.target.value)}
+ className="pl-9"
+ placeholder="添加自定义标签"
+ onKeyDown={(e) => e.key === "Enter" && addCustomTag()}
+ />
+
+
+ 添加
+
+
+
+
+
+
标签匹配逻辑
+
选择多个标签之间的匹配关系
+
+
onOperatorChange(value as "and" | "or")}
+ className="flex flex-col space-y-2"
+ >
+
+
+
+ 所有标签都必须匹配(AND)
+
+
+
+
+
+ 匹配任意一个标签即可(OR)
+
+
+
+
+
+
+
已选择的标签
+
+ {selectedTags.length === 0 ? (
+
未选择任何标签
+ ) : (
+
+ {selectedTags.map((tag) => (
+
+ {tag}
+ removeTag(tag)}>
+
+
+
+ ))}
+
+ )}
+
+
+
+
+
+
+ )
+}
diff --git a/Cunkebao/app/workspace/auto-like/loading.tsx b/Cunkebao/app/workspace/auto-like/loading.tsx
new file mode 100644
index 00000000..f15322a8
--- /dev/null
+++ b/Cunkebao/app/workspace/auto-like/loading.tsx
@@ -0,0 +1,3 @@
+export default function Loading() {
+ return null
+}
diff --git a/Cunkebao/app/workspace/auto-like/new/loading.tsx b/Cunkebao/app/workspace/auto-like/new/loading.tsx
new file mode 100644
index 00000000..f15322a8
--- /dev/null
+++ b/Cunkebao/app/workspace/auto-like/new/loading.tsx
@@ -0,0 +1,3 @@
+export default function Loading() {
+ return null
+}
diff --git a/Cunkebao/app/workspace/auto-like/new/page.tsx b/Cunkebao/app/workspace/auto-like/new/page.tsx
new file mode 100644
index 00000000..42375ea6
--- /dev/null
+++ b/Cunkebao/app/workspace/auto-like/new/page.tsx
@@ -0,0 +1,160 @@
+"use client"
+
+import { useState } from "react"
+import { useRouter } from "next/navigation"
+import { ChevronLeft, Search } from "lucide-react"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { StepIndicator } from "../components/step-indicator"
+import { BasicSettings } from "../components/basic-settings"
+import { DeviceSelectionDialog } from "../components/device-selection-dialog"
+import { TagSelector } from "../components/tag-selector"
+import { api, ApiResponse } from "@/lib/api"
+import { showToast } from "@/lib/toast"
+
+export default function NewAutoLikePage() {
+ const router = useRouter()
+ const [currentStep, setCurrentStep] = useState(1)
+ const [deviceDialogOpen, setDeviceDialogOpen] = useState(false)
+ const [formData, setFormData] = useState({
+ taskName: "",
+ likeInterval: 5, // 默认5秒
+ maxLikesPerDay: 200, // 默认200次
+ timeRange: { start: "08:00", end: "22:00" },
+ contentTypes: ["text", "image", "video"],
+ enabled: true,
+ selectedDevices: [] as number[],
+ selectedTags: [] as string[],
+ tagOperator: "and" as "and" | "or",
+ })
+
+ const handleUpdateFormData = (data: Partial) => {
+ setFormData((prev) => ({ ...prev, ...data }))
+ }
+
+ const handleNext = () => {
+ setCurrentStep((prev) => Math.min(prev + 1, 3))
+ }
+
+ const handlePrev = () => {
+ setCurrentStep((prev) => Math.max(prev - 1, 1))
+ }
+
+ const handleComplete = async () => {
+ try {
+ const response = await api.post('/v1/workbench/create', {
+ type: 1,
+ name: formData.taskName,
+ interval: formData.likeInterval,
+ maxLikes: formData.maxLikesPerDay,
+ startTime: formData.timeRange.start,
+ endTime: formData.timeRange.end,
+ contentTypes: formData.contentTypes,
+ enabled: formData.enabled,
+ devices: formData.selectedDevices,
+ targetGroups: formData.selectedTags,
+ tagOperator: formData.tagOperator === 'and' ? 1 : 2
+ });
+
+ if (response.code === 200) {
+ showToast(response.msg, "success");
+ router.push("/workspace/auto-like");
+ } else {
+ showToast(response.msg || "请稍后再试", "error");
+ }
+ } catch (error: any) {
+ console.error("创建自动点赞任务失败:", error);
+ showToast(error?.message || "请检查网络连接或稍后再试", "error");
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+ {currentStep === 1 && (
+
+ )}
+
+ {currentStep === 2 && (
+
+
+
+ setDeviceDialogOpen(true)}
+ readOnly
+ value={formData.selectedDevices.length > 0 ? `已选择 ${formData.selectedDevices.length} 个设备` : ""}
+ />
+
+
+ {formData.selectedDevices.length > 0 && (
+
已选设备:{formData.selectedDevices.length} 个
+ )}
+
+
+
+ 上一步
+
+
+ 下一步
+
+
+
+
{
+ handleUpdateFormData({ selectedDevices: devices })
+ setDeviceDialogOpen(false)
+ }}
+ />
+
+ )}
+
+ {currentStep === 3 && (
+
+
handleUpdateFormData({ selectedTags: tags })}
+ onOperatorChange={(operator) => handleUpdateFormData({ tagOperator: operator })}
+ onBack={handlePrev}
+ onComplete={handleComplete}
+ />
+
+
+
+ 上一步
+
+
+ 完成
+
+
+
+ )}
+
+
+
+ )
+}
diff --git a/Cunkebao/app/workspace/auto-like/page.tsx b/Cunkebao/app/workspace/auto-like/page.tsx
index fab125f6..f3efaba6 100644
--- a/Cunkebao/app/workspace/auto-like/page.tsx
+++ b/Cunkebao/app/workspace/auto-like/page.tsx
@@ -1,129 +1,403 @@
"use client"
-import { useState } from "react"
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { useState, useEffect } from "react"
+import {
+ ChevronLeft,
+ Plus,
+ Filter,
+ Search,
+ RefreshCw,
+ MoreVertical,
+ Clock,
+ Edit,
+ Trash2,
+ Eye,
+ Copy,
+ ChevronDown,
+ ChevronUp,
+ Settings,
+ Calendar,
+ Users,
+ ThumbsUp,
+} from "lucide-react"
+import { Card } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
-import { StepIndicator } from "./components/step-indicator"
-import { TimeSettings, type TimeSettingsData } from "./components/time-settings"
-import { DeviceSelection, type DeviceSelectionData } from "./components/device-selection"
-import type { LikeConfigData } from "./components/like-config"
+import { Input } from "@/components/ui/input"
+import Link from "next/link"
+import { Badge } from "@/components/ui/badge"
import { useRouter } from "next/navigation"
+import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
+import { Switch } from "@/components/ui/switch"
+import { Progress } from "@/components/ui/progress"
+import { api } from "@/lib/api"
+import { showToast } from "@/lib/toast"
-const steps = [
- {
- id: "device-selection",
- name: "设备选择",
- description: "选择执行自动点赞的设备",
- },
- {
- id: "audience-selection",
- name: "人群选择",
- description: "选择点赞目标人群",
- },
- {
- id: "time-settings",
- name: "时间设置",
- description: "设置点赞时间和频率",
- },
-]
+interface TaskConfig {
+ id: number
+ workbenchId: number
+ interval: number
+ maxLikes: number
+ startTime: string
+ endTime: string
+ contentTypes: string[]
+ devices: number[]
+ targetGroups: string[]
+ tagOperator: number
+ createTime: string
+ updateTime: string
+}
+
+interface Task {
+ id: number
+ name: string
+ type: number
+ status: number
+ autoStart: number
+ createTime: string
+ updateTime: string
+ config: TaskConfig
+}
+
+interface TaskListResponse {
+ code: number
+ msg: string
+ data: {
+ list: Task[]
+ total: number
+ }
+}
+
+interface ApiResponse {
+ code: number
+ msg: string
+}
export default function AutoLikePage() {
const router = useRouter()
- const [currentStep, setCurrentStep] = useState(0)
- const [formData, setFormData] = useState({
- deviceSelection: {
- selectedDevices: [],
- selectedDatabase: "",
- selectedAudience: "",
- } as DeviceSelectionData,
- timeSettings: {
- enableAutoLike: true,
- timeRanges: [{ id: "1", start: "06:00", end: "08:00" }],
- likeInterval: 15,
- randomizeInterval: false,
- } as TimeSettingsData,
- likeConfig: {
- likeAll: false,
- likeFirstPage: true,
- maxLikesPerDay: 50,
- likeImmediately: true,
- excludeTags: [],
- includeTags: [],
- } as LikeConfigData,
- })
+ const [expandedTaskId, setExpandedTaskId] = useState(null)
+ const [tasks, setTasks] = useState([])
+ const [loading, setLoading] = useState(false)
+ const [searchName, setSearchName] = useState("")
+ const [currentPage, setCurrentPage] = useState(1)
+ const [total, setTotal] = useState(0)
+ const pageSize = 10
- const handleDeviceSelectionSave = (data: DeviceSelectionData) => {
- setFormData({ ...formData, deviceSelection: data })
- setCurrentStep(1)
- }
+ const fetchTasks = async (page: number, name?: string) => {
+ try {
+ setLoading(true)
+ const queryParams = new URLSearchParams({
+ type: '1',
+ page: page.toString(),
+ limit: pageSize.toString(),
+ })
+ if (name) {
+ queryParams.append('name', name)
+ }
+ const response = await api.get(`/v1/workbench/list?${queryParams.toString()}`)
- const handleTimeSettingsSave = (data: TimeSettingsData) => {
- setFormData({ ...formData, timeSettings: data })
- setCurrentStep(2)
- }
-
- const handleLikeConfigSave = (data: LikeConfigData) => {
- setFormData({ ...formData, likeConfig: data })
- // 提交表单或导航到确认页面
- router.push("/workspace")
- }
-
- const handleStepClick = (index: number) => {
- if (index <= currentStep) {
- setCurrentStep(index)
+ if (response.code === 200) {
+ setTasks(response.data.list)
+ setTotal(response.data.total)
+ } else {
+ showToast(response.msg || "请稍后再试", "error")
+ }
+ } catch (error: any) {
+ console.error("获取任务列表失败:", error)
+ showToast(error?.message || "请检查网络连接", "error")
+ } finally {
+ setLoading(false)
}
}
- const handleBack = () => {
- if (currentStep > 0) {
- setCurrentStep(currentStep - 1)
- } else {
- router.push("/workspace")
+ useEffect(() => {
+ fetchTasks(currentPage, searchName)
+ }, [currentPage])
+
+ const handleSearch = () => {
+ setCurrentPage(1)
+ fetchTasks(1, searchName)
+ }
+
+ const handleRefresh = () => {
+ fetchTasks(currentPage, searchName)
+ }
+
+ const toggleExpand = (taskId: number) => {
+ setExpandedTaskId(expandedTaskId === taskId ? null : taskId)
+ }
+
+ const handleDelete = async (taskId: number) => {
+ try {
+ const response = await api.delete(`/v1/workbench/delete?id=${taskId}`)
+
+ if (response.code === 200) {
+ // 删除成功后刷新列表
+ fetchTasks(currentPage, searchName)
+ showToast(response.msg || "已成功删除点赞任务", "success")
+ } else {
+ showToast(response.msg || "请稍后再试", "error")
+ }
+ } catch (error: any) {
+ console.error("删除任务失败:", error)
+ showToast(error?.message || "请检查网络连接", "error")
+ }
+ }
+
+ const handleEdit = (taskId: number) => {
+ router.push(`/workspace/auto-like/${taskId}/edit`)
+ }
+
+ const handleView = (taskId: number) => {
+ router.push(`/workspace/auto-like/${taskId}`)
+ }
+
+ const handleCopy = async (taskId: number) => {
+ try {
+ const response = await api.post('/v1/workbench/copy', {
+ id: taskId
+ })
+
+ if (response.code === 200) {
+ // 复制成功后刷新列表
+ fetchTasks(currentPage, searchName)
+ showToast(response.msg || "已成功复制点赞任务", "success")
+ } else {
+ showToast(response.msg || "请稍后再试", "error")
+ }
+ } catch (error: any) {
+ console.error("复制任务失败:", error)
+ showToast(error?.message || "请检查网络连接", "error")
+ }
+ }
+
+ const toggleTaskStatus = async (taskId: number, currentStatus: number) => {
+ try {
+ const response = await api.post('/v1/workbench/update-status', {
+ id: taskId,
+ status: currentStatus === 1 ? 2 : 1
+ })
+
+ if (response.code === 200) {
+ // 更新本地状态
+ setTasks(tasks.map(task =>
+ task.id === taskId
+ ? { ...task, status: currentStatus === 1 ? 2 : 1 }
+ : task
+ ))
+
+ const newStatus = currentStatus === 1 ? 2 : 1
+ showToast(response.msg || `任务${newStatus === 1 ? "已启动" : "已暂停"}`, "success")
+ } else {
+ showToast(response.msg || "请稍后再试", "error")
+ }
+ } catch (error: any) {
+ console.error("更新任务状态失败:", error)
+ showToast(error?.message || "请检查网络连接", "error")
}
}
return (
-
-
-
- ← 返回
-
-
自动点赞
-
设置自动点赞功能,提高互动率和活跃度
-
+
+
-
-
-
-
- {currentStep === 0 && (
-
- )}
-
- {currentStep === 1 && (
-
-
- 人群选择
- 选择需要自动点赞的目标人群
-
-
- {/* 这里可以添加人群选择的具体内容 */}
-
-
setCurrentStep(0)}>
- 上一步
-
-
setCurrentStep(2)}>下一步
+
+
+
+
+
+ setSearchName(e.target.value)}
+ onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
+ />
-
+
+
+
+
+
+
+
- )}
- {currentStep === 2 &&
}
+
+ {tasks.map((task) => (
+
+
+
+
{task.name}
+
+ {task.status === 1 ? "进行中" : "已暂停"}
+
+
+
+ toggleTaskStatus(task.id, task.status)} />
+
+
+
+
+
+
+
+ handleView(task.id)}>
+
+ 查看
+
+ handleEdit(task.id)}>
+
+ 编辑
+
+ handleCopy(task.id)}>
+
+ 复制
+
+ handleDelete(task.id)}>
+
+ 删除
+
+
+
+
+
+
+
+
+
执行设备:{task.config.devices.length} 个
+
目标人群:{task.config.targetGroups.join(', ')}
+
+
+
点赞间隔:{task.config.interval} 秒
+
每日上限:{task.config.maxLikes} 次
+
+
+
+
+
+
+ 更新时间:{task.updateTime}
+
+
+ 创建时间:{task.createTime}
+ toggleExpand(task.id)}>
+ {expandedTaskId === task.id ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ {expandedTaskId === task.id && (
+
+
+
+
+
+
基本设置
+
+
+
+ 点赞间隔:
+ {task.config.interval} 秒
+
+
+ 每日最大点赞数:
+ {task.config.maxLikes} 次
+
+
+ 执行时间段:
+
+ {task.config.startTime} - {task.config.endTime}
+
+
+
+
+
+
+
+
+
目标人群
+
+
+
+ {task.config.targetGroups.map((tag) => (
+
+ {tag}
+
+ ))}
+
+
+ 匹配方式:{task.config.tagOperator === 1 ? "满足所有标签" : "满足任一标签"}
+
+
+
+
+
+
+
+
点赞内容类型
+
+
+
+ {task.config.contentTypes.map((type) => (
+
+ {type === "text" ? "文字" : type === "image" ? "图片" : "视频"}
+
+ ))}
+
+
+
+
+
+ )}
+
+ ))}
+
+
+ {/* 分页 */}
+ {total > pageSize && (
+
+
setCurrentPage(prev => Math.max(1, prev - 1))}
+ disabled={currentPage === 1 || loading}
+ >
+ 上一页
+
+
+ 第 {currentPage} 页
+ 共 {Math.ceil(total / pageSize)} 页
+
+
setCurrentPage(prev => Math.min(Math.ceil(total / pageSize), prev + 1))}
+ disabled={currentPage >= Math.ceil(total / pageSize) || loading}
+ >
+ 下一页
+
+
+ )}
+
)
}
-
diff --git a/Cunkebao/app/workspace/group-sync/components/basic-settings.tsx b/Cunkebao/app/workspace/group-sync/components/basic-settings.tsx
index 61ae6cd4..26b30e5f 100644
--- a/Cunkebao/app/workspace/group-sync/components/basic-settings.tsx
+++ b/Cunkebao/app/workspace/group-sync/components/basic-settings.tsx
@@ -89,7 +89,13 @@ export function BasicSettings({
每日推送:
)
}
-
diff --git a/Cunkebao/app/workspace/moments-sync/components/basic-settings.tsx b/Cunkebao/app/workspace/moments-sync/components/basic-settings.tsx
index 943807e4..9ef6cf88 100644
--- a/Cunkebao/app/workspace/moments-sync/components/basic-settings.tsx
+++ b/Cunkebao/app/workspace/moments-sync/components/basic-settings.tsx
@@ -5,6 +5,7 @@ import { Button } from "@/components/ui/button"
import { Switch } from "@/components/ui/switch"
import { Plus, Minus, Clock, HelpCircle } from "lucide-react"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
+import { useViewMode } from "@/app/components/LayoutWrapper"
interface BasicSettingsProps {
formData: {
@@ -20,132 +21,136 @@ interface BasicSettingsProps {
}
export function BasicSettings({ formData, onChange, onNext }: BasicSettingsProps) {
+ const { viewMode } = useViewMode()
+
return (
-
-
-
任务名称
-
onChange({ taskName: e.target.value })}
- placeholder="请输入任务名称"
- className="h-12 border-0 border-b border-gray-200 rounded-none focus-visible:ring-0 focus-visible:border-blue-600 px-0 text-base"
- />
-
+
+
+
+
任务名称
+
onChange({ taskName: e.target.value })}
+ placeholder="请输入任务名称"
+ className="h-12 border-0 border-b border-gray-200 rounded-none focus-visible:ring-0 focus-visible:border-blue-600 px-0 text-base"
+ />
+
-
-
允许发布时间段
-
-
- onChange({ startTime: e.target.value })}
- className="h-12 pl-10 rounded-xl border-gray-200 text-base"
- />
-
-
-
至
-
-
onChange({ endTime: e.target.value })}
- className="h-12 pl-10 rounded-xl border-gray-200 text-base"
- />
-
+
-
-
-
每日同步数量
-
-
onChange({ syncCount: Math.max(1, formData.syncCount - 1) })}
- className="h-12 w-12 rounded-xl"
- >
-
-
-
{formData.syncCount}
-
onChange({ syncCount: formData.syncCount + 1 })}
- className="h-12 w-12 rounded-xl"
- >
-
-
-
条朋友圈
-
-
-
-
-
账号类型
-
-
-
-
-
- onChange({ accountType: "business" })}
- className={`w-full h-12 justify-between rounded-lg ${
- formData.accountType === "business"
- ? "bg-blue-600 hover:bg-blue-600 text-white"
- : "bg-white hover:bg-gray-50"
- }`}
- >
- 业务号
-
-
-
-
-
- 业务号能够循环推送内容库中的内容。当内容库所有内容循环推送完毕后,若有新内容则优先推送新内容,若无新内容则继续循环推送。
-
-
-
-
-
-
-
-
-
- onChange({ accountType: "personal" })}
- className={`w-full h-12 justify-between rounded-lg ${
- formData.accountType === "personal"
- ? "bg-blue-600 hover:bg-blue-600 text-white"
- : "bg-white hover:bg-gray-50"
- }`}
- >
- 人设号
-
-
-
-
- 用于实时更新同步,有新动态时进行同步,无动态则不同步。
-
-
-
+
+
每日同步数量
+
+
onChange({ syncCount: Math.max(1, formData.syncCount - 1) })}
+ className="h-12 w-12 rounded-xl bg-white border-gray-200"
+ >
+
+
+
{formData.syncCount}
+
onChange({ syncCount: formData.syncCount + 1 })}
+ className="h-12 w-12 rounded-xl bg-white border-gray-200"
+ >
+
+
+
条朋友圈
-
-
-
是否启用
-
onChange({ enabled: checked })}
- className="data-[state=checked]:bg-blue-600 h-7 w-12"
- />
+
+
账号类型
+
+
+
+
+
+ onChange({ accountType: "business" })}
+ className={`w-full h-12 justify-between rounded-lg ${
+ formData.accountType === "business"
+ ? "bg-blue-600 hover:bg-blue-600 text-white"
+ : "bg-white hover:bg-gray-50"
+ }`}
+ >
+ 业务号
+
+
+
+
+
+ 业务号能够循环推送内容库中的内容。当内容库所有内容循环推送完毕后,若有新内容则优先推送新内容,若无新内容则继续循环推送。
+
+
+
+
+
+
+
+
+
+ onChange({ accountType: "personal" })}
+ className={`w-full h-12 justify-between rounded-lg ${
+ formData.accountType === "personal"
+ ? "bg-blue-600 hover:bg-blue-600 text-white"
+ : "bg-white hover:bg-gray-50"
+ }`}
+ >
+ 人设号
+
+
+
+
+ 用于实时更新同步,有新动态时进行同步,无动态则不同步。
+
+
+
+
+
+
+
+
+ 是否启用
+ onChange({ enabled: checked })}
+ className="data-[state=checked]:bg-blue-600 h-7 w-12"
+ />
+
)
}
-
diff --git a/Cunkebao/app/workspace/moments-sync/components/device-selection-dialog.tsx b/Cunkebao/app/workspace/moments-sync/components/device-selection-dialog.tsx
index f4b9cfe3..eb88fe23 100644
--- a/Cunkebao/app/workspace/moments-sync/components/device-selection-dialog.tsx
+++ b/Cunkebao/app/workspace/moments-sync/components/device-selection-dialog.tsx
@@ -9,7 +9,6 @@ import { Search, RefreshCw } from "lucide-react"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { ScrollArea } from "@/components/ui/scroll-area"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
-import { ImeiDisplay } from "@/components/ImeiDisplay"
interface Device {
id: string
@@ -124,15 +123,12 @@ export function DeviceSelectionDialog({ open, onOpenChange, selectedDevices, onS
{device.name}
-
+
{device.status === "online" ? "在线" : "离线"}
-
- IMEI:
-
-
+
IMEI: {device.imei}
微信号: {device.wxid}
{device.usedInPlans > 0 && (
@@ -147,4 +143,3 @@ export function DeviceSelectionDialog({ open, onOpenChange, selectedDevices, onS
)
}
-
diff --git a/Cunkebao/app/workspace/moments-sync/new/steps/BasicSettings.tsx b/Cunkebao/app/workspace/moments-sync/new/steps/BasicSettings.tsx
index 26f65c26..b5c84fbd 100644
--- a/Cunkebao/app/workspace/moments-sync/new/steps/BasicSettings.tsx
+++ b/Cunkebao/app/workspace/moments-sync/new/steps/BasicSettings.tsx
@@ -61,7 +61,9 @@ export function BasicSettings({ formData, onChange, onNext }: BasicSettingsProps
>
-
{formData.syncCount}
+
+ {formData.syncCount}
+
)
}
-
diff --git a/Cunkebao/app/workspace/moments-sync/new/steps/DeviceSelector.tsx b/Cunkebao/app/workspace/moments-sync/new/steps/DeviceSelector.tsx
index f5e2e0b5..bd213d87 100644
--- a/Cunkebao/app/workspace/moments-sync/new/steps/DeviceSelector.tsx
+++ b/Cunkebao/app/workspace/moments-sync/new/steps/DeviceSelector.tsx
@@ -4,7 +4,7 @@ import { useState, useEffect } from "react"
import { Card } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
-import { Search, RefreshCw, X, ChevronLeft, ChevronRight } from "lucide-react"
+import { Search, RefreshCw, X } from "lucide-react"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Checkbox } from "@/components/ui/checkbox"
import { toast } from "@/components/ui/use-toast"
@@ -17,7 +17,6 @@ import {
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination"
-import { ImeiDisplay } from "@/components/ImeiDisplay"
// 定义类型,避免导入错误
interface Device {
@@ -149,16 +148,6 @@ export function DeviceSelector({ formData, onChange, onNext, onPrev }: DeviceSel
}
}
- const handlePrevPage = () => {
- setCurrentPage((prev) => Math.max(1, prev - 1))
- }
-
- const handleNextPage = () => {
- setCurrentPage((prev) => Math.min(Math.ceil(filteredDevices.length / itemsPerPage), prev + 1))
- }
-
- const isLastPage = currentPage === Math.ceil(filteredDevices.length / itemsPerPage)
-
return (
@@ -204,14 +193,15 @@ export function DeviceSelector({ formData, onChange, onNext, onPrev }: DeviceSel
{device.name}
-
+
{device.status === "online" ? "在线" : "离线"}
-
-
-
- IMEI:
-
+
+
IMEI: {device.imei}
微信号: {device.wechatId}
{device.usedInPlans > 0 && (
已用于 {device.usedInPlans} 个计划
@@ -221,25 +211,24 @@ export function DeviceSelector({ formData, onChange, onNext, onPrev }: DeviceSel
))}
-
+
setCurrentPage((prev) => Math.max(1, prev - 1))}
disabled={currentPage === 1}
/>
- {Array.from({ length: Math.ceil(filteredDevices.length / itemsPerPage) }).map((_, index) => (
-
- setCurrentPage(index + 1)}
- isActive={currentPage === index + 1}
- >
- {index + 1}
+ {Array.from({ length: Math.ceil(filteredDevices.length / itemsPerPage) }, (_, i) => i + 1).map((page) => (
+
+ setCurrentPage(page)} isActive={currentPage === page}>
+ {page}
))}
+ setCurrentPage((prev) => Math.min(Math.ceil(filteredDevices.length / itemsPerPage), prev + 1))
+ }
+ disabled={currentPage === Math.ceil(filteredDevices.length / itemsPerPage)}
/>
@@ -279,32 +268,7 @@ export function DeviceSelector({ formData, onChange, onNext, onPrev }: DeviceSel
下一步
-
-
-
-
-
-
-
- {currentPage} / {Math.ceil(filteredDevices.length / itemsPerPage)}
-
-
-
-
-
-
)
}
-
diff --git a/Cunkebao/app/workspace/traffic-distribution/new/loading.tsx b/Cunkebao/app/workspace/traffic-distribution/new/loading.tsx
index e057bceb..2af044d9 100644
--- a/Cunkebao/app/workspace/traffic-distribution/new/loading.tsx
+++ b/Cunkebao/app/workspace/traffic-distribution/new/loading.tsx
@@ -23,7 +23,7 @@ export default function NewTrafficDistributionLoading() {
{[1, 2, 3].map((step) => (
-
+
))}
@@ -35,6 +35,7 @@ export default function NewTrafficDistributionLoading() {
+
@@ -58,4 +59,3 @@ export default function NewTrafficDistributionLoading() {
)
}
-
diff --git a/Cunkebao/app/workspace/traffic-distribution/new/page.tsx b/Cunkebao/app/workspace/traffic-distribution/new/page.tsx
index 6ef484dc..010c611f 100644
--- a/Cunkebao/app/workspace/traffic-distribution/new/page.tsx
+++ b/Cunkebao/app/workspace/traffic-distribution/new/page.tsx
@@ -13,6 +13,7 @@ import { Switch } from "@/components/ui/switch"
import { Badge } from "@/components/ui/badge"
import { TrafficPoolSelector } from "@/app/components/traffic-pool-selector"
import { Checkbox } from "@/components/ui/checkbox"
+import { toast } from "@/components/ui/use-toast"
export default function NewTrafficDistributionPage() {
const router = useRouter()
@@ -50,7 +51,38 @@ export default function NewTrafficDistributionPage() {
}
const handleNext = () => {
- setCurrentStep((prev) => prev + 1)
+ if (currentStep === 1 && !formData.name) {
+ toast({
+ title: "请填写规则名称",
+ description: "规则名称为必填项",
+ variant: "destructive",
+ })
+ return
+ }
+
+ if (currentStep === 2 && !formData.allDevices && (!formData.targetDevices || formData.targetDevices.length === 0)) {
+ toast({
+ title: "请选择设备",
+ description: "请选择至少一台设备或选择所有设备",
+ variant: "destructive",
+ })
+ return
+ }
+
+ if (currentStep === 3 && !formData.selectedPool) {
+ toast({
+ title: "请选择流量池",
+ description: "请选择一个流量池进行分发",
+ variant: "destructive",
+ })
+ return
+ }
+
+ if (currentStep < 3) {
+ setCurrentStep((prev) => prev + 1)
+ } else {
+ handleSubmit()
+ }
}
const handleBack = () => {
@@ -62,15 +94,13 @@ export default function NewTrafficDistributionPage() {
}
const handleSubmit = () => {
- // 这里处理表单提交逻辑
- console.log("提交表单数据:", formData)
+ toast({
+ title: "创建成功",
+ description: "流量分发规则已创建",
+ })
router.push("/workspace/traffic-distribution")
}
- const isStep1Valid = formData.name && formData.source
- const isStep2Valid = formData.targetGroups.length > 0 || formData.targetDevices.length > 0
- const isStep3Valid = true // 规则设置可以有默认值
-
return (
@@ -118,7 +148,7 @@ export default function NewTrafficDistributionPage() {
- {/* 步骤1:基本信息 */}
+ {/* 步骤1:规则设定 */}
{currentStep === 1 && (
@@ -210,7 +240,7 @@ export default function NewTrafficDistributionPage() {
)}
-
+
下一步
@@ -219,7 +249,7 @@ export default function NewTrafficDistributionPage() {
)}
- {/* 步骤2:目标设置 */}
+ {/* 步骤2:选择设备 */}
{currentStep === 2 && (
@@ -282,10 +312,7 @@ export default function NewTrafficDistributionPage() {
上一步
-
+
下一步
@@ -294,7 +321,7 @@ export default function NewTrafficDistributionPage() {
)}
- {/* 步骤3:规则配置 */}
+ {/* 步骤3:选择流量池 */}
{currentStep === 3 && (
@@ -422,4 +449,3 @@ export default function NewTrafficDistributionPage() {
)
}
-
diff --git a/Cunkebao/lib/toast.ts b/Cunkebao/lib/toast.ts
new file mode 100644
index 00000000..6db12548
--- /dev/null
+++ b/Cunkebao/lib/toast.ts
@@ -0,0 +1,31 @@
+export const showToast = (message: string, type: 'success' | 'error' = 'success') => {
+ // 创建提示元素
+ const toast = document.createElement('div');
+ toast.className = `fixed inset-0 flex items-center justify-center z-50`;
+
+ // 创建遮罩层
+ const overlay = document.createElement('div');
+ overlay.className = 'fixed inset-0';
+
+ // 创建内容框
+ const content = document.createElement('div');
+ content.className = `relative bg-black/50 p-4 rounded-lg shadow-lg text-white z-50 min-w-[300px] max-w-[80%]`;
+
+ // 创建消息
+ const messageElement = document.createElement('p');
+ messageElement.className = 'text-base text-center';
+ messageElement.textContent = message;
+
+ // 组装元素
+ content.appendChild(messageElement);
+ toast.appendChild(overlay);
+ toast.appendChild(content);
+
+ // 添加到页面
+ document.body.appendChild(toast);
+
+ // 3秒后自动移除
+ setTimeout(() => {
+ toast.remove();
+ }, 1500);
+};
\ No newline at end of file
diff --git a/Server/application/cunkebao/config/route.php b/Server/application/cunkebao/config/route.php
index b89b9799..32de68e0 100644
--- a/Server/application/cunkebao/config/route.php
+++ b/Server/application/cunkebao/config/route.php
@@ -46,4 +46,13 @@ Route::group('v1/', function () {
Route::group('traffic/pool', function () {
Route::post('import', 'app\\cunkebao\\controller\\TrafficPool@importOrders'); // 导入订单标签
});
+
+ // 工作台相关
+ Route::group('workbench', function () {
+ Route::post('create', 'app\\cunkebao\\controller\\WorkbenchController@create'); // 创建工作台
+ Route::get('list', 'app\\cunkebao\\controller\\WorkbenchController@getList'); // 获取工作台列表
+ Route::post('update-status', 'app\\cunkebao\\controller\\WorkbenchController@updateStatus'); // 更新工作台状态
+ Route::delete('delete', 'app\\cunkebao\\controller\\WorkbenchController@delete'); // 删除工作台
+ Route::post('copy', 'app\\cunkebao\\controller\\WorkbenchController@copy'); // 拷贝工作台
+ });
})->middleware(['jwt']);
\ No newline at end of file
diff --git a/Server/application/cunkebao/controller/WorkbenchController.php b/Server/application/cunkebao/controller/WorkbenchController.php
new file mode 100644
index 00000000..1f1f79d7
--- /dev/null
+++ b/Server/application/cunkebao/controller/WorkbenchController.php
@@ -0,0 +1,469 @@
+request->isPost()) {
+ return json(['code' => 400, 'msg' => '请求方式错误']);
+ }
+
+ // 获取登录用户信息
+ $userInfo = request()->userInfo;
+
+ // 获取请求参数
+ $param = $this->request->post();
+
+ // 验证数据
+ $validate = new WorkbenchValidate;
+ if (!$validate->scene('create')->check($param)) {
+ return json(['code' => 400, 'msg' => $validate->getError()]);
+ }
+
+ Db::startTrans();
+ try {
+ // 创建工作台基本信息
+ $workbench = new Workbench;
+ $workbench->name = $param['name'];
+ $workbench->type = $param['type'];
+ $workbench->status = 1;
+ $workbench->autoStart = !empty($param['autoStart']) ? 1 : 0;
+ $workbench->userId = $userInfo['id'];
+ $workbench->companyId = $userInfo['companyId'];
+ $workbench->createTime = time();
+ $workbench->updateTime = time();
+ $workbench->save();
+
+ // 根据类型创建对应的配置
+ switch ($param['type']) {
+ case self::TYPE_AUTO_LIKE: // 自动点赞
+ $config = new WorkbenchAutoLike;
+ $config->workbenchId = $workbench->id;
+ $config->interval = $param['interval'];
+ $config->maxLikes = $param['maxLikes'];
+ $config->startTime = $param['startTime'];
+ $config->endTime = $param['endTime'];
+ $config->contentTypes = json_encode($param['contentTypes']);
+ $config->devices = json_encode($param['devices']);
+ $config->targetGroups = json_encode($param['targetGroups']);
+ $config->tagOperator = $param['tagOperator'];
+ $config->createTime = time();
+ $config->updateTime = time();
+ $config->save();
+ break;
+
+ case self::TYPE_MOMENTS_SYNC: // 朋友圈同步
+ $config = new WorkbenchMomentsSync;
+ $config->workbenchId = $workbench->id;
+ $config->syncInterval = $param['syncInterval'];
+ $config->syncCount = $param['syncCount'];
+ $config->syncType = $param['syncType'];
+ $config->devices = json_encode($param['devices']);
+ $config->targetGroups = json_encode($param['targetGroups']);
+ $config->createTime = time();
+ $config->updateTime = time();
+ $config->save();
+ break;
+
+ case self::TYPE_GROUP_PUSH: // 群消息推送
+ $config = new WorkbenchGroupPush;
+ $config->workbenchId = $workbench->id;
+ $config->pushInterval = $param['pushInterval'];
+ $config->pushContent = json_encode($param['pushContent']);
+ $config->pushTime = json_encode($param['pushTime']);
+ $config->devices = json_encode($param['devices']);
+ $config->targetGroups = json_encode($param['targetGroups']);
+ $config->save();
+ break;
+
+ case self::TYPE_GROUP_CREATE: // 自动建群
+ $config = new WorkbenchGroupCreate;
+ $config->workbenchId = $workbench->id;
+ $config->groupNamePrefix = $param['groupNamePrefix'];
+ $config->maxGroups = $param['maxGroups'];
+ $config->membersPerGroup = $param['membersPerGroup'];
+ $config->devices = json_encode($param['devices']);
+ $config->targetGroups = json_encode($param['targetGroups']);
+ $config->createTime = time();
+ $config->updateTime = time();
+ $config->save();
+ break;
+ }
+
+ Db::commit();
+ return json(['code' => 200, 'msg' => '创建成功', 'data' => ['id' => $workbench->id]]);
+ } catch (\Exception $e) {
+ Db::rollback();
+ return json(['code' => 500, 'msg' => '创建失败:' . $e->getMessage()]);
+ }
+ }
+
+ /**
+ * 获取工作台列表
+ * @return \think\response\Json
+ */
+ public function getList()
+ {
+ $page = $this->request->param('page', 1);
+ $limit = $this->request->param('limit', 10);
+ $type = $this->request->param('type', '');
+ $keyword = $this->request->param('name', '');
+
+ $where = [
+ ['userId', '=', $this->request->userInfo['id']],
+ ['isDel', '=', 0]
+ ];
+
+ // 添加类型筛选
+ if ($type !== '') {
+ $where[] = ['type', '=', $type];
+ }
+
+ // 添加名称模糊搜索
+ if ($keyword !== '') {
+ $where[] = ['name', 'like', '%' . $keyword . '%'];
+ }
+
+ // 定义关联关系
+ $with = [
+ 'autoLike' => function($query) {
+ $query->field('workbenchId,interval,maxLikes,startTime,endTime,contentTypes,devices,targetGroups');
+ },
+ // 'momentsSync' => function($query) {
+ // $query->field('workbenchId,syncInterval,syncCount,syncType,devices,targetGroups');
+ // },
+ // 'groupPush' => function($query) {
+ // $query->field('workbenchId,pushInterval,pushContent,pushTime,devices,targetGroups');
+ // },
+ // 'groupCreate' => function($query) {
+ // $query->field('workbenchId,groupNamePrefix,maxGroups,membersPerGroup,devices,targetGroups');
+ // }
+ ];
+
+ $list = Workbench::where($where)
+ ->with($with)
+ ->field('id,name,type,status,autoStart,createTime,updateTime')
+ ->order('id', 'desc')
+ ->page($page, $limit)
+ ->select()
+ ->each(function ($item) {
+ // 处理配置信息
+ switch ($item->type) {
+ case self::TYPE_AUTO_LIKE:
+ if (!empty($item->autoLike)) {
+ $item->config = $item->autoLike;
+ $item->config->devices = json_decode($item->config->devices, true);
+ $item->config->targetGroups = json_decode($item->config->targetGroups, true);
+ $item->config->contentTypes = json_decode($item->config->contentTypes, true);
+ }
+ unset($item->autoLike,$item->auto_like);
+ break;
+ case self::TYPE_MOMENTS_SYNC:
+ if (!empty($item->momentsSync)) {
+ $item->config = $item->momentsSync;
+ $item->config->devices = json_decode($item->config->devices, true);
+ $item->config->targetGroups = json_decode($item->config->targetGroups, true);
+ }
+ break;
+ case self::TYPE_GROUP_PUSH:
+ if (!empty($item->groupPush)) {
+ $item->config = $item->groupPush;
+ $item->config->devices = json_decode($item->config->devices, true);
+ $item->config->targetGroups = json_decode($item->config->targetGroups, true);
+ $item->config->pushContent = json_decode($item->config->pushContent, true);
+ $item->config->pushTime = json_decode($item->config->pushTime, true);
+ }
+ break;
+ case self::TYPE_GROUP_CREATE:
+ if (!empty($item->groupCreate)) {
+ $item->config = $item->groupCreate;
+ $item->config->devices = json_decode($item->config->devices, true);
+ $item->config->targetGroups = json_decode($item->config->targetGroups, true);
+ }
+ break;
+ }
+ unset( $item->momentsSync, $item->groupPush, $item->groupCreate);
+ return $item;
+ });
+
+ $total = Workbench::where($where)->count();
+
+ return json([
+ 'code' => 200,
+ 'msg' => '获取成功',
+ 'data' => [
+ 'list' => $list,
+ 'total' => $total,
+ 'page' => $page,
+ 'limit' => $limit
+ ]
+ ]);
+ }
+
+ /**
+ * 获取工作台详情
+ * @param int $id 工作台ID
+ * @return \think\response\Json
+ */
+ public function detail($id)
+ {
+ if (empty($id)) {
+ return json(['code' => 400, 'msg' => '参数错误']);
+ }
+
+ // 定义关联关系
+ $with = [
+ 'autoLike' => function($query) {
+ $query->field('workbenchId,interval,maxLikes,startTime,endTime,contentTypes,devices,targetGroups');
+ },
+ 'momentsSync' => function($query) {
+ $query->field('workbenchId,syncInterval,syncCount,syncType,devices,targetGroups');
+ },
+ 'groupPush' => function($query) {
+ $query->field('workbenchId,pushInterval,pushContent,pushTime,devices,targetGroups');
+ },
+ 'groupCreate' => function($query) {
+ $query->field('workbenchId,groupNamePrefix,maxGroups,membersPerGroup,devices,targetGroups');
+ }
+ ];
+
+ $workbench = Workbench::where([
+ ['id', '=', $id],
+ ['userId', '=', $this->request->userInfo['id']],
+ ['isDel', '=', 0]
+ ])
+ ->field('id,name,type,status,autoStart,createTime,updateTime')
+ ->with($with)
+ ->find();
+
+ if (empty($workbench)) {
+ return json(['code' => 404, 'msg' => '工作台不存在']);
+ }
+
+ // 处理配置信息
+ switch ($workbench->type) {
+ case self::TYPE_AUTO_LIKE:
+ if (!empty($workbench->autoLike)) {
+ $workbench->config = $workbench->autoLike;
+ $workbench->config->devices = json_decode($workbench->config->devices, true);
+ $workbench->config->targetGroups = json_decode($workbench->config->targetGroups, true);
+ $workbench->config->contentTypes = explode(',', $workbench->config->contentTypes);
+ }
+ break;
+ case self::TYPE_MOMENTS_SYNC:
+ if (!empty($workbench->momentsSync)) {
+ $workbench->config = $workbench->momentsSync;
+ $workbench->config->devices = json_decode($workbench->config->devices, true);
+ $workbench->config->targetGroups = json_decode($workbench->config->targetGroups, true);
+ }
+ break;
+ case self::TYPE_GROUP_PUSH:
+ if (!empty($workbench->groupPush)) {
+ $workbench->config = $workbench->groupPush;
+ $workbench->config->devices = json_decode($workbench->config->devices, true);
+ $workbench->config->targetGroups = json_decode($workbench->config->targetGroups, true);
+ $workbench->config->pushContent = json_decode($workbench->config->pushContent, true);
+ $workbench->config->pushTime = json_decode($workbench->config->pushTime, true);
+ }
+ break;
+ case self::TYPE_GROUP_CREATE:
+ if (!empty($workbench->groupCreate)) {
+ $workbench->config = $workbench->groupCreate;
+ $workbench->config->devices = json_decode($workbench->config->devices, true);
+ $workbench->config->targetGroups = json_decode($workbench->config->targetGroups, true);
+ }
+ break;
+ }
+ unset($workbench->autoLike, $workbench->momentsSync, $workbench->groupPush, $workbench->groupCreate);
+
+ return json(['code' => 200, 'msg' => '获取成功', 'data' => $workbench]);
+ }
+
+ /**
+ * 更新工作台状态
+ * @return \think\response\Json
+ */
+ public function updateStatus()
+ {
+ if (!$this->request->isPost()) {
+ return json(['code' => 400, 'msg' => '请求方式错误']);
+ }
+
+ $param = $this->request->post();
+
+ // 验证数据
+ $validate = new WorkbenchValidate;
+ if (!$validate->scene('update_status')->check($param)) {
+ return json(['code' => 400, 'msg' => $validate->getError()]);
+ }
+
+ $workbench = Workbench::where([
+ ['id', '=', $param['id']],
+ ['userId', '=', $this->request->userInfo['id']]
+ ])->find();
+
+ if (empty($workbench)) {
+ return json(['code' => 404, 'msg' => '工作台不存在']);
+ }
+
+ $workbench->status = !$workbench['status'];
+ $workbench->save();
+
+ return json(['code' => 200, 'msg' => '更新成功']);
+ }
+
+ /**
+ * 删除工作台(软删除)
+ */
+ public function delete($id)
+ {
+ if (empty($id)) {
+ return json(['code' => 400, 'msg' => '参数错误']);
+ }
+
+ $workbench = Workbench::where([
+ ['id', '=', $id],
+ ['userId', '=', $this->request->userInfo['id']],
+ ['isDel', '=', 0]
+ ])->find();
+
+ if (!$workbench) {
+ return json(['code' => 404, 'msg' => '工作台不存在']);
+ }
+
+ // 软删除
+ $workbench->isDel = 1;
+ $workbench->deleteTime = date('Y-m-d H:i:s');
+ $workbench->save();
+
+ return json(['code' => 200, 'msg' => '删除成功']);
+ }
+
+ /**
+ * 拷贝工作台
+ * @return \think\response\Json
+ */
+ public function copy()
+ {
+ if (!$this->request->isPost()) {
+ return json(['code' => 400, 'msg' => '请求方式错误']);
+ }
+
+ $id = $this->request->post('id');
+ if (empty($id)) {
+ return json(['code' => 400, 'msg' => '参数错误']);
+ }
+
+ // 验证权限并获取原数据
+ $workbench = Workbench::where([
+ ['id', '=', $id],
+ ['userId', '=', $this->request->userInfo['id']]
+ ])->find();
+
+ if (empty($workbench)) {
+ return json(['code' => 404, 'msg' => '工作台不存在']);
+ }
+
+ Db::startTrans();
+ try {
+ // 创建新的工作台基本信息
+ $newWorkbench = new Workbench;
+ $newWorkbench->name = $workbench->name . ' copy';
+ $newWorkbench->type = $workbench->type;
+ $newWorkbench->status = 1; // 新拷贝的默认启用
+ $newWorkbench->autoStart = $workbench->autoStart;
+ $newWorkbench->userId = $this->request->userInfo['id'];
+ $newWorkbench->save();
+
+ // 根据类型拷贝对应的配置
+ switch ($workbench->type) {
+ case self::TYPE_AUTO_LIKE:
+ $config = WorkbenchAutoLike::where('workbenchId', $id)->find();
+ if ($config) {
+ $newConfig = new WorkbenchAutoLike;
+ $newConfig->workbenchId = $newWorkbench->id;
+ $newConfig->interval = $config->interval;
+ $newConfig->maxLikes = $config->maxLikes;
+ $newConfig->startTime = $config->startTime;
+ $newConfig->endTime = $config->endTime;
+ $newConfig->contentTypes = $config->contentTypes;
+ $newConfig->devices = $config->devices;
+ $newConfig->targetGroups = $config->targetGroups;
+ $newConfig->save();
+ }
+ break;
+ case self::TYPE_MOMENTS_SYNC:
+ $config = WorkbenchMomentsSync::where('workbenchId', $id)->find();
+ if ($config) {
+ $newConfig = new WorkbenchMomentsSync;
+ $newConfig->workbenchId = $newWorkbench->id;
+ $newConfig->syncInterval = $config->syncInterval;
+ $newConfig->syncCount = $config->syncCount;
+ $newConfig->syncType = $config->syncType;
+ $newConfig->devices = $config->devices;
+ $newConfig->targetGroups = $config->targetGroups;
+ $newConfig->save();
+ }
+ break;
+ case self::TYPE_GROUP_PUSH:
+ $config = WorkbenchGroupPush::where('workbenchId', $id)->find();
+ if ($config) {
+ $newConfig = new WorkbenchGroupPush;
+ $newConfig->workbenchId = $newWorkbench->id;
+ $newConfig->pushInterval = $config->pushInterval;
+ $newConfig->pushContent = $config->pushContent;
+ $newConfig->pushTime = $config->pushTime;
+ $newConfig->devices = $config->devices;
+ $newConfig->targetGroups = $config->targetGroups;
+ $newConfig->save();
+ }
+ break;
+ case self::TYPE_GROUP_CREATE:
+ $config = WorkbenchGroupCreate::where('workbenchId', $id)->find();
+ if ($config) {
+ $newConfig = new WorkbenchGroupCreate;
+ $newConfig->workbenchId = $newWorkbench->id;
+ $newConfig->groupNamePrefix = $config->groupNamePrefix;
+ $newConfig->maxGroups = $config->maxGroups;
+ $newConfig->membersPerGroup = $config->membersPerGroup;
+ $newConfig->devices = $config->devices;
+ $newConfig->targetGroups = $config->targetGroups;
+ $newConfig->save();
+ }
+ break;
+ }
+
+ Db::commit();
+ return json(['code' => 200, 'msg' => '拷贝成功', 'data' => ['id' => $newWorkbench->id]]);
+ } catch (\Exception $e) {
+ Db::rollback();
+ return json(['code' => 500, 'msg' => '拷贝失败:' . $e->getMessage()]);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Server/application/cunkebao/model/Workbench.php b/Server/application/cunkebao/model/Workbench.php
new file mode 100644
index 00000000..4e0cd7bb
--- /dev/null
+++ b/Server/application/cunkebao/model/Workbench.php
@@ -0,0 +1,58 @@
+hasOne('WorkbenchAutoLike', 'workbenchId', 'id');
+ }
+
+ // 朋友圈同步配置关联
+ public function momentsSync()
+ {
+ return $this->hasOne('WorkbenchMomentsSync', 'workbenchId', 'id');
+ }
+
+ // 群消息推送配置关联
+ public function groupPush()
+ {
+ return $this->hasOne('WorkbenchGroupPush', 'workbenchId', 'id');
+ }
+
+ // 自动建群配置关联
+ public function groupCreate()
+ {
+ return $this->hasOne('WorkbenchGroupCreate', 'workbenchId', 'id');
+ }
+
+}
\ No newline at end of file
diff --git a/Server/application/cunkebao/model/WorkbenchAutoLike.php b/Server/application/cunkebao/model/WorkbenchAutoLike.php
new file mode 100644
index 00000000..44fd2630
--- /dev/null
+++ b/Server/application/cunkebao/model/WorkbenchAutoLike.php
@@ -0,0 +1,25 @@
+belongsTo('Workbench', 'workbenchId', 'id');
+ }
+}
\ No newline at end of file
diff --git a/Server/application/cunkebao/model/WorkbenchGroupCreate.php b/Server/application/cunkebao/model/WorkbenchGroupCreate.php
new file mode 100644
index 00000000..a0f85094
--- /dev/null
+++ b/Server/application/cunkebao/model/WorkbenchGroupCreate.php
@@ -0,0 +1,22 @@
+belongsTo('Workbench', 'workbenchId', 'id');
+ }
+}
\ No newline at end of file
diff --git a/Server/application/cunkebao/model/WorkbenchGroupPush.php b/Server/application/cunkebao/model/WorkbenchGroupPush.php
new file mode 100644
index 00000000..4dc3aba7
--- /dev/null
+++ b/Server/application/cunkebao/model/WorkbenchGroupPush.php
@@ -0,0 +1,22 @@
+belongsTo('Workbench', 'workbenchId', 'id');
+ }
+}
\ No newline at end of file
diff --git a/Server/application/cunkebao/model/WorkbenchMomentsSync.php b/Server/application/cunkebao/model/WorkbenchMomentsSync.php
new file mode 100644
index 00000000..5a343e2a
--- /dev/null
+++ b/Server/application/cunkebao/model/WorkbenchMomentsSync.php
@@ -0,0 +1,22 @@
+belongsTo('Workbench', 'workbenchId', 'id');
+ }
+}
\ No newline at end of file
diff --git a/Server/application/cunkebao/validate/Workbench.php b/Server/application/cunkebao/validate/Workbench.php
new file mode 100644
index 00000000..63a5d502
--- /dev/null
+++ b/Server/application/cunkebao/validate/Workbench.php
@@ -0,0 +1,128 @@
+ 'require|max:100',
+ 'type' => 'require|in:1,2,3,4',
+ //'autoStart' => 'require|boolean',
+ // 自动点赞特有参数
+ 'interval' => 'requireIf:type,1|number|min:1',
+ 'maxLikes' => 'requireIf:type,1|number|min:1',
+ 'startTime' => 'requireIf:type,1|dateFormat:H:i',
+ 'endTime' => 'requireIf:type,1|dateFormat:H:i',
+ 'contentTypes' => 'requireIf:type,1|array|contentTypeEnum:text,image,video',
+ // 朋友圈同步特有参数
+ 'syncInterval' => 'requireIf:type,2|number|min:1',
+ 'syncCount' => 'requireIf:type,2|number|min:1',
+ 'syncType' => 'requireIf:type,2|in:1,2,3,4',
+ // 群消息推送特有参数
+ 'pushInterval' => 'requireIf:type,3|number|min:1',
+ 'pushContent' => 'requireIf:type,3|array',
+ 'pushTime' => 'requireIf:type,3|array',
+ // 自动建群特有参数
+ 'groupNamePrefix' => 'requireIf:type,4|max:50',
+ 'maxGroups' => 'requireIf:type,4|number|min:1',
+ 'membersPerGroup' => 'requireIf:type,4|number|min:1',
+ // 通用参数
+ 'devices' => 'require|array',
+ 'targetGroups' => 'require|array'
+ ];
+
+ /**
+ * 错误信息
+ */
+ protected $message = [
+ 'name.require' => '请输入任务名称',
+ 'name.max' => '任务名称最多100个字符',
+ 'type.require' => '请选择工作台类型',
+ 'type.in' => '工作台类型错误',
+ 'autoStart.require' => '请选择是否自动启动',
+ 'autoStart.boolean' => '自动启动参数必须为布尔值',
+ // 自动点赞相关提示
+ 'interval.requireIf' => '请设置点赞间隔',
+ 'interval.number' => '点赞间隔必须为数字',
+ 'interval.min' => '点赞间隔必须大于0',
+ 'maxLikes.requireIf' => '请设置每日最大点赞数',
+ 'maxLikes.number' => '每日最大点赞数必须为数字',
+ 'maxLikes.min' => '每日最大点赞数必须大于0',
+ 'startTime.requireIf' => '请设置开始时间',
+ 'startTime.dateFormat' => '开始时间格式错误',
+ 'endTime.requireIf' => '请设置结束时间',
+ 'endTime.dateFormat' => '结束时间格式错误',
+ 'contentTypes.requireIf' => '请选择点赞内容类型',
+ 'contentTypes.array' => '点赞内容类型必须是数组',
+ 'contentTypes.contentTypeEnum' => '点赞内容类型只能是text、image、video',
+ // 朋友圈同步相关提示
+ 'syncInterval.requireIf' => '请设置同步间隔',
+ 'syncInterval.number' => '同步间隔必须为数字',
+ 'syncInterval.min' => '同步间隔必须大于0',
+ 'syncCount.requireIf' => '请设置同步数量',
+ 'syncCount.number' => '同步数量必须为数字',
+ 'syncCount.min' => '同步数量必须大于0',
+ 'syncType.requireIf' => '请选择同步类型',
+ 'syncType.in' => '同步类型错误',
+ // 群消息推送相关提示
+ 'pushInterval.requireIf' => '请设置推送间隔',
+ 'pushInterval.number' => '推送间隔必须为数字',
+ 'pushInterval.min' => '推送间隔必须大于0',
+ 'pushContent.requireIf' => '请设置推送内容',
+ 'pushContent.array' => '推送内容格式错误',
+ 'pushTime.requireIf' => '请设置推送时间',
+ 'pushTime.array' => '推送时间格式错误',
+ // 自动建群相关提示
+ 'groupNamePrefix.requireIf' => '请设置群名称前缀',
+ 'groupNamePrefix.max' => '群名称前缀最多50个字符',
+ 'maxGroups.requireIf' => '请设置最大建群数量',
+ 'maxGroups.number' => '最大建群数量必须为数字',
+ 'maxGroups.min' => '最大建群数量必须大于0',
+ 'membersPerGroup.requireIf' => '请设置每个群的人数',
+ 'membersPerGroup.number' => '每个群的人数必须为数字',
+ 'membersPerGroup.min' => '每个群的人数必须大于0',
+ // 通用提示
+ 'devices.require' => '请选择设备',
+ 'devices.array' => '设备格式错误',
+ 'targetGroups.require' => '请选择目标用户组',
+ 'targetGroups.array' => '目标用户组格式错误'
+ ];
+
+ /**
+ * 验证场景
+ */
+ protected $scene = [
+ 'create' => ['name', 'type', 'autoStart', 'devices', 'targetGroups',
+ 'interval', 'maxLikes', 'startTime', 'endTime', 'contentTypes',
+ 'syncInterval', 'syncCount', 'syncType',
+ 'pushInterval', 'pushContent', 'pushTime',
+ 'groupNamePrefix', 'maxGroups', 'membersPerGroup'
+ ],
+ 'update_status' => ['id', 'status']
+ ];
+
+ /**
+ * 自定义验证规则
+ */
+ protected function contentTypeEnum($value, $rule, $data)
+ {
+ $allowTypes = explode(',', $rule);
+ foreach ($value as $type) {
+ if (!in_array($type, $allowTypes)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
\ No newline at end of file