GitHub 地址:github.com/dom-bro/tas…
虽说是一个任务管理系统,但简单地讲,其实就是任务的增删改查(CRUD)。
其中最重要的又当属增,即创建任务,此为数据之源,删改查都依赖于它所产生的数据。
交互设计
凭着程序员的直觉,最初做成了一个表单如下图,表单项也对应了数据库中的表的字段,简单直接。
后来经过同事的建议,对比了 tower,teambition,worktile 这些成熟产品的交互设计。
tower 看板如下图所示
teambition 看板如下图所示
worktile 看板如下图所示
发现看板形式确实是比较适合任务的展现的。于是最终改版为如下任务卡片看板
任务的创建和展现全部放在一块看板上。交互路径更短,更易用。
数据库表结构设计
作为一个地地道道的前端,数据库知识依然来源于大学时期的残存。为了最小化学习成本,自然而然选择了 MongoDB。使用 MongoDB 可以简单地理解为操作 json 对象,写数据库也只是把一堆 json 对象存到了数据库里。
MongoDB 为每个主流编程语言都提供了相应的 driver,直接给 node 提供了一个 npm 包。
npm i mongodb
接下来就开始设计数据库里的第一张表,任务表。任务表的数据结构完全由一个任务的组成因素去映射。
想一想实际工作中的任务是怎样的
-
任务标题
显然必不可少,除了这个字段必选,其他都是可选项
-
任务排期
至关重要,是之后汇总周报,季报的依据。想必在座的各位都被催过排期吧
-
需求文档
链接也好,文字描述也好,凡是需求相关的通通放进来,好记性不如烂笔头。什么!没有需求文档!全靠嘴说脑记!珍惜生命,趁早放手吧。然鹅这只是辅助记录而已,对频繁需求变更这个老大难问题着实是无能为力哈哈
-
相关人员
产品,UI,后端,测试,各个岗位的对接人得清楚。
-
项目分支
项目再多,分支再乱,也别搞错哦。分支搞不对,加班两行泪。
好了,为了简单起见,先暂定这几个字段吧。其他字段可根据需要再增加。
目前任务的数据结构大致如下
{
title: String, // 任务标题
schedule: [String, String], // 任务排期,[开始时间,结束时间]
doc: { // 相关文档
pm: String, // 需求文档
ui: String, // 设计文档
api: String, // 接口文档
},
workmate: { // 相关人员
pm: Object, // 产品
ui: Object, // UI
api: Object, // 后端
qa: Object, // 测试
},
repos: [ // 项目分支
{
name: String, // 项目名称
branch: String, // 分支名称
}
],
status: String, // 任务状态 未开始|开发中|已提测|已完成
}
后端实现
这里只需要一个创建的接口即可
在开发接口的过程中可能需要频繁重启服务来测试接口,所以在开始开发接口之前,隆重引入一个新轮子 nodemon,服务端进程就由它来守护,实现文件变更时重启服务器。
可以在根目录给 nodemon 一个配置文件 nodemon.json,简单配置下
{
"watch": [
"server.js"
]
}
这样在改变 server.js 的时候服务器就会自动重启
好了,接下来就开始写创建接口
由于是数据库写入,这显然是一个 POST 请求,koa 需要一个中间件来解析 post 请求出入的参数。
npm i koa-bodyparser
使用起来也极其简单,koa 中间件使用方式都一样
import bodyparser from 'koa-bodyparser'
app.use(bodyparser())
万事俱备,只欠写入数据库了
import { MongoClient, ObjectId } from 'mongodb'
// 连接数据库
const client = new MongoClient('mongodb://localhost:27017')
router.post('/task/upsert', async (ctx, next) => {
// 要操作的数据库
const db = client.db('task-manager')
// 要操作的表,mongodb 中叫做集合
const collection = db.collection('task')
// post 请求的参数经 bodyparser 后放在 ctx.request.body 里
const doc = ctx.request.body
const { _id } = doc
const result = await collection.updateOne(
// _id 是 mongodb 默认主键名,ObjectId 可用于生成一个唯一 id
{ _id: _id || ObjectId() },
{ $set: doc },
// upsert 表示存在则更新,不存在则插入
{ upsert: true }
)
// 接口返回
ctx.body = {
doc,
result,
}
})
这里只需要关注一个 api,mongodb 的 db.collection.updateOne(),用于数据的插入或更新。
前端实现
根据交互设计,任务的查看和创建都在同一个页面,即看板视图。
在 components 目录新建一个组件 NewTaskCard.vue
关键代码就是请求创建任务接口
// src/components/NewTaskCard.vue
async submitNewTask () {
await axios.post('/task/upsert', this.task)
},
由于服务器域名和开发服务器域名不一致,所以需要在 main.js 里设置一下服务端的域名
// main.js
axios.defaults.baseURL = `${location.protocol}//${location.hostname}:${SERVER_PORT}`
为了简单起见,看板暂时先放在 src/pages/Home.vue
关键代码就是定义任务的状态
// src/pages/Home.vue
taskStatus: {
draft: '未开始',
dev: '开发中',
qa: '已提测',
done: '已完成',
},
最后
实现效果如下
正文结束。点击查看代码变更
闲言碎语
mongodb or mongoose ?
mongodb 包是 MongoDB 官方给 node.js 出的 driver,通过它就可以直接调用数据库的 api,就像直接在 shell 中使用数据库一样方便。
mongodb 相对传统 MySQL 这种数据库,最重要的区别就是没有了表的概念,取而代之使用集合,集合中的每一条数据甚至不需要结构相同。
例如 mongodb 的集合中可能存的是这样子的数据
[
{ a: 1, b: true },
{ a: 'DOM', c: [ { d: null } ]}
]
一句话,自由,随便存,只要是 json 就能往里存。
mongoose 则是为了重现表的概念,核心概念是 Schema 和 Model,Schema 用来定义数据结构,Model 用来定义表。这样使得集合中的数据结构严整统一,少有冗余,像一张 excel 表格一样。当然 mongoose 还提供了其它高级特性,但我还不太熟悉,这里不再赘述。
为了减少 mongoose 的概念和知识产生的额外学习成本,这里就选择直接自由自在的操作 mongodb 吧
有对 mongoose 了解的同学欢迎评论区补充相对 mongodb 的优势。