# 2020年03月技术日常

# 2020/03/30 周一

# JSON数据转Blob后,怎么还原

在axios请求下载文件接口时,一般设置responseType: 'blob',文件返回正常就没问题,但后台如果处理文件或鉴权有问题,接口返回了包含错误信息的json格式数据,那样json数据也会转为Blob对象,而前端有必要将错误信息展示的,那怎么将Blob数据转JSON呢?下面来看看

let fileType = res.headers['content-type']
if (fileType.startsWith('application/json')) {
  let reader = new FileReader();
  reader.addEventListener("loadend", function() {
    let data = JSON.parse(reader.result)
    console.log(data);
  });
  reader.readAsText(res.data, "UTF-8") // 加UTF-8防止中文乱码
  return
}

# 2020/03/28 周六

# 网页dark mode(深色模式)适配

微信最近推出了深色模式,我试了下,手机切换时页面效果样式是实时刷新的。于是就想着web怎么能够监听深色模式,并设置样式。查了资料后,在Stack Overflow上找到了答案

通过css里的媒体查询就能适配深色模式,先来看看怎么用js获取当前是否是深色模式

// 获取当前是否是深色模式
// window.matchMedia('(prefers-color-scheme: dark)').matches
window.matchMedia && console.log('Is dark mode: ', window.matchMedia('(prefers-color-scheme: dark)').matches)

// 用js监听深色模式的切换事件
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (event) => {
  console.log('dark mode change,已' + (event.matches ? '进入': '退出') + 'dark mode')
})

window.matchMedia到底是用来做什么的?我查了下mdn,发现了这样一个示例

let mql = window.matchMedia('(max-width: 600px)');

document.querySelector(".mq-value").innerText = mql.matches;

从这个例子看,大概就知道怎么用css来支持dark模式了吧,就是加一个类似小屏适配的一个媒体查询样式,来看个例子

/* dark mode support */
@media (prefers-color-scheme: dark) {
  body {
    background-color: black;
    color: #aaa;
  }
  
  body .content article, header, aside > div, footer  {
    border-color: #333;
    color: #aaa;
    background-color: black;
    box-shadow: 0 0 10px #333;
  }
}

深色模式下,一般将背景调暗,字体设置为偏白色即可。zuo11.com 已用上面的方法适配了深色模式,可以体验下。网站是开源的,zuo11.com深色模式支持代码 - github (opens new window)

参考:

# 2020/03/27 周五

# canvas绘制模糊的问题

今天发现同样的代码在两台电脑上绘制的一个清晰,一个模糊,后来查资料发现确实有这个问题

因为canvas不是矢量图,高dpi屏幕每平方英寸有更多的像素,也就是两倍屏,浏览器会以两个像素点的宽度来渲染一个像素,所以在Retina屏上会导致图片、文字都会模糊,怎么解决呢?

获取设备像素比:window.devicePixelRatio || 1

如果绘制的实际区域大小为 750 * 40,假设设备像素比为2,那么,canvas的width、height需要设置为 1500 * 80,然后用内联样式设置width为750,height为40,相当于canvas绘制2倍的大小,然后再缩放,这样就清晰了。

综上,在canvas绘制时,各种长度一定要考虑乘以devicePixelRatio,不然可能显示的不清晰

参考:解决 canvas 在高清屏中绘制模糊的问题 (opens new window)

# 2020/03/26 周四

# canvas不支持文本换行怎么处理

今天在stackoveflow里面搜索ctx.fill的问题时,查到了很多关于canvas ctx.fillText()绘制文本时不支持换行的问题,找到了一个比较好的答案

I'm afraid it is a limitation of Canvas' fillText. There is no multi-line support. Whats worse, there's no built-in way to measure line height, only width, making doing it yourself even harder!

一般解决思路是,根据 ctx.measureText('Hello').width 来看需要显示的文字是否需要换行,写一个for循环来处理

参考:

# canvas绘制不规则形状填充渐变色

在JS高程3中,有一章专门将使用canvas绘图,今天终于用上了,效果还不错,来看效果,原生js,70行不到

cavas_unnormal_shape.png

