news 2026/4/28 20:02:01

基于原生JS与SQLite的轻量级任务看板:零框架设计与OpenClaw集成

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于原生JS与SQLite的轻量级任务看板:零框架设计与OpenClaw集成

1. 项目概述:一个纯粹、轻量的任务看板

最近在整理个人工作流时,我一直在寻找一个足够简单、但又足够强大的任务管理工具。市面上的 Trello、Notion 或者一些国产软件功能固然强大,但要么太重,要么需要联网,要么充斥着我不需要的复杂功能。我的核心需求很简单:一个能快速启动、数据完全由我掌控、界面清爽、并且能和我已有的命令行工具(比如我常用的 OpenClaw)打通的看板。于是,我动手用 Express 和 SQLite 搭建了这个名为 TodoBoard 的轻量级任务看板。

它本质上是一个零前端框架的 Web 应用,后端是 Node.js + Express,数据库是单文件的 SQLite,前端就是最朴素的 HTML、CSS 和 JavaScript。没有 Webpack,没有 React/Vue,没有复杂的构建步骤,git clone下来npm install && npm start就能跑起来。整个项目的哲学就是“够用就好”,专注于任务管理的核心功能:看板拖拽、列表视图、任务详情、搜索过滤、以及优先级和标签。特别值得一提的是,它原生集成了 OpenClaw 技能,这意味着你可以直接在聊天窗口里添加待办、查询进度,甚至设置定时提醒,把任务管理无缝嵌入到你的命令行工作流中。如果你也厌倦了臃肿的 SaaS 工具,想要一个完全受控、可随意定制、部署简单的私人看板,那么这个项目或许正是你需要的。

2. 核心设计思路与技术选型

2.1 为什么选择“无框架”前端与极简技术栈?

在决定技术栈时,我首要考虑的是维护复杂度和启动成本。这个工具主要是我自用,也可能分享给几个同样追求效率的同事,因此它不需要考虑千人级别的并发,也不需要支持 IE 浏览器。基于此,我排除了引入 React 或 Vue 等现代前端框架的方案。原因有三:

第一,依赖与构建的简化。一旦引入前端框架,就意味着需要引入对应的构建工具(如 Webpack、Vite),管理依赖更新,处理可能存在的版本冲突。对于这样一个轻量级工具,这些开销是不必要的。纯原生技术(Vanilla JS)意味着任何懂前端基础的开发者都能毫无障碍地理解和修改代码。

第二,极致的性能与响应速度。没有虚拟 DOM 的 Diff 过程,所有 DOM 操作都是直接的。在看板拖拽、任务状态频繁更新的场景下,直接操作 DOM 反而更简单、更可预测。配合现代浏览器良好的 CSS Grid 和 Flexbox 支持,实现一个响应式、流畅的拖拽界面并不困难。

第三,部署与迁移的便捷性。整个应用就是一堆静态文件(HTML, CSS, JS)加一个 Node.js 服务端。你可以把它扔在任何能运行 Node.js 的环境里,甚至用pm2托管后,用 Nginx 简单配个反向代理就能对外服务。数据库是单个 SQLite 文件,备份和迁移就是复制一个文件的事。

后端选择 Express + better-sqlite3 也是出于类似考量。Express 是 Node.js 生态最成熟、最轻量的 Web 框架,足以应对 RESTful API 的开发。而 better-sqlite3 相比默认的sqlite3包,提供了同步 API(在 Web 服务器这种 I/O 密集场景下,配合 Express 的异步处理并无阻塞风险),并且性能更高,语法更友好。

注意:虽然 better-sqlite3 使用同步 API,但在 Express 的异步路由处理函数中直接调用是安全的,因为 SQLite 操作本质上是磁盘 I/O,better-sqlite3 底层使用了 Node.js 的 worker 线程来避免阻塞主事件循环。但对于超高并发的生产场景,仍需进行压力测试。

2.2 数据模型设计:如何用一张表支撑看板?

很多看板工具会为“列表”(List)和“卡片”(Card)分别建表。但为了极致简单,TodoBoard 的核心数据模型只用了一张todos表。这听起来可能有些激进,但通过合理的字段设计,完全能够满足需求。

