静态站点生成

一、Gridsome 是什么

一个免费、开源、基于Vue.js技术栈的静态网站生成器
GitHub 仓库:https://github.com/gridsome/gridsome
官网:https://gridsome.org/
什么是静态网站生成器

什么是静态网站生成器:
静态网站生成器是使用一系列配置、模板、以及数据,生成静态 HTML 文件以及相关资源的工具
这个功能也叫预渲染
生成的网站不需要类似 PHP 这样的服务器
只需要放到支持静态资源的 Web Server 或 CND 上即可

静态网站有什么好处:
省钱:不需要专业的服务器,只要能托管静态文件的空间即可
快速:不经过后端服务器的处理,只传输内容
安全:没有后端程序的运行,自然会更安全

常见的静态网站生成器:
Jekyll(Ruby)
Hexo(Node)
Hugo(Golang)
Gatsby(Node/React)
Gridsome(Node/Vue)
另外,Next.js,Nuxt.js 也能生成静态网站,但是他们更多被认为是 SSR(服务端渲染)框架
这些应用还有一个更优雅的名字:JAMStack

JAMStack:
JAM:是 JavaScript、API 和 Markup 的首字母组合
本质上是一种胖前端,通过调用各种 API 来实现更多的功能
其实也是一种前后端的模式,只不过离得比较开,甚至前后端来自多个不同的厂商

静态应用的使用场景:

  • 不适合有大量路由页面的应用
    • 如果网站有成百上千个路由页面,则预渲染将非常慢。当然,每次更新只需要做一次,但是可能要花一点时间。大多数人不会最终获得数千条静态路由的页面,这只是以防万一
  • 不适合有大量动态内容的应用
    • 如果渲染路线中包含特定有用户查看其内容或其他动态源的内容,则应确保您具有可以显示的占位符组件,知道动态内容加载到客户端位置,否则可能有点怪异

二、Gridsome 基础 - 创建Gridsome项目

参考: https://gridsome.org/docs/#how-to-install

配置 Gridsome 的环境:
Gridsome 依赖一个特殊的第三方模块:sharp。它的作用是用于处理图片的,比如压缩图片大小,转换图片格式…
sharp 这个包很难安装成功,原因有两点:

1、sharp 里面包含一些 C++ 文件,在安装的时候,要先对其进行编译才能正常安装,所以需要一个 C++ 的编译环境才行
2、sharp 依赖一个 libvips 的第三方模块,这包比较大(大概有几十M),由于国内的网络环境问题,这个包很难安装成功,所以导致 sharp 很难安装成功
解决方式:
网络问题:
https://github.com/lovell/sharp
https://sharp.pixelplumbing.com/install#chinese-mirror

sharp 官方文档中有一个镜像源 chinese-mirror
npm config set sharp_binary_host “https://npm.taobao.org/mirrors/sharp”
npm config set sharp_libvips_binary_host “https://npm.taobao.org/mirrors/sharp-libvips”
C++ 编译环境问题
安装 node-gyp 以及它的相关套件。
node-gyp 用于处理 nodejs 中 C++ 扩展包的
参考:https://github.com/nodejs/node-gyp
安装 node-gyp 的套件(环境)
这里安装的是 windows 的套件:npm install --global windows-build-tools(注:需要使用管理员身份打开命令行窗口)
不管是哪个环境,都必须要安装 python

创建 Gridsome 项目
安装 CLI
npm install --global @gridsome/cli
创建 Gridsome 项目
gridsome create my-gridsome-site
安装过程中,如果在安装依赖的话是看不到进度的。我们可以将它终止掉,然后自己手动安装,
cd my-gridsome-site
ls
rm -rf mode_modules
npm i
这样就能看到安装依赖的进度了。
cod . 可以打看到项目初始的目录结构
进入项目并启动
cd my-gridsome-site
gridsome develop 启动项目的开发模式

3、Gridsome基础 - 预渲染

gridsome 会将 pages 目录下的页面文件,创建成一个个路由页面,我们无需手动创建。
即便是新创建的页面,也能直接在浏览器中访问到该页面
执行 npm run build 打包构建项目 生成dist目录 生成静态网页
本地部署测试可以用个简单的工具 serve 是基于nodejs开发的一个命令行当中的静态web服务
npm i -g serve
通过 serve 部署(启动)项目 serve .\dist\

预渲染是纯静态的网页内容,没有所谓的服务端渲染的东西,服务端只是单纯的将文件内容读取出来,然后发送给客户端。
在客户端中是单页面应用,除非是强制刷新,才会走服务端的请求

4、Gridsome基础 - 目录结构

参考:https://gridsome.org/docs/directory-structure/
Gridsome 基础
目录结构
.
├── package.json
├── gridsome.config.js
├── gridsome.server.js
├── static/
└── src/
├── main.js
├── index.html
├── App.vue
├── layouts/
│ └── Default.vue
├── pages/
│ ├── Index.vue
│ └── Blog.vue
└── templates/
└── BlogPost.vue
main.js:项目入口
main.js 中只是导入了一个 layout 组件,导出一个函数(继集成了vue、router等成员),函数中将 layout 注册到全局组件上

  // This is the main.js file. Import global CSS and scripts here.
  // The Client API can be used here. Learn more: gridsome.org/docs/client-api

  import DefaultLayout from '~/layouts/Default.vue'

  export default function (Vue, { router, head, isClient }) {
    // Set default layout as a global component
    Vue.component('Layout', DefaultLayout)
  }

将来还可以在 main.js 中引入全局样式,或者脚本文件

template:放置集合的节点
所谓的集合节点其实里面也是用来放 vue 组件的,但是这个 vue 组件跟 pages 里面的组件不同,后面会介绍到

pages:页面,每个文件就是一个页面,gridsome 会根据页面文件自动创建路由
每个 page 都可以根据需要,使用前面注册是全局组件 layout
src/layouts:存放 layout
Default.vue




{{ $static.metadata.siteName }}





query { metadata { siteName } } 这里有个特殊的标签 ,后面用到的时候再说,这里只需要知道它是用来获取 GraphQL 数据的即可

