diff --git a/Cunkebao/app/workspace/group-push/[id]/edit/page.tsx b/Cunkebao/app/workspace/group-push/[id]/edit/page.tsx index c9d26b22..d11531ef 100644 --- a/Cunkebao/app/workspace/group-push/[id]/edit/page.tsx +++ b/Cunkebao/app/workspace/group-push/[id]/edit/page.tsx @@ -1,121 +1,137 @@ "use client" -import { useState, useEffect } from "react" +import { use, useState, useEffect } from "react" import { useRouter } from "next/navigation" +import { ArrowLeft } from "lucide-react" import { Button } from "@/components/ui/button" -import { Card, CardContent } from "@/components/ui/card" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" import { StepIndicator } from "../../components/step-indicator" -import { MessageEditor } from "../../components/message-editor" -import { FriendSelector } from "../../components/friend-selector" -import { ArrowLeft, ArrowRight, Check, Loader2 } from "lucide-react" +import { BasicSettings } from "../../components/basic-settings" +import { GroupSelector } from "../../components/group-selector" +import { ContentSelector } from "../../components/content-selector" +import type { WechatGroup, ContentLibrary } from "@/types/group-push" import { useToast } from "@/components/ui/use-toast" +import { api } from "@/lib/api" +import { showToast } from "@/lib/toast" -const steps = ["推送信息", "选择好友"] +const steps = [ + { id: 1, title: "步骤 1", subtitle: "基础设置" }, + { id: 2, title: "步骤 2", subtitle: "选择社群" }, + { id: 3, title: "步骤 3", subtitle: "选择内容库" }, + { id: 4, title: "步骤 4", subtitle: "京东联盟" }, +] -// 模拟数据 -const mockPushTasks = { - "1": { - id: "1", - name: "618活动推广消息", - content: { - text: "618年中大促,全场商品5折起!限时抢购,先到先得!", - images: ["/placeholder.svg?height=200&width=200"], - video: null, - link: "https://example.com/618", - }, - selectedFriends: ["1", "3", "5"], - pushTime: "2025-06-18 10:00:00", - progress: 100, - status: "已完成", - }, - "2": { - id: "2", - name: "新品上市通知", - content: { - text: "我们的新产品已经上市,快来体验吧!", - images: [], - video: "/placeholder.svg?height=400&width=400", - link: null, - }, - selectedFriends: ["2", "4", "6", "8"], - pushTime: "2025-03-25 09:30:00", - progress: 75, - status: "进行中", - }, -} - -export default function EditPushPage({ params }: { params: { id: string } }) { +export default function EditGroupPushPage({ params }: { params: Promise<{ id: string }> }) { + const { id } = use(params) const router = useRouter() const { toast } = useToast() + const [currentStep, setCurrentStep] = useState(1) const [loading, setLoading] = useState(true) - const [currentStep, setCurrentStep] = useState(0) - const [taskName, setTaskName] = useState("") - const [messageContent, setMessageContent] = useState({ - text: "", - images: [], - video: null, - link: null, + const [formData, setFormData] = useState({ + name: "", + pushTimeStart: "06:00", + pushTimeEnd: "23:59", + dailyPushCount: 20, + pushOrder: "latest" as "earliest" | "latest", + isLoopPush: false, + isImmediatePush: false, + isEnabled: false, + groups: [] as WechatGroup[], + contentLibraries: [] as ContentLibrary[], }) - const [selectedFriends, setSelectedFriends] = useState([]) + // 拉取详情 useEffect(() => { - // 模拟加载数据 - setTimeout(() => { - const task = mockPushTasks[params.id as keyof typeof mockPushTasks] - if (task) { - setTaskName(task.name) - setMessageContent(task.content) - setSelectedFriends(task.selectedFriends) - } - setLoading(false) - }, 500) - }, [params.id]) - - const handleNext = () => { - if (currentStep === 0) { - // 验证第一步 - if (!taskName.trim()) { - toast({ - title: "请输入任务名称", - variant: "destructive", - }) - return - } - - if (!messageContent.text && messageContent.images.length === 0 && !messageContent.video && !messageContent.link) { - toast({ - title: "请添加至少一种消息内容", - variant: "destructive", - }) - return + const fetchDetail = async () => { + setLoading(true) + try { + const res = await api.get(`/v1/workbench/detail?id=${id}`) as any + if (res.code === 200 && res.data) { + const data = res.data + setFormData({ + name: data.name || "", + pushTimeStart: data.config?.startTime || "06:00", + pushTimeEnd: data.config?.endTime || "23:59", + dailyPushCount: data.config?.maxPerDay || 20, + pushOrder: data.config?.pushOrder === 2 ? "latest" : "earliest", + isLoopPush: data.config?.isLoop === 1, + isImmediatePush: false, // 详情接口如有此字段可补充 + isEnabled: data.status === 1, + groups: (data.config.groupList || []).map((item: any) => ({ + id: String(item.id), + name: item.groupName, + avatar: item.groupAvatar || item.avatar, + serviceAccount: { + id: item.ownerWechatId, + name: item.nickName, + avatar: "", + }, + })), + contentLibraries: (data.config.contentLibraryList || []).map((item: any) => ({ + id: String(item.id), + name: item.name, + sourceType: item.sourceType, + selectedFriends: item.selectedFriends || [], + selectedGroups: item.selectedGroups || [], + })), + }) + } else { + showToast(res.msg || "获取详情失败", "error") + } + } catch (e) { + showToast((e as any)?.message || "网络错误", "error") + } finally { + setLoading(false) } } + fetchDetail() + // eslint-disable-next-line + }, [id]) - setCurrentStep((prev) => prev + 1) + const handleBasicSettingsNext = (values: any) => { + setFormData((prev) => ({ ...prev, ...values })) + setCurrentStep(2) } - const handlePrevious = () => { - setCurrentStep((prev) => prev - 1) + const handleGroupsChange = (groups: WechatGroup[]) => { + setFormData((prev) => ({ ...prev, groups })) } - const handleSubmit = () => { - if (selectedFriends.length === 0) { - toast({ - title: "请选择至少一个好友", - variant: "destructive", - }) - return + const handleLibrariesChange = (contentLibraries: ContentLibrary[]) => { + setFormData((prev) => ({ ...prev, contentLibraries })) + } + + const handleSave = async () => { + const loadingToast = showToast("正在保存...", "loading", true) + try { + const paramsData = { + id, + name: formData.name, + type: 3, + pushType: 1, + startTime: formData.pushTimeStart, + endTime: formData.pushTimeEnd, + maxPerDay: formData.dailyPushCount, + pushOrder: formData.pushOrder === "latest" ? 2 : 1, + isLoop: formData.isLoopPush ? 1 : 0, + status: formData.isEnabled ? 1 : 0, + groups: (formData.groups || []).filter(g => g && g.id).map((g: any) => g.id), + contentLibraries: (formData.contentLibraries || []).filter(c => c && c.id).map((c: any) => c.id), + } + const res = await api.post("/v1/workbench/update", paramsData) as any + loadingToast.remove() + if (res.code === 200) { + showToast("保存成功", "success") + router.push("/workspace/group-push") + } else { + showToast(res.msg || "保存失败", "error") + } + } catch (e) { + loadingToast.remove() + showToast((e as any)?.message || "网络错误", "error") } + } - // 模拟提交 - toast({ - title: "推送任务更新成功", - description: `已更新推送任务 "${taskName}"`, - }) - - // 跳转回列表页 + const handleCancel = () => { router.push("/workspace/group-push") } @@ -123,7 +139,7 @@ export default function EditPushPage({ params }: { params: { id: string } }) { return (
- +

