关键词:
Vue.js文档、Promise、jquery、webpack、npm、vue-cli、vue-router
一 、cnode社区的基本构架
组件:
Header 头部
PosltList 列表
Article 文章的详情页
SlideBar 侧边栏
UserInfo 用户个人信息
Psgination 分页组件
二 、从头开始
我们先做出大概效果,接收和反馈数据数据
2.1 Header组件
首先搭建好vue-cli,进入编辑器
- 将demo中要用到的图片和logo都存入assets文件夹中,如图
- 创建一个Header组件,用img标签引入存储在assets文件中的logo
- 在li标签分别写出:新手入门、API、关于、注册、登录
- 将组件引入App.vue,就可以在页面显示
<template>
<div id="app">
<Header></Header>
</div>
</template>
<script>
import Header from './components/header.vue'
export default {
name: 'App',
components:{
Header
}
}
- 在加入css,第一个组件基本完成
2.2 PostList组件
- 在数据还未到来时,我们显示出一个正在加载的logo,用img标签引入一个动态图片,用
v-if
操作这个动态图的显示
<div class="loading" v-if="isLoading">
<img src="../assets/loading.gif" alt="">
</div>
- 在组件data中定义一个空数组,并且让加载logo默认不显示
data(){
return{
isLoading: false,
posts:[]
}
}
- 获取数据用到Axios中的get请求,引入之后将Axios全局挂载到Vue原型上,通过this.$http来对网页进行请求
import Axios from 'axios'
Vue.prototype.$http = Axios;
- 在methods方法中定义一个方法getData,通过get方法获取数据,通过promise返回请求成功和请求失败的数据,并做对应的事
methods:{
getData(){
this.$http.get('https://cnodejs.org/api/v1/topics',{
params:{
page:this.postPage,
limit:20
}
})
.then(res=>{
this.isLoading = false //加载成功,去除动画
this.posts = res.data.data
// console.log(this.posts)
})
.catch(err=>{
//处理返回失败后的问题
console.log(err)
})
}
请求成功后给data中定义的posts空数组赋值,如上代码所示
在页面加载之前,触发getData方法,并且在数据没有到来之前,显示加载logo,如代码所示
beforeMount(){
this.isLoading = true //加载成功之前显示加载动画
this.getData() //在页面加载之前获取数据
}
- 得到数据后,渲染页面用v-for,根据数据结构写出所需要的数据
<div class="posts" v-else>
<ul>
<li>
<div class="toobar">
<span v-for="tab in change_tab" :key="tab.name"
@click="changetab(tab)"
:class="{tab_style:tab == current_tab}">{{tab}}</span>
</div>
</li>
<li v-for="post in posts" :key ="post.id">
<!--头像-->
<router-link :to="{
name:'user_info',
params:{name:post.author.loginname}
}">
<img :src="post.author.avatar_url" alt="">
</router-link>
<!--回复/浏览-->
<span class="allcount">
<span class="reply_count">{{post.reply_count}}</span>
/{{post.visit_count}}
</span>
<!--帖子的分类-->
<span :class="[{put_good:(post.good == true),put_top:(post.top == true),
'topiclist-tab':(post.good != true && post.top != true)}]">
<span>
{{post | tabFormatter}}
</span>
</span>
<!--标题-->
<router-link :to="{
name:'post_content',
params:{
id:post.id,
name:post.author.loginname
}
}">
<span>{{post.title}}</span>
</router-link>
<!--最終回复时间-->
<span class="last_reply">{{post.last_reply_at | formatDate}}</span>
</li>
- 页面中有帖子分类,所以这里要用到过滤器,我们将过滤器代码写入main.js中以便于各个组件使用
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动态绑定class控制分类的样式
因为头像和标题都会跳转,所以用
router-link
来包裹,通过to来跳转到需要的路由和要传统的所需参数
<!--头像-->
<router-link :to="{
name:'user_info',
params:{name:post.author.loginname}
}">
<img :src="post.author.avatar_url" alt="">
</router-link>
<!--标题-->
<router-link :to="{
name:'post_content',
params:{
id:post.id,
name:post.author.loginname
}
}">
<span>{{post.title}}</span>
</router-link>
- 在roouter文件夹下的index.js文件中引入
vue-router
,然后定义路由名字,接收传递来的参数,引入其他组件,代码如下:
import Vue from 'vue'
import Router from 'vue-router'
import PostList from '../components/PostList'
import Article from '../components/Article'
import SlideBar from '../components/SlideBar'
Vue.use(Router)
export default new Router({
routes: [
{
name:'root',
path:'/',
components:{
main:PostList
}
},
{
name:'post_content',
path:'/topic/:id&author=:name',
components:{
main:Article,
slidebar:SlideBar
}
}
]
})
- 并且还要在父组件中引入router
import Vue from 'vue'
import App from './App'
import router from './router'
import Axios from 'axios'
Vue.prototype.$http = Axios;
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
- 最后在App.vue组件中由router-view组件来接收,通过name的值来判定需要显示的组件
<template>
<div id="app">
<Header></Header>
<div class="main">
<router-view name="slidebar"></router-view>
<router-view name="main"></router-view>
</div>
</div>
</template>
<script>
import Header from './components/header.vue'
// import PostList from './components/PostList.vue'
// import UserInfo from './components/UserInfo.vue'
export default {
name: 'App',
components:{
Header
}
}
2.3 大同小异
UserInfo组件:
SlideBar组件:
Article组件:
上部分
下部分
后面的Article组件、SlideBar组件、UserInfo组件写法都是一样的,有跳转的用router.link包裹,传递路由名字和参数,遇到需要遍历的就用v-for遍历,需要注意的有两个地方,如下
-
第一个是在Article父组件中,因为写完后会发现点击下图中两个区域(SlideBar组件)页面没有变化,路由的参数虽然变了,但并不会跳转。所以我们要在Article组件中用watch监听路由的变化以便进行跳转
注意看api参数:
监听路由代码:
watch:{
'$route'(to,from){
this.getArticleData()
}
}
- 第二个是在SlideBar组件中,因为只需要获取五条数据,所以要在计算属性中写两个方法,然后在模板中用v-for来渲染,例:
v-for="list in topcilimitby5"
computed:{
topcilimitby5(){
if(this.userinfo.recent_topics){
return this.userinfo.recent_topics.slice(0,5);
}
},
replylimitby5(){
if(this.userinfo.recent_replies){
return this.userinfo.recent_replies.slice(0,5);
}
}
}
2.4 Psgination组件
直接甩代码更明了:
<template>
<div class="pagination">
<button @click="changeBtn">首页</button>
<button @click="changeBtn">上一页</button>
<button v-if="jduge" class="pagebtn">......</button>
<button v-for="btn in pagebtns" :key="btn.page"
:class="[{currentPage:btn == currentPage},'pagebtn']"
@click="changeBtn(btn)">
{{btn}}
</button>
<button @click="changeBtn">下一页</button>
</div>
</template>
<script>
import $ from 'jquery'
export default {
name:'Pagination',
data(){
return{
pagebtns:[1,2,3,4,5,'......'],
currentPage:1,
jduge:false
}
},
methods:{
changeBtn(page){
// console.log(page.target)
if(page == '......') return
if(typeof page != 'number'){
switch(page.target.innerText){
case '上一页':
$('button.currentPage').prev().click();
// console.log( $('button.currentPage'))
break;
case '下一页':
$('button.currentPage').next().click();
break;
case '首页':
this.pagebtns = [1,2,3,4,5,'......'];
this.changeBtn(1);
break;
default:
break;
}
return;
}
this.currentPage = page
if(page>4){
this.jduge = true;
}else{
this.jduge = false;
}
if(page == this.pagebtns[4]){
this.pagebtns.shift()
this.pagebtns.splice(4,0,this.pagebtns[3]+1)
}else if(page == this.pagebtns[0] && page != 1){
this.pagebtns.unshift(this.pagebtns[0]-1)
this.pagebtns.splice(5,1)
}
this.$emit('handleList',this.currentPage);
}
}
}
</script>
在data中定义pagebtns,如:
pagebtns:[1,2,3,4,5,'......']
,在模板中由v-for来遍历,然后在定义个changBtn方法,点击时实现需要的功能,此段需要引入jquery,用switch判定点击上一页、下一页、首页该做什么事最后,因为Psgination组件是定义在PostList组件中,所以是子组件向父组件传递数据,所以这里需要用到,自定义事件,用Vue中的方法
this.$emit('handleList',this.currentPage)
再在PostList组件中定义出handleList方法即可,在给出我的PostList组件最后的代码:
<template>
<div class="PostList">
<!--在数据未返回的时候,显示这个正在加载的gif-->
<div class="loading" v-if="isLoading">
<img src="../assets/loading.gif" alt="">
</div>
<div class="posts" v-else>
<ul>
<li>
<div class="toobar">
<span v-for="tab in change_tab" :key="tab.name"
@click="changetab(tab)"
:class="{tab_style:tab == current_tab}">{{tab}}</span>
</div>
</li>
<li v-for="post in posts" :key ="post.id">
<!--头像-->
<router-link :to="{
name:'user_info',
params:{name:post.author.loginname}
}">
<img :src="post.author.avatar_url" alt="">
</router-link>
<!--回复/浏览-->
<span class="allcount">
<span class="reply_count">{{post.reply_count}}</span>
/{{post.visit_count}}
</span>
<!--帖子的分类-->
<span :class="[{put_good:(post.good == true),put_top:(post.top == true),
'topiclist-tab':(post.good != true && post.top != true)}]">
<span>
{{post | tabFormatter}}
</span>
</span>
<!--标题-->
<router-link :to="{
name:'post_content',
params:{
id:post.id,
name:post.author.loginname
}
}">
<span>{{post.title}}</span>
</router-link>
<!--最終回复时间-->
<span class="last_reply">{{post.last_reply_at | formatDate}}</span>
</li>
<li>
<Pagination @handleList="renderList"></Pagination>
</li>
</ul>
</div>
</div>
</template>
<script>
import Pagination from './Pagination'
export default {
name: 'PostList',
data(){
return{
isLoading: false,
posts:[],
postPage:1,
change_tab:['全部','精华','分享','问答','招聘'],
current_tab:'全部',
url:'https://cnodejs.org/api/v1/topics'
}
},
components:{
Pagination
},
methods:{
getData(){
this.$http.get(this.url,{
params:{
page:this.postPage,
limit:20
}
})
.then(res=>{
this.isLoading = false //加载成功,去除动画
this.posts = res.data.data
// console.log(this.posts)
})
.catch(err=>{
//处理返回失败后的问题
console.log(err)
})
},
renderList(value){
this.postPage =value
this.getData()
},
changetab(tab){
if(tab == '全部'){
console.log(this.url)
this.url = 'https://cnodejs.org/api/v1/topics?tab=all'
}else if(tab == '精华'){
console.log(this.url)
this.url = 'https://cnodejs.org/api/v1/topics?tab=good'
}else if(tab == '分享'){
console.log(this.url)
this.url = 'https://cnodejs.org/api/v1/topics?tab=share'
}else if(tab == '问答'){
console.log(this.url)
this.url = 'https://cnodejs.org/api/v1/topics?tab=ask'
}else{
this.url = 'https://cnodejs.org/api/v1/topics?tab=job'
}
this.current_tab = tab
this.getData()
}
},
beforeMount(){
this.isLoading = true //加载成功之前显示加载动画
this.getData() //在页面加载之前获取数据
}
}
</script>
三、大结局
写到这里,模拟cnode社区基本写完了,后面便是一些自己想要的功能,可以自行添加-----------The early bird catches the worm