components:公共组件
.temp:打包构建构成中自动生成的目录,用于查看打包生成结果,便于调试
.cache: 打包构建过程中缓存的一些内容如图片
dist: npm run build打包编译生成的dist结果
static:主要放一些不需要打包编译的文件,纯静态的资源
gridsome.config.js:gridsome的配置文件如页面标题名称、插件等
gridsome.server.js:也是gridsome相关的配置文件,针对gridsome内部的服务端的,定制一些功能
package-lock.json:防止包的版本自动升级

5、Gridsome基础 - 项目配置

参考文档即可:https://gridsome.org/docs/config/
gridsome.config.js

// This is where project configuration and plugin options are located.
// Learn more: https://gridsome.org/docs/config

// Changes here require a server restart.
// To restart press CTRL + C in terminal and run `gridsome develop`

module.exports = {
  siteName: 'Gridsome',
  plugins: []
}

备注:该文件修改后需要重新启动才能生效
siteName: 网站名,比如 Home - Gridsome,About - Gridsome,页面中也能拿到它
siteDescription:对应页面中的meta标签。网站的 description,利于 SEO
titleTemplate:用于定制网站标题模板 %s -

6、Gridsome基础 - Pages

参考:https://gridsome.org/docs/pages/

Pages项目的中路由组件,将来都会生成对应的路由页面。在编译器的时候最终都成为静态的html文件
在 gridsome 中,有两种创建 pages 的方式:

(1)、文件系统 - 使用单文件组件的方式创建页面

  • src/pages/Index.vue becomes / (The frontpage)

  • src/pages/AboutUs.vue becomes /about-us/

  • src/pages/about/Vision.vue becomes /about/vision/

  • src/pages/blog/Index.vue becomes /blog/
    单文件组件看起来像是这样的

    Hello, world!

(2)、 api方式 - 使用编程的方式创建页面
在 gridsome.server.js文件中,调用 api.createPages 方法

 module.exports = function (api) {
    api.createPages(({ createPage }) => {
      createPage({
        path: '/my-page',
        component: './src/templates/MyPage.vue'
      })
    })
  }

npm run develop 可以访问查看

动态路由:
动态路由有两种方式:
(1)创建基于文件的动态路由

动态页面用于客户端路由。路由参数可以通过将名称包装在方括号中来放置在文件和目录名称中。例如

src/pages/user/[id].vue 成为 /user/:id
src/pages/user/[id]/settings.vue 成为 /user/:id/settings
构建的时候,会先生成 user/_id.html,user/_id/settings.html 并且必须具有 重写规则 以使其正常工作

具有动态路由的页面的优先级会低于固定路由。例如,user/create 和 user/:id,则 user/create 优先

这是一个基本的页面组件,它使用 id 路由中的参数来获取客户端的用户信息:

{{ user.name }}

始终使用 mounted 挂钩来获取客户端数据。created 由于在生成静态HTML时会执行挂钩,因此在挂钩中获取数据会引起问题。
创建动态路由页面

User {{ $route.params.id }} Page

(2)编程式创建动态路由
以编程方式创建带有动态路由的页面,以获取更高级的路径。动态参数通过 : 后面的参数来指定。
每个参数都可以有一个自定义的正则表达式,以仅匹配数字或某些值。

module.exports = function (api) {
api.createPages(({ createPage }) => {
createPage({
path: ‘/user/:id(\d+)’, // (\d+) 正则表达式,用于校验动态路由参数
component: ‘./src/templates/User.vue’
})
})
}
生成重写规则:https://link.zhihu.com/?target=https%3A//gridsome.org/docs/dynamic-routing/%23generating-rewrite-rules
Gridsome 无法为动态路由的每种可能的变体生成 HTML 文件,这意味着直接访问URL时最有可能显示404页。
正确的做法是,Gridsome 生成一个 HTML 文件,该文件可用于重写规则。
例如,类似的路线 /user/:id 将生成位于的 HTML 文件 /user/_id.html,您可以有一个重写规则来映射所有与 /user/:id 文件匹配的路径。

const fs = require(‘fs’)

module.exports = {
afterBuild ({ redirects }) {
for (const rule of redirects) {
// rule.from - The dynamic path
// rule.to - The HTML file path
// rule.status - 200 if rewrite rule
}
}
}
关于重写规则后续会详细介绍

7、Gridsome基础 - 添加集合

参考:https://gridsome.org/docs/collections/
jsonplaceholder 中提供了很多免费的 api 数据接口,可以用于开发测试
需求:就是从 jsonplaceholder 中获取文章列表,然后输出到页面上,点击文章的时候可以调转到文章详情



Posts 1



  • {{ item.title }}




查看渲染结果:页面中的确渲染了数据内容,但是从网络面板中可以看到,服务端返回的 html 页面内容是空的。
也就是说,我们所看到的内容并不是预渲染的,而是从客户端中加载获取到的。

我们想要的是:接口中获取到的动态数据,也需要对它进行预渲染到页面中,然后生成静态页面
此时就需要使用到 Gridsome 中提供的 collections 集合了。

collections 的作用:
承载数据
Gridsome 会将集合中的节点模板预渲染成一个个的页面
添加集合的方式
(1)使用 源插件 Source Plugins 来创建集合,通过封装好的插件,把外部数据集成到gridsome中变成集合去用
本示例从 WordPress 网站创建集合。typeName 源插件的选项通常用于为插件添加的集合名称添加前缀

// gridsome.config.js
module.exports = {
  plugins: [
    {
      use: '@gridsome/source-wordpress',
      options: {
        baseUrl: 'YOUR_WEBSITE_URL',
        typeName: 'WordPress',
      }
    }
  ]
}

(2)使用 data store 数据存储 API 来创建集合
您可以从任何外部API手动添加集合。本示例创建一个名为 Post 的集合,该集合从API获取内容,并将结果作为节点添加到该集合。

// gridsome.server.js
const axios = require('axios')

