设备联动接口

parent 42987c2f
...@@ -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 };
// } }
} }
/** /**
......
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,
};
}
/**
* 设备联动调节 业务逻辑(后台管理配置联动参数)
*
* 联动逻辑与 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 };
}
...@@ -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;
}
...@@ -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())}`;
} // }
...@@ -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,
......
...@@ -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);
}
// ======================= 用户管理 ======================= // ======================= 用户管理 =======================
/** /**
......
...@@ -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
...@@ -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';
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment