使用 Vue、Bulma 和 Airtable 构建作品集网站

2025-05-25

使用 Vue、Bulma 和 Airtable 构建作品集网站

离开全栈训练营已经三年了,这是我第一次需要搭建一个作品集网站。我想做一个相对简单、易于更新、并且随着时间的推移易于扩展和改进的网站。

这个教程完美吗?当然完美!这是我写的第一篇教程,而且我一直在自学 Vue,所以有些部分我肯定可以改进(如果你觉得有什么不同,请在评论区告诉我)。不过,我知道这篇文章可能会对某些人有所帮助!

您可以在 github 上看到我的整个投资组合的代码,这些代码是从这个起点创建的,网址为:https://github.com/markjohnson303/portfolio

完成的示例位于hellomark.dev,但这是一个正在进行的工作,您可能会看到一些与此处描述的不同之处。

工具

Vue.js:我选择 Vue 来做这个项目,是因为它是我最熟悉的框架。有人可能会说,对于像这样的小项目来说,它有点大材小用,对你来说可能确实如此。它对我来说很合适,因为它使用起来很顺手,而且足够灵活,足以应付我将来的各种需求。我也希望在下一个职位上使用它,所以何乐而不为呢!

Bulma:在这个项目之前我从未使用过 Bulma,但我希望它能让我快速上线网站,并随着时间的推移轻松改进样式。Bulma 易于学习,但易于构建。它没有世界上最大的组件库,但它的组件库构建得非常扎实。

Airtable:我一直想在一个项目中使用 Airtable。据 Airtable 介绍,它“一半是电子表格,一半是数据库”,设计灵活,适用于各种用途。我在这里将它用作内容管理系统 (CMS),因为它非常易于使用,并且拥有强大的 API 和完善的文档(可根据您的数据库进行定制)。现在它已经设置好了,我可以在整个网站上使用它来做各种有趣的事情。而且它是免费的!

入门

首先需要设置你的 Vue 项目。我们将使用 Vue CLI 来搭建项目。请确保你已经安装了 Vue 和 Vue CLI:

$ npm install -g vue
$ npm install -g @vue/cli

然后创建你的项目:
$ vue create portfolio

然后启动它:
$ npm run serve

Vue CLI 提供了一个非常有用的起点,其中包含我们需要的大量文件和文件夹。我们将以此为基础进行构建。

现在让我们添加我们的 CSS 框架 Bulma。
$ npm install --s bulma

并将 Sass 样式表添加到我们的App.vue文件中

<style lang="sass">
@import "~bulma/bulma.sass"
</style>
Enter fullscreen mode Exit fullscreen mode

您可以在此处(导入上方)对 Bulma 默认值进行任何调整。

我们将安装 Axios(用于与我们的 Airtable API 配合使用)
$ npm install --s axios

我们需要VueSimpleMarkdown,这样我们就可以用 markdown 撰写和设计我们的帖子。

$ npm install -s vue-simple-markdown

我们main.js将输入:

import VueSimpleMarkdown from 'vue-simple-markdown'
import 'vue-simple-markdown/dist/vue-simple-markdown.css'

Vue.use(VueSimpleMarkdown)
Enter fullscreen mode Exit fullscreen mode

设置我们的路线

我们将为该网站设置 5 个主要路由:关于、联系、主页、项目和项目。让我们在 In 中设置它们src/router.js

import Vue from "vue";
import Router from "vue-router";
import Home from "./views/Home.vue";
import About from "./views/About.vue";
import Contacts from "./views/Contact.vue";
import Projects from "./views/Projects.vue"
import Project from "./views/Project.vue"

Vue.use(Router);

export default new Router({
  mode: "history",
  base: process.env.BASE_URL,
  routes: [
    {
      path: "/",
      name: "home",
      component: Home
    },
    {
      path: "/about",
      name: "about",
      component: About
    },
    {
      path: "/contact",
      name: "contact",
      component: Contact
    },
    {
      path: "/projects",
      name: "projects",
      component: Projects
    },
    {
      path: "/project/:slug",
      name: "project",
      component: Project
    }
  ]
});
}
Enter fullscreen mode Exit fullscreen mode

