请求与响应

请求

1
2
3
4
{
command: 'ping', // 请求指令 (String)
data: {}, // 请求数据 (根据 command(指令) 不同而不同)
}

响应

1
2
3
4
5
{
statusCode: 200, // 响应状态码 200 为成功 (200 成功 | 400 指令错误 | 404 指令不存在 | 408 超时 | 500 设备错误) (Number)
message: '操作成功!', // 成功或失败信息 (String)
data: {}, // 响应数据 (根据 command(指令) 不同而不同)
}

指令

Ping

1
2
3
4
{
command: 'ping',
data: {},
}
1
2
3
4
5
{
statusCode: 200,
message: 'pong!',
data: {},
}

获取设备信息

1
2
3
4
{
command: 'get-info',
data: {},
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
statusCode: 200,
message: '操作成功!',
data: {
id: 'haas531-123456123456', // 设备 ID (String)
version: 1, // 固件版本 (Number)
io: { // 硬件接口信息
uart1: { // 串口1
type: 'UART', // 接口类型 (String)
port: 2, // 端口值 跟芯片 datasheet 上的端口对应 (Number)
dataWidth: 8, // 串口数据宽度值 (Number)
baudRate: 9600, // 串口波特率 (Number)
stopBits: 1, // 串口停止位 (Number)
flowControl: 'disable', // 流控设置 ('disable' | 'cts' | 'rts' | 'rtscts')
parity: 'none', // 奇偶校验 ('none' | 'odd' | 'even')
}
},
supportGB26875Names: ['default'], // 此设备支持的 GB26875 协议变种名称 (String[])
},
}

获取配置

1
2
3
4
{
command: 'get-config',
data: {},
}
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
{
statusCode: 200,
message: '操作成功!',
data: {
server: [
{
host: '127.0.0.1', // 服务器域名或 IP (String)
port: 233, // 服务器端口 (Number)
username: '233', // 服务器用户名 (String)
password: '233', // 服务器密码 (String)
},
], // 可以多个服务器同时连接
debugServer: {
host: '127.0.0.1', // 调试服务器域名或 IP (String)
port: 233, // 调试服务器端口 (Number)
username: '233', // 调试服务器用户名 (String)
password: '233', // 调试服务器密码 (String)
},
active: {
realTimeData: { // 实时数据主动上报
open: true, // 是否开启实时数据主动上报 (Boolean)
uartCommandData: [1, 2, 3, 4, 5, 6], // 通过串口发送的指令 (Number[])
uartCommandInterval: 500, // 通过串口发送指令的间隔 (Number)
},
alarm: { // 告警主动上报
open: true, // 是否开启 (Boolean)
threshold: [ // 阈值
{
key: '压力', // 字段名称 根据 protocol(协议) 解析后得到的 数据(parsed) 进行匹配的键名 (String)
rawRanges: [ // 触发范围值 范围值是原始值 结束范围可空 ([开始(Number), 结束(Number)?])
[0, 5],
[20],
],
filters: [ // 条件过滤器
{
key: '单位', // 条件字段名称 (String)
rawRanges: [
// 达成条件范围值 范围值是原始值 结束范围可空 ([开始(Number), 结束(Number)?])
[4, 4],
],
},
],
message: '压力异常',
},
]
}
fireAlarm: { // 火警主动上报
open: true,
threshold: [
{
key: '火警',
rawRanges: [
[1],
],
filters: [],
message: '设备报告火警',
},
]
}
operation: { // 操作主动上报
open: true,
threshold: [
{
key: '复位',
rawRanges: [
[1],
],
filters: [],
message: '设备报告复位操作'
},
]
}
},
protocol: {
type: 'modbus', // 协议类型 ('modbus' | 'gb26875')
gb26875Config: {
name: 'default',
}, // GB26875 的协议类型 仅 protocol - type 等于 gb26875 时有效 可选值为此设备支持信息中的可选值为此设备信息 (get-info) 中的 supportGB26875Names (String)
modbusConfig: { // Modbus 的协议配置 仅 protocol - type 等于 modbus 时有效
id: 1, // modbus 协议 ID
name: '源诚消防水源水压监测设备', // modbus 协议名称
version: 1, // modbus 协议版本
keys: [ // 解析的键
{
key: '单位', // 字段名称 (String)
position: [4, 5], // 解析时 此字段在 MODBUS 数据中对应下标范围 ([开始(Number), 结束?(Number)])
high: true, // 是否高字节在前
unit: '' // 单位 (String)
switch: [
// 转换器 为空数组时 直接输出原始值
{
range: [0, 0], // 用数组表示范围 ([开始(Number), 结束?(Number)])
value: 'Mpa', // 转换的值 (String || Number)
},
],
},
{
key: '小数点',
position: [6, 7],
high: true,
unit: ''
switch: [
{
range: [1, 1],
value: 0.1,
},
],
},
{
key: '压力',
position: [8, 9],
high: true,
unit: 'Mpa'
switch: [],
},
],
}
},
}
}

