diff --git a/nkebao/package-lock.json b/nkebao/package-lock.json index 5bf25869..351b8861 100644 --- a/nkebao/package-lock.json +++ b/nkebao/package-lock.json @@ -72,6 +72,7 @@ "sonner": "^1.7.4", "tailwind-merge": "^2.5.5", "tailwindcss-animate": "^1.0.7", + "tdesign-mobile-react": "^0.16.0", "vaul": "^0.9.6", "web-vitals": "^2.1.4", "xlsx": "^0.18.5", @@ -3620,6 +3621,16 @@ } } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@radix-ui/number": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/@radix-ui/number/-/number-1.1.1.tgz", @@ -6376,6 +6387,24 @@ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, + "node_modules/@use-gesture/core": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz", + "integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==", + "license": "MIT" + }, + "node_modules/@use-gesture/react": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz", + "integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==", + "license": "MIT", + "dependencies": { + "@use-gesture/core": "10.3.1" + }, + "peerDependencies": { + "react": ">= 16.8.0" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -6658,6 +6687,30 @@ "node": ">= 6.0.0" } }, + "node_modules/ahooks": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ahooks/-/ahooks-3.9.0.tgz", + "integrity": "sha512-r20/C38aFyU3Zqp3620gkdLnxmQhnmWORB3eGGTDlM4i/fOc0GUvM+f2oleMzEu7b3+pHXyzz+FB6ojxsUdYdw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0", + "dayjs": "^1.9.1", + "intersection-observer": "^0.12.0", + "js-cookie": "^3.0.5", + "lodash": "^4.17.21", + "react-fast-compare": "^3.2.2", + "resize-observer-polyfill": "^1.5.1", + "screenfull": "^5.0.0", + "tslib": "^2.4.1" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", @@ -7914,6 +7967,12 @@ "url": "https://polar.sh/cva" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmmirror.com/clean-css/-/clean-css-5.3.3.tgz", @@ -9179,6 +9238,12 @@ "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==", "license": "MIT" }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", @@ -9488,6 +9553,16 @@ "utila": "~0.4" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -11739,6 +11814,21 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmmirror.com/hoopy/-/hoopy-0.1.4.tgz", @@ -12183,6 +12273,12 @@ "node": ">=12" } }, + "node_modules/intersection-observer": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.12.2.tgz", + "integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==", + "license": "Apache-2.0" + }, "node_modules/ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz", @@ -13805,6 +13901,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", @@ -14125,6 +14230,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -16954,6 +17065,12 @@ "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", "license": "MIT" }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" + }, "node_modules/react-hook-form": { "version": "7.59.0", "resolved": "https://registry.npmmirror.com/react-hook-form/-/react-hook-form-7.59.0.tgz", @@ -17209,6 +17326,22 @@ } } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz", @@ -17498,6 +17631,12 @@ "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", "license": "MIT" }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz", @@ -17934,6 +18073,18 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/screenfull": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz", + "integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz", @@ -18321,6 +18472,12 @@ "node": ">=8" } }, + "node_modules/smoothscroll-polyfill": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz", + "integrity": "sha512-TK5ZA9U5RqCwMpfoMq/l1mrH0JAR7y7KRvOBx0n2869aLxch+gT9GhN3yUfjiw+d/DiF1mKo14+hd62JyMmoBg==", + "license": "MIT" + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmmirror.com/sockjs/-/sockjs-0.3.24.tgz", @@ -19280,6 +19437,42 @@ "node": ">=6" } }, + "node_modules/tdesign-icons-react": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/tdesign-icons-react/-/tdesign-icons-react-0.5.0.tgz", + "integrity": "sha512-Gpl1Kxkb8+YXYjMsSW5W3kWy/drVB/huFLIHWhmJdUnEwUAW0Il+nDyb/BsRi51jLnalBwjYbOELEggH2qp6FQ==", + "dependencies": { + "@babel/runtime": "^7.16.5", + "classnames": "^2.2.6" + }, + "peerDependencies": { + "react": ">=16.13.1", + "react-dom": ">=16.13.1" + } + }, + "node_modules/tdesign-mobile-react": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/tdesign-mobile-react/-/tdesign-mobile-react-0.16.0.tgz", + "integrity": "sha512-b5oI3Fk/Fpi64tH/d2+2wvQ5b6wKiRUGX4DBayIXL9LAqqxk2L2LxM8bXvq2rV3WdVg4scM38A/o8F54mzONXg==", + "license": "MIT", + "dependencies": { + "@popperjs/core": "^2.11.8", + "@use-gesture/react": "^10.2.10", + "ahooks": "^3.8.5", + "classnames": "^2.3.1", + "dayjs": "^1.11.13", + "hoist-non-react-statics": "^3.3.2", + "lodash-es": "^4.17.21", + "react-transition-group": "^4.4.2", + "smoothscroll-polyfill": "^0.4.4", + "tdesign-icons-react": "^0.5.0", + "tinycolor2": "^1.6.0" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, "node_modules/temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/temp-dir/-/temp-dir-2.0.0.tgz", @@ -19452,6 +19645,12 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmmirror.com/tmpl/-/tmpl-1.0.5.tgz", diff --git a/nkebao/package.json b/nkebao/package.json index 18c6c45e..9f980db8 100644 --- a/nkebao/package.json +++ b/nkebao/package.json @@ -53,7 +53,7 @@ "date-fns": "latest", "embla-carousel-react": "8.5.1", "input-otp": "1.4.1", - "lucide-react": "^0.454.0", + "lucide-react": "^0.525.0", "react": "^18.2.0", "react-day-picker": "latest", "react-dom": "^18.2.0", @@ -67,6 +67,7 @@ "sonner": "^1.7.4", "tailwind-merge": "^2.5.5", "tailwindcss-animate": "^1.0.7", + "tdesign-mobile-react": "^0.16.0", "vaul": "^0.9.6", "web-vitals": "^2.1.4", "xlsx": "^0.18.5", diff --git a/nkebao/src/App.tsx b/nkebao/src/App.tsx index f0cb3afe..d1725282 100644 --- a/nkebao/src/App.tsx +++ b/nkebao/src/App.tsx @@ -15,6 +15,7 @@ import WechatAccountDetail from './pages/wechat-accounts/WechatAccountDetail'; import Workspace from './pages/workspace/Workspace'; import AutoLike from './pages/workspace/auto-like/AutoLike'; import NewAutoLike from './pages/workspace/auto-like/NewAutoLike'; +import AutoLikeDetail from './pages/workspace/auto-like/AutoLikeDetail'; import AutoGroup from './pages/workspace/auto-group/AutoGroup'; import AutoGroupDetail from './pages/workspace/auto-group/Detail'; import GroupPush from './pages/workspace/group-push/GroupPush'; @@ -58,6 +59,7 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/nkebao/src/api/autoLike.ts b/nkebao/src/api/autoLike.ts index 6157d3dd..3b6ca7d9 100644 --- a/nkebao/src/api/autoLike.ts +++ b/nkebao/src/api/autoLike.ts @@ -1,35 +1,90 @@ -import { get, post } from './request'; - -export interface LikeTask { - id: string; - name: string; - status: 'running' | 'paused'; - deviceCount: number; - targetGroup: string; - likeCount: number; - lastLikeTime: string; - createTime: string; - creator: string; - likeInterval: number; - maxLikesPerDay: number; - timeRange: { start: string; end: string }; - contentTypes: string[]; - targetTags: string[]; -} +import { get, post, del } from './request'; +import { + LikeTask, + CreateLikeTaskData, + UpdateLikeTaskData, + LikeRecord, + ApiResponse, + PaginatedResponse +} from '@/types/auto-like'; +// 获取自动点赞任务列表 export async function fetchAutoLikeTasks(): Promise { - const res = await get('/api/workbench/auto-like/list'); - return res.data?.list || []; + try { + const res = await get>>('/v1/workbench/list?type=1&page=1&limit=100'); + + if (res.code === 200 && res.data) { + return res.data.list || []; + } + return []; + } catch (error) { + console.error('获取自动点赞任务失败:', error); + return []; + } } -export async function deleteAutoLikeTask(id: string) { - return post('/api/workbench/auto-like/delete', { id }); +// 获取单个任务详情 +export async function fetchAutoLikeTaskDetail(id: string): Promise { + try { + const res = await get>(`/v1/workbench/detail/${id}`); + if (res.code === 200 && res.data) { + return res.data; + } + return null; + } catch (error) { + console.error('获取任务详情失败:', error); + return null; + } } -export async function toggleAutoLikeTask(id: string, status: string) { - return post('/api/workbench/auto-like/toggle', { id, status }); +// 创建自动点赞任务 +export async function createAutoLikeTask(data: CreateLikeTaskData): Promise { + return post('/v1/workbench/create', { + ...data, + type: 1 // 自动点赞类型 + }); } -export async function copyAutoLikeTask(id: string) { - return post('/api/workbench/auto-like/copy', { id }); -} \ No newline at end of file +// 更新自动点赞任务 +export async function updateAutoLikeTask(data: UpdateLikeTaskData): Promise { + return post('/v1/workbench/update', { + ...data, + type: 1 // 自动点赞类型 + }); +} + +// 删除自动点赞任务 +export async function deleteAutoLikeTask(id: string): Promise { + return del('/v1/workbench/delete', { params: { id } }); +} + +// 切换任务状态 +export async function toggleAutoLikeTask(id: string, status: string): Promise { + return post('/v1/workbench/update-status', { id, status }); +} + +// 复制自动点赞任务 +export async function copyAutoLikeTask(id: string): Promise { + return post('/v1/workbench/copy', { id }); +} + +// 获取点赞记录 +export async function fetchLikeRecords( + workbenchId: string, + page: number = 1, + limit: number = 20 +): Promise> { + try { + const res = await get>>(`/v1/workbench/like-records?workbenchId=${workbenchId}&page=${page}&limit=${limit}`); + + if (res.code === 200 && res.data) { + return res.data; + } + return { list: [], total: 0, page, limit }; + } catch (error) { + console.error('获取点赞记录失败:', error); + return { list: [], total: 0, page, limit }; + } +} + +export type { LikeTask, LikeRecord, CreateLikeTaskData }; \ No newline at end of file diff --git a/nkebao/src/pages/workspace/auto-like/AutoLike.tsx b/nkebao/src/pages/workspace/auto-like/AutoLike.tsx index dbe28ec9..5c11b567 100644 --- a/nkebao/src/pages/workspace/auto-like/AutoLike.tsx +++ b/nkebao/src/pages/workspace/auto-like/AutoLike.tsx @@ -1,69 +1,108 @@ -import React, { useState, useEffect, useCallback } from 'react'; -import { useNavigate } from 'react-router-dom'; +import React, { useState, useCallback } from "react"; import { - Plus, - Search, - RefreshCw, - MoreVertical, - Clock, - Edit, - Trash2, - Eye, - Copy, ChevronDown, ChevronUp, + MoreVertical, + Eye, + Edit, + Copy, + Trash2, + Clock, + Plus, + Filter, + Search, + RefreshCw, Settings, Calendar, Users, ThumbsUp, -} from 'lucide-react'; -import { Card } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Badge } from '@/components/ui/badge'; -import { Switch } from '@/components/ui/switch'; -import { Progress } from '@/components/ui/progress'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; -import Layout from '@/components/Layout'; -import PageHeader from '@/components/PageHeader'; -import BottomNav from '@/components/BottomNav'; -import { useToast } from '@/components/ui/toast'; -import '@/components/Layout.css'; -import { - fetchAutoLikeTasks, - deleteAutoLikeTask, - toggleAutoLikeTask, - copyAutoLikeTask, - LikeTask, -} from '@/api/autoLike'; +} from "lucide-react"; +import { Card } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Badge } from "@/components/ui/badge"; +import { useNavigate } from "react-router-dom"; +import { Switch } from "@/components/ui/switch"; +import { useToast } from "@/components/ui/toast"; +import { Progress } from "@/components/ui/progress"; +import { fetchAutoLikeTasks, deleteAutoLikeTask, toggleAutoLikeTask, copyAutoLikeTask, LikeTask } from '@/api/autoLike'; + +type CardMenuProps = { + onView: () => void; + onEdit: () => void; + onCopy: () => void; + onDelete: () => void; +}; + +function CardMenu({ onView, onEdit, onCopy, onDelete }: CardMenuProps) { + const [open, setOpen] = React.useState(false); + const menuRef = React.useRef(null); + + React.useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (menuRef.current && !menuRef.current.contains(event.target as Node)) { + setOpen(false); + } + } + if (open) document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [open]); + + return ( +
+ + {open && ( +
+
{ onView(); setOpen(false); }} style={{ padding: 8, cursor: "pointer", display: "flex", alignItems: "center", borderRadius: 6, fontSize: 14, gap: 6, transition: "background .2s" }} onMouseOver={e => (e.currentTarget as HTMLDivElement).style.background="#f5f5f5"} onMouseOut={e => (e.currentTarget as HTMLDivElement).style.background=""}> + 查看 +
+
{ onEdit(); setOpen(false); }} style={{ padding: 8, cursor: "pointer", display: "flex", alignItems: "center", borderRadius: 6, fontSize: 14, gap: 6, transition: "background .2s" }} onMouseOver={e => (e.currentTarget as HTMLDivElement).style.background="#f5f5f5"} onMouseOut={e => (e.currentTarget as HTMLDivElement).style.background=""}> + 编辑 +
+
{ onCopy(); setOpen(false); }} style={{ padding: 8, cursor: "pointer", display: "flex", alignItems: "center", borderRadius: 6, fontSize: 14, gap: 6, transition: "background .2s" }} onMouseOver={e => (e.currentTarget as HTMLDivElement).style.background="#f5f5f5"} onMouseOut={e => (e.currentTarget as HTMLDivElement).style.background=""}> + 复制 +
+
{ onDelete(); setOpen(false); }} style={{ padding: 8, cursor: "pointer", display: "flex", alignItems: "center", borderRadius: 6, fontSize: 14, gap: 6, color: "#e53e3e", transition: "background .2s" }} onMouseOver={e => (e.currentTarget as HTMLDivElement).style.background="#f5f5f5"} onMouseOut={e => (e.currentTarget as HTMLDivElement).style.background=""}> + 删除 +
+
+ )} +
+ ); +} export default function AutoLike() { const navigate = useNavigate(); const { toast } = useToast(); const [expandedTaskId, setExpandedTaskId] = useState(null); - const [searchTerm, setSearchTerm] = useState(''); - const [tasks, setTasks] = useState([]); - const [loading, setLoading] = useState(false); + const [tasks, setTasks] = React.useState([]); + const [searchTerm, setSearchTerm] = useState(""); - // 获取任务列表 const fetchTasks = useCallback(async () => { - setLoading(true); try { const list = await fetchAutoLikeTasks(); setTasks(list); - } catch { - toast({ title: '获取任务失败', variant: 'destructive' }); - } finally { - setLoading(false); + } catch (error) { + toast({ title: "获取任务失败", variant: "destructive" }); } }, [toast]); - useEffect(() => { + React.useEffect(() => { fetchTasks(); }, [fetchTasks]); @@ -72,13 +111,17 @@ export default function AutoLike() { }; const handleDelete = async (id: string) => { - if (!window.confirm('确定要删除该任务吗?')) return; + if (!window.confirm("确定要删除该任务吗?")) return; try { - await deleteAutoLikeTask(id); - toast({ title: '删除成功' }); - fetchTasks(); - } catch { - toast({ title: '删除失败', variant: 'destructive' }); + const response = await deleteAutoLikeTask(id); + if (response.code === 200) { + toast({ title: "删除成功" }); + fetchTasks(); + } else { + toast({ title: "删除失败", description: response.msg || "请稍后重试", variant: "destructive" }); + } + } catch (error) { + toast({ title: "删除失败", description: "请稍后重试", variant: "destructive" }); } }; @@ -92,188 +135,110 @@ export default function AutoLike() { const handleCopy = async (id: string) => { try { - await copyAutoLikeTask(id); - toast({ title: '复制成功' }); - fetchTasks(); - } catch { - toast({ title: '复制失败', variant: 'destructive' }); + const response = await copyAutoLikeTask(id); + if (response.code === 200) { + toast({ title: "复制成功" }); + fetchTasks(); + } else { + toast({ title: "复制失败", description: response.msg || "请稍后重试", variant: "destructive" }); + } + } catch (error) { + toast({ title: "复制失败", description: "请稍后重试", variant: "destructive" }); } }; const toggleTaskStatus = async (id: string, status: string) => { try { - await toggleAutoLikeTask(id, status); - toast({ title: '操作成功' }); - fetchTasks(); - } catch { - toast({ title: '操作失败', variant: 'destructive' }); + // status: 'running' -> 2(要关闭),'paused' -> 1(要开启) + const newStatus = status === "running" ? 2 : 1; + const response = await toggleAutoLikeTask(id, String(newStatus)); + if (response.code === 200) { + toast({ title: "操作成功" }); + fetchTasks(); + } else { + toast({ title: "操作失败", description: response.msg || "请稍后重试", variant: "destructive" }); + } + } catch (error) { + toast({ title: "操作失败", description: "请稍后重试", variant: "destructive" }); } }; const handleCreateNew = () => { - navigate('/workspace/auto-like/new'); + navigate("/workspace/auto-like/new"); }; const filteredTasks = tasks.filter((task) => task.name.toLowerCase().includes(searchTerm.toLowerCase()), ); - - const getStatusColor = (status: string) => { - switch (status) { - case 'running': - return 'bg-green-100 text-green-800'; - case 'paused': - return 'bg-gray-100 text-gray-800'; - default: - return 'bg-gray-100 text-gray-800'; - } - }; - - const getStatusText = (status: string) => { - switch (status) { - case 'running': - return '进行中'; - case 'paused': - return '已暂停'; - default: - return '未知'; - } - }; return ( - - - 新建任务 +
+
+
+
+ - } - /> - } - footer={} - > -
+

