Snow的个人博客


  • 首页

  • 归档

如何制作像GitHub里那样的Heatmap热力图

发表于 2018-11-14

github 上的 heatmap 热力图

avatar

这种热力图e-chart.js也可以做,但是功能比较单一,无法完全满足要求。这里我们使用 Cal-Heatmap

使用Cal-Heatmap 官网地址

注:翻译这篇文章时是2018-11-14,在日后看这篇文章时翻译中的版本可能有改变

开始

安装

经典的安装方式

  1. 下载最新版本的Cal-Heatmap到你的项目中
  2. 引入d3.js文件(使用最新的版本),或者下载到本地使用

    1
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
  3. 通过引入js和css文件来安装 Cal-Heatmap

    1
    2
    <link rel="stylesheet" href="path/to/css/cal-heatmap.css" />
    <script type="text/javascript" src="path/to/cal-heatmap.min.js"></script>

使用 BOWER

1
bower install cal-heatmap

然后继续执行经典方法的第3步,将其安装到应用程序中

使用 JAM

1
jam install cal-heatmap

然后继续执行经典方法的第3步,将其安装到应用程序中

使用 CDN

仅需要在文件HEAD头部里引入以下文件

1
2
3
<script type="text/javascript" src="//d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js"></script>
<link rel="stylesheet" href="//cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.css" />

基本使用

轻松愉快地创建你的第一个日历表吧
1
2
3
4
5
<div id="cal-heatmap"></div>
<script type="text/javascript">
var cal = new CalHeatMap();
cal.init({});
</script>

默认情况下,调用init()方法并传入一个空的对象作为参数,将会初始化一个12小时的空白日历,从当前时间(小时)开始计算,每个有60分钟。

浏览器支持

Cal-Heatmap 可在大多数支持 SVG 的现代浏览器中工作,在以下浏览器中通过了测试:

  • Internet Explorer 9+
  • Firefox 4+
  • Chrome 14+
  • Safari 5.0+
  • Opera 10+

我的备注

在Vue中使用 Cal-Heatmap

在要使用的组件中引用文件,或者全局引用

1
2
3
import '@/plugin/cal-heatmap/cal-heatmap.css'
import '@/plugin/cal-heatmap/d3.v3.js'
import '@/plugin/cal-heatmap/cal-heatmap.js'

后面的操作你懂得….

在vue中通过经典方法来使用Cal-Heatmap时没有成功

  1. 报错 Can't resolve 'd3' in XXX 。这个原因是因为cal-heatmap.js的第 9 行var d3 = typeof require === "function" ? require("d3") : window.d3; 和 d3.v3.js 的第 9270 行module.exports = d3; 共同造成的。
    因为vue是在 node 环境中,所以 require 和 module 都是存在的,但是我这里的module.exports是undefined,而 require 是要求返回一个 module 的,也是有通过module.exports返回,所以出现了错误。这里将cal-heatmap.js的第 9 行改为var d3 = window.d3;
  2. 在 vue 中引用d3.v3.js时,在 9272 行this.d3 = d3; 的 this 不是指向 window 而是 undefined,所以将 this 改为 window

为了满足按需引入这两个比较大的js文件,对cal-heatmap.js 引用 d3.v3.js 文件的方式进行改变

  1. 将d3.v3.js的第 1 行去掉,并且将 9267 ~ 9274 行去掉,然后添加一行export

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // !function() {
    ....
    ....
    ....
    // if (typeof define === "function" && define.amd) {
    // define(d3);
    // } else if (typeof module === "object" && module.exports) {
    // module.exports = d3;
    // } else {
    // window.d3 = d3;
    // }
    // }();
    export default d3;
  2. 将cal-heatmap.js的第 9 行替换为import ...,并将 3475 ~ 3485 行去掉,添加export ...

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // var d3 = typeof require === "function" ? require("d3") : window.d3;
    import d3 from '@/plugin/cal-heatmap/d3.v3.js'
    ...
    ...
    ...
    // if (typeof define === "function" && define.amd) {
    // define(["d3"], function() {
    // "use strict";

    // return CalHeatMap;
    // });
    // } else if (typeof module === "object" && module.exports) {
    // module.exports = CalHeatMap;
    // } else {
    // window.CalHeatMap = CalHeatMap;
    // }
    export default CalHeatMap;
  3. 这样就不需要在组件里引用d3.v3.js文件了,而且没有将 d3 和 CalHeatMap 对象添加到 window 对象。

vue使用webpack代理跨域

发表于 2018-11-09

