cherryjin

如何创建一个 Vue.js Photo Gallery – Hacker Noon

cherryjin · 2017-08-16翻译 · 592阅读 原文链接

如何创建一个 Vue.js Photo Gallery

在这篇教程中,我将向你展示如何使用 HTML5UP中的即用模板来创建一个照片库, 由 Cosmic JS App服务器上的 Cosmic JS API提供支持。

TL;DR

查看 demo 在GitHub上查看代码库

先决条件

你需要使用node JS 和npm.在你开始之前请确定你已经安装了它们。

开始

首先我们需要安装 VueJS CLI 并且创建一个新项目。运行一下命令来执行此操作:

npm install -g vue-cli
vue init webpack vuejs-photo-gallery
cd vuejs-photo-gallery
npm install

在你设置此项目后,你将能够运行

cd vuejs-photo-gallery
npm run dev

在浏览器中运行你的App

使用现有的git repo Doing everything

首先, 你必须确保你已经安装了 node > 6.x, 接下来运行以下命令:

npm install -g vue-cli
git clone https://github.com/cosmicjs/vuejs-photo-gallery.git
cd vuejs-photo-gallery
npm install
npm run dev

当你运行了最后一行命令后,浏览器窗口将自动打开。

安装 Cosmic JS 库

首先, 安装 Cosmic JS Angular/JavaScript 库

`npm install cosmicjs --save`

现在你应该可以导入Cosmic对象并且可以调用Cosmic JS API,就像下面这样:

import Cosmic from 'cosmicjs';
const bucket = { slug: 'your-bucket-slug' };
Cosmic.getObjects({ bucket }, (err, res) => {
  console.log(res.objects);
});

Setting up things with Cosmic JS

创建 bucket 并且记住这个 bucket的名字 (在我们的项目中是vuejs-photo-gallery ):

然后创建一个名为Photo的新对象类型 。

我们还需要一种存储图片的方法。请输入“Metafields Template” 并且添加带有image键的 的“Image/File”类型的metafield。这个metafield会存储图像。我们不需要任何东西,只需要设置名称并保存对象类型。保存后,你将会被重定向到‘New Photo’ 页面。 使用此页面创建一些照片并保存 - 我们将其用作测试数据。

唯一剩下的就是设置整个网站的事情, 像标题, 标语, 社交图标以及页脚文字。让我们再创建一个名为Global的对象。并且添加以下metafields:

  • Tagline — 纯文本区

  • Twitter — 纯文本输入

  • Instagram — 纯文本输入

  • Github — 纯文本输入

  • Email — 纯文本输入

  • Footer — 纯文本区

VueJS 环境

我们想在部署时自动选择我们的 bucket名称 。在这种情况下,我们需要配置文件,在部署期间填充正确的数据。创建 src/config.js 文件并填充以下代码:

Config = {
    bucket: 'vuejs-photo-gallery'
};
`module.exports = Config;`

准备资源

下载压缩版的模板并解压。在我们的例子中我们会有以下内容:

index.html - 这是我们的 HTML文件, 稍后我们把它移动到 Vue 组件中 。 images - 这是一个简单的图片文件夹。我们不需要它, 我们的图片将由 Cosmic JS服务器 assets提供 - 以及其他的资源,像 CSS, fonts, javascript 文件。 我们需要 CSS 和 fonts. 现在先不要管 Javascript, 因为我们打算使用 VueJS。让我们复制 assets/cssassets/fonts 文件夹到项目中的 static文件夹中。这将允许我们将这些资源作为静态资源自动添加到构建中。

准备index.html

现在是时候把我们的资源引入到index.html中了。将以下代码添加进 head 中:

<meta name="viewport" content="width=device-width, initial-scale=1" />
<!--[if lte IE 8]>``<script src="assets/js/ie/html5shiv.js">``</script><![endif]-->
<link rel="stylesheet" href="static/css/main.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="assets/css/ie8.css" /><![endif]-->
<!--[if lte IE 9]><link rel="stylesheet" href="assets/css/ie9.css" /><![endif]-->
<noscript><link rel="stylesheet" href="static/css/noscript.css" /></noscript>

这将包含我们的静态资源。 一旦我们实现正确的标签,此事件总线用于在一个组件中触发事件此事件总线用于在一个组件中触发事件此事件总线用于在一个组件中触发事件 样式将会通过 CSS自动设置。

VueJS 组件

查看我们的页面,我们可以定义一些需要的组件:

