发布于 2026-01-06 5 阅读
0

如何使用 Vue.js、Vuex、Vuetify 和 Firebase 构建单页应用程序(第 4 部分,共 4 部分)

如何使用 Vue.js、Vuex、Vuetify 和 Firebase 构建单页应用程序(第 4 部分,共 4 部分)

学习如何使用 Vue.js、Vuex、Vue Router 和 Firebase 创建一个送餐网站。

这是我的 Vue 应用构建系列文章的第四部分,共四部分。以下是所有部分的列表:

第一部分:安装 Vue 并使用 Vuetify 和 Vue Router 构建 SPA

第二部分:使用 Vue Router

第三部分:使用 Vuex 和访问 API

第四部分:使用 Firebase 进行身份验证

概要

在本系列的第一部分中,我们使用 Vue CLI 创建了 Vue 应用程序。此外,我们还向应用程序中添加了 Vuetify。我们使用 Vuetify 来设置首页样式。

在第二部分,我们使用 Vue Router 为应用程序的不同页面添加了导航。我们为应用程序中的所有页面都添加了组件。

在第三部分,我们学习了 Vuex。我们注册了一个 API 来提供菜谱,并使用 axios 来检索它们。这些数据存储在 Vuex store 中,从而使应用程序中的每个组件都能访问它们。

什么是 Firebase?

Firebase 是面向客户端应用的实时云基础设施。Firebase 可以将任何前端应用转变为可在云端无限扩展的全栈产品。它抽象化了大部分复杂的服务器端功能,例如用户身份验证、数据持久化、文件存储和微服务,让您可以专注于为最终用户打造卓越的体验。

第一步是前往Firebase 并创建一个新帐户。登录您创建的帐户。您将看到以下仪表板:

Firebase 演示

点击Add Project按钮。输入项目名称。我输入的项目名称是“备餐”。勾选所有复选框。然后点击按钮create project

在 Firebase 上添加新项目对话框

项目创建完成后,Firebase 会将您带到项目的主页。

项目首页

我们需要将项目的配置集成到我们的备餐应用程序中。点击网页按钮,将 Firebase 添加到您的应用程序中。(注意:如果您不确定是哪个按钮,它是带有点号的按钮</>。在上图中,该按钮就在“开始使用”字样的正上方。点击复制按钮,将代码片段复制到剪贴板。)

Firebase 配置片段

接下来,我们需要将这段代码片段集成到我们的备餐应用程序中。您可以main.js在文件中初始化您的 Firebase 应用程序App.vue

相反,我们将在 src 文件夹下创建一个名为 firebase 的新目录。在该目录下创建一个名为 firebase.js 的文件index.js。将剪贴板的内容粘贴到此文件中。删除带有 ` script<script>` 标签的两行。在文件的第一行导入 firebase。在最后一行初始化 firebase。您的文件应该如下所示:

import firebase from 'firebase';

const config = {
    apiKey: "<youKeyHere>",
    authDomain: "<youKeyHere>",
    databaseURL: "<youKeyHere>",
    projectId: "<youKeyHere>",
    storageBucket: "<youKeyHere>",
    messagingSenderId: "<youKeyHere>"
};
firebase.initializeApp(config);
Enter fullscreen mode Exit fullscreen mode

我们正在从一个尚未安装的 npm 包中导入 Firebase。现在让我们来安装它。在终端中使用以下命令安装 Firebase:

npm install firebase --save

现在我们已经安装了 Firebase 并创建了配置文件,接下来需要将这个文件添加到我们的应用程序中,以便 Vue 可以识别它。打开该main.js文件并导入我们创建的配置文件。以下是我的main.js配置文件内容:

import '@babel/polyfill';
import Vue from 'vue';
import './plugins/vuetify';
import App from './App.vue';
import router from './router';
import store from './store';
import '@/firebase/';

Vue.config.productionTip = false;

new Vue({
    router,
    store,
    render: h => h(App)
}).$mount('#app');
Enter fullscreen mode Exit fullscreen mode

返回浏览器中的 Firebase 控制台。点击Authentication。点击set up sign-in method按钮。

在 Firebase 中设置身份验证

在登录方式列表中,点击“电子邮件/密码”:

选择电子邮件/密码

启用所有用户使用邮箱地址和密码注册的选项。然后点击按钮save

启用使用邮箱和密码注册的功能

创建注册表单

