Giter VIP home page Giter VIP logo

intern-study's People

Contributors

catee avatar deligent-ant avatar leecade avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

intern-study's Issues

无限加载组件,上拉或是下拉刷新

##无限下拉或是上拉组件

依赖:

  • 依赖 underscore 来编译模版字符串
  • 用户使用插件前要在 html 中先进行引入:
<script src="http://www.bootcss.com/p/underscore/underscore-min.js"></script>
  • 在浏览器端使用
    ;(function() {
      infiniteLoad({
        scollDirection: 'up', //滚动方向,‘up’||‘down’
        domString: '', //页尾栏  选择器,可以省略
        fatherString: '.content', //父亲元素 选择器
        childString: 'li', //标签名字
        template: '<p><%= date %></p><p><%= desc %></p><img src=<%= pic %> >', //模版字符underscore写法
        cb: async function({ limit, offset }) {
          let res = await axios.get('/json', {
            params: { limit, offset }
          })
          let { code, data } = res.data
          if (parseInt(code, 10) !== 0) return
          let mockData = Array.apply(null, { length: 50 }).map((item, index) => {
            return data[index % 2]
          })
          return mockData
        }, //页尾部栏可见时候,远程加载数据的函数,返回一个promise 数据是对象数组[{}]
        options: {
          //一开始的limit和offset限制
          limit: 2,
          offset: 0
        }
      })
    })()

[第二期]无限滚动加载的通用组件

无限滚动组件

基本要求

  1. 要求实现三种列表样式,详见设计图部分
  2. 可无限加载新内容,页面不会卡死

后端要求

  1. 实现数据分页,数据 mock 一份即可

  2. 可自己搭建服务,自选框架,比如 koa2,不强制使用 fe

  3. 有 api 和静态资源分类的概念

  4. 列表数据格式可参考

    [{	
    	id: "hahahahahha",
    	index: 0,
    	title: "我是标题,要短",
    	desc: "我是描述,我很长。",
    	// pic: "http://xxx.xx.x/xxxxx.avi",
    	pic: "http://xxx.xx.x/xxxxx.png",
    	date: 
    },{
    	// ...
    }]
    

前端要求

  1. 实现滚动加载机制,思考 scroll 事件 与 intersection observer 两种实现方式的区别,和各自优缺点,择一进行实现
  2. 重点关注页面滚动性能,页面不卡顿,保持滚动帧率 60fps
  3. 理解优化方案
  4. 适配多移动终端

设计图

  1. 文字列表
    list1

  2. 普通的图文列表
    list2

  3. css3 动画、特效、蒙层、变换 的列表
    list3

技能

  1. 原生 js 编写
  2. 前端调试技巧
  3. 基于 vscode 的 node 调试工具
  4. 自我学习,知识检索能力

记录平时看的前端小小知识点

记录平时看的前端小小知识点

Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]   数组覆盖
[].every(item=>item>2)//true . 空数组直接true
super关键字表示原型对象时,只能用在对象的方法之中,用在其他地方都会报错。
pm2 start --name=cms npm -- run dev

对象编辑

  return {
    guid: id,
    ...(name && {name}),
    ...(age && {age})
  }
foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);
复制代码
如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。
const baz = obj?.foo?.bar?.baz; // 传导符 babel支持
// 没有用管道操作符
double(increment(double(5))); // 22

// 用上管道操作符之后
5 |> double |> increment |> double; // 22
function isClass(func) {
  return typeof func === 'function' 
    && /^class\s/.test(Function.prototype.toString.call(func));
}
(() => {}).prototype // undefined
(function() {}).prototype // {constructor: f}
new function Fun(){}如果一个函数的返回值 不是 一个对象,它会被 new 完全忽略。如果你返回了一个字符串或数字,就好像完全没有 return 一样。
function Person() {}

console.log(Person.prototype); // 🤪 不是 Person 的原型
console.log(Person.__proto__); // 😳 Person 的原型
“原型链”更像是 __proto__.__proto__.__proto__ 而不是 prototype.prototype.prototype
Array.from(document.querySelectorAll('div')) === $$('div')

在 ES6 中,词法 环境和 变量 环境的区别在于前者用于存储**函数声明和变量( let 和 const )绑定,而后者仅用于存储变量( var )**绑定。

在 ES6 中,词法 环境和 变量 环境的区别在于前者用于存储**函数声明和变量( let 和 const )绑定,而后者仅用于存储变量( var )**绑定。

session实现登录与登出的简单实现

session实现登录与登出的简单实现

登录用户的信息存与mysql,mysql上的操作基于objection做了一下封装,把对应的数据库表操作方法挂在fastify的sever实列上,用户是否已经登录通过cookie实现,只是cookie上存储的只是用户信息的一个映射key,也就是sessionId,通过sessionId作索引,在mysql上匹配到对应的用户信息。

实现步骤

  1. 建两个表user表和session表:分别存用户信息、sessionId与user的映射关系。
  2. 用户访问网站时候校验cookie中的sessionId是否存在有效,有效则把对应的用户信息挂在requsert返回,如果无效或是不存在,不作处理。
  3. 如果有效,用户可以在nuxt的中间间,middleware中的req拿到fastify挂上去的用户信息。
    4.如果无效,用户要进行登录操作,这时候发请求校验user表,是否正确,正确就开始生成sessionId,存在用户浏览器的cookie上。
    5 注销操作,清除session会话,以及session中的记录。

简单代码如下:

'use strict'
//用户名要唯一

const fp = require('fastify-plugin')
const uid = require('uid-safe').sync
const sessionPlugin = async (app, options) => {
  let sessionIdName = options.sessionName || 'sessionId_stock_level2'
  let userModel = await app.getQueryModel({
    psm: '11111111',
    table: '222222'
  })
  let sessionModel = await app.getQueryModel({
    psm: '33333333',
    table: '4444444444'
  })
  app.addHook('preHandler', async (req, reply) => {
    const sessionId = req.cookies[sessionIdName]
    if (sessionId) {
      const r = await sessionModel
        .query()
        .where({ sessionId })
        .andWhere('expires', '<', new Date())
        .catch(err => err)
      if (Array.isArray(r) && r.length > 0) {
        req.session = r[0]
      }
    }
  })

  app.post('/login', async (req, reply) => {
    let { body } = req
    let name = body.name
    let password = body.password
    const userName = await userModel
      .query()
      .where('name', name)
      .catch(err => console.log(err))
    if (userName.length === 0) {
      return { code: 1, data: '用户名输入错误!' }
    } else {
      const userPassword = await userModel.query().where({ name, password })
      if (userPassword.length === 0) {
        return { code: 1, data: '密码输入错误!' }
      } else {
        const sessionId = uid(24)
        let maxAge = options.maxAge || 60 * 60 * 1000
        let expires = new Date(Date.now() + maxAge)
        let resInsert = await sessionModel
          .query()
          .insert({ sessionId, expires, name })
          .catch(err => err)
        reply
          .setCookie(sessionIdName, sessionId, {
            domain: '',
            path: '/',
            expires: new Date(expires),
            httpOnly: true
          })
          .send({ code: 0, data: '登陆成功!' })
      }
    }
  })
}