<canvas id="drawing1" width="720" height="45" >A draw of something.</canvas>
<script>
  drawStatus('drawing1', 2)
  function drawStatus(domId, position) {
    let str = ['① 状态一', '② 状态二', '③ 状态三', '④ 状态四', '⑤ 状态五', '⑥ 状态六']
    let config = {
      width: 100,
      height: 40,
      extendLength: 20,
      radius: 4
    }
    let config2 = { ...config, width: 110 }
    let cur = str.length - position

    str.reverse().forEach((item, index) => {
      let pos = str.length - index - 1
      let x = 0 + (str.length - 1) * config.radius
      if (pos !== 0) {
        x = (pos * 100) + (pos -1) * 10 + (str.length - 1 - pos) * config.radius
      }
      console.log(pos,x)
      let curConfig = pos === 0 ? config : config2 
      if (pos < (str.length - cur)) {
        curConfig.isFocus = true
      }
      drawUnnormalShape(domId, x , 0, str[index], curConfig)
    })
  }

  function drawUnnormalShape(domId, x, y, text, config) {
    let drawing = document.getElementById(domId);
    let ctx = drawing.getContext('2d');
    let { width, height, extendLength, radius, isFocus } = config 
    ctx.beginPath(); // 如果都需要重新beginPath 不然,后面的fill会覆盖前面的fill
  
    // 不规则矩形
    ctx.moveTo(x + radius, y) // 从左上角 (x + radius, y) 位置开始
    ctx.arcTo(x, y, x, y + radius, radius) // 左上圆角
    ctx.lineTo(x, y + height - 2 * radius) // 画左边
    ctx.arcTo(x, y + height, x + radius, y + height, radius) // 左下圆角
    ctx.lineTo(x + width - radius, y + height) // 下边
    // ctx.arcTo(x + width - radius, y + height, x + width - radius, y + height - 1, 1) // 圆角

    let extendEndX = x + width + extendLength
    let middleHeight = y + height / 2
    ctx.arcTo(extendEndX, middleHeight, extendEndX - radius, middleHeight - radius, radius) // 线 + 圆角
    ctx.lineTo(x + width - radius, y)
    ctx.lineTo(x + radius, y)

    var gradient = ctx.createLinearGradient(x, y + height / 2, x + width + extendLength, y + height / 2); // 从(130,130)到(160,160)渐变
    gradient.addColorStop(0, isFocus ? '#62ccff' : '#fff'); // 渐变的起点色
    gradient.addColorStop(1, isFocus ? '#0486fe' : '#fff'); // 渐变的结束色

    ctx.shadowOffsetX = 6;
    ctx.shadowOffsetY = 2;
    ctx.shadowBlur = 16; // 模糊像素
    ctx.shadowColor = "rgba(58, 86, 111, 0.15)";

    ctx.fillStyle = gradient
    ctx.fill() // ctx.stroke()

    let textArr = text.split(' ')
    ctx.font = "15px arial"
    ctx.fillStyle = isFocus ? '#fff' : '#ccc'
    ctx.fillText(textArr[0], x + width / 2 - 20, y + height / 2 + 5)
    ctx.font = "11px arial"
    ctx.fillText(textArr[1], x + width / 2, y + height / 2 + 4)
  }
</script>

参考之前的笔记:使用canvas绘图 - JS高程3笔记 (opens new window)

完整demo: canvas画不规则形状填充渐变背景 - github (opens new window)

# canvas 多次fill会覆盖前面的fill

在使用canvas进行绘图时,封装了一个绘制函数,每次都会填充颜色 ctx.fill(),如果多次执行,只会在最后一次时,整体fill一次?

刚开始以为是后面的fill覆盖了前面的fill,后来网上查了下,第一次fill后,再次fill需要再次调用ctx.beginPath(),不然只会在最后一次fill。

参考: HTML5的canvas标签为什么会覆盖之前画的东西的颜色  (opens new window)

# 2020/03/25 周三

# 根据文件名后缀判断文件类型不准确

比如我有个1.png文件,我修改下后缀名 1.txt,那前端如果仅凭文件名的后缀来显然是不行的,我们需要根据文件类型的二进制数据标记来判断对应的文件类型,这样才会更加准确,安全性更高

判断文件类型.png

# 怎么判断两个文件一模一样

一般文件的md5可能会有重复的,怎么减少这种概率呢?校验分三个部分

  1. 比较整个文件的md5
  2. 选择文件固定位置的几个片段分别计算md5进行比对
  3. 比较文件名是否一样

# md5加密是可逆的吗?

理论上md5加密后,在不知道原始消息的前提下,是无法凭借16个字节的消息摘要(Message Digest),还原出原始的消息的.

但为什么有些网站可以破解md5加密后的密码呢?主要是使用的碰撞检测。它会提前算出一些常用弱密码的md5值,一个个比较。才会让人产生md5可逆的错觉。一般除了md5加密外,我们可以再多进行一些处理(加盐),来进行干扰,提高破解难度

参考:为什么说 MD5 是不可逆的? (opens new window)

