Fork me on GitHub
Xiaojun's Blog

  • Home

  • Tags

  • Archives

怎样设置SSH代理

发表于 2019-06-12
| 字数: 550

由于本人服务器 ip 被封,导致国内无法直连,只好设置代理连接了 f**k‼️

Windows

在系统 .ssh 目录下编辑 config 文件(没有就新建一个),路径一般是 C:\Users\xxx\.ssh,其中 xxx 为你电脑用户名

1
2
Host x.x.x.x
ProxyCommand connect -S 127.0.0.1:xxxx %h %p

macOS

编辑 ~/.ssh/config 文件(没有就新建一个)

1
2
Host x.x.x.x
ProxyCommand nc -x 127.0.0.1:xxxx %h %p

结尾

。。

。。

。。

再记录一下 git 设置 http 代理方式吧,通过命令即可

1
2
3
4
git config --global http.proxy 'http://127.0.0.1:1080'
git config --global https.proxy 'http://127.0.0.1:1080'
git config --global http.proxy 'socks5://127.0.0.1:1080'
git config --global https.proxy 'socks5://127.0.0.1:1080'

具体设置视你情况而定

v2ray搭建流程

发表于 2019-06-11
| 字数: 1.1k

使用 v2ray 一键脚本

https://github.com/233boy/v2ray/wiki