设置配置

1
2
3
4
5
6
{
command: 'set-config',
data: {
// 传入设备配置 参考 get-config
},
}
1
2
3
4
5
{
statusCode: 200,
message: '操作成功!',
data: {}
}

主动获取实时数据 (需要设备支持且开启实时数据主动上报)

1
2
3
4
{
command: 'get-data',
data: {},
}
1
2
3
4
5
6
7
8
9
10
11
12
13
{
statusCode: 200,
message: '操作成功!',
data: {
raw: [0, 1, 2, 3, 4, 5, 6], // 原始数据 (Number[])
parsed: { // 解析后的数据对象
key: { // 参数名称 key
value: 0, // 数据 (Number)
unit: '单位' // 单位 (String)
}
},
}
}

发送数据到设备串口

1
2
3
4
5
6
{
command: 'send-uart',
data: {
sendData: [1, 2, 3, 4, 5, 6, 7] // 需要发送的数据 (Number[])
},
}
1
2
3
4
5
{
statusCode: 200,
message: '操作成功!',
data: {}
}

重启设备

1
2
3
4
{
command: 'reboot',
data: {},
}
1
2
3
4
5
{
statusCode: 200,
message: '操作成功!',
data: {}
}

获取/上报类指令

获取配置 (请求/响应)

1
2
3
4
5
{
serialNumber: 1, // 流水号
cmd: '获取配置', // 指令 (String)
data: {}, // 数据
}
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
{
requestNumber: 1, // 请求流水号
cmd: '获取配置', // 指令 (String)
data: {
id: '1aa169628add4c1e965f5bc69e2e9dbb', // 设备唯一ID (String)
version: 1, // 固件版本 (Number)
server: [
{
host: 'localhost', // 服务器域名或IP
port: 23333, // 服务器端口
}
],
interval: {
heartbeatInterval: 5000, // 心跳上报间隔 (Number)
dataInterval: 10000, // 数据上报间隔 (Number)
uartInterval: 1000, // 串口发送指令间隔 (Number) 子协议类型中的 getDataCommand 为空字符串时此项无效
alarmInterval: 0, // 告警/火警上报间隔 (Number)
},
protocol: {
type: 'modbus', // 协议类型 ('modbus' || 'gb26875')
subtype: '源诚消防水源水压监测设备', // 协议子类型 (String)
},
alarmValues: [
// 告警阈值, 为空数组时代表不会产生告警 ({}[])
{
key: '压力', // 字段名称 (String)
alarmType: 'alarm', // 告警类型 ('fireAlarm' | 'alarm' | 'operate')
alarmRawRanges: [
// 触发告警范围值, 范围值是原始值, 结束范围可空 ([开始, 结束?])
[0, 5],
[20],
],
filters: [
// 条件过滤器 ({}[])
{
key: '单位', // 条件字段名称 (String)
rawRanges: [
// 达成条件范围值, 范围值是原始值, 结束范围可空 ([开始, 结束?])
[4, 4],
],
},
],
},
],
modbusSubtypes: [
// modbus 协议的子类型列表 ({}[])
{
name: '源诚消防水源水压监测设备', // 子协议名称
version: 1, // 子协议版本
},
],
gb26875Subtypes: [
// gb26875 协议的子类型列表 ({}[])
{
name: '默认', // 子协议名称
version: 1, // 子协议版本
},
],
},
}

获取 modbus 协议子类型 (请求/响应)

