diff --git a/.gitignore b/.gitignore index ac58e20b..3787ed92 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ Backend/dist Backend/node_modules Store_vue/node_modules Store_vue/unpackage +Server/.specstory/ +Store_vue/.specstory/ +*.zip +*.cursorindexingignore diff --git a/Cunkebao/app/content/page.tsx b/Cunkebao/app/content/page.tsx index d6f99092..ef59a28d 100644 --- a/Cunkebao/app/content/page.tsx +++ b/Cunkebao/app/content/page.tsx @@ -34,6 +34,7 @@ interface ContentLibrary { avatar: string }[] creator: string + creatorName?: string itemCount: number lastUpdated: string enabled: boolean @@ -74,7 +75,7 @@ export default function ContentLibraryPage() { if (response.code === 200 && response.data) { // 转换数据格式以匹配原有UI - const transformedLibraries = response.data.list.map((item) => { + const transformedLibraries = response.data.list.map((item: any) => { const transformedItem: ContentLibrary = { id: item.id, name: item.name, @@ -83,7 +84,8 @@ export default function ContentLibraryPage() { ...(item.sourceFriends || []).map((id: string) => ({ id, nickname: `好友${id}`, avatar: "/placeholder.svg" })), ...(item.sourceGroups || []).map((id: string) => ({ id, nickname: `群组${id}`, avatar: "/placeholder.svg" })) ], - creator: item.creator || "系统", + creator: item.creatorName || "系统", + creatorName: item.creatorName, itemCount: 0, lastUpdated: item.updateTime, enabled: item.isEnabled === 1, diff --git a/Cunkebao/app/workspace/auto-like/[id]/edit/page.tsx b/Cunkebao/app/workspace/auto-like/[id]/edit/page.tsx index 7de7f9e7..f51a421f 100644 --- a/Cunkebao/app/workspace/auto-like/[id]/edit/page.tsx +++ b/Cunkebao/app/workspace/auto-like/[id]/edit/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useState, useEffect } from "react" +import { useState, useEffect, use } from "react" import { useRouter } from "next/navigation" import { ChevronLeft, Search } from "lucide-react" import { Button } from "@/components/ui/button" @@ -38,7 +38,8 @@ interface Task { config: TaskConfig } -export default function EditAutoLikePage({ params }: { params: { id: string } }) { +export default function EditAutoLikePage({ params }: { params: Promise<{ id: string }> }) { + const resolvedParams = use(params) const router = useRouter() const [currentStep, setCurrentStep] = useState(1) const [deviceDialogOpen, setDeviceDialogOpen] = useState(false) @@ -62,7 +63,7 @@ export default function EditAutoLikePage({ params }: { params: { id: string } }) const fetchTaskDetail = async () => { const loadingToast = showToast("正在加载任务信息...", "loading", true); try { - const response = await api.get<{code: number, msg: string, data: Task}>(`/v1/workbench/detail?id=${params.id}`) + const response = await api.get<{code: number, msg: string, data: Task}>(`/v1/workbench/detail?id=${resolvedParams.id}`) if (response.code === 200 && response.data) { const task = response.data @@ -110,7 +111,7 @@ export default function EditAutoLikePage({ params }: { params: { id: string } }) const loadingToast = showToast("正在更新任务...", "loading", true); try { const response = await api.post('/v1/workbench/update', { - id: params.id, + id: resolvedParams.id, type: 1, name: formData.taskName, interval: formData.likeInterval, diff --git a/Cunkebao/app/workspace/moments-sync/[id]/edit/page.tsx b/Cunkebao/app/workspace/moments-sync/[id]/edit/page.tsx index e206c499..07c29d72 100644 --- a/Cunkebao/app/workspace/moments-sync/[id]/edit/page.tsx +++ b/Cunkebao/app/workspace/moments-sync/[id]/edit/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useState, useEffect } from "react" +import { useState, useEffect, use } from "react" import { useRouter } from "next/navigation" import { ChevronLeft, Search } from "lucide-react" import { Button } from "@/components/ui/button" @@ -12,18 +12,23 @@ import { Input } from "@/components/ui/input" import { api, ApiResponse } from "@/lib/api" import { showToast } from "@/lib/toast" -// 定义基本设置表单数据类型,与BasicSettings组件的formData类型匹配 -interface BasicSettingsFormData { - taskName: string - startTime: string - endTime: string - syncCount: number - syncInterval: number - accountType: "business" | "personal" - enabled: boolean +interface Task { + id: string + name: string + status: number + config: { + startTime: string + endTime: string + syncCount: number + syncInterval: number + syncType: number + devices: string[] + contentLibraries: string[] + } } -export default function EditMomentsSyncPage({ params }: { params: { id: string } }) { +export default function EditMomentsSyncPage({ params }: { params: Promise<{ id: string }> }) { + const resolvedParams = use(params) const router = useRouter() const [currentStep, setCurrentStep] = useState(1) const [deviceDialogOpen, setDeviceDialogOpen] = useState(false) @@ -34,31 +39,30 @@ export default function EditMomentsSyncPage({ params }: { params: { id: string } startTime: "06:00", endTime: "23:59", syncCount: 5, - syncInterval: 30, // 同步间隔,默认30分钟 + syncInterval: 30, accountType: "business" as "business" | "personal", enabled: true, selectedDevices: [] as string[], selectedLibraries: [] as string[], }) - // 获取任务详情 useEffect(() => { const fetchTaskDetail = async () => { setIsLoading(true) try { - const response = await api.get(`/v1/workbench/detail?id=${params.id}`) + const response = await api.get<{code: number, msg: string, data: Task}>(`/v1/workbench/detail?id=${resolvedParams.id}`) if (response.code === 200 && response.data) { const taskData = response.data setFormData({ taskName: taskData.name || "", - startTime: taskData.startTime || "06:00", - endTime: taskData.endTime || "23:59", - syncCount: taskData.syncCount || 5, - syncInterval: taskData.syncInterval || 30, - accountType: taskData.syncType === 1 ? "business" : "personal", - enabled: !!taskData.enabled, - selectedDevices: taskData.devices || [], - selectedLibraries: taskData.contentLibraries || [], + startTime: taskData.config.startTime || "06:00", + endTime: taskData.config.endTime || "23:59", + syncCount: taskData.config.syncCount || 5, + syncInterval: taskData.config.syncInterval || 30, + accountType: taskData.config.syncType === 1 ? "business" : "personal", + enabled: !!taskData.status, + selectedDevices: taskData.config.devices || [], + selectedLibraries: taskData.config.contentLibraries || [], }) } else { showToast(response.msg || "获取任务详情失败", "error") @@ -74,17 +78,12 @@ export default function EditMomentsSyncPage({ params }: { params: { id: string } } fetchTaskDetail() - }, [params.id, router]) + }, [resolvedParams.id, router]) const handleUpdateFormData = (data: Partial) => { setFormData((prev) => ({ ...prev, ...data })) } - // 专门用于基本设置的更新函数 - const handleBasicSettingsUpdate = (data: Partial) => { - setFormData((prev) => ({ ...prev, ...data })) - } - const handleNext = () => { setCurrentStep((prev) => Math.min(prev + 1, 3)) } @@ -96,16 +95,16 @@ export default function EditMomentsSyncPage({ params }: { params: { id: string } const handleComplete = async () => { try { const response = await api.post('/v1/workbench/update', { - id: params.id, - type: 2, // 朋友圈同步任务类型为2 + id: resolvedParams.id, + type: 2, name: formData.taskName, syncInterval: formData.syncInterval, syncCount: formData.syncCount, - syncType: formData.accountType === "business" ? 1 : 2, // 业务号为1,人设号为2 + syncType: formData.accountType === "business" ? 1 : 2, startTime: formData.startTime, endTime: formData.endTime, accountType: formData.accountType === "business" ? 1 : 2, - status: formData.enabled ? 1 : 0, // 状态:0=禁用,1=启用 + status: formData.enabled ? 1 : 0, devices: formData.selectedDevices, contentLibraries: formData.selectedLibraries }); @@ -150,16 +149,8 @@ export default function EditMomentsSyncPage({ params }: { params: { id: string }
{currentStep === 1 && ( )} @@ -249,21 +240,6 @@ export default function EditMomentsSyncPage({ params }: { params: { id: string } )}
- - ) } diff --git a/Cunkebao/app/workspace/moments-sync/components/basic-settings.tsx b/Cunkebao/app/workspace/moments-sync/components/basic-settings.tsx index b0c32d6a..7a823ddd 100644 --- a/Cunkebao/app/workspace/moments-sync/components/basic-settings.tsx +++ b/Cunkebao/app/workspace/moments-sync/components/basic-settings.tsx @@ -6,6 +6,7 @@ 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" +import { Label } from "@/components/ui/label" interface BasicSettingsProps { formData: { @@ -24,163 +25,142 @@ interface BasicSettingsProps { export function BasicSettings({ formData, onChange, onNext }: BasicSettingsProps) { const { viewMode } = useViewMode() + const handleSyncCountChange = (delta: number) => { + const newValue = Math.max(1, formData.syncCount + delta) + onChange({ syncCount: newValue }) + } + + const handleSyncIntervalChange = (delta: number) => { + const newValue = Math.max(5, formData.syncInterval + delta) + onChange({ syncInterval: newValue }) + } + 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" + className="mt-1.5 h-12 rounded-xl" />
-
允许发布时间段
-
-
- 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" - /> - -
+ +
+ +
{formData.syncCount}
+ + 条/天
-
每日同步数量
-
+ +
- {formData.syncCount} +
{formData.syncInterval}
- 条朋友圈 -
-
- -
-
同步间隔
-
- - {formData.syncInterval} - 分钟
+

