Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
D
dstTechnologyPlatform
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
dstTechnologyPlatform
Commits
a716b29d
Commit
a716b29d
authored
Sep 01, 2026
by
PC-20251223ZVQQ\Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
设备联动接口
parent
42987c2f
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
994 additions
and
102 deletions
+994
-102
device.ts
src/biz/device.ts
+81
-81
deviceFault.ts
src/biz/deviceFault.ts
+116
-0
deviceLinkage.ts
src/biz/deviceLinkage.ts
+404
-0
running.ts
src/biz/running.ts
+120
-11
admin.ts
src/config/eccParam/admin.ts
+4
-4
mysqlTableConfig.ts
src/config/mysqlTableConfig.ts
+6
-0
admin.ts
src/routers/admin.ts
+242
-1
running.ts
src/routers/running.ts
+6
-5
systemTools.ts
src/tools/systemTools.ts
+15
-0
No files found.
src/biz/device.ts
View file @
a716b29d
...
@@ -7,7 +7,7 @@ import { updateManyData } from "../data/updateData";
...
@@ -7,7 +7,7 @@ import { updateManyData } from "../data/updateData";
import
{
delData
}
from
"../data/delData"
;
import
{
delData
}
from
"../data/delData"
;
import
{
getIndoorUnitList
,
controlIndoorUnit
}
from
"./feiyiClient"
;
import
{
getIndoorUnitList
,
controlIndoorUnit
}
from
"./feiyiClient"
;
import
{
powerMap
,
workModeMap
,
fanSpeedMap
,
WORK_MODE_TEXT_MAP
,
FAN_SPEED_TEXT_MAP
,
DEVICE_STATE_TEXT_MAP
}
from
"../config/businessEnum"
;
import
{
powerMap
,
workModeMap
,
fanSpeedMap
,
WORK_MODE_TEXT_MAP
,
FAN_SPEED_TEXT_MAP
,
DEVICE_STATE_TEXT_MAP
}
from
"../config/businessEnum"
;
import
{
formatLocalTime
}
from
"../
config/eccParam/admin
"
;
import
{
formatLocalTime
}
from
"../
tools/systemTools
"
;
/**
/**
...
@@ -58,69 +58,69 @@ export async function getDeviceList(params: {
...
@@ -58,69 +58,69 @@ export async function getDeviceList(params: {
}
}
// 4. 判断是否需要飞奕后置过滤
// 4. 判断是否需要飞奕后置过滤
//
const needFeiyiFilter = params.power || params.deviceMode;
const
needFeiyiFilter
=
params
.
power
||
params
.
deviceMode
;
//
const targetOnOff = params.power ? powerMap[params.power] : undefined;
const
targetOnOff
=
params
.
power
?
powerMap
[
params
.
power
]
:
undefined
;
//
const targetMode = params.deviceMode ? workModeMap[params.deviceMode] : undefined;
const
targetMode
=
params
.
deviceMode
?
workModeMap
[
params
.
deviceMode
]
:
undefined
;
//
if (needFeiyiFilter) {
if
(
needFeiyiFilter
)
{
//
// ==== 飞奕筛选模式:查全量设备,后置过滤再内存分页 ====
// ==== 飞奕筛选模式:查全量设备,后置过滤再内存分页 ====
//
const deviceRes = await selectDataListByParam(
const
deviceRes
=
await
selectDataListByParam
(
//
TABLENAME.设备表, paramAnys,
TABLENAME
.
设备表
,
paramAnys
,
//
["device_id", "region_key", "device_name", "device_type", "device_state", "linkage_start"]
[
"device_id"
,
"region_key"
,
"device_name"
,
"device_type"
,
"device_state"
,
"linkage_start"
]
//
);
);
//
let devices = (deviceRes.data || []) as any[];
let
devices
=
(
deviceRes
.
data
||
[])
as
any
[];
//
let filteredRows: any[] = [];
let
filteredRows
:
any
[]
=
[];
//
const pad = (n: number) => String(n).padStart(2, '0');
const
pad
=
(
n
:
number
)
=>
String
(
n
).
padStart
(
2
,
'0'
);
//
const now = new Date();
const
now
=
new
Date
();
//
const updateTime = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
const
updateTime
=
`
${
now
.
getFullYear
()}
-
${
pad
(
now
.
getMonth
()
+
1
)}
-
${
pad
(
now
.
getDate
())}
${
pad
(
now
.
getHours
())}
:
${
pad
(
now
.
getMinutes
())}
:
${
pad
(
now
.
getSeconds
())}
`
;
//
if (devices.length > 0) {
if
(
devices
.
length
>
0
)
{
//
// 查区域信息
// 查区域信息
//
const regionIdSet = [...new Set(devices.map((d: any) => d.region_key).filter(Boolean))];
const
regionIdSet
=
[...
new
Set
(
devices
.
map
((
d
:
any
)
=>
d
.
region_key
).
filter
(
Boolean
))];
//
let regionMap: Map<string, any> = new Map();
let
regionMap
:
Map
<
string
,
any
>
=
new
Map
();
//
if (regionIdSet.length) {
if
(
regionIdSet
.
length
)
{
//
const regionRes = await selectDataListByParam(
const
regionRes
=
await
selectDataListByParam
(
//
TABLENAME.区域表,
TABLENAME
.
区域表
,
//
{ region_key: { "%in%": regionIdSet } },
{
region_key
:
{
"%in%"
:
regionIdSet
}
},
//
["region_key", "region_name"]
[
"region_key"
,
"region_name"
]
//
);
);
//
(regionRes.data as any[]).forEach((r: any) => regionMap.set(String(r.region_key), r));
(
regionRes
.
data
as
any
[]).
forEach
((
r
:
any
)
=>
regionMap
.
set
(
String
(
r
.
region_key
),
r
));
//
}
}
//
// 逐台调飞奕,收集符合条件的
// 逐台调飞奕,收集符合条件的
//
for (const device of devices) {
for
(
const
device
of
devices
)
{
//
let feiyiRow: any = {};
let
feiyiRow
:
any
=
{};
//
try {
try
{
//
const result = await getIndoorUnitList({ indoorUnitAddressFull: device.device_id, page: 1, limit: 1 });
const
result
=
await
getIndoorUnitList
({
indoorUnitAddressFull
:
device
.
device_id
,
page
:
1
,
limit
:
1
});
//
feiyiRow = result.rows[0] || {};
feiyiRow
=
result
.
rows
[
0
]
||
{};
//
} catch (e) {
}
catch
(
e
)
{
//
console.error(`[getDeviceList] 获取设备 ${device.device_id} 飞奕数据失败:`, e);
console
.
error
(
`[getDeviceList] 获取设备
${
device
.
device_id
}
飞奕数据失败:`
,
e
);
//
}
}
//
// 飞奕筛选
// 飞奕筛选
//
if (targetOnOff !== undefined && feiyiRow.onOff !== targetOnOff) continue;
if
(
targetOnOff
!==
undefined
&&
feiyiRow
.
onOff
!==
targetOnOff
)
continue
;
//
if (targetMode !== undefined && feiyiRow.workMode !== targetMode) continue;
if
(
targetMode
!==
undefined
&&
feiyiRow
.
workMode
!==
targetMode
)
continue
;
//
const region = regionMap.get(String(device.region_key));
const
region
=
regionMap
.
get
(
String
(
device
.
region_key
));
//
filteredRows.push({
filteredRows
.
push
({
//
deviceId: device.device_id,
deviceId
:
device
.
device_id
,
//
regionName: region ? region.region_name || '' : '',
regionName
:
region
?
region
.
region_name
||
''
:
''
,
//
power: feiyiRow.onOff === 1 ? '开' : '关',
power
:
feiyiRow
.
onOff
===
1
?
'开'
:
'关'
,
//
setTemp: feiyiRow.tempSet ?? '',
setTemp
:
feiyiRow
.
tempSet
??
''
,
//
fanSpeed: FAN_SPEED_TEXT_MAP[feiyiRow.fanSpeed] || String(feiyiRow.fanSpeed || ''),
fanSpeed
:
FAN_SPEED_TEXT_MAP
[
feiyiRow
.
fanSpeed
]
||
String
(
feiyiRow
.
fanSpeed
||
''
),
//
deviceMode: WORK_MODE_TEXT_MAP[feiyiRow.workMode] || String(feiyiRow.workMode || ''),
deviceMode
:
WORK_MODE_TEXT_MAP
[
feiyiRow
.
workMode
]
||
String
(
feiyiRow
.
workMode
||
''
),
//
state: DEVICE_STATE_TEXT_MAP[device.device_state] ?? String(device.device_state ?? ''),
state
:
DEVICE_STATE_TEXT_MAP
[
device
.
device_state
]
??
String
(
device
.
device_state
??
''
),
//
linkageStart: device.linkage_start ?? 'on',
linkageStart
:
device
.
linkage_start
??
'on'
,
//
updateTime,
updateTime
,
//
});
});
//
}
}
//
}
}
//
const total = filteredRows.length;
const
total
=
filteredRows
.
length
;
//
const startIdx = (pageNumber - 1) * pageSize;
const
startIdx
=
(
pageNumber
-
1
)
*
pageSize
;
//
const rows = filteredRows.slice(startIdx, startIdx + pageSize);
const
rows
=
filteredRows
.
slice
(
startIdx
,
startIdx
+
pageSize
);
//
return { total, rows };
return
{
total
,
rows
};
//
} else {
}
else
{
// ==== 无飞奕筛选:DB 层分页(原逻辑) ====
// ==== 无飞奕筛选:DB 层分页(原逻辑) ====
// 查询总数
// 查询总数
const
countRes
=
await
selectDataCountByParam
(
TABLENAME
.
设备表
,
paramAnys
);
const
countRes
=
await
selectDataCountByParam
(
TABLENAME
.
设备表
,
paramAnys
);
...
@@ -153,17 +153,17 @@ export async function getDeviceList(params: {
...
@@ -153,17 +153,17 @@ export async function getDeviceList(params: {
(
regionRes
.
data
as
any
[]).
forEach
((
r
:
any
)
=>
regionMap
.
set
(
String
(
r
.
region_key
),
r
));
(
regionRes
.
data
as
any
[]).
forEach
((
r
:
any
)
=>
regionMap
.
set
(
String
(
r
.
region_key
),
r
));
}
}
//
//
逐台串行调用飞奕接口获取实时数据
// 逐台串行调用飞奕接口获取实时数据
//
const feiyiResults: any[] = [];
const
feiyiResults
:
any
[]
=
[];
//
for (const device of devices) {
for
(
const
device
of
devices
)
{
//
try {
try
{
//
const result = await getIndoorUnitList({ indoorUnitAddressFull: device.device_id, page: 1, limit: 1 });
const
result
=
await
getIndoorUnitList
({
indoorUnitAddressFull
:
device
.
device_id
,
page
:
1
,
limit
:
1
});
//
feiyiResults.push(result.rows[0] || {});
feiyiResults
.
push
(
result
.
rows
[
0
]
||
{});
//
} catch (e) {
}
catch
(
e
)
{
//
console.error(`[getDeviceList] 获取设备 ${device.device_id} 飞奕数据失败:`, e);
console
.
error
(
`[getDeviceList] 获取设备
${
device
.
device_id
}
飞奕数据失败:`
,
e
);
//
feiyiResults.push({ });
feiyiResults
.
push
({
});
//
}
}
//
}
}
// 组装返回数据
// 组装返回数据
const
pad
=
(
n
:
number
)
=>
String
(
n
).
padStart
(
2
,
'0'
);
const
pad
=
(
n
:
number
)
=>
String
(
n
).
padStart
(
2
,
'0'
);
...
@@ -172,7 +172,7 @@ export async function getDeviceList(params: {
...
@@ -172,7 +172,7 @@ export async function getDeviceList(params: {
const
rows
=
devices
.
map
((
device
:
any
,
index
:
number
)
=>
{
const
rows
=
devices
.
map
((
device
:
any
,
index
:
number
)
=>
{
const
region
=
regionMap
.
get
(
String
(
device
.
region_key
));
const
region
=
regionMap
.
get
(
String
(
device
.
region_key
));
//
const row = feiyiResults[index] || {};
const
row
=
feiyiResults
[
index
]
||
{};
return
{
return
{
deviceId
:
device
.
device_id
,
deviceId
:
device
.
device_id
,
...
@@ -180,10 +180,10 @@ export async function getDeviceList(params: {
...
@@ -180,10 +180,10 @@ export async function getDeviceList(params: {
deviceType
:
device
.
device_type
||
''
,
deviceType
:
device
.
device_type
||
''
,
deviceState
:
device
.
device_state
??
''
,
deviceState
:
device
.
device_state
??
''
,
regionName
:
region
?
region
.
region_name
||
''
:
''
,
regionName
:
region
?
region
.
region_name
||
''
:
''
,
//
power: row.onOff === 1 ? '开' : '关',
power
:
row
.
onOff
===
1
?
'开'
:
'关'
,
//
setTemp: row.tempSet ?? '',
setTemp
:
row
.
tempSet
??
''
,
//
fanSpeed: FAN_SPEED_TEXT_MAP[row.fanSpeed] || String(row.fanSpeed || ''),
fanSpeed
:
FAN_SPEED_TEXT_MAP
[
row
.
fanSpeed
]
||
String
(
row
.
fanSpeed
||
''
),
//
deviceMode: WORK_MODE_TEXT_MAP[row.workMode] || String(row.workMode || ''),
deviceMode
:
WORK_MODE_TEXT_MAP
[
row
.
workMode
]
||
String
(
row
.
workMode
||
''
),
state
:
DEVICE_STATE_TEXT_MAP
[
device
.
device_state
]
??
String
(
device
.
device_state
??
''
),
state
:
DEVICE_STATE_TEXT_MAP
[
device
.
device_state
]
??
String
(
device
.
device_state
??
''
),
linkageStart
:
device
.
linkage_start
??
'on'
,
linkageStart
:
device
.
linkage_start
??
'on'
,
updateTime
,
updateTime
,
...
@@ -191,7 +191,7 @@ export async function getDeviceList(params: {
...
@@ -191,7 +191,7 @@ export async function getDeviceList(params: {
});
});
return
{
total
,
rows
};
return
{
total
,
rows
};
//
}
}
}
}
/**
/**
...
...
src/biz/deviceFault.ts
0 → 100644
View file @
a716b29d
import
{
selectPaginatedDataWithOrder
,
selectDataListByParam
}
from
"../data/findData"
;
import
{
TABLENAME
}
from
"../config/dbEnum"
;
/** 故障等级映射 */
const
levelMap
:
Record
<
number
,
string
>
=
{
1
:
'一级'
,
2
:
'二级'
,
3
:
'三级'
,
};
/** 处理状态映射 */
const
statusMap
:
Record
<
number
,
string
>
=
{
0
:
'未处理'
,
1
:
'处理中'
,
2
:
'已解决'
,
};
export
interface
AlertWorkOrderQueryParams
{
pageNumber
?:
number
;
pageSize
?:
number
;
level
?:
number
;
// 故障等级:1/2/3
fault_code
?:
string
;
// 故障代码(模糊匹配)
device_id
?:
string
;
// 设备ID(模糊匹配)
startTime
?:
string
;
// 时间范围起始 'YYYY-MM-DD HH:mm:ss'
endTime
?:
string
;
// 时间范围截止 'YYYY-MM-DD HH:mm:ss'
}
/**
* 预警工单列表查询(分页)
*/
export
async
function
getDeviceFaultList
(
params
:
AlertWorkOrderQueryParams
)
{
const
pageNumber
=
params
.
pageNumber
||
1
;
const
pageSize
=
params
.
pageSize
||
10
;
// 构建查询条件
const
queryParam
:
any
=
{};
if
(
params
.
level
!==
undefined
&&
params
.
level
!==
null
)
{
queryParam
.
level
=
params
.
level
;
}
if
(
params
.
fault_code
)
{
queryParam
.
fault_code
=
{
"%like%"
:
params
.
fault_code
};
}
if
(
params
.
device_id
)
{
queryParam
.
device_id
=
{
"%like%"
:
params
.
device_id
};
}
if
(
params
.
startTime
||
params
.
endTime
)
{
const
start
=
params
.
startTime
||
'1970-01-01 00:00:00'
;
const
end
=
params
.
endTime
||
'2099-12-31 23:59:59'
;
queryParam
.
occurred_time
=
{
"%between%"
:
[
start
,
end
]
};
}
// 分页查询,按创建时间倒序
const
result
=
await
selectPaginatedDataWithOrder
(
TABLENAME
.
设备故障表
,
{
...
queryParam
},
[
"id"
,
"device_id"
,
"fault_type"
,
"fault_code"
,
"fault_description"
,
"occurred_time"
,
"level"
,
"status"
,
"created_at"
],
pageNumber
,
pageSize
,
[[
"created_at"
,
"DESC"
]],
);
// 通过设备编号查询所属区域,拼接 regionName(groups_type_name)
const
rows
=
(
result
.
data
as
any
[])
||
[];
const
deviceIds
=
[...
new
Set
(
rows
.
map
((
item
:
any
)
=>
item
.
device_id
).
filter
(
Boolean
))];
const
deviceRegionNameMap
:
{
[
deviceId
:
string
]:
string
}
=
{};
if
(
deviceIds
.
length
>
0
)
{
const
deviceResult
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
{
device_id
:
{
"%in%"
:
deviceIds
}
},
[
"device_id"
,
"region_key"
]
);
const
regionKeys
=
[...
new
Set
((
deviceResult
.
data
||
[]).
map
((
d
:
any
)
=>
d
.
region_key
).
filter
(
Boolean
))];
const
regionMap
:
{
[
id
:
number
]:
any
}
=
{};
if
(
regionKeys
.
length
>
0
)
{
const
regionResult
=
await
selectDataListByParam
(
TABLENAME
.
区域表
,
{
id
:
{
"%in%"
:
regionKeys
}
},
[
"id"
,
"groups"
,
"type"
,
"name"
]
);
(
regionResult
.
data
||
[]).
forEach
((
r
:
any
)
=>
{
regionMap
[
r
.
id
]
=
r
;
});
}
(
deviceResult
.
data
||
[]).
forEach
((
d
:
any
)
=>
{
const
region
=
regionMap
[
d
.
region_key
];
deviceRegionNameMap
[
d
.
device_id
]
=
region
?
[
region
.
groups
,
region
.
type
,
region
.
name
].
filter
(
Boolean
).
join
(
'_'
)
:
''
;
});
}
// 映射中文名
const
list
=
rows
.
map
((
item
:
any
)
=>
({
id
:
item
.
id
,
device_id
:
item
.
device_id
||
''
,
regionName
:
deviceRegionNameMap
[
item
.
device_id
]
||
''
,
fault_type
:
item
.
fault_type
||
''
,
fault_code
:
item
.
fault_code
||
''
,
fault_description
:
item
.
fault_description
||
''
,
occurred_time
:
item
.
occurred_time
||
''
,
created_at
:
item
.
created_at
||
''
,
level
:
item
.
level
,
levelName
:
levelMap
[
item
.
level
]
||
'未知'
,
status
:
item
.
status
,
statusName
:
statusMap
[
item
.
status
]
||
'未知'
,
order_address
:
''
,
// 预留:工单地址,todo
order_send
:
'on'
,
// 预留:工单推送,默认 on,todo
}));
return
{
list
,
total
:
result
.
pagination
.
total
,
pagination
:
result
.
pagination
,
};
}
src/biz/deviceLinkage.ts
0 → 100644
View file @
a716b29d
/**
* 设备联动调节 业务逻辑(后台管理配置联动参数)
*
* 联动逻辑与 running.ts 中写死的温度联动(calcAcTempSet)保持一致,阈值改为可配置:
* - 制冷(夏季):temperature >= threshold_max → setting_max;中间区间 → setting_mid;< threshold_min → setting_min
* - 制热(冬季):temperature > threshold_max → setting_max;中间区间 → setting_mid;<= threshold_min → setting_min
* - 新风:CO2 联动(threshold_max=1000 → 高风,threshold_min=800 → 低风/待机)
*/
import
{
selectDataListByParam
}
from
"../data/findData"
;
import
{
selectPaginatedDataWithOrder
}
from
"../data/findData"
;
import
{
addData
}
from
"../data/addData"
;
import
{
delData
}
from
"../data/delData"
;
import
{
updateManyData
}
from
"../data/updateData"
;
import
{
TABLENAME
}
from
"../config/dbEnum"
;
/** 联动类型可选值 */
const
LINKAGE_TYPES
=
[
"制冷"
,
"制热"
,
"新风"
];
/** 格式化为本地时间字符串 YYYY-MM-DD HH:mm:ss */
function
formatLocalTime
(
d
:
Date
):
string
{
const
pad
=
(
n
:
number
)
=>
String
(
n
).
padStart
(
2
,
"0"
);
return
`
${
d
.
getFullYear
()}
-
${
pad
(
d
.
getMonth
()
+
1
)}
-
${
pad
(
d
.
getDate
())}
${
pad
(
d
.
getHours
())}
:
${
pad
(
d
.
getMinutes
())}
:
${
pad
(
d
.
getSeconds
())}
`
;
}
// ======================= 联动参数列表 =======================
/**
* 查询设备联动参数列表(分页)
* query: pageNumber, pageSize, deviceName, linkageType
*/
export
async
function
getLinkageList
(
params
:
{
pageNumber
?:
number
;
pageSize
?:
number
;
deviceId
?:
string
;
// 设备编号
deviceName
?:
string
;
// 设备名称(模糊匹配,通过 device 表关联)
linkageType
?:
string
;
// 联动类型:制冷/制热/新风
}):
Promise
<
any
>
{
const
pageNumber
=
params
.
pageNumber
||
1
;
const
pageSize
=
params
.
pageSize
||
10
;
const
queryParam
:
any
=
{};
// 联动类型筛选
if
(
params
.
linkageType
)
{
queryParam
.
linkage_type
=
params
.
linkageType
;
}
// 设备名称筛选:先通过 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
};
}
if
(
params
.
deviceId
)
{
queryParam
.
device_id
=
params
.
deviceId
;
}
// 分页查询,按创建时间倒序
const
result
=
await
selectPaginatedDataWithOrder
(
TABLENAME
.
设备联动调节表
,
queryParam
,
[
"id"
,
"device_id"
,
"linkage_type"
,
"threshold_max"
,
"threshold_min"
,
"threshold_mid"
,
"setting_max"
,
"setting_min"
,
"setting_mid"
,
"fan_speed_max"
,
"fan_speed_mid"
,
"fan_speed_min"
,
"is_use"
,
"divide_method"
,
"begin_validity"
,
"end_validity"
,
"created_at"
,
"updated_at"
],
pageNumber
,
pageSize
,
[[
"id"
,
"DESC"
]],
);
// 补充设备名称映射
const
rows
=
result
.
data
as
any
[];
const
linkageDeviceIds
=
Array
.
from
(
new
Set
(
rows
.
map
((
item
:
any
)
=>
item
.
device_id
)));
let
deviceNameMap
:
Record
<
string
,
string
>
=
{};
if
(
linkageDeviceIds
.
length
>
0
)
{
const
devRes
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
{
device_id
:
{
"%in%"
:
linkageDeviceIds
}
},
[
"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
,
deviceId
:
item
.
device_id
,
deviceName
:
deviceNameMap
[
item
.
device_id
]
||
''
,
linkageType
:
item
.
linkage_type
||
''
,
thresholdMax
:
item
.
threshold_max
??
''
,
thresholdMin
:
item
.
threshold_min
??
''
,
thresholdMid
:
item
.
threshold_mid
??
''
,
settingMax
:
item
.
setting_max
??
''
,
settingMin
:
item
.
setting_min
??
''
,
settingMid
:
item
.
setting_mid
??
''
,
fanSpeedMax
:
item
.
fan_speed_max
??
''
,
fanSpeedMid
:
item
.
fan_speed_mid
??
''
,
fanSpeedMin
:
item
.
fan_speed_min
??
''
,
isUse
:
item
.
is_use
??
'on'
,
divideMethod
:
item
.
divide_method
||
''
,
beginValidity
:
item
.
begin_validity
?
(
typeof
item
.
begin_validity
===
"string"
?
item
.
begin_validity
:
formatLocalTime
(
new
Date
(
item
.
begin_validity
)))
:
""
,
endValidity
:
item
.
end_validity
?
(
typeof
item
.
end_validity
===
"string"
?
item
.
end_validity
:
formatLocalTime
(
new
Date
(
item
.
end_validity
)))
:
""
,
createdAt
:
item
.
created_at
?
(
typeof
item
.
created_at
===
"string"
?
item
.
created_at
:
formatLocalTime
(
new
Date
(
item
.
created_at
)))
:
""
,
updatedAt
:
item
.
updated_at
?
(
typeof
item
.
updated_at
===
"string"
?
item
.
updated_at
:
formatLocalTime
(
new
Date
(
item
.
updated_at
)))
:
""
,
}));
return
{
total
:
result
.
pagination
.
total
,
list
};
}
// ======================= 联动参数详情 =======================
/**
* 查询单条设备联动参数详情
* @param id 联动参数主键
*/
export
async
function
getLinkageDetail
(
id
:
number
):
Promise
<
any
>
{
if
(
!
id
)
{
return
{
isSuccess
:
false
,
msg
:
"缺少必填参数:id"
};
}
const
res
=
await
selectDataListByParam
(
TABLENAME
.
设备联动调节表
,
{
id
},
[
"id"
,
"device_id"
,
"linkage_type"
,
"threshold_max"
,
"threshold_min"
,
"threshold_mid"
,
"setting_max"
,
"setting_min"
,
"setting_mid"
,
"fan_speed_max"
,
"fan_speed_mid"
,
"fan_speed_min"
,
"is_use"
,
"divide_method"
,
"begin_validity"
,
"end_validity"
,
"created_at"
,
"updated_at"
]
);
const
rows
:
any
[]
=
res
.
data
||
[];
if
(
rows
.
length
===
0
)
{
return
{
isSuccess
:
false
,
msg
:
`联动参数
${
id
}
不存在`
};
}
const
item
=
rows
[
0
];
// 补充设备名称
let
deviceName
=
''
;
const
devRes
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
{
device_id
:
item
.
device_id
},
[
"device_id"
,
"device_name"
]
);
if
((
devRes
.
data
as
any
[]).
length
>
0
)
{
deviceName
=
(
devRes
.
data
as
any
[])[
0
].
device_name
||
''
;
}
return
{
isSuccess
:
true
,
data
:
{
id
:
item
.
id
,
deviceId
:
item
.
device_id
,
deviceName
,
linkageType
:
item
.
linkage_type
||
''
,
thresholdMax
:
item
.
threshold_max
??
''
,
thresholdMin
:
item
.
threshold_min
??
''
,
thresholdMid
:
item
.
threshold_mid
??
''
,
settingMax
:
item
.
setting_max
??
''
,
settingMin
:
item
.
setting_min
??
''
,
settingMid
:
item
.
setting_mid
??
''
,
fanSpeedMax
:
item
.
fan_speed_max
??
''
,
fanSpeedMid
:
item
.
fan_speed_mid
??
''
,
fanSpeedMin
:
item
.
fan_speed_min
??
''
,
isUse
:
item
.
is_use
??
'on'
,
divideMethod
:
item
.
divide_method
||
''
,
beginValidity
:
item
.
begin_validity
?
(
typeof
item
.
begin_validity
===
"string"
?
item
.
begin_validity
:
formatLocalTime
(
new
Date
(
item
.
begin_validity
)))
:
""
,
endValidity
:
item
.
end_validity
?
(
typeof
item
.
end_validity
===
"string"
?
item
.
end_validity
:
formatLocalTime
(
new
Date
(
item
.
end_validity
)))
:
""
,
createdAt
:
item
.
created_at
?
(
typeof
item
.
created_at
===
"string"
?
item
.
created_at
:
formatLocalTime
(
new
Date
(
item
.
created_at
)))
:
""
,
updatedAt
:
item
.
updated_at
?
(
typeof
item
.
updated_at
===
"string"
?
item
.
updated_at
:
formatLocalTime
(
new
Date
(
item
.
updated_at
)))
:
""
,
},
};
}
// ======================= 新增联动参数 =======================
/**
* 新增设备联动参数
* body: deviceId, linkageType, thresholdMax, thresholdMin, thresholdMid,
* settingMax, settingMin, settingMid, divideMethod, beginValidity, endValidity
*/
export
async
function
addLinkage
(
params
:
{
deviceId
:
string
;
linkageType
:
string
;
thresholdMax
?:
string
;
thresholdMin
?:
string
;
thresholdMid
?:
string
;
settingMax
?:
string
;
settingMin
?:
string
;
settingMid
?:
string
;
fanSpeedMax
?:
string
;
fanSpeedMid
?:
string
;
fanSpeedMin
?:
string
;
divideMethod
?:
string
;
beginValidity
?:
string
;
endValidity
?:
string
;
}):
Promise
<
any
>
{
const
{
deviceId
,
linkageType
}
=
params
;
if
(
!
deviceId
)
{
return
{
isSuccess
:
false
,
msg
:
"缺少必填参数:deviceId"
};
}
if
(
!
linkageType
||
!
LINKAGE_TYPES
.
includes
(
linkageType
))
{
return
{
isSuccess
:
false
,
msg
:
"linkageType 只能为:制冷/制热/新风"
};
}
// 校验设备存在
const
devRes
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
{
device_id
:
deviceId
},
[
"device_id"
]);
if
(
!
(
devRes
.
data
as
any
[]).
length
)
{
return
{
isSuccess
:
false
,
msg
:
`设备
${
deviceId
}
不存在`
};
}
await
addData
(
TABLENAME
.
设备联动调节表
,
{
device_id
:
deviceId
,
linkage_type
:
linkageType
,
threshold_max
:
params
.
thresholdMax
??
null
,
threshold_min
:
params
.
thresholdMin
??
null
,
threshold_mid
:
params
.
thresholdMid
??
null
,
setting_max
:
params
.
settingMax
??
null
,
setting_min
:
params
.
settingMin
??
null
,
setting_mid
:
params
.
settingMid
??
null
,
fan_speed_max
:
params
.
fanSpeedMax
??
null
,
fan_speed_mid
:
params
.
fanSpeedMid
??
null
,
fan_speed_min
:
params
.
fanSpeedMin
??
null
,
is_use
:
'on'
,
// 新增默认启用
divide_method
:
params
.
divideMethod
??
null
,
begin_validity
:
params
.
beginValidity
||
null
,
end_validity
:
params
.
endValidity
||
null
,
});
console
.
log
(
`[新增联动参数] device=
${
deviceId
}
, type=
${
linkageType
}
`
);
return
{
isSuccess
:
true
};
}
// ======================= 批量新增联动参数 =======================
/**
* 批量新增设备联动参数(为多台设备配置相同联动规则)
* body: deviceIds(数组), linkageType, thresholdMax, thresholdMin, thresholdMid,
* settingMax, settingMin, settingMid, divideMethod, beginValidity, endValidity
*/
export
async
function
batchAddLinkage
(
params
:
{
deviceIds
:
string
[];
linkageType
:
string
;
thresholdMax
?:
string
;
thresholdMin
?:
string
;
thresholdMid
?:
string
;
settingMax
?:
string
;
settingMin
?:
string
;
settingMid
?:
string
;
fanSpeedMax
?:
string
;
fanSpeedMid
?:
string
;
fanSpeedMin
?:
string
;
divideMethod
?:
string
;
beginValidity
?:
string
;
endValidity
?:
string
;
}):
Promise
<
any
>
{
const
{
deviceIds
,
linkageType
}
=
params
;
if
(
!
Array
.
isArray
(
deviceIds
)
||
deviceIds
.
length
===
0
)
{
return
{
isSuccess
:
false
,
msg
:
"缺少必填参数:deviceIds(数组)"
};
}
if
(
!
linkageType
||
!
LINKAGE_TYPES
.
includes
(
linkageType
))
{
return
{
isSuccess
:
false
,
msg
:
"linkageType 只能为:制冷/制热/新风"
};
}
// 去重
const
ids
=
Array
.
from
(
new
Set
(
deviceIds
));
// 校验设备均存在
const
devRes
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
{
device_id
:
{
"%in%"
:
ids
}
},
[
"device_id"
]
);
const
existSet
=
new
Set
((
devRes
.
data
as
any
[]).
map
((
d
:
any
)
=>
d
.
device_id
));
const
notExist
=
ids
.
filter
((
id
)
=>
!
existSet
.
has
(
id
));
if
(
notExist
.
length
>
0
)
{
return
{
isSuccess
:
false
,
msg
:
`以下设备不存在:
${
notExist
.
join
(
", "
)}
`
};
}
// 批量插入(addData 内部走 bulkCreate)
const
rows
=
ids
.
map
((
deviceId
)
=>
({
device_id
:
deviceId
,
linkage_type
:
linkageType
,
threshold_max
:
params
.
thresholdMax
??
null
,
threshold_min
:
params
.
thresholdMin
??
null
,
threshold_mid
:
params
.
thresholdMid
??
null
,
setting_max
:
params
.
settingMax
??
null
,
setting_min
:
params
.
settingMin
??
null
,
setting_mid
:
params
.
settingMid
??
null
,
fan_speed_max
:
params
.
fanSpeedMax
??
null
,
fan_speed_mid
:
params
.
fanSpeedMid
??
null
,
fan_speed_min
:
params
.
fanSpeedMin
??
null
,
is_use
:
'on'
,
// 新增默认启用
divide_method
:
params
.
divideMethod
??
null
,
begin_validity
:
params
.
beginValidity
||
null
,
end_validity
:
params
.
endValidity
||
null
,
}));
await
addData
(
TABLENAME
.
设备联动调节表
,
rows
);
console
.
log
(
`[批量新增联动参数] count=
${
ids
.
length
}
, type=
${
linkageType
}
, ids=
${
ids
.
join
(
","
)}
`
);
return
{
isSuccess
:
true
,
count
:
ids
.
length
};
}
// ======================= 编辑联动参数 =======================
/**
* 编辑设备联动参数
* body: id + 待更新字段(同新增)
*/
export
async
function
editLinkage
(
params
:
{
id
:
number
;
deviceId
?:
string
;
linkageType
?:
string
;
thresholdMax
?:
string
;
thresholdMin
?:
string
;
thresholdMid
?:
string
;
settingMax
?:
string
;
settingMin
?:
string
;
settingMid
?:
string
;
fanSpeedMax
?:
string
;
fanSpeedMid
?:
string
;
fanSpeedMin
?:
string
;
isUse
?:
string
;
divideMethod
?:
string
;
beginValidity
?:
string
;
endValidity
?:
string
;
}):
Promise
<
any
>
{
const
{
id
}
=
params
;
if
(
!
id
)
{
return
{
isSuccess
:
false
,
msg
:
"缺少必填参数:id"
};
}
const
dbEdits
:
any
=
{};
if
(
params
.
deviceId
!==
undefined
)
dbEdits
.
device_id
=
params
.
deviceId
;
if
(
params
.
linkageType
!==
undefined
)
{
if
(
!
LINKAGE_TYPES
.
includes
(
params
.
linkageType
))
{
return
{
isSuccess
:
false
,
msg
:
"linkageType 只能为:制冷/制热/新风"
};
}
dbEdits
.
linkage_type
=
params
.
linkageType
;
}
if
(
params
.
thresholdMax
!==
undefined
)
dbEdits
.
threshold_max
=
params
.
thresholdMax
;
if
(
params
.
thresholdMin
!==
undefined
)
dbEdits
.
threshold_min
=
params
.
thresholdMin
;
if
(
params
.
thresholdMid
!==
undefined
)
dbEdits
.
threshold_mid
=
params
.
thresholdMid
;
if
(
params
.
settingMax
!==
undefined
)
dbEdits
.
setting_max
=
params
.
settingMax
;
if
(
params
.
settingMin
!==
undefined
)
dbEdits
.
setting_min
=
params
.
settingMin
;
if
(
params
.
settingMid
!==
undefined
)
dbEdits
.
setting_mid
=
params
.
settingMid
;
if
(
params
.
fanSpeedMax
!==
undefined
)
dbEdits
.
fan_speed_max
=
params
.
fanSpeedMax
;
if
(
params
.
fanSpeedMid
!==
undefined
)
dbEdits
.
fan_speed_mid
=
params
.
fanSpeedMid
;
if
(
params
.
fanSpeedMin
!==
undefined
)
dbEdits
.
fan_speed_min
=
params
.
fanSpeedMin
;
if
(
params
.
isUse
!==
undefined
)
dbEdits
.
is_use
=
params
.
isUse
;
if
(
params
.
divideMethod
!==
undefined
)
dbEdits
.
divide_method
=
params
.
divideMethod
;
if
(
params
.
beginValidity
!==
undefined
)
dbEdits
.
begin_validity
=
params
.
beginValidity
||
null
;
if
(
params
.
endValidity
!==
undefined
)
dbEdits
.
end_validity
=
params
.
endValidity
||
null
;
if
(
Object
.
keys
(
dbEdits
).
length
===
0
)
{
return
{
isSuccess
:
false
,
msg
:
"没有需要更新的字段"
};
}
await
updateManyData
(
TABLENAME
.
设备联动调节表
,
{
id
},
dbEdits
);
console
.
log
(
`[编辑联动参数] id=
${
id
}
, edits=
${
JSON
.
stringify
(
dbEdits
)}
`
);
return
{
isSuccess
:
true
};
}
// ======================= 删除联动参数 =======================
/**
* 删除设备联动参数
* body: id
*/
export
async
function
deleteLinkage
(
id
:
number
):
Promise
<
any
>
{
if
(
!
id
)
{
return
{
isSuccess
:
false
,
msg
:
"缺少必填参数:id"
};
}
await
delData
(
TABLENAME
.
设备联动调节表
,
{
id
});
console
.
log
(
`[删除联动参数] id=
${
id
}
`
);
return
{
isSuccess
:
true
};
}
src/biz/running.ts
View file @
a716b29d
...
@@ -27,6 +27,9 @@ import {
...
@@ -27,6 +27,9 @@ import {
formatMonthKey
,
formatMonthKey
,
}
from
'../util/dateUtils'
;
}
from
'../util/dateUtils'
;
import
{
get
}
from
'http'
;
import
{
get
}
from
'http'
;
import
{
formatLocalTime
,
getSeason
}
from
'../tools/systemTools'
;
import
{
addData
}
from
'../data/addData'
;
import
{
updateManyData
}
from
'../data/updateData'
;
// ==================== 质量判定函数(从 device.ts 移植,适配新系统) ====================
// ==================== 质量判定函数(从 device.ts 移植,适配新系统) ====================
...
@@ -2315,7 +2318,7 @@ export async function getAcRunningPop(regionKey: string, deviceId: string) {
...
@@ -2315,7 +2318,7 @@ export async function getAcRunningPop(regionKey: string, deviceId: string) {
let
devices
=
await
selectDataListByParam
(
let
devices
=
await
selectDataListByParam
(
TABLENAME
.
设备表
,
TABLENAME
.
设备表
,
{
device_id
:
deviceId
},
// region_key: regionKey,
{
device_id
:
deviceId
},
// region_key: regionKey,
[
"device_id"
,
"device_type"
,
"device_name"
,
"device_param"
]
[
"device_id"
,
"device_type"
,
"device_name"
,
"
linkage_start"
,
"
device_param"
]
);
);
if
(
!
devices
.
data
.
length
)
{
if
(
!
devices
.
data
.
length
)
{
throw
new
BizError
(
ERRORENUM
.
未找到数据
,
`设备
${
deviceId
}
在区域
${
regionKey
}
中未找到`
);
throw
new
BizError
(
ERRORENUM
.
未找到数据
,
`设备
${
deviceId
}
在区域
${
regionKey
}
中未找到`
);
...
@@ -2334,6 +2337,7 @@ export async function getAcRunningPop(regionKey: string, deviceId: string) {
...
@@ -2334,6 +2337,7 @@ export async function getAcRunningPop(regionKey: string, deviceId: string) {
return
{
return
{
deviceInfo
:
devices
.
data
[
0
].
device_param
,
deviceInfo
:
devices
.
data
[
0
].
device_param
,
autoControl
:
devices
.
data
[
0
].
linkage_start
,
latestData
:
deviceData
latestData
:
deviceData
}
}
}
}
...
@@ -2347,9 +2351,9 @@ export async function controlAcRunning(params: {}) {
...
@@ -2347,9 +2351,9 @@ export async function controlAcRunning(params: {}) {
let
mode
=
params
[
"mode"
];
let
mode
=
params
[
"mode"
];
let
power
=
params
[
"power"
]
||
"on"
;
let
power
=
params
[
"power"
]
||
"on"
;
let
setTemp
=
params
[
"setTemp"
];
let
setTemp
=
params
[
"setTemp"
];
let
maxTemp
=
params
[
"maxTemp"
]
||
30
;
//
默认锁定
温度上限
let
maxTemp
=
params
[
"maxTemp"
]
||
30
;
//
联动
温度上限
let
minTemp
=
params
[
"minTemp"
]
||
16
;
//
默认锁定
温度下限
let
minTemp
=
params
[
"minTemp"
]
||
16
;
//
联动
温度下限
let
autoControl
=
params
[
"autoControl"
]
||
"生效"
;
let
autoControl
=
params
[
"autoControl"
]
||
"生效"
;
// 联动是否生效
let
fanSpeed
=
acPopFanSpeedMap
[
params
[
"fanSpeed"
]];
let
fanSpeed
=
acPopFanSpeedMap
[
params
[
"fanSpeed"
]];
console
.
log
(
fanSpeed
)
console
.
log
(
fanSpeed
)
...
@@ -2357,11 +2361,46 @@ export async function controlAcRunning(params: {}) {
...
@@ -2357,11 +2361,46 @@ export async function controlAcRunning(params: {}) {
let
device
=
await
selectOneDataByParam
(
let
device
=
await
selectOneDataByParam
(
TABLENAME
.
设备表
,
TABLENAME
.
设备表
,
{
device_id
:
deviceId
},
{
device_id
:
deviceId
},
[
"id"
,
"device_id"
,
"region_key"
,
"device_name"
,
"device_param"
]
[
"id"
,
"device_id"
,
"region_key"
,
"device_name"
,
"device_param"
,
"linkage_start"
]
);
);
if
(
!
device
.
data
||
!
device
.
data
.
id
)
{
if
(
!
device
.
data
||
!
device
.
data
.
id
)
{
throw
new
BizError
(
ERRORENUM
.
未找到数据
,
`设备
${
deviceId
}
未注册`
);
throw
new
BizError
(
ERRORENUM
.
未找到数据
,
`设备
${
deviceId
}
未注册`
);
}
}
// 空调联动设置
const
deviceType
=
device
.
data
.
device_type
||
''
;
// 1.1 联动开关状态写入 device.linkage_start(autoControl:生效→on,解除→off)
if
(
autoControl
!==
undefined
&&
autoControl
!==
null
&&
autoControl
!==
''
)
{
const
linkageStart
=
autoControl
===
'生效'
?
'on'
:
'off'
;
await
updateManyData
(
TABLENAME
.
设备表
,
{
device_id
:
deviceId
},
{
linkage_start
:
linkageStart
});
}
// 1.2 联动温度上下限写入 device_linkage:与 acPop 同样方式查找,找到则更新,找不到则新增
const
hasMax
=
maxTemp
!==
undefined
&&
maxTemp
!==
null
&&
maxTemp
!==
''
;
const
hasMin
=
minTemp
!==
undefined
&&
minTemp
!==
null
&&
minTemp
!==
''
;
if
(
hasMax
||
hasMin
)
{
const
linkage
=
await
findEffectiveLinkage
(
deviceId
,
deviceType
);
if
(
linkage
)
{
const
edits
:
any
=
{};
if
(
hasMax
)
edits
.
threshold_max
=
String
(
maxTemp
);
if
(
hasMin
)
edits
.
threshold_min
=
String
(
minTemp
);
await
updateManyData
(
TABLENAME
.
设备联动调节表
,
{
id
:
linkage
.
id
},
edits
);
}
else
{
// 无生效配置:新增一条(有效期留空=长期生效)
const
season
=
getSeason
();
let
linkageType
=
''
;
if
(
deviceType
.
includes
(
'新风'
))
linkageType
=
'新风'
;
else
if
(
deviceType
.
includes
(
'空调'
))
linkageType
=
season
===
'summer'
?
'制冷'
:
'制热'
;
await
addData
(
TABLENAME
.
设备联动调节表
,
{
device_id
:
deviceId
,
linkage_type
:
linkageType
||
'制冷'
,
threshold_max
:
hasMax
?
String
(
maxTemp
)
:
null
,
threshold_min
:
hasMin
?
String
(
minTemp
)
:
null
,
});
}
}
// 2. 接入空调控制API,更新空调设备信息
// 2. 接入空调控制API,更新空调设备信息
let
acPrarms
=
{
let
acPrarms
=
{
"indoorUnitAddressFull"
:
[
deviceId
],
"indoorUnitAddressFull"
:
[
deviceId
],
...
@@ -2369,14 +2408,84 @@ export async function controlAcRunning(params: {}) {
...
@@ -2369,14 +2408,84 @@ export async function controlAcRunning(params: {}) {
"onOff"
:
powerMap
[
power
],
"onOff"
:
powerMap
[
power
],
"tempSet"
:
Number
(
setTemp
),
"tempSet"
:
Number
(
setTemp
),
"fanSpeed"
:
fanSpeedMap
[
fanSpeed
],
"fanSpeed"
:
fanSpeedMap
[
fanSpeed
],
"onOffLock"
:
lockMap
[
autoControl
],
//
"onOffLock": lockMap[autoControl],
"workModeLock"
:
lockMap
[
autoControl
],
//
"workModeLock": lockMap[autoControl],
"tempSetLock"
:
lockMap
[
autoControl
],
//
"tempSetLock": lockMap[autoControl],
"tempSetLo"
:
Number
(
minTemp
),
//
"tempSetLo": Number(minTemp),
"tempSetHi"
:
Number
(
maxTemp
),
//
"tempSetHi": Number(maxTemp),
"openApiAction"
:
autoControl
===
"生效"
?
"control"
:
"lock
"
"openApiAction"
:
"control
"
};
};
const
result
=
await
controlIndoorUnit
(
acPrarms
);
// 手动控制日志
const
now
=
new
Date
();
const
operationTime
=
new
Date
();
const
operationTimeStr
=
formatLocalTime
(
operationTime
);
const
acLogs
=
[{
device_id
:
deviceId
,
device_type
:
'空调'
,
operation_content
:
{
option
:
'手动控制'
,
device_id
:
deviceId
,
time
:
operationTimeStr
,
mode
:
mode
,
power
:
power
,
setTemp
:
setTemp
,
fanSpeed
:
fanSpeed
,
success
:
result
.
success
,
message
:
result
.
message
||
(
result
.
success
?
'控制成功'
:
'控制失败'
),
},
status
:
result
.
success
?
1
:
0
,
created_at
:
now
,
updated_at
:
now
,
}];
await
addData
(
TABLENAME
.
设备日志表
,
acLogs
);
return
await
controlIndoorUnit
(
acPrarms
);
return
await
controlIndoorUnit
(
acPrarms
);
}
}
/**
* 查找设备当前生效的联动配置(device_linkage 表)
* 规则:
* 1. 先过滤有效期:begin_validity 为空或 <= 当前时间,且 end_validity 为空或 >= 当前时间
* 2. 有效期内仅 1 条则直接返回
* 3. 多条时根据设备类型 + 当前季节匹配联动类型(空调:夏→制冷/冬→制热;新风→新风)
* 4. 无匹配返回 null
* @param deviceId 设备编号
* @param deviceType 设备类型(空调/新风等)
*/
async
function
findEffectiveLinkage
(
deviceId
:
string
,
deviceType
:
string
):
Promise
<
any
|
null
>
{
const
res
=
await
selectDataListByParam
(
TABLENAME
.
设备联动调节表
,
{
device_id
:
deviceId
},
[
"id"
,
"device_id"
,
"linkage_type"
,
"threshold_max"
,
"threshold_min"
,
"threshold_mid"
,
"setting_max"
,
"setting_min"
,
"setting_mid"
,
"fan_speed_max"
,
"fan_speed_mid"
,
"fan_speed_min"
,
"is_use"
,
"begin_validity"
,
"end_validity"
]
);
const
rows
:
any
[]
=
res
.
data
||
[];
if
(
rows
.
length
===
0
)
return
null
;
const
now
=
new
Date
();
// 过滤有效期 + 启用(is_use=on)的配置
const
valid
=
rows
.
filter
((
r
:
any
)
=>
{
if
(
r
.
is_use
===
'off'
)
return
false
;
const
begin
=
r
.
begin_validity
?
new
Date
(
r
.
begin_validity
)
:
null
;
const
end
=
r
.
end_validity
?
new
Date
(
r
.
end_validity
)
:
null
;
if
(
begin
&&
begin
>
now
)
return
false
;
if
(
end
&&
end
<
now
)
return
false
;
return
true
;
});
if
(
valid
.
length
===
0
)
return
null
;
if
(
valid
.
length
===
1
)
return
valid
[
0
];
// 多条:根据设备类型 + 当前季节匹配联动类型
const
season
=
getSeason
();
let
targetType
:
string
|
null
=
null
;
if
(
deviceType
.
includes
(
'新风'
))
targetType
=
'新风'
;
else
if
(
deviceType
.
includes
(
'空调'
))
targetType
=
season
===
'summer'
?
'制冷'
:
'制热'
;
const
matched
=
valid
.
find
((
r
:
any
)
=>
targetType
&&
r
.
linkage_type
===
targetType
);
return
matched
||
null
;
}
src/config/eccParam/admin.ts
View file @
a716b29d
...
@@ -24,7 +24,7 @@
...
@@ -24,7 +24,7 @@
* 将 Date 对象格式化为本地时间字符串 'YYYY-MM-DD HH:mm:ss'
* 将 Date 对象格式化为本地时间字符串 'YYYY-MM-DD HH:mm:ss'
* 替代 toISOString() 避免 UTC 时区偏移问题
* 替代 toISOString() 避免 UTC 时区偏移问题
*/
*/
export
function
formatLocalTime
(
date
:
Date
):
string
{
//
export function formatLocalTime(date: Date): string {
const
pad
=
(
n
:
number
)
=>
String
(
n
).
padStart
(
2
,
'0'
);
//
const pad = (n: number) => String(n).padStart(2, '0');
return
`
${
date
.
getFullYear
()}
-
${
pad
(
date
.
getMonth
()
+
1
)}
-
${
pad
(
date
.
getDate
())}
${
pad
(
date
.
getHours
())}
:
${
pad
(
date
.
getMinutes
())}
:
${
pad
(
date
.
getSeconds
())}
`
;
//
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
}
//
}
src/config/mysqlTableConfig.ts
View file @
a716b29d
...
@@ -197,6 +197,12 @@ export const TablesConfig = [
...
@@ -197,6 +197,12 @@ export const TablesConfig = [
defaultValue
:
0
,
defaultValue
:
0
,
comment
:
'设备状态:0=在线, 1=离线, 2=故障'
comment
:
'设备状态:0=在线, 1=离线, 2=故障'
},
},
linkage_start
:
{
type
:
DataTypes
.
STRING
(
10
),
allowNull
:
true
,
defaultValue
:
'off'
,
comment
:
'联动启用:on=开 off=关'
},
device_name
:
{
device_name
:
{
type
:
Sequelize
.
STRING
(
255
),
type
:
Sequelize
.
STRING
(
255
),
allowNull
:
false
,
allowNull
:
false
,
...
...
src/routers/admin.ts
View file @
a716b29d
...
@@ -6,10 +6,11 @@ import asyncHandler from "express-async-handler";
...
@@ -6,10 +6,11 @@ import asyncHandler from "express-async-handler";
import
*
as
regionBiz
from
"../biz/region"
;
import
*
as
regionBiz
from
"../biz/region"
;
import
*
as
scheduleBiz
from
"../biz/schedule"
;
import
*
as
scheduleBiz
from
"../biz/schedule"
;
import
*
as
deviceBiz
from
"../biz/device"
;
import
*
as
deviceBiz
from
"../biz/device"
;
import
*
as
alertWorkOrderBiz
from
"../biz/deviceFault"
;
import
*
as
deviceLinkageBiz
from
"../biz/deviceLinkage"
;
import
*
as
usersBiz
from
"../biz/users"
;
import
*
as
usersBiz
from
"../biz/users"
;
import
*
as
logBiz
from
"../biz/log"
;
import
*
as
logBiz
from
"../biz/log"
;
import
{
eccReqParamater
}
from
"../util/verificationParam"
;
import
{
eccReqParamater
}
from
"../util/verificationParam"
;
import
{
get
}
from
"http"
;
import
{
checkUser
}
from
"../tools/system"
;
import
{
checkUser
}
from
"../tools/system"
;
export
function
setRouter
(
httpServer
)
{
export
function
setRouter
(
httpServer
)
{
...
@@ -55,6 +56,22 @@ export function setRouter(httpServer) {
...
@@ -55,6 +56,22 @@ export function setRouter(httpServer) {
/** 设备管理 - 删除设备信息 */
/** 设备管理 - 删除设备信息 */
httpServer
.
post
(
"/api/dq/admin/device/deleteDevice"
,
checkUser
,
asyncHandler
(
deleteLocalDevice
));
httpServer
.
post
(
"/api/dq/admin/device/deleteDevice"
,
checkUser
,
asyncHandler
(
deleteLocalDevice
));
/** 设备联动参数 - 分页列表 */
httpServer
.
get
(
"/api/qdm/admin/linkage/list"
,
asyncHandler
(
getLinkageList
));
/** 设备联动参数 - 详情 */
httpServer
.
get
(
"/api/qdm/admin/linkage/detail"
,
asyncHandler
(
getLinkageDetail
));
/** 设备联动参数 - 新增 */
httpServer
.
post
(
"/api/qdm/admin/linkage/add"
,
asyncHandler
(
addLinkage
));
/** 设备联动参数 - 批量新增 */
httpServer
.
post
(
"/api/qdm/admin/linkage/add_batch"
,
asyncHandler
(
batchAddLinkage
));
/** 设备联动参数 - 编辑 */
httpServer
.
post
(
"/api/qdm/admin/linkage/edit"
,
asyncHandler
(
editLinkage
));
/** 设备联动参数 - 删除 */
httpServer
.
post
(
"/api/qdm/admin/linkage/delete"
,
asyncHandler
(
deleteLinkage
));
/** 预警工单列表 */
httpServer
.
get
(
"/api/qdm/admin/fault/list"
,
asyncHandler
(
getDeviceFaultList
));
/** 用户管理 - 列表 */
/** 用户管理 - 列表 */
httpServer
.
get
(
"/api/dq/admin/users/list"
,
checkUser
,
asyncHandler
(
getUsersList
));
httpServer
.
get
(
"/api/dq/admin/users/list"
,
checkUser
,
asyncHandler
(
getUsersList
));
/** 用户管理 - 详情 */
/** 用户管理 - 详情 */
...
@@ -490,6 +507,230 @@ async function deleteLocalDevice(req, res) {
...
@@ -490,6 +507,230 @@ async function deleteLocalDevice(req, res) {
res
.
success
(
data
);
res
.
success
(
data
);
}
}
// ======================= 设备联动参数管理 =======================
/**
* GET /api/qdm/admin/linkage/list
* 设备联动参数分页列表
* query: pageNumber, pageSize, deviceName(设备名称模糊匹配), linkageType(制冷/制热/新风)
*/
async
function
getLinkageList
(
req
,
res
)
{
const
data
=
await
deviceLinkageBiz
.
getLinkageList
({
pageNumber
:
Number
(
req
.
query
?.
pageNumber
)
||
1
,
pageSize
:
Number
(
req
.
query
?.
pageSize
)
||
10
,
deviceId
:
req
.
query
?.
deviceId
||
undefined
,
deviceName
:
req
.
query
?.
deviceName
||
undefined
,
linkageType
:
req
.
query
?.
linkageType
||
undefined
,
});
res
.
success
(
data
);
}
/**
* GET /api/qdm/admin/linkage/detail?id=1
* 设备联动参数详情
* query: id
*/
async
function
getLinkageDetail
(
req
,
res
)
{
const
id
=
Number
(
req
.
query
?.
id
);
if
(
!
id
)
{
res
.
error
(
"缺少必填参数:id"
);
return
;
}
const
data
=
await
deviceLinkageBiz
.
getLinkageDetail
(
id
);
if
(
data
.
isSuccess
)
{
res
.
success
(
data
.
data
);
}
else
{
res
.
error
(
data
.
msg
);
}
}
/**
* POST /api/qdm/admin/linkage/add
* 新增设备联动参数
* body: deviceId, linkageType, thresholdMax, thresholdMin, thresholdMid,
* settingMax, settingMin, settingMid, divideMethod, beginValidity, endValidity
*/
async
function
addLinkage
(
req
,
res
)
{
let
reqConf
=
{
deviceId
:
'String'
,
linkageType
:
'String'
,
thresholdMax
:
'String'
,
thresholdMin
:
'String'
,
thresholdMid
:
'String'
,
settingMax
:
'String'
,
settingMin
:
'String'
,
settingMid
:
'String'
,
fanSpeedMax
:
'String'
,
fanSpeedMid
:
'String'
,
fanSpeedMin
:
'String'
,
divideMethod
:
'String'
,
beginValidity
:
'Date'
,
endValidity
:
'Date'
,
};
const
NotMustHaveKeys
=
[
"thresholdMax"
,
"thresholdMin"
,
"thresholdMid"
,
"settingMax"
,
"settingMin"
,
"settingMid"
,
"fanSpeedMax"
,
"fanSpeedMid"
,
"fanSpeedMin"
,
"divideMethod"
,
"beginValidity"
,
"endValidity"
];
let
{
deviceId
,
linkageType
,
thresholdMax
,
thresholdMin
,
thresholdMid
,
settingMax
,
settingMin
,
settingMid
,
fanSpeedMax
,
fanSpeedMid
,
fanSpeedMin
,
divideMethod
,
beginValidity
,
endValidity
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
if
(
!
deviceId
||
!
linkageType
)
{
res
.
error
(
"缺少必填参数:deviceId, linkageType"
);
return
;
}
const
data
=
await
deviceLinkageBiz
.
addLinkage
({
deviceId
,
linkageType
,
thresholdMax
:
thresholdMax
??
undefined
,
thresholdMin
:
thresholdMin
??
undefined
,
thresholdMid
:
thresholdMid
??
undefined
,
settingMax
:
settingMax
??
undefined
,
settingMin
:
settingMin
??
undefined
,
settingMid
:
settingMid
??
undefined
,
fanSpeedMax
:
fanSpeedMax
??
undefined
,
fanSpeedMid
:
fanSpeedMid
??
undefined
,
fanSpeedMin
:
fanSpeedMin
??
undefined
,
divideMethod
:
divideMethod
??
undefined
,
beginValidity
:
beginValidity
??
undefined
,
endValidity
:
endValidity
??
undefined
,
});
res
.
success
(
data
);
}
/**
* POST /api/qdm/admin/linkage/add_batch
* 批量新增设备联动参数(为多台设备配置相同规则)
* body: deviceIds(数组), linkageType, thresholdMax, thresholdMin, thresholdMid,
* settingMax, settingMin, settingMid, divideMethod, beginValidity, endValidity
*/
async
function
batchAddLinkage
(
req
,
res
)
{
let
reqConf
=
{
deviceIds
:
'Array'
,
linkageType
:
'String'
,
thresholdMax
:
'String'
,
thresholdMin
:
'String'
,
thresholdMid
:
'String'
,
settingMax
:
'String'
,
settingMin
:
'String'
,
settingMid
:
'String'
,
fanSpeedMax
:
'String'
,
fanSpeedMid
:
'String'
,
fanSpeedMin
:
'String'
,
divideMethod
:
'String'
,
beginValidity
:
'Date'
,
endValidity
:
'Date'
,
};
const
NotMustHaveKeys
=
[
"thresholdMax"
,
"thresholdMin"
,
"thresholdMid"
,
"settingMax"
,
"settingMin"
,
"settingMid"
,
"fanSpeedMax"
,
"fanSpeedMid"
,
"fanSpeedMin"
,
"divideMethod"
,
"beginValidity"
,
"endValidity"
];
let
{
deviceIds
,
linkageType
,
thresholdMax
,
thresholdMin
,
thresholdMid
,
settingMax
,
settingMin
,
settingMid
,
fanSpeedMax
,
fanSpeedMid
,
fanSpeedMin
,
divideMethod
,
beginValidity
,
endValidity
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
if
(
!
deviceIds
||
!
Array
.
isArray
(
deviceIds
)
||
deviceIds
.
length
===
0
||
!
linkageType
)
{
res
.
error
(
"缺少必填参数:deviceIds, linkageType"
);
return
;
}
const
data
=
await
deviceLinkageBiz
.
batchAddLinkage
({
deviceIds
,
linkageType
,
thresholdMax
:
thresholdMax
??
undefined
,
thresholdMin
:
thresholdMin
??
undefined
,
thresholdMid
:
thresholdMid
??
undefined
,
settingMax
:
settingMax
??
undefined
,
settingMin
:
settingMin
??
undefined
,
settingMid
:
settingMid
??
undefined
,
fanSpeedMax
:
fanSpeedMax
??
undefined
,
fanSpeedMid
:
fanSpeedMid
??
undefined
,
fanSpeedMin
:
fanSpeedMin
??
undefined
,
divideMethod
:
divideMethod
??
undefined
,
beginValidity
:
beginValidity
??
undefined
,
endValidity
:
endValidity
??
undefined
,
});
res
.
success
(
data
);
}
/**
* POST /api/qdm/admin/linkage/edit
* 编辑设备联动参数
* body: id + 待更新字段(同新增)
*/
async
function
editLinkage
(
req
,
res
)
{
let
reqConf
=
{
id
:
'String'
,
deviceId
:
'String'
,
linkageType
:
'String'
,
thresholdMax
:
'String'
,
thresholdMin
:
'String'
,
thresholdMid
:
'String'
,
settingMax
:
'String'
,
settingMin
:
'String'
,
settingMid
:
'String'
,
fanSpeedMax
:
'String'
,
fanSpeedMid
:
'String'
,
fanSpeedMin
:
'String'
,
isUse
:
'String'
,
divideMethod
:
'String'
,
beginValidity
:
'Date'
,
endValidity
:
'Date'
,
};
const
NotMustHaveKeys
=
[
"deviceId"
,
"linkageType"
,
"thresholdMax"
,
"thresholdMin"
,
"thresholdMid"
,
"settingMax"
,
"settingMin"
,
"settingMid"
,
"fanSpeedMax"
,
"fanSpeedMid"
,
"fanSpeedMin"
,
"isUse"
,
"divideMethod"
,
"beginValidity"
,
"endValidity"
];
let
{
id
,
deviceId
,
linkageType
,
thresholdMax
,
thresholdMin
,
thresholdMid
,
settingMax
,
settingMin
,
settingMid
,
fanSpeedMax
,
fanSpeedMid
,
fanSpeedMin
,
isUse
,
divideMethod
,
beginValidity
,
endValidity
}
=
eccReqParamater
(
reqConf
,
req
.
body
,
NotMustHaveKeys
);
if
(
!
id
)
{
res
.
error
(
"缺少必填参数:id"
);
return
;
}
const
data
=
await
deviceLinkageBiz
.
editLinkage
({
id
:
Number
(
id
),
deviceId
:
deviceId
??
undefined
,
linkageType
:
linkageType
??
undefined
,
thresholdMax
:
thresholdMax
??
undefined
,
thresholdMin
:
thresholdMin
??
undefined
,
thresholdMid
:
thresholdMid
??
undefined
,
settingMax
:
settingMax
??
undefined
,
settingMin
:
settingMin
??
undefined
,
settingMid
:
settingMid
??
undefined
,
fanSpeedMax
:
fanSpeedMax
??
undefined
,
fanSpeedMid
:
fanSpeedMid
??
undefined
,
fanSpeedMin
:
fanSpeedMin
??
undefined
,
isUse
:
isUse
??
undefined
,
divideMethod
:
divideMethod
??
undefined
,
beginValidity
:
beginValidity
??
undefined
,
endValidity
:
endValidity
??
undefined
,
});
res
.
success
(
data
);
}
/**
* POST /api/qdm/admin/linkage/delete
* 删除设备联动参数
* body: id
*/
async
function
deleteLinkage
(
req
,
res
)
{
const
{
id
}
=
req
.
body
||
{};
if
(
!
id
)
{
res
.
error
(
"缺少必填参数:id"
);
return
;
}
const
data
=
await
deviceLinkageBiz
.
deleteLinkage
(
Number
(
id
));
res
.
success
(
data
);
}
// ======================= 预警工单 =======================
/**
* GET /api/qdm/admin/alertWorkOrder/list
* 预警工单列表(分页)
* query: pageNumber, pageSize, level, fault_code, device_id, startTime, endTime
*/
async
function
getDeviceFaultList
(
req
,
res
)
{
const
pageNumber
=
Number
(
req
.
query
?.
pageNumber
)
||
1
;
const
pageSize
=
Number
(
req
.
query
?.
pageSize
)
||
10
;
const
data
=
await
alertWorkOrderBiz
.
getDeviceFaultList
({
pageNumber
,
pageSize
,
level
:
req
.
query
?.
level
?
Number
(
req
.
query
.
level
)
:
undefined
,
fault_code
:
req
.
query
?.
fault_code
||
undefined
,
device_id
:
req
.
query
?.
device_id
||
undefined
,
startTime
:
req
.
query
?.
startTime
||
undefined
,
endTime
:
req
.
query
?.
endTime
||
undefined
,
});
res
.
success
(
data
);
}
// ======================= 用户管理 =======================
// ======================= 用户管理 =======================
/**
/**
...
...
src/routers/running.ts
View file @
a716b29d
...
@@ -113,10 +113,10 @@ async function controlDeviceRunning(req, res) {
...
@@ -113,10 +113,10 @@ async function controlDeviceRunning(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'
,
regionKey
:
"String"
};
let
reqConf
=
{
deviceId
:
'String'
,
mode
:
'String'
,
power
:
'String'
,
setTemp
:
'String'
,
maxTemp
:
'String'
,
minTemp
:
'String'
,
autoControl
:
'String'
,
fanSpeed
:
'String'
};
const
NotMustHaveKeys
=
[
'
power'
,
'maxTemp'
,
'minTemp'
,
'autoControl'
,
'regionKey
'
];
const
NotMustHaveKeys
=
[
'
mode'
,
'power'
,
'setTemp'
,
'maxTemp'
,
'minTemp'
,
'autoControl'
,
'fanSpeed
'
];
let
{
deviceId
,
mode
,
power
,
setTemp
,
maxTemp
,
minTemp
,
autoControl
,
fanSpeed
,
regionKey
}
=
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
,
regionKey
};
let
params
=
{
deviceId
,
mode
,
power
,
setTemp
,
maxTemp
,
minTemp
,
autoControl
,
fanSpeed
};
//
const result = await runningBiz.controlAcRunning(params);
const
result
=
await
runningBiz
.
controlAcRunning
(
params
);
res
.
success
({
success
:
true
});
res
.
success
({
success
:
true
});
}
}
\ No newline at end of file
src/tools/systemTools.ts
View file @
a716b29d
...
@@ -73,3 +73,18 @@ export function getFileType(fileName) {
...
@@ -73,3 +73,18 @@ export function getFileType(fileName) {
return
fileType
;
return
fileType
;
}
}
/**
* 将 Date 对象格式化为本地时间字符串 'YYYY-MM-DD HH:mm:ss'
* 替代 toISOString() 避免 UTC 时区偏移问题
*/
export
function
formatLocalTime
(
date
:
Date
):
string
{
const
pad
=
(
n
:
number
)
=>
String
(
n
).
padStart
(
2
,
'0'
);
return
`
${
date
.
getFullYear
()}
-
${
pad
(
date
.
getMonth
()
+
1
)}
-
${
pad
(
date
.
getDate
())}
${
pad
(
date
.
getHours
())}
:
${
pad
(
date
.
getMinutes
())}
:
${
pad
(
date
.
getSeconds
())}
`
;
}
// 判断当前季节:5-9月为夏季(制冷),其余为冬季(制热)
export
function
getSeason
():
'summer'
|
'winter'
{
const
month
=
new
Date
().
getMonth
()
+
1
;
return
(
month
>=
5
&&
month
<=
9
)
?
'summer'
:
'winter'
;
}
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