refactor: overhaul UI for streamlined user experience

Redesign navigation, home overview, user portrait, and valuation pages
with improved functionality and responsive design.

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
v0
2025-07-18 13:47:12 +00:00
parent 440b310c6f
commit 2408d50cb0
316 changed files with 55785 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
export default function Loading() {
return null
}

265
app/plans/new/page.tsx Normal file
View File

@@ -0,0 +1,265 @@
"use client"
import { useState, useEffect } from "react"
import { ChevronLeft } from "lucide-react"
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
import { BasicSettings } from "./steps/BasicSettings"
import { FriendRequestSettings } from "./steps/FriendRequestSettings"
import { MessageSettings } from "./steps/MessageSettings"
import { TagSettings } from "./steps/TagSettings"
import { useRouter, useSearchParams } from "next/navigation"
import { toast } from "@/components/ui/use-toast"
const steps = [
{ id: 1, title: "步骤一", subtitle: "基础设置" },
{ id: 2, title: "步骤二", subtitle: "好友申请设置" },
{ id: 3, title: "步骤三", subtitle: "消息设置" },
{ id: 4, title: "步骤四", subtitle: "流量标签设置" },
]
// 场景分类规则
const scenarioRules = {
LIVE: ["直播", "直播间", "主播", "抖音"],
COMMENT: ["评论", "互动", "回复", "小红书"],
GROUP: ["群", "社群", "群聊", "微信群"],
ARTICLE: ["文章", "笔记", "内容", "公众号"],
}
// 根据计划名称和标签自动判断场景
const determineScenario = (planName: string, tags: any[]) => {
// 优先使用标签进行分类
if (tags && tags.length > 0) {
const firstTag = tags[0]
if (firstTag.name?.includes("直播") || firstTag.name?.includes("抖音")) return "douyin"
if (firstTag.name?.includes("评论") || firstTag.name?.includes("小红书")) return "xiaohongshu"
if (firstTag.name?.includes("群") || firstTag.name?.includes("微信")) return "weixinqun"
if (firstTag.name?.includes("文章") || firstTag.name?.includes("公众号")) return "gongzhonghao"
}
// 如果没有标签,使用计划名称进行分类
const planNameLower = planName.toLowerCase()
if (planNameLower.includes("直播") || planNameLower.includes("抖音")) return "douyin"
if (planNameLower.includes("评论") || planNameLower.includes("小红书")) return "xiaohongshu"
if (planNameLower.includes("群") || planNameLower.includes("微信")) return "weixinqun"
if (planNameLower.includes("文章") || planNameLower.includes("公众号")) return "gongzhonghao"
return "other"
}
export default function NewAcquisitionPlan() {
const router = useRouter()
const searchParams = useSearchParams()
const type = searchParams.get("type")
const source = searchParams.get("source")
const [currentStep, setCurrentStep] = useState(1)
const [formData, setFormData] = useState({
planName: "",
scenario: type === "order" ? "order" : "",
accounts: [],
materials: [],
enabled: true,
remarkType: "phone",
remarkKeyword: "",
greeting: "",
addFriendTimeStart: "09:00",
addFriendTimeEnd: "18:00",
addFriendInterval: 1,
maxDailyFriends: 20,
messageInterval: 1,
messageContent: "",
tags: [],
selectedDevices: [],
messagePlans: [],
importedTags: [],
sourceWechatId: source || "",
})
// 如果是从微信号好友转移过来,自动设置计划名称
useEffect(() => {
if (type === "order" && source) {
const today = new Date().toLocaleDateString("zh-CN").replace(/\//g, "")
setFormData((prev) => ({
...prev,
planName: `${source}好友转移${today}`,
scenario: "order",
}))
// 模拟加载好友数据
setTimeout(() => {
toast({
title: "好友数据加载成功",
description: `已从微信号 ${source} 导入好友数据`,
})
}, 1000)
}
}, [type, source])
const handleSave = () => {
// 根据标签和计划名称自动判断场景
const scenario = formData.scenario || determineScenario(formData.planName, formData.tags)
console.log("计划已创建:", { ...formData, scenario })
toast({
title: "创建成功",
description: "获客计划已创建完成",
})
// 跳转到首页
router.push("/")
}
const handlePrev = () => {
setCurrentStep((prevStep) => Math.max(prevStep - 1, 1))
}
const handleNext = () => {
if (isStepValid()) {
if (currentStep === steps.length) {
handleSave()
} else {
setCurrentStep((prevStep) => Math.min(prevStep + 1, steps.length))
}
}
}
const isStepValid = () => {
switch (currentStep) {
case 1:
if (!formData.planName.trim()) {
toast({
title: "请完善信息",
description: "请填写计划名称",
variant: "destructive",
})
return false
}
return true
case 2:
// 如果是订单导入场景,跳过好友申请设置验证
if (formData.scenario === "order") {
return true
}
if (!formData.greeting?.trim() || formData.selectedDevices?.length === 0) {
toast({
title: "请完善信息",
description: "请填写好友申请信息并选择设备",
variant: "destructive",
})
return false
}
return true
case 3:
// 如果是订单导入场景,跳过消息设置验证
if (formData.scenario === "order") {
return true
}
if (formData.messagePlans?.length === 0) {
toast({
title: "请完善信息",
description: "请设置至少一条消息",
variant: "destructive",
})
return false
}
return true
case 4:
return true
default:
return true
}
}
const renderStepContent = () => {
switch (currentStep) {
case 1:
return <BasicSettings formData={formData} onChange={setFormData} onNext={handleNext} />
case 2:
return (
<FriendRequestSettings formData={formData} onChange={setFormData} onNext={handleNext} onPrev={handlePrev} />
)
case 3:
return <MessageSettings formData={formData} onChange={setFormData} onNext={handleNext} onPrev={handlePrev} />
case 4:
return <TagSettings formData={formData} onComplete={handleSave} onPrev={handlePrev} onChange={setFormData} />
default:
return null
}
}
// 如果是订单导入场景,直接跳到标签设置步骤
useEffect(() => {
// 只有在订单场景下才自动开启步骤1而不是直接跳到步骤4
if (formData.scenario === "order" && currentStep === 1 && formData.planName) {
// 保持在步骤1不再自动跳转到步骤4
// 之前的逻辑是直接跳到步骤4setCurrentStep(4)
}
}, [formData.scenario, currentStep, formData.planName])
return (
<div className="min-h-screen bg-gray-50">
<div className="max-w-[390px] mx-auto bg-white min-h-screen flex flex-col">
<header className="sticky top-0 z-10 bg-white border-b">
<div className="flex items-center h-14 px-4">
<Button variant="ghost" size="icon" onClick={() => router.push("/")}>
<ChevronLeft className="h-5 w-5" />
</Button>
<h1 className="ml-2 text-lg font-medium">{formData.sourceWechatId ? "好友转移" : "新建获客计划"}</h1>
</div>
</header>
<div className="flex-1 flex flex-col">
<div className="px-4 py-6">
<div className="relative flex justify-between">
{steps.map((step, index) => (
<div
key={step.id}
className={cn(
"flex flex-col items-center relative z-10",
currentStep >= step.id ? "text-blue-600" : "text-gray-400",
)}
>
<div
className={cn(
"w-8 h-8 rounded-full flex items-center justify-center border-2",
currentStep >= step.id
? "border-blue-600 bg-blue-600 text-white"
: "border-gray-300 bg-white text-gray-400",
)}
>
{step.id}
</div>
<div className="text-xs mt-1">{step.title}</div>
<div className="text-xs mt-0.5 font-medium">{step.subtitle}</div>
{index < steps.length - 1 && (
<div
className={cn(
"absolute top-4 left-[32px] w-[calc(100%-32px)] h-[2px]",
currentStep > step.id ? "bg-blue-600" : "bg-gray-200",
)}
/>
)}
</div>
))}
</div>
</div>
<div className="flex-1 px-4 pb-20">{renderStepContent()}</div>
<div className="sticky bottom-0 left-0 right-0 bg-white border-t p-4">
<div className="flex justify-between max-w-[390px] mx-auto">
{currentStep > 1 && (
<Button variant="outline" onClick={handlePrev}>
</Button>
)}
<Button className={cn("min-w-[120px]", currentStep === 1 ? "w-full" : "ml-auto")} onClick={handleNext}>
{currentStep === steps.length ? "完成" : "下一步"}
</Button>
</div>
</div>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,548 @@
"use client"
import type React from "react"
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 { Switch } from "@/components/ui/switch"
import { QrCode, X, ChevronDown, Plus, Maximize2, Upload, Download } from "lucide-react"
import { TooltipProvider } from "@/components/ui/tooltip"
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/components/ui/table"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"
const scenarios = [
{ id: "haibao", name: "海报", type: "material" },
{ id: "order", name: "订单", type: "api" },
{ id: "douyin", name: "抖音", type: "social" },
{ id: "xiaohongshu", name: "小红书", type: "social" },
{ id: "gongzhonghao", name: "公众号", type: "social" },
{ id: "payment", name: "支付码", type: "material" },
{ id: "weixinqun", name: "微信群", type: "social" },
{ id: "api", name: "API", type: "api" },
]
interface Account {
id: string
nickname: string
avatar: string
}
interface Material {
id: string
name: string
type: "poster" | "payment"
preview: string
}
interface BasicSettingsProps {
formData: any
onChange: (data: any) => void
onNext?: () => void
}
const posterTemplates = [
{
id: "poster-1",
name: "点击领取",
preview:
"https://hebbkx1anhila5yf.public.blob.vercel-storage.com/%E7%82%B9%E5%87%BB%E9%A2%86%E5%8F%961-tipd1HI7da6qooY5NkhxQnXBnT5LGU.gif",
},
{
id: "poster-2",
name: "点击合作",
preview:
"https://hebbkx1anhila5yf.public.blob.vercel-storage.com/%E7%82%B9%E5%87%BB%E5%90%88%E4%BD%9C-LPlMdgxtvhqCSr4IM1bZFEFDBF3ztI.gif",
},
{
id: "poster-3",
name: "点击咨询",
preview:
"https://hebbkx1anhila5yf.public.blob.vercel-storage.com/%E7%82%B9%E5%87%BB%E5%92%A8%E8%AF%A2-FTiyAMAPop2g9LvjLOLDz0VwPg3KVu.gif",
},
{
id: "poster-4",
name: "点击签到",
preview:
"https://hebbkx1anhila5yf.public.blob.vercel-storage.com/%E7%82%B9%E5%87%BB%E7%AD%BE%E5%88%B0-94TZIkjLldb4P2jTVlI6MkSDg0NbXi.gif",
},
{
id: "poster-5",
name: "点击了解",
preview:
"https://hebbkx1anhila5yf.public.blob.vercel-storage.com/%E7%82%B9%E5%87%BB%E4%BA%86%E8%A7%A3-6GCl7mQVdO4WIiykJyweSubLsTwj71.gif",
},
{
id: "poster-6",
name: "点击报名",
preview:
"https://hebbkx1anhila5yf.public.blob.vercel-storage.com/%E7%82%B9%E5%87%BB%E6%8A%A5%E5%90%8D-Mj0nnva0BiASeDAIhNNaRRAbjPgjEj.gif",
},
]
const generateRandomAccounts = (count: number): Account[] => {
return Array.from({ length: count }, (_, index) => ({
id: `account-${index + 1}`,
nickname: `账号-${Math.random().toString(36).substring(2, 7)}`,
avatar: `/placeholder.svg?height=40&width=40&text=${index + 1}`,
}))
}
const generatePosterMaterials = (): Material[] => {
return posterTemplates.map((template) => ({
id: template.id,
name: template.name,
type: "poster",
preview: template.preview,
}))
}
export function BasicSettings({ formData, onChange, onNext }: BasicSettingsProps) {
const [isAccountDialogOpen, setIsAccountDialogOpen] = useState(false)
const [isMaterialDialogOpen, setIsMaterialDialogOpen] = useState(false)
const [isQRCodeOpen, setIsQRCodeOpen] = useState(false)
const [isPreviewOpen, setIsPreviewOpen] = useState(false)
const [previewImage, setPreviewImage] = useState("")
const [accounts] = useState<Account[]>(generateRandomAccounts(50))
const [materials] = useState<Material[]>(generatePosterMaterials())
const [selectedAccounts, setSelectedAccounts] = useState<Account[]>(
formData.accounts?.length > 0 ? formData.accounts : [],
)
const [selectedMaterials, setSelectedMaterials] = useState<Material[]>(
formData.materials?.length > 0 ? formData.materials : [],
)
const [showAllScenarios, setShowAllScenarios] = useState(false)
const [isImportDialogOpen, setIsImportDialogOpen] = useState(false)
const [importedTags, setImportedTags] = useState<
Array<{
phone: string
wechat: string
source?: string
orderAmount?: number
orderDate?: string
}>
>(formData.importedTags || [])
useEffect(() => {
if (!formData.planName) {
if (formData.materials?.length > 0) {
const today = new Date().toLocaleDateString("zh-CN").replace(/\//g, "")
onChange({ ...formData, planName: `海报${today}` })
} else {
onChange({ ...formData, planName: "场景" })
}
}
}, [formData, onChange])
const handleScenarioSelect = (scenarioId: string) => {
onChange({ ...formData, scenario: scenarioId })
}
const handleAccountSelect = (account: Account) => {
const updatedAccounts = [...selectedAccounts, account]
setSelectedAccounts(updatedAccounts)
onChange({ ...formData, accounts: updatedAccounts })
}
const handleMaterialSelect = (material: Material) => {
const updatedMaterials = [material]
setSelectedMaterials(updatedMaterials)
onChange({ ...formData, materials: updatedMaterials })
setIsMaterialDialogOpen(false)
// 更新计划名称
const today = new Date().toLocaleDateString("zh-CN").replace(/\//g, "")
onChange({ ...formData, planName: `海报${today}`, materials: updatedMaterials })
}
const handleRemoveAccount = (accountId: string) => {
const updatedAccounts = selectedAccounts.filter((a) => a.id !== accountId)
setSelectedAccounts(updatedAccounts)
onChange({ ...formData, accounts: updatedAccounts })
}
const handleRemoveMaterial = (materialId: string) => {
const updatedMaterials = selectedMaterials.filter((m) => m.id !== materialId)
setSelectedMaterials(updatedMaterials)
onChange({ ...formData, materials: updatedMaterials })
}
const handlePreviewImage = (imageUrl: string) => {
setPreviewImage(imageUrl)
setIsPreviewOpen(true)
}
const displayedScenarios = showAllScenarios ? scenarios : scenarios.slice(0, 3)
const handleFileImport = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]
if (file) {
const reader = new FileReader()
reader.onload = (e) => {
try {
const content = e.target?.result as string
const rows = content.split("\n").filter((row) => row.trim())
const tags = rows.slice(1).map((row) => {
const [phone, wechat, source, orderAmount, orderDate] = row.split(",")
return {
phone: phone.trim(),
wechat: wechat.trim(),
source: source?.trim(),
orderAmount: orderAmount ? Number(orderAmount) : undefined,
orderDate: orderDate?.trim(),
}
})
setImportedTags(tags)
onChange({ ...formData, importedTags: tags })
} catch (error) {
console.error("导入失败:", error)
}
}
reader.readAsText(file)
}
}
const handleDownloadTemplate = () => {
const template = "电话号码,微信号,来源,订单金额,下单日期\n13800138000,wxid_123,抖音,99.00,2024-03-03"
const blob = new Blob([template], { type: "text/csv" })
const url = window.URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = "订单导入模板.csv"
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
window.URL.revokeObjectURL(url)
}
return (
<TooltipProvider>
<Card className="p-6">
<div className="space-y-6">
<div>
<Label className="text-base mb-4 block"></Label>
<div className="grid grid-cols-3 gap-2">
{displayedScenarios.map((scenario) => (
<button
key={scenario.id}
className={`p-2 rounded-lg text-center transition-all ${
formData.scenario === scenario.id
? "bg-blue-100 text-blue-600 font-medium"
: "bg-gray-50 text-gray-600 hover:bg-gray-100"
}`}
onClick={() => handleScenarioSelect(scenario.id)}
>
{scenario.name}
</button>
))}
</div>
{!showAllScenarios && (
<Button variant="ghost" className="mt-2 w-full text-blue-600" onClick={() => setShowAllScenarios(true)}>
<ChevronDown className="ml-2 h-4 w-4" />
</Button>
)}
</div>
<div>
<Label htmlFor="planName"></Label>
<Input
id="planName"
value={formData.planName}
onChange={(e) => onChange({ ...formData, planName: e.target.value })}
placeholder="请输入计划名称"
className="mt-2"
/>
</div>
{formData.scenario && (
<>
{scenarios.find((s) => s.id === formData.scenario)?.type === "social" && (
<div>
<Label></Label>
<div className="flex gap-2 mt-2">
<Button
variant="outline"
className="flex-1 justify-start"
onClick={() => setIsAccountDialogOpen(true)}
>
{selectedAccounts.length > 0 ? `已选择 ${selectedAccounts.length} 个账号` : "选择账号"}
</Button>
<Button variant="outline" size="icon" onClick={() => setIsQRCodeOpen(true)}>
<QrCode className="h-4 w-4" />
</Button>
</div>
{selectedAccounts.length > 0 && (
<div className="mt-2 flex flex-wrap gap-2">
{selectedAccounts.map((account) => (
<div key={account.id} className="flex items-center bg-gray-100 rounded-full px-3 py-1">
<img
src={account.avatar || "/placeholder.svg"}
alt={account.nickname}
className="w-4 h-4 rounded-full mr-2"
/>
<span className="text-sm">{account.nickname}</span>
<Button
variant="ghost"
size="sm"
className="ml-2 p-0"
onClick={() => handleRemoveAccount(account.id)}
>
<X className="h-4 w-4" />
</Button>
</div>
))}
</div>
)}
</div>
)}
{scenarios.find((s) => s.id === formData.scenario)?.type === "material" && (
<div>
<div className="flex items-center justify-between mb-4">
<Label></Label>
<Button variant="outline" onClick={() => setIsMaterialDialogOpen(true)}>
<Plus className="h-4 w-4" />
</Button>
</div>
{/* 海报展示区域 */}
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-4">
{materials.map((material) => (
<div
key={material.id}
className={`relative cursor-pointer rounded-lg overflow-hidden group ${
selectedMaterials.find((m) => m.id === material.id)
? "ring-2 ring-blue-600"
: "hover:ring-2 hover:ring-blue-600"
}`}
onClick={() => handleMaterialSelect(material)}
>
<img
src={material.preview || "/placeholder.svg"}
alt={material.name}
className="w-full aspect-[9/16] object-cover"
/>
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-all">
<Button
variant="ghost"
size="icon"
className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity"
onClick={(e) => {
e.stopPropagation()
handlePreviewImage(material.preview)
}}
>
<Maximize2 className="h-4 w-4 text-white" />
</Button>
</div>
<div className="absolute bottom-0 left-0 right-0 p-2 bg-black/50 text-white">
<div className="text-sm truncate">{material.name}</div>
</div>
</div>
))}
</div>
{selectedMaterials.length > 0 && (
<div className="mt-4">
<Label></Label>
<div className="mt-2">
<div className="relative w-full max-w-[200px]">
<img
src={selectedMaterials[0].preview || "/placeholder.svg"}
alt={selectedMaterials[0].name}
className="w-full aspect-[9/16] object-cover rounded-lg cursor-pointer"
onClick={() => handlePreviewImage(selectedMaterials[0].preview)}
/>
<Button
variant="secondary"
size="sm"
className="absolute top-2 right-2"
onClick={() => handleRemoveMaterial(selectedMaterials[0].id)}
>
<X className="h-4 w-4" />
</Button>
</div>
</div>
</div>
)}
</div>
)}
{scenarios.find((s) => s.id === formData.scenario)?.id === "order" && (
<div>
<div className="flex items-center justify-between mb-4">
<Label></Label>
<div className="flex gap-2">
<Button variant="outline" onClick={handleDownloadTemplate}>
<Download className="h-4 w-4 mr-2" />
</Button>
<Button onClick={() => setIsImportDialogOpen(true)}>
<Upload className="h-4 w-4 mr-2" />
</Button>
</div>
</div>
{importedTags.length > 0 && (
<div className="mt-4">
<h4 className="text-sm font-medium mb-2"> {importedTags.length} </h4>
<div className="max-h-[300px] overflow-auto border rounded-md">
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{importedTags.slice(0, 5).map((tag, index) => (
<TableRow key={index}>
<TableCell>{tag.phone}</TableCell>
<TableCell>{tag.wechat}</TableCell>
<TableCell>{tag.source}</TableCell>
<TableCell>{tag.orderAmount}</TableCell>
</TableRow>
))}
{importedTags.length > 5 && (
<TableRow>
<TableCell colSpan={4} className="text-center text-gray-500">
{importedTags.length - 5}
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
</div>
)}
</div>
)}
</>
)}
<div className="flex items-center justify-between">
<Label htmlFor="enabled"></Label>
<Switch
id="enabled"
checked={formData.enabled}
onCheckedChange={(checked) => onChange({ ...formData, enabled: checked })}
/>
</div>
<Button className="w-full h-12 text-base" onClick={onNext}>
</Button>
</div>
{/* 账号选择对话框 */}
<Dialog open={isAccountDialogOpen} onOpenChange={setIsAccountDialogOpen}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="mt-4 max-h-[400px] overflow-y-auto">
<div className="space-y-2">
{accounts.map((account) => (
<div
key={account.id}
className="flex items-center space-x-3 p-3 hover:bg-gray-100 rounded-lg cursor-pointer"
onClick={() => handleAccountSelect(account)}
>
<img src={account.avatar || "/placeholder.svg"} alt="" className="w-10 h-10 rounded-full" />
<span className="flex-1">{account.nickname}</span>
{selectedAccounts.find((a) => a.id === account.id) && (
<div className="w-4 h-4 rounded-full bg-blue-600" />
)}
</div>
))}
</div>
</div>
</DialogContent>
</Dialog>
{/* 二维码对话框 */}
<Dialog open={isQRCodeOpen} onOpenChange={setIsQRCodeOpen}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="flex flex-col items-center p-6">
<div className="w-64 h-64 bg-gray-100 rounded-lg flex items-center justify-center">
<img src="/placeholder.svg?height=256&width=256" alt="二维码" className="w-full h-full" />
</div>
<p className="mt-4 text-sm text-gray-600">APP扫码</p>
</div>
</DialogContent>
</Dialog>
{/* 图片预览对话框 */}
<Dialog open={isPreviewOpen} onOpenChange={setIsPreviewOpen}>
<DialogContent className="sm:max-w-3xl">
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="flex justify-center items-center p-4">
<img src={previewImage || "/placeholder.svg"} alt="预览" className="max-h-[80vh] object-contain" />
</div>
</DialogContent>
</Dialog>
{/* 订单导入对话框 */}
<Dialog open={isImportDialogOpen} onOpenChange={setIsImportDialogOpen}>
<DialogContent className="max-w-3xl">
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div className="flex items-center gap-4">
<Input type="file" accept=".csv" onChange={handleFileImport} className="flex-1" />
</div>
<div className="max-h-[400px] overflow-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{importedTags.map((tag, index) => (
<TableRow key={index}>
<TableCell>{tag.phone}</TableCell>
<TableCell>{tag.wechat}</TableCell>
<TableCell>{tag.source}</TableCell>
<TableCell>{tag.orderAmount}</TableCell>
<TableCell>{tag.orderDate}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setIsImportDialogOpen(false)}>
</Button>
<Button
onClick={() => {
onChange({ ...formData, importedTags })
setIsImportDialogOpen(false)
}}
>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</Card>
</TooltipProvider>
)
}

View File

@@ -0,0 +1,227 @@
"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 { DeviceSelectionDialog } from "@/app/components/device-selection-dialog"
import { HelpCircle, ChevronDown, MessageSquare } 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"
interface FriendRequestSettingsProps {
formData: any
onChange: (data: any) => void
onNext: () => void
onPrev: () => void
}
// 招呼语模板
const greetingTemplates = [
"你好,请通过",
"你好,了解XX,请通过",
"你好我是XX产品的客服请通过",
"你好,感谢关注我们的产品",
"你好,很高兴为您服务",
]
// 备注类型选项
const remarkTypes = [
{ value: "phone", label: "手机号" },
{ value: "nickname", label: "昵称" },
{ value: "source", label: "来源" },
]
export function FriendRequestSettings({ formData, onChange, onNext, onPrev }: FriendRequestSettingsProps) {
const [isDeviceDialogOpen, setIsDeviceDialogOpen] = useState(false)
const [isTemplateDialogOpen, setIsTemplateDialogOpen] = useState(false)
// 使用useEffect设置默认值
useEffect(() => {
if (!formData.greeting) {
onChange({
...formData,
greeting: "你好,请通过",
remarkType: "phone", // 默认选择手机号
maxDailyFriends: 20,
addFriendInterval: 1,
})
}
}, [formData, formData.greeting, onChange])
const handleDeviceSelect = (deviceIds: string[]) => {
onChange({
...formData,
selectedDevices: deviceIds,
})
}
const handleTemplateSelect = (template: string) => {
onChange({ ...formData, greeting: template })
setIsTemplateDialogOpen(false)
}
return (
<Card className="p-6">
<div className="space-y-6">
<div>
<Label className="text-base"></Label>
<Button variant="outline" className="w-full mt-2 justify-between" onClick={() => setIsDeviceDialogOpen(true)}>
{formData.selectedDevices?.length ? `已选择 ${formData.selectedDevices.length} 个设备` : "选择设备"}
<ChevronDown className="h-4 w-4" />
</Button>
</div>
<div>
<div className="flex items-center space-x-2">
<Label className="text-base"></Label>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<HelpCircle className="h-4 w-4 text-gray-400" />
</TooltipTrigger>
<TooltipContent>
<p></p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<Select
value={formData.remarkType || "phone"}
onValueChange={(value) => onChange({ ...formData, remarkType: value })}
>
<SelectTrigger className="w-full mt-2">
<SelectValue placeholder="选择备注类型" />
</SelectTrigger>
<SelectContent>
{remarkTypes.map((type) => (
<SelectItem key={type.value} value={type.value}>
{type.label}
</SelectItem>
))}
</SelectContent>
</Select>
{formData.remarkType && (
<div className="mt-2 p-3 bg-gray-50 rounded-lg text-sm">
<div className="text-gray-500 mb-1"></div>
{formData.remarkType === "phone" && "138****1234"}
{formData.remarkType === "nickname" && "小红书用户2851"}
{formData.remarkType === "source" && "抖音直播"}
</div>
)}
</div>
<div>
<div className="flex items-center justify-between">
<Label className="text-base"></Label>
<Button variant="ghost" size="sm" onClick={() => setIsTemplateDialogOpen(true)} className="text-blue-500">
<MessageSquare className="h-4 w-4 mr-2" />
</Button>
</div>
<Input
value={formData.greeting}
onChange={(e) => onChange({ ...formData, greeting: e.target.value })}
placeholder="请输入招呼语"
className="mt-2"
/>
</div>
<div>
<div className="flex items-center space-x-2">
<Label className="text-base"></Label>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<HelpCircle className="h-4 w-4 text-gray-400" />
</TooltipTrigger>
<TooltipContent>
<p>20</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className="mt-2">
<Input
type="number"
value={formData.maxDailyFriends || 20}
onChange={(e) => onChange({ ...formData, maxDailyFriends: Number(e.target.value) })}
max={20}
min={1}
className="w-32"
/>
</div>
</div>
<div>
<Label className="text-base"></Label>
<div className="flex items-center space-x-2 mt-2">
<Input
type="number"
value={formData.addFriendInterval || 1}
onChange={(e) => onChange({ ...formData, addFriendInterval: Number(e.target.value) })}
className="w-32"
/>
<span></span>
</div>
</div>
<div>
<Label className="text-base"></Label>
<div className="flex items-center space-x-2 mt-2">
<Input
type="time"
value={formData.addFriendTimeStart || "09:00"}
onChange={(e) => onChange({ ...formData, addFriendTimeStart: e.target.value })}
className="w-32"
/>
<span></span>
<Input
type="time"
value={formData.addFriendTimeEnd || "18:00"}
onChange={(e) => onChange({ ...formData, addFriendTimeEnd: e.target.value })}
className="w-32"
/>
</div>
</div>
<div className="flex justify-between pt-4">
<Button variant="outline" onClick={onPrev}>
</Button>
<Button onClick={onNext}></Button>
</div>
</div>
<DeviceSelectionDialog
open={isDeviceDialogOpen}
onOpenChange={setIsDeviceDialogOpen}
selectedDevices={formData.selectedDevices || []}
onSelect={handleDeviceSelect}
excludeUsedDevices={true}
/>
<Dialog open={isTemplateDialogOpen} onOpenChange={setIsTemplateDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="space-y-2">
{greetingTemplates.map((template, index) => (
<Button
key={index}
variant="outline"
className="w-full justify-start h-auto py-3 px-4"
onClick={() => handleTemplateSelect(template)}
>
{template}
</Button>
))}
</div>
</DialogContent>
</Dialog>
</Card>
)
}

View File

@@ -0,0 +1,406 @@
"use client"
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 {
MessageSquare,
ImageIcon,
Video,
FileText,
Link2,
Users,
AppWindowIcon as Window,
Plus,
X,
Upload,
} from "lucide-react"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
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"
title?: string
description?: string
address?: string
coverImage?: string
groupId?: string
}
interface DayPlan {
day: number
messages: MessageContent[]
}
interface MessageSettingsProps {
formData: any
onChange: (data: any) => void
onNext: () => void
onPrev: () => void
}
// 消息类型配置
const messageTypes = [
{ id: "text", icon: MessageSquare, label: "文本" },
{ id: "image", icon: ImageIcon, label: "图片" },
{ id: "video", icon: Video, label: "视频" },
{ id: "file", icon: FileText, label: "文件" },
{ id: "miniprogram", icon: Window, label: "小程序" },
{ id: "link", icon: Link2, label: "链<><E993BE><EFBFBD>" },
{ 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) {
const [dayPlans, setDayPlans] = useState<DayPlan[]>([
{
day: 0,
messages: [
{
id: "1",
type: "text",
content: "",
sendInterval: 5,
intervalUnit: "minutes",
},
],
},
])
const [isAddDayPlanOpen, setIsAddDayPlanOpen] = useState(false)
const [isGroupSelectOpen, setIsGroupSelectOpen] = useState(false)
const [selectedGroupId, setSelectedGroupId] = useState("")
// 添加新消息
const handleAddMessage = (dayIndex: number, type = "text") => {
const updatedPlans = [...dayPlans]
updatedPlans[dayIndex].messages.push({
id: Date.now().toString(),
type: type as MessageContent["type"],
content: "",
sendInterval: 5,
intervalUnit: "minutes",
})
setDayPlans(updatedPlans)
onChange({ ...formData, messagePlans: updatedPlans })
}
// 更新消息内容
const handleUpdateMessage = (dayIndex: number, messageIndex: number, updates: Partial<MessageContent>) => {
const updatedPlans = [...dayPlans]
updatedPlans[dayIndex].messages[messageIndex] = {
...updatedPlans[dayIndex].messages[messageIndex],
...updates,
}
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 handleAddDayPlan = () => {
setDayPlans([
...dayPlans,
{
day: dayPlans.length,
messages: [
{
id: Date.now().toString(),
type: "text",
content: "",
sendInterval: 5,
intervalUnit: "minutes",
},
],
},
])
setIsAddDayPlanOpen(false)
toast({
title: "添加成功",
description: `已添加第${dayPlans.length}天的消息计划`,
})
}
// 选择群组
const handleSelectGroup = (groupId: string) => {
setSelectedGroupId(groupId)
setIsGroupSelectOpen(false)
toast({
title: "选择成功",
description: `已选择群组:${mockGroups.find((g) => g.id === groupId)?.name}`,
})
}
// 处理文件上传
const handleFileUpload = (dayIndex: number, messageIndex: number, type: "image" | "video" | "file") => {
// 模拟文件上传
toast({
title: "上传成功",
description: `${type === "image" ? "图片" : type === "video" ? "视频" : "文件"}上传成功`,
})
}
return (
<Card className="p-6">
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold"></h2>
<Button variant="outline" size="icon" onClick={() => setIsAddDayPlanOpen(true)}>
<Plus className="h-4 w-4" />
</Button>
</div>
<Tabs defaultValue="0" className="w-full">
<TabsList className="w-full">
{dayPlans.map((plan) => (
<TabsTrigger key={plan.day} value={plan.day.toString()} className="flex-1">
{plan.day === 0 ? "即时消息" : `${plan.day}`}
</TabsTrigger>
))}
</TabsList>
{dayPlans.map((plan, dayIndex) => (
<TabsContent key={plan.day} value={plan.day.toString()}>
<div className="space-y-4">
{plan.messages.map((message, messageIndex) => (
<div key={message.id} className="space-y-4 p-4 bg-gray-50 rounded-lg">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<Label></Label>
<Input
type="number"
value={message.sendInterval}
onChange={(e) =>
handleUpdateMessage(dayIndex, messageIndex, { sendInterval: Number(e.target.value) })
}
className="w-20"
/>
<Select
value={message.intervalUnit}
onValueChange={(value) =>
handleUpdateMessage(dayIndex, messageIndex, {
intervalUnit: value as "seconds" | "minutes",
})
}
>
<SelectTrigger className="w-[100px]">
<SelectValue placeholder="选择单位" />
</SelectTrigger>
<SelectContent>
<SelectItem value="seconds"></SelectItem>
<SelectItem value="minutes"></SelectItem>
</SelectContent>
</Select>
</div>
<Button variant="ghost" size="sm" onClick={() => handleRemoveMessage(dayIndex, messageIndex)}>
<X className="h-4 w-4" />
</Button>
</div>
<div className="flex items-center space-x-2 bg-white p-2 rounded-lg">
{messageTypes.map((type) => (
<Button
key={type.id}
variant={message.type === type.id ? "default" : "outline"}
size="sm"
onClick={() => handleUpdateMessage(dayIndex, messageIndex, { type: type.id as any })}
className="flex flex-col items-center p-2 h-auto"
>
<type.icon className="h-4 w-4" />
</Button>
))}
</div>
{message.type === "text" && (
<Textarea
value={message.content}
onChange={(e) => handleUpdateMessage(dayIndex, messageIndex, { content: e.target.value })}
placeholder="请输入消息内容"
className="min-h-[100px]"
/>
)}
{message.type === "miniprogram" && (
<div className="space-y-4">
<div className="space-y-2">
<Label>
<span className="text-red-500">*</span>
</Label>
<Input
value={message.title}
onChange={(e) => handleUpdateMessage(dayIndex, messageIndex, { title: e.target.value })}
placeholder="请输入小程序标题"
/>
</div>
<div className="space-y-2">
<Label></Label>
<Input
value={message.description}
onChange={(e) =>
handleUpdateMessage(dayIndex, messageIndex, { description: e.target.value })
}
placeholder="请输入小程序描述"
/>
</div>
<div className="space-y-2">
<Label>
<span className="text-red-500">*</span>
</Label>
<Input
value={message.address}
onChange={(e) => handleUpdateMessage(dayIndex, messageIndex, { address: e.target.value })}
placeholder="请输入小程序路径"
/>
</div>
<div className="space-y-2">
<Label>
<span className="text-red-500">*</span>
</Label>
<div className="border-2 border-dashed rounded-lg p-4 text-center">
{message.coverImage ? (
<div className="relative">
<img
src={message.coverImage || "/placeholder.svg"}
alt="封面"
className="max-w-[200px] mx-auto rounded-lg"
/>
<Button
variant="secondary"
size="sm"
className="absolute top-2 right-2"
onClick={() => handleUpdateMessage(dayIndex, messageIndex, { coverImage: undefined })}
>
<X className="h-4 w-4" />
</Button>
</div>
) : (
<Button
variant="outline"
className="w-full h-[120px]"
onClick={() => handleFileUpload(dayIndex, messageIndex, "image")}
>
<Upload className="h-4 w-4 mr-2" />
</Button>
)}
</div>
</div>
</div>
)}
{message.type === "group" && (
<div className="space-y-2">
<Label>
<span className="text-red-500">*</span>
</Label>
<Button
variant="outline"
className="w-full justify-start"
onClick={() => setIsGroupSelectOpen(true)}
>
{selectedGroupId ? mockGroups.find((g) => g.id === selectedGroupId)?.name : "选择邀请入的群"}
</Button>
</div>
)}
{(message.type === "image" || message.type === "video" || message.type === "file") && (
<div className="border-2 border-dashed rounded-lg p-4 text-center">
<Button
variant="outline"
className="w-full h-[120px]"
onClick={() => handleFileUpload(dayIndex, messageIndex, message.type as any)}
>
<Upload className="h-4 w-4 mr-2" />
{message.type === "image" ? "图片" : message.type === "video" ? "视频" : "文件"}
</Button>
</div>
)}
</div>
))}
<Button variant="outline" onClick={() => handleAddMessage(dayIndex)} className="w-full">
<Plus className="w-4 h-4 mr-2" />
</Button>
</div>
</TabsContent>
))}
</Tabs>
<div className="flex justify-between pt-4">
<Button variant="outline" onClick={onPrev}>
</Button>
<Button onClick={onNext}></Button>
</div>
</div>
{/* 添加天数计划弹窗 */}
<Dialog open={isAddDayPlanOpen} onOpenChange={setIsAddDayPlanOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="py-4">
<p className="text-sm text-gray-500 mb-4"></p>
<Button onClick={handleAddDayPlan} className="w-full">
{dayPlans.length}
</Button>
</div>
</DialogContent>
</Dialog>
{/* 选择群聊弹窗 */}
<Dialog open={isGroupSelectOpen} onOpenChange={setIsGroupSelectOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="py-4">
<div className="space-y-2">
{mockGroups.map((group) => (
<div
key={group.id}
className={`p-4 rounded-lg cursor-pointer hover:bg-gray-100 ${
selectedGroupId === group.id ? "bg-blue-50 border border-blue-200" : ""
}`}
onClick={() => handleSelectGroup(group.id)}
>
<div className="font-medium">{group.name}</div>
<div className="text-sm text-gray-500">{group.memberCount}</div>
</div>
))}
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setIsGroupSelectOpen(false)}>
</Button>
<Button onClick={() => setIsGroupSelectOpen(false)}></Button>
</DialogFooter>
</DialogContent>
</Dialog>
</Card>
)
}

View File

@@ -0,0 +1,284 @@
"use client"
import { useState, useEffect } from "react"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Badge } from "@/components/ui/badge"
import { Users, UserPlus, X } from "lucide-react"
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/components/ui/table"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
interface TeamMember {
id: string
name: string
role: string
status: "active" | "inactive"
}
interface OrderTag {
phone: string
wechat: string
source?: string
orderAmount?: number
orderDate?: string
}
interface WechatFriend {
id: string
nickname: string
wechatId: string
remark: string
addedDate: string
}
interface TagSettingsProps {
formData: any
onChange: (data: any) => void
onComplete: () => void
onPrev?: () => void
}
export function TagSettings({ formData, onChange, onComplete, onPrev }: TagSettingsProps) {
const router = useRouter()
const [teamMembers, setTeamMembers] = useState<TeamMember[]>(formData.teamMembers || [])
const [selectedRole, setSelectedRole] = useState("member")
const [newMemberName, setNewMemberName] = useState("")
const [importedTags, setImportedTags] = useState<OrderTag[]>([])
const [wechatFriends, setWechatFriends] = useState<WechatFriend[]>([])
const [activeTab, setActiveTab] = useState(formData.sourceWechatId ? "friends" : "team")
// 如果是从微信号好友转移过来,加载好友数据
useEffect(() => {
if (formData.sourceWechatId) {
// 模拟加载微信好友数据
const mockFriends = Array.from({ length: 50 }, (_, i) => ({
id: `friend-${i + 1}`,
nickname: `好友${i + 1}`,
wechatId: `wxid_${Math.random().toString(36).substr(2, 8)}`,
remark: Math.random() > 0.5 ? `备注${i + 1}` : "",
addedDate: new Date(Date.now() - Math.floor(Math.random() * 10000000000)).toLocaleDateString(),
}))
setWechatFriends(mockFriends)
setActiveTab("friends")
}
}, [formData.sourceWechatId])
const handleAddMember = () => {
if (newMemberName.trim()) {
const newMember: TeamMember = {
id: Date.now().toString(),
name: newMemberName,
role: selectedRole,
status: "active",
}
const updatedMembers = [...teamMembers, newMember]
setTeamMembers(updatedMembers)
onChange({ ...formData, teamMembers: updatedMembers })
setNewMemberName("")
}
}
const handleRemoveMember = (id: string) => {
const updatedMembers = teamMembers.filter((member) => member.id !== id)
setTeamMembers(updatedMembers)
onChange({ ...formData, teamMembers: updatedMembers })
}
const handleToggleStatus = (id: string) => {
const updatedMembers = teamMembers.map((member) => {
if (member.id === id) {
return {
...member,
status: member.status === "active" ? "inactive" : "active",
}
}
return member
})
setTeamMembers(updatedMembers)
onChange({ ...formData, teamMembers: updatedMembers })
}
const handleComplete = () => {
// 根据当前标签页确定要保存的数据
if (activeTab === "friends" && wechatFriends.length > 0) {
// 将微信好友转换为标签
const friendTags = wechatFriends.map((friend) => ({
phone: "",
wechat: friend.wechatId,
source: formData.sourceWechatId,
orderAmount: undefined,
orderDate: friend.addedDate,
}))
onChange({ ...formData, importedTags: friendTags })
} else if (activeTab === "import" && importedTags.length > 0) {
onChange({ ...formData, importedTags })
}
onComplete()
}
return (
<div className="space-y-6">
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="team"></TabsTrigger>
<TabsTrigger value="import"></TabsTrigger>
{formData.sourceWechatId && <TabsTrigger value="friends"></TabsTrigger>}
</TabsList>
<TabsContent value="team" className="mt-4">
<Card className="p-4">
<div className="space-y-4">
<div className="flex items-center gap-4">
<div className="flex-1">
<Input
value={newMemberName}
onChange={(e) => setNewMemberName(e.target.value)}
placeholder="输入成员名称"
/>
</div>
<Select value={selectedRole} onValueChange={setSelectedRole}>
<SelectTrigger className="w-[120px]">
<SelectValue placeholder="选择角色" />
</SelectTrigger>
<SelectContent>
<SelectItem value="leader"></SelectItem>
<SelectItem value="member"></SelectItem>
</SelectContent>
</Select>
<Button onClick={handleAddMember}>
<UserPlus className="h-4 w-4 mr-2" />
</Button>
</div>
<div className="space-y-2">
{teamMembers.map((member) => (
<div key={member.id} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
<div className="flex items-center gap-2">
<span className="font-medium">{member.name}</span>
<Badge variant="outline">{member.role === "leader" ? "组长" : "组员"}</Badge>
<Badge
variant={member.status === "active" ? "default" : "secondary"}
className="cursor-pointer"
onClick={() => handleToggleStatus(member.id)}
>
{member.status === "active" ? "已启用" : "已停用"}
</Badge>
</div>
<Button variant="ghost" size="sm" onClick={() => handleRemoveMember(member.id)}>
<X className="h-4 w-4" />
</Button>
</div>
))}
</div>
</div>
</Card>
</TabsContent>
<TabsContent value="import" className="mt-4">
<Card className="p-4">
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium"></h3>
</div>
{importedTags.length > 0 && (
<div className="mt-4">
<h4 className="text-sm font-medium mb-2"> {importedTags.length} </h4>
<div className="max-h-[300px] overflow-auto border rounded-md">
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{importedTags.slice(0, 5).map((tag, index) => (
<TableRow key={index}>
<TableCell>{tag.phone}</TableCell>
<TableCell>{tag.wechat}</TableCell>
<TableCell>{tag.source}</TableCell>
<TableCell>{tag.orderAmount}</TableCell>
</TableRow>
))}
{importedTags.length > 5 && (
<TableRow>
<TableCell colSpan={4} className="text-center text-gray-500">
{importedTags.length - 5}
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
</div>
)}
</div>
</Card>
</TabsContent>
{formData.sourceWechatId && (
<TabsContent value="friends" className="mt-4">
<Card className="p-4">
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Users className="h-5 w-5 text-blue-500" />
<h3 className="text-lg font-medium"></h3>
</div>
<Badge variant="outline"> {wechatFriends.length} </Badge>
</div>
<div className="max-h-[400px] overflow-auto border rounded-md">
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{wechatFriends.map((friend) => (
<TableRow key={friend.id}>
<TableCell>{friend.nickname}</TableCell>
<TableCell>{friend.wechatId}</TableCell>
<TableCell>{friend.remark || "-"}</TableCell>
<TableCell>{friend.addedDate}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<div className="text-sm text-gray-500">
<p> </p>
<p> </p>
</div>
</div>
</Card>
</TabsContent>
)}
</Tabs>
<div className="flex justify-between">
{onPrev && (
<Button variant="outline" onClick={onPrev}>
</Button>
)}
<Button onClick={handleComplete} className={onPrev ? "ml-auto" : "w-full"}>
</Button>
</div>
</div>
)
}