feat: 保存了
This commit is contained in:
@@ -8,6 +8,8 @@ import { createMomentsSyncTask } from '@/api/momentsSync';
|
||||
import { ChevronLeft, Clock, Plus, Minus, Search } from 'lucide-react';
|
||||
import Layout from '@/components/Layout';
|
||||
import BottomNav from '@/components/BottomNav';
|
||||
import { DeviceSelectionDialog } from '@/components/DeviceSelectionDialog';
|
||||
import { ContentLibrarySelectionDialog } from '@/components/ContentLibrarySelectionDialog';
|
||||
|
||||
// 步骤指示器组件
|
||||
interface StepIndicatorProps {
|
||||
@@ -23,6 +25,17 @@ function StepIndicator({ currentStep }: StepIndicatorProps) {
|
||||
|
||||
return (
|
||||
<div className="relative flex justify-between px-6">
|
||||
{/* 背景连线 */}
|
||||
<div className="absolute top-4 left-6 right-6 h-[2px] bg-gray-200">
|
||||
<div
|
||||
className="absolute top-0 left-0 h-full bg-blue-600 transition-all duration-300 ease-in-out"
|
||||
style={{
|
||||
width: currentStep > 1 ? `${((currentStep - 1) / (steps.length - 1)) * 100}%` : '0%'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 步骤圆圈 */}
|
||||
{steps.map((step) => (
|
||||
<div
|
||||
key={step.id}
|
||||
@@ -31,10 +44,10 @@ function StepIndicator({ currentStep }: StepIndicatorProps) {
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`w-8 h-8 rounded-full flex items-center justify-center transition-all ${
|
||||
className={`w-8 h-8 rounded-full flex items-center justify-center transition-all duration-300 ${
|
||||
currentStep >= step.id
|
||||
? "bg-blue-600 text-white shadow-sm"
|
||||
: "bg-white border border-gray-200 text-gray-400"
|
||||
: "bg-white border-2 border-gray-200 text-gray-400"
|
||||
}`}
|
||||
>
|
||||
{step.id}
|
||||
@@ -42,12 +55,6 @@ function StepIndicator({ currentStep }: StepIndicatorProps) {
|
||||
<div className="text-xs mt-2 font-medium">{step.subtitle}</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="absolute top-4 left-0 right-0 h-[1px] bg-gray-100 -z-10">
|
||||
<div
|
||||
className="absolute top-0 left-0 h-full bg-blue-600 transition-all duration-300"
|
||||
style={{ width: `${((currentStep - 1) / (steps.length - 1)) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -59,8 +66,12 @@ interface BasicSettingsProps {
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
syncCount: number;
|
||||
interval: number;
|
||||
accountType: "business" | "personal";
|
||||
enabled: boolean;
|
||||
contentTypes: ('text' | 'image' | 'video')[];
|
||||
targetTags: string[];
|
||||
filterKeywords: string[];
|
||||
};
|
||||
onChange: (data: Partial<BasicSettingsProps["formData"]>) => void;
|
||||
onNext: () => void;
|
||||
@@ -112,23 +123,47 @@ function BasicSettings({ formData, onChange, onNext }: BasicSettingsProps) {
|
||||
variant="outline"
|
||||
size="lg"
|
||||
onClick={() => onChange({ syncCount: Math.max(1, formData.syncCount - 1) })}
|
||||
className="h-12 w-12 rounded-xl bg-white border-gray-200"
|
||||
className="h-12 w-12 rounded-xl bg-white border-gray-200 text-xl font-bold"
|
||||
>
|
||||
<Minus className="h-5 w-5" />
|
||||
-
|
||||
</Button>
|
||||
<span className="w-8 text-center text-lg font-medium">{formData.syncCount}</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
onClick={() => onChange({ syncCount: formData.syncCount + 1 })}
|
||||
className="h-12 w-12 rounded-xl bg-white border-gray-200"
|
||||
className="h-12 w-12 rounded-xl bg-white border-gray-200 text-xl font-bold"
|
||||
>
|
||||
<Plus className="h-5 w-5" />
|
||||
+
|
||||
</Button>
|
||||
<span className="text-gray-500">条朋友圈</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="text-base font-medium mb-2">同步时间间隔</div>
|
||||
<div className="flex items-center space-x-5">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
onClick={() => onChange({ interval: Math.max(1, formData.interval - 1) })}
|
||||
className="h-12 w-12 rounded-xl bg-white border-gray-200 text-xl font-bold"
|
||||
>
|
||||
-
|
||||
</Button>
|
||||
<span className="w-8 text-center text-lg font-medium">{formData.interval}</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
onClick={() => onChange({ interval: formData.interval + 1 })}
|
||||
className="h-12 w-12 rounded-xl bg-white border-gray-200 text-xl font-bold"
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
<span className="text-gray-500">分钟</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="text-base font-medium mb-2">账号类型</div>
|
||||
<div className="flex space-x-4">
|
||||
@@ -161,6 +196,32 @@ function BasicSettings({ formData, onChange, onNext }: BasicSettingsProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="text-base font-medium mb-2">内容类型</div>
|
||||
<div className="flex space-x-4">
|
||||
{(['text', 'image', 'video'] as const).map((type) => (
|
||||
<div key={type} className="flex-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
const newTypes = formData.contentTypes.includes(type)
|
||||
? formData.contentTypes.filter(t => t !== type)
|
||||
: [...formData.contentTypes, type];
|
||||
onChange({ contentTypes: newTypes });
|
||||
}}
|
||||
className={`w-full h-12 justify-between rounded-lg ${
|
||||
formData.contentTypes.includes(type)
|
||||
? "bg-blue-600 hover:bg-blue-600 text-white"
|
||||
: "bg-white hover:bg-gray-50"
|
||||
}`}
|
||||
>
|
||||
{type === 'text' ? '文字' : type === 'image' ? '图片' : '视频'}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between py-2">
|
||||
<span className="text-base font-medium">是否启用</span>
|
||||
<Switch
|
||||
@@ -186,15 +247,21 @@ export default function NewMomentsSyncTask() {
|
||||
const { toast } = useToast();
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [deviceDialogOpen, setDeviceDialogOpen] = useState(false);
|
||||
const [contentLibraryDialogOpen, setContentLibraryDialogOpen] = useState(false);
|
||||
const [formData, setFormData] = useState({
|
||||
taskName: '',
|
||||
startTime: '06:00',
|
||||
endTime: '23:59',
|
||||
syncCount: 5,
|
||||
interval: 30, // 时间间隔,单位:分钟
|
||||
accountType: 'business' as 'business' | 'personal',
|
||||
enabled: true,
|
||||
selectedDevices: [] as string[],
|
||||
selectedLibraries: [] as string[],
|
||||
contentTypes: ['text', 'image', 'video'] as ('text' | 'image' | 'video')[],
|
||||
targetTags: [] as string[],
|
||||
filterKeywords: [] as string[],
|
||||
});
|
||||
|
||||
const handleUpdateFormData = (data: Partial<typeof formData>) => {
|
||||
@@ -230,13 +297,13 @@ export default function NewMomentsSyncTask() {
|
||||
devices: formData.selectedDevices,
|
||||
contentLib: formData.selectedLibraries.join(','),
|
||||
syncMode: formData.accountType === 'business' ? 'auto' : 'manual',
|
||||
interval: 30,
|
||||
interval: formData.interval,
|
||||
maxSync: formData.syncCount,
|
||||
startTime: formData.startTime,
|
||||
endTime: formData.endTime,
|
||||
targetTags: [],
|
||||
contentTypes: ['text', 'image', 'video'],
|
||||
filterKeywords: [],
|
||||
targetTags: formData.targetTags,
|
||||
contentTypes: formData.contentTypes,
|
||||
filterKeywords: formData.filterKeywords,
|
||||
friends: [],
|
||||
});
|
||||
toast({ title: '创建成功' });
|
||||
@@ -272,72 +339,100 @@ export default function NewMomentsSyncTask() {
|
||||
<BasicSettings formData={formData} onChange={handleUpdateFormData} onNext={handleNext} />
|
||||
)}
|
||||
|
||||
{currentStep === 2 && (
|
||||
<div className="space-y-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={() => {
|
||||
// TODO: 打开设备选择弹窗
|
||||
toast({ title: '设备选择功能开发中' });
|
||||
}}
|
||||
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>
|
||||
{currentStep === 2 && (
|
||||
<div className="space-y-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>
|
||||
)}
|
||||
|
||||
{currentStep === 3 && (
|
||||
<div className="space-y-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={() => {
|
||||
// TODO: 打开内容库选择弹窗
|
||||
toast({ title: '内容库选择功能开发中' });
|
||||
}}
|
||||
readOnly
|
||||
/>
|
||||
{formData.selectedDevices.length > 0 && (
|
||||
<div className="text-base text-gray-500">
|
||||
已选设备:{formData.selectedDevices.length} 个
|
||||
<div className="text-sm text-gray-400 mt-1">
|
||||
{formData.selectedDevices.map(id => {
|
||||
// 这里可以根据实际API获取设备名称
|
||||
return `设备 ${id}`;
|
||||
}).join(', ')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{formData.selectedLibraries.length > 0 && (
|
||||
<div className="text-base text-gray-500">已选内容库:{formData.selectedLibraries.join(', ')}</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}
|
||||
loading={loading}
|
||||
className="flex-1 h-12 bg-blue-600 hover:bg-blue-700 rounded-xl text-base shadow-sm"
|
||||
>
|
||||
{loading ? '创建中...' : '完成'}
|
||||
</Button>
|
||||
</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 });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{currentStep === 3 && (
|
||||
<div className="space-y-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={() => setContentLibraryDialogOpen(true)}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
|
||||
{formData.selectedLibraries.length > 0 && (
|
||||
<div className="text-base text-gray-500">
|
||||
已选内容库:{formData.selectedLibraries.length} 个
|
||||
<div className="text-sm text-gray-400 mt-1">
|
||||
{formData.selectedLibraries.map(id => {
|
||||
// 这里可以根据实际API获取内容库名称
|
||||
return `朋友圈内容库${id}`;
|
||||
}).join(', ')}
|
||||
</div>
|
||||
</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}
|
||||
loading={loading}
|
||||
className="flex-1 h-12 bg-blue-600 hover:bg-blue-700 rounded-xl text-base shadow-sm"
|
||||
>
|
||||
{loading ? '创建中...' : '完成'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<ContentLibrarySelectionDialog
|
||||
open={contentLibraryDialogOpen}
|
||||
onOpenChange={setContentLibraryDialogOpen}
|
||||
selectedLibraries={formData.selectedLibraries}
|
||||
onSelect={(libraries) => {
|
||||
handleUpdateFormData({ selectedLibraries: libraries });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user