我的第一个Vue.js项目----仿cnode社区

一、环境搭建


使用vue-cli脚手架搭建Vue项目

  1. 全局安装 npm install -g @vue/cli

  2. 查看版本是否正确 vue --version

  3. 新建一个空文件夹,用Git Bash进入这个文件夹,然后输入命令
    vue create .(该命令表示在当前目录下创建一个vue项目)

  4. 接下来会提示我们选择一些配置选项,比如是否安装Router、Babel、Vuex 等,按需选择即可

image

注意:可能我们会遇到命令行下无法 Use arrow keys 的情况,这个时候我们可以使用命令 winpty vue.cmd create . 来解决这个问题

  1. 运行项目在本地预览 npm run serve
image

当在浏览器中出现以下界面时,就表示我们的vue项目搭建成功啦

image

安装路由

前提:你在上一步选择配置选项的时候没有选择安装 Router

  1. 安装:npm install --save vue-router
  2. 在router.js中引用:
import router from 'vue-router'
Vue.use(router)
  1. 在vue实例中注入
new Vue({
  router, // 注入router
  render: h => h(App)
}).$mount('#app')

引入 Axios

  1. 安装:npm install axios
  2. 在main.js中引入Axios:import Axios from 'axios'
  3. 在main.js中把Axios挂载到Vue原型上:Vue.prototype.$http = Axios

二、cnode 社区的基本构架


将 cnode 社区网页分为以下几个组件

  1. Header 头部
  2. PosltList 列表
  3. Article 文章的详情页
  4. SlideBar 侧边栏
  5. UserInfo 用户个人信息
  6. Psgination 分页组件
目录结构

三、开始写代码


以下只记录了我认为在项目中相对重要的部分

1. PosltList 组件

API接口:https://cnodejs.org/api/v1/topics 获取帖子列表

<script>
export default {
  name: "PostList",
  data() {
    return {
      isLoading: false, // 默认不显示loading动画
      posts: [] // 代表当前页面的所有内容列表数组
    };
  },
  methods: {
    getData() {
      this.$http
        .get("https://cnodejs.org/api/v1/topics", {
          page: 1,
          limit: 20
        })
        .then(response => {
          this.isLoading = false; // 加载成功之后去除 loading 动画
          console.log(response);
          this.posts = response.data.data;
        })
        .catch(error => {
          // 处理请求失败
          console.log(error);
        });
    }
  },
  beforeMount() { // 在挂载开始之前被调用:相关的 render 函数首次被调用
    this.isLoading = true; // 在页面加载成功之前显示 lodaing 动画
    this.getData(); // 在页面加载之前获取数据
  }
};
</script>

若请求成功,则会在控制台打印出如下结果

[图片上传失败...(image-b11aef-1569763055194)]

console.log(response)的结果进行分析

  • 头像:author.avatar_url
  • 回复量/浏览量 :reply_count/visit_count
  • 帖子的标题:title
  • 最后回复时间:last_reply_at
  • 帖子分类:
    • top: 代表是否置顶
    • good: 代表是否精华
    • tab 是表示除了置顶和精华之外的其余分区
  • tab 又可分为
    • ­share 分享
    • ­ask 问答
    • ­job 招聘

filter 的使用

过滤帖子的类别

PostList中
<!-- 帖子的分类 -->
<span :class="[{put_good:(post.good === true),put_top:(post.top===true),'topiclist-tab':(post.good !== true && post.top !== true)}]">
   <span>{{post | tabFormat}}</span>
</span>

main.js中
// 处理帖子分类的文字
Vue.filter('tabFormat',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 '招聘'
  }
})

过滤最后回复时间

PostList中
<!-- 最后回复时间 -->
<span class="last_reply">{{post.last_reply_at | formatDate}}</span>

main.js中
Vue.filter('formatDate',function(string){ // 传入 post.last_reply_at 作为参数,注意它为字符串
    if(!string) return ''
    var date = new Date(string)
    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) + '年前'
    }
})

2. Article 组件

API:https://cnodejs.org/api/v1/topics/ + 帖子ID

Article接收PostList传递的参数post.id,获取参数用 this.$route.params.id

methods:{
    getArticleData(){
        this.$http.get(`https://cnodejs.org/api/v1/topics/${this.$route.params.id}`)// ES6 语法,字符串拼接
        .then((response)=>{
            this.isLoading = false
            console.log(response)
           this.posts = response.data.data;
        })
        .catch((error)=>{
            console.log(error)
        })
    }
}

监听路由的变化(解决点击侧边栏title不跳转的问题)

watch:{ // 监听路由的变化,暂时不是很理解为什么要监听路由的变化
        '$route'(to,from){
            this.getArticleData()
        }
}

引入外部的markdown.css把变为makdown格式

/*引入外部CSS时要去掉scoped*/
<style>
@import url('../assets/markdown-github.css');
...
</style>

3. Pagination 分页组件

这里要用到子组件(Pagination)向父组件(PostList)传递数据

首先要在PostList组件中自定义一个事件 @handleList="renderList"

<template>
<!--分页按钮-->
<Pagination @handleList="renderList"></Pagination><!--自定义事件 handleList-->
</template>

<script>
import Pagination from './Pagination'  // 引入组件

