今日学习主题:vue制作入门blog,主要为博客界面的简单设计。其中涉及的主要知识点有路由、Vue-resouce、数据的增删查改、自定义过滤等。
1.my-blog项目创建
创建一个基于webpack模板的新项目【vue init webpack my-blog】
打开命令窗,cd 到需要创建项目的位置,输入【vue init webpack my-blog】,接下来的配置如下图选择,由于需要用到路由,选择安装vue-router,项目的依赖安装完毕后,会有提示【cd my-blog】【npm run dev】提示去运行项目。
接着,黑窗中会出现编译成功标志【 DONE Compiled successfully in 11684ms】,提示【Your application is running here: http://localhost:8080】,此时,打开浏览器访问http://localhost:8080,则会出现以下界面,至此,开发环境已搭建完毕。接下来可以开始写页面了。关于项目中的样式不做解释,因为我也觉得,有必要好好学学css这个无底洞了╮(╯▽╰)╭。
在开始写代码之前,先安装好工具vue-resouce,在黑窗中输入命令【npm i vue-resource --save-dev】进行安装。
端口号可以在目录【my-blog】=>【config】=>【index.js】文件修改,我用的是默认的8080端口
2.【写博客】页面---【AddBlog.vue】
【AddBlog.vue】主要内容包括博客标题,博客作者,博客分类及博客内容。
注意1:数据的双向绑定“v-model”
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1博客内容:<textarea v-model="blog.content"></textarea>
data() {return {blog: {content: ""}}}
2博客内容:{{blog.content}}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
以上【1博客内容:】后的【textarea】中输入的内容将显示在【2博客内容:】后
注意2:使用vue-resourse提交数据
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
<button @click.prevent="post">添加博客</button>
post: function() {
this.$http.post("https://wd*******hatwni.wilddogio.com/posts.json",this.blog)
.then(data => {
*******
});
}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
.prevent:提交事件不再重载页面
"https://wd*******hatwni.wilddogio.com/posts.json"===此处写入你自己的野狗数据库地址,具体方法见https://blog.csdn.net/maidu_xbd/article/details/88033100
注意3:v-for的key参数
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
<ul>
<li v-for="category in blog.categories" :key="category">{{category}}</li>
</ul>
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
:key="category"必须加在v-for 后,否则会报错:Elements in iteration expect to have 'v-bind:key' directives
https://blog.csdn.net/maidu_xbd/article/details/87990811
【AddBlog.vue】完整代码:
<template>
<div id="add-blog">
<h1>添加博客</h1>
<form v-if="!submitted">
<label>博客标题:</label>
<input type="text" v-model="blog.title" required>
<div id="info">
<label>作者:</label>
<select v-model="blog.author">
<option v-for="author in authors" :key="author">{{author}}</option>
</select>
<label>分类:</label>
<label>vue</label>
<input type="checkbox" value="vue" v-model="blog.categories">
<label>node</label>
<input type="checkbox" value="node" v-model="blog.categories">
<label>python</label>
<input type="checkbox" value="python" v-model="blog.categories">
<label>java</label>
<input type="checkbox" value="java" v-model="blog.categories">
</div>
<label>博客内容:</label>
<textarea v-model="blog.content"></textarea>
<!-- .prevent==提交事件不再重载页面 -->
<button @click.prevent="post">添加博客</button>
</form>
<div v-if="submitted">
<h3>您的博客发布成功!</h3>
</div>
<div id="preview">
<h2>博客预览</h2>
<p>博客标题:{{blog.title}}</p>
<p>博客作者:{{blog.author}}</p>
<p>博客分类:</p>
<ul>
<li v-for="category in blog.categories" :key="category">{{category}}</li>
</ul>
<p>博客内容:{{blog.content}}</p>
</div>
</div>
</template>
<script>
export default {
name: "add-blog",
data() {
return {
blog: {
title: "",
author: "",
categories: [],
content: ""
},
authors: ["王显明", "刘洋", "贝贝", "Agou"],
submitted: false
};
},
methods: {
post: function() {
if (this.blog.title == "") {
alert("标题不能为空!");
} else if (this.blog.content == "") {
alert("内容不能为空!");
} else {
this.$http
.post(
"https://wd********hatwni.wilddogio.com/posts.json",
this.blog
)
.then(data => {
this.submitted = true;
});
}
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
#add-blog {
margin: 20px auto;
max-width: 800px;
padding: 20px;
background: #c7ecfb;
text-align: left;
}
h1 {
font-family: "微软雅黑";
}
h3 {
color: orange;
}
label {
display: block;
padding: 8px;
font-size: 18px;
font-family: "微软雅黑";
font-weight: 500;
}
input[type="text"],
textarea {
width: 100%;
border-radius: 5px;
}
textarea {
height: 160px;
}
input[type="text"] {
height: 30px;
}
button {
display: block;
margin: 20px 0;
background: rgb(223, 135, 21);
color: #fff;
border: 0;
padding: 10px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
}
#info {
margin: 10px 0;
display: inline-flex;
height: 40px;
}
select,
input[type="checkbox"] {
margin: 8px;
border-radius: 5px;
height: 30px;
}
#preview {
padding: 10px 20px;
border: 1px dotted rgb(41, 41, 41);
margin: 30px 0;
border-radius: 5px;
}
#preview ul {
text-align: left;
margin-left: 20px;
padding: 0px;
list-style: none;
}
</style>
3.【博客首页】页面---【ShowBlog.vue】
博客首页展示所有博客的标题和部分内容,实现了检索过滤功能
注意1:使用钩子函数created,在页面加载前执行,获取(get)页面展示的blogs数据
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
created() {
this.$http.get("https://wd********hatwni.wilddogio.com/posts.json")
.then(data => {return data.json();})
.then(data => {
var blogArray = [];
for (let key in data) {
data[key].id = key;
blogArray.push(data[key]);
}
this.blogs = blogArray;
});
},
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
key为每条数据的唯一标识,即在post提交数据到野狗,每条数据生成的一个唯一标识,为blog添加id字段,将key赋给id,用于查看,删除,修改指定的单条博客数据。
注意2:自定义过滤器
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
<router-link v-bind:to="'/blog/'+blog.id">
<h3>{{blog.title | to-uppercase}}</h3>
</router-link>
<article>{{blog.content | snippet}}</article>
//自定义过滤器
filters: {
"to-uppercase": function(value) {
return value.toUpperCase();
},
snippet: function(value) {
return value.slice(0, 100) + "...";
}
}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
to-uppercase:将博客标题中的字母小写转为大写;snippet仅展示博客内容的0-100个字符。
注意3:搜索功能:将搜索框的内容进行双向绑定,进行过滤匹配(match)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
<input type="text" placeholder="搜索" v-model="search">
<div class="single-blog" v-for="blog in filterBlogs" :key="blog.title">
<router-link v-bind:to="'/blog/'+blog.id">
<h3>{{blog.title | to-uppercase}}</h3>
</router-link>
<article>{{blog.content | snippet}}</article>
</div>
data() {
return {search: ""};
},
computed: {
// 完成搜索博客过滤功能
filterBlogs() {
return this.blogs.filter(blog => {
return blog.title.match(this.search);
});
}
},
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
【ShowBlog.vue】完整代码:
<template>
<div id="show-blog">
<label>我的博客</label>
<input type="text" placeholder="搜索" v-model="search">
<div class="single-blog" v-for="blog in filterBlogs" :key="blog.title">
<router-link v-bind:to="'/blog/'+blog.id">
<h3>{{blog.title | to-uppercase}}</h3>
</router-link>
<article>{{blog.content | snippet}}</article>
</div>
</div>
</template>
<script>
export default {
name: "show-blog",
data() {
return {
blogs: [],
search: ""
};
},
//钩子函数:用于在页面加载前获取数据
created() {
this.$http
.get("https://wd********hatwni.wilddogio.com/posts.json")
.then(data => {
// console.log("输出data", data);
// console.log("输出data.body", data.body);
return data.body;
})
.then(data => {
var blogArray = [];
for (let key in data) {
// console.log("输出key:", key);
//console.log("输出data[key]", data[key]);
data[key].id = key;
blogArray.push(data[key]);
}
this.blogs = blogArray;
console.log(this.blogs);
});
},
computed: {
// 完成搜索博客过滤功能
filterBlogs() {
return this.blogs.filter(blog => {
return blog.title.match(this.search);
});
}
},
//自定义过滤器
filters: {
"to-uppercase": function(value) {
return value.toUpperCase();
},
snippet: function(value) {
return value.slice(0, 100) + "...";
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
#show-blog {
max-width: 800px;
margin: 0 auto;
margin-top: 20px;
background: rgb(158, 177, 149);
padding: 20px;
text-align: left;
}
.single-blog {
text-align: left;
padding: 10px;
margin: 20px 0;
box-sizing: border-box;
background: #eee;
border: 1px dotted #aaa;
}
label {
font-size: 26px;
font-family: "微软雅黑";
color: rgb(75, 42, 16);
font-weight: 600;
}
input[type="text"] {
width: 30%;
height: 35px;
font-size: 16px;
box-sizing: border-box;
border-radius: 4px;
margin-left: 20px;
background: rgb(243, 236, 236, 0.9);
}
a {
text-decoration: none;
}
h3 {
font-family: "微软雅黑";
color: black;
}
</style>
4.【博客头部】---【HeaderBlog.vue】
注意:设置路由
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
<router-link to="/" exact>首页</router-link>
<router-link to="/add" exact>写博客</router-link>
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
<router-link> 组件支持用户在具有路由功能的应用中(点击)导航。 通过 to 属性指定目标地址,默认渲染成带有正确链接的 <a> 标签。
exact属性说明
-
不加exact属性为全局匹配,如<li><router-link to="/">全局匹配</router-link></li>可匹配路径“/”、“/add”等
-
加exact属性为严格匹配,如<li><router-link to="/" exact>严格匹配</router-link></li>只匹配路径“/”
【HeaderBlog.vue】完整代码:
<template>
<div id="header-blog">
<ul>
<li>
<router-link to="/" exact>首页</router-link>
<router-link to="/add" exact>写博客</router-link>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "header-blog",
data() {
return {};
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
#header-blog {
background: url("../assets/myblog.png") no-repeat;
background-size: cover;
padding: 30px 0;
height: 100px;
}
#header-blog ul {
list-style-type: none;
text-align: right;
margin-right: 30px;
margin-top: 70px;
}
#header-blog li {
display: inline-block;
/* margin: 0 10px; */
}
#header-blog a {
color: rgb(62, 60, 62);
font-size: 18px;
font-weight: 500;
text-decoration: none;
padding: 10px;
border-radius: 5px;
}
li {
list-style-type: none;
}
.router-link-active {
background: rgb(255, 255, 255, 0.8);
color: #888;
}
</style>
5.【博客详情】页面---【SingleBlog】
博客详情页展示本条博客的详细信息,并在此页上有删除、编辑按钮,点击可进行相应的操作。
注意1.获取id,在博客首页点击某条博客标题,即可获取其id(配置在路由中),再从路由处获取id
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//在博客首页点击某条博客标题,即可获取其id
<router-link v-bind:to="'/blog/'+blog.id">
<h3>{{blog.title | to-uppercase}}</h3>
</router-link>
//在路由文件中进行路由配置
import SingleBlog from '@/components/SingleBlog'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/blog/:id',
name: 'SingleBlog',
component: SingleBlog
},
]
})
//在【SingleBlog.vue】中进行id获取
data() {return {
id: this.$route.params.id,
}}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
注意2:使用钩子函数created,在页面加载前执行,获取(get)页面展示的指定博客的数据,根据id获取
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
created() {
this.$http.get("https://wd**********hatwni.wilddogio.com/posts/" + this.id + ".json")
.then(data => {return data.json();})
.then(data => {this.blog = data});
},
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
id已在data中获取到了,注意转换数据格式data.json()
注意3:删除博客
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
<button @click="deleteBlog()">删除</button>
methods: {
deleteBlog() {
var msg = confirm("确定要删除吗?");
if (msg) {
this.$http.delete("https://wd********hatwni.wilddogio.com/posts/" +this.id +".json")
.then(response => {this.$router.push({ path: "/" });});
}
}
}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
删除完后,回到博客首页 .then(response => {this.$router.push({ path: "/" });});
【SingleBlog.vue】完整代码:
<template>
<div id="single-blog">
<h1>{{blog.title}}</h1>
<label>作者:{{blog.author}}</label>
<label>分类:</label>
<label v-for="category in blog.categories" :key="category">{{category}} </label>
<hr>
<p>{{blog.content}}</p>
<div id="btn">
<button style="background:#ec4444" @click="deleteBlog()">删除</button>
<router-link v-bind:to="'/edit/'+id">
<button style="background:orange">编辑</button>
</router-link>
</div>
</div>
</template>
<script>
export default {
name: "single-blog",
data() {
return {
id: this.$route.params.id,
blog: {}
};
},
created() {
this.$http
.get(
"https://wd********hatwni.wilddogio.com/posts/" + this.id + ".json"
)
.then(data => {
//console.log("输出id", this.id);
//console.log("输出data", data);
this.blog = data.body;
console.log("输出博客内容", this.blog);
});
},
methods: {
deleteBlog() {
var msg = confirm("确定要删除吗?");
if (msg) {
this.$http
.delete(
"https://wd********hatwni.wilddogio.com/posts/" +
this.id +
".json"
)
.then(response => {
this.$router.push({ path: "/" });
});
}
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
#single-blog {
max-width: 800px;
margin: 0 auto;
margin-top: 20px;
background: rgb(158, 177, 149);
padding: 20px;
text-align: left;
}
#btn {
text-align: right;
margin-right: 10px;
}
#btn button {
height: 40px;
width: 80px;
border-radius: 8px;
color: white;
font-family: "微软雅黑";
font-size: 20px;
margin: 10px;
/* border: none; */
}
</style>
6.【编辑博客】页面---【EditBlog.vue】
编辑博客的页面可copy【AddBlog.vue】进行修改
注意1.获取id,同【SingleBlog】,在博客详情页面点击【编辑】,即可获取其id(配置在路由中),再从路由处获取id
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//在博客详情页面点击【编辑】,即可获取其id
<router-link v-bind:to="'/edit/'+id">
<button >编辑</button>
</router-link>
//在路由文件中进行路由配置
import EditBlog from '@/components/EditBlog'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/edit/:id',
name: 'EditBlog',
component: EditBlog
},
]
})
//在【EditBlog.vue】中进行id获取
data() {return {
id: this.$route.params.id,
}}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
注意2:使用钩子函数获取该条博客信息
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
created() {
this.$http.get("https://wd********hatwni.wilddogio.com/posts/" + this.id + ".json")
.then(response => {return response.json();})
.then(data => {this.blog = data;
});
}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
注意3:更新数据到野狗,使用put方法
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
post: function() {
this.$http.put("https://wd*******atwni.wilddogio.com/posts/" + this.id + ".json",this.blog)
.then(data => {this.submitted = true;});
}
【EditBlog.vue】完整代码:
<template>
<div id="edit-blog">
<h1>编辑博客</h1>
<form v-if="!submitted">
<label>博客标题:</label>
<input type="text" v-model="blog.title">
<div id="info">
<label>作者:</label>
<select v-model="blog.author">
<option v-for="author in authors" :key="author">{{author}}</option>
</select>
<label>分类:</label>
<label>vue</label>
<input type="checkbox" value="vue" v-model="blog.categories">
<label>node</label>
<input type="checkbox" value="node" v-model="blog.categories">
<label>python</label>
<input type="checkbox" value="python" v-model="blog.categories">
<label>java</label>
<input type="checkbox" value="java" v-model="blog.categories">
</div>
<label>博客内容:</label>
<textarea v-model="blog.content"></textarea>
<!-- .prevent==提交事件不再重载页面 -->
<button @click.prevent="post">编辑博客</button>
</form>
<div v-if="submitted">
<h3>您的博客编辑成功!</h3>
</div>
<div id="preview">
<h2>博客预览</h2>
<p>博客标题:{{blog.title}}</p>
<p>博客作者:{{blog.author}}</p>
<p>博客分类:</p>
<ul>
<li v-for="category in blog.categories" :key="category">{{category}}</li>
</ul>
<p>博客内容:{{blog.content}}</p>
</div>
</div>
</template>
<script>
export default {
name: "edit-blog",
data() {
return {
id: this.$route.params.id,
blog: {},
authors: ["王显明", "刘洋", "贝贝", "Agou"],
submitted: false
};
},
methods: {
//数据更新 提交
post: function() {
this.$http
.put(
"https://wd********hatwni.wilddogio.com/posts/" + this.id + ".json",
this.blog
)
.then(data => {
this.submitted = true;
});
}
},
created() {
this.$http
.get(
"https://wd********hatwni.wilddogio.com/posts/" + this.id + ".json"
)
.then(response => {
//console.log("输出id", this.id);
//console.log("输出data.body", data.body);
this.blog = response.body;
console.log("输出博客分类", this.blog.categories);
});
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
#edit-blog {
margin: 20px auto;
max-width: 800px;
padding: 20px;
background: #c7ecfb;
text-align: left;
}
h1 {
font-family: "微软雅黑";
}
h3 {
color: orange;
}
label {
display: block;
padding: 8px;
font-size: 18px;
font-family: "微软雅黑";
font-weight: 500;
}
input[type="text"],
textarea {
width: 100%;
border-radius: 5px;
}
textarea {
height: 160px;
}
input[type="text"] {
height: 30px;
}
button {
display: block;
margin: 20px 0;
background: rgb(223, 135, 21);
color: #fff;
border: 0;
padding: 10px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
}
#info {
margin: 10px 0;
display: inline-flex;
height: 40px;
}
select,
input[type="checkbox"] {
margin: 8px;
border-radius: 5px;
height: 30px;
}
#preview {
padding: 10px 20px;
border: 1px dotted rgb(41, 41, 41);
margin: 30px 0;
border-radius: 5px;
}
#preview ul {
text-align: left;
margin-left: 20px;
padding: 0px;
list-style: none;
}
</style>
7. 路由配置--【router-->index.js】
import Vue from 'vue'
import Router from 'vue-router'
//import HelloWorld from '@/components/HelloWorld'
import AddBlog from '@/components/AddBlog'
import ShowBlog from '@/components/ShowBlog'
import SingleBlog from '@/components/SingleBlog'
import EditBlog from '@/components/EditBlog'
Vue.use(Router)
export default new Router({
routes: [
// {
// path: '/',
// name: 'HelloWorld',
// component: HelloWorld
// },
{
path: '/add',
name: 'AddBlog',
component: AddBlog
},
{
path: '/',
name: 'ShowBlog',
component: ShowBlog
},
{
path: '/blog/:id',
name: 'SingleBlog',
component: SingleBlog
},
{
path: '/edit/:id',
name: 'EditBlog',
component: EditBlog
}
],
mode: "history",
})
8.【main.js】
// 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 router from './router'
import VueResource from 'vue-resource'
Vue.config.productionTip = false
Vue.use(VueResource)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
9.【App.vue】
<template>
<div id="app">
<header-blog></header-blog>
<router-view/>
</div>
</template>
<script>
import HeaderBlog from "./components/HeaderBlog";
export default {
name: "App",
components: {
HeaderBlog
}
};
</script>
<style>
#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;
}
</style>
终于总结完成了,下次得改进方法,继续努力~