# 两个不同文件md5可能一样吗?

什么是md5?md5 是 messge digest [daɪˈdʒest] 5 的缩写,意思是信息摘要算法

linux下,在terminal中执行man md5,可以查看对应的文档

md5 -- calculate a message-digest fingerprint (checksum) for a file

md5 -- 为一个文件计算信息摘要指纹('校验和'或'校验码')

md5 -s '123456'
# MD5 ("123456") = e10adc3949ba59abbe56e057f20f883e

md5 1.txt
# MD5 (1.txt) = 6f74626e0749e5353cc7e11767418d43

从上面的例子中我们可以看到,将文件或字符串进行 md5校验 会生成一个 32位 的校验码。问题来了,网上看到 md5加密后一般是128位,而这里只有32位为什么呢?我们要分清16进制与2进制,标准说法是,md5加密后的字符为 128bit(16字节),而一个我们看到的32位是16进制,每一位都可以转为4bit,也就是4个二进制位。1 - f 分别对应 0000 - 1111,所以128bit

一个二进制位(bit)只能表示0或1两种情况,128bit可以表示 Math.pow(2, 128) 2的128次方种情况,定死了,最多只能表示这么多种情况,不同内容的文件实在是太多了,理论上绝对是会超过2的128次方的。

综上:两个不同文件md5是有可能相同的,因为md5最多只能表示2的128次方种情况,而不同的文件绝对大于这个数

虽然两个文件的md5可能一致,但给定一个文件的md5值,想伪造另一个文件的md5值与该值一样,相对还是比较困难的,因此可用于判断文件完整性

参考: 有没有两个完全不一样的文件,但是他们的md5值是一样的? (opens new window)

# 2020/03/22 周日

# koa-multer与@koa/multer逻辑差异

之前有了解过以@开头的作用域包,这次在使用koa-multer这个模块时,发现@koa/multer与koa-multer的逻辑居然不一样。源码有些差异,下面来具体看看

// 在使用 koa-multer 时
const multer = require('koa-multer')
router.post('/test', multer().none(), ctx => { 
  let isFormData = ctx.headers['content-type'].startsWith('multipart/form-data')
  // ctx.req node的request对象, ctx.request koa的request对象
  ctx.body = isFormData ? ctx.req.body : ctx.request.body
})

koa-multer这个包是从express的multer包上面加了一层封装,而koa-multer并没有把fileds字段挂载到ctx.request.body上,只维持原来express那样挂载到node的request对象上,也就是ctx.req.body,来看看koa-multer的源码部分

// https://github.com/koa-modules/multer/blob/master/index.js
multer[name] = function () {
    const middleware = fn.apply(this, arguments)

    return (ctx, next) => {
      return new Promise((resolve, reject) => {
        middleware(ctx.req, ctx.res, (err) => {
          err ? reject(err) : resolve(ctx)
        })
      }).then(next)
    }
  }

而@koa/multer则做了处理,可以与上面的例子对比下

// https://github.com/koajs/multer/blob/master/index.js
multer[name] = function() {
    const middleware = Reflect.apply(fn, this, arguments);

    return (ctx, next) => {
      return new Promise((resolve, reject) => {
        middleware(ctx.req, ctx.res, err => {
          if (err) return reject(err);
          if ('request' in ctx) {
            if (ctx.req.body) {
              ctx.request.body = ctx.req.body;
              delete ctx.req.body;
            }

            if (ctx.req.file) {
              ctx.request.file = ctx.req.file;
              ctx.file = ctx.req.file;
              delete ctx.req.file;
            }

            if (ctx.req.files) {
              ctx.request.files = ctx.req.files;
              ctx.files = ctx.req.files;
              delete ctx.req.files;
            }
          }

          resolve(ctx);
        });
      }).then(next);
    };
  };

我们再来看看使用@koa/multer的情况,就比较方便了,和其他数据一样挂载到 ctx.request.body

const multer = require('@koa/multer')
router.post('/test', multer().none(), ctx => { 
  ctx.body = ctx.request.body
}) 

综上,如果某个模块有两种包名,建议先考虑@开头的作用域包,通常这种功能会新点。后面迭代维护应该都是以这个为准

# 2020/03/20 周五

# npm包前面加@是什么意思(vue-cli与@vue/cli)

Vue CLI 的包名称由 vue-cli 改成了 @vue/cli。 如果你已经全局安装了旧版本的 vue-cli (1.x 或 2.x),你需要先通过 npm uninstall vue-cli -g 或 yarn global remove vue-cli 卸载它。