module.exports = fp(sessionPlugin, {
  fastify: '>=1.2.0',
  name: '@fe/byted-auth'
})

附上一个基于json ,而非mysql的简单实现

'use strict'
//用户名要唯一

const fp = require('fastify-plugin')
const sessionPlugin = async (app, options = {}) => {
  // console.log(app.CONFIG)
  let maxAge = options.maxAge || 24 * 60 * 60 * 100
  let sessionIdName = options.sessionName || 'sessionId_stock_level2'
  const { WHITELIST } = app.CONFIG

  app.addHook('preHandler', async (req, reply) => {
    const sessionId = req.cookies[sessionIdName]
    if (sessionId) {
      let userInfos = {}
      let time = new Date()
      const isAvaiable = WHITELIST.some(item => {
        if (
          item.sessionId === sessionId &&
          item.expires < time &&
          item.group === 'SZ'
        ) {
          userInfos = item
          return true
        }
        return false
      })
      if (isAvaiable) {
        req.session = userInfos
      } else {
        req.session = null
      }
    }
  })

  app.post('/szlevel2/login', async (req, reply) => {
    let expires = new Date(Date.now() + maxAge)
    let sessionId = ''
    let { body } = req
    let name = body.name
    let password = body.password
    const isAvaiable = WHITELIST.some(item => {
      if (
        item.name === name &&
        item.password === password &&
        item.group === 'SZ'
      ) {
        sessionId = item.sessionId
        return true
      }
      return false
    })
    if (isAvaiable) {
      reply
        .setCookie(sessionIdName, sessionId, {
          domain: '',
          expires: new Date(expires),
          httpOnly: true
        })
        .send({ code: 0, data: { name, message: '登陆成功' } })
    } else {
      reply.send({ code: 1, data: '输入错误!' })
    }
  })

  app.post('/szlevel2/logout', async (req, reply) => {
    reply
      .setCookie(sessionIdName, '')
      .send({ code: 0, data: 'logout success!' })
  })
}

module.exports = fp(sessionPlugin, {
  fastify: '>=1.2.0',
  name: '@fe/byted-auth'
})

sso单点登录流程

sso单点登录流程

具体源码没有详细去看,走了个大概的流程。并找了一个图片来描述一下。

背景关键字:http是无状态协议 会话机制 cookie与session web多系统

背景关键字,不做介绍,只说sso。

往常一个网站就要登陆一次,同一用户再次登陆另一个网站还得再次登陆,也就是不用的网站在保留
session和cookie是各自独立的。既做session的创立也做用户登陆的校验。但如果这几个网站是同一个登
陆呢,比如公司好几个网站,你只要登陆一次sso认证,就可以访问各个页面。所以sso就是为了实现web
多系统下的登陆认证,多系统都接入同一个的sso认证,用户只要登陆过一次,就可以访问接入这个认证
的任何一个系统,不用重复登陆认证。
sso流程

sso

  1. 用户访问系统1的受保护资源,系统1发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
  2. sso认证中心发现用户未登录,将用户引导至登录页面
  3. 用户输入用户名密码提交登录申请
  4. sso认证中心校验用户信息,创建用户与sso认证中心之间的会话,称为全局会话,同时创建授权令牌
  5. sso认证中心带着令牌跳转会最初的请求地址(系统1)
  6. 系统1拿到令牌,去sso认证中心校验令牌是否有效
  7. sso认证中心校验令牌,返回有效,注册系统1
  8. 系统1使用该令牌创建与用户的会话,称为局部会话,返回受保护资源
  9. 用户访问系统2的受保护资源
  10. 系统2发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
  11. sso认证中心发现用户已登录,跳转回系统2的地址,并附上令牌
  12. 系统2拿到令牌,去sso认证中心校验令牌是否有效
  13. sso认证中心校验令牌,返回有效,注册系统2
  14. 系统2使用该令牌创建与用户的局部会话,返回受保护资源

  用户登录成功之后,会与sso认证中心及各个子系统建立会话,用户与sso认证中心建立的会话称为全局会话,用户与各个子系统建立的会话称为局部会话,局部会话建立之后,用户访问子系统受保护资源将不再通过sso认证中心,全局会话与局部会话有如下约束关系

  • 局部会话存在,全局会话一定存在
  • 全局会话存在,局部会话不一定存在
  • 全局会话销毁,局部会话必须销毁
基于nuxt的sso认证:
  • 项目入口app.register
  • nuxt.config:severMiddleware,中间件:
  • midleware下的auth:利用middle的第一参数context的req和store把用户信息放入store
    具体看源码

[第一期] 一个全栈的抽奖活动

设计图

wheel_draw

设计源文件(sketch)

wheel_draw.zip

需求描述

  1. 基于 fe 完成前后端开发, 最终部署在自己测试机 (如果有的话 😄)

  2. 服务端可以配置奖项和权重(不考虑奖池)

weight: 1 意味着 1/100 几率抽中

[{name: "10元话费", weight: 1}, {name: "50积分", weight: 50}, ...]
  1. 前端动画适配 android 4.4+ / ios 6+

  2. 动画描述:

  • 点击 "开始" 后: 1) 跑马灯闪烁 2) 内部转盘旋转
  • 抽奖结束后: 1) 转盘停止转动 2) 跑马灯快速闪烁2次 3) 更新左下角抽奖结果文案

设计图可能部分不是很标准, 意会 😄

  1. 需考虑安全, 防止作弊

React.createContext简介

React.createContext

功能:Context 旨在共享一个组件树内可被视为 “全局” 的数据,例如当前经过身份验证的用户,主题或首选语言等。(ps:树中的许多组件以及不同的嵌套级别可以访问相同的数据。)

使用

  • 允许您使用 this.context  使用该 Context 类型 的最近的当前值。 您可以在任何生命周期方法中引用它,包括 render 函数。
//.context.js文件中
import React, { Component } from 'react'
const tryContext = React.createContext({ aaa: 28288 })
export { tryContext }
  • context 对象实例

    主要是Provider Consumer这对生产者和消费者

import { tryContext } from '../components/context'
.
.
.
 class A extends PureComponent {
  render() {
    const { bbb } = this.props
    return (
      <div>
        <tryContext.Provider value={bbb}>
          <tryContext.Consumer>
            {state1 => <div className="try">{state1}</div>}
          </tryContext.Consumer>
        </tryContext.Provider>

        <tryContext.Provider value={'BBBBBB'}>
          <B />
        </tryContext.Provider>
      </div>
    )
  }
}

class B extends PureComponent {
  static contextType = tryContext
  render() {
    return <div>{this.context}</div>
  }
}
//B.contextType = tryContext

新老对比

image

老版

  1. 父组件定义 context
//父组件A
class A extends React.Component {
  getChildContext() {
    return { color: 'red' }
  }
}

A.childContextTypes = {
  color: PropTypes.string
}
  1. 子组件使用 context
//子组件B
class B extends React.Component {
  render() {
    return <p>{this.context.color}</p>
  }
}

B.contextTypes = {
  color: PropTypes.string
}

新老对比

