仿 VIP

作者: 不知道的是 | 来源:发表于2018-11-05 11:20 被阅读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/

    相关文章

      网友评论

          本文标题:仿 VIP

          本文链接:https://www.haomeiwen.com/subject/wzajxqtx.html