《刺胳針》期刊觀點 : Improvising Medicine – cultivating ensemble

screenshot.png

這是《刺胳針》期刊上的觀點專欄,是看到Eric Topol在Twitter上的轉推,優美的一篇文章。

Improvise, Verb 在劍橋字典上的定義:

  • to invent or make something, such as a speech or a device, at the time when it is needed without already having planned it
  • When actors or musicians improvise, they perform without prepared speech or music, making up the play, music, etc. as they perform it.

閱讀的時候也是蠻困惑什麼叫Improvising Medicine,難道是即興的治療嗎?那不就是等於隨便治療,原來他想表達的是醫療場域中各領域人員的合作就是一場即興表演,但在醫學教育中往往知識的記憶,技術的演練變成核心,但假如用爵士表演來比喻,那是在練習樂器的聲音本身。

Cultivating ensemble

作者Roger KneeBone醫師提到將醫療場域的行為,類比於爵士樂,是閱讀到一篇2007年由醫師同時也是爵士史愛好者Paul Haidet所寫的:

how jazz musicians become expert in “cultivating ensemble"(working with a team), “creating space" for the music to unfold, and “developing voice"(a fresh and original sound)

作者提到其外科訓練過程中,往往專注在手術的技術習得上,但直到其行醫多年後,才慢慢理解到不能只沈浸在自己的“手術”,還要在意團隊成員在做什麼,適時的保留空間,讓其他人發揮。我想這段想法其實可以拿來用在團隊合作,關鍵在留給別人空間,而非只關注在自己想要的事物上。

The confidence and stability only emerged from a long period of uncertainty, and trying on multiple identities before settling on one that worked.

閱讀參考:

Kneebone, Roger. Improvising medicine. The Lancet , Volume 391 , Issue 10135 , 2097

 

 

探索資料庫應用(二):Taiwan Biobank台灣生物資料庫計畫

台灣生物資料庫計畫是2012年開始的大型資料收集計劃,針對台灣人年紀在30-70歲之間的對象收集健康醫療資料,收集據點遍佈全台灣,第一波收集的方式主要向社區招集自願的民眾,用醫療卡車的方式來吸引民眾去貢獻自己的資料,第二波則是面向各醫學中心,針對十二項慢性疾病為目標,來招募患者。

screenshot.png

screenshot.png

第一波一般民眾預計招募20萬人。

這些個案會追蹤骨質疏鬆狀態、消化系統(腹部超音波)、甲狀腺(TSH, T3, T4, free T4)、心血管疾病(頸動脈超音波)、心電圖(心電圖)。檢體方面,則收集了血液、尿液。比較特別的是還做了各種完整的基因體資訊從基因晶片、甲基化、HLA分型、次世代定序等,是非常完善的資料。

第二波針對特定疾病

十二種疾病分別是肺癌、乳癌、大腸直腸癌、肝癌/肝炎、頭頸癌、心臟血管疾病、糖尿病、腦中風、阿茲海默氏症、氣喘、子宮內膜異位、慢性腎臟疾病),與台大、北榮、北醫、三總、中榮、中國醫、中山醫、彰基、成大、高醫、高榮和慈濟合作,預計招募10萬人。

Taiwan BioBank 開放基因資訊瀏覽器

目前台灣生物資料庫有架設基因資訊瀏覽器,方便去觀看目前的一些基因體資訊,網站功能蠻方便的,可以選擇資料來源的技術(illumina, Ion Proton, Affymetrix array),使用染色體位置或是基因區段來瀏覽資料。screenshot.png

screenshot.png

可申請的檢體種類

目前可以申請的檢體種類有尿液、血液,相關的資料可以有問券資料、理學檢查資訊、實驗資訊(各式基因資訊)。

screenshot.pngscreenshot.pngscreenshot.png

資料來源:
台灣生物資料庫計畫 沈志揚、楊懷壹、褚候維

書籍閱讀:Being Online在線-數據改變商業本質,計算重塑經濟未來

 

愛迪生發明了夜生活

screenshot.png

這算是本老書,在2016年出版的,去年2017年底商業周刊重新發行了繁體版,稍微更訂了內容。作者是王堅,再加入阿里巴巴之前曾任微軟亞洲研究院常務副院長、杭州大學、浙江大學心理系教授、博士生導師兼任主任,再加入阿里巴巴後從一開始擔任首席架構師到現在阿里巴巴集團技術委員會主席。

這本書在用一個非常寬廣且初心的方式去闡述這世界因為網路而造成的改變,其中在談“在線”這件事情對於人類的影響。

這邊有關於王堅的演講視頻在騰訊大學裡分享技術領導力的影片

2016年的氛圍大都在談論“大數據”,而此本書便是在這樣脈絡下撰寫的,現在閱讀起來完全不會讓人感受到“不受用”,對比當下在“人工智能”的氛圍,其實這本書談論的“在線”更加本質,的確是可以幫助自己以更高的角度來看當下所處的時代。書中寫道:

在線,需要以世紀這一時間單位來度量,來進化,來討論

從數字到在線的進化

有了網路之後,許多事情影響力都放大了,不論是生產連接器的公司、戰亂地區的極端事件影片之散播、地圖從傳統數據轉成在線地圖,到把研究論文放置到arxiv網站的數學家,維基百科對比於大英百科全書,“連接”這件事情在本質上跨大了單點的影響力,且賦予了新的意義。以前獲取5000萬用戶需要50-60年,而如今的時代一個服務要獲得一億用戶,可能只需要兩年的時間,一個人能用好雲計算公司的服務,哪怕只有一個人,也可以擁有10000人公司的計算能力。

從信息到數據的進化

數據比功能重要

在互聯網的時代,數據才能帶來不可替代的社會價值,用數據去解決一個今天已知的問題,那部匯市數據最大的價值,頂多只是眾多價值中的一部分,數據可以讓你看到你還不知道的問題,同時幫你解決這個問題。為何數據比功能還重要呢?王堅舉了三個例子:(仔細思考,會發現這邊可以用另一個角度來看,可以參W.Brian Arthur的書The Nature of Technology,如何去看待到科技)
1. 伽利略的望遠鏡:讓人們可以觀察地球以外的世界
2. 顯微鏡:讓世界可以開始理解微觀世界
3. 雷達:讓現代戰爭成為數據的戰爭

如何讓在線的數據成為可能,要先有成熟而令人信任的雲服務,此時所謂的大數據才能開始奠基,而在線的數據緊接者有另一個特點,來加速其擴大,便是利用開放與分享的特性,開放不是免費,開放的關鍵在於原則上大家處於平等的關係,分享的重點則在於資源不會那麼容易被獨佔,壟斷變得非常困難。這一章感覺是在埋阿里雲的理念,假如未來數據是最具價值的,而這些數據是以在綫方式存在,那麼阿里雲的存在,好比銀行在上個世紀的角色,所以阿里雲必須在這個點上努力爭取讓使用者對其的信任。

從計算機到計算的進化

事物之間的區別,有時候不是由它的物理型態決定,而是由他的服務形式決定

這章談的是將雲計算的普及會遭遇的障礙類比於電力系統普及的過程,以前沒有大型電網系統的時候,人們需要用發電機來支持自家的電力供應,當特斯拉用交流電的方式發展大型電力系統時,曾經造成人們的恐懼,如今電力之於人類,就像是服務一樣容易取得,而計算有一天也會脫離所謂的計算機,變成人們唾手可得的服務一樣。

這章最後談了一個關於電影院與空調兩個事物,相輔相成的例子來談雲計算普及後,將只是未來變革的其中一個化學反應,而另一個就是各行各業的應用場景。

兩樣東西的結合,可以產生化學反應從而誕生新的東西。雲計算就是化學反應的一部分。

“過去看電影,只能晚上看,因為天黑了才能看清影像;另外,看電影還得冬天看,如果是夏天,門一關,一堆人擠在電影院裡面,會熱壞的。

1925年,威利斯。開利(Willis Carrier)說服派拉蒙電影公司在紐約時代廣場新建的李沃立大劇院安裝空調系統,承諾能為其觀眾提供涼爽的空氣。劇院開張時非常成功,人們紛紛湧向劇院,空調變得和電影本身依樣享受空調就是在電影院裡面。五年內,全美300多家影院安裝了空調系統。”

App很好,Web更好

我感覺App裡面是長不出亞馬遜叢林的,但是互聯網上可以找出雅馬遜森林,Web上也可以

這章從APP系統談到Web世界,主要在談阿里自主研發的YunOS,其大概的開發始由,緊接者則是談論所謂線上和線下的融合,關於生活因為在線後邊界的改變。

推薦閱讀:

王坚博士,那个行走于现在与未来之间的大顽童 | 二叉树视频