module.exports = function (api) {
  // 加载资源
  api.loadSource(async actions => {
    // 添加集合,集合名称为 Post
    const collection = actions.addCollection('Post')
    // 发起请求,获取数据:想要渲染到页面中的数据,在这里进行预请求
    const { data } = await axios.get('https://jsonplaceholder.typicode.com/**posts**')

    // 遍历数据,
    for (const item of data) {
      // 添加节点,并往节点中添加数据
      // 将来就可以通过节点和数据创建对应的模板,然后生成节点页面了
      collection.addNode({
        id: item.id,
        title: item.title,
        content: item.body
      })
    }
  })
}

重启 npm run develop

八、Gridsome基础 - 在GraphQL中查询数据

每个集合都将向 GraphQL schema 添加两个根字段,这些根字段用于检索页面中的节点。
字段名称是根据集合名称自动生成的。
比如,集合名字为 Post,则将在 schema 中生成以下字段:

  • post: 通过 id 获取单个节点
  • allPost:获取所有的节点列表。(可以排序和过滤)

在开发模式下,启动 web 服务时,命令行中有 3 条 url,其中前两个是网站访问 url,最后一个是 GraphQL 的访问路径 这个地址只有在开发模式下才能访问

在 GraphQL 中查看数据:

post
在这里插入图片描述

allPost
在这里插入图片描述

例如,查询 id 为 2 的文章:
在这里插入图片描述
例如,查询所有文章信息:
在这里插入图片描述

九、Gridsome基础 - 在页面中查询 GraphQL 数据

参考;https://gridsome.org/docs/querying-data/

前面已经知道了如何在 GraphQL 中查询数据了,那么在页面中要如何来查询数据,并渲染到页面模板上呢?

文档中提到,我们可以在任意 页面Page,模板Template,或组件Component 中查询到 GraphQL 的数据层,查询的方式就是在 Vue 组件中添加 或者 块:

在 pages 或者 template 中使用
在 component 中使用

<template>
  <div>
    <div v-for="edge in $page.posts.edges" :key="edge.node.id">
      <h2>{{ edge.node.title }}</h2>
    </div>
  </div>
</template>

<page-query>
query {
  // posts 别名
  posts: allPost {
    edges {
      node {
        id
        title
      }
    }
  }
}
</page-query>

page-query 与 template 是同一层级的,在 page-query 中使用 GraphQL 的语法来查询数据。
Gridsome 会将数据挂载到当前 vue 示例的 $page 上

在开发模式下预览是看不到预渲染的效果的

需要打包之后才能看到 Gridsome 的预渲染的效果。
npm run build
serve dist/
在这里插入图片描述
在这里插入图片描述
如果是通过切换导航栏进入的预渲染页面,它的数据是在客户端加载的(不是静态化的);只有在通过浏览器地址栏直接访问、或者刷新这个页面的时候,这个页面才是静态化(服务端渲染出来)的。

十、Gridsome基础 - 使用模板渲染节点页面

参考:https://gridsome.org/docs/templates/

点击文章标题进入到文章详情中,我们希望的是,文章详情页面也是被静态化的。

在 gridsome-server.js 中预取数据,然后通过集合 collection.addNode 添加节点。但是只有节点,没有模板,所以接下来需要创建模板
模板用于为集合中的节点创建单个页面。节点需要相应的页面才能显示在其自己的URL上。下面示例中显示了如何为名为 Post 的集合设置路由和模板。
如果没有指定模板的话,就会使用 src/templates/{Collection}.vue 的组件作为模板

// gridsome.config.js
module.exports = {
  templates: {
    // Post: '/posts/:id', // 默认就会去 templates 文件夹中寻找同名的文件,路径不能为空
    Post: [
      { // 配置具体规则  类似路由
        path: '/posts/:id', // 这个 id 是在 gridsome.server.js 节点中添加的真实有效的数据
        component: './src/templates/Post.vue'
      }
    ]
  }
}

在 Post 页面组件中获取文章详情

query ($id: ID!):使用 query 接受参数,变量名为 $id,类型为 ID,!: 不能为空

<page-query>
query ($id: ID!) {
  post(id: $id) {
    id,
    title,
    content
  }
}
</page-query>

gridsome 匹配到组件的时候,会将 gridsome.config.js 中配置的 :id 传递给 query

修改 Post2 文章列表

<template>
  <div>
    <div v-for="edge in $page.posts.edges" :key="edge.node.id">
      <g-link :to="edge.node.path">{{ edge.node.title }}</g-link>
    </div>
  </div>
</template>

<page-query>
query {
  // posts 别名
  posts: allPost {
    edges {
      node {
        id
        title
        path    // 把 path 拿进来
      }
    }
  }
}
</page-query>

已经将文章的 path 也渲染了

文章详情

文章详情的页面 title 显示为文章标题

export default {
  metaInfo () {
    return {
      title: this.$page.post.title
    }
  }
  // 这种写法没法访问graphQL里的数据
  // metaInfo:{
	// title: ''
  // }
}

查看构建后的项目目录结构


posts 文件夹下有 1,2,3…100 个文件夹,里面是 gridsome 通过预渲染将数据预取,然后生成一个个的 html 页面。

十一、Gridsome案例 - 创建项目

项目说明:
开发个人博客
项目模板是一个开源的、基于 Bootstrap 的博客项目 fork clone
项目目标:
能够熟练使用 gridsome 开发预渲染的静态页面
并且能解决实际开发中遇到的问题。比如:
预渲染的静态列表的分页
数据的管理
创建项目
初始化项目
gridsome create blog-with-gridsome
ctrl+c打断
cd ./blog-with-gridsome
rm -rf node_modules
npm i 这样就可以看到安装的进度与状态的提示了
安装成功后
npm run develop启动开发模式

十二、Gridsome案例 - 处理首页模板

项目模板是一个开源的、基于 Bootstrap 的博客项目 fork clone … --depth=1 只下载最后一次提交的历史记录 提高下载速度
cd startbootstrap-clean-blog
code -a . 在编辑器当中打开到同一个编辑器当中

在gridsome案例中

