iT邦幫忙

2025 iThome 鐵人賽

DAY 13
0
Modern Web

Modern Web:從基礎、框架到前端學習系列 第 26

Day26:To-Do List 專業版(時間記錄 + 全部清除 + JSON 導出)

  • 分享至 

  • xImage
  •  

學習目標

  1. 在每個任務中加入「建立時間」資訊。
  2. 新增「清除全部任務」按鈕。
  3. 新增「導出 JSON 檔案」功能。
  4. 完整練習Date 物件Blob 檔案下載技巧

新觀念講解

Date 物件

JavaScript 中 Date() 可以用來取得目前時間:

const now = new Date();
now.toLocaleString(); // 顯示像「2025/10/6 下午 10:00:00」

Blob 與下載檔案

想讓使用者下載一個檔案,可用Blob+URL.createObjectURL()

const data = { name: "Test" };
const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "todo.json";
a.click();
URL.revokeObjectURL(url);

實作範例

檔名:day26_todo_pro.html

<!DOCTYPE html>
<html lang="zh-Hant">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Day26 - To-Do List 專業版</title>
  <style>
    :root {
      --bg-light: #f5f7fa;
      --bg-dark: #1e1e1e;
      --text-light: #333;
      --text-dark: #f5f5f5;
      --card-light: white;
      --card-dark: #2c2c2c;
      --primary: #4caf50;
      --secondary: #2196f3;
    }

    body {
      background-color: var(--bg-light);
      color: var(--text-light);
      font-family: "Poppins", sans-serif;
      display: flex;
      flex-direction: column;
      align-items: center;
      padding: 40px;
      transition: 0.3s;
    }

    body.dark {
      background-color: var(--bg-dark);
      color: var(--text-dark);
    }

    h2 {
      margin-bottom: 20px;
    }

    .controls {
      display: flex;
      justify-content: center;
      align-items: center;
      margin-bottom: 20px;
      gap: 10px;
    }

    input {
      padding: 10px;
      width: 250px;
      border: 1px solid #ccc;
      border-radius: 8px;
    }

    button {
      padding: 10px 15px;
      border: none;
      border-radius: 8px;
      cursor: pointer;
      transition: 0.3s;
      color: white;
    }

    .add { background: var(--primary); }
    .clear { background: crimson; }
    .export { background: var(--secondary); }

    ul {
      list-style: none;
      padding: 0;
      width: 320px;
    }

    li {
      background: var(--card-light);
      border: 1px solid #ddd;
      border-radius: 8px;
      padding: 10px;
      margin-bottom: 10px;
      display: flex;
      flex-direction: column;
      transition: 0.3s;
    }

    body.dark li {
      background: var(--card-dark);
    }

    .top-row {
      display: flex;
      justify-content: space-between;
      align-items: center;
    }

    .done {
      text-decoration: line-through;
      opacity: 0.6;
    }

    .time {
      font-size: 0.85em;
      opacity: 0.7;
      margin-top: 4px;
    }

    .actions span {
      cursor: pointer;
      color: red;
      margin-left: 10px;
    }

    .theme {
      margin-top: 20px;
      background: none;
      border: 2px solid var(--secondary);
      color: var(--secondary);
      padding: 8px 15px;
      border-radius: 8px;
      cursor: pointer;
      transition: 0.3s;
    }

    .theme:hover {
      background: var(--secondary);
      color: white;
    }

    @media (max-width: 500px) {
      ul { width: 90%; }
    }
  </style>
</head>
<body>
  <h2>📝 To-Do List 專業版</h2>

  <div class="controls">
    <input type="text" id="taskInput" placeholder="輸入代辦事項...">
    <button class="add" id="addBtn">新增</button>
    <button class="clear" id="clearBtn">清除全部</button>
    <button class="export" id="exportBtn">導出 JSON</button>
  </div>

  <ul id="taskList"></ul>
  <button class="theme" id="themeBtn">切換主題</button>

  <script>
    const taskInput = document.getElementById("taskInput");
    const addBtn = document.getElementById("addBtn");
    const clearBtn = document.getElementById("clearBtn");
    const exportBtn = document.getElementById("exportBtn");
    const taskList = document.getElementById("taskList");
    const themeBtn = document.getElementById("themeBtn");

    let tasks = JSON.parse(localStorage.getItem("tasks")) || [];
    let theme = localStorage.getItem("theme") || "light";

    document.body.classList.toggle("dark", theme === "dark");

    function renderTasks() {
      taskList.innerHTML = "";
      tasks.forEach((task, index) => {
        const li = document.createElement("li");
        li.innerHTML = `
          <div class="top-row">
            <span class="${task.done ? 'done' : ''}" onclick="toggleDone(${index})">${task.text}</span>
            <div class="actions">
              <span onclick="deleteTask(${index})">✖</span>
            </div>
          </div>
          <div class="time">建立時間:${task.time}</div>
        `;
        taskList.appendChild(li);
      });
    }

    function updateStorage() {
      localStorage.setItem("tasks", JSON.stringify(tasks));
    }

    addBtn.addEventListener("click", () => {
      const text = taskInput.value.trim();
      if (text) {
        const time = new Date().toLocaleString();
        tasks.push({ text, done: false, time });
        updateStorage();
        renderTasks();
        taskInput.value = "";
      }
    });

    function toggleDone(index) {
      tasks[index].done = !tasks[index].done;
      updateStorage();
      renderTasks();
    }

    function deleteTask(index) {
      tasks.splice(index, 1);
      updateStorage();
      renderTasks();
    }

    clearBtn.addEventListener("click", () => {
      if (confirm("確定要清除所有任務嗎?")) {
        tasks = [];
        updateStorage();
        renderTasks();
      }
    });

    exportBtn.addEventListener("click", () => {
      const blob = new Blob([JSON.stringify(tasks, null, 2)], { type: "application/json" });
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url;
      a.download = "todo_list.json";
      a.click();
      URL.revokeObjectURL(url);
    });

    themeBtn.addEventListener("click", () => {
      document.body.classList.toggle("dark");
      theme = document.body.classList.contains("dark") ? "dark" : "light";
      localStorage.setItem("theme", theme);
    });

    renderTasks();
  </script>
</body>
</html>

今日挑戰任務

  1. 加入「任務搜尋框」,可以即時篩選關鍵字。
  2. 增加「完成任務統計數」顯示在畫面上。
  3. 設計屬於自己的 UI 風格(顏色、字型、動態過渡)。

上一篇
Day 25 - To-Do List 進階版:完成狀態 + 暗黑模式(含 LocalStorage 持久化)
下一篇
Day 27:To-Do List 加入「搜尋」與「完成統計」
系列文
Modern Web:從基礎、框架到前端學習28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言