feat(alarm): 设备报警监控报警提示升级与警灯交互优化 - 报警提示升级:后端新增报警派单完成接口 POST /api/worker-app/alarm-dispatches/:id/complete(供共享电工APP确认完成,归属校验403/不存在404/已完成幂等,status置1并写completed_at);GET /api/device-alarms 的 summary 由 severeIds 改为 activeSevereIds(grade=1 且无已完成派单的报警id,倒序100条,派单完成自动移出即报警取消)并新增轻量接口 GET /api/device-alarms/active-severe;前端新建全局悬浮立体警灯组件 AlarmSirenLight(红蓝双色3D警灯,挂载主布局所有页面可见,点击跳转报警监控页,30秒轮询activeSevereIds新id即时响+每3分钟重复响,Web Audio合成警笛700/900Hz交替,浏览器自动播放静默降级);index.vue 移除原10秒指示灯与880Hz beep、瘦身为纯列表页;警灯交互优化:警灯可鼠标拖动(位移>5px判为拖动不触发跳转)、位置经localStorage跨刷新记忆(默认右上角)、取消「重置报警提示」按钮;后端tsc与前端vue-tsc编译通过,接口实测派单完成排除逻辑生效,浏览器端到端验证通过(全局显示、点击跳转、拖动、刷新记忆、重置按钮无残留)
This commit is contained in:
1 parent
7ff2405634
commit
2d55947762
13 files changed
+676
-115
No files matched your search
@@ -58,10 +58,13 @@ export class DeviceAlarmController {
|
||||
const d = new Date(new Date().getTime() + 8 * 60 * 60 * 1000);
|
||||
const today = `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}-${String(d.getUTCDate()).padStart(2, "0")}`;
|
||||
const todayStart = new Date(`${today}T00:00:00+08:00`);
|
||||
// 统计:今日报警数(alarm_time >= 当天0点)/ 严重报警数(grade=1)
|
||||
const [todayCount, gradeOneCount] = await Promise.all([
|
||||
this.alarmRepo.count({ where: { lineId: { [Op.ne]: null }, alarmTime: { [Op.gte]: todayStart } } }),
|
||||
this.alarmRepo.count({ where: { lineId: { [Op.ne]: null }, alarmGrade: 1 } })
|
||||
]);
|
||||
// 活跃严重报警 id 集合(grade=1 且无已完成派单,按报警时间倒序取最近 100 条,供前端全局报警灯/声音触发,不受分页筛选影响)
|
||||
const activeSevereIds = await this.getActiveSevereIds();
|
||||
const dispatchedAlarms = await this.dispatchRepo.findAll({ where: { status: 0 }, attributes: ["alarmId"], raw: true });
|
||||
const dispatchedIds = [...new Set(dispatchedAlarms.map((r: any) => Number(r.alarmId)))];
|
||||
const undispatchedCount = await this.alarmRepo.count({
|
||||
@@ -79,11 +82,40 @@ export class DeviceAlarmController {
|
||||
total: count,
|
||||
page,
|
||||
pageSize,
|
||||
summary: { todayCount, gradeOneCount, undispatchedCount }
|
||||
summary: { todayCount, gradeOneCount, undispatchedCount, activeSevereIds }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** 活跃严重报警 id 轻量接口(供全局悬浮警灯轮询,rbac 前缀权限 alarm:list 已覆盖) */
|
||||
@Get("/active-severe") async activeSevere() {
|
||||
const activeSevereIds = await this.getActiveSevereIds();
|
||||
return {
|
||||
code: 200,
|
||||
message: "获取活跃严重报警成功",
|
||||
data: { activeSevereIds }
|
||||
};
|
||||
}
|
||||
|
||||
// 活跃严重报警 id 集合:grade=1、lineId 非空、且无 status=1 已完成派单的报警,
|
||||
// 按报警时间倒序取最近 100 条,供前端全局报警灯/声音触发,不受分页筛选影响
|
||||
private async getActiveSevereIds(): Promise<number[]> {
|
||||
const completedAlarms = await this.dispatchRepo.findAll({ where: { status: 1 }, attributes: ["alarmId"], raw: true });
|
||||
const completedAlarmIds = [...new Set(completedAlarms.map((r: any) => Number(r.alarmId)))];
|
||||
const activeSevereAlarms = await this.alarmRepo.findAll({
|
||||
where: {
|
||||
lineId: { [Op.ne]: null },
|
||||
alarmGrade: 1,
|
||||
id: completedAlarmIds.length ? { [Op.notIn]: completedAlarmIds } : { [Op.ne]: null }
|
||||
},
|
||||
attributes: ["id"],
|
||||
order: [["alarmTime", "DESC"]],
|
||||
limit: 100,
|
||||
raw: true
|
||||
});
|
||||
return activeSevereAlarms.map((r: any) => Number(r.id));
|
||||
}
|
||||
|
||||
/** 发起派单:校验报警/未完成派单/电工,生成派单号,写入派单并给电工发站内通知 */
|
||||
@Post("/:id/dispatch") async dispatch(@Param("id") id: string, @Body() body: any) {
|
||||
const alarmId = Number(id);
|
||||
|
||||
@@ -150,6 +150,12 @@ export class WorkerAppController {
|
||||
return this.workerAppService.completeOrder(this.currentWorkerId(), source, Number(id));
|
||||
}
|
||||
|
||||
// 报警派单完成确认
|
||||
@Post("/alarm-dispatches/:id/complete")
|
||||
async completeAlarmDispatch(@Param("id") id: string) {
|
||||
return this.workerAppService.completeAlarmDispatch(this.currentWorkerId(), Number(id));
|
||||
}
|
||||
|
||||
// 勘察明细维护(仅勘察单):全量替换,含建筑/楼层/房间与现场照片
|
||||
@Put("/orders/:source/:id/details")
|
||||
async saveDetails(@Param("source") source: string, @Param("id") id: string, @Body() body: any) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import WorkerCert from "../entity/worker_cert.entity";
|
||||
import SysNotice from "../entity/sys_notice.entity";
|
||||
import RepairRequest from "../entity/repair_request.entity";
|
||||
import RepairDispatch from "../entity/repair_dispatch.entity";
|
||||
import AlarmDispatch from "../entity/alarm_dispatch.entity";
|
||||
import InstallOrder from "../entity/install_order.entity";
|
||||
import MaintenanceOrder from "../entity/maintenance_order.entity";
|
||||
import SurveyOrder from "../entity/survey_order.entity";
|
||||
@@ -63,6 +64,9 @@ export class WorkerAppService {
|
||||
@InjectRepository(RepairDispatch)
|
||||
dispatchModel!: Repository<RepairDispatch>;
|
||||
|
||||
@InjectRepository(AlarmDispatch)
|
||||
alarmDispatchModel!: Repository<AlarmDispatch>;
|
||||
|
||||
@InjectRepository(InstallOrder)
|
||||
installModel!: Repository<InstallOrder>;
|
||||
|
||||
@@ -503,6 +507,19 @@ export class WorkerAppService {
|
||||
return { code: 200, message: "已完成" };
|
||||
}
|
||||
|
||||
// 报警派单完成确认:校验归属(不属于当前电工返回 403),幂等(已处理不重复更新)
|
||||
async completeAlarmDispatch(workerId: number, id: number): Promise<any> {
|
||||
const dispatch = await this.alarmDispatchModel.findByPk(id);
|
||||
if (!dispatch) return { code: 404, message: "派单不存在" };
|
||||
if (Number(dispatch.workerId) !== Number(workerId)) return { code: 403, message: "无权操作该派单" };
|
||||
if (Number(dispatch.status) === 1) return { code: 200, message: "该派单已完成" };
|
||||
|
||||
await this.sequelize.transaction(async t => {
|
||||
await dispatch.update({ status: 1, completedAt: new Date() }, { transaction: t });
|
||||
});
|
||||
return { code: 200, message: "报警派单完成确认成功" };
|
||||
}
|
||||
|
||||
// ================= 收益汇总 =================
|
||||
|
||||
// 收益汇总:累计/可提现/本月/近 7 日趋势/最近 20 条流水
|
||||
|
||||
Reference in new issue
Block a user