今天看vue-cli文档,发现上面的这段话 vue-cli 改为了 @vue/cli,这两个npm有什么区别呢?npm包前面加@是什么意思,查了下官网

npm包前面加@,代表scopes相关的包,可以理解为作用域(范围)包,作用域使我们可以创建与其他用户或组织创建的包同名,而不会发生冲突。

A scope allows you to create a package with the same name as a package created by another user or Org without conflict.

作用域名称是介于@和斜线之间的所有内容:

The scope name is everything between the @ and the slash:

// “npm” scope:
@npm/package-name
// “npmcorp” scope:
@npmcorp/package-name

npm包一个诟病就是包名很容易被占用的问题,占用后用其他人就不能用了。而作用域包类似于创建了一个命名空间,不同的命名空间,可以使用相同的包名

作用域的命名不是谁便就能用的,只有两种可以使用:自己的用户名、自己创建的组织名

注意:必须先注册一个npm用户帐户,然后才能发布用户作用域的npm软件包。此外,要发布组织作用域的软件包,您必须创建一个npm用户帐户,然后创建一个npm Org(组织)。

在 vue-cli 中可以用@vue/cli说明使用了vue这个npm账号或者组织发布了该包。

参考:

# 2020/03/18 周三

# md怎么加引用注释,脚注

这里有一个注脚[^1],这段话的还有其他意思[^2]在里面

[^1]:这里是注脚内容
[^2]:这里是其他意思的注脚

注脚放到中间也可以

这里有一个注脚^1,这段话的还有其他意思^2在里面

注脚放到中间也可以

# md中链接的另一种写法

我是一段文字,[baidu][1][qq][2]里面有链接

[1]: http://baidu.com "baidu"
[2]: http://qq.com "qq"

我是一段文字,baidu (opens new window)qq (opens new window)里面有链接

# 2020/03/17 周二

# Node.js的核心用处及应用场景

  1. 打包构建、工程化,主要依赖基础的fs模块,文件读写,如xxx-cli(脚手架)、webpack、parcel、hexo,node在打包构建、前端工程化这块基本影响了整个前端的开发过程,各框架基本都有基于node的cli,快速生成脚手架,使开发更加高效、规范。

  2. 写后台接口,主要依赖基础的http模块,处理请求和响应,如 express.js、koa.js,一般主要用于模拟假数据接口, 调UI、交互效果以及做一些请求响应方面的自测

  3. 综合应用:获取数据+渲染页面(高并发、高性能),koa.js对于开发商业化应用来说还是比较单薄,egg.js基于koa做了一些增强,让node也可以做企业级应用。阿里的使用场景就是一个很好的例子,基础设施大部分采用 Java 实现,变化较少,有事务要求的 Business Services 通常使用 Java。而Node主要用于替代过去php、jsp使用场景, 用在需要快速迭代,需求变化非常快的用户侧。node已经经受了阿里双11的考验,技术上是可行的。

题外话:个人认为综合应用这块,自己玩玩还可以,小团队或node不是非常强的技术团队尽量不要尝试,阿里能做好这块是因为国内顶尖的node方面人才基本都在阿里,经过多年实践踩坑,拥有相对完善的node基建和生态。目前市面上前端里node强的比较少,饿了么为了招node服务端开发,还专门写了个node相关的面试教程。可想而知这方面人才有多少。

node支持高并发的原因:

  • node.js基于异步I/O,接收到请求后,直接开一个I/O线程去执行,然后就不管了,立即继续执行主线程。等I/O线程执行完成后,直接执行对应的回调函数即可。省去了许多等待请求的时间
  • 事务驱动,主线程通过event loop事件循环触发的方式来运行程序,这一条暂时还不是很理解,先写上~

参考:

# webpack与parcel区别

webpack与parcel都是打包工具,webpack功能强大,但比较重,配置项比较多,有点繁琐。而parcel就是为了解决配置项太多这个问题的,它默认集成了通用的常规功能,零配置,如果自定义较多,还是推荐webpack

If you don't want to worry about configuring everything and your needs are common needs, you should go directly with parcel. Parcel provides defaults (for babel-preset-env, post-css, html, etc) that fits most scenarios and works for everybody. You don't have to worry about configuring anything.

From the other hand, if you need a more customization, you should go with webpack. Keep in mind that you will have to setup everything that you need, explicitly set those things.

参考:Webpack vs Parcel - Stack Overflow (opens new window)

# mac使用触控板拖动复制、移动窗口

今天才意识到,每次要复制一段文字或移动某个应用窗口,我都是点击触控板再拖动。而且Mac Air的触控板按的声音比较响,于是找了下是否有手势可以支持。

