参考材料：Bootstrap：弹出框和提示框效果以及代码展示Bootstrap 模态框（Modal）插件
将node和vue设置好了虚拟主机之后，在访问vue项目vuetest.cn时，在console里会跳出来[WDS] Disconnected!错误。不过奇怪的是好像服务都不受影响，对后端的增删改查都可以使用。百度了一下，找到两个办法，一个是把localhost改成127.0.0.1，另一个是把vue默认端口改成其它端口，但是试过了之后，发现都不行。继续找答案中...
Vue 3的默认端口是8080，如何修改呢？找到node_modules@vuecli-servicelibcommands下的serve.js文件，找到下面代码，将端口改成自己想要的即可。const defaults = { host: '0.0.0.0', port: 8081, https: false }
由于我目前在阿里云上安装的是WAMP服务，又要安装node.js的服务的话，就不能使用NGINX什么的，于是上网查了一下如何使用Apache来支持node.js和vue.js的服务，发现也并不难配置，使用ProxyPass即可。原理：使用WAMP的时候外面可以在服务器上配置多个虚拟空间，只需要在httpd-vhosts.conf文件里添加 记录，同时在host文件里里添加相应的127.0.0.1记录即可。当我们使用nodes.js和vue.js的时候，会在localhost:5000和localhost:8080分别提供后端和前端的服务。这时候，我们只需要添加两个 记录，使用ProxyPass代理到这两个端口即可。具体操作步骤如下：第一步：在http.conf文件里，将下面两句前面的#删除。LoadModule proxy_module modules/mod_proxy.so LoadModule proxy_http_module modules/mod_proxy_http.so 目的是开启proxy第二步：在httpd-vhosts.conf文件里，添加一个记录，的如： ServerName nodetest.cn ServerAlias nodetest.cn ProxyRequests off Order deny,allow Allow from all ProxyPass http://localhost:5000/ ProxyPassReverse http://localhost:5000/ ServerName vuetest.cn ServerAlias vuetest.cn ProxyRequests off Order deny,allow Allow from all ProxyPass http://localhost:8080/ ProxyPassReverse http://localhost:8080/ 第三步：打开C:WindowsSystem32driversetc的host文件，然后添加下面两条记录：127.0.0.1 nodetest.cn 127.0.0.1 vuetest.cn 第四步：重启Apache，访问nodetest.cn和vuetest.cn即可访问相应的服务。如果需要外网访问，将域名解析到服务器IP地址接口即可。总结：这个ProxyPass的设置甚好，使用这种方式，理论上在服务器上可以利用不同端口提供无数个服务。plus：不知道是不是幻觉，似乎使用http://127.0.0.1:8080比http://localhost:8080反应更快一点。
由于官网提供的云数据库实在是太难连接了，能连上的时候反应特别慢，有时候根本就不能连接，所以迫不得已自己安装一个mongodb数据库试试吧。下载资源安装教程这个教程比较简单一点安装过程中，被卡在这里了：2020-06-14T10:52:32.000+0800 I SHARDING [ftdc] Marking collection local.oplog.rs as collection version: <unsharded>按照网上查的教程，说新建一个powershell然后启动mongo命令就可以了，可是我TM的不行啊，崩溃了！安装这个东西太麻烦了，简直不想弄了。2020.12.18更新一下，解决了https://cloud.mongodb.com提供免费数据库无法连接的问题，其实是因为我用的个人热点导致IP地址发生了变化，在后台把现在的IP加入白名单就可以了。所以暂时先不安装mongodb了，等解决了目前的问题再继续吧。2020.12.21上午今天把前后端分离这个case搞定了，但是发现mongodb.com这个云数据库服务真是太慢了，点一下修改数据库再返回数据，需要好几秒钟，简直没有办法忍受，所以可能还是得自己把mongodb本地安装搞定，要不然真没法用。2020.12.21下午后来发现，其实我的mongodb本地竟然已经安装好了……设置了系统变量了之后，就可以直接使用mongo命令进入操作数据库了。我把node.js链接数据库的地址改成mongodb://127.0.0.1:27017/dbname，竟然就能够连上了！太让人惊喜了。当然后面肯定还会有用户和权限的设置要做，这个后面再学，现在至少我那个前后端分离的简单项目能在本地数据库跑了。
对Promise, resolve, reject, async, await的理解
之前刚刚开始接触node.js的时候就看了一段视频讲同步异步的，看得一头雾水，对这几个单词充满了疑问，看别的视频的时候看到别人新手敲进去，也不知道是为了什么，这两天才好好地研究了一下。不得不说选择视频真的是太重要了，同样的人来讲解一个事情，有些人讲了听得明白，有些人讲了也还是听不明白，有些人只需要讲10分钟，有些人讲了40分钟，也不一定是视频的人讲得不好，每个人的学习都要依靠自己目前的知识体系，只有能和自己的知识网络联系起来，才可能学得会。这次就是在Youtube上学的，一个叫Hitesh Choudhary的印度老哥讲的，视频只有12分钟，不过还是听明白了，下面讲一下的自己的理解。以下正文开始Promise，async和await一般是在请求资源的时候使用，因为请求资源的时候，对方给过来的反馈一般会比较慢（相对于继续执行后面的程序而言），这时候一般需要做一个设置，那就是等对方的反馈回来了之后再执行后面的程序，不过如果没有特别设置，js是不会这么做的，这时候如果有3个方法，中间那个需要请求，不做设置的话，第二个还没有等到拿到请求结果就输出了，结果就是undefined，因为没有拿到任何东西。举例： 执行上面的代码，two()会输出undefined，因为该方法需要3秒才能执行完，但是由于没有设置，还没有执行完就返回了，结果就是undefined。那么，如何才能让run()按顺序执行one() two() three()呢？一共分两步：第一步：在two()里面返回一个Promise，将two()改成：function two () { return new Promise((resolve, reject) => { setTimeout(() => { resolve('I am two') }, 3000) }) } 这段代码里，一个Promise会返回两个状态，resolve或者reject，基本上可以理解为success和error，在resolve里填写如果反馈成功后要返回的东西即可。第二步：调用的时候设置要async和await当你在定义一个类似run()方法时，如果这里面涉及到需要等待结果的方法（比如请求资源的），这时候要在这个方法前面加上async，表示这是一个包含异步请求的方法，然后在里面具体的需要等待结果的方法前面，加上await，也就是说，需要等这个方法获得了返回结果之后，再继续往下面执行。所以run()就可以改成这样： function run () { console.log(one()) console.log(two()) console.log(three()) } 这时候，整体代码变成这样： 再执行的话，就会发现one()先出结果，等3秒后，two()出结果，紧跟着three()的结果也出来了。这个就是promise, resolve, reject, async, await的最基础知识了吧，写出来加深一下理解，如果有初学者能看到，也许也可以获得一些启发。更复杂的知识，参考这里。
这几天在《软技能2》里又看到了一个概念叫测试驱动开发TDD，概念并不复杂，就是在开发之前先编写好测试用例，之后不断开发去实现测试用例里面描述的功能。不过我联想到的并不不仅仅是这个开发过程，而是有两点。第一，这其实就是《高效能人士的七个习惯》里面提到的，以终为始的一种应用，把最终效果写下来，不断去实现最终效果。第二，测试驱动开发让我想到测试驱动学习。我们要学习某个领域的知识，如果最终有一个测试能够帮助我们了解是不是已经掌握了需要的知识，那么只要了解这个测试的范围，我们就有了可以去学习的方向，同时带着动力和效率。考证是一个测试驱动学习的一个很好的应用，我想要学习项目管理，那么PMP就是一个测试，PMP考试的题目就是测试用例，我们用那些题目，来验证自己是不是掌握了相应的知识，一如我们用测试用例去验证我们是不是已经开发了相应的功能。同时我还想到，在孩子的教育上，也可以使用测试驱动学习。其实孩子需要掌握的知识体系已经非常完整地分布在一二三四五六年级，如果我需要看孩子掌握的情况，那么我可以使用每个年级的测试题，来决定现在孩子应该掌握什么知识，应该掌握到什么程度。这比看教学大纲和考试大纲更加实用。人类的行为有很多时候都是某种相通的东西。
学习资源：点击这里后端新建一个文件夹，npm init初始化项目。安装必要的组件：npm i express cors body-parser mongodbnpm 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里添加一个容器 。然后就是在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部分如下： 其中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的我就不太明白。第一次看见有人把 这么分开写的。 {{ `${post.createAt.getDate()}/${post.createAt.getMonth()}/${post.createAt.getFullYear()}` }} {{ post.text }} {{ post.createAt }} 最神奇的事情是，从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全部代码： Latest Posts {{ error }} Say Something... Post! {{ `${post.createAt.getDate()}/${post.createAt.getMonth()}/${post.createAt.getFullYear()}` }} {{ post.text }} 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的之后，这个项目的任督二脉就差发布到服务器上了，等我查查看怎么弄。
我之前以为nosql就是一个一个的key value的集合，觉得条件查询起来肯定会特别麻烦，没想到其实还挺完整的。需要进一步学习。基础入门的教程在这里。