安装并引入 bootstrap 样式库和 @fortawesome/fontawesome-free 图标库
npm i @fortawesome/fontawesome-free bootstrap
在 main.js 文件中引入

 import 'bootstrap/dist/css/bootstrap.min.css'
 import '@fortawesome/fontawesome-free/css/all.min.css'

引入 Google 字体
创建 src/assets/css/index.css 文件,并在文件中引入 Google 字体文件

 @import url('https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic');
  @import url('https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800'); 

然后在 main.js 文件中引入 css

import '@/assets/css/index.css'

引入模板项目自定义的 css 样式
将 css 样式追加到 src/assets/css/index.css 文件中即可

  /*!
   * Start Bootstrap - Clean Blog v5.0.10 (https://startbootstrap.com/theme/clean-blog)
   * Copyright 2013-2020 Start Bootstrap
   * Licensed under MIT (https://github.com/StartBootstrap/startbootstrap-clean-blog/blob/master/LICENSE)
   */

  body {
    font-size: 20px;
    color: #212529;
    font-family: 'Lora', 'Times New Roman', serif;
  }
  p {
    line-height: 1.5;
    margin: 30px 0;
  }
  p a {
    text-decoration: underline;
  }
  ...

项目模板的index.html内容直接复制过来放到gridsome项目下的src/pages/index.vue中template中根节点下

引入 img 资源
将模板项目的 img 文件夹直接拷贝到本项目中的 static 文件夹下

十三、Gridsome案例 - 处理其他页面模板

1、Layout:src/layouts/Default.vue

<template>
    <div class="layout">
      <!-- Navigation -->
      <nav class="navbar navbar-expand-lg navbar-light fixed-top" id="mainNav">
        <div class="container">
          <a class="navbar-brand" href="index.html">Start Bootstrap</a>
          <button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
            Menu
            <i class="fas fa-bars"></i>
          </button>
          <div class="collapse navbar-collapse" id="navbarResponsive">
            <ul class="navbar-nav ml-auto">
              <li class="nav-item">
                <a class="nav-link" href="index.html">Home</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="about.html">About</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="post.html">Sample Post</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="contact.html">Contact</a>
              </li>
            </ul>
          </div>
        </div>
      </nav>

      <!-- 插槽 -->
      <slot/>

      <!-- Footer -->
      <footer>
        <div class="container">
          <div class="row">
            <div class="col-lg-8 col-md-10 mx-auto">
              <ul class="list-inline text-center">
                <li class="list-inline-item">
                  <a href="#">
                    <span class="fa-stack fa-lg">
                      <i class="fas fa-circle fa-stack-2x"></i>
                      <i class="fab fa-twitter fa-stack-1x fa-inverse"></i>
                    </span>
                  </a>
                </li>
                <li class="list-inline-item">
                  <a href="#">
                    <span class="fa-stack fa-lg">
                      <i class="fas fa-circle fa-stack-2x"></i>
                      <i class="fab fa-facebook-f fa-stack-1x fa-inverse"></i>
                    </span>
                  </a>
                </li>
                <li class="list-inline-item">
                  <a href="#">
                    <span class="fa-stack fa-lg">
                      <i class="fas fa-circle fa-stack-2x"></i>
                      <i class="fab fa-github fa-stack-1x fa-inverse"></i>
                    </span>
                  </a>
                </li>
              </ul>
              <p class="copyright text-muted">Copyright &copy; Your Website 2020</p>
            </div>
          </div>
        </div>
      </footer>
    </div>
  </template>

2、首页:pages/index.vue

<template>
    <Layout>
      <!-- Page Header -->
      <header class="masthead" style="background-image: url('/img/home-bg.jpg')">
        <div class="overlay"></div>
        <div class="container">
          <div class="row">
            <div class="col-lg-8 col-md-10 mx-auto">
              <div class="site-heading">
                <h1>Clean Blog</h1>
                <span class="subheading">A Blog Theme by Start Bootstrap</span>
              </div>
            </div>
          </div>
        </div>
      </header>

      <!-- Main Content -->
      <div class="container">
        <div class="row">
          <div class="col-lg-8 col-md-10 mx-auto">
            <div class="post-preview">
              <a href="post.html">
                <h2 class="post-title">
                  Man must explore, and this is exploration at its greatest
                </h2>
                <h3 class="post-subtitle">
                  Problems look mighty small from 150 miles up
                </h3>
              </a>
              <p class="post-meta">Posted by
                <a href="#">Start Bootstrap</a>
                on September 24, 2019</p>
            </div>
            <hr>
            <div class="post-preview">
              <a href="post.html">
                <h2 class="post-title">
                  I believe every human has a finite number of heartbeats. I don't intend to waste any of mine.
                </h2>
              </a>
              <p class="post-meta">Posted by
                <a href="#">Start Bootstrap</a>
                on September 18, 2019</p>
            </div>
            <hr>
            <div class="post-preview">
              <a href="post.html">
                <h2 class="post-title">
                  Science has not yet mastered prophecy
                </h2>
                <h3 class="post-subtitle">
                  We predict too much for the next year and yet far too little for the next ten.
                </h3>
              </a>
              <p class="post-meta">Posted by
                <a href="#">Start Bootstrap</a>
                on August 24, 2019</p>
            </div>
            <hr>
            <div class="post-preview">
              <a href="post.html">
                <h2 class="post-title">
                  Failure is not an option
                </h2>
                <h3 class="post-subtitle">
                  Many say exploration is part of our destiny, but it’s actually our duty to future generations.
                </h3>
              </a>
              <p class="post-meta">Posted by
                <a href="#">Start Bootstrap</a>
                on July 8, 2019</p>
            </div>
            <hr>
            <!-- Pager -->
            <div class="clearfix">
              <a class="btn btn-primary float-right" href="#">Older Posts &rarr;</a>
            </div>
          </div>
        </div>
      </div>
    </Layout>
  </template>

  <script>
  export default {
    name: 'IndexPage',
    metaInfo: {
      title: 'Hello, world!'
    }
  }
  </script>

3、文章详情页面 pages/Post.vue

