Vue + WebApi 小項目:構造自己的線上 Markdown 筆記本應用 目錄 概要 知識點 完整示例圖 代碼與資源文件 流程步驟 概要 基於 MVP 最小可行性產品設計理念,我們先完成一個可以使用,並具備基本功能的 Markdown 筆記本應用,再進行逐步完善。 知識點 本文會指導初學者如何 ...
Vue + WebApi 小項目:構造自己的線上 Markdown 筆記本應用
目錄
- 概要
- 知識點
- 完整示例圖
- 代碼與資源文件
- 流程步驟
概要
基於 MVP 最小可行性產品設計理念,我們先完成一個可以使用,並具備基本功能的 Markdown 筆記本應用,再進行逐步完善。
知識點
本文會指導初學者如何一步步運用 Vue 的計算屬性、雙向綁定、指令、生命周期鉤子,還有 localStorage 和非同步請求等知識點。
完整示例圖
代碼與資源文件
https://github.com/liqingwen2015/MarkdownDemo
為了避免網路原因造成的問題,文中所使用的第三方庫(可自己去官方下載最新版,文章使用的是當前發佈時間最新版本的 js 文件)以及 css 文件都下載好並且已經放入裡面。
body { font-family: sans-serif; font-size: 16px; height: 100%; margin: 0; box-sizing: border-box; } .material-icons { font-size: 24px; line-height: 1; vertical-align: middle; margin: -3px; padding-bottom: 1px; } #app > * { float: left; display: flex; flex-direction: column; height: 100%; > * { flex: auto 0 0; } } .side-bar { background: #f8f8f8; width: 20%; box-sizing: border-box; } .note { padding: 16px; cursor: pointer; } .note:hover { background: #ade2ca; } .note .icon { float: right; } button, input, textarea { font-family: inherit; font-size: inherit; line-height: inherit; box-sizing: border-box; outline: none !important; } button, .note.selected { background: orange; color: white; } button { border-radius: 3px; border: none; display: inline-block; padding: 8px 12px; cursor: pointer; } button:hover { background: #63c89b; } input { border: solid 2px #ade2ca; border-radius: 3px; padding: 6px 10px; background: #f0f9f5; color: #666; } input:focus { border-color: orange; background: white; color: black; } button, input { height: 34px; } .main, .preview { width: 40%; } .toolbar { padding: 4px; box-sizing: border-box; } .status-bar { color: #999; font-style: italic; } textarea { resize: none; border: none; box-sizing: border-box; margin: 0 4px; font-family: monospace; } textarea, .notes, .preview { flex: auto 1 1; overflow: auto; } .preview { padding: 12px; box-sizing: border-box; border-left: solid 4px #f8f8f8; } .preview p:first-child { margin-top: 0; } a { color: orange; } h1, h2, h3 { margin: 10px 0 4px; color: orange; } h1 { font-size: 2em; } h2 { font-size: 1.5em; } h3 { font-size: 1.2em; } h4 { font-size: 1.1em; font-weight: normal; }使用的 index.css 文件
流程步驟
1.先構建一個基本的 html 文件,並引入核心 js 庫。
這裡需要引入的第三方庫為 vue.js、marked.js。
<html> <head> <title></title> <!-- 引入樣式文件 --> <link rel="stylesheet" href="index.css" /> </head> <body> <!-- 引入 js 庫 --> <script src="/lib/vue.js"></script> <script src="/lib/marked.js"></script> <!-- js 代碼 --> <script src="index.js"></script> </body> </html>
因為考慮到項目主要劃分為兩塊,左邊是書寫區域,右邊為預覽區域,<body> 塊代碼修改為:
<body> <!-- 引入 js 庫 --> <script src="lib/vue.js"></script> <script src="lib/marked.js"></script> <div id="app"> <!-- 主區域:書寫 --> <section class="main"></section> <!-- 預覽區域 --> <aside class="preview"></aside> </div> <!-- js 代碼 --> <script src="index.js"></script> </body>
修改 js 代碼:創建 Vue 實例,並將其掛載到 DOM 元素上。
new Vue({ el: '#app' })
【備註】上面的掛載方式是比較常見的一種,我們也可以使用 app.$mount('#app') 進行掛載。
2.接下來我們使用 Vue 的雙向綁定機制控制輸入的內容和預覽的內容。
修改 html:
<body> <!-- 引入 js 庫 --> <script src="lib/vue.js"></script> <script src="lib/marked.js"></script> <div id="app"> <!-- 主區域:書寫 --> <section class="main"> <textarea v-model="editor"></textarea> </section> <!-- 預覽區域 --> <aside class="preview"> {{editor}} </aside> </div> <!-- js 代碼 --> <script src="index.js"></script> </body>
修改 js,增加數據屬性:
new Vue({ el: '#app', data() { return { editor: '編輯器' } } })
現在,打開 index.html 頁面,在瀏覽器頁面中的左側進行輸入就可以在預覽視窗中同步看到輸入後的情況。
3.接下來,我們需要對輸入的內容經過 Markdown 形式轉換,在這裡,我們使用 Vue 的計算屬性來進行優化渲染 Markdown 的實時預覽
修改 js:
new Vue({ // 掛載 el: '#app', // 數據 data() { return { editor: '編輯器' } }, // 計算屬性 computed: { editorPreview() { return marked(this.editor); } } })
修改 <body>,使用 v-html 指令取代 {{ }},以這種方式來渲染 HTML 元素。
<body> <!-- 引入 js 庫 --> <script src="lib/vue.js"></script> <script src="lib/marked.js"></script> <div id="app"> <!-- 主區域:書寫 --> <section class="main"> <textarea v-model="editor"></textarea> </section> <!-- 預覽區域 --> <aside class="preview" v-html="editorPreview"> </aside> </div> <!-- js 代碼 --> <script src="index.js"></script> </body>
運行效果圖
4.保存內容
目前,如果關閉了瀏覽器或者對頁面進行了刷新,所有內容都會丟失。所以,我們目前使用 localStorage 的方式進行數據的保存操作。
現在產生了一個疑問:應該什麼時候進行保存呢?
我們現在使用 Vue 的偵聽器功能來對數據的改動進行保存操作,因為它可以監聽到 editor 的每一改動操作,意思是每次輸入操作都會觸發偵聽器裡面的方法。
修改 js:
new Vue({ // 掛載 el: '#app', // 數據 data() { return { editor: '編輯器' } }, // 計算屬性 computed: { editorPreview() { return marked(this.editor); } }, // 偵聽器 watch: { editor(val) { localStorage.setItem('editor', this.editor); } } })
那麼現在又產生了新的疑問:應該怎樣才能夠在每次進入這個頁面時顯示之前保存的信息呢?
現在,我們通過利用 Vue 的生命周期鉤子(目前使用 created 鉤子)來進行數據的讀取及恢復。
修改 js:
new Vue({ // 掛載 el: '#app', // 數據 data() { return { editor: '編輯器', key: { editor: 'editor' } } }, // 計算屬性 computed: { editorPreview() { return marked(this.editor); } }, // 偵聽器 watch: { editor(val) { localStorage.setItem(this.key.editor, this.editor); } }, // 生命周期鉤子 created() { this.editor = localStorage.getItem(this.key.editor) || '第一次使用 Markdown 筆記本'; } })
【備註】在進行修改 js 後,editor 屬性第一次載入的時候可能為 null,這會導致整個應用出錯,所以這裡採用了預設值。
5.localStorage 畢竟不是永久保存的方式,這裡我使用一種較為簡單的方式,保存方法替換為非同步請求到 WebApi 介面保存到資料庫的方式
修改 html,引入 axios 庫:
<script src="lib/axios.min.js"></script>
同時,修改 js,增加兩個 Http 請求的方法,獲取和保存:
new Vue({ // 掛載 el: '#app', // 數據 data() { return { editor: '', key: { editor: 'editor' }, url: 'http://localhost:34473/api/markdown' // 需要替換成自己的 API 路徑 } }, // 計算屬性 computed: { editorPreview() { return marked(this.editor); } }, // 偵聽器 watch: { editor(val) { //localStorage.setItem(this.key.editor, this.editor); this.save(); } }, // 生命周期鉤子 created() { this.load(); // this.editor = localStorage.getItem(this.key.editor) || '第一次使用 Markdown 筆記本'; }, // 方法 methods: { load() { var that = this; axios.get(that.url).then(function (result) { console.log(result.data); that.editor = result.data; }); }, save() { var that = this; axios.post(that.url, { content: that.editor }).then(function (result) { }); } } })
新增的 API 控制器 MarkdownController.cs 的內容如下:
[Route("api/[controller]")] [ApiController] public class MarkdownController : ControllerBase { public static MarkdownViewModel MarkdownViewModel = new MarkdownViewModel() { Content = "我的第一個 Markdown 應用" }; [HttpGet] public ActionResult<string> Get() { return MarkdownViewModel.Content; } [HttpPost] public void Save([FromBody] MarkdownViewModel vm) { MarkdownViewModel = vm; } }
視圖模型 MarkdownViewModel.cs 的內容如下:
public class MarkdownViewModel { public string Content { get; set; } }
【備註】需要自行進行 WebApi 的跨域配置,演示時進行了忽略配置
【備註】示例代碼可從 https://github.com/liqingwen2015/MarkdownDemo 下載
【切換閱讀方式】https://www.jianshu.com/p/a17033ca91d9
【參考】Vue.js 2 Web Development Projects