学习资源:点击这里

后端

新建一个文件夹,npm init初始化项目。
安装必要的组件:npm i express cors body-parser mongodb
npm i nodemon -D

将package.json里的scripts更新为如下:

  "scripts": {
    "start": "node server/index.js",
    "dev": "nodemon server/index.js"
  },

创建server文件夹,创建index.js文件,输入初始代码:

const express = require('express');  //引入express
const bodyParser = require('body-parser');  //引入bodyParser
const cors = require('cors');  //引入cors

const app = express();  //新建express实例

//Middleware
app.use(bodyParser.json());   //app.use是使用中间件
app.use(cors());  //

const port = process.env.PORT || 5000;  //定义port

app.listen(port, () => console.log(`Server started on port ${port}`));  //监听

此时访问localhost:5000会返回Cannot GET /,因为没有设置路由。

在server文件夹下建立routes文件夹,在下面建立api文件夹,再下面建立posts.js文件。

在index.js里的中间件下添加:

const posts = require('./routes/api/posts');
app.use('/api/posts', posts);

目的是当访问/api/posts路径的时候,直接执行posts.js文件(目前是这么理解的)。

index.js的代码如下:

const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');

const app = express();

//Middleware
app.use(bodyParser.json());
app.use(cors());

const posts = require('./routes/api/posts');

app.use('/api/posts', posts);

const port = process.env.PORT || 5000;

app.listen(port, () => console.log(`Server started on port ${port}`));

posts.js的代码如下:

const express = require('express');
const mongodb = require('mongodb');

const router = express.Router();

// Get Posts
router.get('/', async (req, res) => {
    const posts = await loadPostsCollection();
    res.send(await posts.find({}).toArray());
});

// Add Post
router.post('/', async (req, res) => {
    const posts = await loadPostsCollection();
    await posts.insertOne({
        text: req.body.text,
        createAt: new Date()
    });
    res.status(201).send();
});

// Delete Post
router.delete('/:id', async (req, res) => {
    const posts = await loadPostsCollection();
    await posts.deleteOne({
        _id: new mongodb.ObjectID(req.params.id)
    });
    res.status(200).send();
})

async function loadPostsCollection(){
    const client = await mongodb.MongoClient.connect
    ('mongodb+srv://alex:abcABC123@cluster0.sapok.mongodb.net/vue_express?retryWrites=true&w=majority', {
        useUnifiedTopology: true
    });

    return client.db('vue_express').collection('posts');
}

module.exports = router;

使用postman就可以验证posts的增、删、查,改还不会。

前端

使用vue创建一个client的项目文件夹 vue create client。
进入client文件夹,使用npm run serve启动服务,浏览器输入localhost:8080即可查看。

要完成一个请求,需要几个文件。
首先是main.js文件,默认的不用改。
然后是App.vue文件,是模板主文件,主要是要import新建的component进来,script里添加:

import PostComponent from './components/PostComponent'
export default {
  name: 'app',
  components: {
    PostComponent
  }
}

之后在template里添加一个容器<post-service></post-service>。

然后就是在component里创建一个PostService.vue的模板,用来处理post(此处是帖子的意思,不是POST方法)相关的模板,包括template,script和style三个部分。

在main.js同级目录新建一个PostService.js文件,用来专门处理post向后端的请求,包括get/post/delete等。在PostService.vue里面import PostService.js进来,使用:

import PostService from '../PostService'

此时的请求路径是:
用户发起请求->main.js->App.vue->PostService.vue->PostService.js。

PostService.vue里script部分如下:

<script>
import PostService from '../PostService'

export default {
  name: 'PostComponent',
  data () {
    return {
      posts: '',
      error: '',
      text: ''
    }
  },
  async created () {
    const that = this
    try {
      const posts = await PostService.getPosts()
      that.posts = posts
    } catch (error) {
      that.error = error
    }
  }
}
</script>

其中created是一个生命周期函数(幸好之前研究小程序的时候了解过),也就是运行的时候自动执行的一个函数。
这里面要注意的是使用async和await,说明函数需要等PostService.getPosts()执行完毕之后再赋值给posts,如果把async和await去掉,就只会得到一个空的值。

按照我目前的理解(写在这篇文章里),有async和await必然会有Promise与之相对应的,这个Promise就在PostService.js里面,如下:

class PostService {
  // Get posts
  static getPosts () {
    return new Promise((resolve, reject) => {
      try {
        axios.get(url).then((res) => {
          resolve(res.data)
        })
      } catch (err) {
        reject(err)
      }
    })
  }
}

这里面使用了return new Promise((resolve, reject) => {})返回了axios请求api返回的数据。
感觉这样子之后,请求就相对比较顺畅了,可以继续往下搞了。

模板

使用从后端请求过来的数据在template里展示的时候,发现v-for需要同时使用v-bind进行绑定post, index和key,要不然就会报错,目前还不知道为什么,当然后面对每个item的操作肯定需要用到这些,不过为什么是require的我就不太明白。