<template>
    <Layout>
      <!-- Page Header -->
      <header class="masthead" style="background-image: url('img/post-bg.jpg')">
        <div class="overlay"></div>
        <div class="container">
          <div class="row">
            <div class="col-lg-8 col-md-10 mx-auto">
              <div class="post-heading">
                <h1>Man must explore, and this is exploration at its greatest</h1>
                <h2 class="subheading">Problems look mighty small from 150 miles up</h2>
                <span class="meta">Posted by
                  <a href="#">Start Bootstrap</a>
                  on August 24, 2019</span>
              </div>
            </div>
          </div>
        </div>
      </header>

      <!-- Post Content -->
      <article>
        <div class="container">
          <div class="row">
            <div class="col-lg-8 col-md-10 mx-auto">
              <p>Never in all their history have men been able truly to conceive of the world as one: a single sphere, a globe, having the qualities of a globe, a round earth in which all the directions eventually meet, in which there is no center because every point, or none, is center — an equal earth which all men occupy as equals. The airman's earth, if free men make it, will be truly round: a globe in practice, not in theory.</p>

              <p>Science cuts two ways, of course; its products can be used for both good and evil. But there's no turning back from science. The early warnings about technological dangers also come from science.</p>

              <p>What was most significant about the lunar voyage was not that man set foot on the Moon but that they set eye on the earth.</p>

              <p>A Chinese tale tells of some men sent to harm a young girl who, upon seeing her beauty, become her protectors rather than her violators. That's how I felt seeing the Earth for the first time. I could not help but love and cherish her.</p>

              <p>For those who have seen the Earth from space, and for the hundreds and perhaps thousands more who will, the experience most certainly changes your perspective. The things that we share in our world are far more valuable than those which divide us.</p>

              <h2 class="section-heading">The Final Frontier</h2>

              <p>There can be no thought of finishing for ‘aiming for the stars.’ Both figuratively and literally, it is a task to occupy the generations. And no matter how much progress one makes, there is always the thrill of just beginning.</p>

              <p>There can be no thought of finishing for ‘aiming for the stars.’ Both figuratively and literally, it is a task to occupy the generations. And no matter how much progress one makes, there is always the thrill of just beginning.</p>

              <blockquote class="blockquote">The dreams of yesterday are the hopes of today and the reality of tomorrow. Science has not yet mastered prophecy. We predict too much for the next year and yet far too little for the next ten.</blockquote>

              <p>Spaceflights cannot be stopped. This is not the work of any one man or even a group of men. It is a historical process which mankind is carrying out in accordance with the natural laws of human development.</p>

              <h2 class="section-heading">Reaching for the Stars</h2>

              <p>As we got further and further away, it [the Earth] diminished in size. Finally it shrank to the size of a marble, the most beautiful you can imagine. That beautiful, warm, living object looked so fragile, so delicate, that if you touched it with a finger it would crumble and fall apart. Seeing this has to change a man.</p>

              <a href="#">
                <img class="img-fluid" src="img/post-sample-image.jpg" alt="">
              </a>
              <span class="caption text-muted">To go places and do things that have never been done before – that’s what living is all about.</span>

              <p>Space, the final frontier. These are the voyages of the Starship Enterprise. Its five-year mission: to explore strange new worlds, to seek out new life and new civilizations, to boldly go where no man has gone before.</p>

              <p>As I stand out here in the wonders of the unknown at Hadley, I sort of realize there’s a fundamental truth to our nature, Man must explore, and this is exploration at its greatest.</p>

              <p>Placeholder text by
                <a href="http://spaceipsum.com/">Space Ipsum</a>. Photographs by
                <a href="https://www.flickr.com/photos/nasacommons/">NASA on The Commons</a>.</p>
            </div>
          </div>
        </div>
      </article>
    </Layout>  
  </template>

  <script>
  export default {
    name: 'PostsPage'
  }
  </script>

4、About:pages/About.vue

<template>
    <Layout>
      <!-- Page Header -->
      <header class="masthead" style="background-image: url('/img/about-bg.jpg')">
        <div class="overlay"></div>
        <div class="container">
          <div class="row">
            <div class="col-lg-8 col-md-10 mx-auto">
              <div class="page-heading">
                <h1>About Me</h1>
                <span class="subheading">This is what I do.</span>
              </div>
            </div>
          </div>
        </div>
      </header>

      <!-- Main Content -->
      <div class="container">
        <div class="row">
          <div class="col-lg-8 col-md-10 mx-auto">
            <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Saepe nostrum ullam eveniet pariatur voluptates odit, fuga atque ea nobis sit soluta odio, adipisci quas excepturi maxime quae totam ducimus consectetur?</p>
            <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius praesentium recusandae illo eaque architecto error, repellendus iusto reprehenderit, doloribus, minus sunt. Numquam at quae voluptatum in officia voluptas voluptatibus, minus!</p>
            <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aut consequuntur magnam, excepturi aliquid ex itaque esse est vero natus quae optio aperiam soluta voluptatibus corporis atque iste neque sit tempora!</p>
          </div>
        </div>
      </div>
    </Layout>
  </template>

  <script>
  export default {
    name: 'AboutPage',
    metaInfo: {
      title: 'About us'
    }
  }
  </script>

