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
456f47ee
Commit
456f47ee
authored
Aug 18, 2026
by
PC-20251223ZVQQ\Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
运行分析数据修改
parent
daed81cb
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
396 additions
and
58 deletions
+396
-58
device.ts
src/biz/device.ts
+103
-26
feiyiClient.ts
src/biz/feiyiClient.ts
+0
-0
report.ts
src/biz/report.ts
+228
-25
running.ts
src/biz/running.ts
+0
-0
findData.ts
src/data/findData.ts
+14
-0
admin.ts
src/routers/admin.ts
+50
-6
device.ts
src/routers/device.ts
+1
-1
No files found.
src/biz/device.ts
View file @
456f47ee
import
{
selectOneDataByParam
,
selectDataListByParam
,
selectDataCountByParam
,
selectDataListToPageByParam
}
from
"../data/findData"
;
import
{
selectOneDataByParam
,
selectDataListByParam
,
selectDataCountByParam
,
selectDataListToPageByParam
,
selectPaginatedDataWithOrder
}
from
"../data/findData"
;
import
{
TABLENAME
}
from
"../config/dbEnum"
;
import
{
TABLENAME
}
from
"../config/dbEnum"
;
import
{
getMySqlMs
}
from
"../tools/systemTools"
;
import
{
getMySqlMs
}
from
"../tools/systemTools"
;
import
{
BizError
}
from
"../util/bizError"
;
import
{
BizError
}
from
"../util/bizError"
;
...
@@ -791,7 +791,7 @@ export async function getDeviceList(params: {
...
@@ -791,7 +791,7 @@ export async function getDeviceList(params: {
}
}
// 1. 构建查询参数,只查空调设备,排除 region_key=0 的设备
// 1. 构建查询参数,只查空调设备,排除 region_key=0 的设备
let
paramAnys
:
any
=
{
device_type
:
'空调'
};
let
paramAnys
:
any
=
{
device_type
:
[
'空调'
,
'新风'
]
};
if
(
regionKeys
.
length
)
{
if
(
regionKeys
.
length
)
{
paramAnys
.
region_key
=
{
"%in%"
:
regionKeys
};
paramAnys
.
region_key
=
{
"%in%"
:
regionKeys
};
}
else
{
}
else
{
...
@@ -1041,7 +1041,7 @@ export async function batchEditDevices(params: {
...
@@ -1041,7 +1041,7 @@ export async function batchEditDevices(params: {
edits
:
{
edits
:
{
power
?:
string
;
power
?:
string
;
setTemp
?:
number
;
setTemp
?:
number
;
l
inkageStart
?:
string
;
l
ocked
?:
string
;
};
};
}):
Promise
<
{
total
:
number
;
successCount
:
number
;
failCount
:
number
;
details
:
any
[]
}
>
{
}):
Promise
<
{
total
:
number
;
successCount
:
number
;
failCount
:
number
;
details
:
any
[]
}
>
{
const
{
regionKeys
,
deviceName
,
power
,
state
,
deviceMode
,
startDate
,
endDate
,
edits
}
=
params
;
const
{
regionKeys
,
deviceName
,
power
,
state
,
deviceMode
,
startDate
,
endDate
,
edits
}
=
params
;
...
@@ -1053,9 +1053,9 @@ export async function batchEditDevices(params: {
...
@@ -1053,9 +1053,9 @@ export async function batchEditDevices(params: {
if
(
!
edits
||
typeof
edits
!==
'object'
)
{
if
(
!
edits
||
typeof
edits
!==
'object'
)
{
throw
new
BizError
(
ERRORENUM
.
参数错误
,
"edits 不能为空"
);
throw
new
BizError
(
ERRORENUM
.
参数错误
,
"edits 不能为空"
);
}
}
const
hasEdit
=
edits
.
power
!==
undefined
||
edits
.
setTemp
!==
undefined
||
edits
.
l
inkageStart
!==
undefined
;
const
hasEdit
=
edits
.
power
!==
undefined
||
edits
.
setTemp
!==
undefined
||
edits
.
l
ocked
!==
undefined
;
if
(
!
hasEdit
)
{
if
(
!
hasEdit
)
{
throw
new
BizError
(
ERRORENUM
.
参数错误
,
"edits 中至少需要 power / setTemp / l
inkageStart
其中之一"
);
throw
new
BizError
(
ERRORENUM
.
参数错误
,
"edits 中至少需要 power / setTemp / l
ocked
其中之一"
);
}
}
// 1. 构建设备查询条件
// 1. 构建设备查询条件
...
@@ -1124,20 +1124,8 @@ export async function batchEditDevices(params: {
...
@@ -1124,20 +1124,8 @@ export async function batchEditDevices(params: {
let
successCount
=
0
;
let
successCount
=
0
;
let
failCount
=
0
;
let
failCount
=
0
;
// 4. 处理 linkageStart 编辑(纯数据库更新,一次性批量更新)
// 4. 处理 power / setTemp / locked 编辑(逐台调用飞奕接口)
if
(
edits
.
linkageStart
!==
undefined
)
{
const
needAcControl
=
edits
.
power
!==
undefined
||
edits
.
setTemp
!==
undefined
||
edits
.
locked
!==
undefined
;
try
{
const
deviceIds
=
devices
.
map
(
d
=>
d
.
device_id
);
await
updateManyData
(
TABLENAME
.
设备表
,
{
device_id
:
{
"%in%"
:
deviceIds
}
},
{
linkage_start
:
edits
.
linkageStart
});
console
.
log
(
`[batchEditDevices] 批量设置联动为
${
edits
.
linkageStart
}
,涉及
${
deviceIds
.
length
}
台设备`
);
}
catch
(
e
:
any
)
{
console
.
error
(
'[batchEditDevices] 批量更新 linkage_start 失败:'
,
e
);
throw
new
BizError
(
ERRORENUM
.
操作失败
,
`批量更新联动状态失败:
${
e
.
message
}
`
);
}
}
// 5. 处理 power / setTemp 编辑(逐台调用飞奕接口)
const
needAcControl
=
edits
.
power
!==
undefined
||
edits
.
setTemp
!==
undefined
;
if
(
needAcControl
)
{
if
(
needAcControl
)
{
const
now
=
new
Date
();
const
now
=
new
Date
();
...
@@ -1154,6 +1142,12 @@ export async function batchEditDevices(params: {
...
@@ -1154,6 +1142,12 @@ export async function batchEditDevices(params: {
if
(
edits
.
setTemp
!==
undefined
)
{
if
(
edits
.
setTemp
!==
undefined
)
{
acParams
.
tempSet
=
edits
.
setTemp
;
acParams
.
tempSet
=
edits
.
setTemp
;
}
}
if
(
edits
.
locked
!==
undefined
)
{
const
lockVal
=
edits
.
locked
===
'on'
?
1
:
0
;
acParams
.
onOffLock
=
lockVal
;
acParams
.
workModeLock
=
lockVal
;
acParams
.
tempSetLock
=
lockVal
;
}
const
result
=
await
controlIndoorUnit
(
acParams
);
const
result
=
await
controlIndoorUnit
(
acParams
);
...
@@ -1167,7 +1161,7 @@ export async function batchEditDevices(params: {
...
@@ -1167,7 +1161,7 @@ export async function batchEditDevices(params: {
time
:
formatLocalTime
(
now
),
time
:
formatLocalTime
(
now
),
power
:
edits
.
power
,
power
:
edits
.
power
,
setTemp
:
edits
.
setTemp
,
setTemp
:
edits
.
setTemp
,
l
inkageStart
:
edits
.
linkageStart
,
l
ocked
:
edits
.
locked
,
success
:
result
.
success
,
success
:
result
.
success
,
message
:
result
.
data
||
(
result
.
success
?
'控制成功'
:
'控制失败'
),
message
:
result
.
data
||
(
result
.
success
?
'控制成功'
:
'控制失败'
),
},
},
...
@@ -1190,12 +1184,6 @@ export async function batchEditDevices(params: {
...
@@ -1190,12 +1184,6 @@ export async function batchEditDevices(params: {
console
.
error
(
`[batchEditDevices] 控制设备
${
dev
.
device_id
}
失败:`
,
e
);
console
.
error
(
`[batchEditDevices] 控制设备
${
dev
.
device_id
}
失败:`
,
e
);
}
}
}
}
}
else
{
// 仅 linkageStart 修改,全部成功
successCount
=
total
;
for
(
const
dev
of
devices
)
{
details
.
push
({
deviceId
:
dev
.
device_id
,
success
:
true
});
}
}
}
return
{
total
,
successCount
,
failCount
,
details
};
return
{
total
,
successCount
,
failCount
,
details
};
...
@@ -1254,6 +1242,7 @@ export async function batchEditLocalDevices(params: {
...
@@ -1254,6 +1242,7 @@ export async function batchEditLocalDevices(params: {
deviceType
?:
string
;
deviceType
?:
string
;
controlParams
?:
string
;
controlParams
?:
string
;
deviceState
?:
string
;
deviceState
?:
string
;
linkageStart
?:
string
;
};
};
})
{
})
{
const
{
regionKeys
,
deviceName
,
edits
}
=
params
;
const
{
regionKeys
,
deviceName
,
edits
}
=
params
;
...
@@ -1277,6 +1266,7 @@ export async function batchEditLocalDevices(params: {
...
@@ -1277,6 +1266,7 @@ export async function batchEditLocalDevices(params: {
if
(
edits
.
deviceType
!==
undefined
)
dbEdits
.
device_type
=
edits
.
deviceType
;
if
(
edits
.
deviceType
!==
undefined
)
dbEdits
.
device_type
=
edits
.
deviceType
;
if
(
edits
.
controlParams
!==
undefined
)
dbEdits
.
control_params
=
edits
.
controlParams
;
if
(
edits
.
controlParams
!==
undefined
)
dbEdits
.
control_params
=
edits
.
controlParams
;
if
(
edits
.
deviceState
!==
undefined
)
dbEdits
.
device_state
=
edits
.
deviceState
;
if
(
edits
.
deviceState
!==
undefined
)
dbEdits
.
device_state
=
edits
.
deviceState
;
if
(
edits
.
linkageStart
!==
undefined
)
dbEdits
.
linkage_start
=
edits
.
linkageStart
;
if
(
Object
.
keys
(
dbEdits
).
length
===
0
)
{
if
(
Object
.
keys
(
dbEdits
).
length
===
0
)
{
throw
new
BizError
(
ERRORENUM
.
参数错误
,
"edits 中至少需要一个编辑字段"
);
throw
new
BizError
(
ERRORENUM
.
参数错误
,
"edits 中至少需要一个编辑字段"
);
...
@@ -1286,4 +1276,90 @@ export async function batchEditLocalDevices(params: {
...
@@ -1286,4 +1276,90 @@ export async function batchEditLocalDevices(params: {
console
.
log
(
`[batchEditLocalDevices] 批量更新,条件:`
,
where
,
"更新内容:"
,
dbEdits
);
console
.
log
(
`[batchEditLocalDevices] 批量更新,条件:`
,
where
,
"更新内容:"
,
dbEdits
);
return
{
isSuccess
:
true
};
return
{
isSuccess
:
true
};
}
// ======= 设备日志管理 =======
/**
* 设备管理 - 日志管理分页列表
* @param params { pageNumber, pageSize, deviceName, status }
* @returns { list, pagination }
*
* 展示字段:序号(id)、设备标识(device_id)、设备名称(device_name)、标题(operation_content.option)、内容(operation_content)、状态(status 0失败 1成功)、创建时间(created_at)
*/
export
async
function
getDeviceLogList
(
params
:
{
pageNumber
?:
number
;
pageSize
?:
number
;
deviceName
?:
string
;
// 设备名称(模糊匹配,通过 device 表关联)
status
?:
number
;
// 操作状态:0失败 1成功
})
{
const
pageNumber
=
params
.
pageNumber
||
1
;
const
pageSize
=
params
.
pageSize
||
10
;
// 构建查询条件
const
queryParam
:
any
=
{};
// 状态筛选
if
(
params
.
status
!==
undefined
&&
params
.
status
!==
null
)
{
queryParam
.
status
=
params
.
status
;
}
// 设备名称筛选:先通过 device 表查出匹配的 device_id 列表
if
(
params
.
deviceName
)
{
const
deviceResult
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
{
device_name
:
{
"%like%"
:
params
.
deviceName
}
},
[
"device_id"
]
);
const
deviceIds
=
(
deviceResult
.
data
as
any
[]).
map
((
d
:
any
)
=>
d
.
device_id
);
if
(
deviceIds
.
length
===
0
)
{
return
{
list
:
[],
pagination
:
{
page
:
pageNumber
,
pageSize
,
total
:
0
,
totalPages
:
0
},
};
}
queryParam
.
device_id
=
{
"%in%"
:
deviceIds
};
}
// 分页查询,按创建时间倒序
const
result
=
await
selectPaginatedDataWithOrder
(
TABLENAME
.
设备日志表
,
queryParam
,
[
"id"
,
"device_id"
,
"device_type"
,
"operation_content"
,
"status"
,
"created_at"
],
pageNumber
,
pageSize
,
[[
"created_at"
,
"DESC"
]],
);
// 补充设备名称映射
const
rows
=
result
.
data
as
any
[];
const
logDeviceIds
=
Array
.
from
(
new
Set
(
rows
.
map
((
item
:
any
)
=>
item
.
device_id
)));
let
deviceNameMap
:
Record
<
string
,
string
>
=
{};
if
(
logDeviceIds
.
length
>
0
)
{
const
devRes
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
{
device_id
:
{
"%in%"
:
logDeviceIds
}
},
[
"device_id"
,
"device_name"
]
);
for
(
const
d
of
(
devRes
.
data
as
any
[]))
{
deviceNameMap
[
d
.
device_id
]
=
d
.
device_name
||
''
;
}
}
// 映射展示字段
const
list
=
rows
.
map
((
item
:
any
)
=>
({
id
:
item
.
id
,
device_id
:
item
.
device_id
||
''
,
device_name
:
deviceNameMap
[
item
.
device_id
]
||
''
,
option
:
item
.
operation_content
?.
option
||
''
,
operation_content
:
item
.
operation_content
||
{},
status
:
item
.
status
,
statusName
:
item
.
status
===
1
?
'成功'
:
'失败'
,
created_at
:
item
.
created_at
||
''
,
}));
return
{
list
,
pagination
:
result
.
pagination
,
};
}
}
\ No newline at end of file
src/biz/feiyiClient.ts
View file @
456f47ee
This diff is collapsed.
Click to expand it.
src/biz/report.ts
View file @
456f47ee
...
@@ -219,13 +219,7 @@ function calcMeterDeviceDaily(records: any[]): number {
...
@@ -219,13 +219,7 @@ function calcMeterDeviceDaily(records: any[]): number {
* @param weekOffset 0=最近七天(默认), -1=再往前七天, ...
* @param weekOffset 0=最近七天(默认), -1=再往前七天, ...
*/
*/
export
async
function
getWeeklyReportData
(
weekOffset
:
number
=
0
):
Promise
<
any
>
{
export
async
function
getWeeklyReportData
(
weekOffset
:
number
=
0
):
Promise
<
any
>
{
// ==== 固定查询 7月29日 ~ 8月4日 ====
const
{
start
:
monday
,
end
:
sunday
,
timer
}
=
calcWeekRange
(
weekOffset
);
// const { start: monday, end: sunday, timer } = calcWeekRange(weekOffset);
const
start
=
new
Date
(
2026
,
6
,
29
,
0
,
0
,
0
,
0
);
// 7月29日 00:00
const
end
=
new
Date
(
2026
,
7
,
4
,
23
,
59
,
59
,
999
);
// 8月4日 23:59
const
monday
=
start
;
const
sunday
=
end
;
const
timer
=
`
${
formatChineseDate
(
monday
)}
—
${
formatChineseDate
(
sunday
)}
`
;
console
.
log
(
`[周报告JSON] 查询数据:
${
timer
}
`
);
console
.
log
(
`[周报告JSON] 查询数据:
${
timer
}
`
);
// ===== 第一步:并行查询区域 + 各馆设备分组 =====
// ===== 第一步:并行查询区域 + 各馆设备分组 =====
...
@@ -400,15 +394,12 @@ const ENV_INDICATOR_KEYS = [
...
@@ -400,15 +394,12 @@ const ENV_INDICATOR_KEYS = [
*/
*/
export
async
function
getEnvIndicatorsWeeklyAvg
():
Promise
<
any
>
{
export
async
function
getEnvIndicatorsWeeklyAvg
():
Promise
<
any
>
{
// 1. 计算七天时间范围
// 1. 计算七天时间范围
// ==== 固定查询 7月29日 ~ 8月4日 ====
const
now
=
new
Date
();
// const now = new Date();
const
end
=
new
Date
(
now
);
// const end = new Date(now);
end
.
setHours
(
23
,
59
,
59
,
999
);
// end.setHours(23, 59, 59, 999);
const
start
=
new
Date
(
now
);
// const start = new Date(now);
start
.
setDate
(
now
.
getDate
()
-
6
);
// start.setDate(now.getDate() - 6);
start
.
setHours
(
0
,
0
,
0
,
0
);
// start.setHours(0, 0, 0, 0);
const
start
=
new
Date
(
2026
,
6
,
29
,
0
,
0
,
0
,
0
);
// 7月29日 00:00
const
end
=
new
Date
(
2026
,
7
,
4
,
23
,
59
,
59
,
999
);
// 8月4日 23:59
const
from
=
formatLocalTime
(
start
);
const
from
=
formatLocalTime
(
start
);
const
to
=
formatLocalTime
(
end
);
const
to
=
formatLocalTime
(
end
);
...
@@ -495,15 +486,12 @@ const LEVEL_MAP: { [key: number]: string } = { 1: "一级", 2: "二级", 3: "三
...
@@ -495,15 +486,12 @@ const LEVEL_MAP: { [key: number]: string } = { 1: "一级", 2: "二级", 3: "三
* 每组展示:故障代码、风险内容、报警时间、处理状态、报警设备
* 每组展示:故障代码、风险内容、报警时间、处理状态、报警设备
*/
*/
export
async
function
getFaultInfoLast7Days
():
Promise
<
any
>
{
export
async
function
getFaultInfoLast7Days
():
Promise
<
any
>
{
// ==== 固定查询 7月29日 ~ 8月4日 ====
const
now
=
new
Date
();
// const now = new Date();
const
end
=
new
Date
(
now
);
// const end = new Date(now);
end
.
setHours
(
23
,
59
,
59
,
999
);
// end.setHours(23, 59, 59, 999);
const
start
=
new
Date
(
now
);
// const start = new Date(now);
start
.
setDate
(
now
.
getDate
()
-
6
);
// start.setDate(now.getDate() - 6);
start
.
setHours
(
0
,
0
,
0
,
0
);
// start.setHours(0, 0, 0, 0);
const
start
=
new
Date
(
2026
,
6
,
29
,
0
,
0
,
0
,
0
);
// 7月29日 00:00
const
end
=
new
Date
(
2026
,
7
,
4
,
23
,
59
,
59
,
999
);
// 8月4日 23:59
const
from
=
formatLocalTime
(
start
);
const
from
=
formatLocalTime
(
start
);
const
to
=
formatLocalTime
(
end
);
const
to
=
formatLocalTime
(
end
);
...
@@ -570,3 +558,218 @@ export async function getFaultInfoLast7Days(): Promise<any> {
...
@@ -570,3 +558,218 @@ export async function getFaultInfoLast7Days(): Promise<any> {
console
.
log
(
`[近7天故障] 共
${
totalCount
}
条,
${
groupMap
.
size
}
种故障码`
);
console
.
log
(
`[近7天故障] 共
${
totalCount
}
条,
${
groupMap
.
size
}
种故障码`
);
return
{
timer
,
totalCount
,
groupCount
:
Object
.
keys
(
groupMap
).
length
,
groups
};
return
{
timer
,
totalCount
,
groupCount
:
Object
.
keys
(
groupMap
).
length
,
groups
};
}
}
/**
* 温度与人流对比分析
* - 每小时最高温度和人流展示与对比(今天 vs 昨天)
* - 每天最高温度和人流展示与对比(近 N 天趋势)
*/
import
{
selectDataListBySql
}
from
"../data/findData"
;
/** 格式化日期为 YYYY-MM-DD */
function
formatDate
(
d
:
Date
):
string
{
const
y
=
d
.
getFullYear
();
const
m
=
String
(
d
.
getMonth
()
+
1
).
padStart
(
2
,
"0"
);
const
day
=
String
(
d
.
getDate
()).
padStart
(
2
,
"0"
);
return
`
${
y
}
-
${
m
}
-
${
day
}
`
;
}
/** 前一天的日期 */
function
yesterdayStr
(
dateStr
:
string
):
string
{
const
d
=
new
Date
(
dateStr
);
d
.
setDate
(
d
.
getDate
()
-
1
);
return
formatDate
(
d
);
}
// ==================== 每小时最高温度和人流对比 ====================
/**
* 查询指定日期的每小时最高温度
*/
async
function
getHourlyMaxTemp
(
dateStr
:
string
):
Promise
<
Record
<
number
,
number
>>
{
const
sql
=
`
SELECT
HOUR(dd.received_time) AS hour,
MAX(JSON_EXTRACT(dd.data, '$.temperature')) AS max_temp
FROM device_data dd
INNER JOIN device d ON dd.device_id = d.device_id
WHERE d.device_type = '环境监测'
AND DATE(dd.received_time) = :date
GROUP BY HOUR(dd.received_time)
ORDER BY HOUR(dd.received_time)
`
;
const
{
data
}
=
await
selectDataListBySql
(
sql
,
{
date
:
dateStr
});
const
map
:
Record
<
number
,
number
>
=
{};
for
(
const
row
of
(
data
as
any
[]))
{
map
[
row
.
hour
]
=
Number
(
row
.
max_temp
)
||
0
;
}
return
map
;
}
/**
* 查询指定日期的每小时客流总量
*/
async
function
getHourlyVisitorFlow
(
dateStr
:
string
):
Promise
<
Record
<
number
,
number
>>
{
const
sql
=
`
SELECT
HOUR(dd.received_time) AS hour,
SUM(JSON_EXTRACT(dd.data, '$.in')) AS total_in
FROM device_data dd
INNER JOIN device d ON dd.device_id = d.device_id
WHERE d.device_type = '客流传感器'
AND DATE(dd.received_time) = :date
GROUP BY HOUR(dd.received_time)
ORDER BY HOUR(dd.received_time)
`
;
const
{
data
}
=
await
selectDataListBySql
(
sql
,
{
date
:
dateStr
});
const
map
:
Record
<
number
,
number
>
=
{};
for
(
const
row
of
(
data
as
any
[]))
{
map
[
row
.
hour
]
=
Number
(
row
.
total_in
)
||
0
;
}
return
map
;
}
/**
* 每小时最高温度和人流展示与对比
*
* GET /api/qdm/admin/analysis/hourly?date=2026-08-11
*
* 返回:
* {
* date: "2026-08-11",
* compareDate: "2026-08-10",
* hours: [
* { hour: 0, maxTemp: 25.5, compareMaxTemp: 24.8, totalVisitors: 120, compareTotalVisitors: 100 },
* ...
* ]
* }
*/
export
async
function
getHourlyTempVisitorComparison
(
date
?:
string
)
{
const
targetDate
=
date
||
formatDate
(
new
Date
());
const
compareDate
=
yesterdayStr
(
targetDate
);
const
[
todayTemp
,
yesterdayTemp
,
todayVisitor
,
yesterdayVisitor
]
=
await
Promise
.
all
([
getHourlyMaxTemp
(
targetDate
),
getHourlyMaxTemp
(
compareDate
),
getHourlyVisitorFlow
(
targetDate
),
getHourlyVisitorFlow
(
compareDate
),
]);
const
hours
:
Array
<
{
hour
:
number
;
maxTemp
:
number
|
null
;
compareMaxTemp
:
number
|
null
;
totalVisitors
:
number
;
compareTotalVisitors
:
number
;
}
>
=
[];
for
(
let
h
=
0
;
h
<
24
;
h
++
)
{
hours
.
push
({
hour
:
h
,
maxTemp
:
todayTemp
[
h
]
??
null
,
compareMaxTemp
:
yesterdayTemp
[
h
]
??
null
,
totalVisitors
:
todayVisitor
[
h
]
??
0
,
compareTotalVisitors
:
yesterdayVisitor
[
h
]
??
0
,
});
}
return
{
date
:
targetDate
,
compareDate
,
hours
,
};
}
// ==================== 每天最高温度和人流对比 ====================
/**
* 查询近 N 天的每日最高温度
*/
async
function
getDailyMaxTemp
(
days
:
number
):
Promise
<
Record
<
string
,
number
>>
{
const
sql
=
`
SELECT
DATE(dd.received_time) AS date,
MAX(JSON_EXTRACT(dd.data, '$.temperature')) AS max_temp
FROM device_data dd
INNER JOIN device d ON dd.device_id = d.device_id
WHERE d.device_type = '环境监测'
AND dd.received_time >= DATE_SUB(CURDATE(), INTERVAL :days DAY)
GROUP BY DATE(dd.received_time)
ORDER BY DATE(dd.received_time)
`
;
const
{
data
}
=
await
selectDataListBySql
(
sql
,
{
days
});
const
map
:
Record
<
string
,
number
>
=
{};
for
(
const
row
of
(
data
as
any
[]))
{
map
[
row
.
date
]
=
Number
(
row
.
max_temp
)
||
0
;
}
return
map
;
}
/**
* 查询近 N 天的每日客流总量
*/
async
function
getDailyTotalVisitor
(
days
:
number
):
Promise
<
Record
<
string
,
number
>>
{
const
sql
=
`
SELECT
DATE(dd.received_time) AS date,
SUM(JSON_EXTRACT(dd.data, '$.in')) AS total_in
FROM device_data dd
INNER JOIN device d ON dd.device_id = d.device_id
WHERE d.device_type = '客流传感器'
AND dd.received_time >= DATE_SUB(CURDATE(), INTERVAL :days DAY)
GROUP BY DATE(dd.received_time)
ORDER BY DATE(dd.received_time)
`
;
const
{
data
}
=
await
selectDataListBySql
(
sql
,
{
days
});
const
map
:
Record
<
string
,
number
>
=
{};
for
(
const
row
of
(
data
as
any
[]))
{
map
[
row
.
date
]
=
Number
(
row
.
total_in
)
||
0
;
}
return
map
;
}
/**
* 生成近 N 天的日期列表
*/
function
generateDateRange
(
days
:
number
):
string
[]
{
const
dates
:
string
[]
=
[];
const
today
=
new
Date
();
for
(
let
i
=
days
-
1
;
i
>=
0
;
i
--
)
{
const
d
=
new
Date
(
today
);
d
.
setDate
(
d
.
getDate
()
-
i
);
dates
.
push
(
formatDate
(
d
));
}
return
dates
;
}
/**
* 每天最高温度和人流展示与对比
*
* GET /api/qdm/admin/analysis/daily?days=7
*
* 返回:
* {
* days: [
* { date: "2026-08-05", maxTemp: 27.2, totalVisitors: 3500 },
* { date: "2026-08-06", maxTemp: 26.8, totalVisitors: 4200 },
* ...
* ]
* }
*/
export
async
function
getDailyTempVisitorComparison
(
days
:
number
=
7
)
{
const
[
dailyMaxTemp
,
dailyTotalVisitor
]
=
await
Promise
.
all
([
getDailyMaxTemp
(
days
),
getDailyTotalVisitor
(
days
),
]);
const
dateRange
=
generateDateRange
(
days
);
const
dailyData
=
dateRange
.
map
((
date
)
=>
({
date
,
maxTemp
:
dailyMaxTemp
[
date
]
??
null
,
totalVisitors
:
dailyTotalVisitor
[
date
]
??
0
,
}));
return
{
days
:
dailyData
};
}
src/biz/running.ts
View file @
456f47ee
This diff is collapsed.
Click to expand it.
src/data/findData.ts
View file @
456f47ee
...
@@ -397,6 +397,20 @@ export async function selectSumValue( tableName: string, column: string, wherePa
...
@@ -397,6 +397,20 @@ export async function selectSumValue( tableName: string, column: string, wherePa
/**
/**
* 执行原生SQL查询
* @param sql SQL语句
* @param replacements 参数替换
*/
export
async
function
selectDataListBySql
(
sql
:
string
,
replacements
?:
any
)
{
const
{
mysqlDB
}
=
require
(
"../db/mysqlInit"
);
const
result
=
await
mysqlDB
.
query
(
sql
,
{
replacements
,
type
:
mysqlDB
.
QueryTypes
.
SELECT
,
});
return
{
data
:
result
};
}
/**
* 分页查询(支持自定义排序)
* 分页查询(支持自定义排序)
* @param tableName 表名
* @param tableName 表名
* @param param 查询参数
* @param param 查询参数
...
...
src/routers/admin.ts
View file @
456f47ee
...
@@ -18,6 +18,10 @@ export function setRouter(httpServer) {
...
@@ -18,6 +18,10 @@ export function setRouter(httpServer) {
httpServer
.
get
(
"/api/qdm/report/env/weekly/avg"
,
asyncHandler
(
getEnvWeeklyAvg
));
httpServer
.
get
(
"/api/qdm/report/env/weekly/avg"
,
asyncHandler
(
getEnvWeeklyAvg
));
/** 获取近 7 天故障信息(按 fault_code 分组) */
/** 获取近 7 天故障信息(按 fault_code 分组) */
httpServer
.
get
(
"/api/qdm/report/fault/weekly"
,
asyncHandler
(
getFaultInfo
));
httpServer
.
get
(
"/api/qdm/report/fault/weekly"
,
asyncHandler
(
getFaultInfo
));
/** 每小时最高温度和人流展示与对比 */
httpServer
.
get
(
"/api/qdm/admin/analysis/hourly"
,
asyncHandler
(
getHourlyAnalysis
));
/** 每天最高温度和人流展示与对比 */
httpServer
.
get
(
"/api/qdm/admin/analysis/daily"
,
asyncHandler
(
getDailyAnalysis
));
/** 区域设备树 */
/** 区域设备树 */
httpServer
.
get
(
"/api/qdm/admin/region/device"
,
asyncHandler
(
getRegionDevices
));
httpServer
.
get
(
"/api/qdm/admin/region/device"
,
asyncHandler
(
getRegionDevices
));
...
@@ -45,6 +49,8 @@ export function setRouter(httpServer) {
...
@@ -45,6 +49,8 @@ export function setRouter(httpServer) {
/** 预警工单列表 */
/** 预警工单列表 */
httpServer
.
get
(
"/api/qdm/admin/fault/list"
,
asyncHandler
(
getDeviceFaultList
));
httpServer
.
get
(
"/api/qdm/admin/fault/list"
,
asyncHandler
(
getDeviceFaultList
));
/** 日志管理 - 分页列表 */
httpServer
.
get
(
"/api/qdm/admin/device/logs"
,
asyncHandler
(
getDeviceLogList
));
}
}
/**
/**
...
@@ -245,7 +251,7 @@ async function editDevice(req, res) {
...
@@ -245,7 +251,7 @@ async function editDevice(req, res) {
* POST /api/qdm/admin/device/batch_edit
* POST /api/qdm/admin/device/batch_edit
* 设备管理 - 批量编辑设备
* 设备管理 - 批量编辑设备
* body: { regionKeys, deviceName?, power?, state?, deviceMode?, startDate?, endDate?, edits }
* body: { regionKeys, deviceName?, power?, state?, deviceMode?, startDate?, endDate?, edits }
* edits: { power?, setTemp?, l
inkageStart
? }
* edits: { power?, setTemp?, l
ocked
? }
*/
*/
async
function
batchEditDevices
(
req
,
res
)
{
async
function
batchEditDevices
(
req
,
res
)
{
let
reqConf
=
{
let
reqConf
=
{
...
@@ -270,8 +276,8 @@ async function batchEditDevices(req, res) {
...
@@ -270,8 +276,8 @@ async function batchEditDevices(req, res) {
res
.
error
(
"缺少必填参数:edits"
);
res
.
error
(
"缺少必填参数:edits"
);
return
;
return
;
}
}
if
(
edits
.
power
===
undefined
&&
edits
.
setTemp
===
undefined
&&
edits
.
l
inkageStart
===
undefined
)
{
if
(
edits
.
power
===
undefined
&&
edits
.
setTemp
===
undefined
&&
edits
.
l
ocked
===
undefined
)
{
res
.
error
(
"edits 中至少需要 power / setTemp / l
inkageStart
其中之一"
);
res
.
error
(
"edits 中至少需要 power / setTemp / l
ocked
其中之一"
);
return
;
return
;
}
}
// 校验 edits.power 值合法性
// 校验 edits.power 值合法性
...
@@ -284,9 +290,9 @@ async function batchEditDevices(req, res) {
...
@@ -284,9 +290,9 @@ async function batchEditDevices(req, res) {
res
.
error
(
"edits.setTemp 必须为 16~30 之间的数值"
);
res
.
error
(
"edits.setTemp 必须为 16~30 之间的数值"
);
return
;
return
;
}
}
// 校验 edits.l
inkageStart
值合法性
// 校验 edits.l
ocked
值合法性
if
(
edits
.
l
inkageStart
!==
undefined
&&
!
[
'on'
,
'off'
].
includes
(
edits
.
linkageStart
))
{
if
(
edits
.
l
ocked
!==
undefined
&&
!
[
'on'
,
'off'
].
includes
(
edits
.
locked
))
{
res
.
error
(
"edits.l
inkageStart
只能为 on 或 off"
);
res
.
error
(
"edits.l
ocked
只能为 on 或 off"
);
return
;
return
;
}
}
...
@@ -384,3 +390,41 @@ async function getDeviceFaultList(req, res) {
...
@@ -384,3 +390,41 @@ async function getDeviceFaultList(req, res) {
});
});
res
.
success
(
data
);
res
.
success
(
data
);
}
}
// ======================= 设备日志管理 =======================
/**
* GET /api/qdm/admin/device/logs
* 日志管理分页列表
* query: pageNumber, pageSize, deviceName(设备名称模糊匹配), status(0失败 1成功)
*/
async
function
getDeviceLogList
(
req
,
res
)
{
const
data
=
await
deviceBiz
.
getDeviceLogList
({
pageNumber
:
Number
(
req
.
query
?.
pageNumber
)
||
1
,
pageSize
:
Number
(
req
.
query
?.
pageSize
)
||
10
,
deviceName
:
req
.
query
?.
deviceName
||
undefined
,
status
:
req
.
query
?.
status
!==
undefined
&&
req
.
query
.
status
!==
''
?
Number
(
req
.
query
.
status
)
:
undefined
,
});
res
.
success
(
data
);
}
// ======================= 温度与人流对比分析 =======================
/**
* GET /api/qdm/admin/analysis/hourly?date=2026-08-11
* 每小时最高温度和人流展示与对比(今天 vs 昨天)
*/
async
function
getHourlyAnalysis
(
req
,
res
)
{
const
data
=
await
reportBiz
.
getHourlyTempVisitorComparison
(
req
.
query
?.
date
);
res
.
success
(
data
);
}
/**
* GET /api/qdm/admin/analysis/daily?days=7
* 每天最高温度和人流展示与对比(近 N 天趋势)
*/
async
function
getDailyAnalysis
(
req
,
res
)
{
const
days
=
Number
(
req
.
query
?.
days
)
||
7
;
const
data
=
await
reportBiz
.
getDailyTempVisitorComparison
(
days
);
res
.
success
(
data
);
}
src/routers/device.ts
View file @
456f47ee
...
@@ -144,7 +144,7 @@ async function getRegionList(req, res) {
...
@@ -144,7 +144,7 @@ async function getRegionList(req, res) {
*/
*/
async
function
controlAcRunning
(
req
,
res
)
{
async
function
controlAcRunning
(
req
,
res
)
{
let
reqConf
=
{
deviceId
:
'String'
,
mode
:
'String'
,
power
:
'String'
,
setTemp
:
'String'
,
maxTemp
:
'String'
,
minTemp
:
'String'
,
autoControl
:
'String'
,
fanSpeed
:
'String'
};
let
reqConf
=
{
deviceId
:
'String'
,
mode
:
'String'
,
power
:
'String'
,
setTemp
:
'String'
,
maxTemp
:
'String'
,
minTemp
:
'String'
,
autoControl
:
'String'
,
fanSpeed
:
'String'
};
const
NotMustHaveKeys
=
[
'maxTemp'
,
'minTemp'
,
'autoControl'
,
'fanSpeed'
];
const
NotMustHaveKeys
=
[
'
power'
,
'mode'
,
'setTemp'
,
'
maxTemp'
,
'minTemp'
,
'autoControl'
,
'fanSpeed'
];
let
{
deviceId
,
mode
,
power
,
setTemp
,
maxTemp
,
minTemp
,
autoControl
,
fanSpeed
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
let
{
deviceId
,
mode
,
power
,
setTemp
,
maxTemp
,
minTemp
,
autoControl
,
fanSpeed
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
let
params
=
{
deviceId
,
mode
,
power
,
setTemp
,
maxTemp
,
minTemp
,
autoControl
,
fanSpeed
};
let
params
=
{
deviceId
,
mode
,
power
,
setTemp
,
maxTemp
,
minTemp
,
autoControl
,
fanSpeed
};
const
result
=
await
runningBiz
.
controlAcRunning
(
params
);
const
result
=
await
runningBiz
.
controlAcRunning
(
params
);
...
...
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