雖說是一個任務管理系統,但簡單地講,其實就是任務的增刪改查(CRUD)。 其中最重要的又當屬增,即創建任務,此為數據之源,刪改查都依賴於它所產生的數據。接下來就從交互設計到前端,服務端,資料庫一步步去實現任務的創建。 ...
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.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 的優勢。