Vue + WebApi 小项目:构造自己的在线 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:;
box-sizing: border-box;
}
.material-icons {
font-size: 24px;
line-height:;
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:;
}
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
Vue + WebApi 小项目:构造自己的在线 Markdown 笔记本应用的更多相关文章
- 跟我一起做一个vue的小项目(二)
这个vue项目是紧跟着之前的项目跟我一起做一个vue的小项目(一)来的. 我继续后面的开发(写的比较粗糙,边学边记录) 下图是header头部的样式 header组件内容如下 //header.vue ...
- 跟我一起做一个vue的小项目(APPvue2.5完结篇)
先放一下这个完结项目的整体效果 下面跟我我一起进行下面项目的进行吧~~~ 接下来我们进行的是实现header的渐隐渐显效果,并且点击返回要回到首页 我们先看效果 在处理详情页向下移动过程中,heade ...
- 跟我一起做一个vue的小项目(八)
接下来我们进行的是城市选择页面的路由配置 添加city.vue,使其点击城市,然后跳转到city页面 //router.js import Vue from 'vue' import Router f ...
- 跟我一起做一个vue的小项目(七)
先看下我们所做项目的效果 这些数据都是我们在data中定义的,不是从后端数据中请求的.那么 接下来我们使用axios渲染数据 npm install axios --save 每个组件里面的数据都不相 ...
- 跟我一起做一个vue的小项目(五)
接下来我们要做的是热门推荐页面,我们写一个推荐组件 使用的方法也是前端data中的数据渲染到页面上面,这里对文字过长取省略号的方法不成功使用了一个小技巧 使用了min-width:0 我们来看完整的代 ...
- 跟我一起做一个vue的小项目(四)
接下来我们进行的是轮播页面下面的导航页的开发 我们需要的是实现轮播页下面的图标,并且实现轮播效果 这个话,其实基本思路先是渲染出小图标,然后,我们要对页数进行判断,如果图标的个数展示的就是8个,那个这 ...
- 跟我一起做一个vue的小项目(三)
接下来我们进行轮播的开发 安装插件,选用2.6.7的稳定版本 npm install vue-awesome-swiper@2.6.7 --save 根据其github上面的用法,我们在全局引用,在m ...
- 跟我一起做一个vue的小项目(一)
项目架子 npm install --global vue-cli vue init webpack travel cd travel/ npm run dev 运行效果 添加home页及其路由,添加 ...
- 跟我一起做一个vue的小项目(十一)
接下来我们进行的是详情页动态路由及banner布局 先看页面的效果 下面是代码部分 <template> <div> <div class="banner&qu ...
随机推荐
- 最新.net和Java调用SAP RFC中间件下载
还记得2012年初我发布的全网络第一个关于.net 连接SAP RFC的NCO3原创博文,用的就是SAP出的最新的.Net Connector 3.0的版本,在那个时候都是普遍用其他蹩脚的方式或Web ...
- ABP入门系列目录——学习Abp框架之实操演练
ABP是"ASP.NET Boilerplate Project (ASP.NET样板项目)"的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WE ...
- 【STM32H7教程】第5章 STM32H7下载和调试方法(MDK5)
完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第5章 STM32H7下载和调试方法(MDK5) 本 ...
- re模块的方法总结
re模块的方法总结 一,查找 1:match 匹配string 开头,成功返回Match object, 失败返回None,只匹配一个. 示例: s="abc221kelvin4774&qu ...
- 深入javascript的主流的模块规范
文章首发于sau交流学习社区 一.前言 目前主流的模块规范: 1.UMD通用模块 2.CommonJs 3.es6 module 二.UMD模块(通用模块) (function (global, fa ...
- 依赖注入容器-- Autofac
目录: 一.简介 二.如何使用 2.1.基本使用 2.2.接口使用 2.3. 其他注入 2.4. 注入的生命周期 一.简介 在上一篇文章中讲到替换默认服务容器,我们选择了Autofac Autofac ...
- 学习JVM是如何从入门到放弃的?
前言 只有光头才能变强 JVM在准备面试的时候就有看了,一直没时间写笔记.现在到了一家公司实习,闲的时候就写写,刷刷JVM博客,刷刷电子书. 学习JVM的目的也很简单: 能够知道JVM是什么,为我们干 ...
- [TCP/IP] TCP的传输连接管理
1.连接建立=>数据传输=>连接释放 2.主动发起连接的是客户端,被动接受连接的是服务器 3.三次握手 客户端 ==> SYN是1同步 ,ACK确认标志是0,seq序号是x ==&g ...
- Windows Server 2016-命令行方式管理Windows服务
Microsoft Windows 服务(过去称为 NT 服务)允许用户创建可在其自身的 Windows 会话中长时间运行的可执行应用程序. 这些服务可在计算机启动时自动启动,可以暂停和重启,并且不显 ...
- Chrome内核浏览器打开网页报 错误代码: ERR_TIMED_OUT
升级win10之后如果出现chrome内核的浏览器网页总是打不开 打开很慢 而ie和edge是可以正常访问的 用这个方法可以 我弄了几天终于 搞好了我直接转载过来了近期,工程师收到大量反馈360浏 ...