fix(schedules): 单次显示「当天」、收窄重复方式下拉
单次执行不再出现「重复方式」;「每天」改为「当天」;循环模式下拉宽度收窄。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
# 审计 — 调度周期「当天」文案与单次 UI(2026-06-08)
|
||||
|
||||
**Changelog**: `docs/changelog/2026-06-08-schedule-cycle-day-label-once-ui.md`
|
||||
|
||||
## 审计范围
|
||||
|
||||
| 模块 | 文件 |
|
||||
|------|------|
|
||||
| 周期工具 | `frontend/src/utils/scheduleCycle.ts` |
|
||||
| 选择器 | `frontend/src/components/schedules/ScheduleCyclePicker.vue` |
|
||||
| 调度页 | `frontend/src/pages/SchedulesPage.vue` |
|
||||
| 后端镜像 | `server/utils/schedule_cycle.py` |
|
||||
| 单测 | `tests/test_schedule_cycle.py` |
|
||||
|
||||
## 安全
|
||||
|
||||
| 项 | 结论 |
|
||||
|----|------|
|
||||
| 无新 API | PASS — 仅文案与 UI 布局 |
|
||||
| Cron 逻辑 | PASS — 未改 build/parse 规则 |
|
||||
| 鉴权 | PASS — 无变更 |
|
||||
|
||||
## Step 3
|
||||
|
||||
| H | 规则 | 结论 |
|
||||
|---|------|------|
|
||||
| H1 | 单次不显示「重复方式」 | PASS — singleShot 固定「当天」 |
|
||||
| H2 | 循环保留下拉 | PASS — 宽度 7.5rem |
|
||||
| H3 | 标签「当天」 | PASS — 前后端 + 单测一致 |
|
||||
|
||||
## Closure
|
||||
|
||||
| H | 判定 |
|
||||
|---|------|
|
||||
| H1–H3 | PASS |
|
||||
|
||||
## DoD
|
||||
|
||||
- [x] pytest tests/test_schedule_cycle.py
|
||||
- [x] frontend vite build
|
||||
- [x] changelog
|
||||
@@ -0,0 +1,26 @@
|
||||
# 2026-06-08 — 调度周期:当天文案与单次 UI
|
||||
|
||||
## 摘要
|
||||
|
||||
- 「每天」改为「当天」(列表预览同步)
|
||||
- 单次执行:固定显示「当天 + 时:分」,不再出现「重复方式」下拉
|
||||
- 循环执行:保留「重复方式」下拉,宽度收窄(约 7.5rem)
|
||||
|
||||
## 动机
|
||||
|
||||
用户反馈「每天」窗口过宽、语义应为当天;单次不应标注「重复方式」。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `frontend/src/components/schedules/ScheduleCyclePicker.vue`
|
||||
- `frontend/src/utils/scheduleCycle.ts`
|
||||
- `server/utils/schedule_cycle.py`
|
||||
- `tests/test_schedule_cycle.py`
|
||||
- `frontend/src/pages/SchedulesPage.vue`
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
pytest tests/test_schedule_cycle.py -q
|
||||
cd frontend && npm run build
|
||||
```
|
||||
@@ -3,8 +3,10 @@
|
||||
<div class="text-caption text-medium-emphasis mb-2">开始执行周期</div>
|
||||
|
||||
<v-row dense class="mb-2 align-center">
|
||||
<v-col cols="12" sm="auto" style="min-width: 140px; flex: 1 1 140px">
|
||||
<v-col cols="auto" class="cycle-type-col">
|
||||
<span v-if="singleShot" class="cycle-type-fixed text-body-2">当天</span>
|
||||
<v-select
|
||||
v-else
|
||||
:model-value="modelValue.type === 'custom' ? null : modelValue.type"
|
||||
:items="cycleTypeOptions"
|
||||
item-title="label"
|
||||
@@ -13,13 +15,14 @@
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details
|
||||
class="cycle-type-select"
|
||||
@update:model-value="onTypeChange"
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<!-- 每天 / 每 N 天 / 每周 / 每月:时:分 -->
|
||||
<!-- 当天 / 每 N 天 / 每周 / 每月:时:分 -->
|
||||
<template v-if="showTimeOfDay">
|
||||
<v-col cols="6" sm="auto" style="max-width: 88px">
|
||||
<v-col cols="auto" class="time-col">
|
||||
<v-text-field
|
||||
:model-value="modelValue.hour"
|
||||
label="时"
|
||||
@@ -32,7 +35,7 @@
|
||||
@update:model-value="patch({ hour: toNum($event, 0, 23) })"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="6" sm="auto" style="max-width: 88px">
|
||||
<v-col cols="auto" class="time-col">
|
||||
<v-text-field
|
||||
:model-value="modelValue.minute"
|
||||
label="分"
|
||||
@@ -48,8 +51,8 @@
|
||||
</template>
|
||||
|
||||
<!-- 每小时 / 每 N 小时:第几分 -->
|
||||
<template v-if="modelValue.type === 'hour' || modelValue.type === 'hour-n'">
|
||||
<v-col cols="6" sm="auto" style="max-width: 120px">
|
||||
<template v-if="!singleShot && (modelValue.type === 'hour' || modelValue.type === 'hour-n')">
|
||||
<v-col cols="auto" class="time-col-wide">
|
||||
<v-text-field
|
||||
:model-value="modelValue.minute"
|
||||
label="第几分"
|
||||
@@ -71,7 +74,7 @@
|
||||
|
||||
<!-- 每 N 天 -->
|
||||
<v-text-field
|
||||
v-if="modelValue.type === 'day-n'"
|
||||
v-if="!singleShot && modelValue.type === 'day-n'"
|
||||
:model-value="modelValue.interval"
|
||||
label="天数 N"
|
||||
type="number"
|
||||
@@ -87,7 +90,7 @@
|
||||
|
||||
<!-- 每 N 小时 -->
|
||||
<v-text-field
|
||||
v-if="modelValue.type === 'hour-n'"
|
||||
v-if="!singleShot && modelValue.type === 'hour-n'"
|
||||
:model-value="modelValue.interval"
|
||||
label="小时 N"
|
||||
type="number"
|
||||
@@ -101,7 +104,7 @@
|
||||
|
||||
<!-- 每 N 分钟 -->
|
||||
<v-text-field
|
||||
v-if="modelValue.type === 'minute-n'"
|
||||
v-if="!singleShot && modelValue.type === 'minute-n'"
|
||||
:model-value="modelValue.interval"
|
||||
label="分钟 N"
|
||||
type="number"
|
||||
@@ -117,7 +120,7 @@
|
||||
|
||||
<!-- 每周 -->
|
||||
<v-select
|
||||
v-if="modelValue.type === 'week'"
|
||||
v-if="!singleShot && modelValue.type === 'week'"
|
||||
:model-value="modelValue.week"
|
||||
:items="WEEKDAY_OPTIONS"
|
||||
item-title="label"
|
||||
@@ -131,7 +134,7 @@
|
||||
|
||||
<!-- 每月 -->
|
||||
<v-text-field
|
||||
v-if="modelValue.type === 'month'"
|
||||
v-if="!singleShot && modelValue.type === 'month'"
|
||||
:model-value="modelValue.monthDay"
|
||||
label="每月几号"
|
||||
type="number"
|
||||
@@ -169,7 +172,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, watch } from 'vue'
|
||||
import {
|
||||
CYCLE_TYPE_OPTIONS,
|
||||
WEEKDAY_OPTIONS,
|
||||
@@ -195,17 +198,30 @@ const cycleTypeOptions = CYCLE_TYPE_OPTIONS
|
||||
|
||||
const singleShot = computed(() => props.singleShot === true)
|
||||
|
||||
const showTimeOfDay = computed(() =>
|
||||
['day', 'day-n', 'week', 'month'].includes(props.modelValue.type),
|
||||
)
|
||||
const showTimeOfDay = computed(() => {
|
||||
if (singleShot.value) return true
|
||||
return ['day', 'day-n', 'week', 'month'].includes(props.modelValue.type)
|
||||
})
|
||||
|
||||
const cycleHint = computed(() => {
|
||||
if (singleShot.value) {
|
||||
return '单次:在下一匹配时刻执行一次(精确到时:分,不含日期)'
|
||||
return '单次:当天指定时:分,在下一匹配时刻执行一次(不含日期选择)'
|
||||
}
|
||||
return '循环:按周期重复执行(精确到时:分)'
|
||||
return '循环:按重复方式与时:分持续执行'
|
||||
})
|
||||
|
||||
/** 单次模式固定为「当天」时:分,不出现「重复方式」 */
|
||||
watch(
|
||||
() => props.singleShot,
|
||||
(isOnce) => {
|
||||
if (!isOnce) return
|
||||
if (props.modelValue.type === 'custom') return
|
||||
if (props.modelValue.type === 'day') return
|
||||
emit('update:modelValue', { ...defaultCycleConfig(), ...props.modelValue, type: 'day' })
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const previewLabel = computed(() => {
|
||||
try {
|
||||
return formatCycleLabel(props.modelValue)
|
||||
@@ -243,3 +259,31 @@ function switchToVisual() {
|
||||
emit('update:modelValue', { ...defaultCycleConfig(), type: 'day' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cycle-type-col {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.cycle-type-select {
|
||||
width: 7.5rem;
|
||||
max-width: 7.5rem;
|
||||
}
|
||||
.cycle-type-fixed {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
padding: 0 4px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.time-col {
|
||||
flex: 0 0 auto;
|
||||
width: 4.5rem;
|
||||
max-width: 4.5rem;
|
||||
}
|
||||
.time-col-wide {
|
||||
flex: 0 0 auto;
|
||||
width: 5.5rem;
|
||||
max-width: 5.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
/>
|
||||
|
||||
<v-alert type="info" density="compact" variant="tonal" class="mb-3 text-caption">
|
||||
上方「开始执行周期」含重复方式与时:分。同步模式、目标路径、脚本超时与长任务等执行参数按系统设置及各服务器配置,请在「推送」「脚本库」「系统设置」中调整。
|
||||
单次执行选「当天」时:分;循环执行再选重复方式。同步模式、目标路径等按系统与各服务器配置。
|
||||
</v-alert>
|
||||
|
||||
<template v-if="form.schedule_type === 'push'">
|
||||
|
||||
@@ -26,7 +26,7 @@ export interface ScheduleCycleConfig {
|
||||
}
|
||||
|
||||
export const CYCLE_TYPE_OPTIONS: { value: ScheduleCycleType; label: string }[] = [
|
||||
{ value: 'day', label: '每天' },
|
||||
{ value: 'day', label: '当天' },
|
||||
{ value: 'day-n', label: '每 N 天' },
|
||||
{ value: 'hour', label: '每小时' },
|
||||
{ value: 'hour-n', label: '每 N 小时' },
|
||||
@@ -219,7 +219,7 @@ export function formatCycleLabel(input: ScheduleCycleConfig | string): string {
|
||||
|
||||
switch (config.type) {
|
||||
case 'day':
|
||||
return `每天 ${time}`
|
||||
return `当天 ${time}`
|
||||
case 'day-n':
|
||||
return `每 ${config.interval} 天 ${time}`
|
||||
case 'hour':
|
||||
|
||||
@@ -132,7 +132,7 @@ def format_cycle_label(cron_or_config: str | dict[str, Any]) -> str:
|
||||
time_s = f"{hour:02d}:{minute:02d}"
|
||||
|
||||
if kind == "day":
|
||||
return f"每天 {time_s}"
|
||||
return f"当天 {time_s}"
|
||||
if kind == "day-n":
|
||||
return f"每 {config.get('interval', 1)} 天 {time_s}"
|
||||
if kind == "hour":
|
||||
|
||||
@@ -69,7 +69,7 @@ def test_parse_cron_roundtrip_type(cron, expected_type):
|
||||
|
||||
|
||||
def test_format_cycle_label_day():
|
||||
assert format_cycle_label("0 2 * * *") == "每天 02:00"
|
||||
assert format_cycle_label("0 2 * * *") == "当天 02:00"
|
||||
|
||||
|
||||
def test_unknown_cron_custom():
|
||||
|
||||
Reference in New Issue
Block a user