初探系統設計概念:基本流程、需求

閱讀Ray Dalio那本時,腦中印象深刻的是“計劃(設計)永遠優先於執行”這句話,畢竟在推崇執行力至上的社會氛圍,常會把花很多時間在設計及思考的行為認定為守舊和保守,但在某方面來說,我更相信經典背後的智慧如:"人無遠慮,必有近憂",這恰恰呼應了Ray Dalio提到的,這算是在“認知模式”層面的想法,鐵定可以套用在各領域,比如專案管理或是產品開發,甚至是這篇想要分享的軟體工程概念(不在文章標題寫上"軟體工程",是希望….這篇的觀念是可以用來在更高層次來活用的)。

軟體工程是門頗有趣的學科,會發現裡頭其實藏了很多不確定性,甚至可以說是在工程學門裡加入人文色彩。另一方面,這套想法其實也能套用在做其他事情,規劃研究計畫、解決日常問題、臨床問題的處理,其實都能把這些做法稍稍修改套用進去。

傳統的軟體開發:瀑布式開發Waterfall

瀑布式開發,可以想成是為了“一次成功”的開發方式,從客戶需求到完成成品,一次完成。這方法在過往非互聯網時代,開發桌機軟體時,頗適合的,因為使用者的反饋慢,需求變化少,產品週期慢。

瀑布式開發最簡單的流程如下
Requirement => Design => Implementation => Validation => Operation/Maintenance

顧聞思義,一步步地從定義需求、設計軟體架構、小代碼實踐、驗證、操作和維持,在以往的桌機軟體就是以這種模式來進行的,當然,隨者業務的變化特性,鐵定會有不同變形存在,比如 V-model/ Sashimi Model/ RUP model/ Incremental Model/ Spiral Model。

敏捷開發(Agile): 更多偏向心態(mindset)的轉變

如此,隨者軟體使用者的情境改變,開發和產品週期變短變快,所謂的敏捷開發(Agile)概念被提出來,而敏捷開發談的是開發時心態的改變,而非一套死板的實踐方法,強調短週期開發閉環,如scrum開發,其實就是建立在敏捷開發上的實踐版本。

許多的基於這框架下的想法和觀念被提出,其實核心思想都在思考如何在新的開發和產品週期特性下,組建適合的團隊和心態:
1. Lean startup
2. Design thinking
3. Project Aristotle Google
4. Growth Mindset
5. Dan Pink Drive
6. Autonomy Mastery Purpose

 簡單來說,就是以漸進式地方式演化和迭代,且盡量每個週期能得到最終使用者的意見回饋,而團隊的建立也必須有良好的內在激勵方式和讓團隊成員有能犯錯的安全感。

開發起手式:需求與規格 Software Requirement Specification

就像任何計畫的關鍵是訂立目標,而軟體的構建是為了滿足需求,和特定規格,所以軟體工程的第一步確立需求和規格。在釐清需求以及規格的過程,是以問題導向為主的方式,這過程會建立清楚地描述,來區分什麼是所設計系統要符合的要件,用什麼判斷設計出的系統是對的還是錯的,而其中會產生出文檔,就是傳統所說的SRS文件,這件事的重要性在於釐清了需求和規格,才能在經濟上去估算成本,以及工程實做面去降低支出。

需求規格文檔其撰寫的對象有兩個,或者說是兩類語言,一個是用使用者所熟悉的語言(需求),一個則是開發者的角度(規格),而通常還會有一部分所謂的non-functional requirement,就是無法歸類在使用者的需求或是根據與此衍生的規格,可分三類:

1. Product Requirement:可能產品大牛有特定的想法,作為開發時候的方向
2. Organization Requirement:在公司內開發的話,會有針對現有使用技術的規格
3. External Requirement:比如醫療軟體上會有法規的要求

而是否有好用的模型可以用來分析需求或是規格呢?那WRSPM model算是蠻傳統好用的

screenshot.png

如同這模型所顯示的,右邊的系統是我們要設計出來的,而左邊則代表真實世界(或社會環境),需求來自於社會環境,而規格則是藉由理解工程的人理解需求後對應出來的。

閱讀參考:

Software Development Processes and Methologies

TED: Dan Pink 出乎意料的動機
What Google Learned From Its Quest to Build the Perfect Team
Managing the risk of learning
Automony Mastery Purpose

康威法則Conway’s Law: 人與系統和功能的關係

