仿 VIP

2018-11-05  本文已影响0人  不知道的是

技术栈

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

issue11201748.gif

如何拿到 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 )

image.png

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;
}
iscroll-chrome-mobile-intervention-.gif

参考资料: https://github.com/cubiq/iscroll/issues/1157

区域滚动 iscroll

采用的方案

\iscroll\demos\horizontal

https://www.npmjs.com/package/iscroll
http://caibaojian.com/iscroll-5/init.html

唯品会首页区域滚动

vip_scroll.gif vip_scroll-2.gif

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
<head>
  <title>唯品会手机购物正品商城:全球精选 正品特卖手机版</title>
  <!-- 放最前面导入 -->
  <script src="media.js"></script>
</head>
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'
  }
}
image.png

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/

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>

<body>
  <script src="../assets/js/matchMedia/matchMedia.js"></script>
  <script src="../assets/js/matchMedia/matchMedia.addListener.js"></script>
  <script>
    console.log(matchMedia)

    var result1 = window.matchMedia('(min-width:1200px)');
    var result2 = window.matchMedia('(min-width:992px)');
    var result3 = window.matchMedia('(min-width:768px)');
    if (result1.matches) {
      console.log("大屏幕(>=1200)");
    } else if (result2.matches) {
      console.log("中等屏幕(>=992&<=1200)");
    } else if (result3.matches) {
      console.log("小屏幕(>=768&<=992)");
    } else {
      console.log("超小屏幕(<=768)");
    }
  </script>
</body>

</html>
matchMedia_polyfill.gif

window.matchMedia 兼容性 ( 不采用 )

IE10+

Vip 媒体查询

屏幕像素 540px html 元素 font-size: 54px
屏幕像素 320px html 元素 font-size: 32px
···
vip_media_query_1.gif

媒体查询 ( 不采用 )

https://www.jianshu.com/p/d4b250b81ce5

单文件 export default 外的 JS 代码所有路由中都会执行? ( 基本成立--已测试 )

export_default外的代码都执行了.gif

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 的字段不显示
image.png

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()) 后正常

issue_11181703-1.gif

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

image.png 路由保护_需要携带token.gif

VIP 登录请求中的 参数

image.png

登录 / 登出状态 Cookie 差异

image.png

待完成

JWT

仿 VIP

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})
image.png

后台生成 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)
jwt-example.gif

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 方便本地调试

  1. 下载安装包 mongodb-win32-x86_64-2008plus-ssl-4.0.4-signed.msi
  2. 默认安装
  3. 管理员权限 打开 CMD 执行 net start MongoDB 命令
  4. 执行 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

image.png

http://app.liuchengtu.com/

待解决

自动完成
git push / git pull (频繁修改频繁提交)

本地连接线上 mongodb (无法进行调试)
解决方案:装 windows 版,方便调试

github免密码提交 (频繁修改频繁输密码)

登录接口测试截图 (Done)

image.png

获取验证码接口测试截图(Done)

image.png

input type number maxlength do not work (Done)

input_type_number_maxlength_max.gif

解决方案:
用 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 "your_email@example.com"

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/

Snipaste_2018-11-16_12-08-16.png

MongoDB 其它 IP 访问 (working on)

image.png

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' ]

短信验证登录

  1. 获取手机号
  2. 生成验证码
  3. 将手机号和验证码存入缓存 (设置缓存有效时间和短信中提示的一致)
  4. 调用验证码短信发送接口 (手机号和验证码为参数)
  5. 用户输入验证码点击登录,后台获取手机号和验证码
  6. 校验获取的手机号和验证码是否与缓存中的一致
  7. 如果一致登录成功返回登录成功标识,否则返回超时或验证码输入错误等提示信息

短信验证登录参考文档:
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" })
image.png

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))
assert_equal_strictEqual.gif

参考资料:
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
mongodb_11131401.gif

参考资料:
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

image.png lib_log_start_stop_mongodb.gif image.png

主要参考资料

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 )

image.png image.png

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 )

image.png

express 静态资源

image.png image.png

express 路由

image.png image.png

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: `<div>Hello World</div>`
})

// 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)
  // => <div data-server-rendered="true">Hello World</div>
})

// 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)
})
image.png

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 成功,但短信条数不减

原因分析:
给同一个号码发送短信次数过多,运营商误以为是短信轰炸

image.png

issue 11121302

重定向失败

// app.js
res.redirect('http://localhost:8080')
image.png

