From 8bb7160c9c4fd7ce5893ee673647b13cc35410ae Mon Sep 17 00:00:00 2001 From: liriming <1343021927@qq.com> Date: 星期一, 03 三月 2025 17:27:07 +0800 Subject: [PATCH] Merge remote-tracking branch 'origin/master' --- src/views/infra/apiErrorLog/index.vue | 14 src/views/data/ind/item/CalIndDefineForm.vue | 58 src/views/data/ind/item/IndHistoryChart.vue | 169 + src/views/data/point/index.vue | 37 src/views/model/pre/accuracy/index.vue | 146 + src/views/model/matlab/model/index.vue | 188 + src/views/model/mpk/file/MpkRun.vue | 61 src/views/infra/monitor/components/MonitorDiskForm.vue | 128 + src/views/system/operatelog/index.vue | 4 src/views/infra/monitor/components/MonitorMemForm.vue | 148 + src/views/model/matlab/model/MatlabModelForm.vue | 394 +++ src/views/model/matlab/project/MatlabProjectModelDialog.vue | 101 src/views/data/arc/ArcSettingForm.vue | 119 src/views/model/pre/accuracy/MmItemAccuracyRateForm.vue | 176 + src/views/data/plan/data/DataSetForm.vue | 39 src/views/system/loginlog/LoginLogDetail.vue | 2 src/components/MonitorDiskPie/PieChart.vue | 35 src/router/modules/remaining.ts | 23 src/api/data/ind/item/item.ts | 13 src/api/model/pre/alarm/message.ts | 28 src/api/model/pre/item/index.ts | 10 src/views/model/pre/alarm/MmPredictAlarmMessageForm.vue | 135 + src/views/data/point/DaPointValue.vue | 144 + src/views/infra/monitor/components/MonitorDisk.vue | 487 ++++ src/views/model/pre/alarm/MmPredictAlarmConfigForm.vue | 239 ++ src/utils/dict.ts | 5 src/api/model/sche/model/index.ts | 20 src/views/model/pre/alarm/index.vue | 178 + src/api/model/pre/alarm/config.ts | 57 src/views/data/point/DaPointForm.vue | 1 src/api/infra/monitordisk/index.ts | 57 src/api/data/arc/data.ts | 2 src/views/infra/monitor/index.vue | 20 src/api/model/pre/accuracy/his.ts | 19 src/views/model/pre/item/MmPredictItemChart.vue | 49 src/views/data/ind/item/IndCurrentData.vue | 45 src/views/data/arc/index.vue | 307 +- src/views/data/ind/item/AtomIndDefineForm.vue | 68 src/views/infra/apiAccessLog/index.vue | 14 src/api/data/da/point/daPointChart.ts | 14 src/utils/download.ts | 5 src/api/model/matlab/project.ts | 33 src/views/data/ind/item/index.vue | 250 + src/views/model/matlab/model/MatlabModelSettingForm.vue | 144 + src/views/model/mpk/file/index.vue | 17 src/api/data/arc/index.ts | 13 src/api/model/pre/accuracy/rate.ts | 50 src/views/infra/monitor/components/MonitorMem.vue | 478 ++++ src/views/system/loginlog/index.vue | 14 src/views/model/sche/scheme/record/index.vue | 2 src/views/data/ind/item/DerIndDefineForm.vue | 77 src/views/infra/monitor/components/index.ts | 3 src/views/model/matlab/project/MatlabProjectForm.vue | 156 + src/views/model/pre/analysis/index.vue | 308 ++ src/api/model/matlab/mlModel.ts | 29 src/views/model/pre/item/MmPredictItemForm.vue | 188 + /dev/null | 161 - src/views/data/ind/data/DataSetForm.vue | 47 src/views/model/matlab/model/MatlabRun.vue | 279 ++ src/views/model/matlab/project/index.vue | 221 + src/views/data/arc/ArcData.vue | 12 src/api/infra/monitormem/index.ts | 56 src/store/modules/mall/kefu.ts | 81 src/views/model/sche/model/ScheduleModelForm.vue | 110 src/api/model/mpk/mpk.ts | 4 src/views/data/point/DaPointChart.vue | 2 src/views/model/pre/accuracy/MmItemAccuracyHisForm.vue | 122 + 67 files changed, 5,863 insertions(+), 753 deletions(-) diff --git a/src/api/data/arc/data.ts b/src/api/data/arc/data.ts index 418ba74..a8f77ef 100644 --- a/src/api/data/arc/data.ts +++ b/src/api/data/arc/data.ts @@ -16,5 +16,5 @@ // 查询ArcSetting列表 export const getPage = (params: ArcDataPageReqVO) => { - return request.get({ url: '/data/da/arc/dataPage', params }) + return request.get({ url: '/data/arc/data/page', params }) } diff --git a/src/api/data/arc/index.ts b/src/api/data/arc/index.ts index 2ac6cc9..d6d369b 100644 --- a/src/api/data/arc/index.ts +++ b/src/api/data/arc/index.ts @@ -6,7 +6,8 @@ type: string, point: string, calculate: string, - isEnable: string + sort: number, + isEnable: number } export interface ArcSettingPageReqVO extends PageParam { @@ -16,25 +17,25 @@ // 查询ArcSetting列表 export const getArcSettingPage = (params: ArcSettingPageReqVO) => { - return request.get({ url: '/data/da/arc/page', params }) + return request.get({ url: '/data/arc/setting/page', params }) } // 查询ArcSetting详情 export const getArcSetting = (id: number) => { - return request.get({ url: `/data/da/arc/info/${id}`}) + return request.get({ url: `/data/arc/setting/info/${id}`}) } // 新增ArcSetting export const createArcSetting = (data: ArcSettingVO) => { - return request.post({ url: '/data/da/arc/create', data }) + return request.post({ url: '/data/arc/setting/create', data }) } // 修改ArcSetting export const updateArcSetting = (data: ArcSettingVO) => { - return request.put({ url: '/data/da/arc/update', data }) + return request.put({ url: '/data/arc/setting/update', data }) } // 删除ArcSetting export const deleteArcSetting = (id: number) => { - return request.delete({ url: '/data/da/arc/delete?id=' + id }) + return request.delete({ url: '/data/arc/setting/delete?id=' + id }) } diff --git a/src/api/data/da/point/daPointChart.ts b/src/api/data/da/point/daPointChart.ts index 1090f88..bffedaa 100644 --- a/src/api/data/da/point/daPointChart.ts +++ b/src/api/data/da/point/daPointChart.ts @@ -6,11 +6,25 @@ end?: Date, } +export interface ApiPointPageReqVO extends PageParam { + pointNo?: string +} + // 查询chart列表 export const getPointDaChart = (data: DaPointChartReqVO) => { return request.post({ url: '/data/api/query-points/chart', data }) } +// 查询多个测点当前值 +export const getPointsRealValue = (data: []) => { + return request.post({ url: '/data/api/query-points/real-value', data }) +} + +// 查询计算点当前值 +export const getMathPointCurrentValue = (data: ApiPointPageReqVO) => { + return request.post({ url: '/data/api/query-math-point/current-value', data }) +} + //导出DaPointValue export const exportDaPointValue = (params) => { return request.download({ url: '/data/da/point/exportValue', params }) diff --git a/src/api/data/ind/item/item.ts b/src/api/data/ind/item/item.ts index cd9d04e..0bb8833 100644 --- a/src/api/data/ind/item/item.ts +++ b/src/api/data/ind/item/item.ts @@ -27,6 +27,11 @@ itemCategory: string } +export type IndValueParam = { + itemNo: string + startDate: string + endTime: string +} // 查询列表 export const getItemPage = (params: PageParam) => { @@ -64,3 +69,11 @@ callback(new Error('请输入数字!')); } } + +export const getItemCurrentData = (itemNo: string) => { + return request.get({ url: '/data/api/query-ind/default-value?itemNo=' + itemNo}) +} + +export const getItemValueData = (params: IndValueParam) => { + return request.get({ url: '/data/ind-item-value/getList', params}) +} diff --git a/src/api/infra/monitordisk/index.ts b/src/api/infra/monitordisk/index.ts new file mode 100644 index 0000000..e9a2512 --- /dev/null +++ b/src/api/infra/monitordisk/index.ts @@ -0,0 +1,57 @@ +import request from '@/config/axios' + +// 磁盘监控日志 VO +export interface MonitorDiskVO { + id: number // 访问ID + hostName: string // 主机名称 + hostIp: string // 服务器ip + disk: string // 盘符 + diskName: string // 磁盘名 + spaceTotal: number // 总空间 + spaceUsed: number // 已用空间 + spaceUsable: number // 可用空间 + spaceRatio: number // 空间使用比例 +} + +// 磁盘监控日志 API +export const MonitorDiskApi = { + // 查询磁盘监控日志分页 + getMonitorDiskPage: async (params: any) => { + return await request.get({ url: `/infra/monitor-disk/page`, params }) + }, + + // 查询磁盘监控日志列表 + getMonitorDiskList: async (params: any) => { + return await request.get({ url: `/infra/monitor-disk/getMonitorDiskList`, params }) + }, + + // 查询磁盘监控日志信息 + getMonitorDiskInfo: async (params: any) => { + return await request.get({ url: `/infra/monitor-disk/getMonitorDiskInfo`, params }) + }, + + // 查询磁盘监控日志详情 + getMonitorDisk: async (id: number) => { + return await request.get({ url: `/infra/monitor-disk/get?id=` + id }) + }, + + // 新增磁盘监控日志 + createMonitorDisk: async (data: MonitorDiskVO) => { + return await request.post({ url: `/infra/monitor-disk/create`, data }) + }, + + // 修改磁盘监控日志 + updateMonitorDisk: async (data: MonitorDiskVO) => { + return await request.put({ url: `/infra/monitor-disk/update`, data }) + }, + + // 删除磁盘监控日志 + deleteMonitorDisk: async (id: number) => { + return await request.delete({ url: `/infra/monitor-disk/delete?id=` + id }) + }, + + // 导出磁盘监控日志 Excel + exportMonitorDisk: async (params) => { + return await request.download({ url: `/infra/monitor-disk/export-excel`, params }) + }, +} diff --git a/src/api/infra/monitormem/index.ts b/src/api/infra/monitormem/index.ts new file mode 100644 index 0000000..c402272 --- /dev/null +++ b/src/api/infra/monitormem/index.ts @@ -0,0 +1,56 @@ +import request from '@/config/axios' + +// 内存监控日志 VO +export interface MonitorMemVO { + id: number // 访问ID + hostName: string // 主机名称 + hostIp: string // 服务器ip + serverName: string // 服务名 + physicalTotal: number // 总物理内存 + physicalUsed: number // 已用物理内存 + physicalFree: number // 剩余物理内存 + physicalUsage: number // 物理内存使用率 + runtimeTotal: number // jvm运行总内存 + runtimeMax: number // jvm最大内存 + runtimeUsed: number // jvm已用内存 + runtimeFree: number // jvm空闲内存 + runtimeUsage: number // jvm内存使用率 +} + +// 内存监控日志 API +export const MonitorMemApi = { + // 查询内存监控日志分页 + getMonitorMemPage: async (params: any) => { + return await request.get({ url: `/infra/monitor-mem/page`, params }) + }, + + // 查询统计数据列表 + getMonitorMemList: async (params: any) => { + return await request.get({ url: `/infra/monitor-mem/getMonitorMemList`, params }) + }, + + // 查询内存监控日志详情 + getMonitorMem: async (id: number) => { + return await request.get({ url: `/infra/monitor-mem/get?id=` + id }) + }, + + // 新增内存监控日志 + createMonitorMem: async (data: MonitorMemVO) => { + return await request.post({ url: `/infra/monitor-mem/create`, data }) + }, + + // 修改内存监控日志 + updateMonitorMem: async (data: MonitorMemVO) => { + return await request.put({ url: `/infra/monitor-mem/update`, data }) + }, + + // 删除内存监控日志 + deleteMonitorMem: async (id: number) => { + return await request.delete({ url: `/infra/monitor-mem/delete?id=` + id }) + }, + + // 导出内存监控日志 Excel + exportMonitorMem: async (params) => { + return await request.download({ url: `/infra/monitor-mem/export-excel`, params }) + }, +} diff --git a/src/api/model/matlab/mlModel.ts b/src/api/model/matlab/mlModel.ts new file mode 100644 index 0000000..980d10f --- /dev/null +++ b/src/api/model/matlab/mlModel.ts @@ -0,0 +1,29 @@ +import request from '@/config/axios' + +export const getPage = async (params: PageParam) => { + return await request.get({ url: '/model/matlab/model/page', params }) +} + +export const get = async (id: number) => { + return await request.get({ url: '/model/matlab/model/' + id }) +} + +export const create = async (data) => { + return await request.post({ url: '/model/matlab/model', data: data }) +} + +export const update = async (params) => { + return await request.put({ url: '/model/matlab/model', data: params }) +} + +export const deleteModel = async (id: number) => { + return await request.delete({ url: '/model/matlab/model?id=' + id }) +} + +export const list = (params) => { + return request.get({ url: '/model/matlab/model/list', params}) +} + +export const test = (data) => { + return request.post({ url: '/model/matlab/model/test', data}) +} diff --git a/src/api/model/matlab/project.ts b/src/api/model/matlab/project.ts new file mode 100644 index 0000000..225b952 --- /dev/null +++ b/src/api/model/matlab/project.ts @@ -0,0 +1,33 @@ +import request from '@/config/axios' + +export const getPage = async (params) => { + return await request.get({ url: '/model/matlab/project/page', params }) +} + +export const getProject = async (id: number) => { + return await request.get({ url: '/model/matlab/project/' + id }) +} + +export const createProject = async (data) => { + return await request.post({ url: '/model/matlab/project', data: data }) +} + +export const updateProject = async (params) => { + return await request.put({ url: '/model/matlab/project', data: params }) +} + +export const deleteProject = async (id: number) => { + return await request.delete({ url: '/model/matlab/project?id=' + id }) +} + +export const list = () => { + return request.get({ url: '/model/packageProject/project/list'}) +} + +export const getProjectModel = async (params) => { + return await request.get({ url: '/model/matlab/project/getProjectModel', params }) +} + +export const publish = async (data) => { + return await request.post({ url: '/model/matlab/project/publish', data }) +} diff --git a/src/api/model/mpk/mpk.ts b/src/api/model/mpk/mpk.ts index dc2782f..b0b880b 100644 --- a/src/api/model/mpk/mpk.ts +++ b/src/api/model/mpk/mpk.ts @@ -42,6 +42,10 @@ return request.post({ url: '/model/mpk/api/test', data: params }) } +export const saveModel = (data) => { + return request.downloadByPost({ url: '/model/mpk/api/saveModel', data }) +} + export const list = (params) => { return request.get({ url: '/model/mpk/file/list', params}) } diff --git a/src/api/model/pre/accuracy/his.ts b/src/api/model/pre/accuracy/his.ts new file mode 100644 index 0000000..b655dbf --- /dev/null +++ b/src/api/model/pre/accuracy/his.ts @@ -0,0 +1,19 @@ +import request from '@/config/axios' + +export interface MmItemAccuracyHisVO { + id: string + rateId: string, + inDeviation: number, + inAccuracyRate: number, + outDeviation: number, + outAccuracyRate: number, +} + +export interface MmItemAccuracyHisPageReqVO extends PageParam { + rateId?: string +} + +// 查询MmItemAccuracyHis列表 +export const getMmItemAccuracyHisPage = (params: MmItemAccuracyHisPageReqVO) => { + return request.get({ url: '/model/item/accuracy-his/page', params }) +} diff --git a/src/api/model/pre/accuracy/rate.ts b/src/api/model/pre/accuracy/rate.ts new file mode 100644 index 0000000..692c730 --- /dev/null +++ b/src/api/model/pre/accuracy/rate.ts @@ -0,0 +1,50 @@ +import request from '@/config/axios' + +export interface MmItemAccuracyRateVO { + id: string + itemId: string, + outId: string, + sampleLength: number, + valueType: number, + beginTime: Date, + inDeviation: number, + inAccuracyRate: number, + outDeviation: number, + outAccuracyRate: number, + isEnable: number, +} + +export interface MmItemAccuracyRatePageReqVO extends PageParam { + itemId?: string +} + +// 查询MmItemAccuracyRate列表 +export const getMmItemAccuracyRatePage = (params: MmItemAccuracyRatePageReqVO) => { + return request.get({ url: '/model/item/accuracy-rate/page', params }) +} + +// 查询MmItemAccuracyRate详情 +export const getMmItemAccuracyRate = (id: number) => { + return request.get({ url: `/model/item/accuracy-rate/get/${id}`}) +} + +// 新增MmItemAccuracyRate +export const createMmItemAccuracyRate = (data: MmItemAccuracyRateVO) => { + return request.post({ url: '/model/item/accuracy-rate/create', data }) +} + +// 修改MmItemAccuracyRate +export const updateMmItemAccuracyRate = (data: MmItemAccuracyRateVO) => { + return request.put({ url: '/model/item/accuracy-rate/update', data }) +} + +// 删除MmItemAccuracyRate +export const deleteMmItemAccuracyRate = (id: number) => { + return request.delete({ url: '/model/item/accuracy-rate/delete?id=' + id }) +} + + +// 查询getItemAccuracyRateList详情 +export const getItemAccuracyRateList = () => { + return request.get({ url: `/model/item/accuracy-rate/list`}) +} diff --git a/src/api/model/pre/alarm/config.ts b/src/api/model/pre/alarm/config.ts new file mode 100644 index 0000000..b7deb23 --- /dev/null +++ b/src/api/model/pre/alarm/config.ts @@ -0,0 +1,57 @@ +import request from '@/config/axios' + +export interface MmPredictAlarmConfigVO { + id: string + title: string, + alarmObj: string, + itemId: string, + outId: string, + compLength: number, + upperLimit: number, + lowerLimit: number, + culUpper: number, + culLower: number, + unit: string, + coefficient: number, + scheduleId: string, + isEnable: number, + creator: string, + createTime: Date, + updater: string, + updateTime: Date, +} + +export interface MmPredictAlarmConfigPageReqVO extends PageParam { + title?: string +} + +// 查询MmPredictAlarmConfig列表 +export const getMmPredictAlarmConfigPage = (params: MmPredictAlarmConfigPageReqVO) => { + return request.get({ url: '/model/pre/alarm-config/page', params }) +} + +// 查询MmPredictAlarmConfig详情 +export const getMmPredictAlarmConfig = (id: number) => { + return request.get({ url: `/model/pre/alarm-config/get/${id}`}) +} + +// 新增MmPredictAlarmConfig +export const createMmPredictAlarmConfig = (data: MmPredictAlarmConfigVO) => { + return request.post({ url: '/model/pre/alarm-config/create', data }) +} + +// 修改MmPredictAlarmConfig +export const updateMmPredictAlarmConfig = (data: MmPredictAlarmConfigVO) => { + return request.put({ url: '/model/pre/alarm-config/update', data }) +} + +// 删除MmPredictAlarmConfig +export const deleteMmPredictAlarmConfig = (id: number) => { + return request.delete({ url: '/model/pre/alarm-config/delete?id=' + id }) +} + + +// 查询getPredictAlarmConfigList详情 +export const getPredictAlarmConfigList = () => { + return request.get({ url: `/model/pre/alarm-config/list`}) +} diff --git a/src/api/model/pre/alarm/message.ts b/src/api/model/pre/alarm/message.ts new file mode 100644 index 0000000..30318cd --- /dev/null +++ b/src/api/model/pre/alarm/message.ts @@ -0,0 +1,28 @@ +import request from '@/config/axios' + +export interface MmPredictAlarmMessageVO { + id: string + configId: string, + title: string, + content: string, + alarmObj: string, + pointId: string, + itemId: string, + outId: string, + currentValue: number, + outTime: Date, + outValue: number, + alarmType: string, + alarmTime: Date, + createTime: Date, +} + +export interface MmPredictAlarmMessagePageReqVO extends PageParam { + title?: string +} + +// 查询MmPredictAlarmMessage列表 +export const getMmPredictAlarmMessagePage = (params: MmPredictAlarmMessagePageReqVO) => { + return request.get({ url: '/model/pre/alarm-message/page', params }) +} + diff --git a/src/api/model/pre/item/index.ts b/src/api/model/pre/item/index.ts index 7fe814d..9a498ee 100644 --- a/src/api/model/pre/item/index.ts +++ b/src/api/model/pre/item/index.ts @@ -17,7 +17,10 @@ predictphase: string, workchecked: number, unittransfactor: string, - saveindex: string + saveindex: string, + iscumulant: number, + cumuldivisor: number, + cumulpoint: string }, dmModuleItem: { id: string, @@ -103,6 +106,11 @@ return request.get({ url: `/model/pre/item/list`, params}) } +// 查询MmItemOutput列表 +export const getMmItemOutputList = (params) => { + return request.get({ url: `/model/pre/item-output/list/all`, params}) +} + export const updateModel = (data: any) => { return request.upload({ url: '/model/pre/item/upload-model', data }) } diff --git a/src/api/model/sche/model/index.ts b/src/api/model/sche/model/index.ts index 0ac2b05..39a1184 100644 --- a/src/api/model/sche/model/index.ts +++ b/src/api/model/sche/model/index.ts @@ -4,6 +4,8 @@ import * as PlanItemApi from '@/api/data/plan/item' import {CommonEnabled} from "@/utils/constants"; import {getItemList, ItemVO} from "@/api/data/plan/item"; +import * as ItemApi from '@/api/data/ind/item/item' +import {getPointSimpleList} from "@/api/data/da/point"; export interface ScheduleModelVO { id: string @@ -74,7 +76,7 @@ export const getModelParamList = async (id) => { const dataPointList = ref([] as DataPointApi.DaPointVO) - dataPointList.value = await DataPointApi.getPointList({}) + dataPointList.value = await DataPointApi.getPointSimpleList({}) const pointList = [] if (dataPointList.value) { dataPointList.value.forEach(item => { @@ -143,10 +145,26 @@ }) } + // 指标数据 + const indItemList = await ItemApi.getItemList({}) + const indList = [] + if (indItemList) { + indItemList.forEach(item => { + indList.push( + { + id: item.id, + name: item.itemName + } + ) + }) + } + return { 'DATAPOINT':pointList, 'NormalItem': normalItemList, 'MergeItem': mergeItemList, 'PLAN': planList, + 'IND': indList, + 'IND_ASCII': indList, } } diff --git a/src/assets/svgs/member_balance.svg b/src/assets/svgs/member_balance.svg deleted file mode 100644 index 5395b23..0000000 --- a/src/assets/svgs/member_balance.svg +++ /dev/null @@ -1 +0,0 @@ -<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1693028338187" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="22985" width="128" height="128" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M983.8 312.7C958 251.7 921 197 874 150c-47-47-101.8-83.9-162.7-109.7C648.2 13.5 581.1 0 512 0S375.8 13.5 312.7 40.2C251.7 66 197 102.9 150 150c-47 47-83.9 101.8-109.7 162.7C13.5 375.8 0 442.9 0 512s13.5 136.2 40.2 199.3C66 772.3 102.9 827 150 874c47 47 101.8 83.9 162.7 109.7 63.1 26.7 130.2 40.2 199.3 40.2s136.2-13.5 199.3-40.2C772.3 958 827 921 874 874c47-47 83.9-101.8 109.7-162.7 26.7-63.1 40.2-130.2 40.2-199.3s-13.4-136.2-40.1-199.3z m-55.3 375.2c-22.8 53.8-55.4 102.2-96.9 143.7s-89.9 74.1-143.7 96.9C632.2 952.1 573 964 512 964s-120.2-11.9-175.9-35.5c-53.8-22.8-102.2-55.4-143.7-96.9s-74.1-89.9-96.9-143.7C71.9 632.2 60 573 60 512s11.9-120.2 35.5-175.9c22.8-53.8 55.4-102.2 96.9-143.7s89.9-74.1 143.7-96.9C391.8 71.9 451 60 512 60s120.2 11.9 175.9 35.5c53.8 22.8 102.2 55.4 143.7 96.9s74.1 89.9 96.9 143.7C952.1 391.8 964 451 964 512s-11.9 120.2-35.5 175.9z" fill="#000000" p-id="22986"></path><path d="M706 469.1H574.7l84.2-180.6c7-15 0.4-32.9-14.5-39.9-15-7-32.9-0.4-39.9 14.5L512 461.5l-92.5-198.3c-7-15-24.9-21.5-39.9-14.5s-21.5 24.9-14.5 39.9l84.2 180.6H318c-16.5 0-30 13.5-30 30s13.5 30 30 30h164v64h-92.5c-20.6 0-37.5 13.5-37.5 30s16.9 30 37.5 30H482v95c0 16.5 13.5 30 30 30s30-13.5 30-30v-95h92.5c20.6 0 37.5-13.5 37.5-30s-16.9-30-37.5-30H542v-64h164c16.5 0 30-13.5 30-30 0-16.6-13.5-30.1-30-30.1z" fill="#000000" p-id="22987"></path></svg> \ No newline at end of file diff --git a/src/assets/svgs/member_expenditure_balance.svg b/src/assets/svgs/member_expenditure_balance.svg deleted file mode 100644 index 02d498c..0000000 --- a/src/assets/svgs/member_expenditure_balance.svg +++ /dev/null @@ -1 +0,0 @@ -<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1693028553383" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="28918" width="128" height="128" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M510.72 962.56C262.4 960 61.44 757.76 64 509.44 66.56 263.68 264.96 65.28 510.72 62.72c17.92 0 34.56 14.08 34.56 32s-14.08 34.56-32 34.56h-2.56C299.52 130.56 128 300.8 128 512s171.52 382.72 382.72 382.72S893.44 723.2 893.44 512c0-17.92 16.64-33.28 34.56-32 17.92 0 32 15.36 32 32 0 248.32-200.96 450.56-449.28 450.56z" fill="#000000" p-id="28919"></path><path d="M645.12 480H375.04c-17.92 0-34.56-14.08-34.56-32s14.08-34.56 32-34.56h272.64c17.92 0 33.28 16.64 32 34.56 0 17.92-14.08 32-32 32z m0 130.56H375.04c-17.92 0-33.28-16.64-32-34.56 0-17.92 15.36-32 32-32h270.08c17.92 0 33.28 16.64 32 34.56 0 16.64-14.08 32-32 32z" fill="#000000" p-id="28920"></path><path d="M510.72 746.24c-17.92 0-33.28-15.36-33.28-33.28V441.6c0-17.92 16.64-33.28 34.56-32 17.92 0 32 15.36 32 32v270.08c0 19.2-15.36 34.56-33.28 34.56z" fill="#000000" p-id="28921"></path><path d="M510.72 458.24c-8.96 0-17.92-3.84-24.32-10.24l-111.36-111.36c-14.08-12.8-15.36-33.28-2.56-47.36s33.28-15.36 47.36-2.56l2.56 2.56 111.36 111.36c12.8 12.8 12.8 34.56 0 47.36-6.4 6.4-15.36 10.24-23.04 10.24z" fill="#000000" p-id="28922"></path><path d="M510.72 458.24c-8.96 0-17.92-3.84-24.32-10.24-12.8-12.8-12.8-34.56 0-47.36l111.36-111.36c14.08-12.8 35.84-10.24 47.36 2.56 11.52 12.8 11.52 32 0 44.8L533.76 448c-6.4 6.4-15.36 10.24-23.04 10.24zM925.44 241.92c17.92 0 33.28-15.36 33.28-33.28 0-8.96-3.84-17.92-10.24-24.32l-111.36-111.36c-12.8-14.08-33.28-14.08-47.36-1.28s-14.08 33.28-1.28 47.36l1.28 1.28 111.36 111.36c7.68 6.4 15.36 10.24 24.32 10.24z" fill="#000000" p-id="28923"></path><path d="M815.36 353.28c8.96 0 17.92-3.84 24.32-10.24l111.36-111.36c12.8-14.08 10.24-35.84-2.56-47.36-12.8-11.52-32-11.52-44.8 0l-111.36 111.36c-12.8 12.8-12.8 34.56 0 47.36 5.12 6.4 14.08 10.24 23.04 10.24z" fill="#000000" p-id="28924"></path><path d="M920.32 241.92c17.92 0 34.56-14.08 34.56-32s-14.08-34.56-32-34.56H695.04c-17.92 0-33.28 16.64-32 34.56 0 17.92 15.36 32 32 32h225.28z" fill="#000000" p-id="28925"></path></svg> \ No newline at end of file diff --git a/src/assets/svgs/member_level.svg b/src/assets/svgs/member_level.svg deleted file mode 100644 index cbcc686..0000000 --- a/src/assets/svgs/member_level.svg +++ /dev/null @@ -1 +0,0 @@ -<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1693027700643" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8876" width="128" height="128" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M936.96 385.877333l-203.434667-204.8-18.090667-7.68L308.565333 173.397333l-18.090667 7.68L87.04 385.877333c-9.728 9.898667-9.898667 25.941333-0.170667 35.84l406.869333 421.034667c4.778667 4.949333 11.434667 7.850667 18.432 7.850667 6.997333 0 13.653333-2.901333 18.432-7.850667l406.869333-421.034667C946.858667 411.648 946.688 395.776 936.96 385.877333zM868.522667 389.632l-141.994667 0-163.84-165.034667 141.994667 0L868.522667 389.632zM319.317333 224.768l143.018667 0-163.84 165.034667L155.477333 389.802667 319.317333 224.768zM176.469333 440.832l132.608 0 18.090667-7.509333 185.173333-186.538667 185.173333 186.538667 18.090667 7.509333 131.584 0L512 787.968 176.469333 440.832z" p-id="8877" fill="#000000"></path></svg> \ No newline at end of file diff --git a/src/assets/svgs/member_point.svg b/src/assets/svgs/member_point.svg deleted file mode 100644 index b849ddb..0000000 --- a/src/assets/svgs/member_point.svg +++ /dev/null @@ -1 +0,0 @@ -<svg t="1693027780777" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10083" width="128" height="128"><path d="M509.091764 501.653351c241.775532 0 424.086741-78.085426 424.086741-181.63992 0-103.543238-182.311209-181.628664-424.086741-181.628664S84.993766 216.471217 84.993766 320.014454C84.993766 423.568948 267.316232 501.653351 509.091764 501.653351zM509.091764 184.220698c222.908836 0 378.251833 71.561849 378.251833 135.793756S732.001623 455.818443 509.091764 455.818443c-222.920092 0-378.26309-71.573105-378.26309-135.803989S286.171672 184.220698 509.091764 184.220698z" fill="#000000" p-id="10084"></path><path d="M509.083577 694.061522c241.1155 0 422.937568-77.598332 422.937568-180.482561 0-27.169803-13.127995-52.453652-36.241412-75.131141-0.148379-0.153496-0.26606-0.320295-0.418532-0.468674-0.170892-0.166799-0.285502-0.345877-0.456395-0.51063l-0.11461 0.125867c-3.717671-3.40761-8.576329-5.608741-14.017248-5.608741-11.542894 0-20.898982 9.356089-20.898982 20.898982 0 6.110161 2.721994 11.481496 6.901177 15.302521l-0.082888 0.091074c13.948687 14.024411 21.809725 31.154557 21.809725 45.300742 0 64.785515-155.813718 136.966465-379.419426 136.966465-223.595474 0-379.410216-72.180949-379.410216-136.966465 0-16.139585 4.53734-29.952172 22.323425-45.670156 0.213871-0.204661 0.429789-0.381693 0.635473-0.594541 0.137123-0.118704 0.240477-0.233314 0.378623-0.354064l-0.084934-0.080841c3.416819-3.719718 5.623068-8.588609 5.623068-14.037714 0-11.542894-9.356089-20.898982-20.898982-20.898982-5.770424 0-10.993378 2.340301-14.773472 6.119371l-0.122797-0.118704c-23.408129 22.797215-36.594453 48.27754-36.594453 75.635631C86.158289 616.462167 267.979334 694.061522 509.083577 694.061522z" fill="#000000" p-id="10085"></path><path d="M895.577119 629.529787c-0.168846-0.164752-0.282433-0.342808-0.453325-0.50756l-0.11461 0.124843c-3.717671-3.40761-8.577353-5.608741-14.018272-5.608741-11.540847 0-20.897959 9.356089-20.897959 20.898982 0 6.110161 2.720971 11.482519 6.901177 15.302521l-0.083911 0.091074c13.94971 14.024411 21.810748 31.154557 21.810748 45.300742 0 64.787562-155.813718 136.966465-379.419426 136.966465-223.595474 0-379.410216-72.179926-379.410216-136.966465 0-16.139585 4.53734-29.952172 22.321378-45.670156 0.213871-0.202615 0.429789-0.381693 0.635473-0.594541 0.137123-0.118704 0.240477-0.233314 0.378623-0.354064l-0.084934-0.080841c3.416819-3.719718 5.623068-8.588609 5.623068-14.037714 0-11.542894-9.356089-20.898982-20.897959-20.898982-5.770424 0-10.993378 2.340301-14.773472 6.119371l-0.122797-0.118704c-23.410176 22.797215-36.594453 48.278563-36.594453 75.635631 0 102.884228 181.821045 180.482561 422.926312 180.482561 241.114476 0 422.935522-77.598332 422.935522-180.482561 0-27.166733-13.125949-52.452629-36.235272-75.127048C895.851365 629.847012 895.730615 629.681236 895.577119 629.529787z" fill="#000000" p-id="10086"></path></svg> \ No newline at end of file diff --git a/src/assets/svgs/member_recharge_balance.svg b/src/assets/svgs/member_recharge_balance.svg deleted file mode 100644 index 7519bb2..0000000 --- a/src/assets/svgs/member_recharge_balance.svg +++ /dev/null @@ -1 +0,0 @@ -<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1693028440322" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="25843" width="128" height="128" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M512 750.509317c-19.080745 0-31.801242-12.720497-31.801242-31.801242L480.198758 432.496894c0-19.080745 12.720497-31.801242 31.801242-31.801242s31.801242 12.720497 31.801242 31.801242l0 286.21118C537.440994 737.78882 524.720497 750.509317 512 750.509317z" fill="#000000" p-id="25844"></path><path d="M651.925466 534.26087 365.714286 534.26087c-19.080745 0-31.801242-12.720497-31.801242-31.801242 0-19.080745 12.720497-31.801242 31.801242-31.801242l286.21118 0c19.080745 0 31.801242 12.720497 31.801242 31.801242C683.726708 521.540373 671.006211 534.26087 651.925466 534.26087z" fill="#000000" p-id="25845"></path><path d="M651.925466 648.745342 365.714286 648.745342c-19.080745 0-31.801242-12.720497-31.801242-31.801242 0-19.080745 12.720497-31.801242 31.801242-31.801242l286.21118 0c19.080745 0 31.801242 12.720497 31.801242 31.801242C683.726708 636.024845 671.006211 648.745342 651.925466 648.745342z" fill="#000000" p-id="25846"></path><path d="M512 464.298137c-6.360248 0-19.080745 0-25.440994-6.360248L352.993789 324.372671c-12.720497-12.720497-12.720497-31.801242 0-44.521739 12.720497-12.720497 31.801242-12.720497 44.521739 0l133.565217 133.565217c12.720497 12.720497 12.720497 31.801242 0 44.521739C524.720497 464.298137 518.360248 464.298137 512 464.298137z" fill="#000000" p-id="25847"></path><path d="M512 464.298137c-6.360248 0-19.080745 0-25.440994-6.360248-12.720497-12.720497-12.720497-31.801242 0-44.521739l133.565217-133.565217c12.720497-12.720497 31.801242-12.720497 44.521739 0 12.720497 12.720497 12.720497 31.801242 0 44.521739L531.080745 457.937888C524.720497 464.298137 518.360248 464.298137 512 464.298137z" fill="#000000" p-id="25848"></path><path d="M512 1017.639752c-279.850932 0-508.819876-228.968944-508.819876-508.819876s228.968944-508.819876 508.819876-508.819876 508.819876 228.968944 508.819876 508.819876c0 25.440994 0 50.881988-6.360248 82.68323 0 19.080745-19.080745 31.801242-38.161491 25.440994-19.080745 0-31.801242-19.080745-25.440994-38.161491 6.360248-25.440994 6.360248-44.521739 6.360248-69.962733 0-248.049689-197.167702-445.217391-445.217391-445.217391S66.782609 267.130435 66.782609 515.180124s197.167702 445.217391 445.217391 445.217391c25.440994 0 57.242236 0 82.68323-6.360248 19.080745-6.360248 31.801242 6.360248 38.161491 25.440994 6.360248 19.080745-6.360248 31.801242-25.440994 38.161491C575.602484 1017.639752 543.801242 1017.639752 512 1017.639752z" fill="#000000" p-id="25849"></path><path d="M989.018634 864.993789l-318.012422 0c-19.080745 0-31.801242-12.720497-31.801242-31.801242s12.720497-31.801242 31.801242-31.801242l318.012422 0c19.080745 0 31.801242 12.720497 31.801242 31.801242S1001.73913 864.993789 989.018634 864.993789z" fill="#000000" p-id="25850"></path><path d="M830.012422 1024c-19.080745 0-31.801242-12.720497-31.801242-31.801242l0-318.012422c0-19.080745 12.720497-31.801242 31.801242-31.801242s31.801242 12.720497 31.801242 31.801242l0 318.012422C861.813665 1004.919255 842.732919 1024 830.012422 1024z" fill="#000000" p-id="25851"></path></svg> \ No newline at end of file diff --git a/src/assets/svgs/money.svg b/src/assets/svgs/money.svg deleted file mode 100644 index c1580de..0000000 --- a/src/assets/svgs/money.svg +++ /dev/null @@ -1 +0,0 @@ -<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M54.122 127.892v-28.68H7.513V87.274h46.609v-12.4H7.513v-12.86h38.003L.099 0h22.6l32.556 45.07c3.617 5.144 6.44 9.611 8.487 13.385 1.788-3.05 4.89-7.779 9.301-14.186L103.93 0h24.01L82.385 62.013h38.34v12.862h-46.41v12.4h46.41v11.937h-46.41v28.68H54.123z"/></svg> \ No newline at end of file diff --git a/src/assets/svgs/shopping.svg b/src/assets/svgs/shopping.svg deleted file mode 100644 index f395bc7..0000000 --- a/src/assets/svgs/shopping.svg +++ /dev/null @@ -1 +0,0 @@ -<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M42.913 101.36c1.642 0 3.198.332 4.667.996a12.28 12.28 0 013.89 2.772c1.123 1.184 1.987 2.582 2.592 4.193.605 1.612.908 3.318.908 5.118 0 1.8-.303 3.507-.908 5.118-.605 1.611-1.469 3.01-2.593 4.194a13.3 13.3 0 01-3.889 2.843 10.582 10.582 0 01-4.667 1.066c-1.729 0-3.306-.355-4.732-1.066a13.604 13.604 0 01-3.825-2.843c-1.123-1.185-1.988-2.583-2.593-4.194a14.437 14.437 0 01-.907-5.118c0-1.8.302-3.506.907-5.118.605-1.61 1.47-3.009 2.593-4.193a12.515 12.515 0 013.825-2.772c1.426-.664 3.003-.996 4.732-.996zm53.932.285c1.643 0 3.22.331 4.733.995a11.386 11.386 0 013.889 2.772c1.08 1.185 1.945 2.583 2.593 4.194.648 1.61.972 3.317.972 5.118 0 1.8-.324 3.506-.972 5.117-.648 1.611-1.513 3.01-2.593 4.194a12.253 12.253 0 01-3.89 2.843 11 11 0 01-4.732 1.066 10.58 10.58 0 01-4.667-1.066 12.478 12.478 0 01-3.824-2.843c-1.08-1.185-1.945-2.583-2.593-4.194a13.581 13.581 0 01-.973-5.117c0-1.801.325-3.507.973-5.118.648-1.611 1.512-3.01 2.593-4.194a11.559 11.559 0 013.824-2.772 11.212 11.212 0 014.667-.995zm21.781-80.747c2.42 0 4.3.355 5.64 1.066 1.34.71 2.29 1.587 2.852 2.63a6.427 6.427 0 01.778 3.34c-.044 1.185-.195 2.204-.454 3.057-.26.853-.8 2.606-1.62 5.26a589.268 589.268 0 01-2.788 8.743 1236.373 1236.373 0 00-3.047 9.453c-.994 3.128-1.75 5.592-2.269 7.393-1.123 3.79-2.55 6.42-4.278 7.89-1.728 1.469-3.846 2.203-6.352 2.203H39.023l1.945 12.795h65.342c4.148 0 6.223 1.943 6.223 5.828 0 1.896-.41 3.53-1.232 4.905-.821 1.374-2.442 2.061-4.862 2.061H38.505c-1.729 0-3.176-.426-4.343-1.28-1.167-.852-2.14-1.966-2.917-3.34a21.277 21.277 0 01-1.88-4.478 44.128 44.128 0 01-1.102-4.55c-.087-.568-.324-1.942-.713-4.122-.39-2.18-.865-4.904-1.426-8.174l-1.88-10.947c-.692-4.027-1.383-8.079-2.075-12.154-1.642-9.572-3.5-20.234-5.574-31.986H6.87c-1.296 0-2.377-.356-3.24-1.067a9.024 9.024 0 01-2.14-2.558 10.416 10.416 0 01-1.167-3.2C.108 8.53 0 7.488 0 6.54c0-1.896.583-3.46 1.75-4.69C2.917.615 4.494 0 6.482 0h13.095c1.728 0 3.111.284 4.148.853 1.037.569 1.858 1.28 2.463 2.132a8.548 8.548 0 011.297 2.701c.26.948.475 1.754.648 2.417.173.758.346 1.825.519 3.199.173 1.374.345 2.772.518 4.193.26 1.706.519 3.507.778 5.403h88.678z"/></svg> \ No newline at end of file diff --git a/src/components/MonitorDiskPie/PieChart.vue b/src/components/MonitorDiskPie/PieChart.vue new file mode 100644 index 0000000..b7ba35e --- /dev/null +++ b/src/components/MonitorDiskPie/PieChart.vue @@ -0,0 +1,35 @@ +<template> + <svg :width="size" :height="size" viewBox="0 0 100 100"> + <!-- 背景圆 --> + <circle cx="50" cy="50" r="50" fill="#eee"/> + <!-- 使用率扇形 --> + <path :d="arcPath" fill="#1C134B"/> + </svg> +</template> + +<script setup> +import { computed } from 'vue'; + +const props = defineProps({ + used: { type: Number, required: true }, + total: { type: Number, required: true }, + size: { type: Number, default: 150 } +}); + +const percentage = computed(() => { + if (props.total === 0) return 0; + return (props.used / props.total) * 100; +}); + +const arcPath = computed(() => { + if (percentage.value >= 100) return ''; + + const angle = (percentage.value * 360) / 100; + const radians = (angle - 90) * Math.PI / 180; + const x = 50 + 50 * Math.cos(radians); + const y = 50 + 50 * Math.sin(radians); + const largeArc = angle > 180 ? 1 : 0; + + return `M 50 50 L 50 0 A 50 50 0 ${largeArc} 1 ${x} ${y} L 50 50 Z`; +}); +</script> diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index f603ca5..c47f1a7 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -447,6 +447,29 @@ } ] }, + { + path: '/matlab', + component: Layout, + name: 'matlab', + meta: { + hidden: true + }, + children: [ + { + path: 'model/form/:id?', + component: () => import('@/views/model/matlab/model/MatlabModelForm.vue'), + name: 'MatlabModelForm', + meta: { + title: 'Matlab模型表单', + noCache: true, + hidden: true, + canTo: true, + icon: '', + activeMenu: '/matlab/model' + } + } + ] + }, ] export default remainingRouter diff --git a/src/store/modules/mall/kefu.ts b/src/store/modules/mall/kefu.ts new file mode 100644 index 0000000..2aecee0 --- /dev/null +++ b/src/store/modules/mall/kefu.ts @@ -0,0 +1,81 @@ +import { store } from '@/store' +import { defineStore } from 'pinia' +import { KeFuConversationApi, KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation' +import { KeFuMessageRespVO } from '@/api/mall/promotion/kefu/message' +import { isEmpty } from '@/utils/is' + +interface MallKefuInfoVO { + conversationList: KeFuConversationRespVO[] // 会话列表 + conversationMessageList: Map<number, KeFuMessageRespVO[]> // 会话消息 +} + +export const useMallKefuStore = defineStore('mall-kefu', { + state: (): MallKefuInfoVO => ({ + conversationList: [], + conversationMessageList: new Map<number, KeFuMessageRespVO[]>() // key 会话,value 会话消息列表 + }), + getters: { + getConversationList(): KeFuConversationRespVO[] { + return this.conversationList + }, + getConversationMessageList(): (conversationId: number) => KeFuMessageRespVO[] | undefined { + return (conversationId: number) => this.conversationMessageList.get(conversationId) + } + }, + actions: { + // ======================= 会话消息相关 ======================= + /** 缓存历史消息 */ + saveMessageList(conversationId: number, messageList: KeFuMessageRespVO[]) { + this.conversationMessageList.set(conversationId, messageList) + }, + + // ======================= 会话相关 ======================= + /** 加载会话缓存列表 */ + async setConversationList() { + this.conversationList = await KeFuConversationApi.getConversationList() + this.conversationSort() + }, + /** 更新会话缓存已读 */ + async updateConversationStatus(conversationId: number) { + if (isEmpty(this.conversationList)) { + return + } + const conversation = this.conversationList.find((item) => item.id === conversationId) + conversation && (conversation.adminUnreadMessageCount = 0) + }, + /** 更新会话缓存 */ + async updateConversation(conversationId: number) { + if (isEmpty(this.conversationList)) { + return + } + + const conversation = await KeFuConversationApi.getConversation(conversationId) + this.deleteConversation(conversationId) + conversation && this.conversationList.push(conversation) + this.conversationSort() + }, + /** 删除会话缓存 */ + deleteConversation(conversationId: number) { + const index = this.conversationList.findIndex((item) => item.id === conversationId) + // 存在则删除 + if (index > -1) { + this.conversationList.splice(index, 1) + } + }, + conversationSort() { + // 按置顶属性和最后消息时间排序 + this.conversationList.sort((a, b) => { + // 按照置顶排序,置顶的会在前面 + if (a.adminPinned !== b.adminPinned) { + return a.adminPinned ? -1 : 1 + } + // 按照最后消息时间排序,最近的会在前面 + return (b.lastMessageTime as unknown as number) - (a.lastMessageTime as unknown as number) + }) + } + } +}) + +export const useMallKefuStoreWithOut = () => { + return useMallKefuStore(store) +} diff --git a/src/utils/dict.ts b/src/utils/dict.ts index ce15a93..053411e 100644 --- a/src/utils/dict.ts +++ b/src/utils/dict.ts @@ -166,6 +166,8 @@ PRED_GRANULARITY = 'pred_granularity', ITEM_RUN_STATUS = 'item_run_status', RESULT_TYPE = 'result_type', + MATLAB_PLATFORM= 'matlab_platform', + MATLAB_VERSION= 'matlab_version', // ========== DATA - 数据平台模块 ========== DATA_FIELD_TYPE = 'data_field_type', TAG_DATA_TYPE = 'tag_data_type', @@ -189,5 +191,6 @@ MODEL_RESULT_TYPE = 'model_result_type', DATA_QUALITY = 'data_quality', ARC_TYPE = 'arc_type', - ARC_CALCULATE_TYPE = 'arc_calculate_type' + ARC_CALCULATE_TYPE = 'arc_calculate_type', + SOLIDIFY_FLAG = 'ind_solidify_flag' } diff --git a/src/utils/download.ts b/src/utils/download.ts index 1d07484..943374b 100644 --- a/src/utils/download.ts +++ b/src/utils/download.ts @@ -50,7 +50,10 @@ a.download = 'image.png' a.click() } - } + }, + downloadFile: (data: Blob, fileName: string) => { + download0(data, fileName, 'application/octet-stream') + }, } export default download diff --git a/src/views/data/arc/ArcData.vue b/src/views/data/arc/ArcData.vue index 3e769da..31400a9 100644 --- a/src/views/data/arc/ArcData.vue +++ b/src/views/data/arc/ArcData.vue @@ -1,7 +1,7 @@ <template> <el-drawer v-model="drawer" - size="60%" + size="50%" title="归档数据" :direction="direction" :before-close="handleClose" @@ -21,7 +21,7 @@ format="YYYY-MM-DD HH:mm:00" value-format="YYYY-MM-DD HH:mm:00" type="datetime" - :clearable="false" + :clearable="true" placeholder="选择日期时间"/> </el-form-item> <el-form-item label="结束时间"> @@ -30,7 +30,7 @@ format="YYYY-MM-DD HH:mm:00" value-format="YYYY-MM-DD HH:mm:00" type="datetime" - :clearable="false" + :clearable="true" placeholder="选择日期时间"/> </el-form-item> <el-form-item> @@ -42,7 +42,7 @@ <ContentWrap> <el-table v-loading="loading" :data="list"> <el-table-column - prop="value" + prop="arcValue" label="数据值" header-align="center" align="center" @@ -93,7 +93,7 @@ pageSize: 10, arcId:undefined, startTime: undefined, - endTime: getYMDHM0(new Date()), + endTime: undefined }) const queryFormRef = ref() // 搜索的表单 const exportLoading = ref(false) // 导出的加载中 @@ -139,7 +139,7 @@ queryParams.pageSize = 10 queryParams.arcId = '' queryParams.startTime = '' - queryParams.endTime = getYMDHM0(new Date()) + queryParams.endTime = '' } const handleClose = (done: () => void) => { diff --git a/src/views/data/arc/ArcSettingForm.vue b/src/views/data/arc/ArcSettingForm.vue index 740e1f1..bd5293f 100644 --- a/src/views/data/arc/ArcSettingForm.vue +++ b/src/views/data/arc/ArcSettingForm.vue @@ -7,24 +7,55 @@ :rules="formRules" label-width="120px" > - <el-form-item label="名称" prop="name"> - <el-input v-model="formData.name" placeholder="请输入归档名称" /> - </el-form-item> - <el-form-item label="归档周期" prop="type"> - <el-select - v-model="formData.type" - clearable - placeholder="请选择归档周期" - > - <el-option - v-for="dict in getDictOptions(DICT_TYPE.ARC_TYPE)" - :key="dict.value" - :label="dict.label" - :value="dict.value" - /> - </el-select> - </el-form-item> - <el-form-item label="归档点位" prop="point"> + <el-row> + <el-col :span="12"> + <el-form-item label="编码" prop="code"> + <el-input v-model="formData.code" placeholder="请输入编码" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="名称" prop="name"> + <el-input v-model="formData.name" placeholder="请输入名称" /> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="归档周期" prop="type"> + <el-select + v-model="formData.type" + clearable + placeholder="请选择归档周期" + > + <el-option + v-for="dict in getDictOptions(DICT_TYPE.ARC_TYPE)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="计算方法" prop="calculate"> + <el-select + v-model="formData.calculate" + clearable + placeholder="请选择计算方法" + > + <el-option + v-for="dict in getDictOptions(DICT_TYPE.ARC_CALCULATE_TYPE)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="归档点位" prop="point"> <el-select v-model="formData.point" filterable @@ -35,35 +66,25 @@ :label="item.pointName" :value="item.pointNo"/> </el-select> - </el-form-item> - <el-form-item label="计算方法" prop="calculate"> - <el-select - v-model="formData.calculate" - clearable - placeholder="请选择计算方法" - > - <el-option - v-for="dict in getDictOptions(DICT_TYPE.ARC_CALCULATE_TYPE)" - :key="dict.value" - :label="dict.label" - :value="dict.value" - /> - </el-select> - </el-form-item> - <el-form-item label="是否启用" prop="isEnable"> - <el-select - v-model="formData.isEnable" - clearable - placeholder="请选择是否启用" - > - <el-option - v-for="dict in getIntDictOptions(DICT_TYPE.COM_IS_INT)" - :key="dict.value" - :label="dict.label" - :value="dict.value" - /> - </el-select> - </el-form-item> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="是否启用" prop="isEnable"> + <el-select + v-model="formData.isEnable" + clearable + placeholder="请选择是否启用" + > + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.COM_IS_INT)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> </el-form> <template #footer> <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> @@ -87,13 +108,16 @@ const formType = ref('') // 表单的类型:create - 新增;update - 修改 const formData = ref({ id: undefined, + code: undefined, name: undefined, type: undefined, point: undefined, calculate: undefined, + sort: 1, isEnable: 1 }) const formRules = reactive({ + code: [{ required: true, message: '编码不能为空', trigger: 'blur' }], name: [{ required: true, message: '名称不能为空', trigger: 'blur' }], type: [{ required: true, message: '归档周期不能为空', trigger: 'blur' }], point: [{ required: true, message: '归档点位不能为空', trigger: 'blur' }], @@ -162,6 +186,7 @@ type: undefined, point: undefined, calculate: undefined, + sort: 1, isEnable: 1 } formRef.value?.resetFields() diff --git a/src/views/data/arc/index.vue b/src/views/data/arc/index.vue index 922ae9e..637097c 100644 --- a/src/views/data/arc/index.vue +++ b/src/views/data/arc/index.vue @@ -1,167 +1,176 @@ <template> - <!-- 搜索 --> - <ContentWrap> - <el-form - class="-mb-15px" - :model="queryParams" - ref="queryFormRef" - :inline="true" - label-width="68px" - > - <el-form-item label="名称" prop="name"> - <el-input - v-model="queryParams.name" - placeholder="请输入名称" - clearable - @keyup.enter="handleQuery" - class="!w-240px" - /> - </el-form-item> - <el-form-item> - <el-button @click="handleQuery"> - <Icon icon="ep:search" class="mr-5px"/> - 搜索 - </el-button> - <el-button @click="resetQuery"> - <Icon icon="ep:refresh" class="mr-5px"/> - 重置 - </el-button> - <el-button - type="primary" - plain - @click="openForm('create')" - > - <Icon icon="ep:plus" class="mr-5px"/> - 新增 - </el-button> - </el-form-item> - </el-form> - </ContentWrap> - - <!-- 列表 --> - <ContentWrap> - <el-table v-loading="loading" :data="list"> - <el-table-column label="名称" align="center" prop="name"/> - <el-table-column label="归档周期" align="center" prop="type"/> - <el-table-column label="归档点位" align="center" prop="point"/> - <el-table-column label="计算方法" align="center" prop="calculate"/> - <el-table-column label="是否启用" align="center" prop="isEnable"/> - <el-table-column label="操作" align="center" min-width="110" fixed="right"> - <template #default="scope"> - <el-button - link - type="primary" - @click="openForm('update', scope.row.id)" - > - 编辑 - </el-button> - <el-button - link - type="primary" - @click="openArcData(scope.row.id)" - > - 历史值 - </el-button> - <el-button - link - type="danger" - @click="handleDelete(scope.row.id)" - > - 删除 - </el-button> - </template> - </el-table-column> - </el-table> - <!-- 分页 --> - <Pagination - :total="total" - v-model:page="queryParams.pageNo" - v-model:limit="queryParams.pageSize" - @pagination="getList" + <!-- 搜索 --> + <ContentWrap> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="名称" prop="name"> + <el-input + v-model="queryParams.name" + placeholder="请输入名称" + clearable + @keyup.enter="handleQuery" + class="!w-240px" /> - </ContentWrap> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"> + <Icon icon="ep:search" class="mr-5px"/> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon icon="ep:refresh" class="mr-5px"/> + 重置 + </el-button> + <el-button + type="primary" + plain + @click="openForm('create')" + v-hasPermi="['data:arc:create']" + > + <Icon icon="ep:plus" class="mr-5px"/> + 新增 + </el-button> + </el-form-item> + </el-form> + </ContentWrap> - <!-- 表单弹窗:添加/修改 --> - <ArcSettingForm ref="formRef" @success="getList"/> + <!-- 列表 --> + <ContentWrap> + <el-table v-loading="loading" :data="list"> + <el-table-column label="编码" align="center" prop="code"/> + <el-table-column label="名称" align="center" prop="name"/> + <el-table-column label="归档周期" align="center" prop="type"/> + <el-table-column label="归档点位" align="center" prop="point"/> + <el-table-column label="计算方法" align="center" prop="calculate"/> + <el-table-column label="是否启用" align="center" prop="isEnable"> + <template #default="scope"> + <el-tag v-if="scope.row.isEnable === 1" size="small">是</el-tag> + <el-tag v-else size="small" type="danger">否</el-tag> + </template> + </el-table-column> + <el-table-column label="操作" align="center" min-width="110" fixed="right"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openForm('update', scope.row.id)" + v-hasPermi="['data:arc:update']" + > + 编辑 + </el-button> + <el-button + link + type="primary" + @click="openArcData(scope.row.id)" + > + 历史值 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['data:arc:delete']" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </ContentWrap> - <!-- 历史值弹窗 --> - <ArcData ref="dataRef"/> + <!-- 表单弹窗:添加/修改 --> + <ArcSettingForm ref="formRef" @success="getList"/> + + <!-- 历史值弹窗 --> + <ArcData ref="dataRef"/> </template> <script lang="ts" setup> - import * as ArcSetting from '@/api/data/arc/index' - import ArcSettingForm from './ArcSettingForm.vue' - import ArcData from './ArcData.vue' +import * as ArcSetting from '@/api/data/arc/index' +import ArcSettingForm from './ArcSettingForm.vue' +import ArcData from './ArcData.vue' - defineOptions({name: 'DataArc'}) +defineOptions({name: 'DataArc'}) - const message = useMessage() // 消息弹窗 - const {t} = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 +const {t} = useI18n() // 国际化 - const loading = ref(true) // 列表的加载中 - const total = ref(0) // 列表的总页数 - const list = ref([]) // 列表的数据 - const queryParams = reactive({ - pageNo: 1, - pageSize: 10, - name: undefined, - type: undefined - }) - const queryFormRef = ref() // 搜索的表单 - const exportLoading = ref(false) // 导出的加载中 +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + name: undefined, + type: undefined +}) +const queryFormRef = ref() // 搜索的表单 +const exportLoading = ref(false) // 导出的加载中 - /** 查询列表 */ - const getList = async () => { - loading.value = true - try { - const page = await ArcSetting.getArcSettingPage(queryParams) - list.value = page.list - total.value = page.total - } finally { - loading.value = false - } - } +/** 查询列表 */ +const getList = async () => { + loading.value = true + try { + const page = await ArcSetting.getArcSettingPage(queryParams) + list.value = page.list + total.value = page.total + } finally { + loading.value = false + } +} - /** 搜索按钮操作 */ - const handleQuery = () => { - queryParams.pageNo = 1 - getList() - } +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} - /** 重置按钮操作 */ - const resetQuery = () => { - queryFormRef.value.resetFields() - handleQuery() - } +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} - /** 添加/修改操作 */ - const formRef = ref() - const openForm = (type: string, id?: number) => { - formRef.value.open(type, id) - } +/** 添加/修改操作 */ +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) +} - /** 历史操作 */ - const dataRef = ref() - const openArcData = (id?: string) => { - dataRef.value.open(id) - } +/** 历史操作 */ +const dataRef = ref() +const openArcData = (id?: string) => { + dataRef.value.open(id) +} - /** 删除按钮操作 */ - const handleDelete = async (id: number) => { - try { - // 删除的二次确认 - await message.delConfirm() - // 发起删除 - await ArcSetting.deleteArcSetting(id) - message.success(t('common.delSuccess')) - // 刷新列表 - await getList() - } catch { - } - } +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await ArcSetting.deleteArcSetting(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch { + } +} - /** 初始化 **/ - onMounted(async () => { - await getList() - }) +/** 初始化 **/ +onMounted(async () => { + await getList() +}) </script> diff --git a/src/views/data/ind/data/DataSetForm.vue b/src/views/data/ind/data/DataSetForm.vue index 132e25c..d09745b 100644 --- a/src/views/data/ind/data/DataSetForm.vue +++ b/src/views/data/ind/data/DataSetForm.vue @@ -21,8 +21,13 @@ </el-select> </el-form-item> <el-form-item label="查询语句" prop="querySql"> - <el-input v-model="formData.querySql" placeholder="请输入内容" type="textarea" maxlength="200" + <el-input v-model="formData.querySql" placeholder="请输入内容" type="textarea" maxlength="500" + :rows="6" + @input="checkSensitiveWords" show-word-limit spellcheck="false"/> + </el-form-item> + <el-form-item v-if="showError"> + <p>输入中包含以下敏感词:<span style="color: red">{{sensitiveMessage}}</span></p> </el-form-item> <el-form-item label="备注" prop="remark"> <el-input v-model="formData.remark" placeholder="请输入内容" type="textarea" maxlength="100" @@ -30,25 +35,29 @@ </el-form-item> </el-form> <template #footer> - <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> + <el-button :disabled="disableSubmit" type="primary" @click="submitForm">确 定</el-button> <el-button @click="dialogVisible = false">取 消</el-button> </template> </Dialog> </template> <script lang="ts" setup> - import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' - import * as DataSetApi from '@/api/data/ind/data/data.set' - import { CommonStatusEnum } from '@/utils/constants' - import * as DataSourceConfigApi from "@/api/infra/dataSourceConfig"; +import * as DataSetApi from '@/api/data/ind/data/data.set' +import * as DataSourceConfigApi from "@/api/infra/dataSourceConfig"; - defineOptions({ name: 'IndDataSetForm' }) +defineOptions({ name: 'IndDataSetForm' }) const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 + const showError = ref(false) + const foundSensitiveWords = ref() + const sensitiveMessage = ref('') + const sensitiveWords = [';', 'master', 'truncate', 'insert', 'delete', 'update', 'declare', 'alter', 'drop'] + const dialogVisible = ref(false) // 弹窗的是否展示 const dialogTitle = ref('') // 弹窗的标题 - const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 + const formLoading = ref(false) // 表单的加载中:修改时的数据加载 + const disableSubmit = ref(false) // 禁止提交 const formType = ref('') // 表单的类型:create - 新增;update - 修改 const formData = ref({ id: undefined, @@ -84,10 +93,26 @@ formLoading.value = false } } - - } defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + + /** + * 验证敏感词 + */ + const checkSensitiveWords = () => { + showError.value = false; + const regex = new RegExp(sensitiveWords.map(word => `${word}`).join('|'), 'gi'); + let matches = formData.value.querySql.match(regex); + if (matches) { + showError.value = true; + foundSensitiveWords.value = Array.from(new Set(matches)); + disableSubmit.value = true + sensitiveMessage.value = foundSensitiveWords.value.join('、') + } else { + foundSensitiveWords.value = undefined + disableSubmit.value = false + } + } /** 提交表单 */ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 @@ -98,6 +123,7 @@ if (!valid) return // 提交请求 formLoading.value = true + disableSubmit.value = true try { const data = formData.value as DataSetApi.DataSetVO if (formType.value === 'create') { @@ -112,6 +138,7 @@ emit('success') } finally { formLoading.value = false + disableSubmit.value = false } } diff --git a/src/views/data/ind/item/AtomIndDefineForm.vue b/src/views/data/ind/item/AtomIndDefineForm.vue index 00ab2f5..6368c02 100644 --- a/src/views/data/ind/item/AtomIndDefineForm.vue +++ b/src/views/data/ind/item/AtomIndDefineForm.vue @@ -20,19 +20,19 @@ <el-row> <el-col :span="12"> <el-form-item label="指标分类" prop="itemCategory"> - <el-select v-model="formData.itemCategory" clearable placeholder="请选择指标分类"> - <el-option - v-for="item in dataCategoryList" - :key="item.id" - :label="item.label" - :value="item.id + ''" - /> - </el-select> + <el-tree-select + v-model="formData.itemCategory" + :data="dataCategoryList" + :default-expanded-keys="[0]" + :props="defaultProps" + check-strictly + node-key="id" + /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="时间粒度" prop="timeGranularity"> - <el-select v-model="formData.timeGranularity" placeholder="请选择"> + <el-select v-model="formData.timeGranularity" clearable placeholder="请选择"> <el-option v-for="dict in getStrDictOptions(DICT_TYPE.TIME_GRANULARITY)" :key="dict.value" @@ -59,11 +59,28 @@ <el-input v-model="formData.unit"/> </el-form-item> </el-col> + <el-col :span="6"> + <el-form-item label="固化标识" prop="solidifyFlag"> + <el-select v-model="formData.solidifyFlag" + clearable + filterable + allow-create + placeholder="请选择"> + <el-option + v-for="dict in getStrDictOptions(DICT_TYPE.SOLIDIFY_FLAG)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + </el-col> </el-row> <el-row> - <el-col :span="6"> + <el-col :span="12"> <el-form-item label="数据集" prop="atomItem.dataSet"> - <el-select v-model="formData.atomItem.dataSet" clearable placeholder="请选择数据集" @change="handleDataSetChange($event)"> + <el-select v-model="formData.atomItem.dataSet" filterable + allow-create clearable placeholder="请选择数据集" @change="handleDataSetChange($event)"> <el-option v-for="item in dataSetList" :key="item.id" @@ -75,7 +92,8 @@ </el-col> <el-col :span="6"> <el-form-item label="使用字段" prop="atomItem.usingField"> - <el-select v-model="formData.atomItem.usingField" clearable placeholder="请选择字段"> + <el-select v-model="formData.atomItem.usingField" filterable + allow-create clearable placeholder="请选择字段"> <el-option v-for="item in dataSetFieldList" :key="item.id" @@ -86,7 +104,7 @@ </el-form-item> </el-col> <el-col :span="6"> - <el-form-item label="统计方式" prop="statFunc"> + <el-form-item label="统计方式" prop="atomItem.statFunc"> <el-select v-model="formData.atomItem.statFunc" clearable placeholder="请选择"> <el-option v-for="dict in getStrDictOptions(DICT_TYPE.DATA_STAT_FUNC)" @@ -118,7 +136,7 @@ import * as DataSetApi from '@/api/data/ind/data/data.set' import * as DataSetFieldApi from '@/api/data/ind/data/data.field' import * as CategoryApi from '@/api/data/ind/category/index' - + import {handleTree} from "@/utils/tree"; defineOptions({name: 'IndDataSetForm'}) @@ -141,6 +159,7 @@ timeRange: '', timeGranularity: '', remark: '', + solidifyFlag: '', atomItem:{ dataSource:'', dataSet: '', @@ -157,17 +176,22 @@ const formRules = reactive({ itemName: [{required: true, message: '指标名称不能为空', trigger: 'blur'}], itemCategory: [{required: true, message: '指标分类不能为空', trigger: 'blur'}], - /*precision: [{validator: validateAsNumber, trigger: 'blur' }], - coefficient: [{validator: validateAsNumber, trigger: 'blur' }], - statFunc: [{required: true, message: '统计方式不能为空', trigger: 'blur'}], - timeGranularity: [{required: true, message: '时间粒度不能为空', trigger: 'blur'}], - "atomItem.dataSet": [{required: true, message: '数据集不能为空', trigger: 'blur'}], - "atomItem.usingField":[{required: true, message: '使用字段不能为空', trigger: 'blur'}]*/ + "atomItem.usingField": [{required: true, message: '使用字段不能为空', trigger: 'blur'}], + // "atomItem.statFunc": [{required: true, message: '统计方式不能为空', trigger: 'blur'}], }) const formRef = ref() // 表单 Ref const dataSetList = ref([] as DataSetApi.DataSetVO[]) const dataSetFieldList = ref([] as DataSetFieldApi.DataSetFieldVO[]) - const dataCategoryList = ref([]) + + const dataCategoryList = ref<Tree[]>([]) + + const getCategoryTree = async () => { + dataCategoryList.value = [] + const res = await CategoryApi.getCategoryListAllSimple() + let category: Tree = {id: 0, label: '主类目', children: []} + category.children = handleTree(res, 'id', 'pid') + dataCategoryList.value.push(category) + } /** 打开弹窗 */ const open = async (type: string, id?: string) => { dialogVisible.value = true @@ -176,7 +200,7 @@ resetForm() // 加载数据源列表 dataSetList.value = await DataSetApi.getDataSetList() - dataCategoryList.value = await CategoryApi.getCategoryListAllSimple() + await getCategoryTree() // 修改时,设置数据 if (id) { formLoading.value = true diff --git a/src/views/data/ind/item/CalIndDefineForm.vue b/src/views/data/ind/item/CalIndDefineForm.vue index 0d5fc81..28a1235 100644 --- a/src/views/data/ind/item/CalIndDefineForm.vue +++ b/src/views/data/ind/item/CalIndDefineForm.vue @@ -20,14 +20,14 @@ <el-row> <el-col :span="12"> <el-form-item label="指标分类" prop="itemCategory"> - <el-select v-model="formData.itemCategory" clearable placeholder="请选择指标分类"> - <el-option - v-for="item in dataCategoryList" - :key="item.id" - :label="item.label" - :value="item.id + ''" - /> - </el-select> + <el-tree-select + v-model="formData.itemCategory" + :data="dataCategoryList" + :default-expanded-keys="[0]" + :props="defaultProps" + check-strictly + node-key="id" + /> </el-form-item> </el-col> <el-col :span="12"> @@ -44,19 +44,35 @@ </el-col> </el-row> <el-row> - <el-col :span="8"> + <el-col :span="6"> <el-form-item label="指标精度" prop="precision"> <el-input v-model="formData.precision"/> </el-form-item> </el-col> - <el-col :span="8"> + <el-col :span="6"> <el-form-item label="转换系数" prop="coefficient"> <el-input v-model="formData.coefficient"/> </el-form-item> </el-col> - <el-col :span="8"> + <el-col :span="6"> <el-form-item label="数量单位" prop="unit"> <el-input v-model="formData.unit"/> + </el-form-item> + </el-col> + <el-col :span="6"> + <el-form-item label="固化标识" prop="solidifyFlag"> + <el-select v-model="formData.solidifyFlag" + clearable + filterable + allow-create + placeholder="请选择"> + <el-option + v-for="dict in getStrDictOptions(DICT_TYPE.SOLIDIFY_FLAG)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> </el-form-item> </el-col> </el-row> @@ -139,6 +155,7 @@ import * as ItemApi from '@/api/data/ind/item/item' import { ElMessage } from 'element-plus' import * as CategoryApi from '@/api/data/ind/category/index' + import {handleTree} from "@/utils/tree"; defineOptions({name: 'IndDataSetForm'}) @@ -162,6 +179,7 @@ timeRange: '', timeGranularity: '', remark: '', + solidifyFlag:'', calItem: { id: '', expression: '', @@ -183,14 +201,22 @@ const operatorList = ref(['+', '-', '*', '/', '&', '|', '!', '>', '<']) const formRules = reactive({ itemName: [{required: true, message: '指标名称不能为空', trigger: 'blur'}], - itemCategory: [{required: true, message: '指标类型不能为空', trigger: 'blur'}], - precision: [{validator: validateAsNumber, trigger: 'blur' }], - coefficient: [{validator: validateAsNumber, trigger: 'blur' }], + itemCategory: [{required: true, message: '指标类型不能为空', trigger: 'blur'}] + // precision: [{validator: validateAsNumber, trigger: 'blur' }], + // coefficient: [{validator: validateAsNumber, trigger: 'blur' }], }) const formRef = ref() // 表单 Ref const dataSourceList = ref([] as DataSourceConfigApi.DataSourceConfigVO[]) const queryParams = reactive({}) - const dataCategoryList = ref([] as CategoryApi.IndItemCategoryVO[]) + + const dataCategoryList = ref<Tree[]>([]) + const getCategoryTree = async () => { + dataCategoryList.value = [] + const res = await CategoryApi.getCategoryListAllSimple() + let category: Tree = {id: 0, label: '主类目', children: []} + category.children = handleTree(res, 'id', 'pid') + dataCategoryList.value.push(category) + } /** 打开弹窗 */ const open = async (type: string, id?: number) => { dialogVisible.value = true @@ -199,7 +225,7 @@ resetForm() // 加载数据源列表 - dataCategoryList.value = await CategoryApi.getCategoryListAllSimple() + await getCategoryTree() itemList.value = await ItemApi.getItemList(queryParams) // 修改时,设置数据 if (id) { diff --git a/src/views/data/ind/item/DerIndDefineForm.vue b/src/views/data/ind/item/DerIndDefineForm.vue index 3822e86..eccf7c0 100644 --- a/src/views/data/ind/item/DerIndDefineForm.vue +++ b/src/views/data/ind/item/DerIndDefineForm.vue @@ -7,8 +7,21 @@ :rules="formRules" label-width="100px"> <el-row> <el-col :span="12"> + <el-form-item label="指标编码" prop="itemNo"> + <el-input v-model="formData.itemNo" disabled/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="指标名称" prop="itemName"> + <el-input v-model="formData.itemName"/> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> <el-form-item label="原子指标" prop="atomItem.itemId"> - <el-select v-model="formData.atomItem.itemId" clearable placeholder="请选择原子指标" + <el-select v-model="formData.atomItem.itemId" filterable + allow-create clearable placeholder="请选择原子指标" @change="handleChange($event)"> <el-option v-for="item in atomItemList" @@ -27,27 +40,15 @@ </el-row> <el-row> <el-col :span="12"> - <el-form-item label="指标编码" prop="itemNo"> - <el-input v-model="formData.itemNo" disabled/> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="指标名称" prop="itemName"> - <el-input v-model="formData.itemName"/> - </el-form-item> - </el-col> - </el-row> - <el-row> - <el-col :span="12"> <el-form-item label="指标分类" prop="itemCategory"> - <el-select v-model="formData.itemCategory" clearable placeholder="请选择指标分类"> - <el-option - v-for="item in dataCategoryList" - :key="item.id" - :label="item.label" - :value="item.id + ''" - /> - </el-select> + <el-tree-select + v-model="formData.itemCategory" + :data="dataCategoryList" + :default-expanded-keys="[0]" + :props="defaultProps" + check-strictly + node-key="id" + /> </el-form-item> </el-col> <el-col :span="12"> @@ -79,11 +80,27 @@ <el-input v-model="formData.unit"/> </el-form-item> </el-col> + <el-col :span="6"> + <el-form-item label="固化标识" prop="solidifyFlag"> + <el-select v-model="formData.solidifyFlag" + clearable + filterable + allow-create + placeholder="请选择"> + <el-option + v-for="dict in getStrDictOptions(DICT_TYPE.SOLIDIFY_FLAG)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + </el-col> </el-row> <el-row> <el-col :span="6"> <el-form-item label="时间标识" prop="timeLabel"> - <el-select v-model="formData.derItem.timeLabel" clearable placeholder="请选择时间标识"> + <el-select v-model="formData.derItem.timeLabel" allow-create filterable clearable placeholder="请选择时间标识"> <el-option v-for="item in dataSetFieldList" :key="item.id" @@ -130,7 +147,8 @@ <el-row> <el-col :span="24"> <el-form-item label="分析维度" prop="dimension"> - <el-select v-model="formData.derItem.dimension" clearable placeholder="请选择分析维度" multiple> + <el-select v-model="formData.derItem.dimension" filterable + allow-create clearable placeholder="请选择分析维度" multiple> <el-option v-for="item in dataSetFieldList" :key="item.id" @@ -164,6 +182,7 @@ import {PageParam} from "@/api/data/ind/item/item"; import * as CategoryApi from "@/api/data/ind/category"; import * as DataSetFieldApi from "@/api/data/ind/data/data.field"; + import {handleTree} from "@/utils/tree"; defineOptions({name: 'IndDataSetForm'}) @@ -185,6 +204,7 @@ businessType: '', timeRange: '', timeGranularity: '', + solidifyFlag:'', atomItem: { id: '', itemId: '', @@ -218,9 +238,16 @@ const formRef = ref() // 表单 Ref const atomItemList = ref([] as ItemApi.ItemVO[]) const showTimeChange = ref(false) - const dataCategoryList = ref([] as CategoryApi.IndItemCategoryVO[]) const dataSetFieldList = ref([] as DataSetFieldApi.DataSetFieldVO[]) + const dataCategoryList = ref<Tree[]>([]) + const getCategoryTree = async () => { + dataCategoryList.value = [] + const res = await CategoryApi.getCategoryListAllSimple() + let category: Tree = {id: 0, label: '主类目', children: []} + category.children = handleTree(res, 'id', 'pid') + dataCategoryList.value.push(category) + } /** 打开弹窗 */ const open = async (type: string, id?: string) => { dialogVisible.value = true @@ -228,7 +255,7 @@ formType.value = type resetForm() // 加载数据源列表 - dataCategoryList.value = await CategoryApi.getCategoryListAllSimple() + await getCategoryTree() const queryParams = reactive({ itemType: 'ATOM' }) diff --git a/src/views/data/ind/item/IndCurrentData.vue b/src/views/data/ind/item/IndCurrentData.vue new file mode 100644 index 0000000..624ca10 --- /dev/null +++ b/src/views/data/ind/item/IndCurrentData.vue @@ -0,0 +1,45 @@ +<template> + <el-dialog + title="指标当前值" + :close-on-click-modal="false" + width="30%" + v-model="visible" + > + <el-form + :inline="true" + :model="dataForm" + > + <el-form-item> + <el-button @click="getData()">查询</el-button> + </el-form-item> + <el-form-item> + <el-input v-model="dataForm.itemCurrentData" type="textarea" :rows="15" style="width: 550px" disabled/> + </el-form-item> + </el-form> + </el-dialog> +</template> + +<script lang="ts" setup> +import {ref} from 'vue'; +import * as ItemApi from '@/api/data/ind/item/item' + +const message = useMessage() // 消息弹窗 +const visible = ref(false); +const dataForm = ref({ + itemNo: "", + itemCurrentData: "", +}); + +/** 打开弹窗 */ +const open = async (itemNo: string) => { + visible.value = true + dataForm.value.itemNo = itemNo + dataForm.value.itemCurrentData = JSON.stringify(await ItemApi.getItemCurrentData(itemNo)); +} + +defineExpose({open}) // 提供 open 方法,用于打开弹窗 + +const getData = async() =>{ + dataForm.value.itemCurrentData = JSON.stringify(await ItemApi.getItemCurrentData(dataForm.value.itemNo)); +} +</script> diff --git a/src/views/data/ind/item/IndHistoryChart.vue b/src/views/data/ind/item/IndHistoryChart.vue new file mode 100644 index 0000000..e41ec10 --- /dev/null +++ b/src/views/data/ind/item/IndHistoryChart.vue @@ -0,0 +1,169 @@ +<template> + <el-dialog + title="历史值" + :close-on-click-modal="false" + width="50%" + v-model="visible" + > + <el-form + :inline="true" + :model="dataForm" + @keydown.enter="getDataList()" + > + <el-form-item label="开始时间"> + <el-date-picker + size="mini" + v-model="dataForm.startTime" + format="YYYY-MM-DD HH:mm:00" + value-format="YYYY-MM-DD HH:mm:00" + type="datetime" + :clearable="false" + placeholder="选择日期时间"/> + </el-form-item> + <el-form-item label="结束时间"> + <el-date-picker + size="mini" + v-model="dataForm.endTime" + format="YYYY-MM-DD HH:mm:00" + value-format="YYYY-MM-DD HH:mm:00" + type="datetime" + :clearable="false" + placeholder="选择日期时间"/> + </el-form-item> + <el-form-item> + <el-button @click="getDataList()">查询</el-button> + </el-form-item> + </el-form> + <div ref="chartDom" class="result-chart" v-loading="loading"></div> + </el-dialog> +</template> + +<script lang="ts" setup> +import {ref} from 'vue'; +import * as echarts from 'echarts'; +import * as ItemApi from '@/api/data/ind/item/item' +import {getYMDHM0} from "@/utils/dateUtil" +import download from "@/utils/download"; + +const message = useMessage() // 消息弹窗 +const visible = ref(false); +const chartDom = ref(null); +let myChart = null; +const chartParams = reactive({ + itemNo: undefined, + startTime: undefined, + endTime: undefined, +}) +const dataForm = ref({ + id: "", + itemNo: "", + itemName: "", + startTime: getYMDHM0(new Date() - 60 * 60 * 1000), + endTime: "", +}); + +/** 打开弹窗 */ +const open = async (row: object) => { + visible.value = true + dataForm.value.id = row.id; + dataForm.value.itemNo = row.itemNo; + dataForm.value.itemName = row.itemName; + getDataList(); +} + +defineExpose({open}) // 提供 open 方法,用于打开弹窗 + +const loading = ref(false) + +async function getDataList() { + visible.value = true; + loading.value = true + if (dataForm.value.id) { + try { + chartParams.itemNo = dataForm.value.itemNo; + chartParams.startTime = dataForm.value.startTime; + chartParams.endTime = dataForm.value.endTime; + const result = await ItemApi.getItemValueData(chartParams) + loading.value = false + let xData = result.map(obj => obj['dataTime']); + let yData = result.map(obj => obj['dataValue']); + let data = xData.map((x, index) => [x, yData[index]]); + let seriesData = [] + seriesData.push({ + name: dataForm.value.itemName, + type: "line", + data: data, + showSymbol: true, + smooth: false, + lineStyle: { + normal: { + color: "#5B8FF9", + width: 1, + }, + }, + }); + + myChart = echarts.init(chartDom.value); + const option = { + title: { + text: dataForm.value.itemName, + top: 0, + left: "1%", + textStyle: { + fontSize: 14, + }, + }, + tooltip: { + trigger: "axis", + axisPointer: { + type: "line", + lineStyle: { + color: "#cccccc", + width: "1", + type: "dashed", + }, + }, + }, + legend: { + show: false, + top: 10, + }, + grid: { + top: 30, + left: "3%", + right: "5%", + bottom: 10, + containLabel: true, + }, + xAxis: { + type: "category", + boundaryGap: false, + data: xData, + }, + yAxis: { + type: "value", + }, + dataZoom: [ + { + type: "inside", + }, + ], + series: seriesData, + }; + myChart.setOption(option); + } catch (error) { + console.error(error) + } + } +} + +</script> +<style> +.el-select { + width: 100%; +} + +.result-chart { + height: 500px; +} +</style> diff --git a/src/views/data/ind/item/index.vue b/src/views/data/ind/item/index.vue index b56ed54..3a3c9a9 100644 --- a/src/views/data/ind/item/index.vue +++ b/src/views/data/ind/item/index.vue @@ -1,105 +1,136 @@ <template> - <!-- 搜索工作栏 --> - <ContentWrap> - <el-form ref="queryFormRef" :inline="true" :model="queryParams" class="-mb-15px" - label-width="68px"> - <el-form-item label="指标编码" prop="itemNo"> - <el-input v-model="queryParams.itemNo" class="!w-200px" clearable placeholder="请输入指标编码" - @keyup.enter="handleQuery"/> - </el-form-item> - <el-form-item label="指标名称" prop="itemName"> - <el-input v-model="queryParams.itemName" class="!w-200px" clearable placeholder="请输入指标名称" - @keyup.enter="handleQuery"/> - </el-form-item> - <el-form-item label="指标类型" prop="itemType"> - <el-select v-model="queryParams.itemType" class="!w-200px" clearable placeholder="请选择指标类型"> - <el-option - v-for="dict in getStrDictOptions(DICT_TYPE.IND_ITEM_TYPE)" - :key="dict.value" - :label="dict.label" - :value="dict.value" - /> - </el-select> - </el-form-item> - <el-form-item> - <el-button @click="handleQuery"> - <Icon class="mr-5px" icon="ep:search"/> - 搜索 - </el-button> - <el-button @click="resetQuery"> - <Icon class="mr-5px" icon="ep:refresh"/> - 重置 - </el-button> - <el-button - v-hasPermi="['data:ind-item:create']" - plain - type="primary" - @click="openForm('create')" - > - <Icon class="mr-5px" icon="ep:plus"/> - 新增 - </el-button> - </el-form-item> - </el-form> - </ContentWrap> + <el-row :gutter="20"> + <el-col :span="3" :xs="24"> + <ContentWrap class="h-1/1"> + <el-tree + style="max-width: 600px" + :data="dataCategoryList" + :props="treeProps" + default-expand-all + highlight-current + @node-click="handleNodeClick" + /> + </ContentWrap> + </el-col> + <el-col :span="21" :xs="24"> + <!-- 搜索工作栏 --> + <ContentWrap> + <el-form ref="queryFormRef" :inline="true" :model="queryParams" class="-mb-15px" + label-width="68px"> + <el-form-item label="指标编码" prop="itemNo"> + <el-input v-model="queryParams.itemNo" class="!w-200px" clearable placeholder="请输入指标编码" + @keyup.enter="handleQuery"/> + </el-form-item> + <el-form-item label="指标名称" prop="itemName"> + <el-input v-model="queryParams.itemName" class="!w-200px" clearable placeholder="请输入指标名称" + @keyup.enter="handleQuery"/> + </el-form-item> + <el-form-item label="指标类型" prop="itemType"> + <el-select v-model="queryParams.itemType" class="!w-200px" clearable placeholder="请选择指标类型"> + <el-option + v-for="dict in getStrDictOptions(DICT_TYPE.IND_ITEM_TYPE)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"> + <Icon class="mr-5px" icon="ep:search"/> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon class="mr-5px" icon="ep:refresh"/> + 重置 + </el-button> + <el-button + v-hasPermi="['data:ind-item:create']" + plain + type="primary" + @click="openForm('create')" + > + <Icon class="mr-5px" icon="ep:plus"/> + 新增 + </el-button> + </el-form-item> + </el-form> + </ContentWrap> - <!-- 列表 --> - <ContentWrap> - <el-table v-loading="loading" :data="list"> - <el-table-column prop="itemNo" label="指标编码" header-align="center" align="center" min-width="80"/> - <el-table-column prop="itemName" label="指标名称" header-align="center" align="center" min-width="120"/> - <el-table-column prop="itemCategoryName" label="指标分类" header-align="center" align="center" min-width="100"/> - <el-table-column prop="itemType" label="指标类型" header-align="center" align="center" min-width="60"> - <template #default="scope"> - <dict-tag :type="DICT_TYPE.IND_ITEM_TYPE" :value="scope.row.itemType" /> - </template> - </el-table-column> - <el-table-column prop="coefficient" label="系数" header-align="center" align="center" min-width="60"/> - <el-table-column prop="precision" label="指标精度" header-align="center" align="center" min-width="60"/> - <el-table-column prop="timeGranularity" label="时间粒度" header-align="center" align="center" min-width="40"> - <template #default="scope"> - <dict-tag :type="DICT_TYPE.TIME_GRANULARITY" :value="scope.row.timeGranularity" /> - </template> - </el-table-column> - <el-table-column - :formatter="dateFormatter" - align="center" - label="创建时间" - prop="createTime" - width="180"/> - <el-table-column align="center" label="操作"> - <template #default="scope"> - <el-button - v-hasPermi="['data:ind-item:update']" - link - type="primary" - @click="openForm('update', scope.row)"> - 修改 - </el-button> - <el-button - v-hasPermi="['data:ind-item:delete']" - link - type="danger" - @click="handleDelete(scope.row.id)"> - 删除 - </el-button> - </template> - </el-table-column> - </el-table> - <!-- 分页 --> - <Pagination - v-model:limit="queryParams.pageSize" - v-model:page="queryParams.pageNo" - :total="total" - @pagination="getList" - /> - </ContentWrap> + <!-- 列表 --> + <ContentWrap> + <el-table v-loading="loading" :data="list"> + <el-table-column prop="itemNo" label="指标编码" header-align="center" align="center" min-width="70"/> + <el-table-column prop="itemName" label="指标名称" header-align="center" align="center" min-width="150"/> + <el-table-column prop="itemCategoryName" label="指标分类" header-align="center" align="center" min-width="80"/> + <el-table-column prop="itemType" label="指标类型" header-align="center" align="center" min-width="60"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.IND_ITEM_TYPE" :value="scope.row.itemType" /> + </template> + </el-table-column> + <el-table-column prop="coefficient" label="系数" header-align="center" align="center" min-width="50"/> + <el-table-column prop="precision" label="指标精度" header-align="center" align="center" min-width="50"/> + <el-table-column prop="timeGranularity" label="时间粒度" header-align="center" align="center" min-width="50"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.TIME_GRANULARITY" :value="scope.row.timeGranularity" /> + </template> + </el-table-column> + <el-table-column + :formatter="dateFormatter" + align="center" + label="创建时间" + prop="createTime" + width="200"/> + <el-table-column align="center" label="操作"> + <template #default="scope"> + <el-button + v-hasPermi="['data:ind-item:update']" + link + type="primary" + @click="openForm('update', scope.row)"> + 修改 + </el-button> + <el-button + link + type="primary" + @click="getCurrentData(scope.row.itemNo)"> + 当前值 + </el-button> + <el-button + link + type="primary" + @click="getHistoryData(scope.row)"> + 历史值 + </el-button> + <el-button + v-hasPermi="['data:ind-item:delete']" + link + type="danger" + @click="handleDelete(scope.row.id)"> + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + v-model:limit="queryParams.pageSize" + v-model:page="queryParams.pageNo" + :total="total" + @pagination="getList" + /> + </ContentWrap> + </el-col> + </el-row> + <!-- 表单弹窗:添加/修改 --> <AtomIndDefineForm ref="atomFormRef" @success="getList" /> <DerIndDefineForm ref="derFormRef" @success="getList" /> <CalIndDefineForm ref="calFormRef" @success="getList" /> <SelectItemType ref="itemTypeSel"/> + <IndCurrentData ref="indCurrentData"/> + <IndHistoryChart ref="indHistoryChart"/> </template> <script lang="ts" setup> @@ -113,6 +144,10 @@ import download from '@/utils/download' import * as ItemApi from '@/api/data/ind/item/item' import * as CategoryApi from "@/api/data/ind/category"; + import IndCurrentData from './IndCurrentData.vue' + import IndHistoryChart from './IndHistoryChart.vue' + import {handleTree} from "@/utils/tree"; + defineOptions({ name: 'IndItem' }) @@ -140,10 +175,17 @@ const data = await ItemApi.getItemPage(queryParams) list.value = data.list total.value = data.total - dataCategoryList.value = await CategoryApi.getCategoryListAllSimple() } finally { loading.value = false } + } + + const getCategoryTree = async () => { + dataCategoryList.value = [] + const res = await CategoryApi.getCategoryListAllSimple() + let category: Tree = {id: 0, label: '主类目', children: []} + category.children = handleTree(res, 'id', 'pid') + dataCategoryList.value.push(category) } /** 搜索按钮操作 */ @@ -182,7 +224,15 @@ }else { itemTypeSel.value.open(type) } + } + const indCurrentData = ref() + const getCurrentData = (itemNo: string) => { + indCurrentData.value.open(itemNo) + } + const indHistoryChart = ref() + const getHistoryData = (raw: object) => { + indHistoryChart.value.open(raw) } /** 删除按钮操作 */ @@ -198,8 +248,18 @@ } catch {} } + const handleNodeClick = (data: Tree) => { + if( data.id !== 0 ){ + queryParams.itemCategory = String(data.id) + }else { + queryParams.itemCategory = '' + } + getList() + } + /** 初始化 **/ onMounted(() => { getList() + getCategoryTree() }) </script> diff --git a/src/views/data/plan/data/DataSetForm.vue b/src/views/data/plan/data/DataSetForm.vue index 5c02606..7c4f382 100644 --- a/src/views/data/plan/data/DataSetForm.vue +++ b/src/views/data/plan/data/DataSetForm.vue @@ -22,7 +22,10 @@ </el-form-item> <el-form-item label="查询语句" prop="querySql"> <el-input v-model="formData.querySql" placeholder="请输入内容" type="textarea" maxlength="300" - show-word-limit :rows="6" spellcheck="false"/> + show-word-limit :rows="6" @input="checkSensitiveWords" spellcheck="false"/> + </el-form-item> + <el-form-item v-if="showError"> + <p>输入中包含以下敏感词:<span style="color: red">{{sensitiveMessage}}</span></p> </el-form-item> <el-form-item label="备注" prop="remark"> <el-input v-model="formData.remark" placeholder="请输入内容" type="textarea" maxlength="100" @@ -30,15 +33,13 @@ </el-form-item> </el-form> <template #footer> - <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> + <el-button :disabled="disableSubmit" type="primary" @click="submitForm">确 定</el-button> <el-button @click="dialogVisible = false">取 消</el-button> </template> </Dialog> </template> <script lang="ts" setup> -import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import * as DataSetApi from '@/api/data/plan/data' -import { CommonStatusEnum } from '@/utils/constants' import * as DataSourceConfigApi from "@/api/infra/dataSourceConfig"; defineOptions({ name: 'PlanDataSetForm' }) @@ -46,9 +47,15 @@ const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 +const showError = ref(false) +const foundSensitiveWords = ref() +const sensitiveMessage = ref('') +const sensitiveWords = [';', 'master', 'truncate', 'insert', 'delete', 'update', 'declare', 'alter', 'drop'] + const dialogVisible = ref(false) // 弹窗的是否展示 const dialogTitle = ref('') // 弹窗的标题 -const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formLoading = ref(false) // 表单的加载中:修改时的数据加载; +const disableSubmit = ref(false) // 禁止提交 const formType = ref('') // 表单的类型:create - 新增;update - 修改 const formData = ref({ id: undefined, @@ -84,10 +91,26 @@ formLoading.value = false } } - - } defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** + * 验证敏感词 + */ +const checkSensitiveWords = () => { + showError.value = false; + const regex = new RegExp(sensitiveWords.map(word => `${word}`).join('|'), 'gi'); + let matches = formData.value.querySql.match(regex); + if (matches) { + showError.value = true; + foundSensitiveWords.value = Array.from(new Set(matches)); + disableSubmit.value = true + sensitiveMessage.value = foundSensitiveWords.value.join('、') + } else { + foundSensitiveWords.value = undefined + disableSubmit.value = false + } +} /** 提交表单 */ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 @@ -98,6 +121,7 @@ if (!valid) return // 提交请求 formLoading.value = true + disableSubmit.value = true try { const data = formData.value as DataSetApi.DataSetVO if (formType.value === 'create') { @@ -112,6 +136,7 @@ emit('success') } finally { formLoading.value = false + disableSubmit.value = false } } diff --git a/src/views/data/point/DaPointChart.vue b/src/views/data/point/DaPointChart.vue index caaa271..54e01c5 100644 --- a/src/views/data/point/DaPointChart.vue +++ b/src/views/data/point/DaPointChart.vue @@ -1,6 +1,6 @@ <template> <el-dialog - title="采集值" + title="历史值" :close-on-click-modal="false" width="50%" v-model="visible" diff --git a/src/views/data/point/DaPointForm.vue b/src/views/data/point/DaPointForm.vue index 5a1682b..e4136b6 100644 --- a/src/views/data/point/DaPointForm.vue +++ b/src/views/data/point/DaPointForm.vue @@ -25,6 +25,7 @@ <el-select v-model="formData.pointType" clearable + :disabled = "formType !== 'create'" placeholder="请选择测点类型" > <el-option diff --git a/src/views/data/point/DaPointValue.vue b/src/views/data/point/DaPointValue.vue new file mode 100644 index 0000000..671ede7 --- /dev/null +++ b/src/views/data/point/DaPointValue.vue @@ -0,0 +1,144 @@ +<template> + <el-dialog + title="当前值" + :close-on-click-modal="false" + width="50%" + v-model="visible" + > + <el-form + :model="dataForm" + v-loading="formLoading" + label-width="120px" + > + <el-row> + <el-col :span="12"> + <el-form-item label="测点编码" prop="pointNo"> + <el-input v-model="dataForm.pointNo" readonly/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="测点名称" prop="pointName"> + <el-input v-model="dataForm.pointName" readonly/> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="数据时间" prop="dataTime"> + <el-input v-model="dataForm.dataTime" readonly/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="数据值" prop="dataValue"> + <el-input v-model="dataForm.dataValue" readonly> + <template #append>{{ dataForm.unit }}</template> + </el-input> + </el-form-item> + </el-col> + </el-row> + </el-form> + + <!-- 列表 --> + <ContentWrap v-if="dataForm.pointType === 'CALCULATE'"> + <el-table border stripe v-loading="tableLoading" :data="list"> + <el-table-column type="index" header-align="center" align="center" fixed="left" width="50"/> + <el-table-column fixed label="测点编码" header-align="center" align="left" min-width="150" prop="pointNo" /> + <el-table-column fixed label="测点名称" header-align="center" align="left" min-width="240" prop="pointName" /> + <el-table-column fixed label="当前值" header-align="center" align="left" min-width="150" prop="currentValue" /> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </ContentWrap> + + </el-dialog> +</template> +<script lang="ts" setup> + +import {reactive, ref} from "vue"; +import {getYMDHM0} from "@/utils/dateUtil"; +import * as DaPoint from "@/api/data/da/point/daPointChart"; + +const message = useMessage() // 消息弹窗 +const visible = ref(false); +const formLoading = ref(false) +const tableLoading = ref(false) +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + pointNo: undefined +}) +const queryFormRef = ref() // 搜索的表单 + +const dataForm = ref({ + id: "", + pointNo: "", + pointName: "", + pointType: "", + unit: "", + dataTime: "", + dataValue: "", +}); + +/** 打开弹窗 */ +const open = async (row: object) => { + visible.value = true + resetForm() + dataForm.value.id = row.id; + dataForm.value.pointNo = row.pointNo; + dataForm.value.pointName = row.pointName; + dataForm.value.pointType = row.pointType; + dataForm.value.unit = row.unit; + getCurrentData() + queryParams.pointNo = row.pointNo; + if (dataForm.value.pointType === "CALCULATE") { + getList() + } +} + +defineExpose({open}) // 提供 open 方法,用于打开弹窗 + +async function getCurrentData() { + visible.value = true; + formLoading.value = true + if (dataForm.value.id) { + let params0 = [dataForm.value.pointNo] + const data = await DaPoint.getPointsRealValue(params0) + formLoading.value = false + dataForm.value.dataTime = getYMDHM0(new Date()); + dataForm.value.dataValue = data[dataForm.value.pointNo] + } +} + +/** 查询列表 */ +const getList = async () => { + tableLoading.value = true + try { + const page = await DaPoint.getMathPointCurrentValue(queryParams) + list.value = page.list + total.value = page.total + } finally { + tableLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + dataForm.value = { + id: undefined, + pointNo: undefined, + pointName: undefined, + pointType: undefined, + unit: undefined, + dataTime: undefined, + dataValue: undefined, + } +} + +</script> diff --git a/src/views/data/point/index.vue b/src/views/data/point/index.vue index b11294c..1adf9d0 100644 --- a/src/views/data/point/index.vue +++ b/src/views/data/point/index.vue @@ -26,6 +26,21 @@ class="!w-200px" /> </el-form-item> + <el-form-item label="测点类型" prop="pointType"> + <el-select + v-model="queryParams.pointType" + placeholder="请选择" + clearable + class="!w-240px" + > + <el-option + v-for="dict in getStrDictOptions(DICT_TYPE.DATA_POINT_TYPE)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> <el-form-item label="测点Tag" prop="tagNo"> <el-input v-model="queryParams.tagNo" @@ -145,7 +160,7 @@ </template> </el-table-column> - <el-table-column label="操作" align="center" min-width="130" fixed="right" width="120"> + <el-table-column label="操作" align="center" min-width="130" fixed="right" width="140"> <template #default="scope"> <el-button link @@ -156,7 +171,7 @@ > 编辑 </el-button> - <el-button link size="mini" type="primary" @click="chartHandle(scope.row)">数据</el-button> + <el-button link size="mini" type="primary" @click="chartHandle(scope.row)">历史值</el-button> <el-button link size="mini" @@ -166,6 +181,7 @@ > 删除 </el-button> + <el-button link size="mini" type="primary" @click="pointValueHandle(scope.row)">当前值</el-button> </template> </el-table-column> </el-table> @@ -181,9 +197,13 @@ <!-- 表单弹窗:添加/修改 --> <DaPointForm ref="formRef" @success="getList" /> + <!-- 历史值 --> <DaPointChart ref="chartView" /> - <!-- 用户导入对话框 --> + <!-- 当前值 --> + <DaPointValue ref="pointValue" /> + + <!-- 测点导入对话框 --> <PointImportForm ref="importFormRef" @success="getList" /> </template> <script lang="ts" setup> @@ -193,7 +213,7 @@ import {DICT_TYPE, getDictOptions, getIntDictOptions, getStrDictOptions} from "@/utils/dict"; import DaPointForm from './DaPointForm.vue' import DaPointChart from './DaPointChart.vue' -import * as UserApi from "@/api/system/user"; +import DaPointValue from './DaPointValue.vue' import PointImportForm from './PointImportForm.vue' defineOptions({name: 'DataPoint'}) @@ -209,6 +229,7 @@ pageSize: 10, pointNo: undefined, pointName: undefined, + pointType: undefined, tagNo: undefined, collectQuality: undefined, }) @@ -232,12 +253,18 @@ getList() } - /** 查看数据操作 */ + /** 查看历史值操作 */ const chartView = ref() const chartHandle = (raw: object) => { chartView.value.open(raw) } +/** 查看当前值操作 */ +const pointValue = ref() +const pointValueHandle = (raw: object) => { + pointValue.value.open(raw) +} + /** 重置按钮操作 */ const resetQuery = () => { queryFormRef.value.resetFields() diff --git a/src/views/infra/apiAccessLog/index.vue b/src/views/infra/apiAccessLog/index.vue index e3a6a7c..7e3a070 100644 --- a/src/views/infra/apiAccessLog/index.vue +++ b/src/views/infra/apiAccessLog/index.vue @@ -90,31 +90,31 @@ <ContentWrap> <el-table v-loading="loading" :data="list"> <el-table-column label="日志编号" align="center" prop="id" width="100" fix="right" /> - <el-table-column label="用户编号" align="center" prop="userId" /> - <el-table-column label="用户类型" align="center" prop="userType"> + <el-table-column label="用户编号" align="center" prop="userId" width="80"/> + <el-table-column label="用户类型" align="center" prop="userType" width="100"> <template #default="scope"> <dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" /> </template> </el-table-column> - <el-table-column label="应用名" align="center" prop="applicationName" width="150" /> + <el-table-column label="应用名" align="center" prop="applicationName" width="120" /> <el-table-column label="请求方法" align="center" prop="requestMethod" width="80" /> - <el-table-column label="请求地址" align="center" prop="requestUrl" width="500" /> + <el-table-column label="请求地址" align="center" prop="requestUrl" /> <el-table-column label="请求时间" align="center" prop="beginTime" width="180"> <template #default="scope"> <span>{{ formatDate(scope.row.beginTime) }}</span> </template> </el-table-column> - <el-table-column label="执行时长" align="center" prop="duration" width="180"> + <el-table-column label="执行时长" align="center" prop="duration" width="100"> <template #default="scope"> {{ scope.row.duration }} ms </template> </el-table-column> - <el-table-column label="操作结果" align="center" prop="status"> + <el-table-column label="操作结果" align="center" prop="status" width="120"> <template #default="scope"> {{ scope.row.resultCode === 0 ? '成功' : '失败(' + scope.row.resultMsg + ')' }} </template> </el-table-column> <el-table-column label="操作模块" align="center" prop="operateModule" width="180" /> <el-table-column label="操作名" align="center" prop="operateName" width="180" /> - <el-table-column label="操作类型" align="center" prop="operateType"> + <el-table-column label="操作类型" align="center" prop="operateType" width="80"> <template #default="scope"> <dict-tag :type="DICT_TYPE.INFRA_OPERATE_TYPE" :value="scope.row.operateType" /> </template> diff --git a/src/views/infra/apiErrorLog/index.vue b/src/views/infra/apiErrorLog/index.vue index 22f3116..c337684 100644 --- a/src/views/infra/apiErrorLog/index.vue +++ b/src/views/infra/apiErrorLog/index.vue @@ -86,16 +86,16 @@ <!-- 列表 --> <ContentWrap> <el-table v-loading="loading" :data="list"> - <el-table-column label="日志编号" align="center" prop="id" /> - <el-table-column label="用户编号" align="center" prop="userId" /> - <el-table-column label="用户类型" align="center" prop="userType"> + <el-table-column label="日志编号" align="center" prop="id" width="100"/> + <el-table-column label="用户编号" align="center" prop="userId" width="80"/> + <el-table-column label="用户类型" align="center" prop="userType" width="100"> <template #default="scope"> <dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" /> </template> </el-table-column> - <el-table-column label="应用名" align="center" prop="applicationName" width="200" /> + <el-table-column label="应用名" align="center" prop="applicationName" width="120" /> <el-table-column label="请求方法" align="center" prop="requestMethod" width="80" /> - <el-table-column label="请求地址" align="center" prop="requestUrl" width="180" /> + <el-table-column label="请求地址" align="center" prop="requestUrl" /> <el-table-column label="异常发生时间" align="center" @@ -103,8 +103,8 @@ width="180" :formatter="dateFormatter" /> - <el-table-column label="异常名" align="center" prop="exceptionName" width="180" /> - <el-table-column label="处理状态" align="center" prop="processStatus"> + <el-table-column label="异常名" align="center" prop="exceptionName" /> + <el-table-column label="处理状态" align="center" prop="processStatus" width="100"> <template #default="scope"> <dict-tag :type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS" diff --git a/src/views/infra/monitor/components/MonitorDisk.vue b/src/views/infra/monitor/components/MonitorDisk.vue new file mode 100644 index 0000000..ffec629 --- /dev/null +++ b/src/views/infra/monitor/components/MonitorDisk.vue @@ -0,0 +1,487 @@ +<template> + <ContentWrap> + <!-- 搜索工作栏 --> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <!-- <el-form-item label="主机名称" prop="hostName">--> + <!-- <el-input--> + <!-- v-model="queryParams.hostName"--> + <!-- placeholder="请输入主机名称"--> + <!-- clearable--> + <!-- @keyup.enter="handleQuery"--> + <!-- class="!w-240px"--> + <!-- />--> + <!-- </el-form-item>--> + <el-form-item label="服务器IP" prop="hostIp"> + <el-input + v-model="queryParams.hostIp" + placeholder="请输入服务器IP" + clearable + @keyup.enter="handleQuery" + class="!w-120px" + /> + </el-form-item> + <!-- <el-form-item label="盘符" prop="disk">--> + <!-- <el-input--> + <!-- v-model="queryParams.disk"--> + <!-- placeholder="请输入盘符"--> + <!-- clearable--> + <!-- @keyup.enter="handleQuery"--> + <!-- class="!w-240px"--> + <!-- />--> + <!-- </el-form-item>--> + <el-form-item label="磁盘名" prop="diskName"> + <el-input + v-model="queryParams.diskName" + placeholder="请输入磁盘名" + clearable + @keyup.enter="handleQuery" + class="!w-120px" + /> + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + value-format="YYYY-MM-DD HH:mm:ss" + type="datetimerange" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + class="!w-360px" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"> + <Icon icon="ep:search" class="mr-5px"/> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon icon="ep:refresh" class="mr-5px"/> + 重置 + </el-button> + <el-button + type="primary" + plain + @click="openForm('create')" + v-hasPermi="['infra:monitor-disk:create']" + > + <Icon icon="ep:plus" class="mr-5px"/> + 新增 + </el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['infra:monitor-disk:export']" + > + <Icon icon="ep:download" class="mr-5px"/> + 导出 + </el-button> + </el-form-item> + <el-form-item style="float: right"> + <el-button + v-if="showType == 'chart'" + type="warning" + style="font-weight: bold" + plain + @click="switchShow('data')"> + <Icon icon="fa-solid:th-list" class="mr-5px"/> + 列表展示 + </el-button> + <el-button + v-if="showType == 'data'" + type="danger" + style="font-weight: bold" + plain + @click="switchShow('chart')"> + <Icon icon="fa-solid:chart-pie" class="mr-5px"/> + 图例展示 + </el-button> + </el-form-item> + </el-form> + </ContentWrap> + + <ContentWrap v-if="showType == 'chart'"> + <!-- 磁盘使用率折线图 --> + <el-skeleton :loading="echartsLoading" animated> + <Echart :height="320" :options="diskChartOptions"/> + </el-skeleton> + <!-- 磁盘使用率饼图 --> + <h3 style="margin-top: 20px; margin-bottom: 10px">主机磁盘使用率</h3> + <div v-for="host in hostList" :key="host.name" class="host"> + <div class="host-child"> + <h4>主机名:{{ host.name }} 主机IP:{{ host.ip }}</h4> + <el-skeleton :loading="echartsLoading" animated> + <div class="disks"> + <div v-for="disk in host.disks" :key="disk.name" class="disk"> + <h4 id="diskTitle">{{ disk.disk }}</h4> + <PieChart :used="disk.used" :total="disk.total" /> + <div class="disk-info"> + <div style="margin-bottom: 6px; font-size: 16px"><span style="color: #b9292b ;font-weight: bolder">{{ disk.total != 0 ? ((disk.used / disk.total) * 100).toFixed(1) : 0.0 }}% </span>已使用</div> + <div style="font-weight: bolder">{{ disk.used }}GB / {{ disk.total }}GB</div> + </div> + </div> + </div> + </el-skeleton> + </div> + </div> +<!-- <div v-for="(host, hostIndex) in hostList" :key="hostIndex">--> +<!-- <div style="margin-top: 10px">--> +<!-- <el-skeleton :loading="echartsLoading" animated>--> +<!-- {{ hostIndex }} 主机名: {{ host.name }} --> +<!-- 服务器IP:{{ host.ip }}--> +<!-- <div v-for="(disk, diskIndex) in host.disks" :key="diskIndex">--> +<!-- <h3>{{ disk.name }}</h3>--> +<!-- <div :ref="el => chartRefs[hostIndex][diskIndex] = el"--> +<!-- :style="{ width: '300px', height: '300px' }"></div>--> +<!-- </div>--> +<!-- </el-skeleton>--> +<!-- </div>--> +<!-- </div>--> + </ContentWrap> + + <!-- 列表 --> + <ContentWrap v-if="showType == 'data'"> + <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"> + <el-table-column label="主机名称" align="center" prop="hostName"/> + <el-table-column label="服务器ip" align="center" prop="hostIp"/> + <el-table-column label="盘符" align="center" prop="disk"/> + <el-table-column label="磁盘名" align="center" prop="diskName"/> + <el-table-column label="总空间" align="center" prop="spaceTotal"/> + <el-table-column label="已用空间" align="center" prop="spaceUsed"/> + <el-table-column label="可用空间" align="center" prop="spaceUsable"/> + <el-table-column label="空间使用比例" align="center" prop="spaceRatio"/> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + :formatter="dateFormatter" + width="180px" + /> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openForm('update', scope.row.id)" + v-hasPermi="['infra:monitor-disk:query']" + > + 详情 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['infra:monitor-disk:delete']" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </ContentWrap> + + <!-- 表单弹窗:添加/修改 --> + <MonitorDiskForm ref="formRef" @success="getList"/> +</template> + +<script setup lang="ts"> +import {dateFormatter} from '@/utils/formatTime' +import download from '@/utils/download' +import {MonitorDiskApi, MonitorDiskVO} from '@/api/infra/monitordisk' +import MonitorDiskForm from './MonitorDiskForm.vue' +import {EChartsOption} from "echarts"; +import * as echarts from 'echarts'; +import {formatTime} from "@/utils"; +import {formatDate} from "@vueuse/core"; +import PieChart from '@/components/MonitorDiskPie/PieChart.vue'; + +/** 磁盘监控日志 列表 */ +defineOptions({name: 'MonitorDisk'}) + +const message = useMessage() // 消息弹窗 +const {t} = useI18n() // 国际化 + +const loading = ref(true) // 列表的加载中 +const list = ref<MonitorDiskVO[]>([]) // 列表的数据 +const total = ref(0) // 列表的总页数 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + hostName: undefined, + hostIp: undefined, + disk: undefined, + diskName: undefined, + spaceTotal: undefined, + spaceUsed: undefined, + spaceUsable: undefined, + spaceRatio: undefined, + createTime: [], +}) +const queryFormRef = ref() // 搜索的表单 +const exportLoading = ref(false) // 导出的加载中 +const echartsLoading = ref(true) // 图表加载中 +const showType = ref() //展示类型(chart-图例,data-数据) + +const hostList = ref([ + { + name: 'Thinkpad-E14', + ip: '172.16.216.133', + disks: [ + {disk: '磁盘C', used: 70, total: 200}, + {disk: '磁盘D', used: 40, total: 60} + ] + }, + { + name: 'Thinkpad-E16', + ip: '172.16.216.133', + disks: [ + {disk: '磁盘C', used: 80, total: 500}, + {disk: '磁盘D', used: 20, total: 500} + ] + } +]); + +const chartRefs = ref([]); + +/** 查询列表 */ +const getList = async () => { + loading.value = true + try { + const data = await MonitorDiskApi.getMonitorDiskPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + if (showType.value == 'data') { + getList() + } else { + getMonitorDiskDataList() + usedDiskInstance() + } +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) +} + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await MonitorDiskApi.deleteMonitorDisk(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch { + } +} + +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await MonitorDiskApi.exportMonitorDisk(queryParams) + download.excel(data, '磁盘监控日志.xls') + } catch { + } finally { + exportLoading.value = false + } +} + +/** 堆叠面积图配置 */ +const diskChartOptions = reactive<EChartsOption>({ + title: { + text: '磁盘空间折线图' + }, + dataset: { + dimensions: [], + source: [] + }, + grid: { + left: 30, + right: 20, + bottom: 10, + top: 70, + containLabel: true + }, + legend: { + top: 0 + }, + series: [ + { + name: 'disk', type: 'line', + emphasis: { + focus: 'series' + }, smooth: false + } + ], + toolbox: { + feature: { + // 数据区域缩放 + dataZoom: { + yAxisIndex: false // Y轴不缩放 + }, + brush: { + type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮 + }, + saveAsImage: {show: true, name: '物理内存日志图片'} // 保存为图片 + } + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross', + label: { + backgroundColor: '#6a7985' + } + }, + padding: [5, 10] + }, + xAxis: { + type: 'category', + boundaryGap: false, + axisTick: { + show: false + } + }, + yAxis: { + name: "单位(百分比)", + nameTextStyle: { + color: "#aaa", + nameLocation: "start", + }, + }, +}) as EChartsOption + +/** 查询统计数据列表 */ +const getMonitorDiskDataList = async () => { + const list = await MonitorDiskApi.getMonitorDiskList(queryParams) + if (list != null && list != undefined && list.length > 0) { + diskChartOptions.dataset['dimensions'] = Object.keys(list[0]) + diskChartOptions.series = diskChartOptions.dataset['dimensions'].map(item => ({ + name: item.name, + type: 'line', + emphasis: { + focus: 'series' + }, + smooth: false + })); + diskChartOptions.series.splice(0, 1) + for (let item of list) { + item.createTime = formatTime(item.createTime, 'yyyy-MM-dd HH:mm:ss') + } + } + // 更新 Echarts 数据 + diskChartOptions.dataset['source'] = list + echartsLoading.value = false +} + +const usedDiskInstance = async () => { + const list = await MonitorDiskApi.getMonitorDiskInfo(queryParams) + hostList.value = list + // 仪表盘详情,用于显示数据。 +} + +/** 切换展示方式 */ +const switchShow = (type: String) => { + showType.value = type + if (showType.value == 'data') { + getList() + } else { + getMonitorDiskDataList() + usedDiskInstance() + } +} + +let intervalId; +/** 初始化 **/ +onMounted(() => { + showType.value = 'data'; + const currentDate = new Date(); + const previousDate = new Date(currentDate); + previousDate.setDate(currentDate.getDate() - 1); + queryParams.createTime[0] = formatDate(previousDate, 'YYYY-MM-DD HH:mm:ss'); + queryParams.createTime[1] = formatDate(currentDate, 'YYYY-MM-DD HH:mm:ss'); + intervalId = setInterval(() => { + if (showType.value == 'data') { + getList() + } else { + getMonitorDiskDataList() + usedDiskInstance() + } + }, 30000); +}) + +onUnmounted(() => { + clearInterval(intervalId); +}); +</script> +<style> + .host { + margin-bottom: 20px; + margin-right: 20px; + border-radius: 8px; + } + .host-child { + background: rgba(200, 200, 200, 0.3); + border-radius: 8px; + padding: 10px; + } + .disks { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); + gap: 20px; + margin-top: 20px; + } + .disk { + width: 250px; + background: rgba(100, 100, 150, 0.2); + padding: 15px; + border-radius: 16px; + text-align: center; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + } + + #diskTitle { + margin-bottom: 10px + } + + .disk-info { + margin-top: 10px; + font-size: 0.9em; + color: #666; + } +</style> diff --git a/src/views/infra/monitor/components/MonitorDiskForm.vue b/src/views/infra/monitor/components/MonitorDiskForm.vue new file mode 100644 index 0000000..aeedc9c --- /dev/null +++ b/src/views/infra/monitor/components/MonitorDiskForm.vue @@ -0,0 +1,128 @@ +<template> + <Dialog :title="dialogTitle" v-model="dialogVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="100px" + v-loading="formLoading" + > + <el-form-item label="主机名称" prop="hostName"> + <el-input v-model="formData.hostName" placeholder="请输入主机名称" /> + </el-form-item> + <el-form-item label="服务器ip" prop="hostIp"> + <el-input v-model="formData.hostIp" placeholder="请输入服务器ip" /> + </el-form-item> + <el-form-item label="盘符" prop="disk"> + <el-input v-model="formData.disk" placeholder="请输入盘符" /> + </el-form-item> + <el-form-item label="磁盘名" prop="diskName"> + <el-input v-model="formData.diskName" placeholder="请输入磁盘名" /> + </el-form-item> + <el-form-item label="总空间" prop="spaceTotal"> + <el-input v-model="formData.spaceTotal" placeholder="请输入总空间" /> + </el-form-item> + <el-form-item label="已用空间" prop="spaceUsed"> + <el-input v-model="formData.spaceUsed" placeholder="请输入已用空间" /> + </el-form-item> + <el-form-item label="可用空间" prop="spaceUsable"> + <el-input v-model="formData.spaceUsable" placeholder="请输入可用空间" /> + </el-form-item> + <el-form-item label="空间使用比例" prop="spaceRatio"> + <el-input v-model="formData.spaceRatio" placeholder="请输入空间使用比例" /> + </el-form-item> + </el-form> + <template #footer> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="dialogVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { MonitorDiskApi, MonitorDiskVO } from '@/api/infra/monitordisk' + +/** 磁盘监控日志 表单 */ +defineOptions({ name: 'MonitorDiskForm' }) + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const dialogVisible = ref(false) // 弹窗的是否展示 +const dialogTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + id: undefined, + hostName: undefined, + hostIp: undefined, + disk: undefined, + diskName: undefined, + spaceTotal: undefined, + spaceUsed: undefined, + spaceUsable: undefined, + spaceRatio: undefined, +}) +const formRules = reactive({ + hostName: [{ required: true, message: '主机名称不能为空', trigger: 'blur' }], + hostIp: [{ required: true, message: '服务器ip不能为空', trigger: 'blur' }], +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const open = async (type: string, id?: number) => { + dialogVisible.value = true + dialogTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await MonitorDiskApi.getMonitorDisk(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + await formRef.value.validate() + // 提交请求 + formLoading.value = true + try { + const data = formData.value as unknown as MonitorDiskVO + if (formType.value === 'create') { + await MonitorDiskApi.createMonitorDisk(data) + message.success(t('common.createSuccess')) + } else { + await MonitorDiskApi.updateMonitorDisk(data) + message.success(t('common.updateSuccess')) + } + dialogVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + hostName: undefined, + hostIp: undefined, + disk: undefined, + diskName: undefined, + spaceTotal: undefined, + spaceUsed: undefined, + spaceUsable: undefined, + spaceRatio: undefined, + } + formRef.value?.resetFields() +} +</script> \ No newline at end of file diff --git a/src/views/infra/monitor/components/MonitorMem.vue b/src/views/infra/monitor/components/MonitorMem.vue new file mode 100644 index 0000000..768af0e --- /dev/null +++ b/src/views/infra/monitor/components/MonitorMem.vue @@ -0,0 +1,478 @@ +<template> + <ContentWrap> + <!-- 搜索工作栏 --> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > +<!-- <el-form-item label="主机名称" prop="hostName">--> +<!-- <el-input--> +<!-- v-model="queryParams.hostName"--> +<!-- placeholder="请输入主机名称"--> +<!-- clearable--> +<!-- @keyup.enter="handleQuery"--> +<!-- class="!w-120px"--> +<!-- />--> +<!-- </el-form-item>--> + <el-form-item label="服务器IP" prop="hostIp"> + <el-input + v-model="queryParams.hostIp" + placeholder="请输入服务器IP" + clearable + @keyup.enter="handleQuery" + class="!w-120px" + /> + </el-form-item> + <el-form-item label="服务名" prop="serverName"> + <el-input + v-model="queryParams.serverName" + placeholder="请输入服务名" + clearable + @keyup.enter="handleQuery" + class="!w-120px" + /> + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + value-format="YYYY-MM-DD HH:mm:ss" + type="datetimerange" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + class="!w-360px" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"> + <Icon icon="ep:search" class="mr-5px"/> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon icon="ep:refresh" class="mr-5px"/> + 重置 + </el-button> + <el-button + type="primary" + plain + @click="openForm('create')" + v-hasPermi="['infra:monitor-mem:create']" + > + <Icon icon="ep:plus" class="mr-5px"/> + 新增 + </el-button> + <el-button + type="success" + plain + @click="handleExport" + :loading="exportLoading" + v-hasPermi="['infra:monitor-mem:export']" + > + <Icon icon="ep:download" class="mr-5px"/> + 导出 + </el-button> + </el-form-item> + <el-form-item style="float: right"> + <el-button + v-if="showType == 'chart'" + type="warning" + style="font-weight: bold" + plain + @click="switchShow('data')"> + <Icon icon="fa-solid:th-list" class="mr-5px"/> + 列表展示 + </el-button> + <el-button + v-if="showType == 'data'" + type="danger" + style="font-weight: bold" + plain + @click="switchShow('chart')"> + <Icon icon="fa-solid:chart-pie" class="mr-5px"/> + 图例展示 + </el-button> + </el-form-item> + </el-form> + </ContentWrap> + + <ContentWrap v-if="showType == 'chart'"> + <!-- 物理内存折线图 --> + <el-skeleton :loading="echartsLoading" animated> + <Echart :height="320" :options="physicalChartOptions"/> + </el-skeleton> + <!-- JVM内存折线图 --> + <el-skeleton :loading="echartsLoading" animated> + <Echart style="margin-top: 20px" :height="320" :options="JVMChartOptions"/> + </el-skeleton> + </ContentWrap> + + <!-- 列表 --> + <ContentWrap v-if="showType == 'data'"> + <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"> + <el-table-column label="主机名称" align="center" prop="hostName"/> + <el-table-column label="服务器ip" align="center" prop="hostIp"/> + <el-table-column label="服务名" align="center" prop="serverName" width="120"/> + <el-table-column label="总内存" align="center" prop="physicalTotal"/> + <el-table-column label="已用内存" align="center" prop="physicalUsed"/> + <el-table-column label="空闲内存" align="center" prop="physicalFree"/> + <el-table-column label="内存使用率" align="center" prop="physicalUsage" width="100"/> + <el-table-column label="JVM占用内存" align="center" prop="runtimeTotal"/> + <el-table-column label="JVM最大内存" align="center" prop="runtimeMax"/> + <el-table-column label="JVM可用内存" align="center" prop="runtimeUsed"/> + <el-table-column label="JVM空闲内存" align="center" prop="runtimeFree"/> + <el-table-column label="JVM内存使用率" align="center" prop="runtimeUsage"/> + <el-table-column + label="创建时间" + align="center" + prop="createTime" + :formatter="dateFormatter" + width="180px" + /> + <el-table-column label="操作" align="center"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openForm('update', scope.row.id)" + v-hasPermi="['infra:monitor-mem:query']" + > + 详情 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['infra:monitor-mem:delete']" + > + 删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </ContentWrap> + + <!-- 表单弹窗:添加/修改 --> + <MonitorMemForm ref="formRef" @success="getList"/> +</template> + +<script setup lang="ts"> +import {dateFormatter} from '@/utils/formatTime' +import download from '@/utils/download' +import {MonitorMemApi, MonitorMemVO} from '@/api/infra/monitormem' +import MonitorMemForm from './MonitorMemForm.vue' +import {EChartsOption} from "echarts"; +import {formatTime} from "@/utils"; +import {formatDate} from "@vueuse/core"; + +/** 内存监控日志 列表 */ +defineOptions({name: 'MonitorMem'}) + +const message = useMessage() // 消息弹窗 +const {t} = useI18n() // 国际化 + +const loading = ref(true) // 列表的加载中 +const list = ref<MonitorMemVO[]>([]) // 列表的数据 +const total = ref(0) // 列表的总页数 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + hostName: undefined, + hostIp: undefined, + serverName: undefined, + physicalTotal: undefined, + physicalUsed: undefined, + physicalFree: undefined, + physicalUsage: undefined, + runtimeTotal: undefined, + runtimeMax: undefined, + runtimeUsed: undefined, + runtimeFree: undefined, + runtimeUsage: undefined, + createTime: [], +}) +const queryFormRef = ref() // 搜索的表单 +const exportLoading = ref(false) // 导出的加载中 +const echartsLoading = ref(true) // 图表加载中 +const showType = ref() //展示类型(chart-图例,data-数据) + +/** 查询列表 */ +const getList = async () => { + loading.value = true + try { + const data = await MonitorMemApi.getMonitorMemPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + if(showType.value == 'data') { + getList() + } else { + getMonitorMemDataList() + } +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +/** 添加/修改操作 */ +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) +} + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await MonitorMemApi.deleteMonitorMem(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch { + } +} + +/** 堆叠面积图配置 */ +const physicalChartOptions = reactive<EChartsOption>({ + title: { + text: '物理内存折线图' + }, + dataset: { + dimensions: ['createTime', 'physicalTotal', 'physicalUsed', 'physicalFree'], + source: [] + }, + grid: { + left: 20, + right: 20, + bottom: 10, + top: 70, + containLabel: true + }, + legend: { + top: 0 + }, + series: [ + { + name: '总物理内存', type: 'line', + emphasis: { + focus: 'series' + }, smooth: false + }, + { + name: '已用物理内存', type: 'line', areaStyle: {}, + emphasis: { + focus: 'series' + }, smooth: false + }, + { + name: '剩余物理内存', type: 'line', areaStyle: {}, + emphasis: { + focus: 'series' + }, smooth: false + } + ], + toolbox: { + feature: { + // 数据区域缩放 + dataZoom: { + yAxisIndex: false // Y轴不缩放 + }, + brush: { + type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮 + }, + saveAsImage: {show: true, name: '物理内存日志图片'} // 保存为图片 + } + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross', + label: { + backgroundColor: '#6a7985' + } + }, + padding: [5, 10] + }, + xAxis: { + type: 'category', + boundaryGap: false, + axisTick: { + show: false + } + }, + yAxis: { + name: "单位(MB)", + nameTextStyle: { + color: "#aaa", + nameLocation: "start", + }, + }, +}) as EChartsOption + +/** 堆叠面积图配置 */ +const JVMChartOptions = reactive<EChartsOption>({ + title: { + text: 'JVM内存折线图' + }, + dataset: { + dimensions: ['createTime', 'runtimeMax', 'runtimeTotal', 'runtimeUsed', 'runtimeFree'], + source: [] + }, + grid: { + left: 20, + right: 20, + bottom: 0, + top: 70, + containLabel: true + }, + legend: { + top: 0 + }, + series: [ + { + name: 'JVM最大内存', type: 'line', + emphasis: { + focus: 'series' + }, smooth: false + }, + { + name: 'JVM占用内存', type: 'line', + emphasis: { + focus: 'series' + }, smooth: false + }, + { + name: 'JVM可用内存', type: 'line', areaStyle: {}, + emphasis: { + focus: 'series' + }, smooth: false + }, + { + name: 'JVM空闲内存', type: 'line', areaStyle: {}, + emphasis: { + focus: 'series' + }, smooth: false + } + ], + toolbox: { + feature: { + // 数据区域缩放 + dataZoom: { + yAxisIndex: false // Y轴不缩放 + }, + brush: { + type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮 + }, + saveAsImage: {show: true, name: 'JVM内存日志图片'} // 保存为图片 + } + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross', + label: { + backgroundColor: '#6a7985' + } + }, + padding: [5, 10] + }, + xAxis: { + type: 'category', + boundaryGap: false, + axisTick: { + show: false + } + }, + yAxis: { + name: "单位(MB)", + nameTextStyle: { + color: "#aaa", + nameLocation: "start", + }, + }, +}) as EChartsOption + +/** 查询统计数据列表 */ +const getMonitorMemDataList = async () => { + const list = await MonitorMemApi.getMonitorMemList(queryParams) + for (let item of list) { + item.createTime = formatTime(item.createTime, 'yyyy-MM-dd HH:mm:ss') + } + // 更新 Echarts 数据 + if (physicalChartOptions.dataset && physicalChartOptions.dataset['source']) { + physicalChartOptions.dataset['source'] = list + } + if (JVMChartOptions.dataset && JVMChartOptions.dataset['source']) { + JVMChartOptions.dataset['source'] = list + } + echartsLoading.value = false +} + +/** 切换展示方式 */ +const switchShow = (type: String) => { + showType.value = type + if(showType.value == 'data') { + getList() + } else { + getMonitorMemDataList() + } +} + +/** 导出按钮操作 */ +const handleExport = async () => { + try { + // 导出的二次确认 + await message.exportConfirm() + // 发起导出 + exportLoading.value = true + const data = await MonitorMemApi.exportMonitorMem(queryParams) + download.excel(data, '内存监控日志.xls') + } catch { + } finally { + exportLoading.value = false + } +} + +let intervalId; +/** 初始化 **/ +onMounted(() => { + showType.value = 'data'; + const currentDate = new Date(); + const previousDate = new Date(currentDate); + previousDate.setDate(currentDate.getDate() - 1); + queryParams.createTime[0] = formatDate(previousDate, 'YYYY-MM-DD HH:mm:ss'); + queryParams.createTime[1] = formatDate(currentDate, 'YYYY-MM-DD HH:mm:ss'); + intervalId = setInterval(() => { + if(showType.value == 'data') { + getList() + } else { + getMonitorMemDataList() + } + }, 60000); +}) + +onUnmounted(() => { + clearInterval(intervalId); +}); +</script> diff --git a/src/views/infra/monitor/components/MonitorMemForm.vue b/src/views/infra/monitor/components/MonitorMemForm.vue new file mode 100644 index 0000000..92d889b --- /dev/null +++ b/src/views/infra/monitor/components/MonitorMemForm.vue @@ -0,0 +1,148 @@ +<template> + <Dialog :title="dialogTitle" v-model="dialogVisible"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="100px" + v-loading="formLoading" + > + <el-form-item label="主机名称" prop="hostName"> + <el-input v-model="formData.hostName" placeholder="请输入主机名称" /> + </el-form-item> + <el-form-item label="服务器ip" prop="hostIp"> + <el-input v-model="formData.hostIp" placeholder="请输入服务器ip" /> + </el-form-item> + <el-form-item label="服务名" prop="serverName"> + <el-input v-model="formData.serverName" placeholder="请输入服务名" /> + </el-form-item> + <el-form-item label="总内存" prop="physicalTotal"> + <el-input v-model="formData.physicalTotal" placeholder="请输入总物理内存" /> + </el-form-item> + <el-form-item label="已用内存" prop="physicalUsed"> + <el-input v-model="formData.physicalUsed" placeholder="请输入已用物理内存" /> + </el-form-item> + <el-form-item label="空闲内存" prop="physicalFree"> + <el-input v-model="formData.physicalFree" placeholder="请输入空闲内存" /> + </el-form-item> + <el-form-item label="内存使用率" prop="physicalUsage"> + <el-input v-model="formData.physicalUsage" placeholder="请输入物理内存使用率" /> + </el-form-item> + <el-form-item label="jvm运行总内存" prop="runtimeTotal"> + <el-input v-model="formData.runtimeTotal" placeholder="请输入jvm运行总内存" /> + </el-form-item> + <el-form-item label="jvm最大内存" prop="runtimeMax"> + <el-input v-model="formData.runtimeMax" placeholder="请输入jvm最大内存" /> + </el-form-item> + <el-form-item label="jvm已用内存" prop="runtimeUsed"> + <el-input v-model="formData.runtimeUsed" placeholder="请输入jvm已用内存" /> + </el-form-item> + <el-form-item label="jvm空闲内存" prop="runtimeFree"> + <el-input v-model="formData.runtimeFree" placeholder="请输入jvm空闲内存" /> + </el-form-item> + <el-form-item label="jvm内存使用率" prop="runtimeUsage"> + <el-input v-model="formData.runtimeUsage" placeholder="请输入jvm内存使用率" /> + </el-form-item> + </el-form> + <template #footer> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="dialogVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script setup lang="ts"> +import { MonitorMemApi, MonitorMemVO } from '@/api/infra/monitormem' + +/** 内存监控日志 表单 */ +defineOptions({ name: 'MonitorMemForm' }) + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 + +const dialogVisible = ref(false) // 弹窗的是否展示 +const dialogTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + id: undefined, + hostName: undefined, + hostIp: undefined, + serverName: undefined, + physicalTotal: undefined, + physicalUsed: undefined, + physicalFree: undefined, + physicalUsage: undefined, + runtimeTotal: undefined, + runtimeMax: undefined, + runtimeUsed: undefined, + runtimeFree: undefined, + runtimeUsage: undefined, +}) +const formRules = reactive({ + hostName: [{ required: true, message: '主机名称不能为空', trigger: 'blur' }], + hostIp: [{ required: true, message: '服务器ip不能为空', trigger: 'blur' }], +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const open = async (type: string, id?: number) => { + dialogVisible.value = true + dialogTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await MonitorMemApi.getMonitorMem(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + await formRef.value.validate() + // 提交请求 + formLoading.value = true + try { + const data = formData.value as unknown as MonitorMemVO + if (formType.value === 'create') { + await MonitorMemApi.createMonitorMem(data) + message.success(t('common.createSuccess')) + } else { + await MonitorMemApi.updateMonitorMem(data) + message.success(t('common.updateSuccess')) + } + dialogVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + hostName: undefined, + hostIp: undefined, + serverName: undefined, + physicalTotal: undefined, + physicalUsed: undefined, + physicalFree: undefined, + physicalUsage: undefined, + runtimeTotal: undefined, + runtimeMax: undefined, + runtimeUsed: undefined, + runtimeFree: undefined, + runtimeUsage: undefined, + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/infra/monitor/components/index.ts b/src/views/infra/monitor/components/index.ts new file mode 100644 index 0000000..f329dca --- /dev/null +++ b/src/views/infra/monitor/components/index.ts @@ -0,0 +1,3 @@ +import MonitorMem from './MonitorMem.vue' +import MonitorDisk from './MonitorDisk.vue' +export { MonitorMem, MonitorDisk } diff --git a/src/views/infra/monitor/index.vue b/src/views/infra/monitor/index.vue new file mode 100644 index 0000000..dc29b06 --- /dev/null +++ b/src/views/infra/monitor/index.vue @@ -0,0 +1,20 @@ +<template> + <ContentWrap> + <el-tabs v-model="activeName"> + <el-tab-pane label="内存监控日志" name="monitorMem"> + <monitor-mem ref="memInfoRef" /> + </el-tab-pane> + <el-tab-pane label="硬盘监控日志" name="colum"> + <monitor-disk ref="diskInfoRef" /> + </el-tab-pane> + </el-tabs> + </ContentWrap> +</template> +<script lang="ts" setup> +import { MonitorMem, MonitorDisk } from './components' + +defineOptions({ name: 'InfraMonitor' }) + +const activeName = ref('monitorMem') // Tag 激活的窗口 + +</script> diff --git a/src/views/infra/storage/index_rec.vue b/src/views/infra/storage/index_rec.vue deleted file mode 100644 index f6dfc23..0000000 --- a/src/views/infra/storage/index_rec.vue +++ /dev/null @@ -1,161 +0,0 @@ -<template> - <el-scrollbar height="calc(100vh - 88px - 40px - 50px)"> - <el-row> - <!-- 磁盘使用量统计 --> - <el-col :span="12" class="mt-3"> - <el-card class="ml-3" :gutter="12" shadow="hover"> -<!-- <div ref="chartRef" style="width: 100%; height: 90%"></div>--> - <Echart :options="usedDiskEchartChika" :height="420" /> -<!-- <Echart :options="usedDiskEchartChika" :height="420" />--> - </el-card> - </el-col> - </el-row> - </el-scrollbar> -</template> -<script lang="ts" setup> -import { ref, onMounted } from "vue"; -import * as StorageApi from '@/api/infra/storage' -import { StorageMonitorInfoVO } from '@/api/infra/storage/types' -const disks = ref<StorageMonitorInfoVO>() -const disk = ref<StorageMonitorInfoVO>() - -// 基本信息 -const readDiskInfo = async () => { - const data = await StorageApi.getDiskInfo() - disks.value = data - disk.value = data[0] -} - -// 内存使用情况 -const usedDiskEchartChika = reactive<any>({ - title: { - // 仪表盘标题。 - text: '磁盘使用情况', - left: 'center', - show: true, // 是否显示标题,默认 true。 - offsetCenter: [0, '20%'], //相对于仪表盘中心的偏移位置,数组第一项是水平方向的偏移,第二项是垂直方向的偏移。可以是绝对的数值,也可以是相对于仪表盘半径的百分比。 - color: 'yellow', // 文字的颜色,默认 #333。 - fontSize: 20 // 文字的字体大小,默认 15。 - }, - toolbox: { - show: false, - feature: { - restore: { show: true }, - saveAsImage: { show: true } - } - }, - series: [ - { - name: '峰值', - type: 'gauge', - min: 0, - max: 500, - splitNumber: 10, - //这是指针的颜色 - color: '#F5C74E', - radius: '85%', - center: ['50%', '50%'], - startAngle: 225, - endAngle: -45, - axisLine: { - // 坐标轴线 - lineStyle: { - // 属性lineStyle控制线条样式 - color: [ - [0.2, '#7FFF00'], - [0.8, '#00FFFF'], - [1, '#FF0000'] - ], - //width: 6 外框的大小(环的宽度) - width: 10 - } - }, - axisTick: { - // 坐标轴小标记 - //里面的线长是5(短线) - length: 5, // 属性length控制线长 - lineStyle: { - // 属性lineStyle控制线条样式 - color: '#76D9D7' - } - }, - splitLine: { - // 分隔线 - length: 20, // 属性length控制线长 - lineStyle: { - // 属性lineStyle(详见lineStyle)控制线条样式 - color: '#76D9D7' - } - }, - axisLabel: { - color: '#76D9D7', - distance: 15, - fontSize: 15 - }, - pointer: { - // 指针的大小 - width: 7, - show: true - }, - detail: { - textStyle: { - fontWeight: 'normal', - // 里面文字下的数值大小(50) - fontSize: 15, - color: '#FFFFFF' - }, - valueAnimation: true - }, - progress: { - show: true - } - } - ] -}) - - -/** 加载数据 */ -const getSummary = () => { - // 初始化命令图表 - usedDiskInstance() -} - -const usedDiskInstance = async () => { - try { - const data = await StorageApi.getDiskInfo() - disks.value = data - disk.value = data[0] - // data.forEach((disk) => { - console.log(disk.value) - console.log(disk.value.name) - console.log(disk.value!.restPPT) - // 仪表盘详情,用于显示数据。 - usedDiskEchartChika.series[0].detail = { - show: true, // 是否显示详情,默认 true。 - offsetCenter: [0, '50%'], // 相对于仪表盘中心的偏移位置,数组第一项是水平方向的偏移,第二项是垂直方向的偏移。可以是绝对的数值,也可以是相对于仪表盘半径的百分比。 - color: 'auto', // 文字的颜色,默认 auto。 - fontSize: 30, // 文字的字体大小,默认 15。 - formatter: disk.value!.restPPT // 格式化函数或者字符串 - } - console.log(disk.value.restPPT) - usedDiskEchartChika.series[0].data[0] = { - value: disk.value!.restPPT, - name: '磁盘消耗' - } - console.log(disk.value) - usedDiskEchartChika.tooltip = { - formatter: '{b} <br/>{a} : ' + disk.value!.restPPT - } - // }) - } catch {} -} - -/** 初始化 **/ -onMounted(() => { - readDiskInfo() - // 读取 redis 信息 - // readDiskInfo() - // // 加载数据 - getSummary() -}) -</script> diff --git a/src/views/model/matlab/model/MatlabModelForm.vue b/src/views/model/matlab/model/MatlabModelForm.vue new file mode 100644 index 0000000..8cd37be --- /dev/null +++ b/src/views/model/matlab/model/MatlabModelForm.vue @@ -0,0 +1,394 @@ +<template> + <div class="p-16px" style="background-color: #ffffff"> + <el-header> + {{title}} + </el-header> + <el-main> + <el-form + ref="formRef" + v-loading="formLoading" + :model="formData" + :rules="formRules" + label-width="120px" + > + <el-divider content-position="left">模型信息</el-divider> + <el-row :gutter="8"> + <el-col :span="12"> + <el-form-item label="模型类型" prop="modelType"> + <el-radio-group v-model="formData.modelType"> + <el-radio-button :disabled="actionType == 'edit'" + v-for="dict in getDictOptions(DICT_TYPE.MODEL_TYPE)" + :key="dict.label" + :label="dict.value" + > + {{ dict.label }} + </el-radio-button> + </el-radio-group> + </el-form-item> + </el-col> + </el-row> + <el-row :gutter="8"> + <el-col :span="12"> + <el-form-item label="模型名称" prop="modelName"> + <el-input v-model="formData.modelName" placeholder=""/> + </el-form-item> + </el-col> + </el-row> + <el-row :gutter="8"> + <el-col :span="12"> + <el-form-item label="模型文件" prop="modelFileName"> + <el-input disabled v-model="formData.modelFileName" placeholder=""/> + </el-form-item> + </el-col> + <el-col :span="4"> + <el-upload + ref="uploadRef" + v-model:file-list="fileList" + :show-file-list="false" + :action="importUrl" + :auto-upload="true" + :disabled="uploadLoading" + v-loading="uploadLoading" + :before-upload="beforeUpload" + :headers="uploadHeaders" + :on-error="submitFormError" + :on-success="submitFormSuccess" + accept=".jar" + > + <el-tooltip content="上传算法封装.jar文件" placement="top" effect="light"> + <el-button type="primary"> + <Icon icon="ep:upload"/> + 模型上传 + </el-button> + </el-tooltip> + </el-upload> + </el-col> + </el-row> + <el-row :gutter="8"> + <el-col :span="6"> + <el-form-item label="MATLAB平台" prop="matlabPlatform"> + <el-select v-model="formData.matlabPlatform"> + <el-option + v-for="item in getDictOptions(DICT_TYPE.MATLAB_PLATFORM)" + :key="item.value" + :label="item.label" + :value="item.value" + /> + </el-select> + </el-form-item> + </el-col> + <el-col :span="6"> + <el-form-item label="MATLAB版本" prop="matlabVersion"> + <el-select v-model="formData.matlabVersion"> + <el-option + v-for="item in getDictOptions(DICT_TYPE.MATLAB_VERSION)" + :key="item.value" + :label="item.label" + :value="item.value" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row :gutter="20"> + <el-col :span="12"> + <el-form-item label="备注" prop="remark"> + <el-input v-model="formData.remark" placeholder="" type="textarea"/> + </el-form-item> + </el-col> + </el-row> + <el-divider content-position="left">模型方法</el-divider> +<!-- <el-row :gutter="20">--> +<!-- <el-col :span="4">--> +<!-- <el-button type="primary" size="small" @click="addRow()" >新增</el-button>--> +<!-- </el-col>--> +<!-- </el-row>--> + <el-table :data="formData.modelMethods" border + @expand-change="methodExpandChange" :expand-row-keys="methodExpandedRowKeys" :row-key="row => row.id"> + <el-table-column + prop="" + label="全类名" + align="center" + width="400"> + <template #default="scope"> + <el-input disabled size="small" v-model="scope.row.className" placeholder=""/> + </template> + </el-table-column> + <el-table-column + prop="" + label="方法名" + align="center" + width="250"> + <template #default="scope"> + <el-input disabled size="small" v-model="scope.row.methodName" placeholder=""/> + </template> + </el-table-column> + <el-table-column + prop="" + label="数据长度" + align="center"> + <template #default="scope"> + <el-input-number disabled size="small" step-strictly v-model="scope.row.dataLength" :min="1" + :max="50" value-on-clear="min"/> + </template> + </el-table-column> + <el-table-column + prop="" + label="输出长度" + align="center"> + <template #default="scope"> + <el-input-number disabled size="small" step-strictly v-model="scope.row.outLength" :min="1" + :max="50" value-on-clear="min"/> + </template> + </el-table-column> + <el-table-column label="方法参数" type="expand" width="100px"> + <template #default="props"> + <div class="m-16px"> + <el-button type="primary" size="small" @click="addSetting(props.row.methodSettings)">新增参数</el-button> + <el-table :data="props.row.methodSettings" border size="small"> + <el-table-column align="center" label="参数名称" prop="name"/> + <el-table-column align="center" label="key" prop="settingKey"/> + <el-table-column align="center" label="value" prop="settingValue"/> + <el-table-column align="center" label="参数类型" prop="valueType"/> + <el-table-column label="操作" fixed="right" header-align="center" align="center" width="100"> + <template #default="scope"> + <el-button + @click="updateSetting(scope.row)" + key="danger" + type="primary" + link + >修改 + </el-button> + <el-button + @click="deleteSetting(props.row.methodSettings,scope.$index)" + key="danger" + type="danger" + link + >删除 + </el-button> + </template> + </el-table-column> + </el-table> + </div> + </template> + </el-table-column> + <el-table-column label="操作" fixed="right" header-align="center" align="center" width="100"> + <template #default="scope"> + <el-button + @click="deleteRow(scope.$index)" + key="danger" + type="danger" + link + >删除 + </el-button> + </template> + </el-table-column> + </el-table> + </el-form> + </el-main> + <el-footer> + <div class="flex flex-row justify-end items-center"> + <el-button type="primary" @click="submitForm">确 定</el-button> + </div> + </el-footer> + </div> + <SettingForm ref="settingFormRef"/> +</template> +<script lang="ts" setup> + import {DICT_TYPE,getDictOptions} from '@/utils/dict'; + import * as MatlabApi from '@/api/model/matlab/mlModel' + import {FormRules} from 'element-plus' + import {getAccessToken, getTenantId} from "@/utils/auth"; + import SettingForm from './MatlabModelSettingForm.vue' + import {generateUUID} from "@/utils"; + + const {t} = useI18n() // 国际化 + const message = useMessage() // 消息弹窗 + const title = ref('') // 弹窗的标题 + const actionType = ref('') // 操作类型 + const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 + const formType = ref('') // 表单的类型:create - 新增;update - 修改 + const route = useRoute() // 路由 + const router = useRouter(); + + const staticDir = ref(import.meta.env.VITE_STATIC_DIR) + +/** settingForm弹窗 */ + const settingFormRef = ref() + // 添加setting + const addSetting = (methodSettings) => { + settingFormRef.value.open(undefined,methodSettings) + } + + // 修改setting + const updateSetting = (info) => { + settingFormRef.value.open(info) + } + // 删除setting + const deleteSetting = (methodSettings,index) => { + methodSettings.splice(index, 1); + } + + const methodExpandedRowKeys = ref([]) + const methodExpandChange = async (row: any, expandedRows: any[]) => { + methodExpandedRowKeys.value = expandedRows.map(e => e.id) + } + + const formData = ref({ + id: route.params.id, + modelName: undefined, + modelFileName: undefined, + modelFilePath: undefined, + modelType: 'predict', + matlabPlatform: undefined, + matlabVersion: undefined, + remark: undefined, + modelMethods: [], + }) + + const formRules = reactive<FormRules>({ + modelFileName: [ + {required: true, message: '模型名称不能为空,请上传模型jar文件', trigger: 'blur'} + ], + modelName: [ + {required: true, message: '模型中文名称不能为空', trigger: 'blur'} + ], + modelType: [ + {required: true, message: '模型类型不能为空', trigger: 'blur'} + ], + matlabPlatform: [ + {required: true, message: 'MATLAB平台不能为空', trigger: 'blur'} + ], + matlabVersion: [ + {required: true, message: 'MATLAB版本不能为空', trigger: 'blur'} + ], + }) + + const formRef = ref() // 表单 Ref + + /** 提交表单 */ + const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 模型方法校验 + if (formData.value.modelMethods?.length <= 0) { + message.error('模型方法为空') + return + } + // 模型方法名称校验 + if (formData.value.modelMethods.some(e => e.methodName === undefined || e.methodName === '' || e.dataLength === undefined || e.dataLength === null)) { + message.error('存在不合法模型方法名') + return + } + // 提交请求 + formLoading.value = true + try { + const data = formData.value + if (formType.value === 'create') { + await MatlabApi.create(data) + message.success(t('common.createSuccess')) + } else { + await MatlabApi.update(data) + message.success(t('common.updateSuccess')) + } + } finally { + formLoading.value = false + } + // router.push({path:'/model/mpk'}) + router.back() + } + + /** 重置表单 */ + const resetForm = () => { + formData.value = { + id: undefined, + modelFileName: undefined, + modelName: undefined, + modelType: 'predict', + remark: undefined, + modelMethods: [], + modelFilePath: undefined + } + formRef.value?.resetFields() + } + + const handleChange = function () { + + } + + const addRow = function () { + formData.value.modelMethods.push({ + id: generateUUID(), + className: undefined, + methodName: undefined, + dataLength: 1, + outLength: 1, + methodSettings: [] + }) + } + const deleteRow = function (index) { + formData.value.modelMethods.splice(index, 1) + } + + const fileList = ref([]) // 文件列表 + const importUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/model/matlab/model/upload' + const uploadLoading = ref(false) // 表单的加载中 + const uploadHeaders = ref() // 上传 Header 头 + const beforeUpload = function (file) { + // 提交请求 + uploadHeaders.value = { + Authorization: 'Bearer ' + getAccessToken(), + 'tenant-id': getTenantId() + } + uploadLoading.value = true + return true; + } + const submitFormError = (): void => { + message.error('上传失败!') + uploadLoading.value = false + } + const submitFormSuccess = (response: any) => { + if (response.code !== 0) { + message.error(response.msg) + uploadLoading.value = false + return + } + const data = response.data; + formData.value.modelFilePath = data.filePath + formData.value.modelFileName = data.fileName + const modelMethods = (data.classInfos || []).flatMap(e => { + return (e.methodInfos || []).map(m => { + return { ...m,id: generateUUID(), className: e.className,methodSettings: [] }; + }); + }); + + formData.value.modelMethods = modelMethods + message.success('上传成功') + uploadLoading.value = false + } + + onMounted(async () => { + const id = formData.value.id; + const type = id ? 'edit' : 'create' + actionType.value = type + title.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + debugger + formData.value = await MatlabApi.get(id) + debugger + } finally { + formLoading.value = false + } + } + }) +</script> + +<style scoped lang="scss"> +</style> diff --git a/src/views/model/matlab/model/MatlabModelSettingForm.vue b/src/views/model/matlab/model/MatlabModelSettingForm.vue new file mode 100644 index 0000000..27c8931 --- /dev/null +++ b/src/views/model/matlab/model/MatlabModelSettingForm.vue @@ -0,0 +1,144 @@ +<template> + <Dialog v-model="dialogVisible" :title="dialogTitle"> + <el-form + ref="formRef" + v-loading="formLoading" + :model="formData" + :rules="formRules" + label-width="80px" + > + <el-row :gutter="8"> + <el-col :span="12"> + <el-form-item label="参数名称" prop="name"> + <el-input v-model="formData.name" placeholder=""/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="参数类型" prop="valueType"> + <el-select v-model="formData.valueType"> + <el-option + v-for="item in getDictOptions(DICT_TYPE.MODEL_METHOD_SETTING_VALUE_TYPE)" + :key="item.value" + :label="item.label" + :value="item.value" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row :gutter="8"> + <el-col :span="12"> + <el-form-item label="key" prop="settingKey"> + <el-input v-model="formData.settingKey" placeholder=""/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="value" prop="settingValue"> + <el-input v-model="formData.settingValue" placeholder=""/> + </el-form-item> + </el-col> + </el-row> + </el-form> + <template #footer> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="dialogVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script lang="ts" setup> + import {DICT_TYPE,getDictOptions} from '@/utils/dict'; + import {FormRules} from 'element-plus' + + + const {t} = useI18n() // 国际化 + const message = useMessage() // 消息弹窗 + + const dialogVisible = ref(false) // 弹窗的是否展示 + const dialogTitle = ref('参数设置') // 弹窗的标题 + const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 + const formData = ref({ + settingKey: undefined, + settingValue: undefined, + name: undefined, + valueType: undefined + }) + + + const formRules = reactive<FormRules>({ + settingKey: [ + {required: true, message: 'key不能为空', trigger: 'blur'}, + ], + settingValue: [ + {required: true, message: 'value不能为空', trigger: 'blur'}, + ], + name: [ + {required: true, message: '参数名称不能为空', trigger: 'blur'}, + ], + valueType: [ + {required: true, message: '参数类型不能为空', trigger: 'blur'}, + ] + }) + + const formRef = ref() // 表单 Ref + + let methodSettingsRef = undefined + let infoRef = undefined + /** 打开弹窗 */ + const open = async (info,methodSettings) => { + dialogVisible.value = true + resetForm() + // 修改时,设置数据 + if (info) { + infoRef = info + formLoading.value = true + try { + formData.value = {...info} + } finally { + formLoading.value = false + } + }else { + methodSettingsRef = methodSettings + } + } + defineExpose({open}) // 提供 open 方法,用于打开弹窗 + + + // 数据回调 + const emit = defineEmits(['addSettingCallback']) + /** 提交表单 */ + const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + + // 提交请求 + formLoading.value = true + try { + if (infoRef) { + // 修改 + for (let key in formData.value) { + infoRef[key] = formData.value[key]; + } + infoRef = undefined; + }else { + // 新增 + methodSettingsRef.push({...formData.value}) + } + dialogVisible.value = false + } finally { + formLoading.value = false + } + } + + /** 重置表单 */ + const resetForm = () => { + formData.value = { + settingKey: undefined, + settingValue: undefined, + name: undefined, + valueType: undefined, + } + formRef.value?.resetFields() + } +</script> diff --git a/src/views/model/matlab/model/MatlabRun.vue b/src/views/model/matlab/model/MatlabRun.vue new file mode 100644 index 0000000..b43dc11 --- /dev/null +++ b/src/views/model/matlab/model/MatlabRun.vue @@ -0,0 +1,279 @@ +<template> + <Dialog v-model="dialogVisible" :title="dialogTitle"> + <el-form + class="-mb-15px" + :model="formData" + ref="formRef" + :inline="true" + :rules="formRules" + label-width="68px" + v-loading="formLoading" + > + <el-form-item style="width: 100%"> + <el-divider content-position="left">模型信息</el-divider> + </el-form-item> + <el-row> + <el-col :span="24"> + <el-form-item label="全类名" style="width: 100%" prop="className"> + <el-input disabled v-model="formData.className" placeholder=""/> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="24"> + <el-form-item label="方法名" prop="methodName"> + <el-select v-model="formData.methodId" @change="methodChange" style="width: 240px"> + <el-option + v-for="item in methodList" + :key="item.id" + :label="item.className + '.' + item.methodName + '()'" + :value="item.id" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-divider content-position="left">模型参数信息</el-divider> + <div style="display:flex;flex-direction: row;align-items: center;margin-bottom: 6px"> + <el-button tag="a" :href="staticDir + '/template/模型参数导入模板.xlsx'" download="模型参数导入模板.xlsx" style="text-decoration: none;" type="primary" size="small" link>模板下载</el-button> + <el-upload + ref="uploadRef" + v-model:file-list="fileList" + :show-file-list="false" + :action="importUrl" + :auto-upload="true" + :disabled="formLoading" + :before-upload="beforeUpload" + :headers="uploadHeaders" + :on-error="submitFormError" + :on-success="submitFormSuccess" + accept=".xlsx" + > + <el-button type="primary" size="small" link>参数导入</el-button> + </el-upload> + </div> + <el-row v-for="(item,index) in datas" :key="index" :gutter="20"> + <el-col :span="24"> + <el-form-item :label="'参数_' + (index)" required style="width: 100%"> + <el-input type="textarea" :disabled="true" :rows="3" v-model="datas[index]" placeholder="" /> + </el-form-item> + </el-col> + </el-row> + <el-divider content-position="left">模型设置信息</el-divider> +<!-- <el-row :gutter="20">--> +<!-- <el-col :span="4">--> +<!-- <el-button type="primary" size="small" @click="addRow()">新增</el-button>--> +<!-- </el-col>--> +<!-- </el-row>--> + <el-table :data="formData.modelSettings" border> + <el-table-column + prop="" + label="参数key" + align="center"> + <template #default="scope"> + <el-input size="small" v-model="scope.row.settingKey" :disabled="true" maxlength="50" clearable /> + </template> + </el-table-column> + <el-table-column + prop="" + label="参数名称" + align="center"> + <template #default="scope"> + <el-input size="small" v-model="scope.row.name" :disabled="true" maxlength="50" clearable /> + </template> + </el-table-column> + <el-table-column + prop="" + label="参数value" + align="center"> + <template #default="scope"> + <el-input size="small" v-model="scope.row.settingValue" :disabled="scope.row.settingKey === 'pyFile'" maxlength="50" clearable /> + </template> + </el-table-column> +<!-- <el-table-column label="操作" fixed="right" header-align="center" align="center" width="100">--> +<!-- <template #default="scope">--> +<!-- <el-button--> +<!-- @click="deleteRow(scope.$index)"--> +<!-- key="danger"--> +<!-- type="danger"--> +<!-- :disabled="scope.row.settingKey === 'pyFile'"--> +<!-- link--> +<!-- >删除</el-button>--> +<!-- </template>--> +<!-- </el-table-column>--> + </el-table> + <el-divider content-position="left">模型运行结果</el-divider> + <el-input v-model="modelRunResult" placeholder="" rows="4" type="textarea" /> + <div style="display: flex;flex-direction: row;justify-content: end;margin-top: 16px"> + <el-button :loading="modelRunloading" type="primary" @click="modelRun()">运行</el-button> + </div> + </el-form> + </Dialog> +</template> +<script lang="ts" setup> + import * as MlModelApi from '@/api/model/matlab/mlModel' + import {FormRules} from "element-plus"; + import {getAccessToken, getTenantId} from "@/utils/auth"; + import download from "@/utils/download"; + const staticDir = ref(import.meta.env.VITE_STATIC_DIR) + + const { t } = useI18n() // 国际化 + const message = useMessage() // 消息弹窗 + + const dialogVisible = ref(false) // 弹窗的是否展示 + const dialogTitle = ref('模型运行') // 弹窗的标题 + + const formData = reactive({ + modelFileName: '', + className: '', + methodName: '', + methodId: '', + uuids: [], + modelSettings: [], + outLength: 1 + }) + + const datas = ref([]) + + // 模型方法下拉列表 + const methodList = ref([]) + + /** 打开弹窗 */ + const open = async (row) => { + dialogVisible.value = true + formData.modelFileName = row.modelFileName + const matlabModel = await MlModelApi.get(row.id) + methodList.value = matlabModel.modelMethods + formData.className = matlabModel.modelMethods[0].className + formData.methodId = matlabModel.modelMethods[0].id + formData.methodName = matlabModel.modelMethods[0].methodName + formData.outLength = matlabModel.modelMethods[0].outLength + datas.value = [] + formData.uuids = []; + for (let i = 0 ; i < matlabModel.modelMethods[0].dataLength ; i++) { + datas.value[i] = '[[]]'; + formData.uuids[i] = ''; + } + + // 回显参数 + if (matlabModel.modelMethods[0].methodSettings && matlabModel.modelMethods[0].methodSettings.length > 0) { + formData.modelSettings = matlabModel.modelMethods[0].methodSettings + } + + } + defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + + const formRules = reactive<FormRules>({ + methodName: [ + {required: true, message: '方法名不能为空', trigger: 'blur'} + ], + className: [ + {required: true, message: '全类名不能为空', trigger: 'blur'} + ] + }) + + const addRow = function () { + formData.modelSettings.push({ + settingKey: '', + settingValue: '' + }) + } + const deleteRow = function (index) { + formData.modelSettings.splice(index, 1) + } + const methodChange = function (value) { + datas.value = [] + formData.uuids = []; + var method = methodList.value.find(e => e.id === value); + formData.methodName = method.methodName + formData.className = method.className + for (let i = 0 ; i < method?.dataLength ; i++) { + datas.value[i] = '[[]]'; + formData.uuids[i] = ''; + } + // 回显参数 + if (method.methodSettings && method.methodSettings.length > 0) { + formData.modelSettings = method.methodSettings + }else { + formData.modelSettings = [] + } + } + + const fileList = ref([]) // 文件列表 + const importUrl = + import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/model/matlab/model/importData' + const formLoading = ref(false) // 表单的加载中 + const uploadHeaders = ref() // 上传 Header 头 + /** 上传错误提示 */ + const submitFormError = (): void => { + message.error('导入失败,请检查导入文件!') + formLoading.value = false + } + const submitFormSuccess = (response: any) => { + try { + if (response.code !== 0) { + message.error(response.msg) + return + } + const data = response.data; + if (datas.value.length > data.length) { + message.error("导入数据长度为" + data.length + ",应≥" + datas.value.length) + return + } + for (let i = 0; i < datas.value.length; i++) { + datas.value[i] = data[i].data + formData.uuids[i] = data[i].uuid; + } + message.success('导入成功') + } finally { + formLoading.value = false + } + } + const beforeUpload = function (file) { + // 提交请求 + uploadHeaders.value = { + Authorization: 'Bearer ' + getAccessToken(), + 'tenant-id': getTenantId() + } + formLoading.value = true + return true; + } + + // 模型运行结果 + const modelRunResult = ref('') + // 模型运行loading + const modelRunloading = ref(false) + // 表单 Ref + const formRef = ref() + // 运行 + const modelRun = async () => { + modelRunResult.value = '' + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + + + // 提交请求 + modelRunloading.value = true + try { + const data = { + ...formData + } + + //处理modelSettings + // let settingsPredict = {}; + // data.modelSettings.forEach(e => { + // settingsPredict[e.settingKey] = e.settingValue; + // }) + // data.modelSettings = settingsPredict + + let result = await MlModelApi.test(data) + + modelRunResult.value = result; + message.success('运行成功') + } finally { + modelRunloading.value = false + } + } +</script> diff --git a/src/views/model/matlab/model/index.vue b/src/views/model/matlab/model/index.vue new file mode 100644 index 0000000..9f17b86 --- /dev/null +++ b/src/views/model/matlab/model/index.vue @@ -0,0 +1,188 @@ +<template> + <!-- 搜索工作栏 --> + <ContentWrap> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + @submit.prevent + > + <el-form-item label="模型名称" prop="pyChineseName"> + <el-input + v-model="queryParams.modelName" + placeholder="请输入模型名称" + clearable + class="!w-240px" + /> + </el-form-item> + <el-form-item label="模型文件" prop="modelFileName"> + <el-input + v-model="queryParams.modelFileName" + placeholder="请输入模型文件名称" + clearable + class="!w-240px" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"> + <Icon icon="ep:search" class="mr-5px"/> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon icon="ep:refresh" class="mr-5px"/> + 重置 + </el-button> + <div class="ml-12px"> + <router-link :to="'/matlab/model/form'"> + <el-button type="primary" plain v-hasPermi="['ml:model:create']"> + <Icon icon="ep:plus" class="mr-5px"/>新增</el-button> + </router-link> + </div> + + </el-form-item> + </el-form> + </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table + v-loading="loading" + :data="list" + row-key="id" + > + <el-table-column prop="modelName" label="模型名称" header-align="center" align="center" min-width="100" /> + <el-table-column prop="modelFileName" label="模型文件" header-align="center" align="center" min-width="250"/> + <el-table-column prop="modelType" label="模型类型" header-align="center" align="center" :formatter="(r,c,v) => getDictLabel(DICT_TYPE.MODEL_TYPE,v)"/> + <el-table-column prop="matlabPlatform" label="matlab平台" header-align="center" align="center"/> + <el-table-column prop="matlabVersion" label="matlab版本" header-align="center" align="center"/> +<!-- <el-table-column prop="remark" label="备注" header-align="center" align="center" min-width="100px"/>--> + <el-table-column prop="createDate" label="创建时间" header-align="center" align="center" :formatter="dateFormatter" width="180px"/> + <el-table-column prop="updateDate" label="修改时间" header-align="center" align="center" :formatter="dateFormatter" width="180px"/> + <el-table-column label="操作" align="center" width="200px"> + <template #default="scope"> + <div class="flex items-center justify-center"> + <router-link :to="'/matlab/model/form/' + scope.row.id"> + <el-button type="primary" link v-hasPermi="['ml:model:update']"> + <Icon icon="ep:edit"/>修改 + </el-button> + </router-link> + <el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['ml:model:delete']"> + <Icon icon="ep:delete"/>删除 + </el-button> + <div class="pl-12px"> + <el-dropdown @command="(command) => handleCommand(command, scope.row)" trigger="click"> + <el-button type="primary" link> + <Icon icon="ep:d-arrow-right" /> 更多 + </el-button> + <template #dropdown> + <el-dropdown-menu> + <el-dropdown-item + command="mpkRunDialog" + > + 运行 + </el-dropdown-item> + </el-dropdown-menu> + </template> + </el-dropdown> + </div> + </div> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + v-model:limit="queryParams.limit" + v-model:page="queryParams.page" + :total="total" + @pagination="getList" + /> + </ContentWrap> + + <MatlabRun ref="matlabRun" /> +</template> +<script lang="ts" setup> + import {dateFormatter} from '@/utils/formatTime' + import * as MlModelApi from '@/api/model/matlab/mlModel' + import { DICT_TYPE, getDictLabel } from '@/utils/dict' + import MatlabRun from './MatlabRun.vue' + + defineOptions({name: 'MatlabModel'}) + + const message = useMessage() // 消息弹窗 + const {t} = useI18n() // 国际化 + + const loading = ref(true) // 列表的加载中 + const total = ref(0) // 列表的总页数 + const list = ref([]) // 字典表格数据 + const queryParams = reactive({ + page: 1, + limit: 10, + modelName: '', + modelFileName: '' + }) + const queryFormRef = ref() // 搜索的表单 + + const getList = async () => { + loading.value = true + try { + const data = await MlModelApi.getPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } + } + + /** 操作分发 */ + const handleCommand = (command: string, row) => { + switch (command) { + case 'mpkRunDialog': + matlabRunDialog(row) + break + default: + break + } + } + + /** 搜索按钮操作 */ + const handleQuery = () => { + getList() + } + + /** 重置按钮操作 */ + const resetQuery = () => { + queryParams.page = 1 + queryFormRef.value.resetFields() + handleQuery() + } + + /** 删除按钮操作 */ + const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await MlModelApi.deleteModel(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch { + } + } + + const matlabRun = ref(); + const matlabRunDialog = (row) => { + matlabRun.value.open(row); + } + + onActivated((to) => { + getList() + }) + + /** 初始化 **/ + onMounted(async () => { + await getList() + }) +</script> diff --git a/src/views/model/matlab/project/MatlabProjectForm.vue b/src/views/model/matlab/project/MatlabProjectForm.vue new file mode 100644 index 0000000..e5b379d --- /dev/null +++ b/src/views/model/matlab/project/MatlabProjectForm.vue @@ -0,0 +1,156 @@ +<template> + <Dialog v-model="dialogVisible" :title="dialogTitle" width="60%"> + <el-form + ref="formRef" + v-loading="formLoading" + :model="formData" + :rules="formRules" + label-width="80px" + > + <el-row :gutter="20"> + <el-col :span="10"> + <el-form-item label="项目名称" prop="projectName"> + <el-input v-model="formData.projectName" placeholder=""/> + </el-form-item> + </el-col> + </el-row> + <el-row :gutter="20"> + <el-col :span="10"> + <el-form-item label="项目编码" prop="projectCode"> + <el-input v-model="formData.projectCode" placeholder=""/> + </el-form-item> + </el-col> + </el-row> + <el-row :gutter="20"> + <el-col :span="24"> + <el-form-item label="关联模型" prop="models"> + <el-transfer style="width: 100%" :props="{key: 'id',label: 'modelFileName'}" :titles="['未选模型', '已选模型']" target-order="unshift" filterable :filter-method="filterMethod" v-model="formData.models" :data="modelList"> + <template #default="{ option }"> + <span :title="option.modelFileName + '【' + option.modelName + '】'">{{ option.modelFileName}}</span> + </template> + </el-transfer> + </el-form-item> + </el-col> + </el-row> + </el-form> + <template #footer> + <el-button type="primary" @click="submitForm">确 定</el-button> + <el-button @click="dialogVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script lang="ts" setup> + import * as ProjectApi from '@/api/model/matlab/project' + import * as MlModelApi from '@/api/model/matlab/mlModel' + import {FormRules} from 'element-plus' + + + const {t} = useI18n() // 国际化 + const message = useMessage() // 消息弹窗 + + const dialogVisible = ref(false) // 弹窗的是否展示 + const dialogTitle = ref('') // 弹窗的标题 + const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 + const formType = ref('') // 表单的类型:create - 新增;update - 修改 + const formData = ref({ + id: undefined, + projectName: undefined, + projectCode: undefined, + models: undefined, + }) + + + const formRules = reactive<FormRules>({ + projectName: [ + {required: true, message: '项目名称不能为空', trigger: 'blur'}, + ], + projectCode: [ + {required: true, message: '项目编码不能为空', trigger: 'blur'}, + ], + }) + + const formRef = ref() // 表单 Ref + + /** 打开弹窗 */ + const open = async (type: string, id?: number) => { + dialogVisible.value = true + dialogTitle.value = t('action.' + type) + formType.value = type + getModelList(); + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + const data = await ProjectApi.getProject(id) + data.models = data.models.map(e => e.id) + formData.value = { + ...data + } + } finally { + formLoading.value = false + } + } + } + defineExpose({open}) // 提供 open 方法,用于打开弹窗 + + /** 提交表单 */ + const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 + const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = { + ...formData.value + } + if (data.models && data.models.length > 0) { + data.models = data.models.map(e => { + return {id: e} + }) + } + if (formType.value === 'create') { + await ProjectApi.createProject(data) + message.success(t('common.createSuccess')) + } else { + await ProjectApi.updateProject(data) + message.success(t('common.updateSuccess')) + } + dialogVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } + } + + /** 重置表单 */ + const resetForm = () => { + formData.value = { + id: undefined, + projectName: undefined, + projectCode: undefined, + } + formRef.value?.resetFields() + } + + // 所有模型列表 + const modelList = ref([]) + const getModelList = async () => { + modelList.value = await MlModelApi.list({}) + } + + // 模型筛选 + const filterMethod = function (query, item) { + return item.modelFileName.toLowerCase().indexOf(query.toLowerCase()) !== -1 + } +</script> + +<style scoped> + :deep(.el-transfer-panel) { + width: 40%; + } +</style> diff --git a/src/views/model/matlab/project/MatlabProjectModelDialog.vue b/src/views/model/matlab/project/MatlabProjectModelDialog.vue new file mode 100644 index 0000000..3d950aa --- /dev/null +++ b/src/views/model/matlab/project/MatlabProjectModelDialog.vue @@ -0,0 +1,101 @@ +<template> + <Dialog v-model="dialogVisible" :title="dialogTitle" width="80%"> + <!-- 搜索工作栏 --> + <ContentWrap> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="模型名称" prop="modelFileName"> + <el-input + v-model="queryParams.modelFileName" + placeholder="请输入模型名称" + clearable + class="!w-240px" + /> + </el-form-item> + <el-form-item> + <el-button @click="getList"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> + </el-form-item> + </el-form> + </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table + v-loading="loading" + :data="list" + row-key="id" + border + > + <el-table-column prop="modelName" label="模型中文名称"/> + <el-table-column prop="modelFileName" label="模型名称"/> + <el-table-column prop="modelType" label="模型类型" :formatter="(r,c,v) => getDictLabel(DICT_TYPE.MODEL_TYPE,v)"/> + <el-table-column prop="remark" label="备注" width="300px"/> + <el-table-column label="模型方法" type="expand" width="100px"> + <template #default="props"> + <el-table :data="props.row.modelMethods"> + <el-table-column align="center" label="全类名" prop="className" /> + <el-table-column align="center" label="方法名" prop="methodName" /> + <el-table-column align="center" label="参数长度" prop="dataLength" /> + <el-table-column align="center" label="输出长度" prop="outLength" /> + </el-table> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + v-model:limit="queryParams.pageSize" + v-model:page="queryParams.page" + :total="total" + @pagination="getList" + /> + </ContentWrap> + </Dialog> +</template> +<script lang="ts" setup> + import download from "@/utils/download"; + import * as projectApi from '@/api/model/matlab/project' + import { dateFormatter } from '@/utils/formatTime' + import { DICT_TYPE, getDictLabel } from '@/utils/dict' + + + const { t } = useI18n() // 国际化 + const message = useMessage() // 消息弹窗 + + const dialogVisible = ref(false) // 弹窗的是否展示 + const dialogTitle = ref('关联模型') // 弹窗的标题 + + const loading = ref(true) // 列表的加载中 + const total = ref(0) // 列表的总页数 + const list = ref([]) // 字典表格数据 + const queryParams = reactive({ + page: 1, + pageSize: 10, + projectId: undefined, + modelFileName: undefined, + }) + + /** 打开弹窗 */ + const open = async (projectId: String) => { + dialogVisible.value = true + + queryParams.projectId = projectId; + getList() + } + defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + + const getList = async () => { + loading.value = true + try { + let data = await projectApi.getProjectModel(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } + } +</script> diff --git a/src/views/model/matlab/project/index.vue b/src/views/model/matlab/project/index.vue new file mode 100644 index 0000000..8f1799a --- /dev/null +++ b/src/views/model/matlab/project/index.vue @@ -0,0 +1,221 @@ +<template> + <!-- 搜索工作栏 --> + <ContentWrap> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="项目名称" prop="projectName"> + <el-input + v-model="queryParams.projectName" + placeholder="请输入项目名称" + clearable + class="!w-240px" + /> + </el-form-item> + <el-form-item label="项目编码" prop="projectCode"> + <el-input + v-model="queryParams.projectCode" + placeholder="请输入项目编码" + clearable + class="!w-240px" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"> + <Icon icon="ep:search" class="mr-5px"/> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon icon="ep:refresh" class="mr-5px"/> + 重置 + </el-button> + <el-button + type="primary" + plain + @click="openForm('create')" + v-hasPermi="['mpk:project:create']" + > + <Icon icon="ep:plus" class="mr-5px"/> + 新增 + </el-button> + </el-form-item> + </el-form> + </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table + v-loading="loading" + :data="list" + row-key="id" + > + <el-table-column prop="projectName" label="项目名称"/> + <el-table-column prop="projectCode" label="项目编码"/> + <el-table-column prop="createTime" label="创建时间" :formatter="dateFormatter" width="300px"/> + <el-table-column label="操作" align="center" width="300px"> + <template #default="scope"> + <div class="flex items-center justify-center"> + <el-button + link + type="primary" + @click="openForm('update', scope.row.id)" + v-hasPermi="['mpk:project:update']" + > + <Icon icon="ep:edit"/> + 修改 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['mpk:project:delete']" + > + <Icon icon="ep:delete"/> + 删除 + </el-button> + <el-button + link + type="primary" + @click="viewRelevanceModel(scope.row.id)" + > + <Icon icon="ep:link"/> + 查看关联模型 + </el-button> + <div class="pl-12px"> + <el-dropdown @command="(command) => handleCommand(command, scope.row)" + trigger="click"> + <el-button type="primary" link> + <Icon icon="ep:d-arrow-right"/> + 更多 + </el-button> + <template #dropdown> + <el-dropdown-menu> + <el-dropdown-item + command="publish" + > + <el-button link>发布</el-button> + </el-dropdown-item> + </el-dropdown-menu> + </template> + </el-dropdown> + </div> + </div> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + v-model:limit="queryParams.limit" + v-model:page="queryParams.page" + :total="total" + @pagination="getList" + /> + </ContentWrap> + + <!-- 表单弹窗:添加/修改 --> + <ProjectForm ref="formRef" @success="getList"/> + <!-- 关联模型 --> + <RelevanceModel ref="relevanceModelRef"/> +</template> +<script lang="ts" setup> + import {dateFormatter} from '@/utils/formatTime' + import * as ProjectApi from '@/api/model/matlab/project' + import ProjectForm from './MatlabProjectForm.vue' + import RelevanceModel from './MatlabProjectModelDialog.vue' + + defineOptions({name: 'MatlabProject'}) + + const message = useMessage() // 消息弹窗 + const {t} = useI18n() // 国际化 + + const loading = ref(true) // 列表的加载中 + const total = ref(0) // 列表的总页数 + const list = ref([]) // 字典表格数据 + const queryParams = reactive({ + page: 1, + limit: 10, + projectName: '', + projectCode: '' + }) + const queryFormRef = ref() // 搜索的表单 + + const getList = async () => { + loading.value = true + try { + const data = await ProjectApi.getPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } + } + + /** 操作分发 */ + const handleCommand = (command: string, row) => { + switch (command) { + case 'publish': + publish(row.id,row.projectName) + break + default: + break + } + } + + // 发布 + const publish = async (projectId,projectName) => { + // 发布的二次确认 + await message.confirm('确认发布 ' + projectName) + + // 发布 + await ProjectApi.publish({projectId}) + + message.success('发布成功'); + } + + /** 搜索按钮操作 */ + const handleQuery = () => { + getList() + } + + /** 重置按钮操作 */ + const resetQuery = () => { + queryParams.page = 1 + queryFormRef.value.resetFields() + handleQuery() + } + + /** 添加/修改操作 */ + const formRef = ref() + const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) + } + + /** 删除按钮操作 */ + const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await ProjectApi.deleteProject(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch { + } + } + + // 查看关联模型 + const relevanceModelRef = ref() + const viewRelevanceModel = (id) => { + relevanceModelRef.value.open(id) + } + + /** 初始化 **/ + onMounted(async () => { + await getList() + }) +</script> diff --git a/src/views/model/mpk/file/MpkRun.vue b/src/views/model/mpk/file/MpkRun.vue index 2b9039c..908d883 100644 --- a/src/views/model/mpk/file/MpkRun.vue +++ b/src/views/model/mpk/file/MpkRun.vue @@ -110,6 +110,7 @@ <!-- </el-table-column>--> </el-table> <el-divider content-position="left">模型运行结果</el-divider> + <el-button type="primary" size="small" link @click="saveModel" v-if="showSaveModel && formData.methodName === 'train'">下载模型(.miail)</el-button> <el-input v-model="modelRunResult" placeholder="" rows="4" type="textarea" /> <div style="display: flex;flex-direction: row;justify-content: end;margin-top: 16px"> <el-button :loading="modelRunloading" type="primary" @click="modelRun()">运行</el-button> @@ -121,6 +122,7 @@ import * as MpkApi from '@/api/model/mpk/mpk' import {FormRules} from "element-plus"; import {getAccessToken, getTenantId} from "@/utils/auth"; + import download from "@/utils/download"; const staticDir = ref(import.meta.env.VITE_STATIC_DIR) const { t } = useI18n() // 国际化 @@ -167,6 +169,7 @@ return e; }) } + } defineExpose({ open }) // 提供 open 方法,用于打开弹窗 @@ -260,7 +263,8 @@ // 运行 const modelRun = async () => { modelRunResult.value = '' -// 校验表单 + showSaveModel.value = false + // 校验表单 if (!formRef) return const valid = await formRef.value.validate() if (!valid) return @@ -302,10 +306,63 @@ data.model = undefined } - modelRunResult.value = await MpkApi.modelRun(data) + let result = await MpkApi.modelRun(data) + + modelRunResult.value = result; message.success('运行成功') + // 训练方法 + if (formData.methodName === 'train') { + result = JSON.parse(result); + // 返回结果正确 + if (result?.status_code === '100' && result?.models?.model_path) { + // 有预测方法 + if (methodList.value.some(e => e.methodName === 'predict')) { + saveModelParams.modelResult = result + saveModelParams.model = result?.models + showSaveModel.value = true + } + } + } } finally { modelRunloading.value = false } } + + const showSaveModel = ref(false) + + const saveModelParams = reactive({ + pyName: '', + className: '', + methodName: '', + uuids: [], + modelSettings: [], + predModelSettings: [], + hasModel: false, + model: undefined, + modelResult: undefined, + dataLength: undefined, + resultKey: undefined, + }) + + const saveModel = async () => { + saveModelParams.className = formData.className + saveModelParams.pyName = formData.pyName + saveModelParams.modelSettings = formData.modelSettings + const predMethod = methodList.value.find(e => e.methodName === 'predict'); + saveModelParams.methodName = predMethod.methodName + saveModelParams.resultKey = predMethod.resultKey + //predModelSettings + if (predMethod.methodSettings && predMethod.methodSettings.length > 0) { + saveModelParams.predModelSettings = predMethod.methodSettings.map(e => { + e.settingValue = e.value; + return e; + }) + } + saveModelParams.hasModel = predMethod.model === 1 + + saveModelParams.dataLength = predMethod.dataLength + + const data = await MpkApi.saveModel(saveModelParams) + download.downloadFile(data, saveModelParams.pyName + '.miail') + } </script> diff --git a/src/views/model/mpk/file/index.vue b/src/views/model/mpk/file/index.vue index a3d2bde..ff8f81e 100644 --- a/src/views/model/mpk/file/index.vue +++ b/src/views/model/mpk/file/index.vue @@ -1,7 +1,7 @@ <template> <el-row :gutter="20"> <!-- 左侧树 --> - <el-col :span="4" :xs="24"> + <el-col :span="3" :xs="24"> <ContentWrap class="h-1/1"> <el-tree style="max-width: 600px" @@ -13,7 +13,7 @@ /> </ContentWrap> </el-col> - <el-col :span="20" :xs="24"> + <el-col :span="21" :xs="24"> <!-- 搜索工作栏 --> <ContentWrap> <el-form @@ -67,13 +67,14 @@ :data="list" row-key="id" > - <el-table-column prop="pyChineseName" label="模型名称" header-align="center" align="left" min-width="100" /> - <el-table-column prop="pyName" label="模型文件" header-align="center" align="left" min-width="300"/> + <el-table-column prop="pyChineseName" label="模型名称" header-align="center" align="center" min-width="100" /> + <el-table-column prop="pyName" label="模型文件" header-align="center" align="center" min-width="200"/> <el-table-column prop="pyType" label="模型类型" :formatter="(r,c,v) => getDictLabel(DICT_TYPE.MODEL_TYPE,v)"/> - <el-table-column prop="menuName" label="所属菜单" min-width="120px"/> - <el-table-column prop="groupName" label="所属组" min-width="120px"/> - <el-table-column prop="remark" label="备注" min-width="100px"/> - <el-table-column prop="createDate" label="创建时间" :formatter="dateFormatter" width="180px"/> +<!-- <el-table-column prop="menuName" label="所属菜单" min-width="120px"/>--> +<!-- <el-table-column prop="groupName" label="所属组" min-width="120px"/>--> +<!-- <el-table-column prop="remark" label="备注" min-width="100px"/>--> + <el-table-column prop="createDate" label="创建时间" align="center" :formatter="dateFormatter" width="180px"/> + <el-table-column prop="updateDate" label="修改时间" align="center" :formatter="dateFormatter" width="180px"/> <el-table-column label="操作" align="center" width="200px"> <template #default="scope"> <div class="flex items-center justify-center"> diff --git a/src/views/model/pre/accuracy/MmItemAccuracyHisForm.vue b/src/views/model/pre/accuracy/MmItemAccuracyHisForm.vue new file mode 100644 index 0000000..3e5ac75 --- /dev/null +++ b/src/views/model/pre/accuracy/MmItemAccuracyHisForm.vue @@ -0,0 +1,122 @@ +<template> + <el-drawer + v-model="drawer" + size="60%" + title="历史详情" + direction="rtl" + :before-close="handleClose" + > + <!-- 搜索工作栏 --> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="精准误差" prop="inDeviation"> + <el-input + v-model="queryParams.inDeviation" + placeholder="请输入精准误差" + clearable + class="!w-240px" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"> + <Icon icon="ep:search" class="mr-5px"/> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon icon="ep:refresh" class="mr-5px"/> + 重置 + </el-button> + </el-form-item> + </el-form> + <!-- 列表 --> + <el-table + v-loading="loading" + :data="list" + row-key="id" + > + <el-table-column prop="inDeviation" label="精准误差"/> + <el-table-column prop="inAccuracyRate" label="精准度"/> + <el-table-column prop="outDeviation" label="不可信误差"/> + <el-table-column prop="outAccuracyRate" label="不可信率"/> + </el-table> + <!-- 分页 --> + <Pagination + v-model:limit="queryParams.limit" + v-model:page="queryParams.page" + :total="total" + @pagination="getList" + /> + </el-drawer> +</template> +<script lang="ts" setup> +import * as MmItemAccuracyHis from '@/api/model/pre/accuracy/his' + +defineOptions({name: 'ChartParam'}) + +const drawer = ref(false) +const loading = ref(false) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 字典表格数据 +const queryParams = reactive({ + page: 1, + limit: 10, + rateId: undefined, + inDeviation: undefined, + inAccuracyRate: undefined, + outDeviation: undefined, + outAccuracyRate: undefined, +}) +const queryFormRef = ref() // 搜索的表单 + +const getList = async () => { + loading.value = true + try { + const data = await MmItemAccuracyHis.getMmItemAccuracyHisPage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} + +/** 打开弹窗 */ +const open = async (rateId?: string) => { + resetForm() + drawer.value = true + queryParams.rateId = rateId + if (rateId) { + getList() + } +} +defineExpose({open}) // 提供 open 方法,用于打开弹窗 + +/** 重置表单 */ +const resetForm = () => { + queryParams.rateId = undefined + queryParams.inDeviation = undefined + queryParams.inAccuracyRate = undefined + queryParams.outDeviation = undefined + queryParams.outAccuracyRate = undefined + +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +const handleClose = (done: () => void) => { + drawer.value = false +} +</script> diff --git a/src/views/model/pre/accuracy/MmItemAccuracyRateForm.vue b/src/views/model/pre/accuracy/MmItemAccuracyRateForm.vue new file mode 100644 index 0000000..10e2572 --- /dev/null +++ b/src/views/model/pre/accuracy/MmItemAccuracyRateForm.vue @@ -0,0 +1,176 @@ +<template> + <Dialog v-model="dialogVisible" :title="dialogTitle" width="50%"> + <el-form + ref="formRef" + v-loading="formLoading" + :model="formData" + :rules="formRules" + label-width="120px" + > + <el-row> + <el-col :span="12"> + <el-form-item label="预测项ID" prop="itemId"> + <el-input v-model="formData.itemId" placeholder="请输入预测项ID"/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="输出ID" prop="outId"> + <el-input v-model="formData.outId" placeholder="请输入输出ID"/> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="取样长度" prop="sampleLength"> + <el-input-number v-model="formData.sampleLength" :min="1" clearable controls-position="right" style="width: 100%"/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="值类型" prop="valueType"> + <el-input v-model="formData.valueType" placeholder="请输入值类型"/> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="开始统计时间" prop="beginTime"> + <el-input v-model="formData.beginTime" placeholder="请输入开始统计时间"/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="精准误差" prop="inDeviation"> + <el-input v-model="formData.inDeviation" placeholder="请输入精准误差"/> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="精准度" prop="inAccuracyRate"> + <el-input v-model="formData.inAccuracyRate" placeholder="请输入精准度"/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="不可信误差" prop="outDeviation"> + <el-input v-model="formData.outDeviation" placeholder="请输入不可信误差"/> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="不可信率" prop="outAccuracyRate"> + <el-input v-model="formData.outAccuracyRate" placeholder="请输入不可信率"/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="是否启用" prop="isEnable"> + <el-select v-model="formData.isEnable" placeholder="请选择"> + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.COM_IS_INT)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + </el-form> + <template #footer> + <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> + <el-button @click="dialogVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script lang="ts" setup> +import * as MmItemAccuracyRate from '@/api/model/pre/accuracy/rate' +import {DICT_TYPE, getIntDictOptions} from "@/utils/dict"; + +defineOptions({name: 'DataMmItemAccuracyRateForm'}) + +const {t} = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 +const dialogVisible = ref(false) // 弹窗的是否展示 +const dialogTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData = ref({ + id: undefined, + itemId: undefined, + outId: undefined, + sampleLength: undefined, + valueType: undefined, + beginTime: undefined, + inDeviation: undefined, + inAccuracyRate: undefined, + outDeviation: undefined, + outAccuracyRate: undefined, + isEnable: undefined, +}) +const formRules = reactive({ + itemId: [{required: true, message: '预测项ID不能为空', trigger: 'blur'}], + outId: [{required: true, message: '输出ID不能为空', trigger: 'blur'}], +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const open = async (type: string, id?: number) => { + dialogVisible.value = true + dialogTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await MmItemAccuracyRate.getMmItemAccuracyRate(id) + } finally { + formLoading.value = false + } + } +} +defineExpose({open}) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as unknown as MmItemAccuracyRate.MmItemAccuracyRateVO + if (formType.value === 'create') { + await MmItemAccuracyRate.createMmItemAccuracyRate(data) + message.success(t('common.createSuccess')) + } else { + await MmItemAccuracyRate.updateMmItemAccuracyRate(data) + message.success(t('common.updateSuccess')) + } + dialogVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + itemId: undefined, + outId: undefined, + sampleLength: undefined, + valueType: undefined, + beginTime: undefined, + inDeviation: undefined, + inAccuracyRate: undefined, + outDeviation: undefined, + outAccuracyRate: undefined, + isEnable: undefined, + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/model/pre/accuracy/index.vue b/src/views/model/pre/accuracy/index.vue new file mode 100644 index 0000000..04bccd9 --- /dev/null +++ b/src/views/model/pre/accuracy/index.vue @@ -0,0 +1,146 @@ +<template> + <!-- 搜索 --> + <ContentWrap> + <el-form + class="-mb-15px" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item> + <el-button + type="primary" + plain + @click="openForm('create')" + v-hasPermi="['item:accuracy-rate:create']" + > + <Icon icon="ep:plus" class="mr-5px" /> + 新增 + </el-button> + </el-form-item> + </el-form> + </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table v-loading="loading" :data="list"> + <el-table-column label="预测项ID" header-align="center" align="center" min-width="150" prop="itemId" fixed/> + <el-table-column label="输出ID" header-align="center" align="center" min-width="150" prop="outId" /> + <el-table-column label="取样长度" header-align="center" align="center" min-width="100" prop="sampleLength" /> + <el-table-column label="值类型" header-align="center" align="center" min-width="50" prop="valueType" /> + <el-table-column label="开始统计时间" header-align="center" align="center" min-width="50" prop="beginTime" /> + <el-table-column label="精准误差" header-align="center" align="center" min-width="50" prop="inDeviation" /> + <el-table-column label="精准度" header-align="center" align="center" min-width="100" prop="inAccuracyRate" /> + <el-table-column label="不可信误差" header-align="center" align="center" min-width="100" prop="outDeviation" /> + <el-table-column label="不可信率" header-align="center" align="center" min-width="100" prop="outAccuracyRate" /> + <el-table-column label="是否启用" header-align="center" align="center" min-width="100" prop="isEnable" > + <template #default="scope"> + <dict-tag :type="DICT_TYPE.COM_IS_INT" :value="scope.row.isEnable" /> + </template> + </el-table-column> + <el-table-column label="创建时间" header-align="center" align="center" min-width="100" prop="createTime" /> + <el-table-column label="操作" align="center" min-width="180" fixed="right"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openForm('update', scope.row.id)" + v-hasPermi="['item:accuracy-rate:update']" + > + 编辑 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['item:accuracy-rate:delete']" + > + 删除 + </el-button> + <el-button + link + type="danger" + @click="handleView(scope.row.id)" + > + 查看历史 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </ContentWrap> + + <!-- 表单弹窗:添加/修改 --> + <MmItemAccuracyRateForm ref="formRef" @success="getList" /> + <MmItemAccuracyHis ref="MessageRef" /> + +</template> +<script lang="ts" setup> +import MmItemAccuracyRateForm from './MmItemAccuracyRateForm.vue' +import MmItemAccuracyHis from './MmItemAccuracyHisForm.vue' +import * as MmItemAccuracyRate from '@/api/model/pre/accuracy/rate' +import {DICT_TYPE} from "@/utils/dict"; + +defineOptions({name: 'DataMmItemAccuracyRate'}) + +const message = useMessage() // 消息弹窗 +const {t} = useI18n() // 国际化 + +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, +}) +const queryFormRef = ref() // 搜索的表单 + +/** 查询列表 */ +const getList = async () => { + loading.value = true + try { + const page = await MmItemAccuracyRate.getMmItemAccuracyRatePage(queryParams) + list.value = page.list + total.value = page.total + } finally { + loading.value = false + } +} + +/** 添加/修改操作 */ +const formRef = ref() +const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) +} + +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await MmItemAccuracyRate.deleteMmItemAccuracyRate(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch { + } +} + +/** 查看历史按钮操作 */ +const MessageRef = ref() +const handleView = (id?: string) => { + MessageRef.value.open(id) +} + +/** 初始化 **/ +onMounted(async () => { + await getList() +}) +</script> diff --git a/src/views/model/pre/alarm/MmPredictAlarmConfigForm.vue b/src/views/model/pre/alarm/MmPredictAlarmConfigForm.vue new file mode 100644 index 0000000..c266a1f --- /dev/null +++ b/src/views/model/pre/alarm/MmPredictAlarmConfigForm.vue @@ -0,0 +1,239 @@ +<template> + <Dialog v-model="dialogVisible" :title="dialogTitle" width="50%"> + <el-form + ref="formRef" + v-loading="formLoading" + :model="formData" + :rules="formRules" + label-width="120px" + > + <el-row> + <el-col :span="12"> + <el-form-item label="消息标题" prop="title"> + <el-input v-model="formData.title" placeholder="请输入消息标题"/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="监控对象" prop="alarmObj"> + <el-input v-model="formData.alarmObj" placeholder="请输入监控对象"/> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="预测项" prop="itemId"> + <el-select + v-model="formData.itemId" + filterable + @change="handleChange(formData.itemId)" + placeholder="请选择"> + <el-option + v-for="(item, index) in predictItemList" + :key="index" + :label="item.itemname" + :value="item.id"/> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="输出" prop="outId"> + <el-select + v-model="formData.outId" + filterable + placeholder="请选择"> + <el-option + v-for="(item, index) in outPutList" + :key="index" + :label="item.tagname" + :value="item.id"/> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="比较长度" prop="compLength"> + <el-input-number v-model="formData.compLength" :min="1" clearable controls-position="right" style="width: 100%"/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="单位" prop="unit"> + <el-input v-model="formData.unit" placeholder="请输入单位"/> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="预测上限" prop="upperLimit"> + <el-input-number v-model="formData.upperLimit" clearable controls-position="right" style="width: 100%"/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="预测下限" prop="lowerLimit"> + <el-input-number v-model="formData.lowerLimit" clearable controls-position="right" style="width: 100%"/> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="累计上限" prop="culUpper"> + <el-input-number v-model="formData.culUpper" clearable controls-position="right" style="width: 100%"/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="累计下限" prop="culLower"> + <el-input-number v-model="formData.culLower" clearable controls-position="right" style="width: 100%"/> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="转换系数" prop="coefficient"> + <el-input-number v-model="formData.coefficient" :min="1" clearable controls-position="right" style="width: 100%"/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="调度方案" prop="scheduleId"> + <el-input v-model="formData.scheduleId" placeholder="请输入调度方案"/> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="12"> + <el-form-item label="是否启用" prop="isEnable"> + <el-select v-model="formData.isEnable" placeholder="请选择"> + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.COM_IS_INT)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + </el-col> + </el-row> + </el-form> + <template #footer> + <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> + <el-button @click="dialogVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script lang="ts" setup> +import * as MmPredictAlarmConfig from '@/api/model/pre/alarm/config' +import {DICT_TYPE, getIntDictOptions} from "@/utils/dict"; +import * as MmPredictItem from '@/api/model/pre/item' + +defineOptions({name: 'DataMmPredictAlarmConfigForm'}) + +const {t} = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 +const dialogVisible = ref(false) // 弹窗的是否展示 +const dialogTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const predictItemList = ref([]) +const outPutList = ref([]) +const formData = ref({ + id: undefined, + title: undefined, + alarmObj: undefined, + itemId: undefined, + itemName: undefined, + outId: undefined, + compLength: undefined, + upperLimit: undefined, + lowerLimit: undefined, + culUpper: undefined, + culLower: undefined, + unit: undefined, + coefficient: undefined, + scheduleId: undefined, + isEnable: undefined, +}) +const formRules = reactive({ + title: [{required: true, message: '消息标题不能为空', trigger: 'blur'}], + alarmObj: [{required: true, message: '监控对象不能为空', trigger: 'blur'}], + itemId: [{required: true, message: '预测项ID不能为空', trigger: 'blur'}], + outId: [{required: true, message: '输出ID不能为空', trigger: 'blur'}], +}) +const formRef = ref() // 表单 Ref + +/** 打开弹窗 */ +const open = async (type: string, id?: number) => { + dialogVisible.value = true + dialogTitle.value = t('action.' + type) + formType.value = type + resetForm() + + // 获取normal列表 + predictItemList.value = await MmPredictItem.getMmPredictItemList({ + status: 1 + }) + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + formData.value = await MmPredictAlarmConfig.getMmPredictAlarmConfig(id) + await handleChange(formData.value.itemId) + } finally { + formLoading.value = false + } + } + +} +defineExpose({open}) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as unknown as MmPredictAlarmConfig.MmPredictAlarmConfigVO + if (formType.value === 'create') { + await MmPredictAlarmConfig.createMmPredictAlarmConfig(data) + message.success(t('common.createSuccess')) + } else { + await MmPredictAlarmConfig.updateMmPredictAlarmConfig(data) + message.success(t('common.updateSuccess')) + } + dialogVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +async function handleChange(itemid) { + const queryParams = reactive({ + itemid: itemid, + }) + outPutList.value = await MmPredictItem.getMmItemOutputList(queryParams) +} +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: undefined, + title: undefined, + alarmObj: undefined, + itemId: undefined, + outId: undefined, + compLength: 1, + upperLimit: undefined, + lowerLimit: undefined, + culUpper: undefined, + culLower: undefined, + unit: undefined, + coefficient: 1, + scheduleId: undefined, + isEnable: undefined, + } + formRef.value?.resetFields() +} +</script> diff --git a/src/views/model/pre/alarm/MmPredictAlarmMessageForm.vue b/src/views/model/pre/alarm/MmPredictAlarmMessageForm.vue new file mode 100644 index 0000000..c84a693 --- /dev/null +++ b/src/views/model/pre/alarm/MmPredictAlarmMessageForm.vue @@ -0,0 +1,135 @@ +<template> + <el-drawer + v-model="drawer" + size="60%" + title="历史详情" + direction="rtl" + :before-close="handleClose" + > + <!-- 搜索工作栏 --> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="消息标题" prop="title"> + <el-input + v-model="queryParams.title" + placeholder="请输入消息标题" + clearable + class="!w-240px" + /> + </el-form-item> + <el-form-item label="监控对象" prop="alarmObj"> + <el-input + v-model="queryParams.alarmObj" + placeholder="请输入监控对象" + clearable + class="!w-240px" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"> + <Icon icon="ep:search" class="mr-5px"/> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon icon="ep:refresh" class="mr-5px"/> + 重置 + </el-button> + </el-form-item> + </el-form> + <!-- 列表 --> + <el-table + v-loading="loading" + :data="list" + row-key="id" + > + <el-table-column prop="title" label="消息标题" header-align="center" align="left" min-width="150"/> + <el-table-column prop="content" label="消息内容" header-align="center" align="left" min-width="200"/> + <el-table-column prop="alarmObj" label="监控对象" align="left" min-width="150"/> + <el-table-column prop="alarmType" label="预警类型" align="left" min-width="150"/> + <el-table-column prop="alarmTime" label="预警时间" align="left" min-width="150"/> + </el-table> + <!-- 分页 --> + <Pagination + v-model:limit="queryParams.pageSize" + v-model:page="queryParams.pageNo" + :total="total" + @pagination="getList" + /> + </el-drawer> +</template> +<script lang="ts" setup> +import * as MmPredictAlarmMessage from '@/api/model/pre/alarm/message' + +defineOptions({name: 'ChartParam'}) + +const drawer = ref(false) +const loading = ref(false) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 字典表格数据 +const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + configId: undefined, + title: undefined, + content: undefined, + alarmObj: undefined, + pointId: undefined, + itemId: undefined, + outId: undefined, + currentValue: undefined, + outTime: undefined, + outValue: undefined, + alarmType: undefined, + alarmTime: undefined, + createTime: undefined, +}) +const queryFormRef = ref() // 搜索的表单 + +const getList = async () => { + loading.value = true + try { + const data = await MmPredictAlarmMessage.getMmPredictAlarmMessagePage(queryParams) + list.value = data.list + total.value = data.total + } finally { + loading.value = false + } +} + +/** 打开弹窗 */ +const open = async (configId?: string) => { + resetForm() + drawer.value = true + queryParams.configId = configId + if (configId) { + getList() + } +} +defineExpose({open}) // 提供 open 方法,用于打开弹窗 + +/** 重置表单 */ +const resetForm = () => { + queryParams.title = undefined + queryParams.pointId = undefined +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() +} + +const handleClose = (done: () => void) => { + drawer.value = false +} +</script> diff --git a/src/views/model/pre/alarm/index.vue b/src/views/model/pre/alarm/index.vue new file mode 100644 index 0000000..651f6cc --- /dev/null +++ b/src/views/model/pre/alarm/index.vue @@ -0,0 +1,178 @@ +<template> + <!-- 搜索 --> + <ContentWrap> + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="名称" prop="title"> + <el-input + v-model="queryParams.title" + placeholder="请输入名称" + clearable + @keyup.enter="handleQuery" + class="!w-240px" + /> + </el-form-item> + <el-form-item> + <el-button @click="handleQuery"> + <Icon icon="ep:search" class="mr-5px" /> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon icon="ep:refresh" class="mr-5px" /> + 重置 + </el-button> + <el-button + type="primary" + plain + @click="openForm('create')" + v-hasPermi="['pre:alarm-config:create']" + > + <Icon icon="ep:plus" class="mr-5px" /> + 新增 + </el-button> + </el-form-item> + </el-form> + </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table v-loading="loading" :data="list"> + <el-table-column label="消息标题" header-align="center" align="left" min-width="150" prop="title" fixed/> + <el-table-column label="监控对象" header-align="center" align="center" min-width="150" prop="alarmObj" /> + <el-table-column label="预测项" header-align="center" align="left" min-width="150" prop="itemName" /> + <el-table-column label="输出" header-align="center" align="center" min-width="150" prop="outName" /> + <el-table-column label="比较长度" header-align="center" align="center" min-width="100" prop="compLength" /> + <el-table-column label="预测上限" header-align="center" align="center" min-width="100" prop="upperLimit" /> + <el-table-column label="预测下限" header-align="center" align="center" min-width="100" prop="lowerLimit" /> + <el-table-column label="累计上限" header-align="center" align="center" min-width="100" prop="culUpper" /> + <el-table-column label="累计下限" header-align="center" align="center" min-width="100" prop="culLower" /> + <el-table-column label="单位" header-align="center" align="center" min-width="100" prop="unit" /> + <el-table-column label="转换系数" header-align="center" align="center" min-width="100" prop="coefficient" /> + <el-table-column label="是否启用" header-align="center" align="center" min-width="100" prop="isEnable" > + <template #default="scope"> + <dict-tag :type="DICT_TYPE.COM_IS_INT" :value="scope.row.isEnable" /> + </template> + </el-table-column> + <el-table-column label="操作" align="center" min-width="160" fixed="right"> + <template #default="scope"> + <el-button + link + type="primary" + @click="openForm('update', scope.row.id)" + v-hasPermi="['pre:alarm-config:update']" + > + 编辑 + </el-button> + <el-button + link + type="danger" + @click="handleDelete(scope.row.id)" + v-hasPermi="['pre:alarm-config:delete']" + > + 删除 + </el-button> + <el-button + link + type="primary" + @click="handleView(scope.row.id)" + > + 记录 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </ContentWrap> + + <!-- 表单弹窗:添加/修改 --> + <MmPredictAlarmConfigForm ref="formRef" @success="getList" /> + <MmPredictAlarmMessage ref="MessageRef" /> + +</template> +<script lang="ts" setup> +import MmPredictAlarmConfigForm from './MmPredictAlarmConfigForm.vue' +import MmPredictAlarmMessage from './MmPredictAlarmMessageForm.vue' +import * as MmPredictAlarmConfig from '@/api/model/pre/alarm/config' +import {DICT_TYPE} from "@/utils/dict"; + +defineOptions({name: 'DataMmPredictAlarmConfig'}) + + const message = useMessage() // 消息弹窗 + const {t} = useI18n() // 国际化 + + const loading = ref(true) // 列表的加载中 + const total = ref(0) // 列表的总页数 + const list = ref([]) // 列表的数据 + const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + title: undefined, + }) + const queryFormRef = ref() // 搜索的表单 + + /** 查询列表 */ + const getList = async () => { + loading.value = true + try { + const page = await MmPredictAlarmConfig.getMmPredictAlarmConfigPage(queryParams) + list.value = page.list + total.value = page.total + } finally { + loading.value = false + } + } + + /** 搜索按钮操作 */ + const handleQuery = () => { + queryParams.pageNo = 1 + getList() + } + + /** 重置按钮操作 */ + const resetQuery = () => { + queryFormRef.value.resetFields() + handleQuery() + } + + /** 添加/修改操作 */ + const formRef = ref() + const openForm = (type: string, id?: number) => { + formRef.value.open(type, id) + } + + /** 删除按钮操作 */ + const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await MmPredictAlarmConfig.deleteMmPredictAlarmConfig(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch { + } + } + + /** 查看历史按钮操作 */ + const MessageRef = ref() + const handleView = (id?: string) => { + MessageRef.value.open(id) + } + + /** 初始化 **/ + onMounted(async () => { + await getList() + }) +</script> diff --git a/src/views/model/pre/analysis/index.vue b/src/views/model/pre/analysis/index.vue index ef66aba..dea20fd 100644 --- a/src/views/model/pre/analysis/index.vue +++ b/src/views/model/pre/analysis/index.vue @@ -1,7 +1,7 @@ <template> <el-card shadow="never" class="aui-card--fill"> <div class="mod-his__index"> - <el-form :inline="true" :model="formData" label-width="80px"> + <el-form :inline="true" :model="formData" label-width="70px"> <el-form-item label="开始时间"> <el-date-picker v-model="formData.startTime" @@ -33,13 +33,25 @@ </el-form-item> <el-form-item> <el-button-group> - <el-button type="primary" plain :icon="ArrowLeft" + <el-button type="primary" plain :icon="DArrowLeft" :loading="loading1" @click="leftSearchDataByRange()"/> <el-button type="primary" plain :icon="Search" :loading="loading1" @click="getList()">查询 </el-button> - <el-button type="primary" plain :icon="ArrowRight" + <el-button type="primary" plain :icon="DArrowRight" :loading="loading1" @click="rightSearchDataByRange()"/> + </el-button-group> + </el-form-item> + <el-form-item> + <el-button-group> + <el-button type="primary" plain :icon="CaretLeft" + @click="playChart(true)"/> + <el-button type="primary" plain :icon="VideoPlay" v-if="!isPlay" + @click="playHandle('play')"/> + <el-button type="primary" plain :icon="VideoPause" v-if="isPlay" + @click="playHandle('pause')"/> + <el-button type="primary" plain :icon="CaretRight" + @click="playChart(false)"/> </el-button-group> </el-form-item> @@ -154,7 +166,7 @@ </el-form> <el-form :inline="true" :model="formData" label-width="100px"> <el-row> - <el-col :span="12"> + <el-col :span="16"> <el-form-item label="数据类型"> <el-checkbox-group v-model="formData.chartCheck" @change="changeChartCheck"> <el-checkbox v-for="item in formData.chartOptions" :label="item" @@ -187,7 +199,7 @@ import {getYMDHMS} from "@/utils/dateUtil" import * as McsApi from '@/api/model/mcs' import * as echarts from "echarts"; -import {Search, ArrowLeft, ArrowRight,} from '@element-plus/icons-vue' +import {Search, DArrowLeft, DArrowRight, VideoPlay, VideoPause, CaretLeft, CaretRight} from '@element-plus/icons-vue' defineOptions({name: 'AnalysisformData'}) @@ -213,7 +225,7 @@ currentStamp60: '', predictStamp: '', chartCheck: ['T+L', '真实值'], - chartOptions: ['T+N', 'T+L', '当时', '真实值', '调整值'], + chartOptions: ['T+N', 'T+L', '当时', '真实值', '调整值', '预测累计', '真实累计'], checkedItemData: [], backItem: '', backValue: 0, @@ -223,13 +235,13 @@ queryStep: 2, isMultipleYRadio: '单坐标轴', isMultipleY: false, - predictFreq: 3, + predictFreq: 2, }) const calRateFormRef = ref() const calRateForm = ref({ calItem: undefined, - IN_DEVIATION: 0, - OUT_DEVIATION: 0, + IN_DEVIATION: 10, + OUT_DEVIATION: 50, IN_ACCURACY_RATE: 0, OUT_ACCURACY_RATE: 0, itemAvg: 0, @@ -250,6 +262,7 @@ const itemDataObject = ref() const timer = ref() let myChart = null; +const isPlay = ref(false) const formRules = reactive({ calItem: [{required: true, message: '预测项不能为空', trigger: 'blur'}], @@ -258,7 +271,7 @@ }) /** 查询列表 */ -const getList = async () => { +const getList = async (isClear = true) => { loading1.value = true try { if (!formData.value.chartCheck) { @@ -284,6 +297,22 @@ formData.value.endTime = data.endTime let xAxisData = data.categories; + let defaultYAxis = [ + { + type: 'value', + name: "累计值", + splitLine: {show: false}, + axisLine: {show: true}, + position: 'right' + }, + { + type: 'value', + name: "", + splitLine: {show: false}, + axisLine: {show: true}, + position: 'left' + } + ]; let yAxisData = []; let offset = 0; let yAxisIndex = 0; @@ -314,12 +343,27 @@ }, }); itemDataObject.value = {} + yAxisData.push({ + type: 'value', + name: "累计值", + position: 'right', + splitLine: { + show: false + }, + axisLine: { + show: true, + lineStyle: {} + }, + axisLabel: { + formatter: '{value}' + } + }) for (let i = 0; i < data.dataViewList.length; i++) { let dataView = data.dataViewList[i] itemDataObject.value[dataView.outId] = dataView; let maxValue = dataView.maxValue; let minValue = dataView.minValue; - yAxisIndex = formData.value.isMultipleY ? i : 0; + yAxisIndex = (formData.value.isMultipleY ? i : 0) + 1; let yMax = maxValue; if (maxValue < 0) { maxValue = 1; @@ -369,7 +413,7 @@ type: 'line', yAxisIndex: yAxisIndex, showSymbol: false, - smooth: true, + smooth: false, lineStyle: { width: 2 } @@ -377,13 +421,14 @@ } if (chartCheckArray.indexOf('T+N') !== -1) { let legendName = dataView.resultName + '(T+N)'; + legendData.push(legendName); seriesData.push({ name: legendName, data: dataView.preDataN || [], type: 'line', yAxisIndex: yAxisIndex, showSymbol: false, - smooth: true, + smooth: false, lineStyle: { width: 2 } @@ -399,7 +444,7 @@ showSymbol: false, connectNulls: true, yAxisIndex: yAxisIndex, - smooth: true, + smooth: false, lineStyle: { width: 2 } @@ -413,10 +458,10 @@ data: dataView.curData || [], type: 'line', yAxisIndex: yAxisIndex, - showSymbol: false, - smooth: true, + showSymbol: true, + smooth: false, lineStyle: { - width: 2 + width: 3 } }); } @@ -430,7 +475,51 @@ yAxisIndex: yAxisIndex, showSymbol: false, connectNulls: true, - smooth: true, + smooth: false, + lineStyle: { + width: 2, + type: 'dashed' + } + }); + } + + if (chartCheckArray.indexOf('预测累计') !== -1) { + let legendName = dataView.resultName + '(预测累计)'; + legendData.push(legendName); + let seriesLeiJiData = [] + if (dataView.cumulantPreData) { + seriesLeiJiData = dataView.cumulantPreData + } + seriesData.push({ + name: legendName, + data: seriesLeiJiData, + type: 'line', + yAxisIndex: 0, + showSymbol: false, + connectNulls: true, + smooth: false, + lineStyle: { + width: 2, + type: 'dashed' + } + }); + } + + if (chartCheckArray.indexOf('真实累计') !== -1) { + let legendName = dataView.resultName + '(真实累计)'; + legendData.push(legendName); + let seriesLeiJiData = [] + if (dataView.cumulantRealData) { + seriesLeiJiData = dataView.cumulantRealData + } + seriesData.push({ + name: legendName, + data: seriesLeiJiData, + type: 'line', + yAxisIndex: 0, + showSymbol: false, + connectNulls: true, + smooth: false, lineStyle: { width: 2, type: 'dashed' @@ -448,6 +537,7 @@ } } } + myChart = echarts.init(dataAnalysisChart.value); let option = { title: { @@ -473,11 +563,7 @@ boundaryGap: false, data: xAxisData }, - yAxis: formData.value.isMultipleY ? yAxisData : { - type: 'value', - splitLine: {show: false}, - axisLine: {show: true} - }, + yAxis: formData.value.isMultipleY ? yAxisData : defaultYAxis, dataZoom: [ { type: 'inside', @@ -491,11 +577,15 @@ ], series: seriesData } - myChart.clear() + if (isClear) { + myChart.clear() + } myChart.setOption(option) } finally { loading1.value = false } + + calItemBaseVale() } onMounted(() => { @@ -507,24 +597,67 @@ treeData.value = await McsApi.getPredictItemTree() } +function changeChartCheck(value) { + getList(true) +} + +function onChangeMultipleY() { + getList(true) +} + +function playChart(isBack = false) { + let mins = isBack ? formData.value.predictFreq * -1 : formData.value.predictFreq + let startTime = formData.value.startTime; + let endTime = formData.value.endTime; + let predictTime = formData.value.predictTime; + if (predictTime) { + predictTime = getYMDHMS(new Date(predictTime).getTime() + 1000 * 60 * mins); + formData.value.predictTime = predictTime; + } + if (startTime) { + startTime = getYMDHMS(new Date(startTime).getTime() + 1000 * 60 * mins); + formData.value.startTime = startTime; + } + if (endTime) { + endTime = getYMDHMS(new Date(endTime).getTime() + 1000 * 60 * mins); + formData.value.endTime = endTime; + } + getList(false); +} + +function playHandle(type) { + isPlay.value = 'play' === type + let doPlay = setInterval(function () { + if (isPlay.value) { + playChart() + } else { + clearInterval(doPlay); + } + if (new Date().getTime() - new Date(formData.value.predictTime).getTime() < 1000 * 60 ) { + isPlay.value = false + clearInterval(doPlay); + } + }, 1000) +} + function leftSearchDataByRange() { let mins = getRangeMins(); let startTime = formData.value.startTime; let endTime = formData.value.endTime; let predictTime = formData.value.predictTime; if (predictTime) { - predictTime = getYMDHMS(new Date(predictTime) - 1000 * 60 * mins); + predictTime = getYMDHMS(new Date(predictTime).getTime() - 1000 * 60 * mins); formData.value.predictTime = predictTime; } if (startTime) { - startTime = getYMDHMS(new Date(startTime) - 1000 * 60 * mins); + startTime = getYMDHMS(new Date(startTime).getTime() - 1000 * 60 * mins); formData.value.startTime = startTime; } if (endTime) { - endTime = getYMDHMS(new Date(endTime) - 1000 * 60 * mins); + endTime = getYMDHMS(new Date(endTime).getTime() - 1000 * 60 * mins); formData.value.endTime = endTime; } - getList(); + getList(false); } function getRangeMins() { @@ -541,7 +674,13 @@ function onCheckTree(data, checked, indeterminate) { formData.value.checkedItemData = []; if (checked.checkedNodes) { - formData.value.checkedItemData = [...checked.checkedNodes] + let cns = [...checked.checkedNodes] + for (let i = 0; i < cns.length; i++) { + if (cns[i].id.indexOf('-') !== -1) { + continue + } + formData.value.checkedItemData.push(cns[i]) + } } debounce(getList, 1000); } @@ -578,60 +717,59 @@ calRateForm.value.itemAvg = dataView.hisAvg; calRateForm.value.realCumulant = dataView.hisCumulant; } + calAccuracyRate() } function calAccuracyRate() { - this.$refs['calRateForm'].validate((valid) => { - if (!valid) { - return false - } - let dataView = itemDataObject[calRateForm.value.calItem] - let seriesReaData = dataView.realData; - let seriesPreData = dataView.preDataL; - if (seriesReaData == null || seriesPreData == null || - seriesReaData.length === 0 || seriesPreData.length === 0) { - loading2.value = false; - return; - } - let predictValueMap = {}; - seriesPreData.forEach(function (item) { - predictValueMap[item[0]] = item[1]; - }) - let pointValueMap = {}; - seriesReaData.forEach(function (item) { - pointValueMap[item[0]] = item[1]; - }) - let inDeviation = Number(calRateForm.value.IN_DEVIATION); - let outDeviation = Number(calRateForm.value.OUT_DEVIATION); - if (inDeviation === 0 && outDeviation === 0) { - loading2.value = false; - return; - } - let inDeviationCount = 0; - let outDeviationCount = 0; - let totalCount = 0; - for (let key in predictValueMap) { - let predictValue = predictValueMap[key]; - let pointValue = pointValueMap[key]; - if (pointValue == null || "" === pointValue || predictValue == null || "" === predictValue) { - continue; - } - let deviationAbs = (predictValue - pointValue) >= 0 ? (predictValue - pointValue) : (predictValue - pointValue) * -1; - if (deviationAbs < inDeviation) { - inDeviationCount = inDeviationCount + 1; - } - if (deviationAbs > outDeviation) { - outDeviationCount = outDeviationCount + 1; - } - totalCount = totalCount + 1; - } + const valid = calRateFormRef.value.validate() + if (!valid) return - let rateIn = (inDeviationCount / totalCount * 100).toFixed(2); - let rateOut = (outDeviationCount / totalCount * 100).toFixed(2); - calRateForm.value.IN_ACCURACY_RATE = Number(rateIn); - calRateForm.value.OUT_ACCURACY_RATE = Number(rateOut); + let dataView = itemDataObject.value[calRateForm.value.calItem] + let seriesReaData = dataView.realData; + let seriesPreData = dataView.preDataL; + if (seriesReaData == null || seriesPreData == null || + seriesReaData.length === 0 || seriesPreData.length === 0) { loading2.value = false; + return; + } + let predictValueMap = {}; + seriesPreData.forEach(function (item) { + predictValueMap[item[0]] = item[1]; }) + let pointValueMap = {}; + seriesReaData.forEach(function (item) { + pointValueMap[item[0]] = item[1]; + }) + let inDeviation = Number(calRateForm.value.IN_DEVIATION); + let outDeviation = Number(calRateForm.value.OUT_DEVIATION); + if (inDeviation === 0 && outDeviation === 0) { + loading2.value = false; + return; + } + let inDeviationCount = 0; + let outDeviationCount = 0; + let totalCount = 0; + for (let key in predictValueMap) { + let predictValue = predictValueMap[key]; + let pointValue = pointValueMap[key]; + if (pointValue == null || "" === pointValue || predictValue == null || "" === predictValue) { + continue; + } + let deviationAbs = (predictValue - pointValue) >= 0 ? (predictValue - pointValue) : (predictValue - pointValue) * -1; + if (deviationAbs < inDeviation) { + inDeviationCount = inDeviationCount + 1; + } + if (deviationAbs > outDeviation) { + outDeviationCount = outDeviationCount + 1; + } + totalCount = totalCount + 1; + } + + let rateIn = (inDeviationCount / totalCount * 100).toFixed(2); + let rateOut = (outDeviationCount / totalCount * 100).toFixed(2); + calRateForm.value.IN_ACCURACY_RATE = Number(rateIn); + calRateForm.value.OUT_ACCURACY_RATE = Number(rateOut); + loading2.value = false; } function rightSearchDataByRange() { @@ -640,18 +778,18 @@ let endTime = formData.value.endTime; let predictTime = formData.value.predictTime; if (predictTime) { - predictTime = getYMDHMS(new Date(predictTime) - 0 + 1000 * 60 * mins); + predictTime = getYMDHMS(new Date(predictTime).getTime() + 1000 * 60 * mins); formData.value.predictTime = predictTime; } if (startTime) { - startTime = getYMDHMS(new Date(startTime) - 0 + 1000 * 60 * mins); + startTime = getYMDHMS(new Date(startTime).getTime() + 1000 * 60 * mins); formData.value.startTime = startTime; } if (endTime) { - endTime = getYMDHMS(new Date(endTime) - 0 + 1000 * 60 * mins); + endTime = getYMDHMS(new Date(endTime).getTime() + 1000 * 60 * mins); formData.value.endTime = endTime; } - getList(); + getList(false); } /** 重置表单 */ @@ -671,7 +809,7 @@ currentStamp60: '', predictStamp: '', chartCheck: ['T+L', '真实值'], - chartOptions: ['T+N', 'T+L', '当时', '真实值', '调整值'], + chartOptions: ['T+N', 'T+L', '当时', '真实值', '调整值', '预测累计', '真实累计'], checkedItemData: [], backItem: '', backValue: 0, @@ -681,12 +819,12 @@ queryStep: 2, isMultipleYRadio: '单坐标轴', isMultipleY: false, - predictFreq: 3, + predictFreq: 2, } calRateForm.value = { calItem: undefined, - IN_DEVIATION: 0, - OUT_DEVIATION: 0, + IN_DEVIATION: 10, + OUT_DEVIATION: 50, IN_ACCURACY_RATE: 0, OUT_ACCURACY_RATE: 0, itemAvg: 0, diff --git a/src/views/model/pre/item/MmPredictItemChart.vue b/src/views/model/pre/item/MmPredictItemChart.vue index 4d24c06..824de21 100644 --- a/src/views/model/pre/item/MmPredictItemChart.vue +++ b/src/views/model/pre/item/MmPredictItemChart.vue @@ -167,6 +167,39 @@ }, }, }) + //累计点 + if(viewData.cumulantRealData) { + legendData.push(key + "累计:" + '真实值') + seriesData.push({ + name: key + "累计:" + '真实值', + type: "line", + data: viewData.cumulantRealData, + showSymbol: false, + smooth: false, + lineStyle: { + normal: { + width: 1, + }, + }, + yAxisIndex: 1 + }) + } + if(viewData.cumulantPreData) { + legendData.push(key + "累计:" + '预测值') + seriesData.push({ + name: key + "累计:" + '预测值', + type: "line", + data: viewData.cumulantPreData, + showSymbol: false, + smooth: false, + lineStyle: { + normal: { + width: 1, + }, + }, + yAxisIndex: 1 + }) + } }) } @@ -207,9 +240,19 @@ boundaryGap: false, data: data.categories, }, - yAxis: { - type: "value", - }, + yAxis: [ + { + type: "value", + name: '预测值/真实值' + }, + { + type: "value", + splitLine: { + show: false + }, + name: '累计值' + } + ], dataZoom: [ { type: "inside", diff --git a/src/views/model/pre/item/MmPredictItemForm.vue b/src/views/model/pre/item/MmPredictItemForm.vue index fc87750..a583075 100644 --- a/src/views/model/pre/item/MmPredictItemForm.vue +++ b/src/views/model/pre/item/MmPredictItemForm.vue @@ -1,5 +1,5 @@ <template> - <Dialog v-model="dialogVisible" :title="dialogTitle" width="60%"> + <Dialog v-model="dialogVisible" :title="dialogTitle" width="75%"> <el-form ref="formRef" v-loading="formLoading" @@ -101,31 +101,69 @@ </el-form-item> </el-col> </el-row> - <el-row v-if="dataForm.itemtypename === 'MergeItem'"> - <el-col :span="12"> - <el-form-item label="预测长度" prop="mmPredictItem.predictlength"> - <el-input - @change="clearExpressionList" - v-model="dataForm.mmPredictItem.predictlength" placeholder="预测长度" - maxlength="5"/> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="真实数据点"> - <el-select - v-model="dataForm.pointId" - filterable - clearable - placeholder="请选择"> - <el-option - v-for="item in pointList" - :key="item.id" - :label="item.pointName" - :value="item.id"/> - </el-select> - </el-form-item> - </el-col> - </el-row> + <div v-if="dataForm.itemtypename === 'MergeItem'"> + <el-row> + <el-col :span="12"> + <el-form-item label="预测长度" prop="mmPredictItem.predictlength"> + <el-input + v-model="dataForm.mmPredictItem.predictlength" placeholder="预测长度" + maxlength="5"/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="真实数据点"> + <el-select + v-model="dataForm.pointId" + filterable + clearable + placeholder="请选择"> + <el-option + v-for="item in pointList" + :key="item.id" + :label="item.pointName" + :value="item.id"/> + </el-select> + </el-form-item> + </el-col> + </el-row> + <el-divider content-position="left">累计配置</el-divider> + <el-row> + <el-col :span="6"> + <el-form-item label="是否累计" prop="iscumulant"> + <el-select v-model="dataForm.iscumulant" placeholder="请选择" @change="(value) => iscumulantChange(dataForm)"> + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.COM_IS_INT)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + </el-col> + <el-col :span="6"> + <el-form-item label="累计除数" prop="cumuldivisor"> + <el-input-number v-model="dataForm.cumuldivisor" style="width: 100%" :disabled="dataForm.iscumulant !== 1" + :min="1" :max="60" + :controls="false"/> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="累计测点"> + <el-select + v-model="dataForm.cumulpoint" :disabled="dataForm.iscumulant !== 1" + filterable + clearable + placeholder="请选择"> + <el-option + v-for="item in pointList.filter(e => e.pointType === 'CUMULATE')" + :key="item.id" + :label="item.pointName" + :value="item.id"/> + </el-select> + </el-form-item> + </el-col> + </el-row> + </div> <el-divider content-position="left" v-if="dataForm.itemtypename === 'NormalItem'">模型信息 </el-divider> <el-row :gutter="8" v-if="dataForm.itemtypename === 'NormalItem'"> @@ -275,6 +313,41 @@ </el-select> </template> </el-table-column> + <el-table-column label="是否累计" align="center" width="150px"> + <template #default="scope"> + <el-select v-model="scope.row.iscumulant" placeholder="请选择" @change="(value) => iscumulantChange(scope.row)"> + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.COM_IS_INT)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </template> + </el-table-column> + <el-table-column label="累计除数" align="center" width="100px"> + <template #default="scope"> + <el-input-number v-model="scope.row.cumuldivisor" style="width: 100%" + :min="1" :max="60" :disabled="scope.row.iscumulant !== 1" + :controls="false"/> + </template> + </el-table-column> + <el-table-column label="累计数据点" align="center"> + <template #default="scope"> + <el-select + v-model="scope.row.cumulpoint" + :disabled="scope.row.iscumulant !== 1" + filterable + clearable + placeholder="请选择"> + <el-option + v-for="item in pointList.filter(e => e.pointType === 'CUMULATE')" + :key="item.id" + :label="item.pointName" + :value="item.id"/> + </el-select> + </template> + </el-table-column> <el-table-column prop="" label="操作" width="80" align="center"> <template #default="scope"> <el-button @@ -399,11 +472,11 @@ v-model="scope.row.point" placeholder="请选择" filterable - :no-data-text="'无数据(预测长度:' + dataForm.mmPredictItem.predictlength + ';管网:' + moduleList.find(e => e.id === dataForm.dmModuleItem.moduleid)?.modulename + ')'" + :no-data-text="'无数据(管网:' + moduleList.find(e => e.id === dataForm.dmModuleItem.moduleid)?.modulename + ')'" @change="changeNormalItemSelect" style="width: 100%"> <el-option-group - v-for="group in modelparamListMap['NormalItem'].filter(e => e.predictlength == dataForm.mmPredictItem.predictlength && e.moduleid === dataForm.dmModuleItem.moduleid)" + v-for="group in modelparamListMap['NormalItem'].filter(e => e.moduleid === dataForm.dmModuleItem.moduleid)" :key="group.value" :label="group.label" > @@ -472,6 +545,7 @@ import * as DaPoint from '@/api/data/da/point' import {useUpload} from '@/api/model/pre/item' import * as ScheduleModelApi from '@/api/model/sche/model' +import {getPointSimpleList} from "@/api/data/da/point"; const {uploadUrl, httpRequest} = useUpload() @@ -518,7 +592,7 @@ predictphase: undefined, workchecked: 0, unittransfactor: undefined, - saveindex: undefined + saveindex: undefined, }, dmModuleItem: { id: undefined, @@ -558,7 +632,10 @@ }, mmModelArithSettingsList: [], mmModelParamList: [], - pointId: undefined + pointId: undefined, + iscumulant: 0, + cumuldivisor: undefined, + cumulpoint: undefined, }) const formRules = reactive({ 'mmPredictItem.itemname': [{required: true, message: '预测项名不能为空', trigger: 'blur'}], @@ -574,6 +651,7 @@ trigger: 'blur' }], 'mmPredictItem.status': [{required: true, message: '是否启用不能为空', trigger: 'blur'}], + 'iscumulant': [{required: true, message: '是否累计不能为空', trigger: 'blur'}], 'dmModuleItem.moduleid': [{required: true, message: '管网不能为空', trigger: 'blur'}], 'dmModuleItem.itemorder': [{required: true, message: '排序不能为空', trigger: 'blur'}], 'mmPredictItem.predictlength': [{required: true, message: '预测长度不能为空', trigger: 'blur'}], @@ -611,7 +689,7 @@ mpkProjectList.value = await ProjectApi.list() // 获取数据点列表 - pointNoList.value = await DaPoint.getPointList(queryParams) + pointNoList.value = await DaPoint.getPointSimpleList(queryParams) if (pointNoList.value.length > 0) { pointList.value = [] pointNoList.value.forEach(function (value) { @@ -640,6 +718,13 @@ if (!formRef) return const valid = await formRef.value.validate() if (!valid) return + //校验累计配置 + if (dataForm.value.iscumulant === 1) { + if (dataForm.value.cumuldivisor == undefined) { + message.error("累计除数不为空") + return + } + } //校验模型输出 if (dataForm.value.itemtypename === 'NormalItem') { if (dataForm.value.mmItemOutputList == undefined || dataForm.value.mmItemOutputList.length <= 0) { @@ -647,15 +732,30 @@ return } - let flag = false dataForm.value.mmItemOutputList.forEach(e => { - if (e.resultstr == undefined || e.resultstr === '' || e.resultType == undefined || e.resultType === '' || (e.resultType === 2 && (e.resultIndex == undefined || e.resultIndex === ''))) { - message.error("模型输出数据异常") - flag = true - return + if (e.resultstr == undefined || e.resultstr === '' || e.resultType == undefined || e.resultType === '' + || (e.resultType === 2 && (e.resultIndex == undefined || e.resultIndex === '')) + || (e.iscumulant === 1 && e.cumuldivisor == undefined) + ) { + message.error("输出数据异常") + throw new Error('输出数据异常'); } }) - if (flag) return + + //校验模型输入 + dataForm.value.mmModelParamList.forEach(e => { + if (e.modelparamid == undefined || e.modelparamid == '') { + message.error("输入数据异常") + throw new Error('输入数据异常'); + } + // ind_ascii类型输出的序号必须是1,且所在端口序号最大为1(一个ind_ascii类型输入独占一个端口) + if (e.modelparamtype === 'IND_ASCII') { + if (e.modelparamorder != 1 || dataForm.value.mmModelParamList.filter(p => p.modelparamportorder === e.modelparamportorder).length != 1) { + message.error("输入数据异常:IND_ASCII类型输入独占一个端口") + throw new Error('输入数据异常:IND_ASCII类型输入独占一个端口'); + } + } + }) } if (dataForm.value.itemtypename === 'MergeItem') { if (expressionList.value == undefined || expressionList.value.length <= 1) { @@ -665,7 +765,7 @@ let flag = false expressionList.value.forEach((e,index) => { - if (e.point == undefined || e.point === '' || e.operator == undefined || (e.operator === '' && index != expressionList.value.length - 1)) { + if (e.point == undefined || e.point === '' || ((e.operator == undefined || e.operator === '') && index != expressionList.value.length - 1)) { message.error("表达式数据异常") flag = true return @@ -673,7 +773,6 @@ }) if (flag) return } - // 提交请求 formLoading.value = true @@ -897,6 +996,10 @@ row.resultIndex = 0 } } +function iscumulantChange(row) { + row.cumuldivisor = undefined + row.cumulpoint = undefined +} function orderRow(rows) { let modelparamorder = 0 @@ -957,7 +1060,7 @@ predictphase: '', workchecked: 0, unittransfactor: '', - saveindex: '' + saveindex: '', }, dmModuleItem: { id: '', @@ -993,7 +1096,10 @@ itemid: '', expression: '', num: '' - } + }, + iscumulant: 0, + cumuldivisor: undefined, + cumulpoint: '', } formRef.value?.resetFields() } diff --git a/src/views/model/sche/model/ScheduleModelForm.vue b/src/views/model/sche/model/ScheduleModelForm.vue index 397123b..5cdf9e9 100644 --- a/src/views/model/sche/model/ScheduleModelForm.vue +++ b/src/views/model/sche/model/ScheduleModelForm.vue @@ -103,7 +103,7 @@ width="100" align="center"> <template #default="scope"> - <el-input v-model="scope.row.modelparamportorder" maxlength="5" clearable :disabled="true" + <el-input v-model="scope.row.modelparamportorder" maxlength="5" clearable style="width:100%; hight:100%"/> </template> </el-table-column> @@ -353,6 +353,8 @@ import { CommonStatusEnum } from '@/utils/constants' import * as MpkApi from "@/api/model/mpk/mpk"; import {generateUUID} from "@/utils"; + import { ElMessage,ElMessageBox } from 'element-plus' + import { Refresh } from '@element-plus/icons-vue' defineOptions({ name: 'ScheduleModelForm' }) @@ -375,7 +377,13 @@ resultStrId: undefined, invocation: undefined, status: CommonStatusEnum.ENABLE, - paramList: [], + paramList: [{ + modelparamportorder: 1 + '', + modelparamorder: '1', + modelparamtype: '', + modelparamid: '', + datalength: 0 + }], settingList: [], modelOut: [] }) @@ -448,6 +456,21 @@ if (!formRef) return const valid = await formRef.value.validate() if (!valid) return + //校验模型输入 + formData.value.paramList.forEach(e => { + if (e.modelparamid == undefined || e.modelparamid == '') { + message.error("输入数据异常") + throw new Error('输入数据异常'); + } + // ind_ascii类型输出的序号必须是1,且所在端口序号最大为1(一个ind_ascii类型输入独占一个端口) + if (e.modelparamtype === 'IND_ASCII') { + if (e.modelparamorder != 1 || formData.value.paramList.filter(p => p.modelparamportorder === e.modelparamportorder).length != 1) { + message.error("输入数据异常:IND_ASCII类型输入独占一个端口") + throw new Error('输入数据异常:IND_ASCII类型输入独占一个端口'); + } + } + }) + // 提交请求 formLoading.value = true try { @@ -482,7 +505,13 @@ resultStrId: undefined, invocation: undefined, status: CommonStatusEnum.ENABLE, - paramList: [], + paramList: [{ + modelparamportorder: 1 + '', + modelparamorder: '1', + modelparamtype: '', + modelparamid: '', + datalength: 0 + }], settingList: [], modelOut: [] } @@ -511,23 +540,39 @@ const changeModel = async () => { // 校验 if (model.value && model.value.length > 0) { - const modelInfo = model.value[0] - const methodInfo = model.value[1] - formData.value.modelName = modelInfo.pyChineseName - formData.value.className = modelInfo.pkgName + '.impl.' + modelInfo.pyName + 'Impl'; - formData.value.methodName = methodInfo.methodName - formData.value.portLength = methodInfo.dataLength - // 参数构造 - let paramStructure = [] - for (let i = 0; i < methodInfo.dataLength; i++) { - paramStructure.push('[[D') - } - if (methodInfo.model === 1) { - paramStructure.push('java.util.HashMap') - } + ElMessageBox.confirm( + '是否更新输入参数?', + '提示', + {confirmButtonText: '是', cancelButtonText: '否', type: 'success',icon: markRaw(Refresh),closeOnClickModal:false,closeOnPressEscape:false} + ).then(() => { + relevanceModel(true) + }).catch(() => { + relevanceModel(false) + }) + }else { + message.error("请先选择模型") + } + } + + function relevanceModel(refreshParam) { + const modelInfo = model.value[0] + const methodInfo = model.value[1] + formData.value.modelName = modelInfo.pyChineseName + formData.value.className = modelInfo.pkgName + '.impl.' + modelInfo.pyName + 'Impl'; + formData.value.methodName = methodInfo.methodName + formData.value.portLength = methodInfo.dataLength + // 参数构造 + let paramStructure = [] + for (let i = 0; i < methodInfo.dataLength; i++) { + paramStructure.push('[[D') + } + if (methodInfo.model === 1) { paramStructure.push('java.util.HashMap') - formData.value.paramStructure = paramStructure.join(',') - formData.value.modelPath = modelInfo.pyModule + } + paramStructure.push('java.util.HashMap') + formData.value.paramStructure = paramStructure.join(',') + formData.value.modelPath = modelInfo.pyModule + if (refreshParam) { // 输入参数 let paramList = [] for (let i = 0; i < methodInfo.dataLength; i++) { @@ -539,23 +584,20 @@ datalength: 0 }) } - formData.value.paramList = paramList - // 设置参数 - let settingList = [] - methodInfo.methodSettings.forEach(e => { - settingList.push({ - key: e.settingKey, - value: e.value, - valuetype: e.valueType, - name: e.name - }) - }) - formData.value.settingList = settingList - modelPopover.value.hide() - }else { - message.error("请先选择模型") } + // 设置参数 + let settingList = [] + methodInfo.methodSettings.forEach(e => { + settingList.push({ + key: e.settingKey, + value: e.value, + valuetype: e.valueType, + name: e.name + }) + }) + formData.value.settingList = settingList + modelPopover.value.hide() } function changeModelparamtype(row) { diff --git a/src/views/model/sche/scheme/record/index.vue b/src/views/model/sche/scheme/record/index.vue index 4d74cda..5175503 100644 --- a/src/views/model/sche/scheme/record/index.vue +++ b/src/views/model/sche/scheme/record/index.vue @@ -47,7 +47,7 @@ </ContentWrap> <!-- 列表 --> <ContentWrap> - <el-table v-loading="loading" :data="list"> + <el-table v-loading="loading" :data="list" max-height="300px"> <el-table-column prop="scheduleTime" label="调度时间" diff --git a/src/views/system/loginlog/LoginLogDetail.vue b/src/views/system/loginlog/LoginLogDetail.vue index ff49453..7d58978 100644 --- a/src/views/system/loginlog/LoginLogDetail.vue +++ b/src/views/system/loginlog/LoginLogDetail.vue @@ -16,7 +16,7 @@ <el-descriptions-item label="浏览器"> {{ detailData.userAgent }} </el-descriptions-item> - <el-descriptions-item label="登陆结果"> + <el-descriptions-item label="登录结果"> <dict-tag :type="DICT_TYPE.SYSTEM_LOGIN_RESULT" :value="detailData.result" /> </el-descriptions-item> <el-descriptions-item label="登录日期"> diff --git a/src/views/system/loginlog/index.vue b/src/views/system/loginlog/index.vue index 011992b..4c442b8 100644 --- a/src/views/system/loginlog/index.vue +++ b/src/views/system/loginlog/index.vue @@ -45,7 +45,7 @@ plain @click="handleExport" :loading="exportLoading" - v-hasPermi="['infra:login-log:export']" + v-hasPermi="['system:login-log:export']" > <Icon icon="ep:download" class="mr-5px" /> 导出 </el-button> @@ -56,16 +56,16 @@ <!-- 列表 --> <ContentWrap> <el-table v-loading="loading" :data="list"> - <el-table-column label="日志编号" align="center" prop="id" /> - <el-table-column label="操作类型" align="center" prop="logType"> + <el-table-column label="日志编号" align="center" prop="id" width="100" /> + <el-table-column label="操作类型" align="center" prop="logType" width="100"> <template #default="scope"> <dict-tag :type="DICT_TYPE.SYSTEM_LOGIN_TYPE" :value="scope.row.logType" /> </template> </el-table-column> <el-table-column label="用户名称" align="center" prop="username" width="180" /> <el-table-column label="登录地址" align="center" prop="userIp" width="180" /> - <el-table-column label="浏览器" align="center" prop="userAgent" /> - <el-table-column label="登陆结果" align="center" prop="result"> + <el-table-column label="浏览器" align="center" prop="userAgent" :show-overflow-tooltip="true"/> + <el-table-column label="登录结果" align="center" prop="result" width="100"> <template #default="scope"> <dict-tag :type="DICT_TYPE.SYSTEM_LOGIN_RESULT" :value="scope.row.result" /> </template> @@ -77,13 +77,13 @@ width="180" :formatter="dateFormatter" /> - <el-table-column label="操作" align="center"> + <el-table-column label="操作" align="center" width="80"> <template #default="scope"> <el-button link type="primary" @click="openDetail(scope.row)" - v-hasPermi="['infra:login-log:query']" + v-hasPermi="['system:login-log:query']" > 详情 </el-button> diff --git a/src/views/system/operatelog/index.vue b/src/views/system/operatelog/index.vue index bd67305..dc19764 100644 --- a/src/views/system/operatelog/index.vue +++ b/src/views/system/operatelog/index.vue @@ -79,7 +79,7 @@ plain @click="handleExport" :loading="exportLoading" - v-hasPermi="['infra:operate-log:export']" + v-hasPermi="['system:operate-log:export']" > <Icon icon="ep:download" class="mr-5px" /> 导出 </el-button> @@ -110,7 +110,7 @@ link type="primary" @click="openDetail(scope.row)" - v-hasPermi="['infra:operate-log:query']" + v-hasPermi="['system:operate-log:query']" > 详情 </el-button> -- Gitblit v1.9.3