空間番茄風(fēng)水鐘:用Cursor+Web打造專注時鐘)
1. 心流艙里的那塊表為什么值得自己寫一個第一次在付費(fèi)辦公空間看到那塊表的時候我盯著它看了好一會兒。左邊是一個番茄鐘25 分鐘倒計時紅圈一點(diǎn)點(diǎn)收攏右邊是一個普通時鐘時針分針照常走。兩塊表盤并排放在一塊小屏幕上既讓你盯住當(dāng)下這 25 分鐘又讓你隨時知道現(xiàn)在幾點(diǎn)、今天還剩多少時間。當(dāng)時那塊表標(biāo)價大概 10 美元我心想這東西邏輯這么簡單為什么不自己寫一個。心流艙、付費(fèi)自習(xí)室、共享辦公位這類空間核心賣點(diǎn)就是「幫你專注」。但真正坐進(jìn)去你會發(fā)現(xiàn)手機(jī)上的番茄鐘 App 一打開就容易被消息帶走手表又不想專門戴一塊。桌面上如果有一塊只干一件事的小屏幕——左邊倒計時、右邊看時間反而最不容易分心。這就是「番茄風(fēng)水鐘」這個開源項目的出發(fā)點(diǎn)用 Web 技術(shù)做一個雙表盤時鐘左邊番茄鐘右邊時鐘還能把右側(cè)表盤換成各種吉祥物圖案擺在桌面上既專注又有點(diǎn)意思。它適合誰適合想給自己桌面加一塊專注屏的人適合想練手 Cursor Web 前端的人也適合做智能硬件、桌面擺件、空間氛圍類產(chǎn)品的同學(xué)拿去做原型。整個項目就是純前端一個 HTML 加 JS 就能跑不需要后端、不需要數(shù)據(jù)庫本地雙擊就能打開。下面我會把項目結(jié)構(gòu)、Cursor 提示詞、可復(fù)制的配置和本地驗證步驟都寫清楚你跟著做就能得到一個能用的雙表盤時鐘。核心檢索詞先明確這是一個開源的番茄鐘 時鐘雙表盤 Web 項目用 Cursor 輔助開發(fā)可本地運(yùn)行、可自定義表盤適合桌面專注場景。2. 用 Cursor 搭雙表盤番茄鐘項目結(jié)構(gòu)與提示詞怎么寫先說清楚這個項目為什么用 Cursor 而不是純手寫。雙表盤時鐘的難點(diǎn)不在邏輯而在「布局 表盤繪制 狀態(tài)同步」這三塊反復(fù)調(diào)。番茄鐘要處理倒計時、暫停、重置、結(jié)束提醒時鐘要處理指針角度計算右側(cè)表盤還要支持點(diǎn)擊切換圖案。這些都是一改就要看效果的活用 Cursor 邊描述邊生成、邊預(yù)覽邊改效率比純手敲高很多。項目結(jié)構(gòu)我建議保持極簡一個目錄搞定dual-time/ ├── index.html # 主頁面雙表盤布局 ├── style.css # 布局與表盤樣式 ├── app.js # 番茄鐘邏輯 時鐘指針 表盤切換 └── assets/ └── faces/ # 右側(cè)表盤的吉祥物圖案svg/pngindex.html負(fù)責(zé)把屏幕分成左右兩半左邊放番茄鐘容器右邊放時鐘容器。style.css用 flex 做左右分欄保證在小屏幕上也能并排顯示。app.js里分三個模塊番茄鐘計時器、時鐘指針渲染、表盤切換。在 Cursor 里我建議你按模塊給提示詞而不是一次性讓它生成整個項目。一次性生成的東西往往布局能看、邏輯有坑。分模塊的好處是每步都能驗證。第一步先讓 Cursor 生成布局骨架。提示詞可以這樣寫用原生 HTML CSS 做一個左右分欄的頁面。 左邊是一個圓形番茄鐘表盤中間顯示剩余時間 MM:SS 外圈有一圈進(jìn)度環(huán)表示剩余比例。 右邊是一個圓形時鐘表盤有 12 個刻度、時針、分針、秒針。 兩個表盤尺寸一致頁面背景深色整體居中。 不要用任何框架純 HTML/CSS/JS。第二步補(bǔ)番茄鐘邏輯。提示詞在 app.js 里實現(xiàn)番茄鐘 - 默認(rèn) 25 分鐘可配置 - 開始 / 暫停 / 重置三個按鈕 - 每秒更新剩余時間同步更新外圈進(jìn)度環(huán) - 倒計時結(jié)束時播放一個提示音用 Web Audio API 生成不引入音頻文件 - 時間格式 MM:SS第三步補(bǔ)時鐘指針。提示詞在 app.js 里實現(xiàn)時鐘 - 每秒更新一次 - 根據(jù)當(dāng)前時間計算時針、分針、秒針的旋轉(zhuǎn)角度 - 時針角度 (小時 % 12) * 30 分鐘 * 0.5 - 分針角度 分鐘 * 6 秒 * 0.1 - 秒針角度 秒 * 6 - 用 CSS transform rotate 應(yīng)用角度第四步加表盤切換。提示詞右側(cè)時鐘表盤支持點(diǎn)擊切換背景圖案。 把 assets/faces/ 下的圖片列成一個數(shù)組 點(diǎn)擊表盤時循環(huán)切換到下一張作為表盤背景。 切換時保持指針正常顯示在圖案上方。這四步走完一個能跑的雙表盤時鐘就成型了。你會發(fā)現(xiàn) Cursor 的價值在于布局和角度計算這種「有標(biāo)準(zhǔn)答案但寫起來煩」的部分它一次就能給對你只需要盯著預(yù)覽調(diào)樣式。真正需要你判斷的是交互細(xì)節(jié)比如番茄鐘結(jié)束后是自動進(jìn)入休息還是停住、表盤切換要不要加過渡動畫這些按你自己的使用習(xí)慣定。這里有個我踩過的坑一開始我讓 Cursor 把番茄鐘和時鐘寫在一個大函數(shù)里結(jié)果改番茄鐘邏輯時經(jīng)常誤傷時鐘渲染。后來拆成兩個獨(dú)立的initPomodoro()和initClock()各自維護(hù)自己的狀態(tài)和定時器互不干擾改起來清爽很多。如果你也要用 Cursor 做類似項目建議一開始就要求它「番茄鐘和時鐘邏輯分離各自獨(dú)立初始化」。3. 可復(fù)制的項目配置與 Cursor 規(guī)則文件這一節(jié)給你可以直接抄的配置。首先是index.html的骨架重點(diǎn)是左右分欄和兩個表盤容器!DOCTYPE html html langzh-CN head meta charsetUTF-8 / meta nameviewport contentwidthdevice-width, initial-scale1.0 / title雙表盤番茄風(fēng)水鐘/title link relstylesheet hrefstyle.css / /head body main classstage section classdial pomodoro div classring/div div classtime idpomodoroTime25:00/div div classcontrols button idstartBtn開始/button button idpauseBtn暫停/button button idresetBtn重置/button /div /section section classdial clock idclockDial div classhand hour idhourHand/div div classhand minute idminuteHand/div div classhand second idsecondHand/div /section /main script srcapp.js/script /body /html然后是style.css里最關(guān)鍵的布局和表盤樣式左右分欄用 flex表盤用border-radius: 50%.stage { display: flex; gap: 24px; justify-content: center; align-items: center; min-height: 100vh; background: #0f1115; } .dial { position: relative; width: 280px; height: 280px; border-radius: 50%; background: #1a1d24; display: flex; align-items: center; justify-content: center; } .clock { background-size: cover; background-position: center; cursor: pointer; } .hand { position: absolute; bottom: 50%; left: 50%; transform-origin: bottom center; background: #e8e8e8; } .hour { width: 6px; height: 70px; } .minute { width: 4px; height: 100px; } .second { width: 2px; height: 110px; background: #ff5a5f; }接著是app.js的核心邏輯番茄鐘和時鐘分開寫// 番茄鐘 const POMODORO_SECONDS 25 * 60; let remain POMODORO_SECONDS; let timer null; function renderPomodoro() { const m String(Math.floor(remain / 60)).padStart(2, 0); const s String(remain % 60).padStart(2, 0); document.getElementById(pomodoroTime).textContent ${m}:${s}; } function tick() { if (remain 0) { remain - 1; renderPomodoro(); } else { clearInterval(timer); timer null; playBeep(); } } document.getElementById(startBtn).onclick () { if (!timer) timer setInterval(tick, 1000); }; document.getElementById(pauseBtn).onclick () { clearInterval(timer); timer null; }; document.getElementById(resetBtn).onclick () { clearInterval(timer); timer null; remain POMODORO_SECONDS; renderPomodoro(); }; // 時鐘 function renderClock() { const now new Date(); const h now.getHours() % 12; const m now.getMinutes(); const s now.getSeconds(); document.getElementById(hourHand).style.transform rotate(${h * 30 m * 0.5}deg); document.getElementById(minuteHand).style.transform rotate(${m * 6 s * 0.1}deg); document.getElementById(secondHand).style.transform rotate(${s * 6}deg); } // 表盤切換 const faces [assets/faces/face1.svg, assets/faces/face2.svg]; let faceIndex 0; document.getElementById(clockDial).onclick () { faceIndex (faceIndex 1) % faces.length; document.getElementById(clockDial).style.backgroundImage url(${faces[faceIndex]}); }; // 提示音 function playBeep() { const ctx new (window.AudioContext || window.webkitAudioContext)(); const osc ctx.createOscillator(); osc.frequency.value 880; osc.connect(ctx.destination); osc.start(); osc.stop(ctx.currentTime 0.3); } renderPomodoro(); renderClock(); setInterval(renderClock, 1000);如果你用 Cursor 長期維護(hù)這個項目建議在項目根目錄放一個.cursorrules文件把約定寫進(jìn)去這樣 Cursor 每次生成代碼都會遵守本項目是純前端雙表盤時鐘不使用任何框架。 - 番茄鐘邏輯寫在 initPomodoro 相關(guān)函數(shù)中時鐘邏輯寫在 initClock 相關(guān)函數(shù)中兩者狀態(tài)獨(dú)立。 - 所有 DOM 操作使用原生 API不引入 jQuery。 - 樣式統(tǒng)一寫在 style.css不在 JS 里寫內(nèi)聯(lián)樣式。 - 新增表盤圖案放到 assets/faces/ 目錄并在 faces 數(shù)組中注冊。 - 時間格式統(tǒng)一 MM:SS。這個規(guī)則文件的作用是讓 Cursor 記住你的項目結(jié)構(gòu)避免它每次生成代碼時又給你塞一個框架進(jìn)來。實測下來有了這個文件之后Cursor 補(bǔ)全的代碼風(fēng)格會穩(wěn)定很多。4. 本地運(yùn)行與驗證確認(rèn)雙表盤真的在走配置寫完接下來是驗證。這一步很多人會跳過結(jié)果打開頁面發(fā)現(xiàn)指針不動、倒計時不走又回頭找問題。我們一步步來。最省事的運(yùn)行方式是直接用瀏覽器打開index.html。但如果你后面要加載assets/faces/里的圖片用file://協(xié)議有時會有跨域限制所以更穩(wěn)妥的方式是起一個本地靜態(tài)服務(wù)器。如果你裝了 Python一行命令就行cd dual-time python3 -m http.server 8080然后瀏覽器打開http://localhost:8080。如果你用 Node也可以npx serve .打開后你應(yīng)該看到左邊一個圓形表盤顯示25:00下面三個按鈕右邊一個圓形表盤時針分針秒針在走。驗證分三步第一步驗證時鐘。盯著右邊的秒針看它應(yīng)該每秒跳一格走滿一圈是 60 秒。分針和時針也要跟著動雖然肉眼看不明顯但你可以打開瀏覽器開發(fā)者工具在renderClock里打個斷點(diǎn)看角度值是不是隨時間變化。第二步驗證番茄鐘。點(diǎn)「開始」左邊的25:00應(yīng)該開始往下減每秒減 1。點(diǎn)「暫?!箶?shù)字停住。點(diǎn)「重置」回到25:00。為了快速驗證倒計時結(jié)束的行為你可以臨時把POMODORO_SECONDS改成10跑一遍看結(jié)束時會不會響提示音、定時器會不會停。第三步驗證表盤切換。點(diǎn)右邊的時鐘表盤背景圖案應(yīng)該切換到下一張。前提是assets/faces/里確實放了圖片并且faces數(shù)組里的路徑和文件名對得上。如果你想讓這塊時鐘真正擺在桌面上當(dāng)專注屏可以把它全屏打開或者用手機(jī)瀏覽器打開同一個局域網(wǎng)地址橫屏放在桌上。項目本身是響應(yīng)式的小屏幕上兩個表盤會自動縮小并排。驗證通過后你得到的就是一個完全本地運(yùn)行、不依賴任何外部服務(wù)的雙表盤時鐘。它不聯(lián)網(wǎng)、不上傳數(shù)據(jù)番茄鐘和時鐘都在瀏覽器里跑這也是純前端方案的好處——簡單、可控、隨時能改。5. 常見報錯排查指針不動、圖片不顯示、聲音不響這一節(jié)把幾個高頻問題列出來對照著查。問題一秒針不動或者只動一次。最常見的原因是setInterval(renderClock, 1000)沒執(zhí)行或者renderClock里取 DOM 元素失敗。打開控制臺看有沒有Cannot read properties of null這類報錯。如果有說明id對不上檢查 HTML 里的idsecondHand和 JS 里getElementById(secondHand)是否一致。另一個可能是transform-origin沒設(shè)對指針在轉(zhuǎn)但繞著中心轉(zhuǎn)看起來像沒動。確認(rèn).hand的transform-origin: bottom center生效。問題二表盤圖片不顯示控制臺報 404。這是路徑問題。faces數(shù)組里寫的是assets/faces/face1.svg那你的目錄結(jié)構(gòu)必須真的是dual-time/assets/faces/face1.svg。如果你用file://打開路徑大小寫敏感Face1.svg和face1.svg是兩回事。建議統(tǒng)一用小寫文件名。另外確認(rèn)圖片格式瀏覽器支持svg 和 png 都沒問題。問題三倒計時結(jié)束沒有聲音。瀏覽器的自動播放策略會攔截沒有用戶交互的音頻。playBeep里的AudioContext如果在用戶點(diǎn)擊「開始」之前就創(chuàng)建可能處于 suspended 狀態(tài)。解決辦法是在用戶點(diǎn)擊按鈕時再創(chuàng)建或 resume 音頻上下文??梢栽趕tartBtn的點(diǎn)擊回調(diào)里加一句audioCtx.resume()。另外如果你用的是new AudioContext()每次新建頻繁觸發(fā)可能報錯建議全局維護(hù)一個實例。問題四番茄鐘和時鐘互相干擾。如果你發(fā)現(xiàn)點(diǎn)「暫?!怪髸r鐘也停了說明兩個定時器共用了變量。檢查是不是把timer這個變量同時用在了番茄鐘和時鐘上。正確做法是番茄鐘用timer時鐘用獨(dú)立的clockTimer互不覆蓋。問題五頁面在手機(jī)上兩個表盤擠在一起。這是布局沒做響應(yīng)式。在style.css里加一個媒體查詢窄屏?xí)r把.stage的flex-direction改成column或者縮小表盤尺寸media (max-width: 640px) { .stage { flex-direction: column; gap: 16px; } .dial { width: 200px; height: 200px; } }問題六Cursor 生成的代碼用了框架或外部 CDN。如果你明確要純前端但 Cursor 給你引入了 React 或某個 CDN 庫檢查.cursorrules是否生效或者在提示詞里再強(qiáng)調(diào)一次「不使用任何框架和外部依賴」。純前端的好處是離線可用一旦引入 CDN斷網(wǎng)就廢了。排查這類問題的通用思路是先看控制臺報錯再看 DOM 元素是否存在最后看定時器和狀態(tài)變量。大部分「不動」的問題要么是元素沒取到要么是定時器沒起來要么是角度算錯。逐個排除很快能定位。6. 把這塊鐘用起來從桌面擺件到空間氛圍項目跑通之后怎么用是另一回事。我自己的用法是把它全屏打開放在一塊舊平板上橫屏立在桌角。左邊番茄鐘開始跑右邊時鐘隨時看中間不需要切任何 App。25 分鐘一到提示音響起身活動一下再點(diǎn)重置進(jìn)入下一輪。這種「只干一件事」的屏幕比手機(jī)上的番茄鐘 App 更容易讓人進(jìn)入狀態(tài)。如果你在做付費(fèi)空間、自習(xí)室、共享辦公這類場景這個項目可以直接當(dāng)氛圍組件用。右側(cè)表盤換成空間自己的 logo 或吉祥物點(diǎn)擊切換既是個時鐘又是個品牌展示位。因為它是純前端你可以把它打包進(jìn)任何帶瀏覽器的設(shè)備里小到一塊開發(fā)板接的屏幕大到墻上的顯示器只要能跑瀏覽器就能顯示。想繼續(xù)擴(kuò)展的話有幾個方向把番茄鐘時長做成可配置比如 25/45/60 分鐘幾檔加一個今日完成番茄數(shù)的統(tǒng)計存在 localStorage 里右側(cè)表盤除了切換圖案還可以疊加農(nóng)歷、節(jié)氣這類信息。這些都不需要后端純前端就能做。如果你在接入過程中需要管理 API Key、調(diào)試模型調(diào)用或者想把這類小工具和 AI 能力結(jié)合可以走 TaoToken 的接入流程先在 API Keys 頁面創(chuàng)建密鑰https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite再對照接入文檔https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite把 Base URL 和 Key 配好。想先驗證模型效果可以直接在模型對話頁https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite試幾句。如果你打算長期用 Cursor 做這類小項目、把 AI 編碼變成日常Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite會更合適。官網(wǎng)入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite需要的時候從那里進(jìn)就行。最后留一個實用技巧這個項目最大的價值不是它多復(fù)雜而是它足夠小、足夠可控。你可以在一小時內(nèi)用 Cursor 改出完全屬于自己的版本——換個表盤、改個時長、加個統(tǒng)計。與其去買一塊功能固定的表不如自己寫一塊隨時能改的。