第一次看见有人把<div>这么分开写的。

  <div class="post-container">
    <div class="post" 
      v-for = "(post, index) in posts" 
      v-bind:item = "post" 
      v-bind:index = "index" 
      v-bind:key = "post._id"
    >
      {{ `${post.createAt.getDate()}/${post.createAt.getMonth()}/${post.createAt.getFullYear()}` }}
      <p class="text">{{ post.text }}</p>{{ post.createAt }}
    </div>
  </div>

最神奇的事情是,从api结果返回来的时间,如果想使用getDate()、getMonth()、getFullYear()这种方式,会提示数据不存在,这个时候需要在获得数据的时候,将时间处理一下,才能使用这些方法。

处理的方法如下:

      //原来
      resolve(
        res.data
      )
      //处理
      resolve(
        res.data.map(post => ({
          ...post,
          createAt: new Date(post.createAt)
        }))
      )

使用了一个map()方法,将post重新过滤了一遍,我猜测这个方法是把post数组里每一个元素都过了一遍,...post是说别的元素都不改,只有这个createAt元素的时间,使用new Date()方法进行整理。

当然以上纯属猜测,搜索了一下,发现了这个说明文档,原来map()方法就是元素每个都执行一遍括号里的内容,不过...是什么呢?暂时还没有搜索到。

PostComponent.vue全部代码:

<template>
    <div class="container">
      <h1>Latest Posts</h1>
      <div class="error" v-if="error">{{ error }}</div>
      <div class="create-post">
        <label for="create-post">Say Something...</label>
        <input type="text" id="text" v-model="text" placeholder="create a post">
        <button @click="createPost">Post!</button>
      </div>
      <div class="post-container">
        <div class="post"
          v-for = "(post, index) in posts"
          v-bind:item = "post"
          v-bind:index = "index"
          v-bind:key = "post._id"
          @dblclick="deletePost(post._id)"
        >
          <p class="text">{{ `${post.createAt.getDate()}/${post.createAt.getMonth()}/${post.createAt.getFullYear()}` }} {{ post.text }}</p>
        </div>
      </div>
    </div>
</template>

<script>
import PostService from '../PostService'

export default {
  name: 'PostComponent',
  data () {
    return {
      posts: '',
      error: '',
      text: ''
    }
  },
  async created () {
    const that = this
    try {
      that.posts = await PostService.getPosts()
    } catch (error) {
      that.error = error
    }
  },
  methods: {
    async createPost () {
      const that = this
      await PostService.insertPost(this.text)
      that.posts = await PostService.getPosts()
    },
    async deletePost (id) {
      const that = this
      await PostService.deletePost(id)
      that.posts = await PostService.getPosts()
    }
  }
}
</script>

<style scoped>
.text {
  border: 1px solid black;
  padding: 20px auto;
  background-color: blanchedalmond;
}
</style>

PostService.js全部代码:

import axios from 'axios'

const url = 'http://localhost:5000/api/posts/'

class PostService {
  // Get posts
  static getPosts () {
    return new Promise((resolve, reject) => {
      try {
        axios.get(url).then((res) => {
          resolve(
            res.data.map(post => ({
              ...post,
              createAt: new Date(post.createAt)
            }))
          )
        })
      } catch (err) {
        reject(err)
      }
    })
  }

  // Create posts
  static insertPost (text) {
    return axios.post(url, {
      text
    })
  }

  // Delete posts
  static deletePost (id) {
    return axios.delete(`${url}${id}`)
  }
}

export default PostService

至此前后端就算连接上了,使用的时候发现,这个数据库的请求可真费时间啊。这么看搞一个local的数据库还是非常必要的啊。

发布到Heroku

首先注册heroku的时候需要翻墙,不翻墙就看不到验证码,就无法注册。

然后,在client文件夹下面,新增一个文件叫vue.config.js,内容是:

const path = require('path')

module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:5000'
      }
    }
  }
}

然后把PostService.js里的url改成='api/posts'
重启vue之后,又可以访问了。
坦白讲我不是太知道为什么,这个vue.config.js是预定义的文件名吗?会自动读取到所有文件里吗?为什么没有引入PostService.js就可以使用?里面这堆code是啥意思?

之后,在devServer上面,添加一行:

outputDir: path.resolve(__dirname, '../server/public'),

这个似乎是要定义webpack把文件pack到什么地方去。

之后在vue的console下先停止serve,然后输入npm run build,就会在server文件夹下面打包了一个public文件夹,里面都是前端的内容。

之后要改一下production环境里面对路径的处理,在server/index.js里添加:

好吧,之后一直发布一直说是Application Error,试了无数次还是没有解决,暂时先放一下吧。

发布

mongodb改成local的之后,这个项目的任督二脉就差发布到服务器上了,等我查查看怎么弄。

最后编辑:2021年01月05日 ©著作权归作者所有

发表评论