style(frontend): 重排任务编辑器布局
- 未来运行时间改为全宽三列面板,次数选择器移到面板头部,去掉内部滚动条 - Cron / 超时、时区 / 任务类型两两成行,消除原右列的大片空白 - 校验改为提交时触发,避免 blur 插错误行导致下方控件位移、下拉框点击丢失
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
Notice,
|
||||
PasswordInput,
|
||||
Select,
|
||||
Spinner,
|
||||
Text,
|
||||
Textarea,
|
||||
TextButton,
|
||||
@@ -97,7 +98,10 @@ export function TaskEditorModal({
|
||||
} = useForm<TaskFormValues>({
|
||||
resolver: zodResolver(taskFormSchema),
|
||||
defaultValues: createEmptyTaskFormValues(),
|
||||
mode: 'onBlur',
|
||||
// 长表单在 blur 时插错误行会让下面所有控件瞬间位移,刚点下去的按钮
|
||||
// 可能在 mouseup 前被挪走(下拉框会「点了没反应」)。改为提交时校验,
|
||||
// 提交后仍按 onChange 实时修正。
|
||||
mode: 'onSubmit',
|
||||
})
|
||||
const actionType = watch('actionType')
|
||||
const cronValue = watch('cron')
|
||||
@@ -288,42 +292,6 @@ export function TaskEditorModal({
|
||||
placeholder="0 0 2 * * *"
|
||||
aria-invalid={Boolean(errors.cron)}
|
||||
/>
|
||||
<div className="cron-preview-box" aria-live="polite">
|
||||
<div className="cron-preview-head">
|
||||
<Text size="xs" tone="muted">
|
||||
预览未来运行时间
|
||||
</Text>
|
||||
<Select
|
||||
className="cron-count-select"
|
||||
options={CRON_PREVIEW_COUNTS.map((count) => ({
|
||||
value: String(count),
|
||||
label: `${count} 次`,
|
||||
}))}
|
||||
value={String(cronPreviewCount)}
|
||||
onChange={(value) => setCronPreviewCount(Number(value))}
|
||||
/>
|
||||
</div>
|
||||
{cronPreview.isFetching ? (
|
||||
<Text size="sm" tone="sub">
|
||||
正在计算...
|
||||
</Text>
|
||||
) : cronPreview.isError ? (
|
||||
<Text size="sm" className="cron-preview-error">
|
||||
{cronPreview.error.message}
|
||||
</Text>
|
||||
) : cronPreview.data?.runs.length ? (
|
||||
cronPreview.data.runs.map((run, index) => (
|
||||
<div className="cron-run-item" key={run}>
|
||||
<span className="cron-run-index">#{index + 1}</span>
|
||||
<span className="cron-run-time">{run}</span>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<Text size="sm" tone="sub">
|
||||
等待输入有效 Cron 表达式...
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
@@ -348,7 +316,64 @@ export function TaskEditorModal({
|
||||
</FormField>
|
||||
</div>
|
||||
|
||||
<div className="form-grid form-grid-single">
|
||||
<section
|
||||
className="cron-preview-panel"
|
||||
aria-label="未来运行时间"
|
||||
aria-live="polite"
|
||||
>
|
||||
<div className="cron-preview-head">
|
||||
<div className="cron-preview-title">
|
||||
<Text strong size="sm">
|
||||
未来运行时间
|
||||
</Text>
|
||||
<Text size="xs" tone="muted">
|
||||
按当前 Cron 与时区实时计算
|
||||
</Text>
|
||||
</div>
|
||||
<div className="cron-preview-actions">
|
||||
<Text size="xs" tone="muted">
|
||||
预览
|
||||
</Text>
|
||||
<Select
|
||||
className="cron-count-select"
|
||||
options={CRON_PREVIEW_COUNTS.map((count) => ({
|
||||
value: String(count),
|
||||
label: `${count} 次`,
|
||||
}))}
|
||||
value={String(cronPreviewCount)}
|
||||
onChange={(value) => setCronPreviewCount(Number(value))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{cronPreview.isFetching ? (
|
||||
<div className="cron-preview-status">
|
||||
<Spinner size="sm" label="正在计算" />
|
||||
<Text size="sm" tone="sub">
|
||||
正在计算...
|
||||
</Text>
|
||||
</div>
|
||||
) : cronPreview.isError ? (
|
||||
<Text size="sm" className="cron-preview-error">
|
||||
{cronPreview.error.message}
|
||||
</Text>
|
||||
) : cronPreview.data?.runs.length ? (
|
||||
<div className="cron-run-grid">
|
||||
{cronPreview.data.runs.map((run, index) => (
|
||||
<div className="cron-run-item" key={run}>
|
||||
<span className="cron-run-index">#{index + 1}</span>
|
||||
<span className="cron-run-time">{run}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Text size="sm" tone="sub">
|
||||
等待输入有效 Cron 表达式...
|
||||
</Text>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<div className="form-grid">
|
||||
<FormField
|
||||
label="IANA 时区"
|
||||
htmlFor="task-timezone"
|
||||
|
||||
Reference in New Issue
Block a user