vue-cli3 使用webpack跨域

  1. 在项目根目录下的 vue.config.js 里添加配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    ...
    chainWebpack: config => {
    config.resolve.alias
    .set('@', resolve('src')) // key,value自行定义,比如.set('@@', resolve('src/components'))
    .set('_c', resolve('src/components'))
    .set('_conf', resolve('config'))
    },
    //添加如下配置
    devServer: {
    proxy: {
    'api': {
    target: 'http://article.cn/', // 目标域名,可以是ip地址
    changeOrigin: true, // 将主机头的起始位置更改为目标URL
    secure: false,
    pathRewrite: {'^/api' : ''}, // 将使用时用来匹配的api去掉
    }
    }
    }
    }
  2. 使用:

    1
    2
    3
    4
    5
    6
    7
    axios.request({
    url: '/api/article/create_article',
    data,
    method: 'POST'
    })
    .then(data => { })
    .catch(error => { })

我实际要访问的地址是:http://article.cn/article/create_article。其中http://article.cn域名是通过代理添加上的,/api是pathRewrite: {'^/api' : ''}这句去掉的。

注:我这里axios使用 baseURL 配置了本地域名 http://localhost:8080/

vue-cli2 使用webpack跨域

参考:https://www.jianshu.com/p/7f1c8037dc7b

  1. 在 config 文件夹下的 index.js 文件里找到 proxyTable 项,它默认是空的
    1
    2
    3
    4
    5
    6
    7
    8
    module.exports = {
    dev: {

    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {},
    ...

与上面一样添加配置:

1
2
3
4
5
6
7
8
proxy: {
'api': {
target: 'http://article.cn/',
changeOrigin: true,
secure: false,
pathRewrite: {'^/api' : ''},
}
}

options (‘api’后面的值)

  • options.target:要与url模块解析的url字符串
  • option.forward:要用url模块解析的url字符串
  • option.agent:要传递到http(s)的对象.request(请参阅节点的
  • https代理和http代理对象)
  • option.ssl:要传递给https.createServer()的对象
  • option.ws:true/ false:如果要代理websockets
  • option.xfwd:true / false,添加x-forward标头
  • option.secure:true / false,如果要验证SSL Certs
  • option.toProxy:true / false,将绝对URL传递给path(用于代理代理)
  • option.prependPath:true / false,默认值:true - 指定是否要将目标路径添加到代理路径
  • option.ignorePath:true / false,默认值:false - 指定是否要忽略传入请求的代理路径(注意:如果需要,您将必须附加/手动)。
  • option.localAddress:绑定传出连接的本地接口字符串
  • option.changeOrigin:true / false,默认值:false - 将主机头的起始位置更改为目标URL
  • option.auth:基本认证,即’user:password’来计算授权头。
  • option.hostRewrite:重写(301/302/307/308)重定向上的位置主机名。
  • option.autoRewrite:根据请求的主机/端口重写(301/302/307/308)重定向的位置主机/端口。默认值:false。
  • option.protocolRewrite:将(301/302/307/308)上的位置协议重写为“http”或“https”。默认值:null。
  • option.cookieDomainRewrite:重写set-cookie标题的域。可能的值:false(默认):禁用cookie重写String:例如,新cookieDomainRewrite: “new.domain”。要删除域,请使用cookieDomainRewrite: “”。
    对象:将域映射到新域,用于”*”匹配所有域。例如,保持一个域不变,重写一个域并删除其他域:
    1
    2
    3
    4
    5
    cookieDomainRewrite: {
    "unchanged.domain": "unchanged.domain",
    "old.domain": "new.domain",
    "*": ""
    }

js倒计时

发表于 2018-11-05

摘抄自:https://zhuanlan.zhihu.com/p/20832837

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/**
* @author xiaojue
* @date 20160420
* @fileoverview 倒计时想太多版
*/
(function() {

function timer(delay) {
this._queue = [];
this.stop = false;
this._createTimer(delay);
}

timer.prototype = {
constructor: timer,
_createTimer: function(delay) {
var self = this;
var first = true;
(function() {
var s = new Date();
for (var i = 0; i < self._queue.length; i++) {
self._queue[i]();
}
if (!self.stop) {
var cost = new Date() - s;
delay = first ? delay : ((cost > delay) ? cost - delay : delay);
setTimeout(arguments.callee, delay);
}
})();
first = false;
},
add: function(cb) {
this._queue.push(cb);
this.stop = false;
return this._queue.length - 1;
},
remove: function(index) {
this._queue.splice(index, 1);
if(!this._queue.length){
this.stop = true;
}
}
};

function TimePool(){
this._pool = {};
}

TimePool.prototype = {
constructor:TimePool,
getTimer:function(delayTime){
var t = this._pool[delayTime];
return t ? t : (this._pool[delayTime] = new timer(delayTime));
},
removeTimer:function(delayTime){
if(this._pool[delayTime]){
delete this._pool[delayTime];
}
}
};

var delayTime = 1000;
var msInterval = new TimePool().getTimer(delayTime);

function countDown(config) {
var defaultOptions = {
fixNow: 3 * 1000,
fixNowDate: false,
now: new Date().valueOf(),
template: '{d}:{h}:{m}:{s}',
render: function(outstring) {
console.log(outstring);
},
end: function() {
console.log('the end!');
},
endTime: new Date().valueOf() + 5 * 1000 * 60
};
for (var i in defaultOptions) {
if (defaultOptions.hasOwnProperty(i)) {
this[i] = config[i] || defaultOptions[i];
}
}
this.init();
}

countDown.prototype = {
constructor: countDown,
init: function() {
var self = this;
if (this.fixNowDate) {
var fix = new timer(this.fixNow);
fix.add(function() {
self.getNowTime(function(now) {
self.now = now;
});
});
}
var index = msInterval.add(function() {
self.now += delayTime;
if (self.now >= self.endTime) {
msInterval.remove(index);
self.end();
} else {
self.render(self.getOutString());
}
});
},
getBetween: function() {
return _formatTime(this.endTime - this.now);
},
getOutString: function() {
var between = this.getBetween();
return this.template.replace(/{(\w*)}/g, function(m, key) {
return between.hasOwnProperty(key) ? between[key] : "";
});
},
getNowTime: function(cb) {
var xhr = new XMLHttpRequest();
xhr.open('get', '/', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 3) {
var now = xhr.getResponseHeader('Date');
cb(new Date(now).valueOf());
xhr.abort();
}
};
xhr.send(null);
}
};

function _cover(num) {
var n = parseInt(num, 10);
return n < 10 ? '0' + n : n;
}

function _formatTime(ms) {
var s = ms / 1000,
m = s / 60;
return {
d: _cover(m / 60 / 24),
h: _cover(m / 60 % 24),
m: _cover(m % 60),
s: _cover(s % 60)
};
}

var now = Date.now();

new countDown({});
new countDown({
endTime: now + 8 * 1000
});

})();

主要说思路,实现分成几个步骤,第一个类是timer,通过setTimeout创建一个倒计时实例,内部对阻塞误差做了矫正,但是缺少一步,如果阻塞大于延迟时间,那么应该下次递归直接执行,而我这里还是会延迟1s,只是相对会减少误差。然后这个timer通过add和remove方法管理一个回调队列,让所有通过这个timer实现的倒计时都共用一个定时器。第二个类TimePool,主要实现一个timer池子,我们之前只想到了倒计时都是1s的,那如果有500ms或者10s的倒计时,这里这个池子是可以装多个timer类的,保证不同延迟下,定时器最小。最后一个类就是countDown类,里面同样是默认配置起手,然后主要介绍fixNowDate这个参数,是用来进行系统时间修正的,这里默认的实现是浏览器端的一个修正技巧,主要参见getNowTime方法,通过一个xhr请求拿服务端headers中的Date来进行矫正。

之后摘出来了一个template,对输出的时间格式可以做一个简单的模板替换,抽象了3个公共类,最后的countDonw依赖其他2个类做了最终实现。最后,这段代码也可以复用在nodejs端,只需要修改getNowTime的方法为nodejs的即可。

我的这段实现,没有考虑太多样式的可扩展,更多是性能上的,但是真实情况下性能如何,还需要实际验证,这一步是没有做的,而且只适用页面多定时器和倒计时的场景,可能还存在一些隐藏的未知bug

如何对小于10的数字进行补零操作

发表于 2018-11-05

参考:https://zhuanlan.zhihu.com/p/20832837

npm的leftpad模块的实现

1
2
3
4
5
6
7
8
9
10
function leftpad (str, len, ch) {
str = String(str);
var i = -1;
if (!ch && ch !== 0) ch = ' ';
len = len - str.length;
while (++i < len) {
str = ch + str;
}
return str;
}

使用vue遇到的问题

发表于 2018-10-28

eslint

  1. 问题:报错:
    1
    The keyword 'import' is reserved

背景:使用iview-admin精简版打开eslint后就在下面代码处报import错,在main.js里引用vue哪里的import确没有报错。而且使用vue-cli3安装的项目(初始化时选择了eslint)里确不会出现这个问题。

1
2
3
4
5
meta: {
title: 'Login - 登录',
hideInMenu: true
},
component: () => import(/* webpackChunkName: "about" */ '@/view/login/login.vue')

解决方法:经过对比iview-admin精简版里的eslintrc.js文件和vue-cli3初始化得到的eslintrc.js文件得到解决方法,加上这个配置可解决问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
root: true,
'extends': [
'plugin:vue/essential',
'@vue/standard'
],
rules: {
// allow async-await
'generator-star-spacing': 'off',
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'vue/no-parsing-error': [2, { 'x-invalid-end-tag': false }]
},
// 加上下面这个配置可以解决问题
"parserOptions": {
"parser": "babel-eslint"
}
}