5、Contact:pages/Contact.vue

 <template>
    <Layout>
      <!-- Page Header -->
      <header class="masthead" style="background-image: url('/img/contact-bg.jpg')">
        <div class="overlay"></div>
        <div class="container">
          <div class="row">
            <div class="col-lg-8 col-md-10 mx-auto">
              <div class="page-heading">
                <h1>Contact Me</h1>
                <span class="subheading">Have questions? I have answers.</span>
              </div>
            </div>
          </div>
        </div>
      </header>

      <!-- Main Content -->
      <div class="container">
        <div class="row">
          <div class="col-lg-8 col-md-10 mx-auto">
            <p>Want to get in touch? Fill out the form below to send me a message and I will get back to you as soon as possible!</p>
            <!-- Contact Form - Enter your email address on line 19 of the mail/contact_me.php file to make this form work. -->
            <!-- WARNING: Some web hosts do not allow emails to be sent through forms to common mail hosts like Gmail or Yahoo. It's recommended that you use a private domain email address! -->
            <!-- To use the contact form, your site must be on a live web host with PHP! The form will not work locally! -->
            <form name="sentMessage" id="contactForm" novalidate>
              <div class="control-group">
                <div class="form-group floating-label-form-group controls">
                  <label>Name</label>
                  <input type="text" class="form-control" placeholder="Name" id="name" required data-validation-required-message="Please enter your name.">
                  <p class="help-block text-danger"></p>
                </div>
              </div>
              <div class="control-group">
                <div class="form-group floating-label-form-group controls">
                  <label>Email Address</label>
                  <input type="email" class="form-control" placeholder="Email Address" id="email" required data-validation-required-message="Please enter your email address.">
                  <p class="help-block text-danger"></p>
                </div>
              </div>
              <div class="control-group">
                <div class="form-group col-xs-12 floating-label-form-group controls">
                  <label>Phone Number</label>
                  <input type="tel" class="form-control" placeholder="Phone Number" id="phone" required data-validation-required-message="Please enter your phone number.">
                  <p class="help-block text-danger"></p>
                </div>
              </div>
              <div class="control-group">
                <div class="form-group floating-label-form-group controls">
                  <label>Message</label>
                  <textarea rows="5" class="form-control" placeholder="Message" id="message" required data-validation-required-message="Please enter a message."></textarea>
                  <p class="help-block text-danger"></p>
                </div>
              </div>
              <br>
              <div id="success"></div>
              <button type="submit" class="btn btn-primary" id="sendMessageButton">Send</button>
            </form>
          </div>
        </div>
      </div>
    </Layout>
  </template>

  <script>
  export default {
    name: 'ContactPage'
  }
  </script>

十四、Gridsome案例 - 使用本地 md 文件管理文章内容

Gridsome 中提供了给项目引入数据的方式:importing data
这里使用本地 md 文件来管理网站数据来源:mark down

安装 @gridsome/source-filesystem,用于读取 md 文件

npm i @gridsome/source-filesystem

gridsome.config.js 中引入插件
// gridsome.config.js

  module.exports = {
    plugins: [
      {
        use: '@gridsome/source-filesystem',
        options: {
          typeName: 'BlogPost', // 转换成 GraphQL 后的数据类型
          path: './content/blog/**/*.md', // 文件的路径
        },
      },
    ],
  };

创建 content/blog/**/*.md

// article1.md
  # article 1

  - 1
  - 2
  - 3
  - 4

安装 @gridsome/transformer-remark 用以将 markdown 文件转换为 html 文件
npm i @gridsome/transformer-remark
gridsome.config.js 文件中引入插件

  module.exports = {
    siteName: 'Gridsome',
    plugins: [
      {
        use: '@gridsome/source-filesystem',
        options: {
          typeName: 'BlogPost', // 转换成 GraphQL 后的数据类型
          path: './content/blog/**/*.md', // 文件的路径
        },
      }
    ],
    transformer: {
      remark: {}
    }
  }

在 GraphQL 中查看转换结果:
在这里插入图片描述
查询结果
在这里插入图片描述

十五、Gridsome案例 - Strapi介绍

在后台系统中添加文章信息,然后可以在网站中看到添加的文章信息。
这种方式比本地 markdown 文件管理数据的方式更为方便
但是这种方式也带来了新的学习成本

然而 Gridsome 本身是没有提供这种功能的,它关心的是如何把数据结合模板,预渲染成为静态网页。
至于你要如何去管理你的数据,就是你自己的事情了。

所以如果需要使用后台管理系统的话,至少需要学习掌握一门后端开发技术,比如 Java,PHP,Python,NodeJs 等
但是这样对于本项目来说就太麻烦了,所以这里推荐一个比较通用的 CMS 内容管理系统:strapi

什么是 Strapi
一个通用的 CMS 内容管理系统,通过它可以轻松的帮助我们实现内容的管理。
内容可以是任何东西,比如:博客文章,商品,评论,用户…

特点:自定义内容结果、简单内容管理、对开发者友好的API…

十六、Gridsome案例 - Strapi 基本使用

安装

npx create-strapi-app my-project --quickstart

–quickstart 是快速开始模式,它的数据存储是放在 SQLite 中的,适用于本地开发调试项目。
项目上线之后应该使用更稳定的数据库版本,比如 MySQL,NoSQL,MongoDB 等

启动

npm run develop 

启动之后会自动在浏览器中打开应用:或者手动打开 http://localhost:1337/admin

在这里插入图片描述

首次打开需要注册一个后台管理员账号,要求密码长度大于 8 位,并且要有大小写,字母开头

  • 添加 Collection
    在这里插入图片描述
    添加字段
    在这里插入图片描述
    保存
    在这里插入图片描述

十七、Gridsome案例 - 使用 strapi 接口数据

strapi 默认提供了符合 Restful api 接口规范的数据接口:Content API

修改用户权限
public:开放 查询,查询单个,以及数量 的权限
在这里插入图片描述

postman访问 Posts 接口:http://localhost:1337/posts
在这里插入图片描述

十八、Gridsome案例 - 访问受保护的 API

首先需要注册一个用户
登录用户
在这里插入图片描述
添加文章
在这里插入图片描述

十九、Gridsome案例 - 通过 GraphQL 访问 Strapi

Strapi 默认提供的是 Restful api 的方式访问数据,

安装 GraphQL

npm run strapi install graphql

安装完成之后重启应用,然后访问:http://localhost:1337/graphql

在这里插入图片描述
查询文章列表
在这里插入图片描述

二十、Gridsome案例 - 将 Strapi 数据预取到 Gridsome 应用中

参考:https://gridsome.org/plugins/@gridsome/source-strapi

在 Gridsome 应用渲染之前拿到 strapi 的数据,然后再渲染静态内容
这里就需要使用到 Gridsome 的插件 @gridsome/source-strapi 来处理了
它可以将 strapi 集成到 Gridsome 中,然后查询数据的使用

在blog-with-gridsome安装

npm install @gridsome/source-strapi