发现可以设置使用 三指拖移 复制文字和拖动窗口,三个手指放上去拖动就可以了。

设置方法:打开系统统偏好设置 => 点 “辅助功能” => 点 “鼠标与触控板” => 点 “触控板选项” => 先勾上启用拖移,然后选择“三指拖移”,点击 “好”

图文详情参考: MacBook触控板选中/复制 (opens new window)

# 2020/03/12 周四

# 关于vue和react思考

我个人理解 vue组件设计的对新手超级友好,使用门槛很低。虽然我没用过react,但我可以猜到React应该也是牺牲了一定的用户使用门槛,来实现更加灵活的js控制。而vue牺牲了用js控制的灵活度来降低了用户使用门槛。侧重点不一样,但底层实现基本都差不多,比如虚拟dom、hooks,只是开放给用户的调用方式不一样。

前端框架再怎么变动,就算弄出一朵花来,也只是对dom的修改,最终还是会回归到 js高程3里面讲到的dom章节部分内容。

# git commit 提交信息有误怎么修改

如果不小心提交了,但没有push,可以使用下面的命令来修改上一次的commit信息

git commit --amend -m 'xxx'

# 他人提交了package-lock.json的更新导致拉取时和本地冲突

一般在npm install 时会修改package-lock.json文件,我一般不会提交这个更新,但今天发现有人提交。我拉取时,提示这个文件冲突,导致拉取不下来,我又不想提交更新,所以尝试用下面的命令,将工作区该文件的修改丢弃,再拉取

git checkout -- package-lock.json

拉取成功后,npm run serve 基本没什么影响

# 2020/03/11 周三

# vue封装组件方式的思考

在封装组件时,一般我们使用的方法是

把组件单独放到一个xx.vue,然后需要引入时在components使用懒加载引入再使用

我就在想,每次引入组件都需要三步

  1. 把组件通过 components 引入
  2. 在template中写对应的代码
  3. 在data中写对应的数据,methods里写绑定的事件

会不会太麻烦了,我希望像element的组件那样,通过 this.$message.error(e.message) 这样直接调用一个组件

于是我尝试使用js的方式来调用单文件组件(.vue),在之前02/20号写过方法,除了直接挂载到body外,也可以挂载到任何地方,只要你能拿到对应的dom,可以使用ref属性,再通过this.$refs['xx']来获取其DOM 元素和组件实例。

// 引入该组件
import ShowInfo from 'showInfo.vue'

// 通过js调用
clickShow() {
  // 创建一个vue组件
  const Component = Vue.extend(ShowInfo)

  // 在文档之外渲染并且随后挂载,返回对应的Vue实例(vm)
  let showInfoVue = new Component().$mount() 

  // 将组件实例的dom,append到当前页面body下
  this.$el.appendChild(showInfoVue.$el) 
}

其实你发现没,用js直接调用vue组件可以是可以,但也要比正常情况下写更多代码,比如

  1. js调用vue的方法需要封装为一个class
  2. 以上面的示例为例子,通过js调用组件,我们需要一个成功的回调,以及传参到组件,在获取到ShowInfo时,我们需要知道我们引入的只是一个'js对象'。我们可以在对象的methods里面注入方法,用来获取传入的值,或挂载成功后的回调。这样相当于mixin,但.vue组件实现里使用这些注入的事件时会不好理解,有种默认其妙多出来全局函数的疑惑
  3. 以js方法写的组件,不能兼容默认的引用方法,如果要支持那就要写一些额外的代码
  4. 你会发现逻辑会变的不好理解,不够简单,对新手不友好,如果需要其他人维护时,可能不好理解为什么这么做

综上所述:默认的封装调用组件的方式就很好,简单、明了,你想在调用的时候轻松,那么在封装组件时,就会增加对应的工作量,整体工作量差不多。

我们再来看element组件,对于内容比较少的,比如通知类,element都提供的是js调用方法,而没有普通的组件调用方法。且一般挂载到body上。为什么dialog组件没有封装成js呢?我的理解是dialog里面的内容可扩展性很强,如果改为js调用,可能会出现把大量代码写在js的情况,或者需要写VNode的render方法。就显得不够优雅了。

总结:在封装组件时,什么时候用封装为js调用方式、什么时候采用普通的封装呢?我的理解是可以通过下面几个方面来进行评估

  1. 被封装的组件需求是否稳定,有没有可能会在后面经常变更或者进行渐进式增强,如果不稳定,不建议封装为js调用方式,对于可扩展性强的,还是建议使用普通组件封装方式,更利于维护
  2. 是否是挂载到body下,还是需要放到任意的div内? 一般普通组件更好放置。如果挂载到body下,可以考虑封装为js调用方式
  3. 是否功能相对单一简单,如果组件功能单一简单建议封装为js调用方式
  4. 该组件是否在页面里大量被调用 大量的被调用,意味着大量重复的代码,可以考虑封装为js调用方式,增加组件实现时的复杂度,来降低调用时的复杂度

