mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-30 22:26:38 +08:00
邮箱
This commit is contained in:
@@ -6,6 +6,7 @@ import {toast} from "sonner"
|
||||
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 {Spinner} from "@/components/ui/spinner"
|
||||
import {Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle} from "@/components/ui/dialog"
|
||||
import {Calendar as CalendarIcon, Clock, FileText, Info, Layers, Play} from "lucide-react"
|
||||
@@ -140,6 +141,7 @@ export function TaskManager() {
|
||||
const [startTime, setStartTime] = useState<Date | undefined>(undefined)
|
||||
const [endTime, setEndTime] = useState<Date | undefined>(undefined)
|
||||
const [userId, setUserId] = useState("")
|
||||
const [paramValues, setParamValues] = useState<Record<string, string>>({})
|
||||
|
||||
const fetchTaskTypes = useCallback(async () => {
|
||||
try {
|
||||
@@ -158,6 +160,21 @@ export function TaskManager() {
|
||||
fetchTaskTypes()
|
||||
}, [fetchTaskTypes])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedTaskType) {
|
||||
const targetTask = taskTypes.find(t => t.type === selectedTaskType)
|
||||
if (targetTask?.params) {
|
||||
const initialValues: Record<string, string> = {}
|
||||
targetTask.params.forEach(p => {
|
||||
initialValues[p.name] = ""
|
||||
})
|
||||
setParamValues(initialValues)
|
||||
} else {
|
||||
setParamValues({})
|
||||
}
|
||||
}
|
||||
}, [selectedTaskType, taskTypes])
|
||||
|
||||
const handleDispatch = async () => {
|
||||
if (!selectedTaskType) return
|
||||
|
||||
@@ -170,7 +187,7 @@ export function TaskManager() {
|
||||
task_type: selectedTaskType
|
||||
}
|
||||
|
||||
if (targetTask?.type === 'order_sync') {
|
||||
if (targetTask?.supports_time) {
|
||||
if (startTime) params.start_time = startTime.toISOString()
|
||||
if (endTime) params.end_time = endTime.toISOString()
|
||||
}
|
||||
@@ -179,6 +196,21 @@ export function TaskManager() {
|
||||
if (userId) params.user_id = userId
|
||||
}
|
||||
|
||||
// Handle dynamic parameters
|
||||
if (targetTask?.params && targetTask.params.length > 0) {
|
||||
const payloadData: Record<string, string> = {}
|
||||
for (const param of targetTask.params) {
|
||||
const val = (paramValues[param.name] || "").trim()
|
||||
if (param.required && !val) {
|
||||
toast.error(`${param.label}不能为空`)
|
||||
setDispatching(false)
|
||||
return
|
||||
}
|
||||
payloadData[param.name] = val
|
||||
}
|
||||
params.payload = JSON.stringify(payloadData)
|
||||
}
|
||||
|
||||
const taskID = await AdminService.dispatchTask(params)
|
||||
|
||||
toast.success('任务下发成功', {
|
||||
@@ -189,6 +221,7 @@ export function TaskManager() {
|
||||
setStartTime(undefined)
|
||||
setEndTime(undefined)
|
||||
setUserId("")
|
||||
setParamValues({})
|
||||
} catch (err) {
|
||||
toast.error('任务下发失败', {
|
||||
description: err instanceof Error ? err.message : '未知错误'
|
||||
@@ -318,42 +351,81 @@ export function TaskManager() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedTaskType === 'order_sync' && (
|
||||
<>
|
||||
<div className="grid gap-2">
|
||||
<Label>开始时间</Label>
|
||||
<DatePickerWithTime date={startTime} setDate={setStartTime} />
|
||||
<p className="text-xs text-muted-foreground">选择同步的起始时间点</p>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label>结束时间</Label>
|
||||
<DatePickerWithTime date={endTime} setDate={setEndTime} />
|
||||
<p className="text-xs text-muted-foreground">选择同步的结束时间点</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{(() => {
|
||||
const targetTask = getSelectedTaskMeta()
|
||||
if (targetTask?.supports_time) {
|
||||
return (
|
||||
<>
|
||||
<div className="grid gap-2">
|
||||
<Label>开始时间</Label>
|
||||
<DatePickerWithTime date={startTime} setDate={setStartTime} />
|
||||
<p className="text-xs text-muted-foreground">选择同步的起始时间点</p>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label>结束时间</Label>
|
||||
<DatePickerWithTime date={endTime} setDate={setEndTime} />
|
||||
<p className="text-xs text-muted-foreground">选择同步的结束时间点</p>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return null
|
||||
})()}
|
||||
|
||||
{selectedTaskType === 'user_gamification' && (
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="user-id">用户 ID</Label>
|
||||
<Input
|
||||
id="user-id"
|
||||
type="number"
|
||||
placeholder="e.g. 10086"
|
||||
className="font-mono text-xs"
|
||||
value={userId}
|
||||
onChange={(e) => setUserId(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">指定需要更新积分数据的用户 ID</p>
|
||||
</div>
|
||||
)}
|
||||
{(() => {
|
||||
const targetTask = getSelectedTaskMeta()
|
||||
if (!targetTask || !targetTask.params || targetTask.params.length === 0) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{targetTask.params.map((param) => (
|
||||
<div key={param.name} className="grid gap-2">
|
||||
<Label htmlFor={`param-${param.name}`} className="flex items-center gap-1">
|
||||
{param.label}
|
||||
{param.required && <span className="text-destructive font-bold">*</span>}
|
||||
</Label>
|
||||
{param.type === 'text' ? (
|
||||
<Textarea
|
||||
id={`param-${param.name}`}
|
||||
placeholder={param.placeholder}
|
||||
className="text-xs min-h-[80px]"
|
||||
value={paramValues[param.name] || ""}
|
||||
onChange={(e) => setParamValues(prev => ({ ...prev, [param.name]: e.target.value }))}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
id={`param-${param.name}`}
|
||||
type={param.type === 'number' ? 'number' : 'text'}
|
||||
placeholder={param.placeholder}
|
||||
className="text-xs"
|
||||
value={paramValues[param.name] || ""}
|
||||
onChange={(e) => setParamValues(prev => ({ ...prev, [param.name]: e.target.value }))}
|
||||
/>
|
||||
)}
|
||||
{param.description && (
|
||||
<p className="text-[10px] text-muted-foreground">{param.description}</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
|
||||
{!['order_sync', 'user_gamification'].includes(selectedTaskType || '') && (
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground bg-muted/50 p-3 rounded-md border border-dashed">
|
||||
<Info className="h-4 w-4" />
|
||||
<span>此任务无需额外配置参数,确认后将直接下发。</span>
|
||||
</div>
|
||||
)}
|
||||
{(() => {
|
||||
const targetTask = getSelectedTaskMeta()
|
||||
const hasTime = targetTask?.supports_time
|
||||
const hasParams = targetTask?.params && targetTask.params.length > 0
|
||||
if (!hasTime && !hasParams) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground bg-muted/50 p-3 rounded-md border border-dashed">
|
||||
<Info className="h-4 w-4" />
|
||||
<span>此任务无需额外配置参数,确认后将直接下发。</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return null
|
||||
})()}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
|
||||
@@ -193,6 +193,14 @@ export class AdminService extends BaseService {
|
||||
supports_time: item.SupportsTime ?? item.supports_time ?? false,
|
||||
max_retry: item.MaxRetry ?? item.max_retry ?? 0,
|
||||
queue: item.Queue || item.queue || '',
|
||||
params: (item.Params || item.params || []).map(p => ({
|
||||
name: p.Name || p.name || '',
|
||||
label: p.Label || p.label || '',
|
||||
type: p.Type || p.type || '',
|
||||
required: p.Required ?? p.required ?? false,
|
||||
placeholder: p.Placeholder || p.placeholder || '',
|
||||
description: p.Description || p.description || '',
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,24 @@ export interface UpdateSystemConfigRequest {
|
||||
|
||||
// ==================== 任务管理 ====================
|
||||
|
||||
/**
|
||||
* 任务参数响应
|
||||
*/
|
||||
export interface TaskParamResponse {
|
||||
Name?: string;
|
||||
name?: string;
|
||||
Label?: string;
|
||||
label?: string;
|
||||
Type?: string;
|
||||
type?: string;
|
||||
Required?: boolean;
|
||||
required?: boolean;
|
||||
Placeholder?: string;
|
||||
placeholder?: string;
|
||||
Description?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务类型响应
|
||||
*/
|
||||
@@ -64,6 +82,20 @@ export interface TaskTypeResponse {
|
||||
max_retry?: number;
|
||||
Queue?: string;
|
||||
queue?: string;
|
||||
Params?: TaskParamResponse[];
|
||||
params?: TaskParamResponse[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务参数定义
|
||||
*/
|
||||
export interface TaskParam {
|
||||
name: string;
|
||||
label: string;
|
||||
type: string;
|
||||
required: boolean;
|
||||
placeholder: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,6 +116,8 @@ export interface TaskMeta {
|
||||
max_retry: number;
|
||||
/** 队列名称 */
|
||||
queue: string;
|
||||
/** 任务所需的自定义参数定义 */
|
||||
params?: TaskParam[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,6 +132,8 @@ export interface DispatchTaskRequest {
|
||||
end_time?: string;
|
||||
/** 用户 ID(可选,仅部分任务需要) */
|
||||
user_id?: string;
|
||||
/** 任务自定义参数 JSON 字符串 */
|
||||
payload?: string;
|
||||
}
|
||||
|
||||
export type TaskExecutionStatus = 'pending' | 'running' | 'succeeded' | 'failed';
|
||||
|
||||
Reference in New Issue
Block a user