存客宝 React

This commit is contained in:
柳清爽
2025-03-29 16:50:39 +08:00
parent caea0b4b99
commit 7e7c199996
388 changed files with 53282 additions and 2076 deletions

View File

@@ -0,0 +1,143 @@
"use client"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { ChevronLeft, Search } from "lucide-react"
import { Button } from "@/components/ui/button"
import { StepIndicator } from "../../components/step-indicator"
import { BasicSettings } from "../../components/basic-settings"
import { DeviceSelectionDialog } from "../../components/device-selection-dialog"
import { Input } from "@/components/ui/input"
export default function EditMomentsSyncPage() {
const router = useRouter()
const [currentStep, setCurrentStep] = useState(1)
const [deviceDialogOpen, setDeviceDialogOpen] = useState(false)
const [formData, setFormData] = useState({
taskName: "同步卡若主号",
startTime: "06:00",
endTime: "23:59",
syncCount: 5,
accountType: "business" as const,
enabled: true,
selectedDevices: [] as string[],
selectedLibraries: [] as string[],
})
const handleUpdateFormData = (data: Partial<typeof formData>) => {
setFormData((prev) => ({ ...prev, ...data }))
}
const handleNext = () => {
setCurrentStep((prev) => Math.min(prev + 1, 3))
}
const handlePrev = () => {
setCurrentStep((prev) => Math.max(prev - 1, 1))
}
const handleComplete = () => {
console.log("Form submitted:", formData)
router.push("/workspace/moments-sync")
}
return (
<div className="min-h-screen bg-[#F8F9FA] pb-20">
<header className="sticky top-0 z-10 bg-white">
<div className="flex items-center h-14 px-4">
<Button variant="ghost" size="icon" onClick={() => router.back()} className="hover:bg-gray-50">
<ChevronLeft className="h-6 w-6" />
</Button>
<h1 className="ml-2 text-lg font-medium"></h1>
</div>
</header>
<div className="mt-8">
<StepIndicator currentStep={currentStep} />
<div className="mt-8">
{currentStep === 1 && (
<BasicSettings formData={formData} onChange={handleUpdateFormData} onNext={handleNext} />
)}
{currentStep === 2 && (
<div className="space-y-6 px-6">
<div className="relative">
<Search className="absolute left-3 top-4 h-5 w-5 text-gray-400" />
<Input
placeholder="选择设备"
className="h-12 pl-11 rounded-xl border-gray-200 text-base"
onClick={() => setDeviceDialogOpen(true)}
readOnly
/>
</div>
{formData.selectedDevices.length > 0 && (
<div className="text-base text-gray-500">{formData.selectedDevices.length} </div>
)}
<div className="flex space-x-4 pt-4">
<Button variant="outline" onClick={handlePrev} className="flex-1 h-12 rounded-xl text-base">
</Button>
<Button
onClick={handleNext}
className="flex-1 h-12 bg-blue-600 hover:bg-blue-700 rounded-xl text-base shadow-sm"
>
</Button>
</div>
<DeviceSelectionDialog
open={deviceDialogOpen}
onOpenChange={setDeviceDialogOpen}
selectedDevices={formData.selectedDevices}
onSelect={(devices) => {
handleUpdateFormData({ selectedDevices: devices })
setDeviceDialogOpen(false)
}}
/>
</div>
)}
{currentStep === 3 && (
<div className="space-y-6 px-6">
<div className="relative">
<Search className="absolute left-3 top-4 h-5 w-5 text-gray-400" />
<Input placeholder="选择内容库" className="h-12 pl-11 rounded-xl border-gray-200 text-base" />
</div>
<div className="flex space-x-4 pt-4">
<Button variant="outline" onClick={handlePrev} className="flex-1 h-12 rounded-xl text-base">
</Button>
<Button
onClick={handleComplete}
className="flex-1 h-12 bg-blue-600 hover:bg-blue-700 rounded-xl text-base shadow-sm"
>
</Button>
</div>
</div>
)}
</div>
</div>
<nav className="fixed bottom-0 left-0 right-0 h-16 bg-white border-t flex items-center justify-around px-6">
<button className="flex flex-col items-center text-blue-600">
<span className="text-sm mt-1"></span>
</button>
<button className="flex flex-col items-center text-gray-400">
<span className="text-sm mt-1"></span>
</button>
<button className="flex flex-col items-center text-gray-400">
<span className="text-sm mt-1"></span>
</button>
<button className="flex flex-col items-center text-gray-400">
<span className="text-sm mt-1"></span>
</button>
</nav>
</div>
)
}