配置gridsome.config.js plugins下新增

  {
    use: '@gridsome/source-strapi',
    options: {
      apiURL: 'http://localhost:1337',
      contentTypes: ['post'], // 查询的数据类型
      queryLimit: 1000, // Defaults to 100
      // singleTypes: ['impressum'], // 单个节点
      // loginData: {
      //   identifier: 'xjb',
      //   password: '123456'
      // }
  } 

重启应用服务后,查看 GraphQL
打开第三个url localhost:8080/__explore
在这里插入图片描述
在这里插入图片描述
需要注意的是,在 strapi 中添加新的数据之后,需要重启 Gridsome 应用才能够查询到新增的数据。

二十一、Gridsome案例 - 设置文章和标签数据模型

打开strapi 删除之前的post
创建一个新的Content Type

保存

文章
id:文章id,Strapi 自动生成
title:文章标题,文本,必须的
content:文章内容,富文本,必须的
cover:文章封面,媒体(图片),必须的
is_publish:文章是否发布,布尔,默认为 false

标签
id:标签id,Strapi 自动生成
title:标签名称,文本
posts:标签的文章列表,引用类型,引用着的是文章

http://localhost:1337/graphql 中查看结果
在这里插入图片描述

二十二、Gridsome案例 - 展示文章列表

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

获取数据

<page-query>
query {
  posts: allStrapiPost {
    edges {
      node {
        id
        title
        created_at
        tags {
          id
          title
        }
      }
    }
  }
}
</page-query>

二十三、Gridsome案例 - 列表分页

参考: https://gridsome.org/docs/pagination/

列表分页

 <page-query>
  query ($page: Int) {
    posts: allStrapiPost (perPage: 1, page: $page) @paginate {
      edges {
        node {
          id
          title
          created_at
          tags {
            id
            title
          }
        }
      }
    }
  }
  </page-query>

perPage 表示每页的数据条数,page 表示当前是第几页。$page 可以从 url 中获取,比如第二页:http://localhost:8080/2

页码
Gridsome 提供了 Pager 组件

import { Pager } from 'gridsome'

  export default {
    name: 'IndexPage',
    metaInfo: {
      title: 'Hello, world!'
    },
    components: {
      Pager
    }
  }

Pager 它还需要分页信息

 <Pager :info="$page.posts.pageInfo" />
 
  <page-query>
  query ($page: Int) {
    posts: allStrapiPost (perPage: 1, page: $page) @paginate {
      pageInfo {
        totalPages
        currentPage
      }
      edges {
        node {
          id
          title
          created_at
          tags {
            id
            title
          }
        }
      }
    }
  }
  </page-query>

二十四、Gridsome案例 - 展示文章详情

首先要提供一个详情的模板页面,文章列表对应的是一个集合,而文章详情就对应集合下的一个节点。
所以我们应该提供一个模板,然后由 Gridsome 将数据和模板结合,渲染成一个静态的 HTML 页面

在 gridsome.config.js 中配置模板信息。

module.exports = {
    templates: {
      // 这个是集合的名字,不是由我们自己创建的
      // 而是由 @gridsome/source-strapi 创建的集合
      // 集合名称就是:typeName + contentTypes
      // StrapiPost
      StrapiPost: [
        {
          path: '/post/:id',
          component: './src/templates/Posts.vue' // 注:这里的 .vue 后缀名不能省略
        }
      ]
    }
  }

修改首页的链接,将其指向 Posts 详情页面
详情页面中获取文章详情

在这里插入图片描述

<page-query>
  query ($id: ID!) {
    post: strapiPost (id: $id) {
      id
      title
      content
      created_at
      cover {
        id
        url
      }
      tags {
        id
        title
      }
    }
  }
  </page-query>

二十五、Gridsome案例 - 处理Markdown格式的文章内容

在后台添加的文章内容是支持 markdown 格式的,但是在现有的项目应用中并没有对 markdown 格式的内容进行处理,导致页面会对 markdown 内容原样输出。
所以需要对 markdown 格式的内容进行处理。

使用 markdown-it 处理 markdown 格式的内容
markdown-it 可以将 markdown 内容转换成 HTML 格式

安装 markdown-it
npm install markdown-it --save
使用

<div v-html="mdToHtml($page.post.content)"></div>

<script>
  import MarkdownIt from 'markdown-it'
  const md = new MarkdownIt()

  export default {
    name: 'PostsPage',
    methods: {
      mdToHtml (markdown) {
        return md.render(markdown)
      }
    }
  }
</script>

二十六、Gridsome案例 - 文章标签

加载标签 tag gridsome.config.js
contentTypes: [‘post’, ‘tag’], // 查询的数据类型
标签模板 gridsome.config.js

 StrapiTag: [
    {
      path: '/post/:id',
      component: './src/templates/Tag.vue'
    }
  ]

template/tag.vue

<template>
    <div>
      <h1>Tag Page</h1>
    </div>
  </template>

  <script>
  export default {
    name: 'TagPage'
  }
  </script>
query
  <page-query>
  query ($id: ID!) {
    post: strapiPost (id: $id) {
      id
      title
      content
      created_at
      cover {
        id
        url
      }
      tags {
        id
        title
      }
    }
  }
  </page-query>

二十七、Gridsome案例 - 基本设置

创建Single Type

创建一个名为general的Single Type,有三个字段,如下:

配置general数据
在这里插入图片描述
gridsome.config.js的source-strapi插件增加singleTypes: [‘general’]配置,就可以获取general数据,重启项目就生效。

src/pages/Index.vue

<!-- Page Header -->
    <header
      class="masthead"
      :style="{
        backgroundImage: `url(http://localhost:1337${general.cover.url})`
      }"
    >
      <div class="overlay"></div>
      <div class="container">
        <div class="row">
          <div class="col-lg-8 col-md-10 mx-auto">
            <div class="site-heading">
              <h1>{{general.title}}</h1>
              <span class="subheading">{{general.subtitle}}</span>
            </div>
          </div>
        </div>
      </div>
    </header>


