feat: 新增EMS基础模块的报警通知组、推送日志及相关功能

新增报警通知组管理页面及API
新增报警通知组成员管理页面及API
新增报警推送日志管理页面及API
新增相关类型定义和接口实现
兼容历史图表组件导入路径
main
zangch@mesnac.com 3 months ago
parent 45878f8bf4
commit 97ee812fe6

@ -0,0 +1,110 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { AlarmNotifyGroupVO, AlarmNotifyGroupForm, AlarmNotifyGroupId, AlarmNotifyGroupIds, AlarmNotifyGroupQuery } from '@/api/ems/base/alarmNotifyGroup/types';
/**
*
* @param query
* @returns {*}
*/
export const listAlarmNotifyGroup = (query?: AlarmNotifyGroupQuery): AxiosPromise<AlarmNotifyGroupVO[]> => {
return request({
url: '/ems/base/alarmNotifyGroup/list',
method: 'get',
params: query
});
};
/**
*
*
*/
export const listAlarmNotifyGroupAll = (): AxiosPromise<AlarmNotifyGroupVO[]> => {
return request({
url: '/ems/base/alarmNotifyGroup/listAll',
method: 'get'
});
};
/**
*
* @param id
*/
export const getAlarmNotifyGroup = (id: AlarmNotifyGroupId): AxiosPromise<AlarmNotifyGroupVO> => {
return request({
url: '/ems/base/alarmNotifyGroup/' + id,
method: 'get'
});
};
/**
*
* @param ids
*/
export const getAlarmNotifyGroupByIds = (ids: AlarmNotifyGroupIds): AxiosPromise<AlarmNotifyGroupVO[]> => {
return request({
url: '/ems/base/alarmNotifyGroup/listByIds',
method: 'post',
data: ids
});
};
/**
*
* @param query
*/
export const countAlarmNotifyGroup = (query?: AlarmNotifyGroupQuery): AxiosPromise<number> => {
return request({
url: '/ems/base/alarmNotifyGroup/count',
method: 'get',
params: query
});
};
/**
*
* @param query
*/
export const existsAlarmNotifyGroup = (query?: AlarmNotifyGroupQuery): AxiosPromise<boolean> => {
return request({
url: '/ems/base/alarmNotifyGroup/exists',
method: 'get',
params: query
});
};
/**
*
* @param data
*/
export const addAlarmNotifyGroup = (data: AlarmNotifyGroupForm) => {
return request({
url: '/ems/base/alarmNotifyGroup',
method: 'post',
data: data
});
};
/**
*
* @param data
*/
export const updateAlarmNotifyGroup = (data: AlarmNotifyGroupForm) => {
return request({
url: '/ems/base/alarmNotifyGroup',
method: 'put',
data: data
});
};
/**
*
* @param id
*/
export const delAlarmNotifyGroup = (id: AlarmNotifyGroupId | AlarmNotifyGroupIds) => {
return request({
url: '/ems/base/alarmNotifyGroup/' + id,
method: 'delete'
});
};

@ -0,0 +1,10 @@
import type { AlarmNotifyGroupVO as AlarmNotifyGroupEntity, EmsCrudForm, EmsCrudQuery, EmsId } from '@/api/ems/types';
export type AlarmNotifyGroupId = EmsId;
export type AlarmNotifyGroupIds = AlarmNotifyGroupId[];
export interface AlarmNotifyGroupVO extends AlarmNotifyGroupEntity {}
export interface AlarmNotifyGroupForm extends EmsCrudForm<AlarmNotifyGroupVO> {}
export interface AlarmNotifyGroupQuery extends EmsCrudQuery<AlarmNotifyGroupVO> {}

@ -0,0 +1,99 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { AlarmNotifyGroupUserVO, AlarmNotifyGroupUserForm, AlarmNotifyGroupUserId, AlarmNotifyGroupUserIds, AlarmNotifyGroupUserQuery } from '@/api/ems/base/alarmNotifyGroupUser/types';
/**
*
* @param query
* @returns {*}
*/
export const listAlarmNotifyGroupUser = (query?: AlarmNotifyGroupUserQuery): AxiosPromise<AlarmNotifyGroupUserVO[]> => {
return request({
url: '/ems/base/alarmNotifyGroupUser/list',
method: 'get',
params: query
});
};
/**
*
* @param id
*/
export const getAlarmNotifyGroupUser = (id: AlarmNotifyGroupUserId): AxiosPromise<AlarmNotifyGroupUserVO> => {
return request({
url: '/ems/base/alarmNotifyGroupUser/' + id,
method: 'get'
});
};
/**
*
* @param ids
*/
export const getAlarmNotifyGroupUserByIds = (ids: AlarmNotifyGroupUserIds): AxiosPromise<AlarmNotifyGroupUserVO[]> => {
return request({
url: '/ems/base/alarmNotifyGroupUser/listByIds',
method: 'post',
data: ids
});
};
/**
*
* @param query
*/
export const countAlarmNotifyGroupUser = (query?: AlarmNotifyGroupUserQuery): AxiosPromise<number> => {
return request({
url: '/ems/base/alarmNotifyGroupUser/count',
method: 'get',
params: query
});
};
/**
*
* @param query
*/
export const existsAlarmNotifyGroupUser = (query?: AlarmNotifyGroupUserQuery): AxiosPromise<boolean> => {
return request({
url: '/ems/base/alarmNotifyGroupUser/exists',
method: 'get',
params: query
});
};
/**
*
* @param data
*/
export const addAlarmNotifyGroupUser = (data: AlarmNotifyGroupUserForm) => {
return request({
url: '/ems/base/alarmNotifyGroupUser',
method: 'post',
data: data
});
};
/**
*
* @param data
*/
export const updateAlarmNotifyGroupUser = (data: AlarmNotifyGroupUserForm) => {
return request({
url: '/ems/base/alarmNotifyGroupUser',
method: 'put',
data: data
});
};
/**
*
* @param id
*/
export const delAlarmNotifyGroupUser = (id: AlarmNotifyGroupUserId | AlarmNotifyGroupUserIds) => {
return request({
url: '/ems/base/alarmNotifyGroupUser/' + id,
method: 'delete'
});
};

@ -0,0 +1,10 @@
import type { AlarmNotifyGroupUserVO as AlarmNotifyGroupUserEntity, EmsCrudForm, EmsCrudQuery, EmsId } from '@/api/ems/types';
export type AlarmNotifyGroupUserId = EmsId;
export type AlarmNotifyGroupUserIds = AlarmNotifyGroupUserId[];
export interface AlarmNotifyGroupUserVO extends AlarmNotifyGroupUserEntity {}
export interface AlarmNotifyGroupUserForm extends EmsCrudForm<AlarmNotifyGroupUserVO> {}
export interface AlarmNotifyGroupUserQuery extends EmsCrudQuery<AlarmNotifyGroupUserVO> {}

@ -0,0 +1,99 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { AlarmPushLogVO, AlarmPushLogForm, AlarmPushLogId, AlarmPushLogIds, AlarmPushLogQuery } from '@/api/ems/base/alarmPushLog/types';
/**
*
* @param query
* @returns {*}
*/
export const listAlarmPushLog = (query?: AlarmPushLogQuery): AxiosPromise<AlarmPushLogVO[]> => {
return request({
url: '/ems/base/alarmPushLog/list',
method: 'get',
params: query
});
};
/**
*
* @param id
*/
export const getAlarmPushLog = (id: AlarmPushLogId): AxiosPromise<AlarmPushLogVO> => {
return request({
url: '/ems/base/alarmPushLog/' + id,
method: 'get'
});
};
/**
*
* @param ids
*/
export const getAlarmPushLogByIds = (ids: AlarmPushLogIds): AxiosPromise<AlarmPushLogVO[]> => {
return request({
url: '/ems/base/alarmPushLog/listByIds',
method: 'post',
data: ids
});
};
/**
*
* @param query
*/
export const countAlarmPushLog = (query?: AlarmPushLogQuery): AxiosPromise<number> => {
return request({
url: '/ems/base/alarmPushLog/count',
method: 'get',
params: query
});
};
/**
*
* @param query
*/
export const existsAlarmPushLog = (query?: AlarmPushLogQuery): AxiosPromise<boolean> => {
return request({
url: '/ems/base/alarmPushLog/exists',
method: 'get',
params: query
});
};
/**
*
* @param data
*/
export const addAlarmPushLog = (data: AlarmPushLogForm) => {
return request({
url: '/ems/base/alarmPushLog',
method: 'post',
data: data
});
};
/**
*
* @param data
*/
export const updateAlarmPushLog = (data: AlarmPushLogForm) => {
return request({
url: '/ems/base/alarmPushLog',
method: 'put',
data: data
});
};
/**
*
* @param id
*/
export const delAlarmPushLog = (id: AlarmPushLogId | AlarmPushLogIds) => {
return request({
url: '/ems/base/alarmPushLog/' + id,
method: 'delete'
});
};

@ -0,0 +1,10 @@
import type { AlarmPushLogVO as AlarmPushLogEntity, EmsCrudForm, EmsCrudQuery, EmsId } from '@/api/ems/types';
export type AlarmPushLogId = EmsId;
export type AlarmPushLogIds = AlarmPushLogId[];
export interface AlarmPushLogVO extends AlarmPushLogEntity {}
export interface AlarmPushLogForm extends EmsCrudForm<AlarmPushLogVO> {}
export interface AlarmPushLogQuery extends EmsCrudQuery<AlarmPushLogVO> {}

@ -0,0 +1,99 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { ControlCommandLogVO, ControlCommandLogForm, ControlCommandLogId, ControlCommandLogIds, ControlCommandLogQuery } from '@/api/ems/base/controlCommandLog/types';
/**
*
* @param query
* @returns {*}
*/
export const listControlCommandLog = (query?: ControlCommandLogQuery): AxiosPromise<ControlCommandLogVO[]> => {
return request({
url: '/ems/base/controlCommandLog/list',
method: 'get',
params: query
});
};
/**
*
* @param id
*/
export const getControlCommandLog = (id: ControlCommandLogId): AxiosPromise<ControlCommandLogVO> => {
return request({
url: '/ems/base/controlCommandLog/' + id,
method: 'get'
});
};
/**
*
* @param ids
*/
export const getControlCommandLogByIds = (ids: ControlCommandLogIds): AxiosPromise<ControlCommandLogVO[]> => {
return request({
url: '/ems/base/controlCommandLog/listByIds',
method: 'post',
data: ids
});
};
/**
*
* @param query
*/
export const countControlCommandLog = (query?: ControlCommandLogQuery): AxiosPromise<number> => {
return request({
url: '/ems/base/controlCommandLog/count',
method: 'get',
params: query
});
};
/**
*
* @param query
*/
export const existsControlCommandLog = (query?: ControlCommandLogQuery): AxiosPromise<boolean> => {
return request({
url: '/ems/base/controlCommandLog/exists',
method: 'get',
params: query
});
};
/**
*
* @param data
*/
export const addControlCommandLog = (data: ControlCommandLogForm) => {
return request({
url: '/ems/base/controlCommandLog',
method: 'post',
data: data
});
};
/**
*
* @param data
*/
export const updateControlCommandLog = (data: ControlCommandLogForm) => {
return request({
url: '/ems/base/controlCommandLog',
method: 'put',
data: data
});
};
/**
*
* @param id
*/
export const delControlCommandLog = (id: ControlCommandLogId | ControlCommandLogIds) => {
return request({
url: '/ems/base/controlCommandLog/' + id,
method: 'delete'
});
};

@ -0,0 +1,10 @@
import type { ControlCommandLogVO as ControlCommandLogEntity, EmsCrudForm, EmsCrudQuery, EmsId } from '@/api/ems/types';
export type ControlCommandLogId = EmsId;
export type ControlCommandLogIds = ControlCommandLogId[];
export interface ControlCommandLogVO extends ControlCommandLogEntity {}
export interface ControlCommandLogForm extends EmsCrudForm<ControlCommandLogVO> {}
export interface ControlCommandLogQuery extends EmsCrudQuery<ControlCommandLogVO> {}

@ -0,0 +1,99 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { DataExportTaskVO, DataExportTaskForm, DataExportTaskId, DataExportTaskIds, DataExportTaskQuery } from '@/api/ems/base/dataExportTask/types';
/**
*
* @param query
* @returns {*}
*/
export const listDataExportTask = (query?: DataExportTaskQuery): AxiosPromise<DataExportTaskVO[]> => {
return request({
url: '/ems/base/dataExportTask/list',
method: 'get',
params: query
});
};
/**
*
* @param id
*/
export const getDataExportTask = (id: DataExportTaskId): AxiosPromise<DataExportTaskVO> => {
return request({
url: '/ems/base/dataExportTask/' + id,
method: 'get'
});
};
/**
*
* @param ids
*/
export const getDataExportTaskByIds = (ids: DataExportTaskIds): AxiosPromise<DataExportTaskVO[]> => {
return request({
url: '/ems/base/dataExportTask/listByIds',
method: 'post',
data: ids
});
};
/**
*
* @param query
*/
export const countDataExportTask = (query?: DataExportTaskQuery): AxiosPromise<number> => {
return request({
url: '/ems/base/dataExportTask/count',
method: 'get',
params: query
});
};
/**
*
* @param query
*/
export const existsDataExportTask = (query?: DataExportTaskQuery): AxiosPromise<boolean> => {
return request({
url: '/ems/base/dataExportTask/exists',
method: 'get',
params: query
});
};
/**
*
* @param data
*/
export const addDataExportTask = (data: DataExportTaskForm) => {
return request({
url: '/ems/base/dataExportTask',
method: 'post',
data: data
});
};
/**
*
* @param data
*/
export const updateDataExportTask = (data: DataExportTaskForm) => {
return request({
url: '/ems/base/dataExportTask',
method: 'put',
data: data
});
};
/**
*
* @param id
*/
export const delDataExportTask = (id: DataExportTaskId | DataExportTaskIds) => {
return request({
url: '/ems/base/dataExportTask/' + id,
method: 'delete'
});
};

@ -0,0 +1,10 @@
import type { DataExportTaskVO as DataExportTaskEntity, EmsCrudForm, EmsCrudQuery, EmsId } from '@/api/ems/types';
export type DataExportTaskId = EmsId;
export type DataExportTaskIds = DataExportTaskId[];
export interface DataExportTaskVO extends DataExportTaskEntity {}
export interface DataExportTaskForm extends EmsCrudForm<DataExportTaskVO> {}
export interface DataExportTaskQuery extends EmsCrudQuery<DataExportTaskVO> {}