总之,不管怎么样封装,当有人问你为什么这么封装时,你要能够说出你自己的理由。

# 2020/03/10 周二

# vue自定义组件v-model属性实现dialog组件的二次封装

当某个组件是element的dialog组件时,我们需要对dialog进行隐藏显示,当子组件里的dialog关闭时,需要修改父组件传入的值,尽管不是表单组件也可以使用v-model来解决,先来看看怎么调用

<template>
  <div>
    <user-selection v-model="showUserSelection" @confirm="confirm"/>
    <el-button type="primary" @click="showUserSelection = true">打开弹窗</el-button>
  <div>
<template>
<script>
export default {
  components: {
    UserSelection: () => import("../src/components/user-selection/src/main")
  },
  data() {
    return {
      showUserSelection: false
    }
  },
  methods: {
    confirm(value) {
      console.log(value)
    }
  }
}
</script>

再来看组件实现

<template>
  <div>
    <el-dialog title="提示" :visible.sync="dialogVisible">
      内容
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>
<script>
export default {
  model: {
    prop: 'show', // 设置对应v-model的属性字段
    event: 'close' // 如果不指定默认为input,当$emit该事件,可以自动执行 修改父组件v-model参数的值
  },
  props: ['show'], // 接收v-model的传值
  computed: {
    dialogVisible: {
      get() {
        return this.show
      },
      set(newVal) {
        console.log(newVal)
        this.$emit('close', newVal)
      }
    }
  },
  data() {
    return {}
  }
}
</script>

# 封装组件时预留vue插件入口便于全局引入

在element组件中,我们使用el-input等element元素时,不需要在components里引入。为什么呢?在引入element时,我们有在mian.js里我们使用了Vue.use(elemnt组件),这样进行了全局注入组件,相当于组件做成了一个vue插件,如果我们自己封装组件如何能够在Vue.use后直接可以全局调用呢?

于是我特意去看了下element组件源码,这里我们暂时不要求封装为npm包,只需要在平常自定义组件的基础上做一个增强,可以使用Vue.use全局引入。

element-ui源码中,以alert组件为例,来看目录结构

alert
├── src
│   └── main.vue  # 组件实现
└── index.js # install方法,供全局引入
import Alert from './src/main';

/* istanbul ignore next */
Alert.install = function(Vue) {
  Vue.component(Alert.name, Alert);
};

export default Alert;

综上:我们在开发组件时,可以增加全局引入的接口,层级也可以仿照element的来,多研究源码这样代码才会写的更健壮。

# css hover后改变其他元素样式

css中某个元素hover后,可以对其他元素设置样式,但注意:只限定于改变他的子元素, 以及其后面的元素

<style>
  /* hover后单独改变某一个子元素的样式 */
  .cur-element:hover .child-1 {
    color: red;
  }
  /* 设置相邻的后一个兄弟节点样式 */
  .cur-element:hover + div {
    background: blue;
  }
  /* 设置后面的所有对应的兄弟节点样式,不必相邻,但需要再其后面 */
  .cur-element:hover ~ div {
    background: red;
  }
</style>
<div class="parent">
  <div>再前一个兄弟元素</div>
  <div>前一个兄弟元素</div>
  <div class="cur-element">
    测试hover
    <span class="child-1">child-1</span>
    <span class="child-2">child-2</span>
  </div>
  <div>后一个兄弟元素</div>
  <div>再后一个兄弟元素</div>
  <div>再再后一个兄弟元素</div>
</div>

参考:

# css vw的使用场景

在轮播图纯css的解决方案中:

  • 将图片区域宽度设置为 图片张数 * 100%
  • hover切换按钮时,将图片区域向左移动一个100%:transform: translateX(100%)

这里就会有问题,向左移动100%,宽度是图片张数*100%,而不是视窗宽度,用 100vw就可以很好的解决了。

参考之前的css笔记:css长度单位 相对长度 - HTML权威指南CSS部分 (opens new window)

# 2020/03/08 周日

# widnows nginx部署https服务