设置每次发朋友圈的时间间隔

-
账号类型
-
-
- - - - - - -

- 业务号能够循环推送内容库中的内容。当内容库所有内容循环推送完毕后,若有新内容则优先推送新内容,若无新内容则继续循环推送。 -

-
-
-
-
-
- - - - - - -

用于实时更新同步,有新动态时进行同步,无动态则不同步。

-
-
-
-
+ +
+ onChange({ startTime: e.target.value })} + className="h-12 rounded-xl" + /> + onChange({ endTime: e.target.value })} + className="h-12 rounded-xl" + />
-
- 是否启用 +
+ +
+ + +
+
+ +
+ onChange({ enabled: checked })} - className="data-[state=checked]:bg-blue-600 h-7 w-12" />
diff --git a/Cunkebao/app/workspace/moments-sync/components/content-library-selection-dialog.tsx b/Cunkebao/app/workspace/moments-sync/components/content-library-selection-dialog.tsx index 51d2bf96..5066a869 100644 --- a/Cunkebao/app/workspace/moments-sync/components/content-library-selection-dialog.tsx +++ b/Cunkebao/app/workspace/moments-sync/components/content-library-selection-dialog.tsx @@ -1,11 +1,26 @@ "use client" -import { useState } from "react" -import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" +import { useState, useEffect } from "react" +import { Search, RefreshCw, Loader2 } from "lucide-react" +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" -import { Search, CheckCircle2, Circle } from "lucide-react" import { ScrollArea } from "@/components/ui/scroll-area" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Checkbox } from "@/components/ui/checkbox" +import { Label } from "@/components/ui/label" +import { Badge } from "@/components/ui/badge" +import { api } from "@/lib/api" +import { showToast } from "@/lib/toast" + +interface ContentLibrary { + id: string + name: string + sourceType: number + creatorName: string + updateTime: string + status: number +} interface ContentLibrarySelectionDialogProps { open: boolean @@ -21,93 +36,162 @@ export function ContentLibrarySelectionDialog({ onSelect, }: ContentLibrarySelectionDialogProps) { const [searchQuery, setSearchQuery] = useState("") - const [libraries] = useState([ - { id: "1", name: "卡若朋友圈", count: 58 }, - { id: "2", name: "暗黑4代练", count: 422 }, - { id: "3", name: "家装设计", count: 107 }, - { id: "4", name: "美食分享", count: 321 }, - { id: "5", name: "旅游攻略", count: 89 }, - ]) + const [loading, setLoading] = useState(false) + const [libraries, setLibraries] = useState([]) + const [tempSelected, setTempSelected] = useState([]) - const [tempSelectedLibraries, setTempSelectedLibraries] = useState(selectedLibraries) + // 获取内容库列表 + const fetchLibraries = async () => { + setLoading(true) + try { + const queryParams = new URLSearchParams({ + page: '1', + limit: '100', + ...(searchQuery ? { keyword: searchQuery } : {}) + }) + const response = await api.get<{ + code: number + msg: string + data: { + list: ContentLibrary[] + total: number + } + }>(`/v1/content/library/list?${queryParams.toString()}`) - const toggleLibrary = (libraryId: string) => { - setTempSelectedLibraries((prev) => - prev.includes(libraryId) ? prev.filter((id) => id !== libraryId) : [...prev, libraryId] + if (response.code === 200 && response.data) { + setLibraries(response.data.list) + } else { + showToast(response.msg || "获取内容库列表失败", "error") + } + } catch (error: any) { + console.error("获取内容库列表失败:", error) + showToast(error?.message || "请检查网络连接", "error") + } finally { + setLoading(false) + } + } + + useEffect(() => { + if (open) { + fetchLibraries() + setTempSelected(selectedLibraries) + } + }, [open, searchQuery, selectedLibraries]) + + const handleRefresh = () => { + fetchLibraries() + } + + const handleSelectAll = () => { + if (tempSelected.length === libraries.length) { + setTempSelected([]) + } else { + setTempSelected(libraries.map(lib => lib.id)) + } + } + + const handleLibraryToggle = (libraryId: string) => { + setTempSelected(prev => + prev.includes(libraryId) + ? prev.filter(id => id !== libraryId) + : [...prev, libraryId] ) } - const handleConfirm = () => { - onSelect(tempSelectedLibraries) - onOpenChange(false) + const handleDialogOpenChange = (open: boolean) => { + if (!open) { + setTempSelected(selectedLibraries) + } + onOpenChange(open) } - const handleCancel = () => { - setTempSelectedLibraries(selectedLibraries) - onOpenChange(false) - } - - const filteredLibraries = libraries.filter((library) => - library.name.toLowerCase().includes(searchQuery.toLowerCase()) - ) - return ( - - - + + + 选择内容库 -
-
- +
+
+ setSearchQuery(e.target.value)} />
+ +
- -
- {filteredLibraries.map((library) => ( -
+
+ 已选择 {tempSelected.length} 个内容库 +
+
+ +
+
+ + +
+ {loading ? ( +
+ 加载中... +
+ ) : libraries.length === 0 ? ( +
+ 暂无数据 +
+ ) : ( + libraries.map((library) => ( +
-
-
+ {/* + {library.status === 1 ? "启用" : "已停用"} + */} + + )) + )} +
+ -
- */} + - -
+
) 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 38b9e0b2..a17158e3 100644 --- a/Cunkebao/app/workspace/moments-sync/components/device-selection-dialog.tsx +++ b/Cunkebao/app/workspace/moments-sync/components/device-selection-dialog.tsx @@ -1,58 +1,51 @@ "use client" import { useState, useEffect } from "react" +import { Search, RefreshCw, Loader2 } from "lucide-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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Checkbox } from "@/components/ui/checkbox" +import { Label } from "@/components/ui/label" +import { Badge } from "@/components/ui/badge" import { api } from "@/lib/api" import { showToast } from "@/lib/toast" -interface ServerDevice { - id: number - imei: string - memo: string - wechatId: string - alive: number - totalFriend: number -} - interface Device { - id: number + id: string name: string imei: string - wxid: string - status: "online" | "offline" - totalFriend: number + wechatId: string + memo?: string + alive: number + usedInPlans: number + lastActiveTime: string } interface DeviceSelectionDialogProps { open: boolean onOpenChange: (open: boolean) => void - selectedDevices: number[] - onSelect: (devices: number[]) => void + selectedDevices: string[] + onSelect: (devices: string[]) => void } -export function DeviceSelectionDialog({ open, onOpenChange, selectedDevices, onSelect }: DeviceSelectionDialogProps) { +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 [devices, setDevices] = useState([]) + const [tempSelected, setTempSelected] = useState([]) + // 获取设备列表 const fetchDevices = async () => { - const loadingToast = showToast("正在加载设备列表...", "loading", true); + setLoading(true) try { setLoading(true) const response = await api.get<{code: number, msg: string, data: {list: ServerDevice[], total: number}}>('/v1/devices?page=1&limit=100') @@ -71,58 +64,75 @@ export function DeviceSelectionDialog({ open, onOpenChange, selectedDevices, onS showToast(response.msg || "获取设备列表失败", "error") } } catch (error: any) { - console.error('获取设备列表失败:', error) + console.error("获取设备列表失败:", error) showToast(error?.message || "请检查网络连接", "error") } finally { - loadingToast.remove(); setLoading(false) } } + useEffect(() => { + if (open) { + fetchDevices() + setTempSelected(selectedDevices) + } + }, [open, searchQuery, selectedDevices]) + 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 filteredDevices = devices.filter(device => { - const matchesSearch = searchQuery === "" || + const matchesSearch = !searchQuery || device.name.toLowerCase().includes(searchQuery.toLowerCase()) || device.imei.toLowerCase().includes(searchQuery.toLowerCase()) || - device.wxid.toLowerCase().includes(searchQuery.toLowerCase()) + device.wechatId.toLowerCase().includes(searchQuery.toLowerCase()) - const matchesStatus = statusFilter === "all" || device.status === statusFilter + const matchesStatus = statusFilter === "all" || + (statusFilter === "online" && device.alive === 1) || + (statusFilter === "offline" && device.alive === 0) return matchesSearch && matchesStatus }) + const handleDialogOpenChange = (open: boolean) => { + if (!open) { + setTempSelected(selectedDevices) + } + onOpenChange(open) + } + + const handleSelectAll = () => { + if (tempSelected.length === filteredDevices.length) { + setTempSelected([]) + } else { + setTempSelected(filteredDevices.map(device => device.id)) + } + } + + const handleDeviceToggle = (deviceId: string) => { + setTempSelected(prev => + prev.includes(deviceId) + ? prev.filter(id => id !== deviceId) + : [...prev, deviceId] + ) + } + return ( - - + + 选择设备 -
+
setSearchQuery(e.target.value)} - className="pl-9" />