https://www.cnblogs.com/duhuo/p/5609127.html

手机注册登录按钮 (Done)

手机号码格式错误 不请求后台

参数错误,请先获取验证码 请求后台

{
  code: 0,
  msg: '参数错误,请先获取验证码'
}

短信验证码错误,请重试 请求后台

{
  code: 400,
  msg: '短信验证码错误,请重试!'
}

request method options

"预检"请求(preflight)

request method options 1250.gif

https://www.cnblogs.com/chris-oil/p/8042677.html

优化点 2018年11月12日12:30:42 (Done)

点重新获取后清空错误提示更友好.gif
getCaptcha: function () {
  const { phoneNumber } = this
  const { isValidPhoneNumber } = this.$tools

  if (!isValidPhoneNumber(phoneNumber)) {
    this.appearWarningMsg = true // 显示 错误提示信息
    this.warningMsg = '手机号码格式错误' // 设置 错误提示信息
    return
  }

  this.appearWarningMsg = false //  隐藏 错误提示信息,改善用户体验
}

issue 11121136

官方案例

手机号缓存的是获取验证码的那个

issue 11121136-1.gif

req.body (express) 要想顺利获取数据 需要注意以下几点 (Done)

  1. 导入 body-parser
  2. 使用中间件
  3. req.body
  4. 前端 content-type 设置为 json
image.png

issue 11120933 (Done)

form 内任意按钮点击都会触发 action

原因分析:
button 标签 缺省 type 属性时,默认为 submit

解决对策:

<button @click.prevent.stop="getCaptcha" >获取验证码</button> <!-- 添加 prevent -->

<!-- or -->

<button @click.stop="getCaptcha" type="button">获取验证码</button> <!-- 显示指定类型为 button -->
issue 11120933-1.gif

https://www.w3.org/TR/2011/WD-html5-20110525/the-button-element.html#the-button-element

鼠标点击 手机号输入框,验证码输入框 wrapper 任意范围,对应输入框都会获得焦点

点击任意位置都会获得焦点.gif

点击 获取验证码按钮后,验证码输入框获得焦点

移动端 无效

点击获取验证码按钮后验证码输入框获得焦点.gif
this.focusCaptcha() // 无效

ref

<input type="number" ref="captchaInput" placeholder="请输入验证码">

<script>
methods: {
  getCaptcha: function (e) {
    console.log(this.$refs) // {captchaInput: input#captcha}

    this.$refs.captchaInput.focus() // 验证码输入框获得焦点
  }
}
</script>

https://vuejs.org/v2/api/#ref

获取验证码按钮文本 条件渲染 (Done)

<button @click.stop="getCaptcha" :disabled="getCaptchaDisabled" :class="{'get-captcha-disabled': getCaptchaDisabled}">
  <span v-show="getCaptchaDisabled">{{ !captchaRequested ? '获取验证码' : `${sandClock}秒后重新获取` }}</span>
  <span v-show="!getCaptchaDisabled">{{ !captchaRequested ? '获取验证码' : '重新获取' }}</span>
</button>

issue 11112125 (Done)

获取验证码 点击后 隔几秒才开始倒计时 http-server

结论:手机测试无此问题

获取验证码按钮仅倒计时的时候字体颜色为灰色 (Done)

获取验证码按钮禁用逻辑.gif

手机号注册登录按钮 禁用 (Done)

  1. 手机号输入框、验证码输入框 任一为空,禁用
  2. 点击 手机号 或 验证码 清除按钮后立即禁用
登录按钮禁用逻辑.gif

点击手机注册登录按钮-手机号-验证码 校验功能 (Done)

仿 VIP 仿 VIP
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 属性 )

<button @click.stop="getCaptcha" v-bind:disabled="false">{{ sandClock }}秒后重新获取</button>

读秒 ( setInterval )

setInterval(() => {
  some code... // 需要写箭头函数,否则 this 指向 Window
})

绑定类名

<button @click.stop="getCaptcha" :disabled="getCaptchaDisabled" :class="{'get-captcha-disabled': getCaptchaDisabled}">

issue 11101016 (Done)

手机上测试时 点击获取验证码后 / 未注册用户接口每次调用都会发两条短信,为什么?

原因分析:注册验证码发送(res.json())后函数执行并未完成,接着又执行了发送验证码的逻辑

注册验证码和普通验证码各收到了一条

仿 VIP