在之前的文章中,我们已经创建了 Join.vue 和 Signin.vue 这两个文件。这两个文件的代码几乎相同。我们将首先创建 Join 表单。完成后,我们会将其复制粘贴到 Signin 表单中。

打开 Join.vue 组件。您可以移除模板中的所有内容。Vuetify 为组件提供了一个默认的布局结构,其流程如下:

  • v-容器
  • v 型布局
  • v-flex

现在让我们在组件中创建布局。文件开头如下所示:

<template>
    <v-container fill-height>
        <v-layout align-center justify-center>
            <v-flex xs12 sm8 md4>

            </v-flex>
        </v-layout>
    </v-container>
</template>
Enter fullscreen mode Exit fullscreen mode

我们v-container添加了fill-height`<form>` 属性,使其在窗口中垂直居中。v-flex我们还添加了`<form> xs12 sm8` 和md4`<form>` 值。这类似于 Bootstrap 的列宽定义。在超小型设备上,表单将占据全部 12 列,即整个屏幕。在小型设备上,表单的宽度将为屏幕的 3/4。在中型和大型屏幕上,表单的宽度将为屏幕的 1/3。

在表单内部,v-flex我们将使用一个 `<div>` 标签v-card。我们class=”elevation-12"为其添加 `<div>` 标签v-card,使其看起来像是在页面上方浮动。表单顶部,我们将使用一个 `<div>` 标签v-toolbar。我们将其颜色primary设置为 `<div>`。Vuetify 的默认主色是蓝色。我们希望工具栏中的文本是白色而不是默认的黑色。要将文本变为白色,我们dark为其添加 `<div>` 标签v-toolbar

接下来,我们有一个v-card-text。在其中,我们有一个v-form。对于表单,我们给它赋予一个名为的引用form。我们将其赋值给,v-model值为valid

最后添加的是表单lazy-validation。我们的表单需要收集用户的电子邮件地址和密码。我们将使用两个字段v-text-field来收集这些值。为了使表单看起来更美观,我在每个字段前面添加了一个图标。每个字段都有一个图标v-model和一个空格rules

表单提交前,系统会根据所有已定义的规则验证该字段。如果验证通过,即可提交表单。用户点击“加入”按钮时,我们将利用这一机制。

最后要添加到表单中的是按钮。我们添加一个元素v-card-actions,然后再添加一个按钮。以下是组件的模板示例:

<template>
    <v-container fill-height>
        <v-layout align-center justify-center>
            <v-flex xs12 sm8 md4>
                <v-card class="elevation-12">
                    <v-toolbar dark color="primary">
                        <v-toolbar-title>Join Form</v-toolbar-title>
                    </v-toolbar>
                    <v-card-text>
                        <v-form ref="form" v-model="valid" lazy-validation>
                            <v-text-field prepend-icon="person" name="email" label="Email" type="email"
                                          v-model="email" :rules="emailRules" required>
                            </v-text-field>
                            <v-text-field prepend-icon="lock" name="password" label="Password" id="password"
                                          type="password" required v-model="password" :rules="passwordRules">
                            </v-text-field>
                        </v-form>
                    </v-card-text>
                    <v-card-actions>
                        <v-spacer></v-spacer>
                        <v-btn color="primary" :disabled="!valid" @click="submit">Join</v-btn>
                    </v-card-actions>
                </v-card>
            </v-flex>
        </v-layout>
    </v-container>
</template>
Enter fullscreen mode Exit fullscreen mode

我们在模板中定义了几个模型。我们需要将它们添加到data脚本的相应部分。在脚本中添加一个数据对象。我们将添加 valid、email、password、emailRules 和 passwordRules 这几个模型。

电子邮件和密码将分别包含用户在两个文本字段中输入的值。验证结果将表明我们的表单是否通过了所有已创建的规则。对于电子邮件,我们会检查该字段是否为空。我们还会检查其内容是否与基本的正则表达式匹配,以验证电子邮件地址。对于密码,我们会检查该字段是否为空。我们还会检查密码长度是否至少为六个字符。

以下是数据对象现在的样子:

data() {
    return {
        valid: false,
        email: '',
        password: '',
        emailRules: [
            v => !!v || 'E-mail is required',
            v => /.+@.+/.test(v) || 'E-mail must be valid'
        ],
        passwordRules: [
            v => !!v || 'Password is required',
            v =>
                v.length >= 6 ||
                'Password must be greater than 6 characters'
        ]
    };
},
Enter fullscreen mode Exit fullscreen mode