推荐使用 Cloudflare 中转 V2Ray 流量,这里说俩注重点

  • Cloudflare 是需要域名的,推荐一个申请免费域名的网站 https://www.freenom.com

  • 关于 Caddy 启动失败的问题,是因为 Caddy 与 Nginx 有冲突,你可以抛弃 Caddy 而使用 Nginx,放一份我的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server {
listen 443;
server_name xiaojun1994.cf; # 刚才申请的域名
ssl on;
ssl_certificate /srv/ssl/2332201_xiaojun1994.cf.pem;
ssl_certificate_key /srv/ssl/2332201_xiaojun1994.cf.key;
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 / {
proxy_pass http://localhost:45018; # 这儿填为你 v2ray 端口,查看方法:vim /etc/v2ray/config.json -> inbounds -> port
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}

ssl 申请方法请查看这篇文章

结尾

晚上 v2ray 相比 ss 还是要慢一点,不过稳定就好,因为套了 cdn 的原因,你服务器 ip 被封了仍然还能用 🐶~

使用yum快速安装最新版nodejs

发表于 2019-05-13
| 字数: 158
1
2
curl -sL https://rpm.nodesource.com/setup_10.x | bash -
sudo yum install -y nodejs

以上命令代表我想安装 Node10 最新版本,更多版本参见 https://github.com/nodesource/distributions

规范化Git合作流程

发表于 2019-03-11
| 字数: 3.5k

Commitizen

纵观那些 GitHub 上 star 很多的开源库,开发流程都是极其规范的,Commitizen 便是用来规范化 git 提交信息的一个好工具。

AP2yb6.png

commitizen 通常要与适配器一起使用,通俗点来说是需要一个 commit message 模板,目前主流的是符合 Angular 规范的 cz-conventional-changelog。

✨ 安装

1
2
3
npm i -g commitizen
npm i -g cz-conventional-changelog
echo '{ "path": "cz-conventional-changelog" }' > ~/.czrc

以上命令代表全局使用 cz-conventional-changelog 适配器,你也可以通过以下命令来局部安装适配器

1
2
3
4
# 这种方式是使用npm来安装
commitizen init cz-conventional-changelog --save-dev --save-exact
# 这种方式是使用yarn来安装
commitizen init cz-conventional-changelog --yarn --dev --exact

假如你已经全局安装了适配器,那么上面的命令会报 A previous adapter is already configured. Use –force to override,如它所说,只需要加上 --force 参数即可强制使用局部适配器,成功后会在本地局部安装 cz-conventional-changelog,并在 package.json 中写入以下内容

1
2
3
4
5
6
7
8
9
10
{
"devDependencies": {
"cz-conventional-changelog": "^2.1.0"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}

🎉 使用

安装成功后即可通过命令 git-cz 来代替 git commit 进行提交了

AP6OPJ.png

git-cz 具有 git commit 一切参数,就像这样: git-cz -a

VSCode 用户可以安装一个 Visual Studio Code Commitizen Support 扩展以使用更友好的 commit 界面,
按下 F1,输入 conventional commit,效果如下

APgNpd.png根据步骤操作就 ok 了。


Standard Version

🎈 用途

  • 自动升级 version
  • 自动生成 changelog
  • 自动打 tag

每次更新版本后会自动将 package.json(et al.) 和 CHANGELOG.md 提交

必须确保 Commit Message 符合 Conventional Commits 规范哦!上面使用的 cz-conventional-changelog 是符合这个规范的。

Ai9KgJ.png

🎄 安装

这里我选择全局安装

1
npm i -g standard-version

方便起见,首先新增一个 npm run script

1
2
3
4
5
6
{
"version": "1.0.0",
"scripts": {
"release": "standard-version"
}
}

🎀 发布第一版

发布第一版时运行以下命令,这条命令不会修改版本号

1
2
3
npm run release -- -f
# or
standard-version -f

🎁 升级一个版本

1
2
3
npm run release
# or
standard-version

🎨 生成一个预发布版本

1
2
3
npm run release -- -p
# or
standard-version -p

会得到一个类似 1.0.1-0 1.0.1-1 … 这种版本
如果希望为预发布版本命名,可以通过 --prerelease <name> 指定名称。
例如,假设您的预发行版应该包含 alpha 前缀

1
2
3
npm run release -- -p alpha
# or
standard-version -p alpha

这将得到一个 1.0.1-alpha.0 版本

🎏 手动选择版本

APXW0U.png

standard-version 遵循 Semver 语义化版本规范,假如你上次 commit 时选择的是 fix,这时默认应该更新的是个 path 版本,如果你想手动选择版本,可以这样

1
2
3
npm run release -- -r minor
# or
standard-version -r 1.1.0

🏀 阻止 Git Hooks

当 release 后,程序会自动将修改后的 package.json 和 CHANGELOG.md 文件 commit 掉,所以当使用 pre-commit 这种钩子时候可能就会报错,可以通过 --no-verify 跳过检测

1
2
3
npm run release -- --no-verify
# or
standard-version -n

🔫 生命周期脚本

standard-version 支持一些生命周期脚本

  • prerelease: 发布之前
  • prebump / postbump: 版本号更新之前 / 之后
  • prechangelog / postchangelog: changelog 生成之前 / 之后
  • precommit / postcommit: package.json 和 changelog 文件提交之前 / 提交之后
  • pretag / posttag: 打 tag 之前 / 之后

就像这样,你可以在某个声明周期中偷摸做点事情 🤡

1
2
3
4
5
6
7
{
"standard-version": {
"scripts": {
"prebump": "echo 9.9.9"
}
}
}

changelog 中 issue 地址默认是 GitHub 上的,如果想修改成 Jira 的地址,可以通过 postchangelog 配合 replace 库来修改它的链接地址

1
2
3
4
5
6
7
{
"standard-version": {
"scripts": {
"postchangelog": "replace 'https://github.com/myproject/issues/' 'https://myjira/browse/' CHANGELOG.md"
}
}
}

你也可以跳过某些生命周期 (bump, changelog, commit, tag) ,假如不想它自动打 tag,你可以这样

1
2
3
4
5
6
7
{
"standard-version": {
"skip": {
"tag": true
}
}
}

🎠 Tag 前缀

默认打的 tag 前缀是 v,生成的 tag 都是类似 v1.0.0 这种,可以通过 -t 设置前缀

1
2
3
npm run release -- -t @scope/package\@
# or
standard-version -t @scope/package\@

生成的 tag 看起来像这样 @scope/[email protected]

😊 帮助

1
2
3
npm run release -- -h
# or
standard-version -h

更多

除此之外,还可以利用 husky + prettier eslint commitlint lint-staged 等工具进一步优化 git 流程,利用 husky 的 pre-commit 钩子,可以在 commit 之前处理一些事情,就像

  • 代码格式化
  • 代码校验
  • Commit Message 校验
  • …

判断addEventListener是否支持passive属性

发表于 2018-12-03
| 字数: 939

写法

先看一下 MDN 现在 addEventListener 的写法,传参变成了这样:

1
target.addEventListener(type, listener, { capture: Boolean, passive: Boolean, once: Boolean })

以前第三个参数是一个 Boolean 值,它决定是否为捕获模式,现在可以传入一个对象了

  • capture: 同以前一样,表示 listener 是在捕获阶段执行还是冒泡阶段执行
  • passive: 表示 listener 永远不会调用 preventDefault()。
  • once: 表示 listener 只会调用一次。

用处

看上去懵懵的,其实,总结一下就是 passive 主要是为了优化滑动性能而生,当 passive 设置为 true 时,会告诉浏览器你的 listener 中不会调用 preventDefault() 这个方法,从而浏览器会做一系列优化来提升滑动体验,从而使滑动更顺畅

使用场景

当你的 listener 中不会调用 preventDefault() 方法时,都尽量使用它,如果你设置了 passive 为 true 并且你代码中还调用了 preventDefault() 方法,那么浏览器会抛出一个错误

兼容性

这个属性出来的比较晚,兼容性不太好,怎么检测浏览器是否支持该属性呢?这里有个方法

1
2
3
4
5
6
7
8
9
10
11
var passiveSupported = false

try {
var options = Object.defineProperty({}, 'passive', {
get: function() {
passiveSupported = true
}
})

window.addEventListener('test', null, options)
} catch (err) {}

当浏览器支持该属性时 passiveSupported 会被赋值为 true,这段代码简直是酷到没朋友 👽

如何模拟移动端触摸事件

发表于 2018-11-20
| 字数: 5k

在 pc 端模拟移动端 touch 事件,代码来自 Vant

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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/**
* 模拟移动端 touch 事件
*/

var eventTarget

// polyfills
if (!document.createTouch) {
document.createTouch = function(view, target, identifier, pageX, pageY, screenX, screenY) {
// auto set
return new Touch(
target,
identifier,
{
pageX: pageX,
pageY: pageY,
screenX: screenX,
screenY: screenY,
clientX: pageX - window.pageXOffset,
clientY: pageY - window.pageYOffset
},
0,
0
)
}
}

if (!document.createTouchList) {
document.createTouchList = function() {
var touchList = TouchList()
for (var i = 0; i < arguments.length; i++) {
touchList[i] = arguments[i]
}
touchList.length = arguments.length
return touchList
}
}

/**
* create an touch point
* @constructor
* @param target
* @param identifier
* @param pos
* @param deltaX
* @param deltaY
* @returns {Object} touchPoint
*/

var Touch = function Touch(target, identifier, pos, deltaX, deltaY) {
deltaX = deltaX || 0
deltaY = deltaY || 0

this.identifier = identifier
this.target = target
this.clientX = pos.clientX + deltaX
this.clientY = pos.clientY + deltaY
this.screenX = pos.screenX + deltaX
this.screenY = pos.screenY + deltaY
this.pageX = pos.pageX + deltaX
this.pageY = pos.pageY + deltaY
}

/**
* create empty touchlist with the methods
* @constructor
* @returns touchList
*/
function TouchList() {
var touchList = []

touchList['item'] = function(index) {
return this[index] || null
}

// specified by Mozilla
touchList['identifiedTouch'] = function(id) {
return this[id + 1] || null
}

return touchList
}

/**
* Simple trick to fake touch event support
* this is enough for most libraries like Modernizr and Hammer
*/
function fakeTouchSupport() {
var objs = [window, document.documentElement]
var props = ['ontouchstart', 'ontouchmove', 'ontouchcancel', 'ontouchend']

for (var o = 0; o < objs.length; o++) {
for (var p = 0; p < props.length; p++) {
if (objs[o] && objs[o][props[p]] === undefined) {
objs[o][props[p]] = null
}
}
}
}

/**
* only trigger touches when the left mousebutton has been pressed
* @param touchType
* @returns {Function}
*/
function onMouse(touchType) {
return function(ev) {
// prevent mouse events

if (ev.which !== 1) {
return
}

// The EventTarget on which the touch point started when it was first placed on the surface,
// even if the touch point has since moved outside the interactive area of that element.
// also, when the target doesnt exist anymore, we update it
if (ev.type === 'mousedown' || !eventTarget || (eventTarget && !eventTarget.dispatchEvent)) {
eventTarget = ev.target
}

triggerTouch(touchType, ev)

// reset
if (ev.type === 'mouseup') {
eventTarget = null
}
}
}

/**
* trigger a touch event
* @param eventName
* @param mouseEv
*/
function triggerTouch(eventName, mouseEv) {
var touchEvent = document.createEvent('Event')
touchEvent.initEvent(eventName, true, true)

touchEvent.altKey = mouseEv.altKey
touchEvent.ctrlKey = mouseEv.ctrlKey
touchEvent.metaKey = mouseEv.metaKey
touchEvent.shiftKey = mouseEv.shiftKey

touchEvent.touches = getActiveTouches(mouseEv)
touchEvent.targetTouches = getActiveTouches(mouseEv)
touchEvent.changedTouches = createTouchList(mouseEv)

eventTarget.dispatchEvent(touchEvent)
}

/**
* create a touchList based on the mouse event
* @param mouseEv
* @returns {TouchList}
*/
function createTouchList(mouseEv) {
var touchList = TouchList()
touchList.push(new Touch(eventTarget, 1, mouseEv, 0, 0))
return touchList
}

/**
* receive all active touches
* @param mouseEv
* @returns {TouchList}
*/
function getActiveTouches(mouseEv) {
// empty list
if (mouseEv.type === 'mouseup') {
return TouchList()
}
return createTouchList(mouseEv)
}

/**
* TouchEmulator initializer
*/
function TouchEmulator() {
fakeTouchSupport()

window.addEventListener('mousedown', onMouse('touchstart'), true)
window.addEventListener('mousemove', onMouse('touchmove'), true)
window.addEventListener('mouseup', onMouse('touchend'), true)
}

// start distance when entering the multitouch mode
TouchEmulator['multiTouchOffset'] = 75

new TouchEmulator()

直接引入该文件,pc 端就可以响应移动端的 touchstart、touchmove、touchend 事件了 ✨。

小技巧处理fixed定位后其它内容被覆盖的问题

发表于 2018-11-04
| 字数: 3k

我们知道当对一个元素设置了 position: fixed; 后,该元素会脱离文档流,下面的内容会顶上来,导致被内容被遮盖。常见的一个做法是设置下面内容的 padding 或 margin,虽然能达到效果,但是总归不完美,特别是当我们想封装一个组件给别人用的时候,还得让别人设置一下样式,这样岂不麻烦,所以就有了下面这个方法,以我封装的这个 vue header 组件为例

示例代码

template

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div :class="classList">
<div class="i-nav-bar__inner">
<div class="i-nav-bar__left" @click="handleClickLeft">
<slot name="left">
<i class="i-nav-bar__arrow fa fa-fw fa-angle-left" v-if="leftArrow"></i>
<span class="i-nav-bar__text" v-if="leftText">{{ leftText }}</span>
</slot>
</div>
<div class="i-nav-bar__title">
<slot name="title">{{ title }}</slot>
</div>
<div class="i-nav-bar__right" @click="handleClickRight">
<slot name="right"></slot>
</div>
</div>
</div>

script

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
export default {
name: 'INavBar',
props: {
title: String,
leftArrow: Boolean,
leftText: String,
fixed: Boolean
},
computed: {
classList() {
return [
'i-nav-bar',
{
'i-nav-bar--fixed': this.fixed
}
]
}
},
methods: {
handleClickLeft(ev) {
this.$emit('click-left', ev)
},
handleClickRight(ev) {
this.$emit('click-right', ev)
}
}
}

style(scss)

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
$height: 50px;

.i-nav-bar {
height: $height;
&__inner {
position: relative;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.07);
height: $height;
background-color: #fff;
color: #4a4d5a;
}
&__left {
height: 100%;
position: absolute;
top: 0;
left: 10px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
&:active {
opacity: 0.5;
}
.i-nav-bar__arrow {
font-size: 25px;
margin-top: -1px;
}
.i-nav-bar__text {
font-size: 14px;
margin-left: -6px;
}
}
&__title {
max-width: 50%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto;
font-size: 16px;
color: #34495e;
}
&__right {
height: 100%;
position: absolute;
top: 0;
right: 10px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
&:active {
opacity: 0.5;
}
}
&--fixed {
.i-nav-bar__inner {
position: fixed;
z-index: 1;
top: 0;
left: 0;
width: 100%;
}
}
}

总结

外面套一个父元素,height 假设为 50px,然后我们对里面这个元素设置 position: fixed;,它的 height 也设置 50px,这样的话虽然里面脱离了文档流,但是父元素依然占据着空间,所以下面的元素也就不会顶上来,当别人使用你的组件时再也不用费力设置 padding 或 margin 了。

一些效果截图

图中头部是上面示例代码的效果图,tabbar 是我封装的另一个组件,这里为了演示效果,我把他们放到一起

5mWmzX.png

实现一个简单的Vuex状态持久化功能

发表于 2018-10-09
| 字数: 816

大多数人应该都遇到过 vuex 中 state 刷新后丢失的问题,这是因为 state 是存储在内存中的,刷新后当然就丢失了。
这里有个方案是每次更改 state 后把 state 存储在 localStorage 中,当初始化 vuex 时再从 localStorage 中取出。

使用 vuex 插件功能可以很方便实现上述方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// plugins/vuex-state-persister.js
export default function(store) {
const Key = '__VUEX_STATE__';
if (localStorage[key]) store.replaceState(JSON.parse(localStorage[Key]));
store.subscribe(function(mutation, state) {
localStorage.setItem(Key, JSON.stringify(state));
});
}
// store/index.js
import createStatePersister from '../plugins/vuex-state-persister';
// ...
export default new Vuex.Store({
// ...
plugins: [createStatePersister],
// 开发环境下启用严格模式,官方文档介绍:https://vuex.vuejs.org/zh/guide/strict.html
strict: process.env.NODE_ENV !== 'production'
});

以上只是一个简单实现,项目中建议使用 vuex-persistedstate

利用Git钩子实现自动部署

发表于 2018-09-28
| 字数: 739

当某些重要事件发生时,Git 以调用自定义脚本。有两组挂钩:客户端和服务器端。客户端挂钩用于客户端的操作,如提交和合并。服务器端挂钩用于 Git 服务器端的操作,如接收被推送的提交。

此教程的前提是要求你服务器上必须有个 git 仓库,创建 git 仓库的教程可以参考这篇文章
在CentOS7上搭建自己的Git服务器

以我的 git 仓库 /srv/GitLibrary/blog.git 为例

创建钩子脚本

1
2
3
cd /srv/GitLibrary/blog.git/hooks
# 创建钩子脚本,该脚本会在push完成后运行
vim post-receive

写入两句话

1
2
#!/bin/sh
git --work-tree=/srv/www/blog --git-dir=/srv/GitLibrary/blog.git checkout -f

然后

1
2
3
4
# 赋予可执行权限
chmod +x ./post-receive
# 设置所有者为git用户
chown -R git:git ./post-receive

过程很简单,总结一下:
当客户端 push 完成以后,紧接着远端仓库触发我们定义的钩子脚本,
那句命令会将 /srv/GitLibrary/blog.git 最新版本 checkout 到 /srv/www/blog 中。
你可以自定义这俩目录路径,但是需要注意一点的是该目录必须有写入权限,否则会因为没有权限导致部署失败。
可以使用 chmod 命令赋予权限,以我的文件夹 /srv/www/blog 为例

1
chmod -R 777 /srv/www/blog

OK,大功告成,当客户端 push 完成以后,最新代码就会自动部署到你指定的目录了。🎉🎉🎉

使用ul简单模拟一个在移动端上使用的Vue表格组件

发表于 2018-09-27
| 字数: 3.2k

感觉手动设置 table 样式挺恶心的,而且性能也很烂,今天用 ul + flexbox 简单模拟一个 vue 上用的

my-table.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="table">
<ul class="tr">
<li class="th" :style="styles(item)" v-for="item in columns" :key="item.key">{{item.title}}</li>
</ul>
<ul class="tr" v-for="(row, index) in data" :key="index">
<li
class="td"
:style="styles(item)"
v-for="item in columns"
:key="item.key"
v-html="item.render?item.render(row[item.key]):row[item.key]"
></li>
</ul>
</div>
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
export default {
props: {
columns: {
type: Array,
default() {
return []
}
},
data: {
type: Array,
default() {
return []
}
}
},
methods: {
styles(item) {
let { width, textAlign } = item
return {
width: (width || 100) + 'px',
textAlign: textAlign || 'left'
}
}
}
}
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
.table {
overflow: auto;
font-size: 0;

.tr {
display: inline-flex;
min-width: 100%;
border-bottom: 1px solid #a7bfda;

&:nth-child(2n) {
background-color: #d1dfec;
}

.th {
font-weight: bold;
}

.th,
.td {
display: inline-block;
padding: 10px 15px;
box-sizing: border-box;
flex-grow: 1;
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}

使用方式

1
<my-table :columns="tableColumns" :data="tableData"></my-table>
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
import MyTable from '../components/table'
export default {
components: {
MyTable
},
data() {
return {
tableColumns: [
{
title: 'Name',
key: 'name',
width: 120
},
{
title: 'Age',
key: 'age',
textAlign: 'right',
render(val) {
if (val >= 18) {
return "<span style='color:green'>" + val + '</span>'
} else {
return "<span style='color:red'>" + val + '</span>'
}
}
},
{
title: 'Address',
key: 'address',
width: 250
}
],
tableData: [
{
name: 'John Brown',
age: 14,
address: 'New York No. 1 Lake Park',
date: '2016-10-03'
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01'
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02'
},
{
name: 'Jon Snow',
age: 17,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04'
}
]
}
}
}
123
xiaojun1994

xiaojun1994

25 posts
19 tags
GitHub
友链
  • 赖同学
  • John Stark
  • 胡雨
  • mghio
© 2020 xiaojun1994