奇怪的是path: "/project/:slug"。稍后我们将使用此路线根据 slug 显示来自 Airtable 的单个项目。

我们还将为每个 创建一个空组件src/views,例如这里Contact.vue。我们稍后会填充它们。

<template>
  <div>

  </div>
</template>

<script>

export default {
  name: "contact",
};

</script>
Enter fullscreen mode Exit fullscreen mode

添加页眉和页脚

让我们添加页眉(带导航)和页脚,添加一些样式,并运用一些 Vue 的魔法,让它在移动设备上运行。我们将这段代码添加到App.vue每个视图中,以便它能够显示出来。

<template>
  <div id="app">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <nav class="navbar" role="navigation" aria-label="main navigation">
      <div class="navbar-brand">
        <router-link class="navbar-item" to="/">
          <img src="./assets/name-mark.jpg" width="112" height="28">
        </router-link>

        <a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample" :class="{ 'is-active': showNav }" @click="showNav = !showNav">
          <span aria-hidden="true"></span>
          <span aria-hidden="true"></span>
          <span aria-hidden="true"></span>
        </a>
      </div>

      <div id="navbarBasicExample" class="navbar-menu" :class="{ 'is-active': showNav }">
        <div class="navbar-start">

        </div>

        <div class="navbar-end">
          <router-link to="/" class="navbar-item">
            Home
          </router-link>
          <router-link to="/about" class="navbar-item">
           About
          </router-link>
          <router-link to="/projects" class="navbar-item">
           Projects
          </router-link>
          <router-link to="/contact" class="navbar-item">
           Contact
          </router-link>
       </div>
     </div>
   </nav>
   <router-view />
   <footer class="footer">
    <div class="content has-text-centered">
      <p>
        Built by Mark Johnson with Vue.js, Bulma, and Airtable.
      </p>
    </div>
  </footer>
</div>
</template>

<script>
  export default {
    name: "App",
    data() {
      return{
        showNav: false
      }
    },
  };
</script>

<style type="text/css">
#app {
  min-height: 100vh;
  overflow: hidden;
  display: block;
  position: relative;
  padding-bottom: 168px; /* height of your footer */
}
footer {
 position: absolute;
 bottom: 0;
 width: 100%;
}
</style>

<style lang="sass">
@import "~bulma/bulma.sass"
</style>

Enter fullscreen mode Exit fullscreen mode

静态页面

“关于”、“主页”和“联系”页面没有任何动态内容,您可以随意添加任何您喜欢的内容。例如,以下是我在主页上所做的。我在这里保持了简洁,但您可以根据自己的喜好进行修饰。

<template>
  <div>
    <div class="hero is-cover is-relative is-fullheight-with-navbar is-primary">
        <div class="hero-body">
            <div class="container">
                <h1 class="title is-1">Hello, I'm Mark.</h1>
                <h2 class="subtitle is-3">A customer focused, entrepreneurially minded web developer.</h2>
            </div>
        </div>
    </div>
  </div>
</template>

<script>

export default {
  name: "home",
};
</script>
Enter fullscreen mode Exit fullscreen mode

构建项目页面

项目页面开始变得有趣起来。我们将从 Airtable 中提取信息,并为每个项目显示一个摘要卡。

设置 Airtable

在 Airtable 上创建一个名为“项目”的新库。创建以下字段:“标题”(单行文本)、“slug”(单行文本)、“正文”(长文本)、“图片”(附件)、“发布日期”(日期)、“已发布”(复选框)、“摘录”(单行文本)。

瞧!一个简单的 CMS 就完成了!只需在其中填充几行虚拟数据,就能清楚地看到正在处理的内容。确保“slug”是唯一的!我们将在后续步骤中使用它来构建 URL。

构建用于展示项目的卡片

我们将创建一个组件来显示项目摘要。它可重复使用,以便您以后可以用它创建博客!

src/components新建一个文件,命名为PostCard.vue。填写如下内容:

