Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Q
qingdaoMuseumPlatform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
node_server
qingdaoMuseumPlatform
Commits
878a91c0
Commit
878a91c0
authored
Jun 30, 2026
by
PC-20251223ZVQQ\Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
IAQ页面接口
parent
b16d534d
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
292 additions
and
72 deletions
+292
-72
device.ts
src/biz/device.ts
+278
-71
device.ts
src/routers/device.ts
+14
-1
No files found.
src/biz/device.ts
View file @
878a91c0
...
@@ -7,6 +7,11 @@ import { addData } from "../data/addData";
...
@@ -7,6 +7,11 @@ import { addData } from "../data/addData";
import
{
updateManyData
}
from
"../data/updateData"
;
import
{
updateManyData
}
from
"../data/updateData"
;
import
{
controlAcByEnvironment
}
from
"./running"
;
import
{
controlAcByEnvironment
}
from
"./running"
;
// ======= 模块级缓存(低频变化的数据) =======
let
deviceTreeCache
:
{
data
:
any
;
timestamp
:
number
}
|
null
=
null
;
const
CACHE_TTL
=
5
*
60
*
1000
;
// 5 分钟
const
RECORD_LIMIT
=
200
;
// 每天每设备最多取最近 200 条
/**
/**
* 注册或更新设备信息
* 注册或更新设备信息
...
@@ -73,13 +78,13 @@ export async function handleDevicePush(deviceId: string, data: any, deviceTime?:
...
@@ -73,13 +78,13 @@ export async function handleDevicePush(deviceId: string, data: any, deviceTime?:
}
}
let
nowMs
=
getMySqlMs
();
let
nowMs
=
getMySqlMs
();
let
receivedTime
=
new
Date
(
nowMs
).
toISOString
().
slice
(
0
,
19
).
replace
(
'T'
,
' '
);
let
receivedTime
=
formatTime
(
new
Date
(
nowMs
)
);
await
addData
(
TABLENAME
.
设备数据表
,
{
await
addData
(
TABLENAME
.
设备数据表
,
{
device_id
:
deviceId
,
device_id
:
deviceId
,
data
:
data
,
data
:
data
,
received_time
:
receivedTime
,
received_time
:
receivedTime
,
device_time
:
deviceTime
?
new
Date
(
deviceTime
).
toISOString
().
slice
(
0
,
19
).
replace
(
'T'
,
' '
)
:
null
,
device_time
:
deviceTime
?
formatTime
(
deviceTime
)
:
null
,
created_at
:
receivedTime
,
created_at
:
receivedTime
,
});
});
...
@@ -163,23 +168,110 @@ const INDICATOR_CONFIG = [
...
@@ -163,23 +168,110 @@ const INDICATOR_CONFIG = [
];
];
/**
/**
* 根据数据值判断质量等级
* 质量等级阈值(基于 GB/T 18883-2022 / RESET / WELL 标准)
* 优/良/中/差 四档,区间均为闭区间 [min, max]
*/
const
QUALITY_THRESHOLDS
:
{
[
key
:
string
]:
{
you
:
[
number
,
number
];
liang
:
[
number
,
number
];
zhong
:
[
number
,
number
]
}
}
=
{
co2
:
{
you
:
[
0
,
800
],
liang
:
[
800
,
1000
],
zhong
:
[
1000
,
1500
]
},
// ppm
pm10
:
{
you
:
[
0
,
50
],
liang
:
[
50
,
150
],
zhong
:
[
150
,
250
]
},
// μg/m³
pm2_5
:
{
you
:
[
0
,
35
],
liang
:
[
35
,
75
],
zhong
:
[
75
,
150
]
},
// μg/m³
hcho
:
{
you
:
[
0
,
0.03
],
liang
:
[
0.03
,
0.08
],
zhong
:
[
0.08
,
0.10
]
},
// mg/m³
tvoc
:
{
you
:
[
0
,
0.3
],
liang
:
[
0.3
,
0.6
],
zhong
:
[
0.6
,
1.0
]
},
// mg/m³
// humidity 不在此表中,由 getQuality() 内联双向判断处理(偏低/偏高各方向独立降级)
o3
:
{
you
:
[
0
,
0.05
],
liang
:
[
0.05
,
0.1
],
zhong
:
[
0.1
,
0.16
]
},
// mg/m³
light_level
:
{
you
:
[
300
,
500
],
liang
:
[
100
,
300
],
zhong
:
[
50
,
100
]
},
// lux — 通用办公/室内场景,>500 过于明亮也会判差
};
/**
* 根据数据值判断质量等级(优/良/中/差)
* - 温度:根据当前月份自动判定夏/冬季模式
* - 湿度:双向判断(低于 40 或高于 60 降级)
* - 气压:双向判断(以 1013hPa 为中心,优:1010~1020, 良:1000~1010|1020~1030, 中:990~1000|1030~1040, 差:<990|>1040)
* - 光照度:通用室内办公标准,查阈值表(优:300~500, 良:100~300, 中:50~100, 差:<50 或 >500)
*/
*/
function
getQuality
(
key
:
string
,
value
:
number
):
string
|
null
{
function
getQuality
(
key
:
string
,
value
:
number
):
string
|
null
{
if
(
value
===
null
||
value
===
undefined
)
return
null
;
if
(
value
===
null
||
value
===
undefined
)
return
null
;
// 人体感应:特殊处理
if
(
key
===
"pir"
)
return
value
===
1
?
"有人"
:
"无感应"
;
if
(
key
===
"pir"
)
return
value
===
1
?
"有人"
:
"无感应"
;
const
config
=
INDICATOR_CONFIG
.
find
(
c
=>
c
.
key
===
key
);
if
(
!
config
||
config
.
min
===
null
||
config
.
max
===
null
)
return
null
;
// 温度:季节模式
if
(
value
>=
config
.
min
&&
value
<=
config
.
max
)
return
"优秀"
;
if
(
key
===
"temperature"
)
return
getTemperatureQuality
(
value
);
return
"超标"
;
// 湿度:双向判断
if
(
key
===
"humidity"
)
{
if
(
value
>=
40
&&
value
<=
60
)
return
"优"
;
if
((
value
>=
30
&&
value
<
40
)
||
(
value
>
60
&&
value
<=
70
))
return
"良"
;
if
((
value
>=
20
&&
value
<
30
)
||
(
value
>
70
&&
value
<=
80
))
return
"中"
;
return
"差"
;
// <20 或 >80
}
// 气压:双向判断(以 1013hPa 标准大气压为中心,偏低/偏高各方向独立降级)
if
(
key
===
"pressure"
)
{
if
(
value
>=
1010
&&
value
<=
1020
)
return
"优"
;
if
((
value
>=
1000
&&
value
<
1010
)
||
(
value
>
1020
&&
value
<=
1030
))
return
"良"
;
if
((
value
>=
990
&&
value
<
1000
)
||
(
value
>
1030
&&
value
<=
1040
))
return
"中"
;
return
"差"
;
// <990 或 >1040
}
// 其他指标:查阈值表
const
t
=
QUALITY_THRESHOLDS
[
key
];
if
(
!
t
)
return
null
;
if
(
value
>=
t
.
you
[
0
]
&&
value
<=
t
.
you
[
1
])
return
"优"
;
if
(
value
>=
t
.
liang
[
0
]
&&
value
<=
t
.
liang
[
1
])
return
"良"
;
if
(
value
>=
t
.
zhong
[
0
]
&&
value
<=
t
.
zhong
[
1
])
return
"中"
;
return
"差"
;
// 超出 zhong 区间上限
}
/**
* 温度质量等级(夏/冬季模式,根据当前月份自动判定)
* 设计逻辑:温度控制是一个收敛系统,20℃/24℃ 是收敛目标。
* 良为收敛带(可接受偏移),中为漂移区间(需关注),差为失控(需告警)。
*
* 夏季(3-11月):
* 优 = 24℃(精确收敛点)
* 良 = 22~26℃(收敛带)
* 中 = 18~22℃ 或 26~32℃(漂移区间)
* 差 = <18℃ 或 >32℃(失控,可能设备故障或极端天气)
*
* 冬季(12-2月):
* 优 = 20℃(精确收敛点)
* 良 = 18~21℃(收敛带)
* 中 = 14~18℃ 或 21~27℃(漂移区间)
* 差 = <14℃ 或 >27℃(失控)
*/
function
getTemperatureQuality
(
value
:
number
):
string
{
const
month
=
new
Date
().
getMonth
()
+
1
;
// 1-12
const
isWinter
=
month
===
12
||
month
<=
2
;
if
(
isWinter
)
{
if
(
value
===
20
)
return
"优"
;
if
(
value
>=
18
&&
value
<=
21
)
return
"良"
;
if
(
value
>=
14
&&
value
<=
27
)
return
"中"
;
// 14~18 和 21~27(优/良已在前两步返回)
return
"差"
;
// <14 或 >27
}
else
{
// 夏季(3-11月)
if
(
value
===
24
)
return
"优"
;
if
(
value
>=
22
&&
value
<=
26
)
return
"良"
;
if
(
value
>=
18
&&
value
<=
32
)
return
"中"
;
// 18~22 和 26~32(优/良已在前两步返回)
return
"差"
;
// <18 或 >32
}
}
}
/**
/**
* 格式化
时间为 yyyy-MM-dd HH:mm:ss
* 格式化
为本地时间:yyyy-MM-dd HH:mm:ss(替代 toISOString,避免 UTC 时区偏移)
*/
*/
function
formatTime
(
date
:
Date
):
string
{
function
formatTime
(
date
:
Date
|
string
):
string
{
if
(
!
date
)
return
null
;
if
(
!
date
)
return
null
;
return
new
Date
(
date
).
toISOString
().
slice
(
0
,
19
).
replace
(
"T"
,
" "
);
const
d
=
new
Date
(
date
);
const
y
=
d
.
getFullYear
();
const
mo
=
String
(
d
.
getMonth
()
+
1
).
padStart
(
2
,
'0'
);
const
day
=
String
(
d
.
getDate
()).
padStart
(
2
,
'0'
);
const
h
=
String
(
d
.
getHours
()).
padStart
(
2
,
'0'
);
const
mi
=
String
(
d
.
getMinutes
()).
padStart
(
2
,
'0'
);
const
s
=
String
(
d
.
getSeconds
()).
padStart
(
2
,
'0'
);
return
`
${
y
}
-
${
mo
}
-
${
day
}
${
h
}
:
${
mi
}
:
${
s
}
`
;
}
}
/**
/**
...
@@ -192,15 +284,17 @@ function getTodayStart(): string {
...
@@ -192,15 +284,17 @@ function getTodayStart(): string {
}
}
/**
/**
* 获取当天
的 device_data 记录
* 获取当天
最近的 device_data 记录(按设备测量时间倒序,限量返回)
*/
*/
async
function
getTodayDeviceData
(
deviceId
:
string
):
Promise
<
any
[]
>
{
async
function
getTodayDeviceData
(
deviceId
:
string
,
limit
:
number
=
RECORD_LIMIT
):
Promise
<
any
[]
>
{
const
todayStart
=
getTodayStart
();
const
todayStart
=
getTodayStart
();
const
result
=
await
selectDataListByParam
(
TABLENAME
.
设备数据表
,
{
const
params
:
any
=
{
device_id
:
deviceId
,
device_id
:
deviceId
,
created_at
:
{
"%gte%"
:
todayStart
},
device_time
:
{
"%gte%"
:
todayStart
},
"%orderAsc%"
:
"created_at"
,
"%orderDesc%"
:
"device_time"
,
});
};
if
(
limit
)
params
[
"%limit%"
]
=
limit
;
const
result
=
await
selectDataListByParam
(
TABLENAME
.
设备数据表
,
params
);
return
result
.
data
||
[];
return
result
.
data
||
[];
}
}
...
@@ -208,11 +302,32 @@ async function getTodayDeviceData(deviceId: string): Promise<any[]> {
...
@@ -208,11 +302,32 @@ async function getTodayDeviceData(deviceId: string): Promise<any[]> {
* 构建 indicators 数组(基于当天最新数据)
* 构建 indicators 数组(基于当天最新数据)
*/
*/
function
buildIndicators
(
allLatestData
:
{
[
deviceId
:
string
]:
any
}):
any
[]
{
function
buildIndicators
(
allLatestData
:
{
[
deviceId
:
string
]:
any
}):
any
[]
{
const
sampleData
=
Object
.
values
(
allLatestData
).
find
(
d
=>
d
&&
d
.
data
)
as
any
;
// 收集所有设备的最新数据
const
raw
=
sampleData
?.
data
||
{};
const
allData
=
Object
.
values
(
allLatestData
).
filter
(
d
=>
d
&&
d
.
data
);
if
(
allData
.
length
===
0
)
{
return
INDICATOR_CONFIG
.
map
(
config
=>
({
key
:
config
.
key
,
name
:
config
.
name
,
unit
:
config
.
unit
,
current_value
:
0
,
min
:
config
.
min
,
max
:
config
.
max
,
quality
:
null
,
}));
}
return
INDICATOR_CONFIG
.
map
(
config
=>
{
return
INDICATOR_CONFIG
.
map
(
config
=>
{
const
currentValue
=
raw
[
config
.
key
]
!==
undefined
?
raw
[
config
.
key
]
:
null
;
// 收集所有设备该指标的数值
const
values
=
allData
.
map
(
d
=>
d
.
data
[
config
.
key
])
.
filter
(
v
=>
v
!==
undefined
&&
v
!==
null
&&
typeof
v
===
'number'
);
let
currentValue
:
number
=
0
;
if
(
values
.
length
>
0
)
{
const
sum
=
values
.
reduce
((
acc
,
v
)
=>
acc
+
v
,
0
);
currentValue
=
Math
.
round
((
sum
/
values
.
length
)
*
100
)
/
100
;
// 保留两位小数
}
return
{
return
{
key
:
config
.
key
,
key
:
config
.
key
,
name
:
config
.
name
,
name
:
config
.
name
,
...
@@ -228,50 +343,134 @@ function buildIndicators(allLatestData: { [deviceId: string]: any }): any[] {
...
@@ -228,50 +343,134 @@ function buildIndicators(allLatestData: { [deviceId: string]: any }): any[] {
/**
/**
* 构建 records 数据
* 构建 records 数据
*/
*/
function
buildRecords
(
deviceDataMap
:
{
[
deviceId
:
string
]:
any
[]
}
,
isMultiple
:
boolean
)
{
function
buildRecords
(
deviceDataMap
:
{
[
deviceId
:
string
]:
any
[]
})
{
const
allRows
:
any
[]
=
[];
const
allRows
:
any
[]
=
[];
for
(
const
deviceId
in
deviceDataMap
)
{
for
(
const
deviceId
in
deviceDataMap
)
{
for
(
const
record
of
deviceDataMap
[
deviceId
])
{
for
(
const
record
of
deviceDataMap
[
deviceId
])
{
const
rawData
=
record
.
data
||
{};
const
rawData
=
record
.
data
||
{};
const
row
:
any
=
{
const
row
:
any
=
{
device_id
:
deviceId
,
device_name
:
record
.
device_name
||
deviceId
,
device_time
:
record
.
device_time
?
formatTime
(
record
.
device_time
)
:
formatTime
(
record
.
created_at
),
device_time
:
record
.
device_time
?
formatTime
(
record
.
device_time
)
:
formatTime
(
record
.
created_at
),
};
};
if
(
isMultiple
)
{
row
.
device_id
=
deviceId
;
row
.
device_name
=
record
.
device_name
||
deviceId
;
}
INDICATOR_CONFIG
.
forEach
(
c
=>
{
INDICATOR_CONFIG
.
forEach
(
c
=>
{
row
[
c
.
key
]
=
rawData
[
c
.
key
]
!==
undefined
?
rawData
[
c
.
key
]
:
null
;
row
[
c
.
key
]
=
rawData
[
c
.
key
]
!==
undefined
?
rawData
[
c
.
key
]
:
null
;
});
});
allRows
.
push
(
row
);
allRows
.
push
(
row
);
}
}
}
}
// 按时间
升
序
// 按时间
倒
序
allRows
.
sort
((
a
,
b
)
=>
(
a
.
device_time
||
""
).
localeCompare
(
b
.
device_time
||
""
));
allRows
.
sort
((
a
,
b
)
=>
(
b
.
device_time
||
""
).
localeCompare
(
a
.
device_time
||
""
));
// titleList
// titleList
const
titleList
=
isMultiple
const
titleList
=
[
"设备编号"
,
"设备名"
,
"时间"
,
...
INDICATOR_CONFIG
.
map
(
c
=>
c
.
name
)];
?
[
"设备编号"
,
"设备名"
,
"时间"
,
...
INDICATOR_CONFIG
.
map
(
c
=>
c
.
name
)]
:
[
"时间"
,
...
INDICATOR_CONFIG
.
map
(
c
=>
c
.
name
)];
// dataList:将 row 转为数组按 titleList 顺序
// dataList:将 row 转为数组按 titleList 顺序
const
dataList
=
allRows
.
map
(
row
=>
{
const
dataList
=
allRows
.
map
(
row
=>
[
if
(
isMultiple
)
{
row
.
device_id
,
return
[
row
.
device_name
,
row
.
device_id
,
row
.
device_time
,
row
.
device_name
,
...
INDICATOR_CONFIG
.
map
(
c
=>
row
[
c
.
key
]),
row
.
device_time
,
]);
...
INDICATOR_CONFIG
.
map
(
c
=>
row
[
c
.
key
]),
];
return
{
titleList
,
dataList
};
}
else
{
}
return
[
row
.
device_time
,
/**
...
INDICATOR_CONFIG
.
map
(
c
=>
row
[
c
.
key
]),
* 获取环境设备信息
];
* @param regionGroup
* @param regionType
* @param regionKey
* @param deviceId
*/
/**
* 获取环境监测设备,按 馆 → 层 → 设备 三级树形结构返回
* 响应格式为:[{"key":"A馆","value":"A馆","sub":[{"key":"一层","value":"一层","sub":[{"key":"24E124710E467239","value":"沙盘厅_cgq5","sub":[]}]}]}]
*/
export
async
function
getEnvironmentDevices
()
{
// 内存缓存:设备树结构变化低频,5 分钟内直接复用
if
(
deviceTreeCache
&&
(
Date
.
now
()
-
deviceTreeCache
.
timestamp
)
<
CACHE_TTL
)
{
return
deviceTreeCache
.
data
;
}
// 1. 查询所有环境监测设备
const
deviceResult
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
{
device_type
:
"环境监测"
},
[
"device_id"
,
"device_name"
,
"region_key"
]);
const
devices
=
deviceResult
.
data
||
[];
if
(
devices
.
length
===
0
)
{
return
[];
}
// 2. 收集所有 region_key,查询对应区域信息
const
regionKeys
=
[...
new
Set
(
devices
.
map
((
d
:
any
)
=>
d
.
region_key
).
filter
(
Boolean
))];
if
(
regionKeys
.
length
===
0
)
{
return
[];
}
const
regionResult
=
await
selectDataListByParam
(
TABLENAME
.
区域表
,
{
id
:
{
"%in%"
:
regionKeys
}
},
[
"id"
,
"name"
,
"type"
,
"groups"
]);
const
regions
=
regionResult
.
data
||
[];
// 构建 regionKey → region 的映射
const
regionMap
:
{
[
id
:
number
]:
any
}
=
{};
regions
.
forEach
((
r
:
any
)
=>
{
regionMap
[
r
.
id
]
=
r
;
});
// 3. 构建三级树:groups(馆) → type(层) → device
// 用于跟踪已创建的 group/type 节点,避免重复
const
groupMap
:
{
[
group
:
string
]:
{
key
:
string
;
value
:
string
;
typeMap
:
{
[
type
:
string
]:
{
key
:
string
;
value
:
string
;
sub
:
any
[]
}
}
}
}
=
{};
devices
.
forEach
((
dev
:
any
)
=>
{
const
region
=
regionMap
[
dev
.
region_key
];
if
(
!
region
)
return
;
const
groupName
=
region
.
groups
;
const
typeName
=
region
.
type
;
const
roomName
=
region
.
name
;
// 确保 group 节点存在
if
(
!
groupMap
[
groupName
])
{
groupMap
[
groupName
]
=
{
key
:
groupName
,
value
:
groupName
,
typeMap
:
{}
};
}
}
// 确保 type 节点存在
if
(
!
groupMap
[
groupName
].
typeMap
[
typeName
])
{
groupMap
[
groupName
].
typeMap
[
typeName
]
=
{
key
:
typeName
,
value
:
typeName
,
sub
:
[]
};
}
// 添加设备到 type 下
groupMap
[
groupName
].
typeMap
[
typeName
].
sub
.
push
({
key
:
dev
.
device_id
,
value
:
roomName
+
"_"
+
dev
.
device_name
,
sub
:
[]
});
});
});
return
{
titleList
,
dataList
};
// 4. 组装最终的树形数组
const
result
:
any
[]
=
[];
// 按 group 名称排序,保证稳定顺序
const
sortedGroups
=
Object
.
keys
(
groupMap
).
sort
();
sortedGroups
.
forEach
(
groupName
=>
{
const
group
=
groupMap
[
groupName
];
const
subArr
:
any
[]
=
[];
// 按 type 名称排序
const
sortedTypes
=
Object
.
keys
(
group
.
typeMap
).
sort
();
sortedTypes
.
forEach
(
typeName
=>
{
subArr
.
push
(
group
.
typeMap
[
typeName
]);
});
result
.
push
({
key
:
group
.
key
,
value
:
group
.
value
,
sub
:
subArr
});
});
deviceTreeCache
=
{
data
:
result
,
timestamp
:
Date
.
now
()
};
return
result
;
}
}
/**
/**
...
@@ -282,10 +481,10 @@ function buildRecords(deviceDataMap: { [deviceId: string]: any[] }, isMultiple:
...
@@ -282,10 +481,10 @@ function buildRecords(deviceDataMap: { [deviceId: string]: any[] }, isMultiple:
* @param regionGroup 楼栋(如 "A馆")
* @param regionGroup 楼栋(如 "A馆")
* @param regionType 楼层(如 "1F")
* @param regionType 楼层(如 "1F")
* @param regionKey 区域ID
* @param regionKey 区域ID
* @param deviceId
设备ID,精确查询单个
设备
* @param deviceId
s 设备ID数组,精确查询指定
设备
* @returns
* @returns
*/
*/
export
async
function
getEnvironmentData
(
regionGroup
:
string
,
regionType
:
string
,
regionKey
:
number
,
deviceId
:
string
)
{
export
async
function
getEnvironmentData
(
regionGroup
:
string
,
regionType
:
string
,
regionKey
:
number
,
deviceId
:
string
[]
)
{
// ====== 假数据(调试用) ======
// ====== 假数据(调试用) ======
// return {
// return {
// indicators: [
// indicators: [
...
@@ -302,10 +501,10 @@ export async function getEnvironmentData(regionGroup: string, regionType: string
...
@@ -302,10 +501,10 @@ export async function getEnvironmentData(regionGroup: string, regionType: string
// { key: "o3", name: "臭氧", unit: "mg/m³", current_value: 0.04, min: 0, max: 0.1, quality: "优秀" },
// { key: "o3", name: "臭氧", unit: "mg/m³", current_value: 0.04, min: 0, max: 0.1, quality: "优秀" },
// ],
// ],
// records: {
// records: {
// titleList: ["时间", "二氧化碳", "PM10", "PM2.5", "甲醛", "TVOC", "温度", "湿度", "气压", "光照度", "PIR", "臭氧"],
// titleList: ["
设备", "
时间", "二氧化碳", "PM10", "PM2.5", "甲醛", "TVOC", "温度", "湿度", "气压", "光照度", "PIR", "臭氧"],
// dataList: [
// dataList: [
// ["2025-06-18 10:00:00", 450, 30, 15, 0.02, 0.3, 25, 60, 1013, 300, 0, 0.04],
// ["
沙盘厅_cgq2", "
2025-06-18 10:00:00", 450, 30, 15, 0.02, 0.3, 25, 60, 1013, 300, 0, 0.04],
// ["2025-06-18 10:30:00", 460, 32, 16, 0.02, 0.28, 25.5, 58, 1012, 320, 1, 0.04],
// ["
辉煌厅_cgq5", "
2025-06-18 10:30:00", 460, 32, 16, 0.02, 0.28, 25.5, 58, 1012, 320, 1, 0.04],
// ],
// ],
// },
// },
// areaInfo: {
// areaInfo: {
...
@@ -318,10 +517,10 @@ export async function getEnvironmentData(regionGroup: string, regionType: string
...
@@ -318,10 +517,10 @@ export async function getEnvironmentData(regionGroup: string, regionType: string
let
devices
:
any
[]
=
[];
let
devices
:
any
[]
=
[];
// 1. 如果传了 deviceId,直接查
该
设备
// 1. 如果传了 deviceId,直接查
这些
设备
if
(
deviceId
)
{
if
(
deviceId
&&
deviceId
.
length
>
0
)
{
const
deviceResult
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
{
const
deviceResult
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
{
device_id
:
deviceId
,
device_id
:
{
'%in%'
:
deviceId
}
,
device_type
:
"环境监测"
,
device_type
:
"环境监测"
,
});
});
devices
=
deviceResult
.
data
||
[];
devices
=
deviceResult
.
data
||
[];
...
@@ -355,46 +554,54 @@ export async function getEnvironmentData(regionGroup: string, regionType: string
...
@@ -355,46 +554,54 @@ export async function getEnvironmentData(regionGroup: string, regionType: string
return
{
indicators
:
[],
records
:
{
titleList
:
[],
dataList
:
[]
},
areaInfo
:
null
};
return
{
indicators
:
[],
records
:
{
titleList
:
[],
dataList
:
[]
},
areaInfo
:
null
};
}
}
const
isMultiple
=
devices
.
length
>
1
;
// 查询设备所属区域,构建 regionKey -> 区域信息 的映射
const
deviceRegionKeys
=
[...
new
Set
(
devices
.
map
((
d
:
any
)
=>
d
.
region_key
).
filter
(
Boolean
))];
const
regionNameMap
:
{
[
key
:
number
]:
string
}
=
{};
const
regionInfoMap
:
{
[
key
:
number
]:
any
}
=
{};
if
(
deviceRegionKeys
.
length
>
0
)
{
const
regionResult
=
await
selectDataListByParam
(
TABLENAME
.
区域表
,
{
id
:
{
"%in%"
:
deviceRegionKeys
},
},
[
"id"
,
"name"
,
"type"
,
"groups"
]);
(
regionResult
.
data
||
[]).
forEach
((
r
:
any
)
=>
{
regionNameMap
[
r
.
id
]
=
r
.
name
||
''
;
regionInfoMap
[
r
.
id
]
=
r
;
});
}
// 5.
获取每个设备当天的数据
// 5.
并行获取每个设备当天的数据(取代串行 for...of,N 个设备只需 1 次 DB 往返时间)
const
deviceDataMap
:
{
[
deviceId
:
string
]:
any
[]
}
=
{};
const
deviceDataMap
:
{
[
deviceId
:
string
]:
any
[]
}
=
{};
const
allLatestData
:
{
[
deviceId
:
string
]:
any
}
=
{};
const
allLatestData
:
{
[
deviceId
:
string
]:
any
}
=
{};
for
(
const
device
of
devices
)
{
await
Promise
.
all
(
devices
.
map
(
async
(
device
)
=>
{
const
devId
=
device
.
device_id
;
const
devId
=
device
.
device_id
;
const
records
=
await
getTodayDeviceData
(
devId
);
const
records
=
await
getTodayDeviceData
(
devId
);
const
regionName
=
regionNameMap
[
device
.
region_key
]
||
''
;
records
.
forEach
((
r
:
any
)
=>
{
records
.
forEach
((
r
:
any
)
=>
{
r
.
device_name
=
device
.
device_name
||
devId
;
r
.
device_name
=
regionName
+
'_'
+
(
device
.
device_name
||
devId
)
;
});
});
deviceDataMap
[
devId
]
=
records
;
deviceDataMap
[
devId
]
=
records
;
// 降序查询,第一条即为最新
if
(
records
.
length
>
0
)
{
if
(
records
.
length
>
0
)
{
allLatestData
[
devId
]
=
records
[
records
.
length
-
1
];
allLatestData
[
devId
]
=
records
[
0
];
}
}
}
}
));
// 6. 构建 indicators
// 6. 构建 indicators
const
indicators
=
buildIndicators
(
allLatestData
);
const
indicators
=
buildIndicators
(
allLatestData
);
// 7. 构建 records
// 7. 构建 records
const
records
=
buildRecords
(
deviceDataMap
,
isMultiple
);
const
records
=
buildRecords
(
deviceDataMap
);
// 8. 查询设备所属区域,构建 areaInfo
// 8. 构建 areaInfo(复用已查询的区域信息)
const
deviceRegionKeys
=
[...
new
Set
(
devices
.
map
((
d
:
any
)
=>
d
.
region_key
).
filter
(
Boolean
))];
let
building
=
""
;
let
building
=
""
;
let
floor
=
""
;
let
floor
=
""
;
let
room
=
""
;
let
room
=
""
;
if
(
deviceRegionKeys
.
length
>
0
)
{
const
regionValues
=
Object
.
values
(
regionInfoMap
);
const
regionInfoResult
=
await
selectDataListByParam
(
TABLENAME
.
区域表
,
{
if
(
regionValues
.
length
>
0
)
{
id
:
{
"%in%"
:
deviceRegionKeys
},
const
firstRegion
:
any
=
regionValues
[
0
];
},
[
"id"
,
"name"
,
"type"
,
"groups"
]);
building
=
firstRegion
.
groups
||
""
;
const
regionList
=
regionInfoResult
.
data
||
[];
floor
=
firstRegion
.
type
||
""
;
if
(
regionList
.
length
>
0
)
{
room
=
firstRegion
.
name
||
""
;
// 取第一个区域的信息填充
building
=
regionList
[
0
].
groups
||
""
;
floor
=
regionList
[
0
].
type
||
""
;
room
=
regionList
[
0
].
name
||
""
;
}
}
}
const
areaInfo
=
{
const
areaInfo
=
{
building
,
building
,
...
...
src/routers/device.ts
View file @
878a91c0
...
@@ -38,6 +38,8 @@ export function setRouter(httpServer) {
...
@@ -38,6 +38,8 @@ export function setRouter(httpServer) {
/** 停止数据集成 */
/** 停止数据集成 */
httpServer
.
post
(
'/api/qdm/run/stop/schedule'
,
asyncHandler
(
stopSchedule
));
httpServer
.
post
(
'/api/qdm/run/stop/schedule'
,
asyncHandler
(
stopSchedule
));
/** 获取环境监测设备 */
httpServer
.
post
(
'/api/qdm/device/envDevice'
,
asyncHandler
(
getEnvironmentDevices
));
/** 获取环境监测数据 */
/** 获取环境监测数据 */
httpServer
.
post
(
'/api/qdm/device/environment'
,
asyncHandler
(
getEnvironmentData
));
httpServer
.
post
(
'/api/qdm/device/environment'
,
asyncHandler
(
getEnvironmentData
));
}
}
...
@@ -160,10 +162,21 @@ async function stopSchedule(req, res) {
...
@@ -160,10 +162,21 @@ async function stopSchedule(req, res) {
}
}
/**
/**
* 获取环境设备
*/
async
function
getEnvironmentDevices
(
req
,
res
)
{
let
reqConf
=
{};
let
{
}
=
eccReqParamater
(
reqConf
,
req
.
body
);
const
result
=
await
deviceBiz
.
getEnvironmentDevices
();
res
.
success
(
result
);
}
/**
* 获取环境监测数据
* 获取环境监测数据
*/
*/
async
function
getEnvironmentData
(
req
,
res
)
{
async
function
getEnvironmentData
(
req
,
res
)
{
let
reqConf
=
{
regionGroup
:
'String'
,
regionType
:
'String'
,
regionKey
:
'Number'
,
deviceId
:
'
String
'
};
let
reqConf
=
{
regionGroup
:
'String'
,
regionType
:
'String'
,
regionKey
:
'Number'
,
deviceId
:
'
Array
'
};
const
NotMustHaveKeys
=
[
"regionGroup"
,
"regionType"
,
"regionKey"
,
"deviceId"
];
const
NotMustHaveKeys
=
[
"regionGroup"
,
"regionType"
,
"regionKey"
,
"deviceId"
];
let
{
regionGroup
,
regionType
,
regionKey
,
deviceId
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
let
{
regionGroup
,
regionType
,
regionKey
,
deviceId
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment