Vue.js 框架源码与进阶 - NuxtJS案例 - Realworld项目 - 文章/发布部署

NuxtJS案例 - Realworld项目 - 文章/发布部署

11.5 文章详情

展示基本信息

Vue.js 框架源码与进阶 - NuxtJS案例 - Realworld项目 - 文章/发布部署

请求方法:GET

请求地址:/api/articles/:slug

api/article.js

// 获取文章详情
export const getArticle = slug => {
  return request({
    method: 'GET',
    url: `/api/articles/${slug}`
  })
}

pages/article/index.vue

import { getArticles } from '@/api/article'

export default {
  name: 'ArticleIndex',
  async asyncData ({ params }) {
    const { data } = await getArticles(params.slug)
    console.log(data)
  }
}

Vue.js 框架源码与进阶 - NuxtJS案例 - Realworld项目 - 文章/发布部署

export default {
  name: 'ArticleIndex',
  async asyncData ({ params }) {
    const { data } = await getArticles(params.slug)
    return {
      article: data.article
    }
  }
}
<h1>{{ article.title }}</h1>

...
<div class="row article-content">
  <div class="col-md-12">{{ article.body }}</div>
</div>

Vue.js 框架源码与进阶 - NuxtJS案例 - Realworld项目 - 文章/发布部署

把Markdown转为HTML

markdown-it:将Markdown文档转换为Html

npm install markdown-it --save

在文章的详情页面加载包pages/article/index.vue

import MarkdownIt from 'markdown-it'

export default {
  name: 'ArticleIndex',
  async asyncData ({ params }) {
    const { data } = await getArticle(params.slug)
    const { article } = data
    const md = new MarkdownIt
    article.body = md.render(article.body)
    return {
      article: article
    }
  }
}

Vue.js 框架源码与进阶 - NuxtJS案例 - Realworld项目 - 文章/发布部署

<div class="row article-content">
  <div class="col-md-12" v-html="article.body"></div>
</div>

Vue.js 框架源码与进阶 - NuxtJS案例 - Realworld项目 - 文章/发布部署

展示文章作者相关信息

效果图:

Vue.js 框架源码与进阶 - NuxtJS案例 - Realworld项目 - 文章/发布部署

模板:

<div class="article-meta">
  <a href=""><img src="http://i.imgur.com/Qr71crq.jpg" /></a>
  <div class="info">
    <a href="" class="author">Eric Simons</a>
    <span class="date">January 20th</span>
  </div>
  <button class="btn btn-sm btn-outline-secondary">
    <i class="ion-plus-round"></i>
    &nbsp;
    Follow Eric Simons <span class="counter">(10)</span>
  </button>
  &nbsp;&nbsp;
  <button class="btn btn-sm btn-outline-primary">
    <i class="ion-heart"></i>
    &nbsp;
    Favorite Post <span class="counter">(29)</span>
  </button>
</div>

我们可以把它们封装为组件以便我们的重用:pages/article/components/article-meta.vue

<template>
插入模板...
</template>
export default {
  name: 'ArticleMeta'
}

注册组件:pages/article/index.vue

import ArticleMeta from './components/article-meta.vue'

export default {
  ...
  components: {
    ArticleMeta
  }
}

加载组件:

<article-meta />

数据展示:

<article-meta :article="article" />

子组件声明接收:pages/article/components/article-meta.vue

export default {
  name: 'ArticleMeta',
  props: {
    article: Object,
    required: true // 必须的
  }
}

Vue.js 框架源码与进阶 - NuxtJS案例 - Realworld项目 - 文章/发布部署

<template>
  <div class="article-meta">
    <nuxt-link :to="{
      name: 'profile',
      params: {
        username: article.author.username
      }
    }">
      <img :src="article.author.image" />
    </nuxt-link>
    <div class="info">
      <nuxt-link class="author" :to="{
        name: 'profile',
        params: {
          username: article.author.username
        }
      }">
        {{ article.author.username }}
      </nuxt-link>
      <span class="date">{{ article.createdAt | date('MMMM DD, YYYY') }}</span>
    </div>
    <!-- 用户关注状态 -->
    <button 
      class="btn btn-sm btn-outline-secondary"
      :class="{
        active: article.author.following
      }"
    >
      <i class="ion-plus-round"></i>
      &nbsp;
      Follow Eric Simons <span class="counter">(10)</span>
    </button>
    &nbsp;&nbsp;
    <!-- 用户是否已点赞 -->
    <button 
      class="btn btn-sm btn-outline-primary"
      :class="{
        active: article.favorited
      }"
      >
      <i class="ion-heart"></i>
      &nbsp;
      Favorite Post <span class="counter">(29)</span>
    </button>
  </div>
</template>

Vue.js 框架源码与进阶 - NuxtJS案例 - Realworld项目 - 文章/发布部署

设置页面meta优化SEO

除了根据正文内容来处理SEO,页面的标题以及跟meta标签相关的内容对于收录同样非常重要:

NuxtJS相关文档

Nuxt.js 使用了 vue-meta 更新应用的 头部标签(Head)and html 属性

Nuxt.js 允许你在 nuxt.config.js 里定义应用所需的所有默认 meta 标签,在 head 字段里配置就可以了。

如果我们有针对某个特定的页面来定制:页面头部配置API

pages/article/index.vue

export default {
  ...
  head() {
    return {
      title: `${this.article.title} - RealWorld`,
      meta: [
        {
          hid: 'description',
          name: 'description',
          content: this.article.description
        }
      ]
    }
  }
}

注意:为了避免子组件中的 meta 标签不能正确覆盖父组件中相同的标签而产生重复的现象,建议利用 hid 键为 meta 标签配一个唯一的标识编号。

Vue.js 框架源码与进阶 - NuxtJS案例 - Realworld项目 - 文章/发布部署

文章评论-通过客户端渲染展示评论列表

将评论列表封装为组件:pages/article/components/article-comments.vue

<template>
  <div>
    <form class="card comment-form">
      <div class="card-block">
        <textarea class="form-control" placeholder="Write a comment..." rows="3"></textarea>
      </div>
      <div class="card-footer">
        <img src="http://i.imgur.com/Qr71crq.jpg" class="comment-author-img" />
        <button class="btn btn-sm btn-primary">
        Post Comment
        </button>
      </div>
    </form>

    <div class="card">
      <div class="card-block">
        <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
      </div>
      <div class="card-footer">
        <a href="" class="comment-author">
          <img src="http://i.imgur.com/Qr71crq.jpg" class="comment-author-img" />
        </a>
        &nbsp;
        <a href="" class="comment-author">Jacob Schmidt</a>
        <span class="date-posted">Dec 29th</span>
      </div>
    </div>

    <div class="card">
      <div class="card-block">
        <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
      </div>
      <div class="card-footer">
        <a href="" class="comment-author">
          <img src="http://i.imgur.com/Qr71crq.jpg" class="comment-author-img" />
        </a>
        &nbsp;
        <a href="" class="comment-author">Jacob Schmidt</a>
        <span class="date-posted">Dec 29th</span>
        <span class="mod-options">
          <i class="ion-edit"></i>
          <i class="ion-trash-a"></i>
        </span>
      </div>
    </div>
  </div>
</template>
export default {
  name: 'ArticleComments'
}

加载注册组件:pages/article/index.vue

<article-comments />
import ArticleComments from './components/article-comments.vue'

components: {
  ...
  ArticleComments
}

Vue.js 框架源码与进阶 - NuxtJS案例 - Realworld项目 - 文章/发布部署

请求方法:GET

请求地址:/api/articles/:slug/comments

api/article.js

// 获取文章评论
export const getComments = slug => {
  return request({
    method: 'GET',
    url: `/api/articles/${slug}/comments`
  })
}

加载请求方法:pages/article/components/article-comments.vue

import { getComments } from '@/api/article'

在父组件传递文章对象给子组件:pages/article/index.vue

<article-comments :article="article" />

评论并不需要SEO:pages/article/components/article-comments.vue

export default {
  name: 'ArticleComments',
  props: {
    article: {
      type: Object,
      required: true // 必须的
    }
  },
  data () {
    return {
      comments: [] // 文章列表
    }
  },
  // 评论不需要SEO
  // 客户端加载
  async mounted () {
    const { data } = await getComments(this.article.slug)
    this.comments = data.comments
  }
}

遍历数据:

<div 
  class="card" 
  v-for="comment in comments"
  :key="comment.id"
>
  <div class="card-block">
    <p class="card-text">{{ comment.body }}</p>
  </div>
  <div class="card-footer">
    <nuxt-link 
      class="comment-author"
      :to="{
        name: 'profile',
        params: {
          username: comment.author.username
        }
      }"
    >
      <img :src="comment.author.image" class="comment-author-img" />
    </nuxt-link>
    &nbsp;
    <nuxt-link 
      class="comment-author"
      :to="{
        name: 'profile',
        params: {
          username: comment.author.username
        }
      }"
    >
      {{ comment.author.username }}
    </nuxt-link>
    <span class="date-posted">{{ comment.createdAt | date('MMMM DD, YYYY')}}</span>
  </div>
</div>

11.6 发布部署

打包