1
2
3
4
5
6
7
{
serialNumber: 1,
cmd: '获取modbus协议子类型',
data: {
name: '源诚消防水源水压监测设备', // 子协议名称
},
}
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
{
requestNumber: 1,
cmd: '获取modbus协议子类型',
data: {
name: '源诚消防水源水压监测设备', // 子协议名称
version: 1, // 子协议版本
commands: [
// 子协议指令
{
name: '获取数据', // 指令名称 (String)
data: [1, 3, 0, 0, 0, 5, 133, 201], // 指令内容 (Number[])
},
],
getDataCommand: '获取数据', // 用于获取数据的指令, 空字符串则不主动发送指令 (String)
keys: [
// 解析 ({}[])
{
key: '单位', // 字段名称 (String)
position: [4, 5], // 解析时, 此字段在 MODBUS 数据中的下标, 从0开始 (Number)
high: true, // 是否高字节在前
switch: [
// 转换器 ({}[])
{
range: [0, 0], // 用数组表示范围 [开始, 结束] ([Number, Number])
value: 'Mpa', // 转换的值 (String || Number)
},
],
},
{
key: '小数点',
position: [6, 7],
high: true,
switch: [
{
range: [1, 1],
value: 0.1,
},
],
},
{
key: '压力',
position: [8, 9],
high: true,
switch: [],
},
],
},
}

获取 gb26875 协议子类型 (请求/响应)

1
2
3
4
5
6
7
{
serialNumber: 1,
cmd: '获取gb26875协议子类型',
data: {
name: '默认', // 子协议名称
},
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
requestNumber: 1,
cmd: '获取gb26875协议子类型',
data: {
// GB26875协议的子类型, 此协议解析较为复杂, 只能通过更新固件的方式添加新子类型 (String[])
name: '默认', // 子协议名称
version: 1, // 子协议版本
commands: [
// 子协议指令
{
name: '获取数据', // 指令名称 (String)
data: [233, 332], // 指令内容 (Number[])
},
],
getDataCommand: '获取数据', // 用于获取数据的指令, 空字符串则不主动发送指令 (String)
},
}

获取数据 (请求/响应)

1
2
3
4
5
{
serialNumber: 1,
cmd: '获取数据',
data: {},
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
requestNumber: 1,
cmd: '获取数据',
data: {
raw: [0, 1, 2, 3], // 原始数据 (Number[])
parsed: {
// 解析数据 ({})
数据单元类型: '上传建筑消防设施系统配置情况',
系统类型: '火灾报警系统',
系统地址: 0,
系统说明长度: 0,
系统配置说明: '系统配置说明',
时间: '2021-10-12 15:43:00',
}
},
}

上报数据 (响应)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
cmd: '上报数据',
data: {
raw: [0, 1, 2, 3], // 原始数据 (Number[])
parsed: {
// 解析数据 ({})
数据单元类型: '上传建筑消防设施系统配置情况',
系统类型: '火灾报警系统',
系统地址: 0,
系统说明长度: 0,
系统配置说明: '系统配置说明',
时间: '2021-10-12 15:43:00',
}
},
}

上报告警/火警数据 (响应)

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
{
cmd: '上报告警/火警数据',
data: {
raw: [0, 1, 2, 3], // 原始数据
parsed: {
// 解析数据 ({})
数据单元类型: '上传建筑消防设施系统配置情况',
系统类型: '火灾报警系统',
系统地址: 0,
系统说明长度: 0,
系统配置说明: '系统配置说明',
时间: '2021-10-12 15:43:00',
},
alarms: [
// 告警数组 ({}[])
{
key: '压力', // 字段名称 (String)
alarmType: 'alarm', // 告警类型 ('fireAlarm' | 'alarm' | 'operate')
alarmRawRanges: [
// 触发告警范围值, 范围值是原始值, 结束范围可空 ([开始, 结束?])
[0, 5],
[20],
],
filters: [
// 条件过滤器 ({}[])
{
key: '单位', // 条件字段名称 (String)
rawRanges: [
// 达成条件范围值, 范围值是原始值, 结束范围可空 ([开始, 结束?])
[4, 4],
],
},
],
},
]
},
}

写入/删除类指令

写入服务器地址 (请求/响应)