<page-query>
query ($page: Int) {
  # ----
 
  general: allStrapiGeneral {
    edges {
      node {
        title
        subtitle
        cover {
          url
        }
      }
    }
  }
}
</page-query>

<script>
import { Pager } from 'gridsome'
export default {
// ----
  
  computed: {
    general () {
      return this.$page.general.edges[0].node
    }
  }
};
</script>

二十八、Gridsome案例 - 联系我

创建一个contact的Content Type
在这里插入图片描述
使用Postman创建一条数据
在这里插入图片描述
页面中使用传统客户端表单提交功能
src/pages/Contact.vue

<input v-model="form.name" type="text" class="form-control" placeholder="Name" id="name" required data-validation-required-message="Please enter your name.">

<input v-model="form.email" type="email" class="form-control" placeholder="Email Address" id="email" required data-validation-required-message="Please enter your email address.">

<input v-model="form.phone" type="tel" class="form-control" placeholder="Phone Number" id="phone" required data-validation-required-message="Please enter your phone number.">

<textarea v-model="form.message" rows="5" class="form-control" placeholder="Message" id="message" required data-validation-required-message="Please enter a message."></textarea>

<button type="submit" class="btn btn-primary" id="sendMessageButton" @click.prevent="onSubmit">Send</button>



<script>
import axios from 'axios'
export default {
  name: 'ContactPage',
  data () {
    return {
      form: {
        name: '',
        email: '',
        phone: '',
        message: ''
      }
    }
  },
  methods: {
    async onSubmit () {
      try {
        const { data } = await axios({
          method: 'POST',
          url: 'http://localhost:1337/contacts',
          data: this.form
        })
        window.alert('发送成功')
      } catch (e) {
        alert('发送失败,请稍后重试')
      }
    }
  }
}
</script>


二十九、Gridsome案例 - 部署 Strapi

将应用部署到阿里云服务器上,服务器必须安装 MySQL 数据库,而且要高于 5.6 的版本(Strapi supports Mysql versions >= 5.6)
参考:https://strapi.io/documentation/developer-docs/latest/setup-deployment-guides/configurations.html#database

修改 config 中的 database 文件 打开 Strapi 项目的 config/database.js,项目默认使用的是 sqlite 作为存储数据库的,如果要将项目部署到线上的话,就不推荐使用 sqlite 了,而是使用 MongoDB、MySQL 等其他数据库,这里使用 MySQL。
strapi 文档中修改 database 文件

 module.exports = ({ env }) => ({
    defaultConnection: 'default',
    connections: {
      default: {
        connector: 'bookshelf',
        settings: {
          client: 'mysql',
          host: env('DATABASE_HOST', 'localhost'), // 数据库和项目同一台机器即可使用 localhost
          port: env.int('DATABASE_PORT', 3306),
          database: env('DATABASE_NAME', 'blog'),
          username: env('DATABASE_USERNAME', 'blog'),
          password: env('DATABASE_PASSWORD', '7czT6Pwai7KKGicM'),
        },
        options: {},
      },
    },
  });

git remote add origin 远端仓库地址
git push -u origin master
将项目 clone 到服务器上
ssh root@远程服务器ip地址
直接git clone …
cd blog-backend/
npm i
npm run build
npm run start

在这里插入图片描述
这个服务会占用命令行的应用,如果推出终端 则服务就停止了
持久存活在后端 则使用nodejs的工具 pm2
npm i -g pm2 做进程守护的
启动 pm2 start npm – run start --name blog-backend
在这里插入图片描述
访问到远程地址则部署成功 默认端口1337
后台就是/admin

推送到 gitee,然后再
服务器上 clone 下来,
安装依赖,打包构建,运行
配置 项目启动之后,访问 IP:1337,然后创建数据库,配置权限等(步骤与本地操作一致)

三十、Gridsome案例 - 将本地服务连接远程 Strapi

在gridsome.config.js
修改 apiURL
有时候我们的项目需要使用线上的接口,有可能需要使用本地的接口。
为了避免经常修改 apiURL,我们将根据环境变量来配置 apiRUL
Gridsome 中提供了配置环境变量的方法
参考:https://gridsome.org/docs/environment-variables/
如果是开发模式下,gridsome 会加载 .env.development 文件中的环境变量,
如果是生产模式下,会加载 .env.production 文件中的环境变量

  GRIDSOME_API_URL=http://<IP>:1337
  DB_USER=blog
  DB_PASS=7czT6Pwai7KKGicM 

如果变量以 GRIDSOME_ 开头,它既可以在服务端被访问到也能在客户端被访问到
比如:DB_USER 和 DB_PASS 是只有在服务端构建的时候才能拿到这两个环境变量

然后在配置文件中就可以直接使用 process.env.GRIDSOME_API_URL

修改图片路径 在本地开发时图片的 url 是写死的 localhost:1337 这里需要修改为 env 的环境变量中的 url
在 main.js 中,使用 Vue.mixin({}) 将 env 的环境变量保存到 data 属性中

  // main.js
  export default function (Vue, { router, head, isClient }) {
    Vue.mixin({
      data () {
        return {
          GRIDSOME_API_URL: process.env.GRIDSOME_API_URL
        }
      }
    })
    // Set default layout as a global component
    Vue.component('Layout', DefaultLayout)
  }

然后使用 GRIDSOME_API_URL 替换 localhost:1337

三十、Gridsome案例 - 部署 Gridsome 应用

将 Gridsome 部署到 vercel

使用 GitHub 登录到 vercel
导入项目(从 GitHub 仓库中导入,填写 GitHub 仓库地址【页面地址,不是 clone 的地址】)
在这里插入图片描述
创建远程仓库
在这里插入图片描述
推送本地代码到远程仓库
在这里插入图片描述

修改后台数据后 vercel 自动重新部署
在 vercel 中找到项目,进入项目设置,添加部署钩子
在这里插入图片描述
添加完成之后会生成一个链接

复制链接,然后进入 Strapi 后台,找到 设置 -> Webhooks,然后添加一个 webhooks
在这里插入图片描述
当修改后台修改完成之后,strapi 就会触发 webhooks,触发 vercel 的部署操作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值