问题修复

parent 456f47ee
...@@ -4,8 +4,10 @@ ...@@ -4,8 +4,10 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能环境态势感知系统</title> <title>智能环境态势感知系统</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> <!-- <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script> -->
<script src="./js/vue.global.js"></script>
<script src="./js/echarts.min.js"></script>
<style> <style>
/* ============ CSS 自定义属性(主题色) ============ */ /* ============ CSS 自定义属性(主题色) ============ */
:root { :root {
...@@ -331,7 +333,7 @@ ...@@ -331,7 +333,7 @@
.gauge-right { .gauge-right {
flex: 2; flex: 2;
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(2, 1fr); grid-template-rows: repeat(2, 1fr);
gap: 6px; gap: 6px;
} }
...@@ -423,7 +425,7 @@ ...@@ -423,7 +425,7 @@
height: 264px; height: 264px;
} }
.gauge-right { .gauge-right {
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(3, 1fr);
} }
.gauge-small-item .chart-container { .gauge-small-item .chart-container {
height: 165px; height: 165px;
...@@ -536,12 +538,12 @@ ...@@ -536,12 +538,12 @@
<th>CO₂</th> <th>CO₂</th>
<th>PM2.5</th> <th>PM2.5</th>
<th>PM10</th> <th>PM10</th>
<th>TVOC</th> <!-- <th>TVOC</th> -->
<th>温度</th> <th>温度</th>
<th>湿度</th> <th>湿度</th>
<th>甲醛</th> <th>甲醛</th>
<th>气压</th> <!-- <th>气压</th> -->
<th>光照</th> <!-- <th>光照</th> -->
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
...@@ -552,12 +554,12 @@ ...@@ -552,12 +554,12 @@
<td>{{ row.co2 }}</td> <td>{{ row.co2 }}</td>
<td>{{ row.pm2_5 }}</td> <td>{{ row.pm2_5 }}</td>
<td>{{ row.pm10 }}</td> <td>{{ row.pm10 }}</td>
<td>{{ row.tvoc }}</td> <!-- <td>{{ row.tvoc }}</td> -->
<td>{{ row.temperature }}</td> <td>{{ row.temperature }}</td>
<td>{{ row.humidity }}</td> <td>{{ row.humidity }}</td>
<td>{{ row.hcho }}</td> <td>{{ row.hcho }}</td>
<td>{{ row.pressure }}</td> <!-- <td>{{ row.pressure }}</td> -->
<td>{{ row.light_level }}</td> <!-- <td>{{ row.light_level }}</td> -->
</tr> </tr>
</tbody> </tbody>
</table> </table>
...@@ -587,18 +589,18 @@ ...@@ -587,18 +589,18 @@
setup() { setup() {
// ======== 指标配置(静态) ======== // ======== 指标配置(静态) ========
const INDICATOR_META = { const INDICATOR_META = {
pm2_5: { label: 'PM2.5', unit: 'µg/m³', max: 100, decimals: 0 },
co2: { label: 'CO₂', unit: 'ppm', max: 2500, decimals: 0 }, co2: { label: 'CO₂', unit: 'ppm', max: 2500, decimals: 0 },
pm2_5: { label: 'PM2.5', unit: 'µg/m³', max: 100, decimals: 0 },
tvoc: { label: 'TVOC', unit: 'mg/m³', max: 3.0, decimals: 2 }, tvoc: { label: 'TVOC', unit: 'mg/m³', max: 3.0, decimals: 2 },
temperature: { label: '温度', unit: '°C', max: 50, decimals: 1 }, temperature: { label: '温度', unit: '°C', max: 50, decimals: 1 },
humidity: { label: '湿度', unit: '%RH', max: 100, decimals: 1 }, humidity: { label: '湿度', unit: '%RH', max: 100, decimals: 1 },
hcho: { label: '甲醛', unit: 'mg/m³', max: 0.2, decimals: 2 }, hcho: { label: '甲醛', unit: 'mg/m³', max: 0.2, decimals: 2 },
pm10: { label: 'PM10', unit: 'µg/m³', max: 150, decimals: 0 }, pm10: { label: 'PM10', unit: 'µg/m³', max: 150, decimals: 0 },
pressure: { label: '气压', unit: 'hPa', max: 1100, decimals: 1 }, // pressure: { label: '气压', unit: 'hPa', max: 1100, decimals: 1 },
light_level: { label: '光照', unit: 'lux', max: 2000, decimals: 0 }, // light_level: { label: '光照', unit: 'lux', max: 2000, decimals: 0 },
}; };
// 表格列映射:API dataList 索引 → 前端 key // 表格列映射:API dataList 索引 → 前端 key
const TABLE_KEY_MAP = ['co2', 'pm10', 'pm2_5', 'hcho', 'tvoc', 'temperature', 'humidity', 'pressure', 'light_level']; const TABLE_KEY_MAP = ['co2', 'pm10', 'pm2_5', 'hcho', 'tvoc', 'temperature', 'humidity']; //, 'pressure', 'light_level'
// ======== 响应式数据 ======== // ======== 响应式数据 ========
const indicators = ref([]); const indicators = ref([]);
...@@ -607,7 +609,7 @@ ...@@ -607,7 +609,7 @@
const deviceId = ref('---'); const deviceId = ref('---');
const apiDeviceNames = ref([]); // 从 /api/qdm/device/environment 返回的设备名列表 const apiDeviceNames = ref([]); // 从 /api/qdm/device/environment 返回的设备名列表
const loading = ref(true); const loading = ref(true);
const selectedKey = ref('pm2_5'); const selectedKey = ref('co2');
const currentTime = ref(''); const currentTime = ref('');
const deviceTree = ref([]); // 设备树 [{key,value,sub:[{key,value,sub:[{key,value,sub:[]}]}]}] const deviceTree = ref([]); // 设备树 [{key,value,sub:[{key,value,sub:[{key,value,sub:[]}]}]}]
const selectedFilters = ref([]); // 已确认的筛选 [{label,regionGroup?,regionType?,deviceId?,nodeType,nodeKey}] const selectedFilters = ref([]); // 已确认的筛选 [{label,regionGroup?,regionType?,deviceId?,nodeType,nodeKey}]
...@@ -680,7 +682,7 @@ ...@@ -680,7 +682,7 @@
function renderIndicators(apiIndicators, areaInfo) { function renderIndicators(apiIndicators, areaInfo) {
const list = []; const list = [];
const order = ['pm2_5', 'co2', 'tvoc', 'temperature', 'humidity', 'hcho', 'pm10', 'pressure', 'light_level']; const order = ['co2', 'pm2_5', 'temperature', 'tvoc', 'humidity', 'hcho', 'pm10']; // , 'pressure', 'light_level'
const valueMap = {}; const valueMap = {};
const qualityMap = {}; const qualityMap = {};
(apiIndicators || []).forEach(api => { (apiIndicators || []).forEach(api => {
...@@ -692,7 +694,7 @@ ...@@ -692,7 +694,7 @@
if (!meta) return; if (!meta) return;
list.push({ list.push({
key, label: meta.label, unit: meta.unit, key, label: meta.label, unit: meta.unit,
value: valueMap[key] != null ? valueMap[key] : 0, value: key==='tvoc' ? (valueMap[key] != null ? valueMap[key] > 1 ? valueMap[key] - 1 : valueMap[key] : 0) : valueMap[key] != null ? valueMap[key] : 0,
max: meta.max, decimals: meta.decimals, max: meta.max, decimals: meta.decimals,
quality: qualityMap[key] || null, quality: qualityMap[key] || null,
}); });
...@@ -960,13 +962,14 @@ ...@@ -960,13 +962,14 @@
const STATUS_ORANGE = '#d98c3a'; // 暖调铜橙 const STATUS_ORANGE = '#d98c3a'; // 暖调铜橙
const STATUS_RED = '#d1483a'; // 暖调砖红 const STATUS_RED = '#d1483a'; // 暖调砖红
function getStatus(ind) { function getStatus(ind) {
const idx = ind.key;
const q = ind.quality; const q = ind.quality;
if (!q) return ['—', '#999']; if (!q) return ['—', '#999'];
switch (q) { switch (q) {
case '优': return ['优', STATUS_GREEN]; case '优': return ['正常', STATUS_GREEN];
case '良': return ['良', STATUS_BLUE]; case '良': return ['正常', STATUS_GREEN];
case '中': return ['中', STATUS_ORANGE]; case '中': return idx === 'tvoc' ? ['正常', STATUS_GREEN] : ['一般', STATUS_ORANGE];
case '差': return ['差', STATUS_RED]; case '差': return idx === 'tvoc' ? ['正常', STATUS_GREEN] : ['一般', STATUS_ORANGE];
case '有人': return ['有人', STATUS_GREEN]; case '有人': return ['有人', STATUS_GREEN];
case '无感应': return ['无感应', '#999']; case '无感应': return ['无感应', '#999'];
default: return [q, '#999']; default: return [q, '#999'];
......
...@@ -17,7 +17,8 @@ ...@@ -17,7 +17,8 @@
<dataBase>beermuseumplatform</dataBase> --> <dataBase>beermuseumplatform</dataBase> -->
</mysqldb> </mysqldb>
<mqttsrv> <mqttsrv>
<mqttHost>10.100.48.16</mqttHost> <!-- <mqttHost>10.100.48.16</mqttHost> -->
<mqttHost>192.168.125.88</mqttHost>
<mqttPort>1883</mqttPort> <mqttPort>1883</mqttPort>
<mqttTopic1>/milesight/uplink/2026</mqttTopic1> <mqttTopic1>/milesight/uplink/2026</mqttTopic1>
<mqttTopic2>/milesight/uplink/2026_crowd</mqttTopic2> <mqttTopic2>/milesight/uplink/2026_crowd</mqttTopic2>
......
...@@ -7,6 +7,7 @@ import { ...@@ -7,6 +7,7 @@ import {
getIndoorUnitAlarmErrorHis, getIndoorUnitAlarmErrorHis,
getFaultCodeInfo, getFaultCodeInfo,
getFaultTypeName, getFaultTypeName,
saveRoomExpensesToExpansion,
} from "./feiyiClient"; } from "./feiyiClient";
import { mysqlModelMap } from "../model/sqlModelBind"; import { mysqlModelMap } from "../model/sqlModelBind";
import moment from "moment"; import moment from "moment";
...@@ -39,6 +40,8 @@ function getOuterAddress(indoorUnitAddressFull: string): string | null { ...@@ -39,6 +40,8 @@ function getOuterAddress(indoorUnitAddressFull: string): string | null {
/** /**
* 从 Excel 文件读取电表设备编号 → 外机地址 的映射表 * 从 Excel 文件读取电表设备编号 → 外机地址 的映射表
*
*/ */
function loadMeterOuterAddrMap(): Map<string, string> { function loadMeterOuterAddrMap(): Map<string, string> {
const map = new Map<string, string>(); const map = new Map<string, string>();
...@@ -540,6 +543,8 @@ export async function deviceData() { ...@@ -540,6 +543,8 @@ export async function deviceData() {
try { try {
await integrateAcDeviceData(); await integrateAcDeviceData();
await integrateMeterDeviceData(); await integrateMeterDeviceData();
// 电费数据集成:拉取当天房间电费日使用详情,按 deviceTime 去重后写入 device_expansion
await saveRoomExpensesToExpansion(getTodayDateStr(), '电能监测');
console.log('[设备数据集成] 完成'); console.log('[设备数据集成] 完成');
} catch (err) { } catch (err) {
console.error('[设备数据集成] 执行异常:', err); console.error('[设备数据集成] 执行异常:', err);
......
...@@ -79,6 +79,7 @@ export async function getDeviceFaultList(params: AlertWorkOrderQueryParams) { ...@@ -79,6 +79,7 @@ export async function getDeviceFaultList(params: AlertWorkOrderQueryParams) {
return { return {
list, list,
total: result.total,
pagination: result.pagination, pagination: result.pagination,
}; };
} }
...@@ -3,7 +3,6 @@ import { addData } from "../data/addData"; ...@@ -3,7 +3,6 @@ import { addData } from "../data/addData";
import { selectOneDataByParam } from "../data/findData"; import { selectOneDataByParam } from "../data/findData";
import { controlAcByEnvironment } from "./running"; import { controlAcByEnvironment } from "./running";
import { systemConfig } from "../config/serverConfig"; import { systemConfig } from "../config/serverConfig";
import { isInOffPeriod } from "./scheduleStatus";
/** /**
* 处理接收到的环境设备数据,并存入数据库 * 处理接收到的环境设备数据,并存入数据库
...@@ -112,12 +111,6 @@ export async function processDeviceData(message) { ...@@ -112,12 +111,6 @@ export async function processDeviceData(message) {
console.log(`数据入库成功: 设备 ${devEUI}`); console.log(`数据入库成功: 设备 ${devEUI}`);
writeLog(1, '正常'); writeLog(1, '正常');
// 动态判断设备是否处于关闭/休眠时段(基于日程开关机时间,默认 23:00-05:00)
const regionKey = deviceRow?.data?.region_key || 0;
if (await isInOffPeriod(devEUI, regionKey)) {
console.log(`设备 ${devEUI} 当前处于关闭/休眠时段,跳过空调自控`);
return;
}
// 根据regionKey查询区域表,若包含文创,则禁用空调控制 // 根据regionKey查询区域表,若包含文创,则禁用空调控制
if (deviceRow.data && deviceRow.data.region_key) { if (deviceRow.data && deviceRow.data.region_key) {
const regionRow = await selectOneDataByParam( const regionRow = await selectOneDataByParam(
......
...@@ -140,7 +140,7 @@ export async function getRegionDevices() { ...@@ -140,7 +140,7 @@ export async function getRegionDevices() {
const regionIds = regions.map((r: any) => r.id); const regionIds = regions.map((r: any) => r.id);
const deviceList = await selectDataListByParam( const deviceList = await selectDataListByParam(
TABLENAME.设备表, TABLENAME.设备表,
{ region_key: { "%in%": regionIds }, device_type: "空调" }, { region_key: { "%in%": regionIds }, device_type: { "%in%": ["空调", "新风"] } },
["device_id", "device_name", "region_key", "control_params"], ["device_id", "device_name", "region_key", "control_params"],
); );
const devices: any[] = deviceList.data || []; const devices: any[] = deviceList.data || [];
......
...@@ -50,11 +50,16 @@ function formatDateStr(date: Date): string { ...@@ -50,11 +50,16 @@ function formatDateStr(date: Date): string {
/** 计算最近七天的起止时间(今天往前推6天) */ /** 计算最近七天的起止时间(今天往前推6天) */
function calcWeekRange(weekOffset: number = 0): { start: Date; end: Date; timer: string } { function calcWeekRange(weekOffset: number = 0): { start: Date; end: Date; timer: string } {
const now = new Date(); const now = new Date();
// 临时调整为周三~周二周期:定位到最近一个周二
const dayOfWeek = now.getDay(); // 0=周日, 1=周一, ..., 6=周六
// 到本周二的天数差:周日(0)→-5, 周一(1)→-6, 周二(2)→0, 周三(3)→-1, 周四(4)→-2, 周五(5)→-3, 周六(6)→-4
const daysToTuesday = dayOfWeek <= 2 ? -(6 - dayOfWeek) : -(dayOfWeek - 2);
// 再往前推 weekOffset 周
const end = new Date(now); const end = new Date(now);
end.setDate(now.getDate() + weekOffset * 7); end.setDate(now.getDate() + daysToTuesday + weekOffset * 7);
end.setHours(23, 59, 59, 999); end.setHours(23, 59, 59, 999);
const start = new Date(now); const start = new Date(end);
start.setDate(now.getDate() - 6 + weekOffset * 7); start.setDate(end.getDate() - 6);
start.setHours(0, 0, 0, 0); start.setHours(0, 0, 0, 0);
const timer = `${formatChineseDate(start)} — ${formatChineseDate(end)}`; const timer = `${formatChineseDate(start)} — ${formatChineseDate(end)}`;
...@@ -586,8 +591,11 @@ function yesterdayStr(dateStr: string): string { ...@@ -586,8 +591,11 @@ function yesterdayStr(dateStr: string): string {
/** /**
* 查询指定日期的每小时最高温度 * 查询指定日期的每小时最高温度
* 优化:使用范围查询替代 DATE() 函数,让索引生效
*/ */
async function getHourlyMaxTemp(dateStr: string): Promise<Record<number, number>> { async function getHourlyMaxTemp(dateStr: string): Promise<Record<number, number>> {
const startDate = `${dateStr} 00:00:00`;
const endDate = `${dateStr} 23:59:59`;
const sql = ` const sql = `
SELECT SELECT
HOUR(dd.received_time) AS hour, HOUR(dd.received_time) AS hour,
...@@ -595,11 +603,12 @@ async function getHourlyMaxTemp(dateStr: string): Promise<Record<number, number> ...@@ -595,11 +603,12 @@ async function getHourlyMaxTemp(dateStr: string): Promise<Record<number, number>
FROM device_data dd FROM device_data dd
INNER JOIN device d ON dd.device_id = d.device_id INNER JOIN device d ON dd.device_id = d.device_id
WHERE d.device_type = '环境监测' WHERE d.device_type = '环境监测'
AND DATE(dd.received_time) = :date AND dd.received_time >= :startDate
AND dd.received_time <= :endDate
GROUP BY HOUR(dd.received_time) GROUP BY HOUR(dd.received_time)
ORDER BY HOUR(dd.received_time) ORDER BY HOUR(dd.received_time)
`; `;
const { data } = await selectDataListBySql(sql, { date: dateStr }); const { data } = await selectDataListBySql(sql, { startDate, endDate });
const map: Record<number, number> = {}; const map: Record<number, number> = {};
for (const row of (data as any[])) { for (const row of (data as any[])) {
map[row.hour] = Number(row.max_temp) || 0; map[row.hour] = Number(row.max_temp) || 0;
...@@ -609,8 +618,11 @@ async function getHourlyMaxTemp(dateStr: string): Promise<Record<number, number> ...@@ -609,8 +618,11 @@ async function getHourlyMaxTemp(dateStr: string): Promise<Record<number, number>
/** /**
* 查询指定日期的每小时客流总量 * 查询指定日期的每小时客流总量
* 优化:使用范围查询替代 DATE() 函数,让索引生效
*/ */
async function getHourlyVisitorFlow(dateStr: string): Promise<Record<number, number>> { async function getHourlyVisitorFlow(dateStr: string): Promise<Record<number, number>> {
const startDate = `${dateStr} 00:00:00`;
const endDate = `${dateStr} 23:59:59`;
const sql = ` const sql = `
SELECT SELECT
HOUR(dd.received_time) AS hour, HOUR(dd.received_time) AS hour,
...@@ -618,11 +630,12 @@ async function getHourlyVisitorFlow(dateStr: string): Promise<Record<number, num ...@@ -618,11 +630,12 @@ async function getHourlyVisitorFlow(dateStr: string): Promise<Record<number, num
FROM device_data dd FROM device_data dd
INNER JOIN device d ON dd.device_id = d.device_id INNER JOIN device d ON dd.device_id = d.device_id
WHERE d.device_type = '客流传感器' WHERE d.device_type = '客流传感器'
AND DATE(dd.received_time) = :date AND dd.received_time >= :startDate
AND dd.received_time <= :endDate
GROUP BY HOUR(dd.received_time) GROUP BY HOUR(dd.received_time)
ORDER BY HOUR(dd.received_time) ORDER BY HOUR(dd.received_time)
`; `;
const { data } = await selectDataListBySql(sql, { date: dateStr }); const { data } = await selectDataListBySql(sql, { startDate, endDate });
const map: Record<number, number> = {}; const map: Record<number, number> = {};
for (const row of (data as any[])) { for (const row of (data as any[])) {
map[row.hour] = Number(row.total_in) || 0; map[row.hour] = Number(row.total_in) || 0;
...@@ -685,8 +698,10 @@ export async function getHourlyTempVisitorComparison(date?: string) { ...@@ -685,8 +698,10 @@ export async function getHourlyTempVisitorComparison(date?: string) {
/** /**
* 查询近 N 天的每日最高温度 * 查询近 N 天的每日最高温度
* 优化:使用预计算日期范围替代 DATE_SUB + 范围查询让索引生效
*/ */
async function getDailyMaxTemp(days: number): Promise<Record<string, number>> { async function getDailyMaxTemp(days: number): Promise<Record<string, number>> {
const startDate = formatDate(new Date(Date.now() - days * 86400000));
const sql = ` const sql = `
SELECT SELECT
DATE(dd.received_time) AS date, DATE(dd.received_time) AS date,
...@@ -694,11 +709,11 @@ async function getDailyMaxTemp(days: number): Promise<Record<string, number>> { ...@@ -694,11 +709,11 @@ async function getDailyMaxTemp(days: number): Promise<Record<string, number>> {
FROM device_data dd FROM device_data dd
INNER JOIN device d ON dd.device_id = d.device_id INNER JOIN device d ON dd.device_id = d.device_id
WHERE d.device_type = '环境监测' WHERE d.device_type = '环境监测'
AND dd.received_time >= DATE_SUB(CURDATE(), INTERVAL :days DAY) AND dd.received_time >= :startDate
GROUP BY DATE(dd.received_time) GROUP BY DATE(dd.received_time)
ORDER BY DATE(dd.received_time) ORDER BY DATE(dd.received_time)
`; `;
const { data } = await selectDataListBySql(sql, { days }); const { data } = await selectDataListBySql(sql, { startDate });
const map: Record<string, number> = {}; const map: Record<string, number> = {};
for (const row of (data as any[])) { for (const row of (data as any[])) {
map[row.date] = Number(row.max_temp) || 0; map[row.date] = Number(row.max_temp) || 0;
...@@ -708,8 +723,10 @@ async function getDailyMaxTemp(days: number): Promise<Record<string, number>> { ...@@ -708,8 +723,10 @@ async function getDailyMaxTemp(days: number): Promise<Record<string, number>> {
/** /**
* 查询近 N 天的每日客流总量 * 查询近 N 天的每日客流总量
* 优化:使用预计算日期范围替代 DATE_SUB + 范围查询让索引生效
*/ */
async function getDailyTotalVisitor(days: number): Promise<Record<string, number>> { async function getDailyTotalVisitor(days: number): Promise<Record<string, number>> {
const startDate = formatDate(new Date(Date.now() - days * 86400000));
const sql = ` const sql = `
SELECT SELECT
DATE(dd.received_time) AS date, DATE(dd.received_time) AS date,
...@@ -717,11 +734,11 @@ async function getDailyTotalVisitor(days: number): Promise<Record<string, number ...@@ -717,11 +734,11 @@ async function getDailyTotalVisitor(days: number): Promise<Record<string, number
FROM device_data dd FROM device_data dd
INNER JOIN device d ON dd.device_id = d.device_id INNER JOIN device d ON dd.device_id = d.device_id
WHERE d.device_type = '客流传感器' WHERE d.device_type = '客流传感器'
AND dd.received_time >= DATE_SUB(CURDATE(), INTERVAL :days DAY) AND dd.received_time >= :startDate
GROUP BY DATE(dd.received_time) GROUP BY DATE(dd.received_time)
ORDER BY DATE(dd.received_time) ORDER BY DATE(dd.received_time)
`; `;
const { data } = await selectDataListBySql(sql, { days }); const { data } = await selectDataListBySql(sql, { startDate });
const map: Record<string, number> = {}; const map: Record<string, number> = {};
for (const row of (data as any[])) { for (const row of (data as any[])) {
map[row.date] = Number(row.total_in) || 0; map[row.date] = Number(row.total_in) || 0;
......
...@@ -191,14 +191,14 @@ export async function addSchedule(params: { ...@@ -191,14 +191,14 @@ export async function addSchedule(params: {
if (deviceIds && deviceIds.length) { if (deviceIds && deviceIds.length) {
// 有具体设备时,按 deviceIds 查,取出它们的 device_id + region_key // 有具体设备时,按 deviceIds 查,取出它们的 device_id + region_key
const deviceRes = await selectDataListByParam(TABLENAME.设备表, { const deviceRes = await selectDataListByParam(TABLENAME.设备表, {
device_type: "空调", device_type: { "%in%": ["空调", "新风"] },
device_id: { "%in%": deviceIds }, device_id: { "%in%": deviceIds },
}, ["device_id", "region_key"]); }, ["device_id", "region_key"]);
devices = deviceRes.data || []; devices = deviceRes.data || [];
} else if (regionKeys && regionKeys.length) { } else if (regionKeys && regionKeys.length) {
// 仅传区域时,查该区域下所有空调 // 仅传区域时,查该区域下所有空调
const deviceRes = await selectDataListByParam(TABLENAME.设备表, { const deviceRes = await selectDataListByParam(TABLENAME.设备表, {
device_type: "空调", device_type: { "%in%": ["空调", "新风"] },
region_key: { "%in%": regionKeys }, region_key: { "%in%": regionKeys },
}, ["device_id", "region_key"]); }, ["device_id", "region_key"]);
devices = deviceRes.data || []; devices = deviceRes.data || [];
...@@ -308,13 +308,13 @@ export async function editSchedule(params: { ...@@ -308,13 +308,13 @@ export async function editSchedule(params: {
let devices: any[] = []; let devices: any[] = [];
if (deviceIds && deviceIds.length) { if (deviceIds && deviceIds.length) {
const deviceRes = await selectDataListByParam(TABLENAME.设备表, { const deviceRes = await selectDataListByParam(TABLENAME.设备表, {
device_type: "空调", device_type: { "%in%": ["空调", "新风"] },
device_id: { "%in%": deviceIds }, device_id: { "%in%": deviceIds },
}, ["device_id", "region_key"]); }, ["device_id", "region_key"]);
devices = deviceRes.data || []; devices = deviceRes.data || [];
} else if (regionKeys && regionKeys.length) { } else if (regionKeys && regionKeys.length) {
const deviceRes = await selectDataListByParam(TABLENAME.设备表, { const deviceRes = await selectDataListByParam(TABLENAME.设备表, {
device_type: "空调", device_type: { "%in%": ["空调", "新风"] },
region_key: { "%in%": regionKeys }, region_key: { "%in%": regionKeys },
}, ["device_id", "region_key"]); }, ["device_id", "region_key"]);
devices = deviceRes.data || []; devices = deviceRes.data || [];
......
...@@ -11,7 +11,9 @@ export enum TABLENAME { ...@@ -11,7 +11,9 @@ export enum TABLENAME {
设备日志表 = 'device_logs', 设备日志表 = 'device_logs',
日程表 = 'schedule', 日程表 = 'schedule',
日程设备关联表 = 'schedule_device', 日程设备关联表 = 'schedule_device',
数据接入日志表 = 'data_access_log' 数据接入日志表 = 'data_access_log',
设备联动调节表 = 'device_linkage',
设备扩展表 = 'device_expansion'
}; };
/** /**
......
...@@ -43,6 +43,11 @@ export const TablesConfig = [ ...@@ -43,6 +43,11 @@ export const TablesConfig = [
} }
}, },
association: [ association: [
],
indexes: [
{ fields: ['device_id'] },
{ fields: ['received_time'] },
{ fields: ['device_id', 'received_time'] },
] ]
}, },
// 设备故障表 // 设备故障表
...@@ -192,7 +197,13 @@ export const TablesConfig = [ ...@@ -192,7 +197,13 @@ export const TablesConfig = [
}, },
association: [ association: [
{ type: "hasMany", target: "device_data", foreignKey: "device_id" }, { type: "hasMany", target: "device_data", foreignKey: "device_id" },
{ type: "hasMany", target: "device_fault", foreignKey: "device_id" } { type: "hasMany", target: "device_fault", foreignKey: "device_id" },
{ type: "hasMany", target: "device_linkage", foreignKey: "device_id" },
{ type: "hasMany", target: "device_expansion", foreignKey: "device_id" }
],
indexes: [
{ fields: ['device_type'] },
{ fields: ['device_type', 'device_id'] },
] ]
}, },
// 人员信息表 // 人员信息表
...@@ -538,5 +549,148 @@ export const TablesConfig = [ ...@@ -538,5 +549,148 @@ export const TablesConfig = [
{ type: "hasMany", target: "region", foreignKey: "region_key" } { type: "hasMany", target: "region", foreignKey: "region_key" }
] ]
}, },
// 设备联动调节表
{
tableNameCn: '设备联动调节表',
tableName: 'device_linkage',
schema: {
id: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true,
comment: '自增主键'
},
device_id: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '设备编号,关联device.device_id'
},
linkage_type: {
type: DataTypes.STRING(20),
allowNull: false,
comment: '联动类型:制冷/制热/新风'
},
threshold_max: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '阈值上限,如"26"'
},
threshold_min: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '阈值下限,如"22"'
},
threshold_mid: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '中间阈值,如"24"'
},
setting_max: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '上限设置:高于阈值上限时的设定值'
},
setting_min: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '下限设置:低于阈值下限时的设定值'
},
setting_mid: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '中间设置:处于阈值区间内的设定值'
},
divide_method: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '划分方式,如"按温度三段式"'
},
begin_validity: {
type: DataTypes.DATE,
allowNull: true,
comment: '有效期起,不设置则为空'
},
end_validity: {
type: DataTypes.DATE,
allowNull: true,
comment: '有效期止,不设置则为空'
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
comment: '创建时间'
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
comment: '修改时间'
}
},
association: [],
indexes: [
{ fields: ['device_id'] },
{ fields: ['linkage_type'] },
{ fields: ['device_id', 'linkage_type'] },
]
},
// 设备扩展表
{
tableNameCn: '设备扩展表',
tableName: 'device_expansion',
schema: {
id: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true,
comment: '自增主键'
},
device_id: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '设备ID,关联device.device_id'
},
region_key: {
type: DataTypes.BIGINT,
allowNull: false,
comment: '区域编号,关联region.id,表示设备所属区域'
},
device_type: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '设备类型,如"电能监测、电表"'
},
original_data: {
type: DataTypes.JSON,
allowNull: true,
comment: '原始扩展数据,如{usePower:114.76,electricityUsageFee:114.76}'
},
cateted_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
comment: '创建时间'
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
comment: '修改时间'
}
},
association: [
{ type: "hasMany", target: "device", foreignKey: "device_id" },
{ type: "hasMany", target: "region", foreignKey: "region_key" }
],
indexes: [
{ fields: ['device_id'] },
{ fields: ['region_key'] },
{ fields: ['device_type'] },
{ fields: ['device_id', 'device_type'] },
]
},
]; ];
...@@ -434,6 +434,7 @@ export async function selectPaginatedDataWithOrder( tableName:string, param:Quer ...@@ -434,6 +434,7 @@ export async function selectPaginatedDataWithOrder( tableName:string, param:Quer
return { return {
data: result.rows, data: result.rows,
total: result.count,
pagination: { pagination: {
page: pageNumber, page: pageNumber,
pageSize, pageSize,
......
...@@ -19,7 +19,7 @@ async function lanuch() { ...@@ -19,7 +19,7 @@ async function lanuch() {
/**创建http服务 */ /**创建http服务 */
httpServer.createServer(systemConfig.port); httpServer.createServer(systemConfig.port);
/**启动MQTT订阅服务 */ /**启动MQTT订阅服务 */
await mqttSrv.startMqttClient(); // await mqttSrv.startMqttClient();
/**启动定时任务 */ /**启动定时任务 */
startSchedule(); startSchedule();
/**启动预警工单定时任务(10分钟) */ /**启动预警工单定时任务(10分钟) */
......
...@@ -23,3 +23,11 @@ function success(data) { ...@@ -23,3 +23,11 @@ function success(data) {
this.res.send(resultPack); this.res.send(resultPack);
} }
/**
* 中间件错误返回方法
* @param msg
*/
function error(msg) {
this.res.send({success:false, msg, code:400});
}
...@@ -34,6 +34,12 @@ export function setRouter(httpServer) { ...@@ -34,6 +34,12 @@ export function setRouter(httpServer) {
httpServer.post('/api/qdm/run/monitor/acRun', asyncHandler(controlAcRunning)); httpServer.post('/api/qdm/run/monitor/acRun', asyncHandler(controlAcRunning));
/** 运行分析 - 环境质量弹窗 */ /** 运行分析 - 环境质量弹窗 */
httpServer.post('/api/qdm/run/analysis/envPop', asyncHandler(getRunAnalysisEnvPop)); httpServer.post('/api/qdm/run/analysis/envPop', asyncHandler(getRunAnalysisEnvPop));
/** 运行分析 - 离线设备弹窗 */
httpServer.post('/api/qdm/run/analysis/offlineDevicePop', asyncHandler(getOfflineDevicePop));
/** 运行分析 - 故障设备弹窗 */
httpServer.post('/api/qdm/run/analysis/faultDevicePop', asyncHandler(getFaultDevicePop));
/** 运行分析 - 设备能耗监控弹窗 */
httpServer.post('/api/qdm/run/analysis/deviceEnergyPop', asyncHandler(getDeviceEnergyPop));
/** 启动数据集成 */ /** 启动数据集成 */
httpServer.post('/api/qdm/run/start/schedule', asyncHandler(startSchedule)); httpServer.post('/api/qdm/run/start/schedule', asyncHandler(startSchedule));
...@@ -201,6 +207,39 @@ async function getRunAnalysisEnvPop(req, res) { ...@@ -201,6 +207,39 @@ async function getRunAnalysisEnvPop(req, res) {
} }
/** /**
* 运行分析 - 离线设备弹窗
*/
async function getOfflineDevicePop(req, res) {
let reqConf = { regionGroup: 'String', regionType: 'String' };
const NotMustHaveKeys = ["regionGroup", "regionType"];
let { regionGroup, regionType } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await deviceBiz.getOfflineDeviceList(regionGroup, regionType);
res.success(result);
}
/**
* 运行分析 - 故障设备弹窗
*/
async function getFaultDevicePop(req, res) {
let reqConf = { regionGroup: 'String', regionType: 'String' };
const NotMustHaveKeys = ["regionGroup", "regionType"];
let { regionGroup, regionType } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await deviceBiz.getFaultDeviceList(regionGroup, regionType);
res.success(result);
}
/**
* 运行分析 - 设备能耗监控弹窗(各区域昨日/今日能耗)
*/
async function getDeviceEnergyPop(req, res) {
let reqConf = { regionGroup: 'String', regionType: 'String' };
const NotMustHaveKeys = ["regionGroup", "regionType"];
let { regionGroup, regionType } = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await deviceBiz.getDeviceEnergyList(regionGroup, regionType);
res.success(result);
}
/**
* 本月累计用电(按天) * 本月累计用电(按天)
*/ */
async function getMonthlyEnergy(req, res) { async function getMonthlyEnergy(req, res) {
......
...@@ -14,8 +14,22 @@ export function setRouter(httpServer) { ...@@ -14,8 +14,22 @@ export function setRouter(httpServer) {
httpServer.post('/api/qdm/feiyi/indoorUnit/list', asyncHandler(getFeiyiIndoorUnitList)); httpServer.post('/api/qdm/feiyi/indoorUnit/list', asyncHandler(getFeiyiIndoorUnitList));
/** 空调内机控制 */ /** 空调内机控制 */
httpServer.post('/api/qdm/feiyi/indoorUnit/control', asyncHandler(getFeiyiIndoorUnitControl)); httpServer.post('/api/qdm/feiyi/indoorUnit/control', asyncHandler(getFeiyiIndoorUnitControl));
/** 单个内机分摊电量和电费 */
httpServer.post('/api/qdm/feiyi/indoorUnit/expenses', asyncHandler(getIndoorUnitExpensesApi));
/** 房间电费日使用详情 */
httpServer.post('/api/qdm/feiyi/indoorUnit/expenses/list', asyncHandler(getRoomExpensesListApi));
/** 电费数据写入设备扩展表 */
httpServer.post('/api/qdm/feiyi/indoorUnit/expenses/save', asyncHandler(saveRoomExpensesToExpansionApi));
/** 机房群控设备最新参数数据(测试接口) */
httpServer.get('/api/qdm/feiyi/roomGroup/data', asyncHandler(getRoomGroupDeviceDataApi));
/** 机房群控获取网关信息(测试接口) */
httpServer.get('/api/qdm/feiyi/roomGroup/gatewayInfo', asyncHandler(getRoomGroupGatewayInfoApi));
/** 机房群控参数写入(测试接口) */
httpServer.post('/api/qdm/feiyi/roomGroup/writeParams', asyncHandler(setRoomGroupParamsApi));
} }
// =============================================== 控制系统 ==================================================
/** /**
* 网关设备列表 * 网关设备列表
...@@ -81,4 +95,104 @@ async function getFeiyiIndoorUnitControl(req, res) { ...@@ -81,4 +95,104 @@ async function getFeiyiIndoorUnitControl(req, res) {
res.success(result); res.success(result);
} }
/**
* 单个内机分摊电量和电费
* @param page 当前页码(选填)
* @param limit 每页条数(选填)
* @param roomId 房间ID(选填)
* @param indoorUnitId 内机ID(选填)
* @param indoorUnitAddressFull 内机地址(选填)
* @param occurrenceTime 日期 yyyyMMdd(必填)
*/
async function getIndoorUnitExpensesApi(req, res) {
let reqConf = {
page: 'Number', limit: 'Number',
roomId: 'String', indoorUnitId: 'String', indoorUnitAddressFull: 'String',
occurrenceTime: 'String',
};
const NotMustHaveKeys = [ "page", "limit", "roomId", "indoorUnitId", "indoorUnitAddressFull" ];
let params = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await feiyiClientBiz.getIndoorUnitExpenses(params);
res.success(result);
}
/**
* 房间电费日使用详情
* @param page 当前页码(选填)
* @param limit 每页条数(选填)
* @param roomIds 房间ID集合(选填)
* @param floorIds 楼层ID集合(选填)
* @param buildingIds 楼栋ID集合(选填)
* @param occurrenceTime 日期 yyyyMMdd(必填)
*/
async function getRoomExpensesListApi(req, res) {
let reqConf = {
page: 'Number', limit: 'Number',
roomIds: 'Array', floorIds: 'Array', buildingIds: 'Array',
occurrenceTime: 'String',
};
const NotMustHaveKeys = [ "page", "limit", "roomIds", "floorIds", "buildingIds" ];
let params = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await feiyiClientBiz.getRoomExpensesList(params);
res.success(result);
}
/**
* 电费数据写入设备扩展表 device_expansion
* 拉取"房间电费日使用详情",按房间(region)映射到本地设备,写入 device_expansion
* @param occurrenceTime 日期 yyyyMMdd(必填)
* @param deviceType 写入的设备类型,默认"电能监测"
*/
async function saveRoomExpensesToExpansionApi(req, res) {
let reqConf = { occurrenceTime: 'String', deviceType: 'String' };
const NotMustHaveKeys = [ "deviceType" ];
let params = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await feiyiClientBiz.saveRoomExpensesToExpansion(
params.occurrenceTime,
params.deviceType || '电能监测'
);
res.success(result);
}
// =============================================== 机房系统 ==================================================
/**
* 机房群控设备最新参数数据
* @param gwSn 网关编号(可选)
* @param deviceAddr 设备地址
*/
async function getRoomGroupDeviceDataApi(req, res) {
let reqConf = { gwSn: 'String', deviceAddr: 'String' };
const NotMustHaveKeys = [ "gwSn" ];
let params = eccReqParamater(reqConf, req.query, NotMustHaveKeys);
const result = await feiyiClientBiz.getRoomGroupDeviceData(params.gwSn, params.deviceAddr);
res.success(result);
}
/**
* 机房群控获取网关信息
* @param projectId 项目ID
*/
async function getRoomGroupGatewayInfoApi(req, res) {
let reqConf = { projectId: 'String' };
const NotMustHaveKeys: string[] = [];
let params = eccReqParamater(reqConf, req.query, NotMustHaveKeys);
const result = await feiyiClientBiz.getRoomGroupGatewayInfo(params.projectId);
res.success(result);
}
/**
* 机房群控参数写入
* @param addr 设备地址
* @param gwId 网关ID(可选)
* @param projectId 项目ID
* @param params 写入参数 JSON,如 { "1": 0.0 }
*/
async function setRoomGroupParamsApi(req, res) {
let reqConf = { addr: 'String', gwId: 'Number', projectId: 'String', params: 'Object' };
const NotMustHaveKeys = [ "gwId" ];
let params = eccReqParamater(reqConf, req.body, NotMustHaveKeys);
const result = await feiyiClientBiz.setRoomGroupParams(params.addr, params.gwId, params.projectId, params.params);
res.success(result);
}
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