diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx
index e5b2923..7db3ffb 100644
--- a/frontend/src/pages/Dashboard.jsx
+++ b/frontend/src/pages/Dashboard.jsx
@@ -307,7 +307,10 @@ function CalendarBoard({ todos: seedTodos, onTodoCreated }) {
const [jumpOpen, setJumpOpen] = useState(false)
const [jumpPos, setJumpPos] = useState(null)
const [jumpMonth, setJumpMonth] = useState(() => new Date(today.getFullYear(), today.getMonth(), 1))
- // 年月拨盘:工具条月份文字与跳转浮层标题共用
+ // 跳转浮层标题:直接输入年月(前 4 位年 + 后 1~2 位月,回车跳转)
+ const [jumpDraft, setJumpDraft] = useState(null)
+ const [jumpInvalid, setJumpInvalid] = useState(false)
+ // 年月拨盘:仅工具条月份文字使用
const [wheelOpen, setWheelOpen] = useState(false)
const [wheelPos, setWheelPos] = useState(null)
const [wheelPeriod, setWheelPeriod] = useState(() => ({ year: today.getFullYear(), month: today.getMonth() + 1 }))
@@ -316,6 +319,7 @@ function CalendarBoard({ todos: seedTodos, onTodoCreated }) {
const tipRef = useRef(null)
const jumpRef = useRef(null)
const jumpBtnRef = useRef(null)
+ const jumpInputRef = useRef(null)
const wheelRef = useRef(null)
const wheelAnchorRef = useRef(null)
const anchorElRef = useRef(null)
@@ -577,6 +581,42 @@ function CalendarBoard({ todos: seedTodos, onTodoCreated }) {
const wheelGoToday = () => applyWheelPeriod(today.getFullYear(), today.getMonth() + 1)
+ /* 年月两种写法:「2026年9月」↔「202609」 */
+ const compactPeriod = (date) => `${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}`
+
+ /* 进入编辑态:换成紧凑数字串并全选,方便直接改;已在编辑中就不打断输入 */
+ const primeJumpInput = () => {
+ if (jumpDraft !== null) return
+ setJumpInvalid(false)
+ setJumpDraft(compactPeriod(jumpMonth))
+ requestAnimationFrame(() => jumpInputRef.current?.select())
+ }
+
+ const changeJumpInput = (event) => {
+ setJumpDraft(event.target.value.replace(/\D/g, '').slice(0, 6))
+ if (jumpInvalid) setJumpInvalid(false)
+ }
+
+ /* 前 4 位是年,剩下 1~2 位是月;只写 4 位则沿用当前月 */
+ const commitJumpInput = () => {
+ const digits = (jumpDraft ?? '').replace(/\D/g, '')
+ if (digits.length < 4) { setJumpInvalid(true); return false }
+ const year = Number(digits.slice(0, 4))
+ const tail = digits.slice(4)
+ const monthNum = tail ? Number(tail) : jumpMonth.getMonth() + 1
+ if (year < 1900 || year > 2999 || monthNum < 1 || monthNum > 12) { setJumpInvalid(true); return false }
+ setJumpMonth(new Date(year, monthNum - 1, 1))
+ setJumpInvalid(false)
+ setJumpDraft(null)
+ jumpInputRef.current?.blur()
+ return true
+ }
+
+ const cancelJumpInput = () => {
+ setJumpDraft(null)
+ setJumpInvalid(false)
+ }
+
// 点空白处收起跳转浮层(点按钮本身交给 toggleJump 处理)
useEffect(() => {
if (!jumpOpen) return undefined
@@ -878,7 +918,26 @@ function CalendarBoard({ todos: seedTodos, onTodoCreated }) {
>
-
+ {
+ if (event.key === 'Enter') { event.preventDefault(); commitJumpInput(); return }
+ if (event.key === 'Escape') { event.stopPropagation(); event.preventDefault(); cancelJumpInput(); event.currentTarget.blur() }
+ }}
+ onBlur={cancelJumpInput}
+ inputMode="numeric"
+ autoComplete="off"
+ spellCheck={false}
+ aria-label="输入年月快速跳转"
+ aria-invalid={jumpInvalid || undefined}
+ title="前 4 位是年、后 1~2 位是月,如 202609,回车跳转"
+ />
{WEEKDAYS.map(day => {day.slice(1)})}
diff --git a/frontend/src/styles/luxury.css b/frontend/src/styles/luxury.css
index f9a950b..f537526 100644
--- a/frontend/src/styles/luxury.css
+++ b/frontend/src/styles/luxury.css
@@ -438,9 +438,12 @@ input:focus-visible, select:focus-visible, textarea:focus-visible { outline: 2px
.jump-day.is-selected { border-color: var(--primary); background: var(--primary); color: var(--surface-deep); font-weight: 800; }
.jump-today { width: 100%; height: 27px; margin-top: 8px; border: 1px solid var(--border-light); border-radius: 7px; background: var(--surface-deep); color: var(--text-secondary); font-size: 11px; cursor: pointer; }
.jump-today:hover { border-color: var(--primary); color: var(--primary-light); }
-/* 跳转浮层标题 = 可点的年月(弹出拨盘) */
-.jump-head .jump-month-btn { display: inline-flex; align-items: center; justify-content: center; flex: 1; width: auto; min-width: 0; height: 24px; padding: 0 8px; border-radius: 7px; color: var(--text-primary); font-size: 12px; font-variant-numeric: tabular-nums; }
-.jump-head .jump-month-btn:hover { border-color: var(--primary-border); background: var(--primary-bg); color: var(--primary-light); }
+/* 跳转浮层标题 = 直接输入年月(前 4 位年 + 后 1~2 位月,回车跳转) */
+.jump-head .jump-month-input { display: block; flex: 1; width: auto; min-width: 0; min-height: 0; height: 26px; padding: 0 8px; border: 1px solid var(--border-light); border-radius: 7px; background: var(--surface-deep); box-shadow: none; color: var(--text-primary); font: 12px var(--font-body); font-variant-numeric: tabular-nums; text-align: center; outline: none; cursor: text; transition: border-color var(--transition-fast), background var(--transition-fast), color var(--transition-fast); }
+.jump-head .jump-month-input:hover { border-color: var(--primary-border); background: var(--primary-bg); }
+.jump-head .jump-month-input:focus { border-color: var(--primary); background: var(--primary-bg); color: var(--primary-light); font-weight: 700; letter-spacing: .3px; }
+.jump-head .jump-month-input.is-invalid { border-color: var(--danger); background: var(--danger-bg); color: var(--danger); }
+.jump-head .jump-month-input::selection { background: var(--primary); color: var(--surface-deep); }
/* 年月拨盘(iOS 闹钟式滚轮) */
.calendar-wheel-pop { --wheel-item: 32px; padding: 12px 14px 10px; }
.ymw-body { display: grid; gap: 9px; }