GitHub 地址:https://github.com/dom-bro/task-manager

虽说是一个任务管理系统,但简单地讲,其实就是任务的增删改查(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.vueasync submitNewTask () {  await axios.post('/task/upsert', this.task)},

由于服务器域名和开发服务器域名不一致,所以需要在 main.js 里设置一下服务端的域名

// main.jsaxios.defaults.baseURL = `${location.protocol}//${location.hostname}:${SERVER_PORT}`

为了简单起见,看板暂时先放在 src/pages/Home.vue

关键代码就是定义任务的状态

// src/pages/Home.vuetaskStatus: {  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 的优势。