diff --git a/nkebao/src/components/LayoutWrapper.tsx b/nkebao/src/components/LayoutWrapper.tsx
index 21d43a23d..859eb2e7e 100644
--- a/nkebao/src/components/LayoutWrapper.tsx
+++ b/nkebao/src/components/LayoutWrapper.tsx
@@ -2,25 +2,12 @@ import React from 'react';
import { useLocation } from 'react-router-dom';
import BottomNav from './BottomNav';
-// 不需要底部导航的页面路径
-const NO_BOTTOM_NAV_PATHS = [
- '/login',
- '/register',
- '/forgot-password',
- '/reset-password',
- '/devices',
- '/devices/',
- '/wechat-accounts',
- '/wechat-accounts/',
- '/scenarios/new',
- '/scenarios/',
- '/plans/',
- '/workspace/auto-group/',
- '/workspace/moments-sync/',
- '/workspace/traffic-distribution/',
- '/workspace/auto-like',
- '/404',
- '/500'
+// 配置需要底部导航的页面路径(白名单)
+const BOTTOM_NAV_CONFIG = [
+ '/', // 首页
+ '/scenarios', // 场景获客
+ '/workspace', // 工作台
+ '/profile', // 我的
];
interface LayoutWrapperProps {
@@ -31,16 +18,20 @@ export default function LayoutWrapper({ children }: LayoutWrapperProps) {
const location = useLocation();
// 检查当前路径是否需要底部导航
- const shouldShowBottomNav = !NO_BOTTOM_NAV_PATHS.some(path =>
- location.pathname.startsWith(path)
- );
+ const shouldShowBottomNav = BOTTOM_NAV_CONFIG.some(path => {
+ // 特殊处理首页路由 '/'
+ if (path === '/') {
+ return location.pathname === '/';
+ }
+ return location.pathname === path;
+ });
// 如果是登录页面,直接渲染内容(不显示底部导航)
if (location.pathname === '/login') {
return <>{children}>;
}
- // 其他页面显示底部导航
+ // 只有在配置列表中的页面才显示底部导航
return (
diff --git a/nkebao/src/components/ui/checkbox.tsx b/nkebao/src/components/ui/checkbox.tsx
index 87aa708fd..f5f8daaf9 100644
--- a/nkebao/src/components/ui/checkbox.tsx
+++ b/nkebao/src/components/ui/checkbox.tsx
@@ -2,6 +2,7 @@ import React from 'react';
interface CheckboxProps {
checked?: boolean;
+ onCheckedChange?: (checked: boolean) => void;
onChange?: (checked: boolean) => void;
disabled?: boolean;
className?: string;
@@ -10,17 +11,24 @@ interface CheckboxProps {
export function Checkbox({
checked = false,
+ onCheckedChange,
onChange,
disabled = false,
className = '',
id
}: CheckboxProps) {
+ const handleChange = (e: React.ChangeEvent) => {
+ const newChecked = e.target.checked;
+ onCheckedChange?.(newChecked);
+ onChange?.(newChecked);
+ };
+
return (
onChange?.(e.target.checked)}
+ onChange={handleChange}
disabled={disabled}
className={`w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 focus:ring-2 ${className}`}
/>
diff --git a/nkebao/src/components/ui/input.tsx b/nkebao/src/components/ui/input.tsx
index 9bc18656a..f76731134 100644
--- a/nkebao/src/components/ui/input.tsx
+++ b/nkebao/src/components/ui/input.tsx
@@ -4,30 +4,45 @@ interface InputProps {
value?: string;
onChange?: (e: React.ChangeEvent) => void;
onKeyDown?: (e: React.KeyboardEvent) => void;
+ onClick?: (e: React.MouseEvent) => void;
placeholder?: string;
className?: string;
readOnly?: boolean;
+ readonly?: boolean;
id?: string;
+ type?: string;
+ min?: number;
+ max?: number;
}
export function Input({
value,
onChange,
onKeyDown,
+ onClick,
placeholder,
className = '',
readOnly = false,
- id
+ readonly = false,
+ id,
+ type = 'text',
+ min,
+ max
}: InputProps) {
+ const isReadOnly = readOnly || readonly;
+
return (
);
diff --git a/nkebao/src/components/ui/radio-group.tsx b/nkebao/src/components/ui/radio-group.tsx
new file mode 100644
index 000000000..db9147210
--- /dev/null
+++ b/nkebao/src/components/ui/radio-group.tsx
@@ -0,0 +1,44 @@
+"use client"
+
+import * as React from "react"
+import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
+import { Circle } from "lucide-react"
+
+import { cn } from "@/utils"
+
+const RadioGroup = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ return (
+
+ )
+})
+RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
+
+const RadioGroupItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ return (
+
+
+
+
+
+ )
+})
+RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
+
+export { RadioGroup, RadioGroupItem }
\ No newline at end of file
diff --git a/nkebao/src/components/ui/scroll-area.tsx b/nkebao/src/components/ui/scroll-area.tsx
new file mode 100644
index 000000000..e39870840
--- /dev/null
+++ b/nkebao/src/components/ui/scroll-area.tsx
@@ -0,0 +1,48 @@
+"use client"
+
+import * as React from "react"
+import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
+
+import { cn } from "@/utils"
+
+const ScrollArea = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+ {children}
+
+
+
+
+))
+ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
+
+const ScrollBar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, orientation = "vertical", ...props }, ref) => (
+
+
+
+))
+ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
+
+export { ScrollArea, ScrollBar }
\ No newline at end of file
diff --git a/nkebao/src/components/ui/switch.tsx b/nkebao/src/components/ui/switch.tsx
index 0b2a85f7c..1d363fcc9 100644
--- a/nkebao/src/components/ui/switch.tsx
+++ b/nkebao/src/components/ui/switch.tsx
@@ -5,15 +5,17 @@ interface SwitchProps {
onCheckedChange: (checked: boolean) => void;
disabled?: boolean;
className?: string;
+ id?: string;
}
-export function Switch({ checked, onCheckedChange, disabled = false, className = '' }: SwitchProps) {
+export function Switch({ checked, onCheckedChange, disabled = false, className = '', id }: SwitchProps) {
return (
);
-}
\ No newline at end of file
+}
+
+// 直接导出toast函数,用于在组件中直接使用
+export const toast = (toastData: Omit
) => {
+ // 这里需要确保ToastProvider已经包装了应用
+ // 在实际使用中,应该通过useToast hook来调用
+ console.warn('toast function called without context. Please use useToast hook instead.');
+
+ // 创建一个简单的DOM toast作为fallback
+ const toastElement = document.createElement('div');
+ toastElement.className = `fixed top-4 right-4 z-50 max-w-sm w-full bg-white rounded-lg shadow-lg border p-4 transform transition-all duration-300 ${
+ toastData.variant === 'destructive'
+ ? 'border-red-200 bg-red-50'
+ : 'border-gray-200'
+ }`;
+
+ toastElement.innerHTML = `
+
+
+
+ ${toastData.title}
+
+ ${toastData.description ? `
+
+ ${toastData.description}
+
+ ` : ''}
+
+
+
+ `;
+
+ document.body.appendChild(toastElement);
+
+ // 自动移除
+ setTimeout(() => {
+ if (toastElement.parentElement) {
+ toastElement.remove();
+ }
+ }, 5000);
+};
\ 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 8b9f1581b..3ba30dd0a 100644
--- a/nkebao/src/pages/workspace/auto-like/NewAutoLike.tsx
+++ b/nkebao/src/pages/workspace/auto-like/NewAutoLike.tsx
@@ -1,451 +1,1151 @@
-import React, { useState } from 'react';
-import { useNavigate } from 'react-router-dom';
-import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
-import { Button } from '@/components/ui/button';
-import { Input } from '@/components/ui/input';
-import { Label } from '@/components/ui/label';
-import { Switch } from '@/components/ui/switch';
-import { Badge } from '@/components/ui/badge';
-// import { Progress } from '@/components/ui/progress';
-import { Checkbox } from '@/components/ui/checkbox';
-// import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
-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 { createAutoLikeTask, CreateLikeTaskData } from '@/api/autoLike';
-import { ContentType } from '@/types/auto-like';
-
-interface Device {
- id: string;
- name: string;
- status: 'online' | 'offline';
- lastActive: string;
-}
-
-interface TargetGroup {
- id: string;
- name: string;
- count: number;
- tags: string[];
-}
-
-export default function NewAutoLike() {
- const navigate = useNavigate();
- const { toast } = useToast();
- const [currentStep, setCurrentStep] = useState(1);
- const [formData, setFormData] = useState({
- name: '',
- interval: 30,
- maxLikes: 100,
- friendMaxLikes: 10,
- startTime: '09:00',
- endTime: '18:00',
- contentTypes: ['text', 'image'],
- devices: [],
- friends: [],
- targetTags: [],
- enableFriendTags: false,
- friendTags: '',
- });
-
- // 模拟设备数据
- const devices: Device[] = [
- { id: '1', name: 'iPhone 14 Pro', status: 'online', lastActive: '2024-03-18 15:30:00' },
- { id: '2', name: 'iPhone 13', status: 'online', lastActive: '2024-03-18 15:25:00' },
- { id: '3', name: 'iPhone 12', status: 'offline', lastActive: '2024-03-18 14:20:00' },
- ];
-
- // 模拟目标人群数据
- const targetGroups: TargetGroup[] = [
- { id: '1', name: 'VIP客户', count: 156, tags: ['VIP', '高价值'] },
- { id: '2', name: '活跃用户', count: 234, tags: ['活跃', '互动'] },
- { id: '3', name: '潜在客户', count: 89, tags: ['潜在', '新客户'] },
- ];
-
- // 可用标签
- const availableTags = [
- 'VIP', '高价值', '活跃', '互动', '潜在', '新客户', '男性', '女性', '青年', '中年', '高收入', '中收入'
- ];
-
- const handleInputChange = (field: keyof CreateLikeTaskData, value: any) => {
- setFormData((prev: CreateLikeTaskData) => ({ ...prev, [field]: value }));
- };
-
- const handleDeviceToggle = (deviceId: string) => {
- setFormData((prev: CreateLikeTaskData) => ({
- ...prev,
- devices: prev.devices.includes(deviceId)
- ? prev.devices.filter((id: string) => id !== deviceId)
- : [...prev.devices, deviceId]
- }));
- };
-
- const handleGroupToggle = (groupId: string) => {
- setFormData((prev: CreateLikeTaskData) => ({
- ...prev,
- friends: prev.friends?.includes(groupId)
- ? prev.friends.filter((id: string) => id !== groupId)
- : [...(prev.friends || []), groupId]
- }));
- };
-
- const handleTagToggle = (tag: string) => {
- setFormData((prev: CreateLikeTaskData) => ({
- ...prev,
- targetTags: prev.targetTags.includes(tag)
- ? prev.targetTags.filter((t: string) => t !== tag)
- : [...prev.targetTags, tag]
- }));
- };
-
- const handleContentTypeToggle = (type: ContentType) => {
- setFormData((prev: CreateLikeTaskData) => ({
- ...prev,
- contentTypes: prev.contentTypes.includes(type)
- ? prev.contentTypes.filter((t: ContentType) => t !== type)
- : [...prev.contentTypes, type]
- }));
- };
-
- const handleNext = () => {
- if (currentStep === 1 && (!formData.name || formData.devices.length === 0)) {
- toast({
- title: '请完善信息',
- description: '请填写任务名称并选择至少一个设备',
- variant: 'destructive',
- });
- return;
- }
- if (currentStep === 2 && (!formData.friends || formData.friends.length === 0)) {
- toast({
- title: '请选择目标人群',
- description: '请至少选择一个目标人群',
- variant: 'destructive',
- });
- return;
- }
- setCurrentStep(prev => Math.min(prev + 1, 3));
- };
-
- const handlePrev = () => {
- setCurrentStep(prev => Math.max(prev - 1, 1));
- };
-
- const handleSubmit = async () => {
- try {
- const response = await createAutoLikeTask(formData);
-
- 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: '请检查网络连接后重试',
- variant: 'destructive',
- });
- }
- };
-
- const steps = [
- { title: '基本设置', description: '配置任务基本信息' },
- { title: '目标人群', description: '选择点赞目标' },
- { title: '高级设置', description: '配置高级参数' },
- ];
-
- return (
-
- }
- footer={}
- >
-
-
- {/* 步骤指示器 */}
-
-
-
- {steps.map((step, index) => (
-
-
- {index + 1}
-
-
-
{step.title}
-
{step.description}
-
- {index < steps.length - 1 && (
-
- )}
-
- ))}
-
-
-
-
- {/* 步骤内容 */}
- {currentStep === 1 && (
-
-
-
- 任务基本信息
-
-
-
-
- handleInputChange('name', e.target.value)}
- placeholder="请输入任务名称"
- />
-
-
-
-
-
-
- 选择执行设备
-
-
-
- {devices.map((device) => (
-
handleDeviceToggle(device.id)}
- >
-
handleDeviceToggle(device.id)}
- />
-
-
{device.name}
-
- 状态: {device.status === 'online' ? '在线' : '离线'}
-
-
-
-
- ))}
-
-
-
-
- )}
-
- {currentStep === 2 && (
-
-
-
- 选择目标人群
-
-
-
- {targetGroups.map((group) => (
-
handleGroupToggle(group.id)}
- >
-
handleGroupToggle(group.id)}
- />
-
-
{group.name}
-
- 人数: {group.count} | 标签: {group.tags.join(', ')}
-
-
-
- ))}
-
-
-
-
-
-
- 选择标签
-
-
-
- {availableTags.map((tag) => (
- handleTagToggle(tag)}
- >
- {tag}
-
- ))}
-
-
-
-
- )}
-
- {currentStep === 3 && (
-
-
-
- 点赞设置
-
-
-
-
-
-
-
-
-
- {(['text', 'image', 'video', 'link'] as ContentType[]).map((type) => (
- handleContentTypeToggle(type)}
- >
- {type === 'text' ? '文字' : type === 'image' ? '图片' : type === 'video' ? '视频' : '链接'}
-
- ))}
-
-
-
-
- handleInputChange('enableFriendTags', checked)}
- />
-
-
-
- {formData.enableFriendTags && (
-
-
- handleInputChange('friendTags', e.target.value)}
- placeholder="请输入要添加的标签"
- />
-
- )}
-
-
-
- )}
-
- {/* 操作按钮 */}
-
-
-
-
- {currentStep < 3 ? (
-
- ) : (
-
- )}
-
-
-
-
-
- );
+import React, { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { ChevronLeft, Search, Plus, Minus, Check, X, Tag as TagIcon } from 'lucide-react';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { Switch } from '@/components/ui/switch';
+import { Badge } from '@/components/ui/badge';
+import { Card, CardContent } from '@/components/ui/card';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
+import { ScrollArea } from '@/components/ui/scroll-area';
+import { Checkbox } from '@/components/ui/checkbox';
+import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
+import { createAutoLikeTask } from '@/api/autoLike';
+import { ContentType } from '@/types/auto-like';
+import { useToast } from '@/components/ui/toast';
+import Layout from '@/components/Layout';
+import { fetchDeviceList } from '@/api/devices';
+import type { Device } from '@/types/device';
+import { useEffect } from 'react';
+import { get } from '@/api/request';
+
+interface TagGroup {
+ id: string;
+ name: string;
+ tags: string[];
+}
+
+// 用于设备选择弹窗的简化设备类型
+interface DeviceSelectionItem {
+ id: string;
+ name: string;
+ imei: string;
+ wechatId: string;
+ status: 'online' | 'offline';
+}
+
+// 微信好友接口类型
+interface WechatFriend {
+ id: string;
+ nickname: string;
+ wechatId: string;
+ avatar: string;
+ customer: string;
+}
+
+// 好友列表API响应类型
+interface FriendsResponse {
+ code: number;
+ msg: string;
+ data: {
+ list: Array<{
+ id: number;
+ nickname: string;
+ wechatId: string;
+ avatar?: string;
+ customer?: string;
+ }>;
+ total: number;
+ page: number;
+ limit: number;
+ };
+}
+
+// 获取好友列表API函数
+const fetchFriendsList = async (page: number = 1, limit: number = 20, deviceIds: string[]): Promise => {
+ if (deviceIds.length === 0) {
+ return {
+ code: 200,
+ msg: 'success',
+ data: {
+ list: [],
+ total: 0,
+ page,
+ limit
+ }
+ };
+ }
+
+ const deviceIdsParam = deviceIds.join(',');
+ return get(`/v1/friend?page=${page}&limit=${limit}&deviceIds=${deviceIdsParam}`);
+};
+
+// 修改CreateLikeTaskData接口,确保friends字段不是可选的
+interface CreateLikeTaskDataLocal {
+ name: string;
+ interval: number;
+ maxLikes: number;
+ startTime: string;
+ endTime: string;
+ contentTypes: ContentType[];
+ devices: string[];
+ friends: string[];
+ friendMaxLikes: number;
+ friendTags: string;
+ enableFriendTags: boolean;
+ targetTags: string[];
+}
+
+export default function NewAutoLike() {
+ const navigate = useNavigate();
+ const { toast } = useToast();
+ const [currentStep, setCurrentStep] = useState(1);
+ const [deviceDialogOpen, setDeviceDialogOpen] = useState(false);
+ const [friendDialogOpen, setFriendDialogOpen] = useState(false);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [formData, setFormData] = useState({
+ name: '',
+ interval: 5,
+ maxLikes: 200,
+ startTime: '08:00',
+ endTime: '22:00',
+ contentTypes: ['text', 'image', 'video'],
+ devices: [],
+ friends: [], // 确保初始化为空数组而不是undefined
+ targetTags: [],
+ friendMaxLikes: 10,
+ enableFriendTags: false,
+ friendTags: '',
+ });
+ // 新增自动开启的独立状态
+ const [autoEnabled, setAutoEnabled] = useState(false);
+ const [devices, setDevices] = useState([]);
+
+ useEffect(() => {
+ fetchDeviceList(1, 100).then(res => {
+ if (res && res.data && Array.isArray(res.data.list)) {
+ setDevices(res.data.list.map(d => ({
+ id: d.id?.toString() || '',
+ name: d.memo || d.imei || '',
+ imei: d.imei || '',
+ wechatId: d.wechatId || '',
+ status: d.alive === 1 ? 'online' : 'offline',
+ })));
+ }
+ });
+ }, []);
+
+ // 标签组数据
+ const [tagGroups] = useState([
+ {
+ id: 'intention',
+ name: '意向度',
+ tags: ['高意向', '中意向', '低意向'],
+ },
+ {
+ id: 'customer',
+ name: '客户类型',
+ tags: ['新客户', '老客户', 'VIP客户'],
+ },
+ {
+ id: 'gender',
+ name: '性别',
+ tags: ['男性', '女性'],
+ },
+ {
+ id: 'age',
+ name: '年龄段',
+ tags: ['年轻人', '中年人', '老年人'],
+ },
+ {
+ id: 'location',
+ name: '地区',
+ tags: ['城市', '农村'],
+ },
+ {
+ id: 'income',
+ name: '收入',
+ tags: ['高收入', '中等收入', '低收入'],
+ },
+ {
+ id: 'interaction',
+ name: '互动频率',
+ tags: ['高频互动', '中频互动', '低频互动'],
+ },
+ ]);
+
+ const handleUpdateFormData = (data: Partial) => {
+ setFormData((prev) => ({ ...prev, ...data }));
+ };
+
+ const handleNext = () => {
+ setCurrentStep((prev) => Math.min(prev + 1, 3));
+ // 滚动到顶部
+ const mainElement = document.querySelector('main');
+ if (mainElement) {
+ mainElement.scrollTo({ top: 0, behavior: 'smooth' });
+ }
+ };
+
+ const handlePrev = () => {
+ setCurrentStep((prev) => Math.max(prev - 1, 1));
+ // 滚动到顶部
+ const mainElement = document.querySelector('main');
+ if (mainElement) {
+ mainElement.scrollTo({ top: 0, behavior: 'smooth' });
+ }
+ };
+
+ const handleComplete = async () => {
+ if (isSubmitting) return;
+ setIsSubmitting(true);
+ try {
+ // 转换为API需要的格式
+ const apiFormData = {
+ ...formData,
+ // 如果API需要其他转换,可以在这里添加
+ };
+
+ const response = await createAutoLikeTask(apiFormData);
+ if (response.code === 200) {
+ toast({
+ title: '创建成功',
+ description: '自动点赞任务已创建并开始执行',
+ });
+ navigate('/workspace/auto-like');
+ } else {
+ toast({
+ title: '创建失败',
+ description: response.msg || '请稍后重试',
+ variant: 'destructive',
+ });
+ }
+ } catch (error) {
+ toast({
+ title: '创建失败',
+ description: '请检查网络连接后重试',
+ variant: 'destructive',
+ });
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ const header = (
+
+
+
+
新建自动点赞
+
+
+
+ );
+
+ return (
+
+
+
+
+
+ {currentStep === 1 && (
+
+ )}
+
+ {currentStep === 2 && (
+
+
+
+ setDeviceDialogOpen(true)}
+ value={formData.devices.length > 0 ? `已选择 ${formData.devices.length} 个设备` : ''}
+ />
+
+
+ {formData.devices.length > 0 && (
+
+
已选择的设备
+
+ {formData.devices.map(deviceId => {
+ const device = devices.find(d => d.id === deviceId);
+ if (!device) return null;
+ return (
+
+
+
{device.name}
+
+ {device.status === 'online' ? '在线' : '离线'}
+
+
+
+
IMEI: {device.imei}
+
微信号: {device.wechatId}
+
+
+ );
+ })}
+
+
+ )}
+
+
+
+
+
+
+
handleUpdateFormData({ devices })}
+ devices={devices}
+ />
+
+ )}
+
+ {currentStep === 3 && (
+
+
+
+
+
+
+
setFriendDialogOpen(true)}
+ value={formData.friends && formData.friends.length > 0 ? `已选择 ${formData.friends.length} 个好友` : ''}
+ />
+
+
+
+
+
+
+
handleUpdateFormData({ friends })}
+ deviceIds={formData.devices}
+ />
+
+ )}
+
+
+
+
+ );
+}
+
+// 步骤指示器组件
+interface StepIndicatorProps {
+ currentStep: number;
+}
+
+function StepIndicator({ currentStep }: StepIndicatorProps) {
+ const steps = [
+ { title: '基础设置', description: '设置点赞规则' },
+ { title: '设备选择', description: '选择执行设备' },
+ { title: '人群选择', description: '选择目标人群' },
+ ];
+
+ return (
+
+
+
+ {steps.map((step, index) => (
+
+
+ {index < currentStep ? : index + 1}
+
+
+
+ {step.title}
+
+
{step.description}
+
+
+ ))}
+
+
+
+
+ );
+}
+
+// 基础设置组件
+interface BasicSettingsProps {
+ formData: CreateLikeTaskDataLocal;
+ onChange: (data: Partial) => void;
+ onNext: () => void;
+ autoEnabled: boolean;
+ setAutoEnabled: (v: boolean) => void;
+}
+
+function BasicSettings({ formData, onChange, onNext, autoEnabled, setAutoEnabled }: BasicSettingsProps) {
+ const handleContentTypeChange = (type: ContentType) => {
+ const currentTypes = [...formData.contentTypes];
+ if (currentTypes.includes(type)) {
+ onChange({ contentTypes: currentTypes.filter((t) => t !== type) });
+ } else {
+ onChange({ contentTypes: [...currentTypes, type] });
+ }
+ };
+
+ const incrementInterval = () => {
+ onChange({ interval: Math.min(formData.interval + 5, 60) });
+ };
+
+ const decrementInterval = () => {
+ onChange({ interval: Math.max(formData.interval - 5, 5) });
+ };
+
+ const incrementMaxLikes = () => {
+ onChange({ maxLikes: Math.min(formData.maxLikes + 10, 500) });
+ };
+
+ const decrementMaxLikes = () => {
+ onChange({ maxLikes: Math.max(formData.maxLikes - 10, 10) });
+ };
+
+ return (
+
+
+
+ onChange({ name: e.target.value })}
+ className="h-12 rounded-xl border-gray-200"
+ />
+
+
+
+
+
+
+
+
onChange({ interval: Number.parseInt(e.target.value) || 5 })}
+ className="h-12 rounded-none border-x-0 border-gray-200 text-center"
+ />
+
+ 秒
+
+
+
+
+
设置两次点赞之间的最小时间间隔
+
+
+
+
+
+
+
+
onChange({ maxLikes: Number.parseInt(e.target.value) || 10 })}
+ className="h-12 rounded-none border-x-0 border-gray-200 text-center"
+ />
+
+ 次/天
+
+
+
+
+
设置每天最多点赞的次数
+
+
+
+
+
+
设置每天可以点赞的时间段
+
+
+
+
+
+ {[
+ { id: 'text' as ContentType, label: '文字' },
+ { id: 'image' as ContentType, label: '图片' },
+ { id: 'video' as ContentType, label: '视频' },
+ ].map((type) => (
+
handleContentTypeChange(type.id)}
+ >
+ {type.label}
+
+ ))}
+
+
选择要点赞的内容类型
+
+
+
+
+
+ onChange({ enableFriendTags: checked })}
+ />
+
+ {formData.enableFriendTags && (
+ <>
+
+
+
onChange({ friendTags: e.target.value })}
+ className="h-12 rounded-xl border-gray-200"
+ />
+
只给有此标签的好友点赞
+
+ >
+ )}
+
+
+
+
+
+
+
+
+
+ );
+}
+
+// 设备选择对话框组件
+interface DeviceSelectionDialogProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ selectedDevices: string[];
+ onSelect: (devices: string[]) => void;
+ devices: DeviceSelectionItem[];
+}
+
+function DeviceSelectionDialog({ open, onOpenChange, selectedDevices, onSelect, devices }: DeviceSelectionDialogProps) {
+ const [searchQuery, setSearchQuery] = useState('');
+ const [statusFilter, setStatusFilter] = useState('all');
+
+ const filteredDevices = devices.filter((device) => {
+ const matchesSearch =
+ device.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ device.imei.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ device.wechatId.toLowerCase().includes(searchQuery.toLowerCase());
+
+ const matchesStatus =
+ statusFilter === 'all' ||
+ (statusFilter === 'online' && device.status === 'online') ||
+ (statusFilter === 'offline' && device.status === 'offline');
+
+ return matchesSearch && matchesStatus;
+ });
+
+ // 处理设备选择
+ const handleDeviceToggle = (deviceId: string) => {
+ if (selectedDevices.includes(deviceId)) {
+ onSelect(selectedDevices.filter(id => id !== deviceId));
+ } else {
+ onSelect([...selectedDevices, deviceId]);
+ }
+ };
+
+ return (
+
+ );
+}
+
+// 标签选择器组件
+interface TagSelectorProps {
+ selectedTags: string[];
+ tagOperator: 'and' | 'or';
+ onTagsChange: (tags: string[]) => void;
+ onOperatorChange: (operator: 'and' | 'or') => void;
+ onBack: () => void;
+ onComplete: () => void;
+ tagGroups: TagGroup[];
+}
+
+function TagSelector({
+ selectedTags,
+ tagOperator,
+ onTagsChange,
+ onOperatorChange,
+ onBack,
+ onComplete,
+ tagGroups,
+}: TagSelectorProps) {
+ const [searchQuery, setSearchQuery] = useState('');
+ const [customTag, setCustomTag] = useState('');
+
+ const toggleTag = (tag: string) => {
+ if (selectedTags.includes(tag)) {
+ onTagsChange(selectedTags.filter((t) => t !== tag));
+ } else {
+ onTagsChange([...selectedTags, tag]);
+ }
+ };
+
+ const addCustomTag = () => {
+ if (customTag.trim() && !selectedTags.includes(customTag.trim())) {
+ onTagsChange([...selectedTags, customTag.trim()]);
+ setCustomTag('');
+ }
+ };
+
+ const removeTag = (tag: string) => {
+ onTagsChange(selectedTags.filter((t) => t !== tag));
+ };
+
+ const filteredTagGroups = tagGroups
+ .map((group) => ({
+ ...group,
+ tags: group.tags.filter((tag) => tag.toLowerCase().includes(searchQuery.toLowerCase())),
+ }))
+ .filter((group) => group.tags.length > 0);
+
+ return (
+
+
+
+
+
+
选择目标人群标签
+
+
+
+
+ setSearchQuery(e.target.value)}
+ />
+
+
+
+
+ {tagGroups.slice(0, 4).map((group) => (
+
+ {group.name}
+
+ ))}
+
+ {tagGroups.map((group) => (
+
+
+ {group.tags.map((tag) => (
+ toggleTag(tag)}
+ >
+ {selectedTags.includes(tag) && }
+ {tag}
+
+ ))}
+
+
+ ))}
+
+
+
+
+ {filteredTagGroups.length > 0 ? (
+ filteredTagGroups.map((group) => (
+
+
{group.name}
+
+ {group.tags.map((tag) => (
+
+ toggleTag(tag)}
+ />
+
+
+ ))}
+
+
+ ))
+ ) : (
+
没有找到匹配的标签
+ )}
+
+
+
+
+
+
+ setCustomTag(e.target.value)}
+ className="pl-9"
+ placeholder="添加自定义标签"
+ onKeyDown={(e) => e.key === 'Enter' && addCustomTag()}
+ />
+
+
+
+
+
+
+
标签匹配逻辑
+
选择多个标签之间的匹配关系
+
+
onOperatorChange(value as 'and' | 'or')}
+ className="flex flex-col space-y-2"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
已选择的标签
+
+ {selectedTags.length === 0 ? (
+
未选择任何标签
+ ) : (
+
+ {selectedTags.map((tag) => (
+
+ {tag}
+
+
+ ))}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+// 微信好友选择弹窗组件
+interface FriendSelectionDialogProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ selectedFriends: string[];
+ onSelect: (friends: string[]) => void;
+ deviceIds: string[];
+}
+
+function FriendSelectionDialog({ open, onOpenChange, selectedFriends = [], onSelect, deviceIds }: FriendSelectionDialogProps) {
+ const [searchQuery, setSearchQuery] = useState('');
+ const [currentPage, setCurrentPage] = useState(1);
+ const [totalPages, setTotalPages] = useState(1);
+ const [totalFriends, setTotalFriends] = useState(0);
+ const [friends, setFriends] = useState([]);
+ const [loading, setLoading] = useState(false);
+
+ // 获取好友列表
+ useEffect(() => {
+ if (open && deviceIds.length > 0) {
+ fetchFriends(currentPage);
+ }
+ }, [open, currentPage, deviceIds]);
+
+ // 当设备ID变化时,重置页码
+ useEffect(() => {
+ if (deviceIds.length > 0) {
+ setCurrentPage(1);
+ }
+ }, [deviceIds]);
+
+ // 获取好友列表API
+ const fetchFriends = async (page: number) => {
+ if (deviceIds.length === 0) return;
+
+ setLoading(true);
+ try {
+ const res = await fetchFriendsList(page, 20, deviceIds);
+
+ if (res && res.code === 200 && res.data) {
+ setFriends(res.data.list.map((friend) => ({
+ id: friend.id?.toString() || '',
+ nickname: friend.nickname || '',
+ wechatId: friend.wechatId || '',
+ avatar: friend.avatar || '',
+ customer: friend.customer || '',
+ })));
+
+ setTotalFriends(res.data.total || 0);
+ setTotalPages(Math.ceil((res.data.total || 0) / 20));
+ }
+ } catch (error) {
+ console.error('获取好友列表失败:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const filteredFriends = friends.filter(friend =>
+ friend.nickname.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ friend.wechatId.toLowerCase().includes(searchQuery.toLowerCase())
+ );
+
+ const handleFriendToggle = (friendId: string) => {
+ if (selectedFriends.includes(friendId)) {
+ onSelect(selectedFriends.filter(id => id !== friendId));
+ } else {
+ onSelect([...selectedFriends, friendId]);
+ }
+ };
+
+ const handleSelectAll = () => {
+ // 选择当前页面上所有好友
+ const currentPageIds = filteredFriends.map(friend => friend.id);
+ const newSelectedFriends = [...selectedFriends];
+
+ // 添加未选中的好友
+ currentPageIds.forEach(id => {
+ if (!selectedFriends.includes(id)) {
+ newSelectedFriends.push(id);
+ }
+ });
+
+ onSelect(newSelectedFriends);
+ };
+
+ const handleUnselectAll = () => {
+ // 取消当前页面上所有好友的选择
+ const currentPageIds = filteredFriends.map(friend => friend.id);
+ const newSelectedFriends = selectedFriends.filter(id => !currentPageIds.includes(id));
+ onSelect(newSelectedFriends);
+ };
+
+ const handleConfirm = () => {
+ onOpenChange(false);
+ };
+
+ return (
+
+ );
}
\ No newline at end of file