最后我们需要添加的是方法。在方法中,我们有 ` submit()validate` 方法。这个方法会首先验证我们的表单。如果验证通过,它会调用 Vuex store 中的一个名为 `get` 的操作userJoin。我们会将用户在表单中输入的邮箱和密码传递给这个操作。

以下是这些方法的具体步骤:

methods: {
    submit() {
        if (this.$refs.form.validate()) {
            this.$store.dispatch('userJoin', {
                email: this.email,
                password: this.password
            });
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

在 Vuex 中创建 userJoin 操作

打开store.js文件。我们将创建一个名为 `commit` 的新操作userJoin。默认情况下,传递给此操作的第一个参数是 `value` 。我将使用对象解构来从 `value` 中context获取值。`commit` 是我对 mutation 的调用方式。commitcontext

我将使用 Firebase 在 Firebase 数据库中创建新用户。为了能够在 store 中使用 Firebase,我需要先导入它。在文件顶部使用以下命令导入 Firebase:

import firebase from 'firebase';

Firebase 身份验证提供了一个名为 `registerUser` 的方法createUserWithEmailAndPassword。我们将把用户的电子邮件地址和密码传递给这个方法。如果用户注册成功,它将返回一个用户对象。注册成功后,我们将调用两个 mutation:`getUserUser`setUser和 `getUserUser` setIsAuthenticated。以下是该 action 的示例:

userJoin({ commit }, { email, password }) {
    firebase
        .auth()
        .createUserWithEmailAndPassword(email, password)
        .then(user => {
            commit('setUser', user);
            commit('setIsAuthenticated', true);
        })
        .catch(() => {
            commit('setUser', null);
            commit('setIsAuthenticated', false);
        });
}
Enter fullscreen mode Exit fullscreen mode

此操作会调用两个 mutation。现在让我们来创建它们。在 mutations 中添加一个名为 `<mutation_name>` 的新 mutation setUser。将 `user` 的状态值设置为 payload。接下来,创建第二个名为 `<mutation_name>` 的 mutation setIsAuthenticated。将 `isAuthenticated` 的状态值设置为 payload。以下是这两个 mutation 的示例:

setUser(state, payload) {
    state.user = payload;
},
setIsAuthenticated(state, payload) {
    state.isAuthenticated = payload;
}
Enter fullscreen mode Exit fullscreen mode

在状态中,我们需要添加两个新值:userisAuthenticated。以下是当前状态的样子:

state: {
    recipes: \[\],
    apiUrl: 'https://api.edamam.com/search',
    user: null,
    isAuthenticated: false
},
Enter fullscreen mode Exit fullscreen mode

测试添加新用户

使用命令启动服务器npm run serve。点击Join导航栏中的按钮。输入您的邮箱和密码,然后点击“加入”按钮。点击按钮后,不会有任何显示。要验证用户是否已注册,请在浏览器中打开 Firebase 控制台。点击“注册” Authentication。您应该会看到已注册用户列表。在这里,您可以看到我刚刚注册的用户已创建。

用户已注册

我们需要通知用户他们已成功创建帐户。稍后我们会进行此操作。首先,我们将把 Join.vue 组件的内容复制粘贴到 Signin.vue 组件中。模板中只需要做两处修改:将标题更改为“登录表单”;将按钮的文本更改为“登录”;在提交方法中,将其分发到 [此处应填写提交方法名称] userLogin。这样就完成了。现在,您已经创建了 Join 和 Login 表单。

我们需要创建登录操作。打开store.js文件。创建一个名为 userLogin 的新操作。我们将使用 Firebase 来实现用户登录。Firebase 提供了一个名为 `userLogin` 的方法signInWithEmailAndPassword。我们将调用此方法,并传入用户在表单中输入的电子邮件地址和密码。如果用户正确输入了电子邮件地址和密码,我们将调用 `userLogin` 和 `userLogin` 这两个 mutation setUsersetIsAuthenticated以下是该userLogin操作的代码:

userLogin({ commit }, { email, password }) {
    firebase
        .auth()
        .signInWithEmailAndPassword(email, password)
        .then(user => {
            commit('setUser', user);
            commit('setIsAuthenticated', true);
        })
        .catch(() => {
            commit('setUser', null);
            commit('setIsAuthenticated', false);
        });
},
Enter fullscreen mode Exit fullscreen mode

正在跳转到个人资料

当用户成功注册或登录后,我们希望将其重定向到个人资料页面。最初使用 Vue CLI 3 创建应用时,它为我们创建了两条路由,分别是 `<route>` 和 ` /<route> `。/about最终,个人资料页面将包含用户在该页面订购的所有菜谱列表menu。还记得我们在每个菜谱底部放置的按钮吗?该按钮会将菜谱添加到用户的个人资料中,并将其存储在 Firebase 数据库中。

要将用户重定向到个人资料页面,我们首先需要在 store.js 文件顶部导入路由。路由的导入命令如下:

import router from '@/router';

接下来,在两种操作中,如果用户注册或登录成功,我们都会将用户重定向到 /about 页面。您可以使用以下命令执行重定向:






If the user fails to register an account or login successfully we will redirect the user to the home page. _(NOTE: in a perfect scenario we will provide some notice to the user why the registration or login failed). You can redirect them to the home page with this command:_



```router.push('/');```





To test the redirection, start your server and click on the Login button. Enter the email and password you used when you created your user account. Click the Join button. If everything worked successfully you should be redirected to the About page.

Updating the navigation
-----------------------

The navigation has buttons for `Sign In` and `Join`. When a user successfully registers or login we would like to hide these two buttons. In their place, we want to show a `Logout` button.

Open up the `AppNavigation` component. We are going to group the two current buttons in a div. We are going to remove the class to hide the buttons on small and extra-small devices. Instead, we will place this class on the div. We add a `v-if` to the div to only show if the user is currently not authenticated. Below the `div` we will add a new button for Logout. This new button will have a style of outline with a color of white. When you click on this button it will call the method `logout`. We add a v-else to this button to show when the user is authenticated.

Next, add a method called `logout`. This method will call an action in our store called `userSignOut`.

We also need to add a new computed property called `isAuthenticated`. This property returns the value of isAuthenticated in the state of our store.

Here is what your AppNavigation should look like:



```html
<template>
    <span>
        <v-navigation-drawer app v-model="drawer" class="brown lighten-2" dark disable-resize-watcher>
            <v-list>
                <template v-for="(item, index) in items">
                    <v-list-tile :key="index">
                        <v-list-tile-content>
                            {{item.title}}
                        </v-list-tile-content>
                    </v-list-tile>
                    <v-divider :key="\`divider-${index}\`"></v-divider>
                </template>
            </v-list>
        </v-navigation-drawer>
        <v-toolbar app color="brown darken-4" dark>
            <v-toolbar-side-icon class="hidden-md-and-up" @click="drawer = !drawer"></v-toolbar-side-icon>
            <v-spacer class="hidden-md-and-up"></v-spacer>
            <router-link to="/">
                <v-toolbar-title to="/">{{appTitle}}</v-toolbar-title>
            </router-link>
            <v-btn flat class="hidden-sm-and-down" to="/menu">Menu</v-btn>
            <v-spacer class="hidden-sm-and-down"></v-spacer>
            <div v-if="!isAuthenticated" class="hidden-sm-and-down">
                <v-btn flat to="/sign-in">SIGN IN</v-btn>
                <v-btn color="brown lighten-3" to="/join">JOIN</v-btn>
            </div>
            <v-btn v-else outline color="white" @click="logout">Logout</v-btn>

        </v-toolbar>
    </span>
</template>

<script>
export default {
    name: 'AppNavigation',
    data() {
        return {
            appTitle: 'Meal Prep',
            drawer: false,
            items: \[{ title: 'Menu' }, { title: 'Sign In' }, { title: 'Join' }\]
        };
    },
    computed: {
        isAuthenticated() {
            return this.$store.getters.isAuthenticated;
        }
    },
    methods: {
        logout() {
            this.$store.dispatch('userSignOut');
        }
    }
};
</script>

<style scoped>
a {
    color: white;
    text-decoration: none;
}
</style>
Enter fullscreen mode Exit fullscreen mode

我们需要添加刚刚定义的 getter 和 action。打开store.js文件。创建一个名为 `sign()` 的新 action userSignout。此 action 将使用 `firebase.auth()` 来注销用户。注销用户后,它会将状态变量设置user为 `null` 和isAuthenticated`false`。以下是userSignoutstore 中的方法:

userSignOut({ commit }) {
    firebase
        .auth()
        .signOut()
        .then(() =&gt; {
            commit('setUser', <em class="markup--em markup--pre-em">null</em>);
            commit('setIsAuthenticated', <em class="markup--em markup--pre-em">false</em>);
            router.push('/');
        })
        .catch(() =&gt; {
            commit('setUser', <em class="markup--em markup--pre-em">null</em>);
            commit('setIsAuthenticated', <em class="markup--em markup--pre-em">false</em>);
            router.push('/');
        });
}
Enter fullscreen mode Exit fullscreen mode

接下来,我们需要getters向 store 对象添加一个 section。getterisAuthenticated方法会根据用户身份验证结果返回 true 或 false。以下是gettersstore 中 section 的示例:

getters: {
    isAuthenticated(state) {
        <em class="markup--em markup--pre-em">return </em>state.user !== <em class="markup--em markup--pre-em">null </em>&amp;&amp; state.user !== <em class="markup--em markup--pre-em">undefined</em>;
    }
}
Enter fullscreen mode Exit fullscreen mode

将食谱添加到数据库

用户登录后,可以点击任何食谱将其添加到自己的帐户。他们的食谱将显示在个人资料页面(即“路由”页面)/about。我们需要一个数据库来存储这些食谱。请在浏览器中打开 Firebase 控制台。点击database左侧导航面板中的“管理控制台”。在下一个屏幕上,您会看到创建实时数据库或云端 Firestore 数据库的按钮。请确保创建一个新的实时数据库。在对话框中,确保选择“创建实时数据库” start in test mode。然后点击“创建enable”按钮。

以测试模式启动数据库

现在我们要将用户的食谱存储到数据库中。打开 MealPlans 组件。如果用户尝试订购食谱但未登录,我们应该将其重定向到登录页面。所以我们现在就来处理这个问题。在按钮上Order添加一个@click 事件,调用 orderRecipe 方法。确保将用户食谱item作为参数传递给该方法。你的按钮应该如下所示:

<v-card-actions>
    <v-btn color="green" dark @click="orderRecipe(item)">Order</v-btn>
</v-card-actions>
Enter fullscreen mode Exit fullscreen mode

在创建方法之前,我们需要为 isAuthenticated 创建一个计算值。这段代码与我们之前用于AppNavigation正确显示和隐藏登录/注销按钮的代码完全相同。添加一个计算值 isAuthenticated。它应该如下所示:

export default {
    name: 'MealRecipes',
    computed: {
        recipes() {
            return this.$store.state.recipes;
        },
        isAuthenticated() {
            return this.$store.getters.isAuthenticated;
        }
    }
};
Enter fullscreen mode Exit fullscreen mode

现在我们准备创建 orderRecipe 方法。添加此方法及其参数。在此方法中,我们首先要检查用户是否已登录。如果未登录,则将其重定向到页面/sign-in。如果已登录,则调用 Vuex store 中的一个 action,将菜谱添加到数据库中的用户帐户。以下是该方法的代码:

methods: {
    orderRecipe(item) {
        if (this.isAuthenticated) {
            this.$store.dispatch('addRecipe', item);
        } else {
            this.$router.push('/sign-in');
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

打开 store.js 文件。我们需要创建一个新的 action 来添加菜谱。在这个 action 中,我们将使用 Firebase 将菜谱添加到名为 `<database_name>` 的数据库中users。用户在 Firebase 注册时会被分配一个唯一的用户 ID。我们将使用这个用户 IDuid将菜谱名称存储在数据库中。在这个 action 中,我们将使用`<user_name> state` 获取当前选中用户的值。`<user_name>`userstate一个对象。该对象有一个名为 `user` 的键。在该对象中,我们将找到 `<title> uid`。我们将使用该值将选中菜谱的标题推送到数据库中。以下是该 action:

addRecipe({ state }, payload) {
    firebase
        .database()
        .ref('users')
        .child(state.user.user.uid)
        .push(payload.label);
}
Enter fullscreen mode Exit fullscreen mode

现在启动服务器并登录。从菜单页面选择一种饮食方案。然后订购几份菜谱。您订购的菜谱应该会显示在 Firebase 数据库中。

已将食谱添加到数据库

现在我们已经将食谱添加到数据库,接下来需要将它们显示在用户的个人资料页面上。打开该About.vue文件。每次加载此页面时,它都应该获取用户的所有食谱。为此,我们mounted()在脚本中添加以下代码。这将调用一个名为 `.` 的方法getRecipes

现在我们来创建这个方法。在这个方法中,我们将调用 Vuex store 中的一个 action,该 action 会获取用户的所有菜谱。我们还没有在 store 中创建这个 action,但简单来说,这个 action 会获取用户的菜谱,然后将它们存储在一个state名为`recipes` 的变量中userRecipes。在离开 About.vue 文件之前,为 `recipes` 添加一个计算属性userRecipes。这将返回store 中的userRecipes`recipes`值state。以下是 About.vue 脚本的内容:

export default {
    name: 'About',
    computed: {
        userRecipes() {
            return this.$store.state.userRecipes;
        }
    },
    mounted() {
        this.getRecipes();
    },
    methods: {
        getRecipes() {
            this.$store.dispatch('getUserRecipes');
        }
    }
};
Enter fullscreen mode Exit fullscreen mode

接下来,打开你的store.js文件。我们需要创建getUserRecipes操作。当用户登录时,我们会存储一个state名为 user 的变量。该变量将包含用户在 Firebase 注册时分配的唯一用户 ID。我们希望获取用户数据库中所有具有此用户 ID 的菜谱。获取所有菜谱后,我们需要将它们添加到 userRecipes 中。以下是 getUserRecipes 操作:

getUserRecipes({ state, commit }) {
    <em class="markup--em markup--pre-em">return </em>firebase
        .database()
        .ref('users/' + state.user.user.uid)
        .once('value', snapshot =&gt; {
            commit('setUserRecipes', snapshot.val());
        });
}
Enter fullscreen mode Exit fullscreen mode

在我们的突变体中,我们需要添加一个setUserRecipes。它看起来像这样:

setUserRecipes(state, payload) {
    state.userRecipes = payload;
}
Enter fullscreen mode Exit fullscreen mode

我们还需要添加一个 ` userRecipesin` 元素state。我们将其初始值设置为空数组。以下是我的完整状态对象:

state: {
    recipes: \[\],
    apiUrl: 'https://api.edamam.com/search',
    user: <em class="markup--em markup--pre-em">null</em>,
    isAuthenticated: <em class="markup--em markup--pre-em">false</em>,
    userRecipes: \[\]
},
Enter fullscreen mode Exit fullscreen mode

现在我们已经获取到了菜谱,需要将它们显示在页面上供用户查看。所以请回到你的About.vue文件。在模板中,我们将遍历用户的所有菜谱并显示它们。我先展示一下我的模板代码,然后再解释我做了什么:

<template>
    <v-container >
        <v-layout column>
            <h1 class="title my-3">My Recipes</h1>
            <div v-for="(item, idx) in userRecipes" class="subheading mb-2" :key="idx">
                {{item}}
            </div>
        </v-layout>
    </v-container>
</template>
Enter fullscreen mode Exit fullscreen mode

我已将布局设置为“” column。这样做是因为我希望每个食谱都列在页面上。为了使页面更清晰,我添加了标题。我添加了 `my-3` 来增加上边距和下边距,以便在标题和食谱列表之间留出间距。接下来,我遍历每个食谱并显示它们。以下是用户查看食谱时看到的内容:

我的食谱列表

这很棒,但如果用户登录后没有任何食谱呢?他们会看到“我的食谱”标题和一个空白页面。这样的设计不够人性化。所以我们来修改一下,让它显示更友好的内容。我们会添加一个按钮,点击即可跳转到该menu页面。在我们的模板中,我们会添加这个按钮。为了让按钮跳转到菜单页面,我们可以to=”/menu”在按钮中添加一些代码。以下是我的组件最终模板About.vue

<template>
    <v-container >
        <v-layout column>
            <h1 class="title my-3">My Recipes</h1>
            <div v-for="(item, idx) in userRecipes" class="subheading mb-2" :key="idx">
                {{item}}
            </div>
            <v-flex mt-4>
                <v-btn color="primary" to="/menu">Go To Menu</v-btn>
            </v-flex>
        </v-layout>
    </v-container>
</template>
Enter fullscreen mode Exit fullscreen mode

在导航中显示个人资料

最后,我们需要添加在导航栏中显示个人资料链接的功能。与注销按钮一样,此链接仅在用户通过身份验证后显示。打开 AppNavigation 组件。我们将个人资料按钮和注销按钮放在一个 div 元素中。这与我们之前对登录和登录按钮的处理方式相同Sign InJoin添加一个 div 元素,并将注销按钮移到该 div 元素内。再添加一个登录按钮profile。此按钮将与登录按钮一样,采用扁平化设计Sign In。以下是我的 AppNavigation 组件现在的样子:

<template>
    <span>
        <v-navigation-drawer app v-model="drawer" class="brown lighten-2" dark disable-resize-watcher>
            <v-list>
                <template v-for="(item, index) in items">
                    <v-list-tile :key="index">
                        <v-list-tile-content>
                            {{item.title}}
                        </v-list-tile-content>
                    </v-list-tile>
                    <v-divider :key="\`divider-${index}\`"></v-divider>
                </template>
            </v-list>
        </v-navigation-drawer>
        <v-toolbar app color="brown darken-4" dark>
            <v-toolbar-side-icon class="hidden-md-and-up" @click="drawer = !drawer"></v-toolbar-side-icon>
            <v-spacer class="hidden-md-and-up"></v-spacer>
            <router-link to="/">
                <v-toolbar-title to="/">{{appTitle}}</v-toolbar-title>
            </router-link>
            <v-btn flat class="hidden-sm-and-down" to="/menu">Menu</v-btn>
            <v-spacer class="hidden-sm-and-down"></v-spacer>
            <div v-if="!isAuthenticated" class="hidden-sm-and-down">
                <v-btn flat to="/sign-in">SIGN IN</v-btn>
                <v-btn color="brown lighten-3" to="/join">JOIN</v-btn>
            </div>
            <div v-else>
                <v-btn flat to="/about">PROFILE</v-btn>
                <v-btn outline color="white" @click="logout">Logout</v-btn>
            </div>

        </v-toolbar>
    </span>
</template>
Enter fullscreen mode Exit fullscreen mode

添加路由守卫

目前,用户可以通过在浏览器地址栏中输入网址来访问个人资料页面。我们不希望未登录的用户能够访问该页面。Vue Router 提供了在跳转到特定网址之前添加路由守卫的功能。我们希望在允许用户重定向到该/about页面之前,先验证其是否已通过身份验证。

打开router.js文件。路由守卫需要与 meta 标签配合使用。找到/about路由。我们将authRequired为其添加一个 meta 标签。路由应该如下所示:

{
    path: '/about',
    name: 'about',
    component: () =&gt; <em class="markup--em markup--pre-em">import</em>('./views/About.vue'),
    meta: {
        authRequired: <em class="markup--em markup--pre-em">true
    </em>}
},
Enter fullscreen mode Exit fullscreen mode

路由守卫在 Vue Router 的 beforeEach 方法中进行检查。该方法接收三个参数:

  • 你要去的路线
  • 你来的那条路
  • 下一个方法将继续沿用当前路线

我们的 beforeEach 方法会检查每个路由是否包含 authRequired 元标签。如果包含,它会检查用户是否已通过身份验证。如果用户未通过身份验证,则会将其重定向到页面/sign-in。如果用户已登录,则允许路由继续执行。如果用户路由到任何不包含 authRequired 元标签的页面,则路由将继续执行。以下是我添加到路由器中用于执行此检查的方法:

router.beforeEach((to, from, next) =&gt; {
    <em class="markup--em markup--pre-em">if </em>(to.matched.some(record =&gt; record.meta.authRequired)) {
        <em class="markup--em markup--pre-em">if </em>(!store.state.user) {
            next({
                path: '/sign-in'
            });
        } <em class="markup--em markup--pre-em">else </em>{
            next();
        }
    } <em class="markup--em markup--pre-em">else </em>{
        next();
    }
});
Enter fullscreen mode Exit fullscreen mode

获取代码

虽然这是一个四部分组成的系列教程,但你可以在我的 GitHub 账户中找到最终的代码。如果你下载了代码,请帮我给这个仓库点个星标,以示支持。

概括

在本系列文章的这一部分,您已经学习了:

  • 什么是 Firebase?
  • 使用 Firebase 对使用电子邮件和密码登录的用户进行身份验证
  • 使用 Firebase 存储用户订购的菜谱
  • 使用路由守卫来阻止未认证用户访问页面。
  • 在 Firebase 数据库中显示用户菜谱列表

培训课程

我在我的网站 CodePrep 上创建培训课程。课程内容涵盖 Vue、Webpack、Flexbox、函数式编程等等。点击这里查看。

文章来源:https://dev.to/ratracegrad/how-to-build-a-single-page-application-using-vue-js-vuex-vuetify-and-firebase-part-4-of-4-51hh