康威法則(Conway’s Law)在看極客邦楊波對於架構的介紹時提到的,任何組織所設計的系統架構最終都會反應其組織內的溝通架構。

Any organization that designs a system (defined more broadly here than just information systems) will inevitably produce a design whose structure is a copy of the organization’s communication structure.   

——Melvin Conway, 1967

 

讓人與人和機器與機器工作時,溝通效率和開發效率變成一件很重要的事情,這讓我聯想到人體這複雜系統,似乎也是如此,“how cool it is!!”,我們有各種器官,而器官的組成細胞功能都緊緊扣緊其最終要提供的功能,來看看肝臟細胞:

「liver cell」的圖片搜尋結果

哇喔!很美的組織架構吧!假如細胞是一個人,那麼這組織非常有效率地構築了這個器官!在肝臟裡面的細胞,不會跟肌肉細胞一樣,果然很多事物的原理其實都有那麼共通的法則,之餘生物,當然也之餘人類社會。cool!

反之,康威法則也讓我聯想到,當細胞數量很少(員工很少時),就不要把系統架構弄得太複雜,如同人類胚胎組織在發育的過程,細胞的可塑型強,也就沒那麼需要有層次,區分說哪個細胞要幹什麼,逐漸逐漸地隨者胚胎變成一個人類,架構也就這麼演變成非常複雜,隨者功能需求的改變,所以公司成長之於細胞發育原來是有那麼一點的相似。

來看看這由Manu Cornet所繪製的組織架構圖,是不是有那麼一點跟他們的產品性質很像呢!

“正經"的相關閱讀:

youtube

Hacking Conway’s Law – Raffi Krikorian

部落格

楊波 每個架構師都應該了解一下康威法則

Apply Conway’s Law

康威定律:這50年前都提出來的概念你聽過嗎

 

簡介 快速健康照護互通資源 (FHIR)及相關學習資源

HL7-FHIR快速健康照護互通資源(FHIR)是HL7組織所推出貼近移動醫療的資訊交換標準,關於HL7的介紹可以看這兩篇之前所撰寫的Health Level 7(HL7)研究筆記:五大基本架構, 健康資訊交換第七層協定(Health Level Seven):醫療資訊系統的框架

這邊有兩篇由林口長庚王子豪教授在台灣精準醫學協會所撰寫的介紹文,可以作為入手:

1.快速健康照護互通資源 (FHIR)

2.基於FHIR標準來整合基因體數據到臨床應用的進展

近一步如何利用FHIR的標準,則可以觀看下面這一系列由英國Interop Summit在2018的youtube影片:

Part 1: Welcome and introduction to modelling with ClinFHIR
Part 2: Introduction to FHIR
Part 3: Building a Scenario
Part 4: Structured Data and Profiling

這四部影片,主要由Dr. David Hay這位FHIR標準制定核心人物來講授,教臨床醫師如何將臨床需求轉換成以FHIR標準的模型,使用clinFHIR這個為了讓初學者方便學習使用FHIR概念的網頁工具。

在往FHIR細項的文檔來爬梳,可以閱讀Dr.David Hay的部落格,裡面提供非常多第一線關於FHIR的洞見。

使用vue2+koa2搭建小應用:FHIR questionaire ,符合國際醫療傳輸問券資料格式之json檔產生器

前言

因為不太想花太多時間在學複雜前端框架,所以挑選的工具都是以簡單輕量為主,因手頭最近在設計FHIR相關的應用,藉此機會來稍為活用一下,建一個小工具可以產出符合FHIR格式的問券資料。

框架挑選上,用vue2koa2這兩個輕便型的前端和後端js來完成一個完整的應用程式開發,並且記錄一下實作過程的坑和概念。

為何要用框架?

因為想把時間用在真正開發生資或是醫療應用,然後快速地跟別人分享。

前陣子因為研究需要,用d3.js來設計一些生醫資料視覺化( R無法滿足複雜的視覺設計,只好再跳坑),因此摸了前端的基本概念,而純用html, css, d3.js的過程,讓我發現實在有必要使用一些框架,來減輕花在非“重要”代碼的時間,於是查找了一些前端的框架,其中vue2讓我頗心動,因為其簡單和輕量的出發點。

另外,koa則是在把之前專案視覺化成果放在google cloud時用到的,同樣輕便簡單的特性符合我後端服務器的需求。其洋蔥式的middleware概念,採用javascript generator的方式,頗富趣味。