export default {
  name: "PostList",
  components:{
    Pagination  // 注册组件
  },
  data() {
    return {
      postPage:1
    }
  },
  methods: {
    getData() {
      this.$http
        .get("https://cnodejs.org/api/v1/topics", {
            params:{ // 注意get请求一定要加params
                page: this.postPage, // 把请求的参数变为更新后的 postPage
                limit: 20
            }
        })
        .then(...)
        .catch(...)
    },
    renderList(value){ // 用 value 接收从 Pagination 中传递过来的参数 currentPage
        this.postPage = value
        this.getData()
    }
  }
</script>

然后在Paginnation组件中传递参数给上面的自定义事件

<template>
  <div class="pagination">
    <button @click="changeBtn">首页</button>
    <button @click="changeBtn">上一页</button>
    <button v-if="judge" class="pagebtn">......</button>
    <button v-for="btn in pagebtns"
    :class="[{currentPage:btn===currentPage},'pagebtn']"
    @click="changeBtn(btn)">
    {{btn}}
    </button>
    <button @click="changeBtn">下一页</button>
  </div>
</template>

<script> 
export default {
  name:"Pagination",
  data(){
      return{
          pagebtns:[1,2,3,4,5,'......'],
          currentPage:1
      }
  },
  methods:{
      changeBtn(page){
          this.currentPage = page // 让currentPage = 当前点击的按钮数字(btn)
          this.$emit('handleList',this.currentPage) // 把currentPage传递给父组件PostList
  }
}
</script>

4. SlideBar 侧边栏组件

使用计算属性

<li v-for="userinfo in replylimited">   <!--这里使用了计算属性-->
    <router-link :to="{
        name:'post_content',
            params:{
                id:userinfo.id
            }
        }">
           {{userinfo.title}}
    </router-link>
</li>
export default {
  name:"SlideBar",
  data() {
    return {
      userinfos: [], // 代表当前页面的所有内容
    }
  }
  computed:{
      topiclimited(){
          if(this.userinfos.recent_topics){
              return this.userinfos.recent_topics.slice(0,5) // 只截取前5条topic
          }
      },
      replylimited(){
          if(this.userinfos.recent_replies){
              return this.userinfos.recent_replies.slice(0,5) // 只截取前5条topic
          }
      }
  }
}

5. App.vue

<template>
  <div id="app">
    <Header></Header>
    <div class="main">
    <!--注意先后顺序,SlideBar要放在main的上面-->
      <router-view name="SlideBar"></router-view>
      <router-view name="main"></router-view>     
    </div>
  </div>
</template>

<script> 
import Header from './components/Header'
import PostList from './components/PostList'
import SlideBar from './components/SlideBar'
export default {
  name:"App",
  components:{
    Header,
    PostList
  }
}
</script>

四、项目中 Vue Router 的使用


router.js 中的内容

export default new Router({
    mode: 'history',
    base: process.env.BASE_URL,
    routes: [
        {
            name: 'root',
            path: '/',
            components: {
                main: PostList
            }
        }, 
        {
            name: 'post_content',
            path: '/topic/:id&author=:name', // 接收PostList传过来的id和name
            components: {
                main: Article,
                SlideBar: SlideBar
            }
        },
        {
            name: 'user_info',
            path: '/userinfo/:name', // 接收Article传过来的name
            components: {
                main: UserInfo
            }
        }
    ]
})

PostList 组件

<router-link :to="{
       name:'post_content', 
       params:{
          id:post.id,  // 点击PostList时就把id通过路由传给Article
          name:post.author.loginname // 点击PostList时就把name通过路由传递给SlideBar
       }
}">
  <span>{{post.title}}</span>
</router-link>

Article

<router-link :to="{
        name:'user_info',
        params:{
           name:reply.author.loginname // 点击头像把loginname通过路由传递给Userinfo
        }
}">
  <img :src="reply.author.avatar_url" alt="头像" style="width:30px;height:30px;">
</router-link>

SlideBar

<router-link :to="{
      name:'user_info',
      params:{
          name:userinfos.loginname // 点击侧边栏头像把loginname通过路由传递给Userinfo
      }
}">
  <img :src="userinfos.avatar_url" alt="头像">
</router-link>

<router-link :to="{
      name:'post_content',
      params:{
         id:userinfo.id  // 点击侧边栏标题把id通过路由传递给Article
      }
}">
  {{userinfo.title}}
</router-link>

Userinfo

<router-link :to="{
      name:'post_content',
      params:{
         id:userinfo.id // 点击用户信息界面的title标题把id通过路由传递给Article
      }
}">
  {{userinfo.title}}
</router-link>

五、部署到GitHub


  1. 在GitHub上新建一个空仓库
image
  1. 在项目根目录中新建一个vue.config.js文件
image

  并在该文件中设置正确的 publicPath,添加以下内容即可

module.exports = {
  publicPath: process.env.NODE_ENV === 'production'
    ? '/cnode-demo/' // 这里应该设置为和你的仓库名相同
    : '/'
}
  1. 用GitBash打开该项目,并在命令行中输入npm run build
    此时我们会发现项目中多了一个dist目录
image
  1. 将项目上传到GitHub上

    如果仅仅只是为了预览项目,那么只需要把刚才生成的dist目录上传到我们第一步中新建的那个仓库中就可以进行预览了,但同时我们也可以将整个项目上传

    注意!!!
    上传项目时最好不要把node_modules文件夹也一起上传,因为这会使我们的项目变得非常大。
    我们应设置忽略上传node_modules,在.gitignore文件中写上一行 /node_modules/,即可防止 node_modules 目录被提交

  2. 预览项目

将项目上传成功并设置GitHub Pages后,在URL路径中加一个/dist/就可以预览我们的项目啦

image

六、效果图


Header+PostList
Article+SlideBar
Article评论区
Userinfo
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343