@ -0,0 +1,170 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import {
IntegrationCommandDispatchForm,
IntegrationConnectionTestForm,
IntegrationConnectionTestResult,
IntegrationEndpointVO,
IntegrationEndpointForm,
IntegrationEndpointId,
IntegrationEndpointIds,
IntegrationEndpointQuery,
IntegrationPayloadPreviewForm,
IntegrationPayloadPreviewResult,
IntegrationWorkbenchDetail
} from '@/api/ems/base/integrationEndpoint/types';
import type { ControlCommandLogVO } from '@/api/ems/types';
/**
*
* @param query
* @returns {*}
*/
export const listIntegrationEndpoint = (query?: IntegrationEndpointQuery): AxiosPromise<IntegrationEndpointVO[]> => {
return request({
url: '/ems/base/integrationEndpoint/list',
method: 'get',
params: query
});
};
/**
*
*
*/
export const listIntegrationEndpointAll = (): AxiosPromise<IntegrationEndpointVO[]> => {
return request({
url: '/ems/base/integrationEndpoint/listAll',
method: 'get'
});
};
/**
*
* @param id
*/
export const getIntegrationEndpoint = (id: IntegrationEndpointId): AxiosPromise<IntegrationEndpointVO> => {
return request({
url: '/ems/base/integrationEndpoint/' + id,
method: 'get'
});
};
/**
*
* @param ids
*/
export const getIntegrationEndpointByIds = (ids: IntegrationEndpointIds): AxiosPromise<IntegrationEndpointVO[]> => {
return request({
url: '/ems/base/integrationEndpoint/listByIds',
method: 'post',
data: ids
});
};
/**
*
* @param query
*/
export const countIntegrationEndpoint = (query?: IntegrationEndpointQuery): AxiosPromise<number> => {
return request({
url: '/ems/base/integrationEndpoint/count',
method: 'get',
params: query
});
};
/**
*
* @param query
*/
export const existsIntegrationEndpoint = (query?: IntegrationEndpointQuery): AxiosPromise<boolean> => {
return request({
url: '/ems/base/integrationEndpoint/exists',
method: 'get',
params: query
});
};
/**
*
* @param data
*/
export const addIntegrationEndpoint = (data: IntegrationEndpointForm) => {
return request({
url: '/ems/base/integrationEndpoint',
method: 'post',
data: data
});
};
/**
*
* @param data
*/
export const updateIntegrationEndpoint = (data: IntegrationEndpointForm) => {
return request({
url: '/ems/base/integrationEndpoint',
method: 'put',
data: data
});
};
/**
*
* @param id
*/
export const delIntegrationEndpoint = (id: IntegrationEndpointId | IntegrationEndpointIds) => {
return request({
url: '/ems/base/integrationEndpoint/' + id,
method: 'delete'
});
};
/**
*
* @param id
*/
export const getIntegrationWorkbenchDetail = (id: IntegrationEndpointId): AxiosPromise<IntegrationWorkbenchDetail> => {
return request({
url: '/ems/base/integrationEndpoint/workbench/' + id,
method: 'get'
});
};
/**
*
* @param data
*/
export const testIntegrationConnection = (data: IntegrationConnectionTestForm): AxiosPromise<IntegrationConnectionTestResult> => {
return request({
url: '/ems/base/integrationEndpoint/testConnection',
method: 'post',
data
});
};
/**
*
* @param data
*/
export const previewIntegrationPayload = (data: IntegrationPayloadPreviewForm): AxiosPromise<IntegrationPayloadPreviewResult> => {
return request({
url: '/ems/base/integrationEndpoint/previewPayload',
method: 'post',
data
});
};
/**
*
* @param data
*/
export const dispatchIntegrationCommand = (data: IntegrationCommandDispatchForm): AxiosPromise<ControlCommandLogVO> => {
return request({
url: '/ems/base/integrationEndpoint/dispatchCommand',
method: 'post',
data
});
};

@ -0,0 +1,57 @@
import type { EmsCrudForm, EmsCrudQuery, EmsId, IntegrationEndpointVO as IntegrationEndpointEntity } from '@/api/ems/types';
import type { ControlCommandLogVO, IntegrationPointMapVO } from '@/api/ems/types';
export type IntegrationEndpointId = EmsId;
export type IntegrationEndpointIds = IntegrationEndpointId[];
export interface IntegrationEndpointVO extends IntegrationEndpointEntity {}
export interface IntegrationEndpointForm extends EmsCrudForm<IntegrationEndpointVO> {}
export interface IntegrationEndpointQuery extends EmsCrudQuery<IntegrationEndpointVO> {}
export interface IntegrationConnectionTestForm {
endpointId?: EmsId;
endpointName?: string;
endpointType?: string;
accessMode?: string;
host?: string;
port?: number;
configJson?: string;
status?: string;
}
export interface IntegrationConnectionTestResult {
success?: boolean;
endpointName?: string;
title?: string;
message?: string;
checkedItems?: string[];
}
export interface IntegrationPayloadPreviewForm {
endpointId?: EmsId;
mapId?: EmsId;
monitorCode?: string;
metricCode?: string;
commandType?: string;
commandValue?: string;
dataFormat?: string;
}
export interface IntegrationPayloadPreviewResult {
endpointName?: string;
monitorName?: string;
dataFormat?: string;
payloadText?: string;
jsonPayloadText?: string;
xmlPayloadText?: string;
}
export interface IntegrationCommandDispatchForm extends IntegrationPayloadPreviewForm {}
export interface IntegrationWorkbenchDetail {
endpoint?: IntegrationEndpointVO;
pointMaps?: IntegrationPointMapVO[];
commandLogs?: ControlCommandLogVO[];
}

@ -0,0 +1,99 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { IntegrationPointMapVO, IntegrationPointMapForm, IntegrationPointMapId, IntegrationPointMapIds, IntegrationPointMapQuery } from '@/api/ems/base/integrationPointMap/types';
/**
*
* @param query
* @returns {*}
*/
export const listIntegrationPointMap = (query?: IntegrationPointMapQuery): AxiosPromise<IntegrationPointMapVO[]> => {
return request({
url: '/ems/base/integrationPointMap/list',
method: 'get',
params: query
});
};
/**
*
* @param id
*/
export const getIntegrationPointMap = (id: IntegrationPointMapId): AxiosPromise<IntegrationPointMapVO> => {
return request({
url: '/ems/base/integrationPointMap/' + id,
method: 'get'
});
};
/**
*
* @param ids
*/
export const getIntegrationPointMapByIds = (ids: IntegrationPointMapIds): AxiosPromise<IntegrationPointMapVO[]> => {
return request({
url: '/ems/base/integrationPointMap/listByIds',
method: 'post',
data: ids
});
};
/**
*
* @param query
*/
export const countIntegrationPointMap = (query?: IntegrationPointMapQuery): AxiosPromise<number> => {
return request({
url: '/ems/base/integrationPointMap/count',
method: 'get',
params: query
});
};
/**
*
* @param query
*/
export const existsIntegrationPointMap = (query?: IntegrationPointMapQuery): AxiosPromise<boolean> => {
return request({
url: '/ems/base/integrationPointMap/exists',
method: 'get',
params: query
});
};
/**
*
* @param data
*/
export const addIntegrationPointMap = (data: IntegrationPointMapForm) => {
return request({
url: '/ems/base/integrationPointMap',
method: 'post',
data: data
});
};
/**
*
* @param data
*/
export const updateIntegrationPointMap = (data: IntegrationPointMapForm) => {
return request({
url: '/ems/base/integrationPointMap',
method: 'put',
data: data
});
};
/**
*
* @param id
*/
export const delIntegrationPointMap = (id: IntegrationPointMapId | IntegrationPointMapIds) => {
return request({
url: '/ems/base/integrationPointMap/' + id,
method: 'delete'
});
};

@ -0,0 +1,10 @@
import type { EmsCrudForm, EmsCrudQuery, EmsId, IntegrationPointMapVO as IntegrationPointMapEntity } from '@/api/ems/types';
export type IntegrationPointMapId = EmsId;
export type IntegrationPointMapIds = IntegrationPointMapId[];
export interface IntegrationPointMapVO extends IntegrationPointMapEntity {}
export interface IntegrationPointMapForm extends EmsCrudForm<IntegrationPointMapVO> {}
export interface IntegrationPointMapQuery extends EmsCrudQuery<IntegrationPointMapVO> {}

@ -0,0 +1,99 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { MonitorMetricThresholdVO, MonitorMetricThresholdForm, MonitorMetricThresholdId, MonitorMetricThresholdIds, MonitorMetricThresholdQuery } from '@/api/ems/base/monitorMetricThreshold/types';
/**
*
* @param query
* @returns {*}
*/
export const listMonitorMetricThreshold = (query?: MonitorMetricThresholdQuery): AxiosPromise<MonitorMetricThresholdVO[]> => {
return request({
url: '/ems/base/monitorMetricThreshold/list',
method: 'get',
params: query
});
};
/**
*
* @param id
*/
export const getMonitorMetricThreshold = (id: MonitorMetricThresholdId): AxiosPromise<MonitorMetricThresholdVO> => {
return request({
url: '/ems/base/monitorMetricThreshold/' + id,
method: 'get'
});
};
/**
*
* @param ids
*/
export const getMonitorMetricThresholdByIds = (ids: MonitorMetricThresholdIds): AxiosPromise<MonitorMetricThresholdVO[]> => {
return request({
url: '/ems/base/monitorMetricThreshold/listByIds',
method: 'post',
data: ids
});
};
/**
*
* @param query
*/
export const countMonitorMetricThreshold = (query?: MonitorMetricThresholdQuery): AxiosPromise<number> => {
return request({
url: '/ems/base/monitorMetricThreshold/count',
method: 'get',
params: query
});
};
/**
*
* @param query
*/
export const existsMonitorMetricThreshold = (query?: MonitorMetricThresholdQuery): AxiosPromise<boolean> => {
return request({
url: '/ems/base/monitorMetricThreshold/exists',
method: 'get',
params: query
});
};
/**
*
* @param data
*/
export const addMonitorMetricThreshold = (data: MonitorMetricThresholdForm) => {
return request({
url: '/ems/base/monitorMetricThreshold',
method: 'post',
data: data
});
};
/**
*
* @param data
*/
export const updateMonitorMetricThreshold = (data: MonitorMetricThresholdForm) => {
return request({
url: '/ems/base/monitorMetricThreshold',
method: 'put',
data: data
});
};
/**
*
* @param id
*/
export const delMonitorMetricThreshold = (id: MonitorMetricThresholdId | MonitorMetricThresholdIds) => {
return request({
url: '/ems/base/monitorMetricThreshold/' + id,
method: 'delete'
});
};

@ -0,0 +1,10 @@
import type { EmsCrudForm, EmsCrudQuery, EmsId, MonitorMetricThresholdVO as MonitorMetricThresholdEntity } from '@/api/ems/types';
export type MonitorMetricThresholdId = EmsId;
export type MonitorMetricThresholdIds = MonitorMetricThresholdId[];
export interface MonitorMetricThresholdVO extends MonitorMetricThresholdEntity {}
export interface MonitorMetricThresholdForm extends EmsCrudForm<MonitorMetricThresholdVO> {}
export interface MonitorMetricThresholdQuery extends EmsCrudQuery<MonitorMetricThresholdVO> {}

@ -0,0 +1,129 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import {
ReportPeriodSummaryVO,
ReportPeriodSummaryForm,
ReportPeriodSummaryId,
ReportPeriodSummaryIds,
ReportPeriodSummaryQuery
} from '@/api/ems/base/reportPeriodSummary/types';
/**
*
* @param query
* @returns {*}
*/
export const listReportPeriodSummary = (query?: ReportPeriodSummaryQuery): AxiosPromise<ReportPeriodSummaryVO[]> => {
return request({
url: '/ems/base/reportPeriodSummary/list',
method: 'get',
params: query
});
};
/**
*
* @param query
*/
export const listAggregateReportPeriodSummary = (query?: ReportPeriodSummaryQuery): AxiosPromise<ReportPeriodSummaryVO[]> => {
return request({
url: '/ems/base/reportPeriodSummary/aggregateList',
method: 'get',
params: query
});
};
/**
*
* @param id
*/
export const getReportPeriodSummary = (id: ReportPeriodSummaryId): AxiosPromise<ReportPeriodSummaryVO> => {
return request({
url: '/ems/base/reportPeriodSummary/' + id,
method: 'get'
});
};
/**
*
* @param ids
*/
export const getReportPeriodSummaryByIds = (ids: ReportPeriodSummaryIds): AxiosPromise<ReportPeriodSummaryVO[]> => {
return request({
url: '/ems/base/reportPeriodSummary/listByIds',
method: 'post',
data: ids
});
};
/**
*
* @param query
*/
export const countReportPeriodSummary = (query?: ReportPeriodSummaryQuery): AxiosPromise<number> => {
return request({
url: '/ems/base/reportPeriodSummary/count',
method: 'get',
params: query
});
};
/**
*
* @param query
*/
export const existsReportPeriodSummary = (query?: ReportPeriodSummaryQuery): AxiosPromise<boolean> => {
return request({
url: '/ems/base/reportPeriodSummary/exists',
method: 'get',
params: query
});
};
/**
*
* @param data
*/
export const addReportPeriodSummary = (data: ReportPeriodSummaryForm) => {
return request({
url: '/ems/base/reportPeriodSummary',
method: 'post',
data: data
});
};
/**
*
* @param data
*/
export const updateReportPeriodSummary = (data: ReportPeriodSummaryForm) => {
return request({
url: '/ems/base/reportPeriodSummary',
method: 'put',
data: data
});
};
/**
*
* @param id
*/
export const delReportPeriodSummary = (id: ReportPeriodSummaryId | ReportPeriodSummaryIds) => {
return request({
url: '/ems/base/reportPeriodSummary/' + id,
method: 'delete'
});
};
/**
*
* @param query
*/
export const exportAggregateReportPeriodSummary = (query?: ReportPeriodSummaryQuery) => {
return request({
url: '/ems/base/reportPeriodSummary/aggregateExport',
method: 'post',
params: query
});
};

@ -0,0 +1,10 @@
import type { EmsCrudForm, EmsCrudQuery, EmsId, ReportPeriodSummaryVO as ReportPeriodSummaryEntity } from '@/api/ems/types';
export type ReportPeriodSummaryId = EmsId;
export type ReportPeriodSummaryIds = ReportPeriodSummaryId[];
export interface ReportPeriodSummaryVO extends ReportPeriodSummaryEntity {}
export interface ReportPeriodSummaryForm extends EmsCrudForm<ReportPeriodSummaryVO> {}
export interface ReportPeriodSummaryQuery extends EmsCrudQuery<ReportPeriodSummaryVO> {}

@ -0,0 +1,2 @@
// 兼容项目中历史页面对 `@/components/Charts/Chart` 的无后缀导入,避免逐页改动带来回归风险。
export { default } from './Chart.vue';

