iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0
生成式 AI

30天用React打造AI工具箱系列 第 22

30天用React打造AI工具箱 Day22

  • 分享至 

  • xImage
  •  

30天用React打造AI工具箱 Day22

打字訓練工具 Typing Trainer

今天的進度是完成一個「打字訓練工具」,這次我想做一個不依賴外部 API、能夠直接在瀏覽器裡互動的小功能。

主要功能

  • 顯示隨機英文句子(目前共 5 組)
  • 使用者打字時即時比較正確字元數,計算:
  • 正確率(Accuracy)
  • 打字速度(WPM, Words Per Minute)
  • 當輸入正確完成一句後,自動顯示成績,並可按「再來一句」重置
  • 完全無需後端或 API,所有邏輯皆在前端運算完成

程式碼

//TypingTrainer.jsx
import { useState, useEffect } from "react"

export default function TypingTrainer() {
  const sentences = [
    "The quick brown fox jumps over the lazy dog.",
    "Typing fast takes practice and patience.",
    "Code is like humor. When you have to explain it, it’s bad.",
    "Practice makes perfect.",
    "Never stop learning new things.",
  ]

  const [currentSentence, setCurrentSentence] = useState("")
  const [userInput, setUserInput] = useState("")
  const [startTime, setStartTime] = useState(null)
  const [accuracy, setAccuracy] = useState(0)
  const [wpm, setWpm] = useState(0)
  const [finished, setFinished] = useState(false)

  useEffect(() => {
    newSentence()
  }, [])

  const newSentence = () => {
    const random = sentences[Math.floor(Math.random() * sentences.length)]
    setCurrentSentence(random)
    setUserInput("")
    setAccuracy(0)
    setWpm(0)
    setFinished(false)
    setStartTime(null)
  }

  const handleChange = (e) => {
    const value = e.target.value
    setUserInput(value)

    if (!startTime) setStartTime(Date.now())

    const correctChars = currentSentence
      .split("")
      .filter((ch, i) => ch === value[i]).length

    const acc = Math.round((correctChars / currentSentence.length) * 100)
    setAccuracy(acc)

    if (value === currentSentence) {
      const elapsed = (Date.now() - startTime) / 1000 / 60
      const words = currentSentence.split(" ").length
      setWpm(Math.round(words / elapsed))
      setFinished(true)
    }
  }

  return (
    <div className="space-y-4">
      <div className="bg-white shadow p-6 rounded">
        <p className="text-gray-700 text-lg mb-3">請輸入以下句子:</p>
        <p className="font-mono bg-gray-100 p-3 rounded">{currentSentence}</p>

        <textarea
          value={userInput}
          onChange={handleChange}
          disabled={finished}
          className="w-full border p-3 mt-3 rounded focus:ring focus:ring-blue-300"
          rows="3"
          placeholder="開始打字..."
        />

        <div className="mt-4 flex items-center justify-between text-gray-700">
          <p>🎯 正確率:{accuracy}%</p>
          <p>⚡ 速度:{wpm} WPM</p>
        </div>

        {finished && (
          <div className="mt-4 text-center">
            <p className="text-green-600 font-semibold">✅ 完成!</p>
            <button
              onClick={newSentence}
              className="mt-2 bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
            >
              再來一句
            </button>
          </div>
        )}
      </div>
    </div>
  )
}

再來更改我們原本的layout讓它可以顯示出來

//layout.jsx
import { useState } from "react"
import TodoApp from "./TodoApp" // Day11 做好的待辦清單
import ChatBox from "./ChatBox" // Chat 工具
import TranslationTool from "./TranslationTool" // 翻譯工具
import TypingTrainer from "./TypingTrainer" // 👈 新增打字訓練工具

export default function Layout() {
  const [active, setActive] = useState("todo")

  return (
    <div className="min-h-screen flex">
      {/* 側邊選單 */}
      <aside className="w-64 bg-gray-800 text-white p-6 space-y-4">
        <h2 className="text-xl font-bold mb-6">AI工具箱</h2>
        <nav className="space-y-2">
          <button
            className={`block w-full text-left px-3 py-2 rounded ${
              active === "todo" ? "bg-blue-600" : "hover:bg-gray-700"
            }`}
            onClick={() => setActive("todo")}
          >
            待辦清單
          </button>

          <button
            className={`block w-full text-left px-3 py-2 rounded ${
              active === "chat" ? "bg-blue-600" : "hover:bg-gray-700"
            }`}
            onClick={() => setActive("chat")}
          >
            Chat工具
          </button>

          <button
            className={`block w-full text-left px-3 py-2 rounded ${
              active === "translate" ? "bg-blue-600" : "hover:bg-gray-700"
            }`}
            onClick={() => setActive("translate")}
          >
            翻譯工具
          </button>

          <button
            className={`block w-full text-left px-3 py-2 rounded ${
              active === "typing" ? "bg-blue-600" : "hover:bg-gray-700"
            }`}
            onClick={() => setActive("typing")}
          >
            打字訓練
          </button>
        </nav>
      </aside>

      {/* 主內容區 */}
      <main className="flex-1 bg-gray-50 p-6">
        {active === "todo" && (
          <>
            <h1 className="text-2xl font-bold mb-4">待辦清單</h1>
            <TodoApp />
          </>
        )}

        {active === "chat" && (
          <>
            <h1 className="text-2xl font-bold mb-4">Chat 工具</h1>
            <ChatBox />
          </>
        )}

        {active === "translate" && (
          <>
            <h1 className="text-2xl font-bold mb-4">翻譯工具</h1>
            <TranslationTool />
          </>
        )}

        {active === "typing" && (
          <>
            <h1 className="text-2xl font-bold mb-4">打字訓練</h1>
            <TypingTrainer />
          </>
        )}
      </main>
    </div>
  )
}

這次功能的主要目的是讓我練習前端事件控制(onChange、useEffect、狀態更新)、即時運算與 UI 反饋,讓下次可以來嘗試讓句子可以「中英文切換」或「由使用者自訂內容」。


上一篇
30天用React打造AI工具箱 Day21
系列文
30天用React打造AI工具箱22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言