####老版弊端

  • 代码冗余:提供 context 的组件要定义 childContextTypes 与 getChildContext 才能把 context 传下去。同时接收 context 的也要先定义 contextTypes 才能正确拿到数据。 
  • 传递效率:虽然功能上 context 可以跨层级传递,但是本质上 context 也是同 props 一样一层一层的往下传递的,当层级过深的时候还是会出现效率问题。
  • shouldComponentUpdate:由于 context 的传递也是一层一层传递,因此它也会受到 shouldComponent 的阻断。换句话说,当传递组件的 context 变化时,如果其下面某一个中间组件的 shouldComponentUpdate 方法返回 false,那么之后的接收组件将不会受到任何 context 变化。
  • 父组件 A 通过 setState 设置新的 Context 值同时触发子组件重新 render。
    子组件 B 执行 shouldComponetUpdate,由于组件 B 自身并不依赖 Context,所以 shouldComponetUpdate 检测到 state 与 prop 均未变化因此返回 false。无需重新 render。

####新版注意点

  • Provider  和  Consumer  必须来自同一次  React.createContext  调用。
  • Provider  组件的  value prop 值发生变更时,其内部组件树中对应的  Consumer  组件会接收到新值并重新执行  children  函数。此过程不受 shouldComponentUpdete 方法的影响。因此即使祖先组件退出更新,也会更新 consumer(使用者) 。
  • Provider  组件利用  Object.is  检测  value prop 的值是否有更新。(ps: <Provider value={{something: 'something'}}>不推荐,而是推荐 value 取父组件的某个 state) 4.允许您使用 this.context  使用该 Context 类型 的最近的当前值。 您可以在任何生命周期方法中引用它,包括 render 函数。您只能使用这个 API 订阅单个上下文。 5.一个 Provider 可以连接到许多 consumers (就近原则,包括 contextType 这样的使用)

参考文献
官网
头条技术博客
简书

[第四期] Rethink react + state manage

背景

  1. 数年生产环境实践, redux 体系逐渐退出历史舞台, 对于 react 应用来说是时候反思更佳的状态管理实践了

  2. react 团队近期推动创新了很多新的 patterns, 从 Render Props
    / HOCnew Context / Hooks

  3. 团队技术积累重心放在 react 方向

希望同学们持续学习, 理解社区技术选型背后的逻辑, 拥抱新的变革

需求

兼容并包思考大中小型项目都能复用的状态管理架构理想情况下应该怎样, 权衡之下的分层策略, RN / SSR
/ 低配浏览器等平台环境下如何兼顾

简单考虑如下:

  1. 舍弃 store 概念, 复活 model 更贴切点, 我们定义一个 model 为:
import Model from 'react-model'

export default Model({
  state: {
    count: 0
  },
  actions: {
    increase: async (state, actions, payload) => {count: payload || state.count++}
  },
  middlewates: []
})

需要暴露出的核心 api 非常少, 就一个 Model

  1. 采用文件 import 形式表达导入关联关系, 文件结构组织上会很灵活, 关联分析的心智负担大大减少, 我们设计实例化后的 Model 接口签名为:
const {Provider, connect, actions, subscribe, unsubscribe, getState, useState, useAction} from 'models/modelA.js'

简单起见, 我们这期作业只思考 Provider / connect 的实现, 使用方式如下:

const {Provider, connect, actions} from 'models/modelA.js'

@connect(({ count }) => ({ count }))
class Page extends Component {
  render() {
    <div onClick={e => actions.increase(10)}>{this.props.count}</div>
  }
}

const Root = () => {
  <Provider>
     <Page />
  </Provider>
}

这里沿用了 redux 的范式, Provider 包裹根节点, connect 连接 view 和注入 model state 到 this.props

知识点和实现原理

  • connect 的实现需要理解: HOC / decoration,

  • 数据变更方式可以考虑 new Context(推荐) / Proxy api / immer 方案

更多思考

赶紧学习 Hooks, view 绑定的最终形态应该如下, 有兴趣的同学也可以思考下列实现:

import { useState, useAction } from 'models/modelA.js'

function Page() {
  const count = useState(state => state.count)
  const add = useAction(actions => actions.increase)
  return (
    <div onClick={e => actions.increase(10)}>{this.props.count}</div>
  )
}

[实习笔记] canvas 以及一些平时用到的坑与知识点

canvas操作

  1. 关于canvas的变换矩阵
 this.sector.setTransform(
          Math.cos(s),
          Math.sin(s),
          -1 * Math.sin(s),
          Math.cos(s),
          600 - Math.cos(s) * 600 + Math.sin(s) * 600,
          600 - Math.sin(s) * 600 - Math.cos(s) * 600
        )

不改变旋转中心,
2. canvas适配,配合高清屏幕,可以将canvas的width和height放大,放大倍数看具体的设备的window.devicePixelRatio。最后在将canvas的dom的css . 大小width和height设置成ui要求的大小

关于viewport的适配

使用的是webpack,在postcss.config.js中设置好,如下:

   require('postcss-px-to-viewport')({
      viewportWidth: 750,
      viewportHeight: 1332,
      unitPrecision: 3,
      viewportUnit: 'vw',
      selectorBlackList: ['.nvw', '.hairlines'],
      minPixelValue: 1,
      mediaQuery: false
    }),
    require('postcss-viewport-units')()

#VUE踩坑
1.对象和数组变化时候,ui没监视到,没有触发一定的更新。虽然平时看文档 有见到过 还是在实践中踩坑了。所以不能直接操作对象和数组的改变,可以如下和文档一样的操作:

 

Vue.set(vm.items, indexOfItem, newValue)
   vm.$set(vm.items, indexOfItem, newValue)
vm.items.splice(indexOfItem, 1, newValue)
vm.userProfile = Object.assign({}, vm.userProfile, {
  age: 27,
  favoriteColor: 'Vue Green'
})

git 远程链接

将本地的ssh key放入git上,可以链接该git,查看ssh key:

cat ~/.ssh/id_rsa.pub

将本地的ssh key添加入远程机子

cat ~/.ssh/authoried     具体按tab  忘记是单词了哈

连接远程机子:用户名和ip

ssh xiaoyanhui@ip

收获数据库的2个技巧

  1. 因为线上和线下的数据库不用, 所以,可以定义一个alias指向对应的数据库,这样在开发代码中都
    只是写alias,如下:
 psm: 'toutiao.mysql.testdb',
 alias: 'caijing.cms.stock.read',

遇到的问题

在vue中 引入某个插件(自定义),其顶层对象是window吗,在mouted中给window添加原型链方法会 出现意外,引用错误。

[实习笔记] 单行文本和图片的水平中心线对齐方式

单行文本和图片的水平中心线对齐方式

场景

div里面有一个img标签,然后按照设计图给把图片的宽度和高度赋给了父元素,给img设置宽度和高度为100%,这样子元素img的宽度和高度就等于父元素的宽度和高度,奇怪的是出现了下图所示的问题:

lark20180520-172916

可以看到图1和图2的详情高度不一样,更加奇怪的是,两个img标签的宽度和高度也是一样的。

lark20180520175108
lark20180520175214

思路