@ -0,0 +1,304 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover" class="query-card">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="通知组名称" prop="groupName">
<el-input v-model="queryParams.groupName" placeholder="请输入通知组名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="通知渠道" prop="notifyChannel">
<el-select v-model="queryParams.notifyChannel" placeholder="请选择通知渠道" clearable style="width: 180px">
<el-option v-for="item in notifyChannelOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="启用状态" prop="isEnable">
<el-select v-model="queryParams.isEnable" placeholder="请选择启用状态" clearable style="width: 160px">
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never" class="table-card">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['ems/base:alarmNotifyGroup:add']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['ems/base:alarmNotifyGroup:edit']">
修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['ems/base:alarmNotifyGroup:remove']">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['ems/base:alarmNotifyGroup:export']"></el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" :columns="columns" @queryTable="getList" />
</el-row>
</template>
<el-table v-loading="loading" border :data="alarmNotifyGroupList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" />
<el-table-column v-if="columns[0].visible" label="序号" type="index" width="60" align="center" />
<el-table-column v-if="columns[1].visible" label="通知组名称" align="center" prop="groupName" min-width="180" show-overflow-tooltip />
<el-table-column v-if="columns[2].visible" label="通知渠道" align="center" min-width="140">
<template #default="scope">
<dict-tag :options="notifyChannelOptions" :value="scope.row.notifyChannel" />
</template>
</el-table-column>
<el-table-column v-if="columns[3].visible" label="Webhook地址" align="center" prop="webhookUrl" min-width="220" show-overflow-tooltip />
<el-table-column v-if="columns[4].visible" label="启用状态" align="center" width="110">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.isEnable" />
</template>
</el-table-column>
<el-table-column v-if="columns[5].visible" label="备注" align="center" prop="remark" min-width="180" show-overflow-tooltip />
<el-table-column label="操作" align="center" fixed="right" width="110" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['ems/base:alarmNotifyGroup:edit']" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['ems/base:alarmNotifyGroup:remove']" />
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<el-dialog v-model="dialog.visible" :title="dialog.title" width="620px" append-to-body>
<el-form ref="alarmNotifyGroupFormRef" :model="form" :rules="rules" label-width="96px">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="通知组名称" prop="groupName">
<el-input v-model="form.groupName" placeholder="请输入通知组名称" maxlength="64" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="通知渠道" prop="notifyChannel">
<el-select v-model="form.notifyChannel" placeholder="请选择通知渠道" style="width: 100%">
<el-option v-for="item in notifyChannelOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="Webhook地址" prop="webhookUrl">
<el-input v-model="form.webhookUrl" placeholder="请输入Webhook地址" maxlength="500" />
</el-form-item>
<el-form-item label="启用状态" prop="isEnable">
<el-radio-group v-model="form.isEnable">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="3" maxlength="200" show-word-limit placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="AlarmNotifyGroup" lang="ts">
import { addAlarmNotifyGroup, delAlarmNotifyGroup, getAlarmNotifyGroup, listAlarmNotifyGroup, updateAlarmNotifyGroup } from '@/api/ems/base/alarmNotifyGroup';
import type { AlarmNotifyGroupForm, AlarmNotifyGroupQuery, AlarmNotifyGroupVO } from '@/api/ems/base/alarmNotifyGroup/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const notifyChannelOptions = [
{ label: '站内消息', value: 'SITE' },
{ label: '短信', value: 'SMS' },
{ label: '邮件', value: 'EMAIL' },
{ label: 'Webhook', value: 'WEBHOOK' }
];
const alarmNotifyGroupList = ref<AlarmNotifyGroupVO[]>([]);
const loading = ref(false);
const buttonLoading = ref(false);
const showSearch = ref(true);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const ids = ref<Array<string | number>>([]);
const columns = ref<FieldOption[]>([
{ key: 0, label: '序号', visible: true, children: [] },
{ key: 1, label: '通知组名称', visible: true, children: [] },
{ key: 2, label: '通知渠道', visible: true, children: [] },
{ key: 3, label: 'Webhook地址', visible: true, children: [] },
{ key: 4, label: '启用状态', visible: true, children: [] },
{ key: 5, label: '备注', visible: true, children: [] }
]);
const queryFormRef = ref<ElFormInstance>();
const alarmNotifyGroupFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const createFormData = (): AlarmNotifyGroupForm => ({
id: undefined,
groupName: undefined,
notifyChannel: undefined,
webhookUrl: undefined,
//
isEnable: '0',
remark: undefined
});
const data = reactive<PageData<AlarmNotifyGroupForm, AlarmNotifyGroupQuery>>({
form: createFormData(),
queryParams: {
pageNum: 1,
pageSize: 10,
groupName: undefined,
notifyChannel: undefined,
isEnable: undefined
},
rules: {
groupName: [{ required: true, message: '通知组名称不能为空', trigger: 'blur' }],
notifyChannel: [{ required: true, message: '通知渠道不能为空', trigger: 'change' }]
}
});
const { form, queryParams, rules } = toRefs(data);
const getList = async () => {
loading.value = true;
try {
const res = await listAlarmNotifyGroup(queryParams.value);
alarmNotifyGroupList.value = res.rows ?? [];
total.value = res.total ?? 0;
} finally {
loading.value = false;
}
};
const reset = () => {
form.value = createFormData();
alarmNotifyGroupFormRef.value?.resetFields();
};
const cancel = () => {
dialog.visible = false;
reset();
};
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
const handleSelectionChange = (selection: AlarmNotifyGroupVO[]) => {
ids.value = selection.map((item) => item.id as string | number);
single.value = selection.length !== 1;
multiple.value = selection.length === 0;
};
const handleAdd = () => {
reset();
dialog.title = '新增通知组';
dialog.visible = true;
};
const handleUpdate = async (row?: AlarmNotifyGroupVO) => {
reset();
const id = row?.id ?? ids.value[0];
const res = await getAlarmNotifyGroup(id);
Object.assign(form.value, res.data);
dialog.title = '修改通知组';
dialog.visible = true;
};
const submitForm = async () => {
const valid = await alarmNotifyGroupFormRef.value?.validate().catch(() => false);
if (!valid) {
return;
}
buttonLoading.value = true;
try {
//
const payload: AlarmNotifyGroupForm = {
id: form.value.id,
groupName: form.value.groupName,
notifyChannel: form.value.notifyChannel,
webhookUrl: form.value.webhookUrl,
isEnable: form.value.isEnable,
remark: form.value.remark
};
if (payload.id) {
await updateAlarmNotifyGroup(payload);
} else {
await addAlarmNotifyGroup(payload);
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
} finally {
buttonLoading.value = false;
}
};
const handleDelete = async (row?: AlarmNotifyGroupVO) => {
const targetIds = row?.id ?? ids.value;
await proxy?.$modal.confirm(`是否确认删除通知组编号为“${targetIds}”的数据项?`);
await delAlarmNotifyGroup(targetIds);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
const handleExport = () => {
proxy?.download(
'ems/base/alarmNotifyGroup/export',
{
...queryParams.value
},
`alarmNotifyGroup_${new Date().getTime()}.xlsx`
);
};
onMounted(() => {
getList();
});
</script>
<style scoped lang="scss">
.query-card {
:deep(.el-form-item) {
margin-bottom: 12px;
}
}
.table-card {
:deep(.el-card__body) {
padding-top: 10px;
}
}
</style>

@ -0,0 +1,356 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="82px">
<el-form-item label="通知组" prop="groupId">
<el-select v-model="queryParams.groupId" placeholder="请选择通知组" clearable filterable style="width: 220px">
<el-option v-for="item in groupOptions" :key="item.id" :label="item.groupName" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="用户名称" prop="userName">
<el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="queryParams.phone" placeholder="请输入手机号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="queryParams.email" placeholder="请输入邮箱" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="启用状态" prop="isEnable">
<el-select v-model="queryParams.isEnable" placeholder="请选择启用状态" clearable style="width: 160px">
<el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['ems/base:alarmNotifyGroupUser:add']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['ems/base:alarmNotifyGroupUser:edit']">
修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['ems/base:alarmNotifyGroupUser:remove']">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['ems/base:alarmNotifyGroupUser:export']"></el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" :columns="columns" @queryTable="getList" />
</el-row>
</template>
<el-table v-loading="loading" border :data="alarmNotifyGroupUserList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="columns[0].visible" label="序号" type="index" width="60" align="center" />
<el-table-column v-if="columns[1].visible" label="通知组" min-width="180" show-overflow-tooltip>
<template #default="scope">
{{ resolveGroupName(scope.row) }}
</template>
</el-table-column>
<el-table-column v-if="columns[2].visible" label="用户名称" min-width="160" show-overflow-tooltip>
<template #default="scope">
{{ resolveUserDisplayName(scope.row) }}
</template>
</el-table-column>
<el-table-column v-if="columns[3].visible" label="手机号" align="center" prop="phone" min-width="140" />
<el-table-column v-if="columns[4].visible" label="邮箱" align="center" prop="email" min-width="200" show-overflow-tooltip />
<el-table-column v-if="columns[5].visible" label="启用状态" align="center" width="110">
<template #default="scope">
<dict-tag :options="statusOptions" :value="scope.row.isEnable" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" width="110" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['ems/base:alarmNotifyGroupUser:edit']" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['ems/base:alarmNotifyGroupUser:remove']" />
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<el-dialog :title="dialog.title" v-model="dialog.visible" width="620px" append-to-body>
<el-form ref="alarmNotifyGroupUserFormRef" :model="form" :rules="rules" label-width="88px">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="通知组" prop="groupId">
<el-select v-model="form.groupId" placeholder="请选择通知组" filterable style="width: 100%">
<el-option v-for="item in groupOptions" :key="item.id" :label="item.groupName" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="通知用户" prop="userId">
<el-select v-model="form.userId" placeholder="请选择通知用户" filterable style="width: 100%" @change="handleUserChange">
<el-option v-for="item in userOptions" :key="item.userId" :label="buildUserOptionLabel(item)" :value="item.userId" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="用户名称">
<el-input :model-value="resolveUserDisplayName(form)" readonly />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="手机号">
<el-input v-model="form.phone" readonly />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="邮箱">
<el-input v-model="form.email" readonly />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="启用状态" prop="isEnable">
<el-radio-group v-model="form.isEnable">
<el-radio v-for="item in statusOptions" :key="item.value" :value="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="AlarmNotifyGroupUser" lang="ts">
import { useDict } from '@/utils/dict';
import { listAlarmNotifyGroupAll } from '@/api/ems/base/alarmNotifyGroup';
import { listAlarmNotifyGroupUser, getAlarmNotifyGroupUser, delAlarmNotifyGroupUser, addAlarmNotifyGroupUser, updateAlarmNotifyGroupUser } from '@/api/ems/base/alarmNotifyGroupUser';
import type { AlarmNotifyGroupVO } from '@/api/ems/base/alarmNotifyGroup/types';
import type { AlarmNotifyGroupUserVO, AlarmNotifyGroupUserQuery, AlarmNotifyGroupUserForm } from '@/api/ems/base/alarmNotifyGroupUser/types';
import { optionSelect } from '@/api/system/user';
import type { UserVO } from '@/api/system/user/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const dict = reactive({
type: useDict('sys_normal_disable') as any
});
const statusOptions = computed(() => dict.type.sys_normal_disable ?? []);
const groupOptions = ref<AlarmNotifyGroupVO[]>([]);
const userOptions = ref<UserVO[]>([]);
const alarmNotifyGroupUserList = ref<AlarmNotifyGroupUserVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const columns = ref<FieldOption[]>([
{ key: 0, label: '序号', visible: true, children: [] },
{ key: 1, label: '通知组', visible: true, children: [] },
{ key: 2, label: '用户名称', visible: true, children: [] },
{ key: 3, label: '手机号', visible: true, children: [] },
{ key: 4, label: '邮箱', visible: true, children: [] },
{ key: 5, label: '启用状态', visible: true, children: [] }
]);
const queryFormRef = ref<ElFormInstance>();
const alarmNotifyGroupUserFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const createFormData = (): AlarmNotifyGroupUserForm => ({
id: undefined,
groupId: undefined,
groupName: undefined,
userId: undefined,
userName: undefined,
nickName: undefined,
phone: undefined,
email: undefined,
isEnable: '0'
});
const data = reactive<PageData<AlarmNotifyGroupUserForm, AlarmNotifyGroupUserQuery>>({
form: createFormData(),
queryParams: {
pageNum: 1,
pageSize: 10,
groupId: undefined,
userName: undefined,
phone: undefined,
email: undefined,
isEnable: undefined,
params: {}
},
rules: {
groupId: [{ required: true, message: '通知组不能为空', trigger: 'change' }],
userId: [{ required: true, message: '通知用户不能为空', trigger: 'change' }]
}
});
const { queryParams, form, rules } = toRefs(data);
const buildUserOptionLabel = (user: UserVO) => `${user.nickName || user.userName}${user.userName ? ` (${user.userName})` : ''}`;
const resolveGroupName = (row?: Partial<AlarmNotifyGroupUserVO> | null) =>
row?.groupName || groupOptions.value.find((item) => item.id === row?.groupId)?.groupName || (row?.groupId ? String(row.groupId) : '-');
const resolveUserDisplayName = (row?: Partial<AlarmNotifyGroupUserVO> | Partial<AlarmNotifyGroupUserForm> | null) =>
row?.nickName || row?.userName || userOptions.value.find((item) => item.userId === row?.userId)?.nickName || row?.userId || '-';
const syncUserSnapshot = (userId?: string | number) => {
const selectedUser = userOptions.value.find((item) => item.userId === userId);
//
form.value.userName = selectedUser?.userName;
form.value.nickName = selectedUser?.nickName;
form.value.phone = selectedUser?.phonenumber;
form.value.email = selectedUser?.email;
};
const loadOptions = async () => {
const [groupRes, userRes] = await Promise.all([listAlarmNotifyGroupAll(), optionSelect()]);
groupOptions.value = (((groupRes as any).rows ?? (groupRes as any).data ?? groupRes) || []) as AlarmNotifyGroupVO[];
userOptions.value = (((userRes as any).rows ?? (userRes as any).data ?? userRes) || []) as UserVO[];
};
const getList = async () => {
loading.value = true;
try {
const res = await listAlarmNotifyGroupUser(queryParams.value);
alarmNotifyGroupUserList.value = ((res as any).rows ?? []) as AlarmNotifyGroupUserVO[];
total.value = Number((res as any).total ?? 0);
} finally {
loading.value = false;
}
};
const cancel = () => {
reset();
dialog.visible = false;
};
const reset = () => {
form.value = createFormData();
alarmNotifyGroupUserFormRef.value?.resetFields();
};
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
const handleSelectionChange = (selection: AlarmNotifyGroupUserVO[]) => {
ids.value = selection.map((item) => item.id as string | number);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加报警通知组成员';
};
const handleUpdate = async (row?: AlarmNotifyGroupUserVO) => {
reset();
const targetId = row?.id ?? ids.value[0];
if (!targetId) {
return;
}
const res = await getAlarmNotifyGroupUser(targetId);
form.value = {
...createFormData(),
...((res as any).data ?? {})
};
dialog.visible = true;
dialog.title = '修改报警通知组成员';
};
const handleUserChange = (userId?: string | number) => {
syncUserSnapshot(userId);
};
const submitForm = () => {
alarmNotifyGroupUserFormRef.value?.validate(async (valid: boolean) => {
if (!valid) {
return;
}
buttonLoading.value = true;
try {
syncUserSnapshot(form.value.userId);
if (form.value.id) {
await updateAlarmNotifyGroupUser(form.value);
} else {
await addAlarmNotifyGroupUser(form.value);
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
} finally {
buttonLoading.value = false;
}
});
};
const handleDelete = async (row?: AlarmNotifyGroupUserVO) => {
const targetIds = row?.id ?? ids.value;
await proxy?.$modal.confirm(`是否确认删除报警通知组成员编号为"${targetIds}"的数据项?`);
await delAlarmNotifyGroupUser(targetIds);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
const handleExport = () => {
proxy?.download(
'ems/base/alarmNotifyGroupUser/export',
{
...queryParams.value
},
`alarmNotifyGroupUser_${new Date().getTime()}.xlsx`
);
};
onMounted(async () => {
await loadOptions();
await getList();
});
</script>

@ -0,0 +1,380 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="82px">
<el-form-item label="告警标题" prop="alarmTitle">
<el-input v-model="queryParams.alarmTitle" placeholder="请输入告警标题" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="点位名称" prop="monitorName">
<el-input v-model="queryParams.monitorName" placeholder="请输入点位名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="推送渠道" prop="channelType">
<el-select v-model="queryParams.channelType" placeholder="请选择推送渠道" clearable style="width: 160px">
<el-option v-for="item in channelTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="推送状态" prop="pushStatus">
<el-select v-model="queryParams.pushStatus" placeholder="请选择推送状态" clearable style="width: 160px">
<el-option v-for="item in pushStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="推送目标" prop="targetValue">
<el-input v-model="queryParams.targetValue" placeholder="请输入推送目标" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="推送时间" prop="pushTime">
<el-date-picker clearable v-model="queryParams.pushTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择推送时间" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['ems/base:alarmPushLog:add']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['ems/base:alarmPushLog:edit']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['ems/base:alarmPushLog:remove']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['ems/base:alarmPushLog:export']"></el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" :columns="columns" @queryTable="getList" />
</el-row>
</template>
<el-table v-loading="loading" border :data="alarmPushLogList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="columns[0].visible" label="序号" type="index" width="60" align="center" />
<el-table-column v-if="columns[1].visible" label="告警标题" align="center" min-width="180" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.alarmTitle || '-' }}
</template>
</el-table-column>
<el-table-column v-if="columns[2].visible" label="点位名称" align="center" min-width="180" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.monitorName || scope.row.monitorCode || '-' }}
</template>
</el-table-column>
<el-table-column v-if="columns[3].visible" label="推送渠道" align="center" width="110">
<template #default="scope">
<dict-tag :options="channelTypeOptions" :value="scope.row.channelType" />
</template>
</el-table-column>
<el-table-column v-if="columns[4].visible" label="推送目标" align="center" prop="targetValue" min-width="160" show-overflow-tooltip />
<el-table-column v-if="columns[5].visible" label="告警级别" align="center" prop="alarmLevel" width="110" />
<el-table-column v-if="columns[6].visible" label="推送状态" align="center" width="110">
<template #default="scope">
<dict-tag :options="pushStatusOptions" :value="scope.row.pushStatus" />
</template>
</el-table-column>
<el-table-column v-if="columns[7].visible" label="响应信息" align="center" prop="responseMsg" min-width="220" show-overflow-tooltip />
<el-table-column v-if="columns[8].visible" label="推送时间" align="center" prop="pushTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.pushTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" width="110" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['ems/base:alarmPushLog:edit']" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['ems/base:alarmPushLog:remove']" />
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<el-dialog :title="dialog.title" v-model="dialog.visible" width="720px" append-to-body>
<el-form ref="alarmPushLogFormRef" :model="form" :rules="rules" label-width="96px">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="关联告警" prop="alarmObjId">
<el-select v-model="form.alarmObjId" placeholder="请选择关联告警" filterable style="width: 100%" @change="handleAlarmChange">
<el-option v-for="item in alarmOptions" :key="item.objId" :label="buildAlarmOptionLabel(item)" :value="item.objId" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="告警级别">
<el-input v-model="form.alarmLevel" readonly />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="推送渠道" prop="channelType">
<el-select v-model="form.channelType" placeholder="请选择推送渠道" style="width: 100%">
<el-option v-for="item in channelTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="推送状态" prop="pushStatus">
<el-select v-model="form.pushStatus" placeholder="请选择推送状态" style="width: 100%">
<el-option v-for="item in pushStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="推送目标" prop="targetValue">
<el-input v-model="form.targetValue" placeholder="请输入推送目标,如手机号、邮箱或 Webhook 地址" />
</el-form-item>
<el-form-item label="推送内容" prop="pushContent">
<editor v-model="form.pushContent" :min-height="192" />
</el-form-item>
<el-form-item label="响应信息" prop="responseMsg">
<el-input v-model="form.responseMsg" type="textarea" :rows="3" placeholder="请输入响应信息" />
</el-form-item>
<el-form-item label="推送时间" prop="pushTime">
<el-date-picker clearable v-model="form.pushTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择推送时间" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="AlarmPushLog" lang="ts">
import { listAlarmPushLog, getAlarmPushLog, delAlarmPushLog, addAlarmPushLog, updateAlarmPushLog } from '@/api/ems/base/alarmPushLog';
import { listRecordAlarmData } from '@/api/ems/record/recordAlarmData';
import type { AlarmPushLogVO, AlarmPushLogQuery, AlarmPushLogForm } from '@/api/ems/base/alarmPushLog/types';
import type { EmsRecordAlarmDataVO } from '@/api/ems/types';
import { parseTime } from '@/utils/ruoyi';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const channelTypeOptions = [
{ label: '站内消息', value: 'SITE' },
{ label: '短信', value: 'SMS' },
{ label: '邮件', value: 'EMAIL' },
{ label: 'Webhook', value: 'WEBHOOK' }
];
const pushStatusOptions = [
{ label: '待推送', value: 'PENDING' },
{ label: '推送中', value: 'PROCESSING' },
{ label: '推送成功', value: 'SUCCESS' },
{ label: '推送失败', value: 'FAIL' }
];
const alarmOptions = ref<EmsRecordAlarmDataVO[]>([]);
const alarmPushLogList = ref<AlarmPushLogVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const columns = ref<FieldOption[]>([
{ key: 0, label: '序号', visible: true, children: [] },
{ key: 1, label: '告警标题', visible: true, children: [] },
{ key: 2, label: '点位名称', visible: true, children: [] },
{ key: 3, label: '推送渠道', visible: true, children: [] },
{ key: 4, label: '推送目标', visible: true, children: [] },
{ key: 5, label: '告警级别', visible: true, children: [] },
{ key: 6, label: '推送状态', visible: true, children: [] },
{ key: 7, label: '响应信息', visible: true, children: [] },
{ key: 8, label: '推送时间', visible: true, children: [] }
]);
const queryFormRef = ref<ElFormInstance>();
const alarmPushLogFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const createFormData = (): AlarmPushLogForm => ({
id: undefined,
alarmObjId: undefined,
alarmTitle: undefined,
monitorCode: undefined,
monitorName: undefined,
channelType: 'SITE',
targetValue: undefined,
alarmLevel: undefined,
pushContent: undefined,
pushStatus: 'PENDING',
responseMsg: undefined,
pushTime: undefined
});
const data = reactive<PageData<AlarmPushLogForm, AlarmPushLogQuery>>({
form: createFormData(),
queryParams: {
pageNum: 1,
pageSize: 10,
alarmTitle: undefined,
monitorName: undefined,
channelType: undefined,
targetValue: undefined,
pushStatus: undefined,
pushTime: undefined,
params: {}
},
rules: {
alarmObjId: [{ required: true, message: '关联告警不能为空', trigger: 'change' }],
channelType: [{ required: true, message: '推送渠道不能为空', trigger: 'change' }],
targetValue: [{ required: true, message: '推送目标不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
const buildAlarmOptionLabel = (item: EmsRecordAlarmDataVO) =>
`${item.alarmTitle || '未命名告警'} / ${item.monitorName || item.monitorId || '-'} / ${parseTime(item.collectTime, '{y}-{m}-{d} {h}:{i}:{s}') || '-'}`;
const syncAlarmSnapshot = (alarmObjId?: string | number) => {
const selectedAlarm = alarmOptions.value.find((item) => item.objId === alarmObjId);
//
form.value.alarmTitle = selectedAlarm?.alarmTitle;
form.value.monitorCode = selectedAlarm?.monitorId;
form.value.monitorName = selectedAlarm?.monitorName;
form.value.alarmLevel = selectedAlarm?.alarmLevel;
};
const loadAlarmOptions = async () => {
//
const response = await listRecordAlarmData({
pageNum: 1,
pageSize: 1000
} as any);
alarmOptions.value = (((response as any).rows ?? (response as any).data ?? []) as EmsRecordAlarmDataVO[]) || [];
};
const getList = async () => {
loading.value = true;
try {
const res = await listAlarmPushLog(queryParams.value);
alarmPushLogList.value = ((res as any).rows ?? []) as AlarmPushLogVO[];
total.value = Number((res as any).total ?? 0);
} finally {
loading.value = false;
}
};
const cancel = () => {
reset();
dialog.visible = false;
};
const reset = () => {
form.value = createFormData();
alarmPushLogFormRef.value?.resetFields();
};
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
const handleSelectionChange = (selection: AlarmPushLogVO[]) => {
ids.value = selection.map((item) => item.id as string | number);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加报警推送日志';
};
const handleUpdate = async (row?: AlarmPushLogVO) => {
reset();
const targetId = row?.id ?? ids.value[0];
if (!targetId) {
return;
}
const res = await getAlarmPushLog(targetId);
form.value = {
...createFormData(),
...((res as any).data ?? {})
};
dialog.visible = true;
dialog.title = '修改报警推送日志';
};
const handleAlarmChange = (alarmObjId?: string | number) => {
syncAlarmSnapshot(alarmObjId);
};
const submitForm = () => {
alarmPushLogFormRef.value?.validate(async (valid: boolean) => {
if (!valid) {
return;
}
buttonLoading.value = true;
try {
syncAlarmSnapshot(form.value.alarmObjId);
if (form.value.id) {
await updateAlarmPushLog(form.value);
} else {
await addAlarmPushLog(form.value);
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
} finally {
buttonLoading.value = false;
}
});
};
const handleDelete = async (row?: AlarmPushLogVO) => {
const targetIds = row?.id ?? ids.value;
await proxy?.$modal.confirm(`是否确认删除报警推送日志编号为"${targetIds}"的数据项?`);
await delAlarmPushLog(targetIds);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
const handleExport = () => {
proxy?.download(
'ems/base/alarmPushLog/export',
{
...queryParams.value
},
`alarmPushLog_${new Date().getTime()}.xlsx`
);
};
onMounted(async () => {
await loadAlarmOptions();
await getList();
});
</script>

@ -0,0 +1,413 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="82px">
<el-form-item label="端点名称" prop="endpointId">
<el-select v-model="queryParams.endpointId" placeholder="请选择端点" clearable filterable style="width: 220px">
<el-option v-for="item in endpointOptions" :key="item.id" :label="item.endpointName" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="点位名称" prop="monitorCode">
<el-tree-select
v-model="queryParams.monitorCode"
:data="monitorTreeOptions"
:props="monitorTreeProps"
placeholder="请选择点位"
clearable
filterable
check-strictly
style="width: 240px"
/>
</el-form-item>
<el-form-item label="命令类型" prop="commandType">
<el-select v-model="queryParams.commandType" placeholder="请选择命令类型" clearable style="width: 160px">
<el-option v-for="item in commandTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="执行状态" prop="executeStatus">
<el-select v-model="queryParams.executeStatus" placeholder="请选择执行状态" clearable style="width: 160px">
<el-option v-for="item in executeStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['ems/base:controlCommandLog:add']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['ems/base:controlCommandLog:edit']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['ems/base:controlCommandLog:remove']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['ems/base:controlCommandLog:export']"></el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" :columns="columns" @queryTable="getList" />
</el-row>
</template>
<el-table v-loading="loading" border :data="controlCommandLogList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="columns[0].visible" label="序号" type="index" width="60" align="center" />
<el-table-column v-if="columns[1].visible" label="端点名称" align="center" min-width="180" show-overflow-tooltip>
<template #default="scope">
{{ resolveEndpointName(scope.row) }}
</template>
</el-table-column>
<el-table-column v-if="columns[2].visible" label="点位名称" align="center" min-width="180" show-overflow-tooltip>
<template #default="scope">
{{ resolveMonitorName(scope.row) }}
</template>
</el-table-column>
<el-table-column v-if="columns[3].visible" label="命令类型" align="center" width="110">
<template #default="scope">
<dict-tag :options="commandTypeOptions" :value="scope.row.commandType" />
</template>
</el-table-column>
<el-table-column v-if="columns[4].visible" label="命令内容" align="center" prop="commandContent" min-width="240" show-overflow-tooltip />
<el-table-column v-if="columns[5].visible" label="执行状态" align="center" width="110">
<template #default="scope">
<dict-tag :options="executeStatusOptions" :value="scope.row.executeStatus" />
</template>
</el-table-column>
<el-table-column v-if="columns[6].visible" label="响应内容" align="center" prop="responseContent" min-width="240" show-overflow-tooltip />
<el-table-column v-if="columns[7].visible" label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" width="110" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['ems/base:controlCommandLog:edit']" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['ems/base:controlCommandLog:remove']" />
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<el-dialog :title="dialog.title" v-model="dialog.visible" width="720px" append-to-body>
<el-form ref="controlCommandLogFormRef" :model="form" :rules="rules" label-width="96px">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="端点名称" prop="endpointId">
<el-select v-model="form.endpointId" placeholder="请选择端点" filterable style="width: 100%" @change="handleEndpointChange">
<el-option v-for="item in endpointOptions" :key="item.id" :label="item.endpointName" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="点位名称" prop="monitorCode">
<el-tree-select
v-model="form.monitorCode"
:data="monitorTreeOptions"
:props="monitorTreeProps"
placeholder="请选择点位"
filterable
check-strictly
style="width: 100%"
@change="handleMonitorChange"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="命令类型" prop="commandType">
<el-select v-model="form.commandType" placeholder="请选择命令类型" style="width: 100%">
<el-option v-for="item in commandTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="执行状态" prop="executeStatus">
<el-select v-model="form.executeStatus" placeholder="请选择执行状态" style="width: 100%">
<el-option v-for="item in executeStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="命令内容" prop="commandContent">
<editor v-model="form.commandContent" :min-height="192" />
</el-form-item>
<el-form-item label="响应内容" prop="responseContent">
<editor v-model="form.responseContent" :min-height="192" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="ControlCommandLog" lang="ts">
import { listIntegrationEndpointAll } from '@/api/ems/base/integrationEndpoint';
import { getMonitorInfoTree } from '@/api/ems/base/baseMonitorInfo';
import { listControlCommandLog, getControlCommandLog, delControlCommandLog, addControlCommandLog, updateControlCommandLog } from '@/api/ems/base/controlCommandLog';
import type { IntegrationEndpointVO } from '@/api/ems/base/integrationEndpoint/types';
import type { ControlCommandLogVO, ControlCommandLogQuery, ControlCommandLogForm } from '@/api/ems/base/controlCommandLog/types';
import type { EmsTreeNode } from '@/api/ems/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
interface MonitorTreeNode extends EmsTreeNode {
label?: string;
value?: string | number;
children?: MonitorTreeNode[];
}
const commandTypeOptions = [
{ label: '读取指令', value: 'READ' },
{ label: '写入指令', value: 'WRITE' },
{ label: '心跳校验', value: 'PING' },
{ label: '复位指令', value: 'RESET' }
];
const executeStatusOptions = [
{ label: '待执行', value: 'PENDING' },
{ label: '执行中', value: 'PROCESSING' },
{ label: '执行成功', value: 'SUCCESS' },
{ label: '执行失败', value: 'FAIL' }
];
const endpointOptions = ref<IntegrationEndpointVO[]>([]);
const monitorTreeOptions = ref<MonitorTreeNode[]>([]);
const monitorNameMap = ref<Record<string, string>>({});
const controlCommandLogList = ref<ControlCommandLogVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const columns = ref<FieldOption[]>([
{ key: 0, label: '序号', visible: true, children: [] },
{ key: 1, label: '端点名称', visible: true, children: [] },
{ key: 2, label: '点位名称', visible: true, children: [] },
{ key: 3, label: '命令类型', visible: true, children: [] },
{ key: 4, label: '命令内容', visible: true, children: [] },
{ key: 5, label: '执行状态', visible: true, children: [] },
{ key: 6, label: '响应内容', visible: true, children: [] },
{ key: 7, label: '创建时间', visible: true, children: [] }
]);
const queryFormRef = ref<ElFormInstance>();
const controlCommandLogFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const monitorTreeProps = {
label: 'label',
value: 'value',
children: 'children'
};
const createFormData = (): ControlCommandLogForm => ({
id: undefined,
endpointId: undefined,
endpointName: undefined,
monitorCode: undefined,
monitorName: undefined,
commandType: 'WRITE',
commandContent: undefined,
executeStatus: 'PENDING',
responseContent: undefined
});
const data = reactive<PageData<ControlCommandLogForm, ControlCommandLogQuery>>({
form: createFormData(),
queryParams: {
pageNum: 1,
pageSize: 10,
endpointId: undefined,
monitorCode: undefined,
commandType: undefined,
executeStatus: undefined,
params: {}
},
rules: {
endpointId: [{ required: true, message: '端点不能为空', trigger: 'change' }],
monitorCode: [{ required: true, message: '点位不能为空', trigger: 'change' }],
commandType: [{ required: true, message: '命令类型不能为空', trigger: 'change' }]
}
});
const { queryParams, form, rules } = toRefs(data);
const normalizeMonitorTree = (nodes: MonitorTreeNode[] = []): MonitorTreeNode[] =>
nodes.map((node) => {
const children = Array.isArray(node.children) ? normalizeMonitorTree(node.children) : [];
const label = node.monitorName || node.label || node.name || node.monitorCode || String(node.value ?? '');
const value = node.monitorCode || node.value || node.monitorId || node.objId || node.id;
const key = String(value ?? '');
if (key) {
monitorNameMap.value[key] = label;
}
return {
...node,
label,
value,
children
};
});
const resolveEndpointName = (row?: Partial<ControlCommandLogVO> | null) =>
row?.endpointName || endpointOptions.value.find((item) => item.id === row?.endpointId)?.endpointName || (row?.endpointId ? String(row.endpointId) : '-');
const resolveMonitorName = (row?: Partial<ControlCommandLogVO> | Partial<ControlCommandLogForm> | null) =>
row?.monitorName || monitorNameMap.value[String(row?.monitorCode || '')] || row?.monitorCode || '-';
const loadOptions = async () => {
const [endpointRes, monitorRes] = await Promise.all([
listIntegrationEndpointAll(),
getMonitorInfoTree({
monitorTypeList: [5, 6, 7, 8, 9, 10]
} as any)
]);
endpointOptions.value = (((endpointRes as any).rows ?? (endpointRes as any).data ?? endpointRes) || []) as IntegrationEndpointVO[];
monitorNameMap.value = {};
monitorTreeOptions.value = normalizeMonitorTree(((monitorRes as any).data ?? monitorRes ?? []) as MonitorTreeNode[]);
};
const getList = async () => {
loading.value = true;
try {
const res = await listControlCommandLog(queryParams.value);
controlCommandLogList.value = ((res as any).rows ?? []) as ControlCommandLogVO[];
total.value = Number((res as any).total ?? 0);
} finally {
loading.value = false;
}
};
const cancel = () => {
reset();
dialog.visible = false;
};
const reset = () => {
form.value = createFormData();
controlCommandLogFormRef.value?.resetFields();
};
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
const handleSelectionChange = (selection: ControlCommandLogVO[]) => {
ids.value = selection.map((item) => item.id as string | number);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加回写命令日志';
};
const handleUpdate = async (row?: ControlCommandLogVO) => {
reset();
const targetId = row?.id ?? ids.value[0];
if (!targetId) {
return;
}
const res = await getControlCommandLog(targetId);
form.value = {
...createFormData(),
...((res as any).data ?? {})
};
dialog.visible = true;
dialog.title = '修改回写命令日志';
};
const handleEndpointChange = (endpointId?: string | number) => {
form.value.endpointName = endpointOptions.value.find((item) => item.id === endpointId)?.endpointName;
};
const handleMonitorChange = (monitorCode?: string | number) => {
form.value.monitorName = monitorNameMap.value[String(monitorCode || '')];
};
const submitForm = () => {
controlCommandLogFormRef.value?.validate(async (valid: boolean) => {
if (!valid) {
return;
}
buttonLoading.value = true;
try {
handleEndpointChange(form.value.endpointId);
handleMonitorChange(form.value.monitorCode);
if (form.value.id) {
await updateControlCommandLog(form.value);
} else {
await addControlCommandLog(form.value);
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
} finally {
buttonLoading.value = false;
}
});
};
const handleDelete = async (row?: ControlCommandLogVO) => {
const targetIds = row?.id ?? ids.value;
await proxy?.$modal.confirm(`是否确认删除回写命令日志编号为"${targetIds}"的数据项?`);
await delControlCommandLog(targetIds);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
const handleExport = () => {
proxy?.download(
'ems/base/controlCommandLog/export',
{
...queryParams.value
},
`controlCommandLog_${new Date().getTime()}.xlsx`
);
};
onMounted(async () => {
await loadOptions();
await getList();
});
</script>

@ -0,0 +1,471 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="90px">
<el-form-item label="点位名称" prop="monitorCode">
<el-tree-select
v-model="queryParams.monitorCode"
:data="monitorTreeOptions"
:props="monitorTreeProps"
placeholder="请选择点位"
clearable
filterable
check-strictly
style="width: 240px"
/>
</el-form-item>
<el-form-item label="导出类型" prop="exportType">
<el-input v-model="queryParams.exportType" placeholder="请输入导出类型" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="文件格式" prop="fileFormat">
<el-select v-model="queryParams.fileFormat" placeholder="请选择文件格式" clearable style="width: 160px">
<el-option v-for="item in fileFormatOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="导出状态" prop="exportStatus">
<el-select v-model="queryParams.exportStatus" placeholder="请选择导出状态" clearable style="width: 160px">
<el-option v-for="item in exportStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="导出时间">
<el-date-picker
v-model="daterangeExportTime"
type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss"
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
style="width: 360px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['ems/base:dataExportTask:add']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['ems/base:dataExportTask:edit']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['ems/base:dataExportTask:remove']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['ems/base:dataExportTask:export']"></el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" :columns="columns" @queryTable="getList" />
</el-row>
</template>
<el-table v-loading="loading" border :data="dataExportTaskList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="columns[0].visible" label="序号" type="index" width="60" align="center" />
<el-table-column v-if="columns[1].visible" label="导出类型" align="center" prop="exportType" min-width="120" />
<el-table-column v-if="columns[2].visible" label="点位名称" align="center" min-width="220" show-overflow-tooltip>
<template #default="scope">
<div>{{ resolveMonitorName(scope.row) }}</div>
<div class="sub-text">{{ scope.row.metricCode || '-' }}</div>
</template>
</el-table-column>
<el-table-column v-if="columns[3].visible" label="导出开始时间" align="center" prop="timeStart" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.timeStart, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column v-if="columns[4].visible" label="导出结束时间" align="center" prop="timeEnd" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.timeEnd, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column v-if="columns[5].visible" label="文件格式" align="center" width="110">
<template #default="scope">
<dict-tag :options="fileFormatOptions" :value="scope.row.fileFormat" />
</template>
</el-table-column>
<el-table-column v-if="columns[6].visible" label="文件名称" align="center" prop="fileName" min-width="180" show-overflow-tooltip />
<el-table-column v-if="columns[7].visible" label="导出状态" align="center" width="110">
<template #default="scope">
<dict-tag :options="exportStatusOptions" :value="scope.row.exportStatus" />
</template>
</el-table-column>
<el-table-column v-if="columns[8].visible" label="备注" align="center" prop="remark" min-width="180" show-overflow-tooltip />
<el-table-column label="操作" align="center" fixed="right" width="140" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['ems/base:dataExportTask:edit']" />
</el-tooltip>
<el-tooltip content="下载" placement="top">
<el-button link type="primary" icon="Download" :disabled="!scope.row.fileUrl" @click="handleDownload(scope.row)" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['ems/base:dataExportTask:remove']" />
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<el-dialog :title="dialog.title" v-model="dialog.visible" width="720px" append-to-body>
<el-form ref="dataExportTaskFormRef" :model="form" :rules="rules" label-width="104px">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="点位名称" prop="monitorCode">
<el-tree-select
v-model="form.monitorCode"
:data="monitorTreeOptions"
:props="monitorTreeProps"
placeholder="请选择点位"
filterable
check-strictly
style="width: 100%"
@change="handleMonitorChange"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="测量项编码">
<el-input v-model="form.metricCode" readonly />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="导出类型" prop="exportType">
<el-input v-model="form.exportType" placeholder="请输入导出类型" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="文件格式" prop="fileFormat">
<el-select v-model="form.fileFormat" placeholder="请选择文件格式" style="width: 100%">
<el-option v-for="item in fileFormatOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="导出开始时间" prop="timeStart">
<el-date-picker clearable v-model="form.timeStart" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择导出开始时间" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="导出结束时间" prop="timeEnd">
<el-date-picker clearable v-model="form.timeEnd" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择导出结束时间" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="导出条件" prop="exportCondition">
<el-input v-model="form.exportCondition" type="textarea" :rows="3" placeholder="请输入导出条件说明" />
</el-form-item>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="文件名称" prop="fileName">
<el-input v-model="form.fileName" placeholder="请输入文件名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
<el-alert type="info" :closable="false" show-icon title="文件地址和导出状态由任务执行链路回填,页面不再允许人工录入,避免把生成结果字段误写成业务输入。" />
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="DataExportTask" lang="ts">
import { getMonitorInfoTree } from '@/api/ems/base/baseMonitorInfo';
import { listDataExportTask, getDataExportTask, delDataExportTask, addDataExportTask, updateDataExportTask } from '@/api/ems/base/dataExportTask';
import type { DataExportTaskVO, DataExportTaskQuery, DataExportTaskForm } from '@/api/ems/base/dataExportTask/types';
import type { EmsTreeNode } from '@/api/ems/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
interface MonitorTreeNode extends EmsTreeNode {
label?: string;
value?: string | number;
children?: MonitorTreeNode[];
}
const fileFormatOptions = [
{ label: 'Excel', value: 'XLSX' },
{ label: 'CSV', value: 'CSV' },
{ label: 'JSON', value: 'JSON' }
];
const exportStatusOptions = [
{ label: '待执行', value: 'PENDING' },
{ label: '执行中', value: 'PROCESSING' },
{ label: '已完成', value: 'SUCCESS' },
{ label: '失败', value: 'FAIL' }
];
const monitorTreeOptions = ref<MonitorTreeNode[]>([]);
const monitorInfoMap = ref<Record<string, { monitorName?: string; metricCode?: string }>>({});
const dataExportTaskList = ref<DataExportTaskVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const daterangeExportTime = ref<string[]>([]);
const columns = ref<FieldOption[]>([
{ key: 0, label: '序号', visible: true, children: [] },
{ key: 1, label: '导出类型', visible: true, children: [] },
{ key: 2, label: '点位名称', visible: true, children: [] },
{ key: 3, label: '导出开始时间', visible: true, children: [] },
{ key: 4, label: '导出结束时间', visible: true, children: [] },
{ key: 5, label: '文件格式', visible: true, children: [] },
{ key: 6, label: '文件名称', visible: true, children: [] },
{ key: 7, label: '导出状态', visible: true, children: [] },
{ key: 8, label: '备注', visible: true, children: [] }
]);
const queryFormRef = ref<ElFormInstance>();
const dataExportTaskFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const monitorTreeProps = {
label: 'label',
value: 'value',
children: 'children'
};
const createFormData = (): DataExportTaskForm => ({
id: undefined,
exportType: undefined,
monitorCode: undefined,
monitorName: undefined,
metricCode: undefined,
timeStart: undefined,
timeEnd: undefined,
exportCondition: undefined,
fileFormat: 'XLSX',
fileName: undefined,
fileUrl: undefined,
exportStatus: 'PENDING',
remark: undefined
});
const data = reactive<PageData<DataExportTaskForm, DataExportTaskQuery>>({
form: createFormData(),
queryParams: {
pageNum: 1,
pageSize: 10,
exportType: undefined,
monitorCode: undefined,
fileFormat: undefined,
exportStatus: undefined,
params: {}
},
rules: {
monitorCode: [{ required: true, message: '点位不能为空', trigger: 'change' }],
exportType: [{ required: true, message: '导出类型不能为空', trigger: 'blur' }],
fileFormat: [{ required: true, message: '文件格式不能为空', trigger: 'change' }],
timeStart: [{ required: true, message: '导出开始时间不能为空', trigger: 'change' }],
timeEnd: [{ required: true, message: '导出结束时间不能为空', trigger: 'change' }]
}
});
const { queryParams, form, rules } = toRefs(data);
const normalizeMonitorTree = (nodes: MonitorTreeNode[] = []): MonitorTreeNode[] =>
nodes.map((node) => {
const children = Array.isArray(node.children) ? normalizeMonitorTree(node.children) : [];
const label = node.monitorName || node.label || node.name || node.monitorCode || String(node.value ?? '');
const value = node.monitorCode || node.value || node.monitorId || node.objId || node.id;
const key = String(value ?? '');
if (key) {
monitorInfoMap.value[key] = {
monitorName: node.monitorName || label,
metricCode: node.metricCode as string | undefined
};
}
return {
...node,
label,
value,
children
};
});
const syncQueryTimeRange = () => {
queryParams.value.params = queryParams.value.params || {};
queryParams.value.params.beginTimeStart = daterangeExportTime.value?.[0];
queryParams.value.params.beginTimeEnd = daterangeExportTime.value?.[1];
};
const resolveMonitorName = (row?: Partial<DataExportTaskVO> | Partial<DataExportTaskForm> | null) =>
row?.monitorName || monitorInfoMap.value[String(row?.monitorCode || '')]?.monitorName || row?.monitorCode || '-';
const loadMonitorOptions = async () => {
const response = await getMonitorInfoTree({
monitorTypeList: [5, 6, 7, 8, 9, 10]
} as any);
monitorInfoMap.value = {};
monitorTreeOptions.value = normalizeMonitorTree(((response as any).data ?? response ?? []) as MonitorTreeNode[]);
};
const getList = async () => {
loading.value = true;
try {
syncQueryTimeRange();
const res = await listDataExportTask(queryParams.value);
dataExportTaskList.value = ((res as any).rows ?? []) as DataExportTaskVO[];
total.value = Number((res as any).total ?? 0);
} finally {
loading.value = false;
}
};
const cancel = () => {
reset();
dialog.visible = false;
};
const reset = () => {
form.value = createFormData();
dataExportTaskFormRef.value?.resetFields();
};
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
const resetQuery = () => {
queryFormRef.value?.resetFields();
daterangeExportTime.value = [];
handleQuery();
};
const handleSelectionChange = (selection: DataExportTaskVO[]) => {
ids.value = selection.map((item) => item.id as string | number);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加数据导出任务';
};
const handleUpdate = async (row?: DataExportTaskVO) => {
reset();
const targetId = row?.id ?? ids.value[0];
if (!targetId) {
return;
}
const res = await getDataExportTask(targetId);
form.value = {
...createFormData(),
...((res as any).data ?? {})
};
dialog.visible = true;
dialog.title = '修改数据导出任务';
};
const handleMonitorChange = (monitorCode?: string | number) => {
const matched = monitorInfoMap.value[String(monitorCode || '')];
//
form.value.monitorName = matched?.monitorName;
form.value.metricCode = matched?.metricCode;
};
const submitForm = () => {
dataExportTaskFormRef.value?.validate(async (valid: boolean) => {
if (!valid) {
return;
}
buttonLoading.value = true;
try {
handleMonitorChange(form.value.monitorCode);
form.value.exportStatus = form.value.exportStatus || 'PENDING';
if (form.value.id) {
await updateDataExportTask(form.value);
} else {
await addDataExportTask(form.value);
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
} finally {
buttonLoading.value = false;
}
});
};
const handleDelete = async (row?: DataExportTaskVO) => {
const targetIds = row?.id ?? ids.value;
await proxy?.$modal.confirm(`是否确认删除数据导出任务编号为"${targetIds}"的数据项?`);
await delDataExportTask(targetIds);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
const handleExport = () => {
syncQueryTimeRange();
proxy?.download(
'ems/base/dataExportTask/export',
{
...queryParams.value
},
`dataExportTask_${new Date().getTime()}.xlsx`
);
};
const handleDownload = (row: DataExportTaskVO) => {
if (!row.fileUrl) {
return;
}
window.open(row.fileUrl, '_blank');
};
onMounted(async () => {
await loadMonitorOptions();
await getList();
});
</script>
<style scoped>
.sub-text {
color: var(--el-text-color-secondary);
font-size: 12px;
line-height: 18px;
}
</style>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,450 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="90px">
<el-form-item label="端点名称" prop="endpointId">
<el-select v-model="queryParams.endpointId" placeholder="请选择端点" clearable filterable style="width: 220px">
<el-option v-for="item in endpointOptions" :key="item.id" :label="item.endpointName" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="点位名称" prop="monitorCode">
<el-tree-select
v-model="queryParams.monitorCode"
:data="monitorTreeOptions"
:props="monitorTreeProps"
placeholder="请选择点位"
clearable
filterable
check-strictly
style="width: 240px"
/>
</el-form-item>
<el-form-item label="第三方源点位" prop="sourcePointCode">
<el-input v-model="queryParams.sourcePointCode" placeholder="请输入第三方源点位" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="第三方目标点位" prop="targetPointCode">
<el-input v-model="queryParams.targetPointCode" placeholder="请输入第三方目标点位" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="数据格式" prop="dataFormat">
<el-select v-model="queryParams.dataFormat" placeholder="请选择数据格式" clearable style="width: 160px">
<el-option v-for="item in dataFormatOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="启用状态" prop="isEnable">
<el-select v-model="queryParams.isEnable" placeholder="请选择启用状态" clearable style="width: 160px">
<el-option v-for="item in enableStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['ems/base:integrationPointMap:add']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['ems/base:integrationPointMap:edit']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['ems/base:integrationPointMap:remove']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['ems/base:integrationPointMap:export']"></el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" :columns="columns" @queryTable="getList" />
</el-row>
</template>
<el-table v-loading="loading" border :data="integrationPointMapList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="columns[0].visible" label="序号" type="index" width="60" align="center" />
<el-table-column v-if="columns[1].visible" label="端点名称" align="center" min-width="180" show-overflow-tooltip>
<template #default="scope">
{{ resolveEndpointName(scope.row) }}
</template>
</el-table-column>
<el-table-column v-if="columns[2].visible" label="点位名称" align="center" min-width="220" show-overflow-tooltip>
<template #default="scope">
<div>{{ resolveMonitorName(scope.row) }}</div>
<div class="sub-text">{{ scope.row.metricCode || '-' }}</div>
</template>
</el-table-column>
<el-table-column v-if="columns[3].visible" label="第三方源点位" align="center" prop="sourcePointCode" min-width="180" show-overflow-tooltip />
<el-table-column v-if="columns[4].visible" label="第三方目标点位" align="center" prop="targetPointCode" min-width="180" show-overflow-tooltip />
<el-table-column v-if="columns[5].visible" label="数据格式" align="center" width="110">
<template #default="scope">
<dict-tag :options="dataFormatOptions" :value="scope.row.dataFormat" />
</template>
</el-table-column>
<el-table-column v-if="columns[6].visible" label="启用状态" align="center" width="110">
<template #default="scope">
<dict-tag :options="enableStatusOptions" :value="scope.row.isEnable" />
</template>
</el-table-column>
<el-table-column v-if="columns[7].visible" label="转换脚本" align="center" prop="transformScript" min-width="220" show-overflow-tooltip />
<el-table-column label="操作" align="center" fixed="right" width="110" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['ems/base:integrationPointMap:edit']" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['ems/base:integrationPointMap:remove']" />
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<el-dialog :title="dialog.title" v-model="dialog.visible" width="720px" append-to-body>
<el-form ref="integrationPointMapFormRef" :model="form" :rules="rules" label-width="104px">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="端点名称" prop="endpointId">
<el-select v-model="form.endpointId" placeholder="请选择端点" filterable style="width: 100%" @change="handleEndpointChange">
<el-option v-for="item in endpointOptions" :key="item.id" :label="item.endpointName" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="点位名称" prop="monitorCode">
<el-tree-select
v-model="form.monitorCode"
:data="monitorTreeOptions"
:props="monitorTreeProps"
placeholder="请选择点位"
filterable
check-strictly
style="width: 100%"
@change="handleMonitorChange"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="测量项编码">
<el-input v-model="form.metricCode" readonly />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="数据格式" prop="dataFormat">
<el-select v-model="form.dataFormat" placeholder="请选择数据格式" style="width: 100%">
<el-option v-for="item in dataFormatOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="第三方源点位" prop="sourcePointCode">
<el-input v-model="form.sourcePointCode" placeholder="请输入第三方源点位编码" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="第三方目标点位" prop="targetPointCode">
<el-input v-model="form.targetPointCode" placeholder="请输入第三方目标点位编码" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="转换脚本" prop="transformScript">
<el-input
v-model="form.transformScript"
type="textarea"
:rows="4"
placeholder="请输入转换说明或脚本片段,留空时默认按原值透传"
/>
</el-form-item>
<el-form-item label="启用状态" prop="isEnable">
<el-radio-group v-model="form.isEnable">
<el-radio v-for="item in enableStatusOptions" :key="item.value" :value="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="IntegrationPointMap" lang="ts">
import { listIntegrationEndpointAll } from '@/api/ems/base/integrationEndpoint';
import { getMonitorInfoTree } from '@/api/ems/base/baseMonitorInfo';
import { listIntegrationPointMap, getIntegrationPointMap, delIntegrationPointMap, addIntegrationPointMap, updateIntegrationPointMap } from '@/api/ems/base/integrationPointMap';
import type { IntegrationEndpointVO } from '@/api/ems/base/integrationEndpoint/types';
import type { IntegrationPointMapVO, IntegrationPointMapQuery, IntegrationPointMapForm } from '@/api/ems/base/integrationPointMap/types';
import type { EmsTreeNode } from '@/api/ems/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
interface MonitorTreeNode extends EmsTreeNode {
label?: string;
value?: string | number;
children?: MonitorTreeNode[];
}
const dataFormatOptions = [
{ label: 'JSON', value: 'JSON' },
{ label: 'XML', value: 'XML' }
];
const enableStatusOptions = [
{ label: '启用', value: '0' },
{ label: '停用', value: '1' }
];
const endpointOptions = ref<IntegrationEndpointVO[]>([]);
const monitorTreeOptions = ref<MonitorTreeNode[]>([]);
const monitorInfoMap = ref<Record<string, { monitorName?: string; metricCode?: string }>>({});
const integrationPointMapList = ref<IntegrationPointMapVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const columns = ref<FieldOption[]>([
{ key: 0, label: '序号', visible: true, children: [] },
{ key: 1, label: '端点名称', visible: true, children: [] },
{ key: 2, label: '点位名称', visible: true, children: [] },
{ key: 3, label: '第三方源点位', visible: true, children: [] },
{ key: 4, label: '第三方目标点位', visible: true, children: [] },
{ key: 5, label: '数据格式', visible: true, children: [] },
{ key: 6, label: '启用状态', visible: true, children: [] },
{ key: 7, label: '转换脚本', visible: true, children: [] }
]);
const queryFormRef = ref<ElFormInstance>();
const integrationPointMapFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const monitorTreeProps = {
label: 'label',
value: 'value',
children: 'children'
};
const createFormData = (): IntegrationPointMapForm => ({
id: undefined,
endpointId: undefined,
endpointName: undefined,
monitorCode: undefined,
monitorName: undefined,
metricCode: undefined,
sourcePointCode: undefined,
targetPointCode: undefined,
transformScript: undefined,
dataFormat: 'JSON',
isEnable: '0'
});
const data = reactive<PageData<IntegrationPointMapForm, IntegrationPointMapQuery>>({
form: createFormData(),
queryParams: {
pageNum: 1,
pageSize: 10,
endpointId: undefined,
monitorCode: undefined,
sourcePointCode: undefined,
targetPointCode: undefined,
dataFormat: undefined,
isEnable: undefined,
params: {}
},
rules: {
endpointId: [{ required: true, message: '端点不能为空', trigger: 'change' }],
monitorCode: [{ required: true, message: '点位不能为空', trigger: 'change' }],
sourcePointCode: [{ required: true, message: '第三方源点位不能为空', trigger: 'blur' }],
dataFormat: [{ required: true, message: '数据格式不能为空', trigger: 'change' }]
}
});
const { queryParams, form, rules } = toRefs(data);
const normalizeMonitorTree = (nodes: MonitorTreeNode[] = []): MonitorTreeNode[] =>
nodes.map((node) => {
const children = Array.isArray(node.children) ? normalizeMonitorTree(node.children) : [];
const label = node.monitorName || node.label || node.name || node.monitorCode || String(node.value ?? '');
const value = node.monitorCode || node.value || node.monitorId || node.objId || node.id;
const key = String(value ?? '');
if (key) {
monitorInfoMap.value[key] = {
monitorName: node.monitorName || label,
metricCode: node.metricCode as string | undefined
};
}
return {
...node,
label,
value,
children
};
});
const resolveEndpointName = (row?: Partial<IntegrationPointMapVO> | null) =>
row?.endpointName || endpointOptions.value.find((item) => item.id === row?.endpointId)?.endpointName || (row?.endpointId ? String(row.endpointId) : '-');
const resolveMonitorName = (row?: Partial<IntegrationPointMapVO> | Partial<IntegrationPointMapForm> | null) =>
row?.monitorName || monitorInfoMap.value[String(row?.monitorCode || '')]?.monitorName || row?.monitorCode || '-';
const loadOptions = async () => {
const [endpointRes, monitorRes] = await Promise.all([
listIntegrationEndpointAll(),
getMonitorInfoTree({
monitorTypeList: [5, 6, 7, 8, 9, 10]
} as any)
]);
endpointOptions.value = (((endpointRes as any).rows ?? (endpointRes as any).data ?? endpointRes) || []) as IntegrationEndpointVO[];
monitorInfoMap.value = {};
monitorTreeOptions.value = normalizeMonitorTree(((monitorRes as any).data ?? monitorRes ?? []) as MonitorTreeNode[]);
};
const getList = async () => {
loading.value = true;
try {
const res = await listIntegrationPointMap(queryParams.value);
integrationPointMapList.value = ((res as any).rows ?? []) as IntegrationPointMapVO[];
total.value = Number((res as any).total ?? 0);
} finally {
loading.value = false;
}
};
const cancel = () => {
reset();
dialog.visible = false;
};
const reset = () => {
form.value = createFormData();
integrationPointMapFormRef.value?.resetFields();
};
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
const handleSelectionChange = (selection: IntegrationPointMapVO[]) => {
ids.value = selection.map((item) => item.id as string | number);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加对接点位映射';
};
const handleUpdate = async (row?: IntegrationPointMapVO) => {
reset();
const targetId = row?.id ?? ids.value[0];
if (!targetId) {
return;
}
const res = await getIntegrationPointMap(targetId);
form.value = {
...createFormData(),
...((res as any).data ?? {})
};
dialog.visible = true;
dialog.title = '修改对接点位映射';
};
const handleEndpointChange = (endpointId?: string | number) => {
form.value.endpointName = endpointOptions.value.find((item) => item.id === endpointId)?.endpointName;
};
const handleMonitorChange = (monitorCode?: string | number) => {
const matched = monitorInfoMap.value[String(monitorCode || '')];
//
form.value.monitorName = matched?.monitorName;
form.value.metricCode = matched?.metricCode;
};
const submitForm = () => {
integrationPointMapFormRef.value?.validate(async (valid: boolean) => {
if (!valid) {
return;
}
buttonLoading.value = true;
try {
handleEndpointChange(form.value.endpointId);
handleMonitorChange(form.value.monitorCode);
if (form.value.id) {
await updateIntegrationPointMap(form.value);
} else {
await addIntegrationPointMap(form.value);
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
} finally {
buttonLoading.value = false;
}
});
};
const handleDelete = async (row?: IntegrationPointMapVO) => {
const targetIds = row?.id ?? ids.value;
await proxy?.$modal.confirm(`是否确认删除对接点位映射编号为"${targetIds}"的数据项?`);
await delIntegrationPointMap(targetIds);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
const handleExport = () => {
proxy?.download(
'ems/base/integrationPointMap/export',
{
...queryParams.value
},
`integrationPointMap_${new Date().getTime()}.xlsx`
);
};
onMounted(async () => {
await loadOptions();
await getList();
});
</script>
<style scoped>
.sub-text {
color: var(--el-text-color-secondary);
font-size: 12px;
line-height: 18px;
}
</style>

@ -0,0 +1,501 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover" class="query-card">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="点位名称" prop="monitorCode">
<el-tree-select
v-model="queryParams.monitorCode"
:data="monitorTreeOptions"
:props="monitorTreeProps"
placeholder="请选择点位名称"
clearable
filterable
check-strictly
style="width: 240px"
/>
</el-form-item>
<el-form-item label="通知组" prop="notifyGroupId">
<el-select v-model="queryParams.notifyGroupId" placeholder="请选择通知组" clearable filterable style="width: 240px">
<el-option v-for="item in notifyGroupOptions" :key="item.id" :label="item.groupName" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="启用状态" prop="isEnable">
<el-select v-model="queryParams.isEnable" placeholder="请选择启用状态" clearable style="width: 160px">
<el-option v-for="item in sysNormalDisableOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never" class="table-card">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['ems/base:monitorMetricThreshold:add']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate()"
v-hasPermi="['ems/base:monitorMetricThreshold:edit']"
>
修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete()"
v-hasPermi="['ems/base:monitorMetricThreshold:remove']"
>
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['ems/base:monitorMetricThreshold:export']"
>导出</el-button
>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
</el-row>
</template>
<el-table v-loading="loading" border :data="monitorMetricThresholdList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="点位名称" align="center" min-width="180" show-overflow-tooltip>
<template #default="scope">
<div class="main-cell">{{ resolveMonitorName(scope.row) }}</div>
</template>
</el-table-column>
<el-table-column label="预警上限" align="center" prop="warnUpper" min-width="110" />
<el-table-column label="预警下限" align="center" prop="warnLower" min-width="110" />
<el-table-column label="告警上限" align="center" prop="alarmUpper" min-width="110" />
<el-table-column label="告警下限" align="center" prop="alarmLower" min-width="110" />
<el-table-column label="回差值" align="center" prop="hysteresis" min-width="100" />
<el-table-column label="持续触发秒数" align="center" prop="durationSec" min-width="120" />
<el-table-column label="告警级别" align="center" prop="alarmLevel" min-width="110" />
<el-table-column label="通知组" align="center" min-width="180" show-overflow-tooltip>
<template #default="scope">
{{ resolveNotifyGroupName(scope.row) }}
</template>
</el-table-column>
<el-table-column label="启用状态" align="center" width="110">
<template #default="scope">
<dict-tag :options="sysNormalDisableOptions" :value="scope.row.isEnable" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" min-width="180" show-overflow-tooltip />
<el-table-column label="操作" align="center" fixed="right" width="110" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['ems/base:monitorMetricThreshold:edit']" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['ems/base:monitorMetricThreshold:remove']" />
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<el-dialog v-model="dialog.visible" :title="dialog.title" width="720px" append-to-body>
<el-form ref="monitorMetricThresholdFormRef" :model="form" :rules="rules" label-width="110px">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="点位名称" prop="monitorCode">
<el-tree-select
v-model="form.monitorCode"
:data="monitorTreeOptions"
:props="monitorTreeProps"
placeholder="请选择点位名称"
clearable
filterable
check-strictly
style="width: 100%"
@change="handleMonitorChange"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="预警上限" prop="warnUpper">
<el-input-number v-model="form.warnUpper" :precision="4" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="预警下限" prop="warnLower">
<el-input-number v-model="form.warnLower" :precision="4" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="告警上限" prop="alarmUpper">
<el-input-number v-model="form.alarmUpper" :precision="4" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="告警下限" prop="alarmLower">
<el-input-number v-model="form.alarmLower" :precision="4" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="回差值" prop="hysteresis">
<el-input-number v-model="form.hysteresis" :precision="4" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="持续触发秒数" prop="durationSec">
<el-input-number v-model="form.durationSec" :min="0" :step="1" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="告警级别" prop="alarmLevel">
<el-input v-model="form.alarmLevel" placeholder="请输入告警级别" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="通知组" prop="notifyGroupId">
<el-select v-model="form.notifyGroupId" placeholder="请选择通知组" clearable filterable style="width: 100%">
<el-option v-for="item in notifyGroupOptions" :key="item.id" :label="item.groupName" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="启用状态" prop="isEnable">
<el-radio-group v-model="form.isEnable">
<el-radio v-for="item in sysNormalDisableOptions" :key="item.value" :label="item.value">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" type="textarea" :rows="3" maxlength="200" show-word-limit />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { getCurrentInstance } from 'vue';
import { useDict } from '@/utils/dict';
import {
addMonitorMetricThreshold,
delMonitorMetricThreshold,
getMonitorMetricThreshold,
listMonitorMetricThreshold,
updateMonitorMetricThreshold
} from '@/api/ems/base/monitorMetricThreshold';
import { listAlarmNotifyGroupAll } from '@/api/ems/base/alarmNotifyGroup';
import { getMonitorInfoTree } from '@/api/ems/base/baseMonitorInfo';
import type { AlarmNotifyGroupVO } from '@/api/ems/base/alarmNotifyGroup/types';
import type { MonitorMetricThresholdForm, MonitorMetricThresholdQuery, MonitorMetricThresholdVO } from '@/api/ems/base/monitorMetricThreshold/types';
import type { EmsTreeNode } from '@/api/ems/types';
defineOptions({
name: 'MonitorMetricThreshold'
} as any);
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const dict = reactive({
type: useDict('sys_normal_disable') as any
});
const sysNormalDisableOptions = computed(() => dict.type.sys_normal_disable ?? []);
interface ThresholdTreeNode extends EmsTreeNode {
label?: string;
value?: string | number;
children?: ThresholdTreeNode[];
}
const monitorTreeOptions = ref<ThresholdTreeNode[]>([]);
const notifyGroupOptions = ref<AlarmNotifyGroupVO[]>([]);
const monitorNameMap = ref<Record<string, string>>({});
const monitorMetaMap = ref<Record<string, { label: string; metricCode?: string }>>({});
const notifyGroupNameMap = ref<Record<string, string>>({});
const monitorTreeProps = {
label: 'label',
value: 'value',
children: 'children'
};
const monitorMetricThresholdList = ref<MonitorMetricThresholdVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const monitorMetricThresholdFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const normalizeMonitorTree = (nodes: ThresholdTreeNode[] = []): ThresholdTreeNode[] =>
nodes.map((node) => {
const children = Array.isArray(node.children) ? normalizeMonitorTree(node.children) : [];
const label = node.monitorName || node.label || node.name || node.monitorCode || String(node.value ?? '');
const value = node.monitorCode || node.value || node.monitorId || node.objId || node.id;
return {
...node,
label,
value,
children
};
});
const collectMonitorNames = (nodes: ThresholdTreeNode[] = []) => {
nodes.forEach((node) => {
const key = String(node.value ?? node.monitorCode ?? '');
if (key) {
monitorNameMap.value[key] = node.label || node.monitorName || key;
monitorMetaMap.value[key] = {
label: node.label || node.monitorName || key,
metricCode: node.metricCode as string | undefined
};
}
if (node.children?.length) {
collectMonitorNames(node.children);
}
});
};
const flattenMonitorOptions = (nodes: ThresholdTreeNode[] = []): ThresholdTreeNode[] =>
nodes.reduce<ThresholdTreeNode[]>((acc, node) => {
acc.push(node);
if (node.children?.length) {
acc.push(...flattenMonitorOptions(node.children));
}
return acc;
}, []);
const initFormData: MonitorMetricThresholdForm = {
id: undefined,
monitorCode: undefined,
metricCode: undefined,
warnUpper: undefined,
warnLower: undefined,
alarmUpper: undefined,
alarmLower: undefined,
hysteresis: undefined,
durationSec: undefined,
alarmLevel: undefined,
notifyGroupId: undefined,
isEnable: '0',
remark: undefined
};
const data = reactive<PageData<MonitorMetricThresholdForm, MonitorMetricThresholdQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
monitorCode: undefined,
metricCode: undefined,
warnUpper: undefined,
warnLower: undefined,
alarmUpper: undefined,
alarmLower: undefined,
hysteresis: undefined,
durationSec: undefined,
alarmLevel: undefined,
notifyGroupId: undefined,
isEnable: undefined,
params: {}
},
rules: {
id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
monitorCode: [{ required: true, message: '点位名称不能为空', trigger: 'change' }],
metricCode: [{ required: true, message: '测量项编码不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
const loadOptions = async () => {
const [treeRes, groupRes] = await Promise.all([getMonitorInfoTree({}), listAlarmNotifyGroupAll()]);
// label/value
const treeData = normalizeMonitorTree(((treeRes as any).data ?? treeRes ?? []) as ThresholdTreeNode[]);
monitorTreeOptions.value = treeData;
monitorNameMap.value = {};
monitorMetaMap.value = {};
collectMonitorNames(flattenMonitorOptions(treeData));
// groupName使 id
const groups = ((groupRes as any).rows ?? (groupRes as any).data ?? groupRes ?? []) as AlarmNotifyGroupVO[];
notifyGroupOptions.value = groups;
notifyGroupNameMap.value = Object.fromEntries(groups.map((item) => [String(item.id), item.groupName || String(item.id)]));
};
const getList = async () => {
loading.value = true;
try {
const res = await listMonitorMetricThreshold(queryParams.value);
monitorMetricThresholdList.value = ((res as any).rows ?? (res as any).data ?? []) as MonitorMetricThresholdVO[];
total.value = (res as any).total ?? monitorMetricThresholdList.value.length ?? 0;
} finally {
loading.value = false;
}
};
const resolveMonitorName = (row: MonitorMetricThresholdVO) => {
// 退
return row.monitorName || monitorNameMap.value[String(row.monitorCode ?? '')] || row.monitorCode || '-';
};
const resolveNotifyGroupName = (row: MonitorMetricThresholdVO) => {
// ID
return row.notifyGroupName || notifyGroupNameMap.value[String(row.notifyGroupId ?? '')] || (row.notifyGroupId ? String(row.notifyGroupId) : '-');
};
const reset = () => {
form.value = { ...initFormData };
monitorMetricThresholdFormRef.value?.resetFields();
};
const handleMonitorChange = (monitorCode?: string | number) => {
//
form.value.metricCode = monitorMetaMap.value[String(monitorCode || '')]?.metricCode;
};
const cancel = () => {
reset();
dialog.visible = false;
};
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
const handleSelectionChange = (selection: MonitorMetricThresholdVO[]) => {
ids.value = selection.map((item) => item.id as string | number);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加统一点位阈值';
};
const handleUpdate = async (row?: MonitorMetricThresholdVO) => {
reset();
const id = row?.id || ids.value[0];
const res = await getMonitorMetricThreshold(id as number);
//
form.value = {
...initFormData,
...((res as any).data ?? {})
};
dialog.visible = true;
dialog.title = '修改统一点位阈值';
};
const submitForm = async () => {
const valid = await monitorMetricThresholdFormRef.value?.validate().catch(() => false);
if (!valid) {
return;
}
buttonLoading.value = true;
try {
if (form.value.id) {
await updateMonitorMetricThreshold(form.value);
proxy?.$modal.msgSuccess('修改成功');
} else {
await addMonitorMetricThreshold(form.value);
proxy?.$modal.msgSuccess('新增成功');
}
dialog.visible = false;
await getList();
} finally {
buttonLoading.value = false;
}
};
const handleDelete = async (row?: MonitorMetricThresholdVO) => {
const targetIds = row?.id || ids.value;
await proxy?.$modal.confirm(`是否确认删除统一点位阈值编号为"${targetIds}"的数据项?`);
await delMonitorMetricThreshold(targetIds as any);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
const handleExport = () => {
proxy?.download(
'ems/base/monitorMetricThreshold/export',
{
...queryParams.value
},
`monitorMetricThreshold_${new Date().getTime()}.xlsx`
);
};
onMounted(async () => {
await loadOptions();
await getList();
});
</script>
<style scoped>
.main-cell {
line-height: 20px;
font-weight: 600;
}
.sub-cell {
color: var(--el-text-color-secondary);
font-size: 12px;
line-height: 18px;
}
</style>

@ -0,0 +1,744 @@
<template>
<div class="report-page p-2">
<el-card shadow="never" class="hero-card">
<div class="hero-grid">
<div class="hero-main">
<div class="hero-kicker">IOT PERIOD REPORT</div>
<div class="hero-title">物联周期报表中心</div>
<div class="hero-desc">统一小时报日报周报月报统计口径直接复用动态分表数据源避免历史查询导出报表三条链路各算各的</div>
<div class="hero-tags">
<el-tag effect="dark" round class="hero-tag">{{ currentPeriodLabel }}</el-tag>
<el-tag effect="plain" round class="hero-tag">{{ selectedMonitorLabel }}</el-tag>
<el-tag effect="plain" round class="hero-tag">{{ activeMetricLabel }}</el-tag>
</div>
</div>
<div class="hero-side">
<div class="hero-panel">
<div class="hero-panel-label">当前窗口</div>
<div class="hero-panel-value">{{ currentRangeShortLabel }}</div>
<div class="hero-panel-foot">默认支持从曲线页或看板页带参进入无需二次选择上下文</div>
</div>
</div>
</div>
</el-card>
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover" class="query-card">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="88px">
<el-form-item label="点位名称" prop="monitorCode">
<el-tree-select
v-model="queryParams.monitorCode"
:data="monitorTreeOptions"
:props="monitorTreeProps"
placeholder="请选择点位"
clearable
filterable
check-strictly
style="width: 240px"
/>
</el-form-item>
<el-form-item label="测量项" prop="metricCode">
<el-select v-model="queryParams.metricCode" placeholder="全部测量项" clearable filterable style="width: 180px">
<el-option v-for="item in metricOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="统计周期" prop="periodType">
<el-select v-model="queryParams.periodType" style="width: 160px">
<el-option v-for="item in periodTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="统计时间">
<el-date-picker
v-model="daterangePeriodTime"
type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss"
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
style="width: 360px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
<el-button type="warning" plain icon="Download" @click="handleExport"></el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<div class="summary-grid">
<el-card shadow="never" class="summary-card">
<div class="summary-label">统计周期</div>
<div class="summary-value">{{ currentPeriodLabel }}</div>
<div class="summary-foot">{{ currentRangeLabel }}</div>
</el-card>
<el-card shadow="never" class="summary-card">
<div class="summary-label">报表行数</div>
<div class="summary-value">{{ total }}</div>
<div class="summary-foot">当前筛选条件下的聚合结果数</div>
</el-card>
<el-card shadow="never" class="summary-card">
<div class="summary-label">报警总次数</div>
<div class="summary-value">{{ totalAlarmCount }}</div>
<div class="summary-foot">按周期窗口累计的告警条数</div>
</el-card>
<el-card shadow="never" class="summary-card">
<div class="summary-label">最大样本数</div>
<div class="summary-value">{{ maxSampleCount }}</div>
<div class="summary-foot">单个周期窗口内的最大采样量</div>
</el-card>
</div>
<el-card shadow="never" class="table-card">
<template #header>
<div class="card-header">
<div>
<div class="card-title">物联周期报表</div>
<div class="card-subtitle">基于动态分表实时聚合统一输出小时月统计口径</div>
<div class="card-tags">
<span class="card-tag-item">点位{{ selectedMonitorLabel }}</span>
<span class="card-tag-item">测量项{{ activeMetricLabel }}</span>
<span class="card-tag-item">时间{{ currentRangeShortLabel }}</span>
</div>
</div>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
</div>
</template>
<el-table v-loading="loading" border :data="reportRows" class="report-table">
<el-table-column label="序号" type="index" width="60" align="center">
<template #default="scope">
{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column label="点位名称" prop="monitorName" min-width="180" show-overflow-tooltip>
<template #default="scope">
<div class="main-cell">{{ resolveMonitorName(scope.row) }}</div>
<div class="sub-cell">{{ scope.row.monitorCode || '-' }}</div>
</template>
</el-table-column>
<el-table-column label="测量项" min-width="150" show-overflow-tooltip>
<template #default="scope">
<div class="main-cell">{{ scope.row.metricName || resolveMetricName(scope.row.metricCode) }}</div>
<div class="sub-cell">{{ scope.row.metricCode || '-' }}</div>
</template>
</el-table-column>
<el-table-column label="周期" align="center" width="100" prop="periodType">
<template #default="scope">
<el-tag size="small" effect="plain">{{ resolvePeriodTypeLabel(scope.row.periodType) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="周期开始" align="center" min-width="170">
<template #default="scope">
{{ formatDateTime(scope.row.periodStart) }}
</template>
</el-table-column>
<el-table-column label="周期结束" align="center" min-width="170">
<template #default="scope">
{{ formatDateTime(scope.row.periodEnd) }}
</template>
</el-table-column>
<el-table-column label="起始值" align="right" min-width="110">
<template #default="scope">
{{ formatNumber(scope.row.beginValue) }}
</template>
</el-table-column>
<el-table-column label="结束值" align="right" min-width="110">
<template #default="scope">
{{ formatNumber(scope.row.endValue) }}
</template>
</el-table-column>
<el-table-column label="平均值" align="right" min-width="110">
<template #default="scope">
{{ formatNumber(scope.row.avgValue) }}
</template>
</el-table-column>
<el-table-column label="最大值" align="right" min-width="110">
<template #default="scope">
{{ formatNumber(scope.row.maxValue) }}
</template>
</el-table-column>
<el-table-column label="最小值" align="right" min-width="110">
<template #default="scope">
{{ formatNumber(scope.row.minValue) }}
</template>
</el-table-column>
<el-table-column label="增量" align="right" min-width="110">
<template #default="scope">
{{ formatNumber(scope.row.consumeValue) }}
</template>
</el-table-column>
<el-table-column label="告警次数" align="center" min-width="100" prop="alarmCount" />
<el-table-column label="样本数" align="center" min-width="100" prop="sampleCount" />
</el-table>
<div v-if="!loading && reportRows.length === 0" class="empty-panel">
<div class="empty-icon">01</div>
<div class="empty-title">当前筛选条件下没有可展示的周期报表</div>
<div class="empty-desc">可以尝试放宽时间范围切换统计周期或者从点位树中改选其他设备后重新查询</div>
</div>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
</div>
</template>
<script setup name="ReportPeriodSummary" lang="ts">
import { getCurrentInstance } from 'vue';
import { useRoute } from 'vue-router';
import { getMonitorInfoTree } from '@/api/ems/base/baseMonitorInfo';
import { listAggregateReportPeriodSummary } from '@/api/ems/base/reportPeriodSummary';
import type { ReportPeriodSummaryQuery, ReportPeriodSummaryVO } from '@/api/ems/base/reportPeriodSummary/types';
import type { EmsTreeNode } from '@/api/ems/types';
import { parseTime } from '@/utils/ruoyi';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const route = useRoute();
interface ReportTreeNode extends EmsTreeNode {
label?: string;
value?: string | number;
children?: ReportTreeNode[];
}
interface MetricOption {
label: string;
value: string;
}
interface PeriodTypeOption {
label: string;
value: string;
}
const periodTypeOptions: PeriodTypeOption[] = [
{ label: '小时报', value: 'HOUR' },
{ label: '日报', value: 'DAY' },
{ label: '周报', value: 'WEEK' },
{ label: '月报', value: 'MONTH' }
];
const metricOptions: MetricOption[] = [
{ label: '温度', value: 'temperature' },
{ label: '湿度', value: 'humidity' },
{ label: '照度', value: 'illuminance' },
{ label: '噪声', value: 'noise' },
{ label: '气体浓度', value: 'concentration' },
{ label: '振动速度', value: 'vibrationSpeed' },
{ label: '振动位移', value: 'vibrationDisplacement' },
{ label: '振动加速度', value: 'vibrationAcceleration' },
{ label: '振动温度', value: 'vibrationTemp' }
];
const monitorTreeOptions = ref<ReportTreeNode[]>([]);
const monitorNameMap = ref<Record<string, string>>({});
const loading = ref(false);
const showSearch = ref(true);
const total = ref(0);
const reportRows = ref<ReportPeriodSummaryVO[]>([]);
const daterangePeriodTime = ref<string[]>([]);
const queryFormRef = ref<ElFormInstance>();
const monitorTreeProps = {
label: 'label',
value: 'value',
children: 'children'
};
const queryParams = reactive<ReportPeriodSummaryQuery>({
pageNum: 1,
pageSize: 10,
monitorCode: undefined,
metricCode: undefined,
periodType: 'DAY',
periodStart: undefined,
periodEnd: undefined,
params: {}
});
const currentPeriodLabel = computed(() => resolvePeriodTypeLabel(queryParams.periodType));
const currentRangeLabel = computed(() => {
if (!daterangePeriodTime.value?.length) {
return '请选择统计时间范围';
}
return `${daterangePeriodTime.value[0]}${daterangePeriodTime.value[1]}`;
});
const currentRangeShortLabel = computed(() => {
if (!daterangePeriodTime.value?.length) {
return '未设置时间窗口';
}
return `${daterangePeriodTime.value[0]} ~ ${daterangePeriodTime.value[1]}`;
});
const selectedMonitorLabel = computed(() => {
if (!queryParams.monitorCode) {
return '全部物联点位';
}
return monitorNameMap.value[String(queryParams.monitorCode)] || String(queryParams.monitorCode);
});
const activeMetricLabel = computed(() => {
if (!queryParams.metricCode) {
return '全部测量项';
}
return resolveMetricName(String(queryParams.metricCode));
});
const totalAlarmCount = computed(() => reportRows.value.reduce((sum, item) => sum + Number(item.alarmCount || 0), 0));
const maxSampleCount = computed(() => reportRows.value.reduce((max, item) => Math.max(max, Number(item.sampleCount || 0)), 0));
const normalizeMonitorTree = (nodes: ReportTreeNode[] = []): ReportTreeNode[] =>
nodes.map((node) => {
const children = Array.isArray(node.children) ? normalizeMonitorTree(node.children) : [];
const label = node.monitorName || node.label || node.name || node.monitorCode || String(node.value ?? '');
const value = node.monitorCode || node.value || node.monitorId || node.objId || node.id;
return {
...node,
label,
value,
children
};
});
const collectMonitorNames = (nodes: ReportTreeNode[] = []) => {
nodes.forEach((node) => {
const key = String(node.value ?? node.monitorCode ?? '');
if (key) {
monitorNameMap.value[key] = node.label || node.monitorName || key;
}
if (node.children?.length) {
collectMonitorNames(node.children);
}
});
};
const syncTimeRangeToQuery = () => {
queryParams.periodStart = daterangePeriodTime.value?.[0];
queryParams.periodEnd = daterangePeriodTime.value?.[1];
};
const initQueryFromRoute = () => {
const beginRecordTime = route.query.beginRecordTime as string | undefined;
const endRecordTime = route.query.endRecordTime as string | undefined;
if (beginRecordTime && endRecordTime) {
daterangePeriodTime.value = [beginRecordTime, endRecordTime];
} else if (!daterangePeriodTime.value.length) {
// 24
const end = new Date();
const begin = new Date(end.getTime() - 24 * 60 * 60 * 1000);
daterangePeriodTime.value = [parseTime(begin, '{y}-{m}-{d} {h}:{i}:{s}'), parseTime(end, '{y}-{m}-{d} {h}:{i}:{s}')];
}
if (typeof route.query.monitorId === 'string' && route.query.monitorId) {
queryParams.monitorCode = route.query.monitorId;
}
if (typeof route.query.periodType === 'string' && route.query.periodType) {
queryParams.periodType = route.query.periodType.toUpperCase();
}
if (typeof route.query.metricCode === 'string' && route.query.metricCode) {
queryParams.metricCode = route.query.metricCode;
}
syncTimeRangeToQuery();
};
const loadMonitorTree = async () => {
const response = await getMonitorInfoTree({
monitorTypeList: [5, 6, 7, 8, 9, 10]
} as any);
monitorTreeOptions.value = normalizeMonitorTree(response.data || []);
collectMonitorNames(monitorTreeOptions.value);
};
/** 查询周期报表 */
const getList = async () => {
syncTimeRangeToQuery();
if (!queryParams.periodStart || !queryParams.periodEnd) {
proxy?.$modal.msgWarning('请选择完整的统计时间范围');
return;
}
loading.value = true;
try {
const res = await listAggregateReportPeriodSummary(queryParams);
reportRows.value = res.rows || [];
total.value = Number(res.total || 0);
} finally {
loading.value = false;
}
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
queryParams.monitorCode = undefined;
queryParams.metricCode = undefined;
queryParams.periodType = 'DAY';
initQueryFromRoute();
handleQuery();
};
/** 导出按钮操作 */
const handleExport = () => {
syncTimeRangeToQuery();
if (!queryParams.periodStart || !queryParams.periodEnd) {
proxy?.$modal.msgWarning('请选择完整的统计时间范围');
return;
}
proxy?.download(
'ems/base/reportPeriodSummary/aggregateExport',
{
...queryParams
},
`iot_period_report_${new Date().getTime()}.xlsx`
);
};
const resolveMonitorName = (row: ReportPeriodSummaryVO) =>
row.monitorName || monitorNameMap.value[String(row.monitorCode || '')] || row.monitorCode || '-';
const resolveMetricName = (metricCode?: string) => metricOptions.find((item) => item.value === metricCode)?.label || metricCode || '-';
const resolvePeriodTypeLabel = (periodType?: string) => periodTypeOptions.find((item) => item.value === periodType)?.label || periodType || '-';
const formatDateTime = (value?: string | number | Date) => {
if (!value) {
return '-';
}
return parseTime(value as any, '{y}-{m}-{d} {h}:{i}:{s}');
};
const formatNumber = (value?: string | number) => {
if (value === null || value === undefined || value === '') {
return '-';
}
const numericValue = Number(value);
if (Number.isNaN(numericValue)) {
return value;
}
return numericValue.toFixed(2);
};
onMounted(async () => {
initQueryFromRoute();
await loadMonitorTree();
await getList();
});
</script>
<style scoped lang="scss">
.report-page {
--report-accent: #c46b23;
--report-deep: #184a45;
--report-soft: #f8efe4;
--report-surface: #fffdfa;
background:
radial-gradient(circle at top left, rgba(196, 107, 35, 0.08), transparent 28%), linear-gradient(180deg, #fff9f2 0%, #f7f8f6 48%, #f3f5f3 100%);
min-height: 100%;
.hero-card {
background:
linear-gradient(135deg, rgba(24, 74, 69, 0.97), rgba(27, 89, 71, 0.92)), linear-gradient(45deg, rgba(196, 107, 35, 0.18), transparent 40%);
border: none;
border-radius: 22px;
color: #f5efe6;
margin-bottom: 12px;
overflow: hidden;
position: relative;
}
.hero-card::after {
background: radial-gradient(circle at center, rgba(255, 255, 255, 0.16), transparent 60%);
content: '';
inset: -30% auto auto 60%;
pointer-events: none;
position: absolute;
width: 360px;
height: 360px;
}
.hero-grid {
align-items: stretch;
display: grid;
gap: 18px;
grid-template-columns: minmax(0, 2fr) minmax(280px, 0.95fr);
position: relative;
z-index: 1;
}
.hero-main {
padding: 8px 4px 4px;
}
.hero-kicker {
color: rgba(255, 240, 219, 0.72);
font-size: 12px;
letter-spacing: 0.22em;
margin-bottom: 10px;
}
.hero-title {
color: #fff8ef;
font-size: 30px;
font-weight: 700;
letter-spacing: 0.02em;
line-height: 1.15;
}
.hero-desc {
color: rgba(255, 245, 234, 0.86);
font-size: 14px;
line-height: 1.8;
margin-top: 12px;
max-width: 760px;
}
.hero-tags {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 18px;
}
.hero-tag {
border-radius: 999px;
padding: 0 10px;
}
.hero-side {
display: flex;
align-items: stretch;
justify-content: flex-end;
}
.hero-panel {
background: linear-gradient(180deg, rgba(255, 248, 239, 0.14), rgba(255, 248, 239, 0.06));
backdrop-filter: blur(8px);
border: 1px solid rgba(255, 239, 220, 0.16);
border-radius: 18px;
display: flex;
flex-direction: column;
justify-content: center;
min-height: 100%;
padding: 20px 22px;
width: 100%;
}
.hero-panel-label {
color: rgba(255, 241, 221, 0.72);
font-size: 12px;
letter-spacing: 0.14em;
text-transform: uppercase;
}
.hero-panel-value {
color: #fffaf3;
font-size: 22px;
font-weight: 600;
line-height: 1.4;
margin-top: 12px;
}
.hero-panel-foot {
color: rgba(255, 241, 221, 0.76);
font-size: 13px;
line-height: 1.7;
margin-top: 10px;
}
.query-card {
background: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(255, 250, 244, 0.96));
border-radius: 18px;
border: 1px solid rgba(196, 107, 35, 0.08);
box-shadow: 0 16px 32px rgba(24, 74, 69, 0.05);
}
.summary-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
margin-bottom: 12px;
}
.summary-card {
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(255, 250, 244, 0.95)),
linear-gradient(135deg, rgba(196, 107, 35, 0.05), transparent 55%);
border: 1px solid rgba(196, 107, 35, 0.08);
border-radius: 18px;
box-shadow: 0 14px 28px rgba(24, 74, 69, 0.05);
}
.summary-label {
color: #7d6c57;
font-size: 13px;
margin-bottom: 8px;
}
.summary-value {
color: var(--report-deep);
font-size: 28px;
font-weight: 600;
line-height: 1.1;
}
.summary-foot {
color: #9b8f81;
font-size: 12px;
margin-top: 10px;
}
.table-card {
background: rgba(255, 253, 250, 0.98);
border: 1px solid rgba(196, 107, 35, 0.08);
border-radius: 20px;
box-shadow: 0 18px 40px rgba(24, 74, 69, 0.06);
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.card-title {
color: var(--report-deep);
font-size: 18px;
font-weight: 600;
}
.card-subtitle {
color: #7d6c57;
font-size: 13px;
margin-top: 4px;
}
.card-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.card-tag-item {
background: rgba(196, 107, 35, 0.08);
border: 1px solid rgba(196, 107, 35, 0.1);
border-radius: 999px;
color: #865b34;
font-size: 12px;
line-height: 1;
padding: 8px 12px;
}
.main-cell {
color: var(--report-deep);
font-weight: 500;
}
.sub-cell {
color: #a28f7d;
font-size: 12px;
line-height: 1.4;
}
:deep(.report-table .el-table__cell) {
padding: 10px 0;
}
:deep(.report-table) {
border-radius: 14px;
overflow: hidden;
}
:deep(.report-table .el-table__header-wrapper th) {
background: linear-gradient(180deg, #f4eadb 0%, #efe0ca 100%);
color: #4c4d4f;
font-weight: 600;
}
:deep(.report-table .el-table__row:nth-child(even) td) {
background: rgba(250, 244, 235, 0.55);
}
.empty-panel {
align-items: center;
background: linear-gradient(180deg, rgba(247, 239, 227, 0.72), rgba(255, 252, 246, 0.96));
border: 1px dashed rgba(196, 107, 35, 0.22);
border-radius: 18px;
display: flex;
flex-direction: column;
justify-content: center;
margin-top: 16px;
padding: 36px 20px;
text-align: center;
}
.empty-icon {
align-items: center;
background: linear-gradient(135deg, #c46b23, #d08d4e);
border-radius: 18px;
color: #fff8ef;
display: inline-flex;
font-size: 24px;
font-weight: 700;
height: 58px;
justify-content: center;
letter-spacing: 0.08em;
width: 58px;
}
.empty-title {
color: var(--report-deep);
font-size: 18px;
font-weight: 600;
margin-top: 16px;
}
.empty-desc {
color: #7d6c57;
font-size: 13px;
line-height: 1.8;
margin-top: 8px;
max-width: 520px;
}
}
@media (max-width: 1200px) {
.report-page {
.hero-grid {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
.summary-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
}
@media (max-width: 768px) {
.report-page {
.summary-grid {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
.card-header {
align-items: flex-start;
flex-direction: column;
}
}
}
</style>
Loading…
Cancel
Save