diff --git a/Cunkebao/components/ExcelImporter.tsx b/Cunkebao/components/ExcelImporter.tsx index d0b55979..380ab859 100644 --- a/Cunkebao/components/ExcelImporter.tsx +++ b/Cunkebao/components/ExcelImporter.tsx @@ -27,7 +27,10 @@ interface ExcelImporterProps { onReset?: () => void; } -export default function ExcelImporter({ onImport, onReset }: ExcelImporterProps) { +export default function ExcelImporter({ + onImport, + onReset, +}: ExcelImporterProps) { const [parsedData, setParsedData] = useState([]); const [error, setError] = useState(null); const [fileName, setFileName] = useState(""); @@ -51,62 +54,64 @@ export default function ExcelImporter({ onImport, onReset }: ExcelImporterProps) setParsedData([]); setDetectedColumns({}); setIsProcessing(true); - + const file = files[0]; setFileName(file.name); - + const reader = new FileReader(); reader.onload = (event) => { try { const data = event.target?.result; - + if (!data) { setError("文件内容为空,请检查文件是否有效"); setIsProcessing(false); return; } - + // 尝试将读取的二进制数据解析为Excel工作簿 let workbook; try { - workbook = XLSX.read(data, { type: 'binary' }); + workbook = XLSX.read(data, { type: "binary" }); } catch (parseErr) { console.error("解析Excel内容失败:", parseErr); - setError("无法解析文件内容,请确保上传的是有效的Excel文件(.xlsx或.xls格式)"); + setError( + "无法解析文件内容,请确保上传的是有效的Excel文件(.xlsx或.xls格式)" + ); setIsProcessing(false); return; } - + if (!workbook.SheetNames || workbook.SheetNames.length === 0) { setError("Excel文件中没有找到工作表"); setIsProcessing(false); return; } - + // 取第一个工作表 const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; - + if (!worksheet) { setError(`无法读取工作表 "${sheetName}",请检查文件是否损坏`); setIsProcessing(false); return; } - + // 将工作表转换为JSON const jsonData = XLSX.utils.sheet_to_json(worksheet); - + if (!jsonData || jsonData.length === 0) { setError("Excel 文件中没有数据"); setIsProcessing(false); return; } - + // 查找栏位对应的列 let mobileColumn: string | null = null; let fromColumn: string | null = null; let aliasColumn: string | null = null; - + // 遍历第一行查找栏位 const firstRow = jsonData[0] as Record; if (!firstRow) { @@ -114,88 +119,116 @@ export default function ExcelImporter({ onImport, onReset }: ExcelImporterProps) setIsProcessing(false); return; } - + for (const key in firstRow) { if (!firstRow[key]) continue; // 跳过空值 - + const value = String(firstRow[key]).toLowerCase(); - + // 扩展匹配列表,提高识别成功率 - if (value.includes("手机") || value.includes("电话") || value.includes("mobile") || - value.includes("phone") || value.includes("tel") || value.includes("cell")) { + if ( + value.includes("手机") || + value.includes("电话") || + value.includes("mobile") || + value.includes("phone") || + value.includes("tel") || + value.includes("cell") + ) { mobileColumn = key; - } else if (value.includes("来源") || value.includes("source") || value.includes("from") || - value.includes("channel") || value.includes("渠道")) { + } else if ( + value.includes("来源") || + value.includes("source") || + value.includes("from") || + value.includes("channel") || + value.includes("渠道") + ) { fromColumn = key; - } else if (value.includes("微信") || value.includes("alias") || value.includes("wechat") || - value.includes("wx") || value.includes("id") || value.includes("账号")) { + } else if ( + value.includes("微信") || + value.includes("alias") || + value.includes("wechat") || + value.includes("wx") || + value.includes("id") || + value.includes("账号") + ) { aliasColumn = key; } } - + // 保存检测到的列名 if (mobileColumn && firstRow[mobileColumn]) { - setDetectedColumns(prev => ({ ...prev, mobile: String(firstRow[mobileColumn]) })); + setDetectedColumns((prev) => ({ + ...prev, + mobile: String(firstRow[mobileColumn]), + })); } if (fromColumn && firstRow[fromColumn]) { - setDetectedColumns(prev => ({ ...prev, from: String(firstRow[fromColumn]) })); + setDetectedColumns((prev) => ({ + ...prev, + from: String(firstRow[fromColumn]), + })); } if (aliasColumn && firstRow[aliasColumn]) { - setDetectedColumns(prev => ({ ...prev, alias: String(firstRow[aliasColumn]) })); + setDetectedColumns((prev) => ({ + ...prev, + alias: String(firstRow[aliasColumn]), + })); } - + if (!mobileColumn) { - setError("未找到手机号码栏位,请确保Excel中包含手机、电话、mobile或phone等栏位名称"); + setError( + "未找到手机号码栏位,请确保Excel中包含手机、电话、mobile或phone等栏位名称" + ); setIsProcessing(false); return; } - + // 取第二行开始的数据(跳过标题行) const importedData: ContactData[] = []; for (let i = 1; i < jsonData.length; i++) { const row = jsonData[i] as Record; - + // 确保手机号存在且不为空 if (!row || !row[mobileColumn]) continue; - + // 处理手机号,去掉非数字字符 let mobileValue = row[mobileColumn]; let mobileNumber: number; - - if (typeof mobileValue === 'number') { + + if (typeof mobileValue === "number") { mobileNumber = mobileValue; } else { // 如果是字符串,去掉非数字字符 - const mobileStr = String(mobileValue).trim().replace(/\D/g, ''); + const mobileStr = String(mobileValue).trim().replace(/\D/g, ""); if (!mobileStr) continue; // 如果手机号为空,跳过该行 mobileNumber = Number(mobileStr); if (isNaN(mobileNumber)) continue; // 如果转换后不是数字,跳过该行 } - + // 构建数据对象 const contact: ContactData = { - mobile: mobileNumber + mobile: mobileNumber, }; - + // 添加来源字段(如果存在) if (fromColumn && row[fromColumn]) { contact.from = String(row[fromColumn]).trim(); } - + // 添加微信号字段(如果存在) if (aliasColumn && row[aliasColumn]) { contact.alias = String(row[aliasColumn]).trim(); } - + importedData.push(contact); } - + if (importedData.length === 0) { setError("未找到有效数据,请确保Excel中至少有一行有效的手机号码"); setIsProcessing(false); return; } - + setParsedData(importedData); setIsProcessing(false); } catch (err) { @@ -204,12 +237,12 @@ export default function ExcelImporter({ onImport, onReset }: ExcelImporterProps) setIsProcessing(false); } }; - + reader.onerror = () => { setError("读取文件时出错,请重试"); setIsProcessing(false); }; - + reader.readAsBinaryString(file); }; @@ -245,29 +278,27 @@ export default function ExcelImporter({ onImport, onReset }: ExcelImporterProps) disabled={isProcessing} /> {fileName && ( -

- 当前文件: {fileName} -

+

当前文件: {fileName}

)}
请确保Excel文件包含以下列: 手机号码(必需)、来源(可选)、微信号(可选)
- + {isProcessing && (

正在处理Excel文件...

)} - + {error && ( {error} )} - + {isImportSuccessful && ( @@ -275,30 +306,40 @@ export default function ExcelImporter({ onImport, onReset }: ExcelImporterProps) )} - + {parsedData.length > 0 && !isImportSuccessful && (

已解析 {parsedData.length} 条有效数据,点击下方按钮确认导入。

- + {Object.keys(detectedColumns).length > 0 && (

检测到的列名:

    - {detectedColumns.mobile &&
  • 手机号: {detectedColumns.mobile}
  • } - {detectedColumns.from &&
  • 来源: {detectedColumns.from}
  • } - {detectedColumns.alias &&
  • 微信号: {detectedColumns.alias}
  • } + {detectedColumns.mobile && ( +
  • 手机号: {detectedColumns.mobile}
  • + )} + {detectedColumns.from && ( +
  • 来源: {detectedColumns.from}
  • + )} + {detectedColumns.alias && ( +
  • 微信号: {detectedColumns.alias}
  • + )}
)} - +

数据示例:

{JSON.stringify(parsedData.slice(0, 3), null, 2)}
- {parsedData.length > 3 &&

...共 {parsedData.length} 条

} + {parsedData.length > 3 && ( +

+ ...共 {parsedData.length} 条 +

+ )}
@@ -317,17 +358,19 @@ export default function ExcelImporter({ onImport, onReset }: ExcelImporterProps)
)} - +
- -
-
@@ -111,9 +133,9 @@ export function ContentLibrarySelectionDialog({ 已选择 {tempSelected.length} 个内容库
-
@@ -171,11 +200,11 @@ export function ContentLibrarySelectionDialog({ 取消 ); -} \ No newline at end of file +} diff --git a/nkebao/src/components/DeviceSelection.tsx b/nkebao/src/components/DeviceSelection.tsx index 7f341d76..79fcdca8 100644 --- a/nkebao/src/components/DeviceSelection.tsx +++ b/nkebao/src/components/DeviceSelection.tsx @@ -1,11 +1,16 @@ -import React, { useState, useEffect } from 'react'; -import { Search } from 'lucide-react'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { ScrollArea } from '@/components/ui/scroll-area'; -import { Checkbox } from '@/components/ui/checkbox'; -import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; -import { fetchDeviceList } from '@/api/devices'; +import React, { useState, useEffect } from "react"; +import { Search } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Checkbox } from "@/components/ui/checkbox"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { fetchDeviceList } from "@/api/devices"; // 设备选择项接口 interface DeviceSelectionItem { @@ -13,7 +18,7 @@ interface DeviceSelectionItem { name: string; imei: string; wechatId: string; - status: 'online' | 'offline'; + status: "online" | "offline"; } // 组件属性接口 @@ -24,16 +29,16 @@ interface DeviceSelectionProps { className?: string; } -export default function DeviceSelection({ - selectedDevices, - onSelect, +export default function DeviceSelection({ + selectedDevices, + onSelect, placeholder = "选择设备", - className = "" + className = "", }: DeviceSelectionProps) { const [dialogOpen, setDialogOpen] = useState(false); const [devices, setDevices] = useState([]); - const [searchQuery, setSearchQuery] = useState(''); - const [statusFilter, setStatusFilter] = useState('all'); + const [searchQuery, setSearchQuery] = useState(""); + const [statusFilter, setStatusFilter] = useState("all"); const [loading, setLoading] = useState(false); // 当弹窗打开时获取设备列表 @@ -49,16 +54,18 @@ export default function DeviceSelection({ try { const res = await fetchDeviceList(1, 100); 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', - }))); + 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", + })) + ); } } catch (error) { - console.error('获取设备列表失败:', error); + console.error("获取设备列表失败:", error); } finally { setLoading(false); } @@ -72,9 +79,9 @@ export default function DeviceSelection({ device.wechatId.toLowerCase().includes(searchQuery.toLowerCase()); const matchesStatus = - statusFilter === 'all' || - (statusFilter === 'online' && device.status === 'online') || - (statusFilter === 'offline' && device.status === 'offline'); + statusFilter === "all" || + (statusFilter === "online" && device.status === "online") || + (statusFilter === "offline" && device.status === "offline"); return matchesSearch && matchesStatus; }); @@ -82,7 +89,7 @@ export default function DeviceSelection({ // 处理设备选择 const handleDeviceToggle = (deviceId: string) => { if (selectedDevices.includes(deviceId)) { - onSelect(selectedDevices.filter(id => id !== deviceId)); + onSelect(selectedDevices.filter((id) => id !== deviceId)); } else { onSelect([...selectedDevices, deviceId]); } @@ -90,7 +97,7 @@ export default function DeviceSelection({ // 获取显示文本 const getDisplayText = () => { - if (selectedDevices.length === 0) return ''; + if (selectedDevices.length === 0) return ""; return `已选择 ${selectedDevices.length} 个设备`; }; @@ -108,10 +115,9 @@ export default function DeviceSelection({ /> - {/* 设备选择弹窗 */} - + 选择设备 @@ -128,7 +134,7 @@ export default function DeviceSelection({ setStatusFilter(e.target.value)} className="w-32 px-3 py-2 border border-gray-300 rounded-md text-sm" > @@ -119,8 +134,17 @@ export function DeviceSelectionDialog({ - @@ -149,17 +173,23 @@ export function DeviceSelectionDialog({
{device.name} - - {device.status === 'online' ? '在线' : '离线'} + + {device.status === "online" ? "在线" : "离线"}
IMEI: {device.imei}
-
微信号: {device.wxid||'-'}
-
昵称: {device.nickname||'-'}
+
微信号: {device.wxid || "-"}
+
昵称: {device.nickname || "-"}
{device.usedInPlans > 0 && ( -
已用于 {device.usedInPlans} 个计划
+
+ 已用于 {device.usedInPlans} 个计划 +
)}
@@ -177,11 +207,12 @@ export function DeviceSelectionDialog({ 取消
); -} \ No newline at end of file +} diff --git a/nkebao/src/components/FriendSelection.tsx b/nkebao/src/components/FriendSelection.tsx index 6e976dc7..a23584f7 100644 --- a/nkebao/src/components/FriendSelection.tsx +++ b/nkebao/src/components/FriendSelection.tsx @@ -1,10 +1,15 @@ -import React, { useState, useEffect } from 'react'; -import { Search, X } from 'lucide-react'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { ScrollArea } from '@/components/ui/scroll-area'; -import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; -import { get } from '@/api/request'; +import React, { useState, useEffect } from "react"; +import { Search, X } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { get } from "@/api/request"; // 微信好友接口类型 interface WechatFriend { @@ -42,18 +47,20 @@ const fetchFriendsList = async (params: { if (params.deviceIds && params.deviceIds.length === 0) { return { code: 200, - msg: 'success', + msg: "success", data: { list: [], total: 0, page: params.page, - limit: params.limit - } + limit: params.limit, + }, }; } - - const deviceIdsParam = params?.deviceIds?.join(',') || '' - return get(`/v1/friend?page=${ params.page}&limit=${params.limit}&deviceIds=${deviceIdsParam}`); + + const deviceIdsParam = params?.deviceIds?.join(",") || ""; + return get( + `/v1/friend?page=${params.page}&limit=${params.limit}&deviceIds=${deviceIdsParam}` + ); }; // 组件属性接口 @@ -67,18 +74,18 @@ interface FriendSelectionProps { className?: string; } -export default function FriendSelection({ - selectedFriends, - onSelect, +export default function FriendSelection({ + selectedFriends, + onSelect, onSelectDetail, deviceIds = [], enableDeviceFilter = true, placeholder = "选择微信好友", - className = "" + className = "", }: FriendSelectionProps) { const [dialogOpen, setDialogOpen] = useState(false); const [friends, setFriends] = useState([]); - const [searchQuery, setSearchQuery] = useState(''); + const [searchQuery, setSearchQuery] = useState(""); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [totalFriends, setTotalFriends] = useState(0); @@ -116,49 +123,52 @@ export default function FriendSelection({ } else { res = await fetchFriendsList({ page, limit: 20 }); } - + 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 || '', - }))); + 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); + console.error("获取好友列表失败:", error); } finally { setLoading(false); } }; // 过滤好友 - const filteredFriends = friends.filter(friend => - friend.nickname.toLowerCase().includes(searchQuery.toLowerCase()) || - friend.wechatId.toLowerCase().includes(searchQuery.toLowerCase()) + const filteredFriends = friends.filter( + (friend) => + friend.nickname.toLowerCase().includes(searchQuery.toLowerCase()) || + friend.wechatId.toLowerCase().includes(searchQuery.toLowerCase()) ); // 处理好友选择 const handleFriendToggle = (friendId: string) => { let newIds: string[]; if (selectedFriends.includes(friendId)) { - newIds = selectedFriends.filter(id => id !== friendId); + newIds = selectedFriends.filter((id) => id !== friendId); } else { newIds = [...selectedFriends, friendId]; } onSelect(newIds); if (onSelectDetail) { - const selectedObjs = friends.filter(f => newIds.includes(f.id)); + const selectedObjs = friends.filter((f) => newIds.includes(f.id)); onSelectDetail(selectedObjs); } }; // 获取显示文本 const getDisplayText = () => { - if (selectedFriends.length === 0) return ''; + if (selectedFriends.length === 0) return ""; return `已选择 ${selectedFriends.length} 个好友`; }; @@ -171,8 +181,19 @@ export default function FriendSelection({ {/* 输入框 */}
- - + + - +
- 选择微信好友 - + + 选择微信好友 + +
- @@ -223,7 +246,13 @@ export default function FriendSelection({ onClick={() => handleFriendToggle(friend.id)} >
-
+
{selectedFriends.includes(friend.id) && (
)} @@ -232,16 +261,24 @@ export default function FriendSelection({
{friend.avatar ? ( - {friend.nickname} + {friend.nickname} ) : ( friend.nickname.charAt(0) )}
{friend.nickname}
-
微信ID: {friend.wechatId}
+
+ 微信ID: {friend.wechatId} +
{friend.customer && ( -
归属客户: {friend.customer}
+
+ 归属客户: {friend.customer} +
)}
@@ -251,7 +288,7 @@ export default function FriendSelection({ ) : (
- {deviceIds.length === 0 ? '请先选择设备' : '没有找到好友'} + {deviceIds.length === 0 ? "请先选择设备" : "没有找到好友"}
)} @@ -271,11 +308,15 @@ export default function FriendSelection({ > < - {currentPage} / {totalPages} + + {currentPage} / {totalPages} +
- -
@@ -296,4 +344,4 @@ export default function FriendSelection({ ); -} \ No newline at end of file +} diff --git a/nkebao/src/components/GroupSelection.tsx b/nkebao/src/components/GroupSelection.tsx index 2ee6b419..534cf146 100644 --- a/nkebao/src/components/GroupSelection.tsx +++ b/nkebao/src/components/GroupSelection.tsx @@ -1,10 +1,10 @@ -import React, { useState, useEffect } from 'react'; -import { Search, X } from 'lucide-react'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { ScrollArea } from '@/components/ui/scroll-area'; -import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog'; -import { get } from '@/api/request'; +import React, { useState, useEffect } from "react"; +import { Search, X } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; +import { get } from "@/api/request"; // 群组接口类型 interface WechatGroup { @@ -36,8 +36,13 @@ interface GroupsResponse { }; } -const fetchGroupsList = async (params: { page: number; limit: number; }): Promise => { - return get(`/v1/chatroom?page=${params.page}&limit=${params.limit}`); +const fetchGroupsList = async (params: { + page: number; + limit: number; +}): Promise => { + return get( + `/v1/chatroom?page=${params.page}&limit=${params.limit}` + ); }; interface GroupSelectionProps { @@ -52,12 +57,12 @@ export default function GroupSelection({ selectedGroups, onSelect, onSelectDetail, - placeholder = '选择群聊', - className = '' + placeholder = "选择群聊", + className = "", }: GroupSelectionProps) { const [dialogOpen, setDialogOpen] = useState(false); const [groups, setGroups] = useState([]); - const [searchQuery, setSearchQuery] = useState(''); + const [searchQuery, setSearchQuery] = useState(""); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [totalGroups, setTotalGroups] = useState(0); @@ -84,49 +89,52 @@ export default function GroupSelection({ try { const res = await fetchGroupsList({ page, limit: 20 }); if (res && res.code === 200 && res.data) { - setGroups(res.data.list.map((group) => ({ - id: group.id?.toString() || '', - chatroomId: group.chatroomId || '', - name: group.name || '', - avatar: group.avatar || '', - ownerWechatId: group.ownerWechatId || '', - ownerNickname: group.ownerNickname || '', - ownerAvatar: group.ownerAvatar || '', - }))); + setGroups( + res.data.list.map((group) => ({ + id: group.id?.toString() || "", + chatroomId: group.chatroomId || "", + name: group.name || "", + avatar: group.avatar || "", + ownerWechatId: group.ownerWechatId || "", + ownerNickname: group.ownerNickname || "", + ownerAvatar: group.ownerAvatar || "", + })) + ); setTotalGroups(res.data.total || 0); setTotalPages(Math.ceil((res.data.total || 0) / 20)); } } catch (error) { - console.error('获取群组列表失败:', error); + console.error("获取群组列表失败:", error); } finally { setLoading(false); } }; // 过滤群组 - const filteredGroups = groups.filter(group => - group.name.toLowerCase().includes(searchQuery.toLowerCase()) || - group.chatroomId.toLowerCase().includes(searchQuery.toLowerCase()) + const filteredGroups = groups.filter( + (group) => + group.name.toLowerCase().includes(searchQuery.toLowerCase()) || + group.chatroomId.toLowerCase().includes(searchQuery.toLowerCase()) ); // 处理群组选择 const handleGroupToggle = (groupId: string) => { let newIds: string[]; if (selectedGroups.includes(groupId)) { - newIds = selectedGroups.filter(id => id !== groupId); + newIds = selectedGroups.filter((id) => id !== groupId); } else { newIds = [...selectedGroups, groupId]; } onSelect(newIds); if (onSelectDetail) { - const selectedObjs = groups.filter(g => newIds.includes(g.id)); + const selectedObjs = groups.filter((g) => newIds.includes(g.id)); onSelectDetail(selectedObjs); } }; // 获取显示文本 const getDisplayText = () => { - if (selectedGroups.length === 0) return ''; + if (selectedGroups.length === 0) return ""; return `已选择 ${selectedGroups.length} 个群聊`; }; @@ -139,8 +147,19 @@ export default function GroupSelection({ {/* 输入框 */}
- - + + - +
- 选择群聊 + + 选择群聊 +
setSearchQuery('')} + onClick={() => setSearchQuery("")} > @@ -190,7 +211,13 @@ export default function GroupSelection({ onClick={() => handleGroupToggle(group.id)} >
-
+
{selectedGroups.includes(group.id) && (
)} @@ -199,16 +226,24 @@ export default function GroupSelection({
{group.avatar ? ( - {group.name} + {group.name} ) : ( group.name.charAt(0) )}
{group.name}
-
群ID: {group.chatroomId}
+
+ 群ID: {group.chatroomId} +
{group.ownerNickname && ( -
群主: {group.ownerNickname}
+
+ 群主: {group.ownerNickname} +
)}
@@ -236,11 +271,15 @@ export default function GroupSelection({ > < - {currentPage} / {totalPages} + + {currentPage} / {totalPages} +
- -
@@ -261,4 +307,4 @@ export default function GroupSelection({ ); -} \ No newline at end of file +} diff --git a/nkebao/src/components/ui/dialog.tsx b/nkebao/src/components/ui/dialog.tsx index 5238163b..6e20ba64 100644 --- a/nkebao/src/components/ui/dialog.tsx +++ b/nkebao/src/components/ui/dialog.tsx @@ -1,18 +1,18 @@ -"use client" +"use client"; -import * as React from "react" -import * as DialogPrimitive from "@radix-ui/react-dialog" -import { X } from "lucide-react" +import * as React from "react"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; -import { cn } from "@/utils" +import { cn } from "@/utils"; -const Dialog = DialogPrimitive.Root +const Dialog = DialogPrimitive.Root; -const DialogTrigger = DialogPrimitive.Trigger +const DialogTrigger = DialogPrimitive.Trigger; -const DialogPortal = DialogPrimitive.Portal +const DialogPortal = DialogPrimitive.Portal; -const DialogClose = DialogPrimitive.Close +const DialogClose = DialogPrimitive.Close; const DialogOverlay = React.forwardRef< React.ElementRef, @@ -22,12 +22,12 @@ const DialogOverlay = React.forwardRef< ref={ref} className={cn( "fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", - className, + className )} {...props} /> -)) -DialogOverlay.displayName = DialogPrimitive.Overlay.displayName +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; const DialogContent = React.forwardRef< React.ElementRef, @@ -38,8 +38,8 @@ const DialogContent = React.forwardRef< @@ -50,18 +50,36 @@ const DialogContent = React.forwardRef< -)) -DialogContent.displayName = DialogPrimitive.Content.displayName +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; -const DialogHeader = ({ className, ...props }: React.HTMLAttributes) => ( -
-) -DialogHeader.displayName = "DialogHeader" +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = "DialogHeader"; -const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => ( -
-) -DialogFooter.displayName = "DialogFooter" +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = "DialogFooter"; const DialogTitle = React.forwardRef< React.ElementRef, @@ -69,19 +87,26 @@ const DialogTitle = React.forwardRef< >(({ className, ...props }, ref) => ( -)) -DialogTitle.displayName = DialogPrimitive.Title.displayName +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; const DialogDescription = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -DialogDescription.displayName = DialogPrimitive.Description.displayName + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; export { Dialog, @@ -94,4 +119,4 @@ export { DialogFooter, DialogTitle, DialogDescription, -} +}; diff --git a/nkebao/src/pages/scenarios/new/page.tsx b/nkebao/src/pages/scenarios/new/page.tsx index 835557fa..ff64477d 100644 --- a/nkebao/src/pages/scenarios/new/page.tsx +++ b/nkebao/src/pages/scenarios/new/page.tsx @@ -1,25 +1,25 @@ -import { useState, useEffect } from "react" -import { useNavigate } from "react-router-dom" -import { ChevronLeft, Settings } from "lucide-react" -import { Button } from "@/components/ui/button" -import { toast } from "@/components/ui/use-toast" -import { Steps, StepItem } from 'tdesign-mobile-react'; -import { BasicSettings } from "./steps/BasicSettings" -import { FriendRequestSettings } from "./steps/FriendRequestSettings" -import { MessageSettings } from "./steps/MessageSettings" -import Layout from "@/components/Layout" -import { getPlanScenes } from '@/api/scenarios'; +import { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { ChevronLeft, Settings } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { toast } from "@/components/ui/use-toast"; +import { Steps, StepItem } from "tdesign-mobile-react"; +import { BasicSettings } from "./steps/BasicSettings"; +import { FriendRequestSettings } from "./steps/FriendRequestSettings"; +import { MessageSettings } from "./steps/MessageSettings"; +import Layout from "@/components/Layout"; +import { getPlanScenes } from "@/api/scenarios"; // 步骤定义 - 只保留三个步骤 const steps = [ { id: 1, title: "步骤一", subtitle: "基础设置" }, { id: 2, title: "步骤二", subtitle: "好友申请设置" }, { id: 3, title: "步骤三", subtitle: "消息设置" }, -] +]; export default function NewPlan() { - const router = useNavigate() - const [currentStep, setCurrentStep] = useState(1) + const router = useNavigate(); + const [currentStep, setCurrentStep] = useState(1); const [formData, setFormData] = useState({ planName: "", scenario: "haibao", @@ -32,14 +32,14 @@ export default function NewPlan() { endTime: "18:00", enabled: true, // 移除tags字段 - }) + }); const [sceneList, setSceneList] = useState([]); const [sceneLoading, setSceneLoading] = useState(true); useEffect(() => { setSceneLoading(true); getPlanScenes() - .then(res => { + .then((res) => { setSceneList(res?.data || []); }) .finally(() => setSceneLoading(false)); @@ -47,89 +47,112 @@ export default function NewPlan() { // 更新表单数据 const onChange = (data: any) => { - setFormData((prev) => ({ ...prev, ...data })) - } + setFormData((prev) => ({ ...prev, ...data })); + }; // 处理保存 const handleSave = async () => { try { // 这里应该是实际的API调用 - await new Promise((resolve) => setTimeout(resolve, 1000)) + await new Promise((resolve) => setTimeout(resolve, 1000)); toast({ title: "创建成功", description: "获客计划已创建", - }) - router("/plans") + }); + router("/plans"); } catch (error) { toast({ title: "创建失败", description: "创建计划失败,请重试", variant: "destructive", - }) + }); } - } + }; // 下一步 const handleNext = () => { if (currentStep === steps.length) { - handleSave() + handleSave(); } else { - setCurrentStep((prev) => prev + 1) + setCurrentStep((prev) => prev + 1); } - } + }; // 上一步 const handlePrev = () => { - setCurrentStep((prev) => Math.max(prev - 1, 1)) - } + setCurrentStep((prev) => Math.max(prev - 1, 1)); + }; // 渲染当前步骤内容 const renderStepContent = () => { switch (currentStep) { case 1: - return + return ( + + ); case 2: - return + return ( + + ); case 3: - return + return ( + + ); default: - return null + return null; } - } + }; return ( + +
+
+
+ +
+
+
- -
-
-
- - +
+ + {steps.map((step) => ( + + ))} +
- -
-
- -
- - {steps.map((step) => ( - - ))} - -
- - }> - -
- {renderStepContent()} -
+ + } + > +
{renderStepContent()}
- - ) + ); } diff --git a/nkebao/src/pages/scenarios/new/steps/BasicSettings.tsx b/nkebao/src/pages/scenarios/new/steps/BasicSettings.tsx index b62d7064..0fd2fdd9 100644 --- a/nkebao/src/pages/scenarios/new/steps/BasicSettings.tsx +++ b/nkebao/src/pages/scenarios/new/steps/BasicSettings.tsx @@ -5,13 +5,11 @@ import { Input, Tag, Grid, - Dialog, ImageViewer, - Table, Switch, } from "tdesign-mobile-react"; import EyeIcon from "@/components/icons/EyeIcon"; -import UploadImage from "@/components/UploadImage"; +import { uploadFile } from "@/api/utils"; const phoneCallTags = [ { id: "tag-1", name: "咨询", color: "bg-blue-100 text-blue-800" }, @@ -338,6 +336,11 @@ export function BasicSettings({ const today = new Date().toLocaleDateString("zh-CN").replace(/\//g, ""); onChange({ ...formData, planName: `海报${today}` }); } + + // 检查是否已经有上传的订单文件 + if (formData.orderFileUploaded) { + setOrderUploaded(true); + } }, [formData, onChange]); // 选中场景 @@ -500,6 +503,28 @@ export function BasicSettings({ window.URL.revokeObjectURL(url); }; + // 修改订单表格上传逻辑,使用 uploadFile 公共方法 + const [orderUploaded, setOrderUploaded] = useState(false); + + const handleOrderFileUpload = async ( + event: React.ChangeEvent + ) => { + const file = event.target.files?.[0]; + if (file) { + try { + await uploadFile(file); // 默认接口即可 + setOrderUploaded(true); + onChange({ ...formData, orderFileUploaded: true }); + // 可用 toast 或其它方式提示成功 + // alert('上传成功'); + } catch (err) { + // 可用 toast 或其它方式提示失败 + // alert('上传失败'); + } + event.target.value = ""; + } + }; + // 账号弹窗关闭时清理搜索等状态 const handleAccountDialogClose = () => { setIsAccountDialogOpen(false); @@ -816,41 +841,35 @@ export function BasicSettings({
支持 CSV、Excel 格式,上传后将文件保存到服务器
- {/* 已导入数据表格可复用原有Table渲染 */} - {importedTags.length > 0 && ( - - )} {/* 电话获客设置区块,仅在选择电话获客场景时显示 */} {formData.scenario === 5 && ( @@ -940,6 +959,21 @@ export function BasicSettings({ )} + +
+ 是否启用 + onChange({ ...formData, enabled: value })} + /> +
diff --git a/nkebao/src/pages/scenarios/new/steps/FriendRequestSettings.tsx b/nkebao/src/pages/scenarios/new/steps/FriendRequestSettings.tsx index cbfa37cf..6895c13b 100644 --- a/nkebao/src/pages/scenarios/new/steps/FriendRequestSettings.tsx +++ b/nkebao/src/pages/scenarios/new/steps/FriendRequestSettings.tsx @@ -1,23 +1,28 @@ -"use client" +"use client"; -import { useState, useEffect } from "react" -import { Card } from "@/components/ui/card" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import { HelpCircle, MessageSquare, AlertCircle } from "lucide-react" -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" -import { Alert, AlertDescription } from "@/components/ui/alert" -import { ChevronsUpDown } from "lucide-react" -import { Checkbox } from "@/components/ui/checkbox" +import { useState, useEffect } from "react"; +import { Card } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; + +import { HelpCircle, MessageSquare, AlertCircle } from "lucide-react"; + +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { ChevronsUpDown } from "lucide-react"; +import { Checkbox } from "@/components/ui/checkbox"; +import DeviceSelection from "@/components/DeviceSelection"; interface FriendRequestSettingsProps { - formData: any - onChange: (data: any) => void - onNext: () => void - onPrev: () => void + formData: any; + onChange: (data: any) => void; + onNext: () => void; + onPrev: () => void; } // 招呼语模板 @@ -27,45 +32,43 @@ const greetingTemplates = [ "你好,我是XX产品的客服请通过", "你好,感谢关注我们的产品", "你好,很高兴为您服务", -] +]; // 备注类型选项 const remarkTypes = [ { value: "phone", label: "手机号" }, { value: "nickname", label: "昵称" }, { value: "source", label: "来源" }, -] +]; -// 模拟设备数据 -const mockDevices = [ - { id: "1", name: "iPhone 13 Pro", status: "online" }, - { id: "2", name: "Xiaomi 12", status: "online" }, - { id: "3", name: "Huawei P40", status: "offline" }, - { id: "4", name: "OPPO Find X3", status: "online" }, - { id: "5", name: "Samsung S21", status: "online" }, -] - -export function FriendRequestSettings({ formData, onChange, onNext, onPrev }: FriendRequestSettingsProps) { - const [isTemplateDialogOpen, setIsTemplateDialogOpen] = useState(false) - const [hasWarnings, setHasWarnings] = useState(false) - const [isDeviceSelectorOpen, setIsDeviceSelectorOpen] = useState(false) - const [selectedDevices, setSelectedDevices] = useState(formData.selectedDevices || []) +export function FriendRequestSettings({ + formData, + onChange, + onNext, + onPrev, +}: FriendRequestSettingsProps) { + const [isTemplateDialogOpen, setIsTemplateDialogOpen] = useState(false); + const [hasWarnings, setHasWarnings] = useState(false); + const [selectedDevices, setSelectedDevices] = useState( + formData.selectedDevices || [] + ); + const [showRemarkTip, setShowRemarkTip] = useState(false); // 获取场景标题 const getScenarioTitle = () => { switch (formData.scenario) { case "douyin": - return "抖音直播" + return "抖音直播"; case "xiaohongshu": - return "小红书" + return "小红书"; case "weixinqun": - return "微信群" + return "微信群"; case "gongzhonghao": - return "公众号" + return "公众号"; default: - return formData.planName || "获客计划" + return formData.planName || "获客计划"; } - } + }; // 使用useEffect设置默认值 useEffect(() => { @@ -76,172 +79,158 @@ export function FriendRequestSettings({ formData, onChange, onNext, onPrev }: Fr remarkType: "phone", // 默认选择手机号 remarkFormat: `手机号+${getScenarioTitle()}`, // 默认备注格式 addFriendInterval: 1, - }) + }); } - }, [formData, formData.greeting, onChange]) + }, [formData, formData.greeting, onChange]); // 检查是否有未完成的必填项 useEffect(() => { - const hasIncompleteFields = !formData.greeting?.trim() - setHasWarnings(hasIncompleteFields) - }, [formData]) + const hasIncompleteFields = !formData.greeting?.trim(); + setHasWarnings(hasIncompleteFields); + }, [formData]); const handleTemplateSelect = (template: string) => { - onChange({ ...formData, greeting: template }) - setIsTemplateDialogOpen(false) - } + onChange({ ...formData, greeting: template }); + setIsTemplateDialogOpen(false); + }; const handleNext = () => { // 即使有警告也允许进入下一步,但会显示提示 - onNext() - } - - const toggleDeviceSelection = (device: any) => { - const isSelected = selectedDevices.some((d) => d.id === device.id) - let newSelectedDevices - - if (isSelected) { - newSelectedDevices = selectedDevices.filter((d) => d.id !== device.id) - } else { - newSelectedDevices = [...selectedDevices, device] - } - - setSelectedDevices(newSelectedDevices) - onChange({ ...formData, selectedDevices: newSelectedDevices }) - } + onNext(); + }; return ( - + <>
- -
- + 选择设备 +
+ d.id)} + onSelect={(deviceIds) => { + const newSelectedDevices = deviceIds.map((id) => ({ + id, + name: `设备 ${id}`, + status: "online", + })); + setSelectedDevices(newSelectedDevices); + onChange({ ...formData, selectedDevices: newSelectedDevices }); + }} + placeholder="选择设备" + /> +
+
- {isDeviceSelectorOpen && ( -
-
- -
- {mockDevices.map((device) => ( -
toggleDeviceSelection(device)} - > -
- d.id === device.id)} - onCheckedChange={() => toggleDeviceSelection(device)} - /> - {device.name} -
- - {device.status === "online" ? "在线" : "离线"} - -
- ))} -
+
+
+ 好友备注 + setShowRemarkTip(true)} + onMouseLeave={() => setShowRemarkTip(false)} + onClick={() => setShowRemarkTip((v) => !v)} + > + ? + + {showRemarkTip && ( +
+
设置添加好友时的备注格式
+
备注格式预览:
+
+ {formData.remarkType === "phone" && + `138****1234+${getScenarioTitle()}`} + {formData.remarkType === "nickname" && + `小红书用户2851+${getScenarioTitle()}`} + {formData.remarkType === "source" && + `抖音直播+${getScenarioTitle()}`}
)}
-
- -
-
- - - - - - - -

设置添加好友时的备注格式

-

备注格式预览:

-

{formData.remarkType === "phone" && `138****1234+${getScenarioTitle()}`}

-

{formData.remarkType === "nickname" && `小红书用户2851+${getScenarioTitle()}`}

-

{formData.remarkType === "source" && `抖音直播+${getScenarioTitle()}`}

-
-
-
-
- + {remarkTypes.map((type) => ( + + ))} +
- -
onChange({ ...formData, greeting: e.target.value })} + onChange={(e) => + onChange({ ...formData, greeting: e.target.value }) + } placeholder="请输入招呼语" className="mt-2" />
- + 添加间隔
onChange({ ...formData, addFriendInterval: Number(e.target.value) })} - className="w-32" + onChange={(e) => + onChange({ + ...formData, + addFriendInterval: Number(e.target.value), + }) + } /> - 分钟 +
分钟
- + 允许加人的时间段
onChange({ ...formData, addFriendTimeStart: e.target.value })} + onChange={(e) => + onChange({ ...formData, addFriendTimeStart: e.target.value }) + } className="w-32" /> onChange({ ...formData, addFriendTimeEnd: e.target.value })} + onChange={(e) => + onChange({ ...formData, addFriendTimeEnd: e.target.value }) + } className="w-32" />
{hasWarnings && ( - + - 您有未完成的设置项,建议完善后再进入下一步。 + + 您有未完成的设置项,建议完善后再进入下一步。 + )} @@ -253,8 +242,11 @@ export function FriendRequestSettings({ formData, onChange, onNext, onPrev }: Fr
- - + + 招呼语模板 @@ -272,6 +264,6 @@ export function FriendRequestSettings({ formData, onChange, onNext, onPrev }: Fr
- - ) + + ); } diff --git a/nkebao/src/pages/scenarios/new/steps/MessageSettings.tsx b/nkebao/src/pages/scenarios/new/steps/MessageSettings.tsx index 3c8fc4a9..99d00e8b 100644 --- a/nkebao/src/pages/scenarios/new/steps/MessageSettings.tsx +++ b/nkebao/src/pages/scenarios/new/steps/MessageSettings.tsx @@ -1,9 +1,7 @@ -import { useState } from "react" -import { Card } from "@/components/ui/card" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import { Textarea } from "@/components/ui/textarea" +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; import { MessageSquare, ImageIcon, @@ -16,40 +14,46 @@ import { X, Upload, Clock, -} from "lucide-react" -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog" -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" -import { toast } from "@/components/ui/use-toast" +} from "lucide-react"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from "@/components/ui/dialog"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { toast } from "@/components/ui/use-toast"; interface MessageContent { - id: string - type: "text" | "image" | "video" | "file" | "miniprogram" | "link" | "group" - content: string - sendInterval?: number - intervalUnit?: "seconds" | "minutes" + id: string; + type: "text" | "image" | "video" | "file" | "miniprogram" | "link" | "group"; + content: string; + sendInterval?: number; + intervalUnit?: "seconds" | "minutes"; scheduledTime?: { - hour: number - minute: number - second: number - } - title?: string - description?: string - address?: string - coverImage?: string - groupId?: string - linkUrl?: string + hour: number; + minute: number; + second: number; + }; + title?: string; + description?: string; + address?: string; + coverImage?: string; + groupId?: string; + linkUrl?: string; } interface DayPlan { - day: number - messages: MessageContent[] + day: number; + messages: MessageContent[]; } interface MessageSettingsProps { - formData: any - onChange: (data: any) => void - onNext: () => void - onPrev: () => void + formData: any; + onChange: (data: any) => void; + onNext: () => void; + onPrev: () => void; } // 消息类型配置 @@ -61,16 +65,21 @@ const messageTypes = [ { id: "miniprogram", icon: Window, label: "小程序" }, { id: "link", icon: Link2, label: "链接" }, { id: "group", icon: Users, label: "邀请入群" }, -] +]; // 模拟群组数据 const mockGroups = [ { id: "1", name: "产品交流群1", memberCount: 156 }, { id: "2", name: "产品交流群2", memberCount: 234 }, { id: "3", name: "产品交流群3", memberCount: 89 }, -] +]; -export function MessageSettings({ formData, onChange, onNext, onPrev }: MessageSettingsProps) { +export function MessageSettings({ + formData, + onChange, + onNext, + onPrev, +}: MessageSettingsProps) { const [dayPlans, setDayPlans] = useState([ { day: 0, @@ -84,67 +93,71 @@ export function MessageSettings({ formData, onChange, onNext, onPrev }: MessageS }, ], }, - ]) - const [isAddDayPlanOpen, setIsAddDayPlanOpen] = useState(false) - const [isGroupSelectOpen, setIsGroupSelectOpen] = useState(false) - const [selectedGroupId, setSelectedGroupId] = useState("") + ]); + const [isAddDayPlanOpen, setIsAddDayPlanOpen] = useState(false); + const [isGroupSelectOpen, setIsGroupSelectOpen] = useState(false); + const [selectedGroupId, setSelectedGroupId] = useState(""); // 添加新消息 const handleAddMessage = (dayIndex: number, type = "text") => { - const updatedPlans = [...dayPlans] + const updatedPlans = [...dayPlans]; const newMessage: MessageContent = { id: Date.now().toString(), type: type as MessageContent["type"], content: "", - } + }; if (dayPlans[dayIndex].day === 0) { // 即时消息使用间隔设置 - newMessage.sendInterval = 5 - newMessage.intervalUnit = "seconds" // 默认改为秒 + newMessage.sendInterval = 5; + newMessage.intervalUnit = "seconds"; // 默认改为秒 } else { // 非即时消息使用具体时间设置 newMessage.scheduledTime = { hour: 9, minute: 0, second: 0, - } + }; } - updatedPlans[dayIndex].messages.push(newMessage) - setDayPlans(updatedPlans) - onChange({ ...formData, messagePlans: updatedPlans }) - } + updatedPlans[dayIndex].messages.push(newMessage); + setDayPlans(updatedPlans); + onChange({ ...formData, messagePlans: updatedPlans }); + }; // 更新消息内容 - const handleUpdateMessage = (dayIndex: number, messageIndex: number, updates: Partial) => { - const updatedPlans = [...dayPlans] + const handleUpdateMessage = ( + dayIndex: number, + messageIndex: number, + updates: Partial + ) => { + const updatedPlans = [...dayPlans]; updatedPlans[dayIndex].messages[messageIndex] = { ...updatedPlans[dayIndex].messages[messageIndex], ...updates, - } - setDayPlans(updatedPlans) - onChange({ ...formData, messagePlans: updatedPlans }) - } + }; + setDayPlans(updatedPlans); + onChange({ ...formData, messagePlans: updatedPlans }); + }; // 删除消息 const handleRemoveMessage = (dayIndex: number, messageIndex: number) => { - const updatedPlans = [...dayPlans] - updatedPlans[dayIndex].messages.splice(messageIndex, 1) - setDayPlans(updatedPlans) - onChange({ ...formData, messagePlans: updatedPlans }) - } + const updatedPlans = [...dayPlans]; + updatedPlans[dayIndex].messages.splice(messageIndex, 1); + setDayPlans(updatedPlans); + onChange({ ...formData, messagePlans: updatedPlans }); + }; // 切换时间单位 const toggleIntervalUnit = (dayIndex: number, messageIndex: number) => { - const message = dayPlans[dayIndex].messages[messageIndex] - const newUnit = message.intervalUnit === "minutes" ? "seconds" : "minutes" - handleUpdateMessage(dayIndex, messageIndex, { intervalUnit: newUnit }) - } + const message = dayPlans[dayIndex].messages[messageIndex]; + const newUnit = message.intervalUnit === "minutes" ? "seconds" : "minutes"; + handleUpdateMessage(dayIndex, messageIndex, { intervalUnit: newUnit }); + }; // 添加新的天数计划 const handleAddDayPlan = () => { - const newDay = dayPlans.length + const newDay = dayPlans.length; setDayPlans([ ...dayPlans, { @@ -162,47 +175,63 @@ export function MessageSettings({ formData, onChange, onNext, onPrev }: MessageS }, ], }, - ]) - setIsAddDayPlanOpen(false) + ]); + setIsAddDayPlanOpen(false); toast({ title: "添加成功", description: `已添加第${newDay}天的消息计划`, - }) - } + }); + }; // 选择群组 const handleSelectGroup = (groupId: string) => { - setSelectedGroupId(groupId) - setIsGroupSelectOpen(false) + setSelectedGroupId(groupId); + setIsGroupSelectOpen(false); toast({ title: "选择成功", - description: `已选择群组:${mockGroups.find((g) => g.id === groupId)?.name}`, - }) - } + description: `已选择群组:${ + mockGroups.find((g) => g.id === groupId)?.name + }`, + }); + }; // 处理文件上传 - const handleFileUpload = (dayIndex: number, messageIndex: number, type: "image" | "video" | "file") => { + const handleFileUpload = ( + dayIndex: number, + messageIndex: number, + type: "image" | "video" | "file" + ) => { // 模拟文件上传 toast({ title: "上传成功", - description: `${type === "image" ? "图片" : type === "video" ? "视频" : "文件"}上传成功`, - }) - } + description: `${ + type === "image" ? "图片" : type === "video" ? "视频" : "文件" + }上传成功`, + }); + }; return ( - + <>

