通过了前面的 Vue 基础的铺垫,如今终于开始进行实战部分了。javascript
代码连接:GitHubcss
预览连接: Git Pageshtml
图片预览:vue
Header 头部java
PostList 列表node
Article 文章详情页ios
SideBar 侧边栏git
UserInfo 我的信息github
Pagination 分页组件web
项目 Header 组件,主要展现 logo 及 一级菜单。
项目中的 文章列表,其中包括做者、点击量、评论量、文章标题、发表时间等。
经过官方提供的 API : https://cnodejs.org/api/v1/topics 获取帖子列表
而后经过 Chrome 的一个小插件 yformater 格式化 API 返回的 JSON 文件,分析须要获取的数据。
"id": "5baee8de9545eaf107b9c6f3", // 文章ID "author_id": "51f0f267f4963ade0e08f503", // 做者ID "tab": "share", // 文章分类-分享 表示除了置顶和精华以外的其他分区 share 分享 / ask 问答 / job 招聘 "content": ..., // 文章内容 "title": "Node 地下铁第七期「深圳站」线下沙龙邀约 - Node.js 新生态", // 文章标题 "last_reply_at": "2018-10-12T00:40:26.741Z", // 文章最后回复时间 "good": false, // 表明是否精华 "top": true, // 表明是否置顶 "reply_count": 13, // 回复数量 "visit_count": 1420, // 浏览数量 "create_at": "2018-09-29T02:52:14.701Z", // 文章发表时间 "author": { "loginname": "lellansin", // 做者名称 "avatar_url": "https://avatars2.githubusercontent.com/u/2081487?v=4&s=120" // 做者头像 }
使用 axios 教程
把获取的数据渲染到页面上
运用 filter
使用 filter 对时间戳进行处理:
Vue.filter('formatDate', function (str) { if (!str) return '' var date = new Date(str) var time = new Date().getTime() - date.getTime() //如今的时间-传入的时间 = 相差的时间(单位 = 毫秒) if (time < 0) { return '' } else if ((time / 1000 < 30)) { return '刚刚' } else if (time / 1000 < 60) { return parseInt((time / 1000)) + '秒前' } else if ((time / 60000) < 60) { return parseInt((time / 60000)) + '分钟前' } else if ((time / 3600000) < 24) { return parseInt(time / 3600000) + '小时前' } else if ((time / 86400000) < 31) { return parseInt(time / 86400000) + '天前' } else if ((time / 2592000000) < 12) { return parseInt(time / 2592000000) + '月前' } else { return parseInt(time / 31536000000) + '年前' } })
使用过滤器来判断帖子分类:
Vue.filter('tabFormatter',function (post) { if(post.good == true){ return '精华' }else if(post.top == true){ return '置顶' }else if(post.tab == 'ask'){ return '问答' }else if(post.tab == 'share'){ return '分享' }else{ return '招聘' } })
使用 v-bind 动态绑定样式:
我的整理博客:v-bind
<span :class="[ {put_good:(post.good === true)}, {put_top:(post.top === true)}, {'topiclist-tab':(post.good !== true && post.top !== true)} ]"> {{ post | tabFormatter}} </span>
文章详情页,其中包括文章标题、发布日期、正文、评论等内容
API https://cnodejs.org/api/v1/topic/ + 帖子ID
主要利用 router-link 从文章列表 PostList 跳转到文章详情页 Article。
实现思路:
1.在 PostList.vue 中的每条文章添加 router-link:
<router-link :to="{name:'post_content',params:{id:post.id}}"> <span>{{ post.title}}</span> </router-link>
2.点击后带着参数 id:post.id
找到 router 中的 index.js 中设定的路径 name:'post_content'
3.打开 url path:'/topic/:id'
,渲染组件 Article
4.Article 中经过 API 获取了单篇文章的数据 this.$axios.get(`https://cnodejs.org/api/v1/topic/${this.$route.params.id}`)
,而后赋值给了组件中的 data,在页面中渲染出来。
总得来讲就是: router-link
-> router/index.js
-> router-view
API https://cnodejs.org/api/v1/user/ + username
而后就是重复 router-link 的套路
问题 说说 markdown-github.css
经过 API 返回了一篇文章的内容 content,content 是由 markdown 语法编写的。
一开始的处理思路是,引入 assets 目录下的 markdown-github.css ,而后在组件中引入 @import url('../assets/markdown-github.css');
,接着经过 v-html
在页面中把内容渲染出来,可是发现没有效果,样式没有起到变化。
而后从遇到一样问题的同窗那里获得了解决方法:
1.在项目中安装: cnpm i markdown-github-css
2.在 main.js 中引入:import markdown-github-css
3.在容器div添加类名 markdown-body
:
<div v-html="post.content" class="topic_content markdown-body"></div>
展现侧边栏,包括做者信息、最近主题,最近回复等。
使用 computed 对取得的文章列表作一个筛选,只显示前 5 条:
computed:{ topicLimitBy5(){ // 这里不用 length 判断是由于刚开始渲染的时候 userinfo 是空的,是没有 length 的,因此会报错 if(this.userinfo.recent_replies){ return this.userinfo.recent_replies.slice(0,5); } }, repliesLimitBy5(){ if(this.userinfo.recent_replies){ return this.userinfo.recent_replies.slice(0,5); } } },
问题 提示 [vue-router] missing param for named route "user_info": Expected "name" to be defined
点击连接后 url 有变化,可是不跳转。
<!-- SideBar.vue --> <li v-for="item in topicLimitBy5"> <router-link :to="{name:'post_content',params:{id:item.id,name:item.author.loginname}}"> {{item.title}} </router-link> </li>
缘由:没有对路由进行检测。
在 vue.js 的文档中,他是这样解释的:
提醒一下,当使用路由参数时,例如从 /user/foo 导航到 /user/bar,原来的组件实例会被复用。由于两个路由都渲染同个组件,比起销毁再建立,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。
复用组件时,想对路由参数的变化做出响应的话,你能够简单地 watch (监测变化) $route 对象
在这个错误中,由于点击的连接路由名称都是 /topic/...
,因此至关于复用了组件,没有对路由参数的变化作出响应,所以作出修改:
// Article.vue watch:{ '$route'(to,from){ // 经过 id 获取文章详情 this.getArticleData() } }
每当 Article 检测到路由发生变化,则执行方法,经过新的文章 id 获取文章数据,渲染新的页面。
分页器
:class
绑定样式,@click='changebBtn'
实现点击不一样页码后按钮样式切换,同时经过 $emit 向父组件发出信息,从 API 获取不一样页码的数据,渲染在页面上。<!-- Pagination.vue --> <button v-for="btn in pagebtns" :class="[{currentPage:btn === currentPage},{pagebtn:true}]"> {{btn}} </button>
data() { return { // 先给分页器一个固定的数组 'pagebtns':[1,2,3,4,5,'...'], // 给每一个按钮一个「坐标」 currentPage:1, isEllipsis:false }; }, methods:{ changeBtn(page){ if(typeof page !== 'number'){ switch (page.currentTarget.innerText){ case '首页': this.pagebtns = [1,2,3,4,5,'...'] this.changeBtn(1) break; case '上一页': $('button.currentPage').prev().click() break; case '下一页': $('button.currentPage').next().click() break; default: break; } return } if(page >4){ this.isEllipsis = true }else{ this.isEllipsis = false } this.currentPage = page // 当点击的按钮是第5个时 if(page === this.pagebtns[4]){ this.pagebtns.shift() this.pagebtns.splice(4,0,this.pagebtns[3]+1 ) // 当点击的按钮是第1个时 }else if(page === this.pagebtns[0] && this.pagebtns[1]>2){ this.pagebtns.splice(4,1) this.pagebtns.unshift(this.pagebtns[0]-1) } // 传递数据给父组件 PostList this.$emit('handleList',this.currentPage) } },
能够选择不一样的主题进行浏览:
1.tab 菜单中每一个选项绑定点击事件,点击后根据传入参数的不一样获取不一样主题的内容:
<!-- PostList.vue --> <span @click="changeTab('')">所有</span> <span @click="changeTab('good')">精华</span> <span @click="changeTab('share')">分享</span> <span @click="changeTab('ask')">问答</span> <span @click="changeTab('job')">招聘</span>
// PostList.vue changeTab(value){ this.tab = value this.getData() }
改变 tab,从新执行方法 getData(),获取不一样主题帖子的数据,在页面中渲染出来。
2.点击了 tab 菜单后,页码回到该主题的第1页
若是只停留在上一步,则会出现这样的问题:点击 问答
-> 跳转到第6页 -> 再点击 首页
-> 页码显示停留在 首页
的第6页,可是内容其实是 首页
的第1页
也就是他的样式没有转换过来。
解决方法:父组件把 tab 当成参数传递给子组件,子组件 watch 这个 tab,一旦这个 tab 发生变化,则回到这个 tab 对应的主题的第一页:
<!-- PostList.vue --> <Pagination @handleList='renderList' :tab='tab'></Pagination>
// Pagination ... props:[ 'tab' ], ... watch:{ tab:function(val,oldVal){ this.pagebtns = [1,2,3,4,5,'...'] this.changeBtn(1) } }
对文章中同时包含了中英文的字符串的长度进行解析,限制字符串长度
Vue.filter('postListConversion',function(str,len){ var result = ""; var strlen = 0; for(var i = 0;i < str.length; i++){ if(str.charCodeAt(i) > 255){ strlen += 2; //若是是汉字,则字符串长度加2 } else { strlen++; } result += str.substr(i,1); if(strlen >= len){ break; } } if(strlen < len){ return result }else{ return `${result}...`; } })
如:
@media screen and (max-width: 979px){ .autherinfo{ float: none; position: absolute; bottom: -4px; left: 22px; } ul a{ max-width: 96%; -o-text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle; line-height: 30px; overflow: hidden; text-overflow: ellipsis; } }
当设备分辨率宽度小于 979px 时,样式会生效。
原本打算是另写一个 css 文件,存放在 /assets/css 的文件目录下,而后在 main.js 中经过 import './assets/css/main.css'
引入的,可是查阅资料的时候看到说这样作并很差,到时候须要修改样式会很麻烦,因此就写在了每一个组件的 <style>
中。
注册和登陆页
1.注册页:
使用 localStorage 存储注册用户的用户名和密码,v-model 绑定输入框的 value ,判断 localStorage 里有没有 value:
有,能够直接登陆;无,则注册成功。
methods:{ submitInfo(){ //判断是否存在此用户名 if (localStorage.getItem(this.username) === null) { this.usernameIsRight = false //存入用户名和密码 localStorage.setItem(this.username,this.password) this.isWorks = true setTimeout(()=>{ // 跳转到首页 this.$router.push({path:'/login'}) },2000) }else{ this.usernameIsRight = true } } }
2.登陆页:
经过 localStorage 判断输入框 value,匹配则转到首页,不匹配则提示密码错误或者用户未注册。
methods:{ submitInfo(){ //判断是否存在此用户名 if (localStorage.getItem(this.username) === null) { this.usernameIsRight = true // 判断用户名和密码是否匹配 }else if(localStorage.getItem(this.username) !== this.password){ this.passwordIsRight = true } // 用户名和密码匹配则带着参数(用户名)跳转到 /user/ else if(localStorage.getItem(this.username) === this.password){ this.usernameIsRight = false this.$router.push({name:'user',params:{name:this.username}}) // 这里先留个坑,若是不刷新的话,则页面登陆状态不会改变,应该是和组件的生命周期有关系,目前暂时没有搞清楚 window.location.reload() } } }
3.首页:
经过 url 参数拿到 用户名:username:this.$route.params.name
而后把用户名渲染到页面中。
这里说说没有解决的 bug :
原本打算使用 eventBus 来传递数据,登陆的用户名传给首页,而后首页判断用户名是否存在 localStorage 中,再去渲染,这样感受流程比较流畅;可是点击提交按钮后页面会跳转到首页,组件的生命周期也会变化,全部没有想到好的接收数据的方法。
我想我应该试试 vuex。