加载中...

@@ -131,62 +147,77 @@ export default function EditPushPage({ params }: { params: { id: string } }) { } return ( -
- {/* 顶部导航栏 */} -
- -

编辑推送

-
{/* 占位元素,保持标题居中 */} +

编辑社群推送任务

- - - {currentStep === 0 ? ( -
-
- - setTaskName(e.target.value)} - /> -
+
+ {currentStep === 1 && ( + + )} -
- - -
+ {currentStep === 2 && ( + setCurrentStep(1)} + onNext={() => setCurrentStep(3)} + onSave={handleSave} + onCancel={handleCancel} + /> + )} -
- -
+ {currentStep === 3 && ( + setCurrentStep(2)} + onNext={() => setCurrentStep(4)} + onSave={handleSave} + onCancel={handleCancel} + /> + )} + + {currentStep === 4 && ( +
+
+ 京东联盟设置(此步骤为占位,实际功能待开发)
- ) : ( -
- -
- - -
+
+ + +
- )} - - +
+ )} +
) } diff --git a/Cunkebao/app/workspace/group-push/components/basic-settings.tsx b/Cunkebao/app/workspace/group-push/components/basic-settings.tsx index d53475e8..c8e5d04e 100644 --- a/Cunkebao/app/workspace/group-push/components/basic-settings.tsx +++ b/Cunkebao/app/workspace/group-push/components/basic-settings.tsx @@ -16,7 +16,7 @@ interface BasicSettingsProps { dailyPushCount: number pushOrder: "earliest" | "latest" isLoopPush: boolean - isImmediatePush: boolean + pushTypePush: boolean isEnabled: boolean } onNext: (values: any) => void @@ -32,7 +32,7 @@ export function BasicSettings({ dailyPushCount: 20, pushOrder: "latest", isLoopPush: false, - isImmediatePush: false, + pushTypePush: false, isEnabled: false, }, onNext, @@ -164,20 +164,20 @@ export function BasicSettings({ {/* 是否立即推送 */}
-
- {values.isImmediatePush && ( + {values.pushTypePush && (
如果启用,系统会把内容库里所有的内容按顺序推送到指定的社群
diff --git a/Cunkebao/app/workspace/group-push/components/content-selector.tsx b/Cunkebao/app/workspace/group-push/components/content-selector.tsx index ee2fd1ad..34123fd4 100644 --- a/Cunkebao/app/workspace/group-push/components/content-selector.tsx +++ b/Cunkebao/app/workspace/group-push/components/content-selector.tsx @@ -1,6 +1,6 @@ "use client" -import { useState } from "react" +import { useState, useEffect } from "react" import { Button } from "@/components/ui/button" import { Card, CardContent } from "@/components/ui/card" import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" @@ -8,32 +8,8 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@ import { Input } from "@/components/ui/input" import { Search, Plus, Trash2 } from "lucide-react" import type { ContentLibrary } from "@/types/group-push" - -// 模拟数据 -const mockContentLibraries: ContentLibrary[] = [ - { - id: "1", - name: "测试11", - targets: [{ id: "t1", avatar: "/placeholder.svg?height=40&width=40" }], - }, - { - id: "2", - name: "测试166666", - targets: [ - { id: "t2", avatar: "/placeholder.svg?height=40&width=40" }, - { id: "t3", avatar: "/placeholder.svg?height=40&width=40" }, - { id: "t4", avatar: "/placeholder.svg?height=40&width=40" }, - ], - }, - { - id: "3", - name: "产品介绍", - targets: [ - { id: "t5", avatar: "/placeholder.svg?height=40&width=40" }, - { id: "t6", avatar: "/placeholder.svg?height=40&width=40" }, - ], - }, -] +import { api } from "@/lib/api" +import { showToast } from "@/lib/toast" interface ContentSelectorProps { selectedLibraries: ContentLibrary[] @@ -54,6 +30,44 @@ export function ContentSelector({ }: ContentSelectorProps) { const [isDialogOpen, setIsDialogOpen] = useState(false) const [searchTerm, setSearchTerm] = useState("") + const [libraries, setLibraries] = useState([]) + const [loading, setLoading] = useState(false) + + // 拉取内容库列表 + const fetchLibraries = async (keyword = "") => { + setLoading(true) + try { + const params = new URLSearchParams({ + page: "1", + limit: "100", + keyword: keyword.trim(), + }) + const res = await api.get(`/v1/content/library/list?${params.toString()}`) as any + if (res.code === 200 && Array.isArray(res.data?.list)) { + setLibraries(res.data.list) + } else { + setLibraries([]) + showToast(res.msg || "获取内容库失败", "error") + } + } catch (e) { + setLibraries([]) + showToast((e as any)?.message || "网络错误", "error") + } finally { + setLoading(false) + } + } + + // 弹窗打开/搜索时拉取 + useEffect(() => { + if (isDialogOpen) { + fetchLibraries(searchTerm) + } + // eslint-disable-next-line + }, [isDialogOpen]) + + const handleSearch = () => { + fetchLibraries(searchTerm) + } const handleAddLibrary = (library: ContentLibrary) => { if (!selectedLibraries.some((l) => l.id === library.id)) { @@ -66,7 +80,7 @@ export function ContentSelector({ onLibrariesChange(selectedLibraries.filter((library) => library.id !== libraryId)) } - const filteredLibraries = mockContentLibraries.filter((library) => + const filteredLibraries = libraries.filter((library) => library.name.toLowerCase().includes(searchTerm.toLowerCase()), ) @@ -104,14 +118,14 @@ export function ContentSelector({ {library.name}
- {library.targets.map((target) => ( + {(((library as any).sourceType === 1 ? (library as any).selectedFriends : (library as any).selectedGroups) || []).map((target: any) => (
Target
@@ -161,17 +175,24 @@ export function ContentSelector({ 选择内容库
-
+
setSearchTerm(e.target.value)} + onKeyDown={e => e.key === 'Enter' && handleSearch()} /> +
+ {loading ? ( +
加载中...
+ ) : filteredLibraries.length === 0 ? ( +
暂无内容库
+ ) : ( @@ -188,11 +209,14 @@ export function ContentSelector({ {library.name}
- {library.targets.map((target) => ( -
+ {(((library as any).sourceType === 1 ? (library as any).selectedFriends : (library as any).selectedGroups) || []).map((target: any) => ( +
Target
@@ -201,19 +225,19 @@ export function ContentSelector({ ))}
+ )}
diff --git a/Cunkebao/app/workspace/group-push/components/group-selector.tsx b/Cunkebao/app/workspace/group-push/components/group-selector.tsx index 8072d7f9..cf84eba4 100644 --- a/Cunkebao/app/workspace/group-push/components/group-selector.tsx +++ b/Cunkebao/app/workspace/group-push/components/group-selector.tsx @@ -1,6 +1,6 @@ "use client" -import { useState } from "react" +import { useState, useEffect } from "react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Card, CardContent } from "@/components/ui/card" @@ -8,40 +8,8 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/u import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { Search, Plus, Trash2 } from "lucide-react" import type { WechatGroup } from "@/types/group-push" - -// 模拟数据 -const mockGroups: WechatGroup[] = [ - { - id: "1", - name: "快捷语", - avatar: "/placeholder.svg?height=40&width=40", - serviceAccount: { - id: "sa1", - name: "贝蒂喜品牌wxid_rtlwsjytjk1991", - avatar: "/placeholder.svg?height=40&width=40", - }, - }, - { - id: "2", - name: "产品交流群", - avatar: "/placeholder.svg?height=40&width=40", - serviceAccount: { - id: "sa1", - name: "贝蒂喜品牌wxid_rtlwsjytjk1991", - avatar: "/placeholder.svg?height=40&width=40", - }, - }, - { - id: "3", - name: "客户服务群", - avatar: "/placeholder.svg?height=40&width=40", - serviceAccount: { - id: "sa2", - name: "客服小助手wxid_abc123", - avatar: "/placeholder.svg?height=40&width=40", - }, - }, -] +import { api } from "@/lib/api" +import { showToast } from "@/lib/toast" interface GroupSelectorProps { selectedGroups: WechatGroup[] @@ -63,6 +31,63 @@ export function GroupSelector({ const [isDialogOpen, setIsDialogOpen] = useState(false) const [searchTerm, setSearchTerm] = useState("") const [serviceFilter, setServiceFilter] = useState("") + const [groups, setGroups] = useState([]) + const [total, setTotal] = useState(0) + const [loading, setLoading] = useState(false) + const [currentPage, setCurrentPage] = useState(1) + const pageSize = 10 + + // 拉取群列表 + const fetchGroups = async (page = 1, keyword = "") => { + setLoading(true) + try { + const params = new URLSearchParams({ + page: page.toString(), + limit: pageSize.toString(), + keyword: keyword.trim(), + }) + const res = await api.get(`/v1/workbench/group-list?${params.toString()}`) as any + if (res.code === 200 && Array.isArray(res.data.list)) { + const mappedList = (res.data.list || []).map((item: any) => ({ + ...item, // 保留所有原始字段,方便渲染 + id: String(item.id), + name: item.groupName, + avatar: item.groupAvatar, + serviceAccount: { + id: item.ownerWechatId, + name: item.nickName, + avatar: item.avatar, // 可补充 + }, + })) + setGroups(mappedList) + setTotal(res.data.total || mappedList.length) + } else { + setGroups([]) + setTotal(0) + showToast(res.msg || "获取群列表失败", "error") + } + } catch (e) { + setGroups([]) + setTotal(0) + showToast((e as any)?.message || "网络错误", "error") + } finally { + setLoading(false) + } + } + + // 弹窗打开/搜索/翻页时拉取 + useEffect(() => { + if (isDialogOpen) { + fetchGroups(currentPage, searchTerm) + } + // eslint-disable-next-line + }, [isDialogOpen, currentPage]) + + // 搜索时重置页码 + const handleSearch = () => { + setCurrentPage(1) + fetchGroups(1, searchTerm) + } const handleAddGroup = (group: WechatGroup) => { if (!selectedGroups.some((g) => g.id === group.id)) { @@ -75,10 +100,10 @@ export function GroupSelector({ onGroupsChange(selectedGroups.filter((group) => group.id !== groupId)) } - const filteredGroups = mockGroups.filter((group) => { - const matchesSearch = group.name.toLowerCase().includes(searchTerm.toLowerCase()) - const matchesService = !serviceFilter || group.serviceAccount.name.includes(serviceFilter) - return matchesSearch && matchesService + // 过滤客服(本地过滤) + const filteredGroups = groups.filter((group) => { + const matchesService = !serviceFilter || group.serviceAccount?.name?.includes(serviceFilter) + return matchesService }) return ( @@ -121,22 +146,14 @@ export function GroupSelector({ className="w-full h-full object-cover" />
- {group.name} +
+
{group.name}
+
群主:{group.serviceAccount?.name}
+
-
-
-
- {group.serviceAccount.name} -
-
- {group.serviceAccount.name} -
+ 群主:{group.serviceAccount?.name}
@@ -200,51 +218,69 @@ export function GroupSelector({ onChange={(e) => setServiceFilter(e.target.value)} />
+
- -
- - - - 序号 - 群信息 - 归属客服 - 操作 - - - - {filteredGroups.map((group, index) => ( - - {index + 1} - -
-
- {group.name} -
- {group.name} -
-
- {group.serviceAccount.name} - - - +
+ {loading ? ( +
加载中...
+ ) : filteredGroups.length === 0 ? ( +
暂无群聊
+ ) : ( +
+ + + 序号 + 群信息 + 推送客服 + 操作 - ))} - -
+ + + {filteredGroups.map((group, index) => ( + + {(currentPage - 1) * pageSize + index + 1} + +
+
+ {group.name} +
+
+
{group.name}
+
群主:{group.serviceAccount?.name}
+
+
+
+ + 群主:{group.serviceAccount?.name} + + + + +
+ ))} +
+ + )}
+ {/* 分页 */} + {total > pageSize && ( +
+ + 第 {currentPage} / {Math.ceil(total / pageSize)} 页 + +
+ )} diff --git a/Cunkebao/app/workspace/group-push/new/page.tsx b/Cunkebao/app/workspace/group-push/new/page.tsx index e989b88e..f795771a 100644 --- a/Cunkebao/app/workspace/group-push/new/page.tsx +++ b/Cunkebao/app/workspace/group-push/new/page.tsx @@ -10,6 +10,8 @@ import { GroupSelector } from "../components/group-selector" import { ContentSelector } from "../components/content-selector" import type { WechatGroup, ContentLibrary } from "@/types/group-push" import { useToast } from "@/components/ui/use-toast" +import { api } from "@/lib/api" +import { showToast } from "@/lib/toast" const steps = [ { id: 1, title: "步骤 1", subtitle: "基础设置" }, @@ -48,13 +50,34 @@ export default function NewGroupPushPage() { setFormData((prev) => ({ ...prev, contentLibraries })) } - const handleSave = () => { - // 这里可以添加保存逻辑,例如API调用 - toast({ - title: "保存成功", - description: `社群推送任务"${formData.name || "未命名任务"}"已保存`, - }) - router.push("/workspace/group-push") + const handleSave = async () => { + const loadingToast = showToast("正在保存...", "loading", true) + try { + const params = { + name: formData.name, + type: 3, + pushType: 1, + startTime: formData.pushTimeStart, + endTime: formData.pushTimeEnd, + maxPerDay: formData.dailyPushCount, + pushOrder: formData.pushOrder === "latest" ? 2 : 1, + isLoop: formData.isLoopPush ? 1 : 0, + status: formData.isEnabled ? 1 : 0, + groups: formData.groups.map(g => g.id), + contentLibraries: formData.contentLibraries.map(c => c.id), + } + const res = await api.post("/v1/workbench/create", params) + loadingToast.remove() + if (res.code === 200) { + showToast("保存成功", "success") + router.push("/workspace/group-push") + } else { + showToast(res.msg || "保存失败", "error") + } + } catch (e) { + loadingToast.remove() + showToast(e?.message || "网络错误", "error") + } } const handleCancel = () => { diff --git a/Cunkebao/app/workspace/group-push/page.tsx b/Cunkebao/app/workspace/group-push/page.tsx index 0580e5e2..906b0555 100644 --- a/Cunkebao/app/workspace/group-push/page.tsx +++ b/Cunkebao/app/workspace/group-push/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useState } from "react" +import { useState, useEffect } from "react" import Link from "next/link" import { useRouter } from "next/navigation" import { Button } from "@/components/ui/button" @@ -18,78 +18,121 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge import { PlusCircle, MoreVertical, Edit, Trash2, ArrowLeft, Clock, Search, Filter, RefreshCw } from "lucide-react" import { Input } from "@/components/ui/input" import { Badge } from "@/components/ui/badge" +import { api } from "@/lib/api" +import { showToast } from "@/lib/toast" -// 模拟数据 -const mockTasks = [ - { - id: "1", - name: "社群推送测试", - pushTimeRange: "06:00 - 23:59", - dailyPushCount: 20, - pushOrder: "latest", - isLoopPush: false, - isEnabled: true, - groupCount: 3, - contentLibraryCount: 2, - createdAt: "2025-03-15 14:30", - lastPushTime: "2025-03-20 10:25", - totalPushCount: 245, - }, - { - id: "2", - name: "产品更新推送", - pushTimeRange: "09:00 - 21:00", - dailyPushCount: 15, - pushOrder: "earliest", - isLoopPush: true, - isEnabled: false, - groupCount: 5, - contentLibraryCount: 1, - createdAt: "2025-03-10 10:15", - lastPushTime: "2025-03-19 16:45", - totalPushCount: 128, - }, - { - id: "3", - name: "新客户欢迎", - pushTimeRange: "08:00 - 22:00", - dailyPushCount: 10, - pushOrder: "latest", - isLoopPush: true, - isEnabled: true, - groupCount: 2, - contentLibraryCount: 1, - createdAt: "2025-03-05 09:20", - lastPushTime: "2025-03-18 11:30", - totalPushCount: 87, - }, -] +interface GroupPushTask { + id: string + name: string + status: number + config: { + maxPerDay: number + pushOrder: number + isLoop: number + groups: any[] + contentLibraries: any[] + lastPushTime: string + createTime: string + } +} export default function GroupPushPage() { const router = useRouter() - const [tasks, setTasks] = useState(mockTasks) + const [tasks, setTasks] = useState([]) const [searchTerm, setSearchTerm] = useState("") const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) const [taskToDelete, setTaskToDelete] = useState(null) + const [loading, setLoading] = useState(false) + const [currentPage, setCurrentPage] = useState(1) + const [total, setTotal] = useState(0) + const pageSize = 10 + + // 拉取数据 + const fetchTasks = async (page = 1, search = "") => { + setLoading(true) + const loadingToast = showToast("正在加载...", "loading", true) + try { + const params = new URLSearchParams({ + type: "3", + page: page.toString(), + limit: pageSize.toString(), + }) + if (search) params.append("keyword", search) + const res = await api.get(`/v1/workbench/list?${params.toString()}`) as any + loadingToast.remove() + if (res.code === 200) { + setTasks(res.data.list) + setTotal(res.data.total) + } else { + showToast(res.msg || "获取失败", "error") + } + } catch (e) { + loadingToast.remove() + showToast((e as any)?.message || "网络错误", "error") + } finally { + setLoading(false) + } + } + + useEffect(() => { + fetchTasks(currentPage, searchTerm) + // eslint-disable-next-line + }, [currentPage]) const handleDelete = (id: string) => { setTaskToDelete(id) setDeleteDialogOpen(true) } - const confirmDelete = () => { + const confirmDelete = async () => { if (taskToDelete) { - setTasks(tasks.filter((task) => task.id !== taskToDelete)) + const loadingToast = showToast("正在删除...", "loading", true) + try { + const res = await api.delete(`/v1/workbench/delete?id=${taskToDelete}`) as any + loadingToast.remove() + if (res.code === 200) { + showToast("删除成功", "success") + fetchTasks(currentPage, searchTerm) + } else { + showToast(res.msg || "删除失败", "error") + } + } catch (e) { + loadingToast.remove() + showToast((e as any)?.message || "网络错误", "error") + } setTaskToDelete(null) } setDeleteDialogOpen(false) } - const handleToggleStatus = (id: string, isEnabled: boolean) => { - setTasks(tasks.map((task) => (task.id === id ? { ...task, isEnabled } : task))) + const handleToggleStatus = async (id: string, enabled: boolean) => { + const loadingToast = showToast("正在更新状态...", "loading", true) + try { + const res = await api.post('/v1/workbench/update-status', { + id, + status: enabled ? 1 : 0 + }) as any + loadingToast.remove() + if (res.code === 200) { + showToast("状态已更新", "success") + fetchTasks(currentPage, searchTerm) + } else { + showToast(res.msg || "操作失败", "error") + } + } catch (e) { + loadingToast.remove() + showToast((e as any)?.message || "网络错误", "error") + } } - const filteredTasks = tasks.filter((task) => task.name.toLowerCase().includes(searchTerm.toLowerCase())) + const handleSearch = () => { + setCurrentPage(1) + fetchTasks(1, searchTerm) + } + + const handleRefresh = () => { + fetchTasks(currentPage, searchTerm) + } return (
@@ -122,31 +165,59 @@ export default function GroupPushPage() { className="pl-9" value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} + onKeyDown={e => e.key === 'Enter' && handleSearch()} />
- - {/* 任务列表 */} + {loading ? (
- {filteredTasks.map((task) => ( + {[...Array(3)].map((_, index) => ( + +
+
+
+
+
+
+ ))} +
+ ) : tasks.length === 0 ? ( +
+
+ +
+

暂无社群推送任务

+

点击"新建任务"按钮创建您的第一个社群推送任务

+ + + +
+ ) : ( +
+ {tasks.map((task) => (

{task.name}

- - {task.isEnabled ? "进行中" : "已暂停"} + + {task.status === 1 ? "进行中" : "已暂停"}
handleToggleStatus(task.id, checked)} /> @@ -193,42 +264,51 @@ export default function GroupPushPage() {
-
推送设备:{task.groupCount} 个
-
内容库:{task.contentLibraryCount} 个
-
推送时间:{task.pushTimeRange}
+
推送群数:{task.config.groups?.length || 0} 个
+
内容库:{task.config.contentLibraries?.length || 0} 个
-
每日推送:{task.dailyPushCount} 条
-
已推送:{task.totalPushCount} 条
-
推送顺序:{task.pushOrder === "latest" ? "按最新" : "按最早"}
+
每日推送:{task.config.maxPerDay} 条
+
推送顺序:{task.config.pushOrder === 2 ? "按最新" : "按最早"}
+
循环推送:{task.config.isLoop === 1 ? "是" : "否"}
- 上次推送:{task.lastPushTime} + 上次推送:{task.config.lastPushTime || "--"}
-
创建时间:{task.createdAt}
+
创建时间:{task.config.createTime || "--"}
))}
+ )} - {/* 空状态 */} - {filteredTasks.length === 0 && ( -
-
- + {/* 分页 */} + {!loading && total > pageSize && ( +
+ +
+ 第 {currentPage} 页 + 共 {Math.ceil(total / pageSize)} 页
-

暂无社群推送任务

-

点击"新建任务"按钮创建您的第一个社群推送任务

- - -
)}
diff --git a/Server/application/cunkebao/config/route.php b/Server/application/cunkebao/config/route.php index 331a7c5c..f5349467 100644 --- a/Server/application/cunkebao/config/route.php +++ b/Server/application/cunkebao/config/route.php @@ -65,6 +65,7 @@ Route::group('v1/', function () { Route::get('like-records', 'app\cunkebao\controller\WorkbenchController@getLikeRecords'); // 获取点赞记录列表 Route::get('moments-records', 'app\cunkebao\controller\WorkbenchController@getMomentsRecords'); // 获取朋友圈发布记录列表 Route::get('device-labels', 'app\cunkebao\controller\WorkbenchController@getDeviceLabels'); // 获取设备微信好友标签统计 + Route::get('group-list', 'app\cunkebao\controller\WorkbenchController@getGroupList'); // 获取群列表 }); // 内容库相关 diff --git a/Server/application/cunkebao/controller/WorkbenchController.php b/Server/application/cunkebao/controller/WorkbenchController.php index 0fa2b422..c088eb3a 100644 --- a/Server/application/cunkebao/controller/WorkbenchController.php +++ b/Server/application/cunkebao/controller/WorkbenchController.php @@ -11,6 +11,7 @@ use app\cunkebao\validate\Workbench as WorkbenchValidate; use think\Controller; use think\Db; use app\cunkebao\model\WorkbenchTrafficConfig; +use app\cunkebao\model\ContentLibrary; /** * 工作台控制器 @@ -101,11 +102,17 @@ class WorkbenchController extends Controller 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->pushType = !empty($param['pushType']) ? 1 : 0; // 推送方式:定时/立即 + $config->startTime = $param['startTime']; + $config->endTime = $param['endTime']; + $config->maxPerDay = intval($param['maxPerDay']); // 每日推送数 + $config->pushOrder = $param['pushOrder']; // 推送顺序 + $config->isLoop = !empty($param['isLoop']) ? 1 : 0; // 是否循环 + $config->status = !empty($param['status']) ? 1 : 0; // 是否启用 + $config->groups = json_encode($param['groups'], JSON_UNESCAPED_UNICODE); // 群组信息 + $config->contentLibraries = json_encode($param['contentLibraries'], JSON_UNESCAPED_UNICODE); // 内容库信息 + $config->createTime = time(); + $config->updateTime = time(); $config->save(); break; case self::TYPE_GROUP_CREATE: // 自动建群 @@ -153,7 +160,7 @@ class WorkbenchController extends Controller $page = $this->request->param('page', 1); $limit = $this->request->param('limit', 10); $type = $this->request->param('type', ''); - $keyword = $this->request->param('name', ''); + $keyword = $this->request->param('keyword', ''); $where = [ ['userId', '=', $this->request->userInfo['id']], @@ -181,6 +188,9 @@ class WorkbenchController extends Controller 'trafficConfig' => function($query) { $query->field('workbenchId,distributeType,maxPerDay,timeType,startTime,endTime,devices,pools'); }, + 'groupPush' => function($query) { + $query->field('workbenchId,pushType,startTime,endTime,maxPerDay,pushOrder,isLoop,status,groups,contentLibraries'); + }, 'user' => function($query) { $query->field('id,username'); } @@ -228,7 +238,7 @@ class WorkbenchController extends Controller // 获取内容库名称 if (!empty($item->config->contentLibraries)) { - $libraryNames = \app\cunkebao\model\ContentLibrary::where('id', 'in', $item->config->contentLibraries) + $libraryNames = ContentLibrary::where('id', 'in', $item->config->contentLibraries) ->column('name'); $item->config->contentLibraryNames = $libraryNames; } else { @@ -240,10 +250,16 @@ class WorkbenchController extends Controller 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); + $item->config->pushType = $item->config->pushType; + $item->config->startTime = $item->config->startTime; + $item->config->endTime = $item->config->endTime; + $item->config->maxPerDay = $item->config->maxPerDay; + $item->config->pushOrder = $item->config->pushOrder; + $item->config->isLoop = $item->config->isLoop; + $item->config->status = $item->config->status; + $item->config->groups = json_decode($item->config->groups, true); + $item->config->contentLibraries = json_decode($item->config->contentLibraries, true); + $item->config->lastPushTime = '22222'; } unset($item->groupPush,$item->group_push); break; @@ -279,6 +295,7 @@ class WorkbenchController extends Controller $q->whereOrRaw("JSON_CONTAINS(wf.labels, '\"{$label}\"')"); } })->count(); + $totalAccounts = Db::table('s2_company_account') ->alias('a') ->where(['a.departmentId' => $item->companyId, 'a.status' => 0]) @@ -287,15 +304,15 @@ class WorkbenchController extends Controller ->group('a.id') ->count(); - $todayStart = strtotime(date('Y-m-d 00:00:00')); - $todayEnd = strtotime(date('Y-m-d 23:59:59')); $dailyAverage = Db::name('workbench_traffic_config_item') ->where('workbenchId', $item->id) - ->whereTime('createTime', 'between', [$todayStart, $todayEnd]) ->count(); + $day = (time() - strtotime($item->createTime)) / 86400; + $day = intval($day); + if($dailyAverage > 0){ - $dailyAverage = $dailyAverage / $totalAccounts; + $dailyAverage = $dailyAverage / $totalAccounts / $day; } $item->config->total = [ @@ -353,9 +370,9 @@ class WorkbenchController extends Controller 'trafficConfig' => function($query) { $query->field('workbenchId,distributeType,maxPerDay,timeType,startTime,endTime,devices,pools'); }, - // 'groupPush' => function($query) { - // $query->field('workbenchId,pushInterval,pushContent,pushTime,devices,targetGroups'); - // }, + 'groupPush' => function($query) { + $query->field('workbenchId,pushType,startTime,endTime,maxPerDay,pushOrder,isLoop,status,groups,contentLibraries'); + }, // 'groupCreate' => function($query) { // $query->field('workbenchId,groupNamePrefix,maxGroups,membersPerGroup,devices,targetGroups'); // } @@ -366,7 +383,7 @@ class WorkbenchController extends Controller ['userId', '=', $this->request->userInfo['id']], ['isDel', '=', 0] ]) - ->field('id,name,type,status,autoStart,createTime,updateTime') + ->field('id,name,type,status,autoStart,createTime,updateTime,companyId') ->with($with) ->find(); @@ -414,10 +431,78 @@ class WorkbenchController extends Controller 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); + $workbench->config->groups = json_decode($workbench->config->groups, true); + $workbench->config->contentLibraries = json_decode($workbench->config->contentLibraries, true); + + // 获取群 + $groupList = Db::name('wechat_group')->alias('wg') + ->join('wechat_account wa', 'wa.wechatId = wg.ownerWechatId') + ->where('wg.id', 'in', $workbench->config->groups) + ->order('wg.id', 'desc') + ->field('wg.id,wg.name as groupName,wg.ownerWechatId,wa.nickName,wa.avatar,wa.alias,wg.avatar as groupAvatar') + ->select(); + $workbench->config->groupList = $groupList; + // 获取群组内容库 + $contentLibraryList = ContentLibrary::where('id', 'in', $workbench->config->contentLibraries) + ->field('id,name,sourceFriends,sourceGroups,keywordInclude,keywordExclude,aiEnabled,aiPrompt,timeEnabled,timeStart,timeEnd,status,sourceType,userId,createTime,updateTime') + ->with(['user' => function($query) { + $query->field('id,username'); + }]) + ->order('id', 'desc') + ->select(); + + + + // 处理JSON字段 + foreach ($contentLibraryList as &$item) { + $item['sourceFriends'] = json_decode($item['sourceFriends'] ?: '[]', true); + $item['sourceGroups'] = json_decode($item['sourceGroups'] ?: '[]', true); + $item['keywordInclude'] = json_decode($item['keywordInclude'] ?: '[]', true); + $item['keywordExclude'] = json_decode($item['keywordExclude'] ?: '[]', true); + // 添加创建人名称 + $item['creatorName'] = $item['user']['username'] ?? ''; + $item['itemCount'] = Db::name('content_item')->where('libraryId', $item['id'])->count(); + + // 获取好友详细信息 + if (!empty($item['sourceFriends'] && $item['sourceType'] == 1)) { + $friendIds = $item['sourceFriends']; + $friendsInfo = []; + + if (!empty($friendIds)) { + // 查询好友信息,使用wechat_friendship表 + $friendsInfo = Db::name('wechat_friendship')->alias('wf') + ->field('wf.id,wf.wechatId, wa.nickname, wa.avatar') + ->join('wechat_account wa', 'wf.wechatId = wa.wechatId') + ->whereIn('wf.id', $friendIds) + ->select(); + } + + // 将好友信息添加到返回数据中 + $item['selectedFriends'] = $friendsInfo; + } + + + if (!empty($item['sourceGroups']) && $item['sourceType'] == 2) { + $groupIds = $item['sourceGroups']; + $groupsInfo = []; + + if (!empty($groupIds)) { + // 查询群组信息 + $groupsInfo = Db::name('wechat_group')->alias('g') + ->field('g.id, g.chatroomId, g.name, g.avatar, g.ownerWechatId') + ->whereIn('g.id', $groupIds) + ->select(); + } + + // 将群组信息添加到返回数据中 + $item['selectedGroups'] = $groupsInfo; + } + + unset($item['user']); // 移除关联数据 + } + $workbench->config->contentLibraryList = $contentLibraryList; + + unset($workbench->groupPush, $workbench->group_push); } break; case self::TYPE_GROUP_CREATE: @@ -432,13 +517,52 @@ class WorkbenchController extends Controller $workbench->config = $workbench->trafficConfig; $workbench->config->devices = json_decode($workbench->config->devices, true); $workbench->config->pools = json_decode($workbench->config->pools, true); + $config_item = Db::name('workbench_traffic_config_item')->where(['workbenchId' => $workbench->id])->order('id DESC')->find(); + $workbench->config->lastUpdated = !empty($config_item) ? date('Y-m-d H:i',$config_item['createTime']) : '--'; + + + //统计 + $labels = $workbench->config->pools; + $totalUsers = Db::table('s2_wechat_friend')->alias('wf') + ->join(['s2_company_account' => 'sa'], 'sa.id = wf.accountId', 'left') + ->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left') + ->where([ + ['wf.isDeleted', '=', 0], + ['sa.departmentId', '=', $workbench->companyId] + ]) + ->whereIn('wa.currentDeviceId', $workbench->config->devices) + ->field('wf.id,wf.wechatAccountId,wf.wechatId,wf.labels,sa.userName,wa.currentDeviceId as deviceId') + ->where(function ($q) use ($labels) { + foreach ($labels as $label) { + $q->whereOrRaw("JSON_CONTAINS(wf.labels, '\"{$label}\"')"); + } + })->count(); + + $totalAccounts = Db::table('s2_company_account') + ->alias('a') + ->where(['a.departmentId' => $workbench->companyId, 'a.status' => 0]) + ->whereNotLike('a.userName', '%_offline%') + ->whereNotLike('a.userName', '%_delete%') + ->group('a.id') + ->count(); + + $dailyAverage = Db::name('workbench_traffic_config_item') + ->where('workbenchId', $workbench->id) + ->count(); + $day = (time() - strtotime($workbench->createTime)) / 86400; + $day = intval($day); + + + if($dailyAverage > 0){ + $dailyAverage = $dailyAverage / $totalAccounts / $day; + } + $workbench->config->total = [ - 'dailyAverage' => 0, + 'dailyAverage' => intval($dailyAverage), + 'totalAccounts' => $totalAccounts, 'deviceCount' => count($workbench->config->devices), - 'poolCount' => count($workbench->config->pools ), - 'dailyAverage' => $workbench->config->maxPerDay, - 'totalUsers' => $workbench->config->maxPerDay * count($workbench->config->devices) * count($workbench->config->pools) - + 'poolCount' => count($workbench->config->pools), + 'totalUsers' => $totalUsers >> 0 ]; unset($workbench->trafficConfig,$workbench->traffic_config); } @@ -527,11 +651,16 @@ class WorkbenchController extends Controller case self::TYPE_GROUP_PUSH: $config = WorkbenchGroupPush::where('workbenchId', $param['id'])->find(); if ($config) { - $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->pushType = !empty($param['pushType']) ? 1 : 0; // 推送方式:定时/立即 + $config->startTime = $param['startTime']; + $config->endTime = $param['endTime']; + $config->maxPerDay = intval($param['maxPerDay']); // 每日推送数 + $config->pushOrder = $param['pushOrder']; // 推送顺序 + $config->isLoop = !empty($param['isLoop']) ? 1 : 0; // 是否循环 + $config->status = !empty($param['status']) ? 1 : 0; // 是否启用 + $config->groups = json_encode($param['groups'], JSON_UNESCAPED_UNICODE); // 群组信息 + $config->contentLibraries = json_encode($param['contentLibraries'], JSON_UNESCAPED_UNICODE); // 内容库信息 + $config->updateTime = time(); $config->save(); } break; @@ -709,11 +838,15 @@ class WorkbenchController extends Controller 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->pushType = $config->pushType; + $newConfig->startTime = $config->startTime; + $newConfig->endTime = $config->endTime; + $newConfig->maxPerDay = $config->maxPerDay; + $newConfig->pushOrder = $config->pushOrder; + $newConfig->isLoop = $config->isLoop; + $newConfig->status = $config->status; + $newConfig->groups = $config->groups; + $newConfig->contentLibraries = $config->contentLibraries; $newConfig->save(); } break; @@ -1224,4 +1357,48 @@ class WorkbenchController extends Controller // 返回结果 return json(['code' => 200, 'msg' => '获取成功', 'data' => $newLabel,'total'=> count($newLabel)]); } + + + /** + * 获取群列表 + * @return \think\response\Json + */ + public function getGroupList() + { + $page = $this->request->param('page', 1); + $limit = $this->request->param('limit', 10); + $keyword = $this->request->param('keyword', ''); + + $where = [ + ['wg.deleteTime', '=', 0], + ['wg.companyId', '=', $this->request->userInfo['companyId']], + ]; + + if (!empty($keyword)) { + $where[] = ['wg.name', 'like', '%' . $keyword . '%']; + } + + $query = Db::name('wechat_group')->alias('wg') + ->join('wechat_account wa', 'wa.wechatId = wg.ownerWechatId') + ->where($where); + + $total = $query->count(); + $list = $query->order('wg.id', 'desc') + ->field('wg.id,wg.name as groupName,wg.ownerWechatId,wa.nickName,wg.createTime,wa.avatar,wa.alias,wg.avatar as groupAvatar') + ->page($page, $limit) + ->select(); + + // 优化:格式化时间,头像兜底 + $defaultGroupAvatar = ''; + $defaultAvatar = ''; + foreach ($list as &$item) { + $item['createTime'] = $item['createTime'] ? date('Y-m-d H:i:s', $item['createTime']) : ''; + $item['groupAvatar'] = $item['groupAvatar'] ?: $defaultGroupAvatar; + $item['avatar'] = $item['avatar'] ?: $defaultAvatar; + } + + return json(['code' => 200, 'msg' => '获取成功', 'data' => ['total' => $total,'list' => $list]]); + } + + } \ No newline at end of file diff --git a/Server/application/cunkebao/validate/Workbench.php b/Server/application/cunkebao/validate/Workbench.php index 5bd222d9..f9f39ea8 100644 --- a/Server/application/cunkebao/validate/Workbench.php +++ b/Server/application/cunkebao/validate/Workbench.php @@ -36,21 +36,28 @@ class Workbench extends Validate 'accountType' => 'requireIf:type,2|in:1,2', 'contentLibraries' => 'requireIf:type,2|array', // 群消息推送特有参数 - 'pushInterval' => 'requireIf:type,3|number|min:1', - 'pushContent' => 'requireIf:type,3|array', - 'pushTime' => 'requireIf:type,3|array', + 'pushType' => 'requireIf:type,3|in:1,2', // 推送方式 1定时 2立即 + 'startTime' => 'requireIf:type,3|dateFormat:H:i', + 'endTime' => 'requireIf:type,3|dateFormat:H:i', + 'maxPerDay' => 'requireIf:type,3|number|min:1', + 'pushOrder' => 'requireIf:type,3|in:1,2', // 1最早 2最新 + 'isLoop' => 'requireIf:type,3|in:0,1', + 'status' => 'requireIf:type,3|in:0,1', + 'groups' => 'requireIf:type,3|array|min:1', + 'contentLibraries' => 'requireIf:type,3|array|min:1', // 自动建群特有参数 'groupNamePrefix' => 'requireIf:type,4|max:50', 'maxGroups' => 'requireIf:type,4|number|min:1', 'membersPerGroup' => 'requireIf:type,4|number|min:1', - // 通用参数 - 'devices' => 'require|array', // 流量分发特有参数 'distributeType' => 'requireIf:type,5|in:1,2', 'maxPerDay' => 'requireIf:type,5|number|min:1', 'timeType' => 'requireIf:type,5|in:1,2', 'startTime' => 'requireIf:type,5|dateFormat:H:i', 'endTime' => 'requireIf:type,5|dateFormat:H:i', + + // 通用参数 + 'devices' => 'requireIf:type,1,2,5|array', ]; /** @@ -95,13 +102,24 @@ class Workbench extends Validate 'contentLibraries.requireIf' => '请选择内容库', 'contentLibraries.array' => '内容库格式错误', // 群消息推送相关提示 - 'pushInterval.requireIf' => '请设置推送间隔', - 'pushInterval.number' => '推送间隔必须为数字', - 'pushInterval.min' => '推送间隔必须大于0', - 'pushContent.requireIf' => '请设置推送内容', - 'pushContent.array' => '推送内容格式错误', - 'pushTime.requireIf' => '请设置推送时间', - 'pushTime.array' => '推送时间格式错误', + 'pushType.requireIf' => '请选择推送方式', + 'pushType.in' => '推送方式错误', + 'startTime.requireIf' => '请设置推送开始时间', + 'startTime.dateFormat' => '推送开始时间格式错误', + 'endTime.requireIf' => '请设置推送结束时间', + 'endTime.dateFormat' => '推送结束时间格式错误', + 'maxPerDay.requireIf' => '请设置每日最大推送数', + 'maxPerDay.number' => '每日最大推送数必须为数字', + 'maxPerDay.min' => '每日最大推送数必须大于0', + 'pushOrder.requireIf' => '请选择推送顺序', + 'pushOrder.in' => '推送顺序错误', + 'isLoop.requireIf' => '请选择是否循环推送', + 'isLoop.in' => '循环推送参数错误', + 'status.requireIf' => '请选择推送状态', + 'status.in' => '推送状态错误', + 'groups.requireIf' => '请选择推送群组', + 'groups.array' => '推送群组格式错误', + 'groups.min' => '至少选择一个推送群组', // 自动建群相关提示 'groupNamePrefix.requireIf' => '请设置群名称前缀', 'groupNamePrefix.max' => '群名称前缀最多50个字符', @@ -133,10 +151,16 @@ class Workbench extends Validate 'create' => ['name', 'type', 'autoStart', 'devices', 'targetGroups', 'interval', 'maxLikes', 'startTime', 'endTime', 'contentTypes', 'syncInterval', 'syncCount', 'syncType', - 'pushInterval', 'pushContent', 'pushTime', + 'pushType', 'startTime', 'endTime', 'maxPerDay', 'pushOrder', 'isLoop', 'status', 'groups', 'contentLibraries', 'groupNamePrefix', 'maxGroups', 'membersPerGroup' ], - 'update_status' => ['id', 'status'] + 'update_status' => ['id', 'status'], + 'edit' => ['name', 'type', 'autoStart', 'devices', 'targetGroups', + 'interval', 'maxLikes', 'startTime', 'endTime', 'contentTypes', + 'syncInterval', 'syncCount', 'syncType', + 'pushType', 'startTime', 'endTime', 'maxPerDay', 'pushOrder', 'isLoop', 'status', 'groups', 'contentLibraries', + 'groupNamePrefix', 'maxGroups', 'membersPerGroup' + ] ]; /**