1. 项目概述一个由AI驱动的看板式待办事项应用最近我完成了一个很有意思的练手项目一个全栈的看板式待办事项应用。这个项目本身的技术栈并不复杂核心是React前端、Express.js后端搭配SQLite数据库实现一个经典的拖拽式任务管理工具。但这次开发的整个过程我尝试了一种全新的方式——几乎全程由AI工具辅助完成。从最初的UI原型设计到代码生成、架构规划再到最后的调试和优化我深度使用了包括Cursor、Visily、Gemini、ChatGPT以及Bolt在内的多款AI工具。这个项目更像是一次关于“AI如何改变现代开发工作流”的实践探索我想把这次从零到一、由AI深度参与构建一个完整应用的经验和踩过的坑完整地分享给你。这个应用具备一个标准看板应用的核心功能三个状态列待办、进行中、已完成支持拖拽移动任务卡片每张卡片包含标题、描述、创建时间、更新时间以及状态变更时间戳。数据通过后端API持久化到数据库中。对于前端开发者或全栈初学者来说这是一个绝佳的练手项目能覆盖到现代Web开发中常见的CRUD操作、状态管理、API联调等环节。而更吸引人的是我将详细拆解如何利用不同的AI工具高效地完成其中每一个环节让你看到在AI的加持下个人开发者的效率能提升到什么程度。2. 技术栈选型与AI工具组合策略2.1 为什么选择React Express SQLite这个组合在启动项目时我首先需要确定技术栈。我的核心诉求是轻量、快速、易于部署并且有广泛的学习资源和社区支持。基于这几点我选择了这个经典的“MERN”变体。React (前端)这几乎是一个无需纠结的选择。其组件化思想和庞大的生态系统使得构建交互复杂的单页面应用SPA非常高效。特别是对于需要频繁更新UI的拖拽看板应用React的虚拟DOM和状态驱动渲染机制非常合适。更重要的是几乎所有主流的AI编程助手对React的支持都极为成熟无论是代码补全、组件生成还是问题解答都能获得高质量的反馈。Express.js (后端)作为Node.js最流行的Web框架Express以其极简和灵活著称。对于这样一个以RESTful API为核心的后端服务Express能让我用最少的样板代码快速搭建起路由、中间件和数据库连接。它不像Nest.js那样有严格的结构约束在AI辅助快速迭代的初期阶段这种灵活性反而是优势。SQLite (数据库)原项目描述中提到了MongoDB但在实际AI辅助开发过程中我为了追求极致的简洁和“开箱即用”临时切换到了SQLite。原因很简单SQLite是一个服务器零配置的数据库整个数据库就是一个文件。这意味着我不需要在本地安装并运行一个额外的数据库服务如MongoDB或PostgreSQL项目克隆下来直接npm install和npm start就能跑通极大地降低了环境配置的复杂度特别适合分享和快速演示。虽然它不适合高并发的生产场景但对于个人项目或原型验证它是完美的选择。注意技术栈的最终选择与你项目的目标紧密相关。如果你明确要学习NoSQL或计划将来扩展成分布式应用那么坚持使用MongoDB是更好的选择。我这里选择SQLite纯粹是为了让“AI辅助开发”的演示流程更顺畅减少外部依赖。2.2 AI工具矩阵如何为开发流程的每个环节选择利器这次开发我没有只依赖某一个AI工具而是根据开发阶段的不同需求组建了一个“AI工具矩阵”。每个工具都有其擅长的领域组合使用才能发挥最大效能。架构与规划阶段ChatGPT角色资深技术顾问。使用场景在项目伊始我会向ChatGPT描述我想要一个“具有拖拽功能的看板式Todo应用使用React和Express”。它会帮我梳理出大致的项目结构、需要创建的核心文件如App.jsx,TaskCard.jsx,server.js, 路由文件等、数据库表/集合设计字段定义。我还会让它给出一个初步的package.json依赖列表。这个阶段不需要精确的代码而是需要一个可靠的蓝图。UI/原型设计阶段Visily Bolt角色快速原型设计师。Visily我通过自然语言描述如“一个简洁的现代看板有三个列分别是To-do, In Progress, Done每个列里有可拖拽的卡片”它能迅速生成一个可交互的UI线框图。这个视觉参考对后续前端开发至关重要。Bolt我主要用它来生成具体的UI组件代码片段。例如输入“一个Material-UI风格的卡片组件包含标题、描述和两个时间戳”它能直接给出高质量的JSX代码节省了大量查阅文档和编写样式的时间。核心编码阶段Cursor角色主力编程伙伴。使用场景这是我最深度使用的工具。在VS Code中安装Cursor后它基于强大的模型如Claude 3、GPT-4提供沉浸式的编码体验。代码补全与生成在编写一个函数或组件时我只需写下注释或函数名Cursor能自动补全整个逻辑块。代码修改选中一段代码在Chat面板中输入指令如“将这段逻辑改为使用async/await”、“为这个组件添加PropTypes定义”它能精准地重构代码。错误诊断运行代码遇到错误时直接将终端报错信息粘贴给Cursor它能快速定位问题根源并给出修复方案甚至直接修改文件。文件操作我可以通过对话让它“在backend目录下创建一个新的tasks.js路由文件并实现GET和POST接口”它能一次性创建文件并写入基础代码。调试与优化阶段Gemini ChatGPT角色问题排查专家和代码评审员。使用场景当遇到Cursor也无法解决的复杂Bug或者想对某部分代码进行性能、安全性审查时我会将相关代码段和问题描述同时抛给Gemini和ChatGPT。不同模型有时会从不同角度给出解决方案对比分析它们的回答往往能获得更优解。例如在实现拖拽后状态更新的API时我会问“如何设计这个更新接口才能保证数据一致性并防止潜在的安全问题”3. 从零开始AI辅助下的项目搭建与核心实现3.1 初始化项目与基础结构搭建首先我们创建一个标准的Monorepo结构来管理前后端代码。打开终端执行以下命令mkdir ai-kanban-todo cd ai-kanban-todo npm init -y这会生成一个根目录的package.json。接下来我们分别创建前端和后端文件夹。这里我直接使用了Cursor的Chat功能在Cursor中打开项目在Chat面板输入“请为我初始化一个Monorepo项目包含一个React前端使用Vite和一个Express后端。前端在frontend目录后端在backend目录。”Cursor几乎在瞬间就为我生成了完整的命令和文件结构# 创建前端使用Vite比Create React App更轻快 npm create vitelatest frontend -- --template react cd frontend npm install # 返回根目录创建后端 cd .. mkdir backend cd backend npm init -y npm install express cors sqlite3 dotenv同时它还在根目录生成了一个便利的package.json里面定义了同时启动前后端的脚本// 根目录 package.json { name: ai-kanban-todo, version: 1.0.0, scripts: { start: concurrently \npm run start:frontend\ \npm run start:backend\, start:frontend: cd frontend npm run dev, start:backend: cd backend node server.js, install-all: npm install cd frontend npm install cd ../backend npm install }, devDependencies: { concurrently: ^8.2.2 } }install-all脚本可以一键安装所有依赖start脚本使用concurrently包同时启动前后端服务这比手动开两个终端方便太多了。这个细节就是AI基于常见实践给出的优秀建议。3.2 后端核心Express API 与 SQLite 集成后端的目标是提供对“任务”进行增删改查的RESTful API。在backend目录下我创建了三个核心文件server.js主入口、database.js数据库连接与初始化、routes/tasks.js任务路由。1. 数据库初始化 (backend/database.js)我让Cursor“创建一个SQLite数据库连接并初始化一个tasks表包含id, title, description, status, createdAt, updatedAt字段其中status默认为’todo‘”。它生成的代码如下import sqlite3 from sqlite3; import { open } from sqlite; import path from path; import { fileURLToPath } from url; const __filename fileURLToPath(import.meta.url); const __dirname path.dirname(__filename); // 初始化并连接数据库 async function initializeDatabase() { const db await open({ filename: path.join(__dirname, kanban.db), driver: sqlite3.Database }); // 创建tasks表 await db.exec( CREATE TABLE IF NOT EXISTS tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, description TEXT, status TEXT CHECK(status IN (todo, inProgress, done)) DEFAULT todo, createdAt DATETIME DEFAULT CURRENT_TIMESTAMP, updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP ) ); // 创建一个触发器当任务更新时自动更新updatedAt字段 await db.exec( CREATE TRIGGER IF NOT EXISTS update_tasks_timestamp AFTER UPDATE ON tasks BEGIN UPDATE tasks SET updatedAt CURRENT_TIMESTAMP WHERE id NEW.id; END ); console.log(Database initialized successfully.); return db; } export default initializeDatabase;这里有几个AI生成的亮点一是使用了更现代的sqlite包基于sqlite3的Promise封装代码更简洁二是使用了CHECK约束来确保status字段的值只能是三个预定义状态之一三是创建了一个数据库触发器自动更新updatedAt时间戳。这个触发器的引入完全超出了我的初始需求但AI基于“维护更新时间”这个通用最佳实践自动补充了这个细节非常专业。2. Express 服务器与路由 (backend/server.jsbackend/routes/tasks.js)在server.js中我让Cursor“创建一个Express服务器连接数据库启用CORS和JSON解析并将/api/tasks路径路由到单独的路由文件”。它清晰地设置了中间件和路由挂载。真正的业务逻辑在routes/tasks.js中。我通过对话逐步构建了所有端点GET /api/tasks获取所有任务可按状态过滤。POST /api/tasks创建新任务。PUT /api/tasks/:id更新任务包括拖拽后的状态变更。DELETE /api/tasks/:id删除任务。以更新任务的PUT接口为例我给出的提示是“实现更新任务详情的接口要能处理部分字段更新并且如果状态改变了需要记录状态变更历史。请使用SQLite参数化查询防止SQL注入。”Cursor生成的代码不仅处理了基础更新还引入了一个statusChangeHistory的概念虽然在实际中我简化为了一个lastStatusChangeAt字段并严格使用了?占位符的参数化查询安全性考虑得很周到。// backend/routes/tasks.js 片段 - 更新任务 router.put(/:id, async (req, res) { const { id } req.params; const { title, description, status } req.body; const fieldsToUpdate []; const values []; if (title ! undefined) { fieldsToUpdate.push(title ?); values.push(title); } if (description ! undefined) { fieldsToUpdate.push(description ?); values.push(description); } if (status ! undefined) { fieldsToUpdate.push(status ?); values.push(status); // 可以在这里添加逻辑将状态变更时间戳存入另一个表作为历史记录 } if (fieldsToUpdate.length 0) { return res.status(400).json({ error: No fields to update }); } values.push(id); // WHERE id ? 的参数 const sql UPDATE tasks SET ${fieldsToUpdate.join(, )} WHERE id ?; try { const result await db.run(sql, values); if (result.changes 0) { return res.status(404).json({ error: Task not found }); } const updatedTask await db.get(SELECT * FROM tasks WHERE id ?, id); res.json(updatedTask); } catch (error) { res.status(500).json({ error: error.message }); } });3.3 前端核心React看板与拖拽实现前端部分我选择使用dnd-kit库来实现拖拽功能这是目前React生态中最强大、最灵活的拖拽库。使用AI辅助开发UI组件效率极高。1. 状态管理与API调用首先我创建了一个services/api.js文件来封装所有对后端API的调用。我告诉Cursor“创建一个Axios实例基础URL指向http://localhost:5000/api并导出getTasks,createTask,updateTask,deleteTask这几个函数。”它一气呵成还贴心地处理了错误。然后在主要的App.jsx中我使用React的useState和useEffect来管理任务状态和获取初始数据。这里我让Cursor“编写一个函数将任务数组按照status分类为todo, inProgress, done三个列表”。它给出了一个非常简洁的reduce函数实现。2. 看板列与拖拽上下文我创建了三个主要组件KanbanBoard.jsx看板容器、KanbanColumn.jsx状态列、TaskCard.jsx任务卡片。在KanbanBoard中需要设置dnd-kit的核心上下文。我的提示词是“使用dnd-kit的DndContext, DragOverlay, PointerSensor和KeyboardSensor来设置一个拖拽上下文。需要处理onDragStart,onDragOver,onDragEnd事件。使用sortable策略进行垂直排序。”Cursor生成的代码几乎可以直接运行它正确配置了传感器、碰撞检测算法和屏幕阅读器无障碍支持这些都是手动配置容易忽略的细节。3. 任务卡片与拖拽感知TaskCard组件需要既能作为拖拽源也能作为放置目标。我让Cursor“使用dnd-kit的useSortable钩子将这个div变成一个可排序、可拖拽的项目。根据isDragging状态改变样式。”它生成的代码包含了必要的attributes,listeners,setNodeRef并应用了拖动时的透明度样式。4. 列组件的放置逻辑KanbanColumn的逻辑更复杂一些它需要作为放置目标接收被拖拽的任务。我给出的指令是“使用dnd-kit的useDroppable钩子使这个列能接收拖拽过来的任务。当有任务被拖拽到其上时高亮显示列。”AI不仅实现了基本功能还添加了一个根据isOver状态改变背景色的视觉反馈提升了用户体验。5. 拖拽结束后的状态更新这是前后端联调的关键。在onDragEnd事件处理函数中我需要判断拖拽的起点和终点然后调用后端的updateTaskAPI来更新任务的状态。我向Cursor描述了整个逻辑“如果拖拽项是一个任务并且落在了不同的列里就找到这个任务的数据将其status更新为目标列的status然后调用更新API。”它帮我写出了清晰的判断逻辑和状态更新代码并处理了拖拽取消的情况。4. 样式与用户体验的AI优化4.1 利用Bolt和Visily生成设计系统虽然功能实现了但最初的界面非常简陋。这时我转向了UI设计工具。我首先在Visily中输入“设计一个现代简约的看板界面包含三个列使用柔和的色系如蓝色、黄色、绿色区分状态卡片有轻微的阴影和圆角。”Visily在几分钟内给出了多个设计方案我选择了一个最符合Material Design风格的布局作为参考。接着我使用Bolt来生成具体的组件代码。我将Visily的截图和描述同时提供给Bolt“根据这个线框图生成一个React组件使用Material-UI库实现这个看板列的样式。列标题要大而清晰卡片间距均匀。”Bolt输出了高质量的JSX和sx样式代码直接复制到我的KanbanColumn组件中稍作调整就获得了专业的视觉效果。对于TaskCard我让Bolt生成一个“包含标题大字体、描述小字体、灰色、以及创建时间和更新时间的两行文本所有内容有合理的内边距”的卡片。它生成的代码甚至考虑了文本溢出时的省略号处理。4.2 交互反馈与状态指示一个好的UI必须有即时的反馈。我通过AI辅助添加了以下细节加载状态在App.jsx中我让Cursor“在调用API获取数据时添加一个加载中的状态并显示一个环形进度条”。它引入了MUI的CircularProgress组件。操作反馈创建、更新、删除任务后应该给用户一个提示。我使用MUI的Snackbar轻提示组件。我告诉Cursor“在成功创建任务后显示一个‘任务已创建’的Snackbar提示持续3秒。”它帮我将状态管理和触发逻辑集成到了事件处理函数中。空状态当一个列里没有任务时应该显示友好的提示。我在KanbanColumn组件中添加了条件渲染“如果任务列表为空显示一个带有图标和文字‘暂无任务’的占位区域。”AI建议使用MUI的Typography和InboxIcon来实现效果很好。5. 开发中遇到的典型问题与AI调试实录即使有AI辅助开发过程也绝非一帆风顺。下面记录了几个典型问题以及如何利用AI工具解决它们。5.1 问题一拖拽后任务状态更新了但列表没有立即重新渲染现象在前端拖拽一个任务到另一列后调用更新API成功后端数据库状态已改变但前端UI上的任务没有立刻移动到新列需要手动刷新页面。排查过程我首先检查了浏览器网络控制台确认PUT请求返回了200状态码和更新后的任务数据。然后检查前端onDragEnd函数中的状态更新逻辑。我发现我只是调用了updateTaskAPI但没有用其返回值来更新本地的React状态。AI辅助解决我将相关的onDragEnd函数代码和问题描述发给Cursor“你看这段代码API调用成功了但tasks状态没有更新所以UI不变。我应该如何在成功回调里更新本地状态”Cursor立刻指出问题并给出了修改建议在updateTask的.then()回调中根据返回的更新后数据使用setTasks更新整个任务列表或者更高效地使用map方法只更新那一个任务。// 修改后的代码片段 updateTask(activeTask.id, { status: newStatus }) .then(updatedTaskFromServer { // 更新本地状态 setTasks(prevTasks prevTasks.map(task task.id updatedTaskFromServer.id ? updatedTaskFromServer : task ) ); enqueueSnackbar(任务已移动到${newStatusLabel}, { variant: success }); }) .catch(error { enqueueSnackbar(更新失败: error.message, { variant: error }); // 可选将任务状态回滚到拖动前 });5.2 问题二SQLite中updatedAt字段在更新时没有自动变化现象虽然定义了触发器但通过API更新任务后返回的数据中updatedAt字段还是旧的时间。排查过程我首先直接查询数据库发现updatedAt确实没变说明触发器可能没生效。我检查了触发器SQL语法是正确的。我怀疑是更新语句没有实际改变任何行导致触发器不触发。于是我在PUT接口的更新语句后添加了一条SELECT语句来验证。AI辅助解决我把database.js中的触发器代码和路由中的更新代码片段发给ChatGPT并描述现象。ChatGPT首先肯定了我的触发器逻辑是正确的然后提出了一个关键点在SQLite中如果UPDATE语句没有导致任何字段的值发生“实际”改变例如将title设置为它本身那么触发器可能不会触发。它建议我在更新前先检查字段值是否真的不同或者确保前端总会发送一些可变的字段。但更简单的解决方案是在UPDATE语句中强制设置一个无关紧要的字段比如updatedAt CURRENT_TIMESTAMP但这会与触发器冲突。最终我们采用了更干净的做法在更新路由中如果状态改变我们显式地更新一个lastStatusChangeAt字段这个字段没有触发器而对于updatedAt则完全信任数据库触发器。我们修改了UPDATE语句确保至少有一个字段如title或description被包含在更新中即使值不变。这确保了触发器的执行。5.3 问题三跨域请求CORS错误现象前端运行在localhost:3000后端在localhost:5000前端调用API时浏览器控制台报CORS错误。排查过程这是一个非常经典的问题。我检查后端代码发现虽然安装了cors包但没有正确配置。AI辅助解决我直接问Cursor“我的Express后端报了CORS错误我已经安装了cors包应该如何配置才能允许来自localhost:3000的请求”Cursor给出了标准且安全的配置方案import cors from cors; const app express(); // 配置CORS app.use(cors({ origin: http://localhost:3000, // 只允许前端域名 credentials: true // 如果需要传递cookies则设置为true }));它解释说在生产环境中origin应该配置为实际的前端域名数组而不是简单的*允许所有以增强安全性。这个解释让我理解了配置背后的原因。6. 项目优化、部署与AI辅助的边界思考6.1 性能与体验优化点在基本功能完成后我利用AI对项目进行了一些优化防抖搜索我让Cursor“在任务列表上方添加一个搜索框输入时过滤任务标题和描述。使用防抖技术避免频繁触发过滤函数。”它引入了lodash.debounce库并实现了防抖逻辑。本地存储备份为了在网络不稳定或后端未启动时提供基本功能我添加了将任务备份到localStorage的逻辑。AI帮我设计了一个优雅的降级方案优先从API获取失败则读取本地备份任何更新同时写入API和本地存储。代码分割与懒加载虽然项目不大但我还是让Cursor演示了如何用React.lazy和Suspense对看板组件进行代码分割为未来功能扩展做准备。6.2 部署到Vercel与Render我想把这个项目部署到网上供人体验。对于前端Vite React应用Vercel是最佳选择对于后端Node.js SQLite我选择了Render。部署前端到Vercel将代码推送到GitHub仓库。在Vercel中导入该项目选择frontend目录作为根目录。Vercel会自动检测到是Vite项目并配置好构建命令和输出目录。关键步骤需要设置环境变量将API请求的基地址从http://localhost:5000改为部署的后端地址。我让Cursor帮我写了一个适配环境的配置// services/api.js const API_BASE_URL import.meta.env.VITE_API_BASE_URL || http://localhost:5000; export const axiosInstance axios.create({ baseURL: API_BASE_URL });然后在Vercel的项目设置中添加环境变量VITE_API_BASE_URLhttps://your-backend-app.onrender.com。部署后端到RenderRender支持直接部署Node.js应用。在Render控制台创建新的Web Service连接GitHub仓库。关键问题SQLite是文件数据库而Render的服务器是无状态的文件系统是临时的。这意味着每次部署或重启数据库文件都会丢失。AI提供的解决方案我将这个问题抛给ChatGPT和Cursor。它们给出了几个方案方案A临时演示在启动服务器时如果数据库文件不存在则自动创建一个并插入一些初始数据。这保证了应用始终有数据可展示但数据无法持久化。方案B使用持久化存储将SQLite数据库文件存储在Render的持久化磁盘Disk功能中或者切换到真正的云数据库如PostgreSQL。对于这个练手项目我选择了方案A因为它的目的是演示功能而非持久化数据服务。AI帮我修改了database.js的初始化函数添加了插入初始演示数据的逻辑。6.3 对AI辅助开发的反思与心得经过这个项目的完整实践我对AI编程工具的能力和边界有了更深的体会。优势与提效消灭了“从零开始”的恐惧面对一个空白项目目录AI能快速搭建起骨架让你立刻进入“填充血肉”的实质开发阶段心理门槛大大降低。极佳的“脚手架”和“代码片段”生成器对于创建标准化的文件结构、编写重复性的样板代码如API路由、React组件模板、实现常见功能如表单验证、数据过滤AI的速度和准确率远超人类。优秀的“技术顾问”和“调试伙伴”遇到错误信息时AI能快速解读并给出修复方向。对于不熟悉的技术点如dnd-kit的具体用法它能提供准确的代码示例和配置说明比翻阅文档更直接。激发灵感与提供备选方案当你对某个实现犹豫不决时可以让AI给出2-3种不同的实现方案并分析其优劣帮助你做出更明智的决策。局限性与注意事项缺乏真正的系统设计与架构能力AI擅长执行具体指令但无法替你进行高层次的系统架构设计。项目的核心数据流、状态管理方案是否引入Redux/Zustand、模块拆分粒度仍然需要开发者自己把握。AI可能会给出过于复杂或过于简单的建议。代码可能“正确”但“不优雅”AI生成的代码功能上可能没问题但有时会冗余、缺乏抽象、或不符合项目特定的代码风格。你需要具备足够的代码审查和重构能力不能无脑接受。对业务逻辑的理解是短板AI不理解你项目的独特业务规则。例如在这个看板应用中“一个任务能否从‘完成’状态拖回‘进行中’”这个业务规则AI无法替你决定需要你明确告知。存在“幻觉”风险AI有时会生成看似合理但实际无法运行的代码或引用不存在的库、API。你必须对生成的结果保持警惕在真实环境中进行测试。我的核心工作流建议 将AI定位为你的“超级实习生”或“结对编程伙伴”而不是替代者。你仍然是项目的总工程师负责提出正确的问题指令清晰、具体、分步骤的提示词是成功的关键。制定架构与规范决定技术栈、项目结构、代码规范。进行代码审查与集成仔细检查AI生成的每一段代码理解其原理并将其优雅地集成到你的项目中。掌控业务逻辑所有与核心业务规则相关的决策和实现必须由你亲自把关。这个由AI深度参与构建的看板应用最终代码清晰、功能完整、UI美观。整个过程让我深刻感受到AI工具已经不再是玩具而是能够显著提升开发效率、降低认知负荷的实用生产力工具。它并没有取代编程而是重新定义了编程的形态——开发者更多地扮演架构师、审查者和整合者的角色而将大量模式化、查找式的工作交给了AI。对于学习者而言这或许是一个更高效的学习路径在AI的帮助下快速实现一个可运行的项目获得正反馈然后再去深入理解AI生成的每一行代码背后的原理。