| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418 |
- import { normalizeDeviceRow } from './waterSupplyUiModel.js'
- import { getFacilityStatusMeta, getMonitoringPointIcon, getMonitoringPointStatus } from './gisMapColors.js'
- /**
- * 供水首页工作台数据模型(T05 #48)。
- *
- * 首页只组合既有只读接口,不在本模块调用后端;所有口径(供水范围、工单状态、
- * 报警等级、在线率)都收敛到这里,便于用 node 单测锁定。
- */
- // 工单状态 0-7,与 ADR-0010 权威枚举一致
- const WORK_ORDER_STATUS_META = {
- 0: { text: '待派单', type: 'danger' },
- 1: { text: '待接单', type: 'warning' },
- 2: { text: '已接单', type: 'primary' },
- 3: { text: '处理中', type: 'warning' },
- 4: { text: '待验收', type: 'primary' },
- 5: { text: '已结案', type: 'success' },
- 6: { text: '延期中', type: 'warning' },
- 7: { text: '已取消', type: 'info' }
- }
- const WORK_ORDER_LEVEL_META = {
- 0: { text: '紧急', type: 'danger' },
- 1: { text: '一般', type: 'warning' },
- 2: { text: '低', type: 'info' }
- }
- const ALARM_LEVEL_META = {
- 1: { text: '一级报警', type: 'danger' },
- 2: { text: '二级报警', type: 'warning' },
- 3: { text: '三级报警', type: 'primary' },
- 4: { text: '四级报警', type: 'success' }
- }
- /**
- * 供水设备根类型:设备接口 /equipment-base 的数据范围参数,取值对应 EquipmentTypeEnum
- * 的 1-供水设备。设备与工单两处取数都以此限定供水范围,避免各自写死。
- */
- export const WATER_DEVICE_ROOT_TYPE_ID = 1
- export const HOME_QUICK_ENTRIES = [
- { key: 'abnormal', label: '设备异常管理', suffix: '/device/abnormal', icon: 'WarningFilled' },
- { key: 'alarm', label: '报警管理', suffix: '/monitorAlarm/index', icon: 'BellFilled' },
- { key: 'audit', label: '报警审核', suffix: '/monitorAlarm/audit', icon: 'Finished' },
- { key: 'map', label: '监测报警一张图', suffix: '/waterSupplyAlarmGIS', query: { rootTypeId: WATER_DEVICE_ROOT_TYPE_ID }, icon: 'MapLocation' }
- ]
- /** 供水工单统计/列表的固定范围参数:设备父类别=供水。 */
- export function buildHomeWorkOrderQuery(extra = {}) {
- return {
- deviceTypeParentId: String(WATER_DEVICE_ROOT_TYPE_ID),
- pageNum: 1,
- pageSize: 5,
- ...extra
- }
- }
- /** 供水设施总数:overview 会追加 facilityType=total 合计行,须排除避免重复累计。 */
- export function normalizeFacilityTotal(facilityOverview = []) {
- const rows = Array.isArray(facilityOverview) ? facilityOverview : []
- return rows
- .filter(item => item && item.facilityType !== 'total')
- .reduce((sum, item) => sum + Number(item.totalCount ?? item.count ?? 0), 0)
- }
- /** A 区指挥台四项核心总量。 */
- export function normalizeHomeKpis({ facilityRows = [], ledgerStats = {}, alarmStats = {} } = {}) {
- const realtimeAlarm = Number(alarmStats.realtimeAlarmCount ?? alarmStats.realtimeCount ?? 0)
- return [
- { key: 'facility', label: '设施总数', value: normalizeFacilityTotal(facilityRows), unit: '处', tone: 'primary' },
- { key: 'device', label: '监测设备', value: Number(ledgerStats.total ?? 0), unit: '台', tone: 'primary' },
- { key: 'online', label: '在线设备', value: Number(ledgerStats.online ?? 0), unit: '台', tone: 'primary' },
- { key: 'alarm', label: '实时报警', value: realtimeAlarm, unit: '条', tone: realtimeAlarm > 0 ? 'danger' : 'primary' }
- ]
- }
- /**
- * 统一设备统计口径:/equipment-base/statistics 返回 total/online/offline/alarm,
- * 首页所需的在线率在此推导,避免各页面各自计算。
- */
- export function normalizeDeviceStatistics(stats = {}) {
- const total = Number(stats.total ?? 0)
- const online = Number(stats.online ?? 0)
- const offline = Number(stats.offline ?? Math.max(total - online, 0))
- const alarm = Number(stats.alarm ?? 0)
- return {
- total,
- online,
- offline,
- alarm,
- onlineRate: total > 0 ? Math.round(online * 1000 / total) / 10 : 0
- }
- }
- function countByStatus(stat, status) {
- const counts = Array.isArray(stat?.counts) ? stat.counts : []
- const hit = counts.find(item => Number(item?.status) === Number(status))
- return Number(hit?.count ?? 0)
- }
- /** 工单状态统计:待处理 0/1,处理中 2/3/6,待验收 4,已完成 5/7。 */
- export function normalizeWorkOrderStatusStat(stat = {}) {
- const pendingCount = countByStatus(stat, 0) + countByStatus(stat, 1)
- const processingCount = countByStatus(stat, 2) + countByStatus(stat, 3) + countByStatus(stat, 6)
- const verifyingCount = countByStatus(stat, 4)
- const finishedCount = countByStatus(stat, 5) + countByStatus(stat, 7)
- const byStatus = Object.entries(WORK_ORDER_STATUS_META)
- .map(([status, meta]) => ({
- status: Number(status),
- label: meta.text,
- type: meta.type,
- count: countByStatus(stat, Number(status))
- }))
- return {
- total: Number(stat?.total ?? 0),
- pendingCount,
- processingCount,
- verifyingCount,
- finishedCount,
- byStatus
- }
- }
- /** C 区数据总览卡片:报警总数、报警中设备、待验收工单、在线率。 */
- export function normalizeAlarmOverviewCards({ dashboard = {}, deviceStats = {}, workOrderStat = {} } = {}) {
- const normalized = normalizeWorkOrderStatusStat(workOrderStat)
- return [
- { key: 'alarmTotal', label: '报警总数', value: Number(dashboard.totalCount ?? 0), unit: '条', tone: 'primary' },
- { key: 'alarmingDevice', label: '报警中设备', value: Number(dashboard.alarmingDeviceCount ?? 0), unit: '台', tone: Number(dashboard.alarmingDeviceCount ?? 0) > 0 ? 'danger' : 'primary' },
- { key: 'verifyingOrder', label: '待验收工单', value: normalized.verifyingCount, unit: '单', tone: normalized.verifyingCount > 0 ? 'warning' : 'primary' },
- { key: 'onlineRate', label: '在线率', value: formatOnlineRate(deviceStats.onlineRate), unit: '%', tone: 'primary' }
- ]
- }
- function formatOnlineRate(value) {
- const numeric = Number(value)
- return Number.isFinite(numeric) ? Number(numeric.toFixed(1)) : 0
- }
- /** 报警趋势:按四个等级分别成系列,空天补 0。 */
- export function normalizeAlarmTrend(trend = []) {
- const items = Array.isArray(trend) ? trend : []
- const dates = items.map(item => item?.date || '-')
- const levels = [1, 2, 3, 4].map(level => ({
- name: `级${level}`,
- text: (ALARM_LEVEL_META[level] || {}).text || `${level}级报警`,
- data: items.map(item => Number(item[`level${level}Count`] ?? 0))
- }))
- return { dates, levels }
- }
- /** 占比饼图:空值名称兜底、数量转数字、只取最大值前若干项。 */
- export function normalizePieRatio(items = [], fallbackName = '未分类', top = 6) {
- const rows = (Array.isArray(items) ? items : [])
- .map(item => ({
- name: String(item?.name || '').trim() || fallbackName,
- count: Number(item?.count ?? 0)
- }))
- .filter(item => item.count > 0)
- .sort((a, b) => b.count - a.count)
- return rows.slice(0, top)
- }
- export function alarmLevelText(level) {
- const numeric = Number(level)
- const meta = ALARM_LEVEL_META[numeric]
- return meta?.text || (Number.isFinite(numeric) && numeric > 0 ? `${numeric}级报警` : '未分级')
- }
- const ALARM_DISPLAY_STATUS_META = {
- 1: { text: '待审核', type: 'warning' },
- 2: { text: '已转工单', type: 'primary' },
- 3: { text: '已解除', type: 'success' }
- }
- const ALARM_DISPLAY_RELEASED = 3
- /**
- * A 区实时报警列表。
- *
- * 实时列表只展示仍需要关注的报警:已解除(displayStatus=3)直接过滤;
- * 待审核 / 已转工单行保留展示状态与关联工单号,供首页按状态分流跳转。
- *
- * 后端 5c14625 起实时行已携带 displayStatus;为兼容旧进程/旧产物返回的
- * “只有 orderNo 没有 displayStatus” 的行,这里按工单是否在办兜底推导:
- * 有未取消工单 -> 已转工单,否则 -> 待审核。兜底只影响缺失字段的行,
- * 后端明确给出的展示状态始终优先。
- */
- export function normalizeRealtimeAlarmRows(rows = [], limit = 5) {
- const list = (Array.isArray(rows) ? rows : [])
- .filter(row => derivedDisplayStatus(row) !== ALARM_DISPLAY_RELEASED)
- .slice()
- .sort((a, b) => String(b?.alarmTime || '').localeCompare(String(a?.alarmTime || '')))
- .slice(0, limit)
- .map(row => {
- const displayStatus = derivedDisplayStatus(row)
- return {
- id: row?.id ?? row?.alarmId,
- deviceCode: row?.deviceCode ?? '-',
- deviceName: row?.deviceName || row?.deviceCode || '监测设备',
- warningType: row?.warningType || '监测报警',
- alarmLevel: Number(row?.alarmLevel ?? 0),
- alarmLevelText: row?.alarmLevelText || alarmLevelText(row?.alarmLevel),
- alarmTime: row?.alarmTime || '-',
- location: row?.equipmentLocation || '-',
- displayStatus,
- displayStatusText: row?.displayStatusText
- || ALARM_DISPLAY_STATUS_META[displayStatus]?.text
- || '待审核',
- displayStatusType: ALARM_DISPLAY_STATUS_META[displayStatus]?.type || 'warning',
- orderNo: row?.orderNo || row?.relatedOrderNo || ''
- }
- })
- return list
- }
- function derivedDisplayStatus(row = {}) {
- const explicit = Number(row?.displayStatus)
- if (Number.isFinite(explicit) && explicit !== 0) {
- return explicit
- }
- // 与后端 resolveDisplayStatus 同序:在办工单优先,
- // 其次才看报警是否已处理/误报解除,避免把“已转工单”误判成“已解除”。
- const hasOrder = String(row?.orderNo || row?.relatedOrderNo || '').trim().length > 0
- const orderStatus = Number(row?.orderStatus ?? row?.relatedOrderStatus ?? -1)
- const liveOrder = hasOrder && orderStatus !== 7
- if (liveOrder) {
- return 2
- }
- return Number(row?.alarmStatus) === 1 ? ALARM_DISPLAY_RELEASED : 1
- }
- /**
- * 实时报警跳转目标:已转工单带工单号 -> 设备维修派单;其余(含待审核)-> 报警审核。
- */
- export function realtimeAlarmJumpTarget(row = {}) {
- if (Number(row?.displayStatus) === 2) {
- const orderNo = String(row?.orderNo || '').trim()
- if (orderNo) {
- return { path: '/device/maintain', query: { keyword: orderNo } }
- }
- }
- return {
- path: '/monitorAlarm/audit',
- query: {
- alarmId: row?.id ?? row?.alarmId ?? undefined,
- deviceCode: row?.deviceCode || undefined
- }
- }
- }
- /** B 区异常待办清单:复用设备行标准化,避免口径漂移。 */
- export function normalizeAbnormalTodoRows(rows = [], limit = 6) {
- return (Array.isArray(rows) ? rows : []).slice(0, limit).map(normalizeDeviceRow)
- }
- /** B 区选中设备摘要。 */
- export function normalizeDeviceSummary(row = {}) {
- const normalized = normalizeDeviceRow(row)
- return {
- ...normalized,
- name: row.equipmentName || row.deviceName || '-',
- code: row.equipmentCode || row.deviceCode || '-',
- location: row.equipmentLocation || row.location || '-',
- district: row.district || '-',
- typeName: normalized.equipmentTypeName || '-'
- }
- }
- /** A 区工单进展列表。 */
- export function normalizeWorkOrderRows(rows = [], limit = 5) {
- return (Array.isArray(rows) ? rows : [])
- .slice()
- .sort((a, b) => String(b?.createTime || '').localeCompare(String(a?.createTime || '')))
- .slice(0, limit)
- .map(row => {
- const orderStatus = Number(row?.orderStatus ?? -1)
- const orderLevel = Number(row?.orderLevel ?? -1)
- const statusMeta = WORK_ORDER_STATUS_META[orderStatus] || { text: '未知', type: 'info' }
- const levelMeta = WORK_ORDER_LEVEL_META[orderLevel] || { text: '-', type: 'info' }
- return {
- orderId: row?.orderId,
- orderNo: row?.orderNo || '-',
- orderStatus,
- orderStatusText: statusMeta.text,
- orderStatusType: statusMeta.type,
- orderLevel,
- orderLevelText: levelMeta.text,
- orderLevelType: levelMeta.type,
- deviceCode: row?.deviceCode || '-',
- deviceName: row?.deviceName || '-',
- orderDesc: row?.orderDesc || '-',
- createTime: row?.createTime || '-'
- }
- })
- }
- function finiteCoordinate(value) {
- const numeric = Number(value)
- return Number.isFinite(numeric) ? numeric : null
- }
- function hasCoordinates(point = {}) {
- const lng = finiteCoordinate(point?.lng ?? point?.longitude)
- const lat = finiteCoordinate(point?.lat ?? point?.latitude)
- return lng !== null && lat !== null
- }
- function normalizeHomeOverviewPoint(point, kind) {
- const lng = finiteCoordinate(point?.lng ?? point?.longitude)
- const lat = finiteCoordinate(point?.lat ?? point?.latitude)
- if (lng === null || lat === null) return null
- if (kind === 'facility') {
- if (point?.type === 'pipe') return null
- // 设施状态语义统一由共享色板定义,首页与设施 GIS 一张图同源
- const statusMeta = getFacilityStatusMeta(point?.type, point?.status)
- return {
- ...point,
- id: point?.id ?? point?.featureId,
- kind,
- name: point?.name || point?.facilityName || point?.code || '供水设施',
- code: point?.code || point?.facilityCode || '-',
- lng,
- lat,
- statusKey: statusMeta.key,
- statusText: statusMeta.text,
- iconUrl: getMonitoringPointIcon({ statusKey: statusMeta.key }),
- meta: point?.road || '供水设施'
- }
- }
- if (kind === 'alarm') {
- return {
- ...point,
- id: point?.alarmId ?? point?.id,
- kind,
- name: point?.deviceName || point?.name || point?.deviceCode || '报警点',
- code: point?.deviceCode || point?.code || '-',
- lng,
- lat,
- statusKey: 'alarm',
- iconUrl: getMonitoringPointIcon({ statusKey: 'alarm' }),
- meta: point?.warningType || '监测报警'
- }
- }
- const normalized = normalizeDeviceRow(point)
- const statusKey = getMonitoringPointStatus(normalized)
- return {
- ...point,
- kind,
- id: point?.equipmentId ?? point?.id ?? normalized.equipmentCode ?? point?.deviceCode,
- name: normalized.equipmentName || normalized.equipmentCode || '监测设备',
- code: normalized.equipmentCode || point?.deviceCode || '-',
- lng,
- lat,
- statusKey,
- iconUrl: getMonitoringPointIcon({ ...normalized, statusKey }),
- meta: normalized.equipmentTypeName || '监测设备'
- }
- }
- /**
- * 首页全域态势:聚合设施、设备与报警三类一张图数据,统一交给公共 GisMapCanvas 渲染。
- *
- * 同一设备的报警点与设备点按 deviceCode 合并,报警状态优先于设备自身状态;
- * 设施点位与管网线段原样透传,颜色/图标沿用各一张图规范。
- */
- export function buildHomeGisOverview({
- facilityPoints = [],
- facilityLines = [],
- devicePoints = [],
- alarmPoints = []
- } = {}) {
- const deviceList = (Array.isArray(devicePoints) ? devicePoints : [])
- .map(point => normalizeHomeOverviewPoint(point, 'device'))
- .filter(Boolean)
- const alarmList = (Array.isArray(alarmPoints) ? alarmPoints : [])
- .map(point => normalizeHomeOverviewPoint(point, 'alarm'))
- .filter(Boolean)
- const merged = new Map()
- deviceList.forEach(point => {
- const key = String(point.code && point.code !== '-' ? point.code : point.id || '')
- if (key) merged.set(key, point)
- })
- alarmList.forEach(point => {
- const key = String(point.code && point.code !== '-' ? point.code : point.id || '')
- const existing = key ? merged.get(key) : undefined
- if (!key || !existing || existing.statusKey !== 'alarm') {
- if (key) merged.set(key, point)
- }
- })
- const facilityPointList = (Array.isArray(facilityPoints) ? facilityPoints : [])
- .map(point => normalizeHomeOverviewPoint(point, 'facility'))
- .filter(Boolean)
- const lines = (Array.isArray(facilityLines) ? facilityLines : [])
- .map(line => ({
- ...line,
- name: line?.name || line?.pipeName || '供水管网',
- color: '#f1c40f',
- points: (line?.points || [])
- .map(point => ({
- lng: finiteCoordinate(point?.lng ?? point?.longitude),
- lat: finiteCoordinate(point?.lat ?? point?.latitude)
- }))
- .filter(point => point.lng !== null && point.lat !== null)
- }))
- .filter(line => (line.points || []).length >= 2)
- return {
- points: [...facilityPointList, ...merged.values()].filter(hasCoordinates),
- lines
- }
- }
|