style(frontend): 重排任务编辑器布局
- 未来运行时间改为全宽三列面板,次数选择器移到面板头部,去掉内部滚动条 - Cron / 超时、时区 / 任务类型两两成行,消除原右列的大片空白 - 校验改为提交时触发,避免 blur 插错误行导致下方控件位移、下拉框点击丢失
This commit is contained in:
+39
-10
@@ -211,10 +211,6 @@
|
|||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-grid-single {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-select {
|
.form-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@@ -236,15 +232,13 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cron-preview-box {
|
.cron-preview-panel {
|
||||||
display: flex;
|
display: flex;
|
||||||
max-height: 160px;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 6px;
|
gap: 10px;
|
||||||
overflow-y: auto;
|
|
||||||
border: 1px solid var(--su-border);
|
border: 1px solid var(--su-border);
|
||||||
border-radius: var(--su-radius-sm);
|
border-radius: var(--su-radius-sm);
|
||||||
padding: 10px 12px;
|
padding: 12px 14px;
|
||||||
background-color: var(--su-bg-subtle);
|
background-color: var(--su-bg-subtle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,15 +246,42 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 12px;
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cron-preview-title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cron-preview-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cron-preview-status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cron-count-select {
|
.cron-count-select {
|
||||||
min-width: 96px;
|
min-width: 96px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 未来运行时间按三列铺开:默认 5 条一眼看完,不需要内部滚动条 */
|
||||||
|
.cron-run-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 6px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.cron-run-item {
|
.cron-run-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
color: var(--su-text-secondary);
|
color: var(--su-text-secondary);
|
||||||
@@ -270,18 +291,22 @@
|
|||||||
|
|
||||||
.cron-run-index {
|
.cron-run-index {
|
||||||
width: 28px;
|
width: 28px;
|
||||||
|
flex: none;
|
||||||
color: var(--su-text-muted);
|
color: var(--su-text-muted);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cron-run-time {
|
.cron-run-time {
|
||||||
|
overflow: hidden;
|
||||||
border: 1px solid var(--su-border);
|
border: 1px solid var(--su-border);
|
||||||
border-radius: var(--su-radius-xs);
|
border-radius: var(--su-radius-xs);
|
||||||
padding: 2px 8px;
|
padding: 2px 8px;
|
||||||
background-color: var(--su-bg-card);
|
background-color: var(--su-bg-card);
|
||||||
color: var(--su-text-main);
|
color: var(--su-text-main);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cron-preview-error {
|
.cron-preview-error {
|
||||||
@@ -854,6 +879,10 @@
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cron-run-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
.argument-row,
|
.argument-row,
|
||||||
.key-value-row {
|
.key-value-row {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
Notice,
|
Notice,
|
||||||
PasswordInput,
|
PasswordInput,
|
||||||
Select,
|
Select,
|
||||||
|
Spinner,
|
||||||
Text,
|
Text,
|
||||||
Textarea,
|
Textarea,
|
||||||
TextButton,
|
TextButton,
|
||||||
@@ -97,7 +98,10 @@ export function TaskEditorModal({
|
|||||||
} = useForm<TaskFormValues>({
|
} = useForm<TaskFormValues>({
|
||||||
resolver: zodResolver(taskFormSchema),
|
resolver: zodResolver(taskFormSchema),
|
||||||
defaultValues: createEmptyTaskFormValues(),
|
defaultValues: createEmptyTaskFormValues(),
|
||||||
mode: 'onBlur',
|
// 长表单在 blur 时插错误行会让下面所有控件瞬间位移,刚点下去的按钮
|
||||||
|
// 可能在 mouseup 前被挪走(下拉框会「点了没反应」)。改为提交时校验,
|
||||||
|
// 提交后仍按 onChange 实时修正。
|
||||||
|
mode: 'onSubmit',
|
||||||
})
|
})
|
||||||
const actionType = watch('actionType')
|
const actionType = watch('actionType')
|
||||||
const cronValue = watch('cron')
|
const cronValue = watch('cron')
|
||||||
@@ -288,42 +292,6 @@ export function TaskEditorModal({
|
|||||||
placeholder="0 0 2 * * *"
|
placeholder="0 0 2 * * *"
|
||||||
aria-invalid={Boolean(errors.cron)}
|
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>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
@@ -348,7 +316,64 @@ export function TaskEditorModal({
|
|||||||
</FormField>
|
</FormField>
|
||||||
</div>
|
</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
|
<FormField
|
||||||
label="IANA 时区"
|
label="IANA 时区"
|
||||||
htmlFor="task-timezone"
|
htmlFor="task-timezone"
|
||||||
|
|||||||
Reference in New Issue
Block a user