1
2
3
4
5
6
7
8
{
serialNumber: 1,
cmd: '写入服务器地址', // 指令 (String)
data: {
host: 'localhost', // 服务器域名或IP
port: 23333, // 服务器端口
},
}
1
2
3
4
5
6
7
{
requestNumber: 1,
cmd: '写入服务器地址', // 指令 (String)
data: {
result: 1, // 指令执行结果 (1 = 成功 || 0 = 失败 || 其他)
},
}

写入上报间隔 (请求/响应)

1
2
3
4
5
6
7
8
9
10
{
serialNumber: 1,
cmd: '写入上报间隔',
data: {
heartbeatInterval: 5000, // 心跳上报间隔 (Number)
dataInterval: 10000, // 数据上报间隔 (Number)
uartInterval: 1000, // 串口发送指令间隔 (Number)
alarmInterval: 0, // 告警/火警上报间隔 (Number)
},
}
1
2
3
4
5
6
7
{
requestNumber: 1,
cmd: '写入上报间隔',
data: {
result: 1, // 指令执行结果 (1 = 成功 || 0 = 失败 || 其他)
},
}

写入协议设置 (请求/响应)

1
2
3
4
5
6
7
8
{
serialNumber: 1,
cmd: '写入协议设置',
data: {
type: 'modbus', // 协议类型 ('modbus' || 'gb26875')
subtype: '源诚消防水源水压监测设备', // 协议子类型 (String)
},
}
1
2
3
4
5
6
7
{
requestNumber: 1,
cmd: '写入协议配置',
data: {
result: 1, // 指令执行结果 (1 = 成功 || 0 = 失败 || 其他)
},
}

写入设备告警阈值 (请求/响应)

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
{
serialNumber: 1,
cmd: '写入设备告警阈值',
data: {
alarmValues: [
// 告警阈值, 为空数组时代表不会产生告警 ({}[])
{
key: '压力', // 字段名称 (String)
alarmType: 'alarm', // 告警类型 ('fireAlarm' | 'alarm' | 'operate')
alarmRawRanges: [
// 触发告警范围值, 范围值是原始值, 结束范围可空 ([开始, 结束?])
[0, 5],
[20],
],
filters: [
// 条件过滤器 ({}[])
{
key: '单位', // 条件字段名称 (String)
rawRanges: [
// 达成条件范围值, 范围值是原始值, 结束范围可空 ([开始, 结束?])
[4, 4],
],
},
],
},
],
},
}
1
2
3
4
5
6
7
{
requestNumber: 1,
cmd: '写入设备告警阈值',
data: {
result: 1, // 指令执行结果 (1 = 成功 || 0 = 失败 || 其他)
},
}

写入 modbus 协议子类型 (请求/响应)

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
{
serialNumber: 1,
cmd: '写入modbus协议子类型',
data: {
subtypes: [
// 需要写入的 modbus 协议子类型 ({}[])
{
name: '源诚消防水源水压监测设备', // 子协议名称
version: 1, // 子协议版本
commands: [
// 子协议指令
{
name: '获取数据', // 指令名称 (String)
data: [1, 3, 0, 0, 0, 5, 133, 201], // 指令内容 (Number[])
},
],
getDataCommand: '获取数据', // 用于获取数据的指令, 空字符串则不启用自动数据上报功能 (String)
keys: [
// 解析 ({}[])
{
key: '单位', // 字段名称 (String)
position: [4, 5], // 解析时, 此字段在 MODBUS 数据中的下标, 从0开始 (Number)
high: true,
switch: [
// 转换器 ({}[])
{
range: [0, 0], // 用数组表示范围 [开始, 结束] ([Number, Number])
value: 'Mpa', // 转换的值 (String || Number)
},
],
},
{
key: '小数点',
position: [6, 7],
high: true,
switch: [
{
range: [1, 1],
value: 0.1,
},
],
},
{
key: '压力',
position: [8, 9],
high: true,
switch: [],
},
],
}
]
}
}
1
2
3
4
5
6
7
{
requestNumber: 1,
cmd: '写入modbus子类型',
data: {
result: 1, // 指令执行结果 (1 = 成功 || 0 = 失败 || 其他)
},
}

写入监听设备数据日志状态 (请求/响应)