因为不同字体会造成不同的渲染方式,比如下面这样,给这三个span标签的字体都设置了字体大小为100px,但是设置了不同的font-family,而表现出所看见的不一样的字体高度。所以首先给font-size设置为0,消除这个因素的影响。深究起来,其实每个内联元素会有两个高度:

  • line-height

  • 内容区域

每行内容可以由多个内联元素组成(内联标签或者是包含文本的匿名内联元素),每一行都叫做一个 line-box。line-height是实际的高度,这个高度用于计算 line-box 的高度,具体来说就是从子元素的最高点到最低点的高度。

vertical-align 属性也是计算 line-box 高度的重要因素之一,它的默认值是 baseline,也就是与其父元素基线相对齐;baseline 所处的高度跟字体也有关,浏览器认为每个 line-box 的起始位置都有一个宽度为 0 的字符(CSS 文档将其称为 strut),并将其纳入 line-box 的高度的计算中。下图所示为vertical-align的取值效果:

方案

最终img标签的样式:

font-size:0;
vertical-align:top;
    line-height:0;

效果:
lark20180520185048

简单过一眼react文档,记录我留意的点。

简单过一眼react文档,记录我留意的点。

这是从官方文档中记录,个人觉得比较需要留点心眼的知识,并不是为了介绍react。
所以不会介绍reacts生命周期、setState等知识。

  1. React 可以将多个setState() 调用合并成一个调用来提高性能。
    React 中另一个不同是你不能使用返回 false 的方式阻止默认行为.
<button onClick={this.handleClick}>

你必须谨慎对待 JSX 回调函数中的 this,类的方法默认是不会绑定 this 的:
// This binding is necessary to make this work in the callback

  • 方法一:
this.handleClick = this.handleClick.bind(this);
  • 方法二:
<button onClick={(e) => this.handleClick(e)}>
  • 方法三:
<button onClick={this.deleteRow.bind(this, id)}>
  1. 用花括号包裹代码在 JSX 中嵌入任何表达式
 <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>
  1. 数组元素中使用的key在其兄弟之间应该是独一无二的。然而,它们不需要是全局唯一的。
    key会作为给React的提示,但不会传递给你的组件。
    如果您的组件中需要使用和key相同的值,请将其作为属性传递.

  2. 在HTML当中,<textarea> 元素通过子节点来定义它的文本内容:
    在React中,<textarea>会用value属性来代替。

  3. 在React中,并不使用之前的selected属性,而在根select标签上用value属性来表示选中项。

  4. 当你有处理多个受控的input元素时,你可以通过给每个元素添加一个name属性,
    来让处理函数根据 event.target.name的值来选择做什么。

  5. Math.round(output * 1000) / 1000;

  6. 你应该在应用中保持 自上而下的数据流,而不是尝试在不同组件中同步状态。

  7. 属性上可以是组件:

<SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  1. 如果要在组件之间复用 UI 无关的功能,我们建议将其提取到单独的 JavaScript 模块中。

  2. 单一功能原则,在理想状况下,一个组件应该只做一件事情。如果这个组件功能不断丰富,它应该被分成更小的组件。

  3. 你能够根据组件中任何其他的 state 或 props 把它计算出来吗?如果是,它不是 state。

  4. JSX 只是为 React.createElement(component, props, ...children)
    React.createElement(CustomButton, {color: 'red'}, null);

  5. 点表示法:JSX 中的点表示法来引用 React 组件。你可以方便地从一个模块中导出许多 React 组件。

const MyComponents = {
  DatePicker: function DatePicker(props) {
    return <div>Imagine a {props.color} datepicker here.</div>;
  }
}
<MyComponents.DatePicker color="blue" />
  1. 当元素类型以小写字母开头时,它表示一个内置的组件,如

    ,并将字符串 ‘div’ 或 ‘span’ 传 递给 React.createElement。 以大写字母开头的类型,如 编译为React.createElement(Foo),并它正对应于你在 JavaScript 文件中定义或导入的组件。

  2. 可以使用 ... 作为扩展操作符来传递整个属性对象。
    const props = {firstName: 'Ben', lastName: 'Hector'};
    return <Greeting {...props} />;

  3. JSX 会移除空行和开始与结尾处的空格。标签邻近的新行也会被移除,字符串常量内部的换行会被压缩成一个空格,所以下面这些都等价:

  4. false、null、undefined 和 true 都是有效的子代,但它们不会直接被渲染。

  5. JavaScript 中的一些 “falsy” 值(比如数字0),它们依然会被渲染。

  6. 为属性指定默认值:

Greeting.defaultProps = {
  name: 'Stranger'
};

类型检查发生在 defaultProps 赋值之后,所以类型检查也会应用在 defaultProps 上面。
componentWillUpdate(object nextProps, object nextState)

  1. React 提供一个工具方法 React.Children 来处理 this.props.children 。我们可以用 React.Children.map 来遍历子节点,而不用担心 this.props.children 的数据类型是 undefined 还是 object。

  2. 3步使用ref:

  • 在class构造器constructor创建ref存储dom元素:this.textInput = React.createRef();
  • 关联元素
  • 通过 "current" 取得 DOM 节点: this.textInput.current.focus();

23 ref 注意点:

  • React 会在组件加载时将 DOM 元素传入 current 属性,在卸载时则会改回 null。ref 的更新会发生在componentDidMount 或 componentDidUpdate 生命周期钩子之前。

  • 你不能在函数式组件上使用 ref 属性,因为它们没有实例。可以在函数式组件内部使用 ref

  1. 回调 Refs: 不同于传递 createRef() 创建的 ref 属性,你会传递一个函数。这个函数接受 React 组件的实例或 HTML DOM 元素作为参数,将他们存储在react上并使它们能被其他地方访问。React 将在组件挂载时将 DOM 元素传入ref 回调函数并调用,当卸载时传入 null 并调用它。ref 回调函数会在 componentDidMout 和 componentDidUpdate 生命周期函数前被调用。

  2. 受控组件中,表单数据由 React 组件处理

  3. 和 支持 defaultChecked, 和 <textarea> 支持 defaultValue. 在React中, 始终是一个不受控制的组件,因为它的值只能由用户设置,而不是以编程方式设置。

 shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }

[实习笔记]iOS中position:fixed吸底时的滑动出现抖动的解决方案

两种抖动

为什么抖动还会有两种?

其实是我碰到过两种抖动的场景,第一个场景是native的抖动,第二个场景是h5的抖动。

native的抖动

前端开发人员会在app中打开webview,这个时候iOS中position:fixed吸底时的滑动出现抖动应该是native造成的抖动,整个viewport跟着动,所以可以在生成schema的时候将参数bounce_disable设置为1禁止native的弹性效果,然后加上h5的这个效果,-webkit-overflow-scrolling 属性可以帮我们实现这个效果,它控制元素在移动设备上是否使用滚动回弹效果。

h5的抖动

方案一

