正计时 added temporary by AI
This commit is contained in:
@@ -865,3 +865,9 @@ html, body {
|
|||||||
.cd-item .cd-remain.overdue {
|
.cd-item .cd-remain.overdue {
|
||||||
color: var(--accent-red);
|
color: var(--accent-red);
|
||||||
}
|
}
|
||||||
|
/* ── Count-up timer (正计时) ──────────────── */
|
||||||
|
.countup-section { margin-top: clamp(8px, 1.5vh, 18px); }
|
||||||
|
.countup-label { font-size: clamp(12px, 1.6vw, 18px); color: var(--text-dim); margin-bottom: clamp(2px, 0.4vh, 8px); }
|
||||||
|
#countup-value { font-size: clamp(24px, 5vw, 64px); margin-bottom: 0; }
|
||||||
|
.countup-btns { display: flex; gap: clamp(10px, 1.5vw, 20px); margin-top: clamp(8px, 1.2vh, 16px); }
|
||||||
|
.countup-btns .pomo-btn { padding: clamp(6px, 1vh, 12px) clamp(20px, 3vw, 44px); }
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
_cdModalMode: false, // true = countdown modal, false = alarm modal
|
_cdModalMode: false, // true = countdown modal, false = alarm modal
|
||||||
_editingCountdown: null,
|
_editingCountdown: null,
|
||||||
timezone: 'Asia/Shanghai', // 账户独立时区(来自 /api/display/state)
|
timezone: 'Asia/Shanghai', // 账户独立时区(来自 /api/display/state)
|
||||||
|
countUp: { status: 'idle', startedAtMs: null, baseSec: 0, frozenSec: 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
// ─── DOM refs ─────────────────────────────
|
// ─── DOM refs ─────────────────────────────
|
||||||
@@ -107,6 +108,10 @@
|
|||||||
if (name === 'pomodoro') {
|
if (name === 'pomodoro') {
|
||||||
fetchPomodoro();
|
fetchPomodoro();
|
||||||
}
|
}
|
||||||
|
// Immediately render count-up when switching back to time panel
|
||||||
|
if (name === 'time') {
|
||||||
|
renderCountUpPanel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cards.forEach(function (card) {
|
cards.forEach(function (card) {
|
||||||
@@ -1298,6 +1303,108 @@
|
|||||||
n.y + '年' + n.mo + '月' + n.d + '日 星期' + days[n.wd];
|
n.y + '年' + n.mo + '月' + n.d + '日 星期' + days[n.wd];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _calcCountUpElapsed() {
|
||||||
|
var c = state.countUp;
|
||||||
|
if (!c) return 0;
|
||||||
|
if (c.status !== 'running') return c.frozenSec || 0;
|
||||||
|
try {
|
||||||
|
if (c.startedAtMs == null) return c.frozenSec || 0;
|
||||||
|
return (c.baseSec || 0) + Math.floor((Date.now() - c.startedAtMs) / 1000);
|
||||||
|
} catch (_) {
|
||||||
|
return c.frozenSec || 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _formatCountUp(totalSec) {
|
||||||
|
if (typeof totalSec !== 'number' || !isFinite(totalSec) || totalSec < 0) {
|
||||||
|
return '00:00:00';
|
||||||
|
}
|
||||||
|
var h = Math.floor(totalSec / 3600);
|
||||||
|
var m = Math.floor((totalSec % 3600) / 60);
|
||||||
|
var s = totalSec % 60;
|
||||||
|
return _pad2(h) + ':' + _pad2(m) + ':' + _pad2(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCountUpPanel() {
|
||||||
|
try {
|
||||||
|
var c = state.countUp || {};
|
||||||
|
var el = $('countup-value');
|
||||||
|
if (!el) return;
|
||||||
|
el.textContent = _formatCountUp(_calcCountUpElapsed());
|
||||||
|
var btnStart = $('countup-start');
|
||||||
|
var btnStop = $('countup-stop');
|
||||||
|
var btnReset = $('countup-reset');
|
||||||
|
var running = c.status === 'running';
|
||||||
|
if (btnStart) btnStart.style.display = running ? 'none' : '';
|
||||||
|
if (btnReset) btnReset.style.display = running ? 'none' : '';
|
||||||
|
if (btnStop) btnStop.style.display = running ? '' : 'none';
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('countUp render failed:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function countUpStart() {
|
||||||
|
try {
|
||||||
|
var c = state.countUp;
|
||||||
|
if (c.status === 'running') return;
|
||||||
|
c.baseSec = 0;
|
||||||
|
c.startedAtMs = Date.now();
|
||||||
|
c.status = 'running';
|
||||||
|
renderCountUpPanel();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('countUp start failed:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function countUpStop() {
|
||||||
|
try {
|
||||||
|
var c = state.countUp;
|
||||||
|
if (c.status !== 'running') return;
|
||||||
|
c.frozenSec = _calcCountUpElapsed();
|
||||||
|
c.startedAtMs = null;
|
||||||
|
c.status = 'idle';
|
||||||
|
renderCountUpPanel();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('countUp stop failed:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function countUpReset() {
|
||||||
|
try {
|
||||||
|
var c = state.countUp;
|
||||||
|
c.frozenSec = 0;
|
||||||
|
c.startedAtMs = null;
|
||||||
|
c.status = 'idle';
|
||||||
|
renderCountUpPanel();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('countUp reset failed:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupCountUpControls() {
|
||||||
|
var btnStart = $('countup-start');
|
||||||
|
var btnStop = $('countup-stop');
|
||||||
|
var btnReset = $('countup-reset');
|
||||||
|
if (btnStart) {
|
||||||
|
btnStart.addEventListener('click', function (e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
countUpStart();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (btnStop) {
|
||||||
|
btnStop.addEventListener('click', function (e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
countUpStop();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (btnReset) {
|
||||||
|
btnReset.addEventListener('click', function (e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
countUpReset();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function renderWeatherPanel() {
|
function renderWeatherPanel() {
|
||||||
var w = state.weather || {};
|
var w = state.weather || {};
|
||||||
if (w.temp === undefined) {
|
if (w.temp === undefined) {
|
||||||
@@ -1621,6 +1728,7 @@
|
|||||||
renderCountdownCard();
|
renderCountdownCard();
|
||||||
renderPomodoroCard();
|
renderPomodoroCard();
|
||||||
if (state.activePanel === 'time') renderTimePanel();
|
if (state.activePanel === 'time') renderTimePanel();
|
||||||
|
if (state.activePanel === 'time') renderCountUpPanel();
|
||||||
if (state.activePanel === 'pomodoro') renderPomodoro();
|
if (state.activePanel === 'pomodoro') renderPomodoro();
|
||||||
if (state.activePanel === 'countdown') renderCountdownPanel();
|
if (state.activePanel === 'countdown') renderCountdownPanel();
|
||||||
// Refresh update schedule info every 10s to keep next-update times current
|
// Refresh update schedule info every 10s to keep next-update times current
|
||||||
@@ -1648,5 +1756,13 @@
|
|||||||
fetchState();
|
fetchState();
|
||||||
connectSSE();
|
connectSSE();
|
||||||
switchPanel('time');
|
switchPanel('time');
|
||||||
|
setupCountUpControls();
|
||||||
|
document.addEventListener('visibilitychange', function () {
|
||||||
|
try {
|
||||||
|
if (document.visibilityState === 'visible' && state.activePanel === 'time') {
|
||||||
|
renderCountUpPanel();
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -76,6 +76,15 @@
|
|||||||
<h2>时间</h2>
|
<h2>时间</h2>
|
||||||
<div id="detailed-time" class="big-clock">--:--:--</div>
|
<div id="detailed-time" class="big-clock">--:--:--</div>
|
||||||
<div class="date-str" id="date-str"></div>
|
<div class="date-str" id="date-str"></div>
|
||||||
|
<div class="countup-section">
|
||||||
|
<div class="countup-label">正计时</div>
|
||||||
|
<div id="countup-value" class="big-clock">00:00:00</div>
|
||||||
|
<div class="countup-btns">
|
||||||
|
<button id="countup-start" class="pomo-btn pomo-btn-primary countup-btn">启动</button>
|
||||||
|
<button id="countup-stop" class="pomo-btn pomo-btn-secondary countup-btn" style="display:none;">停止</button>
|
||||||
|
<button id="countup-reset" class="pomo-btn pomo-btn-secondary countup-btn">清零</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="panel-weather" class="panel-content">
|
<div id="panel-weather" class="panel-content">
|
||||||
<h2>天气</h2>
|
<h2>天气</h2>
|
||||||
|
|||||||
Reference in New Issue
Block a user