-- 简化的表结构核心字段 CREATE TABLE todos ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, description TEXT, -- 对应详情面板的“需求” notes TEXT, -- 对应详情面板的“解决方案/笔记” status TEXT NOT NULL DEFAULT 'todo' CHECK(status IN ('todo', 'doing', 'done')), category TEXT, -- 任务分类/标签 priority INTEGER DEFAULT 0, -- 优先级,数字越大越优先 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, started_at DATETIME, completed_at DATETIME, due_date DATETIME -- 截止日期,用于计算是否逾期 );

关键设计点解析

  1. status字段替代多表关联todo,doing,done三个状态直接对应看板的三个列。查询某个列的任务只需WHERE status = ?。这避免了多表 JOIN 的复杂度,在数据量不大时(个人或小团队使用),性能完全不是问题。
  2. 时间戳自动化created_at是固定的创建时间。started_atcompleted_at则由业务逻辑更新。当任务从todo拖拽到doing时,如果started_at为空,则自动设置为当前时间。当任务从doing拖拽到done时,自动设置completed_at。这为后续的时间追踪和统计提供了数据基础。
  3. prioritycategory:优先级用整数表示,方便排序。在前端,高优先级(例如priority >= 3)的任务会有视觉上的高亮(如红色边框或角标)。category字段存储标签文本,前端会将其渲染为一个个“药丸”状(Pill)的标签。
  4. 评论功能:评论被设计为独立的comments表,通过todo_idtodos表关联。这样保证了任务主体信息的简洁,也符合评论可能较多的场景。

这种单表核心的设计,使得 API 非常简洁。大部分操作都围绕/api/todos这个端点进行,通过查询参数来实现过滤(?status=doing?category=开发?q=关键词),后端逻辑清晰明了。

3. 前端实现详解:原生JS下的流畅看板

3.1 看板视图与拖拽交互的实现

看板(Kanban)是核心交互界面。我们用 CSS Grid 来布局三列,每一列是一个status的容器。

<div class="kanban-board"> <div class="column">.kanban-board { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem; padding: 1rem; } .column { background: var(--bg-secondary); border-radius: 8px; padding: 1rem; min-height: 60vh; }

