前言:本文是「Koa2 实战:从零构建 Node+Koa2+MongoDB+Vue 移动端全栈项目」的第二篇,如果您还没有阅读第一篇,建议先阅读第一篇,再看本文。
在前一篇文章中,我们已经成功的搭建了我们的项目架构,学习了如何使用 Koa2 进行接口开发。接下来,我们将更进一步,学习使用 MongoDB 存储我们的数据,同时使用 Vue.js 开发我们的前端项目。
数据库与数据模型
在本项目中,我们选择使用 MongoDB 作为我们的数据库,这个决定的原因主要是 MongoDB 的数据存储方式和格式,比较契合我们这种非关系型数据的存储需求。
安装 MongoDB
首先,我们需要安装 MongoDB,可以在官网中下载对应系统的 MongoDB 安装包,安装完成后,我们需要手动创建 MongoDB 的数据保存路径。
假设我们的 MongoDB 数据是保存在本地的 data 目录中,在命令行中运行以下命令:
mkdir ~/data mkdir ~/data/db
Mongoose
作为 Node.js 中一种非常成熟的 ORM 框架,Mongoose 提供了非常方便的 API 以使得我们能够轻松地连接 MongoDB 数据库,并且操作数据模型。我们可以使用 npm 或者 yarn 进行安装。
npm install mongoose --save
数据模型设计与定义
在项目中,我们的常见数据模型包括用户、文章、评论等功能模块。下面,让我们以用户模型为例,简单介绍如何定义一个数据模型。
在 server/models
目录下,我们新建一个 user.js
文件,这是我们定义用户模型的文件。我们在文件中使用 Mongoose 的 Schema
和 model
,实现定义和导出一个 User
类。
const mongoose = require('mongoose') const Schema = mongoose.Schema const UserSchema = new Schema({ userName: { type: String, unique: true, required: true }, password: { type: String, required: true }, email: { type: String, unique: true, required: true }, avatar: { type: String }, description: { type: String }, following: [ { type: Schema.Types.ObjectId, ref: 'User' } ], followers: [ { type: Schema.Types.ObjectId, ref: 'User' } ], }, { timestamps: true }) const User = mongoose.model('User', UserSchema) module.exports = User
在上述代码中,我们定义了 User
的 Schema 设计模型,包含了:
userName
:用户名,通过unique
、required
等策略来保证唯一性和必要性。password
:密码,同样需要加密和保护,在我们后面的实现中,我们会学习到如何进行密码处理和保护。email
:邮箱,同样需要确保唯一性和必要性。avatar
:用户头像,不是必需项,因此不需要加required
属性。description
:用户描述,不是必需项,因此不需要加required
属性。following
、followers
:这里是用户与用户之间的关注关系,这里使用了 mongoose 自带的ref
语法,来实现 User 之间的引用。
注意这里是如何使用 Mongoose Schema 实现定义数据模型的。
连接数据库
在现代开发中,我们的代码往往分为 controller、model、service 三个逻辑层。model 层提供对数据存储、关系维护的支持。因此,在我们的项目中,我们需要编写一个 MongoDB 连接的模块进行数据库的连接操作。
在 server/connectDB.js
文件中,我们实现 MongoDB 的连接和操作。
const mongoose = require('mongoose') const dbUrl = 'mongodb://localhost:27017/koa2todolist' mongoose.connect(dbUrl, { useNewUrlParser: true, useUnifiedTopology: true }) mongoose.connection.on('connected', () => { console.log(`Mongoose connection open to ${dbUrl}`) }) mongoose.connection.on('error', (err) => { console.log(`Mongoose connection error: ${err}`) }) mongoose.connection.on('disconnected', () => { console.log(`Mongoose connection disconnected`) }) module.exports = mongoose
在上述代码中,我们使用 mongoose.connect()
进行数据库连接,对于连接成功、失败、断开等情况使用 mongoose.connection.on()
绑定事件监听器,以便于在连接过程中不同的阶段中,向控制台输出对应的信息。
应用修改
上述步骤都完成后,我们需要将上述代码整合到我们的项目中。首先,我们在 app.js
中引入 mongoose
,并且调用 connectDB.js
。
const mongoose = require('./connectDB') mongoose.Promise = global.Promise
在上述代码中,第一行代码引用了 mongoose
,并且 connectDB.js
建立了连接,这里需要指定 Promise
类型为全局的 Promise
。
使用 MongoDB 存储用户
接下来,我们要为我们的注册接口实现数据保存操作,更具体的,就是将用户数据存入到 MongoDB 的 Users
集合中。下面是实现 register
API 的核心代码。
const User = require('../models/user') module.exports = { async register(ctx) { try { const { userName, password, email } = ctx.request.body const user = new User({ userName, password, email }) const result = await user.save() ctx.body = { success: true, data: result } } catch (err) { ctx.body = { success: false, message: err.message } } } }
在上述代码中,我们引入 user
的数据模型,并且使用 ctx.request.body
实现读取出用户提交的用户名、密码、邮箱等数据,并且将其实例化成一个 User 对象,通过实例对象的 save()
方法实现保存操作。
前端项目搭建
接下来,我们使用 Vue.js 来搭建我们的前端项目。在本项目中,我们将会使用 Vue.js 进行开发。
在开始这一步之前,我们需要了解关于 Vue.js 的常用开发模式、框架和插件。 比如说:
- 模块化开发:使用 Vue.js 的组件开发模式,将页面拆分成多个组件,以适应复杂页面的设计。
vue-router
:前端路由,实现 SPA(单页面应用程序)。vuex
:前端状态管理,用于管理全局状态和数据。
创建 Vue 项目
在命令行中输入以下命令创建一个新的 Vue 项目:
vue create my-project
这里使用了 Vue.js 提供的脚手架工具创建了一个新的 Vue.js 项目。这里面可以进行配置、安装插件等操作,其中可以选用典型的 Babel 7+Linter/Formatter 和 ESlint + Prettier/Standard,以进行代码风格管理。
在创建成功之后,可以使用以下命令启动项目:
cd my-project npm run serve
这样,在运行时,你可以在浏览器中通过 http://localhost:8080/
访问到我们的 Vue 项目。
安装依赖
由于我们的项目中要用到很多依赖项,因此,在进入项目目录后,先使用以下命令安装我们的依赖:
yarn add axios moment vee-validate babel-runtime vue-router vuex vuex-persistedstate vuex-router-sync
这里简单介绍一下这些依赖的作用:
axios
:处理前端与后端接口交互。moment
:用于处理时间戳格式。vee-validate
:功能强大、灵活的表单数据验证器。babel-runtime
:用于转换 ES6 代码为 ES5 代码。vue-router
:实现前端路由。vuex
:用于前端状态管理。vuex-persistedstate
:状态持久化,保证状态管理数据不被刷新清空。vuex-router-sync
:路由管理、状态管理同步处理。
项目目录结构设计
在前端项目中,我们使用 Vue.js 进行开发,而在 Vue.js 的项目开发中,我们的代码往往规划为:
components
:存储 Vue 公共组件。router
:Vue Router 模块。store
:Vuex 模块。views
:存储项目页面。App.vue
:根组件。main.js
:入口文件。static
:存储静态资源。public
:存储公共资源和模板 HTML。
下面是我们的项目结构图:
my-project/ |-- src/ | |-- router/ | |-- store/ | |-- views/ | |-- components/ | |-- assets/ | |-- utils/ | |-- App.vue | |-- main.js | |-- vendor.js |-- dist/ |-- node_modules/ |-- package.json |-- babel.config.js
在上面的项目结构中,我们使用了常见的 web 开发者的一种目录结构设计方式,使得每个模块、组件、页面之间可以相互独立并且呈现出严谨的目录结构。
配置 Vue.js
在使用 Vue.js 进行开发时,我们需要在 public/index.html
的 head
标签中引入 vue.js
。
<body> <div id="app"></div> <!-- built files will be auto injected --> <script src="/js/vue.js"></script> </body>
引入 Vue Router
我们使用 vue-router 实现前端路由功能。在本项目中,我们需要配置以下两个文件:路由配置 router.js
和路由守卫配置 auth.js
。
在 router.js
文件中,我们可以简单地定义路由配置并且使用 Vue Router 中的 router.beforeEach()
实现路由守卫。
import Vue from 'vue' import Router from 'vue-router' import store from '../store' Vue.use(Router) const routes = [ { path: '/', name: 'Home', component: () => import('@/views/Home.vue'), }, { path: '/login', name: 'Login', component: () => import('@/views/Login.vue'), }, { path: '/register', name: 'Register', component: () => import('@/views/Register.vue'), }, { path: '/article/:id', name: 'ArticleDetail', component: () => import('@/views/ArticleDetail.vue'), }, ] const router = new Router({ mode: 'history', base: process.env.VUE_APP_BASE_URL, routes }) router.beforeEach(async (to, from, next) => { const isLogin = store.getters.token && store.getters.username if (to.path === '/login' || to.path === '/register') { // 如果是登录或注册页面,则直接让用户进入 next() } else if (!isLogin) { // 判断用户是否登录 if (localStorage.getItem('username') && localStorage.getItem('token')) { store.commit('SET_TOKEN', localStorage.getItem('token')) store.commit('SET_USERNAME', localStorage.getItem('username')) next() } else { next('/login') } } else { next() } }) export default router
在上述代码中,我们首先引入了 Vue Router,使用 Vue.use(Router)
进行初始化。然后在 routes
中定义了几个常见的路由规则,并且使用 router.beforeEach()
配置路由守卫功能。如果用户没有登录,并且目标路径不是 /login
,我们要求其跳转到登录页面,如果用户已登录,则直接放行,即进入到目标路由对应的页面。
使用 Vuex 实现全局状态管理
在前端项目中,由于有很多数据需要在组件间传递和共享,如果每次都需要在组件间传递,那么就会非常繁琐,并且维护性也很差。而使用 Vuex,可以非常方便地实现全局状态管理,方案极佳。因此,在本项目中,我们将使用 Vuex 进行全局状态管理。
在 src/store/
目录下我们新建 index.js
作为我们的 Vuex Store。
在 index.js
文件中,我们将集中存储我们 Web 项目中常见的配置、常量、状态等数据。同时,也可以在这里定义数据修改的行为模块:
import Vue from 'vue' import Vuex from 'vuex' import createPersistedState from 'vuex-persistedstate' import { sync } from 'vuex-router-sync' import routers from '@/router' Vue.use(Vuex) const state = { isLoading: false, username: '', token: '' } const mutations = { SET_IS_LOADING: (state, isLoading) => { state.isLoading = isLoading }, SET_USERNAME: (state, username) => { state.username = username }, SET_TOKEN: (state, token) => { state.token = token } } const actions = {} const plugins = [ createPersistedState({ storage: window.localStorage, reducer (val) { return { // 只存储localStorage中需要持久化的数据。 token: val.token, username: val.username } } }) ] const store = new Vuex.Store({ state, mutations, actions, plugins }) sync(store, routers) export default store
在 index.js
中,我们使用了常见的配置 state
、mutations
、actions
,以及使用 Vuex 插件进行状态持久化。使用 sync()
配置路由状态和 store 状态的绑定。
通常情况下,外部组件是无法理解 Vuex 的实现方式和原理的,因此我们使用 Vuex 中的 vuex-persistedstate
实现状态持久化,保证数据信息在页面刷新后也不丢失。
项目 API 模块设计
在 Vue.js 项目中,通常需要频繁调用 API 接口与服务器进行通讯,这里我们可以把所有接口都统一通过一个 api 模块来进行管理。
在 src/api
目录下,我们新建一个 index.js
文件用于管理所有的 APIs,我们需要针对不同实体进行管理。
在该文件中,我们会根据不同的接口实现不同的 RESTful API 风格。
import axios from 'axios' const apiBase = 'http://localhost:4000/api/' const instance = axios.create({ baseURL: apiBase, timeout: 30000 }) const request = (url, method, params = {}, data = {}, headers = {}) => { const options = { url, method: method.toLowerCase(), params, data, headers } options.headers['token'] = localStorage.getItem('token') || '' return instance(options) } const api = { // 用户模块 register(data) { return request(`users/register`, 'POST', {}, data) }, login(data) { return request(`users/login`, 'POST', {}, data) }, getUserInfo () { return request(`users/info`, 'GET') }, // 文章模块 getAllArticles() { return request(`articles`, 'GET') }, getArticleDetail(id) { return request(`articles/${id}`, 'GET') }, createNewArticle(data) { return request(`articles`, 'POST', {}, data) }, deleteArticleById(id) { return request(`articles/${id}`, 'DELETE') } } export default api
在上述代码中,我们使用了 Axios.js 来实现与服务器的接口交互,并且通过 api
对象暴露RESTful API 接口。对于接口调用,我们需要提供对应的参数、数据、请求方式,并且附带 token 等其他信息。
编写页面组件
在 Vue.js 中,我们可以通过编写组件来实现页面的构建,这里简单以首页(Home.vue
)为例,展示页面组件的编写实践:
<template> <div class="home-container"> <el-row> <el-col :span="14"> <ArticleList :articles="articles" :tag="tag" /> </el-col> <el-col :span="10"> <div class="home-right"> <div class="home-tags"> <h1 class="title">标签</h1> <el-tag @click="getArticlesByTag(tag)" :key="tag" v-for="tag in tags"> {{ tag }} <el-tag type="info">{{ tagCountList[tag] }}</el-tag> </el-tag> </div> </div> </el-col> </el-row> </div> </template> <script> import ArticleList from '@/components/ArticleList.vue' import api from '@/api' export default { name: 'Home', components: { ArticleList }, data() { return { tag: '', tags: [], tagCountList: {}, articles: [] } }, async created() { this.getArticleList() this.getTagList() }, methods: { async getArticleList() { const res = await api.getAllArticles() if (res.data.success) { this.articles = res.data.data || [] } }, async getTagList() { const res = await api.getTags() if (res.data.success) { const { tags, tagCountList } = res.data.data this.tags = tags this.tagCountList = tagCountList } }, async getArticlesByTag(tag) { this.tag = tag const res = await api.getArticlesByTag(tag) if (res.data.success) { this.articles = res.data.data } } } } </script>
在上述代码中,我们首先引入了查询用的 API 接口。使用 ArticleList
组件展现我们的文章列表,使用 Home-right
组件渲染右侧模块分区。
Home.vue
主要实现了以下功能:
- 使用了
api.getAllArticles()
获取文章列表,使用ArticleList
展示数据。 - 还使用了
api.getTags()
获取标签总数以及总数列表,使用tags
数组渲染标签列表,点击标签时可以再次查询对应标签的文章列表。
安装 Element UI
Element UI
是由饿了么前端团队所开发的一套符合Vue.js
美学的前端UI组件库,适用于快速构建Web应用界面。在本项目中,我们也会使用它进行后台管理页面的布局及组件构建。
在使用 npm
/ yarn
安装 Element UI 前,我们需要先安装 node-sass
、sass-loader
和 less
,在安装这些之前,我们需要使用管理员权限启动命令行(也称为“管理员模式”)。
npm install node-sass sass-loader less less-loader --save-dev # 或者你可以先全局安装 sass,下面的则可以不装 npm install -g sass
安装完上述依赖后,我们再来安装 Element UI
:
npm i element-ui -S
在我们需要使用 Element UI
的 Vue 组件中,我们需要将 Element UI
组件在该页面下,进行主题的统一调配:
// themes/element-variables.scss @import './../../node_modules/element-ui/packages/theme-chalk/src/index.scss'; // app.scss @import './themes/element-variables.scss'; $--color-primary: #007f53; @import './themes/global.scss';
上述样式文件是 Element UI
样式文件的一个扩展。在该样式文件中,我们可以更方便、快捷的修改 Element UI
组件中的样式。同样在全局 app.scss
文件中引入该样式文件,以确保样式覆盖和全局样式的展示统一性。
总结
在本篇文章中,我们学习了如何通过 MongoDB 存储我们的数据,并且利用 Vue.js 构建前端界面,同时使用 Vuex 实现了全局状态管理。通过组件、路由、状态管理等多种手段,我们成功地构建了一个功能完整的 web 项目,展示了项目的开发流程和实践方法。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65aa4bf2add4f0e0ff3e8607