技术栈
Vue、Vue Router、NodeJS、MongoDB、memory-cache、js-cookie、iconfn.cn、iscroll、vuex
用 Vuex 管理状态
// store/index.js
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
count: 0,
count2: 10000
},
mutations: {
increment: state => state.count++,
decrement: state => state.count--
}
})
// main.js
import Vue from 'vue'
import store from './store'
new Vue({
store
})
优化点
margin 改用 padding后计算scroller的宽度就会简单很多
issue11201748
如何拿到 margin 等值
IE9+
window.getComputedStyle(element)
https://stackoverflow.com/questions/14275304/how-to-get-margin-value-of-a-div-in-plain-javascript
https://j11y.io/jquery/#v=1.11.2&fn=jQuery.fn.outerWidth
component lifecycle mounted ( 操作 DOM )
https://alligator.io/vuejs/component-lifecycle/
https://codingexplained.com/coding/front-end/vue-js/accessing-dom-refs
issue 11201528 (Done)
iscroll chrome pc 下正常,mobile 下不能正常使用
解决方案:
html {
touch-action: none;
}
参考资料: https://github.com/cubiq/iscroll/issues/1157
区域滚动 iscroll
采用的方案
\iscroll\demos\horizontal
https://www.npmjs.com/package/iscroll
http://caibaojian.com/iscroll-5/init.html
唯品会首页区域滚动
flexbox
IE10+
https://www.cnblogs.com/yangjie-space/p/4856109.html
http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html
style guide
// main.js
import tools from 'tools'
Vue.prototype.$_m_tools = tools // $_yourPluginName_
https://cn.vuejs.org/v2/style-guide/index.html
媒体查询实现方法 ( 采用 )
// index.html
唯品会手机购物正品商城:全球精选 正品特卖手机版
const html = document.documentElement || document.querySelector('html')
let width = window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth
console.log('ff' + width) // 打开页面会执行 2 次,为什么?
if (width <= 540) {
html.style.fontSize = width / 10 + 'px'
} else {
html.style.fontSize = '54px'
}
window.onresize = function () {
width = window.innerWidth
|| document.documentElement.clientWidth
|| document.body.clientWidth
console.log(width)
if (width <= 540) {
html.style.fontSize = width / 10 + 'px'
} else {
html.style.fontSize = '54px'
}
}
Element.clientWidth ( read only )
https://developer.mozilla.org/en-US/docs/Web/API/Element/clientWidth
window.matchMedia polyfill ( 不采用 )
IE9+
polyfill https://github.com/paulirish/matchMedia.js/
Document
window.matchMedia 兼容性 ( 不采用 )
IE10+
Vip 媒体查询
屏幕像素 540px html 元素 font-size: 54px
屏幕像素 320px html 元素 font-size: 32px
···
媒体查询 ( 不采用 )
https://www.jianshu.com/p/d4b250b81ce5
单文件 export default 外的 JS 代码所有路由中都会执行? ( 基本成立--已测试 )
issue 11191220 (Done)
通过写 demo 搞定的(重要!!!)
为什么过滤不掉? _id: 0... 不起作用
仅在 shell 中起作用
db.collection('users').findOne({ "phoneNumber": 18521444717}, {_id: 0}, function (err, item) {
console.log(item)
})
NodeJS 中起作用
db.collection('users').findOne({ "phoneNumber": 18521444717}, {projection: {_id: 0}}, function (err, item) {
console.log(item)
})
https://stackoverflow.com/questions/52053003/nodejs-mongodb-projection-not-working
https://docs.mongodb.com/manual/reference/method/db.collection.findOne/
MongoDB updateOne
db.users.updateOne({ "phoneNumber": 18521447788 },
{ $set: { userFavProduct: [{ id: 1, price: 3000, imgUrl: 'example.jpg' }] } })
MongoDB 条件查找
MongoDB查询操作限制返回字段的方法
db.users.find({ phoneNumber: 18566998877, userFavProduct: { $exists: true, $not: { $size: 0 } } })
db.users.findOne({"phoneNumber":18521592149, userFavProduct: {$not: {$size:0}}}, {_id: 0, userFavStore: 0, userFavBrand: 0, phoneNumber: 0}) // 值为 0 的字段不显示
https://docs.mongodb.com/manual/reference/method/db.collection.findOne/
https://stackoverflow.com/questions/14789684/find-mongodb-records-where-array-field-is-not-empty
https://docs.mongodb.com/manual/tutorial/query-for-null-fields/
http://www.runoob.com/mongodb/mongodb-operators.html
MongoDB 删除 collection
db['user-fav'].drop() // true
https://www.tutorialkart.com/mongodb/mongodb-delete-collection/
如何实现路由保护?导航守卫 (Done)
闵:登录完成 返回 token 时,同时返回个登录状态存在前端,通过判断登录状态实现路由保护
双重验证:
通过 API 获取进行路由保护的页面的数据时,再进行 token 验证
In-Component Guards
// UserFavProduct
export default {
beforeRouteEnter: function (to, from, next) {
const status = localStorage.getItem('status')
if (status) {
next()
return
}
next('/')
}
}
https://segmentfault.com/q/1010000014500261?sort=created
https://router.vuejs.org/zh/guide/advanced/navigation-guards.html
issue 11181805 (Done)
/login 接口 重定向失败
Ajax 请求不能重定向
解决方案:
location.href
https://stackoverflow.com/questions/199099/how-to-manage-a-redirect-request-after-a-jquery-ajax-call
https://segmentfault.com/q/1010000009661851
issue 11181703 (Done)
不太靠谱的调试方法
断点调试.mp4
VSCode Node 、 Chrome Node 、 get 路由调试失败的原因
思路有问题:
失败的原因,前端没有对应路由?
app.use(history()) // 导致失败的原因
解决方案:
注释 app.use(history())
后正常
debugging nodejs with chrome
断点无效,先放一边
https://medium.com/the-node-js-collection/debugging-node-js-with-google-chrome-4965b5f910f4
淡入淡出 (尝试用了一下,后面再改善 2018年11月19日17:23:10)
Vue 原生支持
https://blog.csdn.net/SilenceJude/article/details/82221348
https://cn.vuejs.org/v2/guide/transitions.html#ad
受保护的路由 几例
待收货 https://m.vip.com/user-order-list-unreceive.html
待付款 https://m.vip.com/user-order-list-unpay.html
全部订单 https://m.vip.com/user-order-list-more.html
申请售后 https://m.vip.com/user-order-list-return.html
我的收藏 https://m.vip.com/user-fav-brand.html
修改登录密码 https://m.vip.com/user-updatepwd.html
VIP 登录请求中的 参数
登录 / 登出状态 Cookie 差异
待完成
JWT
JWT
Usage
jwt.sign(payload, secretOrPrivateKey, [options, callback])
There are no default values for expiresIn, notBefore, audience, subject, issuer
. These claims can also be provided in the payload directly with exp, nbf, aud, sub and iss respectively, but you can't include in both places
.
const jwt = require('jsonwebtoken')
/**
* payload 和 options 中都声明了过期时间
* - Bad "options.expiresIn" option the payload already has an "exp" property.
*/
jwt.sign({exp: (Date.now() / 1000) + 60, username: 'jack'}, 'secretkey', {expiresIn: 60})
后台生成 JWT 后如何传递给前端?
jwt.sign({ user }, 'secretkey', { expiresIn: '30s' }, (err, token) => {
res.json({
token // 作为 response content
})
})
https://github.com/MonguDykrai/JWT-Demo
https://www.youtube.com/watch?v=7nafaH9SddU
https://www.jianshu.com/p/a7882080c541
JWT example
install: yarn add jsonwebtoken
// D:\Study\JSON Web Token\jwt-001
const jwt = require('jsonwebtoken')
const secret = 'aaa' // 撒盐:加密的时候混淆
// jwt生成token
const token = jwt.sign({
name: 123
}, secret, {
expiresIn: 10 //秒到期时间
})
console.log(token)
// 解密token
jwt.verify(token, secret, function (err, decoded) {
if (!err) {
console.log(decoded)
console.log(decoded.name) // 会输出123,如果过了10秒,则有错误。
}
})
setTimeout(function () {
jwt.verify(token, secret, function (err, decoded) {
console.log(decoded) // undefined
if (err) throw err // 将 decoded.exp 的值 和 当前时间戳做比较,确认 token 是否已过期?
// 不会执行
if (!err) {
console.log(decoded)
console.log(decoded.name)
}
})
}, 11000)
js-cookie
install: yarn add js-cookie
import Cookies from 'js-cookie'
Cookies.set(key, value)
Cookies.get(key)
Cookies.remove(key)
https://www.npmjs.com/package/js-cookie
安装 windows 版 MongoDB 方便本地调试
- 下载安装包 mongodb-win32-x86_64-2008plus-ssl-4.0.4-signed.msi
- 默认安装
- 管理员权限 打开 CMD 执行 net start MongoDB 命令
- 执行 mongo.exe
https://www.mongodb.com/download-center/community
https://docs.mongodb.com/manual/tutorial/install-mongodb-on-windows/#start-mdb-edition-as-a-windows-service
what's next
http://app.liuchengtu.com/
待解决
自动完成
git push / git pull (频繁修改频繁提交)
本地连接线上 mongodb (无法进行调试)
解决方案:装 windows 版,方便调试
github免密码提交 (频繁修改频繁输密码)
登录接口测试截图 (Done)
获取验证码接口测试截图(Done)
input type number maxlength do not work (Done)
解决方案:
用 tel 替代 number
https://stackoverflow.com/questions/18510845/maxlength-ignored-for-input-type-number-in-chrome
SSH github
Generating a new SSH key
ssh-keygen -t rsa -b 4096 -C "[email protected]"
iptables
https://blog.csdn.net/irokay/article/details/72717132
https://wiki.centos.org/HowTos/Network/IPTables
https://www.howtogeek.com/177621/the-beginners-guide-to-iptables-the-linux-firewall/
MongoDB 其它 IP 访问 (working on)
https://blog.csdn.net/weixin_42529849/article/details/80786426
https://www.jianshu.com/p/f9f1454f251f
https://stackoverflow.com/questions/22054856/using-meteor-mongo-on-localhost-but-with-remote-database
https://docs.mongodb.com/manual/tutorial/enable-authentication/
history.pushState
https://developer.mozilla.org/zh-CN/docs/Web/API/History_API
expressjs Router-level middleware
每次请求都会执行相应代码的中间件
const express = require('express')
const app = express()
const router = express.Router() //
// a middleware function with no mount path. This code is executed for every request to the router
router.use(function (req, res, next) {
console.log('Time: ', Date.now())
next()
})
memory-cache 如何被共享 (Done)
将 cache 放 res 对象上 / express 中间件
// middlewares/memory-cache.js
const cache = require('memory-cache')
const installMemoryCache = function (req, res, next) {
res.cache = cache
next()
}
module.exports = installMemoryCache
// app.js
const express = require('express')
const app = express()
const installMemoryCache = require('./middlewares/memory-cache')
app.use(installMemoryCache)
app.get('/login', function (req, res, next) {
const { cache } = res
cache.put(18521447788, { captcha: 123456 })
})
app.get('/about', function (req, res, next) {
const { cache } = res
console.log(cache.get(18521447788)) // { captcha: 123456 }
cache.put(18521477829, {captcha: 'abcedf'})
})
app.get('/list', function (req, res, next) {
const { cache } = res
console.log(cache.keys()) // [ "18521447788", "18521477829" ]
})
app.listen(8080, function () {
console.log('http://localhost:8080')
})
express 中间件
https://expressjs.com/en/guide/using-middleware.html
https://medium.com/the-node-js-collection/simple-server-side-cache-for-express-js-with-node-js-45ff296ca0f0
封装数据库查询的方法 (Done)
查询是否为已注册用户
// query.js
const findOne = function (phoneNumber, callback) {
const MongoClient = require('mongodb').MongoClient
const assert = require('assert').strict
const url = 'mongodb://127.0.0.1:27017'
const dbName = 'vip'
const client = new MongoClient(url)
client.connect(function (err) {
assert.strictEqual(null, err)
console.log('Connected successfully to server')
const db = client.db(dbName)
const collection = db.collection('users')
collection.findOne({ phoneNumber: Number(phoneNumber) }, function (err, item) {
assert.strictEqual(null, err)
callback(item)
client.close(function (err) {
assert.strictEqual(null, err)
console.log('db has been closed.')
})
})
})
}
module.exports = findOne
// app.js
var findOne = require('../query')
findOne(phoneNumber, function (item) {
console.log(item)
})
验证码 存缓存里 (Done)
https://www.npmjs.com/package/memory-cache
Memory Cache NodeJS
demo:
// app.js
console.time('t1')
const cache = require('memory-cache')
cache.put(18521447789, { captcha: 123456 })
console.log(cache.get(18521447789))
console.log(cache.get(18521447789).captcha)
cache.clear()
console.log(cache.keys())
const obj = { a: 1 }
cache.put(obj, 333)
console.log(cache.get(obj))
console.timeEnd('t1') // ≈ 8ms
/**
* 执行结果:
* { captcha: 123456 }
* 123456
* []
* 333
*
*/
key 是唯一的
console.time('t1')
const cache = require('memory-cache')
cache.put(18521447789, { captcha: 123456 })
cache.put(18521447789, { captcha: 123456 })
cache.put(18521447781, { captcha: 123456 })
console.log(cache.keys()) // [ '18521447789', '18521447781' ]
短信验证登录
- 获取手机号
- 生成验证码
- 将手机号和验证码存入缓存 (设置缓存有效时间和短信中提示的一致)
- 调用验证码短信发送接口 (手机号和验证码为参数)
- 用户输入验证码点击登录,后台获取手机号和验证码
- 校验获取的手机号和验证码是否与缓存中的一致
- 如果一致登录成功返回登录成功标识,否则返回超时或验证码输入错误等提示信息
短信验证登录参考文档:
https://blog.csdn.net/zxllynu/article/details/78705560
其它资料:
https://redis.io
https://scotch.io/tutorials/how-to-optimize-node-requests-with-simple-caching-strategies
https://www.npmjs.com/package/node-cache
NodeJS mongodb
唯一键
// 唯一键 ( 适合一次性设置好,不适合频繁访问数据库时设置 )
db.users.createIndex({ "phone" : 1 /* 似乎没有特别要求,写其它值也可以 */ }, { "unique" : true })
db.users.insertOne{ "phone" : 18521447125 })
/**
* AssertionError [ERR_ASSERTION]: null ==
* { MongoError: E11000 duplicate key error collection: vip.users index: phone_1 dup key: { : 18521447125.0 }
*/
db.users.insertOne{ "phone" : 18521447125 })
查询不到
// 找不到 返回 null
/**
* exsit: { "_id" : ObjectId("5bebbcf89b404ac559ee588c"), "name" : "jack" }
* otherwise: null
*/
> db.users.findOne({ "name" : "jack" })
close
// https://mongodb.github.io/node-mongodb-native/api-generated/mongoclient.html
client.close(function (err) {
})
重要参考资料:
https://mongodb.github.io/node-mongodb-native/api-generated/collection.html
NodeJS assert
// app.js
const assert = require('assert')
console.log(assert.equal(1, 2))
// app.js
const assert = require('assert').strict
console.log(assert.strictEqual(1, 2))
参考资料:
https://nodejs.org/api/assert.html#assert_assert_equal_actual_expected_message
https://nodejs.org/api/assert.html#assert_assert_strictequal_actual_expected_message
MongoDB collection methods
> show dbs;
> use vip;
> db.createCollection('users');
/**
* https://docs.mongodb.com/manual/reference/method/db.collection.insertOne/#db.collection.insertOne
* db.getCollection('users') | db['users']
*/
> db.users.insertOne({ "name" : "jack" });
{
"acknowledged" : true,
"insertedId" : ObjectId("5bebbcf89b404ac559ee588c")
}
> db.users.find(); // https://docs.mongodb.com/manual/reference/method/db.collection.find/#db.collection.find
{ "_id" : ObjectId("5bebbcf89b404ac559ee588c"), "name" : "jack" }
/**
* exsit: { "_id" : ObjectId("5bebbcf89b404ac559ee588c"), "name" : "jack" }
* otherwise: null
*/
> db.users.findOne({ "name" : "jack" })
/**
* https://docs.mongodb.com/manual/reference/method/db.collection.updateOne/#db.collection.updateOne
*/
> db.users.updateOne({ "name" : "jack" }, { { $set: { "name":"rose" } });
{ "_id" : ObjectId("5bebbcf89b404ac559ee588c"), "name" : "rose" }
> db.users.deleteOne({ "name" : "jack" });
{ "acknowledged" : true, "deletedCount" : 1 }
https://docs.mongodb.com/manual/reference/method/js-collection/
使用 MongoDB
mongo
show dbs; // 查看数据库
db.version(); // 查看数据库版本
db.help(); // 常用命令帮助
>show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
> use vip // The command will create a new database if it doesn't exist.
switched to db vip
> db.getName()
vip
> db.getCollectionNames()
[ ]
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
> use vip
switched to db vip
> db.getName()
vip
> db.createCollection('users')
{ "ok" : 1 }
> db.getCollectionNames()
[ "users" ]
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
vip 0.000GB
> use vip
switched to db vip
> db.dropDatabase() // Delete Database
{ "dropped" : "vip", "ok" : 1 }
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
参考资料:
https://www.tutorialspoint.com/mongodb/mongodb_create_database.htm
https://docs.mongodb.com/manual/reference/method/db.createCollection/
https://www.tutorialkart.com/mongodb/mongodb-delete-database/
MongoDb 几条指令
new
systemctl start mongod (A)
systemctl stop mongod (B)
cat /var/log/mongodb/mongo.log (C)
systemctl status mongod (D)
参考链接:
https://www.digitalocean.com/community/tutorials/systemd-essentials-working-with-services-units-and-the-journal
old
(1) (2) (3) 正常
service mongod start (1)
service mongod stop (2)
cat /var/log/mongodb/mongo.log (3)
chkconfig mongod on (4)
(4) 不正常
// Note: Forwarding request to 'systemctl enable mongod.service'.
chkconfig mongod on (4)
原因:
CentOS 7 no longer uses chkconfig and service commands / use systemctl
replace.
参考链接:
https://www.centos.org/forums/viewtopic.php?t=55834
导入 MongoDB 替代 users.json
安装
CentOS环境下安装 mongodb.mp4
主要参考资料
Centos环境下安装mongoDB
次要参考资料
https://docs.mongodb.com/manual/tutorial/install-mongodb-on-red-hat/#using-rpm-packages-recommended
其它
https://docs.mongodb.com/manual/installation/#x86-64
How To Set Up and Use Yum Repositories on a CentOS 6 VPS
https://docs.mongodb.com/manual/tutorial/getting-started/
vi / vim 使用教程
http://www.runoob.com/linux/linux-vim.html
what is yum / rpm
https://baike.baidu.com/item/yum/2835771?fr=aladdin
https://baike.baidu.com/item/RPM/3794648
云主机配置
CentOS 7.4 ( 可以安装 4.0 Community & Enterprise
)
NodeJS https / http
http://qugstart.com/blog/linux/quickest-way-to-create-a-self-signed-ssl-certificate-in-ubuntu/
http://qugstart.com/blog/node-js/node-js-restify-server-with-both-http-and-https/
跨域问题 (Done)
前端 和 后端代码放在同一个 Node 服务里,即可解决,反之亦然
丢 阿里云 上相关功能都在 手机端 和 PC 测试过了,没有问题
history mode 临时解决方案 注释掉 ( 页面刷新后 404 )
express 静态资源
express 路由
https://expressjs.com/en/starter/basic-routing.html
Vue SSR
renderer.renderToString(app).then(html => {
更容易理解
// Step 1: Create a Vue instance
const Vue = require('vue')
const app = new Vue({
template: `Hello World`
})
// Step 2: Create a renderer
const renderer = require('vue-server-renderer').createRenderer()
// Step 3: Render the Vue instance to HTML
renderer.renderToString(app, (err, html) => {
if (err) throw err
console.log(html)
// => Hello World
})
// in 2.5.0+, returns a Promise if no callback is passed:
renderer.renderToString(app).then(html => {
console.log(html)
}).catch(err => {
console.error(err)
})
https://ssr.vuejs.org/guide/#rendering-a-vue-instance
NodeJS __dirname module
找上级目录的文件
var path = require('path')
console.log(__dirname) // ...src/routes
console.log(path.join(__dirname, '../', 'users.json')) // ...src/users.json
https://stackoverflow.com/questions/7083045/fs-how-do-i-locate-a-parent-folder
https://nodejs.org/docs/latest/api/modules.html#modules_dirname
NodeJS 模块化
// tools.js
var moment = require('moment')
function getCurrTime() {
return moment(Date.now()).format('YYYY-MM-DD HH:mm:ss')
}
const tools = {
getCurrTime
}
module.exports = tools
// getCaptcha.js
var tools = require('../assets/js/tools')
https://medium.com/@crohacz_86666/basics-of-modular-javascript-2395c82dd93a
issue 11121633 (Done)
调用 API 成功,但短信条数不减
原因分析:
给同一个号码发送短信次数过多,运营商误以为是短信轰炸
issue 11121302
重定向失败
// app.js
res.redirect('http://localhost:8080')
https://www.cnblogs.com/duhuo/p/5609127.html
手机注册登录按钮 (Done)
手机号码格式错误 不请求后台
参数错误,请先获取验证码 请求后台
{
code: 0,
msg: '参数错误,请先获取验证码'
}
短信验证码错误,请重试 请求后台
{
code: 400,
msg: '短信验证码错误,请重试!'
}
request method options
"预检"请求(preflight)
https://www.cnblogs.com/chris-oil/p/8042677.html
优化点 2018年11月12日12:30:42 (Done)
getCaptcha: function () {
const { phoneNumber } = this
const { isValidPhoneNumber } = this.$tools
if (!isValidPhoneNumber(phoneNumber)) {
this.appearWarningMsg = true // 显示 错误提示信息
this.warningMsg = '手机号码格式错误' // 设置 错误提示信息
return
}
this.appearWarningMsg = false // 隐藏 错误提示信息,改善用户体验
}
issue 11121136
官方案例
手机号缓存的是获取验证码的那个
req.body (express) 要想顺利获取数据 需要注意以下几点 (Done)
- 导入 body-parser
- 使用中间件
- req.body
- 前端 content-type 设置为 json
issue 11120933 (Done)
form 内任意按钮点击都会触发 action
原因分析:
button 标签 缺省 type 属性时,默认为 submit
解决对策:
https://www.w3.org/TR/2011/WD-html5-20110525/the-button-element.html#the-button-element
鼠标点击 手机号输入框,验证码输入框 wrapper 任意范围,对应输入框都会获得焦点
点击 获取验证码按钮后,验证码输入框获得焦点
移动端 无效
this.focusCaptcha() // 无效
ref
https://vuejs.org/v2/api/#ref
获取验证码按钮文本 条件渲染 (Done)
issue 11112125 (Done)
获取验证码 点击后 隔几秒才开始倒计时 http-server
结论:手机测试无此问题
获取验证码按钮仅倒计时的时候字体颜色为灰色 (Done)
手机号注册登录按钮 禁用 (Done)
- 手机号输入框、验证码输入框 任一为空,禁用
- 点击 手机号 或 验证码 清除按钮后立即禁用
点击手机注册登录按钮-手机号-验证码 校验功能 (Done)
login: function () {
const { captcha, arrivedCaptcha, phoneNumber } = this
const { isMatched } = this.$tools
if (!isMatched(phoneNumber)) {
this.appearWarningMsg = true
this.warningMsg = '手机号格式错误'
return
}
if (!arrivedCaptcha) {
this.appearWarningMsg = true
this.warningMsg = '参数错误,请先获取验证码'
return
}
if (captcha != arrivedCaptcha) {
this.appearWarningMsg = true
this.warningMsg = '短信验证码错误,请重试!'
return
}
this.$router.push({ path: '/' })
}
读秒功能 (Done)
区间 ( 59~0 )
读秒期间按钮禁用 ( v-bind:disabled 由 vue 接管 disabled 属性 )
读秒 ( setInterval )
setInterval(() => {
some code... // 需要写箭头函数,否则 this 指向 Window
})
绑定类名
issue 11101016 (Done)
手机上测试时 点击获取验证码后 / 未注册用户接口每次调用都会发两条短信,为什么?
原因分析:注册验证码发送(res.json())后函数执行并未完成,接着又执行了发送验证码的逻辑
注册验证码和普通验证码各收到了一条
因此发生如下错误 (Cannot set headers after they are sent to the client, 由于已经发送过一次
)
解决方案:加 return 语句,终止执行后续已注册用户验证码的发送的逻辑(成立)
issue 11101014
opera mini mobile
手机号输入框 输入数字后不显示
限制输入框字符长度
最大长度 max-length
禁用 autocomplete
扩展 Vue.prototype
// tools.js
const tools = {
isMatched: function (phoneNumber) {
return /^(13[0-9]|14[0-9]|15[0-9]|16[0-9]|17[0-9]|18[0-9]|19[0-9])\d{8}$/.test(phoneNumber)
}
}
export default tools
// main.js
import Vue from 'vue'
import tools from './assets/js/tools'
Vue.prototype.$tools = tools
// Register.vue
const { isMatched } = this.$tools
唯品会 没有对未激活的号(空号)进行校验
移动新号
只要手机号格式合法即可
toastr
https://codeseven.github.io/toastr/demo.html
vue-fontawesome
install
yarn add @fortawesome/fontawesome-svg-core
yarn add @fortawesome/free-solid-svg-icons
yarn add @fortawesome/vue-fontawesome
usage
// main.js
import Vue from 'vue'
import App from './App'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faUserSecret } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
library.add(faUserSecret)
Vue.component('font-awesome-icon', FontAwesomeIcon)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
components: { App },
template: ' '
})
https://github.com/FortAwesome/vue-fontawesome
https://fontawesome.com/icons
how to css placeholder
::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
color: red;
opacity: 1; /* Firefox */
}
:-ms-input-placeholder { /* Internet Explorer 10-11 */
color: red;
}
::-ms-input-placeholder { /* Microsoft Edge */
color: red;
}
https://www.w3schools.com/howto/howto_css_placeholder.asp
Issue 11081600
手机端 input 框 请输入手机号 和 手机号 对不齐
阻止手机端用户缩放页面 (Done)
reset.css
https://gist.github.com/anthonyshort/552543
支持 less
安装后即可使用,无需额外配置
yarn add less-loader less --dev
https://cli.vuejs.org/guide/css.html#pre-processors
注册页 (Done)
点 圈叉 后提示会被清除
最大宽度 750 px ( 大于后 背景图不够 )
为什么请求短信接口返回 -1 (Done)
接口的问题导致的
短信模板241 正常 233 不正常
linux 读文件
云服务器上读 JS 文件
https://www.cyberciti.biz/faq/unix-linux-command-to-view-file/
将后台业务放到阿里云服务器上遇到的问题
8081 端口无法正常访问,改成9090端口后能够正常访问
linux 环境变量
https://jingyan.baidu.com/article/b87fe19e6b408852183568e8.html
获取验证码成功
正则手机号
// 修改后
const isMatched = /^(13[0-9]|14[0-9]|15[0-9]|16[0-9]|17[0-9]|18[0-9]|19[0-9])\d{8}$/.test(phoneNumber)
// 修改前
const phoneNumber = req.query.phone // 18521447789
const isMatched = /^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\d{8}$/.test(phoneNumber) // true
if (!isMatched) {
return res.json({ code: -999, msg: '手机号不合法' })
}
模拟数据库 users.json
users.json
[
{
"userId": 1,
"phone": 18521002112
},
{
"userId": 2,
"phone": 18512592331
}
]
nodejs 读取文件 fs.readFile
app.get('/login', function (req, res, next) {
const phoneNumber = req.query.phone
fs.readFile("users.json", "utf8", function (err, data) {
if (err) throw err;
data = JSON.parse(data)
let isRegisterd = data.some(function (value, index, array) {
const { userId, phone } = value
return phone == phoneNumber
})
console.log(`isRegisterd: ${isRegisterd}`)
res.json(data)
});
})
nodejs 写文件 fs.writeFile
const dbData = JSON.stringify(...some data)
fs.writeFile(path.join(__dirname, '../', 'users.json'), dbData, function (err) {
if (err) throw err;
console.log('The file has been saved!');
res.json(message)
})
接口测试
nodejs express 调试
后台配置 CORS
var express = require('express')
var cors = require('cors') // ( 1 )
var app = express()
app.use(cors()) // ( 2 )
app.get('/login', function (req, res, next) {
res.json({msg: 'This is CORS-enabled for all origins!'})
})
app.listen(8081, function () {
console.log('CORS-enabled web server listening on port 8081')
})
VUE CLI 3 配置反向代理
代理配置似乎被忽略的,完全不起作用
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:9000',
ws: true,
changeOrigin: true
}
}
}
}
事件修饰符 prevent
https://vuejs.org/v2/guide/events.html#Event-Modifiers
事件修饰符 stop
nodemon
热更新
https://www.npmjs.com/package/nodemon
注册模块分析
用户输入验证码点击 手机号注册登录
按钮后,后台会校验验证码是否准确并返回响应状态信息
注册模块 —— 验证码短信
使用 筋斗云 提供的验证码短信服务 ( 认证比较简单 )
筋斗云
短信接口API文档
其它 验证码短信服务商
阿里云 | 云通信
https://cloud.tencent.com/product/yy
单文件组件 name 首字母自动转大写
export default {
name: 'register', // => Register
props: {}
}
HTML5 History 模式
const router = new VueRouter({
mode: 'history',
routes: [...]
})
现象:刷新后 404
成熟解决方案:( history.pushState 模式 )
// https://github.com/bripkens/connect-history-api-fallback
var express = require('express');
var history = require('connect-history-api-fallback');
var app = express();
app.use(history());
临时解决方案: ( 哈希模式 )
注释掉 mode: 'history'
参考资料:
https://github.com/bripkens/connect-history-api-fallback
https://www.cnblogs.com/fayin/p/7221619.html?utm_source=itdadao&utm_medium=referral
https://router.vuejs.org/guide/essentials/history-mode.html#example-server-configurations
https://vuejs.org/v2/guide/migration-vue-router.html#history-true-replaced
https://stackoverflow.com/questions/36399319/vue-router-return-404-when-revisit-to-the-url
Vue CLI 3
安装
yarn global add @vue/cli
Terminology
PWA ( Progressive Web Apps ) 渐进式的网页应用程序
PWA介绍及快速上手搭建一个PWA应用
Mbps
Accept Encoding: br
ES6 module export / import
// index.js
export const PI = 3.14
// main.js
import { PI } from './index.js'
// index.js
const PI = 3.14
export default PI
// main.js
import PI from './index.js'
issues
eslint 5.8.0 不兼容
重装 yarn 和 nodejs 后 解决
runtime-only
需要配置 vue.config.js,如下
项目 src 文件夹目录结构
└── src ·······································
├── assets ································ 公共资源
├── demo ·································· 案例
├── dist ·································· 前端
├── messages ······························ 短信验证码
├── middlewares ··························· 中间件
├── query ································· 数据库查询语句
├── routes ································ 路由
└── app.js ································ 入口
flex-box
flex-内部inline-会变成Block
○
https://codepen.io/MonguDykrai/pen/wQxNyX
order
Flex items have a default order value of 0
, therefore items with an integer value greater than 0 will be displayed after any items that have not been given an explicit order value.
You can also use negative values with order, which can be quite useful. If you want to make one item display first, and leave the order of all other items unchanged, you can give that item an order of -1. As this is lower than 0 the item will always be displayed first.
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Ordering_Flex_Items
https://flexboxfroggy.com/
https://www.jianshu.com/p/9e2b6620a361
取消a标签在移动端点击时的背景颜色
a {
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
-webkit-user-select: none;
-moz-user-focus: none;
-moz-user-select: none;
}
https://www.cnblogs.com/karila/p/6276861.html
https://blog.csdn.net/fb_01/article/details/50352612?utm_source=blogxgwz4
a 标签点击事件 native 修饰符
@click.native="showWarningBox"
清移动端高亮
// https://blog.csdn.net/lily2016n/article/details/78228464 ( 解决移动端页面点击图标或按钮产生透明灰色背景 )
html,body{-webkit-text-size-adjust: 100%;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);}
::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
color: #CCCCCC;
opacity: 1; /* Firefox */
}
:-ms-input-placeholder { /* Internet Explorer 10-11 */
color: #CCCCCC;
}
::-ms-input-placeholder { /* Microsoft Edge */
color: #CCCCCC;
}
a, a:link, a:visited, a:hover, a:focus, a:active{
color: inherit;
text-decoration: none;
}
// https://www.cnblogs.com/karila/p/6276861.html ( 取消a标签在移动端点击时的蓝色 )
a, span {
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
-webkit-user-select: none;
-moz-user-focus: none;
-moz-user-select: none;
}
// https://www.cnblogs.com/karila/p/6276861.html ( 去除ios input框点击时的灰色背景 )
input {
-webkit-tap-highlight-color:rgba(0,0,0,0);
}
// https://www.cnblogs.com/karila/p/6276861.html ( 使用图片作为a标签的点击按钮时,当触发touchstart的时候,往往会有一个灰色的背景 )
a,a:hover,a:active,a:visited,a:link,a:focus{
-webkit-tap-highlight-color:rgba(0,0,0,0);
-webkit-tap-highlight-color: transparent;
outline:none;
background: none;
text-decoration: none;
}
// ---
// https://blog.csdn.net/fb_01/article/details/50352612
::selection {
background: #FFF;
color: #333;
}
::-moz-selection {
background: #FFF;
color: #333;
}
::-webkit-selection {
background: #FFF;
color: #333;
}
// ---
巧妙的布局方法
先缩放页面得到整数的间隙 例:10
算出总间隙 例:50
50 / 432 ≈ 0.11574 / 5 约等于 0.023148 = 2.23148 gutter宽度
100 - 11.574 ≈ 88.426 / 4 =22.1065 内容宽度
iconfont IE8+ eot + woff 就可以
仅 eot 时 || eot + svg
chrome
firefox
IE9 10 11
svg2ttf (已测试能用)
研究研究怎么做 iconfont 参考 vip.svg
yarn add global svg2ttf
svg2ttf demo.svg demo.ttf
https://github.com/fontello/svg2ttf
ttf editor
https://www.glyphrstudio.com/online/