背景

上次,我们已经完成了基本的静态网页向vuejs风格过渡,留下了一个疑问,如何结合CMS。这次我们来搞定它。

选型

我的初步想法是既然用了vuejs了,那就不应该再去修改什么CMS的模板了,vuejs就没意义了,而且修改模板会导致每次更新CMS的时候,会特别小心,怕遗漏了什么细节或者把我们自己修改的部分给弄掉了,哪怕是fork的,有Git保驾护航。所以,我倾向于选择具有Public API的CMS。

监于我对php的不熟悉,所以我排除了wordpress,虽然看起来他是最屌的。然后就是nodejs和java的,这两个我最熟,java的cms?不知为何,就是觉得java做cms有点不对路子,开发效率不如nodejs和php,还比较吃服务器资源,用XX云的朋友可能会增加成本,嗯,所以就剩下nodejs,nodejs里面比较酷的,我知道的应该就是hexo和ghost了,当然本博客就是ghost弄出来的,既然如此那就ghost好了,hexo不知道有没有public api,虽然也考虑到了有内容网站可以外部调用的平台,但是这样总感觉内容会被人家玩弄的样子,那么就这么定了,ghost。

环境

接下来就是安装ghost了,这一点也是顺带提一下,ghost现在安装、启动、升级、卸载非常简单,只需要安装,ghost-cli就可以了,具体的部分可以查看ghost的官方文档

ghost运行的环境有点意思,目前是v1.8.1,推荐最好是:

  • ubuntu 16.04。其实ubuntu 14也可以,但是有点问题的就是启动时会被卡住,实际上已经启动成功了。MAC环境当然也是可以,具体可见官方网站。
  • node v6,现在最新的是v8,v7不支持,v8在计划中,所以最好是v6,这个可以用npm -g install n。然后n v6.x.x来解决
  • 至少1GB的内存。这个要求基本没问题吧

其他我觉得都是可选的了,包括什么Nginx、Mysql什么的。

安装ghost上直接用

    npm install -g ghost-cli

即可,安装完成后就可以使用ghost命令了。

然后在一个你喜欢的目录执行命令

    ghost install --db=sqlite3

如果是测试,去掉--db就行了。过程中会询问一些问题,在问到是否配置nginx和systemd的时候,选择no,我的ubuntu 14.04环境会因为这两个选项出问题。完成之后,即可用

    ghost start

运行blog。

Ghost配置

ghost运行起来之后,在//:2368进行访问,可以看到一个默认的博客首页,我们不管它,因为它是hbs的模板文件,我们现在要用vuejs的前端来自己显示blog的内容。

接下来,我们进入在//:2368/ghost界面,设置完成之后,进入首页,blog的一些相关设定请自行解决,接下来,我们可以看到左侧菜单的Labs菜单页面中有一条Public API选项,不出意外,已经默认勾上了,这表示我们可以通过public API来进行调用,接下来我们得了解一下API有哪些,我们会用到哪些。

middle

API

所有的API都可以在官方API中能够找到,我们可以看到右侧已经罗列当前(v1.8.1)所有的API

  • get /posts/
  • get /posts/:id
  • get /posts/slug/:slug
  • get /tags/
  • get /tags/:id
  • get /tags/slug/:slug
  • get /users/
  • get /users/:id
  • get /users/slug/:slug
  • get /users/email/:email

其他的不多说,我们暂时只要posts和posts/:id这两个。具体的调用方法可以查询文档解决,简单的使用会在下面vuejs部分描述。

跨域和验证

好了,如果你用vuejs写的主站是:www.yoursite.com。然后ghost也是配置成:www.yoursite.com。那么这个跨域就没问题了。但是也有可能是这样的情况,主站是除了内容还有其他的介绍,我们用vuejs做得很炫酷,只是想用ghost的内容来充盈一下(例如首页放置最新的四个博文内容),同时也保留Blog独立的页面,那么可能的站点目录就是

  • www.yoursite.com 是主站
  • blog.yoursite.com 是ghost的主页
  • blog.yoursite.com/ghost 是ghost的管理页面

这样。所以这个时候就有跨域问题了,我们需要设置跨域和验证机制。

还记得我们安装的数据库是sqlite3吗,我们需要进入到数据库中去添加记录。执行命令

    sqlite3 content/data/ghost.db

接着,我们要修改一下记录,我们执行相关命令

    select * from clients;

能够查询到所有的clients,我们只需要字段name为ghost-frontend这个client。顺便解释一下clients的结构,因为后面会用到。

    id|uuid|name|slug|secret|type|description|created_at|created_by|updated_at|updated_by|

这些字段当中,对我们有用的是id,secret,后面插入记录和验证时会用到。

接下来我们要向表client_trusted_domains插入一条记录来允许我们的主站能够访问


insert into client_trusted_domains (uuid, client_id, trusted_domain) values ('b2fabe77-686e-4e79-a055-cdb10740c77f', '599710cf4e9babcd12345678', 'http://www.yoursite.com');

第一个uuid可以在uuid-generator去获得一个,当然,任何其他方式都可以。第二个client_id就是我们刚刚去查询的id,最后一个是www.yoursite.com,即是你的官网域名。

设置完成后接下来就是验证了,所幸的是ghost有一个js的sdk,我们可以很方便的进行访问和验证设置,这时候我们就需要client_id和secret了。因为是Public API,所以你也就不用担心secret写在页面上的问题了。


ghost.init({
  clientId: "ghost-frontend",
  clientSecret: "<letters-and-numbers>"
 });
 

好了,完事具备,接下来是前端了。

Vuejs设置

首先是在index.html中,加入ghost的js-sdk,并初始化调用账号。

  <script type="text/javascript" src="//blog.yoursite.com/public/ghost-sdk.min.js"></script>
  <script type="text/javascript">
    ghost.init({
      clientId: "ghost-frontend",
      clientSecret: "0b1234567890"
    })
  </script>

这样,我们已经具备了调用ghost的Public API的能力。下面我们在首页上放入列表,和点击进入内容页。

列表页

列表页,我们先在script部分写入调用代码,

export default {
  name: 'home',
  data: function() {
    return {
      posts: []
    }
  },
  mounted: function() {
    var self = this
    $.get(ghost.url.api('posts', { fields: 'title,feature_image,id,meta_description,published_at', limit: '4' })
    ).done(function(data) {
      self.posts = data.posts
    })
  }

可以看到,我们需要的内容主要是这5个,title,feature_image,id,meta_description,published_at,那么post具体有哪些参数呢,这个可以在api文件中找到,不再赘述,需要注意的一点就是meta_description是在ghost发布blog的meta中添加,没有添加的话是不会返回的哟,当然你也可以直接返回所有的内容,然后提取前面N个字符为description。limit参数是指取多少个,不写的话会有一个默认值,要做分页的话,可以在调用的地方加上分页参数

...
  data() {
    return {
      posts: [],
      page: 1,
      pagination: {}
    }
  },
...
$.get(ghost.url.api('posts'), { page: self.page++, limit: 6 }).done
...

然后是把每个博文条目显示出来

    <div class="row">
        <div class="col-sm-3" :key="post.id" v-for="post in posts">
            <div class="blog">
                <div class="img">
                  <img v-lazy="post.feature_image">
                </div>
                <div class="headline">
                  <router-link :to="{name:'NewsContent',params:{id:post.id}}">{{ post.title }}</router-link>
                </div>
                <div class="summary">
                  {{ post.meta_description }}
                </div>
                <div class="date">
                  {{ post.published_at | formatDate }}
                </div>
            </div>
        </div>
    </div>

这里的v-lazy是一个延迟加载图片的vue组件。其他的都是简单的取值和vuejs的for循环,写过hbs、jade、ejs、dust等等模板的,都会很快理解。

middle

列表效果看起来还不错

内容页

内容页和列表页基本相同,在单个blog的调用方面,获取的属性更全面,当然也可以使用fields来过滤,同时可以使用include来包含其他的需要取的值


    fetchPost() {
      var self = this
      $.get(ghost.url.api('posts/' + self.$route.params.id), { fields: 'title,html,updated_at', include: 'author, tags, next, previous' })
        .done(function (data) {
          self.$set(self, 'post', data.posts[0])
        })
    }

可以看到,调用的接口变成了posts/:id,但是在取得内容的时候,注意是一个数组,我们取[0]第一个。self.$set表示直接设置值,这个是vuejs的一种写法。

展示内容会多说一点,本来我们取得的内容,如果是其他的CMS的话,可能直接是HTML的片段,加上一点class名称,这样就不得不自己重新定义全套class来显示内容,但是因为我们使用的ghost的内容是markdown来写成的,那这样就省事了,我们只需要引用一个能够显示markdown内容的css即可,这里我们选择了github的样式。

首先引入css组件

npm install --save github-markdown-css

然后将组件应用到vuejs

<script>
require('github-markdown-css')
</script>

然后是展示的部分代码

    <div class="container news">
      <div class="news-head">
        <h1>{{ post.title }}</h1>
        <div class="news-date">{{ post.published_at | formatDate }}</div>
      </div>
      <hr>
      <div class="news-body markdown-body" v-html="post.html">
      </div>
      <div class="news-nav">
        <div class="news-nav-link" v-if="post.previous">
          <router-link :to="{name:'NewsContent',params:{id:post.previous.id}}" push>前一篇:{{ post.previous.title }}</router-link>
        </div>
        <div class="news-nav-link" v-if="post.next">
          <router-link :to="{name:'NewsContent',params:{id:post.next.id}}" push>后一篇:{{ post.next.title }}</router-link>
        </div>
      </div>
    </div>

代码中的markdown-body即是我们刚刚引入的markdown的css部分。打完收工,不出意外的话,可以看到很漂亮的内容了。如下图。

middle

小注意

如果是不同的domain会出现这样的情况,因为返回的image是/content/image/xxxx.(png|jpg),所以直接用{post.feature_image}会导致调用当前domain的图片,例如:(http://www.yoursite.com/content/image/1.jpg)的情况。

我们有两种处理方法,

  • 直接在前面加上当前domain,不管是在html里面或者是在js中,返利如下:
'http://www.yoursite.com/' + {post.feature_image}
  • 在nginx的site的配置中增加一个转发,这样所有的图片都更换domain为正确的domain,范例如下:
        location ^~ /content/images/ {
                proxy_pass http://127.0.0.1:2368;
        }

下一步

现在cms已经引入了,接下里干点啥呢,嗯,有空的话,研究研究vuejs的组件吧。