feat没问题
This commit is contained in:
@@ -8,6 +8,7 @@ interface ButtonProps {
|
|||||||
onClick?: (e?: React.MouseEvent) => void;
|
onClick?: (e?: React.MouseEvent) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
type?: 'button' | 'submit' | 'reset';
|
type?: 'button' | 'submit' | 'reset';
|
||||||
|
loading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Button({
|
export function Button({
|
||||||
@@ -17,7 +18,8 @@ export function Button({
|
|||||||
className = '',
|
className = '',
|
||||||
onClick,
|
onClick,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
type = 'button'
|
type = 'button',
|
||||||
|
loading = false
|
||||||
}: ButtonProps) {
|
}: ButtonProps) {
|
||||||
const baseClasses = 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none';
|
const baseClasses = 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none';
|
||||||
|
|
||||||
@@ -43,9 +45,15 @@ export function Button({
|
|||||||
<button
|
<button
|
||||||
className={classes}
|
className={classes}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
disabled={disabled}
|
disabled={disabled || loading}
|
||||||
type={type}
|
type={type}
|
||||||
>
|
>
|
||||||
|
{loading && (
|
||||||
|
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||||
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ interface InputProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
id?: string;
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
type?: string;
|
||||||
|
required?: boolean;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
step?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Input({
|
export function Input({
|
||||||
@@ -17,17 +23,28 @@ export function Input({
|
|||||||
placeholder,
|
placeholder,
|
||||||
className = '',
|
className = '',
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
id
|
id,
|
||||||
|
name,
|
||||||
|
type = 'text',
|
||||||
|
required = false,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
step,
|
||||||
}: InputProps) {
|
}: InputProps) {
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
id={id}
|
id={id}
|
||||||
type="text"
|
name={name}
|
||||||
|
type={type}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
|
required={required}
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
step={step}
|
||||||
className={`flex h-10 w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-gray-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${className}`}
|
className={`flex h-10 w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-gray-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${className}`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
135
nkebao/src/pages/workspace/moments-sync/new.tsx
Normal file
135
nkebao/src/pages/workspace/moments-sync/new.tsx
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { useToast } from '@/components/ui/toast';
|
||||||
|
import { createMomentsSyncTask } from '@/api/momentsSync';
|
||||||
|
|
||||||
|
export default function NewMomentsSyncTask() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [form, setForm] = useState({
|
||||||
|
name: '',
|
||||||
|
deviceIds: '', // 逗号分隔
|
||||||
|
contentLib: '',
|
||||||
|
syncMode: 'auto',
|
||||||
|
syncInterval: '30',
|
||||||
|
maxSyncPerDay: '100',
|
||||||
|
timeStart: '08:00',
|
||||||
|
timeEnd: '22:00',
|
||||||
|
targetTags: '', // 逗号分隔
|
||||||
|
contentTypes: '', // 逗号分隔
|
||||||
|
filterKeywords: '', // 逗号分隔
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setForm(prev => ({
|
||||||
|
...prev,
|
||||||
|
[name]: value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!form.name.trim()) {
|
||||||
|
toast({ title: '请输入任务名称', variant: 'destructive' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!form.deviceIds.trim()) {
|
||||||
|
toast({ title: '请输入推送设备', variant: 'destructive' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!form.contentLib.trim()) {
|
||||||
|
toast({ title: '请输入内容库', variant: 'destructive' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await createMomentsSyncTask({
|
||||||
|
name: form.name,
|
||||||
|
devices: form.deviceIds.split(',').map(s => s.trim()).filter(Boolean),
|
||||||
|
contentLib: form.contentLib,
|
||||||
|
syncMode: form.syncMode as 'auto' | 'manual',
|
||||||
|
interval: Number(form.syncInterval),
|
||||||
|
maxSync: Number(form.maxSyncPerDay),
|
||||||
|
startTime: form.timeStart,
|
||||||
|
endTime: form.timeEnd,
|
||||||
|
targetTags: form.targetTags.split(',').map(s => s.trim()).filter(Boolean),
|
||||||
|
contentTypes: form.contentTypes.split(',').map(s => s.trim()).filter(Boolean) as ('text' | 'image' | 'video' | 'link')[],
|
||||||
|
filterKeywords: form.filterKeywords.split(',').map(s => s.trim()).filter(Boolean),
|
||||||
|
});
|
||||||
|
toast({ title: '创建成功' });
|
||||||
|
navigate('/workspace/moments-sync');
|
||||||
|
} catch (error) {
|
||||||
|
toast({ title: '创建失败', variant: 'destructive' });
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex-1 bg-gray-50 min-h-screen pb-20">
|
||||||
|
<div className="max-w-xl mx-auto bg-white rounded shadow p-6 mt-8">
|
||||||
|
<h2 className="text-lg font-bold mb-6">新建朋友圈同步任务</h2>
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-5">
|
||||||
|
<div>
|
||||||
|
<label className="block mb-1 font-medium">任务名称</label>
|
||||||
|
<Input value={form.name} onChange={handleChange} placeholder="请输入任务名称" name="name" required />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block mb-1 font-medium">推送设备ID(逗号分隔)</label>
|
||||||
|
<Input value={form.deviceIds} onChange={handleChange} placeholder="如:dev1,dev2" name="deviceIds" required />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block mb-1 font-medium">内容库</label>
|
||||||
|
<Input value={form.contentLib} onChange={handleChange} placeholder="请输入内容库名称" name="contentLib" required />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block mb-1 font-medium">同步模式</label>
|
||||||
|
<select name="syncMode" value={form.syncMode} onChange={handleChange} className="w-full border rounded px-3 py-2">
|
||||||
|
<option value="auto">自动</option>
|
||||||
|
<option value="manual">手动</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-4">
|
||||||
|
<div className="flex-1">
|
||||||
|
<label className="block mb-1 font-medium">同步间隔(秒)</label>
|
||||||
|
<Input value={form.syncInterval} onChange={handleChange} name="syncInterval" type="number" min={1} required />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<label className="block mb-1 font-medium">每日最大同步数</label>
|
||||||
|
<Input value={form.maxSyncPerDay} onChange={handleChange} name="maxSyncPerDay" type="number" min={1} required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-4">
|
||||||
|
<div className="flex-1">
|
||||||
|
<label className="block mb-1 font-medium">开始时间</label>
|
||||||
|
<Input value={form.timeStart} onChange={handleChange} name="timeStart" type="time" required />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<label className="block mb-1 font-medium">结束时间</label>
|
||||||
|
<Input value={form.timeEnd} onChange={handleChange} name="timeEnd" type="time" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block mb-1 font-medium">目标人群标签(逗号分隔)</label>
|
||||||
|
<Input value={form.targetTags} onChange={handleChange} placeholder="如:重要客户,活跃用户" name="targetTags" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block mb-1 font-medium">内容类型(逗号分隔,支持text,image,video)</label>
|
||||||
|
<Input value={form.contentTypes} onChange={handleChange} placeholder="如:text,image,video" name="contentTypes" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block mb-1 font-medium">关键词过滤(逗号分隔,可选)</label>
|
||||||
|
<Input value={form.filterKeywords} onChange={handleChange} placeholder="如:产品,服务,优惠" name="filterKeywords" />
|
||||||
|
</div>
|
||||||
|
<div className="pt-2">
|
||||||
|
<Button type="submit" loading={loading} className="w-full">{loading ? '提交中...' : '创建任务'}</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user