今天打算用使用 zuo11.com的 二级域名 api 来写接口玩玩,打算使用https。

  1. 在阿里云将域名免费的ssl证书分配给api.zuo11.com
  2. 在域名解析里,增加 api.zuo11.com 的解析,解析到服务器
  3. 初始化一个koa项目,监听某个端口,比如 9000端口,写一些测试的接口
  4. 部署到服务器
  5. nginx 添加对https的支持:①. 在ssl证书位置下载证书,会有两个文件 xxx.pem, xxx.key,在服务器nignx目录下的conf目录新建cert目录,将两个文件拷贝进去,修改conf下nginx.conf的配置
# HTTPS server
server {
    listen       443 ssl;
    server_name  api.zuo11.com;

    ssl_certificate     cert\3391782_api.zuo11.com.pem;
    ssl_certificate_key cert\3391782_api.zuo11.com.key;

    ssl_session_cache    shared:SSL:1m;
    ssl_session_timeout  5m;

    # 注释掉默认的加密方式
    # ssl_ciphers  HIGH:!aNULL:!MD5;
    # ssl_prefer_server_ciphers  on;

    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;  #使用此加密套件。
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;   #使用该协议进行配置。
    ssl_prefer_server_ciphers on;

    location / {
        # root   html;
        # index  index.html index.htm;
        proxy_pass http://127.0.0.1:9000;
    }
}

# 2020/03/07 周六

# nginx 中文路径404的问题

在mac本地调试时,都是ok的,部署到windows服务器上后时,发现一个图片出现了404的问题,最开始以为是缓存的问题,清了缓存后还是404。这张图片是中文路径,之前全部用的是英文的,没发现这个问题。试了下英文的图片链接是ok的。百度了下,发现确实有这种问题。是nginx设置的编码与操作系统的编码不一致的问题。

# linux查看电脑默认编码
echo  $LANG
# zh_CN.UTF-8

# windows下查看默认字符编码
chcp
# 如果显示 活动代码页 936 表示GBK编码  我的服务器就是这个编码,修改了nginx charset utf-8;没效果
# 65001 表示utf-8

# 设置字符编码,但发现只在当前控制台生效,重新开一个就没了。
chcp 65001

# 有个修改注册表的方法:不知道是否可行,但怕影响服务器的其它服务,还是算了,改英文名比较稳。。。。
# https://blog.csdn.net/yangzhong0808/article/details/79012628?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

参考:windows下cmd命令行显示UTF8字符设置(CHCP命令) (opens new window)

# Invalid character in header content ["Content-Disposition"]

在koa中,如果Content-Disposition里设置文件名有中文会提示错误,需要用类似 encodeURIComponent 转码的函数转码后才行

const fileName = encodeURIComponent('这是一个文件') // 需要先转码才行
ctx.set({
  'Content-Type': 'application/x-tar',
  'Content-Disposition': `attachment; filename="${fileName}.tar"`
})

# 2020/03/03 周二

# github Badge 与npm 徽章图片生成

在写readme时,最开始一般会贴一些徽章图片,比如 build passing,license MIT 等,这些都是引入的图片

badge徽章图片.png