HeaderFooter - 这两个是非常简单的组件,用来显示app加载时的公共数据。 Thumbs - 此组件将显示照片缩略图 Viwer - 这个组件会显示大照片并提供上一张/下一张 导航。请注意- 我们创建的组件的模板将采用我们之前下载的模板 (从 index.html 文件中复制粘贴并应用VueJS指令).

Header 组件:

<template>
    <header id="header">
        <h1>{{ header }}</h1>
        <div v-html="text"></div>
        <ul class="icons">
            <li><a :href="twitter" class="icon fa-twitter"><span class="label">Twitter</span></a></li>
            <li><a :href="instagram" class="icon fa-instagram"><span class="label">Instagram</span></a></li>
            <li><a :href="github" class="icon fa-github"><span class="label">Github</span></a></li>
            <li><a :href="email" class="icon fa-envelope-o"><span class="label">Email</span></a></li>
        </ul>
    </header>
</template>
<script>
import {EventBus} from '../event_bus';
export default {
    name: 'app-header',
    created() {
        EventBus.$on('global_loaded', (obj) => {
            this.header = obj.title;
            this.text = obj.metafield.tagline.value;
            this.twitter = obj.metafield.twitter.value;
            this.instagram = obj.metafield.instagram.value;
            this.github = obj.metafield.github.value;
            this.email = 'mailto:' + obj.metafield.email.value;
        });
    },
    data () {
        return {
            text: null,
            twitter: '',
            instagram: '',
            github: '',
            email: '',
            header: ''
        }
    }
}
</script>

Footer 组件非常简单:

<template>
    <footer id="footer">
        <div v-html="text"></div>
    </footer>
</template>
<script>
import {EventBus} from '../event_bus';
export default {
    name: 'app-footer',
    created() {
        EventBus.$on('global_loaded', (obj) => {
            this.text = obj.metafield.footer.value;
        });
    },
    data () {
        return {
            text: null
        }
    }
}
</script>

这两个组件都使用 EventBus 从父组件接收数据. 稍后在这篇文章中我会讲述事件总线.

Thumbs 组件

这个组件比前两个都要复杂:

<template>
    <section id="thumbnails">
        <article v-for="(item, index) in items" v-bind:class="{ 'active': activeIndex == index }">
            <a class="thumbnail" v-on:click="selectImage(item, index)">
                <img v-bind:src="item.metafield.image.imgix_url" alt="" />
            </a>
            <h2>{{ item.title }}</h2>
            <div v-html="item.content"></div>
        </article>
    </section>
</template>
<script>
import Cosmic from 'cosmicjs';
import * as Config from '../config';
import {EventBus} from '../event_bus';
`const bucket = { slug: Config.bucket };`
export default {
    name: 'thumbs',
    props: ['bus'],
    created() {
        Cosmic.getObjectType({ bucket }, { type_slug: 'photos' }, (err, res) => {
            this.items = res.objects.all;
            EventBus.$emit('loaded', this.items[0]);
        });
        EventBus.$on('move', (dir) => {
            this.activeIndex = this.activeIndex + dir;
            if (dir > 0 && this.activeIndex >= this.items.length) {
                this.activeIndex = 0;
            }
            if (dir < 0 && this.activeIndex < 0) {
                this.activeIndex = this.items.length - 1;
            }
            EventBus.$emit('loaded', this.items[this.activeIndex]);
        });
    },
    data () {
        return {
            items: [],
            activeIndex: 0
        }
    },
    methods: {
        selectImage (itm, index) {
            EventBus.$emit('loaded', itm);
            this.activeIndex = index;
        }
    }
}
</script>

在创建组件时,我们获取图片列表并订阅事件总线中的 ‘move’ 事件. 接着我们渲染这些图片并且在用户每次选择一个新图片时就触发一个新事件。

Viewer 组件

<template>
    <div id="viewer">
        <div class="inner">
            <div class="nav-next" v-on:click="selectNext()"></div>
            <div class="nav-previous" v-on:click="selectPrev()"></div>
        </div>
        <div class="slide active" v-if="img">
            <div class="caption">
                <h2>{{ img.title }}</h2>
                <div v-html="img.content"></div>
            </div>
            <div class="image" v-bind:style='{ backgroundImage: "url(" + img.metafield.image.imgix_url + ")" }'>
            </div>
        </div> 
    </div>
</template>
<script>
import {EventBus} from '../event_bus';
export default {
    name: 'viewer',
    props: ['bus'],
    created() {
        EventBus.$on('loaded', (obj) => {
            this.img = obj;
        });
    },
    data () {
        return {
            img: null
        }
    },
    methods: {
        selectNext() {
            EventBus.$emit('move', 1);
        },
        selectPrev() {
            EventBus.$emit('move', -1);
        }
    }
}
</script>

