gridsome demo

What

Gridsome is a free and open source Vue.js-powered framework for building websites & apps that are fast by default 🚀.

译:Gridsome 是一个由 Vue.js 提供的免费且开源的框架,用于建立默认情况下快速运行的网站和应用

Why

至于为什么要用geidsome,官网中给的9个理由,大家可以去看一看

What makes Gridsome sites fast?

  • 构建时预先渲染了HTML生成了静态文件
  • 自动代码拆分,每页仅加载需要的内容
  • 遵循PRPL模式进行即时页面加载
  • 使用Intersection Observer加载下一页
  • 具有自动图像压缩和延迟加载的渐进图像
  • Vue.js SPA可快速浏览而无需刷新页面

Install & Create

Install Gridsome CLI tool

  • 使用 yarn 安装

    $ yarn global add @gridsome/cli
    
  • 使用 npm 安装

    $ npm install --global @gridsome/cli
    

Create a gridsome project

$ gridsome create my-gridsome-site
$ cd my-gridsome-site
$ gridsome develop 

完成上面的三步之后,本地的开发服务器应该运行在http://localhost:8080

目录结构

├── package.json ( 包含有关项目中安装了哪些插件的信息)
├── gridsome.config.js (包含已安装插件的配置和选项)
├── gridsome.server.js (文件可选。用于挂接到Gridsome服务器的各个部分。该文件必须导出可以访问API的函数。
├── static/ (在构建过程中,此目录中的文件将直接复制到dist
└── src/
    ├── main.js(在该文件中可导入全局样式和脚本,可访问客户端API的导出功能,是安装Vue插件,注册组件和指令的地方)
    ├── index.html (有时需要覆盖Gridsome用于生成页面的基本HTML模板。 Gridsome使这变得非常容易。 要做的就是在src目录中创建一个新的index.html文件)
    ├── App.vue (App.vue文件是包装所有页面和模板的主要组件。 可以在src目录中通过拥有自己的App.vue文件来覆盖默认文件。 如果希望所有页面都共享一个布局,则覆盖默认值很有用)
    ├── layouts/ (如果要共享页面或模板的一个或多个布局,请在此目录中创建组件)
    │   └── Default.vue
    ├── pages/ (该目录中的所有组件均成为您网站的页面。 每个页面将根据其.vue文件的位置获取其路径。 src / pages / Index.vue将成为您网站的主页,而src / pages / AboutUs.vue将是example.com/about-us)
    │   ├── Index.vue
    │   └── Blog.vue
    └── templates/ (如果要将外部数据源(例如来自WordPress博客的帖子)导入到项目中,则每个 posts 都会在此目录中寻找其模板的组件。 组件文件的名称必须与GraphQL模式中的节点类型匹配)
        └── BlogPost.vue

⚠️在Gridsome中,可以使用别名〜或@链接到/ src文件夹中的文件。 例如,可以使用’〜/ components / Card’中的import Card来导入Vue组件

推荐

  • Assets

    全局样式,图像,字体和图标通常添加到src / assets目录中

  • Shared or global components

    要在多个页面或模板中使用的组件可以存储在src / components目录中

  • Data files

    要导入到组件中的.json或.yaml之类的数据文件可以存储在src / data目录中

项目配置

Gridsome 的项目配置在 gridsome.config.js 这个文件中,基本的文件配置如下👇

module.exports = {
  siteName: 'Gridsome',
  siteDescription: 'A simple, hackable & minimalistic starter for Gridsome that uses Markdown for content.',
  plugins: []
}
  • siteName

    为项目设置一个名称。 该名称通常在标题标签中使用

  • siteDescription

    首页的描述

  • plugins

    通过将插件添加到plugins数组来激活插件

    module.exports = {
      plugins: [
        {
          use: '@gridsome/source-filesystem',
          options: {
            path: 'blog/**/*.md',
            route: '/blog/:year/:month/:day/:slug',
            typeName: 'Post'
          }
        }
      ]
    }
    

除此之外,还可以配置一些其他的,比如:port,metadata,icon,css.loaderOptions 等等

核心概念

Pages

页面负责在URL上显示你的数据。 每个页面将静态生成,并具有自己的带有标记的index.html文件

在Gridsome中创建页面有两个选择👇

基于文件的页面

src / pages 目录中的单个文件组件将自动具有其自身的URL。 文件位置用于生成URL 👇

文件位置URL
src/pages/Index.vue/
src/pages/AboutUs.vue/about-us
src/pages/about/Vision.vue/about/vision
src/pages/blog/Index.vue/blog
基于程序的页面

可以使用 gridsome.server.js 中的 createPages 钩子以编程方式创建页面。这个对从外部API手动创建页面而不使用GraphQL数据层非常有用

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

gridsome使用vue-meta处理有关页面的元信息

<template>
  <div>
    <h1>Hello, world!</h1>
  </div>
</template>

<script>
export default {
  metaInfo: {
    title: 'Hi,Doris',
    meta: [
      { name: 'author', content: 'John Doe' }
    ]
  }
}
</script>

上面title的内容就显示在这里👇

页面的动态路由

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

// gridsome.server.js
module.exports = function (api) {
  api.createPages(({ createPage }) => {
    createPage({
      path: '/user/:id(\\d+)',
      component: './src/templates/MyPage.vue'
    })
  })
}

核心概念

Connections

connection 是一组节点,每个节点包含带有自定义数据的字段。connection 对在网站上建立博客文章,标签,产品等很有用

connection有两种添加方式:
  • source plugins

    这种一般比较常用,比如说下面的这个就是比较常见的👇

    // gridsome.config.js
    plugins: [{
    use: '@gridsome/source-filesystem',
    options: {
      typeName: 'Post',
      path: 'content/posts/*.md',
      refs: {
        tags: {
          typeName: 'Tag',
          create: true
        }
      }
    }
    }],
    
  • Data Store API

    这种一般是在 gridsome.server.js(挂载到gridsome服务器的各部分) 这个文件中配置的,下面这个操作是创建了一个名为Post的集合,该集合从API中获取内容,并将结果作为节点添加到该集合中

    // gridsome.server.js
    const axios = require('axios')
    
    module.exports = function (api) {
      api.loadSource(async actions => {
        const collection = actions.addCollection('Post')
    
        const { data } = await axios.get('https://api.example.com/posts')
    
        for (const item of data) {
          collection.addNode({
            id: item.id,
            title: item.title,
            content: item.content
          })
        }
      })
    }
    

我这里是通过第一种方法添加的 connection,配置完成运行项目后可以在http://localhost:8080/___explore 的 schema 中看到GraphQL 模式出现了两个和 post 相关根字段( 字段名称是根据集合名称自动生成的。 之前的集合命名为Post),这些根字段用于检索页面中的节点

其中:

  • post 可以通过id获取单个节点
  • allPost 可以获取节点列表(可以排序或filter)

说明:模式中的每个 connection 类型都将具有根据启动时发现的数据而自动生成的字段的功能,这对于简单项目比较有用,但对于较复杂的项目,当数据更改时还是通过 Schema API 来定义自己的schema较好

Templates

templates 用于为 connection 中的节点创建单个页面。 节点需要相应的页面才能显示在其自己的URL上

templates 的建立方式:
// gridsome.config.js
module.exports = {
  templates: {
    Post: [
      {
      	name: 'post',
        path: '/blog/:year/:month/:title',
        // 指定自定义组件路径
        component: './src/other/location/Post.vue'
      }
    ]
  }
}

说明:

  • name
    为模板指定名称,以获取GraphQL中的路径

  • path
    定义动态路由,并使用任何节点字段作为参数。

  • compoment
    指定要用作每个页面模板的组件

Add data to a template

通过模板配置生成的页面将在 page-query 中将节点ID用作查询变量。 使用$ id变量获取当前页面的节点 👇

<template>
  <Layout>
    <div class="post-title">
      <h1 class="post-title__text">
        {{$page.post.title}}
      </h1>
    </div>

    <div class="post content-box">
      <div class="post__header">
        <g-image alt="Cover image" v-if="$page.post.cover_image" :src="$page.post.cover_image" />
      </div>
    </div>
  </Layout>
</template>
<page-query>
query Post ($id: ID!) {
  post: post (id: $id) {
    title
    path
    description
    cover_image (width: 860, blur: 10)
  }
}
</page-query>

Layouts

Layout 组件用于包装页面。 Layout 应包含将在整个网站中使用的组件,例如页眉,页脚或侧边栏。Layout 是位于 src / layouts 中的 Default.vue组件,需要声明为全局组件或每页导入才能使用

每个布局都需要一个 组件。 这是将插入来自页面和模板的内容的位置。 布局可以具有多个插槽

<!-- Layout -->
<template>
  <div>
    <header />
    <slot /> <!-- Page content will be inserted here  -->
    <footer />
  </div>
</template>
Import layouts

每页引入布局👇

<!-- Page -->
<template>
  <Layout>
    Add page content here
  </Layout>
</template>

<script>
import Layout from '~/layouts/Default.vue'

export default {
  components: {
    Layout
  }
}
</script>

布局全局化

直接在全局引入布局👇

// src/main.js
import Layout from '~/layouts/Default.vue'

export default function (Vue, { head, router, isServer }) {
  Vue.component('Layout', Layout)
}

全局引入之后就可以在你需要的任何地方通过 来直接使用了 👇

<!-- Page -->
<template>
  <Layout>
    Add page content here
  </Layout>
</template>

布局就像组件一样,这时候也可以给布局传递 props ,下面的这个例子应该不难理解👇

<!-- Page -->
<template>
  <Layout :sidebar="true">
    Add page content here
  </Layout>
</template>
<!-- Layout -->
<template>
  <div>
    <div class="main-content">
      <slot />
    </div>
    <div v-if="sidebar">
      Lets show the sidebar!
    </div>
  </div>
</template>

<script>
export default {
  props: ['sidebar']
}
</script>

Components

这里的 components 给我的感觉其实和 Vue中的 components 是差不多的 👇

components/partOne.vue
<template>
  <div class="partOne">
    <g-image class="partOne__image" src="@/assets/images/0306_2.jpeg"></g-image>

    <h1 class="partOne__site-title">{{ $static.metadata.siteName }}</h1>

    <p class="partOne__intro">Welcome to my blog~</p>

    <p class="partOne__links">
      <a href="https://gridsome.org/">Gridsome</a>
    </p>
  </div>
</template>

<static-query>
query {
  metadata {
    siteName
  }
}
</static-query>
<template>
  <Layout>
    <part-one></part-one>
  </Layout>
</template>

<script>
import partOne from "@/components/partOne.vue";
export default {
  components: {
    partOne
  },
  metaInfo: {
    title: "Home"
  }
};
</script>

和上面的例子展示的一样,每个 components 都可以具有一个带有 GraphQL查询的 块,以从数据源获取数据。 结果将存储在组件内部的 $static 属性中

环境搭建完成后的目录

├── package.json
├── gridsome.config.js 
├── gridsome.server.js 
├── static/ 
└── src/
    ├── main.js
    ├── index.html 
    ├── App.vue 
    ├── layouts/ 
    │   └── Default.vue
    ├── pages/ 
    └── templates/ 

markdown

首先来看一下 markdown 文件的大致内容

---
title: Tom & Jerry
cover_image: ./images/0309_2.jpeg
published: true
tags: ['Mark','Test']
canonical_url: false
description: "Tom of Tom and Jerry fame must be one of the all time favorite cartoon cats."
---

### The Story Of Tom & Jerry
Tom of Tom and Jerry fame must be one of the all time favorite cartoon cats.
This cat and mouse cartoon series kicked of on February 20,1940 with the short "Puss Gets the Boot".Distributed by MGM,directed by Bill Hanna and Joe Barbera and produced by Rudolf Ising,this Academy Award nominee ran for just over 9 mins.Apart from the cat being referred to as "Jasper" and not Tom,and the mouse not having a name,this first adventure pretty much established the Tom and Jerry format.

Cat views mouse as a tasty snack.Cat chases mouse but is usually outwitted.Plenty of violence,mayhem and destruction.Lots of visual gags,little dialogue.This basically is the formula for every successful cat and mouse animated cartoon,and no other feline and rodent pair were better performers of the formula than Tom and Jerry.
The first series of Tom and Jerry pictures,directed by Hanna and Barbera for MGM,1940 -1957,were absolute masterpieces of animation.Beautifully drawn,very fast paced action all the way and lovable,likable characters.

code

  • layouts/Default.vue

主要展示页面分为header、content 和 footer 三个部分

```javascript
<template>
  <div>
    <header-content class="header"></header-content>
    <main class="main">
      <slot />
    </main>
    <footer-content class="footer"></footer-content>
  </div>
</template>

<script>
import headerContent from "@/components/headerContent.vue";
import footerContent from "@/components/footerContent.vue";
export default {
  components: {
    headerContent,
    footerContent
  }
};
</script>

<style lang="scss" scoped>
.header {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: var(--header-height);
  padding: 0 calc(var(--space) / 2);
  top: 0;
  z-index: 10;
  background: lightsteelblue;

  @media screen and (min-width: 1300px) {
    position: sticky;
    width: 100%;
  }
}

.main {
  margin: 0 auto;
  padding: 1.5vw 15px 0;
  background: lightslategray;
}
.footer {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(var(--space) / 2);
  text-align: center;
  font-size: 1em;
  background: lightsteelblue;

  > span {
    margin: 0 0.35em;
  }

  a {
    color: currentColor;
  }
}
</style>
```
  • main.js 中引入布局

    import '~/assets/style/index.scss'
    
    import DefaultLayout from '~/layouts/Default.vue'
    
    export default function (Vue, { router, head, isClient }) {
      Vue.component('Layout', DefaultLayout)
    }
    
  • pages/index.vue

    这个页面是home页,主要分为两个部分,分别引入后query,传值给partTwo

    <template>
      <Layout>
        <part-one></part-one>
        <div class="posts">
          <part-two v-for="edge in $page.posts.edges" :key="edge.node.id" :post="edge.node"></part-two>
        </div>
      </Layout>
    </template>
    
    <script>
    import partOne from "@/components/partOne.vue";
    import partTwo from "@/components/partTwo.vue";
    export default {
      components: {
        partOne,
        partTwo
      },
      metaInfo: {
        title: "Home"
      }
    };
    </script>
    
    <page-query>
    query {
      posts: allPost(filter: { published: { eq: true }}) {
        edges {
          node {
            id
            title
            cover_image (width: 770, height: 380, blur: 10)
            description
            content
            path
          }
        }
      }
    }
    </page-query>
    
    <style lang="scss" scoped>
    </style>
    
  • components/partOne.vue

    <template>
      <div class="partOne">
        <g-image class="partOne__image" src="@/assets/images/0306_2.jpeg"></g-image>
    
        <h1 class="partOne__site-title">{{ $static.metadata.siteName }}</h1>
    
        <p class="partOne__intro">Welcome to my blog~</p>
    
        <p class="partOne__links">
          <a href="https://gridsome.org/">Gridsome</a>
        </p>
      </div>
    </template>
    
    <static-query>
    query {
      metadata {
        siteName
      }
    }
    </static-query>
    
    <script>
    export default {};
    </script>
    
    <style lang="scss" scoped>
    .partOne {
      margin: 0 auto;
      max-width: 500px;
      text-align: center;
      padding: calc(var(--space) / 2) 0;
      // background: peachpuff;
    
      &__image {
        border-radius: 100%;
        width: 100px;
        height: 100px;
        margin-bottom: 1em;
      }
    
      &__intro {
        opacity: 0.8;
      }
    
      &__site-title {
        font-size: 1.5em;
      }
    
      &__links {
        margin-top: -0.5em;
        a {
          margin: 0 0.5em;
        }
      }
    }
    </style>
    
  • components/partTwo.vue

    <template>
      <div class="partTwo content-box">
        <div class="partTwo__header">
          <g-image class="partTwo__image" v-if="post.cover_image" :src="post.cover_image"></g-image>
        </div>
    
        <div class="partTwo__content">
          <h2 class="partTwo__title">{{post.title}}</h2>
          <p>{{post.description}}</p>
        </div>
    
        <g-link class="partTwo__link" :to="post.path">Link</g-link>
      </div>
    </template>
    
    <script>
    
    export default {
      components: {
        postTags
      },
      props: ["post"],
    };
    </script>
    
    <style lang="scss" scoped>
    .partTwo {
      margin-bottom: var(--space);
      position: relative;
    
      &__header {
        margin-left: calc(var(--space) * -1);
        margin-right: calc(var(--space) * -1);
        margin-bottom: calc(var(--space) / 2);
        margin-top: calc(var(--space) * -1);
        overflow: hidden;
        border-radius: var(--radius) var(--radius) 0 0;
    
        &:empty {
          display: none;
        }
      }
    
      &__image {
        min-width: 100%;
      }
    
      &__title {
        text-align: center;
        margin-top: 0;
      }
    
      &:hover {
        transform: translateY(-5px);
        box-shadow: 1px 10px 30px 0 rgba(0,0,0,.1);
      }
    
      &__link {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        opacity: 0.0;
        overflow: hidden;
        text-indent: -9999px;
        z-index: 0;
      }
    }
    </style>
    
  • templates/Post.vue

    <template>
      <Layout>
        <div class="post-title">
          <h1 class="post-title__text">
            {{$page.post.title}}
          </h1>
        </div>
    
        <div class="post content-box">
          <div class="post__header">
            <g-image alt="Cover image" v-if="$page.post.cover_image" :src="$page.post.cover_image" />
          </div>
    
          <div class="post__content" v-html="$page.post.content" />
    
        </div>
      </Layout>
    </template>
    <page-query>
    query Post ($id: ID!) {
      post: post (id: $id) {
        title
        path
        description
        content
        cover_image (width: 860, blur: 10)
      }
    }
    </page-query>
    <script>
    export default {
      props: ["post"]
    };
    </script>
    
    <style lang="scss" scoped>
    .post-title {
      padding: calc(var(--space) / 2) 0 calc(var(--space) / 2);
      text-align: center;
    }
    
    .post {
    
      &__header {
        width: calc(100% + var(--space) * 2);
        margin-left: calc(var(--space) * -1);
        margin-top: calc(var(--space) * -1);
        margin-bottom: calc(var(--space) / 2);
        overflow: hidden;
        border-radius: var(--radius) var(--radius) 0 0;
    
        img {
          width: 100%;
        }
    
        &:empty {
          display: none;
        }
      }
    
      &__content {
        h2:first-child {
          margin-top: 0;
        }
    
        p:first-of-type {
          font-size: 1.2em;
          color: var(--title-color);
        }
    
        img {
          width: calc(100% + var(--space) * 2);
          margin-left: calc(var(--space) * -1);
          display: block;
          max-width: none;
        }
      }
    }
    </style>
    
  • gridsome.config.js

记着要在这个文件中进行配置

```javascript
module.exports = {
  siteName: 'Doris Gridsome Blog',
  siteDescription: 'A gridsome project test.',

  templates: {
    Post: '/:title'
  },

  plugins: [{
    use: '@gridsome/source-filesystem',
    options: {
      typeName: 'Post',
      path: 'content/posts/*.md',
    }
  }]
}
```

最后看一下成果图👇

优化

上次分享的Demo是直接搭了一个架子,这次来优化一下~

主要是给之前的Demo添加了一个可以点击跳转到相应地址的Tag,还有就是用了一下之前公司官网的UI框架 👉 Buefy

Tags

  • 这里还是先在 gridsome.config.js 文件中添加配置,这里的配置主要是在 template 中添加 Tag 对应的路径,还有就是创建一个tags 的 GraphQL 集合并引用

    module.exports = {
      siteName: 'Home',
      siteDescription: 'A simple, hackable & minimalistic starter for Gridsome that uses Markdown for content.',
    
      templates: {
        Post: '/:title',
        Tag: '/tag/:id'
      },
    
      plugins: [
        {
          use: '@gridsome/source-filesystem',
          options: {
            typeName: 'Post',
            path: 'content/posts/*.md',
            refs: {
              tags: {
                typeName: 'Tag',
                create: true
              }
            }
          }
        }
      ]
    }
    
  • 当然还要在 Markdown 文件中添加相应的 tags 👇

    tags: ['Markdown', 'Releases']
    

    基本配置完毕之后就是要在页面中展示并跳转了…

    这里想要让 tag 展示在下图位置

  • 首先要在 components 中建立一个 PostTags.vue 的文件,这里要展示的数据也是从上个组件中传递过来的。可以看到这里展示的 tag.title 是在 g-link 中包着的,说明是需要跳转的,而跳转的路径就是 post 传参过来 tag.path 了

    <template>
       <div class="post-tags">
       		<g-link class="post-tags__link" v-for="tag in post.tags" :key="tag.id" :to="tag.path">
       			<span>#</span> {{ tag.title }}
       		</g-link>
        </div>
    </template>
    
    <script>
    export default {
      props: ['post']
    }
    </script>
    
    <style lang="scss">
    .post-tags {
      margin: 1em 0 0;
    
      &__link {
      	margin-right: .7em;
      	font-size: .8em;
      	color: currentColor;
      	text-decoration: none;
      	background-color: var(--bg-color);
      	color: currentColor!important; //Todo: remove important;
      	padding: .5em;
      	border-radius: var(--radius);
      }
    }
    </style>
    
  • 然后在 components/partTwo.vue 中去引用 PostTags.vue 并展示到相应位置

    <template>
      <div class="post-card content-box" :class="{'post-card--has-poster' : post.poster}">
        <!-- card img -->
        <div class="post-card__header">
          <g-image alt="Cover image" v-if="post.cover_image" class="post-card__image" :src="post.cover_image" />
        </div>
        <!-- card content -->
        <div class="post-card__content">
          <!-- card title   a post with a cover img.. -->
          <h2 class="post-card__title" v-html="post.title" />
          <!-- markdown is imtended... -->
          <p class="post-card__description" v-html="post.description" />
    
          <!-- #Markdown #cover img -->
          <PostTags class="post-card__tags" :post="post" />
          <!-- 给card加的点击跳转链接 -->
          <g-link class="post-card__link" :to="post.path">Link</g-link>
        </div>
      </div>
    </template>
    
  • 接下来是 template/Tag.vue ,它的作用就是跳转之后的页面展示,这里可以看到数据是直接从 Tag 中通过 title 关联到 Post 来取的,和之前的有所不同。拿到数据之后就直接传给 partTwo 这个组件,这里 partTwo.vue 的引入和声明就不多说了… 数据的渲染格式和上次分享的是一样的…

    <template>
      <Layout>
        <h1 class="tag-title text-center space-bottom">
          # {{ $page.tag.title }}
        </h1>
        <div class="posts">
          <part-two v-for="edge in $page.tag.belongsTo.edges" :key="edge.node.id" :post="edge.node"/>
        </div>
      </Layout>
    </template>
    
    <page-query>
    query Tag ($id: ID!) {
      tag (id: $id) {
        title
        belongsTo {
          edges {
            node {
              ...on Post {
                title
                path
                timeToRead
                description
                content
                cover_image
              }
            }
          }
        }
      }
    }
    </page-query>
    

这块的效果就是点击 #markdown 的 tag 后跳转到对应的tag列表页,点击具体的 tag 后跳转到对应的 tag页(因为上传文件最大是二十多M,所以这快展示不了动态效果,就简单说一下…)

Buefy

  • install

    npm install buefy
    
  • 配置

    // 看这里
    import 'buefy/dist/buefy.css'
    
    export default function (Vue, {
      router,
      head,
      isClient
    }) {
      Vue.component('Layout', DefaultLayout)
      // 看这里
      Vue.use(Buefy)
    }
    
  • 使用

    我这里是直接在 headerContent 中加了一个 Navbar,然后主要是给其中的 dropdown 连接跳转了 Post 的 path

    <template>
      <b-navbar>
        <template slot="brand">
          <b-navbar-item tag="router-link" :to="{ path: '/' }">
            <img src="../assets/images/belstar-logo.png">
          </b-navbar-item>
        </template>
        <template slot="start">
          <b-navbar-item href="/">
            Home
          </b-navbar-item>
          <b-navbar-item href="https://gridsome.org/">
            Documentation
          </b-navbar-item>
          <b-navbar-dropdown label="Blog">
            <b-navbar-item :href="edge.node.path" v-for="edge in $page.posts.edges" :key="edge.node.id">
              {{edge.node.title}}
            </b-navbar-item>
          </b-navbar-dropdown>
        </template>
      </b-navbar>
    </template>
    
    
    <page-query>
    query {
      posts: allPost(filter: { published: { eq: true }}) {
        edges {
          node {
            id
            title
            description
            cover_image (width: 770, height: 380, blur: 10)
            path
            tags {
              id
              title
              path
            }
          }
        }
      }
    }
    </page-query>
    

在这里点击跳转的话也是直接跳转到对应的 Post

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值