使用iview遇到的问题

发表于 2018-10-28

使用iview框架和iview-admin遇到的问题记录

iview

iview-admin

使用 iview-admin 简化版模板

  1. 问题背景:在使用iview-admin简化版模板的过程中,当我使用 Select 标签时出现问题:
    1
    vue.runtime.esm.js?329b:587 [Vue warn]: Error in render: "TypeError: Cannot read property '_t' of undefined"

问题分析:因为在 iview的 Select 标签中使用了 i18n,所以我们要为 iview 添加 i18n 配置项
解决方法:在使用 iview 时添加i18n配置项。具体为:在main.js文件中,将 Vue.use(iView) 替换为 { i18n: (key, value) => i18n.t(key, value) }

获取浏览器可视区宽高

发表于 2018-10-25

参考:https://www.cnblogs.com/pengchengzhong/p/6050644.html
获取浏览器可视区宽高要考虑兼容情况

1
2
3
4
5
6
7
8
9
function getViewXY () {
let w = window,
d = document,
e = d.documentElement,
g = d.getElementsByTagName('body')[0],
x = w.innerWidth || e.clientWidth || g.clientWidth,
y = w.innerHeight|| e.clientHeight|| g.clientHeight;
return {width: x, height: y}
}

未命名

发表于 2018-10-25
var EventUtil = { addHandler: function (element, type, handler) { if (element.addEventListener) { element.addEventListener(type, handler, false); } else if (element.attachEvent) { element.attachEvent('on' + type, handler); } else { element['on' + type] = handler; } }, removeHandler: function (element, type, handler) { if (element.removeEventListener) { element.removeEventListener(type, handler, false); } else if (element.detachEvent) { element.detachEvent('on' + type, handler); } else { element['on' + type] = null; } }, getEvent: function (event) { return event ? event: window.event; }, getTarget: function (event) { return event.target || event.srcElement; }, preventDefault: function (event) { if (event.preventDefault) { event.preventDefault(); } else { event.returnValue = false; } } }

