liriming
2025-03-03 8bb7160c9c4fd7ce5893ee673647b13cc35410ae
Merge remote-tracking branch 'origin/master'
已修改34个文件
已删除8个文件
已添加32个文件
6623 ■■■■ 文件已修改
src/api/data/arc/data.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/data/arc/index.ts 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/data/da/point/daPointChart.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/data/ind/item/item.ts 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/infra/monitordisk/index.ts 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/infra/monitormem/index.ts 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/model/matlab/mlModel.ts 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/model/matlab/project.ts 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/model/mpk/mpk.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/model/pre/accuracy/his.ts 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/model/pre/accuracy/rate.ts 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/model/pre/alarm/config.ts 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/model/pre/alarm/message.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/model/pre/item/index.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/model/sche/model/index.ts 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/svgs/member_balance.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/svgs/member_expenditure_balance.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/svgs/member_level.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/svgs/member_point.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/svgs/member_recharge_balance.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/svgs/money.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/svgs/shopping.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/MonitorDiskPie/PieChart.vue 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/modules/remaining.ts 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/mall/kefu.ts 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/dict.ts 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/download.ts 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/arc/ArcData.vue 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/arc/ArcSettingForm.vue 119 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/arc/index.vue 307 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/ind/data/DataSetForm.vue 47 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/ind/item/AtomIndDefineForm.vue 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/ind/item/CalIndDefineForm.vue 58 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/ind/item/DerIndDefineForm.vue 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/ind/item/IndCurrentData.vue 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/ind/item/IndHistoryChart.vue 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/ind/item/index.vue 250 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/plan/data/DataSetForm.vue 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/point/DaPointChart.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/point/DaPointForm.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/point/DaPointValue.vue 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/point/index.vue 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/apiAccessLog/index.vue 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/apiErrorLog/index.vue 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/monitor/components/MonitorDisk.vue 487 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/monitor/components/MonitorDiskForm.vue 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/monitor/components/MonitorMem.vue 478 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/monitor/components/MonitorMemForm.vue 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/monitor/components/index.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/monitor/index.vue 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/storage/index_rec.vue 161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/matlab/model/MatlabModelForm.vue 394 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/matlab/model/MatlabModelSettingForm.vue 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/matlab/model/MatlabRun.vue 279 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/matlab/model/index.vue 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/matlab/project/MatlabProjectForm.vue 156 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/matlab/project/MatlabProjectModelDialog.vue 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/matlab/project/index.vue 221 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/mpk/file/MpkRun.vue 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/mpk/file/index.vue 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/pre/accuracy/MmItemAccuracyHisForm.vue 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/pre/accuracy/MmItemAccuracyRateForm.vue 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/pre/accuracy/index.vue 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/pre/alarm/MmPredictAlarmConfigForm.vue 239 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/pre/alarm/MmPredictAlarmMessageForm.vue 135 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/pre/alarm/index.vue 178 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/pre/analysis/index.vue 308 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/pre/item/MmPredictItemChart.vue 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/pre/item/MmPredictItemForm.vue 188 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/sche/model/ScheduleModelForm.vue 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/sche/scheme/record/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/loginlog/LoginLogDetail.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/loginlog/index.vue 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/operatelog/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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 })
}
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 })
}
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 })
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})
}
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 })
  },
}
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 })
  },
}
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})
}
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 })
}
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})
}
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 })
}
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`})
}
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`})
}
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 })
}
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 })
}
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,
  }
}
src/assets/svgs/member_balance.svg
文件已删除
src/assets/svgs/member_expenditure_balance.svg
文件已删除
src/assets/svgs/member_level.svg
文件已删除
src/assets/svgs/member_point.svg
文件已删除
src/assets/svgs/member_recharge_balance.svg
文件已删除
src/assets/svgs/money.svg
文件已删除
src/assets/svgs/shopping.svg
文件已删除
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>
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
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)
}
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'
}
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
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) => {
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()
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>
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
    }
  }
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
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) {
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'
    })
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>
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>
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>
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
  }
}
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"
src/views/data/point/DaPointForm.vue
@@ -25,6 +25,7 @@
            <el-select
              v-model="formData.pointType"
              clearable
              :disabled = "formType !== 'create'"
              placeholder="请选择测点类型"
            >
              <el-option
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>
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()
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>
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"
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 }}&nbsp;&nbsp;&nbsp;&nbsp;主机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 }} &nbsp;&nbsp;&nbsp;&nbsp;主机名: {{ host.name }}&nbsp;&nbsp;&nbsp;&nbsp;-->
<!--          服务器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>
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>
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>
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>
src/views/infra/monitor/components/index.ts
对比新文件
@@ -0,0 +1,3 @@
import MonitorMem from './MonitorMem.vue'
import MonitorDisk from './MonitorDisk.vue'
export { MonitorMem, MonitorDisk }
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>
src/views/infra/storage/index_rec.vue
文件已删除
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>
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>
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>
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>
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>
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>
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>
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>
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">
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>
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>
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>
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>
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>
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>
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,
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",
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()
}
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) {
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="调度时间"
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="登录日期">
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>
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>