FHIR是什麼?

FHIR(Fast Health Interoperability Resource)快速健康照護互通資源,是在世界最大醫療標準機構HL7下針對移動世代。詳細的介紹可以參考林口長庚王子豪教授在台灣精準醫學學會的文章:
快速健康照護互通資源FHIR
基於FHIR標準來整合基因體數據到臨床應用的進展

Questionaire 資料格式

這次主要是希望有一個簡易工具能產出FHIR questionaire資料。

螢幕快照 2018-05-03 上午10.52.13
這是原始的資料格式,可以直接到FHIR文檔中看其他如xml, json, turtle等資料格式。

項目的資料夾架構

這是整個FHIR questionaire產生器項目的資料夾架構:

螢幕快照 2018-04-28 上午11.21.11

實作流程

第一步 使用vue-cli建立webpack project

先假設已經安裝好nods.js的環境

npm install -g vue-cli
vue init webpack-simple my-vue-project

第二步 安裝相關的package

這裡會使用到koa, koa-static package

npm install koa koa-static

第三步 基本架構

會獨立一篇來稍微介紹一下vue的觀念。基本上他用很簡單的方式,把前端的每個要件能以一個系統性的方式構築起來。

螢幕快照 2018-05-03 下午10.43.16

下面是實際上vue工作時的資料夾內檔案架構:

螢幕快照 2018-05-03 下午10.47.50

每個副檔名.vue的檔案,都是vue裡面的要件(components),而最上面的root是App.vue。

Main.js

這是最一開始被執行的entry檔案,裡頭算是文檔的入口,一切就以這為root往下掛載。

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import TreeView from "vue-json-tree-view";

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  components: { App },
  template: ''
})

Vue.use(TreeView);

App.vue

</pre>
<div id="app"><i class="material-icons">description</i>
<h3>FHIR Questionaire Generator</h3>
Recommendation: choose simple and clarity Id, and subset questions in groups<footer>Created by weiting, 2018</footer></div>
<pre>
import Generator from './components/Generator'

export default {
  name: 'App',
  components: {
    Generator
  }
}

.header{
    font-weight:bolder;
}
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
.material-icons {
  font-size: 30px;
  line-height: 1;
  vertical-align: middle;
  margin: -3px;
  padding-bottom: 1px;
}
div.tree-view-item-node{
  background:grey
}
span.tree-view-item-value{
  color:blue
}

components/Generator.vue

</pre>
<div class="Generator">
<div class="bigbox">
<div class="box">
<div></div>
<div class="buttonPanel"><i class="material-icons">add</i> Add Question Group</div>
</div>
<div class="box">
<div><i class="material-icons">add</i> Add Item</div>
<div>問題類型: Choice Integar Boolean Text <i class="material-icons">add</i></div>
</div>
</div>
<div class="outputbox"></div>
<div><i class="material-icons">add</i>Download JSON</div>
</div>
<pre>
import TreeView from "vue-json-tree-view";
// use plugin
var Vue = require('vue');
Vue.default.use(TreeView);