<template>
    <div class="post-card">
        <div class="card">
            <div class="card-image">
                <figure class="image is-square">
                    <img :src="image" alt="Placeholder image">
                </figure>
            </div>
            <div class="card-content">
                <div class="media">
                    <div class="media-content">
                        <p class="title is-4">{{title}}</p>
                        <p class="subtitle is-6">{{date}}</p>
                    </div>
                </div>
                <div class="content">
                    <p>{{snippet}}</p>
                    <router-link :to="'/project/'+slug" class="button is-fullwidth">View Project</router-link>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: "PostCard",
        props: {
            title: String,
            date: String,
            snippet: String,
            image: String,
            slug: String
        }
    };
</script>

Enter fullscreen mode Exit fullscreen mode

从 Airtable 的 API 获取项目后,我们将从“项目”页面引入道具。它们会将内容和完整项目视图的链接填充到模板中。

创建服务来引入项目

让我们建立与 Airtable API 的连接。在 创建一个目录src/services,并在其中放置一个名为 的文件ProjectsService.js

在项目服务中,我们将使用 Axios 调用 Airtable API 并获取所有项目。您的文件应如下所示:

import axios from 'axios'

const Axios = axios.create({
  baseURL: "https://api.airtable.com/v0/[YOUR APP ID]/Projects"
});

Axios.defaults.headers.common = {'Authorization': `Bearer ` + process.env.VUE_APP_AIRTABLEKEY}

export default{
  getProjects() {
    return Axios.get()
  }
}
Enter fullscreen mode Exit fullscreen mode

您需要设置baseURLAirtable 基础的 ID。您可以在 Airtable 的 URL 中找到它。

您还需要从 Airtable 添加您的 API 密钥。我将我的密钥放在.env.localVue 项目根目录中名为 的文件中。此文件已列在 .gitignore 中,因此您无需担心它被暴露。文件中的内容如下VUE_APP_AIRTABLEKEY=[YOUR API KEY]

最后,我们导出一个在 baseURL 中调用 API 端点的 get 函数。

显示项目卡

我们将在“项目”视图中显示项目的卡片。在您的Projects.vue模板中:

<template>
    <div>
        <section class="hero is-medium is-primary is-bold">
            <div class="hero-body">
                <div class="container">
                    <h1 class="title is-2">
                        Projects that I have built
                    </h1>
                </div>
            </div>
        </section>
        <section class="section">
            <div class="container is-fluid">
                <div class="columns is-multiline">
                        <div class="column is-one-third" v-for="project in projects">
                            <post-card v-bind="project"></post-card>
                        </div>              
                </div>
            </div>
        </section>
    </div>
</template>
Enter fullscreen mode Exit fullscreen mode

这里要注意的是,v-for="project in projects"我们post-card为每个项目创建一个实例,并使用传递项目详细信息v-bind

模板的脚本部分如下所示:

<script>
    import ProjectsService from '@/services/ProjectsService'
    import PostCard from '@/components/PostCard'
    export default {
        name: "projects",
        components: {
            PostCard
        },
        data() {
            return{
                airtableResponse: []
            }
        },
        mounted: function () {
            let self = this
            async function getProjects() {
                try{
                    const response = await ProjectsService.getProjects()
                    console.log(response)
                    self.airtableResponse = response.data.records

                }catch(err){
                    console.log(err)
                }
                }
              getProjects()         
        },
        computed: {
            projects(){
                let self = this
                let projectList = []
                for (var i = 0; i < self.airtableResponse.length; i++) {
                    if (self.airtableResponse[i].fields.Published){
                        let project = {
                            title: self.airtableResponse[i].fields.Title,
                            date: self.airtableResponse[i].fields["Date Published"],
                            snippet: self.airtableResponse[i].fields.Excerpt,
                            image: self.airtableResponse[i].fields.Image[0].url,
                            slug: self.airtableResponse[i].fields.slug
                        }
                        projectList.push(project)
                    }
                }
                return projectList
            }
        }
    };
</script>
Enter fullscreen mode Exit fullscreen mode