跨浏览器DOM事件方法

发表于 2018-10-25
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
var EventUtil = {
// 添加事件
addHandler: function (element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
// 移除事件
removeHandler: function (element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
}
// 获取事件对象
getEvent: function (event) {
return event ? event : window.event;
},
// 获取目标对象
getTarget: function (event) {
return event.target || event.srcElement;
},
// 阻止默认事件
preventDefault: function (event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
// 阻止(冒泡)事件传播
stopPropagation: function (event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
},
// 获取鼠标按键
getButton: function (event) {
if (document.implementation.hasFeature("MouseEvents", "2.0")) {
return event.button;
} else {
switch (event.button) {
case 0:
case 1:
case 3:
case 5:
case 7:
return 0;
case 2:
case 6:
return 2;
case 4:
return 1;
}
}
}
}

vue-cli3取消eslint

发表于 2018-10-23

刚开始初始化项目时我们添加了eslint,但是后面项目里要整合别人的代码,那不规范的写法实在让人抓狂,由于工程量太大自己也没法对代码格式进行,所以只能取消eslint了

vue-cli2

在vue-cli2中有一个配置项(在config/index.js里),直接将useEslint: true改为useEslint: false就可以了。

vue-cli3

在vue-cli3中没有这个配置,但可以用以下方法。

选择生成的时候,有一个询问是否把插件配置文件写在package.json文件里还是单独的文件,这里我选择了写在package.json文件里
打开package.json文件有这么一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"@vue/standard" // 这行是选用的eslint规则配置,你的可能和我的不一样,删除即可,别忘了还有上面的‘,’
],
"rules": {},
"parserOptions": {
"parser": "babel-eslint"
}
},

1234
Snow Liu

Snow Liu

36 日志
9 标签
© 2018 Snow Liu
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4