消息设置

-
- + {dayPlans.map((plan) => ( - + {plan.day === 0 ? "即时消息" : `第${plan.day}天`} ))} @@ -212,33 +241,44 @@ export function MessageSettings({ formData, onChange, onNext, onPrev }: MessageS
{plan.messages.map((message, messageIndex) => ( -
+
{plan.day === 0 ? ( <> - +
间隔
- handleUpdateMessage(dayIndex, messageIndex, { sendInterval: Number(e.target.value) }) + handleUpdateMessage(dayIndex, messageIndex, { + sendInterval: Number(e.target.value), + }) } - className="w-20" /> ) : ( <> - +
发送时间
handleUpdateMessage(dayIndex, messageIndex, { scheduledTime: { - ...(message.scheduledTime || { hour: 0, minute: 0, second: 0 }), + ...(message.scheduledTime || { + hour: 0, + minute: 0, + second: 0, + }), hour: Number(e.target.value), }, }) @@ -260,11 +304,17 @@ export function MessageSettings({ formData, onChange, onNext, onPrev }: MessageS type="number" min={0} max={59} - value={String(message.scheduledTime?.minute || 0)} + value={String( + message.scheduledTime?.minute || 0 + )} onChange={(e) => handleUpdateMessage(dayIndex, messageIndex, { scheduledTime: { - ...(message.scheduledTime || { hour: 0, minute: 0, second: 0 }), + ...(message.scheduledTime || { + hour: 0, + minute: 0, + second: 0, + }), minute: Number(e.target.value), }, }) @@ -276,11 +326,17 @@ export function MessageSettings({ formData, onChange, onNext, onPrev }: MessageS type="number" min={0} max={59} - value={String(message.scheduledTime?.second || 0)} + value={String( + message.scheduledTime?.second || 0 + )} onChange={(e) => handleUpdateMessage(dayIndex, messageIndex, { scheduledTime: { - ...(message.scheduledTime || { hour: 0, minute: 0, second: 0 }), + ...(message.scheduledTime || { + hour: 0, + minute: 0, + second: 0, + }), second: Number(e.target.value), }, }) @@ -291,7 +347,13 @@ export function MessageSettings({ formData, onChange, onNext, onPrev }: MessageS )}
-
@@ -300,9 +362,15 @@ export function MessageSettings({ formData, onChange, onNext, onPrev }: MessageS {messageTypes.map((type) => (