1
2
3
4
5
6
7
{
serialNumber: 1,
cmd: '写入监听设备数据日志状态',
data: {
listening: true, // 是否监听设备数据日志 (Boolean)
},
}
1
2
3
4
5
6
7
{
requestNumber: 1,
cmd: '写入监听设备数据日志状态',
data: {
result: 1, // 指令执行结果 (1 = 成功 || 0 = 失败 || 其他)
},
}

系统类指令

确认 (请求)

1
2
3
4
5
6
{
cmd: '确认',
data: {
serialNumber: 0 // 流水编号 (Number)
},
}

发送数据到串口 (请求/响应)

1
2
3
4
5
6
7
{
serialNumber: 1,
cmd: '发送数据到串口',
data: {
bytes: [1, 3, 0, 0, 0, 0, 0, 0], // 发送给设备串口的数据
},
}
1
2
3
4
5
6
7
{
requestNumber: 1,
cmd: '发送数据到串口',
data: {
result: 1, // 指令执行结果 (1 = 成功 || 0 = 失败 || 其他)
},
}

设备串口数据上报 (响应)

1
2
3
4
5
6
{
cmd: '设备串口数据上报',
data: {
bytes: [1, 3, 0, 0, 0, 0, 0, 0], // 设备实时回传原始数据
},
}

错误类指令

指令错误 (响应)

1
2
3
4
5
6
7
{
requestNumber: 1,
cmd: '指令错误',
data: {
message: '指令错误' // 错误消息
},
}

jpush-expo-config-plugin

一个极光推送的 Expo 自动配置插件, 免去配置原生项目的繁琐步骤

此软件包不能在 “Expo Go” 应用程序中使用

1. 安装

1
2
3
npm install jpush-expo-config-plugin --save
或者
yarn add jpush-expo-config-plugin

注意: 如果项目里没有 jpush-react-native、jcore-react-native, 需要安装

1
2
3
npm install jpush-react-native jcore-react-native --save
或者
yarn add jpush-react-native jcore-react-native

2. 配置

安装此 npm 包后, 请将 配置插件 添加到 app.json 或 app.config.js 的 插件数组 :

app.json

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"expo": {
"plugins": [
[
"jpush-expo-config-plugin",
{
"appKey": "你的极光推送AppKey",
"channel": "你的极光推送Channel"
}
]
]
}
}

接下来, 按照 “添加自定义 Native 代码” 指南中的描述重新构建应用程序

1
expo prebuild

expo 没办法使用 react-native-amap3d (尝试过, 会报错 module null), 需要 expo eject 变为 react-native 项目, 进行 react-native link (从 v2.0.0 开始支持 RN 的 autolinking,不再需要手动配置。), expo eject 会导致 expo start 等功能不能用.
萌新前端, 请多多包涵!~OWO

初始化 Expo 项目

如果没有安装 expo/cli 先 npm i -g expo/cliyarn global add expo/cli 进行安装

1
2
3
4
5
$ expo init

What would you like to name your app? (你想为你的应用命名什么)

Choose a template (选择模板)

Eject Expo 项目

1
2
3
4
5
6
7
$ npm run eject
or
$ yarn eject

What would you like your Android package name to be? (您希望 Android 包名是什么?)

What would you like your iOS bundle identifier to be? (您希望 iOS bundle identifier 是什么?)

初始化项目环境

如果直接使用 npm run androidyarn android 会报错 error Failed to install the app. Make sure you have the Android development environment set up (错误:无法安装应用程序。确保您已经设置了 Android 开发环境)

需要使用 Android Studio 先打开此项目下的 android 文件夹, 它会自动帮你设置环境!

启动项目

1
2
3
$ npm run android
or
$ yarn android