// 上图对应的三个徽章图片
![version-v0.2.0](https://img.shields.io/badge/version-v0.2.0-yellow.svg) 
![build-passing](https://img.shields.io/badge/build-passing-green.svg) 
![license-MIT](https://img.shields.io/badge/license-MIT-green.svg) 
// 发现规律没,更改后面的参数,就可以生成不同的图片

// 比如
https://img.shields.io/badge/JAVA-1.8+-green.svg

npm 徽章图片,主要适用于npm包,显示npm包的一些信息

// 文档 https://www.npmjs.com/package/npm-badge
// zuo-blog npm包使用示例,只需要把对应的npm 包名称修改即可
[![NPM](https://nodei.co/npm/zuo-blog.png)](https://npmjs.org/package/zuo-blog)

# 个人小程序主体可以做什么

目前小程序名称变得很严格,只要包含稍微通用点的关键字,比如 "管理",都会要求上传手持身份证照片,且要有对应的商标证书。但貌似商标申请需要1000,且审批时间需要1年多... 太难了。而且个人小程序能做的东西越来越少了。

参考:个人主体小程序开放的服务类目 - 微信开放文档 (opens new window)

# 前端pc web获取时间是不准确的

我们在 new Date() 时,如果是在pc端,电脑时间不对,获取的时间也会不对。如果需要记录时间,应该发送请求给后端,让后端计算时间。依赖服务器时间

修改本地时间后,用 new Date() 获取时间,如下图会是错误的时间

修改本地时间后获取时间.png

# 2020/03/02 周一

# 浏览器tab页切换时更改标题

当用户点击了浏览器其他tab页离开页面,或者从其他tab页进入当前页,都会触发visibilitychange事件,根据docuemnt.hidden可以判断是否离开或回来,我们可以修改标题达到可视化的一个效果

// 实现tab间切换时,隐藏页面title改变功能
// JS高程3 Page Visibility API(页面可见性API)
// 参考:https://www.yuque.com/guoqzuo/js_es6/nocthb#0cf7a8b7
var title = document.title;
document.addEventListener('visibilitychange', function (event) {
  document.title =  document.hidden ? '~ 你快回来 ~ ' : title
  if (document.hidden) {
    // 做一些暂停操作
  } else {
    // 开始操作
  }
}, false)

# 网页中网络异常或网络正常时动态提示

监听页面的online和offline事件,显示或隐藏对应的信息

// 当网络状态发生改变时(有网 => 无网,无网 => 有网),提示信息
// JS高程3 离线检测
// 参考: https://www.yuque.com/guoqzuo/js_es6/sp2k81#244d3090
let errorMsgNode // 用来移除错误信息节点
window.ononline = function(event) {
  errorMsgNode && document.body.removeChild(errorMsgNode)
  message('success', '网络已连接', 3000)
}
window.onoffline = function(event) {
  message('error', '网络已断开')
}
/**
 * 为了显示网络信息,专门写了个小tips提示函数,在顶部显示信息
 * @param {}} type 文字颜色 error 为红色,其他为绿色
 * @param {*} msg 显示信息
 * @param {*} sec 如果有传入时间,sec秒后关闭提示
 */
function message(type, msg, sec) {
  let color = type === 'error' ? 'red' : 'green'
  let cssArr = [
    'position:fixed;top:8px;left:50%;z-index:9999999;',
    'transform:translateX(-50%);padding:5px 10px;background:#fff;'
  ]
  let htmlStr = `
    <div style="${cssArr.join('')}color:${color}">${msg}</di>
  `
  let node = document.createElement('div')
  node.innerHTML = htmlStr
  document.body.appendChild(node)
  if (Number.isInteger(sec) && sec > 0) {
    setTimeout(() => {
      document.body.removeChild(node)
    }, sec)
  } else {
    // 错误信息,一直提示,需要设置到变量里,等网络连接上时移除
    errorMsgNode = node
  }
}

# 页面滚动比例监听实现

监听页面的scroll事件,整个滚动距离为 document.documentElement.scrollHeight - window.innerHeight,当前scrollTop除以整个滚动距离,就是页面的百分比,向body挂载两个div来显示进度信息

// 页面滚动比例监听
// posTop 顶部类似阮一峰ES6网页的滚动进度条
// pos 右下角滚动百分比
// JS高程3 - UI事件 scroll事件
// https://www.yuque.com/guoqzuo/js_es6/elgng1#e38771e5
let htmlStr = `
  <div id="posTop" style="position: fixed;top:0;height:2px;background: #25b864;z-index:999999;"></div>
  <div id="pos" style="display:none;position:fixed;bottom: 100px;right:20px;padding:10px;background: #25b864;color:white;width:40px;text-align: center;border-radius:5px;"></div>
`
let eleNode = document.createElement('div')
eleNode.innerHTML = htmlStr
document.body.appendChild(eleNode)

window.addEventListener('scroll', function(e) {
  let scrollTop = document.documentElement.scrollTop;
  let total = document.documentElement.scrollHeight - window.innerHeight;
  let persentage = parseInt(scrollTop/total*100);
  // console.log(scrollTop);  

  document.getElementById('pos').style.display = scrollTop === 0 ? 'none' : 'block';
  document.getElementById('pos').innerHTML = `${persentage}%`;
  document.getElementById('posTop').style.width = `${persentage}%`;
}, false)

# 复制内容后,在内容中插入作者及当前文章信息

监听body里的copy事件,然后用 document.getSelection()获取内容,追加内容后,再使用event.clipboardData.setData像粘贴板里写入内容

// 操作粘贴板
// JS高程3 表单脚本 操作粘贴板
// https://www.yuque.com/guoqzuo/js_es6/ubpn7k#8482e7c5
document.body.oncopy = function(event) {
  console.log('copy', event);
  // 获取copy的内容
  // console.log(document.getSelection().toString());
  // 在copy内容里加入信息
  var msg = `
-----------------------------
标题:${document.title}
链接:${location.href}
作者:guoqzuo (http://github/zuoxiaobai)
  `
  event.clipboardData.setData('text/plain', `${document.getSelection().toString()} ${msg}`);
  event.preventDefault();
};
上次更新: 2020/10/29 22:59:19