定时任务操作添加点击反馈和文案提示
- 启用/禁用/立即执行/删除按钮添加 loading 状态(Spinner) - 操作成功后显示绿色提示(如「已启用任务 xxx」) - 操作失败后显示红色错误提示 - 按钮禁用期间防止重复点击 Co-Authored-By: new-provider/LongCat-2.0 <<EMAIL>>
This commit is contained in:
co-authored by
new-provider/LongCat-2.0 <
parent
0a5a14cbbb
commit
7a4e61ebc4
@@ -54,6 +54,8 @@ export default function SettingsPage() {
|
|||||||
// 定时任务
|
// 定时任务
|
||||||
const [schedules, setSchedules] = useState<ScheduleItem[]>([])
|
const [schedules, setSchedules] = useState<ScheduleItem[]>([])
|
||||||
const [schedulesLoading, setSchedulesLoading] = useState(true)
|
const [schedulesLoading, setSchedulesLoading] = useState(true)
|
||||||
|
const [scheduleNotice, setScheduleNotice] = useState<{ ok: boolean; text: string } | null>(null)
|
||||||
|
const [scheduleBusyId, setScheduleBusyId] = useState<string | null>(null)
|
||||||
|
|
||||||
// ── 数据加载 ──
|
// ── 数据加载 ──
|
||||||
const loadSettings = useCallback(async () => {
|
const loadSettings = useCallback(async () => {
|
||||||
@@ -132,6 +134,48 @@ export default function SettingsPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── 定时任务操作 ──
|
||||||
|
const handleToggleSchedule = async (s: ScheduleItem) => {
|
||||||
|
setScheduleBusyId(s.id)
|
||||||
|
setScheduleNotice(null)
|
||||||
|
try {
|
||||||
|
await updateSchedule(s.id, { enabled: !s.enabled })
|
||||||
|
setScheduleNotice({ ok: true, text: `已${!s.enabled ? '启用' : '禁用'}任务「${s.id}」` })
|
||||||
|
setSchedules(prev => prev.map(x => x.id === s.id ? { ...x, enabled: !x.enabled } : x))
|
||||||
|
} catch {
|
||||||
|
setScheduleNotice({ ok: false, text: '操作失败' })
|
||||||
|
} finally {
|
||||||
|
setScheduleBusyId(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRunSchedule = async (s: ScheduleItem) => {
|
||||||
|
setScheduleBusyId(s.id)
|
||||||
|
setScheduleNotice(null)
|
||||||
|
try {
|
||||||
|
await runScheduleNow(s.id)
|
||||||
|
setScheduleNotice({ ok: true, text: `任务「${s.id}」已启动,请在日志页查看进度` })
|
||||||
|
} catch {
|
||||||
|
setScheduleNotice({ ok: false, text: '启动失败' })
|
||||||
|
} finally {
|
||||||
|
setScheduleBusyId(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteSchedule = async (s: ScheduleItem) => {
|
||||||
|
setScheduleBusyId(s.id)
|
||||||
|
setScheduleNotice(null)
|
||||||
|
try {
|
||||||
|
await deleteSchedule(s.id)
|
||||||
|
setScheduleNotice({ ok: true, text: `已删除任务「${s.id}」` })
|
||||||
|
setSchedules(prev => prev.filter(x => x.id !== s.id))
|
||||||
|
} catch {
|
||||||
|
setScheduleNotice({ ok: false, text: '删除失败' })
|
||||||
|
} finally {
|
||||||
|
setScheduleBusyId(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const detectLLMModels = useCallback(async (): Promise<string[]> => {
|
const detectLLMModels = useCallback(async (): Promise<string[]> => {
|
||||||
const r = await fetchLLMModels()
|
const r = await fetchLLMModels()
|
||||||
if (!r.ok) throw new Error(r.detail)
|
if (!r.ok) throw new Error(r.detail)
|
||||||
@@ -390,6 +434,11 @@ export default function SettingsPage() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
|
{scheduleNotice && (
|
||||||
|
<div className="mb-3">
|
||||||
|
<Alert kind={scheduleNotice.ok ? 'ok' : 'error'} title={scheduleNotice.text} onClose={() => setScheduleNotice(null)} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{schedulesLoading ? (
|
{schedulesLoading ? (
|
||||||
<SkeletonBlock className="h-10 w-full" />
|
<SkeletonBlock className="h-10 w-full" />
|
||||||
) : schedules.length === 0 ? (
|
) : schedules.length === 0 ? (
|
||||||
@@ -414,22 +463,25 @@ export default function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => runScheduleNow(s.id)}
|
onClick={() => handleRunSchedule(s)}
|
||||||
|
disabled={scheduleBusyId === s.id}
|
||||||
className="btn btn-sm"
|
className="btn btn-sm"
|
||||||
>
|
>
|
||||||
立即执行
|
{scheduleBusyId === s.id ? <Spinner /> : '立即执行'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => updateSchedule(s.id, { enabled: !s.enabled }).then(() => fetchSchedules().then(setSchedules))}
|
onClick={() => handleToggleSchedule(s)}
|
||||||
|
disabled={scheduleBusyId === s.id}
|
||||||
className={`btn btn-sm ${s.enabled ? '' : 'btn-solid'}`}
|
className={`btn btn-sm ${s.enabled ? '' : 'btn-solid'}`}
|
||||||
>
|
>
|
||||||
{s.enabled ? '禁用' : '启用'}
|
{scheduleBusyId === s.id ? <Spinner /> : (s.enabled ? '禁用' : '启用')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => deleteSchedule(s.id).then(() => fetchSchedules().then(setSchedules))}
|
onClick={() => handleDeleteSchedule(s)}
|
||||||
|
disabled={scheduleBusyId === s.id}
|
||||||
className="btn btn-sm btn-danger"
|
className="btn btn-sm btn-danger"
|
||||||
>
|
>
|
||||||
删除
|
{scheduleBusyId === s.id ? <Spinner /> : '删除'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user