7d003ff822
Extracted cron utilities to utils/schedule.js, all state management (15+ useState) to useSettingsForm hook, and 5 config sections + 2 dialogs + 1 status badge into focused components. SettingsPage reduced from ~700 lines to ~130 line container. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
110 lines
6.1 KiB
React
110 lines
6.1 KiB
React
// frontend/src/components/settings/ScheduleSection.jsx
|
|
import { CalendarClock, Clock, PlayCircle } from 'lucide-react';
|
|
import {
|
|
SCHEDULE_TYPE, DEFAULT_CRON, DEFAULT_TIME,
|
|
getScheduleSummary, buildDailyCron, buildWeeklyCron
|
|
} from '../../utils/schedule';
|
|
|
|
export default function ScheduleSection({ schedule = {}, onUpdate }) {
|
|
const enabled = schedule.enabled !== false;
|
|
const type = schedule.type || SCHEDULE_TYPE.DAILY;
|
|
const time = schedule.time || DEFAULT_TIME;
|
|
const dayOfWeek = schedule.dayOfWeek || '1';
|
|
const cron = schedule.cron || DEFAULT_CRON;
|
|
|
|
const setEnabled = (val) => onUpdate('schedule', { ...schedule, enabled: val });
|
|
const setType = (val) => {
|
|
if (val === SCHEDULE_TYPE.DAILY) {
|
|
onUpdate('schedule', { ...schedule, type: val, cron: buildDailyCron(time) });
|
|
} else if (val === SCHEDULE_TYPE.WEEKLY) {
|
|
onUpdate('schedule', { ...schedule, type: val, dayOfWeek, cron: buildWeeklyCron(dayOfWeek, time) });
|
|
} else {
|
|
onUpdate('schedule', { ...schedule, type: val, cron: schedule.cron || DEFAULT_CRON });
|
|
}
|
|
};
|
|
const setTime = (val) => {
|
|
const newCron = type === SCHEDULE_TYPE.WEEKLY
|
|
? buildWeeklyCron(dayOfWeek, val) : buildDailyCron(val);
|
|
onUpdate('schedule', { ...schedule, type, time: val, cron: newCron });
|
|
};
|
|
const setDay = (val) => {
|
|
onUpdate('schedule', { ...schedule, type, dayOfWeek: val, cron: buildWeeklyCron(val, time) });
|
|
};
|
|
const setCron = (val) => {
|
|
onUpdate('schedule', { ...schedule, type: SCHEDULE_TYPE.CRON, cron: val });
|
|
};
|
|
|
|
return (
|
|
<div className="bg-slate-900 border border-slate-800 rounded-xl p-6 shadow-lg">
|
|
<div className="flex justify-between items-center mb-5 border-b border-slate-800 pb-3">
|
|
<h3 className="text-base font-semibold text-white flex items-center">
|
|
<CalendarClock className="w-5 h-5 mr-2 text-amber-400" />
|
|
自动化定时任务
|
|
</h3>
|
|
<label className="flex items-center cursor-pointer">
|
|
<div className="relative">
|
|
<input type="checkbox" className="sr-only" checked={enabled} onChange={(e) => setEnabled(e.target.checked)} />
|
|
<div className={`block w-10 h-6 rounded-full transition-colors ${enabled ? 'bg-amber-500' : 'bg-slate-700'}`}></div>
|
|
<div className={`dot absolute left-1 top-1 bg-white w-4 h-4 rounded-full transition-transform ${enabled ? 'transform translate-x-4' : ''}`}></div>
|
|
</div>
|
|
<span className="ml-3 text-sm font-medium text-slate-300">{enabled ? '已启用自动入库' : '已暂停自动入库'}</span>
|
|
</label>
|
|
</div>
|
|
|
|
<div className={`flex flex-col gap-4 transition-opacity ${enabled ? 'opacity-100' : 'opacity-40 pointer-events-none'}`}>
|
|
<div className="flex flex-wrap gap-4 items-end">
|
|
<div className="w-44 shrink-0">
|
|
<label className="flex items-center text-xs font-medium text-slate-400 mb-1.5">执行频率</label>
|
|
<select className="w-full px-3 py-2 rounded bg-slate-950 border border-slate-700 text-slate-200 text-sm focus:ring-amber-500 focus:border-amber-500 focus:outline-none"
|
|
value={type} onChange={(e) => setType(e.target.value)}>
|
|
<option value={SCHEDULE_TYPE.DAILY}>每天执行</option>
|
|
<option value={SCHEDULE_TYPE.WEEKLY}>每周执行</option>
|
|
<option value={SCHEDULE_TYPE.CRON}>专家模式 (Cron)</option>
|
|
</select>
|
|
</div>
|
|
|
|
{type === SCHEDULE_TYPE.WEEKLY && (
|
|
<div className="w-32 shrink-0">
|
|
<label className="flex items-center text-xs font-medium text-slate-400 mb-1.5">星期</label>
|
|
<select className="w-full px-3 py-2 rounded bg-slate-950 border border-slate-700 text-slate-200 text-sm focus:ring-amber-500 focus:border-amber-500 focus:outline-none"
|
|
value={dayOfWeek} onChange={(e) => setDay(e.target.value)}>
|
|
<option value="1">星期一</option><option value="2">星期二</option>
|
|
<option value="3">星期三</option><option value="4">星期四</option>
|
|
<option value="5">星期五</option><option value="6">星期六</option>
|
|
<option value="0">星期日</option>
|
|
</select>
|
|
</div>
|
|
)}
|
|
|
|
{type !== SCHEDULE_TYPE.CRON && (
|
|
<div className="w-32 shrink-0">
|
|
<label className="flex items-center text-xs font-medium text-slate-400 mb-1.5">具体时间</label>
|
|
<input type="time" className="w-full px-3 py-2 rounded bg-slate-950 border border-slate-700 text-slate-200 text-sm font-mono focus:ring-amber-500 focus:border-amber-500 focus:outline-none"
|
|
style={{ colorScheme: 'dark' }} value={time} onChange={(e) => setTime(e.target.value)} />
|
|
</div>
|
|
)}
|
|
|
|
{type === SCHEDULE_TYPE.CRON && (
|
|
<div className="flex-1 min-w-[200px]">
|
|
<label className="flex items-center text-xs font-medium text-slate-400 mb-1.5">Cron 表达式</label>
|
|
<input type="text" className="w-full px-3 py-2 rounded bg-slate-950 border border-slate-700 text-amber-400 text-sm font-mono focus:ring-amber-500 focus:border-amber-500 focus:outline-none"
|
|
value={cron} onChange={(e) => setCron(e.target.value)} placeholder="例如: 0 2 * * *" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between gap-4 rounded-xl border border-slate-800/90 bg-[#0b1328] px-4 py-3.5">
|
|
<div className="flex min-w-0 items-center text-sm">
|
|
<Clock className="mr-2 h-4 w-4 shrink-0 text-amber-400" />
|
|
<span className="mr-2 shrink-0 text-slate-200">解析结果:</span>
|
|
<span className="truncate font-medium text-amber-400">{getScheduleSummary(schedule)}</span>
|
|
</div>
|
|
<button className="flex shrink-0 items-center justify-center rounded-lg border border-slate-600 bg-slate-700/70 px-4 py-2 text-sm font-medium text-slate-100 shadow-inner shadow-slate-950/30 transition hover:bg-slate-600/80">
|
|
<PlayCircle className="mr-1.5 h-4 w-4" /> 立即运行测试
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|