//我是吸顶头部
.header{
    width:100%;
    height:50px;
    position:fixed;
    top:0px;
}
//我是中间要滑动的部分
.main{
    width:100%;
    height:auto;
    position:absolute;
    padding-top:50px;
    padding-bottom:50px;
    box-sizing:border-box;
    overflow-y:scroll;
    -webkit-overflow-scrolling :touch
}
//我是吸底尾部
.footer{
    width:100%;
    height:50px;
    position:fixed;
    bottom:0px;
}

解释:滑动部分overflow-y:scroll;所以在上下方向超出一屏的部分会变成滚动模式并且不溢出,然后这边吸顶和吸底设置的高度都是50,所以对应的中间滑动部分分别有padding-top:50px;和padding-bottom:50px;设置box-sizing:border-box;所以padding的增加不会增加.main的高度。

方案二

transform: translateZ(0);
-webkit-transform: translateZ(0);

解释:在使用position:fixed的元素上加上该属性。

promise的错误捕获

promise的错误捕获

起源于一个问题:promise的错误能不能被外层的try catch捕获?

主要关键点:
  • try catch是同步捕获错误的
  • promise异步执行,microwork的一种
  • await 可以捕获错误
  • promise自带的catch方法
  • await与try catch
问题的导入:
try{
Promoise.reject('我就是要报错!')
}catch(err){console.log(err)}

try{
await Promoise.reject('我就是要报错!')
}catch(err){console.log(err)}

第一个是没办法捕获到这个错误的,而第2个是可以的。


  • 第一个因为try catch是同步执行的,在promise还没状态改变时就已经运行完毕了,所以不能在捕获错误了。
  • 第二个 await后面加的一个promise会等待promise运行结果,有报错有catch这个错误,然后throw出来给外层。

promise自带的catch方法

catch 是 .then(null, rejection)的别名,用于指定发生错误时的回调函数。

  • 一旦catch前面的任何一个Promise发生异常,都会被catch捕获,包括Promise函数创建的Promise,还有.then返回的Promise,甚至catch前面如果还有一个catch在这个catch抛出的异常也会被后一个catch捕获。

所以常常见到下面这个写法:

promise.then().catch()
promise.then().then().catch()
promise.then().then().catch().then().catch()

await与try catch

async function tryPromise(){
await Promise.reject('错误')
await Promise.resolve('输出')
return
}
try{
await tryPromsie()}catch(err){
}

第一个await出错,错误给后面的catch。async函数直接结束,后面的await就不会执行。
如果想在第一个await出错时候,后面的await还执行,可以在第一个await外层套一个try catch。

后面再看看await是怎么处理错误的,后面在更新。

[实习笔记]移动端1px线&will-change

移动端1px线

viewport告诉你1px为什么变粗

html的代码中经常会有这么一句

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

这句话的意思是当前viewport的宽度为设备宽度,初始缩放值和最大缩放值都为1,并禁止了用户缩放viewport通俗的讲是浏览器上可用来显示页面的区域。而这句话设置的意义是先不需要用户缩放和横向滚动条就能正常的查看网站的所有内容;第二,显示的文字的大小是合适,比如一段14px大小的文字,不会因为在一个高密度像素的屏幕里显示得太小而无法看清,理想的情况是这段14px的文字无论是在何种密度屏幕,何种分辨率下,显示出来的大小都是差不多的。

还有一个因素也会引起css中px的变化,那就是用户缩放。例如,当用户把页面放大一倍,那么css中1px所代表的物理像素也会增加一倍;反之把页面缩小一倍,css中1px所代表的物理像素也会减少一倍。

在早先的移动设备中,屏幕像素密度都比较低,如iphone3,它的分辨率为320x480,在iphone3上,一个css像素确实是等于一个屏幕物理像素的。后来随着技术的发展,移动设备的屏幕像素密度越来越高,从iphone4开始,苹果公司便推出了所谓的Retina屏,分辨率提高了一倍,变成640x960,但屏幕尺寸却没变化,这就意味着同样大小的屏幕上,像素却多了一倍,这时,一个css像素是等于两个物理像素的。其他品牌的移动设备也是这个道理。例如安卓设备根据屏幕像素密度可分为ldpi、mdpi、hdpi、xhdpi等不同的等级,分辨率也是五花八门,安卓设备上的一个css像素相当于多少个屏幕物理像素,也因设备的不同而不同,没有一个定论。

有一个devicePixelRatio属性,它的官方的定义为:设备物理像素和设备独立像素的比例,也就是 devicePixelRatio = 物理像素 / 独立像素。我们在css中使用的px就可以看做是设备的独立像素,所以通过devicePixelRatio,我们可以知道该设备上一个css像素代表多少个物理像素。例如,在Retina屏的iphone上,devicePixelRatio的值为2,也就是说1个css像素相当于2个物理像素。

所以这就能解释为什么我们在css中设置的1px看上去比较粗了,也就是比如说我们当前的设备设置了width=device-width, initial-scale=1.0并且当前的设备devicePixelRatio为2,那么在css设置的1px就相当于物理像素的2px啦。

viewport的宽度可以通过 document.documentElement.clientWidth 来获取

1px解决方法

用小数的px

iOS8已经支持带小数的px,但是安卓和低版本的iOS不适用,所以在要考虑兼容性的情况下这个解决方案不太可取。