自动点赞

+
+ +
+
- {/* 搜索和筛选 */}
- setSearchTerm(e.target.value)} - /> + setSearchTerm(e.target.value)} />
- {/* 移除筛选按钮 */} - {/* */}
- - {/* 任务列表 */}
- {loading ? ( - - -

加载中...

-

请稍候,正在获取任务列表

-
- ) : filteredTasks.length === 0 ? ( - - -

暂无点赞任务

-

创建您的第一个自动点赞任务

- -
- ) : ( - filteredTasks.map((task) => ( + {filteredTasks.map((task) => (

{task.name}

- - {getStatusText(task.status)} + + {task.status === "running" ? "进行中" : "已暂停"}
- toggleTaskStatus(task.id, task.status === 'running' ? 'paused' : 'running')} - /> - - - - - - handleView(task.id)}> - - 查看 - - handleEdit(task.id)}> - - 编辑 - - handleCopy(task.id)}> - - 复制 - - handleDelete(task.id)}> - - 删除 - - - + toggleTaskStatus(task.id, task.status)} /> + handleView(task.id)} + onEdit={() => handleEdit(task.id)} + onCopy={() => handleCopy(task.id)} + onDelete={() => handleDelete(task.id)} + />
- -
+
-
执行设备:{task.deviceCount} 个
-
目标人群:{task.targetGroup}
+
执行设备:{task.deviceCount} 个
+
目标人群:{task.targetGroup}
-
已点赞:{task.likeCount} 次
-
创建人:{task.creator}
+
已点赞:{task.likeCount} 次
+
创建人:{task.creator}
- -
-
- - 上次点赞:{task.lastLikeTime} -
-
- 创建时间:{task.createTime} - +
+
+ 上次点赞:{task.lastLikeTime} +
+
+ 创建时间:{task.createTime} +
- {expandedTaskId === task.id && (
@@ -285,21 +250,20 @@ export default function AutoLike() {
点赞间隔: - {task.likeInterval} 秒 + {task.likeInterval} 秒
每日最大点赞数: - {task.maxLikesPerDay} 次 + {task.maxLikesPerDay} 次
执行时间段: - {task.timeRange.start} - {task.timeRange.end} + {task.timeRange.start} - {task.timeRange.end}
-
@@ -307,15 +271,14 @@ export default function AutoLike() {
- {task.targetTags.map((tag) => ( - + {task.targetTags.map((tag) => ( + {tag} ))}
-
@@ -323,42 +286,39 @@ export default function AutoLike() {
- {task.contentTypes.map((type) => ( - - {type === 'text' ? '文字' : type === 'image' ? '图片' : '视频'} + {task.contentTypes.map((type) => ( + + {type === "text" ? "文字" : type === "image" ? "图片" : "视频"} ))}
- -
-
- -

执行进度

-
-
-
- 今日已点赞: - - {task.likeCount} / {task.maxLikesPerDay} - -
- +
+
+ +

执行进度

+
+
+
+ 今日已点赞: + + {task.likeCount} / {task.maxLikesPerDay} + +
+
)} - )) - )} -
+ ))}
- +
); } \ No newline at end of file diff --git a/nkebao/src/pages/workspace/auto-like/AutoLikeDetail.tsx b/nkebao/src/pages/workspace/auto-like/AutoLikeDetail.tsx new file mode 100644 index 00000000..e34a28f6 --- /dev/null +++ b/nkebao/src/pages/workspace/auto-like/AutoLikeDetail.tsx @@ -0,0 +1,452 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { + Edit, + Trash2, + Copy, + ThumbsUp, + Settings, + Calendar, + Eye, + RefreshCw, +} from 'lucide-react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Progress } from '@/components/ui/progress'; +import { Switch } from '@/components/ui/switch'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import Layout from '@/components/Layout'; +import PageHeader from '@/components/PageHeader'; +import BottomNav from '@/components/BottomNav'; +import { useToast } from '@/components/ui/toast'; +import '@/components/Layout.css'; +import { + fetchAutoLikeTaskDetail, + toggleAutoLikeTask, + deleteAutoLikeTask, + copyAutoLikeTask, + fetchLikeRecords, + LikeTask, + LikeRecord, +} from '@/api/autoLike'; + +export default function AutoLikeDetail() { + const { id } = useParams<{ id: string }>(); + const navigate = useNavigate(); + const { toast } = useToast(); + const [task, setTask] = useState(null); + const [records, setRecords] = useState([]); + const [loading, setLoading] = useState(true); + const [recordsLoading, setRecordsLoading] = useState(false); + + const fetchTaskDetail = useCallback(async () => { + if (!id) return; + + try { + const taskData = await fetchAutoLikeTaskDetail(id); + if (taskData) { + setTask(taskData); + } else { + toast({ + title: '任务不存在', + description: '该任务可能已被删除', + variant: 'destructive', + }); + navigate('/workspace/auto-like'); + } + } catch (error) { + console.error('获取任务详情失败:', error); + toast({ + title: '获取失败', + description: '请稍后重试', + variant: 'destructive', + }); + } finally { + setLoading(false); + } + }, [id, toast, navigate]); + + const fetchRecords = useCallback(async () => { + if (!id) return; + + setRecordsLoading(true); + try { + const response = await fetchLikeRecords(id, 1, 20); + setRecords(response.list || []); + } catch (error) { + console.error('获取点赞记录失败:', error); + } finally { + setRecordsLoading(false); + } + }, [id]); + + useEffect(() => { + if (id) { + fetchTaskDetail(); + fetchRecords(); + } + }, [id, fetchTaskDetail, fetchRecords]); + + const handleToggleStatus = async () => { + if (!task) return; + + try { + const newStatus = task.status === 'running' ? 'paused' : 'running'; + const response = await toggleAutoLikeTask(task.id, newStatus); + + if (response.code === 200) { + setTask(prev => prev ? { ...prev, status: newStatus } : null); + toast({ title: '操作成功' }); + } else { + toast({ + title: '操作失败', + description: response.msg || '请稍后重试', + variant: 'destructive', + }); + } + } catch (error) { + console.error('操作失败:', error); + toast({ + title: '操作失败', + description: '请稍后重试', + variant: 'destructive', + }); + } + }; + + const handleDelete = async () => { + if (!task || !window.confirm('确定要删除该任务吗?')) return; + + try { + const response = await deleteAutoLikeTask(task.id); + if (response.code === 200) { + toast({ title: '删除成功' }); + navigate('/workspace/auto-like'); + } else { + toast({ + title: '删除失败', + description: response.msg || '请稍后重试', + variant: 'destructive', + }); + } + } catch (error) { + console.error('删除失败:', error); + toast({ + title: '删除失败', + description: '请稍后重试', + variant: 'destructive', + }); + } + }; + + const handleCopy = async () => { + if (!task) return; + + try { + const response = await copyAutoLikeTask(task.id); + if (response.code === 200) { + toast({ title: '复制成功' }); + navigate('/workspace/auto-like'); + } else { + toast({ + title: '复制失败', + description: response.msg || '请稍后重试', + variant: 'destructive', + }); + } + } catch (error) { + console.error('复制失败:', error); + toast({ + title: '复制失败', + description: '请稍后重试', + variant: 'destructive', + }); + } + }; + + const getStatusColor = (status: string) => { + switch (status) { + case 'running': + return 'bg-green-100 text-green-800'; + case 'paused': + return 'bg-gray-100 text-gray-800'; + case 'completed': + return 'bg-blue-100 text-blue-800'; + default: + return 'bg-gray-100 text-gray-800'; + } + }; + + const getStatusText = (status: string) => { + switch (status) { + case 'running': + return '进行中'; + case 'paused': + return '已暂停'; + case 'completed': + return '已完成'; + default: + return '未知'; + } + }; + + if (loading) { + return ( + } + footer={} + > +
+
+ + +

加载中...

+

请稍候,正在获取任务详情

+
+
+
+
+ ); + } + + if (!task) { + return null; + } + + return ( + + + + + + navigate(`/workspace/auto-like/${task.id}/edit`)}> + + 编辑 + + + + 复制 + + + + 删除 + + + + } + /> + } + footer={} + > +
+
+ {/* 任务基本信息 */} + + +
+ + + {task.name} + +
+ + {getStatusText(task.status)} + + +
+
+
+ +
+
+
创建时间
+
{task.createTime}
+
+
+
创建人
+
{task.creator}
+
+
+
执行设备
+
{task.deviceCount} 个
+
+
+
目标人群
+
{task.targetGroup}
+
+
+
+
+ + {/* 执行进度 */} + + + + + 执行进度 + + + +
+
+ 今日已点赞 + + {task.todayLikeCount} / {task.maxLikesPerDay} + +
+ +
+
+
+
总点赞数
+
{task.totalLikeCount} 次
+
+
+
上次点赞
+
{task.lastLikeTime}
+
+
+
+
+ + {/* 任务配置 */} + + + + + 任务配置 + + + +
+
+
点赞间隔
+
{task.likeInterval} 秒
+
+
+
每日最大点赞数
+
{task.maxLikesPerDay} 次
+
+
+
单个好友最大点赞数
+
{task.friendMaxLikes} 次
+
+
+
执行时间段
+
+ {task.timeRange.start} - {task.timeRange.end} +
+
+
+ +
+
内容类型
+
+ {task.contentTypes.map((type) => ( + + {type === 'text' ? '文字' : type === 'image' ? '图片' : type === 'video' ? '视频' : '链接'} + + ))} +
+
+ + {task.targetTags.length > 0 && ( +
+
目标标签
+
+ {task.targetTags.map((tag) => ( + + {tag} + + ))} +
+
+ )} + + {task.enableFriendTags && task.friendTags && ( +
+
好友标签
+
+ {task.friendTags} +
+
+ )} +
+
+ + {/* 点赞记录 */} + + +
+ + + 点赞记录 + + +
+
+ + {recordsLoading ? ( +
+ +

加载中...

+
+ ) : records.length === 0 ? ( +
+ +

暂无点赞记录

+
+ ) : ( +
+ {records.slice(0, 10).map((record) => ( +
+
+ +
+
+
+ {record.friendName} +
+
+ {record.content} +
+
+
+ {record.likeTime} +
+
+ ))} +
+ )} +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/nkebao/src/pages/workspace/auto-like/NewAutoLike.tsx b/nkebao/src/pages/workspace/auto-like/NewAutoLike.tsx index ff75dfcf..8b9f1581 100644 --- a/nkebao/src/pages/workspace/auto-like/NewAutoLike.tsx +++ b/nkebao/src/pages/workspace/auto-like/NewAutoLike.tsx @@ -14,6 +14,8 @@ import PageHeader from '@/components/PageHeader'; import BottomNav from '@/components/BottomNav'; import { useToast } from '@/components/ui/toast'; import '@/components/Layout.css'; +import { createAutoLikeTask, CreateLikeTaskData } from '@/api/autoLike'; +import { ContentType } from '@/types/auto-like'; interface Device { id: string; @@ -33,20 +35,17 @@ export default function NewAutoLike() { const navigate = useNavigate(); const { toast } = useToast(); const [currentStep, setCurrentStep] = useState(1); - const [formData, setFormData] = useState({ + const [formData, setFormData] = useState({ name: '', - description: '', - likeInterval: 30, - maxLikesPerDay: 100, + interval: 30, + maxLikes: 100, friendMaxLikes: 10, startTime: '09:00', endTime: '18:00', contentTypes: ['text', 'image'], - includeKeywords: '', - excludeKeywords: '', - selectedDevices: [] as string[], - selectedGroups: [] as string[], - targetTags: [] as string[], + devices: [], + friends: [], + targetTags: [], enableFriendTags: false, friendTags: '', }); @@ -70,48 +69,48 @@ export default function NewAutoLike() { 'VIP', '高价值', '活跃', '互动', '潜在', '新客户', '男性', '女性', '青年', '中年', '高收入', '中收入' ]; - const handleInputChange = (field: string, value: any) => { - setFormData(prev => ({ ...prev, [field]: value })); + const handleInputChange = (field: keyof CreateLikeTaskData, value: any) => { + setFormData((prev: CreateLikeTaskData) => ({ ...prev, [field]: value })); }; const handleDeviceToggle = (deviceId: string) => { - setFormData(prev => ({ + setFormData((prev: CreateLikeTaskData) => ({ ...prev, - selectedDevices: prev.selectedDevices.includes(deviceId) - ? prev.selectedDevices.filter(id => id !== deviceId) - : [...prev.selectedDevices, deviceId] + devices: prev.devices.includes(deviceId) + ? prev.devices.filter((id: string) => id !== deviceId) + : [...prev.devices, deviceId] })); }; const handleGroupToggle = (groupId: string) => { - setFormData(prev => ({ + setFormData((prev: CreateLikeTaskData) => ({ ...prev, - selectedGroups: prev.selectedGroups.includes(groupId) - ? prev.selectedGroups.filter(id => id !== groupId) - : [...prev.selectedGroups, groupId] + friends: prev.friends?.includes(groupId) + ? prev.friends.filter((id: string) => id !== groupId) + : [...(prev.friends || []), groupId] })); }; const handleTagToggle = (tag: string) => { - setFormData(prev => ({ + setFormData((prev: CreateLikeTaskData) => ({ ...prev, targetTags: prev.targetTags.includes(tag) - ? prev.targetTags.filter(t => t !== tag) + ? prev.targetTags.filter((t: string) => t !== tag) : [...prev.targetTags, tag] })); }; - const handleContentTypeToggle = (type: string) => { - setFormData(prev => ({ + const handleContentTypeToggle = (type: ContentType) => { + setFormData((prev: CreateLikeTaskData) => ({ ...prev, contentTypes: prev.contentTypes.includes(type) - ? prev.contentTypes.filter(t => t !== type) + ? prev.contentTypes.filter((t: ContentType) => t !== type) : [...prev.contentTypes, type] })); }; const handleNext = () => { - if (currentStep === 1 && (!formData.name || formData.selectedDevices.length === 0)) { + if (currentStep === 1 && (!formData.name || formData.devices.length === 0)) { toast({ title: '请完善信息', description: '请填写任务名称并选择至少一个设备', @@ -119,7 +118,7 @@ export default function NewAutoLike() { }); return; } - if (currentStep === 2 && formData.selectedGroups.length === 0) { + if (currentStep === 2 && (!formData.friends || formData.friends.length === 0)) { toast({ title: '请选择目标人群', description: '请至少选择一个目标人群', @@ -136,16 +135,23 @@ export default function NewAutoLike() { const handleSubmit = async () => { try { - // 模拟API调用 - await new Promise(resolve => setTimeout(resolve, 1000)); + const response = await createAutoLikeTask(formData); - toast({ - title: '创建成功', - description: '自动点赞任务已创建', - }); - - navigate('/workspace/auto-like'); + if (response.code === 200) { + toast({ + title: '创建成功', + description: '自动点赞任务已创建', + }); + navigate('/workspace/auto-like'); + } else { + toast({ + title: '创建失败', + description: response.msg || '请稍后重试', + variant: 'destructive', + }); + } } catch (error) { + console.error('创建失败:', error); toast({ title: '创建失败', description: '请检查网络连接后重试', @@ -200,30 +206,21 @@ export default function NewAutoLike() {
- {/* 步骤1: 基本设置 */} + {/* 步骤内容 */} {currentStep === 1 && (
- 任务信息 + 任务基本信息
handleInputChange('name', e.target.value)} - /> -
-
- - handleInputChange('description', e.target.value)} + placeholder="请输入任务名称" />
@@ -231,36 +228,33 @@ export default function NewAutoLike() { - 执行设备 + 选择执行设备 - -
+ +
{devices.map((device) => (
handleDeviceToggle(device.id)} > -
-
-
-
{device.name}
-
- {device.status === 'online' ? '在线' : '离线'} · - 最后活跃: {device.lastActive} -
-
-
handleDeviceToggle(device.id)} /> +
+
{device.name}
+
+ 状态: {device.status === 'online' ? '在线' : '离线'} +
+
+
))}
@@ -269,42 +263,34 @@ export default function NewAutoLike() {
)} - {/* 步骤2: 目标人群 */} {currentStep === 2 && (
- 目标人群 + 选择目标人群 - -
+ +
{targetGroups.map((group) => (
handleGroupToggle(group.id)} > -
-
{group.name}
-
- {group.count} 个好友 -
-
- {group.tags.map((tag) => ( - - {tag} - - ))} -
-
handleGroupToggle(group.id)} /> +
+
{group.name}
+
+ 人数: {group.count} | 标签: {group.tags.join(', ')} +
+
))}
@@ -313,14 +299,14 @@ export default function NewAutoLike() { - 标签筛选 + 选择标签
{availableTags.map((tag) => ( handleTagToggle(tag)} > @@ -333,7 +319,6 @@ export default function NewAutoLike() {
)} - {/* 步骤3: 高级设置 */} {currentStep === 3 && (
@@ -344,38 +329,45 @@ export default function NewAutoLike() {
- handleInputChange('likeInterval', parseInt(e.target.value))} + type="number" + className="w-full border rounded px-3 py-2 text-sm" + value={formData.interval} + onChange={(e) => handleInputChange('interval', parseInt(e.target.value))} />
- handleInputChange('maxLikesPerDay', parseInt(e.target.value))} + type="number" + className="w-full border rounded px-3 py-2 text-sm" + value={formData.maxLikes} + onChange={(e) => handleInputChange('maxLikes', parseInt(e.target.value))} />
- handleInputChange('friendMaxLikes', parseInt(e.target.value))} />
+
handleInputChange('startTime', e.target.value)} - className="flex h-10 w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-gray-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" />
@@ -383,138 +375,54 @@ export default function NewAutoLike() { handleInputChange('endTime', e.target.value)} - className="flex h-10 w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-gray-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" />
- -
- - - 内容筛选 - - -
- {[ - { value: 'text', label: '文字' }, - { value: 'image', label: '图片' }, - { value: 'video', label: '视频' }, - { value: 'link', label: '链接' }, - ].map((type) => ( - handleContentTypeToggle(type.value)} - > - {type.label} - - ))} -
-
- -