View File

@@ -0,0 +1,39 @@
interface StepIndicatorProps {
currentStep: number
}
export function StepIndicator({ currentStep }: StepIndicatorProps) {
const steps = [
{ number: 1, title: "步骤 1", subtitle: "基础设置" },
{ number: 2, title: "步骤 2", subtitle: "设备选择" },
{ number: 3, title: "步骤 3", subtitle: "选择内容库" },
]
return (
<div className="flex justify-between relative">
{steps.map((step, index) => (
<div key={step.number} className="flex flex-col items-center relative z-10">
<div
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm
${currentStep >= step.number ? "bg-blue-600 text-white" : "bg-gray-100 text-gray-400"}`}
>
{step.number}
</div>
<div className={`text-xs mt-2 ${currentStep >= step.number ? "text-blue-600" : "text-gray-400"}`}>
{step.title}
</div>
<div className={`text-xs ${currentStep >= step.number ? "text-gray-600" : "text-gray-400"}`}>
{step.subtitle}
</div>
</div>
))}
<div className="absolute top-4 left-0 right-0 h-[1px] bg-gray-200 -z-10">
<div
className="h-full bg-blue-600 transition-all duration-300"
style={{ width: `${((currentStep - 1) / (steps.length - 1)) * 100}%` }}
/>
</div>
</div>
)
}

View File

@@ -0,0 +1,101 @@
"use client"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import { Switch } from "@/components/ui/switch"
import { Plus, Minus } from "lucide-react"
interface BasicSettingsProps {
formData: {
taskName: string
startTime: string
endTime: string
syncCount: number
accountType: string
enabled: boolean
}
onChange: (data: Partial<BasicSettingsProps["formData"]>) => void
onNext: () => void
}
export function BasicSettings({ formData, onChange, onNext }: BasicSettingsProps) {
return (
<div className="space-y-6">
<div>
<div className="text-sm mb-2"></div>
<Input
value={formData.taskName}
onChange={(e) => onChange({ taskName: e.target.value })}
placeholder="请输入任务名称"
/>
</div>
<div>
<div className="text-sm mb-2"></div>
<div className="flex items-center space-x-3">
<Input
type="time"
value={formData.startTime}
onChange={(e) => onChange({ startTime: e.target.value })}
className="w-32"
/>
<span className="text-gray-500"></span>
<Input
type="time"
value={formData.endTime}
onChange={(e) => onChange({ endTime: e.target.value })}
className="w-32"
/>
</div>
</div>
<div>
<div className="text-sm mb-2"></div>
<div className="flex items-center space-x-4">
<Button
variant="outline"
size="icon"
onClick={() => onChange({ syncCount: Math.max(1, formData.syncCount - 1) })}
>
<Minus className="h-4 w-4" />
</Button>
<span className="w-8 text-center">{formData.syncCount}</span>
<Button variant="outline" size="icon" onClick={() => onChange({ syncCount: formData.syncCount + 1 })}>
<Plus className="h-4 w-4" />
</Button>
<span className="text-gray-500"></span>
</div>
</div>
<div>
<div className="text-sm mb-2"></div>
<div className="flex space-x-4">
<Button
variant={formData.accountType === "business" ? "default" : "outline"}
onClick={() => onChange({ accountType: "business" })}
className="flex-1"
>
</Button>
<Button
variant={formData.accountType === "personal" ? "default" : "outline"}
onClick={() => onChange({ accountType: "personal" })}
className="flex-1"
>
</Button>
</div>
</div>
<div className="flex items-center justify-between">
<span className="text-sm"></span>
<Switch checked={formData.enabled} onCheckedChange={(checked) => onChange({ enabled: checked })} />
</div>
<Button onClick={onNext} className="w-full mt-8">
</Button>
</div>
)
}

View File

@@ -0,0 +1,40 @@
"use client"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Search } from "lucide-react"
interface ContentLibrarySelectionProps {
selectedLibraries: string[]
onChange: (libraries: string[]) => void
onComplete: () => void
onPrev: () => void
}
export function ContentLibrarySelection({
selectedLibraries,
onChange,
onComplete,
onPrev,
}: ContentLibrarySelectionProps) {
return (
<div className="space-y-4">
<div className="relative">
<Search className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
<Input placeholder="选择内容库" className="pl-9" />
</div>
<div className="min-h-[300px] flex items-center justify-center text-gray-400"></div>
<div className="flex space-x-3 mt-8">
<Button variant="outline" onClick={onPrev} className="flex-1">
</Button>
<Button onClick={onComplete} className="flex-1">
</Button>
</div>
</div>
)
}

View File

@@ -0,0 +1,35 @@
"use client"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Search } from "lucide-react"
interface DeviceSelectionProps {
selectedDevices: string[]
onChange: (devices: string[]) => void
onNext: () => void
onPrev: () => void
}
export function DeviceSelection({ selectedDevices, onChange, onNext, onPrev }: DeviceSelectionProps) {
return (
<div className="space-y-4">
<div className="relative">
<Search className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
<Input placeholder="选择设备" className="pl-9" />
</div>
<div className="min-h-[300px] flex items-center justify-center text-gray-400"></div>
<div className="flex space-x-3 mt-8">
<Button variant="outline" onClick={onPrev} className="flex-1">
</Button>
<Button onClick={onNext} className="flex-1">
</Button>
</div>
</div>
)
}

View File

@@ -0,0 +1,108 @@
"use client"
import { useState, useEffect } from "react"
import { useRouter } from "next/navigation"
import { ChevronLeft } from "lucide-react"
import { Card } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Switch } from "@/components/ui/switch"
interface SyncTask {
id: string
name: string
status: "running" | "paused"
deviceCount: number
contentLib: string
syncCount: number
lastSyncTime: string
createTime: string
creator: string
}
export default function ViewMomentsSyncTask({ params }: { params: { id: string } }) {
const router = useRouter()
const [task, setTask] = useState<SyncTask | null>(null)
useEffect(() => {
// Fetch task data from API
// For now, we'll use mock data
setTask({
id: params.id,
name: "同步卡若主号",
deviceCount: 2,
contentLib: "卡若朋友圈",
syncCount: 307,
lastSyncTime: "2025-02-06 13:12:35",
createTime: "2024-11-20 19:04:14",
creator: "karuo",
status: "running",
})
}, [params.id])
const toggleTaskStatus = () => {
if (task) {
setTask({ ...task, status: task.status === "running" ? "paused" : "running" })
}
}
if (!task) {
return <div>Loading...</div>
}
return (
<div className="flex-1 bg-gray-50 min-h-screen">
<header className="sticky top-0 z-10 bg-white border-b">
<div className="flex items-center justify-between p-4">
<div className="flex items-center space-x-3">
<Button variant="ghost" size="icon" onClick={() => router.back()}>
<ChevronLeft className="h-5 w-5" />
</Button>
<h1 className="text-lg font-medium"></h1>
</div>
<Button onClick={() => router.push(`/workspace/moments-sync/${task.id}/edit`)}></Button>
</div>
</header>
<div className="p-4">
<Card className="p-6">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center space-x-2">
<h2 className="text-2xl font-bold">{task.name}</h2>
<Badge variant={task.status === "running" ? "success" : "secondary"}>
{task.status === "running" ? "进行中" : "已暂停"}
</Badge>
</div>
<Switch checked={task.status === "running"} onCheckedChange={toggleTaskStatus} />
</div>
<div className="grid grid-cols-2 gap-6 mb-6">
<div>
<h3 className="text-lg font-semibold mb-2"></h3>
<div className="space-y-2">
<p>{task.deviceCount} </p>
<p>{task.contentLib}</p>
<p>{task.syncCount} </p>
<p>{task.creator}</p>
</div>
</div>
<div>
<h3 className="text-lg font-semibold mb-2"></h3>
<div className="space-y-2">
<p>{task.createTime}</p>
<p>{task.lastSyncTime}</p>
</div>
</div>
</div>
<div className="border-t pt-4">
<h3 className="text-lg font-semibold mb-2"></h3>
{/* Add content preview here */}
<p className="text-gray-500"></p>
</div>
</Card>
</div>
</div>
)
}

View File

@@ -0,0 +1,130 @@
"use client"
import { useState } from "react"
import { ChevronLeft, Edit2, RefreshCw } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
interface MomentContent {
id: string
type: string
content: string
images: string[]
publishTime: string
pushTime: string
status: "已发送" | "待发送" | "已终止"
}
const mockData: MomentContent[] = [
{
id: "399401",
type: "图文",
content: "一定要把安全意识这件事情刻在DNA里......",
images: [
"https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-Z8LxL98X5Bwm5Jr5ke3755Qd97PSYC.png",
"https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-Z8LxL98X5Bwm5Jr5ke3755Qd97PSYC.png",
"https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-Z8LxL98X5Bwm5Jr5ke3755Qd97PSYC.png",
],
publishTime: "2025-02-15 23:37:49",
pushTime: "2025-02-16 08:50:36",
status: "已发送",
},
]
export default function MomentsSyncViewPage() {
const [syncName] = useState("同步卡若主号")
return (
<div className="min-h-screen bg-gray-50">
<header className="sticky top-0 z-10 bg-white border-b">
<div className="flex items-center justify-between h-14 px-4">
<div className="flex items-center">
<Button variant="ghost" size="icon" onClick={() => window.history.back()}>
<ChevronLeft className="h-5 w-5" />
</Button>
<h1 className="ml-2 text-lg font-medium">{syncName}</h1>
</div>
<div className="flex items-center space-x-2">
<Button variant="outline" size="sm">
<Edit2 className="h-4 w-4 mr-2" />
</Button>
<Button variant="outline" size="sm">
<RefreshCw className="h-4 w-4 mr-2" />
</Button>
</div>
</div>
</header>
<div className="p-4 max-w-7xl mx-auto space-y-6">
<Card className="p-6">
<h2 className="text-lg font-medium mb-4"></h2>
<div className="space-y-4">
<div className="flex items-center">
<span className="text-gray-500 w-32"></span>
<span>{syncName}</span>
</div>
</div>
</Card>
<Card className="p-6">
<h2 className="text-lg font-medium mb-4"></h2>
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">ID</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{mockData.map((item) => (
<TableRow key={item.id}>
<TableCell>{item.id}</TableCell>
<TableCell>{item.type}</TableCell>
<TableCell className="max-w-[300px] truncate">{item.content}</TableCell>
<TableCell>
<div className="flex space-x-2">
{item.images.map((img, index) => (
<img
key={index}
src={img || "/placeholder.svg"}
alt={`Content ${index + 1}`}
className="w-16 h-16 object-cover rounded"
/>
))}
</div>
</TableCell>
<TableCell>{item.pushTime}</TableCell>
<TableCell>{item.publishTime}</TableCell>
<TableCell>
<span
className={`px-2 py-1 rounded-full text-xs ${
item.status === "已发送"
? "bg-green-100 text-green-800"
: item.status === "待发送"
? "bg-blue-100 text-blue-800"
: "bg-red-100 text-red-800"
}`}
>
{item.status}
</span>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</Card>
</div>
</div>
)
}