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
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
646 additions
and
235 deletions
+646
-235
device.ts
src/biz/device.ts
+103
-26
feiyiClient.ts
src/biz/feiyiClient.ts
+143
-122
report.ts
src/biz/report.ts
+228
-25
running.ts
src/biz/running.ts
+107
-55
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
{
getMySqlMs
}
from
"../tools/systemTools"
;
import
{
BizError
}
from
"../util/bizError"
;
...
...
@@ -791,7 +791,7 @@ export async function getDeviceList(params: {
}
// 1. 构建查询参数,只查空调设备,排除 region_key=0 的设备
let
paramAnys
:
any
=
{
device_type
:
'空调'
};
let
paramAnys
:
any
=
{
device_type
:
[
'空调'
,
'新风'
]
};
if
(
regionKeys
.
length
)
{
paramAnys
.
region_key
=
{
"%in%"
:
regionKeys
};
}
else
{
...
...
@@ -1041,7 +1041,7 @@ export async function batchEditDevices(params: {
edits
:
{
power
?:
string
;
setTemp
?:
number
;
l
inkageStart
?:
string
;
l
ocked
?:
string
;
};
}):
Promise
<
{
total
:
number
;
successCount
:
number
;
failCount
:
number
;
details
:
any
[]
}
>
{
const
{
regionKeys
,
deviceName
,
power
,
state
,
deviceMode
,
startDate
,
endDate
,
edits
}
=
params
;
...
...
@@ -1053,9 +1053,9 @@ export async function batchEditDevices(params: {
if
(
!
edits
||
typeof
edits
!==
'object'
)
{
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
)
{
throw
new
BizError
(
ERRORENUM
.
参数错误
,
"edits 中至少需要 power / setTemp / l
inkageStart
其中之一"
);
throw
new
BizError
(
ERRORENUM
.
参数错误
,
"edits 中至少需要 power / setTemp / l
ocked
其中之一"
);
}
// 1. 构建设备查询条件
...
...
@@ -1124,20 +1124,8 @@ export async function batchEditDevices(params: {
let
successCount
=
0
;
let
failCount
=
0
;
// 4. 处理 linkageStart 编辑(纯数据库更新,一次性批量更新)
if
(
edits
.
linkageStart
!==
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
;
// 4. 处理 power / setTemp / locked 编辑(逐台调用飞奕接口)
const
needAcControl
=
edits
.
power
!==
undefined
||
edits
.
setTemp
!==
undefined
||
edits
.
locked
!==
undefined
;
if
(
needAcControl
)
{
const
now
=
new
Date
();
...
...
@@ -1154,6 +1142,12 @@ export async function batchEditDevices(params: {
if
(
edits
.
setTemp
!==
undefined
)
{
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
);
...
...
@@ -1167,7 +1161,7 @@ export async function batchEditDevices(params: {
time
:
formatLocalTime
(
now
),
power
:
edits
.
power
,
setTemp
:
edits
.
setTemp
,
l
inkageStart
:
edits
.
linkageStart
,
l
ocked
:
edits
.
locked
,
success
:
result
.
success
,
message
:
result
.
data
||
(
result
.
success
?
'控制成功'
:
'控制失败'
),
},
...
...
@@ -1190,12 +1184,6 @@ export async function batchEditDevices(params: {
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
};
...
...
@@ -1254,6 +1242,7 @@ export async function batchEditLocalDevices(params: {
deviceType
?:
string
;
controlParams
?:
string
;
deviceState
?:
string
;
linkageStart
?:
string
;
};
})
{
const
{
regionKeys
,
deviceName
,
edits
}
=
params
;
...
...
@@ -1277,6 +1266,7 @@ export async function batchEditLocalDevices(params: {
if
(
edits
.
deviceType
!==
undefined
)
dbEdits
.
device_type
=
edits
.
deviceType
;
if
(
edits
.
controlParams
!==
undefined
)
dbEdits
.
control_params
=
edits
.
controlParams
;
if
(
edits
.
deviceState
!==
undefined
)
dbEdits
.
device_state
=
edits
.
deviceState
;
if
(
edits
.
linkageStart
!==
undefined
)
dbEdits
.
linkage_start
=
edits
.
linkageStart
;
if
(
Object
.
keys
(
dbEdits
).
length
===
0
)
{
throw
new
BizError
(
ERRORENUM
.
参数错误
,
"edits 中至少需要一个编辑字段"
);
...
...
@@ -1286,4 +1276,90 @@ export async function batchEditLocalDevices(params: {
console
.
log
(
`[batchEditLocalDevices] 批量更新,条件:`
,
where
,
"更新内容:"
,
dbEdits
);
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
...
...
@@ -72,7 +72,7 @@ function generateSign(params: Record<string, any>, timestamp: string, nonce: str
*/
export
async
function
getFeiYiToken
():
Promise
<
string
>
{
const
url
=
`
${
FEIYI_CONFIG
.
baseUrl
}
/oauth2/openapi/token`
;
// 校验token是否有效,有效则直接返回
const
now
=
Date
.
now
();
if
(
feiYiToken
&&
expireTime
>
now
)
{
...
...
@@ -259,34 +259,34 @@ export async function getGatewayDeviceList(page: number = 1, limit: number = 10)
* @param params
* @returns
{
"code": "00000", //状态码 00000 为正常 其他为异常
"message": "success", //返回状态 success 为成功, 其他为失败
"data": { // 返回数据
"page": 1, //当前页码
"limit": 0, //每页条数
"total": 579, //总数
"rows": [{
"indoorUnitId": "98566729709518848", //内机 ID
"roomId": "98452624688414720", //房间 ID
"floorId": "98452624680026112", //楼层 ID
"buildingId": "34390184208629760", //楼栋 ID
"indoorUnitAddressFull": "11-1-1-1", //内机四段地址
"onOffLock": 1, //是否开关锁定 1 锁定 0 未锁定
"onOff": 0, //开关状态 1 开 0 关
"workModeLock": 0, //是否工作模式锁定 1 锁定 0 未锁定
"workMode": 2, //工作模式 1 制冷 2 制热 3 送风 4 除湿
"tempSetLock": 0, //是否温度锁定 1 锁定 0 未锁定
"tempSet": 16.0, // 设定温度
"tempSetLo": 16.0, // 锁定下限温度
"tempSetHi": 32.0, // 锁定下限温度
"fanSpeed": 4, // 风速 1 高风 2 中风 4 低风
"hasTimingTasks": 0, //是否有定时任务
"alarmCode": "LOST", //空调故障码
"roomTemp": 27 //室内温度(实际为空调回风温度,有一定的误差仅做参考)
"lockedSwitch": 0, // 锁定的开关:0 关 1 开
"lockedMode": 0, // 锁定模式 1 制冷 2 制热 3 送风 4 除湿 10 制热/送风 11 制冷/送风 12 制冷/除湿 13 制冷/除湿/送风 14 除湿/送风
}]
}
"code": "00000", //状态码 00000 为正常 其他为异常
"message": "success", //返回状态 success 为成功, 其他为失败
"data": { // 返回数据
"page": 1, //当前页码
"limit": 0, //每页条数
"total": 579, //总数
"rows": [{
"indoorUnitId": "98566729709518848", //内机 ID
"roomId": "98452624688414720", //房间 ID
"floorId": "98452624680026112", //楼层 ID
"buildingId": "34390184208629760", //楼栋 ID
"indoorUnitAddressFull": "11-1-1-1", //内机四段地址
"onOffLock": 1, //是否开关锁定 1 锁定 0 未锁定
"onOff": 0, //开关状态 1 开 0 关
"workModeLock": 0, //是否工作模式锁定 1 锁定 0 未锁定
"workMode": 2, //工作模式 1 制冷 2 制热 3 送风 4 除湿
"tempSetLock": 0, //是否温度锁定 1 锁定 0 未锁定
"tempSet": 16.0, // 设定温度
"tempSetLo": 16.0, // 锁定下限温度
"tempSetHi": 32.0, // 锁定下限温度
"fanSpeed": 4, // 风速 1 高风 2 中风 4 低风
"hasTimingTasks": 0, //是否有定时任务
"alarmCode": "LOST", //空调故障码
"roomTemp": 27 //室内温度(实际为空调回风温度,有一定的误差仅做参考)
"lockedSwitch": 0, // 锁定的开关:0 关 1 开
"lockedMode": 0, // 锁定模式 1 制冷 2 制热 3 送风 4 除湿 10 制热/送风 11 制冷/送风 12 制冷/除湿 13 制冷/除湿/送风 14 除湿/送风
}]
}
}
*/
export
async
function
getIndoorUnitList
(
params
:
any
):
Promise
<
any
>
{
...
...
@@ -297,6 +297,27 @@ export async function getIndoorUnitList(params: any): Promise<any> {
}
/**
* 单条查询内机
*
* @param deviceId
* @returns
*/
export
async
function
getIndoorUnitStatus
(
deviceId
:
string
):
Promise
<
any
>
{
const
path
=
'/openApi/indoorUnit/list'
;
const
body
=
{
indoorUnitAddressFull
:
deviceId
};
const
indoorUnits
=
await
feiYiPost
(
path
,
body
);
let
indoorUnit
=
indoorUnits
.
data
?.
rows
?.[
0
]
||
null
;
let
result
:
any
=
{};
result
.
mode
=
indoorUnit
.
workMode
;
result
.
power
=
indoorUnit
.
onOff
;
result
.
fanSpeed
=
indoorUnit
.
fanSpeed
;
result
.
setTemp
=
indoorUnit
.
tempSet
;
result
.
minTemp
=
indoorUnit
.
tempSetLo
;
result
.
maxTemp
=
indoorUnit
.
tempSetHi
;
return
result
;
}
/**
* 空调内机控制
* @param params
* @returns
...
...
@@ -310,7 +331,7 @@ export async function controlIndoorUnit(params: any): Promise<any> {
const
body
=
{
...
params
};
const
result
=
await
feiYiPost
(
path
,
body
);
// {code:"", message:""}
console
.
log
(
'控制内机响应:'
,
result
?.
code
);
let
success
=
result
&&
result
.
code
===
'00000'
?
true
:
false
;
let
success
=
result
&&
result
.
code
===
'00000'
?
true
:
false
;
return
{
success
,
data
:
result
.
message
};
}
...
...
@@ -319,35 +340,35 @@ export async function controlIndoorUnit(params: any): Promise<any> {
* @param params
* @returns
{
"code": "00000", //状态码 00000 为正常,其他为异常
"message": "success", //状态信息 success 为成功,其他为异常
"data": [{
"buildingId": "97474765970866176", //建筑物 ID
"parentId": "0", //建筑物父 ID
"buildingName": "飞奕 1", // 建筑物名称
"buildingType": 1, //建筑物类型 1 楼栋 2 楼层 3 房间
"children": [ //子建筑物
{
"buildingId": "97474766339964928",
"parentId": "97474765970866176",
"buildingName": "1 层",
"buildingType": 2,
"children": [{
"buildingId": "97474766696480768",
"parentId": "97474766339964928",
"buildingName": "1-0",
"buildingType": 3,
"children": []
}]
}
]
}],
"timestamp": 1714290880301
"code": "00000", //状态码 00000 为正常,其他为异常
"message": "success", //状态信息 success 为成功,其他为异常
"data": [{
"buildingId": "97474765970866176", //建筑物 ID
"parentId": "0", //建筑物父 ID
"buildingName": "飞奕 1", // 建筑物名称
"buildingType": 1, //建筑物类型 1 楼栋 2 楼层 3 房间
"children": [ //子建筑物
{
"buildingId": "97474766339964928",
"parentId": "97474765970866176",
"buildingName": "1 层",
"buildingType": 2,
"children": [{
"buildingId": "97474766696480768",
"parentId": "97474766339964928",
"buildingName": "1-0",
"buildingType": 3,
"children": []
}]
}
]
}],
"timestamp": 1714290880301
}
*/
export
async
function
getRegionTree
():
Promise
<
any
>
{
const
path
=
'/openApi/building/tree'
;
const
body
=
{
};
const
body
=
{};
const
result
=
await
feiYiPost
(
path
,
body
);
return
result
.
data
;
}
...
...
@@ -357,29 +378,29 @@ export async function getRegionTree(): Promise<any> {
* @param params
* @returns
{
"code": "00000", //状态码 00000 为成功,其他为失败
"message": "success", //提示信息 success 为成功 其他为失败
"data": {
"page": 0, //当前页码
"limit": 0, //每页条数
"total": 6, //总条数
"rows": [{
"meterId": "110510431701041152", //电表 ID
"gatewayComId": , //电表网关设备通信 ID
"gatewayCode": , //电表网关设备编码
"protocolType": "DLT645-2007", //电表协议类型
"ammeterReadingAfterTransformationRatio": 6547.68, //电表变比后读数
"transformationRatio": 80.0, //电表变比
"meterComId": "210505015447" //电表地址
}, {
"gatewayComId": , //电表网关设备通信 ID
"gatewayCode": , //电表网关设备编码
"meterId": "110510346153885697",
"transformationRatio": 50.0,
"meterComId": "210505015560"
}]
},
"timestamp": 1713245734235
"code": "00000", //状态码 00000 为成功,其他为失败
"message": "success", //提示信息 success 为成功 其他为失败
"data": {
"page": 0, //当前页码
"limit": 0, //每页条数
"total": 6, //总条数
"rows": [{
"meterId": "110510431701041152", //电表 ID
"gatewayComId": , //电表网关设备通信 ID
"gatewayCode": , //电表网关设备编码
"protocolType": "DLT645-2007", //电表协议类型
"ammeterReadingAfterTransformationRatio": 6547.68, //电表变比后读数
"transformationRatio": 80.0, //电表变比
"meterComId": "210505015447" //电表地址
}, {
"gatewayComId": , //电表网关设备通信 ID
"gatewayCode": , //电表网关设备编码
"meterId": "110510346153885697",
"transformationRatio": 50.0,
"meterComId": "210505015560"
}]
},
"timestamp": 1713245734235
}
*/
export
async
function
getElectricMaterList
(
params
:
any
):
Promise
<
any
>
{
...
...
@@ -394,27 +415,27 @@ export async function getElectricMaterList(params: any): Promise<any> {
* @param params
* @returns
{
"code": "00000", //状态码 00000 表示成功,其他为失败
"message": "success", //提示信息 success 为成功,其他为失败
"data": {
"page": 1, //当前页码
"limit": 0, //每页条数
"total": 6, //总条数
"rows": [{
"electricMeterStateId": "112111594590437376", //抄表记录 ID
"gatewayId": "97477557167063040", //主机设备 ID
"gatewayCode": "fa000001400001240231212000200026" //主机设备编码
"meterComId": "210505015595", //电表地址
"gatewayComId": 11, // 电表网关通信 ID
"transformationRatio": 100.0, //电表变比
"powerConsumptionAfterTransformationRatio": 0.08, //电表变比后用电量
"ammeterReadingAfterTransformationRatio": 19167.4, // 电表变比后读数
"ammeterReadingBeforeTransformationRatio": 191.67, //电表变比前读数
"energyLoss": 0.00, //损耗电量
"deviceTime": 1706371200000, //设备时间
}]
},
"timestamp": 1713145563759
"code": "00000", //状态码 00000 表示成功,其他为失败
"message": "success", //提示信息 success 为成功,其他为失败
"data": {
"page": 1, //当前页码
"limit": 0, //每页条数
"total": 6, //总条数
"rows": [{
"electricMeterStateId": "112111594590437376", //抄表记录 ID
"gatewayId": "97477557167063040", //主机设备 ID
"gatewayCode": "fa000001400001240231212000200026" //主机设备编码
"meterComId": "210505015595", //电表地址
"gatewayComId": 11, // 电表网关通信 ID
"transformationRatio": 100.0, //电表变比
"powerConsumptionAfterTransformationRatio": 0.08, //电表变比后用电量
"ammeterReadingAfterTransformationRatio": 19167.4, // 电表变比后读数
"ammeterReadingBeforeTransformationRatio": 191.67, //电表变比前读数
"energyLoss": 0.00, //损耗电量
"deviceTime": 1706371200000, //设备时间
}]
},
"timestamp": 1713145563759
}
*/
export
async
function
getMaterStateList
(
params
:
any
):
Promise
<
any
>
{
...
...
@@ -429,29 +450,29 @@ export async function getMaterStateList(params: any): Promise<any> {
* @param params
* @returns
{
"code": "00000",
"message": "success",
"path": null,
"data": {
"page": 1,
"limit": 0,
"total": 278,
"rows": [{
"indoorUnitEventId": "140718238987321344", //内机事件 ID
"indoorUnitId": "97478680942739586", //内机 ID
"gatewayComId": 1, //主机通信 ID
"roomId": "97474774841819136", //内机所在房间 ID
"floorId": "97474774485303296", //内机所在楼层 ID
"buildingId": "97474765970866176", //内机所在楼栋 ID
"indoorUnitAddressFull": "1-8-128-16", //内机四段地址
"onOff": 0, //内机开关标志 0 关 1 开
"workMode": 4, //工作模式 1 制冷 2 制热 3 送风 4 除湿
"tempSet": 18.0, //内机温度设置
"fanSpeed": 2, //内机风速 风速 1 高风 2 中风 4 低风
"createTime": 1713173801018 //事件发生时间
}]
},
"timestamp": 1713173831195
"code": "00000",
"message": "success",
"path": null,
"data": {
"page": 1,
"limit": 0,
"total": 278,
"rows": [{
"indoorUnitEventId": "140718238987321344", //内机事件 ID
"indoorUnitId": "97478680942739586", //内机 ID
"gatewayComId": 1, //主机通信 ID
"roomId": "97474774841819136", //内机所在房间 ID
"floorId": "97474774485303296", //内机所在楼层 ID
"buildingId": "97474765970866176", //内机所在楼栋 ID
"indoorUnitAddressFull": "1-8-128-16", //内机四段地址
"onOff": 0, //内机开关标志 0 关 1 开
"workMode": 4, //工作模式 1 制冷 2 制热 3 送风 4 除湿
"tempSet": 18.0, //内机温度设置
"fanSpeed": 2, //内机风速 风速 1 高风 2 中风 4 低风
"createTime": 1713173801018 //事件发生时间
}]
},
"timestamp": 1713173831195
}
*/
export
async
function
getIndoorUnitStateList
(
params
:
any
):
Promise
<
any
>
{
...
...
src/biz/report.ts
View file @
456f47ee
...
...
@@ -219,13 +219,7 @@ function calcMeterDeviceDaily(records: any[]): number {
* @param weekOffset 0=最近七天(默认), -1=再往前七天, ...
*/
export
async
function
getWeeklyReportData
(
weekOffset
:
number
=
0
):
Promise
<
any
>
{
// ==== 固定查询 7月29日 ~ 8月4日 ====
// 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
)}
`
;
const
{
start
:
monday
,
end
:
sunday
,
timer
}
=
calcWeekRange
(
weekOffset
);
console
.
log
(
`[周报告JSON] 查询数据:
${
timer
}
`
);
// ===== 第一步:并行查询区域 + 各馆设备分组 =====
...
...
@@ -400,15 +394,12 @@ const ENV_INDICATOR_KEYS = [
*/
export
async
function
getEnvIndicatorsWeeklyAvg
():
Promise
<
any
>
{
// 1. 计算七天时间范围
// ==== 固定查询 7月29日 ~ 8月4日 ====
// const now = new Date();
// const end = new Date(now);
// end.setHours(23, 59, 59, 999);
// const start = new Date(now);
// start.setDate(now.getDate() - 6);
// 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
now
=
new
Date
();
const
end
=
new
Date
(
now
);
end
.
setHours
(
23
,
59
,
59
,
999
);
const
start
=
new
Date
(
now
);
start
.
setDate
(
now
.
getDate
()
-
6
);
start
.
setHours
(
0
,
0
,
0
,
0
);
const
from
=
formatLocalTime
(
start
);
const
to
=
formatLocalTime
(
end
);
...
...
@@ -495,15 +486,12 @@ const LEVEL_MAP: { [key: number]: string } = { 1: "一级", 2: "二级", 3: "三
* 每组展示:故障代码、风险内容、报警时间、处理状态、报警设备
*/
export
async
function
getFaultInfoLast7Days
():
Promise
<
any
>
{
// ==== 固定查询 7月29日 ~ 8月4日 ====
// const now = new Date();
// const end = new Date(now);
// end.setHours(23, 59, 59, 999);
// const start = new Date(now);
// start.setDate(now.getDate() - 6);
// 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
now
=
new
Date
();
const
end
=
new
Date
(
now
);
end
.
setHours
(
23
,
59
,
59
,
999
);
const
start
=
new
Date
(
now
);
start
.
setDate
(
now
.
getDate
()
-
6
);
start
.
setHours
(
0
,
0
,
0
,
0
);
const
from
=
formatLocalTime
(
start
);
const
to
=
formatLocalTime
(
end
);
...
...
@@ -570,3 +558,218 @@ export async function getFaultInfoLast7Days(): Promise<any> {
console
.
log
(
`[近7天故障] 共
${
totalCount
}
条,
${
groupMap
.
size
}
种故障码`
);
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
...
...
@@ -4,7 +4,7 @@ import { ERRORENUM } from "../config/errorEnum";
import
{
selectOneDataByParam
,
selectDataListByParam
}
from
"../data/findData"
;
import
{
addData
}
from
"../data/addData"
;
import
{
BizError
}
from
"../util/bizError"
;
import
{
controlIndoorUnit
}
from
"./feiyiClient"
;
import
{
controlIndoorUnit
,
getIndoorUnitStatus
}
from
"./feiyiClient"
;
import
{
getRegionList
}
from
"./region"
;
import
{
startSchedule
,
stopSchedule
}
from
"../config/schedule"
;
...
...
@@ -299,7 +299,7 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
}
}
}
if
(
latestAir
.
data
.
length
)
{
if
(
latestAir
&&
latestAir
.
data
.
length
)
{
let
airData
=
latestAir
.
data
[
0
].
data
;
currentTemperature
=
`
${
airData
.
temperature
??
0
}
℃
`;
currentHumidity = `
$
{
airData
.
humidity
??
0
}
%
`;
...
...
@@ -340,33 +340,23 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
for (let i = 0; i < 24; i++) {
let key = `
$
{
i
}
时
`;
let val = hourlyData.get(key);
let avg = (sumKey: string) => val?.cnt ? ((val[sumKey] || 0) / val.cnt).toFixed(1) : '0';
if (i <= currentHour) {
temperatureTrend.push({ time: `
$
{
i
}:
00
`, value: val?.cnt ? avg('tempSum') : '0' });
humidityTrend.push({ time: `
$
{
i
}:
00
`, value: val?.cnt ? avg('humSum') : '0' });
pm25Trend.push({ time: `
$
{
i
}:
00
`, value: val?.cnt ? avg('pmSum') : '0' });
let co2Val = val?.cnt ? ((val['co2Sum'] || 0) / val.cnt) : 0;
co2TrendDetail.push({ time: `
$
{
i
}:
00
`, value: (co2Val / 100).toFixed(1) });
co2Trend.push({ time: `
$
{
i
}
时`
,
value
:
co2Val
.
toFixed
(
0
)
});
hchoTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
val
?.
cnt
?
avg
(
'hchoSum'
)
:
'0'
});
lightLevelTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
val
?.
cnt
?
avg
(
'lightLevelSum'
)
:
'0'
});
pm10Trend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
val
?.
cnt
?
avg
(
'pm10Sum'
)
:
'0'
});
pressureTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
val
?.
cnt
?
avg
(
'pressureSum'
)
:
'0'
});
tvocTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
val
?.
cnt
?
avg
(
'tvocSum'
)
:
'0'
});
}
else
{
temperatureTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
'0'
});
humidityTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
'0'
});
pm25Trend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
'0'
});
co2TrendDetail
.
push
({
time
:
`
${
i
}
:00`
,
value
:
'0'
});
co2Trend
.
push
({
time
:
`
${
i
}
时`
,
value
:
'0'
});
hchoTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
'0'
});
lightLevelTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
'0'
});
pm10Trend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
'0'
});
pressureTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
'0'
});
tvocTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
'0'
});
// 不补零:仅输出实际有数据的小时,无数据/未来时段直接跳过
if (i > currentHour || !val?.cnt) {
continue;
}
let avg = (sumKey: string) => ((val[sumKey] || 0) / val.cnt).toFixed(1);
temperatureTrend.push({ time: `
$
{
i
}:
00
`, value: avg('tempSum') });
humidityTrend.push({ time: `
$
{
i
}:
00
`, value: avg('humSum') });
pm25Trend.push({ time: `
$
{
i
}:
00
`, value: avg('pmSum') });
let co2Val = (val['co2Sum'] || 0) / val.cnt;
co2TrendDetail.push({ time: `
$
{
i
}:
00
`, value: (co2Val / 100).toFixed(1) });
co2Trend.push({ time: `
$
{
i
}
时`
,
value
:
co2Val
.
toFixed
(
0
)
});
hchoTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
avg
(
'hchoSum'
)
});
lightLevelTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
avg
(
'lightLevelSum'
)
});
pm10Trend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
avg
(
'pm10Sum'
)
});
pressureTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
avg
(
'pressureSum'
)
});
tvocTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
avg
(
'tvocSum'
)
});
}
}
...
...
@@ -702,12 +692,11 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
}
let
currentHour
=
nowTime
.
getHours
();
for
(
let
i
=
0
;
i
<
24
;
i
++
)
{
if
(
i
<=
currentHour
)
{
let
key
=
`
${
i
}
时`
;
let
val
=
hourlyData
.
get
(
key
);
acOnlineTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
val
?
((
val
.
online
/
val
.
total
)
*
100
).
toFixed
(
2
)
:
'0'
});
}
else
{
acOnlineTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
'0'
});
// 不补零:仅输出实际有数据的小时
let
key
=
`
${
i
}
时`
;
let
val
=
hourlyData
.
get
(
key
);
if
(
i
<=
currentHour
&&
val
)
{
acOnlineTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
((
val
.
online
/
val
.
total
)
*
100
).
toFixed
(
2
)
});
}
}
}
...
...
@@ -751,9 +740,12 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
}
}
for
(
let
i
=
0
;
i
<
24
;
i
++
)
{
// 不补零:仅输出实际有数据的小时
let
key
=
`
${
i
}
时`
;
let
val
=
hourlyData
.
get
(
key
);
fanOnlineTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
val
?
val
.
airSupply
:
'0'
});
if
(
val
)
{
fanOnlineTrend
.
push
({
time
:
`
${
i
}
:00`
,
value
:
val
.
airSupply
});
}
}
}
return
{
fanDeviceIds
,
fanCount
,
fanOnlineCount
,
fanOfflineCount
,
fanFaultCount
,
fanOnlineTrend
};
...
...
@@ -888,41 +880,95 @@ export async function getRunAnalysis(regionGroup: string, regionType: string) {
* 智能管控-空调设备运行弹窗
* @param regionKey
* @param deviceId
* {
* "deviceInfo": {
* "mode": [
* "制冷",
* "制热",
* "送风",
* "除湿"
* ],
* "power": [
* "on",
* "off"
* ],
* "maxTemp": "20~40",
* "minTemp": "-10~20",
* "setTemp": "16~30",
* "fanSpeed": [
* "自动",
* "弱",
* "强",
* "超强"
* ],
* "roomTemp": "-20~50",
* "autoControl": [
* "生效",
* "解除"
* ]
* },
* "latestData": {
* "mode": "制冷",
* "power": "on",
* "maxTemp": 28,
* "minTemp": 16,
* "setTemp": 19,
* "fanSpeed": 1,
* "roomTemp": 0,
* "autoControl": "生效"
* }
* }
*/
export
async
function
getAcRunningPop
(
regionKey
:
number
,
deviceId
:
string
)
{
// 获取设备信息
let
devices
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
{
device_id
:
deviceId
},
// region_key: regionKey,
[
"device_id"
,
"device_type"
,
"device_name"
,
"control_params"
]
[
"device_id"
,
"device_type"
,
"device_
state"
,
"device_
name"
,
"control_params"
]
);
if
(
!
devices
.
data
.
length
)
{
throw
new
BizError
(
ERRORENUM
.
未找到数据
,
`设备
${
deviceId
}
在区域
${
regionKey
}
中未找到`
);
}
// 获取设备最新数据
let
deviceData
=
await
selectDataListByParam
(
TABLENAME
.
设备数据表
,
{
device_id
:
deviceId
,
"%orderDesc%"
:
"device_time"
,
"%limit%"
:
1
},
[
"data"
,
"device_time"
]
);
let
detailData
=
deviceData
.
data
.
length
?
deviceData
.
data
[
0
].
data
:
null
;
detailData
.
fanSpeed
=
detailData
&&
detailData
.
fanSpeed
?
acDeviceKeyMap
[
detailData
.
fanSpeed
]
:
2
;
return
{
deviceInfo
:
devices
.
data
[
0
].
control_params
,
latestData
:
detailData
let
deviceType
=
devices
?.
data
[
0
].
device_type
;
if
(
deviceType
!==
"环境监测"
)
{
// 通过飞奕内机列表获取实时设备数据
let
detailData
=
await
getIndoorUnitStatus
(
deviceId
);
detailData
.
fanSpeed
=
detailData
&&
detailData
.
fanSpeed
?
acDeviceKeyMap
[
detailData
.
fanSpeed
]
:
2
;
detailData
.
autoControl
=
devices
&&
devices
.
data
[
0
].
linkage_start
&&
devices
.
data
[
0
].
linkage_start
===
"on"
?
'生效'
:
'解除'
;
return
{
deviceFault
:
{
deviceState
:
devices
.
data
[
0
].
deviceState
,
// 0 正常 1 硬件故障 2 通信故障 3 离线
faultContent
:
devices
.
data
[
0
].
deviceState
===
1
?
'硬件故障'
:
devices
.
data
[
0
].
deviceState
===
2
?
'通信故障'
:
''
,
},
deviceInfo
:
devices
.
data
[
0
].
control_params
,
latestData
:
detailData
}
}
else
{
// 获取设备最新数据
let
deviceData
=
await
selectDataListByParam
(
TABLENAME
.
设备数据表
,
{
device_id
:
deviceId
,
"%orderDesc%"
:
"device_time"
,
"%limit%"
:
1
},
[
"data"
,
"device_time"
]
);
let
detailData
=
deviceData
.
data
.
length
?
deviceData
.
data
[
0
].
data
:
null
;
return
{
latestData
:
detailData
}
}
}
/**
* 设备运行弹框 - 空调启停
* @param
deviceId
* @param
{autoControl: "生效",deviceId: "2-3-0-2",fanSpeed: "3",maxTemp: "28",minTemp: "16",mode: "制冷",power: "on",setTemp: "22"}
*/
export
async
function
controlAcRunning
(
params
:
{})
{
let
deviceId
=
params
[
"deviceId"
];
let
mode
=
params
[
"mode"
];
let
power
=
params
[
"power"
];
let
mode
=
params
[
"mode"
]
||
"制冷"
;
let
power
=
params
[
"power"
]
||
'on'
;
let
setTemp
=
params
[
"setTemp"
];
let
maxTemp
=
params
[
"maxTemp"
]
||
28
;
// 默认锁定温度上限
let
minTemp
=
params
[
"minTemp"
]
||
16
;
// 默认锁定温度下限
...
...
@@ -941,11 +987,10 @@ export async function controlAcRunning(params: {}) {
// 2. 接入空调控制API,更新空调设备信息
let
acPrarms
=
{
"indoorUnitAddressFull"
:
[
deviceId
],
"workMode"
:
mode
?
acDeviceKeyMap
[
mode
]
:
1
,
// 关机时传一个默认模式
// "onOff": acDeviceKeyMap[power],
"onOff"
:
1
,
// TODO:临时处理,等待前端调好就改回通过传参处理
"workMode"
:
acDeviceKeyMap
[
mode
],
// 关机时传一个默认模式
"onOff"
:
acDeviceKeyMap
[
power
],
"tempSet"
:
Number
(
setTemp
),
"fanSpeed"
:
acDeviceKeyMap
[
fanSpeed
],
"fanSpeed"
:
acDeviceKeyMap
[
acFanSpeedKeyMap
[
fanSpeed
]
],
"onOffLock"
:
acDeviceKeyMap
[
autoControl
],
"workModeLock"
:
acDeviceKeyMap
[
autoControl
],
"tempSetLock"
:
acDeviceKeyMap
[
autoControl
],
...
...
@@ -1328,6 +1373,13 @@ export let acDeviceKeyMap: { [key: string]: number } = {
"开"
:
1
,
"关"
:
0
}
export
let
acFanSpeedKeyMap
:
{
[
key
:
number
]:
string
}
=
{
4
:
"高风"
,
3
:
"中风"
,
2
:
"低风"
,
1
:
"中风"
,
0
:
"低风"
}
/**
* 区域楼层到Key的映射
...
...
src/data/findData.ts
View file @
456f47ee
...
...
@@ -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 param 查询参数
...
...
src/routers/admin.ts
View file @
456f47ee
...
...
@@ -18,6 +18,10 @@ export function setRouter(httpServer) {
httpServer
.
get
(
"/api/qdm/report/env/weekly/avg"
,
asyncHandler
(
getEnvWeeklyAvg
));
/** 获取近 7 天故障信息(按 fault_code 分组) */
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
));
...
...
@@ -45,6 +49,8 @@ export function setRouter(httpServer) {
/** 预警工单列表 */
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) {
* POST /api/qdm/admin/device/batch_edit
* 设备管理 - 批量编辑设备
* body: { regionKeys, deviceName?, power?, state?, deviceMode?, startDate?, endDate?, edits }
* edits: { power?, setTemp?, l
inkageStart
? }
* edits: { power?, setTemp?, l
ocked
? }
*/
async
function
batchEditDevices
(
req
,
res
)
{
let
reqConf
=
{
...
...
@@ -270,8 +276,8 @@ async function batchEditDevices(req, res) {
res
.
error
(
"缺少必填参数:edits"
);
return
;
}
if
(
edits
.
power
===
undefined
&&
edits
.
setTemp
===
undefined
&&
edits
.
l
inkageStart
===
undefined
)
{
res
.
error
(
"edits 中至少需要 power / setTemp / l
inkageStart
其中之一"
);
if
(
edits
.
power
===
undefined
&&
edits
.
setTemp
===
undefined
&&
edits
.
l
ocked
===
undefined
)
{
res
.
error
(
"edits 中至少需要 power / setTemp / l
ocked
其中之一"
);
return
;
}
// 校验 edits.power 值合法性
...
...
@@ -284,9 +290,9 @@ async function batchEditDevices(req, res) {
res
.
error
(
"edits.setTemp 必须为 16~30 之间的数值"
);
return
;
}
// 校验 edits.l
inkageStart
值合法性
if
(
edits
.
l
inkageStart
!==
undefined
&&
!
[
'on'
,
'off'
].
includes
(
edits
.
linkageStart
))
{
res
.
error
(
"edits.l
inkageStart
只能为 on 或 off"
);
// 校验 edits.l
ocked
值合法性
if
(
edits
.
l
ocked
!==
undefined
&&
!
[
'on'
,
'off'
].
includes
(
edits
.
locked
))
{
res
.
error
(
"edits.l
ocked
只能为 on 或 off"
);
return
;
}
...
...
@@ -384,3 +390,41 @@ async function getDeviceFaultList(req, res) {
});
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) {
*/
async
function
controlAcRunning
(
req
,
res
)
{
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
params
=
{
deviceId
,
mode
,
power
,
setTemp
,
maxTemp
,
minTemp
,
autoControl
,
fanSpeed
};
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