我所在的 Expo 版本有个坑, 如果直接使用 npm run androidyarn android 会报错 (如果启动成功, 直接略过此处)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
xxx/my-app/android/app/src/main/java/com/runomeow/myapp/MainActivity.java:19: 错误: 对于show(MainActivity,SplashScreenImageResizeMode,boolean), 找不到合适的方法
SplashScreen.show(this, SplashScreenImageResizeMode.CONTAIN, false);
^
方法 SplashScreen.show(Activity,SplashScreenViewProvider,Class<? extends ViewGroup>,boolean,Function0<Unit>,Function1<? super String,Unit>)不适用
(实际参数列表和形式参数列表长度不同)
方法 SplashScreen.show(Activity,SplashScreenViewProvider,Class<? extends ViewGroup>,boolean,Function0<Unit>)不适用
(实际参数列表和形式参数列表长度不同)
方法 SplashScreen.show(Activity,SplashScreenViewProvider,Class<? extends ViewGroup>,boolean)不适用
(实际参数列表和形式参数列表长度不同)
方法 SplashScreen.show(Activity,SplashScreenImageResizeMode,Class<? extends ViewGroup>,boolean,SplashScreenViewProvider,Function0<Unit>,Function1<? super String,Unit>)不适用
(实际参数列表和形式参数列表长度不同)
方法 SplashScreen.show(Activity,SplashScreenImageResizeMode,Class<? extends ViewGroup>,boolean,SplashScreenViewProvider,Function0<Unit>)不适用
(实际参数列表和形式参数列表长度不同)
方法 SplashScreen.show(Activity,SplashScreenImageResizeMode,Class<? extends ViewGroup>,boolean,SplashScreenViewProvider)不适用
(实际参数列表和形式参数列表长度不同)
方法 SplashScreen.show(Activity,SplashScreenImageResizeMode,Class<? extends ViewGroup>,boolean)不适用
(实际参数列表和形式参数列表长度不同)

issues#9047

结合 issues 修改 /android/app/src/main/java/com/runomeow/myapp/MainActivity.java:19 就可以了

1
2
3
4
5
找到
SplashScreen.show(this, SplashScreenImageResizeMode.CONTAIN, false);

改为
SplashScreen.show(this, SplashScreenImageResizeMode.CONTAIN, ReactRootView.class, false);

使用 react-native-amap3d

注意, 一定要设置 <MapView /> 宽高才能显示. react-native-amap3d 不支持 x86 平台, 模拟器运行会闪退, 真机运行正常!

1
2
3
$ npm install react-native-amap3d
or
$ yarn add react-native-amap3d

修改 App.tsx

1
2
3
4
5
6
7
8
9
10
11
12
import { MapView } from 'react-native-amap3d';

<MapView
center={{
latitude: 3,
longitude: 106,
}}
style={{
width: 233,
height: 233,
}}
/>;

一直用别人的有点没意思 QAQ
摸鱼自己写一个试试
渣渣代码慎入!!!

演示图片

JavaScript

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
const list = document.getElementById('list'); // 获取元素
list.addEventListener('click', () => scrollAnimate(list, 200, 1000, 120)); // 绑定事件

// scrollElement 列表元素
// newScrollTop 移动到什么位置
// direction 动画时间
// fps 帧率
function scrollAnimate(scrollElement, newScrollTop, direction, fps = 60) {
const oldScrollTop = scrollElement.scrollTop; // 原来的位置
const diffScrollTop = newScrollTop - oldScrollTop;// 相差多少
const tickTime = 1000 / fps; // 根据FPS计算每一帧延迟
const num = 1 - 60 / fps / (direction * 0.01); // 根据FPS计算每一帧移动百分之多少
let rate = num; // 当前百分比(默认移动一帧)

const timer = setInterval(() => {
const nowScrollTop = oldScrollTop + diffScrollTop * (1 - rate); // 原来的位置 + 差多少 * 百分比 (0 + 200 * (1 - 0.95))
if (Math.abs(newScrollTop - nowScrollTop) > 1) {
// 如果位置绝对值大于1px, 移动滚动条
scrollElement.scrollTo(0, nowScrollTop);
} else {
// 清除时钟, 直接设为需要移动到的位置
clearInterval(timer);
scrollElement.scrollTo(0, newScrollTop);
}
rate *= num; // 计算百分比(0.95 * 0.95 不断减少)
}, tickTime);
}

HTML

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
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Runo</title>
<link rel="stylesheet" href="main.css" />
</head>
<body>
<div id="list">
<div class="list-item">
<div class="list-title">
<div class="list-left">
<span>喵了个咪喵了个咪</span>
</div>
<div class="list-right">
<span>2020-7-24 10:51:49</span>
</div>
</div>
<div class="list-content">
<span>喵呜喵呜喵呜</span>
</div>
</div>
<div class="list-item">
<div class="list-title">
<div class="list-left">
<span>喵了个咪喵了个咪</span>
</div>
<div class="list-right">
<span>2020-7-24 10:51:49</span>
</div>
</div>
<div class="list-content">
<span>喵呜喵呜喵呜</span>
</div>
</div>
<div class="list-item">
<div class="list-title">
<div class="list-left">
<span>喵了个咪喵了个咪</span>
</div>
<div class="list-right">
<span>2020-7-24 10:51:49</span>
</div>
</div>
<div class="list-content">
<span>喵呜喵呜喵呜</span>
</div>
</div>
<div class="list-item">
<div class="list-title">
<div class="list-left">
<span>喵了个咪喵了个咪</span>
</div>
<div class="list-right">
<span>2020-7-24 10:51:49</span>
</div>
</div>
<div class="list-content">
<span>喵呜喵呜喵呜</span>
</div>
</div>
</div>
<script src="./main.js"></script>
</body>
</html>

CSS

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
body {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}

#list {
width: 750px;
height: 750px;
overflow-y: auto;

background-color: #f0f0f0;
border-radius: 10px;
}
.list-item {
position: sticky;
top: 10px;
margin: 10px;
padding: 10px;
display: flex;
align-items: center;
flex-direction: column;
height: 100px;
overflow: hidden;
box-sizing: border-box;

background-color: white;
border: 1px solid black;
border-radius: 10px;
}
.list-item:last-of-type {
margin-bottom: 640px;
}
.list-title, .list-content {
width: 100%;
}
.list-title {
display: flex;
align-items: center;
justify-content: space-between;

}
.list-left {

}
.list-right {

}
.list-content {
overflow: hidden;
}

每次更新文章都要生成一次就太麻烦啦!
于是利用 GitHub 的 Webhooks 做一个自动化部署
大概说一下搭建的经过~

Runo+ Blog
GitHub Webhooks

安装以下几个包

  • express 服务端
  • node-cmd 用于执行命令
  • crypto 校验 sha1(可选)
  • dotenv 加载.env(可选)
1
npm install express node-cmd crypto dotenv --save

在 hexo 项目下新建一个 webhooks.js

hmmm 萌新代码写的不好 见谅

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
const express = require('express');
const app = express();
const nodeCmd = require('node-cmd');
const crypto = require('crypto');
require('dotenv').config();

// JSON支持
app.use(express.json());

// 监听POST请求
app.post('/apis/update', (req, res) => {
// github通过秘钥+请求体算出来的sha1(如果不需要校验的话 下面的校验过程可不要)
const sign = req.headers['x-hub-signature'];
const sha1 =
'sha1=' +
crypto
.createHmac('sha1', process.env.GITHUB_WEBHOOKS_SECRET)
.update(JSON.stringify(req.body))
.digest()
.toString('hex');
if (!sign || sign !== sha1) return res.send('secret error');
// 使用 node-cmd 执行 git pull 拉取仓库
nodeCmd.get('git pull', (err, data) => {
if (!err) {
console.log(data);
// 使用 node-cmd 执行 npx hexo generate 生成页面
nodeCmd.get('npx hexo generate', (err, data) => {
if (err) console.error(err);
else console.log(data);
});
} else console.error(err);
});
res.send('success');
});

// 监听端口
app.listen(process.env.GITHUB_WEBHOOKS_PORT, () =>
console.log('listening ' + process.env.GITHUB_WEBHOOKS_PORT)
);

进行服务器的部署

  1. 将代码提交后, 在服务器 git clone

  2. 执行 npm run build 会在 public 文件夹生成静态网页, 再使用 nginx 等web服务器, 把域名指向 public 文件夹

  3. 使用 pm2 start 启动 webhooks.js, 服务端就算是部署好啦!

为仓库添加 Webhook

  1. 打开 GitHub 仓库

  2. 依次点击 Setting → Webhooks → Add webhook

设置 Webhook

  1. Payload URL: 需要响应的地址 (例如 https://runo.plus/xxx)

  2. Content type: 请求的内容类型 (选择 application/json)

  3. Secret: 秘钥 (需要与服务器上的一样! GITHUB_WEBHOOKS_SECRET, 如果不需要校验则省略)

  4. 选择 Just the push event. (仓库接到推送后触发)

  5. 勾选 Active

  6. 点击 Add webhook 完成添加

提交一次仓库试试效果owo~