.border { border: 1px solid #999 }
@media screen and (-webkit-min-device-pixel-ratio: 2) {
.border { border: 0.5px solid #999 }
}
@media screen and (-webkit-min-device-pixel-ratio: 3) {
.border { border: 0.333333px solid #999 }
}

上文有提到devicePixelRatio,media query对应devicePixelRatio有个查询值-webkit-min-device-pixel-ratio,

transform缩放

版本1:

@mixin border1pxtop($color: rgb(232, 232, 232)) {
position: relative;
&:before {
content: '';
position: absolute;
background: $color;
left: 0;
top: 0;
width: 100%;
height: 1px;
// dpr 小于2.9视为2倍屏
@media screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-max-device-pixel-ratio: 2.9) {
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
// dpr 大于2.9视为3倍屏
@media screen and (-webkit-min-device-pixel-ratio: 2.9) {
-webkit-transform: scaleY((1/3));
transform: scaleY((1/3));
}
}
}

版本2:
解释一下,先给外面的元素定位position:relative,然后给伪元素定位position:absolute,这样伪元素就相对于外面元素定位,所以需要给伪元素加上left:0,top:0,另外给伪元素设置边框border:1px,这边可以定制自己想要的border,包括颜色,上边框或者右边框之类的,然后给width:200%,height:200%,这样就相对于定位的那个元素放大了两倍,此时再加上 transform: scale(0.5);就可以将宽度、高度都缩放到和相对定位的那个元素一样大小,另外边框的宽度也会缩放一倍,达到我们想要的效果。

@mixin border1pxtop2($color: rgb(232, 232, 223)) {
position: relative;
&::before {
pointer-events: none;
content: '';
box-sizing: border-box;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
border-top: 1px solid $color;
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 2dppx) {
width: 200%;
height: 200%;
-webkit-transform: scale(0.5);
transform: scale(0.5);
}
@media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 3dppx) {
width: 300%;
height: 300%;
-webkit-transform: scale(0.333333);
transform: scale(0.333333);
}
}
}

用background

@mixin border1pxleft2($color: rgb(232, 232, 232)) {
background-size: 1px 100%;
background-repeat: no-repeat;
background-position: left top;
background-image: linear-gradient(90deg, $color, $color 100%, transparent 0%);
@media screen and (-webkit-min-device-pixel-ratio: 2) and (-webkit-max-device-pixel-ratio: 2.99) {
background-image: linear-gradient(
90deg,
$color,
$color 50%,
transparent 50%
);
}

will-change

transform方法的版本1有在一些机型不奏效的情况,这个时候可以尝试加上will-change:transform属性。will-change是css3新增的属性。这个属性如果形象一点说,那就是在真正的行为触发之前告诉浏览器:注意咯,我一会儿要有变化啦,做好准备哦!作为回应,浏览器会把GPU给拉上,从容应对即将到来的变化。这样浏览器可以在元素属性真正发生变化之前提前做好对应的优化准备工作。 这种优化可以将一部分复杂的计算工作提前准备好,使页面的反应更为快速灵敏。这也应该是我们使用这个属性的初衷。

但是不得不说想要用好这个属性并不容易,试想那种全局都开启will-change的做法,等于是让浏览器的各个元素都随时GPU渲染加速待命,效果是适得其反的。

下图数据来自https://caniuse.com/#feat=will-change
lark20180627-115620
will-change的几个取值:

  • will-change: auto;

    表示没有特别指定哪些属性会变化,浏览器需要自己去猜,然后使用浏览器经常使用的一些常规方法优化。

  • will-change: scroll-position;

    告诉浏览器优化改变滚动条的位置的场景,并且这个场景在将来会发生。

  • will-change: contents;

    表示开发者希望在不久后改变元素内容中的某些东西,或者使它们产生动画。

  • will-change: ;

    比如will-change: transform;will-change: opacity;

一分钟进入线上cms-fe调试

一分钟进入线上cms-fe调试

当线上出现bug,真的是手忙脚乱啊。所以只能线上调试fe(ps:下文只是口译潘导师操作流程),主要步骤是杀掉线上进程,用node --inspect 启动一个调试的node的进程。然后在本地浏览器进行调试

  1. 进入线上机器
  2. 找到启动脚本
   1 node start

改完后的启动脚本如下:

  1 # fe start
  2 node --inspect=0.0.0.0:8889 node start

0.0.0.0:8889端口其实不固定,不占用就可以。
4.杀点线上正在运行的进程,服务器会按照修改后的bootstrap进行重启,故只要杀掉进程即可

  • 查看当前运行进程的pid
    ps aux | grep node
  • 然后kill
kill -s 9 640190

5.待进程重启完成后,进入浏览器inspect
image
6.本地浏览器配置调试的ip与端口,写入线上docker具体的ip与端口
image
7.进入到调试模式(断点,下一步,blablaba)
image

Docker网络模式学习

Docker基本概念

Docker是对Linux容器的一种封装,提供简单易用的容器使用接口。不是对操作系统的模拟,而是对进程的隔离。Docker相比虚拟机具有启动快,占用资源少,体积少的优点。
image

  • 镜像是一个特殊的文件系统,提供容器运行时所需的程序、库、资源、配置文件及运行时的参数,它是分层存储的架构,构建镜像时,会一层层构建,前一层是后一层的基础,每一层构建完成后就不会再发生改变,这样有利于镜像的复用。
  • 通过 docker run image ,可以生成一个容器,容器在本质上是一个进程,但是每个容器都有自己的命名空间,容器之间是隔离的。容器运行时,以镜像为基础层,在其上创建容器存储层,这个容器存储层的生命周期和容器一样,容器消亡,容器存储层也会消亡,除非使用 docker commit 来生成一个新的镜像
  • Registry用来管理仓库,一个Docker Registry里有多个仓库,每个仓库里有多个tag,每个tag对应一个image文件。tag就好比是一个项目的版本。
  • volume用来做数据持久化

Docker网络模式

  • host模式
    Docker使用Linux的Namespaces技术来进行资源隔离,其中Network Namespace用来隔离网络。通常一个Docker容器会分配一个独立的Network Namespace。Host模式下容器不会获得一个独立的Network Namespace,而是和宿主机共用一个NetworkNamespace.rongqi不会虚拟出自己的网卡,ip等,而是使用宿主机的ip和端口
  • bridge模式
    bridge模式是Docker默认的网络设置,此模式会为每一个容器分配Network Namespace,设置IP等
  • container模式
    这个模式指定新创建的容器和一个指定的已存在的容器共享IP,端口范围等
  • none模式
    Docker容器有自己的Network Namespace,但是并不为docker容器进行任何网络配置,需要我们自己为Docker容器添加网卡、配置IP等

[实习笔记] 怎样更有效率地开发小项目

关于稍微复杂项目开始之前对准备

  1. 查看所需要的数据,以及观察其来源,是本地运算还是远程fetch
  2. init store fe有rootStore和Module_Store 。然后考虑什么样的数据要放在Module_store,什么样的数据不需要放入store 只需要放在自己的组建data.
rootStore.registerModule(MODULE_NAME, moduleStore)
rootStore.unregisterModule(state.moduleName) // 卸载上一个路由对应的 module store
  1. 划分功能,每个组件只负责自己的功能部分,负责后,调用上层传过来的函数

详细的一些技巧

  • 父子通信:子组件定义一个prop,父组件把自己method方法传过去
  • 数据获取:父组件,一般是index.vue会定义method,而该方法在做了一些判断后,会调用store中actions的方法,异步获得数据
  • 单页面还是多页面:如果公用的信息比较多,业务也比较简单,写成单页面,这样在数据共享上会很方便,对应需要在不同情况,展示不一样的组件,可以使用v-if。

比如这个需求,看是多个页面其实只要写一个单页面,然后分别加一个v-if就可以。

  • 页面比较多的时候,在组册组件时候,可以利用路由命名如:
    default

关于excel的导入与导出

使用js-xlsx 插件,具体使用可以看一下github js-xlsx
有搭配的工具,util 无论是转json或是array都比较方便

关于git

git clone   
git checkout -b branch-name origin/branch-name //建立与远程分支关联都本地分支
git pull 与 git push //同步远程分支,与上传分支

注意事项

  • store的mutation 是没有return,会有问题。

  • 以及在使用mapMutations等引入子store时候加上mudule-name

computed: {
    ...mapState(MODULE_NAME, [
      'userBasic',
      'loanDetail',
      'repayList',
      'repayPlan',
      'cardList'
    ]) //从store加入数据
  • 在method上调用自身组件的method方法记得加上this(可执行上下文),关于ActiveObject与scope(作用域链)知识需要在加强一下

  • 关于es5的声明提前,变量和函数的优先级,函数的优先级比变量高。

[实习笔记]强大的stylus!

百闻不如一用。
Talk is cheap,show you the code.
使用前:

.lottery-sector li:nth-child(1) {
background-color: #fff;
transform: rotate(0deg) skew(54deg);
}

.lottery-sector li:nth-child(2) {
background-color: rgb(254, 246, 225);
transform: rotate(36deg) skew(54deg);
}

.lottery-sector li:nth-child(3) {
background-color: #fff;
transform: rotate(72deg) skew(54deg);
}

.lottery-sector li:nth-child(4) {
background-color: rgb(254, 246, 225);
transform: rotate(108deg) skew(54deg);
}

.lottery-sector li:nth-child(5) {
background-color: #FFF;
transform: rotate(144deg) skew(54deg);
}

.lottery-sector li:nth-child(6) {
background-color: rgb(254, 246, 225);
transform: rotate(180deg) skew(54deg);
}

.lottery-sector li:nth-child(7) {
background-color: #FFF;
transform: rotate(216deg) skew(54deg);
}

.lottery-sector li:nth-child(8) {
background-color: rgb(254, 246, 225);
transform: rotate(252deg) skew(54deg);
}

.lottery-sector li:nth-child(9) {
background-color: #FFF;
transform: rotate(288deg) skew(54deg);
}

.lottery-sector li:nth-child(10) {
background-color: rgb(254, 246, 225);
transform: rotate(324deg) skew(54deg);
}

使用后:

.lottery-sector 
  for row in 1..10 
    li:nth-child({row}) 
      if (row % 2 == 0) 
        background-color: rgb(254, 246, 225);
      else 
        background-color: #fff;
      transform: rotate((row * 36)deg) skew(54deg);

是不是觉得清爽了很多!这里有用到stylus的几个语法:插值、嵌套、计算、条件、循环、省略花括号、取一段值的简便写法。

  • 嵌套

    lottery-sector的子元素li的样式直接定义在了它里面
    
  • 插值

    变量文本要作为内容的一部分,用{}括起来,比如 li:nth-child({row})

  • 计算

    比如row * 36,row % 2 == 0

这边我感觉是我踩的一个坑,不过也应该成为编写代码的习惯,变量和计算符号之间要有空格

  • 条件

    if和else语句的使用是允许的

  • 循环

    用for in循环,这边值得夸奖的是它的简便写法,循环1~10可以直接写成1..10(包含边界,即包含10),1...10(不包含边界,不包含10)。

分享一波上周使用vue细节2018.06.04

#分享一波上周使用vue细节2018.06.04

只是分享了坑,没有详细去说VUE里面的源码实现逻辑(因为也没看哈)

  1. vue style 加不加scoped区别:
    当 <style> 标签有 scoped 属性时,它的 CSS 只作用于当前组件中的元素。机制是给dom添加一个data-*的自定义属性,然后style会这么操作.classNamep[data-],也就是属性选择器。注意,只要静态的dom和class才有效,动态添加的dom和class样式是无效的。解决办法是可以去掉scoped或是用内联样式dom.style......

  2. vue watch是可以深度监听的(默认就是深度监听),也就是没有vue那个this.$set这个问题。ps:记得 这个watch是没有s 不是watchs.

  3. methods方法不能用箭头函数绑定,因为箭头函数声明时候回绑定声明时父级的作用域。ps:vue会给methods等 函数执行时候绑定当前组件的实例

  4. 关于el-element中关于表格 el-table里面那个自定义scope.如果和表单一起使用,那个row绑定 也会改变以前的数值,属于引用绑定

  5. vue的v-for。知道在es6中,有new Map()对象的 for of 循环 ,也就是迭代器。在vue中虽然也有v-for,有 for of和 for in。但是呢 我在使用过程中发现是不能用迭代器的。

jsonArray _to_excel

个人插件 写一些平时自己用到的一些组件,主要基于 vue

具体插件

  • jsonArray 导出到excel
    import FileSaver from 'file-saver'
    import XLSX from 'xlsx'
  props: {
  params: {
    type: Object,
    default: () => {} //{jsonData:[],keyMap:{name:'名字'}}
  }
},

这个组件输入,一个是待转化的对象数组,字段会是表格的表头,然后 keyMap,传入可将对应的英文字段 在 excel 用中文展示,以及表头的顺序。如果没传入 keyMap,默认使用 Object.key(jsonData)

对应的的组件链接outportXlsx

附上

  • 从excel导入至jsonArray
    对应的的组件链接ImportXLSX
 props: {
    onImport: {
      type: Function,
      default: () => {}
    }
  },
  • 这个函数的第一个参数是excel数据,[sheet1,sheet2,...],所以第一个参数arrays 对应的arrays[0],才是第一个工作表对应的数据。

chart in 2b project

2B的报表组件

主要使用 frappe-chart 组件,对其做了一个简单封装。主要提供以下几个功能。

  1. 图形的放大
  2. 图片的导出,可导出svg格式和导出excel表格(自己封装的一个组件)
  3. 插槽添加扩展按钮
  4. 显示最大值 最小值 平均值
  5. 对单点数据错误做了处理
  6. byte上的包解决import 错误和 stacked bar 在0值的ui问题

主要属性

chartData: {}
chartConfig:{}

chartData与chartConfig设置和官网一样 主要增加如下配置

chartConfig: {
        btnOptions: {
          sizeBtn: false,//{ show: true,}
          exportBtn: { show: true, type: 'data', xlabel: '时间' }
        },
        showMax_Min_Mean: {
          max: false,
          min: false,
          mean: false
        }}

组件比较简短粗燥 ,所以直接端上来了。

<template>
  <div :class="{component_chart:true,chartLarge:hasLarged}"
    v-if="chartShow">
    <div class="chartWrap"
      ref="chartWrap">
      <div class="chart"
        ref="chart">
      </div>

      <!-- 扩展的按钮 -->
      <div class="anatherBtn">
        <slot>
        </slot>
      </div>

      <div :class="{largeBtn:(chartConfig.btnOptions||{}).sizeBtn, chartBtn:true}"
        @click="changeSize"
        v-if="(chartConfig.btnOptions||{}).sizeBtn">
        <i class="el-icon-rank"></i>
      </div>
      <div :class="{downloadBtn:(chartConfig.btnOptions||{}).exportBtn, chartBtn:true}"
        @click="exportChart"
        v-if="((chartConfig.btnOptions||{}).exportBtn||{}).show&&chartConfig.btnOptions.exportBtn.type=='pic'">
        <i class="el-icon-download"></i>
      </div>

      <out-put :class="{downloadBtn:(chartConfig.btnOptions||{}).exportBtn, chartBtn:true}"
        :params="outputParams"
        v-if="((chartConfig.btnOptions||{}).exportBtn||{}).show&&chartConfig.btnOptions.exportBtn.type=='data'">
        <i class="el-icon-download"></i>
      </out-put>
    </div>
  </div>
</template>

<script>
import _ from 'lodash'
import OutPut from '~/components/common/outputXLSX'
import { Chart } from 'frappe-charts'
export default {
  data() {
    return {
      chartShow: true,
      chartDom: {},
      hasLarged: false,
      chart: {},
      config: {}
    }
  },
  props: {
    chartData: { type: Object, default: () => ({}) },
    chartConfig: { type: Object, default: () => ({}) }
  },
  computed: {
    outputParams() {
      let jsonData = []
      let keyMap = {
        xlabel: ((this.chartConfig.btnOptions || {}).exportBtn || {}).xlabel
      }
      //验证数据格式
      if (
        !(
          Array.isArray(this.chartData.datasets) &&
          this.chartData.datasets.length &&
          Array.isArray(this.chartData.labels) &&
          this.chartData.labels.length
        )
      ) {
        return { jsonData, keyMap }
      }
      this.chartData.datasets.forEach((line, index) => {
        keyMap['key' + index] = line.name
      })

      this.chartData.labels.forEach((x, i) => {
        let oneRow = {}
        oneRow.xlabel = x
        this.chartData.datasets.forEach((line, index) => {
          oneRow['key' + index] = line.values[i]
        })
        jsonData.push(oneRow)
      })
      return { jsonData, keyMap, fileName: this.chartConfig.title }
    }
  },
  watch: {
    chartData(newValue, oldValue) {
      let chartDom = this.$refs.chart
      if (
        Array.isArray(newValue.labels) &&
        Array.isArray(oldValue.labels) &&
        newValue.labels.length == oldValue.labels.length &&
        newValue.datasets.length == oldValue.datasets.length
      ) {
        this.chart.update(newValue)
      } else {
        this.initChart()
      }
    }
  },
  components: { OutPut },
  methods: {
    changeSize() {
      this.hasLarged = !this.hasLarged
      if (this.hasLarged) {
        this.$set(this.config, 'height', this.config.height * 2)
      } else {
        this.$set(this.config, 'height', this.chartConfig.height)
      }
      this.initChart(true)
    },
    exportChart() {
      this.chart.export()
    },
    initChart(isSize) {
      if (!isSize) {
        this.config = _.cloneDeep(this.chartConfig)
      }
      //验证数据格式
      if (
        !(
          Array.isArray(this.chartData.datasets) &&
          this.chartData.datasets.length &&
          Array.isArray(this.chartData.labels) &&
          this.chartData.labels.length
        )
      ) {
        return
      }
      //单点情况
      if (this.chartData.labels.length == 1) {
        this.chartData.labels.push('')
        if (this.config.type == 'line') {
          this.config.lineOptions = {
            dotSize: 4,
            hideDots: 0
          }
        } else if (this.config.type == 'bar') {
          this.config.barOptions = {}
        }
      } else {
        if (!isSize) {
          this.config = _.cloneDeep(this.chartConfig)
        }
      }

      let allData = []
      this.chartData.datasets.forEach(element => {
        allData.push(...element.values)
      })
      //确保坐标最大值和最小值能显示
      if (this.chartConfig.showMax_Min_Mean) {
        if (
          !(this.chartData.yMarkers && Array.isArray(this.chartData.yMarkers))
        ) {
          this.chartData.yMarkers = []
        }
        if (this.chartConfig.showMax_Min_Mean.max) {
          this.chartData.yMarkers.push({
            label: 'max',
            value: Math.max(...allData),
            options: { labelPos: 'left' }
          })
        }
        if (this.chartConfig.showMax_Min_Mean.min) {
          this.chartData.yMarkers.push({
            label: 'min',
            value: Math.min(...allData),
            options: { labelPos: 'left' }
          })
        }
        if (this.chartConfig.showMax_Min_Mean.mean) {
          let sum = 0
          allData.forEach(item => {
            sum += item
          })
          this.chartData.yMarkers.push({
            label: 'mean',
            value: sum / allData.length,
            options: { labelPos: 'left' }
          })
        }
      }

      if (
        this.chartData.labels.length < 15 &&
        this.config.lineOptions &&
        !this.config.lineOptions.hideDots
      ) {
        this.chartConfig.lineOptions.dotSize = 4
      }
      setTimeout(() => {
        this.chart = new Chart(this.chartDom, {
          data: this.chartData,
          ...this.config
        })
      }, 50)
    }
  },
  mounted() {
    let chartDom = this.$refs.chart
    this.chartDom = chartDom
    this.initChart()
  },
  beforeDestroy() {
    //离开时候 注销chart
    if (
      this.chart.unbindWindowEvents &&
      typeof this.chart.unbindWindowEvents == 'function'
    ) {
      this.chart.unbindWindowEvents()
    }
  }
}
</script>
<style lang="stylus">
.chart
  .frappe-chart.chart
    text.title
      font-size 14px
      fill rgb(34 34 34)
</style>
<style scoped lang="stylus">
.component_chart
    font-family 'PingFangSC-Regular'
    display flex
    flex-direction column
    justify-content center
    width 100%
    overflow hidden
    .chartWrap
      position relative
      text-align center
      overflow hidden
      .chart
        width 100%
      .chartBtn
          font-size 18px
          padding 2px 5px
          position absolute
          color rgb(182 192 226)
          &:hover
            cursor pointer
        .largeBtn
          top 10px
          right 1px
        .downloadBtn
          bottom 0px
          right 1px
        .anatherBtn
          position absolute
          top 4px
          left 115px
          // top 10px
          // right 60px
.chartLarge
  width 970px
  height 500px
  .largeBtn
     top 10px
     right 20px!important
  .downloadBtn
     bottom 0px
     right 20px!important
</style>

[第三期] determine the user ip from the request

需求描述

这里定义为获取终端 "用户" ip, 考虑到实际场景需要 bypass proxy / LB

基本要求

  • 以 fastify 的plugin 形式产出组件
  • 编写 unit test,实现代码 100% 全覆盖

单元测试的编写参考:@fe/byted-consul

思路

  1. 直连或透明代理由 TCP 协议获取

  2. proxy / LB 从 http 协议(header)获取

  3. 一些标准化的云服务 / LB 服务, 从 header 中标准定义获取

策略梳理

  1. 优先 header 关键字判定, 命中则为 proxy / LB

    1. 检查最标准无歧义的 header: X-Client-IP

      一般国外云服务商(Amazon / Heroku)遵循的业界标准

    2. 检查记录代理信息的 header: x-forwarded-forx-real-ip

      都为约定速成的 header, x-forwarded-for 目前应用最广泛, x-real-ip 是早期 fastcgi 等默认的 header

      node 服务最常见的 proxy 和 LB 默认配置, 属性值为 [client_IP, proxy1_IP, proxy2_IP, ...]

      如果是可控的 proxy 配置, 例如公司 TLB(nginx), 确保: location 配置包含 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Real-IP $remote_addr;

    3. 检查一些常见云服务商约定的 header:

      1. cf-connecting-ip(Cloudflare)
      2. true-client-ip(Akamai)
      3. x-cluster-client-ip(一些 LB 服务商标准)
    4. 检查 x-forwarded-for 的常见变体:

      1. x-forwarded
      2. forwarded-for
      3. forwarded
  2. 取 TCP 协议中的 remoteAddress (只有两种情况下可以获取 client ip, 与 client 直连或是经过透明代理)

    透明代理的 nginx 配置: proxy_bind $remote_addr transparent;

    按 node net 模块 实现, 依次检查:

    1. req.connection.remoteAddress
    2. req.socket.remoteAddress
    3. req.connection.socket.remoteAddress
    4. req.info.remoteAddress
  3. 兼容一些 serverless 服务商, 从 req 附加 context 获取

    1. req.requestContext.identity.sourceIp (AWS Lambda)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.