因此发生如下错误 (Cannot set headers after they are sent to the client, 由于已经发送过一次

image.png

解决方案:加 return 语句,终止执行后续已注册用户验证码的发送的逻辑(成立)

仿 VIP

issue 11101014

opera mini mobile

手机号输入框 输入数字后不显示

限制输入框字符长度

最大长度 max-length

<input type="tel" placeholder="请输入手机号" id="phone" v-model="phoneNumber" maxlength="11">

禁用 autocomplete

<input type="tel" placeholder="请输入手机号" id="phone" v-model="phoneNumber" maxlength="11" @input.stop="iptPhoneNumber"
        autocomplete="off">

扩展 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

image.png

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: '<App/>'
})
<!-- App.vue -->
<template>
  <div id="app">
    <font-awesome-icon icon="user-secret" />
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

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 框 请输入手机号 和 手机号 对不齐

image.png

阻止手机端用户缩放页面 (Done)

<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />

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)

image.png

点 圈叉 后提示会被清除

image.png

最大宽度 750 px ( 大于后 背景图不够 )

image.png

为什么请求短信接口返回 -1 (Done)

接口的问题导致的

短信模板241 正常 233 不正常

linux 读文件

云服务器上读 JS 文件

https://www.cyberciti.biz/faq/unix-linux-command-to-view-file/

将后台业务放到阿里云服务器上遇到的问题

8081 端口无法正常访问,改成9090端口后能够正常访问

image.png image.png

linux 环境变量

https://jingyan.baidu.com/article/b87fe19e6b408852183568e8.html

获取验证码成功

image.png

正则手机号

// 修改后
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: '手机号不合法' })
}
image.png

模拟数据库 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)

})

接口测试

image.png

nodejs express 调试

nodejs debugger-1758.gif

后台配置 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')
})
image.png

VUE CLI 3 配置反向代理

代理配置似乎被忽略的,完全不起作用

module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:9000',
        ws: true,
        changeOrigin: true
      }
    }
  }
}

事件修饰符 prevent

<button type="submit" @submit.prevent.stop="login">登录</button>

https://vuejs.org/v2/guide/events.html#Event-Modifiers

事件修饰符 stop

<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>

nodemon

热更新

https://www.npmjs.com/package/nodemon

注册模块分析

用户输入验证码点击 手机号注册登录 按钮后,后台会校验验证码是否准确并返回响应状态信息

仿 VIP image.png image.png image.png

注册模块 —— 验证码短信

使用 筋斗云 提供的验证码短信服务 ( 认证比较简单 )

筋斗云
短信接口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 mode issue.gif

成熟解决方案:( 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_refresh_404_solution_connect_history_api_fallback.gif

临时解决方案: ( 哈希模式 )
注释掉 mode: 'history'

history mode issue-1.gif

参考资料:
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 <Brotli compression format>

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,如下

image.png

项目 src 文件夹目录结构

└── src ·······································
    ├── assets ································ 公共资源
    ├── demo ·································· 案例
    ├── dist ·································· 前端
    ├── messages ······························ 短信验证码
    ├── middlewares ··························· 中间件
    ├── query ································· 数据库查询语句
    ├── routes ································ 路由
    └── app.js ································ 入口

flex-box

flex-内部inline-会变成Block

<style>
.outer {
  display: flex;
}

.inner {
  font-size: 20px;
}
</style>

<div class="outer">
  <i class="inner">○</i>
</div>

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 内容宽度

<template>
  <div id="recommend">
    <!-- <div class="gutter"></div> -->
    <div class="t1"></div>
    <!-- <div class="gutter"></div> -->
    <div class="t2"></div>
    <!-- <div class="gutter"></div> -->
    <div class="t3"></div>
    <!-- <div class="gutter"></div> -->
    <div class="t4"></div>
  </div>
</template>

<script>
  export default {
    name: 'recommend'
  }
</script>

<style scoped>
  #recommend {
    width: 100%;
    height: 100px;
  } 

  /* #recommend .gutter {
    float: left;
    width: 3.12999%;
    height: 100px;
    background-color: green;
  } */

  #recommend div {
    float: left;
    margin-left: 3.12999%;
    width: 21.084%;
    height: 100px;
    background-color: #f00;
  }
</style>
利用 gutter的布局.gif

iconfont IE8+ eot + woff 就可以

仅 eot 时 || eot + svg

chrome

image.png

firefox

image.png

IE9 10 11

image.png

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/

上一篇下一篇

猜你喜欢

热点阅读