拖拽功能使用 HTML5 原生的 Drag and Drop API。虽然业界有 Sortable.js 这样的优秀库,但为了保持零依赖,我选择了原生实现。关键在于处理好以下几个事件:

  • dragstart: 在任务卡片被拖拽时触发。我们需要在event.dataTransfer.setData中存储被拖拽任务的id
  • dragover: 当拖拽元素经过某个列容器时触发。必须在这里preventDefault()才能允许放置。
  • drop: 当在列容器中松开鼠标时触发。这里我们从event.dataTransfer.getData获取任务id,并向服务器发送PUT /api/todos/:id请求,更新该任务的status为当前列的>function handleDragOver(e) { e.preventDefault(); if (!e.currentTarget.classList.contains('drag-over')) { e.currentTarget.classList.add('drag-over'); } } function handleDrop(e) { e.preventDefault(); e.currentTarget.classList.remove('drag-over'); const taskId = e.dataTransfer.getData('text/plain'); const newStatus = e.currentTarget.parentElement.dataset.status; // 调用API更新任务状态 updateTaskStatus(taskId, newStatus); }

    实操心得:原生拖拽 API 在桌面端体验不错,但在移动端支持有限。因此,TodoBoard 的移动端适配主要依赖于响应式布局和点击操作。在手机屏幕上,看板可能会变为垂直堆叠,或者通过下拉菜单来改变任务状态。这是“渐进增强”思想的体现:在高级浏览器上享受拖拽的便利,在基础设备上保证功能的可用性。

    3.2 列表视图与详情面板的协同

    除了看板视图,另一个高频使用的视图是“列表视图”。它以一个表格的形式展示所有任务,方便快速扫描和比较。

    列表视图的 HTML 结构就是一个<table>,通过 JavaScript 动态填充行(<tr>)。每一行包含任务的核心信息:ID、标题、优先级标签、分类标签、状态、创建时间和截止时间。点击某一行,会触发右侧详情面板的更新。

    详情面板是一个固定在右侧或下层的侧边栏,用于展示和编辑任务的完整信息。它被划分为几个区域:

    1. 需求描述:任务的详细描述,来自description字段。
    2. 笔记/解决方案:记录任务执行过程中的笔记或最终解决方案,来自notes字段。这是一个纯文本区域,但渲染时会保留换行符。
    3. 评论线程:一个按时间倒序排列的评论列表,每条评论显示作者(当前版本固定为“用户”)、时间和内容。底部有一个文本框用于添加新评论,提交后会POST /api/todos/:id/comments
    4. 时间线:直观展示任务的created_atstarted_atcompleted_at三个关键时间点,让任务的生命周期一目了然。

    列表视图和详情面板的联动,是通过共享状态实现的。前端维护一个当前选中任务 ID 的变量。当在列表视图中点击一行,或在看板中点击一个卡片,就会更新这个 ID,并触发一个函数去获取该任务的完整数据(包括评论),然后渲染到详情面板中。

    let selectedTaskId = null; async function loadTaskDetail(taskId) { selectedTaskId = taskId; const response = await fetch(`/api/todos/${taskId}`); const taskDetail = await response.json(); renderDetailPanel(taskDetail); // 同时高亮列表视图或看板中的对应项 highlightActiveTask(taskId); }

    这种设计使得用户可以在看板上宏观把控,在列表上快速筛选,再通过详情面板进行深度操作(编辑、评论),三者形成了一个高效的工作闭环。

    4. 后端API设计与数据持久化

    4.1 RESTful API 的构建与错误处理

    后端 API 遵循经典的 RESTful 设计风格,围绕todoscomments两个资源展开。我使用 Express 的 Router 来组织路由,使代码结构清晰。

    // routes/todos.js const express = require('express'); const router = express.Router(); const db = require('../db'); // better-sqlite3 实例 // 获取任务列表(带过滤) router.get('/', (req, res) => { const { q, category, status } = req.query; let sql = `SELECT * FROM todos WHERE 1=1`; const params = []; if (q) { sql += ` AND (title LIKE ? OR description LIKE ?)`; params.push(`%${q}%`, `%${q}%`); } if (category) { sql += ` AND category = ?`; params.push(category); } if (status) { sql += ` AND status = ?`; params.push(status); } sql += ` ORDER BY priority DESC, created_at DESC`; try { const stmt = db.prepare(sql); const todos = stmt.all(...params); res.json(todos); } catch (error) { console.error('Database error:', error); res.status(500).json({ error: 'Internal server error' }); } }); // 创建新任务 router.post('/', (req, res) => { const { title, description, category, priority, due_date } = req.body; if (!title) { return res.status(400).json({ error: 'Title is required' }); } const sql = `INSERT INTO todos (title, description, category, priority, due_date) VALUES (?, ?, ?, ?, ?)`; try { const info = db.prepare(sql).run(title, description, category, priority, due_date); res.status(201).json({ id: info.lastInsertRowid, message: 'Task created' }); } catch (error) { res.status(500).json({ error: error.message }); } }); // ... 其他 PUT, DELETE 路由

    关键设计点

    1. 过滤查询GET /api/todos支持q(关键词全文搜索)、categorystatus多个查询参数。它们被动态拼接到 SQL 语句的WHERE子句中。这里使用了WHERE 1=1的小技巧,可以方便地后续拼接AND条件而无需判断是否是第一个条件。
    2. 参数化查询:所有用户输入(查询参数、请求体)在拼接 SQL 时都使用?占位符和参数数组,这是防止 SQL 注入攻击的必备措施。better-sqlite3 的.run().all()方法天然支持参数化查询。
    3. 统一的错误处理:在每个路由的try...catch中捕获数据库错误,返回 500 状态码和错误信息。对于客户端输入错误(如缺少必填字段),返回 400 状态码和明确的错误提示。在生产环境中,你可能希望隐藏详细的数据库错误信息,只返回通用的错误提示。
    4. 时间戳的自动管理:对于started_atcompleted_at,逻辑放在更新状态的PUT接口中。当检测到状态从todo变为doingstarted_at为空时,自动设置started_at = CURRENT_TIMESTAMP。从doing变为done时,设置completed_at

    4.2 统计接口与数据聚合

    GET /api/stats接口提供了一些关键的聚合数据,用于仪表盘或 OpenClaw 的定时提醒。它通过一次查询返回多个统计值,比前端分别计算高效得多。

    -- 对应的 SQL 查询 SELECT COUNT(*) as total, SUM(CASE WHEN status = 'todo' THEN 1 ELSE 0 END) as todo, SUM(CASE WHEN status = 'doing' THEN 1 ELSE 0 END) as doing, SUM(CASE WHEN status = 'done' THEN 1 ELSE 0 END) as done, SUM(CASE WHEN status != 'done' AND due_date IS NOT NULL AND due_date < DATE('now') THEN 1 ELSE 0 END) as overdue FROM todos;

    这个查询使用了CASE WHEN条件聚合,在一行内统计了总数、各状态数量以及逾期任务数。逾期任务的逻辑是:状态不是“已完成”,且截止日期不为空,且截止日期早于今天。

    这个接口的输出是一个简单的 JSON 对象:

    { "total": 42, "todo": 10, "doing": 5, "done": 27, "overdue": 3 }

    前端可以将其展示在页眉或仪表盘上,而 OpenClaw 的定时任务则可以定期调用此接口,如果overdue > 0,就通过飞书、钉钉等 Webhook 发送提醒消息,实现自动化催办。

    5. 与 OpenClaw 的深度集成

    5.1 OpenClaw Skill 的工作原理

    OpenClaw 是一个本地运行的、可扩展的 CLI 助手工具。它的技能(Skill)机制允许用户通过自然语言命令来操作外部系统。TodoBoard 提供的技能,本质上是一组预定义的命令模板和对应的 API 调用逻辑

    技能文件通常包含:

    • skill.yml: 技能元数据,如名称、描述、触发命令模式。
    • index.js: 核心逻辑,解析用户输入,调用 TodoBoard 的 API,格式化返回结果。

    例如,当你在 OpenClaw 中输入“帮我加个待办:明天之前把 README 写完”,OpenClaw 会匹配到“加个待办”这个模式,并提取出“明天之前把 README 写完”作为参数。技能逻辑会:

    1. 解析出任务标题(“把 README 写完”)和可能的截止日期(“明天之前”)。
    2. 将自然语言的日期“明天”转换为标准的日期格式(如2023-10-27)。
    3. http://localhost:3010/api/todos发送 POST 请求创建任务。
    4. 将创建成功的任务信息(如新任务的 ID)用友好的语言返回给用户。

    5.2 部署技能与配置定时提醒

    部署技能非常简单,只需要将skills/todo-board目录复制到 OpenClaw 的技能目录下即可。OpenClaw 启动时会自动加载。

    # 假设你的 OpenClaw 工作空间在 ~/.openclaw cp -r /path/to/todo-board/skills/todo-board ~/.openclaw/workspace/skills/

    更强大的功能是定时提醒。OpenClaw 支持配置 cron 任务。你可以编辑 OpenClaw 的配置文件(通常是~/.openclaw/config.json),添加一个定时任务:

    { "cron": { "jobs": [ { "name": "todo-overdue-check", "schedule": "0 9 * * *", // 每天上午9点执行 "task": "Check http://localhost:3010/api/stats for overdue tasks. If overdue > 0, message me on Feishu with the list." } ] } }

    这个配置的意思是:每天上午9点,OpenClaw 会执行一个任务。这个任务会调用一个内置的 HTTP 客户端去访问http://localhost:3010/api/stats,获取 JSON 数据。然后,它内置的 JavaScript 引擎会解析这个 JSON,判断overdue字段是否大于 0。如果大于 0,它会再调用飞书(或其他已配置的通讯工具)的 Webhook,发送一条包含逾期任务列表的消息给你。

    注意:这里的task字段内容是一个简化的描述。实际 OpenClaw 的技能可能需要你编写一小段具体的 JavaScript 代码来精确实现“获取逾期任务列表并发送消息”的逻辑。具体语法请参考 OpenClaw 的文档。这个集成点展示了 TodoBoard 如何从一个被动工具,变成一个能主动触达你的智能助手。

    6. 部署、配置与维护指南

    6.1 生产环境部署与进程守护

    本地开发使用npm start(即node server.js)没问题,但对于长期运行的服务,我们需要一个进程管理器来保证应用崩溃后能自动重启。PM2 是一个绝佳的选择。

    # 1. 全局安装 PM2 npm install -g pm2 # 2. 进入项目目录,用 PM2 启动应用 # --name 指定进程名称,方便管理 pm2 start server.js --name todo-board # 3. 保存当前进程列表,以便服务器重启后能自动恢复 pm2 save # 4. 生成开机自启动脚本(根据系统不同) pm2 startup # 执行上一条命令后,PM2会给出具体需要运行的命令,复制执行即可。

    PM2 还提供了丰富的监控功能:

    • pm2 list: 查看所有进程状态。
    • pm2 logs todo-board: 查看该应用的实时日志。
    • pm2 monit: 进入一个带图表监控的仪表盘。

    为了让外部网络能够访问,我们通常会在前面加一个反向代理,比如 Nginx。

    # Nginx 配置示例 (在 /etc/nginx/sites-available/ 或 conf.d 下) server { listen 80; server_name your-domain.com; # 或服务器IP location / { proxy_pass http://localhost:3010; # 指向 PM2 运行的端口 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }

    配置完成后,重启 Nginx (sudo systemctl restart nginx),你就可以通过http://your-domain.com访问你的 TodoBoard 了。

    6.2 数据备份与迁移

    由于使用 SQLite,数据备份变得极其简单。数据库就是一个名为database.sqlite(或你在代码中指定的其他名字)的单一文件。

    备份

    # 直接复制数据库文件即可 cp /path/to/your/project/data/database.sqlite /path/to/backup/database_backup_$(date +%Y%m%d).sqlite

    你可以将上述命令加入 crontab,实现每日自动备份。

    迁移: 如果你想将整个 TodoBoard 搬到另一台服务器:

    1. 在新服务器上安装 Node.js 环境。
    2. 将整个项目目录(包括node_modules,或者只复制package.json然后在新环境npm install)复制过去。
    3. 将备份的database.sqlite文件复制到项目的数据目录。
    4. 使用 PM2 启动应用。

    整个过程几乎无需任何配置更改,因为 SQLite 是服务器无状态的,所有配置(如端口)都通过环境变量或代码常量管理,数据库路径也是相对的。

    环境变量配置: 项目目前只定义了一个环境变量TODO_PORT。在实际部署中,你可能还需要设置数据库路径、日志级别等。建议使用.env文件来管理,并通过dotenv包在应用启动时加载。

    # .env 文件示例 TODO_PORT=3010 NODE_ENV=production DB_PATH=./data/production.sqlite LOG_LEVEL=info

    server.js开头加入:

    if (process.env.NODE_ENV !== 'production') { require('dotenv').config(); }

    这样,在开发环境会从.env文件读取配置,在生产环境则直接使用系统环境变量,更加安全和灵活。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/28 20:01:58

用Arduino Uno和ILI9341 TFT屏做个桌面天气站:从接线到显示完整指南

用Arduino Uno和ILI9341 TFT屏打造智能桌面天气站&#xff1a;从硬件搭建到数据可视化的全流程实战 在创客圈里&#xff0c;把传感器数据变成直观的可视化界面一直是个热门话题。想象一下&#xff0c;早晨起床时&#xff0c;书桌上的小屏幕不仅显示实时温湿度&#xff0c;还能…

作者头像 李华
网站建设 2026/4/28 19:55:28

用Java+SSM+Vue2从零搭建一个医学影像Web系统(含Dicom文件处理全流程)

用JavaSSMVue2构建医学影像Web系统的实战指南 医疗信息化浪潮下&#xff0c;医学影像系统的开发需求日益增长。作为一名Java开发者&#xff0c;如何快速搭建一个支持Dicom标准的轻量级PACS系统&#xff1f;本文将带你从零开始&#xff0c;逐步实现一个完整的医学影像Web解决方…

作者头像 李华