export default {
  name: 'Generator',
  data(){
   return {
            questionaireIdentifier:'',
            questionaireTitle:'',
            questionairePurpose:'',
            questionaireStatus:'',
            items:[],
            grouplinkId:'',
            grouptext:'',
            groupitems:[],
            itemlinkId:'',
            itemtext:'',
            selected:'',
            itemOption:'',
            itemOptions:[]
            }
  },
  computed:{
    FHIRgroupoutput(){
      let groupPrefix  = this.items.length+'.'+"0.0";
      let itemPrefix   = this.items.length+'.'+this.groupitems.length + '.0'
      let groupitems = this.groupitems.map(item =&gt; item);
      let groupObject = {
                          "linkId":this.grouplinkId,
                          "type":"group",
                          "text":this.grouptext,
                          "prefix":groupPrefix
                        };
      let itemObject = {
                          "linkId":this.itemlinkId,
                          "type":this.selected,
                          "text":this.itemtext,
                          "prefix":itemPrefix
                        };
       console.log(itemObject);
      if (this.selected == 'choice'){
       Object.assign(itemObject, {"option":this.itemOptions});
      }
       groupitems.push(itemObject);
       Object.assign(groupObject, {"item":groupitems});
       return groupObject;

    },
    FHIRoutput(){
        let FHIRquestion = {"resourceType":"questionaire",
                            "identifier":this.questionaireIdentifier,
                            "purpose":this.questionairePurpose,
                            "status":this.questionaireStatus,
                            "title":this.questionaireTitle,
                            "item":this.items};
        return FHIRquestion;
    }

  },
  methods:{
    addGroup:function(event){
        let groupPrefix  = this.items.length+'.'+"0.0";
        let groupitems = this.groupitems.map(item =&gt; item);
        let itemPrefix = this.items.length+'.'+this.groupitems.length+".0";
        let groupObject = {
                        "linkId":this.grouplinkId,
                        "type":"group",
                        "text":this.grouptext,
                        "prefix":groupPrefix
                      };
        let itemObject = {
                        "linkId":this.itemlinkId,
                        "type":this.selected,
                        "text":this.itemtext,
                        "prefix":itemPrefix
                      };
                      console.log(itemObject);
        if (this.selected == 'choice'){
           Object.assign(itemObject, {"option":this.itemOptions});
        }
        groupitems.push(itemObject);
        Object.assign(groupObject, {"item":groupitems});
        this.items.push(groupObject);
        this.groupitems = [];
    },
    addItem:function(event){
       let groupPrefix  = this.items.length+'.'+"0.0";
       let itemPrefix = this.items.length+'.'+this.groupitems.length+".0";
       let itemObject = {"linkId":this.itemlinkId,
                         "text":this.itemtext,
                         "prefix":itemPrefix};
        if (this.selected == 'choice'){
         Object.assign(itemObject, {"option":this.itemOptions});
        }
        this.groupitems.push(itemObject);
        this.itemOptions = [];

    },
    addOption:function(event){
       let itemOption = this.itemOption;
       this.itemOptions.push(itemOption);

    },
    downloadJSON:function(event){
        let evt = new MouseEvent('click');
        let exportObj = {
                            "resourceType":"questionaire",
                            "identifier":this.questionaireIdentifier,
                            "purpose":this.questionairePurpose,
                            "status":this.questionaireStatus,
                            "title":this.questionaireTitle,
                            "item":this.items
                        };
        let exportName = "test";
        let dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportObj));
        let downloadAnchorNode = document.createElement('a');
        downloadAnchorNode.setAttribute("href",dataStr);
        downloadAnchorNode.setAttribute("download",exportName + ".json");
        downloadAnchorNode.dispatchEvent(evt);
        let canceled = !downloadAnchorNode.dispatchEvent(evt);
        if (canceled) {
                // A handler called preventDefault
                    console.log("canceled");
                } else {
                // None of the handlers called preventDefault
                    console.log("not canceled");
        }

    }
  },
    mounted:function(){

  }
}

<!-- Add "scoped" attribute to limit CSS to this component only -->

.MDinput{
   border:0;
   border-bottom:1px black solid;
}
input.MDinput:focus{
   border:0;
   border-bottom:2px blue solid;
}
.bigbox{
   display:grid;
   grid-template-columns:50% 50%;
}

.outputbox{
    display: grid;
    grid-template-columns: 50% 50% ;
}
.box{
    display: grid;
    grid-template-columns: 50% 50% ;
    border-radius: 3px;
    border: 1px black solid;
    margin: 10px 10px 10px 10px;
}

.itembox{
    border-radius: 3px;
    border: 1px black solid;
    margin: 10px 10px 10px 10px;
}

.buttonPanel{
    margin: 10px 10px 10px 10px;
}
.material-icons {
  font-size: 24px;
  line-height: 1;
  vertical-align: middle;
  margin: -3px;
  padding-bottom: 1px;
}

button:hover {
  background: #63c89b;
}
.smallbutton{
  border-radius: 10px;
  border: none;
  display: inline-block;
  padding: 2px 4px;
  cursor: pointer;
}
button {
  border-radius: 3px;
  border: none;
  display: inline-block;
  padding: 8px 12px;
  cursor: pointer;
  background: #40b883;
  color: white;
}

老實說,這邊的代碼是最原始的,理論上要更modular一點,但總是要有個開始!><
其中,使用到的知識點有:

  1. 如何下載在前端產生的JSON檔案 link1/ link2
  2. vue掛載component的原理
  3. vue中Directive attribute:v-if, v-model, v-on在元件中的使用
  4. 在建立vue元件時的data, methods, computed特性

閱讀參考:
MARKSZ blog:全線開發實戰:用vue2+koa1開發完整的前後端項目(koa2更新)