alarmOperationsModel.js 3.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
  1. import { hasActualValue } from './alarmReviewModel.js'
  2. // GIS 曲线和研判候选共用;按报警 ID 去重,保留实际值为 0 的事件。
  3. export function alarmCurveChoices(rows = []) {
  4. return [...new Map(rows.map(row => [row.id, row])).values()].filter(hasActualValue)
  5. }
  6. export const levelTone = level => ({ 1: 'danger', 2: 'warning', 3: 'primary', 4: 'success' }[level] || 'info')
  7. // 只映射展示颜色,状态名称仍以接口返回值为准。
  8. export const monitorStatusTone = status => ({ 0: 'danger', 1: 'success' }[status] || 'info')
  9. export const auditStatusTone = status => ({ 0: 'warning', 1: 'primary', 2: 'info' }[status] || 'info')
  10. export const levelColor = level => ({ 1: '#f56c6c', 2: '#e6a23c', 3: '#409eff', 4: '#67c23a' }[level] || '#909399')
  11. export const scopeFromRoute = query => ({ rootTypeId: Number(query.rootTypeId), warningCode: query.warning_code || query.warningCode || undefined })
  12. export const sampleTime = time => Date.parse(/[zZ]$|[+-]\d\d:\d\d$/.test(time) ? time : String(time).replace(' ', 'T') + '+08:00')
  13. // 原始采样间隔超过本区间中位间隔的3倍时断线;不补零或插值。
  14. export function seriesPoints(samples, raw = false) {
  15. const points = samples.map(p => [sampleTime(p.time), p.value]).filter(p => Number.isFinite(p[0])).sort((a, b) => a[0] - b[0])
  16. if (!raw || points.length < 3) return points
  17. const intervals = points.slice(1).map((p, i) => p[0] - points[i][0]).filter(n => n > 0).sort((a, b) => a - b)
  18. const median = intervals[Math.floor((intervals.length - 1) / 2)]
  19. return points.flatMap((p, i) => i && p[0] - points[i - 1][0] > median * 3 ? [[points[i - 1][0] + 1, null], p] : [p])
  20. }
  21. export function rangeParams(range) {
  22. return range?.length === 2 ? { startTime: `${range[0]} 00:00:00`, endTime: `${range[1]} 23:59:59.999999` } : {}
  23. }
  24. export function groupAlarmDevices(rows = []) {
  25. const devices = new Map()
  26. for (const row of rows) {
  27. const key = row.equipmentId || row.deviceCode
  28. if (!key) continue
  29. if (!devices.has(key)) devices.set(key, { ...row, key, name: row.deviceName || row.deviceCode, alarms: [] })
  30. devices.get(key).alarms.push(row)
  31. }
  32. return [...devices.values()].map(device => {
  33. device.alarms.sort((a, b) => String(b.createTime).localeCompare(String(a.createTime)))
  34. const current = device.alarms.filter(row => row.alarmStatus === 0)
  35. device.alarmLevel = Math.min(...(current.length ? current : device.alarms.slice(0, 1)).map(row => Number(row.alarmLevel) || 4))
  36. device.lng = device.longitude === null || device.longitude === '' ? NaN : Number(device.longitude)
  37. device.lat = device.latitude === null || device.latitude === '' ? NaN : Number(device.latitude)
  38. device.hasCoordinates = Number.isFinite(device.lng) && Number.isFinite(device.lat) && Math.abs(device.lng) <= 180 && Math.abs(device.lat) <= 90 && !(device.lng === 0 && device.lat === 0)
  39. device.iconUrl = 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" width="36" height="44"><path d="M18 42C15 35 2 26 2 18a16 16 0 1 1 32 0c0 8-13 17-16 24Z" fill="${levelColor(device.alarmLevel)}" stroke="white" stroke-width="2"/><text x="18" y="24" text-anchor="middle" fill="white" font-size="18">${device.alarmLevel}</text></svg>`)
  40. return device
  41. })
  42. }
  43. export function findAlarmMenu(routes, leaf, rootTypeId, parent = '') {
  44. for (const route of routes || []) {
  45. const path = route.path?.startsWith('/') ? route.path : `${parent}/${route.path || ''}`.replace(/\/+/g, '/')
  46. let query = route.query || {}
  47. try { if (typeof query === 'string') query = JSON.parse(query) } catch { query = {} }
  48. if (path.split('/').at(-1) === leaf && Number(query.rootTypeId) === Number(rootTypeId)) return { path, query }
  49. const child = findAlarmMenu(route.children, leaf, rootTypeId, path)
  50. if (child) return child
  51. }
  52. return null
  53. }