这个组件通过事件总线订阅了 loaded事件. 这个事件意味着,使用选择的新照片时,我们要显示更大的尺寸; 这个组件的另一个任务就是在用户点击上一个/下一个时通知 Thumbs 组件。 组件使用事件总线也是为了此目的。

Event bus

事件总线遵循发布 - 订阅模式,并允许我们设置Thumbs和Viewer组件之间的通信。两个处在同一个层次的组件 (不是父-子的关系), 所以我们需要一个比简单的事件触发复杂点的东西。 事件总线的实现非常简单 (src/event_bus.js):

import Vue from 'vue';
export const EventBus = new Vue();

此事件总线用于在一个组件中触发事件(通过 EventBus.$emit) 并且可在其他的组件中订阅它们 (通过 EventBus.$on).

连接在一起

现在是时候用 App 组件连接所有的东西了:

<template>
  <div>
    <div id="main">
      <app-header></app-header>
      <thumbs></thumbs>
      <app-footer></app-footer>
    </div>
    <viewer></viewer>
  </div>
</template>
<script>
import AppHeader from './components/AppHeader'
import AppFooter from './components/AppFooter'
import Thumbs from './components/Thumbs'
import Viewer from './components/Viewer'
import Vue from 'vue';
import Cosmic from 'cosmicjs';
import * as Config from './config';
import {EventBus} from './event_bus';
`const bucket = { slug: Config.bucket };`
export default {
  name: 'app',
  components: {
    AppFooter,
    AppHeader,
    Thumbs,
    Viewer
  },
  created() {
    Cosmic.getObjectType({ bucket }, { type_slug: 'globals' }, (err, res) => {
      EventBus.$emit('global_loaded', res.objects.all[0]);
      console.log(res.objects.all[0]);
    });
  },
}
</script>

这个组件在创建时会加载全局变量数据并通过EventBus连接 AppHeaderAppFooter组件。

部署到Cosmic JS服务器

Cosmic JS对部署应用程序有一些要求:

  • 它必须在公开的git repo中

  • 必须满足具体要求 ,具体取决于你的平台。

在我们的例子中,我们实际上拥有了一个HTML5 app, 因此我们需要添加一些额外的软件。

准备配置文件

在你的项目目录中创建一个 prepare.js 文件:

`var fs = require('fs');`
var str = `
    Config = {
        bucket: '${process.env.COSMIC_BUCKET}'
    };
 module.exports = Config;
`;
fs.writeFile("./src/config.js", str, function(err) {
    if(err) {
        return console.log(err);
    }
    console.log("The file was saved!");
});

这个代码会重写应用程序的配置文件(更多信息请查阅上面)文件以使用你的 Cosmic JS bucket 写入key 和 bucket名字。

修改 package.json

VueJS CLI 添加一些包到package.json 作为 devDependencies. 我们必须将它们全部转移到 dependencies当中,使我们的脚本在 Cosmic JS服务器上可以工作。

准备软件

我们还需要一些东西来服务我们的 Angular app. 我们将使用 Express 框架:

`npm install --save express`

把下面的代码添加到你的 package.json中:

{
  ...
  "scripts": {
    ...
    "start": "node app.js"
  },
  ...
}

最主要的一点是在 scriptsstart命令定义的内容(你可以放心地替换默认的 angular start 命令). 这个命令会在启动我们的app是运行. 所以现在我们还有一件事 -创建 app.js文件:

const express = require('express')
const app = express()
`app.use(express.static('./dist'));`
app.listen(process.env.PORT, function () {
});

这是一个简单的Express应用程序,它将“dist”目录用作静态文件的目录。 请注意 - app 应用程序监听通过PORT环境变量指定的端口,因此要保证app是在Cosmic JS App Server上运行.

构建VueJS应用程序进行生产

我们将使用 app.json 来完成 (dokku predeploy 部分):

{
    "scripts": {
        "dokku": {
            "predeploy": "node prepare.js && npm run build"
        }
    }
}

这个脚本将在我们启动我们的 express app 构建用于生产的 VueJS app 之前运行.

运行它!

现在你输入 ‘Deploy Web App’ 页面在你的Cosmic JS Dashboard上.

只需输入你的 repo URL然后单击 ‘Deploy to Web’ — 部署过程将开始,应用程序将在在几分钟内就绪。

结论

使用Cosmic JS App Server可以使用git repo将应用程序快速部署到主机,而不用担心服务器配置和软件安装 - 一切都将由Cosmic JS服务器完成.

本文最初发表在 Cosmic JS Blog.

相关文章