从顶部开始,发生的事情如下:

  • 导入我们之前创建的 ProjectsService 和 PostCard
  • 设置一个变量来保存来自 Airtable 的响应
  • 当组件挂载的时候,调用我们在服务中设置的端点。
  • 将我们需要的响应部分放入 airtableResponse 变量中。
  • 使用名为“Projects”的数组创建计算属性,其中 Airtable 响应中的每个已发布项目都有一个对象,每张卡片都需要一个变量。

如果一切顺利,您应该能够/projects在网格中加载并查看在 Airtable 中创建的每个项目

克里特岛项目视图

还记得我们路由器设置中的这段代码吗?

{
 path: "/project/:slug",
 name: "project",
 component: Project
}
Enter fullscreen mode Exit fullscreen mode

它将使我们能够访问我们的 Project 组件中的 slug,并将其传递到我们的 Projects Service 中,以便我们可以使用该 slug Airtable 检索该项目的所有信息。

让我们在中添加一个调用ProjectsService.js

getProject(slug) {
  return Axios.get("?filterByFormula={Slug}='" + slug + "'")
}
Enter fullscreen mode Exit fullscreen mode

我们在这里使用 Airtable 的 API 功能来搜索包含 slug 的帖子并返回它。

现在让我们创建我们的项目视图模板:

<template>
    <div>
        <section class="hero is-medium is-primary is-bold">
            <div class="hero-body">
                <div class="container">
                    <h1 class="title is-2">
                        {{project.title}}
                    </h1>
                    <h2 class="subtitle is-4">
                        {{project.snippet}}
                    </h2>
                </div>
            </div>
        </section>
        <section class="section">
            <div class="container is-fluid">
                <div class="columns">
                    <div class="column is-two-thirds">
                        <vue-simple-markdown :source="project.body"></vue-simple-markdown>
                    </div>
                    <div class="column is-one-third">
                        <div class="columns is-multiline">
                            <div class="column is-full" v-for="image in project.images">
                                <img :src="image.url"/>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </div>
</template>
Enter fullscreen mode Exit fullscreen mode

此模板使用了我们之前安装的 VueSimpleMarkdown 插件。这意味着您可以在 Airtable 的 body 字段中使用 markdown 来设置项目样式。我们将 body 显示在左侧的一列中,并将该项目的所有图片显示在右侧。

最后是项目组件的脚本部分:

<script>
    import ProjectsService from '@/services/ProjectsService'
    import PostCard from '@/components/PostCard'
    export default {
        name: "project",
        components: {
            PostCard
        },
        data() {
            return{
                airtableResponse: []
            }
        },
        mounted: function () {
            let self = this
            console.log("here 1")
            async function getProject() {
                try{
                    const response = await ProjectsService.getProject(self.$route.params.slug)
                    console.log(response)
                    self.airtableResponse = response.data.records

                }catch(err){
                    console.log(err)
                }
            }
            getProject()            
        },
        computed: {
            project(){
                let self = this
                if (self.airtableResponse[0]){
                    let thisProject = {
                        title: self.airtableResponse[0].fields.Title,
                        snippet: self.airtableResponse[0].fields.Excerpt,
                        images: self.airtableResponse[0].fields.Image,
                        body: self.airtableResponse[0].fields.Body
                    }
                    return thisProject
                }
            }
        }
    };
</script>
Enter fullscreen mode Exit fullscreen mode

与“项目”视图类似,但这次我们将 slug 传递给调用getProject。如果 slug 是唯一的,我们应该只会收到一个响应。

前往 /projects/[your-slug] 查看您的项目!

结论

呼!好多内容!现在我们完成了,一个用 Vue 构建、用 Bulma 设置样式的简单 CMS 站点,可以实时显示数据。太酷了!

我将尝试各种样式,添加一些新功能,并清理一些东西,所以请关注hellomark.dev看看接下来会发生什么!

请告诉我您对本教程的看法以及您有的任何问题!

文章来源:https://dev.to/markjohnson303/building-a-portfolio-site-with-vue-bulma-and-airtable-5a58
PREV
CI/CD 管道:GCP Cloud Build 简介
NEXT
我希望在开始我的编程之旅时就知道的 10 件事