命令 描述
nuxt 启动一个热加载的 Web 服务器(开发模式) localhost:3000
nuxt build 利用 webpack 编译应用,压缩 JS 和 CSS 资源(发布用)。
nuxt start 以生产模式启动一个 Web 服务器 (需要先执行nuxt build)。
nuxt generate 编译应用,并依据路由配置生成对应的 HTML 文件 (用于静态站点的部署)。

package.json

{
  "scripts": {
    "dev": "nuxt",
    "build": "nuxt build",
    "start": "nuxt start"
  }
}
npm run build

生成结果:.nuxt以及.nuxt/dist

npm run start

最简单的部署方式

  • 配置 Host + Post

nuxt.config.js

server: {
  host: '0.0.0.0', // 默认localhost
  port: 3000
}
  • 压缩发布包

    • .nuxt:Nuxt打包生成的资源文件
    • static:项目静态资源
    • nuxt.config.js:提供给Nuxt服务
    • package.json:用于服务端第三方包的安装
    • package-lock.json:用于服务端第三方包的安装
  • 把发布包传到服务端

得到realworld-nuxtjs.zip传递到服务端

ssh root@xxx.xxx.xxx.xxx

mkdir realworld-nuxtjs

cd realworld-nuxtjs/

pwd

复制路径pathexit退出服务

scp .\realworld-nuxtjs.zip root@xxx.xxx.xxx.xxx:path
  • 解压
ssh root@xxx.xxx.xxx.xxx

cd realworld-nuxtjs/

unzip realworld-nuxtjs.zip

ls -a
  • 安装依赖
npm i
  • 启动服务
npm run start

使用PM2启动Node服务

PM2 是什么?

刚才我们在服务端是直接通过npm run start命令来启动了Web服务,如果我们通过这种方式启动起来以后,此时占用了命令行。我们希望它在后台运行这个应用,因此就需要用到pm2工具:

使用 PM2 启动服务

# 没有node环境的话
# sudo yum install -y nodejs

npm install --global pm2

pm2 start npm -- start

PM2 常用命令

命令 说明
pm2 list 查看应用列表
pm2 start 启动应用
pm2 stop 停止应用
pm2 reload 重载应用
pm2 restart 重启应用
pm2 delete 删除应用

自动化部署介绍

传统的部署方式需要反复地更新、构建、发布,显得很麻烦。我们希望这件事情能够自动化,即使用现代化的部署方式(CI/CD)

Vue.js 框架源码与进阶 - NuxtJS案例 - Realworld项目 - 文章/发布部署

CI/CD 服务

  • Jenkins
  • Gitlab CI
  • GitHub Actions
  • Travis CI
  • Circle CI

GitHub Actions 自动部署

这里我们使用 GitHub Actions 实现自动部署:

环境准备

  • Linux 服务器
  • 把代码提交到 GitHub 远程仓库

配置 GitHub Access Token

这是我的配置,供参考:
Vue.js 框架源码与进阶 - NuxtJS案例 - Realworld项目 - 文章/发布部署
复制令牌(令牌只显示一次,请保管)
Vue.js 框架源码与进阶 - NuxtJS案例 - Realworld项目 - 文章/发布部署

Vue.js 框架源码与进阶 - NuxtJS案例 - Realworld项目 - 文章/发布部署

新建TOKEN,将生成的token放入
Vue.js 框架源码与进阶 - NuxtJS案例 - Realworld项目 - 文章/发布部署

配置 GitHub Actions 执行脚本

  • 在项目根目录创建 .github/workflows 目录
  • 下载 main.yml 到 workflows 目录中

Vue.js 框架源码与进阶 - NuxtJS案例 - Realworld项目 - 文章/发布部署
Vue.js 框架源码与进阶 - NuxtJS案例 - Realworld项目 - 文章/发布部署

  • 修改配置
# 打包构建
- run: tar -zcvf release.tgz .nuxt static nuxt.config.js package.json package-lock.json pm2.config.json

...

pm2 reload pm2.config.json

Vue.js 框架源码与进阶 - NuxtJS案例 - Realworld项目 - 文章/发布部署

  • 配置 PM2 配置文件

pm2.config.json

{
  "apps": [
    {
      "name": "RealWorld",
      "script": "npm",
      "args": "start"
    }
  ]
}
  • 提交更新
git add .

git commit -m "发布部署-测试"

git tag v0.1.0

git push origin v0.1.0
  • 查看自动部署状态
  • 访问网络
  • 提交更新

项目仓库地址:https://github.com/shiguanghai/realworld-nuxtjs

上一篇:Nuxt源码 — plugin (middleware) 参数之谜


下一篇:nuxt操作,代理、请求转发配置