22 Commits

Author SHA1 Message Date
guanj
4f32f84132 修改项目树问题 绘制稳态治理分析页面 2026-06-04 09:08:37 +08:00
guanj
c2805d7e9e 联调电镀数据功能 2026-06-02 16:09:21 +08:00
guanj
7deafa6d69 联调电镀数据页面 2026-06-01 20:35:26 +08:00
guanj
c2f23aa957 修改测试问题 2026-05-28 20:36:49 +08:00
guanj
9466141bff 去除部门树接口 2026-05-28 15:10:40 +08:00
guanj
faac12615d 提交 2026-05-26 16:23:18 +08:00
guanj
8b80e0678f 提交代码 2026-04-25 15:22:28 +08:00
guanj
7abcdb3a6b 联调程序升级 2026-04-24 09:13:48 +08:00
guanj
c8a42948de 修改台账 2026-04-20 09:28:04 +08:00
guanj
99bc99a6fc 绘制稳态事件配置页面 2026-04-17 08:49:22 +08:00
guanj
01a28d88f3 修改台账 2026-04-15 19:29:36 +08:00
guanj
632a0104fb 调整台账录入页面 2026-04-15 13:44:28 +08:00
guanj
cfcbfc45d6 修改台账 2026-04-13 10:48:32 +08:00
guanj
2601068a55 1 2026-04-08 15:51:46 +08:00
guanj
3ffb11defa 调整台账 2026-04-03 14:47:36 +08:00
guanj
0b9aafc1b5 联调文件管理页面 2026-04-02 09:08:57 +08:00
guanj
762965b1e4 联调设备文件 2026-03-30 09:03:53 +08:00
guanj
a30379ab01 绘制 运维版本管理页面 2026-03-19 11:29:26 +08:00
dk
9f1fbf93cd 新增实时运维页面 2026-03-18 21:06:48 +08:00
dk
b5fc946ce2 测试修改提交 2026-03-17 14:32:14 +08:00
guanj
1171d37a86 添加工程树 2026-03-06 09:36:42 +08:00
guanj
3fdb41c468 修改测试bug 2026-02-04 09:35:24 +08:00
135 changed files with 11622 additions and 7047 deletions

2
.gitignore vendored
View File

@@ -23,3 +23,5 @@ dist-ssr
*.sln *.sln
*.sw? *.sw?
pnpm-lock.yaml pnpm-lock.yaml
#test

View File

@@ -0,0 +1,42 @@
import createAxios from '@/utils/request'
/**
* 删除数据合理范围
**/
export const pqDelete = (data: any) => {
return createAxios({
url: '/algorithm-boot/pqReasonableRange/delete',
method: 'post',
params: data
})
}
/**
* 按条件获取数据合理范围
**/
export const getData = (data: any) => {
return createAxios({
url: '/algorithm-boot/pqReasonableRange/getData',
method: 'post',
data
})
}
/**
* 新增数据合理范围
**/
export const save = (data: any) => {
return createAxios({
url: '/algorithm-boot/pqReasonableRange/save',
method: 'post',
data
})
}
/**
* 更新数据合理范围
**/
export const update = (data: any) => {
return createAxios({
url: '/algorithm-boot/pqReasonableRange/update',
method: 'post',
data
})
}

View File

@@ -1,144 +1,153 @@
import createAxios from '@/utils/request' import createAxios from '@/utils/request'
// 装置基础数据和模板数据 // 设备基础数据和模板数据
export function getDeviceData(deviceId: string, type: string, lineId: string) { export function getDeviceData(deviceId: string, type: string, lineId: string) {
let form = new FormData() let form = new FormData()
form.append('deviceId', deviceId) form.append('deviceId', deviceId)
form.append('lineId', lineId) form.append('lineId', lineId)
form.append('type', type) form.append('type', type)
return createAxios({ return createAxios({
url: '/cs-device-boot/EquipmentDelivery/deviceData', url: '/cs-device-boot/EquipmentDelivery/deviceData',
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded' 'Content-Type': 'application/x-www-form-urlencoded'
}, },
data: form data: form
}) })
} }
//获取趋势数据、暂态数据、实时数据 //获取趋势数据、暂态数据、实时数据
export function getTabsDataByType(data: any) { export function getTabsDataByType(data: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/csGroup/deviceDataByType', url: '/cs-device-boot/csGroup/deviceDataByType',
method: 'POST', method: 'POST',
data data
}) })
} }
/**** 获取基础实施数据 ****/ /**** 获取基础实施数据 ****/
export function getBasicRealData(id: any) { export function getBasicRealData(id: any) {
return createAxios({ return createAxios({
url: `/cs-harmonic-boot/realData/getBaseRealData?lineId=${id}`, url: `/cs-harmonic-boot/realData/getBaseRealData?lineId=${id}`,
method: 'POST' method: 'POST'
}) })
} }
/**** 获取谐波实时数据 ****/ /**** 获取谐波实时数据 ****/
export function getHarmRealData(id: any, target: any) { export function getHarmRealData(id: any, target: any) {
return createAxios({ return createAxios({
url: `/cs-harmonic-boot/realData/getHarmRealData?lineId=${id}&target=${target}`, url: `/cs-harmonic-boot/realData/getHarmRealData?lineId=${id}&target=${target}`,
method: 'POST' method: 'POST'
}) })
} }
/**** 获取国标限值 ****/ /**** 获取国标限值 ****/
export function getOverLimitData(id: any) { export function getOverLimitData(id: any) {
return createAxios({ return createAxios({
url: `/cs-device-boot/csline/getOverLimitData?id=${id}`, url: `/cs-device-boot/csline/getOverLimitData?id=${id}`,
method: 'POST' method: 'POST'
}) })
} }
//获取实时数据列表数据 //获取实时数据列表数据
export function getRealTimeTableList() { export function getRealTimeTableList() {
return createAxios({ return createAxios({
url: '/cs-device-boot/csGroup/getGroupPortableStatistical', url: '/cs-device-boot/csGroup/getGroupPortableStatistical',
method: 'GET' method: 'GET'
}) })
} }
//离线数据导入 //离线数据导入
export function uploadOffLineDataFile(data: any) { export function uploadOffLineDataFile(data: any) {
return createAxios({ return createAxios({
headers: { headers: {
'Content-Type': 'multipart/form-data' 'Content-Type': 'multipart/form-data'
}, },
url: '/cs-device-boot/portableOfflLog/importEquipment', url: '/cs-device-boot/portableOfflLog/importEquipment',
method: 'POST', method: 'POST',
data data
}) })
} }
//查询实时数据中实时趋势中指标分组 //查询实时数据中实时趋势中指标分组
export function getDeviceTrendDataGroup() { export function getDeviceTrendDataGroup() {
return createAxios({ return createAxios({
url: '/cs-device-boot/csGroup/getDeviceTrendDataGroup', url: '/cs-device-boot/csGroup/getDeviceTrendDataGroup',
method: 'GET' method: 'GET'
}) })
} }
//根据指标分组查询实时数据中实时趋势 //根据指标分组查询实时数据中实时趋势
export function getDeviceTrendData(query: any) { export function getDeviceTrendData(query: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/csGroup/getDeviceTrendData', url: '/cs-device-boot/csGroup/getDeviceTrendData',
method: 'GET', method: 'GET',
params: query params: query
}) })
} }
//查询实时数据-谐波频谱-稳态指标 //查询实时数据-谐波频谱-稳态指标
export function getGroupPortableStatistical() { export function getGroupPortableStatistical() {
return createAxios({ return createAxios({
url: '/cs-device-boot/csGroup/getGroupPortableStatistical', url: '/cs-device-boot/csGroup/getGroupPortableStatistical',
method: 'GET' method: 'GET'
}) })
} }
//查询实时数据-谐波频谱 //查询实时数据-谐波频谱
export function getDeviceHarmonicSpectrumData(data: any) { export function getDeviceHarmonicSpectrumData(data: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/csGroup/getDeviceHarmonicSpectrumData', url: '/cs-device-boot/csGroup/getDeviceHarmonicSpectrumData',
method: 'POST', method: 'POST',
data: data data: data
}) })
} }
//获取指标类型-谐波频谱 //获取指标类型-谐波频谱
export function queryDictType(data?: any) { export function queryDictType(data?: any) {
return createAxios({ return createAxios({
url: '/system-boot/dictTree/queryDictType', url: '/system-boot/dictTree/queryDictType',
method: 'GET', method: 'GET',
params: data params: data
}) })
} }
//根据监测点id获取监测点详情 //根据监测点id获取监测点详情
export function getById(data?: any) { export function getById(data?: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/csline/getById', url: '/cs-device-boot/csline/getById',
method: 'POST', method: 'POST',
params: data params: data
}) })
} }
//测试项日志修改 //测试项日志修改
export function updateRecordData(data?: any) { export function updateRecordData(data?: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/wlRecord/updateRecordData', url: '/cs-device-boot/wlRecord/updateRecordData',
method: 'POST', method: 'POST',
data data
}) })
} }
//模块数据 //模块数据
export function allModelData(data?: any) { export function allModelData(data?: any) {
return createAxios({ return createAxios({
url: '/cs-harmonic-boot/data/allModelData', url: '/cs-harmonic-boot/data/allModelData',
method: 'POST', method: 'POST',
data data
}) })
} }
//刷新状态 //刷新状态
export function getModuleState(data?: any) { export function getModuleState(data?: any) {
return createAxios({ return createAxios({
url: '/cs-harmonic-boot/data/getModuleState', url: '/cs-harmonic-boot/data/getModuleState',
method: 'POST', method: 'POST',
params: data params: data
}) })
} }
//获取运行取数
export function getRawData(data?: any) {
return createAxios({
url: '/cs-device-boot/pqsCommunicate/getRawData',
method: 'POST',
data
})
}

View File

@@ -1,4 +1,4 @@
import createAxios from "@/utils/request"; import createAxios from '@/utils/request'
//根据Id获取台账信息 //根据Id获取台账信息
export function getInfoById(id: any) { export function getInfoById(id: any) {
@@ -11,7 +11,6 @@ export function getInfoById(id: any) {
}) })
} }
//工程查询通过id获取 //工程查询通过id获取
export function getEngineerById(id: any) { export function getEngineerById(id: any) {
let form = new FormData() let form = new FormData()
@@ -23,7 +22,6 @@ export function getEngineerById(id: any) {
}) })
} }
//项目查询通过id获取 //项目查询通过id获取
export function getProjectById(id: any) { export function getProjectById(id: any) {
let form = new FormData() let form = new FormData()
@@ -53,7 +51,7 @@ export function getById(id: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/csline/getById', url: '/cs-device-boot/csline/getById',
method: 'POST', method: 'POST',
data: form data: form
}) })
} }
@@ -75,13 +73,15 @@ export function addLedger(data: any) {
} }
//修改-删除项目 //修改-删除项目
export function deleteProject(id: any,name:any,area:any,description:any,status:any) { export function deleteProject(id: any, name: any, area: any, description: any, status: any, sort: any, topoIds: any) {
let form = new FormData() let form = new FormData()
form.append('id', id) form.append('id', id)
form.append('name', name) form.append('name', name)
form.append('area', area) form.append('area', area)
form.append('description', description) form.append('description', description)
form.append('status', status) form.append('status', status)
form.append('sort', sort)
form.append('topoIds', topoIds)
return createAxios({ return createAxios({
url: '/cs-device-boot/project/auditAppProject', url: '/cs-device-boot/project/auditAppProject',
method: 'post', method: 'post',
@@ -105,7 +105,7 @@ export const deleteLine = (id: any) => {
let form = new FormData() let form = new FormData()
form.append('id', id) form.append('id', id)
return createAxios({ return createAxios({
url: '/cs-device-boot/csline/delCldLine', url: '/cs-device-boot/csline/delCldLine',
method: 'POST', method: 'POST',
data: form data: form
}) })
@@ -120,7 +120,6 @@ export function updateEquipment(data: any) {
}) })
} }
//修改监测点 //修改监测点
export function updateLine(data: any) { export function updateLine(data: any) {
return createAxios({ return createAxios({
@@ -134,8 +133,7 @@ export function updateLine(data: any) {
export function pushLog() { export function pushLog() {
return createAxios({ return createAxios({
url: '/cs-device-boot/csTerminalLogs/pushCldInfo', url: '/cs-device-boot/csTerminalLogs/pushCldInfo',
method: 'post', method: 'post'
}) })
} }
@@ -143,7 +141,14 @@ export function pushLog() {
export function queryPushResult() { export function queryPushResult() {
return createAxios({ return createAxios({
url: '/cs-device-boot/csTerminalReply/queryData', url: '/cs-device-boot/csTerminalReply/queryData',
method: 'post', method: 'post'
}) })
} }
//查询升级日志
export function getByDevId(data: any) {
return createAxios({
url: '/cs-device-boot/csUpgradeLogs/getByDevId',
method: 'get',
params:data
})
}

View File

@@ -1,86 +1,86 @@
import createAxios from '@/utils/request' import createAxios from '@/utils/request'
// 查询分组 // 查询分组
export function getGroup(dataSet: string) { export function getGroup(dataSet: string) {
let form = new FormData() let form = new FormData()
form.append('dataSet', dataSet) form.append('dataSet', dataSet)
return createAxios({ return createAxios({
url: '/cs-device-boot/csGroup/getGroup', url: '/cs-device-boot/csGroup/getGroup',
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded' 'Content-Type': 'application/x-www-form-urlencoded'
}, },
data: form data: form
}) })
} }
// 装置分组实时数据 // 设备分组实时数据
export function deviceHisData(data: any) { export function deviceHisData(data: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/csGroup/deviceHistoryData', url: '/cs-device-boot/csGroup/deviceHistoryData',
method: 'POST', method: 'POST',
data: Object.assign( data: Object.assign(
{ {
endTime: '', endTime: '',
id: '', id: '',
lineId: '', lineId: '',
pageNum: 1, pageNum: 1,
pageSize: 20, pageSize: 20,
startTime: '' startTime: ''
}, },
data data
) )
}) })
} }
// 装置分组历史数据 // 设备分组历史数据
export function deviceRtData(data: any) { export function deviceRtData(data: any) {
let form = new FormData() let form = new FormData()
form.append('id', data.id) form.append('id', data.id)
form.append('lineId', data.lineId) form.append('lineId', data.lineId)
form.append('pageNum', data.pageNum) form.append('pageNum', data.pageNum)
form.append('pageSize', data.pageSize) form.append('pageSize', data.pageSize)
form.append('searchValue', data.searchValue) form.append('searchValue', data.searchValue)
form.append('dataLevel', data.dataLevel) form.append('dataLevel', data.dataLevel)
return createAxios({ return createAxios({
url: '/cs-device-boot/csGroup/deviceRtData', url: '/cs-device-boot/csGroup/deviceRtData',
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded' 'Content-Type': 'application/x-www-form-urlencoded'
}, },
data: form data: form
}) })
} }
// 装置分组历史数据 // 设备分组历史数据
export function realTimeData(data: any) { export function realTimeData(data: any) {
let form = new FormData() let form = new FormData()
form.append('id', data.id) form.append('id', data.id)
form.append('lineId', data.lineId) form.append('lineId', data.lineId)
form.append('pageNum', data.pageNum) form.append('pageNum', data.pageNum)
form.append('pageSize', data.pageSize) form.append('pageSize', data.pageSize)
form.append('searchValue', data.searchValue) form.append('searchValue', data.searchValue)
form.append('targetType', data.targetType) form.append('targetType', data.targetType)
form.append('dataLevel', data.dataLevel) form.append('dataLevel', data.dataLevel)
return createAxios({ return createAxios({
url: '/cs-harmonic-boot/data/realTimeData', url: '/cs-harmonic-boot/data/realTimeData',
method: 'POST', method: 'POST',
data data
}) })
} }
// 设备监控-》测试项数据 // 设备监控-》测试项数据
export function getTestData(data: any) { export function getTestData(data: any) {
return createAxios({ return createAxios({
url: '/cs-harmonic-boot/data/getTestData', url: '/cs-harmonic-boot/data/getTestData',
method: 'POST', method: 'POST',
data data
}) })
} }
// 设备监控-删除装置测试项 // 设备监控-删除设备测试项
export function deleteItem(data: any) { export function deleteItem(data: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/wlRecord/deleteItem', url: '/cs-device-boot/wlRecord/deleteItem',
method: 'POST', method: 'POST',
params: data params: data
}) })
} }

View File

@@ -1,18 +1,20 @@
import createAxios from '@/utils/request' import createAxios from '@/utils/request'
// 设备列表 // 设备列表
export function getDeviceTree() { export function getDeviceTree(params?: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/csLedger/deviceTree', url: '/cs-device-boot/csLedger/deviceTree',
method: 'POST' method: 'POST',
params
}) })
} }
// 监测点列表 // 监测点列表
export function getLineTree() { export function getLineTree(params?: any) {
return createAxios({ return createAxios({
url: '/cs-device-boot/csLedger/lineTree', url: '/cs-device-boot/csLedger/lineTree',
method: 'POST' method: 'POST',
params
}) })
} }
// 监测点列表治理 // 监测点列表治理

View File

@@ -79,4 +79,12 @@ export const addProject = (data: any) => {
data: data data: data
}) })
} }
// 修改项目
export const updateProjects = (data: any) => {
return request({
url: '/cs-device-boot/engineeringProjectRelation/updateProject',
method: 'post',
data: data
})
}

View File

@@ -1,93 +1,140 @@
import createAxios from '@/utils/request' import createAxios from '@/utils/request'
// 设备文件根目录查询 // 设备文件根目录查询
export function getDeviceRootPath(nDid) { export function getDeviceRootPath(nDid) {
return createAxios({ return createAxios({
url: '/cs-device-boot/deviceFile/askDeviceRootPath?nDid=' + nDid, url: '/cs-device-boot/deviceFile/askDeviceRootPath?nDid=' + nDid,
method: 'POST' method: 'POST'
}) })
} }
// 设备文件-目录信息询问 // 设备文件-目录信息询问
export function getFileServiceFileOrDir(data) { export function getFileServiceFileOrDir(data) {
return createAxios({ return createAxios({
url: `cs-device-boot/deviceFile/askDeviceFileOrDir?nDid=${data.nDid}&name=${data.name}&type=${data.type}`, url: `cs-device-boot/deviceFile/askDeviceFileOrDir?nDid=${data.nDid}&name=${data.name}&type=${data.type}`,
method: 'POST' method: 'POST'
}) })
} }
// 监测设备-目录信息询问
//设备文件下载 export function listDir(data) {
export function downLoadDeviceFile(data) { return createAxios({
return createAxios({ url: `/zl-event-boot/file/listDir`,
url: `/cs-device-boot/deviceFile/downloadFile?nDid=${data.nDid}&name=${data.name}&fileCheck=${data.fileCheck}&size=${data.size}`, method: 'POST',
method: 'POST' data: data
}) })
} }
// 下载文件
//获取下载文件的文件路径地址 export function downloadFileFromFrontr(data: any) {
export function downLoadDeviceFilePath(obj) { return createAxios({
let form = new FormData() url: `/zl-event-boot/file/downloadFileFromFront`,
form.append('name', obj.name) method: 'POST',
form.append('nDid', obj.nDid) data: data,
return createAxios({ responseType: 'blob'
url: `/cs-device-boot/deviceFile/getDownloadFilePath`, })
method: 'POST', }
headers: { // 删除文件
'Content-Type': 'application/x-www-form-urlencoded' export function deleteCld(data: any) {
}, return createAxios({
data: form url: `/zl-event-boot/file/delete`,
}) method: 'POST',
} data: data
//装置重启 })
export function reStartDevice(data) { }
return createAxios({ // 新建文件
url: `/cs-device-boot/EquipmentDelivery/rebootDevice?nDid=${data.nDid}`, export function mkdir(data: any) {
method: 'POST' return createAxios({
}) url: `/zl-event-boot/file/mkdir`,
} method: 'POST',
data: data
//上传文件至装置 })
export function uploadDeviceFile(data) { }
let form = new FormData() // 上传文件
form.append('file', data.file) export function uploadFileToFront(obj: any) {
form.append('filePath', data.filePath) let form = new FormData()
form.append('id', data.id) form.append('file', obj.file)
return createAxios({ form.append('devId', obj.devId)
url: `/access-boot/analyzeModel/uploadDevFile`, form.append('dirPath', obj.dirPath)
method: 'POST', return createAxios({
headers: { url: `/zl-event-boot/file/uploadFileToFront`,
'Content-Type': 'application/x-www-form-urlencoded' method: 'POST',
}, headers: {
data: form 'Content-Type': 'application/x-www-form-urlencoded'
}) },
} data: form
})
//新建文件夹目录 }
export function addDeviceDir(data) { //设备文件下载
let form = new FormData() export function downLoadDeviceFile(data) {
form.append('nDid', data.nDid) return createAxios({
form.append('path', data.path) url: `/cs-device-boot/deviceFile/downloadFile?nDid=${data.nDid}&name=${data.name}&fileCheck=${data.fileCheck}&size=${data.size}`,
return createAxios({ method: 'POST'
url: `/access-boot/askDeviceData/createFolder`, })
method: 'POST', }
headers: {
'Content-Type': 'application/x-www-form-urlencoded' //获取下载文件的文件路径地址
}, export function downLoadDeviceFilePath(obj) {
data: form let form = new FormData()
}) form.append('name', obj.name)
} form.append('nDid', obj.nDid)
return createAxios({
//删除文件/文件夹 url: `/cs-device-boot/deviceFile/getDownloadFilePath`,
export function delDeviceDir(data) { method: 'POST',
let form = new FormData() headers: {
form.append('nDid', data.nDid) 'Content-Type': 'application/x-www-form-urlencoded'
form.append('path', data.path) },
return createAxios({ data: form
url: `/access-boot/askDeviceData/deleteFolder`, })
method: 'POST', }
headers: { //设备重启
'Content-Type': 'application/x-www-form-urlencoded' export function reStartDevice(data) {
}, return createAxios({
data: form url: `/cs-device-boot/EquipmentDelivery/rebootDevice?nDid=${data.nDid}`,
}) method: 'POST'
} })
}
//上传文件至设备
export function uploadDeviceFile(data) {
let form = new FormData()
form.append('file', data.file)
form.append('filePath', data.filePath)
form.append('id', data.id)
return createAxios({
url: `/access-boot/analyzeModel/uploadDevFile`,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: form
})
}
//新建文件夹目录
export function addDeviceDir(data) {
let form = new FormData()
form.append('nDid', data.nDid)
form.append('path', data.path)
return createAxios({
url: `/access-boot/askDeviceData/createFolder`,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: form
})
}
//删除文件/文件夹
export function delDeviceDir(data) {
let form = new FormData()
form.append('nDid', data.nDid)
form.append('path', data.path)
return createAxios({
url: `/access-boot/askDeviceData/deleteFolder`,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: form
})
}

View File

@@ -65,3 +65,35 @@ export function savePageIdWithUser(data: any) {
params: data params: data
}) })
} }
//新增稳态指标方案
export function save(data: any) {
return createAxios({
url: '/cs-harmonic-boot/csHarmonicPlan/save',
method: 'post',
data: data
})
}
//修改稳态指标方案
export function update(data: any) {
return createAxios({
url: '/cs-harmonic-boot/csHarmonicPlan/update',
method: 'post',
data: data
})
}
//新增稳态指标方案
export function deletePlan(data: any) {
return createAxios({
url: '/cs-harmonic-boot/csHarmonicPlan/delete',
method: 'post',
data: data
})
}
//根据ID查询稳态指标方案
export function getById(data: any) {
return createAxios({
url: '/cs-harmonic-boot/csHarmonicPlan/getById',
method: 'GET',
params: data
})
}

View File

@@ -8,7 +8,7 @@ export function getMakeUpData(data: any) {
}) })
} }
//查询装置目录-文件 //查询设备目录-文件
export function getAskDirOrFile(data: any) { export function getAskDirOrFile(data: any) {
return createAxios({ return createAxios({
url: `/cs-harmonic-boot/offlineDataUpload/askDirOrFile?fileType=${data.fileType}&nDid=${data.nDid}&path=${data.path}&prjName=${data.prjName}`, url: `/cs-harmonic-boot/offlineDataUpload/askDirOrFile?fileType=${data.fileType}&nDid=${data.nDid}&path=${data.path}&prjName=${data.prjName}`,

View File

@@ -1,30 +1,69 @@
import createAxios from '@/utils/request' import createAxios from '@/utils/request'
/** /**
* 查询app个人中心信息详情 * 查询app个人中心信息详情
* @param id * @param id
*/ */
export const queryAppInfo = (type: string) => { export const queryAppInfo = (type: string) => {
let form = new FormData() let form = new FormData()
form.append('type', type) form.append('type', type)
return createAxios({ return createAxios({
url: '/cs-system-boot/appinfo/queryAppInfoByType', url: '/cs-system-boot/appinfo/queryAppInfoByType',
method: 'post', method: 'post',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded' 'Content-Type': 'application/x-www-form-urlencoded'
}, },
data: form data: form
}) })
} }
/**
/** * 新增app基础信息
* 新增app基础信息 **/
**/ export const addAppInfo = (data: { type: string; content: string }) => {
export const addAppInfo = (data: { type: string, content: string }) => { return createAxios({
return createAxios({ url: '/cs-system-boot/appinfo/addAppInfo',
url: '/cs-system-boot/appinfo/addAppInfo', method: 'post',
method: 'post', data: data
data: data })
}) }
} /**
* 切换告警配置启用状态
**/
export const toggleActive = (data: any) => {
return createAxios({
url: '/cs-system-boot/csAlarmSet/toggleActive',
method: 'post',
params: data
})
}
/**
* 切换告警配置启用状态
**/
export const csDelete = (data: any) => {
return createAxios({
url: '/cs-system-boot/csAlarmSet/delete',
method: 'post',
params: data
})
}
/**
* 新增告警配置
**/
export const add = (data: any) => {
return createAxios({
url: '/cs-system-boot/csAlarmSet/add',
method: 'post',
data: data
})
}
/**
* 修改告警配置
**/
export const update = (data: any) => {
return createAxios({
url: '/cs-system-boot/csAlarmSet/update',
method: 'post',
data: data
})
}

View File

@@ -1,98 +1,121 @@
import createAxios from '@/utils/request' import createAxios from '@/utils/request'
// 新增出厂设备 // 新增出厂设备
export const addEquipmentDelivery = (data: any) => { export const addEquipmentDelivery = (data: any) => {
return createAxios({ return createAxios({
url: '/cs-device-boot/EquipmentDelivery/addEquipmentDelivery', url: '/cs-device-boot/EquipmentDelivery/addEquipmentDelivery',
method: 'POST', method: 'POST',
data: data data: data
}) })
} }
// 删除出厂设备 // 删除出厂设备
export const deleteEquipmentDelivery = (id: any) => { export const deleteEquipmentDelivery = (id: any) => {
let form = new FormData() let form = new FormData()
form.append('id', id) form.append('id', id)
return createAxios({ return createAxios({
url: '/cs-device-boot/EquipmentDelivery/AuditEquipmentDelivery', url: '/cs-device-boot/EquipmentDelivery/AuditEquipmentDelivery',
method: 'POST', method: 'POST',
data: form data: form
}) })
} }
// 恢复出厂设置 // 恢复出厂设置
export const resetEquipmentDelivery = (id: any) => { export const resetEquipmentDelivery = (id: any) => {
let form = new FormData() let form = new FormData()
form.append('nDid', id) form.append('nDid', id)
return createAxios({ return createAxios({
url: '/access-boot/device/resetFactory', url: '/access-boot/device/resetFactory',
method: 'POST', method: 'POST',
data: form data: form
}) })
} }
// 编辑出厂设备 // 编辑出厂设备
export const editEquipmentDelivery = (data: any) => { export const editEquipmentDelivery = (data: any) => {
return createAxios({ return createAxios({
url: '/cs-device-boot/EquipmentDelivery/updateEquipmentDelivery', url: '/cs-device-boot/EquipmentDelivery/updateEquipmentDelivery',
method: 'POST', method: 'POST',
data: data data: data
}) })
} }
// 上传拓扑图 // 上传拓扑图
export const uploadTopo = (file: any) => { export const uploadTopo = (file: any) => {
let form = new FormData() let form = new FormData()
form.append('file', file) form.append('file', file)
return createAxios({ return createAxios({
url: '/cs-device-boot/topologyTemplate/uploadImage', url: '/cs-device-boot/topologyTemplate/uploadImage',
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'multipart/form-data' 'Content-Type': 'multipart/form-data'
}, },
data: form data: form
}) })
} }
// 批量导入设备 // 批量导入设备
export const batchImportDevice = (file: any) => { export const batchImportDevice = (file: any) => {
let form = new FormData() let form = new FormData()
form.append('file', file) form.append('file', file)
return createAxios({ return createAxios({
url: '/cs-device-boot/EquipmentDelivery/importEquipment', url: '/cs-device-boot/EquipmentDelivery/importEquipment',
method: 'POST', method: 'POST',
responseType: 'blob', responseType: 'blob',
data: form data: form
}) })
} }
// 直连设备注册接入 // 直连设备注册接入
export const governDeviceRegister = (data: any) => { export const governDeviceRegister = (data: any) => {
return createAxios({ return createAxios({
url: `/access-boot/device/register?nDid=${data.nDid}&type=${data.type}`, url: `/access-boot/device/register?nDid=${data.nDid}&type=${data.type}`,
method: 'POST' method: 'POST'
}) })
} }
// 便携式设备注册 // 便携式设备注册
export const portableDeviceRegister = (params: any) => { export const portableDeviceRegister = (params: any) => {
return createAxios({ return createAxios({
url: `/access-boot/device/wlRegister`, url: `/access-boot/device/wlRegister`,
method: 'POST', method: 'POST',
params params
}) })
} }
// 便携式设备接入 // 便携式设备接入
export const portableDeviceAccess = (data: any) => { export const portableDeviceAccess = (data: any) => {
return createAxios({ return createAxios({
url: `/access-boot/device/wlAccess?nDid=${data.nDid}`, url: `/access-boot/device/wlAccess?nDid=${data.nDid}`,
method: 'POST', method: 'POST'
}) })
} }
// 下载模版 // 下载模版
export function getExcelTemplate() { export function getExcelTemplate() {
return createAxios({ return createAxios({
url: '/cs-device-boot/EquipmentDelivery/getExcelTemplate', url: '/cs-device-boot/EquipmentDelivery/getExcelTemplate',
method: 'get', method: 'get',
responseType: 'blob' responseType: 'blob'
}) })
} }
// 查询工程信息列表
export function engineeringProject() {
return createAxios({
url: '/cs-device-boot/engineeringProjectRelation/list',
method: 'post'
})
}
//监测设备接入
export function onlineRegister(data: any) {
return createAxios({
url: '/access-boot/device/onlineRegister',
method: 'post',
params: data
})
}
//重启设备
export function resetFactory(data: any) {
return createAxios({
url: '/access-boot/device/resetFactory',
method: 'post',
params: data
})
}

View File

@@ -27,3 +27,23 @@ export const removeUserDev = (data: any) => {
data: data data: data
}) })
} }
/**
* 短信配置
*/
export const addUserDevices = (data: any) => {
return createAxios({
url: '/cs-system-boot/appMsgSet/addUserDevices',
method: 'post',
data: data
})
}
/**
* 短信配置
*/
export const queryDeviceIdsByUserId = (data: any) => {
return createAxios({
url: '/cs-system-boot/appMsgSet/queryDeviceIdsByUserId',
method: 'post',
params: data
})
}

View File

@@ -94,3 +94,19 @@ export function codeDicTree(data: any) {
params: data params: data
}) })
} }
// 根据装置型号获取装置类型
export function findByDevTypeId(data: any) {
return createAxios({
url: '/cs-device-boot/edData/queryEdDataPage',
method: 'post',
data
})
}
// 装置升级
export function upgrade(params: any) {
return createAxios({
url: '/zl-event-boot/device/upgrade',
method: 'get',
params
})
}

View File

@@ -147,8 +147,9 @@ const tableStore: any = new TableStore({
backgroundColor: 'rgba(0,0,0,0.55)', backgroundColor: 'rgba(0,0,0,0.55)',
borderWidth: 0, borderWidth: 0,
formatter: function (a: any) { formatter: function (a: any) {
var relVal = '' var relVal = `<strong>${a.seriesName}</strong><br/>`
relVal = "<font style='color:" + "'>发生时间:" + a.value[2] + '</font><br/>'
relVal += "<font style='color:" + "'>发生时间:" + a.value[2] + '</font><br/>'
relVal += "<font style='color:" + "'>持续时间:" + a.value[0] + 's</font><br/>' relVal += "<font style='color:" + "'>持续时间:" + a.value[0] + 's</font><br/>'
relVal += "<font style='color:" + "'>特征幅值:" + a.value[1].toFixed(2) + '%</font>' relVal += "<font style='color:" + "'>特征幅值:" + a.value[1].toFixed(2) + '%</font>'
return relVal return relVal
@@ -214,18 +215,18 @@ const tableStore: any = new TableStore({
// [0.2, 10, '2023-01-01 10:00:00'], // [0.2, 10, '2023-01-01 10:00:00'],
// [0.4, 50, '2023-01-01 11:00:00'] // [0.4, 50, '2023-01-01 11:00:00']
// ], // ],
legendSymbol: 'circle', legendSymbol: 'circle'
tooltip: { // tooltip: {
show: true, // show: true,
trigger: 'item', // trigger: 'item',
formatter: function (params: any) { // formatter: function (params: any) {
return `<strong>可容忍事件</strong><br/> // return `<strong>可容忍事件</strong><br/>
持续时间: ${params.value[0]}s<br/> // 持续时间: ${params.value[0]}s<br/>
特征幅值: ${params.value[1].toFixed(2)}%<br/> // 特征幅值: ${params.value[1].toFixed(2)}%<br/>
发生时间: ${params.value[2] || 'N/A'}` // 发生时间: ${params.value[2] || 'N/A'}`
} // }
} // }
}, },
{ {
name: '不可容忍事件', name: '不可容忍事件',

View File

@@ -47,7 +47,6 @@ const init = () => {
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
formatter: (params: any) => { formatter: (params: any) => {
console.log('🚀 ~ init ~ params:', params)
if (!params || params.length === 0) return '' if (!params || params.length === 0) return ''
// 使用第一个项目的轴标签作为时间标题 // 使用第一个项目的轴标签作为时间标题
@@ -90,8 +89,6 @@ const initData = async (row: any) => {
let [min, max] = yMethod(res.data.map((item: any) => item.value.split(',')).flat()) let [min, max] = yMethod(res.data.map((item: any) => item.value.split(',')).flat())
// 从第一条数据中提取时间作为x轴数据 // 从第一条数据中提取时间作为x轴数据
const firstItem = res.data[0]
const xAxisData = firstItem.time.split(',')
// 定义相位颜色映射 // 定义相位颜色映射
const phaseColors: any = { const phaseColors: any = {
@@ -107,6 +104,7 @@ const initData = async (row: any) => {
return a.phasic.localeCompare(b.phasic) return a.phasic.localeCompare(b.phasic)
}) })
.map((item: any) => { .map((item: any) => {
const xAxisData = item.time.split(',')
const values = xAxisData.map((time: string, index: number) => { const values = xAxisData.map((time: string, index: number) => {
// 将传入的日期与时间拼接成完整的时间字符串 // 将传入的日期与时间拼接成完整的时间字符串
const fullTime = `${row.time} ${time}` const fullTime = `${row.time} ${time}`

View File

@@ -1,27 +1,15 @@
<template> <template>
<div> <div>
<!--指标越限程度 --> <!--指标越限程度 -->
<TableHeader <TableHeader ref="TableHeaderRef" :showReset="false" @selectChange="selectChange" datePicker
ref="TableHeaderRef" :timeKeyList="prop.timeKey" v-if="fullscreen"></TableHeader>
:showReset="false" <my-echart class="tall" :options="echartList"
@selectChange="selectChange" :style="{ width: prop.width, height: `calc(${prop.height} / 2 )` }" />
datePicker <Table ref="tableRef" @cell-click="cellClickEvent"
:timeKeyList="prop.timeKey" :height="`calc(${prop.height} / 2 - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`" isGroup></Table>
v-if="fullscreen"
></TableHeader>
<my-echart
class="tall"
:options="echartList"
:style="{ width: prop.width, height: `calc(${prop.height} / 2 )` }"
/>
<Table
ref="tableRef"
@cell-click="cellClickEvent"
:height="`calc(${prop.height} / 2 - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`"
isGroup
></Table>
<!-- 指标日趋势图 --> <!-- 指标日趋势图 -->
<DailyTrendChart v-if="dialogTrendChart" ref="dailyTrendChartRef" @close="dialogTrendChart = false" /> <HarmonicRatio ref="harmonicRatioRef" v-if="dialogFlag" @close="onHarmonicRatioClose" :showIndex="false" />
<!-- <DailyTrendChart v-if="dialogTrendChart" ref="dailyTrendChartRef" @close="dialogTrendChart = false" /> -->
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -31,9 +19,10 @@ import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import MyEchart from '@/components/echarts/MyEchart.vue' import MyEchart from '@/components/echarts/MyEchart.vue'
import { getTimeOfTheMonth } from '@/utils/formatTime' import { getTimeOfTheMonth } from '@/utils/formatTime'
import { ElMessage, ElMessageBox } from 'element-plus'
import DailyTrendChart from '@/components/cockpit/exceedanceLevel/components/dailyTrendChart.vue' import DailyTrendChart from '@/components/cockpit/exceedanceLevel/components/dailyTrendChart.vue'
import { getTime } from '@/utils/formatTime' import { getTime } from '@/utils/formatTime'
import HarmonicRatio from '@/components/cockpit/overLimitStatistics/components/harmonicRatio.vue'
const prop = defineProps({ const prop = defineProps({
w: { type: [String, Number] }, w: { type: [String, Number] },
h: { type: [String, Number] }, h: { type: [String, Number] },
@@ -47,7 +36,7 @@ const prop = defineProps({
const TableHeaderRef = ref() const TableHeaderRef = ref()
const headerHeight = ref(57) const headerHeight = ref(57)
const harmonicRatioRef: any = ref(null)
const dialogTrendChart = ref(false) const dialogTrendChart = ref(false)
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => { const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
@@ -118,7 +107,7 @@ const tableStore: any = new TableStore({
field: 'extent', field: 'extent',
minWidth: '70', minWidth: '70',
formatter: (row: any) => { formatter: (row: any) => {
return Math.floor(row.cellValue * 100) / 100 return Math.floor(row.cellValue * 100) / 100
} }
}, },
{ {
@@ -143,23 +132,14 @@ const tableStore: any = new TableStore({
}, },
loadCallback: () => { loadCallback: () => {
// 定义 x 轴标签顺序 // 定义 x 轴标签顺序
const xAxisLabels = ['长时闪变', '谐波电压', '谐波电流', '电压偏差', '三相不平衡']
// 根据指标名称顺序提取对应的 extent 数据
const chartData = xAxisLabels.map(label => {
// 在表格数据中查找对应指标名称的数据项
const item = tableStore.table.data.find((row: any) => row.name === label)
// 如果找到对应项,则返回 extent 值,否则返回 0并保留两位小数
const extentValue = item ? item.extent || 0 : 0
return Math.round(extentValue * 100) / 100
})
echartList.value = { echartList.value = {
title: { title: {
text: '指标越限严重度' text: '指标越限严重度'
}, },
xAxis: { xAxis: {
data: xAxisLabels data: tableStore.table.data.map((item: any) => item.name)
}, },
yAxis: { yAxis: {
@@ -175,7 +155,7 @@ const tableStore: any = new TableStore({
{ {
type: 'bar', type: 'bar',
name: '越限占比', name: '越限占比',
data: chartData, data: tableStore.table.data.map((item: any) => Math.floor(item.extent * 100) / 100),
barMaxWidth: 30 barMaxWidth: 30
} }
] ]
@@ -188,15 +168,32 @@ const tableRef = ref()
provide('tableRef', tableRef) provide('tableRef', tableRef)
provide('tableStore', tableStore) provide('tableStore', tableStore)
const codeMap = [
{ key: '闪变', code: 'flickerOvertime' },
{ key: '电压偏差', code: 'voltageDevOvertime' },
{ key: '三相', code: 'ubalanceOvertime' },
{ key: '谐波电压', code: 'uharm' },
{ key: '谐波电流', code: 'iharm' },
];
// 点击行 // 点击行
const cellClickEvent = ({ row, column }: any) => { const cellClickEvent = ({ row, column }: any) => {
dialogTrendChart.value = true dialogTrendChart.value = true
if (column.field == 'maxValue' && row.lineId) { if (column.field == 'maxValue') {
nextTick(() => { if (row.lineId == null) {
dailyTrendChartRef.value.open(row) ElMessage.info('暂无越限监测点!')
}) } else {
nextTick(() => {
// dailyTrendChartRef.value.open(row)
dialogFlag.value = true
nextTick(() => {
const code = codeMap.find(item => row.name.includes(item.key))?.code || '';
harmonicRatioRef.value.openDialog(row, code, column.title.replace(/次/g, ""))
})
})
}
} }
} }
@@ -218,6 +215,14 @@ const setTime = () => {
console.warn('获取时间失败time 不是一个有效数组') console.warn('获取时间失败time 不是一个有效数组')
} }
} }
const dialogFlag = ref(false)
// 谐波弹窗关闭时的回调
const onHarmonicRatioClose = () => {
dialogFlag.value = false
// 重新打开指标越限详情弹窗
}
onMounted(() => { onMounted(() => {
tableStore.index() tableStore.index()
@@ -238,6 +243,6 @@ watch(
} }
) )
const addMenu = () => {} const addMenu = () => { }
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@@ -1,15 +1,37 @@
<template> <template>
<div> <div>
<!--治理效果报表 --> <!--治理效果报表 -->
<TableHeader :showReset="false" :timeKeyList="prop.timeKey" ref="TableHeaderRef" datePicker @selectChange="selectChange" v-if="fullscreen"> <TableHeader
:showReset="false"
:timeKeyList="prop.timeKey"
ref="TableHeaderRef"
datePicker
@selectChange="selectChange"
v-if="fullscreen"
>
<template v-slot:select> <template v-slot:select>
<el-form-item label="模板策略"> <el-form-item label="模板策略">
<el-select filterable v-model="tableStore.table.params.tempId" placeholder="请选择模板策略" clearable> <el-select
<el-option v-for="item in templateList" :key="item.id" :label="item.excelName" :value="item.id" /> filterable
v-model="tableStore.table.params.tempId"
placeholder="请选择模板策略"
clearable
>
<el-option
v-for="item in templateList"
:key="item.id"
:label="item.excelName"
:value="item.id"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="监测对象"> <el-form-item label="监测对象">
<el-select filterable v-model="tableStore.table.params.sensitiveUserId" placeholder="请选择监测对象" clearable> <el-select
filterable
v-model="tableStore.table.params.sensitiveUserId"
placeholder="请选择监测对象"
clearable
>
<el-option v-for="item in idList" :key="item.id" :label="item.name" :value="item.id" /> <el-option v-for="item in idList" :key="item.id" :label="item.name" :value="item.id" />
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -43,7 +65,7 @@ const prop = defineProps({
h: { type: [String, Number] }, h: { type: [String, Number] },
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
timeKey: { type: Array as () => string[] }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object }, timeValue: { type: Object },
interval: { type: Number } interval: { type: Number }
}) })
@@ -59,13 +81,21 @@ const idList = ref()
// 监测对象 // 监测对象
const initListByIds = () => { const initListByIds = () => {
getListByIds({}).then((res: any) => { getListByIds({}).then((res: any) => {
if (res.data.length > 0) { if (res.data?.length > 0) {
idList.value = res.data idList.value = res.data
if (!tableStore.table.params.sensitiveUserId && idList.value?.length > 0) { if (!tableStore.table.params.sensitiveUserId && idList.value?.length > 0) {
tableStore.table.params.sensitiveUserId = idList.value[0].id tableStore.table.params.sensitiveUserId = idList.value[0].id
} }
templateListData() templateListData()
} else {
querySysExcel({}).then(res => {
templateList.value = res.data.filter(item => item.excelType == 4)
if (!tableStore.table.params.tempId && templateList.value?.length > 0) {
tableStore.table.params.tempId = templateList.value[0].id
}
})
tableStore.table.loading = false
} }
}) })
} }

View File

@@ -93,15 +93,7 @@ const tableStore: any = new TableStore({
return `<span style='cursor: pointer;text-decoration: underline;'>${row.flickerOvertime}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.flickerOvertime}</span>`
} }
}, },
{ {
title: '谐波电压越限(%)',
children: loop50('uharm')
},
{
title: '谐波电流越限(%)',
children: loop50('iharm')
},
{
title: '电压偏差越限(%)', title: '电压偏差越限(%)',
field: 'voltageDevOvertime', field: 'voltageDevOvertime',
width: '100', width: '100',
@@ -119,6 +111,15 @@ const tableStore: any = new TableStore({
return `<span style='cursor: pointer;text-decoration: underline;'>${row.ubalanceOvertime}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.ubalanceOvertime}</span>`
} }
}, },
{
title: '谐波电压越限(%)',
children: loop50('uharm')
},
{
title: '谐波电流越限(%)',
children: loop50('iharm')
},
// { // {
// title: '频率偏差越限(%)', // title: '频率偏差越限(%)',

View File

@@ -186,7 +186,7 @@ const initProbabilityData = () => {
var tips = '' var tips = ''
tips += '指标类型: ' + yAxisData[yIndex] + '</br>' tips += '指标类型: ' + yAxisData[yIndex] + '</br>'
tips += '越限程度: ' + params.seriesName + '</br>' tips += '越限程度: ' + params.seriesName + '</br>'
tips += '越限数: ' + params.value[2] + '</br>' tips += '越限数: ' + params.value[2] + '</br>'
return tips return tips
} }
}, },
@@ -228,7 +228,7 @@ const initProbabilityData = () => {
}, },
zAxis3D: { zAxis3D: {
type: 'value', type: 'value',
name: '越限数', name: '越限数',
nameLocation: 'middle', nameLocation: 'middle',
nameGap: 30, nameGap: 30,
minInterval: 10 minInterval: 10

View File

@@ -185,7 +185,7 @@ const initProbabilityData = () => {
var tips = '' var tips = ''
tips += '指标类型: ' + yAxisData[yIndex] + '</br>' tips += '指标类型: ' + yAxisData[yIndex] + '</br>'
tips += '越限程度: ' + params.seriesName + '</br>' tips += '越限程度: ' + params.seriesName + '</br>'
tips += '越限数: ' + params.value[2] + '</br>' tips += '越限数: ' + params.value[2] + '</br>'
return tips return tips
} }
}, },
@@ -227,7 +227,7 @@ const initProbabilityData = () => {
}, },
zAxis3D: { zAxis3D: {
type: 'value', type: 'value',
name: '越限数', name: '越限数',
nameLocation: 'middle', nameLocation: 'middle',
nameGap: 30, nameGap: 30,
minInterval: 10 minInterval: 10

View File

@@ -93,15 +93,7 @@ const tableStore: any = new TableStore({
customTemplate: (row: any) => { customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.flickerOvertime}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.flickerOvertime}</span>`
} }
}, }, {
{
title: '谐波电压越限(分钟)',
children: loop50('uharm')
},
{
title: '谐波电流越限(分钟)',
children: loop50('iharm')
}, {
title: '电压偏差越限(分钟)', title: '电压偏差越限(分钟)',
field: 'uaberranceOvertime', field: 'uaberranceOvertime',
width: '100', width: '100',
@@ -119,6 +111,14 @@ const tableStore: any = new TableStore({
return `<span style='cursor: pointer;text-decoration: underline;'>${row.ubalanceOvertime}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.ubalanceOvertime}</span>`
} }
}, },
{
title: '谐波电压越限(分钟)',
children: loop50('uharm')
},
{
title: '谐波电流越限(分钟)',
children: loop50('iharm')
},
// { // {
// title: '频率偏差越限(分钟)', // title: '频率偏差越限(分钟)',

View File

@@ -73,15 +73,7 @@ const tableStore: any = new TableStore({
field: 'flicker', field: 'flicker',
width: '80' width: '80'
}, },
{ {
title: '谐波电压越限(分钟)',
children: loop50('voltage')
},
{
title: '谐波电流越限(分钟)',
children: loop50('harmonicCurrent')
},
{
title: '三相不平衡度越限(分钟)', title: '三相不平衡度越限(分钟)',
field: 'flicker', field: 'flicker',
width: '100' width: '100'
@@ -95,7 +87,16 @@ const tableStore: any = new TableStore({
title: '频率偏差越限(分钟)', title: '频率偏差越限(分钟)',
field: 'flicker', field: 'flicker',
width: '100' width: '100'
} },
{
title: '谐波电压越限(分钟)',
children: loop50('voltage')
},
{
title: '谐波电流越限(分钟)',
children: loop50('harmonicCurrent')
},
], ],
beforeSearchFun: () => {}, beforeSearchFun: () => {},
loadCallback: () => { loadCallback: () => {

View File

@@ -92,15 +92,7 @@ const tableStore: any = new TableStore({
return `<span style='cursor: pointer;text-decoration: underline;'>${row.flicker}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.flicker}</span>`
} }
}, },
{ {
title: '谐波电压越限(分钟)',
children: loop50('voltage')
},
{
title: '谐波电流越限(分钟)',
children: loop50('harmonicCurrent')
},
{
title: '三相不平衡度越限(分钟)', title: '三相不平衡度越限(分钟)',
field: 'flicker', field: 'flicker',
width: '100' width: '100'
@@ -114,7 +106,16 @@ const tableStore: any = new TableStore({
title: '频率偏差越限(分钟)', title: '频率偏差越限(分钟)',
field: 'flicker', field: 'flicker',
width: '100' width: '100'
} },
{
title: '谐波电压越限(分钟)',
children: loop50('voltage')
},
{
title: '谐波电流越限(分钟)',
children: loop50('harmonicCurrent')
},
], ],
beforeSearchFun: () => {}, beforeSearchFun: () => {},
loadCallback: () => { loadCallback: () => {

View File

@@ -150,6 +150,7 @@ const indicatorList = ref()
const initLineList = async () => { const initLineList = async () => {
cslineList({}).then(res => { cslineList({}).then(res => {
setTime()
if (res.data.length == 0) { if (res.data.length == 0) {
lineShow.value = false lineShow.value = false
return (tableStore.table.loading = false) return (tableStore.table.loading = false)

View File

@@ -93,15 +93,7 @@ const tableStore: any = new TableStore({
return `<span style='cursor: pointer;text-decoration: underline;'>${row.flickerOvertime}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.flickerOvertime}</span>`
} }
}, },
{ {
title: '谐波电压越限(%)',
children: loop50('uharm')
},
{
title: '谐波电流越限(%)',
children: loop50('iharm')
},
{
title: '电压偏差越限(%)', title: '电压偏差越限(%)',
field: 'voltageDevOvertime', field: 'voltageDevOvertime',
width: '100', width: '100',
@@ -119,6 +111,15 @@ const tableStore: any = new TableStore({
return `<span style='cursor: pointer;text-decoration: underline;'>${row.ubalanceOvertime}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.ubalanceOvertime}</span>`
} }
}, },
{
title: '谐波电压越限(%)',
children: loop50('uharm')
},
{
title: '谐波电流越限(%)',
children: loop50('iharm')
},
// { // {
// title: '频率偏差越限(%)', // title: '频率偏差越限(%)',

View File

@@ -5,10 +5,22 @@
ref="TableHeaderRef" ref="TableHeaderRef"
:showReset="false" :showReset="false"
@selectChange="selectChange" @selectChange="selectChange"
v-if="fullscreen" v-if="fullscreen"
:timeKeyList="prop.timeKey" :timeKeyList="prop.timeKey"
></TableHeader> >
<template #select>
<el-form-item label="关键字筛选">
<el-input
maxlength="32"
show-word-limit
style="width: 240px"
v-model.trim="tableStore.table.params.searchValue"
clearable
placeholder="请输入监测点名称"
/>
</el-form-item>
</template>
</TableHeader>
<Table <Table
ref="tableRef" ref="tableRef"
@cell-click="cellClickEvent" @cell-click="cellClickEvent"
@@ -189,7 +201,7 @@ const tableStore: any = new TableStore({
field: 'volGrade', field: 'volGrade',
minWidth: '80', minWidth: '80',
formatter: (row: any) => { formatter: (row: any) => {
return row.cellValue==0?'/': row.cellValue+'kV' || '/' return row.cellValue == 0 ? '/' : row.cellValue + 'kV' || '/'
} }
}, },
{ {
@@ -236,7 +248,8 @@ const tableStore: any = new TableStore({
} }
}, },
{ {
title: '操作', fixed: 'right', title: '操作',
fixed: 'right',
width: 150, width: 150,
render: 'buttons', render: 'buttons',
buttons: [ buttons: [
@@ -294,6 +307,7 @@ const tableStore: any = new TableStore({
const tableRef = ref() const tableRef = ref()
provide('tableRef', tableRef) provide('tableRef', tableRef)
tableStore.table.params.keywords = '' tableStore.table.params.keywords = ''
tableStore.table.params.searchValue = ''
provide('tableStore', tableStore) provide('tableStore', tableStore)
const setTime = () => { const setTime = () => {
@@ -304,7 +318,6 @@ const setTime = () => {
// ? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime] // ? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime]
// : prop.timeValue // : prop.timeValue
// ) // )
// if (Array.isArray(time)) { // if (Array.isArray(time)) {
// tableStore.table.params.searchBeginTime = time[0] // tableStore.table.params.searchBeginTime = time[0]
// tableStore.table.params.searchEndTime = time[1] // tableStore.table.params.searchEndTime = time[1]

View File

@@ -1,5 +1,5 @@
<template> <template>
<el-dialog draggable title="趋势图" v-model="dialogVisible" append-to-body width="70%"> <el-dialog draggable :title="titles" v-model="dialogVisible" append-to-body width="70%">
<!-- 总体指标占比详情谐波含有率 --> <!-- 总体指标占比详情谐波含有率 -->
<div> <div>
<TableHeader ref="tableHeaderRef" :showSearch="false" @selectChange="selectChange"> <TableHeader ref="tableHeaderRef" :showSearch="false" @selectChange="selectChange">
@@ -7,7 +7,7 @@
<el-form-item> <el-form-item>
<DatePicker ref="datePickerRef"></DatePicker> <DatePicker ref="datePickerRef"></DatePicker>
</el-form-item> </el-form-item>
<el-form-item label="统计指标" label-width="80px"> <el-form-item label="统计指标" label-width="80px" v-if="props.showIndex">
<el-select <el-select
multiple multiple
:multiple-limit="2" :multiple-limit="2"
@@ -107,9 +107,14 @@ defineOptions({
const props = defineProps({ const props = defineProps({
TrendList: { TrendList: {
type: Array type: Array
},
showIndex:{
type: Boolean,
default: true
} }
}) })
const titles = ref('趋势图')
const dialogVisible: any = ref(false) const dialogVisible: any = ref(false)
// console.log("🚀 ~ props:", props.TrendList) // console.log("🚀 ~ props:", props.TrendList)
const showEchart = ref(true) const showEchart = ref(true)
@@ -268,6 +273,7 @@ const lineStyle = [{ type: 'solid' }, { type: 'dashed' }, { type: 'dotted' }]
const init = async () => { const init = async () => {
loading.value = true loading.value = true
// 选择指标的时候切换legend内容和data数据 // 选择指标的时候切换legend内容和data数据
echartsData.value = {}
let list: any = [] let list: any = []
legendDictList.value?.selectedList?.map((item: any) => { legendDictList.value?.selectedList?.map((item: any) => {
searchForm.value.index.map((vv: any) => { searchForm.value.index.map((vv: any) => {
@@ -407,6 +413,7 @@ const setEchart = () => {
formatter(params: any) { formatter(params: any) {
const xname = params[0].value[0] const xname = params[0].value[0]
let str = `${xname}<br>` let str = `${xname}<br>`
params.forEach((el: any, index: any) => { params.forEach((el: any, index: any) => {
let marker = '' let marker = ''
@@ -651,6 +658,7 @@ watch(
) )
const openDialog = async (row: any, field: any, title: any) => { const openDialog = async (row: any, field: any, title: any) => {
titles.value = `${row?.lineName ? `${row.lineName}_` : ''}趋势图`
dialogVisible.value = true dialogVisible.value = true
trendRequestData.value = row trendRequestData.value = row

View File

@@ -92,14 +92,6 @@ const tableStore: any = new TableStore({
customTemplate: (row: any) => { customTemplate: (row: any) => {
return `<span style='cursor: pointer;text-decoration: underline;'>${row.flickerOvertime}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.flickerOvertime}</span>`
} }
},
{
title: '谐波电压越限(%)',
children: loop50('uharm')
},
{
title: '谐波电流越限(%)',
children: loop50('iharm')
}, },
{ {
title: '电压偏差越限(%)', title: '电压偏差越限(%)',
@@ -119,7 +111,16 @@ const tableStore: any = new TableStore({
return `<span style='cursor: pointer;text-decoration: underline;'>${row.ubalanceOvertime}</span>` return `<span style='cursor: pointer;text-decoration: underline;'>${row.ubalanceOvertime}</span>`
} }
}, },
{
title: '谐波电压越限(%)',
children: loop50('uharm')
},
{
title: '谐波电流越限(%)',
children: loop50('iharm')
},
// { // {
// title: '频率偏差越限(%)', // title: '频率偏差越限(%)',
// field: 'freqDevOvertime', // field: 'freqDevOvertime',

View File

@@ -5,10 +5,22 @@
ref="TableHeaderRef" ref="TableHeaderRef"
:showReset="false" :showReset="false"
@selectChange="selectChange" @selectChange="selectChange"
v-if="fullscreen" v-if="fullscreen"
:timeKeyList="prop.timeKey" :timeKeyList="prop.timeKey"
></TableHeader> >
<template #select>
<el-form-item label="关键字筛选">
<el-input
maxlength="32"
show-word-limit
style="width: 240px"
v-model.trim="tableStore.table.params.searchValue"
clearable
placeholder="请输入敏感负荷名称"
/>
</el-form-item>
</template>
</TableHeader>
<Table <Table
ref="tableRef" ref="tableRef"
@cell-click="cellClickEvent" @cell-click="cellClickEvent"
@@ -66,7 +78,7 @@ const fullscreen = computed(() => {
const OverLimitDetailsRef = ref() const OverLimitDetailsRef = ref()
const tableStore: any = new TableStore({ const tableStore: any = new TableStore({
url: '/cs-harmonic-boot/pqSensitiveUser/getList', url: '/cs-harmonic-boot/pqSensitiveUser/getListByUser',
method: 'POST', method: 'POST',
showPage: fullscreen.value ? true : false, showPage: fullscreen.value ? true : false,
column: [ column: [
@@ -115,7 +127,7 @@ const tableStore: any = new TableStore({
loadCallback: () => {} loadCallback: () => {}
}) })
tableStore.table.params.searchValue = ''
const tableRef = ref() const tableRef = ref()
provide('tableRef', tableRef) provide('tableRef', tableRef)
@@ -137,7 +149,6 @@ const setTime = () => {
// ? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime] // ? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime]
// : prop.timeValue // : prop.timeValue
// ) // )
// if (Array.isArray(time)) { // if (Array.isArray(time)) {
// tableStore.table.params.searchBeginTime = time[0] // tableStore.table.params.searchBeginTime = time[0]
// tableStore.table.params.searchEndTime = time[1] // tableStore.table.params.searchEndTime = time[1]

View File

@@ -4,8 +4,8 @@
<el-dialog draggable title="暂态事件详情 " v-model="dialogVisible" append-to-body width="70%"> <el-dialog draggable title="暂态事件详情 " v-model="dialogVisible" append-to-body width="70%">
<TableHeader datePicker showExport :showReset="false" ref="tableHeaderRef" @selectChange="selectChange"> <TableHeader datePicker showExport :showReset="false" ref="tableHeaderRef" @selectChange="selectChange">
<template v-slot:select> <template v-slot:select>
<el-form-item label="监测点"> <el-form-item label="监测点" v-if="props.showLine">
<el-select v-model="tableStore.table.params.lineId" placeholder="请选择监测点名称"> <el-select v-model="tableStore.table.params.lineId" filterable placeholder="请选择监测点名称">
<el-option <el-option
v-for="item in options" v-for="item in options"
:key="item.value" :key="item.value"
@@ -14,6 +14,21 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="暂态类型">
<el-select
v-model="tableStore.table.params.eventType"
style="min-width: 150px"
clearable
placeholder="请选择暂态类型"
>
<el-option
v-for="item in eventList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</template> </template>
</TableHeader> </TableHeader>
<Table ref="tableRef" isGroup :height="heightRef"></Table> <Table ref="tableRef" isGroup :height="heightRef"></Table>
@@ -46,6 +61,13 @@ import { mainHeight } from '@/utils/layout'
import waveFormAnalysis from '@/views/govern/device/control/tabs/components/waveFormAnalysis.vue' import waveFormAnalysis from '@/views/govern/device/control/tabs/components/waveFormAnalysis.vue'
import { analyseWave } from '@/api/common' import { analyseWave } from '@/api/common'
import { getSimpleLine } from '@/api/harmonic-boot/cockpit/cockpit' import { getSimpleLine } from '@/api/harmonic-boot/cockpit/cockpit'
interface Props {
showLine?: boolean
}
const props = withDefaults(defineProps<Props>(), {
showLine: true
})
const dialogVisible: any = ref(false) const dialogVisible: any = ref(false)
const waveFormAnalysisRef: any = ref(null) const waveFormAnalysisRef: any = ref(null)
@@ -62,7 +84,11 @@ const heightRef = ref(mainHeight(168, 2.1).height)
const selectChange = (flag: boolean, h: any) => { const selectChange = (flag: boolean, h: any) => {
heightRef.value = mainHeight(h, 2.1).height heightRef.value = mainHeight(h, 2.1).height
} }
const eventList = [
{ label: '电压暂降', value: '1' },
{ label: '电压中断', value: '2' },
{ label: '电压暂升', value: '3' }
]
const getSimpleLineList = async () => { const getSimpleLineList = async () => {
const res = await getSimpleLine() const res = await getSimpleLine()
options.value = res.data options.value = res.data
@@ -141,14 +167,15 @@ const tableStore: any = new TableStore({
type: 'primary', type: 'primary',
icon: 'el-icon-DataLine', icon: 'el-icon-DataLine',
render: 'basicButton', render: 'basicButton',
loading: 'loading1',
disabled: row => { disabled: row => {
return !row.wavePath return !row.wavePath
}, },
click: async row => { click: async row => {
row.loading1 = true row.loading1 = true
loading.value = true
isWaveCharts.value = true
dialogVisible.value = false dialogVisible.value = false
// 在打开弹窗时立即设置高度 // 在打开弹窗时立即设置高度
nextTick(() => { nextTick(() => {
@@ -159,6 +186,8 @@ const tableStore: any = new TableStore({
}) })
await analyseWave(row.id) await analyseWave(row.id)
.then(res => { .then(res => {
isWaveCharts.value = true
loading.value = true
row.loading1 = false row.loading1 = false
if (res != undefined) { if (res != undefined) {
boxoList.value = row boxoList.value = row
@@ -200,9 +229,10 @@ const tableStore: any = new TableStore({
beforeSearchFun: () => {}, beforeSearchFun: () => {},
loadCallback: () => {} loadCallback: () => {}
}) })
tableStore.table.params.eventType = ''
provide('tableStore', tableStore) provide('tableStore', tableStore)
const open = async (time: any) => { const open = async (time: any) => {
tableStore.table.params.eventType = ''
dialogVisible.value = true dialogVisible.value = true
getSimpleLineList() getSimpleLineList()
tableStore.table.params.lineId = '' tableStore.table.params.lineId = ''

View File

@@ -7,7 +7,7 @@
@selectChange="selectChange" @selectChange="selectChange"
datePicker datePicker
v-if="fullscreen" v-if="fullscreen"
:timeKeyList="prop.timeKey" :timeKeyList="prop.timeKey"
></TableHeader> ></TableHeader>
<el-calendar <el-calendar
v-model="value" v-model="value"
@@ -58,7 +58,7 @@
</template> </template>
</el-calendar> </el-calendar>
<!-- 暂态事件列表 --> <!-- 暂态事件列表 -->
<TransientList ref="transientListRef" /> <TransientList ref="transientListRef" :showLine="false" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -74,7 +74,7 @@ const prop = defineProps({
h: { type: [String, Number] }, h: { type: [String, Number] },
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
timeKey: { type: Array as () => string[] }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object }, timeValue: { type: Object },
interval: { type: Number } interval: { type: Number }
}) })

View File

@@ -14,6 +14,21 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="暂态类型">
<el-select
v-model="tableStore.table.params.eventType"
style="min-width: 150px"
clearable
placeholder="请选择暂态类型"
>
<el-option
v-for="item in eventList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</template> </template>
</TableHeader> </TableHeader>
<Table ref="tableRef" isGroup :height="heightRef"></Table> <Table ref="tableRef" isGroup :height="heightRef"></Table>
@@ -62,7 +77,11 @@ const heightRef = ref(mainHeight(168, 2.2).height)
const selectChange = (flag: boolean, h: any) => { const selectChange = (flag: boolean, h: any) => {
heightRef.value = mainHeight(h, 2.2).height heightRef.value = mainHeight(h, 2.2).height
} }
const eventList = [
{ label: '电压暂降', value: '1' },
{ label: '电压中断', value: '2' },
{ label: '电压暂升', value: '3' }
]
const getSimpleLineList = async () => { const getSimpleLineList = async () => {
const res = await getSimpleLine() const res = await getSimpleLine()
options.value = res.data options.value = res.data
@@ -141,15 +160,14 @@ const tableStore: any = new TableStore({
type: 'primary', type: 'primary',
icon: 'el-icon-DataLine', icon: 'el-icon-DataLine',
render: 'basicButton', render: 'basicButton',
loading: 'loading1',
disabled: row => { disabled: row => {
return !row.wavePath return !row.wavePath
}, },
click: async row => { click: async row => {
row.loading1 = true row.loading1 = true
loading.value = true
isWaveCharts.value = true
dialogVisible.value = false
// 在打开弹窗时立即设置高度 // 在打开弹窗时立即设置高度
nextTick(() => { nextTick(() => {
if (waveFormAnalysisRef.value) { if (waveFormAnalysisRef.value) {
@@ -159,6 +177,10 @@ const tableStore: any = new TableStore({
}) })
await analyseWave(row.id) await analyseWave(row.id)
.then(res => { .then(res => {
loading.value = true
isWaveCharts.value = true
dialogVisible.value = false
row.loading1 = false row.loading1 = false
if (res != undefined) { if (res != undefined) {
boxoList.value = row boxoList.value = row
@@ -201,9 +223,10 @@ const tableStore: any = new TableStore({
beforeSearchFun: () => {}, beforeSearchFun: () => {},
loadCallback: () => {} loadCallback: () => {}
}) })
tableStore.table.params.eventType = ''
provide('tableStore', tableStore) provide('tableStore', tableStore)
const open = async (row: any, searchBeginTime: any, searchEndTime: any) => { const open = async (row: any, searchBeginTime: any, searchEndTime: any) => {
tableStore.table.params.eventType = ''
dialogVisible.value = true dialogVisible.value = true
getSimpleLineList() getSimpleLineList()
tableStore.table.params.lineId = row.id tableStore.table.params.lineId = row.id

View File

@@ -69,7 +69,8 @@ const initChart = () => {
top: 15, top: 15,
feature: { feature: {
saveAsImage: { saveAsImage: {
title: '保存图片' title: '下载图片',
name: props.options?.title?.text || '图表'
}, },
...(props.options?.toolbox?.featureProps || null) ...(props.options?.toolbox?.featureProps || null)
}, },
@@ -106,13 +107,15 @@ const initChart = () => {
start: 0, start: 0,
bottom: '20px', bottom: '20px',
end: 100 end: 100,
filterMode: 'none'
}, },
{ {
start: 0, start: 0,
height: 13, height: 13,
bottom: '20px', bottom: '20px',
end: 100 end: 100,
filterMode: 'none'
} }
// { // {
// show: true, // show: true,

View File

@@ -748,8 +748,10 @@ const initWave = (
rotation: 0, rotation: 0,
y: -10 y: -10
}, },
max: rmscm[0]?.[1] * 1.06 || 0, // max: rmscm[0]?.[1] * 1.06 || 0,
min: rmscu[0]?.[1] - rmscu[0]?.[1] * 0.2 || 0, // min: rmscu[0]?.[1] - rmscu[0]?.[1] * 0.2 || 0,
max: Math.floor((rmscm[0]?.[1] * 1.06 || 0) * 1.1 * 10) / 10,
min: Math.floor((rmscu[0]?.[1] - rmscu[0]?.[1] * 0.2 || 0) * 10) / 10,
boundaryGap: [0, '100%'], boundaryGap: [0, '100%'],
showLastLabel: true, showLastLabel: true,
opposite: false, opposite: false,
@@ -780,11 +782,11 @@ const initWave = (
} }
}, },
grid: { grid: {
left: '1%', left: '60px',
right: '45px', right: '45px',
bottom: '40px', bottom: '40px',
top: '60px', top: '60px'
containLabel: true // containLabel: true
}, },
dataZoom: [ dataZoom: [
{ {
@@ -1077,6 +1079,8 @@ const drawPics = (
boundaryGap: [0, '100%'], boundaryGap: [0, '100%'],
showLastLabel: true, showLastLabel: true,
opposite: false, opposite: false,
// max: Math.floor((rmscm[0]?.[1] * 1.06 || 0) * 1.1 * 10) / 10,
// min: Math.floor((rmscu[0]?.[1] - rmscu[0]?.[1] * 0.2 || 0) * 10) / 10,
nameTextStyle: { nameTextStyle: {
fontSize: '12px', fontSize: '12px',
color: props.DColor ? '#000' : echartsColor.WordColor color: props.DColor ? '#000' : echartsColor.WordColor
@@ -1105,11 +1109,11 @@ const drawPics = (
} }
}, },
grid: { grid: {
left: '1%', left: '60px',
right: '45px', right: '45px',
bottom: '40px', bottom: '40px',
top: '60px', top: '60px'
containLabel: true // containLabel: true
}, },
dataZoom: [ dataZoom: [
{ {

View File

@@ -481,8 +481,10 @@ const initWave = (
}, },
boundaryGap: [0, '100%'], boundaryGap: [0, '100%'],
showLastLabel: true, showLastLabel: true,
max: max.toFixed(2) * 1.1, // max: max.toFixed(2) * 1.1,
min: min.toFixed(2) > 0 ? min.toFixed(2) - min.toFixed(2) * 0.1 : min.toFixed(2) * 1.1, // min: min.toFixed(2) > 0 ? min.toFixed(2) - min.toFixed(2) * 0.1 : min.toFixed(2) * 1.1,
max: Math.floor(max.toFixed(2) * 1.1 * 10) / 10,
min: Math.floor(min.toFixed(2) > 0 ? min.toFixed(2) - min.toFixed(2) * 0.1 : min.toFixed(2) * 1.1 * 10) / 10 ,
opposite: false, opposite: false,
nameTextStyle: { nameTextStyle: {
fontSize: '12px', fontSize: '12px',
@@ -512,11 +514,11 @@ const initWave = (
} }
}, },
grid: { grid: {
left: '1%', left: '60px',
right: '45px', right: '45px',
bottom: '40px', bottom: '40px',
top: '60px', top: '60px'
containLabel: true // containLabel: true
}, },
dataZoom: [ dataZoom: [
{ {
@@ -789,8 +791,8 @@ const drawPics = (
}, },
boundaryGap: [0, '100%'], boundaryGap: [0, '100%'],
showLastLabel: true, showLastLabel: true,
max: max.toFixed(2) * 1.1, max: Math.floor(max.toFixed(2) * 1.1 * 10) / 10,
min: min.toFixed(2) > 0 ? min.toFixed(2) - min.toFixed(2) * 0.1 : min.toFixed(2) * 1.1, min: Math.floor(min.toFixed(2) > 0 ? min.toFixed(2) - min.toFixed(2) * 0.1 : min.toFixed(2) * 1.1 * 10) / 10 ,
opposite: false, opposite: false,
nameTextStyle: { nameTextStyle: {
fontSize: '12px', fontSize: '12px',
@@ -820,11 +822,11 @@ const drawPics = (
} }
}, },
grid: { grid: {
left: '1%', left: '60px',
right: '45px', right: '45px',
bottom: '40px', bottom: '40px',
top: '60px', top: '60px'
containLabel: true // containLabel: true
}, },
dataZoom: [ dataZoom: [
{ {

View File

@@ -1,35 +1,36 @@
<template> <template>
<div class="mac-address-input" :class="{ disabled: disabled }"> <div class="mac-address-input" :class="{ disabled: disabled }">
<el-input <el-input
ref="inputRef" ref="inputRef"
v-model="macValue" placeholder="请输入设备mac地址"
type="text" v-model="macValue"
maxlength="17" type="text"
:disabled="disabled" maxlength="17"
@input="handleInput" :disabled="disabled"
@keydown="handleKeydown" @input="handleInput"
@focus="handleFocus" @keydown="handleKeydown"
@blur="handleBlur" @focus="handleFocus"
@paste="handlePaste" @blur="handleBlur"
/> @paste="handlePaste"
</div> />
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
interface Props { interface Props {
modelValue?: string modelValue?: string
disabled?: boolean disabled?: boolean
} }
interface Emits { interface Emits {
(e: 'update:modelValue', value: string): void (e: 'update:modelValue', value: string): void
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
modelValue: '', modelValue: '',
disabled: false disabled: false
}) })
const emit = defineEmits<Emits>() const emit = defineEmits<Emits>()
@@ -42,35 +43,35 @@ const macValue = ref<string>('')
// 解析传入的MAC地址 // 解析传入的MAC地址
const parseMacAddress = (mac: string): string => { const parseMacAddress = (mac: string): string => {
if (!mac) return '' if (!mac) return ''
// 移除非十六进制字符并转为大写 // 移除非十六进制字符并转为大写
const cleanMac = mac.replace(/[^0-9a-fA-F]/g, '').toUpperCase() const cleanMac = mac.replace(/[^0-9a-fA-F]/g, '').toUpperCase()
// 按每2个字符分割并用冒号连接 // 按每2个字符分割并用冒号连接
let result = '' let result = ''
for (let i = 0; i < cleanMac.length; i += 2) { for (let i = 0; i < cleanMac.length; i += 2) {
if (i > 0) result += ':' if (i > 0) result += ':'
result += cleanMac.substr(i, 2) result += cleanMac.substr(i, 2)
} }
return result.substring(0, 17) // 最多17个字符 (12个数字+5个冒号) return result.substring(0, 17) // 最多17个字符 (12个数字+5个冒号)
} }
// 格式化MAC地址 - 改进版 // 格式化MAC地址 - 改进版
const formatMac = (value: string): string => { const formatMac = (value: string): string => {
// 移除所有冒号 // 移除所有冒号
const cleanValue = value.replace(/:/g, '') const cleanValue = value.replace(/:/g, '')
// 只保留十六进制字符并转为大写 // 只保留十六进制字符并转为大写
const hexOnly = cleanValue.replace(/[^0-9a-fA-F]/g, '').toUpperCase() const hexOnly = cleanValue.replace(/[^0-9a-fA-F]/g, '').toUpperCase()
// 按每两个字符添加冒号最多6段 // 按每两个字符添加冒号最多6段
let formatted = '' let formatted = ''
for (let i = 0; i < Math.min(hexOnly.length, 12); i += 2) { for (let i = 0; i < Math.min(hexOnly.length, 12); i += 2) {
if (i > 0) formatted += ':' if (i > 0) formatted += ':'
formatted += hexOnly.substr(i, 2) formatted += hexOnly.substr(i, 2)
} }
return formatted return formatted
} }
// 当前聚焦的输入框索引 // 当前聚焦的输入框索引
@@ -78,88 +79,86 @@ const focusedIndex = ref<number | null>(null)
// 处理输入事件 // 处理输入事件
const handleInput = (value: string) => { const handleInput = (value: string) => {
const formatted = formatMac(value) const formatted = formatMac(value)
macValue.value = formatted macValue.value = formatted
// 发出不带冒号的纯净值 // 发出不带冒号的纯净值
emit('update:modelValue', formatted.replace(/:/g, '')) emit('update:modelValue', formatted.replace(/:/g, ''))
} }
// 处理键盘事件 // 处理键盘事件
const handleKeydown = (event: KeyboardEvent) => { const handleKeydown = (event: KeyboardEvent) => {
const target = event.target as HTMLInputElement const target = event.target as HTMLInputElement
// 处理退格键 // 处理退格键
if (event.key === 'Backspace') { if (event.key === 'Backspace') {
// 处理在冒号前删除的情况 // 处理在冒号前删除的情况
const cursorPos = target.selectionStart || 0 const cursorPos = target.selectionStart || 0
if (cursorPos > 0 && macValue.value[cursorPos - 1] === ':' && if (cursorPos > 0 && macValue.value[cursorPos - 1] === ':' && target.selectionStart === target.selectionEnd) {
target.selectionStart === target.selectionEnd) { event.preventDefault()
event.preventDefault() // 删除冒号前的两个字符
// 删除冒号前的两个字符 const newValue = macValue.value.substring(0, cursorPos - 3) + macValue.value.substring(cursorPos)
const newValue = macValue.value.substring(0, cursorPos - 3) + macValue.value = newValue
macValue.value.substring(cursorPos) // 设置光标位置
macValue.value = newValue setTimeout(() => {
// 设置光标位置 if (target.setSelectionRange) {
setTimeout(() => { target.setSelectionRange(cursorPos - 3, cursorPos - 3)
if (target.setSelectionRange) { }
target.setSelectionRange(cursorPos - 3, cursorPos - 3) }, 0)
emit('update:modelValue', newValue.replace(/:/g, ''))
} }
}, 0)
emit('update:modelValue', newValue.replace(/:/g, ''))
} }
}
} }
// 处理焦点事件 // 处理焦点事件
const handleFocus = () => { const handleFocus = () => {
focusedIndex.value = 0 focusedIndex.value = 0
} }
// 处理失焦事件 // 处理失焦事件
const handleBlur = () => { const handleBlur = () => {
focusedIndex.value = null focusedIndex.value = null
} }
// 处理粘贴事件 // 处理粘贴事件
const handlePaste = (event: ClipboardEvent) => { const handlePaste = (event: ClipboardEvent) => {
event.preventDefault() event.preventDefault()
const pastedText = event.clipboardData?.getData('text') || '' const pastedText = event.clipboardData?.getData('text') || ''
// 清理粘贴的文本 // 清理粘贴的文本
const cleanPastedText = pastedText.replace(/[^0-9a-fA-F]/g, '').toUpperCase() const cleanPastedText = pastedText.replace(/[^0-9a-fA-F]/g, '').toUpperCase()
const formatted = formatMac(cleanPastedText) const formatted = formatMac(cleanPastedText)
macValue.value = formatted macValue.value = formatted
emit('update:modelValue', formatted.replace(/:/g, '')) emit('update:modelValue', formatted.replace(/:/g, ''))
} }
// 监听modelValue变化 // 监听modelValue变化
watch( watch(
() => props.modelValue, () => props.modelValue,
(newVal) => { newVal => {
const cleanNewVal = (newVal || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase() const cleanNewVal = (newVal || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase()
const currentCleanValue = macValue.value.replace(/:/g, '') const currentCleanValue = macValue.value.replace(/:/g, '')
if (cleanNewVal !== currentCleanValue) { if (cleanNewVal !== currentCleanValue) {
macValue.value = parseMacAddress(cleanNewVal) macValue.value = parseMacAddress(cleanNewVal)
} }
}, },
{ immediate: true } { immediate: true }
) )
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.mac-address-input { .mac-address-input {
width: 100%; width: 100%;
&.disabled { &.disabled {
opacity: 0.7; opacity: 0.7;
} }
:deep(.el-input__wrapper) { :deep(.el-input__wrapper) {
input { input {
text-transform: uppercase; text-transform: uppercase;
font-family: inherit; // 使用继承的字体而不是等宽字体 font-family: inherit; // 使用继承的字体而不是等宽字体
}
} }
}
} }
</style> </style>

View File

@@ -1,20 +1,20 @@
<template> <template>
<div :style="{ width: menuCollapse ? '40px' : props.width }" style="transition: all 0.3s; overflow: hidden"> <div :style="{ width: menuCollapse ? '40px' : props.width }" style="transition: all 0.3s; overflow: hidden">
<div class="mt10 mr10" style="display: flex; justify-content: end"> <div class="mt10 mr10" style="display: flex; justify-content: end" v-if="showBut">
<el-button type="primary" icon="el-icon-Select" @click="save" :loading="loading">保存</el-button> <el-button type="primary" icon="el-icon-Select" @click="save" :loading="loading">保存</el-button>
</div> </div>
<Icon <!-- <Icon
v-show="menuCollapse" v-show="menuCollapse"
@click="onMenuCollapse" @click="onMenuCollapse"
:name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
:class="menuCollapse ? 'unfold' : ''" :class="menuCollapse ? 'unfold' : ''"
size="18" size="18px"
class="fold ml10 mt20 menu-collapse" class="fold ml10 mt20 menu-collapse"
style="cursor: pointer" style="cursor: pointer"
/> /> -->
<div class="cn-tree" :style="{ opacity: menuCollapse ? 0 : 1 }"> <div class="cn-tree" :style="{ opacity: menuCollapse ? 0 : 1 }">
<div style="display: flex; align-items: center" class="mb10"> <div style="display: flex; align-items: center" class="mb10">
<el-input maxlength="32" show-word-limit v-model.trim="filterText" placeholder="请输入内容" clearable> <el-input maxlength="32" v-model.trim="filterText" placeholder="请输入内容" clearable>
<template #prefix> <template #prefix>
<Icon name="el-icon-Search" style="font-size: 16px" /> <Icon name="el-icon-Search" style="font-size: 16px" />
</template> </template>
@@ -23,23 +23,19 @@
<template #content> <template #content>
<span>台账推送</span> <span>台账推送</span>
</template> </template>
<Icon <Icon
name="el-icon-Promotion" name="el-icon-Promotion"
size="20" size="20px"
class="fold ml10 menu-collapse" class="fold ml10 menu-collapse"
style="cursor: pointer" style="cursor: pointer"
:style="{ color: config.getColorVal('elementUiPrimary') }" :style="{ color: config.getColorVal('elementUiPrimary') }"
@click="onAdd" @click="onAdd"
/> />
</el-tooltip> </el-tooltip>
<!-- <Icon @click='onMenuCollapse' :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" v-else
:class="menuCollapse ? 'unfold' : ''" size='18' class='fold ml10 menu-collapse'
style='cursor: pointer' v-if='props.canExpand' /> -->
</div> </div>
<el-tree <el-tree
:style="{ height: 'calc(100vh - 267px)' }" :style="{ height: `calc(100vh - ${height}px)` }"
style="overflow: auto" style="overflow: auto"
ref="treeRef" ref="treeRef"
:props="defaultProps" :props="defaultProps"
@@ -50,13 +46,13 @@
node-key="id" node-key="id"
v-bind="$attrs" v-bind="$attrs"
> >
<template #default="{ node, data }"> <template #default="{ node, data: nodeData }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<Icon <Icon
:name="data.icon" :name="nodeData.icon"
style="font-size: 16px" style="font-size: 16px"
:style="{ color: data.color }" :style="{ color: nodeData.color }"
v-if="data.icon" v-if="nodeData.icon"
/> />
<span style="margin-left: 4px">{{ node.label }}</span> <span style="margin-left: 4px">{{ node.label }}</span>
</span> </span>
@@ -69,89 +65,55 @@
<script lang="ts" setup> <script lang="ts" setup>
import useCurrentInstance from '@/utils/useCurrentInstance' import useCurrentInstance from '@/utils/useCurrentInstance'
import { ElTree } from 'element-plus' import { ElTree } from 'element-plus'
import { emit } from 'process'
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { t } from 'vxe-table'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { createTreeFilterNode } from './govern/treeFilterUtils'
defineOptions({ defineOptions({ name: 'govern/allocation', inheritAttrs: false })
name: 'govern/tree'
})
interface Props { interface Props {
width?: string width?: string
canExpand?: boolean canExpand?: boolean
showPush?: boolean showPush?: boolean
showBut?: boolean
height?: number
} }
const loading = ref(false)
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
width: '280px', width: '280px',
canExpand: true, canExpand: true,
showPush: false showPush: false,
showBut: true,
height: 267
}) })
const emit = defineEmits(['checkTreeNodeChange', 'onAdd', 'checkChange'])
const config = useConfig() const config = useConfig()
const { proxy } = useCurrentInstance() const { proxy } = useCurrentInstance()
const menuCollapse = ref(false) const menuCollapse = ref(false)
const filterText = ref('') const filterText = ref('')
const defaultProps = { const loading = ref(false)
label: 'name', const defaultProps = { label: 'name', value: 'id' }
value: 'id' const filterNode = createTreeFilterNode()
}
const emit = defineEmits(['checkTreeNodeChange', 'onAdd', 'checkChange']) watch(filterText, val => treeRef.value?.filter(val))
watch(filterText, val => {
treeRef.value!.filter(val)
})
const onMenuCollapse = () => { const onMenuCollapse = () => {
menuCollapse.value = !menuCollapse.value menuCollapse.value = !menuCollapse.value
proxy.eventBus.emit('cnTreeCollapse', menuCollapse) proxy.eventBus.emit('cnTreeCollapse', menuCollapse)
} }
const save = () => { const save = () => {
loading.value = true loading.value = true
emit('checkChange') emit('checkChange')
} }
const filterNode = (value: string, data: any, node: any) => {
console.log(value, data, node, 'filterNode')
if (!value) return true
// return data.name.includes(value)
if (data.name) {
return chooseNode(value, data, node)
}
}
// 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配则返回该节点以及其下的所有子节点如果参数是子节点则返回该节点的父节点。name是中文字符enName是英文字符.
const chooseNode = (value: string, data: any, node: any) => {
if (data.name.indexOf(value) !== -1) {
return true
}
const level = node.level
// 如果传入的节点本身就是一级节点就不用校验了
if (level === 1) {
return false
}
// 先取当前节点的父节点
let parentData = node.parent
// 遍历当前节点的父节点
let index = 0
while (index < level - 1) {
// 如果匹配到直接返回此处name值是中文字符enName是英文字符。判断匹配中英文过滤
if (parentData.data.name.indexOf(value) !== -1) {
return true
}
// 否则的话再往上一层做匹配
parentData = parentData.parent
index++
}
// 没匹配到返回false
return false
}
const checkTreeNodeChange = () => { const checkTreeNodeChange = () => {
// console.log(treeRef.value?.getCheckedNodes(), "ikkkkkiisiiisis");
emit('checkTreeNodeChange', treeRef.value?.getCheckedNodes()) emit('checkTreeNodeChange', treeRef.value?.getCheckedNodes())
} }
const onAdd = () => { const onAdd = () => emit('onAdd')
emit('onAdd')
}
const treeRef = ref<InstanceType<typeof ElTree>>() const treeRef = ref<InstanceType<typeof ElTree>>()
defineExpose({ treeRef, loading }) defineExpose({ treeRef, loading })
@@ -183,7 +145,5 @@ defineExpose({ treeRef, loading })
.custom-tree-node { .custom-tree-node {
display: flex; display: flex;
align-items: center; align-items: center;
} }
</style> </style>

View File

@@ -1,27 +1,43 @@
<template> <template>
<div :style="{ width: menuCollapse ? '40px' : props.width }" style='transition: all 0.3s; overflow: hidden;'> <div :style="{ width: menuCollapse ? '40px' : props.width }" style="transition: all 0.3s; overflow: hidden">
<Icon v-show='menuCollapse' @click='onMenuCollapse' :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" <!-- <Icon
:class="menuCollapse ? 'unfold' : ''" size='18' class='fold ml10 mt20 menu-collapse' v-show="menuCollapse"
style='cursor: pointer' /> @click="onMenuCollapse"
<div class='cn-tree' :style='{ opacity: menuCollapse ? 0 : 1 }'> :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
<div style='display: flex; align-items: center' class='mb10'> :class="menuCollapse ? 'unfold' : ''"
<el-input maxlength="32" show-word-limit v-model.trim='filterText' placeholder='请输入内容' clearable> size="18px"
class="fold ml10 mt20 menu-collapse"
style="cursor: pointer"
/> -->
<div class="cn-tree" :style="{ opacity: menuCollapse ? 0 : 1 }">
<div style="display: flex; align-items: center" class="mb10">
<el-input maxlength="32" v-model.trim="filterText" placeholder="请输入内容" clearable>
<template #prefix> <template #prefix>
<Icon name='el-icon-Search' style='font-size: 16px' /> <Icon name="el-icon-Search" style="font-size: 16px" />
</template> </template>
</el-input> </el-input>
<!-- <Icon @click='onMenuCollapse' :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
:class="menuCollapse ? 'unfold' : ''" size='18' class='fold ml10 menu-collapse'
style='cursor: pointer' v-if='props.canExpand' /> -->
</div> </div>
<el-tree :style="{ height: 'calc(100vh - 230px)' }" <el-tree
style=' overflow: auto;' ref='treeRef' :props='defaultProps' highlight-current :default-expand-all="false" :style="{ height: 'calc(100vh - 230px)' }"
@check-change="checkTreeNodeChange" :filter-node-method='filterNode' node-key='id' v-bind='$attrs'> style="overflow: auto"
<template #default='{ node, data }'> ref="treeRef"
<span class='custom-tree-node'> :props="defaultProps"
<Icon :name='data.icon' style='font-size: 16px' :style='{ color: data.color }' highlight-current
v-if='data.icon' /> :default-expand-all="false"
<span style='margin-left: 4px'>{{ node.label }}</span> @check-change="checkTreeNodeChange"
:filter-node-method="filterNode"
node-key="id"
v-bind="$attrs"
>
<template #default="{ node, data: nodeData }">
<span class="custom-tree-node">
<Icon
:name="nodeData.icon"
style="font-size: 16px"
:style="{ color: nodeData.color }"
v-if="nodeData.icon"
/>
<span style="margin-left: 4px">{{ node.label }}</span>
</span> </span>
</template> </template>
</el-tree> </el-tree>
@@ -29,14 +45,13 @@
</div> </div>
</template> </template>
<script lang='ts' setup> <script lang="ts" setup>
import useCurrentInstance from '@/utils/useCurrentInstance' import useCurrentInstance from '@/utils/useCurrentInstance'
import { ElTree } from 'element-plus' import { ElTree } from 'element-plus'
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { createTreeFilterNode } from './govern/treeFilterUtils'
defineOptions({ defineOptions({ name: 'govern/cloudDevice', inheritAttrs: false })
name: 'govern/tree'
})
interface Props { interface Props {
width?: string width?: string
@@ -47,66 +62,31 @@ const props = withDefaults(defineProps<Props>(), {
width: '280px', width: '280px',
canExpand: true canExpand: true
}) })
const emit = defineEmits(['checkTreeNodeChange'])
const { proxy } = useCurrentInstance() const { proxy } = useCurrentInstance()
const menuCollapse = ref(false) const menuCollapse = ref(false)
const filterText = ref('') const filterText = ref('')
const defaultProps = { const defaultProps = { label: 'name', value: 'id' }
label: 'name', const filterNode = createTreeFilterNode()
value: 'id'
} watch(filterText, val => treeRef.value?.filter(val))
const emit = defineEmits(['checkTreeNodeChange'])
watch(filterText, val => {
treeRef.value!.filter(val)
})
const onMenuCollapse = () => { const onMenuCollapse = () => {
menuCollapse.value = !menuCollapse.value menuCollapse.value = !menuCollapse.value
proxy.eventBus.emit('cnTreeCollapse', menuCollapse) proxy.eventBus.emit('cnTreeCollapse', menuCollapse)
} }
const filterNode = (value: string, data: any, node: any) => {
if (!value) return true
// return data.name.includes(value)
if (data.name) {
return chooseNode(value, data, node)
}
}
// 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配则返回该节点以及其下的所有子节点如果参数是子节点则返回该节点的父节点。name是中文字符enName是英文字符.
const chooseNode = (value: string, data: any, node: any) => {
if (data.name.indexOf(value) !== -1) {
return true
}
const level = node.level
// 如果传入的节点本身就是一级节点就不用校验了
if (level === 1) {
return false
}
// 先取当前节点的父节点
let parentData = node.parent
// 遍历当前节点的父节点
let index = 0
while (index < level - 1) {
// 如果匹配到直接返回此处name值是中文字符enName是英文字符。判断匹配中英文过滤
if (parentData.data.name.indexOf(value) !== -1) {
return true
}
// 否则的话再往上一层做匹配
parentData = parentData.parent
index++
}
// 没匹配到返回false
return false
}
const checkTreeNodeChange = () => { const checkTreeNodeChange = () => {
// console.log(treeRef.value?.getCheckedNodes(), "ikkkkkiisiiisis");
emit('checkTreeNodeChange', treeRef.value?.getCheckedNodes()) emit('checkTreeNodeChange', treeRef.value?.getCheckedNodes())
} }
const treeRef = ref<InstanceType<typeof ElTree>>() const treeRef = ref<InstanceType<typeof ElTree>>()
defineExpose({ treeRef }) defineExpose({ treeRef })
</script> </script>
<style lang='scss' scoped> <style lang="scss" scoped>
.cn-tree { .cn-tree {
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;

View File

@@ -1,61 +1,66 @@
<!-- 设备管理使用折叠面板渲染多个tree --> <!-- 设备管理使用折叠面板渲染多个tree -->
<template> <template>
<div :style="{ width: menuCollapse ? '40px' : props.width }" style="display: flex; overflow: hidden"> <div :style="{ width: menuCollapse ? '40px' : props.width }" style="display: flex; overflow: hidden">
<Icon <!-- <Icon
v-show="menuCollapse" v-show="menuCollapse"
@click="onMenuCollapse" @click="onMenuCollapse"
:name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
:class="menuCollapse ? 'unfold' : ''" :class="menuCollapse ? 'unfold' : ''"
size="18" size="18px"
class="fold ml10 mt20 menu-collapse" class="fold ml10 mt20 menu-collapse"
style="cursor: pointer" style="cursor: pointer"
/> /> -->
<div class="cn-tree" :style="{ opacity: menuCollapse ? 0 : 1 }"> <div class="cn-tree" :style="{ opacity: menuCollapse ? 0 : 1 }">
<div style="display: flex; align-items: center" class="mb10"> <div style="display: flex; align-items: center" class="mb10">
<!-- <el-form-item> -->
<el-input <el-input
maxlength="32" maxlength="32"
show-word-limit
v-model.trim="filterText" v-model.trim="filterText"
autocomplete="off" autocomplete="off"
placeholder="请输入内容" placeholder="请输入内容"
clearable clearable
> >
<template #prepend>
<el-select v-model="treeType" @change="changeTreeType" style="min-width: 75px">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
<template #prefix> <template #prefix>
<Icon name="el-icon-Search" style="font-size: 16px" /> <Icon name="el-icon-Search" style="font-size: 16px" />
</template> </template>
</el-input> </el-input>
<!-- </el-form-item> -->
<Icon <!-- <Icon
@click="onMenuCollapse" @click="onMenuCollapse"
:name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
:class="menuCollapse ? 'unfold' : ''" :class="menuCollapse ? 'unfold' : ''"
size="18" size="18px"
class="fold ml10 menu-collapse" class="fold ml10 menu-collapse"
style="cursor: pointer" style="cursor: pointer"
v-if="props.canExpand" v-if="props.canExpand"
/> /> -->
</div> </div>
<el-collapse <el-collapse
:accordion="true" :accordion="true"
v-model.trim="activeName" v-model="activeName"
style="flex: 1; height: 100%" style="flex: 1; height: 100%"
@change="changeDevice" @change="changeDevice"
v-if="treeType == '1'"
v-loading="loading"
> >
<el-collapse-item title="治理设备" name="0" v-if="zlDeviceData.length != 0"> <el-collapse-item title="治理设备" name="0" v-if="zlDeviceData.length">
<el-select v-model.trim="process" clearable placeholder="请选择状态" class="mb10"> <el-select v-model="process" clearable placeholder="请选择状态" class="mb10">
<el-option label="功能调试" value="2"></el-option> <el-option label="功能调试" value="2" />
<el-option label="出厂调试" value="3"></el-option> <el-option label="出厂调试" value="3" />
<el-option label="正式投运" value="4"></el-option> <el-option label="正式投运" value="4" />
</el-select> </el-select>
<el-tree <el-tree
:style="{ :style="{ height: governTreeHeight }"
height:
bxsDeviceData.length != 0
? `calc(100vh - 380px - ${props.height}px)`
: 'calc(100vh - 278px)'
}"
ref="treeRef1" ref="treeRef1"
:props="defaultProps" :props="defaultProps"
highlight-current highlight-current
@@ -66,27 +71,22 @@
:data="zlDevList" :data="zlDevList"
style="overflow: auto" style="overflow: auto"
> >
<template #default="{ node, data }"> <template #default="{ node, data: nodeData }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<Icon <Icon
:name="data.icon" :name="nodeData.icon"
style="font-size: 16px" style="font-size: 16px"
:style="{ color: data.color }" :style="{ color: nodeData.color }"
v-if="data.icon" v-if="nodeData.icon"
/> />
<span style="margin-left: 4px">{{ node.label }}</span> <span style="margin-left: 4px">{{ node.label }}</span>
</span> </span>
</template> </template>
</el-tree> </el-tree>
</el-collapse-item> </el-collapse-item>
<el-collapse-item title="便携式设备" name="1" v-if="bxsDeviceData.length != 0"> <el-collapse-item title="便携式设备" name="1" v-if="bxsDeviceData.length">
<el-tree <el-tree
:style="{ :style="{ height: otherTreeHeight }"
height:
zlDeviceData.length != 0
? `calc(100vh - 340px - ${props.height}px)`
: 'calc(100vh - 238px)'
}"
ref="treeRef2" ref="treeRef2"
:props="defaultProps" :props="defaultProps"
highlight-current highlight-current
@@ -97,27 +97,22 @@
v-bind="$attrs" v-bind="$attrs"
style="overflow: auto" style="overflow: auto"
> >
<template #default="{ node, data }"> <template #default="{ node, data: nodeData }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<Icon <Icon
:name="data.icon" :name="nodeData.icon"
style="font-size: 16px" style="font-size: 16px"
:style="{ color: data.color }" :style="{ color: nodeData.color }"
v-if="data.icon" v-if="nodeData.icon"
/> />
<span style="margin-left: 4px">{{ node.label }}</span> <span style="margin-left: 4px">{{ node.label }}</span>
</span> </span>
</template> </template>
</el-tree> </el-tree>
</el-collapse-item> </el-collapse-item>
<el-collapse-item title="监测设备" name="2" v-if="frontDeviceData.length != 0"> <el-collapse-item title="监测设备" name="2" v-if="frontDeviceData.length">
<el-tree <el-tree
:style="{ :style="{ height: otherTreeHeight }"
height:
zlDeviceData.length != 0
? `calc(100vh - 340px - ${props.height}px)`
: 'calc(100vh - 238px)'
}"
ref="treeRef3" ref="treeRef3"
:props="defaultProps" :props="defaultProps"
highlight-current highlight-current
@@ -128,13 +123,13 @@
v-bind="$attrs" v-bind="$attrs"
style="overflow: auto" style="overflow: auto"
> >
<template #default="{ node, data }"> <template #default="{ node, data: nodeData }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<Icon <Icon
:name="data.icon" :name="nodeData.icon"
style="font-size: 16px" style="font-size: 16px"
:style="{ color: data.color }" :style="{ color: nodeData.color }"
v-if="data.icon" v-if="nodeData.icon"
/> />
<span style="margin-left: 4px">{{ node.label }}</span> <span style="margin-left: 4px">{{ node.label }}</span>
</span> </span>
@@ -142,246 +137,249 @@
</el-tree> </el-tree>
</el-collapse-item> </el-collapse-item>
</el-collapse> </el-collapse>
<div v-if="treeType == '2'" v-loading="loading">
<el-tree
:style="{ height: engineeringTreeHeight }"
ref="treeRef4"
:props="defaultProps"
highlight-current
:filter-node-method="filterNode"
node-key="id"
v-bind="$attrs"
:data="props.data"
style="overflow: auto"
:default-expand-all="false"
>
<template #default="{ node, data: nodeData }">
<span class="custom-tree-node">
<Icon
:name="nodeData.icon"
style="font-size: 16px"
:style="{ color: nodeData.color }"
v-if="nodeData.icon"
/>
<span style="margin-left: 4px">{{ node.label }}</span>
</span>
</template>
</el-tree>
</div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import useCurrentInstance from '@/utils/useCurrentInstance' import useCurrentInstance from '@/utils/useCurrentInstance'
import { ElTree } from 'element-plus' import { ElTree, type CollapseModelValue } from 'element-plus'
import { ref, watch, defineEmits, onMounted, nextTick } from 'vue' import { ref, watch, onMounted, nextTick, computed } from 'vue'
import { collectDeviceLeaves } from './govern/lineTreeUtils'
import { collectDeviceApiLeaves } from './govern/deviceTreeUtils'
defineOptions({ defineOptions({
name: 'govern/tree' name: 'govern/tree',
inheritAttrs: false
}) })
const emit = defineEmits(['changeDeviceType'])
const emit = defineEmits(['changeDeviceType', 'changeTreeType'])
interface Props { interface Props {
width?: string width?: string
canExpand?: boolean canExpand?: boolean
type?: string type?: string
data?: any data?: any[]
height?: number height?: number
engineering?: boolean
/** line: getLineTree 四层叶子device: getDeviceTree 三层叶子 */
leafMode?: 'line' | 'device'
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
width: '280px', width: '280px',
canExpand: true, canExpand: true,
type: '', type: '',
data: [], data: () => [],
height: 0 height: 0,
engineering: false,
leafMode: 'line'
}) })
const treeType = ref('1')
const options = [
{ label: '设备', value: '1' },
{ label: '工程', value: '2' }
]
const { proxy } = useCurrentInstance() const { proxy } = useCurrentInstance()
const menuCollapse = ref(false) const menuCollapse = ref(false)
const activeName = ref('0') const activeName = ref('0')
const filterText = ref('') const filterText = ref('')
const defaultProps = {
label: 'name',
value: 'id'
}
const process = ref('') const process = ref('')
//治理设备数据 const loading = ref(false)
const zlDeviceData = ref([])
const zlDevList = ref<any>([]) const defaultProps = { label: 'name', value: 'id' }
//便携式设备数据
const bxsDeviceData = ref([]) const zlDeviceData = ref<any[]>([])
//前置设备数据 const zlDevList = ref<any[]>([])
const frontDeviceData = ref([]) const bxsDeviceData = ref<any[]>([])
const frontDeviceData = ref<any[]>([])
const governTreeHeight = computed(() => `calc(100vh - 380px - ${props.height}px)`)
const otherTreeHeight = computed(() =>
zlDeviceData.value.length ? `calc(100vh - 340px - ${props.height}px)` : `calc(100vh - 238px - ${props.height}px)`
)
const engineeringTreeHeight = computed(() => `calc(100vh - 188px - ${props.height}px)`)
const treeRef1 = ref<InstanceType<typeof ElTree>>()
const treeRef2 = ref<InstanceType<typeof ElTree>>()
const treeRef3 = ref<InstanceType<typeof ElTree>>()
const treeRef4 = ref<InstanceType<typeof ElTree>>()
defineExpose({ treeRef1, treeRef2, treeRef3, treeRef4 })
function splitTreeData(val: any[]) {
zlDeviceData.value = []
bxsDeviceData.value = []
frontDeviceData.value = []
val.forEach(item => {
if (item.name === '治理设备') {
zlDeviceData.value = item.children ?? []
} else if (item.name === '便携式设备') {
bxsDeviceData.value = item.children ?? []
} else if (item.name === '监测设备') {
frontDeviceData.value = item.children ?? []
}
})
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
}
function filterProcess(nodes: any[]): any[] {
if (!process.value) return nodes
return nodes
.map(node => {
const children = node.children ? filterProcess(node.children) : []
if (node.process == process.value || children.length > 0) {
return { ...node, children }
}
return null
})
.filter(Boolean)
}
function getActiveTreeRef() {
if (treeType.value === '2') return treeRef4.value
if (activeName.value === '0') return treeRef1.value
if (activeName.value === '1') return treeRef2.value
return treeRef3.value
}
function resolveActiveName() {
if (zlDeviceData.value.length) return '0'
if (bxsDeviceData.value.length) return '1'
if (frontDeviceData.value.length) return '2'
return ''
}
function selectDevicePanel(panelName: string, node?: any) {
if (!node) return
emit('changeDeviceType', panelName, node)
nextTick(() => {
getActiveTreeRef()?.setCurrentKey(node.id)
})
}
const changeDevice = (val: CollapseModelValue) => {
if (Array.isArray(val) || val == null || val === '') return
const panelName = String(val)
const collectLeaves = props.leafMode === 'device' ? collectDeviceApiLeaves : collectDeviceLeaves
const { govern, portable, monitor } = collectLeaves(
zlDevList.value,
bxsDeviceData.value,
frontDeviceData.value
)
const panelMap: Record<string, { nodes: any[]; clearOthers: any[][] }> = {
'0': { nodes: govern, clearOthers: [portable, monitor] },
'1': { nodes: portable, clearOthers: [govern, monitor] },
'2': { nodes: monitor, clearOthers: [govern, portable] }
}
const panel = panelMap[panelName]
if (!panel) return
panel.clearOthers.forEach(list => list.forEach(item => (item.checked = false)))
selectDevicePanel(panelName, panel.nodes[0])
}
const setActiveName = () => {
activeName.value = resolveActiveName()
if (activeName.value) {
nextTick(() => changeDevice(activeName.value))
}
}
watch( watch(
() => props.data, () => props.data,
(val, oldVal) => { val => {
if (val && val.length != 0) { if (!val?.length) return
val.map((item: any) => { splitTreeData(val)
if (item.name == '治理设备') { if (treeType.value === '1') {
zlDeviceData.value = [] nextTick(() => setActiveName())
item.children.map((vv: any) => {
zlDeviceData.value.push(vv)
})
zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value))
} else if (item.name == '便携式设备') {
bxsDeviceData.value = []
item.children.map((vv: any) => {
bxsDeviceData.value.push(vv)
})
} else if (item.name == '监测设备') {
frontDeviceData.value = []
item.children.map((vv: any) => {
frontDeviceData.value.push(vv)
})
}
})
} }
}, },
{ { immediate: true, deep: true }
immediate: true,
deep: true
}
) )
watch(filterText, val => { watch(filterText, val => {
if (activeName.value == '0') { getActiveTreeRef()?.filter(val)
treeRef1.value!.filter(val) })
} else if (activeName.value == '1') {
treeRef2.value!.filter(val) watch(process, () => {
} else { zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
treeRef3.value!.filter(val) if (activeName.value === '0') {
nextTick(() => changeDevice(activeName.value))
} }
}) })
watch(process, val => {
if (val == '' || val == undefined) {
zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value))
console.log('🚀 ~ zlDevList.value:', zlDeviceData.value)
} else {
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
}
setTimeout(() => {
changeDevice(activeName.value)
}, 0)
})
function filterProcess(nodes: any) {
if (process.value == '') {
return nodes
}
return nodes
.map(node => {
// 递归处理子节点
const children = node.children ? filterProcess(node.children) : []
// 如果当前节点的process=4或者有子节点满足条件则保留当前节点
if (node.process == process.value || children.length > 0) {
return {
...node,
children: children
}
}
// 否则过滤掉当前节点
return null
})
.filter(Boolean) // 移除null节点
}
const onMenuCollapse = () => { const onMenuCollapse = () => {
menuCollapse.value = !menuCollapse.value menuCollapse.value = !menuCollapse.value
proxy.eventBus.emit('cnTreeCollapse', menuCollapse) proxy.eventBus.emit('cnTreeCollapse', menuCollapse)
} }
const filterNode = (value: string, data: any, node: any) => {
const filterNode = (value: string, data: any, node: any): boolean => {
if (!value) return true if (!value) return true
// return data.name.includes(value) if (!data.name) return false
if (data.name) { return chooseNode(value, data, node)
return chooseNode(value, data, node)
}
} }
// 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配则返回该节点以及其下的所有子节点如果参数是子节点则返回该节点的父节点。name是中文字符enName是英文字符. const chooseNode = (value: string, data: any, node: any): boolean => {
const chooseNode = (value: string, data: any, node: any) => { if (data.name.indexOf(value) !== -1) return true
if (data.name.indexOf(value) !== -1) {
return true
}
const level = node.level const level = node.level
// 如果传入的节点本身就是一级节点就不用校验了 if (level === 1) return false
if (level === 1) {
return false
}
// 先取当前节点的父节点
let parentData = node.parent let parentData = node.parent
// 遍历当前节点的父节点 for (let i = 0; i < level - 1; i++) {
let index = 0 if (parentData?.data?.name?.indexOf(value) !== -1) return true
while (index < level - 1) {
// 如果匹配到直接返回此处name值是中文字符enName是英文字符。判断匹配中英文过滤
if (parentData.data.name.indexOf(value) !== -1) {
return true
}
// 否则的话再往上一层做匹配
parentData = parentData.parent parentData = parentData.parent
index++
} }
// 没匹配到返回false
return false return false
} }
const changeDevice = (val: any) => { const changeTreeType = (val: string) => {
let arr1: any = [] loading.value = true
emit('changeTreeType', val)
//zlDeviceData if (val === '1') {
nextTick(() => setActiveName())
zlDevList.value.forEach((item: any) => {
item.children.forEach((item2: any) => {
item2.children.forEach((item3: any) => {
arr1.push(item3)
})
})
})
let arr2: any = []
bxsDeviceData.value.forEach((item: any) => {
// item.children.forEach((item2: any) => {
arr2.push(item)
// })
})
let arr3: any = []
frontDeviceData.value.forEach((item: any) => {
item.children.forEach((item2: any) => {
item2.children.forEach((item3: any) => {
arr3.push(item3)
})
})
})
if (val == '0') {
arr2.map((item: any) => {
item.checked = false
})
emit('changeDeviceType', activeName.value, arr1[0])
setTimeout(() => {
treeRef1.value?.setCurrentKey(arr1[0]?.id)
}, 100)
} }
if (val == '1') {
arr1.map((item: any) => {
item.checked = false
})
emit('changeDeviceType', activeName.value, arr2[0])
setTimeout(() => {
treeRef2.value?.setCurrentKey(arr2[0]?.id)
}, 100)
}
if (val == '2') {
arr3.map((item: any) => {
item.checked = false
})
emit('changeDeviceType', activeName.value, arr3[0])
setTimeout(() => {
treeRef3.value?.setCurrentKey(arr3[0]?.id)
}, 100)
}
}
//治理
const treeRef1 = ref<InstanceType<typeof ElTree>>()
//便携式
const treeRef2 = ref<InstanceType<typeof ElTree>>()
//前置
const treeRef3 = ref<InstanceType<typeof ElTree>>()
defineExpose({ treeRef1, treeRef2, treeRef3 })
onMounted(() => {
setTimeout(() => { setTimeout(() => {
if (zlDeviceData.value.length != 0) { loading.value = false
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value))) }, 300)
activeName.value = '0' }
}
if (zlDeviceData.value.length === 0 && bxsDeviceData.value.length != 0) { onMounted(() => {
activeName.value = '1' treeType.value = props.engineering ? '2' : '1'
}
if (zlDeviceData.value.length === 0 && bxsDeviceData.value.length === 0) {
activeName.value = '2'
}
if (!zlDeviceData.value && !bxsDeviceData.value) {
activeName.value = ''
}
nextTick(() => {
changeDevice(activeName.value)
})
}, 500)
}) })
</script> </script>
@@ -412,4 +410,10 @@ onMounted(() => {
display: flex; display: flex;
align-items: center; align-items: center;
} }
:deep(.el-input-group__prepend) {
background-color: var(--el-fill-color-blank);
}
:deep(.is-disabled) {
display: none !important;
}
</style> </style>

View File

@@ -0,0 +1,205 @@
<template>
<Tree ref="treRef" @check="handleCheck" @check-change="handleCheckChange" :default-checked-keys="defaultCheckedKeys" check-strictly
:show-checkbox="props.showCheckbox" :data="tree" :height="props.height" @changeDeviceType="changeDeviceType"
@changeTreeType="loadTree" :engineering="props.engineering" />
</template>
<script lang="ts" setup>
import { ref, nextTick, onMounted } from 'vue'
import Tree from '../device.vue'
import { getLineTree } from '@/api/cs-device-boot/csLedger'
import { useConfig } from '@/stores/config'
import {
createLineTreeDecorators,
decorateLineTree,
type LineTreeLeaves
} from './lineTreeUtils'
defineOptions({
name: 'govern/analyzeTree'
})
const props = withDefaults(
defineProps<{
showCheckbox?: boolean
defaultCheckedKeys?: any
height?: number
engineering?: boolean
}>(),
{
showCheckbox: false,
defaultCheckedKeys: () => [],
height: 0,
engineering: false
}
)
const emit = defineEmits(['init', 'checkChange', 'check', 'deviceTypeChange'])
const config = useConfig()
const tree = ref<any[]>([])
const treRef = ref<InstanceType<typeof Tree>>()
const decorators = createLineTreeDecorators(() => config.getColorVal('elementUiPrimary'))
const changeDeviceType = (val: any, obj: any) => {
emit('deviceTypeChange', val, obj)
}
type TreeRefKey = 'treeRef1' | 'treeRef2' | 'treeRef3' | 'treeRef4'
async function waitForTreeRef(refKey: TreeRefKey, maxRetries = 20) {
for (let i = 0; i < maxRetries; i++) {
await nextTick()
if (treRef.value?.[refKey]) return treRef.value[refKey]
await new Promise(resolve => setTimeout(resolve, 50))
}
return null
}
async function selectInitialNode(
type: string | undefined,
leaves: LineTreeLeaves
) {
const candidates: { refKey: TreeRefKey; list: any[]; level: number }[] =
type === '2'
? [{ refKey: 'treeRef4', list: leaves.engineering, level: 3 }]
: [
{ refKey: 'treeRef1', list: leaves.govern, level: 2 },
{ refKey: 'treeRef2', list: leaves.portable, level: 2 },
{ refKey: 'treeRef3', list: leaves.monitor, level: 2 }
]
for (const { refKey, list, level } of candidates) {
const node = list[0]
if (!node) continue
const treeInstance = await waitForTreeRef(refKey)
treeInstance?.setCurrentKey(node.id)
emit('init', { level, ...node })
return
}
emit('init')
}
const loadTree = (type?: string) => {
getLineTree({ type: type === '2' ? 'engineering' : '' }).then(res => {
const leaves = decorateLineTree(res.data, type, decorators)
tree.value = res.data
selectInitialNode(type, leaves)
})
}
onMounted(() => {
loadTree(props.engineering ? '2' : '1')
})
const handleCheck = (data: any, state: any) => {
emit('check', data, state)
}
const handleCheckChange = (data: any, checked: any, indeterminate: any) => {
emit('checkChange', { data, checked, indeterminate })
}
defineExpose({ treRef })
</script>

View File

@@ -1,179 +1,81 @@
<template> <template>
<Tree ref="treRef" :width="width" :showPush="props.showPush" :data="tree" default-expand-all @changePointType="changePointType" @onAdd="onAdd"/> <Tree
ref="treRef"
:width="width"
:showPush="props.showPush"
:expand-on-click-node="false"
:data="tree"
@changePointType="changePointType"
@onAdd="onAdd"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, nextTick, onMounted, defineProps } from 'vue' import { ref } from 'vue'
import Tree from '../index.vue' import Tree from '../index.vue'
import { getLineTree,getCldTree } from '@/api/cs-device-boot/csLedger' import { getCldTree } from '@/api/cs-device-boot/csLedger'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { querySysExcel } from '@/api/harmonic-boot/luckyexcel' import { querySysExcel } from '@/api/harmonic-boot/luckyexcel'
import { useDictData } from '@/stores/dictData' import { useDictData } from '@/stores/dictData'
import { createLineTreeDecorators } from './lineTreeUtils'
import { decorateCloudTree } from './deviceTreeUtils'
import { bootstrapWithTemplate, selectTreeNode } from './treeCommonUtils'
interface Props { interface Props {
template?: boolean template?: boolean
showPush?: boolean showPush?: boolean
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
template: false, template: false,
showPush: false showPush: false
}) })
defineOptions({
name: 'govern/deviceTree'
})
const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy','onAdd']) defineOptions({ name: 'govern/cloudDeviceEntryTree' })
const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy', 'onAdd'])
const config = useConfig() const config = useConfig()
const tree = ref()
const dictData = useDictData() const dictData = useDictData()
const treRef = ref() const tree = ref<any[]>([])
const treRef = ref<InstanceType<typeof Tree>>()
const width = ref('') const width = ref('')
const decorators = createLineTreeDecorators(() => config.getColorVal('elementUiPrimary'))
const info = (selectedNodeId?: string) => { const changePointType = (_val: any, obj: any) => {
emit('pointTypeChange', _val, obj)
}
const onAdd = () => emit('onAdd')
async function loadTree() {
tree.value = [] tree.value = []
let arr1: any[] = [] const res = await getCldTree()
getCldTree().then(res => { const leaves = decorateCloudTree(res.data, decorators)
try { tree.value = [res.data]
// 检查响应数据结构
let rootData = null;
if (Array.isArray(res.data)) {
// 旧的数据结构 - 数组
rootData = res.data.find((item: any) => item.name == '监测设备');
} else if (res.data && res.data.name == '监测设备') {
// 新的数据结构 - 单个对象
rootData = res.data;
}
// 治理设备 const node = leaves[0]
if (rootData) { if (!node) {
rootData.icon = 'el-icon-Menu' emit('init')
rootData.level = 0 return
rootData.color = config.getColorVal('elementUiPrimary') }
// 确保根节点的 children 是数组
if (!Array.isArray(rootData.children)) {
rootData.children = []
}
rootData.children.forEach((item: any) => {
item.icon = 'el-icon-HomeFilled'
item.level = 1
item.color = config.getColorVal('elementUiPrimary')
// 确保 children 是数组
if (!Array.isArray(item.children)) {
item.children = []
}
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-List'
item2.level = 2
item2.color = config.getColorVal('elementUiPrimary')
// 确保 children 是数组
if (!Array.isArray(item2.children)) {
item2.children = []
}
item2.children.forEach((item3: any) => {
item3.icon = 'el-icon-Platform'
item3.level = 3
item3.color =
item3.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
// 确保 children 是数组
if (!Array.isArray(item3.children)) {
item3.children = []
}
item3.children.forEach((item4: any) => {
item4.icon = 'el-icon-Platform'
item4.level = 4
item4.color =
item4.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
arr1.push(item4)
})
})
})
})
tree.value = [rootData] // 确保 tree.value 是数组
} else {
tree.value = []
}
nextTick(() => {
if (arr1.length) {
// 安全检查 treRef 和 treeRef 是否存在
console.log("🚀 ~ info ~ treRef.value && treRef.value.treeRef && treRef.value.treeRef.setCurrentKey:", treRef.value && treRef.value.treeRef1 && treRef.value.treeRef1.setCurrentKey)
if (treRef.value && treRef.value.treeRef && treRef.value.treeRef.setCurrentKey) { await selectTreeNode(treRef.value, node, {
// 如果传入了要选中的节点ID则选中该节点否则选中第一个节点 level: 3,
console.log('selectedNodeId:', selectedNodeId); onSelect: selected => {
if (selectedNodeId) { emit('init', { level: 3, ...selected })
treRef.value.treeRef.setCurrentKey(selectedNodeId); changePointType('4', selected)
// 查找对应的节点数据并触发事件
let selectedNode = null;
const findNode = (nodes: any[]) => {
for (const node of nodes) {
if (node.id === selectedNodeId) {
selectedNode = node;
return true;
}
if (node.children && findNode(node.children)) {
return true;
}
}
return false;
};
findNode(tree.value);
if (selectedNode) {
emit('init', {
level: selectedNode.level,
...selectedNode
});
}
} else {
// 初始化选中第一个节点
treRef.value.treeRef.setCurrentKey(arr1[0].id);
emit('init', {
level: 2,
...arr1[0]
});
}
}
} else {
}
})
} catch (error) {
console.error('Error in processing getCldTree response:', error)
} }
}) })
} }
bootstrapWithTemplate(
props.template,
loadTree,
() => querySysExcel({ id: dictData.state.area[0]?.id }),
data => emit('Policy', data)
)
const changePointType = (val: any, obj: any) => { defineExpose({ info: loadTree })
emit('pointTypeChange', val, obj)
}
const onAdd = () => {
emit('onAdd')
}
if (props.template) {
querySysExcel({ id: dictData.state.area[0]?.id })
.then((res: any) => {
emit('Policy', res.data)
info()
})
.catch(err => {
info()
})
} else {
info()
}
// 暴露 info 方法给父组件调用
defineExpose({
info
})
onMounted(() => {})
</script> </script>

View File

@@ -1,106 +1,70 @@
<template> <template>
<Tree
ref="treRef" <Tree ref="treRef" :width="width" :height="height" :showPush="props.showPush" :data="tree" default-expand-all
:width="width" @changePointType="changePointType" @onAdd="onAdd" />
:showPush="props.showPush"
:data="tree"
default-expand-all
@changePointType="changePointType"
@onAdd="onAdd"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, nextTick, onMounted, defineProps } from 'vue' import { ref } from 'vue'
import Tree from '../index.vue' import Tree from '../index.vue'
import { getLineTree, objTree } from '@/api/cs-device-boot/csLedger' import { objTree } from '@/api/cs-device-boot/csLedger'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { querySysExcel } from '@/api/harmonic-boot/luckyexcel' import { querySysExcel } from '@/api/harmonic-boot/luckyexcel'
import { useDictData } from '@/stores/dictData' import { useDictData } from '@/stores/dictData'
import { createLineTreeDecorators } from './lineTreeUtils'
import { decorateObjTree } from './deviceTreeUtils'
import { bootstrapWithTemplate, selectTreeNode } from './treeCommonUtils'
interface Props { interface Props {
template?: boolean template?: boolean
showPush?: boolean showPush?: boolean
height?: number
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
template: false, template: false,
showPush: false showPush: false,
}) height: 0
defineOptions({
name: 'govern/deviceTree'
}) })
defineOptions({ name: 'govern/cloudDeviceEntryTreeZL' })
const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy', 'onAdd']) const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy', 'onAdd'])
const config = useConfig() const config = useConfig()
const tree = ref()
const dictData = useDictData() const dictData = useDictData()
const treRef = ref() const tree = ref<any[]>([])
const treRef = ref<InstanceType<typeof Tree>>()
const width = ref('') const width = ref('')
const info = (selectedNodeId?: string) => { const decorators = createLineTreeDecorators(() => config.getColorVal('elementUiPrimary'))
const changePointType = (val: any, obj: any) => emit('pointTypeChange', val, obj)
const onAdd = () => emit('onAdd')
async function loadTree() {
tree.value = [] tree.value = []
let arr1: any[] = [] const res = await objTree()
objTree().then(res => { const leaves = decorateObjTree(res.data, decorators)
try { tree.value = res.data
res.data.map((item: any) => {
item.icon = 'el-icon-HomeFilled' const node = leaves[0]
item.level = 1 if (!node) {
item.color = config.getColorVal('elementUiPrimary') emit('init')
item.children.forEach((item: any) => { return
item.icon = 'el-icon-List' }
item.level = 2
item.color = config.getColorVal('elementUiPrimary') await selectTreeNode(treRef.value, node, {
item.children.forEach((item2: any) => { onSelect: selected => emit('init', selected)
arr1.push(item2)
item2.icon = 'el-icon-Platform'
item2.level = 3
item2.color = config.getColorVal('elementUiPrimary')
})
})
})
tree.value = res.data
nextTick(() => {
if (arr1.length) {
//初始化选中
treRef.value.treeRef.setCurrentKey(arr1[0].id)
// 注册父组件事件
emit('init', arr1[0])
return
} else {
emit('init')
return
}
})
} catch (error) {
console.error('Error in processing getCldTree response:', error)
}
}) })
} }
const changePointType = (val: any, obj: any) => { bootstrapWithTemplate(
emit('pointTypeChange', val, obj) props.template,
} loadTree,
() => querySysExcel({ id: dictData.state.area[0]?.id }),
data => emit('Policy', data)
)
const onAdd = () => { defineExpose({ info: loadTree })
emit('onAdd')
}
if (props.template) {
querySysExcel({ id: dictData.state.area[0]?.id })
.then((res: any) => {
emit('Policy', res.data)
info()
})
.catch(err => {
info()
})
} else {
info()
}
// 暴露 info 方法给父组件调用
defineExpose({
info
})
onMounted(() => {})
</script> </script>

View File

@@ -1,179 +1,80 @@
<template> <template>
<Tree ref="treRef" :width="width" :showPush="props.showPush" :data="tree" default-expand-all @changePointType="changePointType" @onAdd="onAdd"/> <Tree
ref="treRef"
:width="width"
:showPush="props.showPush"
:data="tree"
default-expand-all
@changePointType="changePointType"
@onAdd="onAdd"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, nextTick, onMounted, defineProps } from 'vue' import { ref } from 'vue'
import Tree from '../index.vue' import Tree from '../index.vue'
import { getLineTree,lineTree } from '@/api/cs-device-boot/csLedger' import { lineTree } from '@/api/cs-device-boot/csLedger'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { querySysExcel } from '@/api/harmonic-boot/luckyexcel' import { querySysExcel } from '@/api/harmonic-boot/luckyexcel'
import { useDictData } from '@/stores/dictData' import { createLineTreeDecorators } from './lineTreeUtils'
import { decorateLedgerLineTree, resolveMonitorRoot } from './deviceTreeUtils'
import { bootstrapWithTemplate, findNodeById, selectTreeNode } from './treeCommonUtils'
interface Props { interface Props {
template?: boolean template?: boolean
showPush?: boolean showPush?: boolean
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
template: false, template: false,
showPush: false showPush: false
}) })
defineOptions({
name: 'govern/deviceTree'
})
const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy','onAdd']) defineOptions({ name: 'govern/csLedgerLineTree' })
const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy', 'onAdd'])
const config = useConfig() const config = useConfig()
const tree = ref() const tree = ref<any[]>([])
const dictData = useDictData() const treRef = ref<InstanceType<typeof Tree>>()
const treRef = ref()
const width = ref('') const width = ref('')
const decorators = createLineTreeDecorators(() => config.getColorVal('elementUiPrimary'))
const info = (selectedNodeId?: string) => { const changePointType = (val: any, obj: any) => emit('pointTypeChange', val, obj)
const onAdd = () => emit('onAdd')
async function loadTree(selectedNodeId?: string) {
tree.value = [] tree.value = []
let arr1: any[] = [] const res = await lineTree()
lineTree().then(res => { const rootData = resolveMonitorRoot(res.data)
try { const leaves = decorateLedgerLineTree(rootData, decorators)
// 检查响应数据结构 tree.value = rootData ? [rootData] : []
let rootData = null;
if (Array.isArray(res.data)) {
// 旧的数据结构 - 数组
rootData = res.data.find((item: any) => item.name == '监测设备');
} else if (res.data && res.data.name == '监测设备') {
// 新的数据结构 - 单个对象
rootData = res.data;
}
// 治理设备 if (!leaves.length) {
if (rootData) { emit('init')
rootData.icon = 'el-icon-Menu' return
rootData.level = 0 }
rootData.color = config.getColorVal('elementUiPrimary')
// 确保根节点的 children 是数组
if (!Array.isArray(rootData.children)) {
rootData.children = []
}
rootData.children.forEach((item: any) => {
item.icon = 'el-icon-HomeFilled'
item.level = 1
item.color = config.getColorVal('elementUiPrimary')
// 确保 children 是数组
if (!Array.isArray(item.children)) {
item.children = []
}
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-List'
item2.level = 2
item2.color = config.getColorVal('elementUiPrimary')
// 确保 children 是数组
if (!Array.isArray(item2.children)) {
item2.children = []
}
item2.children.forEach((item3: any) => {
item3.icon = 'el-icon-Platform'
item3.level = 3
item3.color =
item3.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
// 确保 children 是数组
if (!Array.isArray(item3.children)) {
item3.children = []
}
item3.children.forEach((item4: any) => {
item4.icon = 'el-icon-Platform'
item4.level = 4
item4.color =
item4.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
arr1.push(item4)
})
})
})
})
tree.value = [rootData] // 确保 tree.value 是数组
} else {
tree.value = []
}
nextTick(() => {
if (arr1.length) {
// 安全检查 treRef 和 treeRef 是否存在
console.log("🚀 ~ info ~ treRef.value && treRef.value.treeRef && treRef.value.treeRef.setCurrentKey:", treRef.value && treRef.value.treeRef1 && treRef.value.treeRef1.setCurrentKey)
if (treRef.value && treRef.value.treeRef && treRef.value.treeRef.setCurrentKey) { const targetNode = selectedNodeId
// 如果传入了要选中的节点ID则选中该节点否则选中第一个节点 ? findNodeById(tree.value, selectedNodeId) ?? leaves[0]
console.log('selectedNodeId:', selectedNodeId); : leaves[0]
if (selectedNodeId) {
treRef.value.treeRef.setCurrentKey(selectedNodeId); await selectTreeNode(treRef.value, targetNode, {
// 查找对应的节点数据并触发事件 onSelect: selected =>
let selectedNode = null; emit('init', {
const findNode = (nodes: any[]) => { level: selected.level ?? 2,
for (const node of nodes) { ...selected
if (node.id === selectedNodeId) {
selectedNode = node;
return true;
}
if (node.children && findNode(node.children)) {
return true;
}
}
return false;
};
findNode(tree.value);
if (selectedNode) {
emit('init', {
level: selectedNode.level,
...selectedNode
});
}
} else {
// 初始化选中第一个节点
treRef.value.treeRef.setCurrentKey(arr1[0].id);
emit('init', {
level: 2,
...arr1[0]
});
}
}
} else {
}
}) })
} catch (error) {
console.error('Error in processing getCldTree response:', error)
}
}) })
} }
bootstrapWithTemplate(
props.template,
() => loadTree(),
() => querySysExcel({}),
data => emit('Policy', data)
)
const changePointType = (val: any, obj: any) => { defineExpose({ info: loadTree })
emit('pointTypeChange', val, obj)
}
const onAdd = () => {
emit('onAdd')
}
if (props.template) {
querySysExcel({ id: dictData.state.area[0]?.id })
.then((res: any) => {
emit('Policy', res.data)
info()
})
.catch(err => {
info()
})
} else {
info()
}
// 暴露 info 方法给父组件调用
defineExpose({
info
})
onMounted(() => {})
</script> </script>

View File

@@ -1,107 +1,64 @@
<template> <template>
<Tree <Tree
ref="treRef" ref="treRef"
@checkTreeNodeChange="handleCheckChange" @checkTreeNodeChange="handleCheckChange"
:default-checked-keys="defaultCheckedKeys" :default-checked-keys="defaultCheckedKeys"
:show-checkbox="props.showCheckbox" :show-checkbox="props.showCheckbox"
:data="tree" :data="tree"
/> />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, nextTick, defineProps } from 'vue' import { ref } from 'vue'
import Tree from '../index.vue' import Tree from '../index.vue'
import { getDeviceTree } from '@/api/cs-device-boot/csLedger' import { getDeviceTree } from '@/api/cs-device-boot/csLedger'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
defineOptions({ import { createLineTreeDecorators } from './lineTreeUtils'
name: 'govern/deviceTree' import { decorateDeviceInfoTree } from './deviceTreeUtils'
}) import { selectTreeNode } from './treeCommonUtils'
const props = withDefaults(
defineProps<{ defineOptions({ name: 'govern/deviceInfoTree' })
showCheckbox?: boolean
defaultCheckedKeys?: any const props = withDefaults(
}>(), defineProps<{
{ showCheckbox?: boolean
showCheckbox: false, defaultCheckedKeys?: any[]
defaultCheckedKeys: [] }>(),
} {
) showCheckbox: false,
defaultCheckedKeys: () => []
const emit = defineEmits(['init', 'checkChange']) }
const config = useConfig() )
const tree = ref()
const treRef = ref() const emit = defineEmits(['init', 'checkChange'])
getDeviceTree().then(res => {
return const config = useConfig()
let arr: any[] = [] const tree = ref<any[]>([])
res.data.forEach((item: any) => { const treRef = ref<InstanceType<typeof Tree>>()
item.icon = 'el-icon-HomeFilled' const decorators = createLineTreeDecorators(() => config.getColorVal('elementUiPrimary'))
item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item2: any) => { async function initTree(list: any[]) {
item2.icon = 'el-icon-List' const leaves = decorateDeviceInfoTree(list, decorators)
item2.color = config.getColorVal('elementUiPrimary') tree.value = list
item2.children.forEach((item3: any) => {
item3.icon = 'el-icon-Platform' const node = leaves[0]
item3.color = config.getColorVal('elementUiPrimary') if (!node) {
if (item3.comFlag === 1) { emit('init')
item3.color = '#e26257 !important' return
} }
arr.push(item3)
}) await selectTreeNode(treRef.value, node, {
}) onSelect: selected => emit('init', { level: 2, ...selected })
}) })
tree.value = res.data }
nextTick(() => {
if (arr.length) { getDeviceTree().then(res => initTree(res.data))
treRef.value.treeRef.setCurrentKey(arr[0].id)
// 注册父组件事件 const getTreeList = (list: any[]) => initTree(list)
emit('init', {
level: 2, const handleCheckChange = (data: any) => {
...arr[0] emit('checkChange', { data })
}) }
} else {
emit('init') defineExpose({ getTreeList })
} </script>
})
})
const getTreeList = (list: any) => {
let arr: any[] = []
list.forEach((item: any) => {
item.icon = 'el-icon-HomeFilled'
item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-List'
item2.color = config.getColorVal('elementUiPrimary')
item2.children?.forEach((item3: any) => {
item3.icon = 'el-icon-Platform'
item3.color = config.getColorVal('elementUiPrimary')
if (item3.comFlag === 1) {
item3.color = '#e26257 !important'
item3.color = item3.comFlag == 3 ? '#e26257 !important' : config.getColorVal('elementUiPrimary')
}
arr.push(item3)
})
})
})
tree.value = list
nextTick(() => {
if (arr.length) {
treRef.value.treeRef.setCurrentKey(arr[0].id)
// 注册父组件事件
emit('init', {
level: 2,
...arr[0]
})
} else {
emit('init')
}
})
}
//接收tree选择的数据后传递给父级组件
const handleCheckChange = (data: any) => {
emit('checkChange', {
data
})
}
defineExpose({ getTreeList })
</script>

View File

@@ -7,168 +7,87 @@
:data="tree" :data="tree"
:height="props.height" :height="props.height"
@changeDeviceType="changeDeviceType" @changeDeviceType="changeDeviceType"
@changeTreeType="loadTree"
:engineering="props.engineering"
leaf-mode="device"
/> />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, nextTick } from 'vue' import { ref, onMounted } from 'vue'
import { throttle } from 'lodash'
import Tree from '../device.vue' import Tree from '../device.vue'
import { getDeviceTree } from '@/api/cs-device-boot/csLedger' import { getDeviceTree } from '@/api/cs-device-boot/csLedger'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { throttle } from 'lodash' import { waitForTreeRef, type TreeRefKey } from './lineTreeUtils'
defineOptions({ import { createLineTreeDecorators } from './lineTreeUtils'
name: 'govern/deviceTree' import { decorateDeviceTree } from './deviceTreeUtils'
}) import type { LineTreeLeaves } from './lineTreeUtils'
defineOptions({ name: 'govern/deviceTree' })
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
showCheckbox?: boolean showCheckbox?: boolean
defaultCheckedKeys?: any defaultCheckedKeys?: any[]
height?: number height?: number
engineering?: boolean
}>(), }>(),
{ {
showCheckbox: false, showCheckbox: false,
defaultCheckedKeys: [], defaultCheckedKeys: () => [],
height: 0 height: 0,
engineering: false
} }
) )
const emit = defineEmits(['init', 'checkChange', 'deviceTypeChange']) const emit = defineEmits(['init', 'checkChange', 'deviceTypeChange'])
const config = useConfig() const config = useConfig()
const tree = ref() const tree = ref<any[]>([])
const treRef = ref() const treRef = ref<InstanceType<typeof Tree>>()
const changeDeviceType = (val: any, obj: any) => { const decorators = createLineTreeDecorators(() => config.getColorVal('elementUiPrimary'))
emit('deviceTypeChange', val, obj)
const changeDeviceType = (val: any, obj: any) => emit('deviceTypeChange', val, obj)
async function selectInitialNode(type: string | undefined, leaves: LineTreeLeaves) {
const candidates: { refKey: TreeRefKey; list: any[]; level: number }[] =
type === '2'
? [{ refKey: 'treeRef4', list: leaves.engineering, level: 2 }]
: [
{ refKey: 'treeRef1', list: leaves.govern, level: 2 },
{ refKey: 'treeRef2', list: leaves.portable, level: 2 },
{ refKey: 'treeRef3', list: leaves.monitor, level: 2 }
]
for (const { refKey, list, level } of candidates) {
const node = list[0]
if (!node) continue
const treeInstance = await waitForTreeRef(treRef.value, refKey)
treeInstance?.setCurrentKey(node.id)
emit('init', { level, ...node })
return
}
emit('init')
} }
getDeviceTree().then(res => {
let arr: any[] = [] const loadTree = (type?: string) => {
let arr2: any[] = [] getDeviceTree({ type: type === '2' ? 'engineering' : '' }).then(res => {
let arr3: any[] = [] const leaves = decorateDeviceTree(res.data, type, decorators)
//治理设备 tree.value = res.data
res.data.map((item: any) => { selectInitialNode(type, leaves)
if (item.name == '治理设备') {
item.children.forEach((item: any) => {
item.icon = 'el-icon-HomeFilled'
item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-List'
item2.color = config.getColorVal('elementUiPrimary')
item2.children.forEach((item3: any) => {
item3.pName = '治理设备'
item3.icon = 'el-icon-Platform'
item3.level = 2
item3.color = config.getColorVal('elementUiPrimary')
if (item3.comFlag === 1) {
item3.color = '#e26257 !important'
}
arr.push(item3)
})
})
})
} else if (item.name == '便携式设备') {
item.children.forEach((item: any) => {
item.icon = 'el-icon-Platform'
item.color = config.getColorVal('elementUiPrimary')
item.color = '#e26257 !important'
item.color = item.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
// item.disabled =true
item.pName = '便携式设备'
if (item.type == 'device') {
arr2.push(item)
}
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-Platform'
item2.color = item2.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
item2.pName = '便携式设备'
// item2.children.forEach((item3: any) => {
// item3.icon = 'el-icon-Platform'
// item3.color = config.getColorVal('elementUiPrimary')
// if (item3.comFlag === 1) {
// item3.color = '#e26257 !important'
// }
// arr.push(item3)
// })
})
})
} else if (item.name == '监测设备') {
item.children.forEach((item: any) => {
item.icon = 'el-icon-HomeFilled'
item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-List'
item2.color = config.getColorVal('elementUiPrimary')
item2.children.forEach((item3: any) => {
item3.pName = '监测设备'
item3.icon = 'el-icon-Platform'
item3.color = config.getColorVal('elementUiPrimary')
if (item3.comFlag === 1) {
item3.color = '#e26257 !important'
}
arr3.push(item3)
})
})
})
}
}) })
tree.value = res.data }
nextTick(() => { onMounted(() => loadTree(props.engineering ? '2' : '1'))
setTimeout(() => {
if (arr.length > 0) {
treRef.value.treeRef1.setCurrentKey(arr[0].id)
// 注册父组件事件
emit('init', {
level: 2,
...arr[0]
})
return
} else if (arr2.length > 0) {
treRef.value.treeRef2.setCurrentKey(arr2[0].id)
// 注册父组件事件
emit('init', {
level: 2,
...arr2[0]
})
return
} else if (arr3.length > 0) {
console.log('🚀 ~ arr3:', arr3)
treRef.value.treeRef3.setCurrentKey(arr3[0].id) const handleCheckChange = throttle(
// 注册父组件事件
emit('init', {
level: 2,
...arr3[0]
})
return
} else {
emit('init')
return
}
}, 500)
})
})
throttle(
(data: any, checked: any, indeterminate: any) => { (data: any, checked: any, indeterminate: any) => {
emit('checkChange', { emit('checkChange', { data, checked, indeterminate })
data,
checked,
indeterminate
})
}, },
300, 300,
{ { leading: true, trailing: false }
leading: true, // 首次触发立即执行(可选,默认 true
trailing: false // 节流结束后是否执行最后一次(可选,默认 true根据需求调整
}
) )
const handleCheckChange = (data: any, checked: any, indeterminate: any) => { defineExpose({ treRef })
emit('checkChange', {
data,
checked,
indeterminate
})
}
defineExpose({
treRef
})
</script> </script>

View File

@@ -0,0 +1,222 @@
import type { LineTreeDecorators, LineTreeLeaves } from './lineTreeUtils'
/** getDeviceTree 接口专用装饰(与 getLineTree 层级不同) */
export function decorateDeviceTree(
data: any[],
type: string | undefined,
decorators: LineTreeDecorators
): LineTreeLeaves {
const leaves: LineTreeLeaves = { govern: [], portable: [], monitor: [], engineering: [] }
const { primary, statusColor, applyMeta } = decorators
data.forEach(item => {
if (type === '2') {
applyMeta(item, { icon: 'el-icon-HomeFilled', color: primary() })
item.children?.forEach((child: any) => {
applyMeta(child, { icon: 'el-icon-List', color: primary() })
child.children?.forEach((grand: any) => {
applyMeta(grand, {
icon: 'el-icon-Platform',
color: statusColor(grand.comFlag)
})
leaves.engineering.push(grand)
})
})
return
}
if (item.name === '治理设备') {
item.children?.forEach((l1: any) => {
applyMeta(l1, { icon: 'el-icon-HomeFilled', color: primary() })
l1.children?.forEach((l2: any) => {
applyMeta(l2, { icon: 'el-icon-List', color: primary() })
l2.children?.forEach((l3: any) => {
l3.pName = '治理设备'
applyMeta(l3, {
icon: 'el-icon-Platform',
level: 2,
color: l3.comFlag === 1 ? '#e26257 !important' : primary()
})
leaves.govern.push(l3)
})
})
})
} else if (item.name === '便携式设备') {
item.children?.forEach((l1: any) => {
applyMeta(l1, {
icon: 'el-icon-Platform',
color: statusColor(l1.comFlag)
})
l1.pName = '便携式设备'
if (l1.type === 'device') {
leaves.portable.push(l1)
}
l1.children?.forEach((l2: any) => {
applyMeta(l2, {
icon: 'el-icon-Platform',
color: statusColor(l2.comFlag)
})
l2.pName = '便携式设备'
})
})
} else if (item.name === '监测设备') {
item.children?.forEach((l1: any) => {
applyMeta(l1, { icon: 'el-icon-HomeFilled', color: primary() })
l1.children?.forEach((l2: any) => {
applyMeta(l2, { icon: 'el-icon-List', color: primary() })
l2.children?.forEach((l3: any) => {
l3.pName = '监测设备'
applyMeta(l3, {
icon: 'el-icon-Platform',
color: l3.comFlag === 1 ? '#e26257 !important' : primary()
})
leaves.monitor.push(l3)
})
})
})
}
})
return leaves
}
/** 装饰 getDeviceTree 扁平列表deviceInfoTree */
export function decorateDeviceInfoTree(list: any[], decorators: LineTreeDecorators): any[] {
const { primary, applyMeta } = decorators
const leaves: any[] = []
list.forEach(item => {
applyMeta(item, { icon: 'el-icon-HomeFilled', color: primary() })
item.children?.forEach((l2: any) => {
applyMeta(l2, { icon: 'el-icon-List', color: primary() })
l2.children?.forEach((l3: any) => {
applyMeta(l3, {
icon: 'el-icon-Platform',
color: l3.comFlag === 1 || l3.comFlag === 3 ? '#e26257 !important' : primary()
})
leaves.push(l3)
})
})
})
return leaves
}
/** 装饰云端设备树 getCldTree */
export function decorateCloudTree(root: any, decorators: LineTreeDecorators): any[] {
const { primary, statusColor, applyMeta } = decorators
const leaves: any[] = []
applyMeta(root, { icon: 'el-icon-Menu', color: primary() })
root.children?.forEach((l1: any) => {
applyMeta(l1, { icon: 'el-icon-HomeFilled', color: primary() })
l1.children?.forEach((l2: any) => {
applyMeta(l2, { icon: 'el-icon-List', color: primary() })
l2.children?.forEach((l3: any) => {
applyMeta(l3, {
icon: 'el-icon-Platform',
level: 2,
color: statusColor(l3.comFlag)
})
leaves.push(l3)
})
})
})
return leaves
}
/** 装饰 objTree治理对象树 */
export function decorateObjTree(data: any[], decorators: LineTreeDecorators): any[] {
const { primary, applyMeta } = decorators
const leaves: any[] = []
data.forEach(l1 => {
applyMeta(l1, { icon: 'el-icon-HomeFilled', color: primary(), level: 1 })
l1.children?.forEach((l2: any) => {
applyMeta(l2, { icon: 'el-icon-List', color: primary(), level: 2 })
l2.children?.forEach((l3: any) => {
applyMeta(l3, { icon: 'el-icon-Platform', color: primary(), level: 3 })
leaves.push(l3)
})
})
})
return leaves
}
/** 装饰 lineTree 台账线路树(监测设备根节点) */
export function decorateLedgerLineTree(root: any, decorators: LineTreeDecorators): any[] {
const { primary, statusColor, applyMeta } = decorators
const leaves: any[] = []
if (!root) return leaves
applyMeta(root, { icon: 'el-icon-Menu', color: primary(), level: 0 })
if (!Array.isArray(root.children)) root.children = []
root.children.forEach((l1: any) => {
applyMeta(l1, { icon: 'el-icon-HomeFilled', color: primary(), level: 1 })
if (!Array.isArray(l1.children)) l1.children = []
l1.children.forEach((l2: any) => {
applyMeta(l2, { icon: 'el-icon-List', color: primary(), level: 2 })
if (!Array.isArray(l2.children)) l2.children = []
l2.children.forEach((l3: any) => {
applyMeta(l3, {
icon: 'el-icon-Platform',
color: statusColor(l3.comFlag),
level: 3
})
if (!Array.isArray(l3.children)) l3.children = []
l3.children.forEach((l4: any) => {
applyMeta(l4, {
icon: 'el-icon-Platform',
color: statusColor(l4.comFlag),
level: 4
})
leaves.push(l4)
})
})
})
})
return leaves
}
/** getDeviceTree 接口叶子收集3 层结构,便携式为 type=device 节点) */
export function collectDeviceApiLeaves(
governNodes: any[],
portableNodes: any[],
monitorNodes: any[]
): { govern: any[]; portable: any[]; monitor: any[] } {
const govern: any[] = []
const portable: any[] = []
const monitor: any[] = []
governNodes.forEach(l1 => {
l1.children?.forEach((l2: any) => {
l2.children?.forEach((l3: any) => govern.push(l3))
})
})
portableNodes.forEach(l1 => {
if (l1.type === 'device') portable.push(l1)
})
monitorNodes.forEach(l1 => {
l1.children?.forEach((l2: any) => {
l2.children?.forEach((l3: any) => monitor.push(l3))
})
})
return { govern, portable, monitor }
}
/** 从 lineTree 数据中解析监测设备根节点 */
export function resolveMonitorRoot(data: any): any | null {
if (Array.isArray(data)) {
return data.find(item => item.name === '监测设备') ?? null
}
if (data?.name === '监测设备') return data
return null
}

View File

@@ -1,29 +1,28 @@
<template> <template>
<Tree ref="treRef" :data="tree" /> <Tree ref="treRef" :data="tree" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'
import { getMarketList } from '@/api/user-boot/user' import { getMarketList } from '@/api/user-boot/user'
import Tree from '../cloudDevice.vue' import Tree from '../cloudDevice.vue'
import { useConfig } from '@/stores/config' import { mapUserTreeNodes, selectTreeNode } from './treeCommonUtils'
import { ref, reactive, nextTick } from 'vue'
const config = useConfig() const tree = ref<any[]>([])
const tree = ref() const treRef = ref<InstanceType<typeof Tree>>()
const treRef = ref()
const emit = defineEmits(['selectUser']) const emit = defineEmits(['selectUser'])
getMarketList().then((res: any) => {
if (res.code === 'A0000') { async function loadTree() {
tree.value = res.data.map((item: any) => { const res: any = await getMarketList()
return { if (res.code !== 'A0000') return
...item,
icon: 'el-icon-User', tree.value = mapUserTreeNodes(res.data)
color: 'royalblue' const first = tree.value[0]
} if (!first) return
})
emit('selectUser', tree.value[0]) emit('selectUser', first)
nextTick(() => { await selectTreeNode(treRef.value, first)
treRef.value.treeRef.setCurrentKey(tree.value[0].id) }
})
} loadTree()
})
</script> </script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,172 @@
import { nextTick } from 'vue'
export interface LineTreeLeaves {
govern: any[]
portable: any[]
monitor: any[]
engineering: any[]
}
export interface LineTreeDecorators {
primary: () => string
statusColor: (comFlag: number) => string
applyMeta: (
node: any,
meta: { icon: string; color?: string; level?: number; disabled?: boolean }
) => void
}
export function createLineTreeDecorators(getPrimaryColor: () => string): LineTreeDecorators {
const offlineColor = '#e26257 !important'
const statusColor = (comFlag: number) => (comFlag === 2 ? getPrimaryColor() : offlineColor)
const applyMeta = (
node: any,
meta: { icon: string; color?: string; level?: number; disabled?: boolean }
) => {
node.icon = meta.icon
if (meta.color !== undefined) node.color = meta.color
if (meta.level !== undefined) node.level = meta.level
if (meta.disabled) node.disabled = true
}
return {
primary: getPrimaryColor,
statusColor,
applyMeta
}
}
export type TreeRefKey = 'treeRef1' | 'treeRef2' | 'treeRef3' | 'treeRef4'
export interface DecorateLineTreeOptions {
/** 是否禁用父级节点(分析树隐藏父节点,测点树不禁用) */
disableParents?: boolean
}
/** 装饰线路树节点并收集可选叶子节点 */
export function decorateLineTree(
data: any[],
type: string | undefined,
decorators: LineTreeDecorators,
options: DecorateLineTreeOptions = {}
): LineTreeLeaves {
const leaves: LineTreeLeaves = { govern: [], portable: [], monitor: [], engineering: [] }
const { primary, statusColor, applyMeta } = decorators
const disableParents = options.disableParents ?? true
const parentDisabled = disableParents ? ({ disabled: true } as const) : {}
data.forEach(item => {
if (type === '2') {
applyMeta(item, { icon: 'el-icon-HomeFilled', color: primary(), ...parentDisabled })
item.children?.forEach((child: any) => {
applyMeta(child, { icon: 'el-icon-List', color: primary(), ...parentDisabled })
child.children?.forEach((grand: any) => {
applyMeta(grand, {
icon: 'el-icon-Platform',
color: statusColor(grand.comFlag),
level: 2,
...parentDisabled
})
grand.children?.forEach((leaf: any) => {
applyMeta(leaf, { icon: 'el-icon-Platform', color: statusColor(leaf.comFlag) })
leaves.engineering.push(leaf)
})
})
})
return
}
if (item.name === '治理设备') {
item.children?.forEach((l1: any) => {
applyMeta(l1, { icon: 'el-icon-HomeFilled', color: primary(), level: 1, ...parentDisabled })
l1.children?.forEach((l2: any) => {
applyMeta(l2, { icon: 'el-icon-List', color: primary(), level: 1, ...parentDisabled })
l2.children?.forEach((l3: any) => {
applyMeta(l3, {
icon: 'el-icon-Platform',
color: statusColor(l3.comFlag),
level: 2,
...parentDisabled
})
l3.children?.forEach((l4: any) => {
applyMeta(l4, { icon: 'el-icon-Platform', color: statusColor(l4.comFlag) })
leaves.govern.push(l4)
})
})
})
})
} else if (item.name === '便携式设备') {
item.children?.forEach((l1: any) => {
applyMeta(l1, { icon: 'el-icon-Platform', color: statusColor(l1.comFlag) })
l1.children?.forEach((l2: any) => {
applyMeta(l2, { icon: 'el-icon-Platform', color: statusColor(l2.comFlag) })
leaves.portable.push(l2)
})
})
} else if (item.name === '监测设备') {
item.children?.forEach((l1: any) => {
applyMeta(l1, { icon: 'el-icon-HomeFilled', color: primary(), level: 1, ...parentDisabled })
l1.children?.forEach((l2: any) => {
applyMeta(l2, { icon: 'el-icon-List', color: primary(), level: 1, ...parentDisabled })
l2.children?.forEach((l3: any) => {
applyMeta(l3, {
icon: 'el-icon-Platform',
color: statusColor(l3.comFlag),
level: 1,
...parentDisabled
})
l3.children?.forEach((l4: any) => {
applyMeta(l4, { icon: 'el-icon-Platform', color: statusColor(l4.comFlag) })
leaves.monitor.push(l4)
})
})
})
})
}
})
return leaves
}
/** 从折叠面板树数据中收集叶子节点(与 decorateLineTree 层级一致) */
export function collectDeviceLeaves(
governNodes: any[],
portableNodes: any[],
monitorNodes: any[]
): Pick<LineTreeLeaves, 'govern' | 'portable' | 'monitor'> {
const govern: any[] = []
const portable: any[] = []
const monitor: any[] = []
governNodes.forEach(l1 => {
l1.children?.forEach((l2: any) => {
l2.children?.forEach((l3: any) => {
l3.children?.forEach((l4: any) => govern.push(l4))
})
})
})
portableNodes.forEach(l1 => {
l1.children?.forEach((l2: any) => portable.push(l2))
})
monitorNodes.forEach(l1 => {
l1.children?.forEach((l2: any) => {
l2.children?.forEach((l3: any) => {
l3.children?.forEach((l4: any) => monitor.push(l4))
})
})
})
return { govern, portable, monitor }
}
export async function waitForTreeRef(treRef: any, refKey: TreeRefKey, maxRetries = 20) {
for (let i = 0; i < maxRetries; i++) {
await nextTick()
if (treRef?.[refKey]) return treRef[refKey]
await new Promise(resolve => setTimeout(resolve, 50))
}
return null
}

View File

@@ -1,29 +1,28 @@
<template> <template>
<Tree ref="treRef" :data="tree" /> <Tree ref="treRef" :data="tree" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'
import { getFormalUserList } from '@/api/user-boot/official' import { getFormalUserList } from '@/api/user-boot/official'
import Tree from '../cloudDevice.vue' import Tree from '../cloudDevice.vue'
import { useConfig } from '@/stores/config' import { mapUserTreeNodes, selectTreeNode } from './treeCommonUtils'
import { ref, reactive, nextTick } from 'vue'
const config = useConfig() const tree = ref<any[]>([])
const tree = ref() const treRef = ref<InstanceType<typeof Tree>>()
const treRef = ref()
const emit = defineEmits(['selectUser']) const emit = defineEmits(['selectUser'])
getFormalUserList().then((res: any) => {
if (res.code === 'A0000') { async function loadTree() {
tree.value = res.data.map((item: any) => { const res: any = await getFormalUserList()
return { if (res.code !== 'A0000') return
...item,
icon: 'el-icon-User', tree.value = mapUserTreeNodes(res.data)
color: 'royalblue' const first = tree.value[0]
} if (!first) return
})
emit('selectUser', tree.value[0]) emit('selectUser', first)
nextTick(() => { await selectTreeNode(treRef.value, first)
treRef.value.treeRef.setCurrentKey(tree.value[0].id) }
})
} loadTree()
})
</script> </script>
<style lang="scss" scoped></style>

View File

@@ -1,153 +1,103 @@
<template> <template>
<Tree ref="treRef" :width="width" :data="tree" default-expand-all @changePointType="changePointType" /> <Tree
ref="treRef"
:width="width"
:data="tree"
default-expand-all
@changePointType="changePointType"
@changeTreeType="loadTree"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, nextTick, onMounted, defineProps } from 'vue' import { ref, nextTick } from 'vue'
import Tree from '../point.vue' import Tree from '../point.vue'
import { getLineTree } from '@/api/cs-device-boot/csLedger' import { getLineTree } from '@/api/cs-device-boot/csLedger'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { querySysExcel } from '@/api/harmonic-boot/luckyexcel' import { querySysExcel } from '@/api/harmonic-boot/luckyexcel'
import { useDictData } from '@/stores/dictData' import {
// const props = defineProps(['template']) createLineTreeDecorators,
decorateLineTree,
waitForTreeRef,
type LineTreeLeaves,
type TreeRefKey
} from './lineTreeUtils'
interface Props { interface Props {
template?: boolean template?: boolean
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
template: false template: false
}) })
defineOptions({ defineOptions({
name: 'govern/deviceTree' name: 'govern/pointTree'
}) })
const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy']) const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy'])
const config = useConfig() const config = useConfig()
const tree = ref() const tree = ref<any[]>([])
const dictData = useDictData() const treRef = ref<InstanceType<typeof Tree>>()
const treRef = ref()
const width = ref('') const width = ref('')
const info = () => { const decorators = createLineTreeDecorators(() => config.getColorVal('elementUiPrimary'))
tree.value = []
let arr1: any[] = []
let arr2: any[] = []
let arr3: any[] = []
getLineTree().then(res => {
//治理设备
res.data.map((item: any) => {
if (item.name == '治理设备') {
item.children.forEach((item: any) => {
item.icon = 'el-icon-HomeFilled'
item.level = 1
item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-List'
item2.level = 1
item2.color = config.getColorVal('elementUiPrimary')
item2.children.forEach((item3: any) => {
item3.icon = 'el-icon-Platform'
item3.level = 2
item3.color =
item3.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
item3.children.forEach((item4: any) => {
item4.icon = 'el-icon-Platform'
item4.color =
item4.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
// item4.color = '#e26257 !important'
arr1.push(item4)
})
})
})
})
} else if (item.name == '便携式设备') {
item.children.forEach((item: any) => {
item.icon = 'el-icon-Platform'
item.color = config.getColorVal('elementUiPrimary')
item.color = item.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-Platform'
item2.color =
item2.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
arr2.push(item2)
})
})
} else if (item.name == '监测设备') {
item.children.forEach((item: any) => {
item.icon = 'el-icon-HomeFilled'
item.level = 1
item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-List'
item2.level = 1
item2.color = config.getColorVal('elementUiPrimary')
item2.children.forEach((item3: any) => {
item3.icon = 'el-icon-Platform'
item3.level = 1
item3.color =
item3.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
item3.children.forEach((item4: any) => {
item4.icon = 'el-icon-Platform'
item4.color =
item4.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
// item4.color = '#e26257 !important'
arr3.push(item4)
})
})
})
})
}
})
tree.value = res.data
nextTick(() => {
setTimeout(() => {
if (arr1.length > 0) {
//初始化选中
treRef.value?.treeRef1.setCurrentKey(arr1[0].id)
// 注册父组件事件
emit('init', {
level: 2,
...arr1[0]
})
return
} else if (arr2.length > 0) {
//初始化选中
treRef.value?.treeRef2.setCurrentKey(arr2[0].id)
// 注册父组件事件
emit('init', {
level: 2,
...arr2[0]
})
return
} else if (arr3.length > 0) {
treRef.value?.treeRef3?.setCurrentKey(arr3[0].id)
emit('init', {
level: 2,
...arr3[0]
})
return
} else {
emit('init')
return
}
}, 500)
})
})
}
const changePointType = (val: any, obj: any) => { const changePointType = (val: any, obj: any) => {
emit('pointTypeChange', val, obj) emit('pointTypeChange', val, obj)
} }
if (props.template) {
querySysExcel({ id: dictData.state.area[0]?.id }) async function selectInitialNode(type: string | undefined, leaves: LineTreeLeaves) {
.then((res: any) => { const candidates: { refKey: TreeRefKey; list: any[]; level: number }[] =
emit('Policy', res.data) type === '2'
info() ? [{ refKey: 'treeRef4', list: leaves.engineering, level: 3 }]
}) : [
.catch(err => { { refKey: 'treeRef1', list: leaves.govern, level: 2 },
info() { refKey: 'treeRef2', list: leaves.portable, level: 2 },
}) { refKey: 'treeRef3', list: leaves.monitor, level: 2 }
} else { ]
info()
for (const { refKey, list, level } of candidates) {
const node = list[0]
if (!node) continue
const treeInstance = await waitForTreeRef(treRef.value, refKey)
treeInstance?.setCurrentKey(node.id)
emit('init', { level, ...node })
if (type === '2') {
changePointType('4', node)
}
return
}
emit('init')
} }
onMounted(() => {})
const loadTree = (type?: string) => {
tree.value = []
getLineTree({ type: type === '2' ? 'engineering' : '' }).then(res => {
const leaves = decorateLineTree(res.data, type, decorators, { disableParents: false })
tree.value = res.data
nextTick(() => selectInitialNode(type, leaves))
})
}
function bootstrap() {
if (props.template) {
querySysExcel({})
.then((res: any) => {
emit('Policy', res.data)
loadTree()
})
.catch(() => loadTree())
} else {
loadTree()
}
}
bootstrap()
defineExpose({ treRef })
</script> </script>

View File

@@ -3,13 +3,7 @@
<div style="transition: all 0.3s; overflow: hidden; height: 100%"> <div style="transition: all 0.3s; overflow: hidden; height: 100%">
<div class="cn-tree"> <div class="cn-tree">
<div style="display: flex; align-items: center" class="mb10"> <div style="display: flex; align-items: center" class="mb10">
<el-input <el-input maxlength="32" v-model.trim="filterText" placeholder="请输入内容" clearable>
maxlength="32"
show-word-limit
v-model.trim="filterText"
placeholder="请输入内容"
clearable
>
<template #prefix> <template #prefix>
<Icon name="el-icon-Search" style="font-size: 16px" /> <Icon name="el-icon-Search" style="font-size: 16px" />
</template> </template>
@@ -28,16 +22,16 @@
@node-click="clickNode" @node-click="clickNode"
:expand-on-click-node="false" :expand-on-click-node="false"
> >
<template #default="{ node, data }"> <template #default="{ node, data: nodeData }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<div class="left" style="display: flex; align-items: center"> <div class="left" style="display: flex; align-items: center">
<Icon <Icon
:name="data.icon" :name="nodeData.icon"
style="font-size: 16px" style="font-size: 16px"
:style="{ color: data.color }" :style="{ color: nodeData.color }"
v-if="data.icon" v-if="nodeData.icon"
/> />
<span style="margin-left: 5px;">{{ node.label }}</span> <span style="margin-left: 5px">{{ node.label }}</span>
</div> </div>
</span> </span>
</template> </template>
@@ -48,127 +42,83 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, nextTick, watch, defineProps, defineEmits } from 'vue' import { ref, watch } from 'vue'
import { getSchemeTree, getTestRecordInfo } from '@/api/cs-device-boot/planData' import { getSchemeTree } from '@/api/cs-device-boot/planData'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import useCurrentInstance from '@/utils/useCurrentInstance'
import { ElTree } from 'element-plus' import { ElTree } from 'element-plus'
import { querySysExcel } from '@/api/harmonic-boot/luckyexcel' import { querySysExcel } from '@/api/harmonic-boot/luckyexcel'
import { useDictData } from '@/stores/dictData' import { createLineTreeDecorators } from './lineTreeUtils'
defineOptions({ import { bootstrapWithTemplate } from './treeCommonUtils'
name: 'govern/schemeTree' import { createTreeFilterNode } from './treeFilterUtils'
})
defineOptions({ name: 'govern/schemeTree', inheritAttrs: false })
interface Props { interface Props {
template?: boolean template?: boolean
} }
const dictData = useDictData()
const props = withDefaults(defineProps<Props>(), {
template: false
})
const filterText = ref('')
watch(filterText, val => {
treRef.value!.filter(val)
})
const filterNode = (value: string, data: any, node: any) => { const props = withDefaults(defineProps<Props>(), { template: false })
if (!value) return true
// return data.name.includes(value)
if (data.name) {
return chooseNode(value, data, node)
}
}
const chooseNode = (value: string, data: any, node: any) => {
if (data.name.indexOf(value) !== -1) {
return true
}
const level = node.level
// 如果传入的节点本身就是一级节点就不用校验了
if (level === 1) {
return false
}
// 先取当前节点的父节点
let parentData = node.parent
// 遍历当前节点的父节点
let index = 0
while (index < level - 1) {
// 如果匹配到直接返回此处name值是中文字符enName是英文字符。判断匹配中英文过滤
if (parentData.data.name.indexOf(value) !== -1) {
return true
}
// 否则的话再往上一层做匹配
parentData = parentData.parent
index++
}
// 没匹配到返回false
return false
}
/** 树形结构数据 */
const defaultProps = {
children: 'children',
label: 'name',
value: 'id'
}
const emit = defineEmits(['init', 'checkChange', 'nodeChange', 'editNode', 'getChart', 'Policy']) const emit = defineEmits(['init', 'checkChange', 'nodeChange', 'editNode', 'getChart', 'Policy'])
const config = useConfig()
const tree = ref()
const treRef = ref()
const id: any = ref(null)
const treeData = ref({})
//获取方案树形数据
const getTreeList = () => {
getSchemeTree().then(res => {
let arr: any[] = []
res.data.forEach((item: any) => { const config = useConfig()
item.icon = 'el-icon-Menu' const tree = ref<any[]>([])
item.color = config.getColorVal('elementUiPrimary') const treRef = ref<InstanceType<typeof ElTree>>()
item?.children.forEach((item2: any) => { const filterText = ref('')
item2.icon = 'el-icon-Document' const id = ref<string | null>(null)
item2.color = config.getColorVal('elementUiPrimary') const planId = ref('')
arr.push(item2)
}) const defaultProps = { children: 'children', label: 'name', value: 'id' }
const decorators = createLineTreeDecorators(() => config.getColorVal('elementUiPrimary'))
const filterNode = createTreeFilterNode()
watch(filterText, val => treRef.value?.filter(val))
function decorateSchemeTree(data: any[]): any[] {
const { primary, applyMeta } = decorators
const leaves: any[] = []
data.forEach(item => {
applyMeta(item, { icon: 'el-icon-Menu', color: primary() })
item.children?.forEach((child: any) => {
applyMeta(child, { icon: 'el-icon-Document', color: primary() })
leaves.push(child)
}) })
})
return leaves
}
function getTreeList() {
getSchemeTree().then(res => {
const leaves = decorateSchemeTree(res.data)
tree.value = res.data tree.value = res.data
nextTick(() => {
if (arr.length) { const node = id.value ? leaves.find(item => item.id == id.value) : leaves[0]
treRef.value.setCurrentKey(id.value || arr[0].id) if (!node) {
let list = id.value ? arr.find((item: any) => item.id == id.value) : arr[0] emit('init')
// 注册父组件事件 return
emit('init', { }
level: 2,
...list treRef.value?.setCurrentKey(node.id)
}) emit('init', { level: 2, ...node })
} else {
emit('init')
}
})
}) })
} }
//方案id const clickNode = (e: any) => {
const planId: any = ref('') planId.value = e?.children ? e.id : e.pid
const clickNode = (e: anyObj) => {
e?.children ? (planId.value = e.id) : (planId.value = e.pid)
id.value = e.id id.value = e.id
emit('nodeChange', e) emit('nodeChange', e)
} }
if (props.template) { bootstrapWithTemplate(
querySysExcel({ id: dictData.state.area[0]?.id }) props.template,
.then((res: any) => { getTreeList,
emit('Policy', res.data) () => querySysExcel({}),
getTreeList() data => emit('Policy', data)
}) )
.catch(err => {
getTreeList()
})
} else {
getTreeList()
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.cn-tree { .cn-tree {
flex-shrink: 0; flex-shrink: 0;

View File

@@ -1,105 +1,64 @@
<template> <template>
<Tree ref="treRef" :width="width" :data="tree" default-expand-all @checkedNodesChange="handleCheckedNodesChange"/> <Tree
ref="treRef"
:width="width"
:data="tree"
default-expand-all
@checkedNodesChange="handleCheckedNodesChange"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, nextTick, onMounted, defineProps } from 'vue' import { ref } from 'vue'
import Tree from '../select.vue' import Tree from '../select.vue'
import { getLineTree } from '@/api/cs-device-boot/csLedger' import { getLineTree } from '@/api/cs-device-boot/csLedger'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { getTemplateByDept } from '@/api/harmonic-boot/luckyexcel' import { getTemplateByDept } from '@/api/harmonic-boot/luckyexcel'
import { useDictData } from '@/stores/dictData' import { useDictData } from '@/stores/dictData'
// const props = defineProps(['template']) import { createLineTreeDecorators, decorateLineTree } from './lineTreeUtils'
import { bootstrapWithTemplate, selectTreeNode } from './treeCommonUtils'
interface Props { interface Props {
template?: boolean template?: boolean
} }
const props = withDefaults(defineProps<Props>(), {
template: false const props = withDefaults(defineProps<Props>(), { template: false })
})
defineOptions({ defineOptions({ name: 'govern/selectTree' })
name: 'govern/deviceTree'
})
const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy']) const emit = defineEmits(['init', 'checkChange', 'pointTypeChange', 'Policy'])
const config = useConfig() const config = useConfig()
const tree = ref()
const dictData = useDictData() const dictData = useDictData()
const treRef = ref() const tree = ref<any[]>([])
const treRef = ref<InstanceType<typeof Tree>>()
const width = ref('') const width = ref('')
const info = () => { const decorators = createLineTreeDecorators(() => config.getColorVal('elementUiPrimary'))
const handleCheckedNodesChange = (nodes: any[]) => emit('checkChange', nodes)
async function loadTree() {
tree.value = [] tree.value = []
let arr3: any[] = [] const res = await getLineTree()
getLineTree().then(res => { const leaves = decorateLineTree(res.data, '1', decorators, { disableParents: false })
//治理设备 tree.value = res.data.filter((item: any) => item.name === '监测设备')
res.data.map((item: any) => {
if (item.name == '监测设备') { const node = leaves.monitor[0]
item.children.forEach((item: any) => { if (!node) {
item.icon = 'el-icon-HomeFilled' emit('init')
item.level = 1 return
item.color = config.getColorVal('elementUiPrimary') }
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-List' await selectTreeNode(treRef.value, node, {
item2.level = 1 onSelect: selected => emit('init', { level: 2, ...selected })
item2.color = config.getColorVal('elementUiPrimary')
item2.children.forEach((item3: any) => {
item3.icon = 'el-icon-Platform'
item3.level = 1
item3.color =
item3.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
item3.children.forEach((item4: any) => {
item4.icon = 'el-icon-Platform'
item4.color =
item4.comFlag === 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
// item4.color = '#e26257 !important'
arr3.push(item4)
})
})
})
})
}
})
tree.value = res.data.filter(item => item.name == '监测设备')
nextTick(() => {
if (arr3.length) {
//初始化选中
treRef.value.treeRef.setCurrentKey(arr3[0].id)
// 注册父组件事件
emit('init', {
level: 2,
...arr3[0]
})
return
}
else {
emit('init')
return
}
})
}) })
} }
// 处理子组件传递的勾选节点变化,并转发给父组件 bootstrapWithTemplate(
const handleCheckedNodesChange = (nodes: any[]) => { props.template,
// 先给父组件 loadTree,
emit('checkChange', nodes) () => getTemplateByDept({ id: dictData.state.area[0]?.id }),
} data => emit('Policy', data)
)
if (props.template) {
getTemplateByDept({ id: dictData.state.area[0]?.id })
.then((res: any) => {
emit('Policy', res.data)
info()
})
.catch(err => {
info()
})
} else {
info()
}
onMounted(() => {})
</script> </script>

View File

@@ -0,0 +1,58 @@
import { nextTick } from 'vue'
import { createLineTreeDecorators, type LineTreeDecorators } from './lineTreeUtils'
export { createLineTreeDecorators, type LineTreeDecorators }
export function findNodeById(nodes: any[], id: string): any | null {
for (const node of nodes) {
if (node.id === id) return node
if (node.children?.length) {
const found = findNodeById(node.children, id)
if (found) return found
}
}
return null
}
export async function waitForSingleTreeRef(treRef: any, maxRetries = 20) {
for (let i = 0; i < maxRetries; i++) {
await nextTick()
if (treRef?.treeRef) return treRef.treeRef
await new Promise(resolve => setTimeout(resolve, 50))
}
return null
}
export async function selectTreeNode(
treRef: any,
node: any | undefined,
options?: { level?: number; onSelect?: (node: any) => void }
) {
if (!node) return false
const treeInstance = await waitForSingleTreeRef(treRef)
treeInstance?.setCurrentKey(node.id)
options?.onSelect?.(node)
return true
}
export function bootstrapWithTemplate(
template: boolean,
loadFn: () => void,
fetchTemplate: () => Promise<any>,
onPolicy: (data: any) => void
) {
if (template) {
fetchTemplate()
.then(res => {
onPolicy(res.data)
loadFn()
})
.catch(() => loadFn())
} else {
loadFn()
}
}
export function mapUserTreeNodes(data: any[], icon = 'el-icon-User', color = 'royalblue') {
return data.map(item => ({ ...item, icon, color }))
}

View File

@@ -0,0 +1,20 @@
/** 树节点搜索:匹配当前节点或任一祖先节点名称 */
export function matchTreeNodeName(value: string, data: any, node: any): boolean {
if (!value) return true
if (!data?.name) return false
if (data.name.indexOf(value) !== -1) return true
const level = node.level
if (level === 1) return false
let parentData = node.parent
for (let i = 0; i < level - 1; i++) {
if (parentData?.data?.name?.indexOf(value) !== -1) return true
parentData = parentData.parent
}
return false
}
export function createTreeFilterNode() {
return (value: string, data: any, node: any): boolean => matchTreeNodeName(value, data, node)
}

View File

@@ -1,41 +1,67 @@
<template> <template>
<div :style="{ width: menuCollapse ? '40px' : props.width }" style='transition: all 0.3s; overflow: hidden;'> <div
<Icon v-show='menuCollapse' @click='onMenuCollapse' :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" class="cn-tree-root"
:class="menuCollapse ? 'unfold' : ''" size='18' class='fold ml10 mt20 menu-collapse' :class="{ 'is-collapsed': menuCollapse, 'is-fill-height': props.fillHeight }"
style='cursor: pointer' /> :style="{ width: menuCollapse ? '40px' : props.width }"
<div class='cn-tree' :style='{ opacity: menuCollapse ? 0 : 1 }'> >
<div style='display: flex; align-items: center' class='mb10'> <!-- <Icon
<el-input maxlength="32" show-word-limit v-model.trim='filterText' placeholder='请输入内容' clearable> v-show="menuCollapse"
@click="onMenuCollapse"
:name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
size="18px"
class="cn-tree-root__collapse-btn cn-tree-root__collapse-btn--float"
/> -->
<div class="cn-tree" :class="{ 'is-hidden': menuCollapse }">
<div class="cn-tree__toolbar">
<el-input
maxlength="32"
v-model.trim="filterText"
placeholder="请输入内容"
clearable
class="cn-tree__search"
>
<template #prefix> <template #prefix>
<Icon name='el-icon-Search' style='font-size: 16px' /> <Icon name="el-icon-Search" style="font-size: 16px" />
</template> </template>
</el-input> </el-input>
<el-tooltip placement="bottom" :hide-after="0" v-if="props.showPush"> <el-tooltip v-if="props.showPush" placement="bottom" :hide-after="0" content="台账推送">
<template #content> <Icon
<span>台账推送</span> name="el-icon-Promotion"
</template> size="20px"
class="cn-tree__push-btn"
<Icon :style="{ color: config.getColorVal('elementUiPrimary') }"
name="el-icon-Promotion" @click="onAdd"
size="20" />
class="fold ml10 menu-collapse"
style="cursor: pointer;"
:style="{ color: config.getColorVal('elementUiPrimary') }"
@click="onAdd" />
</el-tooltip> </el-tooltip>
<!-- <Icon @click='onMenuCollapse' :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" v-else <!-- <Icon
:class="menuCollapse ? 'unfold' : ''" size='18' class='fold ml10 menu-collapse' v-if="props.canExpand"
style='cursor: pointer' v-if='props.canExpand' /> --> @click="onMenuCollapse"
name="el-icon-Fold"
size="18px"
class="cn-tree__collapse-btn"
/> -->
</div> </div>
<el-tree :style="{ height: 'calc(100vh - 190px)' }" <el-tree
style=' overflow: auto;' ref='treeRef' :props='defaultProps' highlight-current :default-expand-all="false" ref="treeRef"
@check-change="checkTreeNodeChange" :filter-node-method='filterNode' node-key='id' v-bind='$attrs'> :style="{ height: treeHeight }"
<template #default='{ node, data }'> :props="defaultProps"
<span class='custom-tree-node'> highlight-current
<Icon :name='data.icon' style='font-size: 16px' :style='{ color: data.color }' :default-expand-all="false"
v-if='data.icon' /> @check-change="checkTreeNodeChange"
<span style='margin-left: 4px'>{{ node.label }}</span> :filter-node-method="filterNode"
node-key="id"
v-bind="$attrs"
>
<template #default="{ node, data: nodeData }">
<span class="custom-tree-node">
<Icon
v-if="nodeData.icon"
:name="nodeData.icon"
style="font-size: 16px"
:style="{ color: nodeData.color }"
/>
<span class="custom-tree-node__label">{{ node.label }}</span>
</span> </span>
</template> </template>
</el-tree> </el-tree>
@@ -43,120 +69,158 @@
</div> </div>
</template> </template>
<script lang='ts' setup> <script lang="ts" setup>
import useCurrentInstance from '@/utils/useCurrentInstance' import useCurrentInstance from '@/utils/useCurrentInstance'
import { ElTree } from 'element-plus' import { ElTree } from 'element-plus'
import { emit } from 'process'; import { ref, watch, computed } from 'vue'
import { ref, watch } from 'vue'
import { t } from 'vxe-table';
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { createTreeFilterNode } from './govern/treeFilterUtils'
defineOptions({ defineOptions({ name: 'govern/indexTree', inheritAttrs: false })
name: 'govern/tree'
})
interface Props { interface Props {
width?: string width?: string
canExpand?: boolean canExpand?: boolean
showPush?: boolean showPush?: boolean
/** 默认高度基准偏移100vh - baseOffset */
baseOffset?: number
/** 在 baseOffset 基础上额外减去的高度 */
height?: number
/** 自定义树高度,优先级最高 */
treeHeight?: string
/** 撑满父容器高度 */
fillHeight?: boolean
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
width: '280px', width: '280px',
canExpand: true, canExpand: true,
showPush: false showPush: false,
baseOffset: 190,
height: 0,
treeHeight: '',
fillHeight: false
}) })
const emit = defineEmits(['checkTreeNodeChange', 'onAdd', 'changePointType'])
const config = useConfig() const config = useConfig()
const { proxy } = useCurrentInstance() const { proxy } = useCurrentInstance()
const menuCollapse = ref(false) const menuCollapse = ref(false)
const filterText = ref('') const filterText = ref('')
const defaultProps = { const defaultProps = { label: 'name', value: 'id' }
label: 'name', const filterNode = createTreeFilterNode()
value: 'id'
} const SEARCH_BAR_HEIGHT = 42
const emit = defineEmits(['checkTreeNodeChange','onAdd'])
watch(filterText, val => { const treeHeight = computed(() => {
treeRef.value!.filter(val) if (props.treeHeight) return props.treeHeight
if (props.fillHeight) return `calc(100% - ${SEARCH_BAR_HEIGHT}px)`
return `calc(100vh - ${props.baseOffset}px - ${props.height}px)`
}) })
watch(filterText, val => treeRef.value?.filter(val))
const onMenuCollapse = () => { const onMenuCollapse = () => {
menuCollapse.value = !menuCollapse.value menuCollapse.value = !menuCollapse.value
proxy.eventBus.emit('cnTreeCollapse', menuCollapse) proxy.eventBus.emit('cnTreeCollapse', menuCollapse)
} }
const filterNode = (value: string, data: any, node: any) => {
console.log(value, data, node, 'filterNode');
if (!value) return true
// return data.name.includes(value)
if (data.name) {
return chooseNode(value, data, node)
}
}
// 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配则返回该节点以及其下的所有子节点如果参数是子节点则返回该节点的父节点。name是中文字符enName是英文字符.
const chooseNode = (value: string, data: any, node: any) => {
if (data.name.indexOf(value) !== -1) {
return true
}
const level = node.level
// 如果传入的节点本身就是一级节点就不用校验了
if (level === 1) {
return false
}
// 先取当前节点的父节点
let parentData = node.parent
// 遍历当前节点的父节点
let index = 0
while (index < level - 1) {
// 如果匹配到直接返回此处name值是中文字符enName是英文字符。判断匹配中英文过滤
if (parentData.data.name.indexOf(value) !== -1) {
return true
}
// 否则的话再往上一层做匹配
parentData = parentData.parent
index++
}
// 没匹配到返回false
return false
}
const checkTreeNodeChange = () => { const checkTreeNodeChange = () => {
// console.log(treeRef.value?.getCheckedNodes(), "ikkkkkiisiiisis");
emit('checkTreeNodeChange', treeRef.value?.getCheckedNodes()) emit('checkTreeNodeChange', treeRef.value?.getCheckedNodes())
} }
const onAdd = () => { const onAdd = () => emit('onAdd')
emit('onAdd')
}
const treeRef = ref<InstanceType<typeof ElTree>>() const treeRef = ref<InstanceType<typeof ElTree>>()
defineExpose({ treeRef }) defineExpose({ treeRef })
</script> </script>
<style lang='scss' scoped> <style lang="scss" scoped>
.cn-tree { .cn-tree-root {
flex-shrink: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden;
height: 100%;
transition: width 0.3s;
box-sizing: border-box;
&.is-fill-height {
height: 100%;
}
&__collapse-btn {
color: var(--el-color-primary);
cursor: pointer;
flex-shrink: 0;
&--float {
margin: 20px 0 0 10px;
}
}
}
.cn-tree {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
width: 100%;
box-sizing: border-box; box-sizing: border-box;
padding: 10px; padding: 10px;
height: 100%; transition: opacity 0.3s;
width: 100%;
&.is-hidden {
opacity: 0;
pointer-events: none;
}
&__toolbar {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
flex-shrink: 0;
}
&__search {
flex: 1;
min-width: 0;
}
&__push-btn,
&__collapse-btn {
color: var(--el-color-primary);
cursor: pointer;
flex-shrink: 0;
}
&__body {
flex: 1;
min-height: 0;
overflow: auto;
}
:deep(.el-tree) { :deep(.el-tree) {
border: 1px solid var(--el-border-color); border: 1px solid var(--el-border-color);
border-radius: 4px;
} }
:deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content) { :deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content) {
background-color: var(--el-color-primary-light-7); background-color: var(--el-color-primary-light-7);
} }
.menu-collapse {
color: var(--el-color-primary);
}
} }
.custom-tree-node { .custom-tree-node {
display: flex; display: flex;
align-items: center; align-items: center;
min-width: 0;
&__label {
margin-left: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
} }
</style> </style>

View File

@@ -1,49 +1,60 @@
<!-- 设备监控使用折叠面板渲染多个tree --> <!-- 设备监控使用折叠面板渲染多个tree -->
<template> <template>
<div :style="{ width: menuCollapse ? '40px' : props.width }" style="display: flex; overflow: hidden"> <div :style="{ width: menuCollapse ? '40px' : props.width }" style="display: flex; overflow: hidden">
<Icon <!-- <Icon
v-show="menuCollapse" v-show="menuCollapse"
@click="onMenuCollapse" @click="onMenuCollapse"
:name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
:class="menuCollapse ? 'unfold' : ''" :class="menuCollapse ? 'unfold' : ''"
size="18" size="18px"
class="fold ml10 mt20 menu-collapse" class="fold ml10 mt20 menu-collapse"
style="cursor: pointer" style="cursor: pointer"
v-if="route.path != '/admin/govern/reportCore/statistics/index'" v-if="route.path != '/admin/govern/reportCore/statistics/index'"
/> /> -->
<div class="cn-tree" :style="{ opacity: menuCollapse ? 0 : 1, display: menuCollapse ? 'none' : '' }"> <div class="cn-tree" :style="{ opacity: menuCollapse ? 0 : 1, display: menuCollapse ? 'none' : '' }">
<div style="display: flex; align-items: center" class="mb10"> <div style="display: flex; align-items: center" class="mb10">
<el-input maxlength="32" show-word-limit v-model.trim="filterText" placeholder="请输入内容" clearable> <el-input maxlength="32" v-model.trim="filterText" placeholder="请输入内容" clearable>
<template #prepend>
<el-select v-model="treeType" @change="changeTreeType" style="min-width: 75px">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
<template #prefix> <template #prefix>
<Icon name="el-icon-Search" style="font-size: 16px" /> <Icon name="el-icon-Search" style="font-size: 16px" />
</template> </template>
</el-input> </el-input>
<Icon <!-- <Icon
@click="onMenuCollapse" @click="onMenuCollapse"
:name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
:class="menuCollapse ? 'unfold' : ''" :class="menuCollapse ? 'unfold' : ''"
size="18" size="18px"
class="fold ml10 menu-collapse" class="fold ml10 menu-collapse"
style="cursor: pointer" style="cursor: pointer"
v-if="props.canExpand && route.path != '/admin/govern/reportCore/statistics/index'" v-if="props.canExpand && route.path != '/admin/govern/reportCore/statistics/index'"
/> /> -->
</div> </div>
<el-collapse <el-collapse
:accordion="true" :accordion="true"
v-model.trim="activeName" v-model="activeName"
style="flex: 1; height: 100%" style="flex: 1; height: 100%"
@change="changeDevice" @change="changeDevice"
v-if="treeType == '1'"
v-loading="loading"
> >
<el-collapse-item title="治理设备" name="0" v-if="zlDeviceData.length != 0"> <el-collapse-item title="治理设备" name="0" v-if="zlDeviceData.length">
<el-select v-model.trim="process" clearable placeholder="请选择状态" class="mb10"> <el-select v-model="process" clearable placeholder="请选择状态" class="mb10">
<el-option label="功能调试" value="2"></el-option> <el-option label="功能调试" value="2" />
<el-option label="出厂调试" value="3"></el-option> <el-option label="出厂调试" value="3" />
<el-option label="正式投运" value="4"></el-option> <el-option label="正式投运" value="4" />
</el-select> </el-select>
<el-tree <el-tree
:style="{ height: bxsDeviceData.length != 0 ? 'calc(100vh - 380px)' : 'calc(100vh - 278px)' }" :style="{ height: governTreeHeight }"
ref="treeRef1" ref="treeRef1"
:props="defaultProps" :props="defaultProps"
highlight-current highlight-current
@@ -54,22 +65,22 @@
style="overflow: auto" style="overflow: auto"
:default-expand-all="false" :default-expand-all="false"
> >
<template #default="{ node, data }"> <template #default="{ node, data: nodeData }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<Icon <Icon
:name="data.icon" :name="nodeData.icon"
style="font-size: 16px" style="font-size: 16px"
:style="{ color: data.color }" :style="{ color: nodeData.color }"
v-if="data.icon" v-if="nodeData.icon"
/> />
<span style="margin-left: 4px">{{ node.label }}</span> <span style="margin-left: 4px">{{ node.label }}</span>
</span> </span>
</template> </template>
</el-tree> </el-tree>
</el-collapse-item> </el-collapse-item>
<el-collapse-item title="便携式设备" name="1" v-if="bxsDeviceData.length != 0"> <el-collapse-item title="便携式设备" name="1" v-if="bxsDeviceData.length">
<el-tree <el-tree
:style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 340px)' : 'calc(100vh - 238px)' }" :style="{ height: otherTreeHeight }"
ref="treeRef2" ref="treeRef2"
:props="defaultProps" :props="defaultProps"
highlight-current highlight-current
@@ -80,22 +91,22 @@
v-bind="$attrs" v-bind="$attrs"
style="overflow: auto" style="overflow: auto"
> >
<template #default="{ node, data }"> <template #default="{ node, data: nodeData }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<Icon <Icon
:name="data.icon" :name="nodeData.icon"
style="font-size: 16px" style="font-size: 16px"
:style="{ color: data.color }" :style="{ color: nodeData.color }"
v-if="data.icon" v-if="nodeData.icon"
/> />
<span style="margin-left: 4px">{{ node.label }}</span> <span style="margin-left: 4px">{{ node.label }}</span>
</span> </span>
</template> </template>
</el-tree> </el-tree>
</el-collapse-item> </el-collapse-item>
<el-collapse-item title="监测设备" name="2" v-if="yqfDeviceData.length != 0"> <el-collapse-item title="监测设备" name="2" v-if="yqfDeviceData.length">
<el-tree <el-tree
:style="{ height: zlDeviceData.length != 0 ? 'calc(100vh - 340px)' : 'calc(100vh - 238px)' }" :style="{ height: otherTreeHeight }"
ref="treeRef3" ref="treeRef3"
:props="defaultProps" :props="defaultProps"
highlight-current highlight-current
@@ -106,13 +117,13 @@
v-bind="$attrs" v-bind="$attrs"
style="overflow: auto" style="overflow: auto"
> >
<template #default="{ node, data }"> <template #default="{ node, data: nodeData }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<Icon <Icon
:name="data.icon" :name="nodeData.icon"
style="font-size: 16px" style="font-size: 16px"
:style="{ color: data.color }" :style="{ color: nodeData.color }"
v-if="data.icon" v-if="nodeData.icon"
/> />
<span style="margin-left: 4px">{{ node.label }}</span> <span style="margin-left: 4px">{{ node.label }}</span>
</span> </span>
@@ -120,288 +131,251 @@
</el-tree> </el-tree>
</el-collapse-item> </el-collapse-item>
</el-collapse> </el-collapse>
<div v-if="treeType == '2'" v-loading="loading">
<el-tree
:style="{ height: engineeringTreeHeight }"
class="pt10"
ref="treeRef4"
:props="defaultProps"
highlight-current
:filter-node-method="filterNode"
node-key="id"
v-bind="$attrs"
:data="props.data"
style="overflow: auto"
:default-expand-all="false"
>
<template #default="{ node, data: nodeData }">
<span class="custom-tree-node">
<Icon
:name="nodeData.icon"
style="font-size: 16px"
:style="{ color: nodeData.color }"
v-if="nodeData.icon"
/>
<span style="margin-left: 4px">{{ node.label }}</span>
</span>
</template>
</el-tree>
</div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import useCurrentInstance from '@/utils/useCurrentInstance' import useCurrentInstance from '@/utils/useCurrentInstance'
import { ElTree } from 'element-plus' import { ElTree, type CollapseModelValue } from 'element-plus'
import { el, fa } from 'element-plus/es/locale' import { ref, watch, nextTick, computed } from 'vue'
import { ref, watch, defineEmits, onMounted, nextTick, computed } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { collectDeviceLeaves } from './govern/lineTreeUtils'
defineOptions({ defineOptions({
name: 'govern/tree' name: 'govern/point',
inheritAttrs: false
}) })
const emit = defineEmits(['changePointType'])
const emit = defineEmits(['changePointType', 'changeTreeType'])
interface Props { interface Props {
width?: string width?: string
canExpand?: boolean canExpand?: boolean
type?: string type?: string
data?: any data?: any[]
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
width: '100%', width: '100%',
canExpand: true, canExpand: true,
type: '', type: '',
data: [] data: () => []
}) })
const process = ref('')
const route = useRoute() const route = useRoute()
const { proxy } = useCurrentInstance() const { proxy } = useCurrentInstance()
const treeType = ref('1')
const options = [
{ label: '设备', value: '1' },
{ label: '工程', value: '2' }
]
const menuCollapse = ref(false) const menuCollapse = ref(false)
const activeName = ref('0') const activeName = ref('0')
const filterText = ref('') const filterText = ref('')
const defaultProps = { const process = ref('')
label: 'name', const loading = ref(false)
value: 'id'
} const defaultProps = { label: 'name', value: 'id' }
//治理设备数据
const zlDeviceData = ref<any>([]) const zlDeviceData = ref<any[]>([])
const zlDevList = ref<any>([]) const zlDevList = ref<any[]>([])
//便携式设备数据 const bxsDeviceData = ref<any[]>([])
const bxsDeviceData = ref<any>([]) const yqfDeviceData = ref<any[]>([])
//监测设备数据
const yqfDeviceData = ref<any>([]) const governTreeHeight = computed(() => 'calc(100vh - 380px)')
watch( const otherTreeHeight = computed(() =>
() => props.data, zlDeviceData.value.length ? 'calc(100vh - 340px)' : 'calc(100vh - 238px)'
(val, oldVal) => {
if (val && val.length != 0) {
val.map((item: any) => {
if (item.name == '治理设备') {
zlDeviceData.value = []
item.children.map((vv: any) => {
zlDeviceData.value.push(vv)
})
zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value))
} else if (item.name == '便携式设备') {
bxsDeviceData.value = []
item.children.map((vv: any) => {
bxsDeviceData.value.push(vv)
})
} else if (item.name == '监测设备') {
yqfDeviceData.value = []
item.children.map((vv: any) => {
yqfDeviceData.value.push(vv)
})
}
})
}
},
{
immediate: true,
deep: true
}
) )
const engineeringTreeHeight = computed(() => 'calc(100vh - 188px)')
watch(filterText, val => { const treeRef1 = ref<InstanceType<typeof ElTree>>()
if (activeName.value == '0') { const treeRef2 = ref<InstanceType<typeof ElTree>>()
treeRef1.value!.filter(val) const treeRef3 = ref<InstanceType<typeof ElTree>>()
} else if (activeName.value == '1') { const treeRef4 = ref<InstanceType<typeof ElTree>>()
treeRef2.value!.filter(val)
} else {
treeRef3.value!.filter(val)
}
})
watch(process, val => {
if (val == '' || val == undefined) {
zlDevList.value = JSON.parse(JSON.stringify(zlDeviceData.value))
} else {
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
}
setTimeout(() => { defineExpose({ treeRef1, treeRef2, treeRef3, treeRef4 })
changeDevice(activeName.value)
}, 0)
})
const changeDevice = (val: any) => { function splitTreeData(val: any[]) {
console.log('🚀 ~ changeDevice ~ val:', val) zlDeviceData.value = []
bxsDeviceData.value = []
yqfDeviceData.value = []
let arr1: any = [] val.forEach(item => {
//zlDeviceData if (item.name === '治理设备') {
zlDevList.value.forEach((item: any) => { zlDeviceData.value = item.children ?? []
item.children.forEach((item2: any) => { } else if (item.name === '便携式设备') {
item2.children.forEach((item3: any) => { bxsDeviceData.value = item.children ?? []
item3.children.forEach((item4: any) => { } else if (item.name === '监测设备') {
arr1.push(item4) yqfDeviceData.value = item.children ?? []
}) }
})
})
}) })
let arr2: any = []
bxsDeviceData.value.forEach((item: any) => { zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
item.children.forEach((item2: any) => {
arr2.push(item2)
})
})
let arr3: any = []
yqfDeviceData.value.forEach((item: any) => {
item.children.forEach((item2: any) => {
item2.children.forEach((item3: any) => {
item3.children.forEach((item4: any) => {
arr3.push(item4)
})
})
})
})
activeName.value = val
if (val == '0') {
arr2.map((item: any) => {
item.checked = false
})
emit('changePointType', activeName.value, arr1[0])
setTimeout(() => {
treeRef1.value?.setCurrentKey(arr1[0]?.id)
}, 100)
}
if (val == '1') {
arr1.map((item: any) => {
item.checked = false
})
emit('changePointType', activeName.value, arr2[0])
setTimeout(() => {
treeRef2.value?.setCurrentKey(arr2[0]?.id)
}, 100)
}
if (val == '2') {
arr3.map((item: any) => {
item.checked = false
})
emit('changePointType', activeName.value, arr3[0])
setTimeout(() => {
treeRef3.value?.setCurrentKey(arr3[0]?.id)
}, 100)
}
// if(activeName.value){
// emit('changePointType', activeName.value)
// }
}
const onMenuCollapse = () => {
menuCollapse.value = !menuCollapse.value
proxy.eventBus.emit('cnTreeCollapse', menuCollapse)
}
const filterNode = (value: string, data: any, node: any) => {
if (!value) return true
// return data.name.includes(value)
if (data.name) {
return chooseNode(value, data, node)
}
} }
function filterProcess(nodes: any) { /** 测点树专用level=2 按 process 过滤,其余层级保留有子节点或匹配的节点 */
if (process.value == '') { function filterProcess(nodes: any[]): any[] {
return nodes if (!process.value) return nodes
}
return nodes return nodes
.map(node => { .map(node => {
// 递归处理子节点
const children = node.children ? filterProcess(node.children) : [] const children = node.children ? filterProcess(node.children) : []
// 对于装置层级level=2只保留 process 值匹配的节点
if (node.level === 2) { if (node.level === 2) {
if (node.process == process.value) { if (node.process == process.value) {
return { return { ...node, children }
...node,
children: children
}
} }
return null return null
} }
// 对于其他节点: if (children.length > 0 || node.process == process.value || !node.children?.length) {
// 1. 如果有满足条件的子节点则保留 return { ...node, children }
// 2. 如果本身 process 值匹配则保留
// 3. 如果是叶子节点也保留(监测点通常没有子节点)
if (children.length > 0 || node.process == process.value || !node.children || node.children.length === 0) {
return {
...node,
children: children
}
} }
return null return null
}) })
.filter(Boolean) // 移除null节点 .filter(Boolean)
} }
// function filterProcess(nodes: any) { function getActiveTreeRef() {
// if (process.value == '') { if (treeType.value === '2') return treeRef4.value
// return nodes if (activeName.value === '0') return treeRef1.value
// } if (activeName.value === '1') return treeRef2.value
// return nodes return treeRef3.value
// .map(node => { }
// // 递归处理子节点
// const children = node.children ? filterProcess(node.children) : []
// // 如果当前节点的process=4或者有子节点满足条件则保留当前节点 function resolveActiveName() {
if (zlDeviceData.value.length) return '0'
if (bxsDeviceData.value.length) return '1'
if (yqfDeviceData.value.length) return '2'
return ''
}
// if (node.process == process.value || children.length > 0) { function selectPointPanel(panelName: string, node?: any) {
// return { if (!node) return
// ...node, emit('changePointType', panelName, node)
// children: node.children nextTick(() => {
// } getActiveTreeRef()?.setCurrentKey(node.id)
// } })
}
// // 否则过滤掉当前节点 const changeDevice = (val: CollapseModelValue) => {
// return null if (Array.isArray(val) || val == null || val === '') return
// }) const panelName = String(val)
// .filter(Boolean) // 移除null节点 const { govern, portable, monitor } = collectDeviceLeaves(
// } zlDevList.value,
// 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配则返回该节点以及其下的所有子节点如果参数是子节点则返回该节点的父节点。name是中文字符enName是英文字符. bxsDeviceData.value,
const chooseNode = (value: string, data: any, node: any) => { yqfDeviceData.value
if (data.name.indexOf(value) !== -1) { )
return true
const panelMap: Record<string, { nodes: any[]; clearOthers: any[][] }> = {
'0': { nodes: govern, clearOthers: [portable, monitor] },
'1': { nodes: portable, clearOthers: [govern, monitor] },
'2': { nodes: monitor, clearOthers: [govern, portable] }
} }
const level = node.level
// 如果传入的节点本身就是一级节点就不用校验了 const panel = panelMap[panelName]
if (level === 1) { if (!panel) return
return false
panel.clearOthers.forEach(list => list.forEach(item => (item.checked = false)))
selectPointPanel(panelName, panel.nodes[0])
}
const setActiveName = () => {
activeName.value = resolveActiveName()
if (activeName.value) {
nextTick(() => changeDevice(activeName.value))
} }
// 先取当前节点的父节点 }
let parentData = node.parent
// 遍历当前节点的父节点 watch(
let index = 0 () => props.data,
while (index < level - 1) { val => {
// 如果匹配到直接返回此处name值是中文字符enName是英文字符。判断匹配中英文过滤 if (!val?.length) return
if (parentData.data.name.indexOf(value) !== -1) { splitTreeData(val)
return true if (treeType.value === '1') {
nextTick(() => setActiveName())
} }
// 否则的话再往上一层做匹配 },
parentData = parentData.parent { immediate: true, deep: true }
index++ )
watch(filterText, val => {
getActiveTreeRef()?.filter(val)
})
watch(process, () => {
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value)))
if (activeName.value === '0') {
nextTick(() => changeDevice(activeName.value))
}
})
const onMenuCollapse = () => {
menuCollapse.value = !menuCollapse.value
proxy.eventBus.emit('cnTreeCollapse', menuCollapse)
}
const filterNode = (value: string, data: any, node: any): boolean => {
if (!value) return true
if (!data.name) return false
return chooseNode(value, data, node)
}
const chooseNode = (value: string, data: any, node: any): boolean => {
if (data.name.indexOf(value) !== -1) return true
const level = node.level
if (level === 1) return false
let parentData = node.parent
for (let i = 0; i < level - 1; i++) {
if (parentData?.data?.name?.indexOf(value) !== -1) return true
parentData = parentData.parent
} }
// 没匹配到返回false
return false return false
} }
//治理
const treeRef1 = ref<InstanceType<typeof ElTree>>() const changeTreeType = (val: string) => {
//便携式 loading.value = true
const treeRef2 = ref<InstanceType<typeof ElTree>>() emit('changeTreeType', val)
//在线 if (val === '1') {
const treeRef3 = ref<InstanceType<typeof ElTree>>() nextTick(() => setActiveName())
defineExpose({ treeRef1, treeRef2, treeRef3 }) }
onMounted(() => {
setTimeout(() => { setTimeout(() => {
if (zlDeviceData.value.length != 0) { loading.value = false
zlDevList.value = filterProcess(JSON.parse(JSON.stringify(zlDeviceData.value))) }, 300)
activeName.value = '0' }
}
if (zlDeviceData.value.length === 0 && bxsDeviceData.value.length != 0) {
activeName.value = '1'
}
if (zlDeviceData.value.length === 0 && bxsDeviceData.value.length === 0) {
activeName.value = '2'
}
if (!zlDeviceData.value && !bxsDeviceData.value) {
activeName.value = '2'
}
nextTick(() => {
changeDevice(activeName.value)
})
}, 500)
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -432,4 +406,7 @@ onMounted(() => {
display: flex; display: flex;
align-items: center; align-items: center;
} }
:deep(.el-input-group__prepend) {
background-color: var(--el-fill-color-blank);
}
</style> </style>

View File

@@ -1,36 +1,45 @@
<template> <template>
<div :style="{ width: menuCollapse ? '40px' : props.width }" style='transition: all 0.3s; overflow: hidden;'> <div :style="{ width: menuCollapse ? '40px' : props.width }" style="transition: all 0.3s; overflow: hidden">
<Icon v-show='menuCollapse' @click='onMenuCollapse' :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" <!-- <Icon
:class="menuCollapse ? 'unfold' : ''" size='18' class='fold ml10 mt20 menu-collapse' v-show="menuCollapse"
style='cursor: pointer' /> @click="onMenuCollapse"
<div class='cn-tree' :style='{ opacity: menuCollapse ? 0 : 1 }'> :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
<div style='display: flex; align-items: center' class='mb10'> :class="menuCollapse ? 'unfold' : ''"
<el-input maxlength="32" show-word-limit v-model.trim='filterText' placeholder='请输入内容' clearable> size="18px"
class="fold ml10 mt20 menu-collapse"
style="cursor: pointer"
/> -->
<div class="cn-tree" :style="{ opacity: menuCollapse ? 0 : 1 }">
<div style="display: flex; align-items: center" class="mb10">
<el-input maxlength="32" v-model.trim="filterText" placeholder="请输入内容" clearable>
<template #prefix> <template #prefix>
<Icon name='el-icon-Search' style='font-size: 16px' /> <Icon name="el-icon-Search" style="font-size: 16px" />
</template> </template>
</el-input> </el-input>
</div> </div>
<el-tree <el-tree
:style="{ height: 'calc(100vh)' }" :style="{ height: 'calc(100vh - 120px)' }"
style='overflow: auto;' style="overflow: auto"
ref='treeRef' ref="treeRef"
:props='defaultProps' :props="defaultProps"
highlight-current highlight-current
:filter-node-method='filterNode' :filter-node-method="filterNode"
node-key='id' node-key="id"
show-checkbox show-checkbox
@check="handleCheckChange" @check="handleCheckChange"
@node-click="handleNodeClick"
:default-checked-keys="defaultCheckedKeys" :default-checked-keys="defaultCheckedKeys"
v-bind='$attrs' v-bind="$attrs"
:default-expand-all="false" :default-expand-all="false"
> >
<template #default='{ node, data }'> <template #default="{ node, data: nodeData }">
<span class='custom-tree-node'> <span class="custom-tree-node">
<Icon :name='data.icon' style='font-size: 16px' :style='{ color: data.color }' <Icon
v-if='data.icon' /> :name="nodeData.icon"
<span style='margin-left: 4px'>{{ node.label }}</span> style="font-size: 16px"
:style="{ color: nodeData.color }"
v-if="nodeData.icon"
/>
<span style="margin-left: 4px">{{ node.label }}</span>
</span> </span>
</template> </template>
</el-tree> </el-tree>
@@ -38,15 +47,13 @@
</div> </div>
</template> </template>
<script lang='ts' setup> <script lang="ts" setup>
import useCurrentInstance from '@/utils/useCurrentInstance' import useCurrentInstance from '@/utils/useCurrentInstance'
import { ElMessage, ElTree } from 'element-plus' import { ElMessage, ElTree } from 'element-plus'
import { emit } from 'process';
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { createTreeFilterNode } from './govern/treeFilterUtils'
defineOptions({ defineOptions({ name: 'govern/select', inheritAttrs: false })
name: 'govern/tree'
})
interface Props { interface Props {
width?: string width?: string
@@ -57,174 +64,66 @@ const props = withDefaults(defineProps<Props>(), {
width: '280px', width: '280px',
canExpand: true canExpand: true
}) })
const emit = defineEmits(['changePointType', 'checkedNodesChange'])
const { proxy } = useCurrentInstance() const { proxy } = useCurrentInstance()
const menuCollapse = ref(false) const menuCollapse = ref(false)
const filterText = ref('') const filterText = ref('')
const defaultProps = { const defaultProps = { label: 'name', value: 'id' }
label: 'name', const filterNode = createTreeFilterNode()
value: 'id' const checkedNodes = ref<any[]>([])
} const defaultCheckedKeys = ref<string[]>([])
const emit = defineEmits(['changePointType', 'checkedNodesChange']) const MAX_CHECK = 5
const MONITOR_LEVEL = 3
watch(filterText, val => treeRef.value?.filter(val))
watch(filterText, val => {
treeRef.value!.filter(val)
})
const onMenuCollapse = () => { const onMenuCollapse = () => {
menuCollapse.value = !menuCollapse.value menuCollapse.value = !menuCollapse.value
proxy.eventBus.emit('cnTreeCollapse', menuCollapse) proxy.eventBus.emit('cnTreeCollapse', menuCollapse)
} }
const filterNode = (value: string, data: any, node: any) => {
if (!value) return true
// return data.name.includes(value)
if (data.name) {
return chooseNode(value, data, node) const handleCheckChange = (_data: any, checkInfo: any) => {
} const monitoringPointNodes = (checkInfo.checkedNodes as any[]).filter(node => node.level === MONITOR_LEVEL)
}
// 过滤父节点 / 子节点 (如果输入的参数是父节点且能匹配则返回该节点以及其下的所有子节点如果参数是子节点则返回该节点的父节点。name是中文字符enName是英文字符. if (monitoringPointNodes.length > MAX_CHECK) {
const chooseNode = (value: string, data: any, node: any) => { const previousCheckedNodes = checkedNodes.value
if (data.name.indexOf(value) !== -1) {
return true
}
const level = node.level
// 如果传入的节点本身就是一级节点就不用校验了
if (level === 1) {
return false
}
// 先取当前节点的父节点
let parentData = node.parent
// 遍历当前节点的父节点
let index = 0
while (index < level - 1) {
// 如果匹配到直接返回此处name值是中文字符enName是英文字符。判断匹配中英文过滤
if (parentData.data.name.indexOf(value) !== -1) {
return true
}
// 否则的话再往上一层做匹配
parentData = parentData.parent
index++
}
// 没匹配到返回false
return false
}
// 处理节点点击事件
const handleNodeClick = (data: any, node: any, event: any) => {
}
// 存储所有勾选的节点
const checkedNodes = ref<any[]>([])
const defaultCheckedKeys = ref<string[]>([])
// 处理节点勾选变化
const handleCheckChange = (data: any, checkInfo: any) => {
const { checkedNodes: nodes } = checkInfo
// 过滤出监测点层级(level=3)的节点
const monitoringPointNodes = nodes.filter((node: any) => {
return node.level === 3
})
// 限制最多只能勾选5个监测点
if (monitoringPointNodes.length > 5) {
// 获取之前选中的节点
const previousCheckedNodes = checkedNodes.value || []
// 计算新增的节点
const newNodes = monitoringPointNodes.filter( const newNodes = monitoringPointNodes.filter(
(node: any) => !previousCheckedNodes.some((prev: any) => prev.id === node.id) node => !previousCheckedNodes.some(prev => prev.id === node.id)
) )
// 如果是从父级勾选导致超过限制,保留前几个直到达到限制数量
if (newNodes.length > 0) { if (newNodes.length > 0) {
const allowedNewCount = 5 - previousCheckedNodes.length const allowedNewCount = MAX_CHECK - previousCheckedNodes.length
if (allowedNewCount > 0) { if (allowedNewCount > 0) {
// 允许添加allowedNewCount个新节点 const finalNodes = [...previousCheckedNodes, ...newNodes.slice(0, allowedNewCount)]
const allowedNewNodes = newNodes.slice(0, allowedNewCount)
const finalNodes = [...previousCheckedNodes, ...allowedNewNodes]
checkedNodes.value = finalNodes checkedNodes.value = finalNodes
// 设置树的勾选状态为正确的节点
treeRef.value?.setCheckedNodes(finalNodes) treeRef.value?.setCheckedNodes(finalNodes)
// 将勾选的监测点节点暴露出去
emit('checkedNodesChange', finalNodes) emit('checkedNodesChange', finalNodes)
// 更新节点的可勾选状态
updateNodeCheckStatus(finalNodes.length) updateNodeCheckStatus(finalNodes.length)
if (monitoringPointNodes.length > MAX_CHECK) {
// 只有在真正超过5个时才提示警告 ElMessage.warning(`最多只能选择${MAX_CHECK}个监测点`)
if (monitoringPointNodes.length > 5) {
ElMessage.warning('最多只能选择5个监测点')
} }
return return
} }
} }
// 其他情况回滚到之前的状态 ElMessage.warning(`最多只能选择${MAX_CHECK}个监测点`)
ElMessage.warning('最多只能选择5个监测点')
treeRef.value?.setCheckedNodes(checkedNodes.value) treeRef.value?.setCheckedNodes(checkedNodes.value)
return return
} }
checkedNodes.value = monitoringPointNodes checkedNodes.value = monitoringPointNodes
// 将勾选的监测点节点暴露出去
emit('checkedNodesChange', monitoringPointNodes) emit('checkedNodesChange', monitoringPointNodes)
// 更新节点的可勾选状态
updateNodeCheckStatus(monitoringPointNodes.length)
}
// 处理节点勾选变化
const handleCheckChange2 = (data: any, checkInfo: any) => {
const { checkedNodes: nodes } = checkInfo
// 过滤出监测点层级(level=3)的节点
const monitoringPointNodes = nodes.filter((node: any) => {
// 监测点节点通常具有 comFlag 属性或其他标识
return node.level === 3
})
// 限制最多只能勾选5个监测点
if (monitoringPointNodes.length > 5) {
ElMessage.warning('最多只能选择5个监测点')
// 保持之前勾选的状态
treeRef.value?.setCheckedNodes(checkedNodes.value)
return
}
checkedNodes.value = monitoringPointNodes
// 将勾选的监测点节点暴露出去
emit('checkedNodesChange', monitoringPointNodes)
// 更新节点的可勾选状态
updateNodeCheckStatus(monitoringPointNodes.length) updateNodeCheckStatus(monitoringPointNodes.length)
} }
// 更新节点的可勾选状态
const updateNodeCheckStatus = (currentCount: number) => { const updateNodeCheckStatus = (currentCount: number) => {
if (!treeRef.value) return if (!treeRef.value) return
const isMaxSelected = currentCount >= MAX_CHECK
// 如果已经选了5个则禁用其他未选中的监测点节点 treeRef.value.store._getAllNodes().forEach((node: any) => {
const isMaxSelected = currentCount >= 5 if (node.level === MONITOR_LEVEL) {
node.data.disabled = isMaxSelected && !node.checked
// 获取所有节点并更新状态
const allNodes = treeRef.value.store._getAllNodes()
allNodes.forEach((node: any) => {
if (node.level === 3) { // 监测点层级
// 如果已达到最大数量且该节点未被选中,则禁用勾选
if (isMaxSelected && !node.data.checked) {
node.data.disabled = true
} else {
node.data.disabled = false
}
} }
}) })
} }
@@ -233,8 +132,7 @@ const treeRef = ref<InstanceType<typeof ElTree>>()
defineExpose({ treeRef }) defineExpose({ treeRef })
</script> </script>
<style lang="scss" scoped>
<style lang='scss' scoped>
.cn-tree { .cn-tree {
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;

View File

@@ -1,79 +1,79 @@
<template> <template>
<div class="layout-logo"> <div class="layout-logo">
<img v-if="!config.layout.menuCollapse" class="logo-img" :src="getTheme.logoUrl" /> <img v-if="!config.layout.menuCollapse" class="logo-img" :src="getTheme.logoUrl" />
<!-- <div--> <!-- <div-->
<!-- v-if="!config.layout.menuCollapse"--> <!-- v-if="!config.layout.menuCollapse"-->
<!-- :style="{ color: config.getColorVal('menuActiveColor') }"--> <!-- :style="{ color: config.getColorVal('menuActiveColor') }"-->
<!-- class="website-name"--> <!-- class="website-name"-->
<!-- >--> <!-- >-->
<!-- 灿能--> <!-- 灿能-->
<!-- </div>--> <!-- </div>-->
<Icon <!-- <Icon
v-if="config.layout.layoutMode != 'Streamline'" v-if="config.layout.layoutMode != 'Streamline'"
@click="onMenuCollapse" @click="onMenuCollapse"
:name="config.layout.menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" :name="config.layout.menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
:class="config.layout.menuCollapse ? 'unfold' : ''" :class="config.layout.menuCollapse ? 'unfold' : ''"
:color="config.getColorVal('menuActiveColor')" :color="config.getColorVal('menuActiveColor')"
style="margin: 15px;" style="margin: 15px;"
size="18" size="18"
class="fold" class="fold"
/> /> -->
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { closeShade } from '@/utils/pageShade' import { closeShade } from '@/utils/pageShade'
import { Session } from '@/utils/storage' import { Session } from '@/utils/storage'
import { setNavTabsWidth } from '@/utils/layout' import { setNavTabsWidth } from '@/utils/layout'
const config = useConfig() const config = useConfig()
const getTheme = JSON.parse(window.localStorage.getItem('getTheme') as string) const getTheme = JSON.parse(window.localStorage.getItem('getTheme') as string)
const onMenuCollapse = function () { const onMenuCollapse = function () {
if (config.layout.shrink && !config.layout.menuCollapse) { if (config.layout.shrink && !config.layout.menuCollapse) {
closeShade() closeShade()
} }
config.setLayout('menuCollapse', !config.layout.menuCollapse) config.setLayout('menuCollapse', !config.layout.menuCollapse)
// 等待侧边栏动画结束后重新计算导航栏宽度 // 等待侧边栏动画结束后重新计算导航栏宽度
setTimeout(() => { setTimeout(() => {
setNavTabsWidth() setNavTabsWidth()
}, 350) }, 350)
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.layout-logo { .layout-logo {
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
box-sizing: border-box; box-sizing: border-box;
padding: 5px 10px; padding: 5px 10px;
background: v-bind( background: v-bind(
'config.layout.layoutMode != "Streamline" ? config.getColorVal("menuTopBarBackground"):"transparent"' 'config.layout.layoutMode != "Streamline" ? config.getColorVal("menuTopBarBackground"):"transparent"'
); );
} }
.logo-img { .logo-img {
height: 50px; height: 50px;
object-fit: cover; object-fit: cover;
margin: auto; margin: auto;
} }
.website-name { .website-name {
display: block; display: block;
width: 180px; width: 180px;
padding-left: 4px; padding-left: 4px;
font-size: var(--el-font-size-extra-large); font-size: var(--el-font-size-extra-large);
font-weight: 600; font-weight: 600;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.fold { .fold {
margin-left: auto; margin-left: auto;
} }
.unfold { .unfold {
margin: 0 auto; margin: 0 auto;
} }
</style> </style>

View File

@@ -1,111 +1,111 @@
<template> <template>
<div class="nav-bar"> <div class="nav-bar">
<div v-if="config.layout.shrink && config.layout.menuCollapse" class="unfold"> <div v-if="config.layout.shrink && config.layout.menuCollapse" class="unfold">
<Icon @click="onMenuCollapse" name="fa fa-indent" :color="config.getColorVal('menuActiveColor')" <!-- <Icon @click="onMenuCollapse" name="fa fa-indent" :color="config.getColorVal('menuActiveColor')"
size="18" /> size="18" /> -->
</div> </div>
<span class="nav-bar-title">{{ getTheme.name }} <span style="font-size: 14px;" v-if="Version?.versionName"> <span class="nav-bar-title">{{ getTheme.name }} <span style="font-size: 14px;" v-if="Version?.versionName">
({{ Version?.versionName }}) ({{ Version?.versionName }})
</span></span> </span></span>
<NavMenus /> <NavMenus />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import NavTabs from '@/layouts/admin/components/navBar/tabs.vue' import NavTabs from '@/layouts/admin/components/navBar/tabs.vue'
import NavMenus from '../navMenus.vue' import NavMenus from '../navMenus.vue'
import { showShade } from '@/utils/pageShade' import { showShade } from '@/utils/pageShade'
import { onMounted } from 'vue' import { onMounted } from 'vue'
import { getLastData } from '@/api/systerm' import { getLastData } from '@/api/systerm'
const config = useConfig() const config = useConfig()
const getTheme = JSON.parse(window.localStorage.getItem('getTheme') as string) const getTheme = JSON.parse(window.localStorage.getItem('getTheme') as string)
const Version: any = ref({}) const Version: any = ref({})
const onMenuCollapse = () => { const onMenuCollapse = () => {
showShade('ba-aside-menu-shade', () => { showShade('ba-aside-menu-shade', () => {
config.setLayout('menuCollapse', true) config.setLayout('menuCollapse', true)
}) })
config.setLayout('menuCollapse', false) config.setLayout('menuCollapse', false)
} }
onMounted(() => { onMounted(() => {
getLastData({ versionType: 'WEB' }).then(res => { getLastData({ versionType: 'WEB' }).then(res => {
Version.value = res.data Version.value = res.data
}) })
document.title = getTheme.name document.title = getTheme.name
}) })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.nav-bar { .nav-bar {
display: flex; display: flex;
align-items: center; align-items: center;
height: 60px; height: 60px;
width: 100%; width: 100%;
background-color: v-bind('config.getColorVal("headerBarBackground")'); background-color: v-bind('config.getColorVal("headerBarBackground")');
.nav-bar-title { .nav-bar-title {
color: v-bind('config.getColorVal("headerBarTabColor")'); color: v-bind('config.getColorVal("headerBarTabColor")');
font-size: 24px; font-size: 24px;
margin-left: 10px; margin-left: 10px;
font-weight: 700; font-weight: 700;
} }
:deep(.nav-tabs) { :deep(.nav-tabs) {
display: flex; display: flex;
height: 100%; height: 100%;
position: relative; position: relative;
.ba-nav-tab { .ba-nav-tab {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 0 20px; padding: 0 20px;
cursor: pointer; cursor: pointer;
z-index: 1; z-index: 1;
height: 100%; height: 100%;
user-select: none; user-select: none;
color: v-bind('config.getColorVal("headerBarTabColor")'); color: v-bind('config.getColorVal("headerBarTabColor")');
transition: all 0.2s; transition: all 0.2s;
-webkit-transition: all 0.2s; -webkit-transition: all 0.2s;
.close-icon { .close-icon {
padding: 2px; padding: 2px;
margin: 2px 0 0 4px; margin: 2px 0 0 4px;
color: v-bind('config.getColorVal("headerBarTabColor")') !important; color: v-bind('config.getColorVal("headerBarTabColor")') !important;
} }
&.active { &.active {
color: v-bind('config.getColorVal("headerBarTabActiveColor")'); color: v-bind('config.getColorVal("headerBarTabActiveColor")');
.close-icon { .close-icon {
color: v-bind('config.getColorVal("headerBarTabActiveColor")') !important; color: v-bind('config.getColorVal("headerBarTabActiveColor")') !important;
} }
} }
&:hover { &:hover {
color: v-bind('config.getColorVal("headerBarTabActiveColor")'); color: v-bind('config.getColorVal("headerBarTabActiveColor")');
background-color: v-bind('config.getColorVal("headerBarHoverBackground")'); background-color: v-bind('config.getColorVal("headerBarHoverBackground")');
.close-icon { .close-icon {
color: v-bind('config.getColorVal("headerBarTabActiveColor")') !important; color: v-bind('config.getColorVal("headerBarTabActiveColor")') !important;
} }
} }
} }
.nav-tabs-active-box { .nav-tabs-active-box {
position: absolute; position: absolute;
height: 50px; height: 50px;
background-color: v-bind('config.getColorVal("headerBarTabActiveBackground")'); background-color: v-bind('config.getColorVal("headerBarTabActiveBackground")');
transition: all 0.2s; transition: all 0.2s;
-webkit-transition: all 0.2s; -webkit-transition: all 0.2s;
} }
} }
} }
.unfold { .unfold {
align-self: center; align-self: center;
padding-left: var(--ba-main-space); padding-left: var(--ba-main-space);
} }
</style> </style>

View File

@@ -1,51 +1,51 @@
<template> <template>
<el-dialog draggable width="600px" v-model.trim="dialogVisible" :title="title"> <el-dialog draggable width="500px" v-model.trim="dialogVisible" :title="title">
<el-form :inline="false" :model="form" label-width="auto" class="form-one"> <el-form :inline="false" :model="form" label-width="auto" class="form-one">
<el-form-item label="用户名称:"> <el-form-item label="用户名称:">
<el-input v-model.trim="form.name" :disabled="true"></el-input> <el-input v-model.trim="form.name" :disabled="true"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="登录名称:" class="top"> <el-form-item label="登录名称:" class="top">
<el-input v-model.trim="form.loginName" :disabled="true"></el-input> <el-input v-model.trim="form.loginName" :disabled="true"></el-input>
</el-form-item> </el-form-item>
<!-- <el-form-item label="归属部门名称:" class="top"> <!-- <el-form-item label="归属部门名称:" class="top">
<el-input v-model.trim="form.deptName" :disabled="true"></el-input> <el-input v-model.trim="form.deptName" :disabled="true"></el-input>
</el-form-item> --> </el-form-item> -->
<el-form-item label="拥有的角色:" class="top"> <el-form-item label="拥有的角色:" class="top">
<el-input v-model.trim="form.role" :disabled="true"></el-input> <el-input v-model.trim="form.role" :disabled="true"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="电话号码:" class="top"> <el-form-item label="电话号码:" class="top">
<el-input v-model.trim="form.phone" :disabled="true"></el-input> <el-input v-model.trim="form.phone" :disabled="true"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="电子邮箱:" class="top"> <el-form-item label="电子邮箱:" class="top">
<el-input v-model.trim="form.email" :disabled="true"></el-input> <el-input v-model.trim="form.email" :disabled="true"></el-input>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-dialog> </el-dialog>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, inject } from 'vue' import { ref, inject } from 'vue'
import { reactive } from 'vue' import { reactive } from 'vue'
import { useAdminInfo } from '@/stores/adminInfo' import { useAdminInfo } from '@/stores/adminInfo'
const dialogVisible = ref(false) const dialogVisible = ref(false)
const title = ref('用户信息') const title = ref('用户信息')
const adminInfo = useAdminInfo() const adminInfo = useAdminInfo()
const formRef = ref() const formRef = ref()
const form = reactive({ const form = reactive({
name: '', name: '',
deptName: '', deptName: '',
phone: '', phone: '',
email: '', email: '',
role: '', role: '',
loginName: '' loginName: ''
}) })
const open = () => { const open = () => {
dialogVisible.value = true dialogVisible.value = true
for (const key in form) { for (const key in form) {
form[key] = Array.isArray(adminInfo.$state[key]) ? adminInfo.$state[key].join(',') : adminInfo.$state[key] form[key] = Array.isArray(adminInfo.$state[key]) ? adminInfo.$state[key].join(',') : adminInfo.$state[key]
} }
} }
defineExpose({ open }) defineExpose({ open })
</script> </script>

View File

@@ -51,11 +51,11 @@ onBeforeMount(() => {
}) })
const init = async () => { const init = async () => {
await Promise.all([getAreaList(), dictDataCache(), getUserById(), areaSelect(),getAllUserSimpleList()]).then(res => { await Promise.all([ dictDataCache(), getUserById(), areaSelect(),getAllUserSimpleList()]).then(res => {
dictData.state.area = res[0].data // dictData.state.area = res[0].data
dictData.state.basic = res[1].data dictData.state.basic = res[0].data
// dictData.state.userList=res[4].data // dictData.state.userList=res[4].data
adminInfo.dataFill(res[2].data) adminInfo.dataFill(res[1].data)
// dictData.state.areaTree = res[3].data // dictData.state.areaTree = res[3].data
}) })
/** /**

View File

@@ -35,6 +35,16 @@ export const adminBaseRoute = {
title: pageTitle('router.supplementaryRecruitment') title: pageTitle('router.supplementaryRecruitment')
} }
}, },
{
// 版本维护
path: '/version',
name: 'version',
component: () => import('@/views/govern/manage/basic/version.vue'),
meta: {
title: pageTitle('router.version')
}
},
{ {
path: 'cockpit', path: 'cockpit',
name: '项目管理', name: '项目管理',

View File

@@ -1,5 +1,5 @@
<template> <template>
<el-dialog width="600px" v-model.trim='dialogVisible' :title='title'> <el-dialog width="500px" v-model.trim='dialogVisible' :title='title'>
<el-scrollbar> <el-scrollbar>
<el-form :inline='false' :model='form' label-width='auto' class="form-one" :rules='rules' ref='formRef'> <el-form :inline='false' :model='form' label-width='auto' class="form-one" :rules='rules' ref='formRef'>
<el-form-item label='角色名称'> <el-form-item label='角色名称'>

103
src/utils/downloadFile.ts Normal file
View File

@@ -0,0 +1,103 @@
import { ElMessage, ElMessageBox, ElInput, ElSegmented } from 'element-plus'
export const downLoadFile = (name: string, key: string, res: any) => {
let blob = new Blob([res], {
type: getFileType(key)
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a') // 创建a标签
link.href = url
link.download = name // 设置下载的文件名
document.body.appendChild(link)
link.click() //执行下载
document.body.removeChild(link)
ElMessage.success('下载成功')
}
const getFileType = (url: string) => {
const ext = url.split('.').pop()?.toLowerCase() || ''
const mimeMap: Record<string, string> = {
// Excel
xls: 'application/vnd.ms-excel',
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
// CSV
csv: 'text/csv',
// Word
doc: 'application/msword',
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
// PDF
pdf: 'application/pdf',
// PowerPoint
ppt: 'application/vnd.ms-powerpoint',
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
// 图片
png: 'image/png',
gif: 'image/gif',
jpeg: 'image/jpeg',
jpg: 'image/jpeg',
bmp: 'image/bmp',
ico: 'image/vnd.microsoft.icon',
tif: 'image/tiff',
tiff: 'image/tiff',
svg: 'image/svg+xml',
webp: 'image/webp',
// 音频
mp3: 'audio/mpeg',
aac: 'audio/aac',
mid: 'audio/midi',
midi: 'audio/midi',
oga: 'audio/ogg',
wav: 'audio/wav',
weba: 'audio/webm',
// 视频
avi: 'video/x-msvideo',
mpeg: 'video/mpeg',
ogv: 'video/ogg',
webm: 'video/webm',
'3gp': 'video/3gpp',
'3g2': 'video/3gpp2',
// 网页/代码
html: 'text/html',
css: 'text/css',
js: 'text/javascript',
mjs: 'text/javascript',
json: 'application/json',
jsonld: 'application/ld+json',
xhtml: 'application/xhtml+xml',
xml: 'application/xml',
xul: 'application/vnd.mozilla.xul+xml',
// 文档
abw: 'application/x-abiword',
odp: 'application/vnd.oasis.opendocument.presentation',
ods: 'application/vnd.oasis.opendocument.spreadsheet',
odt: 'application/vnd.oasis.opendocument.text',
rtf: 'application/rtf',
txt: 'text/plain',
vsd: 'application/vnd.visio',
// 字体
otf: 'font/otf',
ttf: 'font/ttf',
woff: 'font/woff',
woff2: 'font/woff2',
eot: 'application/vnd.ms-fontobject',
// 压缩/归档
arc: 'application/x-freearc',
bz: 'application/x-bzip',
bz2: 'application/x-bzip2',
rar: 'application/x-rar-compressed',
tar: 'application/x-tar',
zip: 'application/zip',
'7z': 'application/x-7z-compressed',
// 其他
bin: 'application/octet-stream',
csh: 'application/x-csh',
epub: 'application/epub+zip',
azw: 'application/vnd.amazon.ebook',
ics: 'text/calendar',
jar: 'application/java-archive',
mpkg: 'application/vnd.apple.installer+xml',
ogx: 'application/ogg',
sh: 'application/x-sh',
swf: 'application/x-shockwave-flash'
}
return mimeMap[ext] || ''
}

View File

@@ -1,5 +1,3 @@
import { number } from 'vue-types'
const dataProcessing = (arr: any[]) => { const dataProcessing = (arr: any[]) => {
return arr return arr
.filter(item => typeof item === 'number' || (typeof item === 'string' && !isNaN(parseFloat(item)))) .filter(item => typeof item === 'number' || (typeof item === 'string' && !isNaN(parseFloat(item))))
@@ -259,6 +257,32 @@ export const completeTimeSeries = (rawData: string[][]): (string | null)[][] =>
} }
}) })
// 补首尾边界:首日 00:00:00、末日 23:59:59
const createPadItem = (timeStr: string): (string | null | undefined)[] => {
const result: (string | null | undefined)[] = [timeStr, '/']
if (template.length > 2) result.push(template[2])
if (template.length > 3) result.push(template[3])
return result
}
const firstDate = new Date(completedData[0][0] as string)
const dayStartStr = formatTime(
new Date(firstDate.getFullYear(), firstDate.getMonth(), firstDate.getDate(), 0, 0, 0)
)
const lastDate = new Date(completedData[completedData.length - 1][0] as string)
const dayEndStr = formatTime(new Date(lastDate.getFullYear(), lastDate.getMonth(), lastDate.getDate(), 23, 59, 59))
const firstTimeStr = formatTime(new Date(completedData[0][0] as string))
if (firstTimeStr !== dayStartStr) {
completedData.unshift(createPadItem(dayStartStr))
}
const lastTimeStr = formatTime(new Date(completedData[completedData.length - 1][0] as string))
if (lastTimeStr !== dayEndStr) {
completedData.push(createPadItem(dayEndStr))
}
return completedData return completedData
} }

View File

@@ -4,7 +4,7 @@ import { ElLoading, ElMessage, ElNotification, type LoadingOptions } from 'eleme
import { refreshToken } from '@/api/user-boot/user' import { refreshToken } from '@/api/user-boot/user'
import router from '@/router/index' import router from '@/router/index'
import { useAdminInfo } from '@/stores/adminInfo' import { useAdminInfo } from '@/stores/adminInfo'
import { useNavTabs } from '@/stores/navTabs'
window.requests = [] window.requests = []
window.tokenRefreshing = false window.tokenRefreshing = false
let loginExpireTimer: any = null let loginExpireTimer: any = null
@@ -13,7 +13,7 @@ const loadingInstance: LoadingInstance = {
target: null, target: null,
count: 0 count: 0
} }
const navTabs = useNavTabs()
/** /**
* 根据运行环境获取基础请求URL * 根据运行环境获取基础请求URL
*/ */
@@ -76,6 +76,7 @@ function createAxios<Data = any, T = ApiPromise<Data>>(
config.url == '/cs-harmonic-boot/limitRateDetailD/limitTimeProbabilityData' || config.url == '/cs-harmonic-boot/limitRateDetailD/limitTimeProbabilityData' ||
config.url == '/cs-harmonic-boot/limitRateDetailD/limitProbabilityData' || config.url == '/cs-harmonic-boot/limitRateDetailD/limitProbabilityData' ||
config.url == '/system-boot/dictTree/queryByCode' || config.url == '/system-boot/dictTree/queryByCode' ||
config.url == '/system-boot/dictTree/queryByid' ||
config.url == '/system-boot/dictTree/query' config.url == '/system-boot/dictTree/query'
) )
) )
@@ -164,6 +165,9 @@ function createAxios<Data = any, T = ApiPromise<Data>>(
message: response.data.message message: response.data.message
}) })
adminInfo.removeToken() adminInfo.removeToken()
navTabs.closeTabs()
window.localStorage.clear()
adminInfo.reset()
router.push({ name: 'login' }) router.push({ name: 'login' })
loginExpireTimer = null // 执行后清空定时器 loginExpireTimer = null // 执行后清空定时器
}, 100) // 可根据实际情况调整延迟时间 }, 100) // 可根据实际情况调整延迟时间

View File

@@ -57,7 +57,7 @@
<el-input maxlength="32" show-word-limit v-model.trim="form.email" placeholder="请输入描述" /> <el-input maxlength="32" show-word-limit v-model.trim="form.email" placeholder="请输入描述" />
</el-form-item> </el-form-item>
<el-form-item label="时间段" prop="limitTime"> <el-form-item label="时间段" prop="limitTime">
<el-slider v-model.trim="form.limitTime" style="width: 95%" range show-stops :max="24" /> <el-slider v-model.trim="form.limitTime" style="width: 95%" range :max="24" />
</el-form-item> </el-form-item>
<el-form-item label="起始IP" prop="limitIpStart"> <el-form-item label="起始IP" prop="limitIpStart">
<el-input maxlength="32" show-word-limit v-model.trim="form.limitIpStart" placeholder="请输入描述" /> <el-input maxlength="32" show-word-limit v-model.trim="form.limitIpStart" placeholder="请输入描述" />

View File

@@ -100,6 +100,7 @@ const tableStore = new TableStore({
} }
}, },
{ title: '设备名称', field: 'equipmentName', align: 'center', width: 120 }, { title: '设备名称', field: 'equipmentName', align: 'center', width: 120 },
{ title: '监测点名称', field: 'lineName', align: 'center', width: 140 },
{ title: '工程名称', field: 'engineeringName', align: 'center', width: 120 }, { title: '工程名称', field: 'engineeringName', align: 'center', width: 120 },
{ title: '项目名称', field: 'projectName', align: 'center', width: 120 }, { title: '项目名称', field: 'projectName', align: 'center', width: 120 },
{ title: '发生时刻', field: 'startTime', align: 'center', width: 180, sortable: true }, { title: '发生时刻', field: 'startTime', align: 'center', width: 180, sortable: true },

View File

@@ -1,5 +1,5 @@
<template> <template>
<div ref="refheader" v-show="!isWaveCharts" style="width: 100%;"> <div ref="refheader" v-show="!isWaveCharts" style="width: 100%">
<TableHeader datePicker showExport> <TableHeader datePicker showExport>
<template v-slot:select> <template v-slot:select>
<el-form-item label="数据来源"> <el-form-item label="数据来源">
@@ -141,10 +141,11 @@ const tableStore = new TableStore({
{ title: '暂降(聚升)幅值(%)', minWidth: 100, field: 'evtParamVVaDepth', align: 'center', sortable: true }, { title: '暂降(聚升)幅值(%)', minWidth: 100, field: 'evtParamVVaDepth', align: 'center', sortable: true },
{ {
title: '操作', fixed: 'right', title: '操作',
fixed: 'right',
align: 'center', align: 'center',
width: '180', width: '180',
render: 'buttons', render: 'buttons',
buttons: [ buttons: [
{ {
@@ -159,12 +160,12 @@ const tableStore = new TableStore({
}, },
click: async row => { click: async row => {
row.loading1 = true row.loading1 = true
loading.value = true
isWaveCharts.value = true
await analyseWave(row.id) await analyseWave(row.id)
.then(res => { .then(res => {
row.loading1 = false row.loading1 = false
if (res != undefined) { if (res != undefined) {
loading.value = true
isWaveCharts.value = true
boxoList.value = row boxoList.value = row
boxoList.value.persistTime = row.evtParamTm boxoList.value.persistTime = row.evtParamTm
boxoList.value.featureAmplitude = boxoList.value.featureAmplitude =

View File

@@ -0,0 +1,129 @@
<template>
<el-dialog class="cn-operate-dialog" draggable v-model="dialogVisible" :title="title" width="500px"
@closed="handleClosed">
<el-form ref="formRef" :model="form" :rules="rules" label-width="auto" class="form-one">
<el-form-item label="方案名称" prop="name">
<el-input v-model.trim="form.name" maxlength="64" show-word-limit placeholder="请输入方案名称" clearable />
</el-form-item>
<el-form-item label="在线率阈值" prop="onlineRateLimit">
<el-input-number v-model="form.onlineRateLimit" :min="0" :max="100" :precision="0" style="width: 100%"
placeholder="0-100" />
</el-form-item>
<el-form-item label="完整性阈值" prop="integrityLimit">
<el-input-number v-model="form.integrityLimit" :min="0" :max="100" :precision="0" style="width: 100%"
placeholder="0-100" />
</el-form-item>
<!-- <el-form-item label="是否启用" prop="active">
<el-select v-model="form.active" placeholder="请选择" style="width: 100%">
<el-option label="启用" value="1" />
<el-option label="停用" value="0" />
</el-select>
</el-form-item> -->
</el-form>
<template #footer>
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="onSubmit">确定</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import { inject } from 'vue'
import { add, update } from '@/api/cs-system-boot/appinfo'
const emit = defineEmits(['Cancels'])
const dialogVisible = ref(false)
const title = ref('')
const formRef = ref()
const defaultForm = () => ({
id: '',
name: '',
onlineRateLimit: undefined as number | undefined,
integrityLimit: undefined as number | undefined,
active: '0' as string
})
const form = reactive(defaultForm())
const percentRule = (label: string) => [
{ required: true, message: `请输入${label}`, trigger: 'blur' },
{
type: 'number',
min: 0,
max: 100,
message: `${label}范围为 0-100`,
trigger: 'blur'
}
]
const rules: any = {
name: [
{ required: true, message: '请输入方案名称', trigger: 'blur' },
{ min: 1, max: 64, message: '长度 1-64 个字符', trigger: 'blur' }
],
onlineRateLimit: percentRule('在线率阈值'),
integrityLimit: percentRule('完整性阈值'),
active: [{ required: true, message: '请选择是否启用', trigger: 'change' }]
}
const isEdit = () => title.value.includes('修改')
const resetForm = () => {
Object.assign(form, defaultForm())
}
const open = (e: { text: string; row?: any }) => {
formRef.value?.resetFields()
title.value = e.text
dialogVisible.value = true
resetForm()
if (e.row) {
form.id = e.row.id ?? ''
form.name = e.row.name ?? ''
form.onlineRateLimit = e.row.onlineRateLimit != null ? Number(e.row.onlineRateLimit) : undefined
form.integrityLimit = e.row.integrityLimit != null ? Number(e.row.integrityLimit) : undefined
form.active = String(e.row.active ?? '0')
}
}
const handleCancel = () => {
dialogVisible.value = false
emit('Cancels')
}
const handleClosed = () => {
resetForm()
}
const onSubmit = () => {
formRef.value?.validate(async (valid: boolean) => {
if (!valid) return
const payload = {
id: form.id,
name: form.name,
onlineRateLimit: form.onlineRateLimit,
integrityLimit: form.integrityLimit,
active: form.active
}
try {
if (isEdit()) {
await update(payload)
ElMessage.success('修改成功')
} else {
await add(payload)
ElMessage.success('新增成功')
}
dialogVisible.value = false
emit('Cancels')
} catch {
/* 请求层一般会统一提示 */
}
})
}
defineExpose({ open })
</script>

View File

@@ -0,0 +1,152 @@
<template>
<div class="default-main">
<TableHeader select :showReset="false" ref="TableHeaderRef">
<template #operation>
<el-button icon="el-icon-Plus" type="primary" @click="add">新增</el-button>
</template>
</TableHeader>
<Table ref="tableRef" />
<Form ref="formRef" @Cancels="tableStore.index()" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, nextTick } from 'vue'
import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue'
import { ElMessage } from 'element-plus'
import { toggleActive, csDelete } from '@/api/cs-system-boot/appinfo'
import Form from './form.vue'
defineOptions({
name: 'govern/alarmConfig'
})
const formTabRef = ref()
const formRef = ref()
const tableStore: any = new TableStore({
url: '/cs-system-boot/csAlarmSet/listAll',
method: 'POST',
showPage: false,
column: [
{
field: 'index',
title: '序号',
width: '80',
formatter: (row: any) => {
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{ field: 'name', title: '名称' },
{ field: 'onlineRateLimit', title: '在线率阈值' },
{ field: 'integrityLimit', title: '完整性阈值' },
{ field: 'updateTime', title: '创建时间' },
{
title: '是否启用',
render: 'switch',
width: 100,
field: 'active',
activeText: '启用',
inactiveText: '停用',
inactiveValue: '0',
activeValue: '1',
onChangeField: (row: any, value: any) => {
if (row.active == 1) {
return ElMessage({ message: '至少需要保留一条启用的配置!', type: 'warning' })
// 至少需要保留一条启用的配置
}
toggleActive({
id: row.id
}).then(res => {
ElMessage({ message: '启用成功!', type: 'success' })
tableStore.index()
})
}
},
{
title: '操作',
fixed: 'right',
width: '180',
render: 'buttons',
buttons: [
// {
// name: 'edit',
// title: '启用 ',
// type: 'primary',
// icon: 'el-icon-Plus',
// render: 'basicButton',
// disabled: row => {
// return row.active == 1
// },
// click: row => {
// toggleActive({ id: row.id }).then(res => {
// ElMessage({
// message: '启用成功!',
// type: 'success'
// })
// tableStore.index()
// })
// }
// },
{
name: 'edit',
title: '修改 ',
type: 'primary',
icon: 'el-icon-Plus',
render: 'basicButton',
click: row => {
setTimeout(() => {
formRef.value.open({
text: '修改配置',
row: row
})
}, 10)
}
},
{
name: 'edit',
title: '删除',
type: 'danger',
icon: 'el-icon-Delete',
render: 'confirmButton',
disabled: row => {
return row.active == 1
},
popconfirm: {
confirmButtonText: '确认',
cancelButtonText: '取消',
confirmButtonType: 'danger',
title: '确定删除吗?'
},
click: row => {
csDelete({ id: row.id }).then(res => {
ElMessage({
message: '删除成功!',
type: 'success'
})
tableStore.index()
})
}
}
]
}
],
loadCallback: () => {
}
})
provide('tableStore', tableStore)
// 新增主题
const add = () => {
setTimeout(() => {
formRef.value.open({
text: '新增配置'
})
}, 10)
}
onMounted(() => {
tableStore.index()
})
</script>

View File

@@ -7,15 +7,15 @@
<el-tab-pane label="前置告警" name="2"> <el-tab-pane label="前置告警" name="2">
<Front v-if="activeName == '2'" :deviceTree="deviceTree" :key="key" /> <Front v-if="activeName == '2'" :deviceTree="deviceTree" :key="key" />
</el-tab-pane> </el-tab-pane>
<!-- <el-tab-pane label="稳态越限告警" name="3"> <el-tab-pane label="稳态越限告警" name="3">
<Steady v-if="activeName == '3'" :deviceTree="deviceTree" :key="key" /> <Steady v-if="activeName == '3'" :deviceTree="deviceTree" :key="key" />
</el-tab-pane> --> </el-tab-pane>
<el-tab-pane label="暂态事件" name="4"> <el-tab-pane label="暂态事件" name="4">
<Transient v-if="activeName == '4'" :deviceTree="deviceTree" :key="key" /> <Transient v-if="activeName == '4'" :deviceTree="deviceTree" :key="key" />
</el-tab-pane> </el-tab-pane>
<!-- <el-tab-pane label="异常事件" name="5"> <el-tab-pane label="异常事件" name="5">
<Abnormal v-if="activeName == '5'" :deviceTree="deviceTree" :key="key" /> <Abnormal v-if="activeName == '5'" :deviceTree="deviceTree" :key="key" />
</el-tab-pane> --> </el-tab-pane>
</el-tabs> </el-tabs>
</div> </div>
</template> </template>
@@ -30,8 +30,9 @@ import { getDeviceTree } from '@/api/cs-device-boot/csLedger'
defineOptions({ defineOptions({
name: 'govern/alarm/index' name: 'govern/alarm/index'
}) })
const deviceTree = ref([]) const deviceTree = ref([])
const activeName = ref('0') const activeName = ref('1')
const key = ref(0) const key = ref(0)
getDeviceTree().then(res => { getDeviceTree().then(res => {
// res.data.forEach((item: any) => { // res.data.forEach((item: any) => {

View File

@@ -0,0 +1,270 @@
<template>
<el-dialog class="cn-operate-dialog" draggable v-model="dialogVisible" :title="title" width="900px"
@closed="handleClosed">
<el-scrollbar max-height="60vh">
<el-form ref="formRef" :model="form" :rules="rules" label-width="auto" class="form-two">
<el-form-item label="指标名称" prop="indexName">
<el-input v-model.trim="form.indexName" placeholder="请输入指标名称" clearable maxlength="128" />
</el-form-item>
<el-form-item label="指标code" prop="indexCode">
<el-input v-model.trim="form.indexCode" placeholder="请输入指标code" clearable maxlength="64" />
</el-form-item>
<el-form-item label="表名" prop="influxdbTableName">
<el-input v-model.trim="form.influxdbTableName" placeholder="请输入表名" clearable />
</el-form-item>
<el-form-item label="列属性" prop="influxdbColumnName">
<el-input v-model.trim="form.influxdbColumnName" placeholder="实体类属性名" clearable maxlength="128" />
</el-form-item>
<el-form-item label="谐波次数">
<el-slider v-model.trim="form.harmSlider" range :max="50" style="width: 90%" />
</el-form-item>
<el-form-item label="相别" prop="phaseList">
<el-select v-model.trim="form.phaseList" filterable multiple clearable collapse-tags
collapse-tags-tooltip placeholder="请选择相别">
<el-option v-for="item in phaseSelect" :key="item.id" :label="item.name"
:value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="指标下限" prop="minValue">
<el-input-number v-model="form.minValue" style="width: 100%" placeholder="下限" />
</el-form-item>
<el-form-item label="指标上限" prop="maxValue">
<el-input-number v-model="form.maxValue" style="width: 100%" placeholder="上限" />
</el-form-item>
<el-form-item label="电压等级参与" prop="isVoltage">
<el-radio-group v-model="form.isVoltage">
<el-radio :value="0">不参与</el-radio>
<el-radio :value="1">参与</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="Ct变比参与" prop="ctAttendFlag">
<el-radio-group v-model="form.ctAttendFlag">
<el-radio :value="0">不参与</el-radio>
<el-radio :value="1">参与</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="数据来源" prop="dataSource">
<el-select v-model="form.dataSource" placeholder="请选择" clearable style="width: 100%">
<el-option label="InfluxDB" value="InfluxDB" />
<!-- <el-option label="oracle" value="oracle" /> -->
<el-option label="MySql" value="MySql" />
</el-select>
</el-form-item>
<el-form-item label="所属系统" prop="belongingSystem">
<el-select v-model="form.belongingSystem" placeholder="请选择" clearable style="width: 100%">
<el-option label="pqs" value="pqs" />
<el-option label="govern" value="govern" />
</el-select>
</el-form-item>
<el-form-item label="单位" prop="unit">
<el-input v-model.trim="form.unit" placeholder="请输入单位" clearable maxlength="32" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="form.sort" :min="0" :precision="0" style="width: 100%" />
</el-form-item>
<!-- <el-form-item label="状态" prop="state">
<el-select v-model="form.state" placeholder="请选择" style="width: 100%">
<el-option label="正常" :value="1" />
<el-option label="删除" :value="0" />
</el-select>
</el-form-item> -->
<el-form-item label="条件描述" prop="otherAlgorithm" class="form-item-full">
<el-input v-model.trim="form.otherAlgorithm" type="textarea" :rows="2" placeholder="无具体范围时的判断条件描述"
maxlength="500" show-word-limit />
</el-form-item>
<el-form-item label="备注" prop="remark" class="form-item-full">
<el-input v-model.trim="form.remark" type="textarea" :rows="2" placeholder="备注" maxlength="500"
show-word-limit />
</el-form-item>
</el-form>
</el-scrollbar>
<template #footer>
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="onSubmit">确定</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import { save, update } from '@/api/algorithm-boot/scopeConfig'
const emit = defineEmits(['Cancels'])
const dialogVisible = ref(false)
const title = ref('')
const formRef = ref()
const phaseSelect = [
{
name: 'A相',
id: 'A'
},
{
name: 'B相',
id: 'B'
},
{
name: 'C相',
id: 'C'
},
{
name: '无相别',
id: 'T'
},
{
name: 'AB相',
id: 'AB'
},
{
name: 'BC相',
id: 'BC'
},
{
name: 'CA相',
id: 'CA'
},
// {
// name: '无相别',
// id: 'M'
// },
]
const defaultForm = () => ({
id: '',
indexCode: '',
indexName: '',
harmSlider: [0, 0],
harmStart: undefined as number | undefined,
harmEnd: undefined as number | undefined,
phaseType: '',
phaseList: [] as any,
influxdbTableName: '',
influxdbColumnName: '',
minValue: undefined as number | undefined,
maxValue: undefined as number | undefined,
isVoltage: 0,
ctAttendFlag: 0,
dataSource: '',
otherAlgorithm: '',
remark: '',
unit: '',
sort: 100,
belongingSystem: 'govern',
state: 1
})
const form = reactive(defaultForm())
const validateMinMax = (_rule: any, _value: any, callback: (e?: Error) => void) => {
if (form.minValue == null || form.maxValue == null) {
callback()
return
}
if (form.minValue > form.maxValue) {
callback(new Error('指标下限不能大于指标上限'))
} else {
callback()
}
}
const rules: any = {
indexCode: [{ required: true, message: '请输入指标code', trigger: 'blur' }],
indexName: [{ required: true, message: '请输入指标名称', trigger: 'blur' }],
phaseList: [{ required: true, message: '请输入指标名称', trigger: 'change' }],
influxdbTableName: [{ required: true, message: '请输入表名', trigger: 'blur' }],
influxdbColumnName: [{ required: true, message: '请输入列属性', trigger: 'blur' }],
minValue: [
{ required: true, message: '请输入指标下限', trigger: 'blur' },
{ validator: validateMinMax, trigger: 'change' }
],
maxValue: [
{ required: true, message: '请输入指标上限', trigger: 'blur' },
{ validator: validateMinMax, trigger: 'change' }
],
isVoltage: [{ required: true, message: '请选择电压等级是否参与', trigger: 'change' }],
ctAttendFlag: [{ required: true, message: '请选择Ct变比是否参与', trigger: 'change' }],
dataSource: [{ required: true, message: '请选择数据来源', trigger: 'change' }],
belongingSystem: [{ required: true, message: '请选择所属系统', trigger: 'change' }],
sort: [{ required: true, message: '请输入排序', trigger: 'blur' }],
state: [{ required: true, message: '请选择状态', trigger: 'change' }]
}
const isEdit = () => title.value.includes('修改')
const resetForm = () => {
Object.assign(form, defaultForm())
}
const fillForm = (row: any) => {
const keys = Object.keys(defaultForm()) as (keyof ReturnType<typeof defaultForm>)[]
keys.forEach(key => {
if (row[key] === undefined || row[key] === null) return
if (['harmStart', 'harmEnd', 'sort', 'isVoltage', 'ctAttendFlag', 'state'].includes(key)) {
; (form as any)[key] = Number(row[key])
} else if (['minValue', 'maxValue'].includes(key)) {
; (form as any)[key] = Number(row[key])
} else {
; (form as any)[key] = row[key]
}
})
form.phaseList = form.phaseType.split(',')
form.harmSlider = [form.harmStart || 0, form.harmEnd || 0]
}
const open = (e: { text: string; row?: any }) => {
formRef.value?.resetFields()
title.value = e.text
dialogVisible.value = true
resetForm()
if (e.row) {
fillForm(e.row)
}
}
const handleCancel = () => {
dialogVisible.value = false
emit('Cancels')
}
const handleClosed = () => {
resetForm()
}
const onSubmit = () => {
formRef.value?.validate(async (valid: boolean) => {
if (!valid) return
form.harmStart = form.harmSlider[0]
form.harmEnd = form.harmSlider[1]
form.phaseType = form.phaseList.join(',')
const payload = { ...form }
try {
if (isEdit()) {
await update(payload)
ElMessage.success('修改成功!')
} else {
await save({ ...payload, id: '' })
ElMessage.success('新增成功!')
}
dialogVisible.value = false
emit('Cancels')
} catch {
/* 统一错误提示 */
}
})
}
defineExpose({ open })
</script>
<style scoped>
.form-item-full {
grid-column: 1 / -1;
}
</style>

View File

@@ -0,0 +1,193 @@
<template>
<div class="default-main">
<TableHeader ref="TableHeaderRef">
<template #select>
<el-form-item label="数据来源">
<el-select v-model="tableStore.table.params.dataSource" clearable placeholder="请选择数据来源">
<el-option v-for="item in dataSourceOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="系统类型">
<el-select v-model="tableStore.table.params.systemType" clearable placeholder="请选择系统类型">
<el-option v-for="item in systemTypeOptions" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="表名">
<el-input v-model.trim="tableStore.table.params.tableName" placeholder="请输入表名" clearable
maxlength="64" />
</el-form-item>
</template>
<template #operation>
<el-button icon="el-icon-Plus" type="primary" @click="add">新增</el-button>
</template>
</TableHeader>
<Table ref="tableRef" />
<Form ref="formRef" @Cancels="tableStore.index()" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, nextTick } from 'vue'
import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue'
import { ElMessage } from 'element-plus'
import { pqDelete } from '@/api/algorithm-boot/scopeConfig'
import Form from './form.vue'
defineOptions({
name: 'govern/alarmConfig'
})
const dataSourceOptions = [
{ label: 'InfluxDB', value: 'InfluxDB' },
// { label: 'oracle', value: 'oracle' },
{ label: 'MySql', value: 'MySql' }
]
const systemTypeOptions = [
{ label: 'pqs', value: 'pqs' },
{ label: 'govern', value: 'govern' }
]
const yesNo = (v: number) => (v === 1 ? '是' : v === 0 ? '否' : '/')
const stateMap: Record<number, string> = { 0: '删除', 1: '正常' }
const formRef = ref()
const tableStore: any = new TableStore({
url: '/algorithm-boot/pqReasonableRange/getData',
method: 'POST',
showPage: false,
column: [
{
field: 'index',
title: '序号',
width: '80',
formatter: (row: any) => {
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{ field: 'indexName', title: '指标名称', minWidth: 200 },
{ field: 'indexCode', title: '指标code', minWidth: 150 },
{ field: 'influxdbTableName', title: '表名', minWidth: 150 },
{ field: 'influxdbColumnName', title: '列属性', minWidth: 150 },
{
field: 'harmStart', title: '谐波次数', width: 90,
formatter: (row: any) => {
return row.cellValue == null ? '/' : row.cellValue + '-' + row.row.harmEnd
}
},
{
field: 'phaseType', title: '相别', width: 100, formatter: (row: any) => {
return row.cellValue == 'T' ? '/' : row.cellValue
}
},
{ field: 'minValue', title: '指标下限', width: 100 },
{ field: 'maxValue', title: '指标上限', width: 100 },
{
field: 'isVoltage',
title: '电压等级参与',
width: 110,
formatter: (row: any) => yesNo(row.cellValue)
},
{
field: 'ctAttendFlag',
title: 'Ct变比参与',
width: 100,
formatter: (row: any) => yesNo(row.cellValue)
},
{ field: 'dataSource', title: '数据来源', width: 100 },
{ field: 'belongingSystem', title: '所属系统', width: 100 },
{
field: 'unit', title: '单位', width: 80,
formatter: (row: any) => {
return row.cellValue || '/'
}
},
{ field: 'sort', title: '排序', width: 70 },
{
field: 'otherAlgorithm', title: '条件描述', minWidth: 200,
formatter: (row: any) => {
return row.cellValue || '/'
}
},
{
field: 'remark', title: '备注', minWidth: 200,
formatter: (row: any) => {
return row.cellValue || '/'
}
},
{
title: '操作',
fixed: 'right',
width: '180',
render: 'buttons',
buttons: [
{
name: 'edit',
title: '修改 ',
type: 'primary',
icon: 'el-icon-Plus',
render: 'basicButton',
click: row => {
setTimeout(() => {
formRef.value.open({
text: '修改配置',
row: row
})
}, 10)
}
},
{
name: 'edit',
title: '删除',
type: 'danger',
icon: 'el-icon-Delete',
render: 'confirmButton',
disabled: row => {
return row.active == 1
},
popconfirm: {
confirmButtonText: '确认',
cancelButtonText: '取消',
confirmButtonType: 'danger',
title: '确定删除吗?'
},
click: row => {
pqDelete({ id: row.id }).then(res => {
ElMessage({
message: '删除成功!',
type: 'success'
})
tableStore.index()
})
}
}
]
}
],
loadCallback: () => {
}
})
tableStore.table.params.dataSource = ''
tableStore.table.params.tableName = ''
tableStore.table.params.systemType = ''
provide('tableStore', tableStore)
// 新增主题
const add = () => {
setTimeout(() => {
formRef.value.open({
text: '新增配置'
})
}, 10)
}
onMounted(() => {
tableStore.index()
})
</script>

View File

@@ -0,0 +1,440 @@
<template>
<div class="default-main" style="display: flex" :style="height">
<div style="width: 400px; overflow: hidden">
<div class="custom-table-header">
<div class="title">方案列表</div>
<el-button :icon="Plus" type="primary" @click="addRole" class="ml10">新增</el-button>
</div>
<Table ref="tableRef" :row-config="{ isCurrent: true, isHover: true }" @currentChange="currentChange" />
</div>
<div style="flex: 1; overflow: hidden">
<div class="custom-table-header">
<div class="title">指标配置</div>
<!-- <el-button :icon="Select" type="primary" @click="saveIndicator" class="ml10">保存</el-button> -->
</div>
<div class="pd10 borderBox" :style="height1" style="overflow-y: auto">
<H4 class="mt10">基础指标</H4>
<el-checkbox-group v-model="indicator">
<el-checkbox label="频率偏差" value="频率偏差" />
<el-checkbox label="电压偏差" value="电压偏差" />
<el-checkbox label="三相电压不平衡度" value="三相电压不平衡度" />
<el-checkbox label="闪变" value="闪变" />
<el-checkbox label="电压总谐波畸变率" value="电压总谐波畸变率" />
<el-checkbox label="负序电流" value="负序电流" />
</el-checkbox-group>
<H4 class="mt10">谐波电压</H4>
<div class="df">
<div class="title">谐波电压含有率:</div>
<div style="flex: 1">
<el-checkbox v-model="checkAll1" @change="handleCheckAllChange1">全选</el-checkbox>
<el-checkbox-group v-model="indicator" @change="handleHarmonicVoltageChange">
<el-checkbox
v-for="num in 24"
:key="`harmonicVoltage_${num + 1}`"
:label="`${num + 1}次`"
:value="`${num + 1}次谐波电压含有率`"
/>
</el-checkbox-group>
</div>
</div>
<H4 class="mt10">谐波电流</H4>
<div class="df">
<div class="title">谐波电流有效值:</div>
<div style="flex: 1">
<el-checkbox v-model="checkAll2" @change="handleCheckAllChange2">全选</el-checkbox>
<el-checkbox-group v-model="indicator" @change="handleHarmonicCurrentChange">
<el-checkbox
v-for="num in 24"
:key="`harmonicCurrent_${num + 1}`"
:label="`${num + 1}次`"
:value="`${num + 1}次谐波电流有效值`"
/>
</el-checkbox-group>
</div>
</div>
<H4 class="mt10">间谐波电压</H4>
<div class="df">
<div class="title">间谐波电压含有率:</div>
<div style="flex: 1">
<el-checkbox v-model="checkAll3" @change="handleCheckAllChange3">全选</el-checkbox>
<el-checkbox-group v-model="indicator" @change="handleInterharmonicVoltageChange">
<el-checkbox
v-for="num in 16"
:key="`interharmonicVoltage_${num}`"
:label="`${num - 0.5}次`"
:value="`${num - 0.5}次间谐波电压含有率`"
/>
</el-checkbox-group>
</div>
</div>
</div>
</div>
<div style="width: 370px">
<div class="custom-table-header">
<div class="title">监测点绑定</div>
<el-button :icon="Select" type="primary" @click="saveIndicator" class="ml10" :loading="loading">
保存
</el-button>
</div>
<!-- <steadyStateTree /> -->
<Tree
class="borderBox"
ref="treeRef"
show-checkbox
:showBut="false"
width="370px"
:height="260"
:data="menuTree"
:checkStrictly="checkStrictly"
></Tree>
</div>
<!-- 新增/编辑弹框 -->
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="400px" :before-close="handleClose">
<el-form :model="formData" :rules="formRules" ref="formRef" label-width="100px">
<el-form-item label="方案名称" prop="name">
<el-input v-model.trim="formData.name" placeholder="请输入方案名称" clearable />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input maxlength="32" show-word-limit-number v-model.number="formData.sort" :min="0" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { Plus, Select } from '@element-plus/icons-vue'
import { ref, onMounted, provide, computed } from 'vue'
import TableStore from '@/utils/tableStore'
import { getLineTree, getCldTree } from '@/api/cs-device-boot/csLedger'
import Table from '@/components/table/index.vue'
import { save, deletePlan, update, getById } from '@/api/cs-harmonic-boot/mxgraph'
import { mainHeight } from '@/utils/layout'
import { ElMessage } from 'element-plus'
import steadyStateTree from '@/components/tree/govern/steadyStateTree.vue'
import { useConfig } from '@/stores/config'
import Tree from '@/components/tree/allocation.vue'
import { id } from 'element-plus/es/locale'
defineOptions({
name: 'govern/steadyStateEvent'
})
const height = mainHeight(20)
const height1 = mainHeight(90)
const config = useConfig()
const tableRef = ref()
const checkAll1 = ref(false)
const checkAll2 = ref(false)
const checkAll3 = ref(false)
const menuTree: any = ref([])
const checkStrictly = ref(false)
const indicator: any = ref([])
const lineList: any = ref([])
const copyRow: any = ref({})
const treeRef = ref()
const loading = ref(false)
// 弹框相关
const dialogVisible = ref(false)
const dialogTitle = ref('新增方案')
const formRef = ref()
const formData = ref({
id: '',
sort: 0,
lineList: [],
harmonicTarget: '',
name: ''
})
const formRules = {
name: [{ required: true, message: '请输入方案名称', trigger: 'blur' }],
sort: [{ required: true, message: '请输入排序', trigger: 'blur' }]
}
// 生成各分组的指标值数组
const harmonicVoltageValues = computed(() => Array.from({ length: 24 }, (_, i) => `${i + 2}次谐波电压含有率`))
const harmonicCurrentValues = computed(() => Array.from({ length: 24 }, (_, i) => `${i + 2}次谐波电流有效值`))
const interharmonicVoltageValues = computed(() => Array.from({ length: 16 }, (_, i) => `${i + 0.5}次间谐波电压含有率`))
// 谐波电压全选/取消全选
const handleCheckAllChange1 = (val: any) => {
if (val) {
const newValues = harmonicVoltageValues.value.filter(v => !indicator.value.includes(v))
indicator.value = [...indicator.value, ...newValues]
} else {
indicator.value = indicator.value.filter(v => !harmonicVoltageValues.value.includes(v))
}
}
// 谐波电流全选/取消全选
const handleCheckAllChange2 = (val: any) => {
if (val) {
const newValues = harmonicCurrentValues.value.filter(v => !indicator.value.includes(v))
indicator.value = [...indicator.value, ...newValues]
} else {
indicator.value = indicator.value.filter(v => !harmonicCurrentValues.value.includes(v))
}
}
// 间谐波电压全选/取消全选
const handleCheckAllChange3 = (val: any) => {
if (val) {
const newValues = interharmonicVoltageValues.value.filter(v => !indicator.value.includes(v))
indicator.value = [...indicator.value, ...newValues]
} else {
indicator.value = indicator.value.filter(v => !interharmonicVoltageValues.value.includes(v))
}
}
// 谐波电压分组内值变化时,更新全选状态
const handleHarmonicVoltageChange = () => {
const selectedInGroup = harmonicVoltageValues.value.filter(v => indicator.value.includes(v))
checkAll1.value =
selectedInGroup.length == harmonicVoltageValues.value.length && harmonicVoltageValues.value.length > 0
}
// 谐波电流分组内值变化时,更新全选状态
const handleHarmonicCurrentChange = () => {
const selectedInGroup = harmonicCurrentValues.value.filter(v => indicator.value.includes(v))
checkAll2.value =
selectedInGroup.length == harmonicCurrentValues.value.length && harmonicCurrentValues.value.length > 0
}
// 间谐波电压分组内值变化时,更新全选状态
const handleInterharmonicVoltageChange = () => {
const selectedInGroup = interharmonicVoltageValues.value.filter(v => indicator.value.includes(v))
checkAll3.value =
selectedInGroup.length == interharmonicVoltageValues.value.length && interharmonicVoltageValues.value.length > 0
}
const tableStore = new TableStore({
showPage: false,
url: '/cs-harmonic-boot/csHarmonicPlan/list',
method: 'GET',
publicHeight: 70,
column: [
{ title: '方案名称', field: 'name', align: 'center' },
{
title: '操作',
fixed: 'right',
align: 'center',
width: '100',
render: 'buttons',
buttons: [
{
name: 'edit',
title: '编辑',
type: 'primary',
icon: 'el-icon-EditPen',
render: 'basicButton',
click: row => {
openDialog('edit', row)
}
},
{
name: 'del',
title: '删除',
type: 'danger',
icon: 'el-icon-Delete',
render: 'confirmButton',
popconfirm: {
confirmButtonText: '确认',
cancelButtonText: '取消',
confirmButtonType: 'danger',
title: '确定删除该角色吗?'
},
click: row => {
deletePlan([row.id]).then(() => {
ElMessage.success('删除成功')
tableStore.index()
})
}
}
]
}
],
beforeSearchFun: () => {},
loadCallback: () => {
tableRef.value.getRef().setCurrentRow(tableStore.table.data[0])
currentChange({
row: tableStore.table.data[0]
})
}
})
tableStore.table.params.searchValue = ''
getCldTree().then(res => {
res.data?.children.map((item: any) => {
item.icon = 'el-icon-HomeFilled'
item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item: any) => {
item.icon = 'el-icon-List'
item.color = config.getColorVal('elementUiPrimary')
item.children.forEach((item2: any) => {
item2.icon = 'el-icon-Platform'
item2.color = item2.comFlag == 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
item2.children.forEach((item3: any) => {
item3.icon = 'el-icon-Platform'
item3.color = item3.comFlag == 2 ? config.getColorVal('elementUiPrimary') : '#e26257 !important'
})
})
})
})
menuTree.value = filterTreeKeepPath(res.data.children)
})
provide('tableStore', tableStore)
const filterTreeKeepPath = (treeData: any) => {
const result = []
for (const node of treeData) {
// 递归处理子节点
const filteredChildren: any = node.children?.length ? filterTreeKeepPath(node.children) : []
// 如果当前节点 level == 3或者子节点中有符合条件的数据
if (node.level == 3 || filteredChildren.length > 0) {
result.push({
...node,
children: filteredChildren
})
}
}
return result
}
// 打开弹框
const openDialog = (type: string, row?: any) => {
if (type == 'add') {
dialogTitle.value = '新增方案'
formData.value = {
id: '',
name: '',
lineList: [],
harmonicTarget: '',
sort: 0
}
} else if (type == 'edit') {
dialogTitle.value = '编辑方案'
formData.value = {
id: row.id,
lineList: row.lineList,
harmonicTarget: row.harmonicTarget,
sort: row.sort,
name: row.name
}
}
dialogVisible.value = true
}
// 关闭弹框
const handleClose = () => {
dialogVisible.value = false
formRef.value?.resetFields()
}
// 提交表单
const submitForm = async () => {
await formRef.value?.validate()
if (dialogTitle.value == '新增方案') {
await save({
harmonicTarget: '',
id: '',
lineList: [],
name: formData.value.name,
sort: formData.value.sort
}).then((res: any) => {
ElMessage.success(`${dialogTitle.value}成功`)
dialogVisible.value = false
tableStore.index() // 刷新列表
})
} else {
await update({
harmonicTarget: formData.value.harmonicTarget,
id: formData.value.id,
lineList: formData.value.lineList,
name: formData.value.name,
sort: formData.value.sort
}).then((res: any) => {
ElMessage.success(`${dialogTitle.value}成功`)
dialogVisible.value = false
tableStore.index() // 刷新列表
})
}
// 模拟保存成功
}
// 保存指标
const saveIndicator = () => {
// TODO: 调用保存指标接口
loading.value = true
update({
harmonicTarget: indicator.value.join(','),
id: copyRow.value.id,
lineList: treeRef.value.treeRef
.getCheckedNodes(false, true)
.filter(item => item.level == 3)
.map((node: any) => node.id),
name: copyRow.value.name,
sort: copyRow.value.sort
})
.then((res: any) => {
ElMessage.success(`保存成功`)
loading.value = false
tableStore.index()
})
.catch(() => {
loading.value = false
})
}
const currentChange = (data: any) => {
getById({ id: data.row.id }).then((res: any) => {
checkStrictly.value = true
indicator.value = res.data.harmonicTarget.split(',')
lineList.value = res.data.lineList || []
copyRow.value = res.data
treeRef.value.treeRef.setCheckedKeys(lineList.value)
setTimeout(() => {
handleInterharmonicVoltageChange()
handleHarmonicCurrentChange()
handleHarmonicVoltageChange()
checkStrictly.value = false
}, 100)
})
}
onMounted(() => {
tableStore.index()
})
const addRole = () => {
openDialog('add')
}
</script>
<style lang="scss" scoped>
:deep(.el-tabs--border-card > .el-tabs__content) {
padding: 0px !important;
}
.df {
display: flex;
.title {
width: 125px;
}
:deep(.el-checkbox) {
width: 70px;
}
}
.custom-table-header {
height: 60px;
}
.borderBox {
border: 1px solid var(--el-border-color);
}
</style>

View File

@@ -245,10 +245,10 @@ const setEchart = () => {
for (let j in phaseList) { for (let j in phaseList) {
color.push(j == 'A' ? '#DAA520' : j == 'B' ? '#2E8B57' : j == 'C' ? '#A52a2a' : '#0000CC') color.push(j == 'A' ? '#DAA520' : j == 'B' ? '#2E8B57' : j == 'C' ? '#A52a2a' : '#0000CC')
legend.push( legend.push(
j == 'M' ? k : j == 'A' ? `A相_${k}` : j == 'B' ? `B相_${k}` : j == 'C' ? `C相_${k}` : j j == 'T' ? k : j == 'A' ? `A相_${k}` : j == 'B' ? `B相_${k}` : j == 'C' ? `C相_${k}` : j
) )
series.push({ series.push({
name: j == 'M' ? k : j == 'A' ? `A相_${k}` : j == 'B' ? `B相_${k}` : j == 'C' ? `C相_${k}` : j, name: j == 'T' ? k : j == 'A' ? `A相_${k}` : j == 'B' ? `B相_${k}` : j == 'C' ? `C相_${k}` : j,
symbol: 'none', symbol: 'none',
smooth: true, smooth: true,
type: 'line', type: 'line',

View File

@@ -6,31 +6,15 @@
<TableHeader datePicker showExport> <TableHeader datePicker showExport>
<template v-slot:select> <template v-slot:select>
<el-form-item label="事件类型"> <el-form-item label="事件类型">
<el-select <el-select v-model.trim="tableStore.table.params.eventType" clearable placeholder="请选择事件类型">
v-model.trim="tableStore.table.params.eventType" <el-option v-for="item in eventList" :key="item.value" :label="item.label"
clearable :value="item.value" />
placeholder="请选择事件类型"
>
<el-option
v-for="item in eventList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="位置"> <el-form-item label="位置">
<el-select <el-select v-model.trim="tableStore.table.params.location" clearable placeholder="请选择位置">
v-model.trim="tableStore.table.params.location" <el-option v-for="item in locationList" :key="item.value" :label="item.label"
clearable :value="item.value" />
placeholder="请选择位置"
>
<el-option
v-for="item in locationList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
</template> </template>
@@ -39,14 +23,8 @@
</div> </div>
<el-empty v-else description="请选择设备" class="analyze-dvr-right" /> <el-empty v-else description="请选择设备" class="analyze-dvr-right" />
</div> </div>
<waveFormAnalysis <waveFormAnalysis v-loading="loading" v-if="isWaveCharts" ref="waveFormAnalysisRef"
v-loading="loading" @handleHideCharts="isWaveCharts = false" :wp="wp" style="padding: 10px" />
v-if="isWaveCharts"
ref="waveFormAnalysisRef"
@handleHideCharts="isWaveCharts = false"
:wp="wp"
style="padding: 10px"
/>
<!-- <div :style="{ height: pageHeight.height }" style="padding: 10px; overflow: hidden" v-if="!view"> <!-- <div :style="{ height: pageHeight.height }" style="padding: 10px; overflow: hidden" v-if="!view">
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
@@ -145,14 +123,26 @@ const tableStore = new TableStore({
} }
}, },
{ title: '事件描述', field: 'showName', minWidth: 150 }, { title: '事件描述', field: 'showName', minWidth: 150 },
{ title: '发生位置', field: 'evtParamPosition', minWidth: 150 }, {
title: '发生位置', field: 'evtParamPosition', minWidth: 150,
formatter: (row: any) => {
const val = row.cellValue
if (val === null || val === undefined || val === '' || val === '-') return '/'
return val
}
},
{ {
title: '持续时间(s)', title: '持续时间(s)',
field: 'evtParamTm', field: 'evtParamTm',
sortable: true, sortable: true,
minWidth: 110, minWidth: 110,
formatter: (row: any) => { formatter: (row: any) => {
return Math.floor(row.cellValue * 10000) / 100 const val = row.cellValue
if (val === null || val === undefined || val === '' || val === '-') return '/'
const num = Number(val)
if (Number.isNaN(num)) return '/'
return Math.floor(num * 10000) / 100
} }
}, },
{ {
@@ -179,16 +169,19 @@ const tableStore = new TableStore({
type: 'primary', type: 'primary',
icon: 'el-icon-DataLine', icon: 'el-icon-DataLine',
render: 'basicButton', render: 'basicButton',
loading: 'loading1',
disabled: row => { disabled: row => {
return !row.wavePath && row.evtParamTm < 20 return !row.wavePath && row.evtParamTm < 20
}, },
click: async row => { click: async row => {
row.loading1 = true row.loading1 = true
loading.value = true
isWaveCharts.value = true
await analyseWave(row.id) await analyseWave(row.id)
.then(res => { .then(res => {
isWaveCharts.value = true
loading.value = true
row.loading1 = false row.loading1 = false
if (res != undefined) { if (res != undefined) {
boxoList.value = row boxoList.value = row

View File

@@ -0,0 +1,392 @@
<template>
<div class="analyze-steadyState" v-loading="loading">
<analyzeTree ref="treeRef" :showCheckbox="true" :default-checked-keys="checkedKeyIds" @check="onCheck"
:height="40" :engineering="true" />
<div class="analyze-steadyState-right">
<div ref="headerRef">
<TableHeader :showSearch="false" ref="tableHeaderRef" @selectChange="selectChange">
<template v-slot:select>
<el-form-item label="时间:">
<DatePicker ref="datePickerRef"></DatePicker>
</el-form-item>
<el-form-item label="统计指标:">
<el-select style="width: 200px" v-model.trim="formInline.statisticalId" filterable
@change="frequencyFlag" placeholder="请选择">
<el-option v-for="item in zblist" :key="item.value" :label="item.label"
:value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="谐波次数:" v-show="frequencyShow">
<el-select v-model.trim="formInline.frequency" filterable placeholder="请选择"
style="width: 100px">
<el-option v-for="item in 49" :key="item + 1" :label="item + 1"
:value="item + 1"></el-option>
</el-select>
</el-form-item>
<el-form-item label="值类型:">
<el-select v-model.trim="formInline.valueType" filterable placeholder="请选择">
<el-option v-for="item in typelist" :key="item.value" :label="item.label"
:value="item.value"></el-option>
</el-select>
</el-form-item>
</template>
<template v-slot:operation>
<el-button type="primary" @click="search" icon="el-icon-Search">查询</el-button>
<el-button :type="timeControl ? 'primary' : ''" icon="el-icon-Sort" @click="setTimeControl">
缺失数据
</el-button>
</template>
</TableHeader>
</div>
<el-empty description="暂无数据" v-if="!echartsData" style="flex: 1"></el-empty>
<template v-else>
<div :style="echartHeight">
<MyEchart :options="echartsData" />
</div>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import { mainHeight } from '@/utils/layout'
import analyzeTree from '@/components/tree/govern/analyzeTree.vue'
import { queryByCode, queryCsDictTree } from '@/api/system-boot/dictTree'
import { queryCommonStatisticalByTime } from '@/api/cs-harmonic-boot/stable'
import DatePicker from '@/components/form/datePicker/index.vue'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { yMethod, completeTimeSeries } from '@/utils/echartMethod'
import TableHeader from '@/components/table/header/index.vue'
const MAX_CHECK = 2
const defaultCheckedKeys = ref<any[]>([])
const checkedKeyIds = computed(() => defaultCheckedKeys.value.map(item => item.id))
const treeRef = ref()
const isSyncing = ref(false)
const tableHeaderRef = ref()
const headerRef = ref()
const echartHeight = ref(mainHeight(80))
const loading = ref(false)
const echartsData = ref<any>(null)
const datePickerRef = ref()
const timeControl = ref(false)
const timeFlag = ref(true)
const frequencyShow = ref(false)
const dataLists = ref<any[]>([])
const zblist = ref<any[]>([])
const formInline = reactive({
statisticalId: '',
valueType: '',
startTime: '',
endTime: '',
frequency: '' as string | number
})
const typelist = [
{ label: '平均值', value: 'avg' },
{ label: '最大值', value: 'max' },
{ label: '最小值', value: 'min' },
{ label: 'CP95值', value: 'cp95' }
]
const lineStyle = [
{ type: 'solid', width: 3 },
{ type: 'dotted', width: 3 },
{ type: 'dashed', width: 3 }
]
const getEngineeringTree = () => treeRef.value?.treRef?.treeRef4
const init = () => {
return queryByCode('portable-harmonic-jx').then(res => {
return queryCsDictTree(res.data?.id).then(res => {
zblist.value = res.data.map((item: any) => ({
value: item.id,
label: item.name,
...item
}))
formInline.statisticalId = zblist.value[0]?.value
formInline.valueType = typelist[0].value
})
})
}
onMounted(() => {
init()
})
const onCheck = (_data: any, { checkedNodes }: { checkedNodes: any[] }) => {
if (isSyncing.value) return
const tree = getEngineeringTree()
if (!tree) return
if (checkedNodes.length <= MAX_CHECK) {
defaultCheckedKeys.value = checkedNodes.map(node => ({ ...node }))
if (checkedNodes.length === 0) {
echartsData.value = null
dataLists.value = []
}
return
}
const allowedIds = new Set(defaultCheckedKeys.value.map(item => item.id))
const extras = checkedNodes.filter(node => !allowedIds.has(node.id))
if (defaultCheckedKeys.value.length >= MAX_CHECK) {
isSyncing.value = true
extras.forEach(node => tree.setChecked(node.id, false, false))
nextTick(() => {
isSyncing.value = false
})
ElMessage.warning(`最多只能选择${MAX_CHECK}`)
return
}
const room = MAX_CHECK - defaultCheckedKeys.value.length
extras.slice(0, room).forEach(node => {
if (!defaultCheckedKeys.value.some(item => item.id === node.id)) {
defaultCheckedKeys.value.push({ ...node })
}
})
const rejected = extras.slice(room)
if (rejected.length) {
isSyncing.value = true
rejected.forEach(node => tree.setChecked(node.id, false, false))
nextTick(() => {
isSyncing.value = false
})
ElMessage.warning(`最多只能选择${MAX_CHECK}`)
}
}
const search = async () => {
if (defaultCheckedKeys.value.length === 0) {
ElMessage.warning('请选择设备')
return
}
if (zblist.value.length === 0) {
await init()
}
if (timeFlag.value) {
datePickerRef.value?.setInterval(5)
timeFlag.value = false
}
loading.value = true
formInline.startTime = datePickerRef.value.timeValue[0]
formInline.endTime = datePickerRef.value.timeValue[1]
if (!frequencyShow.value) {
formInline.frequency = ''
}
try {
const results = await Promise.all(
defaultCheckedKeys.value.map(device =>
queryCommonStatisticalByTime({
...formInline,
devId: device.id
}).then(({ data }: { data: any[] }) => ({
deviceName: device.name,
data
}))
)
)
dataLists.value = results.flatMap(({ deviceName, data }) =>
data.map((item: any) => ({
...item,
anotherName:
defaultCheckedKeys.value.length > 1 ? `${deviceName}_${item.anotherName}` : item.anotherName
}))
)
setEchart()
} catch {
loading.value = false
}
}
const setEchart = () => {
loading.value = true
const data = JSON.parse(JSON.stringify(dataLists.value))
if (data.length) {
const list = processingOfData(data, 'unit')
echartsData.value = {}
const legend: any[] = []
const xAxis: any[] = []
const yAxis: any[] = []
const series: any[] = []
const color: any[] = []
data.forEach((item: any) => {
if (!xAxis.includes(item.time)) {
xAxis.push(item.time)
}
})
const units = Object.keys(list)
for (const unit in list) {
const [min, max] = yMethod(list[unit].map((item: any) => item.statisticalData))
yAxis.push({
name: unit == 'null' ? '' : unit,
type: 'value',
min,
max,
axisLine: {
show: true,
lineStyle: { color: '#333' }
}
})
const anotherList = processingOfData(list[unit], 'anotherName')
for (const k in anotherList) {
const lineName = lineStyle[Object.keys(anotherList).indexOf(k)]
const phaseList = processingOfData(anotherList[k], 'phase')
for (const j in phaseList) {
color.push(j == 'A' ? '#DAA520' : j == 'B' ? '#2E8B57' : j == 'C' ? '#A52a2a' : '#0000CC')
legend.push(
j == 'T' ? k : j == 'A' ? `A相_${k}` : j == 'B' ? `B相_${k}` : j == 'C' ? `C相_${k}` : j
)
series.push({
name: j == 'T' ? k : j == 'A' ? `A相_${k}` : j == 'B' ? `B相_${k}` : j == 'C' ? `C相_${k}` : j,
symbol: 'none',
smooth: true,
type: 'line',
data: timeControl.value
? completeTimeSeries(
phaseList[j].map((item: any) => [
item.time,
Math.floor(item.statisticalData * 100) / 100,
unit,
lineName.type
])
)
: phaseList[j].map((item: any) => [
item.time,
Math.floor(item.statisticalData * 100) / 100,
unit,
lineName.type
]),
lineStyle: lineName,
yAxisIndex: units.indexOf(unit)
})
}
}
}
echartsData.value = {
title: {
text: zblist.value.filter(item => item.id == formInline.statisticalId)[0]?.name
},
tooltip: {
axisPointer: {
type: 'cross',
label: { color: '#fff', fontSize: 16 }
},
textStyle: { color: '#fff', fontStyle: 'normal', opacity: 0.35, fontSize: 14 },
backgroundColor: 'rgba(0,0,0,0.55)',
borderWidth: 0,
formatter(params: any) {
const xname = params[0].value[0]
let str = `${xname}<br>`
params.forEach((el: any) => {
let marker = ''
if (el.value[3] == 'dashed') {
for (let i = 0; i < 3; i++) {
marker += `<span style="display:inline-block;border: 2px ${el.color} solid;margin-right:5px;width:10px;height:0px;background-color:#ffffff00;"></span>`
}
} else {
marker = `<span style="display:inline-block;border: 2px ${el.color} ${el.value[3]};margin-right:5px;width:40px;height:0px;background-color:#ffffff00;"></span>`
}
str += `${marker}${el.seriesName.split('(')[0]}${el.value[1] != null ? el.value[1] + ' ' + (el.value[2] == 'null' ? '' : el.value[2]) : '-'
}<br>`
})
return str
}
},
legend: {
itemWidth: 20,
itemHeight: 20,
itemStyle: { opacity: 0 },
type: 'scroll',
top: 25
},
grid: {
left: '20px',
right: '40px',
bottom: '50px',
top: '80px',
containLabel: true
},
toolbox: { feature: { saveAsImage: {} } },
color,
xAxis: {
name: '',
type: 'time',
axisLabel: {
formatter: { day: '{MM}-{dd}', month: '{MM}', year: '{yyyy}' }
}
},
yAxis,
options: { series }
}
} else {
echartsData.value = null
}
loading.value = false
}
const setTimeControl = () => {
timeControl.value = !timeControl.value
setEchart()
}
const processingOfData = (data: any, type: string) => {
const groupedData: any = {}
data.forEach((item: any) => {
if (!groupedData[item[type]]) {
groupedData[item[type]] = []
}
groupedData[item[type]].push(item)
})
return groupedData
}
const frequencyFlag = () => {
const name = zblist.value.filter(item => item.id == formInline.statisticalId)[0]?.name
if (name?.includes('含有率') || name?.includes('幅值')) {
frequencyShow.value = true
formInline.frequency = 2
} else {
frequencyShow.value = false
}
tableHeaderRef.value?.computedSearchRow()
}
const selectChange = () => {
setTimeout(() => {
echartHeight.value = mainHeight(23 + headerRef.value.offsetHeight)
}, 100)
}
</script>
<style lang="scss">
.analyze-steadyState {
display: flex;
height: 100%;
min-height: 0;
&-right {
height: 100%;
overflow: hidden;
flex: 1;
padding: 10px 10px 10px 0;
display: flex;
flex-direction: column;
}
}
</style>
<style lang="scss" scoped>
.el-select {
min-width: 100px;
}
</style>

View File

@@ -0,0 +1,204 @@
<template>
<div class="default-main report-zl-page1" :style="height">
<div class="report-zl-sidebar">
<!-- <pointTreeWx :default-expand-all="false" template @node-click="handleNodeClick" @init="handleNodeClick"
@Policy="stencil">
</pointTreeWx> -->
<CloudDeviceEntryTree ref="TerminalRef" :height="37" template @Policy="stencil" @node-click="handleNodeClick"
@init="handleNodeClick"></CloudDeviceEntryTree>
</div>
<div class="report-zl-main">
<TableHeader datePicker ref="TableHeaderRef" :showReset="false">
<template v-slot:select>
<!-- <el-form-item label="时间:">
<DatePicker ref="datePickerRef"></DatePicker>
</el-form-item> -->
<el-form-item label="模板策略">
<el-select v-model.trim="Template" @change="changetype" placeholder="请选择模版" value-key="id">
<el-option v-for="item in templatePolicy" :key="item.id" :label="item.excelName"
:value="item"></el-option>
</el-select>
</el-form-item>
<!-- <el-form-item label="监测对象">
<el-select
filterable
v-model="tableStore.table.params.sensitiveUserId"
placeholder="请选择监测对象"
clearable
>
<el-option v-for="item in idList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item> -->
</template>
<template #operation>
<el-button icon="el-icon-Download" type="primary" @click="exportEvent">导出</el-button>
</template>
</TableHeader>
<div class="box" v-loading="tableStore.table.loading">
<div id="luckysheet" class="report-zl-sheet" v-if="tableStore.table.data.length > 0"></div>
<el-empty class="report-zl-sheet" v-else description="暂无数据" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, provide } from 'vue'
import TableStore from '@/utils/tableStore'
import pointTreeWx from '@/components/tree/govern/pointTreeWx.vue'
import TableHeader from '@/components/table/header/index.vue'
import { useDictData } from '@/stores/dictData'
import { mainHeight } from '@/utils/layout'
import { exportExcel } from '@/views/system/reportForms/export.js'
import DatePicker from '@/components/form/datePicker/time.vue'
import CloudDeviceEntryTree from '@/components/tree/govern/cloudDeviceEntryTreeZL.vue'
import { getListByIds } from '@/api/harmonic-boot/cockpit/cockpit'
import { ElMessage } from 'element-plus'
// import data from './123.json'
const height = mainHeight(70)
const dictData = useDictData()
const TableHeaderRef = ref()
const dotList: any = ref({})
const Template: any = ref({})
const reportForm: any = ref('')
const datePickerRef = ref()
const templatePolicy: any = ref([])
const name = ref('')
const tableStore = new TableStore({
url: '/cs-harmonic-boot/customReport/getSensitiveUserReport',
method: 'POST',
column: [],
showPage: false,
beforeSearchFun: () => {
tableStore.table.params.tempId = Template.value.id
tableStore.table.params.lineId = dotList.value.id
tableStore.table.params.sensitiveUserId = dotList.value.id
// ;(tableStore.table.params.startTime = datePickerRef.value.timeValue[0]),
// (tableStore.table.params.endTime = datePickerRef.value.timeValue[1]),
if (!tableStore.table.params.tempId) {
return ElMessage.warning('请选择模板')
}
delete tableStore.table.params.searchBeginTime
delete tableStore.table.params.searchEndTime
delete tableStore.table.params.timeFlag
},
loadCallback: () => {
console.log('🚀 ~ tableStore.table:', tableStore.table.data)
name.value = dotList.value.name
// tableStore.table.data.forEach((item: any) => {
// item.data1 ? (item.data = JSON.parse(item.data1)) : ''
// item.celldata.forEach((k: any) => {
// item.data[k.r][k.c].v ? (item.data[k.r][k.c] = k.v) : ''
// })
// })
setTimeout(() => {
luckysheet.create({
container: 'luckysheet',
title: '', // 表 头名
lang: 'zh', // 中文
showtoolbar: false, // 是否显示工具栏
showinfobar: false, // 是否显示顶部信息栏
showsheetbar: true, // 是否显示底部sheet按钮
allowEdit: false, // 禁止所有编辑操作(必填)
data: tableStore.table.data
// tableStore.table.data
})
}, 10)
}
})
provide('tableStore', tableStore)
tableStore.table.params.resourceType = 1
tableStore.table.params.customType = 1
const flag = ref(true)
onMounted(() => {
initListByIds()
})
const idList = ref([])
// 监测对象
const initListByIds = () => {
getListByIds({}).then((res: any) => {
if (res.data.length > 0) {
idList.value = res.data
if (!tableStore.table.params.sensitiveUserId && idList.value?.length > 0) {
tableStore.table.params.sensitiveUserId = idList.value[0].id
}
// templateListData()
}
})
}
const stencil = (val: any) => {
console.log('🚀 ~ stencil ~ val:', val)
templatePolicy.value = val.filter((item: any) => item.excelType == '4')
Template.value = templatePolicy.value[0]
reportForm.value = templatePolicy.value[0]?.excelType
}
const changetype = (val: any) => {
reportForm.value = val.excelType
}
const handleNodeClick = (data: any, node: any) => {
if (data?.level == 3) {
dotList.value = data
setTimeout(() => {
tableStore.index()
}, 500)
} else {
tableStore.table.loading = false
}
}
const exportEvent = () => {
const now = new Date()
const year = now.getFullYear() // 4位年份
const month = now.getMonth() + 1 // 月份0-11需+1
const day = now.getDate() // 日期1-31
// 格式化YYYY - MM - DD补零
const formattedDate = `${year}${String(month).padStart(2, '0')}${String(day).padStart(2, '0')}`
exportExcel(luckysheet.getAllSheets(), name.value + formattedDate)
}
</script>
<style lang="scss">
.report-zl-page1 {
display: flex;
overflow: hidden;
.cn-tree {
padding: 0 10px 0 0 !important;
}
}
.report-zl-sidebar {
width: 280px;
flex-shrink: 0;
min-height: 0;
overflow: hidden;
}
.report-zl-main {
flex: 1;
min-width: 0;
min-height: 0;
background: #fff;
display: flex;
flex-direction: column;
overflow: hidden;
}
.box {
flex: 1;
min-height: 0;
padding: 10px 0;
overflow: hidden;
}
.report-zl-sheet {
height: 100%;
}
</style>
<style></style>

View File

@@ -0,0 +1,37 @@
<template>
<div class="default-main" :style="{ height: pageHeight.height }">
<el-tabs v-model="activeTab" type="border-card">
<el-tab-pane label="图表" name="chart">
<chart v-if="activeTab === 'chart'"/>
</el-tab-pane>
<el-tab-pane label="报表" name="report">
<report v-if="activeTab === 'report'"/>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { mainHeight } from '@/utils/layout'
import chart from './components/chart.vue'
import report from './components/report.vue'
defineOptions({
name: 'govern/analyze/steadyState'
})
const pageHeight = mainHeight(20)
const activeTab = ref('chart')
</script>
<style scoped lang="scss">
:deep(.el-tabs__content) {
padding: 0;
}
</style>

View File

@@ -0,0 +1,13 @@
<template>
<div class="default-main">
<h1>暂态治理分析</h1>
</div>
</template>
<script setup lang="ts">
defineOptions({
name: 'govern/analyze/transient'
})
</script>
<style scoped lang="scss"></style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,298 +1,297 @@
<template> <template>
<div class="device-control-detail child-router"> <div class="device-control-detail child-router">
<TableHeader :showSearch="false"> <TableHeader :showSearch="false">
<template #select> <template #select>
<el-form-item label="日期"> <el-form-item label="日期">
<DatePicker ref="datePickerRef"></DatePicker> <DatePicker ref="datePickerRef"></DatePicker>
</el-form-item> </el-form-item>
<el-form-item label="值类型"> <el-form-item label="值类型">
<el-select v-model.trim="form.dataLevel" :disabled="props.dataLevel == 'Primary'"> <el-select v-model.trim="form.dataLevel" :disabled="props.dataLevel == 'Primary'">
<el-option value="Primary" label="一次值"></el-option> <el-option value="Primary" label="一次值"></el-option>
<el-option value="Secondary" label="二次值"></el-option> <el-option value="Secondary" label="二次值"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="数据类型" label-width="80px"> <el-form-item label="数据类型" label-width="80px">
<el-select v-model.trim="form.statMethod" placeholder="请选择数据类型"> <el-select v-model.trim="form.statMethod" placeholder="请选择数据类型">
<el-option v-for="item in typeOptions" :key="item.id" :label="item.name" <el-option v-for="item in typeOptions" :key="item.id" :label="item.name"
:value="item.id"></el-option> :value="item.id"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</template> </template>
<template #operation> <template #operation>
<el-button type="primary" icon="el-icon-Search" @click="init">查询</el-button> <el-button type="primary" icon="el-icon-Search" @click="init">查询</el-button>
<el-button icon="el-icon-Back" @click="$emit('close')">返回</el-button> <el-button icon="el-icon-Back" @click="$emit('close')">返回</el-button>
</template> </template>
</TableHeader> </TableHeader>
<MyEchart :options="echartsData" v-if="echartsData" style="flex: 1" class="mt10" /> <MyEchart :options="echartsData" v-if="echartsData" style="flex: 1" class="mt10" />
<el-empty description="暂无数据" v-else style="flex: 1" v-loading="loading"></el-empty> <el-empty description="暂无数据" v-else style="flex: 1" v-loading="loading"></el-empty>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, inject, nextTick, onMounted } from 'vue' import { ref, inject, nextTick, onMounted } from 'vue'
import { reactive } from 'vue' import { reactive } from 'vue'
import DatePicker from '@/components/form/datePicker/index.vue' import DatePicker from '@/components/form/datePicker/index.vue'
import { getDeviceDataTrend } from '@/api/cs-harmonic-boot/datatrend' import { getDeviceDataTrend } from '@/api/cs-harmonic-boot/datatrend'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import MyEchart from '@/components/echarts/MyEchart.vue' import MyEchart from '@/components/echarts/MyEchart.vue'
import { yMethod, exportCSV } from '@/utils/echartMethod' import { yMethod, exportCSV } from '@/utils/echartMethod'
import { ITEM_RENDER_EVT } from 'element-plus/es/components/virtual-list/src/defaults' import { ITEM_RENDER_EVT } from 'element-plus/es/components/virtual-list/src/defaults'
interface Props { interface Props {
detail: anyObj detail: anyObj
dataLevel: string dataLevel: string
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
detail: () => { detail: () => {
return {} return {}
}, },
dataLevel: () => { dataLevel: () => {
return '' return ''
} }
}) })
const datePickerRef = ref() const datePickerRef = ref()
const form: any = reactive({ const form: any = reactive({
code: '', code: '',
icon: '', icon: '',
id: '', id: '',
name: '', name: '',
path: '', path: '',
pid: '0', pid: '0',
remark: '', remark: '',
routeName: '', routeName: '',
sort: 100, sort: 100,
dataLevel: '', dataLevel: '',
statMethod: 'avg' statMethod: 'avg'
}) })
const typeOptions = [ const typeOptions = [
{ {
name: '平均值', name: '平均值',
id: 'avg' id: 'avg'
}, },
{ {
name: '最大值', name: '最大值',
id: 'max' id: 'max'
}, },
{ {
name: '最小值', name: '最小值',
id: 'min' id: 'min'
}, },
{ {
name: 'CP95值', name: 'CP95值',
id: 'cp95' id: 'cp95'
} }
] ]
const echartsData = ref<any>(null) const echartsData = ref<any>(null)
const dialogVisible = ref(false) const dialogVisible = ref(false)
const loading = ref(true) const loading = ref(true)
onMounted(() => { onMounted(() => {
if (props.dataLevel == 'Secondary') { if (props.dataLevel == 'Secondary') {
form.dataLevel = 'Primary' form.dataLevel = 'Primary'
} else { } else {
form.dataLevel = props.dataLevel form.dataLevel = props.dataLevel
} }
init() init()
}) })
const init = () => { const init = () => {
echartsData.value = null echartsData.value = null
loading.value = true loading.value = true
// console.log(props.detail.children, 'props.detail.children') // console.log(props.detail.children, 'props.detail.children')
let statisticalParams = props.detail.children let statisticalParams = props.detail.children
statisticalParams[0].statMethod = form.statMethod statisticalParams[0].statMethod = form.statMethod
getDeviceDataTrend({ getDeviceDataTrend({
devId: props.detail.devId, devId: props.detail.devId,
endTime: datePickerRef.value.timeValue[1], endTime: datePickerRef.value.timeValue[1],
lineId: props.detail.lineId, lineId: props.detail.lineId,
startTime: datePickerRef.value.timeValue[0], startTime: datePickerRef.value.timeValue[0],
statisticalParams: statisticalParams, statisticalParams: statisticalParams,
dataLevel: form.dataLevel, dataLevel: form.dataLevel,
statMethod: form.statMethod statMethod: form.statMethod
}).then(res => { }).then(res => {
if (res.data.length && res.data[0].length) { if (res.data.length && res.data[0].length) {
let arr: any[] = [] let arr: any[] = []
res.data.forEach((item: any[]) => { res.data.forEach((item: any[]) => {
arr.push(...item) arr.push(...item)
}) })
let [min, max] = yMethod(arr.map((item: any) => item.statisticalData.toFixed(2))) let [min, max] = yMethod(arr.map((item: any) => item.statisticalData.toFixed(2)))
echartsData.value = { echartsData.value = {
options: { options: {
legend: { legend: {
right: 70, right: 70,
top: 5, top: 5,
data: res.data.map((item: any[]) => { data: res.data.map((item: any[]) => {
return item[0]?.anotherName return item[0]?.anotherName
}) })
}, },
grid: { grid: {
top: '60px', top: '60px',
left: '10px', left: '10px',
right: '20px', right: '20px',
bottom: '40px', bottom: '40px',
containLabel: true containLabel: true
}, },
series: res.data.map((item: any) => { series: res.data.map((item: any) => {
return { return {
data: item.map((item: any, i: any) => { data: item.map((item: any, i: any) => {
return [res.data[0][i]?.time, item.statisticalData.toFixed(2)] return [res.data[0][i]?.time, item.statisticalData.toFixed(2)]
}), }),
// data: [ // data: [
// [1584086222000, '573'], // [1584086222000, '573'],
// [1584086342000, '57'], // [1584086342000, '57'],
// [1584086462000, '56'] // [1584086462000, '56']
// ], // ],
// markPoint: { // markPoint: {
// symbol: 'circle', // symbol: 'circle',
// symbolSize: 0, // symbolSize: 0,
// label: { // label: {
// show: false // show: false
// }, // },
// data: [ // data: [
// { type: 'max', name: 'Max' }, // { type: 'max', name: 'Max' },
// { type: 'min', name: 'Min' } // { type: 'min', name: 'Min' }
// ] // ]
// }, // },
type: 'line', type: 'line',
showSymbol: false, showSymbol: false,
smooth: true, smooth: true,
name: item[0]?.anotherName name: item[0]?.anotherName
} }
}) })
}, },
title: { title: {
text: props.detail.name?.split('(')[0], text: props.detail.name?.split('(')[0],
}, },
tooltip: { tooltip: {
axisPointer: { axisPointer: {
type: 'cross', type: 'cross',
label: { label: {
color: '#fff', color: '#fff',
fontSize: 16 fontSize: 16
} }
}, },
textStyle: { textStyle: {
color: '#fff', color: '#fff',
fontStyle: 'normal', fontStyle: 'normal',
opacity: 0.35, opacity: 0.35,
fontSize: 14 fontSize: 14
}, },
backgroundColor: 'rgba(0,0,0,0.55)', borderWidth: 0
borderWidth: 0 },
},
yAxis: {
yAxis: { name: `单位:(${arr[0].unit == null ? ' / ' : arr[0].unit})`,
name: `单位:(${arr[0].unit == null ? ' / ' : arr[0].unit})`, type: 'value',
type: 'value',
min: min,
min: min, max: max,
max: max, // interval:interval,
// interval:interval,
// min: 134,
// min: 134, // max: 500,
// max: 500, // min: Math.ceil(
// min: Math.ceil( // arr
// arr // .map((item: any) => item.statisticalData)
// .map((item: any) => item.statisticalData) // .sort((a, b) => {
// .sort((a, b) => { // return a - b
// return a - b // })[0]
// })[0] // ),
// ), // max: Math.floor(
// max: Math.floor( // arr
// arr // .map(item => item.statisticalData)
// .map(item => item.statisticalData) // .sort((a, b) => {
// .sort((a, b) => { // return b - a
// return b - a // })[0]
// })[0] // ),
// ), // interval: (Math.floor(
// interval: (Math.floor( // arr
// arr // .map(item => item.statisticalData)
// .map(item => item.statisticalData) // .sort((a, b) => {
// .sort((a, b) => { // return b - a
// return b - a // })[0]
// })[0] // ) - Math.ceil(
// ) - Math.ceil( // arr
// arr // .map((item: any) => item.statisticalData)
// .map((item: any) => item.statisticalData) // .sort((a, b) => {
// .sort((a, b) => { // return a - b
// return a - b // })[0]
// })[0] // )) / 5,
// )) / 5,
},
},
xAxis: {
xAxis: { type: 'time',
type: 'time', axisLabel: {
axisLabel: { formatter: {
formatter: { day: '{MM}-{dd}',
day: '{MM}-{dd}', month: '{MM}',
month: '{MM}', year: '{yyyy}',
year: '{yyyy}', }
} }
} // data: res.data[0].map((item: any) => {
// data: res.data[0].map((item: any) => { // return item.time
// return item.time // }),
// }),
// axisLabel: {
// axisLabel: { // formatter: function (value: string) {
// formatter: function (value: string) { // return value.split(' ').join('\n')
// return value.split(' ').join('\n') // }
// } // }
// } },
}, toolbox: {
toolbox: { featureProps: {
featureProps: { myTool1: {
myTool1: { show: true,
show: true, title: '下载csv',
title: '下载csv',
icon: 'path://M588.8 551.253333V512H352v39.253333h236.373333z m0 78.933334v-39.253334H352v39.253334h236.373333z m136.533333 78.933333V334.933333l-157.866666-157.866666H273.066667A59.306667 59.306667 0 0 0 213.333333 236.373333v551.253334a59.306667 59.306667 0 0 0 59.306667 59.306666h274.773333v42.666667H853.333333v-180.48zM568.746667 234.666667l100.266666 100.693333h-81.066666a20.053333 20.053333 0 0 1-19.626667-20.053333z m-20.48 573.013333H273.066667a19.2 19.2 0 0 1-17.493334-19.626667V236.373333a19.2 19.2 0 0 1 19.626667-19.626666h256v98.133333a58.88 58.88 0 0 0 58.88 59.306667h96.426667v334.933333h-98.133334v-39.68H352v39.68h196.266667z m100.266666 23.04a37.973333 37.973333 0 0 1-32 15.786667 38.826667 38.826667 0 0 1-32.426666-15.786667 53.76 53.76 0 0 1-10.24-32.853333 42.666667 42.666667 0 0 1 42.666666-47.786667 35.84 35.84 0 0 1 37.546667 29.866667h-12.8a23.893333 23.893333 0 0 0-24.746667-19.2c-17.066667 0-29.013333 14.08-29.013333 35.84s11.52 37.546667 28.586667 37.546666a26.453333 26.453333 0 0 0 26.453333-25.6h12.8a39.253333 39.253333 0 0 1-7.253333 22.186667z m59.733334 15.786667a35.84 35.84 0 0 1-40.106667-34.56H682.666667a23.893333 23.893333 0 0 0 26.88 23.04c12.8 0 22.613333-6.4 22.613333-15.786667s-4.266667-11.52-14.506667-13.653333l-21.333333-5.12c-17.066667-4.266667-24.32-11.52-24.32-23.893334s12.8-26.453333 34.133333-26.453333a31.573333 31.573333 0 0 1 35.413334 30.293333h-13.653334a19.626667 19.626667 0 0 0-22.613333-18.773333c-12.8 0-20.48 5.12-20.48 12.8s5.12 11.093333 17.066667 13.653333l14.933333 2.986667a42.666667 42.666667 0 0 1 20.906667 8.96 23.893333 23.893333 0 0 1 7.68 17.92c-0.426667 17.066667-14.506667 28.16-37.12 28.16z m88.746666 0h-14.506666l-32.426667-92.16h14.08l19.626667 59.733333 6.4 20.053333c0-9.386667 3.413333-12.8 5.546666-20.053333l19.2-59.733333h14.08z',
icon: 'path://M588.8 551.253333V512H352v39.253333h236.373333z m0 78.933334v-39.253334H352v39.253334h236.373333z m136.533333 78.933333V334.933333l-157.866666-157.866666H273.066667A59.306667 59.306667 0 0 0 213.333333 236.373333v551.253334a59.306667 59.306667 0 0 0 59.306667 59.306666h274.773333v42.666667H853.333333v-180.48zM568.746667 234.666667l100.266666 100.693333h-81.066666a20.053333 20.053333 0 0 1-19.626667-20.053333z m-20.48 573.013333H273.066667a19.2 19.2 0 0 1-17.493334-19.626667V236.373333a19.2 19.2 0 0 1 19.626667-19.626666h256v98.133333a58.88 58.88 0 0 0 58.88 59.306667h96.426667v334.933333h-98.133334v-39.68H352v39.68h196.266667z m100.266666 23.04a37.973333 37.973333 0 0 1-32 15.786667 38.826667 38.826667 0 0 1-32.426666-15.786667 53.76 53.76 0 0 1-10.24-32.853333 42.666667 42.666667 0 0 1 42.666666-47.786667 35.84 35.84 0 0 1 37.546667 29.866667h-12.8a23.893333 23.893333 0 0 0-24.746667-19.2c-17.066667 0-29.013333 14.08-29.013333 35.84s11.52 37.546667 28.586667 37.546666a26.453333 26.453333 0 0 0 26.453333-25.6h12.8a39.253333 39.253333 0 0 1-7.253333 22.186667z m59.733334 15.786667a35.84 35.84 0 0 1-40.106667-34.56H682.666667a23.893333 23.893333 0 0 0 26.88 23.04c12.8 0 22.613333-6.4 22.613333-15.786667s-4.266667-11.52-14.506667-13.653333l-21.333333-5.12c-17.066667-4.266667-24.32-11.52-24.32-23.893334s12.8-26.453333 34.133333-26.453333a31.573333 31.573333 0 0 1 35.413334 30.293333h-13.653334a19.626667 19.626667 0 0 0-22.613333-18.773333c-12.8 0-20.48 5.12-20.48 12.8s5.12 11.093333 17.066667 13.653333l14.933333 2.986667a42.666667 42.666667 0 0 1 20.906667 8.96 23.893333 23.893333 0 0 1 7.68 17.92c-0.426667 17.066667-14.506667 28.16-37.12 28.16z m88.746666 0h-14.506666l-32.426667-92.16h14.08l19.626667 59.733333 6.4 20.053333c0-9.386667 3.413333-12.8 5.546666-20.053333l19.2-59.733333h14.08z', onclick: (e) => {
onclick: (e) => {
let list = echartsData.value.options.series.map(item => item.data)
let list = echartsData.value.options.series.map(item => item.data) let dataList = list[0].map((item, index) => {
let dataList = list[0].map((item, index) => { const value1 = list[1] && list[1][index] ? list[1][index][1] : null;
const value1 = list[1] && list[1][index] ? list[1][index][1] : null; const value2 = list[2] && list[2][index] ? list[2][index][1] : null;
const value2 = list[2] && list[2][index] ? list[2][index][1] : null; const value3 = list[3] && list[3][index] ? list[3][index][1] : null;
const value3 = list[3] && list[3][index] ? list[3][index][1] : null;
return [item[0], item[1], value1, value2, value3];
return [item[0], item[1], value1, value2, value3]; });
}); exportCSV(echartsData.value.options.series.map(item => item.name), dataList, echartsData.value.title.text + '.csv')
exportCSV(echartsData.value.options.series.map(item => item.name), dataList, echartsData.value.title.text + '.csv')
}
} }
} }
} }
} }
} if ((echartsData.value.legend = ['A相', 'B相', 'C相'])) {
if ((echartsData.value.legend = ['A相', 'B相', 'C相'])) { echartsData.value.color = ['#DAA520', '#2E8B57', '#A52a2a']
echartsData.value.color = ['#DAA520', '#2E8B57', '#A52a2a'] }
}
} else {
} else { echartsData.value = null
echartsData.value = null }
} loading.value = false
loading.value = false })
}) }
}
defineExpose({ open })
defineExpose({ open }) </script>
</script> <style lang="scss">
<style lang="scss"> .device-control-detail {
.device-control-detail { padding-bottom: 10px;
padding-bottom: 10px; display: flex;
display: flex; flex-direction: column;
flex-direction: column; }
}
.el-form--inline .el-form-item {
.el-form--inline .el-form-item { margin-bottom: 10px !important;
margin-bottom: 10px !important; }
} </style>
</style>

View File

@@ -130,6 +130,7 @@
:showSearch="false" :showSearch="false"
v-if=" v-if="
(dataSet.indexOf('_trenddata') == -1 && (dataSet.indexOf('_trenddata') == -1 &&
dataSet.indexOf('_kilowattHour') == -1 &&
dataSet.indexOf('_realtimedata') == -1 && dataSet.indexOf('_realtimedata') == -1 &&
dataSet.indexOf('_event') == -1) || dataSet.indexOf('_event') == -1) ||
realTimeFlag realTimeFlag
@@ -141,7 +142,8 @@
v-show=" v-show="
dataSet.includes('_items') || dataSet.includes('_items') ||
dataSet.indexOf('_history') != -1 || dataSet.indexOf('_history') != -1 ||
dataSet.indexOf('_moduleData') != -1 dataSet.indexOf('_moduleData') != -1 ||
dataSet.indexOf('_devRunTrend') != -1
" "
> >
<DatePicker ref="datePickerRef"></DatePicker> <DatePicker ref="datePickerRef"></DatePicker>
@@ -165,7 +167,11 @@
</el-select> --> </el-select> -->
<el-radio-group <el-radio-group
v-model.trim="formInline.dataLevel" v-model.trim="formInline.dataLevel"
v-if="!dataSet.includes('_moduleData') && TrendList?.lineType == 1" v-if="
!dataSet.includes('_devRunTrend') &&
!dataSet.includes('_moduleData') &&
TrendList?.lineType == 1
"
:disabled="TrendList?.lineType != 1" :disabled="TrendList?.lineType != 1"
@change="handleClick" @change="handleClick"
> >
@@ -226,7 +232,7 @@
<el-button <el-button
type="primary" type="primary"
:disabled="tableLoading" :disabled="tableLoading"
v-if="realTimeFlag" v-if="showButton"
:icon="DataLine" :icon="DataLine"
@click="handleTrend" @click="handleTrend"
> >
@@ -234,13 +240,22 @@
</el-button> </el-button>
<el-button <el-button
type="primary" type="primary"
v-if="realTimeFlag" v-if="showButton"
:icon="TrendCharts" :icon="TrendCharts"
@click="handleHarmonicSpectrum" @click="handleHarmonicSpectrum"
:disabled="tableLoading" :disabled="tableLoading"
> >
实时趋势 实时趋势
</el-button> </el-button>
<el-button
type="primary"
v-if="showButton"
:icon="Download"
@click="downloadTxt"
:disabled="tableLoading"
>
下载数据
</el-button>
</template> </template>
</TableHeader> </TableHeader>
<div <div
@@ -283,6 +298,7 @@
:style="{ height: tableHeight }" :style="{ height: tableHeight }"
v-if=" v-if="
dataSet.indexOf('_trenddata') == -1 && dataSet.indexOf('_trenddata') == -1 &&
dataSet.indexOf('_kilowattHour') == -1 &&
dataSet.indexOf('_realtimedata') == -1 && dataSet.indexOf('_realtimedata') == -1 &&
dataSet.indexOf('_event') == -1 && dataSet.indexOf('_event') == -1 &&
tableData.length != 0 tableData.length != 0
@@ -468,6 +484,10 @@
<div style="height: calc(100vh - 340px)" v-if="dataSet.indexOf('_trenddata') != -1"> <div style="height: calc(100vh - 340px)" v-if="dataSet.indexOf('_trenddata') != -1">
<Trend ref="trendRef" :TrendList="TrendList"></Trend> <Trend ref="trendRef" :TrendList="TrendList"></Trend>
</div> </div>
<!-- 电镀数据 -->
<div style="height: calc(100vh - 340px)" v-if="dataSet.indexOf('_kilowattHour') != -1">
<electroplating ref="electroplatingRef" :TrendList="TrendList"></electroplating>
</div>
<!-- 实时数据 --> <!-- 实时数据 -->
<div <div
:style="`height: calc(100vh - (${sonTab == 1 ? '378px' : sonTab == 2 ? '340px' : '425px'}))`" :style="`height: calc(100vh - (${sonTab == 1 ? '378px' : sonTab == 2 ? '340px' : '425px'}))`"
@@ -525,6 +545,14 @@
> >
<moduleData ref="moduleDataRef" @onSubmit="handleClick" /> <moduleData ref="moduleDataRef" @onSubmit="handleClick" />
</div> </div>
<!-- 运行趋势 -->
<div
style="height: calc(100vh - 395px)"
v-if="dataSet.indexOf('_devRunTrend') != -1"
v-loading="tableLoading"
>
<operatingTrend ref="operatingTrendRef" @onSubmit="handleClick" />
</div>
<div v-if="!tableData" style="height: 42px"></div> <div v-if="!tableData" style="height: 42px"></div>
</el-tabs> </el-tabs>
</div> </div>
@@ -548,7 +576,8 @@ import {
getOverLimitData, getOverLimitData,
queryDictType, queryDictType,
getById, getById,
allModelData allModelData,
getRawData
} from '@/api/cs-device-boot/EquipmentDelivery' } from '@/api/cs-device-boot/EquipmentDelivery'
import { deviceHisData, deviceRtData, realTimeData, getTestData } from '@/api/cs-device-boot/csGroup' import { deviceHisData, deviceRtData, realTimeData, getTestData } from '@/api/cs-device-boot/csGroup'
import { ref, reactive, onMounted, onUnmounted, inject, nextTick, onBeforeUnmount } from 'vue' import { ref, reactive, onMounted, onUnmounted, inject, nextTick, onBeforeUnmount } from 'vue'
@@ -556,7 +585,9 @@ import { ElMessage } from 'element-plus'
import DatePicker from '@/components/form/datePicker/index.vue' import DatePicker from '@/components/form/datePicker/index.vue'
import Trend from './tabs/trend.vue' //趋势数据 import Trend from './tabs/trend.vue' //趋势数据
import realTime from './tabs/realtime.vue' //实时数据-主界面 import realTime from './tabs/realtime.vue' //实时数据-主界面
import electroplating from './tabs/electroplating.vue' //电镀数据-主界面
import realTrend from './tabs/components/realtrend.vue' //实时数据-实时趋势 import realTrend from './tabs/components/realtrend.vue' //实时数据-实时趋势
import operatingTrend from './tabs/operatingTrend.vue' //运行趋势
import harmonicSpectrum from './tabs/components/harmonicSpectrum.vue' //实时数据-谐波频谱子页面 import harmonicSpectrum from './tabs/components/harmonicSpectrum.vue' //实时数据-谐波频谱子页面
import recordWoves from './tabs/components/recordwoves.vue' //实时数据-实时录波子页面 import recordWoves from './tabs/components/recordwoves.vue' //实时数据-实时录波子页面
import offLineDataImport from './offLineDataImport/index.vue' import offLineDataImport from './offLineDataImport/index.vue'
@@ -569,7 +600,7 @@ import { useRouter } from 'vue-router'
import TableHeader from '@/components/table/header/index.vue' import TableHeader from '@/components/table/header/index.vue'
import { useAdminInfo } from '@/stores/adminInfo' import { useAdminInfo } from '@/stores/adminInfo'
import { import {
Histogram, Download,
TrendCharts, TrendCharts,
DataLine, DataLine,
DataAnalysis, DataAnalysis,
@@ -581,6 +612,7 @@ import {
} from '@element-plus/icons-vue' } from '@element-plus/icons-vue'
import analysisList from './analysisList/index.vue' import analysisList from './analysisList/index.vue'
import mqtt from 'mqtt' import mqtt from 'mqtt'
import { json } from 'node:stream/consumers'
defineOptions({ defineOptions({
name: 'govern/device/control' name: 'govern/device/control'
}) })
@@ -660,6 +692,9 @@ const volConTypeList = dictData.getBasicData('Dev_Connect')
// } // }
//谐波频谱 //谐波频谱
const realTrendRef = ref() const realTrendRef = ref()
const txtContent = ref('')
const operatingTrendRef = ref()
const electroplatingRef = ref()
const changeTrendType = (val: any) => { const changeTrendType = (val: any) => {
trendDataTime.value = '' trendDataTime.value = ''
activeTrendName.value = val * 1 activeTrendName.value = val * 1
@@ -669,7 +704,7 @@ const changeTrendType = (val: any) => {
const activeTrendName: any = ref(0) const activeTrendName: any = ref(0)
const trendTimer: any = ref() const trendTimer: any = ref()
const trendDataTime: any = ref() const trendDataTime: any = ref()
const showButton = ref(false)
//谐波频谱方法 //谐波频谱方法
const handleTrend = async () => { const handleTrend = async () => {
realTimeFlag.value = false realTimeFlag.value = false
@@ -685,7 +720,7 @@ const handleTrend = async () => {
.then((res: any) => { .then((res: any) => {
if (res.code == 'A0000') { if (res.code == 'A0000') {
trendDataTime.value = '' trendDataTime.value = ''
ElMessage.success('装置应答成功') ElMessage.success('设备应答成功')
//每隔30s调用一下接口通知后台推送mqtt消息 //每隔30s调用一下接口通知后台推送mqtt消息
trendTimer.value = window.setInterval(() => { trendTimer.value = window.setInterval(() => {
if (!dataSet.value.includes('_realtimedata')) return if (!dataSet.value.includes('_realtimedata')) return
@@ -709,7 +744,7 @@ const handleTrend = async () => {
// } // }
}) })
} else { } else {
ElMessage.warning('装置应答失败') ElMessage.warning('设备应答失败')
} }
}) })
.catch(e => { .catch(e => {
@@ -741,9 +776,9 @@ const handleHarmonicSpectrum = async () => {
// getRealDataMqttMsg() // getRealDataMqttMsg()
await getBasicRealData(lineId.value).then((res: any) => { await getBasicRealData(lineId.value).then((res: any) => {
if (res.code == 'A0000') { if (res.code == 'A0000') {
ElMessage.success('装置应答成功') ElMessage.success('设备应答成功')
// mqttMessage.value = {} // mqttMessage.value = {}
showButton.value = true
realDataTimer.value = window.setInterval(() => { realDataTimer.value = window.setInterval(() => {
if (!dataSet.value.includes('_realtimedata')) return if (!dataSet.value.includes('_realtimedata')) return
@@ -771,7 +806,8 @@ const handleReturn = async () => {
tableLoading.value = true tableLoading.value = true
await getBasicRealData(lineId.value).then((res: any) => { await getBasicRealData(lineId.value).then((res: any) => {
if (res.code == 'A0000') { if (res.code == 'A0000') {
ElMessage.success('装置应答成功') ElMessage.success('设备应答成功')
showButton.value = true
// mqttMessage.value = {} // mqttMessage.value = {}
realDataTimer.value = window.setInterval(() => { realDataTimer.value = window.setInterval(() => {
if (!dataSet.value.includes('_realtimedata')) return if (!dataSet.value.includes('_realtimedata')) return
@@ -904,6 +940,10 @@ const nodeClick = async (e: anyObj, node: any) => {
if (item.type === 'trenddata') { if (item.type === 'trenddata') {
item.id = item.id + '_trenddata' item.id = item.id + '_trenddata'
} }
//电镀数据
if (item.type === 'kilowattHour') {
item.id = item.id + '_kilowattHour'
}
//实时数据 //实时数据
if (item.type === 'realtimedata') { if (item.type === 'realtimedata') {
item.id = item.id + '_realtimedata' item.id = item.id + '_realtimedata'
@@ -920,6 +960,10 @@ const nodeClick = async (e: anyObj, node: any) => {
if (item.type === 'moduleData') { if (item.type === 'moduleData') {
item.id = item.id + '_moduleData' item.id = item.id + '_moduleData'
} }
// 模块数据
if (item.type === 'devRunTrend') {
item.id = item.id + '_devRunTrend'
}
}) })
res.data.dataSetList = res.data.dataSetList.filter((item: any) => item.name != '历史统计数据') res.data.dataSetList = res.data.dataSetList.filter((item: any) => item.name != '历史统计数据')
//便携式设备默认二次值 //便携式设备默认二次值
@@ -982,9 +1026,9 @@ const getRealDataMqttMsg = async () => {
await getBasicRealData(lineId.value) await getBasicRealData(lineId.value)
.then((res: any) => { .then((res: any) => {
if (res.code == 'A0000') { if (res.code == 'A0000') {
ElMessage.success('装置应答成功') ElMessage.success('设备应答成功')
mqttMessage.value = {} mqttMessage.value = {}
showButton.value = true
realDataTimer.value = window.setInterval(async () => { realDataTimer.value = window.setInterval(async () => {
if (!dataSet.value.includes('_realtimedata')) return if (!dataSet.value.includes('_realtimedata')) return
await getBasicRealData(lineId.value).then((res: any) => { await getBasicRealData(lineId.value).then((res: any) => {
@@ -1113,6 +1157,7 @@ const getRealDataMqttMsg = async () => {
} }
if (obj.hasOwnProperty('pA') && obj.hasOwnProperty('pB')) { if (obj.hasOwnProperty('pA') && obj.hasOwnProperty('pB')) {
mqttMessage.value = obj mqttMessage.value = obj
txtContent.value = JSON.stringify(obj)
//更新实时数据主页面值 //更新实时数据主页面值
realTimeFlag.value && realTimeFlag.value &&
@@ -1145,8 +1190,11 @@ const getRealDataMqttMsg = async () => {
mqttRef.value.on('close', function () { mqttRef.value.on('close', function () {
console.log('mqtt客户端已断开连接.....') console.log('mqtt客户端已断开连接.....')
}) })
setTimeout(() => {
tableLoading.value = false
}, 6000)
} else { } else {
ElMessage.success('装置应答失败') ElMessage.success('设备应答失败')
tableLoading.value = false tableLoading.value = false
} }
}) })
@@ -1162,6 +1210,7 @@ const realDataTimer: any = ref()
const mqttMessage = ref<any>({}) const mqttMessage = ref<any>({})
const handleClick = async (tab?: any) => { const handleClick = async (tab?: any) => {
tableLoading.value = true tableLoading.value = true
showButton.value = false
if (realDataTimer.value) { if (realDataTimer.value) {
window.clearInterval(realDataTimer.value) window.clearInterval(realDataTimer.value)
} }
@@ -1223,6 +1272,26 @@ const handleClick = async (tab?: any) => {
tableLoading.value = false tableLoading.value = false
}, 0) }, 0)
} }
//电镀数据
if (dataSet.value.includes('_kilowattHour')) {
let obj = {
devId: deviceId.value, //e.id
lineId: lineId.value, //e.pid
type: 4,
list: [
{
lineId: lineId.value,
devId: dataSet.value.replace('_kilowattHour', '')
}
]
// startTime: datePickerRef.value && datePickerRef.value.timeValue[0],
// endTime: datePickerRef.value && datePickerRef.value.timeValue[1]
}
setTimeout(() => {
electroplatingRef.value && electroplatingRef.value.getTrendRequest(obj)
tableLoading.value = false
}, 0)
}
//查询实时数据 //查询实时数据
if (dataSet.value.includes('_realtimedata')) { if (dataSet.value.includes('_realtimedata')) {
tableLoading.value = true tableLoading.value = true
@@ -1316,6 +1385,36 @@ const handleClick = async (tab?: any) => {
}, 0) }, 0)
}, 0) }, 0)
} }
//运行趋势
if (dataSet.value.includes('_devRunTrend')) {
setTimeout(async () => {
if (tab.props != undefined) await (datePickerRef.value && datePickerRef.value?.setInterval(5))
let obj = {
// devId: deviceId.value, //e.id
lineId: [deviceId.value], //e.pid
startTime: datePickerRef.value && datePickerRef.value.timeValue[0],
endTime: datePickerRef.value && datePickerRef.value.timeValue[1]
}
await setTimeout(() => {
getRawData(obj)
.then((res: any) => {
tableLoading.value = false
setTimeout(() => {
operatingTrendRef.value?.setData(res.data)
}, 500)
setTimeout(() => {
loading.value = false
}, 1500)
})
.catch(e => {
setTimeout(() => {
tableLoading.value = false
}, 1500)
})
}, 0)
}, 0)
}
//查询当前指标 //查询当前指标
if (!dataSet.value.includes('_')) { if (!dataSet.value.includes('_')) {
@@ -1435,6 +1534,21 @@ const queryList: any = ref([])
const echoName = (value: any, arr: any[]) => { const echoName = (value: any, arr: any[]) => {
return value ? arr.find(item => item.value == value)?.label : '/' return value ? arr.find(item => item.value == value)?.label : '/'
} }
// 下载txt
const downloadTxt = () => {
const content = txtContent.value // 文件内容
const blob = new Blob([content], { type: 'text/plain' }) // 创建 Blob 对象
const url = URL.createObjectURL(blob) // 生成下载链接
// 创建 <a> 标签并触发下载
const link = document.createElement('a')
link.href = url
link.download = '实时数据.txt' // 文件名
link.click()
// 释放 URL 对象
URL.revokeObjectURL(url)
}
onMounted(() => {}) onMounted(() => {})
onBeforeUnmount(() => { onBeforeUnmount(() => {

View File

@@ -241,7 +241,7 @@ const tableStore = new TableStore({
width: '180', width: '180',
render: 'buttons', render: 'buttons',
buttons: [ buttons: [
//直连装置注册 //直连设备注册
{ {
title: '注册', title: '注册',
type: 'primary', type: 'primary',

View File

@@ -20,14 +20,8 @@
<el-button @click="handleBack" :icon="Back">返回</el-button> <el-button @click="handleBack" :icon="Back">返回</el-button>
</div> </div>
<!-- v-loading="loading" --> <!-- v-loading="loading" -->
<el-tabs <el-tabs class="home_body" type="border-card" v-model.trim="activeName1" @tab-click="handleClick">
class="home_body"
type="border-card"
v-model.trim="activeName1"
@tab-click="handleClick"
>
<el-tab-pane label="瞬时波形" name="ssbx" :style="'height:' + bxecharts + ';overflow-y: auto;'"> <el-tab-pane label="瞬时波形" name="ssbx" :style="'height:' + bxecharts + ';overflow-y: auto;'">
<shushiboxi <shushiboxi
v-if="isWp && wp && activeName == 'ssbx' && showBoxi" v-if="isWp && wp && activeName == 'ssbx' && showBoxi"
@@ -110,9 +104,15 @@ const value = ref(1)
const isWp = ref(false) const isWp = ref(false)
const boxoList: any = ref([]) const boxoList: any = ref([])
const getWpData = (val: any, list: any) => { const getWpData = (val: any, list: any) => {
wp.value = val wp.value = {}
isWp.value = true isWp.value = false
boxoList.value = list boxoList.value = []
setTimeout(() => {
wp.value = val
isWp.value = true
boxoList.value = list
}, 100)
} }
const changeView = () => { const changeView = () => {
showBoxi.value = false showBoxi.value = false
@@ -140,7 +140,6 @@ const handleBack = () => {
emit('handleHideCharts') emit('handleHideCharts')
} }
const setHeight = (h: any, vh: any, num = 1) => { const setHeight = (h: any, vh: any, num = 1) => {
console.log('🚀 ~ setHeight ~ h:', h)
if (h != false) { if (h != false) {
parentHeight.value = h parentHeight.value = h
} }

View File

@@ -0,0 +1,507 @@
<template>
<div>
<!-- 电镀数据数据 -->
<div>
<TableHeader ref="tableHeaderRef" :showSearch="false" @selectChange="selectChange">
<template v-slot:select>
<el-form-item>
<DatePicker ref="datePickerRef"></DatePicker>
</el-form-item>
<el-form-item label="电度指标" label-width="80px">
<el-select style="width: 200px" multiple :multiple-limit="3" collapse-tags filterable
collapse-tags-tooltip v-model="searchForm.index" placeholder="请选择统计指标"
@change="onIndexChange($event)">
<el-option v-for="item in indexOptions" :key="item.id" :label="item.name"
:value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-radio-group v-model="searchForm.dataLevel" @change="init()">
<el-radio-button label="一次值" value="Primary" />
<el-radio-button label="二次值" value="Secondary" />
</el-radio-group>
</el-form-item>
<el-form-item>
<el-radio-group v-model="searchForm.dataModel" @change="init()">
<el-radio-button label="全量" value="0" />
<el-radio-button label="增量" value="1" />
</el-radio-group>
</el-form-item>
<!-- <el-form-item label="统计类型">
<el-select style="min-width: 120px !important" placeholder="请选择" v-model="searchForm.valueType">
<el-option value="max" label="最大值"></el-option>
<el-option value="min" label="最小值"></el-option>
<el-option value="avg" label="平均值"></el-option>
<el-option value="cp95" label="cp95"></el-option>
</el-select>
</el-form-item> -->
</template>
<template #operation>
<el-button type="primary" icon="el-icon-Search" @click="init()">查询</el-button>
<el-button :type="timeControl ? 'primary' : ''" icon="el-icon-Sort" @click="setTimeControl">
缺失数据
</el-button>
</template>
</TableHeader>
</div>
<div class="history_chart" :style="pageHeight" v-loading="loading">
<MyEchart ref="historyChart" :options="echartsData" v-if="showEchart" />
<el-empty :style="pageHeight" v-else description="暂无数据" />
</div>
</div>
</template>
<script lang="ts" setup>
import { mainHeight } from '@/utils/layout'
import { queryByCode, queryCsDictTree } from '@/api/system-boot/dictTree'
import { ref, onMounted } from 'vue'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { useDictData } from '@/stores/dictData'
import { yMethod, exportCSV, completeTimeSeries } from '@/utils/echartMethod'
import TableHeader from '@/components/table/header/index.vue'
import { getTabsDataByType } from '@/api/cs-device-boot/EquipmentDelivery'
import DatePicker from '@/components/form/datePicker/index.vue'
import { color } from '@/components/echarts/color'
import { ElMessage } from 'element-plus'
const dictData = useDictData()
defineOptions({
// name: 'govern/device/control'
})
const props = defineProps({
TrendList: {
type: Array
}
})
// console.log("🚀 ~ props:", props.TrendList)
const showEchart = ref(true)
//电压等级
const voltageLevelList = dictData.getBasicData('Dev_Voltage_Stand')
//接线方式
const volConTypeList = dictData.getBasicData('Dev_Connect')
const timeControl = ref(false)
//值类型
const pageHeight = ref(mainHeight(290))
const loading = ref(true)
const searchForm: any = ref({})
const tableHeaderRef = ref()
const typeOptions = [
{
name: '平均值',
id: 'avg'
},
{
name: '最大值',
id: 'max'
},
{
name: '最小值',
id: 'min'
},
{
name: 'CP95值',
id: 'cp95'
}
]
searchForm.value = {
index: [],
type: typeOptions[0].id,
searchBeginTime: '',
searchEndTime: '',
dataLevel: 'Primary',
dataModel: '0',
valueType: 'avg'
}
//统计指标
const indexOptions: any = ref([])
queryByCode('Kilowatt_Hour').then(res => {
queryCsDictTree(res.data.id).then(item => {
indexOptions.value = item.data.sort((a: any, b: any) => {
return a.sort - b.sort
})
searchForm.value.index[0] = indexOptions.value[0].id
init()
})
})
const chartsList = ref<any>([])
const activeName: any = ref()
const deviceData: any = ref([])
//历史趋势devId
const historyDevId: any = ref('')
const chartTitle: any = ref('')
const echartsData = ref<any>(null)
//加载echarts图表
//历史趋势数据
const historyDataList: any = ref([])
//获取请求趋势数据参数
const trendRequestData = ref()
const getTrendRequest = (val: any) => {
trendRequestData.value = val
// init()
}
//初始化趋势图
const headerRef = ref()
const datePickerRef = ref()
const lineStyle = [{ type: 'solid' }, { type: 'dashed' }, { type: 'dotted' }]
const init = async () => {
loading.value = true
echartsData.value = {}
historyDataList.value = []
chartTitle.value = ''
searchForm.value.index.map((item: any, indexs: any) => {
indexOptions.value.map((vv: any) => {
if (vv.id == item) {
chartTitle.value += indexs == searchForm.value.index.length - 1 ? vv.name : vv.name + '/'
}
})
})
const lists = searchForm.value.index.map((id: any) => ({
statisticalId: id,
frequencys: []
}))
let obj = {
...trendRequestData.value,
list: lists,
// valueType: searchForm.value.type,
dataLevel: searchForm.value.dataLevel,
dataModel: searchForm.value.dataModel,
// valueType: searchForm.value.valueType,
startTime: datePickerRef.value && datePickerRef.value.timeValue[0],
endTime: datePickerRef.value && datePickerRef.value.timeValue[1]
}
if (searchForm.value.index.length == 0) {
ElMessage.warning('请选择统计指标')
loading.value = false
return
}
if (obj.devId && obj.list.length != 0) {
try {
showEchart.value = true
await getTabsDataByType(obj)
.then((res: any) => {
if (res.code == 'A0000') {
if (res.data.length == 0) {
loading.value = false
showEchart.value = false
return
}
historyDataList.value = res.data
chartsList.value = formatChartData(JSON.parse(JSON.stringify(res.data)))
loading.value = false
setEchart()
}
})
.catch(error => {
loading.value = false
})
} catch (error) {
loading.value = false
}
}
}
const setEchart = () => {
loading.value = true
echartsData.value = {}
//icon图标替换legend图例
// y轴单位数组
let unitList: any = []
let groupedData = chartsList.value.reduce((acc: any, item: any) => {
const key = item.statisticalName
if (!acc[key]) {
acc[key] = []
}
acc[key].push(item)
return acc
}, {})
let result = Object.values(groupedData)
// console.log("🚀 ~ .then ~ result:", result)
// console.log("🚀 ~ .then ~ result:", result)
if (chartsList.value.length > 0) {
unitList = result.map((item: any) => {
return item[0].unit
})
}
echartsData.value = {
legend: {
itemWidth: 20,
itemHeight: 20,
itemStyle: { opacity: 0 }, //去圆点
type: 'scroll', // 开启滚动分页
// orient: 'vertical', // 垂直排列
top: 5,
right: 70
// width: 550,
// height: 50
},
grid: {
top: '80px'
},
tooltip: {
axisPointer: {
type: 'cross',
label: {
color: '#fff',
fontSize: 16
}
},
textStyle: {
color: '#fff',
fontStyle: 'normal',
opacity: 0.35,
fontSize: 14
},
backgroundColor: 'rgba(0,0,0,0.55)',
borderWidth: 0,
formatter(params: any) {
const xname = params[0].value[0]
let str = `${xname}<br>`
params.forEach((el: any, index: any) => {
let marker = ''
if (el.value[3] == 'dashed') {
for (let i = 0; i < 3; i++) {
marker += `<span style="display:inline-block;border: 2px ${el.color} solid;margin-right:5px;width:10px;height:0px;background-color:#ffffff00;"></span>`
}
} else {
marker = `<span style="display:inline-block;border: 2px ${el.color} ${el.value[3]};margin-right:5px;width:40px;height:0px;background-color:#ffffff00;"></span>`
}
let unit = el.value[2] ? el.value[2] : ''
str += `${marker}${el.seriesName.split('(')[0]}${el.value[1]}${unit}
<br>`
})
return str
}
},
color: ['#DAA520', '#2E8B57', '#A52a2a', ...color],
xAxis: {
type: 'time',
axisLabel: {
formatter: {
day: '{MM}-{dd}',
month: '{MM}',
year: '{yyyy}'
}
}
},
yAxis: [{}],
toolbox: {
featureProps: {
myTool1: {
show: true,
title: '下载csv',
icon: 'path://M588.8 551.253333V512H352v39.253333h236.373333z m0 78.933334v-39.253334H352v39.253334h236.373333z m136.533333 78.933333V334.933333l-157.866666-157.866666H273.066667A59.306667 59.306667 0 0 0 213.333333 236.373333v551.253334a59.306667 59.306667 0 0 0 59.306667 59.306666h274.773333v42.666667H853.333333v-180.48zM568.746667 234.666667l100.266666 100.693333h-81.066666a20.053333 20.053333 0 0 1-19.626667-20.053333z m-20.48 573.013333H273.066667a19.2 19.2 0 0 1-17.493334-19.626667V236.373333a19.2 19.2 0 0 1 19.626667-19.626666h256v98.133333a58.88 58.88 0 0 0 58.88 59.306667h96.426667v334.933333h-98.133334v-39.68H352v39.68h196.266667z m100.266666 23.04a37.973333 37.973333 0 0 1-32 15.786667 38.826667 38.826667 0 0 1-32.426666-15.786667 53.76 53.76 0 0 1-10.24-32.853333 42.666667 42.666667 0 0 1 42.666666-47.786667 35.84 35.84 0 0 1 37.546667 29.866667h-12.8a23.893333 23.893333 0 0 0-24.746667-19.2c-17.066667 0-29.013333 14.08-29.013333 35.84s11.52 37.546667 28.586667 37.546666a26.453333 26.453333 0 0 0 26.453333-25.6h12.8a39.253333 39.253333 0 0 1-7.253333 22.186667z m59.733334 15.786667a35.84 35.84 0 0 1-40.106667-34.56H682.666667a23.893333 23.893333 0 0 0 26.88 23.04c12.8 0 22.613333-6.4 22.613333-15.786667s-4.266667-11.52-14.506667-13.653333l-21.333333-5.12c-17.066667-4.266667-24.32-11.52-24.32-23.893334s12.8-26.453333 34.133333-26.453333a31.573333 31.573333 0 0 1 35.413334 30.293333h-13.653334a19.626667 19.626667 0 0 0-22.613333-18.773333c-12.8 0-20.48 5.12-20.48 12.8s5.12 11.093333 17.066667 13.653333l14.933333 2.986667a42.666667 42.666667 0 0 1 20.906667 8.96 23.893333 23.893333 0 0 1 7.68 17.92c-0.426667 17.066667-14.506667 28.16-37.12 28.16z m88.746666 0h-14.506666l-32.426667-92.16h14.08l19.626667 59.733333 6.4 20.053333c0-9.386667 3.413333-12.8 5.546666-20.053333l19.2-59.733333h14.08z',
onclick: e => {
// console.log("🚀 ~ init ~ echartsData.value:", echartsData.value.options.series.map(item => item.data))
let list = echartsData.value.options.series?.map((item: any) => item.data)
let dataList = list[0]?.map((item: any, index: any) => {
let value = [item[0], item[1]]
list.forEach((item1: any, index1: any) => {
if (index1 > 0) {
value.push(item1 && item1[index] ? item1[index][1] : null)
}
})
return value
})
exportCSV(
echartsData.value.options.series.map((item: any) => item.name),
dataList,
'电镀数据.csv'
)
}
}
}
},
options: {
series: []
}
}
// console.log("🚀 ~ unitList.forEach ~ unitList:", unitList)
if (chartsList.value.length > 0) {
let yData: any = []
echartsData.value.yAxis = []
let setList = [...new Set(unitList)]
setList.forEach((item: any, index: any) => {
if (index > 2) {
echartsData.value.grid.right = (index - 1) * 80
}
yData.push([])
let right = {
position: 'right',
offset: (index - 1) * 80
}
// console.log("🚀 ~ unitList.forEach ~ right.index:", index)
echartsData.value.yAxis.push({
name: item,
yAxisIndex: index,
splitNumber: 5,
minInterval: 1,
splitLine: {
show: false
},
...(index > 0 ? right : null)
})
})
// console.log("🚀 ~ result.forEach ~ result:", result)
// '电压负序分量', '电压正序分量', '电压零序分量'
let ABCName = [...new Set(chartsList.value.map((item: any) => item.statisticalName))]
// console.log("🚀 ~ .then ~ ABCName:", ABCName)
result.forEach((item: any, index: any) => {
let yMethodList: any = []
let ABCList = Object.values(
item.reduce((acc: any, cur: any) => {
const key = cur.phase == null ? cur.statisticalIndex : cur.phase
if (!acc[key]) {
acc[key] = []
}
acc[key].push(cur)
return acc
}, {})
)
// console.log("🚀 ~ ABCList.forEach ~ ABCList:", ABCList)
ABCList.forEach((kk: any) => {
let colorName = kk[0].phase?.charAt(0).toUpperCase()
let lineS = ABCName.findIndex(name => name === kk[0].statisticalName)
const seriesLineStyle = lineStyle[lineS] || lineStyle[0]
const seriesColor = kk[0].phase
? colorName == 'A'
? '#DAA520'
: colorName == 'B'
? '#2E8B57'
: colorName == 'C'
? '#A52a2a'
: ''
: '#082e6c'
let seriesList: any = []
const sortedData = [...kk].sort(
(a: any, b: any) => new Date(a.time).getTime() - new Date(b.time).getTime()
)
sortedData.forEach((cc: any) => {
if (cc.statisticalData !== null) {
yData[setList.indexOf(kk[0].unit)].push(cc.statisticalData?.toFixed(2))
}
seriesList.push([cc.time, cc.statisticalData?.toFixed(2), cc.unit, seriesLineStyle.type])
})
echartsData.value.options.series.push({
name: kk[0].phase ? kk[0].phase + '相' + kk[0].anotherName : kk[0].anotherName,
type: 'line',
smooth: true,
color: seriesColor,
symbol: 'none',
data: timeControl.value ? completeTimeSeries(seriesList) : seriesList,
lineStyle: seriesLineStyle,
yAxisIndex: setList.indexOf(kk[0].unit)
})
})
// let [min, max] = yMethod(yMethodList)
// echartsData.value.yAxis[index].min = min
// echartsData.value.yAxis[index].max = max
})
yData.forEach((item: any, index: any) => {
let [min, max] = yMethod(item)
echartsData.value.yAxis[index].min = min
echartsData.value.yAxis[index].max = max
})
// console.log("🚀 ~ result.forEach ~ echartsData.value:", echartsData.value)
}
loading.value = false
}
const setTimeControl = () => {
timeControl.value = !timeControl.value
setEchart()
}
const selectChange = (flag: boolean) => {
if (flag) {
pageHeight.value = mainHeight(332)
} else {
pageHeight.value = mainHeight(290)
}
}
// 电度趋势数据预处理:按时间排序
const formatChartData = (data: any[]) => {
return [...data].sort((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime())
}
const onIndexChange = (val: any) => {
let pp: any = []
indexOptions.value.forEach((item: any) => {
const filteredResult = val.filter((vv: any) => item.id == vv)
if (filteredResult.length > 0) {
pp.push(filteredResult[0])
}
})
searchForm.value.index = pp
}
onMounted(() => {
datePickerRef.value.setInterval(5)
})
defineExpose({ getTrendRequest })
</script>
<style lang="scss" scoped>
.history_header {
display: flex;
// flex-wrap: wrap;
#history_select {
width: 100%;
display: flex;
// justify-content: flex-start;
// overflow-x: auto;
height: auto;
flex-wrap: wrap;
.el-form-item {
flex: none !important;
// max-width: 380px;
}
.el-select {
margin-right: 10px;
}
}
// #history_select::-webkit-scrollbar {
// width: 0 !important;
// display: none !important;
// }
.history_searchBtn {
flex: 1;
display: flex;
justify-content: flex-end;
margin-top: 3px;
}
}
.history_chart {
width: 100%;
// flex: 1;
margin-top: 10px;
}
:deep(.el-select__selected-item) {
.is-closable {
width: 100px !important;
}
}
:deep(.el-form--inline .el-form-item) {
margin-right: 15px !important;
}
</style>

View File

@@ -111,10 +111,11 @@ const tableStore: any = new TableStore({
}, },
click: async row => { click: async row => {
row.loading1 = true row.loading1 = true
isWaveCharts.value = true
loading.value = true
await analyseWave(row.id) await analyseWave(row.id)
.then(res => { .then(res => {
isWaveCharts.value = true
loading.value = true
row.loading1 = false row.loading1 = false
if (res != undefined) { if (res != undefined) {
boxoList.value = row boxoList.value = row
@@ -182,7 +183,7 @@ const tableStore: any = new TableStore({
icon: 'el-icon-Check', icon: 'el-icon-Check',
render: 'basicButton', render: 'basicButton',
disabled: row => { disabled: row => {
return (props.deviceType === '2' && row.wavePath) || row.showName === '未知' return !(props.deviceType == '2' && row.wavePath == null)
}, },
click: row => { click: row => {
getFileByEventId(row.id).then(res => { getFileByEventId(row.id).then(res => {
@@ -219,6 +220,8 @@ const handleBack = async () => {
} }
defineExpose({ getTableParams }) defineExpose({ getTableParams })
onMounted(() => { onMounted(() => {
console.log('🚀 ~ props.deviceType:', props.deviceType)
tableStore.index() tableStore.index()
}) })
</script> </script>

View File

@@ -0,0 +1,165 @@
<template>
<div :style="height">
<MyEchart v-if="list.length != 0" :options="options1" style="height: 100%; width: 100%" class="pt10" />
<el-empty description="暂无数据" style="width: 100%; height: 100%" v-else></el-empty>
</div>
</template>
<script setup lang="ts">
import MyEchart from '@/components/echarts/MyEchart.vue'
import { ref, reactive } from 'vue'
import { mainHeight } from '@/utils/layout'
import { max } from 'lodash'
const height = ref(mainHeight(290))
const list = ref([])
const options1 = ref({})
const setData = (data: any) => {
// list.value = [...fillDataFromFirstTime(data),...data]
list.value = data
init()
}
/**
* 补全离散数据严格从数组第一个time开始最后一个time结束
* @param {Array} data 原始离散数据数组
* @param {number} interval 补全时间间隔默认10秒
* @returns {Array} 补全后的连续数据
*/
function fillDataFromFirstTime(data, interval = 10) {
// 1. 基础校验
if (!Array.isArray(data) || data.length === 0) {
console.error('原始数据必须是非空数组')
return []
}
// 2. 锁定起始时间直接取数组第一个元素的time不做任何偏移
const firstItem = data[0]
const startTime = new Date(firstItem.time)
const startTimestamp = startTime.getTime()
// 3. 锁定结束时间取数组最后一个元素的time
const lastItem = data[data.length - 1]
const endTime = new Date(lastItem.time)
const endTimestamp = endTime.getTime()
// 4. 预处理:按时间排序(防止原始数据乱序)
const sortedData = [...data].sort((a, b) => new Date(a.time) - new Date(b.time))
// 5. 初始化状态继承第一个数据的type
let currentType = firstItem.type
let currentDesc = firstItem.description
let nextStatusIndex = 1 // 指向待切换的下一个状态点
const filledData = []
// 6. 核心循环从第一个time开始按间隔补全到最后一个time
for (let ts = startTimestamp; ts <= endTimestamp; ts += interval * 1000) {
const currentDate = new Date(ts)
// 格式化时间为和原始数据一致的 "YYYY-MM-DD HH:mm:ss" 格式
const formattedTime = `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(
2,
'0'
)}-${String(currentDate.getDate()).padStart(2, '0')} ${String(currentDate.getHours()).padStart(
2,
'0'
)}:${String(currentDate.getMinutes()).padStart(2, '0')}:${String(currentDate.getSeconds()).padStart(2, '0')}`
// 7. 检查是否到达下一个状态切换点
if (nextStatusIndex < sortedData.length) {
const nextStatusTime = new Date(sortedData[nextStatusIndex].time).getTime()
if (ts >= nextStatusTime) {
currentType = sortedData[nextStatusIndex].type
currentDesc = sortedData[nextStatusIndex].description
nextStatusIndex++
}
}
// 8. 生成补全数据项(结构与原始数据完全一致)
filledData.push({
time: formattedTime,
devId: firstItem.devId,
description: currentDesc,
type: currentType
})
}
return filledData
}
const init = () => {
options1.value = {
title: {
text: '运行状态'
},
legend: {
show: false
},
tooltip: {
formatter: function (params: any) {
var res = params[0].data[0] + '<br/>运行状态:'
var texts = ''
if (params[0].data[1] === 1 || params[0].data[1] === '1') {
texts = '中断'
} else if (params[0].data[1] === 10 || params[0].data[1] === '10') {
texts = '正常'
}
res = res + texts
return res
}
},
xAxis: {
// type: 'category',
// data: data.updateTime
type: 'time',
name: '时间',
//
axisLabel: {
formatter: {
day: '{MM}-{dd}',
month: '{MM}',
year: '{yyyy}'
}
}
},
yAxis: {
name: '',
type: 'value',
max: 11,
interval: 1,
splitLine: {
lineStyle: {
// 使用深浅的间隔色
color: ['#ccc'],
type: 'dashed',
opacity: 0
}
},
axisLabel: {
// 这里重新定义就可以
formatter: function (value: number) {
var texts = []
if (value === 1) {
texts.push('中断')
} else if (value === 10) {
texts.push('正常')
}
return texts
}
}
},
series: [
{
name: '运行状态',
data: list.value.map((item: any, index: number) => [item.time, item.type == 0 ? 1 : 10]),
type: 'line',
showSymbol: false,
step: 'end'
}
]
}
}
defineExpose({
setData
})
</script>
<style lang="scss" scoped></style>

View File

@@ -1,12 +1,10 @@
<template> <template>
<div class=" device-manage" :style="{ height: pageHeight.height }" v-loading="loading"> <div class="device-manage" :style="{ height: pageHeight.height }" v-loading="loading">
<GetMarketList @node-click="selectUser" @selectUser="selectUser"></GetMarketList> <GetMarketList @node-click="selectUser" @selectUser="selectUser"></GetMarketList>
<div class="device-manage-right" :style="{ height: pageHeight.height }"> <div class="device-manage-right" :style="{ height: pageHeight.height }">
<el-descriptions title="用户基本信息" class="mb10" :column="2" border> <el-descriptions title="用户基本信息" class="mb10" :column="2" border>
<template #extra> <template #extra>
<el-button type="primary" icon="el-icon-Sort" @click="getMarketEnginner"> <el-button type="primary" icon="el-icon-Sort" @click="getMarketEnginner">绑定工程</el-button>
绑定工程
</el-button>
</template> </template>
<el-descriptions-item label="名称"> <el-descriptions-item label="名称">
{{ user.name }} {{ user.name }}
@@ -29,18 +27,36 @@
</vxe-table> </vxe-table>
</div> </div>
</div> </div>
<el-dialog v-model.trim="dialogVisible" title="绑定工程" class="cn-operate-dialog" :close-on-click-modal="false"> <el-dialog
<el-input maxlength="32" show-word-limit v-model.trim="filterText" icon="el-icon-Search" placeholder="请输入内容" v-model.trim="dialogVisible"
clearable style="margin-bottom: 10px"> title="绑定工程"
class="cn-operate-dialog"
:close-on-click-modal="false"
>
<el-input
maxlength="32"
show-word-limit
v-model.trim="filterText"
icon="el-icon-Search"
placeholder="请输入内容"
clearable
style="margin-bottom: 10px"
>
<template #prefix> <template #prefix>
<Icon name="el-icon-Search" style="font-size: 16px" /> <Icon name="el-icon-Search" style="font-size: 16px" />
</template> </template>
</el-input> </el-input>
<vxe-table ref="tableRef" v-bind="defaultAttribute" :data="tableData2.filter((item: any) => { <vxe-table
ref="tableRef"
v-bind="defaultAttribute"
:data="tableData2.filter((item: any) => {
return item.name.includes(filterText) return item.name.includes(filterText)
}) })
" height="500px" style="width: 100%"> "
height="500px"
style="width: 100%"
>
<vxe-column type="checkbox" width="60"></vxe-column> <vxe-column type="checkbox" width="60"></vxe-column>
<vxe-column field="name" title="工程名称"></vxe-column> <vxe-column field="name" title="工程名称"></vxe-column>
</vxe-table> </vxe-table>
@@ -77,6 +93,7 @@ const filterText = ref('')
const tableRef = ref() const tableRef = ref()
const selectUser = (e: any) => { const selectUser = (e: any) => {
if (e == undefined) return (loading.value = false)
user.value = e user.value = e
loading.value = true loading.value = true
queryByUseId({ queryByUseId({

View File

@@ -2,7 +2,7 @@
<template> <template>
<div class="default-main main" :style="{ height: pageHeight.height }"> <div class="default-main main" :style="{ height: pageHeight.height }">
<div class="main_left"> <div class="main_left">
<DeviceTree @node-click="nodeClick" @init="nodeClick"></DeviceTree> <DeviceTree @node-click="nodeClick" @deviceTypeChange="deviceTypeChange"></DeviceTree>
</div> </div>
<div class="main_right" v-loading="loading"> <div class="main_right" v-loading="loading">
<div class="right_nav"> <div class="right_nav">
@@ -19,7 +19,7 @@
</el-breadcrumb> </el-breadcrumb>
</div> </div>
<!-- <el-button :icon="Refresh" @click="handleRestartDevice" type="primary" :loading="deviceRestartLoading"> <!-- <el-button :icon="Refresh" @click="handleRestartDevice" type="primary" :loading="deviceRestartLoading">
装置重启 设备重启
</el-button> --> </el-button> -->
</div> </div>
@@ -197,22 +197,28 @@
import DeviceTree from '@/components/tree/govern/deviceTree.vue' import DeviceTree from '@/components/tree/govern/deviceTree.vue'
import { mainHeight } from '@/utils/layout' import { mainHeight } from '@/utils/layout'
import { ref, watch, onMounted, onBeforeUnmount, h, inject } from 'vue' import { ref, watch, onMounted, onBeforeUnmount, h, inject } from 'vue'
import { ElMessage, ElMessageBox, ElInput } from 'element-plus' import { ElMessage, ElMessageBox, ElInput, ElSegmented } from 'element-plus'
import { import {
getFileServiceFileOrDir, getFileServiceFileOrDir,
uploadDeviceFile, uploadDeviceFile,
reStartDevice, reStartDevice,
addDeviceDir, addDeviceDir,
delDeviceDir delDeviceDir,
} from '@/api/cs-device-boot/fileService.ts' listDir,
downloadFileFromFrontr,
deleteCld,
uploadFileToFront,
mkdir
} from '@/api/cs-device-boot/fileService'
import { defaultAttribute } from '@/components/table/defaultAttribute' import { defaultAttribute } from '@/components/table/defaultAttribute'
import { Delete, Download, Upload, Plus, Refresh, Search } from '@element-plus/icons-vue' import { Delete, Download, Upload, Plus, Refresh, Search } from '@element-plus/icons-vue'
import popup from './popup.vue' import popup from './popup.vue'
import mqtt from 'mqtt' import mqtt from 'mqtt'
import { useAdminInfo } from '@/stores/adminInfo' import { useAdminInfo } from '@/stores/adminInfo'
import { passwordConfirm } from '@/api/user-boot/user' import { passwordConfirm } from '@/api/user-boot/user'
import { downLoadFile } from '@/utils/downloadFile.ts'
defineOptions({ defineOptions({
name: 'govern/device/fileService' name: 'govern/device/fileService/index'
}) })
const pageHeight = mainHeight(20) const pageHeight = mainHeight(20)
const tableHeight = mainHeight(130) const tableHeight = mainHeight(130)
@@ -220,31 +226,53 @@ const adminInfo = useAdminInfo()
const loading = ref(false) const loading = ref(false)
//nDid //nDid
const nDid = ref<string>('') const nDid = ref<string>('')
const devId = ref<string>('')
//当前目录 //当前目录
const activePath = ref<string>('') const activePath = ref<string>('')
//判断是否是根目录 //判断是否是根目录
const isRoot = ref<boolean>(true) const isRoot = ref<boolean>(true)
//储存所有点击过的目录 //储存所有点击过的目录
const activePathList: any = ref([]) const activePathList: any = ref([])
const devConType = ref<string>('')
const deviceTypeChange = (val: any, obj: any) => {
nodeClick(obj)
}
const nodeClick = (e: any) => { const nodeClick = (e: any) => {
if (e && (e.level == 2 || e.type == 'device')) { if (e && (e.level == 2 || e.type == 'device')) {
loading.value = true loading.value = true
nDid.value = e.ndid nDid.value = e.ndid
devId.value = e.id
dirList.value = [] dirList.value = []
activePathList.value = [] activePathList.value = []
activePath.value = '/' activePath.value = '/'
getFileServiceFileOrDir({ nDid: nDid.value, name: activePath.value, type: 'dir' }) devConType.value = e.devConType
.then((resp: any) => { if (devConType.value == 'CLD') {
if (resp.code == 'A0000') { listDir({ devId: devId.value, filePath: activePath.value })
dirList.value = resp.data .then((resp: any) => {
currentDirList.value = resp.data if (resp.code == 'A0000') {
activePathList.value = [{ path: activePath.value }] dirList.value = resp.data
currentDirList.value = resp.data
activePathList.value = [{ path: activePath.value }]
loading.value = false
}
})
.catch(e => {
loading.value = false loading.value = false
} })
}) } else {
.catch(e => { getFileServiceFileOrDir({ nDid: nDid.value, name: activePath.value, type: 'dir' })
loading.value = false .then((resp: any) => {
}) if (resp.code == 'A0000') {
dirList.value = resp.data
currentDirList.value = resp.data
activePathList.value = [{ path: activePath.value }]
loading.value = false
}
})
.catch(e => {
loading.value = false
})
}
} }
} }
//搜索文件或文件夹 //搜索文件或文件夹
@@ -291,11 +319,11 @@ const vNode = () => {
]) ])
} }
//装置重启 //设备重启
const deviceRestartLoading = ref<boolean>(false) const deviceRestartLoading = ref<boolean>(false)
const handleRestartDevice = () => { const handleRestartDevice = () => {
deviceRestartLoading.value = true deviceRestartLoading.value = true
ElMessageBox.prompt('二次校验密码确认', '装置重启', { ElMessageBox.prompt('二次校验密码确认', '设备重启', {
confirmButtonText: '确认', confirmButtonText: '确认',
cancelButtonText: '取消', cancelButtonText: '取消',
customClass: 'customInput', customClass: 'customInput',
@@ -360,21 +388,41 @@ const handleIntoDir = (row: any) => {
if (activePathList.value.indexOf(obj.name) == -1) { if (activePathList.value.indexOf(obj.name) == -1) {
activePathList.value.push({ path: obj.name }) activePathList.value.push({ path: obj.name })
} }
getFileServiceFileOrDir(obj)
.then(res => { if (devConType.value == 'CLD') {
dirList.value = res.data listDir({ devId: devId.value, filePath: row.prjDataPath })
loading.value = false .then((resp: any) => {
currentDirList.value = res.data if (resp.code == 'A0000') {
activePathList.value.map((item: any, index: any) => { dirList.value = resp.data
if (item.path.includes(activePath.value) && item.path.length > activePath.value.length) { currentDirList.value = resp.data
activePathList.value.splice(index, 1) activePathList.value.map((item: any, index: any) => {
if (item.path.includes(activePath.value) && item.path.length > activePath.value.length) {
activePathList.value.splice(index, 1)
}
})
loading.value = false
} }
}) })
isRoot.value = false .catch(e => {
}) loading.value = false
.catch(e => { })
loading.value = false } else {
}) getFileServiceFileOrDir(obj)
.then(res => {
dirList.value = res.data
loading.value = false
currentDirList.value = res.data
activePathList.value.map((item: any, index: any) => {
if (item.path.includes(activePath.value) && item.path.length > activePath.value.length) {
activePathList.value.splice(index, 1)
}
})
isRoot.value = false
})
.catch(e => {
loading.value = false
})
}
} }
//处理导航栏路径 //处理导航栏路径
@@ -406,19 +454,38 @@ const handleIntoByPath = async (val: any) => {
} }
activePath.value = val.path activePath.value = val.path
loading.value = true loading.value = true
getFileServiceFileOrDir(obj) if (devConType.value == 'CLD') {
.then(res => { listDir({ devId: devId.value, filePath: val.path })
dirList.value = res.data .then((resp: any) => {
activePathList.value.map((item: any, index: any) => { if (resp.code == 'A0000') {
if (item.path.includes(activePath.value) && item.path.length > activePath.value.length) { dirList.value = resp.data
activePathList.value.splice(index, 1)
activePathList.value.map((item: any, index: any) => {
if (item.path.includes(activePath.value) && item.path.length > activePath.value.length) {
activePathList.value.splice(index, 1)
}
})
loading.value = false
} }
}) })
loading.value = false .catch(e => {
}) loading.value = false
.catch(e => { })
loading.value = false } else {
}) getFileServiceFileOrDir(obj)
.then(res => {
dirList.value = res.data
activePathList.value.map((item: any, index: any) => {
if (item.path.includes(activePath.value) && item.path.length > activePath.value.length) {
activePathList.value.splice(index, 1)
}
})
loading.value = false
})
.catch(e => {
loading.value = false
})
}
} }
const form = ref({ const form = ref({
path: '' path: ''
@@ -437,45 +504,84 @@ const formRef = ref()
//重新加载当前页面菜单 //重新加载当前页面菜单
const reloadCurrentMenu = (msg: string) => { const reloadCurrentMenu = (msg: string) => {
loading.value = true loading.value = true
getFileServiceFileOrDir({ nDid: nDid.value, name: activePath.value, type: 'dir' })
.then((resp: any) => {
if (resp.code == 'A0000') {
loading.value = false
dirList.value = resp.data
currentDirList.value = resp.data
activePathList.value.map((item: any, index: any) => {
if (item.path.includes(activePath.value) && item.path.length > activePath.value.length) {
activePathList.value.splice(index, 1)
}
})
loading.value = false
if (!msg) return if (devConType.value == 'CLD') {
ElMessage({ message: msg, type: 'success', duration: 5000 }) listDir({ devId: devId.value, filePath: activePath.value })
} .then((resp: any) => {
}) if (resp.code == 'A0000') {
.catch(e => { dirList.value = resp.data
loading.value = false currentDirList.value = resp.data
}) activePathList.value.map((item: any, index: any) => {
if (item.path.includes(activePath.value) && item.path.length > activePath.value.length) {
activePathList.value.splice(index, 1)
}
})
loading.value = false
if (!msg) return
ElMessage({ message: msg, type: 'success', duration: 5000 })
}
})
.catch(e => {
loading.value = false
})
} else {
getFileServiceFileOrDir({ nDid: nDid.value, name: activePath.value, type: 'dir' })
.then((resp: any) => {
if (resp.code == 'A0000') {
loading.value = false
dirList.value = resp.data
currentDirList.value = resp.data
activePathList.value.map((item: any, index: any) => {
if (item.path.includes(activePath.value) && item.path.length > activePath.value.length) {
activePathList.value.splice(index, 1)
}
})
loading.value = false
if (!msg) return
ElMessage({ message: msg, type: 'success', duration: 5000 })
}
})
.catch(e => {
loading.value = false
})
}
} }
//新建文件夹 //新建文件夹
const submitDeviceDir = () => { const submitDeviceDir = () => {
formRef.value.validate((valid: any) => { formRef.value.validate((valid: any) => {
if (valid) { if (valid) {
let obj = { if (devConType.value == 'CLD') {
nDid: nDid.value, let obj = {
path: devId: devId.value,
activePath.value == '/' filePath:
? activePath.value + form.value.path activePath.value == '/'
: activePath.value + '/' + form.value.path ? activePath.value + form.value.path
} : activePath.value + '/' + form.value.path
loading.value = true
addDeviceDir(obj).then((res: any) => {
if (res.code == 'A0000') {
reloadCurrentMenu(res.message)
addDeviceDirOpen.value = false
} }
}) loading.value = true
mkdir(obj).then((res: any) => {
if (res.code == 'A0000') {
reloadCurrentMenu(res.message)
addDeviceDirOpen.value = false
}
})
} else {
let obj = {
nDid: nDid.value,
path:
activePath.value == '/'
? activePath.value + form.value.path
: activePath.value + '/' + form.value.path
}
loading.value = true
addDeviceDir(obj).then((res: any) => {
if (res.code == 'A0000') {
reloadCurrentMenu(res.message)
addDeviceDirOpen.value = false
}
})
}
} }
}) })
} }
@@ -507,13 +613,30 @@ const handleDelDirOrFile = (row: any) => {
passwordConfirm(value) passwordConfirm(value)
.then((resp: any) => { .then((resp: any) => {
if (resp.code == 'A0000') { if (resp.code == 'A0000') {
delDeviceDir({ nDid: nDid.value, path: row.prjDataPath }).then((res: any) => { if (devConType.value == 'CLD') {
if (res.code == 'A0000') { deleteCld({
reloadCurrentMenu(res.message) devId: devId.value,
filePath: row.prjDataPath
// ElMessage({ message: res.message, type: 'success', duration: 5000 }) })
} .then((res: any) => {
}) if (res.code == 'A0000') {
reloadCurrentMenu(res.message)
}
})
.catch(e => {
loading.value = false
})
} else {
delDeviceDir({ nDid: nDid.value, path: row.prjDataPath })
.then((res: any) => {
if (res.code == 'A0000') {
reloadCurrentMenu(res.message)
}
})
.catch(e => {
loading.value = false
})
}
} }
}) })
.catch(e => { .catch(e => {
@@ -526,11 +649,21 @@ const changeType = ref<any>('')
//下载文件 //下载文件
const fileRef = ref() const fileRef = ref()
const handleDownLoad = async (row: any) => { const handleDownLoad = async (row: any) => {
;(await nDid.value) && fileRef.value && fileRef.value.open(row, nDid.value) if (devConType.value == 'CLD') {
// fileName.value = row?.prjDataPath.split('/')[row?.prjDataPath.split('/').length - 1] ElMessage.info('下载中,请稍等...')
// localStorage.setItem('fileName', fileName.value) downloadFileFromFrontr({
changeType.value = 'download' devId: devId.value,
localStorage.setItem('changeType', changeType.value) filePath: row.prjDataPath
}).then(res => {
downLoadFile(row.name, row.name, res)
})
} else {
;(await nDid.value) && fileRef.value && fileRef.value.open(row, nDid.value)
// fileName.value = row?.prjDataPath.split('/')[row?.prjDataPath.split('/').length - 1]
// localStorage.setItem('fileName', fileName.value)
changeType.value = 'download'
localStorage.setItem('changeType', changeType.value)
}
} }
//上传文件 //上传文件
const fileName = ref<any>('') const fileName = ref<any>('')
@@ -540,17 +673,32 @@ const handleUpload = (e: any, fileList: any, row: any) => {
localStorage.setItem('fileName', fileName.value) localStorage.setItem('fileName', fileName.value)
changeType.value = 'upload' changeType.value = 'upload'
localStorage.setItem('changeType', changeType.value) localStorage.setItem('changeType', changeType.value)
const obj = {
id: nDid.value, if (devConType.value == 'CLD') {
file: e.raw, const obj = {
filePath: row || row.prjDataPath devId: devId.value,
} file: e.raw,
uploadDeviceFile(obj).then((res: any) => { dirPath: row || row.prjDataPath
if (res.code == 'A0000') {
reloadCurrentMenu(res.message)
status.value = 100
} }
}) uploadFileToFront(obj).then((res: any) => {
if (res.code == 'A0000') {
reloadCurrentMenu(res.message)
status.value = 100
}
})
} else {
const obj = {
id: nDid.value,
file: e.raw,
filePath: row || row.prjDataPath
}
uploadDeviceFile(obj).then((res: any) => {
if (res.code == 'A0000') {
reloadCurrentMenu(res.message)
status.value = 100
}
})
}
} }
watch( watch(
() => activePathList.value, () => activePathList.value,
@@ -598,26 +746,28 @@ const mqttMessage = ref<any>({})
const status: any = ref() const status: any = ref()
function parseStringToObject(str: string) { function parseStringToObject(str: string) {
const content = str.replace(/^{|}$/g, '') const content = str.replace(/^{|}$/g, '')
const pairs = content.split(',')
const result: any = {} const result: any = {}
pairs.forEach(pair => {
const [key, value] = pair.split(':') // 正则匹配:key:value 格式,支持 value 里带 : / 等字符
// 尝试将数字转换为Number类型 const regex = /([^,:]+):([^,]+)(?=,|$)/g
result[key.trim()] = isNaN(Number(value)) ? value.trim() : Number(value) let match
})
while ((match = regex.exec(content)) !== null) {
const key = match[1].trim()
const value = match[2].trim()
// 数字自动转 Number
result[key] = isNaN(Number(value)) ? value : Number(value)
}
return result return result
} }
mqttRef.value.on('message', (topic: any, message: any) => { mqttRef.value.on('message', (topic: any, message: any) => {
// console.log('mqtt接收到消息', JSON.parse(JSON.stringify(JSON.parse(new TextDecoder().decode(message))))) // console.log('mqtt接收到消息', JSON.parse(JSON.stringify(JSON.parse(new TextDecoder().decode(message)))))
let str = JSON.parse(JSON.stringify(JSON.parse(new TextDecoder().decode(message)))) let str = JSON.parse(JSON.stringify(JSON.parse(new TextDecoder().decode(message))))
let regex = /fileName:(.*?),allStep/
let regex1 = /allStep:(.*?),nowStep/
let regex2 = /nowStep:(.*?),userId/
let regex3 = /userId:(.*?)}/
mqttMessage.value = parseStringToObject(str) mqttMessage.value = parseStringToObject(str)
if (adminInfo.id != mqttMessage.value.userId) return
// console.log("🚀 ~ str.match(regex3)[1]:", str.match(regex3)[1]) // console.log("🚀 ~ str.match(regex3)[1]:", str.match(regex3)[1])
status.value = parseInt(Number((mqttMessage.value.nowStep / mqttMessage.value.allStep) * 100)) status.value = parseInt(Number((mqttMessage.value.nowStep / mqttMessage.value.allStep) * 100))
fileRef.value.setStatus(mqttMessage.value) fileRef.value.setStatus(mqttMessage.value)

View File

@@ -3,6 +3,7 @@
<div :class="downLoading ? 'all_disabled' : ''"> <div :class="downLoading ? 'all_disabled' : ''">
<el-dialog v-model.trim="dialogVisible" title="文件信息" width="50%" @closed="handleClose"> <el-dialog v-model.trim="dialogVisible" title="文件信息" width="50%" @closed="handleClose">
<div v-loading="loading"> <div v-loading="loading">
<div <div
class="download_progress" class="download_progress"
v-if="mqttFileName.includes(fileNameInfoMation) && status != 0 && status != 100" v-if="mqttFileName.includes(fileNameInfoMation) && status != 0 && status != 100"
@@ -57,7 +58,7 @@ import {
getFileServiceFileOrDir, getFileServiceFileOrDir,
downLoadDeviceFile, downLoadDeviceFile,
downLoadDeviceFilePath downLoadDeviceFilePath
} from '@/api/cs-device-boot/fileService.ts' } from '@/api/cs-device-boot/fileService'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { useAdminInfo } from '@/stores/adminInfo' import { useAdminInfo } from '@/stores/adminInfo'
import { downLoadFile } from '@/api/cs-system-boot/manage.ts' import { downLoadFile } from '@/api/cs-system-boot/manage.ts'

View File

@@ -20,7 +20,7 @@
type="primary" type="primary"
:loading="deviceRestartLoading" :loading="deviceRestartLoading"
> >
装置重启 设备重启
</el-button> </el-button>
</template> </template>
<el-descriptions-item label="名称"> <el-descriptions-item label="名称">
@@ -237,11 +237,11 @@ const openGroup = () => {
}) })
}) })
} }
//装置重启 //设备重启
const deviceRestartLoading = ref<boolean>(false) const deviceRestartLoading = ref<boolean>(false)
const handleRestartDevice = () => { const handleRestartDevice = () => {
deviceRestartLoading.value = true deviceRestartLoading.value = true
ElMessageBox.prompt('二次校验密码确认', '装置重启', { ElMessageBox.prompt('二次校验密码确认', '设备重启', {
confirmButtonText: '确认', confirmButtonText: '确认',
cancelButtonText: '取消', cancelButtonText: '取消',
customClass: 'customInput', customClass: 'customInput',

View File

@@ -1,141 +1,159 @@
<template> <template>
<div class=" device-manage" :style="{ height: pageHeight.height }" v-loading="loading"> <div class="device-manage" :style="{ height: pageHeight.height }" v-loading="loading">
<OfficialUserTree @node-click="selectUser" @selectUser="selectUser"></OfficialUserTree> <OfficialUserTree @node-click="selectUser" @selectUser="selectUser"></OfficialUserTree>
<div class="device-manage-right" :style="{ height: pageHeight.height }"> <div style="flex: 1" class="pt10 pr10">
<div class="el-descriptions__header"> <div class="el-descriptions__header">
<el-button type="primary" icon="el-icon-Sort" @click="getEnginnerDev">绑定工程 / 设备</el-button> <div class="el-descriptions__title">工程 / 设备列表</div>
</div> <el-button type="primary" icon="el-icon-Sort" @click="getEnginnerDev">绑定工程 / 设备</el-button>
</div>
<!-- 使用flex布局平均分配高度 --> <!-- 使用flex布局平均分配高度 -->
<div class="tables-container"> <div class="tables-container">
<div class="table-wrapper"> <div class="table-wrapper" :style="{ height: pageHeight1.height }">
<vxe-table v-bind="defaultAttribute" :data="tableData" height="auto" style="width: 100%"> <vxe-table v-bind="defaultAttribute" :data="tableData" height="auto" style="width: 100%">
<vxe-column field="name" title="工程名称"></vxe-column> <vxe-column field="name" title="工程名称"></vxe-column>
<vxe-column title="操作" width="200px"> <vxe-column title="操作" width="200px">
<template v-slot:default="scoped"> <template v-slot:default="scoped">
<el-button link size="small" type="danger" @click="deleteEngineering(scoped.row)"> <el-button link size="small" type="danger" @click="deleteEngineering(scoped.row)">
移除 移除
</el-button> </el-button>
</template> </template>
</vxe-column> </vxe-column>
</vxe-table> </vxe-table>
</div>
</div>
</div>
<!-- 短信配置 -->
<div style="width: 400px" class="pt10 pr10">
<div class="el-descriptions__header">
<div class="el-descriptions__title">短信配置</div>
<el-button type="primary" icon="el-icon-Select" @click="saveConfiguration">保存</el-button>
</div>
<!-- 使用flex布局平均分配高度 -->
<div :style="{ height: pageHeight1.height }" class="border">
<el-tree
ref="treeRef"
class="mt10"
node-key="id"
default-expand-all
:data="tableData"
:props="defaultProps"
show-checkbox
>
<template #default="{ node, data }">
<span>{{ data.name.replace(/\([^)]*\)/, '') }}</span>
</template>
</el-tree>
</div>
</div> </div>
<!-- 新增设备列表 --> <!-- 对话框为左右布局 -->
<div class="device-list-section table-wrapper"> <el-dialog
<vxe-table v-bind="defaultAttribute" :data="deviceTableData" height="auto" style="width: 100%"> v-model.trim="dialogVisible"
<vxe-column field="name" title="设备名称"></vxe-column> draggable
<vxe-column title="操作" width="200px"> title="绑定工程 / 设备"
<template v-slot:default="scoped"> class="cn-operate-dialog"
<el-button link size="small" type="danger" @click="deleteDevice(scoped.row)"> :close-on-click-modal="false"
移除 >
</el-button> <div class="dialog-content">
</template> <!-- 工程部分 -->
</vxe-column> <div class="dialog-section">
</vxe-table> <div class="section-header">
</div> <span>工程列表</span>
</div> <el-input
maxlength="32"
show-word-limit
v-model.trim="filterText"
placeholder="搜索工程"
clearable
class="search-input"
>
<template #prefix>
<Icon name="el-icon-Search" style="font-size: 16px" />
</template>
</el-input>
</div>
<vxe-table
ref="tableRef"
v-bind="defaultAttribute"
:data="tableData2.filter((item: any) => {
return item.name.includes(filterText)
})"
height="400px"
style="width: 100%"
>
<vxe-column type="checkbox" width="60"></vxe-column>
<vxe-column field="name" title="工程名称"></vxe-column>
</vxe-table>
</div>
<!-- 设备部分 -->
<div class="dialog-section">
<div class="section-header">
<span>设备列表</span>
<el-input
maxlength="32"
show-word-limit
v-model.trim="deviceFilterText"
placeholder="搜索设备"
clearable
class="search-input"
>
<template #prefix>
<Icon name="el-icon-Search" style="font-size: 16px" />
</template>
</el-input>
</div>
<vxe-table
ref="deviceTableRef"
v-bind="defaultAttribute"
:data="deviceTableData2.filter((item: any) => {
return item.name.includes(deviceFilterText)
})"
height="400px"
style="width: 100%"
>
<vxe-column type="checkbox" width="60"></vxe-column>
<vxe-column field="name" title="便携式设备名称"></vxe-column>
</vxe-table>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="addData"> </el-button>
</span>
</template>
</el-dialog>
</div> </div>
<!-- 对话框为左右布局 -->
<el-dialog
v-model.trim="dialogVisible"
draggable
title="绑定工程 / 设备"
class="cn-operate-dialog"
:close-on-click-modal="false"
>
<div class="dialog-content">
<!-- 工程部分 -->
<div class="dialog-section">
<div class="section-header">
<span>工程列表</span>
<el-input
maxlength="32"
show-word-limit
v-model.trim="filterText"
placeholder="搜索工程"
clearable
class="search-input"
>
<template #prefix>
<Icon name="el-icon-Search" style="font-size: 16px" />
</template>
</el-input>
</div>
<vxe-table
ref="tableRef"
v-bind="defaultAttribute"
:data="tableData2.filter((item: any) => {
return item.name.includes(filterText)
})"
height="400px"
style="width: 100%"
>
<vxe-column type="checkbox" width="60"></vxe-column>
<vxe-column field="name" title="工程名称"></vxe-column>
</vxe-table>
</div>
<!-- 设备部分 -->
<div class="dialog-section">
<div class="section-header">
<span>设备列表</span>
<el-input
maxlength="32"
show-word-limit
v-model.trim="deviceFilterText"
placeholder="搜索设备"
clearable
class="search-input"
>
<template #prefix>
<Icon name="el-icon-Search" style="font-size: 16px" />
</template>
</el-input>
</div>
<vxe-table
ref="deviceTableRef"
v-bind="defaultAttribute"
:data="deviceTableData2.filter((item: any) => {
return item.name.includes(deviceFilterText)
})"
height="400px"
style="width: 100%"
>
<vxe-column type="checkbox" width="60"></vxe-column>
<vxe-column field="name" title="设备名称"></vxe-column>
</vxe-table>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="addData"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
defineOptions({ defineOptions({
name: 'govern/officialUser/index' name: 'govern/officialUser/index'
}) })
import { defaultAttribute } from '@/components/table/defaultAttribute' import { defaultAttribute } from '@/components/table/defaultAttribute'
import OfficialUserTree from '@/components/tree/govern/officialUserTree.vue' import OfficialUserTree from '@/components/tree/govern/officialUserTree.vue'
import { mainHeight } from '@/utils/layout' import { mainHeight } from '@/utils/layout'
import { queryRunPortableDevByUseId ,queryUnlinkEngineeringByUseId} from '@/api/cs-device-boot/user' import { queryRunPortableDevByUseId, queryUnlinkEngineeringByUseId } from '@/api/cs-device-boot/user'
import { add, removeUserDev, queryDevByUseId } from '@/api/cs-system-boot/official' import {
add,
removeUserDev,
queryDevByUseId,
addUserDevices,
queryDeviceIdsByUserId
} from '@/api/cs-system-boot/official'
import { ref } from 'vue' import { ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
const pageHeight = mainHeight(60) const pageHeight = mainHeight(60)
const pageHeight1 = mainHeight(125)
const loading = ref(true) const loading = ref(true)
const user: any = ref({}) const user: any = ref({})
@@ -148,185 +166,222 @@ const filterText = ref('') // 工程搜索文本
const deviceFilterText = ref('') // 设备搜索文本 const deviceFilterText = ref('') // 设备搜索文本
const tableRef = ref() const tableRef = ref()
const deviceTableRef = ref() // 设备表格引用 const deviceTableRef = ref() // 设备表格引用
const defaultProps = {
children: 'devList',
label: 'name'
}
const selectUser = (e: any) => { const selectUser = (e: any) => {
user.value = e if (e == undefined) return (loading.value = false)
loading.value = true user.value = e
loading.value = true
queryDevByUseId({ userId: e.id }).then((engineeringRes) => { queryDevByUseId({ userId: e.id })
loading.value = false .then(engineeringRes => {
tableData.value = engineeringRes.data.engineeringList || [] loading.value = false
deviceTableData.value = engineeringRes.data.portableDevList || []
}).catch(() => { tableData.value = engineeringRes.data.engineeringList || []
loading.value = false deviceTableData.value = engineeringRes.data.portableDevList || []
}) queryDeviceIdsByUserId({
userId: user.value.id
}).then(res => {
treeRef.value!.setCheckedKeys(res.data, false)
})
})
.catch(() => {
loading.value = false
})
} }
const getEnginnerDev = () => { const getEnginnerDev = () => {
filterText.value = '' filterText.value = ''
deviceFilterText.value = '' deviceFilterText.value = ''
// 同时获取工程和设备数据 // 同时获取工程和设备数据
Promise.all([ Promise.all([
queryUnlinkEngineeringByUseId({ userId: user.value.id }), queryUnlinkEngineeringByUseId({ userId: user.value.id }),
queryRunPortableDevByUseId({ userId: user.value.id }) queryRunPortableDevByUseId({ userId: user.value.id })
]).then(([engineeringRes, deviceRes]) => { ]).then(([engineeringRes, deviceRes]) => {
tableData2.value = engineeringRes.data || [] tableData2.value = engineeringRes.data || []
deviceTableData2.value = deviceRes.data || [] deviceTableData2.value = deviceRes.data || []
dialogVisible.value = true dialogVisible.value = true
setTimeout(() => { setTimeout(() => {
tableRef.value?.clearCheckboxRow() tableRef.value?.clearCheckboxRow()
deviceTableRef.value?.clearCheckboxRow() deviceTableRef.value?.clearCheckboxRow()
}, 0) }, 0)
}) })
} }
const deleteEngineering = (row: any) => { const deleteEngineering = (row: any) => {
ElMessageBox.confirm('是否移出该工程?', '请确认', { ElMessageBox.confirm('是否移出该工程?', '请确认', {
confirmButtonText: '确认', confirmButtonText: '确认',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
// 只移除工程,设备列表为空 // 只移除工程,设备列表为空
const form = { const form = {
engineeringList: [row.id], // 要移除的工程ID engineeringList: [row.id], // 要移除的工程ID
portableDevList: [], // 设备列表为空 portableDevList: [], // 设备列表为空
userId: user.value.id userId: user.value.id
} }
removeUserDev(form).then((res: any) => { removeUserDev(form).then((res: any) => {
ElMessage.success(res.message) ElMessage.success(res.message)
selectUser(user.value) selectUser(user.value)
})
}) })
})
} }
// 删除设备方法 // 删除设备方法
const deleteDevice = (row: any) => { const deleteDevice = (row: any) => {
console.log('删除设备', row) console.log('删除设备', row)
ElMessageBox.confirm('是否移出该设备?', '请确认', { ElMessageBox.confirm('是否移出该设备?', '请确认', {
confirmButtonText: '确认', confirmButtonText: '确认',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
// 只移除设备,工程列表为空 // 只移除设备,工程列表为空
const form = { const form = {
engineeringList: [], // 工程列表为空 engineeringList: [], // 工程列表为空
portableDevList: [row.id], // 要移除的设备ID portableDevList: [row.id], // 要移除的设备ID
userId: user.value.id userId: user.value.id
} }
removeUserDev(form).then((res: any) => { removeUserDev(form).then((res: any) => {
ElMessage.success(res.message) ElMessage.success(res.message)
selectUser(user.value) selectUser(user.value)
})
}) })
})
} }
const addData = () => { const addData = () => {
const selectedEngineers = tableRef.value.getCheckboxRecords() const selectedEngineers = tableRef.value.getCheckboxRecords()
const selectedDevices = deviceTableRef.value.getCheckboxRecords() const selectedDevices = deviceTableRef.value.getCheckboxRecords()
// 如果没有选择任何项,则提示 // 如果没有选择任何项,则提示
if (selectedEngineers.length === 0 && selectedDevices.length === 0) { if (selectedEngineers.length === 0 && selectedDevices.length === 0) {
ElMessage.warning('请至少选择一项工程或设备') ElMessage.warning('请至少选择一项工程或设备')
return return
} }
// 构造请求参数对象 // 构造请求参数对象
const form = { const form = {
engineeringList: [] as any[], engineeringList: [] as any[],
portableDevList: [] as any[], portableDevList: [] as any[],
userId: user.value.id userId: user.value.id
} }
// // 处理已有的工程数据 // // 处理已有的工程数据
// tableData.value.forEach((item: any) => { // tableData.value.forEach((item: any) => {
// form.engineeringList.push(item.id) // form.engineeringList.push(item.id)
// }) // })
// // 处理已有的设备数据 // // 处理已有的设备数据
// deviceTableData.value.forEach((item: any) => { // deviceTableData.value.forEach((item: any) => {
// form.portableDevList.push(item.id) // form.portableDevList.push(item.id)
// }) // })
// 添加新选择的工程 // 添加新选择的工程
selectedEngineers.forEach((item: any) => { selectedEngineers.forEach((item: any) => {
form.engineeringList.push( item.id) form.engineeringList.push(item.id)
}) })
// 添加新选择的设备 // 添加新选择的设备
selectedDevices.forEach((item: any) => { selectedDevices.forEach((item: any) => {
form.portableDevList.push(item.id) form.portableDevList.push(item.id)
}) })
// 发送请求 // 发送请求
add(form).then((res: any) => { add(form).then((res: any) => {
ElMessage.success(res.message) ElMessage.success(res.message)
selectUser(user.value) selectUser(user.value)
dialogVisible.value = false dialogVisible.value = false
}) })
}
const treeRef = ref()
// 保存短信配置
const saveConfiguration = () => {
// treeRef.value!.getCheckedNodes(false, false)
let deviceIds = treeRef
.value!.getCheckedNodes(false, false)
.filter((item: any) => item.devList == null)
.map((item: any) => item.id)
if (deviceIds.length == 0) {
return ElMessage.warning('请选择设备!')
}
addUserDevices({
deviceIds: deviceIds,
userId: user.value.id
}).then((res: any) => {
return ElMessage.success('短信配置成功!')
})
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.device-manage { .device-manage {
display: flex;
&-right {
overflow: hidden;
flex: 1;
padding: 10px 10px 10px 0;
display: flex; display: flex;
flex-direction: column;
.el-descriptions__header { &-right {
height: 27px; overflow: hidden;
display: flex;
justify-content: flex-end; /* 靠右显示 */
align-items: center;
}
.tables-container {
flex: 1;
display: flex;
flex-direction: column;
.table-wrapper {
flex: 1; flex: 1;
min-height: 0; padding: 10px 10px 10px 0;
display: flex;
flex-direction: column;
&:first-child { .el-descriptions__header {
margin-bottom: 10px; // height: 27px;
display: flex;
justify-content: flex-end;
/* 靠右显示 */
align-items: center;
}
.tables-container {
flex: 1;
display: flex;
flex-direction: column;
.table-wrapper {
flex: 1;
min-height: 0;
&:first-child {
// margin-bottom: 10px;
}
}
} }
}
} }
} }
:deep(.el-descriptions__header) {
margin-bottom: 10px !important;
} }
.dialog-content { .dialog-content {
display: flex; display: flex;
gap: 20px; gap: 20px;
.dialog-section { .dialog-section {
flex: 1; flex: 1;
.section-header { .section-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 10px; margin-bottom: 10px;
span { span {
font-weight: bold; font-weight: bold;
font-size: 16px; font-size: 16px;
} }
.search-input { .search-input {
width: 200px; width: 200px;
} }
}
} }
}
} }
</style> .border {
border: 1px solid var(--el-border-color);
}
</style>

View File

@@ -1,399 +1,399 @@
<template> <template>
<div> <div>
<div :style="{ width: menuCollapse ? '40px' : '280px' }" style=" overflow: hidden; height: 100%"> <div :style="{ width: menuCollapse ? '40px' : '280px' }" style=" overflow: hidden; height: 100%">
<Icon v-show="menuCollapse" @click="onMenuCollapse" :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" <Icon v-show="menuCollapse" @click="onMenuCollapse" :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
:class="menuCollapse ? 'unfold' : ''" size="18" class="fold ml10 mt20 menu-collapse" :class="menuCollapse ? 'unfold' : ''" size="18" class="fold ml10 mt20 menu-collapse"
style="cursor: pointer" /> style="cursor: pointer" />
<div class="cn-tree" :style="{ opacity: menuCollapse ? 0 : 1, display: menuCollapse ? 'none' : '' }"> <div class="cn-tree" :style="{ opacity: menuCollapse ? 0 : 1, display: menuCollapse ? 'none' : '' }">
<div style="display: flex; align-items: center" class="mb10"> <div style="display: flex; align-items: center" class="mb10">
<el-input maxlength="32" show-word-limit v-model.trim="filterText" placeholder="请输入内容" clearable> <el-input maxlength="32" v-model.trim="filterText" placeholder="请输入内容" clearable>
<template #prefix> <template #prefix>
<Icon name="el-icon-Search" style="font-size: 16px" /> <Icon name="el-icon-Search" style="font-size: 16px" />
</template> </template>
</el-input> </el-input>
<el-tooltip placement="bottom" :hide-after="0"> <el-tooltip placement="bottom" :hide-after="0">
<template #content> <template #content>
<span>新增方案</span> <span>新增方案</span>
</template> </template>
<Icon name="el-icon-Plus" size="18" class="fold ml10 menu-collapse" style="cursor: pointer" <Icon name="el-icon-Plus" size="18" class="fold ml10 menu-collapse" style="cursor: pointer"
@click="onAdd" /> @click="onAdd" />
</el-tooltip> </el-tooltip>
<Icon @click="onMenuCollapse" :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'" <!-- <Icon @click="onMenuCollapse" :name="menuCollapse ? 'el-icon-Expand' : 'el-icon-Fold'"
:class="menuCollapse ? 'unfold' : ''" size="18" class="fold ml10 menu-collapse" :class="menuCollapse ? 'unfold' : ''" size="18" class="fold ml10 menu-collapse"
style="cursor: pointer" /> style="cursor: pointer" /> -->
</div> </div>
<el-tree style="flex: 1; overflow: auto" :props="defaultProps" highlight-current <el-tree style="flex: 1; overflow: auto" :props="defaultProps" highlight-current
:filter-node-method="filterNode" node-key="id" v-bind="$attrs" default-expand-all :data="tree" :filter-node-method="filterNode" node-key="id" v-bind="$attrs" default-expand-all :data="tree"
ref="treRef" @node-click="clickNode" :expand-on-click-node="false"> ref="treRef" @node-click="clickNode" :expand-on-click-node="false">
<template #default="{ node, data }"> <template #default="{ node, data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<div class="left"> <div class="left">
<Icon :name="data.icon" style="font-size: 16px" :style="{ color: data.color }" <Icon :name="data.icon" style="font-size: 16px" :style="{ color: data.color }"
v-if="data.icon" /> v-if="data.icon" />
<span>{{ node.label }}</span> <span>{{ node.label }}</span>
</div> </div>
<div class="right"> <div class="right">
<a :style="{ marginRight: '0.5rem' }" v-if="data?.children"> <a :style="{ marginRight: '0.5rem' }" v-if="data?.children">
<el-icon :style="{ color: '#0000FF' }"> <el-icon :style="{ color: '#0000FF' }">
<el-tooltip placement="bottom" :hide-after="0"> <el-tooltip placement="bottom" :hide-after="0">
<template #content> <template #content>
<span>新增测试项</span> <span>新增测试项</span>
</template> </template>
<Plus @click.stop="add(node, data)" /> <Plus @click.stop="add(node, data)" />
</el-tooltip> </el-tooltip>
</el-icon> </el-icon>
</a> </a>
<a :style="{ marginRight: '0.5rem' }" v-else> <a :style="{ marginRight: '0.5rem' }" v-else>
<el-icon :style="{ color: '#0000FF' }"> <el-icon :style="{ color: '#0000FF' }">
<el-tooltip placement="bottom" :hide-after="0"> <el-tooltip placement="bottom" :hide-after="0">
<template #content> <template #content>
<span>数据绑定</span> <span>数据绑定</span>
</template> </template>
<SetUp @click.stop="bind(node, data)" /> <SetUp @click.stop="bind(node, data)" />
</el-tooltip> </el-tooltip>
</el-icon> </el-icon>
</a> </a>
<a :style="{ marginRight: '0.5rem' }"> <a :style="{ marginRight: '0.5rem' }">
<el-icon :style="{ color: '#0000FF' }"> <el-icon :style="{ color: '#0000FF' }">
<el-tooltip placement="bottom" :hide-after="0"> <el-tooltip placement="bottom" :hide-after="0">
<template #content> <template #content>
<span> {{ data.pid ? '修改测试项' : ' 修改测试方案' }}</span> <span> {{ data.pid ? '修改测试项' : ' 修改测试方案' }}</span>
</template> </template>
<Edit @click.stop="edit(node, data)" /> <Edit @click.stop="edit(node, data)" />
</el-tooltip> </el-tooltip>
</el-icon> </el-icon>
</a> </a>
<a :style="{ marginRight: '0.5rem' }"> <a :style="{ marginRight: '0.5rem' }">
<el-icon :style="{ color: '#DA3434' }"> <el-icon :style="{ color: '#DA3434' }">
<Delete @click.stop="del(node, data)" /> <Delete @click.stop="del(node, data)" />
</el-icon> </el-icon>
</a> </a>
</div> </div>
</span> </span>
</template> </template>
</el-tree> </el-tree>
</div> </div>
</div> </div>
<popup ref="dialogRef" @onSubmit="getTreeList"></popup> <popup ref="dialogRef" @onSubmit="getTreeList"></popup>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, nextTick, watch, defineProps, defineEmits } from 'vue' import { ref, nextTick, watch, defineProps, defineEmits } from 'vue'
import { getSchemeTree, getTestRecordInfo } from '@/api/cs-device-boot/planData' import { getSchemeTree, getTestRecordInfo } from '@/api/cs-device-boot/planData'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import useCurrentInstance from '@/utils/useCurrentInstance' import useCurrentInstance from '@/utils/useCurrentInstance'
import { ElTree } from 'element-plus' import { ElTree } from 'element-plus'
import { Plus, Edit, Delete, SetUp } from '@element-plus/icons-vue' import { Plus, Edit, Delete, SetUp } from '@element-plus/icons-vue'
import { delRecord } from '@/api/cs-device-boot/planData' import { delRecord } from '@/api/cs-device-boot/planData'
import popup from './popup.vue' import popup from './popup.vue'
import { getDeviceList } from '@/api/cs-device-boot/planData' import { getDeviceList } from '@/api/cs-device-boot/planData'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
defineOptions({ defineOptions({
name: 'govern/schemeTree' name: 'govern/schemeTree'
}) })
interface Props { interface Props {
width?: string width?: string
canExpand?: boolean canExpand?: boolean
} }
const visible1 = ref(false) const visible1 = ref(false)
const visible2 = ref(false) const visible2 = ref(false)
const visible3 = ref(false) const visible3 = ref(false)
const visible4 = ref(false) const visible4 = ref(false)
const { proxy } = useCurrentInstance() const { proxy } = useCurrentInstance()
const menuCollapse = ref(false) const menuCollapse = ref(false)
const filterText = ref('') const filterText = ref('')
const treeRef = ref<InstanceType<typeof ElTree>>() const treeRef = ref<InstanceType<typeof ElTree>>()
watch(filterText, val => { watch(filterText, val => {
treRef.value!.filter(val) treRef.value!.filter(val)
}) })
const onMenuCollapse = () => { const onMenuCollapse = () => {
menuCollapse.value = !menuCollapse.value menuCollapse.value = !menuCollapse.value
proxy.eventBus.emit('cnTreeCollapse', menuCollapse) proxy.eventBus.emit('cnTreeCollapse', menuCollapse)
} }
const filterNode = (value: string, data: any, node: any) => { const filterNode = (value: string, data: any, node: any) => {
if (!value) return true if (!value) return true
// return data.name.includes(value) // return data.name.includes(value)
if (data.name) { if (data.name) {
return chooseNode(value, data, node) return chooseNode(value, data, node)
} }
} }
const chooseNode = (value: string, data: any, node: any) => { const chooseNode = (value: string, data: any, node: any) => {
if (data.name.indexOf(value) !== -1) { if (data.name.indexOf(value) !== -1) {
return true return true
} }
const level = node.level const level = node.level
// 如果传入的节点本身就是一级节点就不用校验了 // 如果传入的节点本身就是一级节点就不用校验了
if (level === 1) { if (level === 1) {
return false return false
} }
// 先取当前节点的父节点 // 先取当前节点的父节点
let parentData = node.parent let parentData = node.parent
// 遍历当前节点的父节点 // 遍历当前节点的父节点
let index = 0 let index = 0
while (index < level - 1) { while (index < level - 1) {
// 如果匹配到直接返回此处name值是中文字符enName是英文字符。判断匹配中英文过滤 // 如果匹配到直接返回此处name值是中文字符enName是英文字符。判断匹配中英文过滤
if (parentData.data.name.indexOf(value) !== -1) { if (parentData.data.name.indexOf(value) !== -1) {
return true return true
} }
// 否则的话再往上一层做匹配 // 否则的话再往上一层做匹配
parentData = parentData.parent parentData = parentData.parent
index++ index++
} }
// 没匹配到返回false // 没匹配到返回false
return false return false
} }
// 新增方案 // 新增方案
const onAdd = () => { const onAdd = () => {
emit('onAdd') emit('onAdd')
} }
// 绑定数据 // 绑定数据
const bind = (node: any, data: any) => { const bind = (node: any, data: any) => {
emit('bind', data) emit('bind', data)
} }
/** 树形结构数据 */ /** 树形结构数据 */
const defaultProps = { const defaultProps = {
children: 'children', children: 'children',
label: 'name', label: 'name',
value: 'id' value: 'id'
} }
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
showCheckbox?: boolean showCheckbox?: boolean
defaultCheckedKeys?: any defaultCheckedKeys?: any
}>(), }>(),
{ {
showCheckbox: false, showCheckbox: false,
defaultCheckedKeys: [] defaultCheckedKeys: []
} }
) )
const emit = defineEmits(['init', 'checkChange', 'nodeChange', 'editNode', 'getChart', 'onAdd', 'bind']) const emit = defineEmits(['init', 'checkChange', 'nodeChange', 'editNode', 'getChart', 'onAdd', 'bind'])
const config = useConfig() const config = useConfig()
const tree = ref() const tree = ref()
const treRef = ref() const treRef = ref()
const id: any = ref(null) const id: any = ref(null)
const treeData = ref({}) const treeData = ref({})
//获取方案树形数据 //获取方案树形数据
const getTreeList = () => { const getTreeList = () => {
getSchemeTree().then(res => { getSchemeTree().then(res => {
let arr: any[] = [] let arr: any[] = []
res.data.forEach((item: any) => { res.data.forEach((item: any) => {
item.icon = 'el-icon-Menu' item.icon = 'el-icon-Menu'
item.color = config.getColorVal('elementUiPrimary') item.color = config.getColorVal('elementUiPrimary')
item?.children.forEach((item2: any) => { item?.children.forEach((item2: any) => {
item2.icon = 'el-icon-Document' item2.icon = 'el-icon-Document'
item2.color = config.getColorVal('elementUiPrimary') item2.color = config.getColorVal('elementUiPrimary')
arr.push(item2) arr.push(item2)
}) })
}) })
tree.value = res.data tree.value = res.data
nextTick(() => { nextTick(() => {
if (arr.length) { if (arr.length) {
treRef.value.setCurrentKey(id.value || arr[0].id) treRef.value.setCurrentKey(id.value || arr[0].id)
let list = id.value ? arr.find((item: any) => item.id == id.value) : arr[0] let list = id.value ? arr.find((item: any) => item.id == id.value) : arr[0]
// 注册父组件事件 // 注册父组件事件
emit('init', { emit('init', {
level: 2, level: 2,
...list ...list
}) })
} else { } else {
emit('init') emit('init')
} }
}) })
}) })
} }
getTreeList() getTreeList()
const dialogRef = ref() const dialogRef = ref()
const handleOpen = (val: any, id: any) => { const handleOpen = (val: any, id: any) => {
dialogRef.value.open(val, id) dialogRef.value.open(val, id)
} }
//方案id //方案id
const planId: any = ref('') const planId: any = ref('')
//测试项id //测试项id
const monitorId: any = ref('') const monitorId: any = ref('')
const planData: any = ref({}) const planData: any = ref({})
const getPlanData = (row: any) => { const getPlanData = (row: any) => {
planData.value = {} planData.value = {}
planData.value = JSON.parse(JSON.stringify(row)) planData.value = JSON.parse(JSON.stringify(row))
} }
/** 添加树节点 */ /** 添加树节点 */
// 0 新增方案 1 修改方案 2 新增测试项 3 修改测试项 4 设备信息 // 0 新增方案 1 修改方案 2 新增测试项 3 修改测试项 4 设备信息
const add = (node: any, data: any) => { const add = (node: any, data: any) => {
planId.value = data.id planId.value = data.id
//添加测试项 //添加测试项
if (data?.children) { if (data?.children) {
dialogRef.value.detailsType('tree') dialogRef.value.detailsType('tree')
handleOpen(2, planId.value) handleOpen(2, planId.value)
} }
} }
/** 编辑树节点 */ /** 编辑树节点 */
const edit = async (node: Node, data: any) => { const edit = async (node: Node, data: any) => {
planId.value = data.id planId.value = data.id
await getTestRecordInfo(planId.value) await getTestRecordInfo(planId.value)
.then(res => { .then(res => {
//修改方案 //修改方案
if (data?.children) { if (data?.children) {
dialogRef.value.detailsType('') dialogRef.value.detailsType('')
dialogRef.value.details(res.data) dialogRef.value.details(res.data)
handleOpen(1, planId.value) handleOpen(1, planId.value)
} }
//修改测试项 //修改测试项
else { else {
monitorId.value = data.id monitorId.value = data.id
dialogRef.value.detailsType('tree') dialogRef.value.detailsType('tree')
dialogRef.value.details(res.data.records[0]) dialogRef.value.details(res.data.records[0])
handleOpen(3, planId.value) handleOpen(3, planId.value)
} }
}) })
.catch(e => { }) .catch(e => { })
} }
/** 删除树节点 */ /** 删除树节点 */
const del = async (node: Node, data: any) => { const del = async (node: Node, data: any) => {
let titleList = '' let titleList = ''
planId.value = data.id planId.value = data.id
await getDeviceList({ await getDeviceList({
id: data.id, id: data.id,
isTrueFlag: 1 isTrueFlag: 1
}).then(res => { }).then(res => {
if (res.data.length > 0) { if (res.data.length > 0) {
titleList = '已绑定数据_' titleList = '已绑定数据_'
} }
}) })
//删除方案/测试项 //删除方案/测试项
ElMessageBox.confirm(titleList + '是否确认删除?', { ElMessageBox.confirm(titleList + '是否确认删除?', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}) })
.then(() => { .then(() => {
delRecord({ id: data.id }).then((res: any) => { delRecord({ id: data.id }).then((res: any) => {
if (res.code == 'A0000') { if (res.code == 'A0000') {
ElMessage.success('删除成功') ElMessage.success('删除成功')
id.value = null id.value = null
getTreeList() getTreeList()
} }
}) })
}) })
.catch(() => { .catch(() => {
ElMessage({ ElMessage({
type: 'info', type: 'info',
message: '已取消' message: '已取消'
}) })
}) })
} }
//取消删除 //取消删除
const cancelDel = () => { } const cancelDel = () => { }
const clickNode = (e: anyObj) => { const clickNode = (e: anyObj) => {
e?.children ? (planId.value = e.id) : (planId.value = e.pid) e?.children ? (planId.value = e.id) : (planId.value = e.pid)
id.value = e.id id.value = e.id
emit('nodeChange', e) emit('nodeChange', e)
} }
const setCheckedNode = (e: anyObj) => { const setCheckedNode = (e: anyObj) => {
// console.log('🚀 ~ setCheckedNode ~ e:', e) // console.log('🚀 ~ setCheckedNode ~ e:', e)
id.value = e id.value = e
treRef.value.setCurrentKey(e) treRef.value.setCurrentKey(e)
} }
watch( watch(
() => planData.value, () => planData.value,
(val, oldVal) => { (val, oldVal) => {
if (val && dialogRef.value) { if (val && dialogRef.value) {
const obj = JSON.parse(JSON.stringify(val)) const obj = JSON.parse(JSON.stringify(val))
obj.records = [ obj.records = [
val.records.find(item => { val.records.find(item => {
return item.id == monitorId.value return item.id == monitorId.value
}) })
] ]
dialogRef.value.details(obj) dialogRef.value.details(obj)
} }
}, },
{ {
immediate: true, immediate: true,
deep: true deep: true
} }
) )
defineExpose({ treeRef, getPlanData, getTreeList, setCheckedNode }) defineExpose({ treeRef, getPlanData, getTreeList, setCheckedNode })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.cn-tree { .cn-tree {
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
box-sizing: border-box; box-sizing: border-box;
padding: 10px; padding: 10px;
height: 100%; height: 100%;
width: 100%; width: 100%;
:deep(.el-tree) { :deep(.el-tree) {
border: 1px solid var(--el-border-color); border: 1px solid var(--el-border-color);
} }
:deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content) { :deep(.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content) {
background-color: var(--el-color-primary-light-7); background-color: var(--el-color-primary-light-7);
} }
.menu-collapse { .menu-collapse {
color: var(--el-color-primary); color: var(--el-color-primary);
} }
} }
.ml10 { .ml10 {
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
.add_plan { .add_plan {
width: 100%; width: 100%;
height: 40px; height: 40px;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
} }
.custom-tree-node { .custom-tree-node {
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
.left, .left,
.right { .right {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.left { .left {
span { span {
margin-left: 2px; margin-left: 2px;
} }
} }
.right { .right {
a { a {
margin-left: 2px; margin-left: 2px;
} }
} }
} }
</style> </style>

View File

@@ -95,10 +95,11 @@ const tableStore = new TableStore({
}, },
click: async row => { click: async row => {
row.loading1 = true row.loading1 = true
loading.value = true
isWaveCharts.value = true
await analyseWave(row.id) await analyseWave(row.id)
.then(res => { .then(res => {
loading.value = true
isWaveCharts.value = true
row.loading1 = false row.loading1 = false
if (res != undefined) { if (res != undefined) {
boxoList.value = row boxoList.value = row

View File

@@ -6,12 +6,13 @@
:default-checked-keys="defaultCheckedKeys" :default-checked-keys="defaultCheckedKeys"
@checkChange="checkChange" @checkChange="checkChange"
:height="35" :height="35"
:engineering="true"
></DeviceTree> ></DeviceTree>
<div class="device-manage-right" :style="{ height: pageHeight.height }"> <div class="device-manage-right" :style="{ height: pageHeight.height }">
<vxe-table v-bind="defaultAttribute" :data="tableData" height="auto" style="width: 100%"> <vxe-table v-bind="defaultAttribute" :data="tableData" height="auto" style="width: 100%">
<vxe-column field="enginerName" title="工程名称"></vxe-column> <vxe-column field="enginerName" title="工程名称"></vxe-column>
<vxe-column field="projectName" title="项目名称"></vxe-column> <vxe-column field="projectName" title="项目名称"></vxe-column>
<vxe-column field="deviceName" title="装置名称"></vxe-column> <vxe-column field="deviceName" title="设备名称"></vxe-column>
</vxe-table> </vxe-table>
</div> </div>
</div> </div>
@@ -27,6 +28,7 @@ import { mainHeight } from '@/utils/layout'
import { getVisitorConfig, updateVisitorConfig } from '@/api/cs-device-boot/user' import { getVisitorConfig, updateVisitorConfig } from '@/api/cs-device-boot/user'
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { debounce } from 'lodash-es'
const pageHeight = mainHeight(60) const pageHeight = mainHeight(60)
const loading = ref(true) const loading = ref(true)
const defaultCheckedKeys: any = ref([]) const defaultCheckedKeys: any = ref([])
@@ -34,6 +36,7 @@ const tableData = ref([])
const treeRef = ref(null) const treeRef = ref(null)
const ignoreCheckChange = ref(false) const ignoreCheckChange = ref(false)
const checkChange = (data: any) => { const checkChange = (data: any) => {
if (data == undefined) return (loading.value = false)
if (data.data.pName == '便携式设备') { if (data.data.pName == '便携式设备') {
if (ignoreCheckChange.value) { if (ignoreCheckChange.value) {
ignoreCheckChange.value = false // 清除标记,不影响后续正常触发 ignoreCheckChange.value = false // 清除标记,不影响后续正常触发
@@ -54,7 +57,7 @@ const checkChange = (data: any) => {
updateVisitorConfigs() updateVisitorConfigs()
} }
} }
const updateVisitorConfigs = () => { const updateVisitorConfigs = debounce(() => {
const result = Array.from(new Set(defaultCheckedKeys.value)) const result = Array.from(new Set(defaultCheckedKeys.value))
updateVisitorConfig( updateVisitorConfig(
result.map(item => { result.map(item => {
@@ -67,7 +70,7 @@ const updateVisitorConfigs = () => {
getVisitorConfigs() getVisitorConfigs()
} }
}) })
} }, 500)
const getVisitorConfigs = () => { const getVisitorConfigs = () => {
getVisitorConfig().then((res: any) => { getVisitorConfig().then((res: any) => {
if (res.code === 'A0000') { if (res.code === 'A0000') {

View File

@@ -3,47 +3,22 @@
<TableHeader> <TableHeader>
<template #select> <template #select>
<el-form-item label="数据分类"> <el-form-item label="数据分类">
<el-select <el-select v-model.trim="tableStore.table.params.dataType" multiple filterable collapse-tags
v-model.trim="tableStore.table.params.dataType" clearable placeholder="请选择数据分类">
multiple <el-option v-for="item in DataTypeSelect" :key="item.id" :label="item.name"
filterable :value="item.id"></el-option>
collapse-tags
clearable
placeholder="请选择数据分类"
>
<el-option
v-for="item in DataTypeSelect"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="数据存储"> <el-form-item label="数据存储">
<el-select <el-select v-model.trim="tableStore.table.params.classId" multiple filterable collapse-tags
v-model.trim="tableStore.table.params.classId" clearable placeholder="请选择数据存储">
multiple <el-option v-for="item in DataSelect" :key="item.id" :label="item.name"
filterable :value="item.id"></el-option>
collapse-tags
clearable
placeholder="请选择数据存储"
>
<el-option
v-for="item in DataSelect"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="关键字筛选"> <el-form-item label="关键字筛选">
<el-input <el-input maxlength="32" show-word-limit v-model.trim="tableStore.table.params.searchValue"
maxlength="32" placeholder="数据名称、别名、展示名称" clearable></el-input>
show-word-limit
v-model.trim="tableStore.table.params.searchValue"
placeholder="数据名称、别名、展示名称"
clearable
></el-input>
</el-form-item> </el-form-item>
</template> </template>
<template #operation> <template #operation>
@@ -99,8 +74,9 @@ const tableStore = new TableStore({
? row.cellValue == '/' ? row.cellValue == '/'
? '/' ? '/'
: row.cellValue == 'M' : row.cellValue == 'M'
? '无相别' ? '/' : row.cellValue == 'T'
: row.cellValue + '' ? '/'
: row.cellValue + '相'
: '/' : '/'
} }
}, },
@@ -114,7 +90,7 @@ const tableStore = new TableStore({
title: '操作', fixed: 'right', title: '操作', fixed: 'right',
align: 'center', align: 'center',
width: '180', width: '180',
render: 'buttons', render: 'buttons',
buttons: [ buttons: [
{ {

View File

@@ -1,10 +1,10 @@
<template> <template>
<el-dialog class="cn-operate-dialog" draggable v-model.trim="dialogVisible" :title="title" @close="emit('close')"> <el-dialog class="cn-operate-dialog" draggable v-model.trim="dialogVisible" width="900px" :title="title" @close="emit('close')">
<el-scrollbar> <el-scrollbar>
<div style="padding-left: 50px"> <div style="padding-left: 50px">
<el-divider content-position="center">基础数据</el-divider> <el-divider content-position="center">基础数据</el-divider>
</div> </div>
<el-form :model="form" label-width="140px" ref="formRef" class="form-two" :rules="rules"> <el-form :model="form" label-width="130px" ref="formRef" class="form-two" :rules="rules">
<el-form-item label="数据分类:" prop="dataType"> <el-form-item label="数据分类:" prop="dataType">
<el-select v-model.trim="form.dataType" filterable clearable placeholder="请选择数据分类"> <el-select v-model.trim="form.dataType" filterable clearable placeholder="请选择数据分类">
<el-option <el-option
@@ -71,7 +71,7 @@
></el-input> ></el-input>
</el-form-item> </el-form-item>
<el-form-item label="开始结束次数:"> <el-form-item label="开始结束次数:">
<el-slider v-model.trim="form.harm" range show-stops :max="50" style="width: 95%" /> <el-slider v-model.trim="form.harm" range :max="50" style="width: 90%" />
</el-form-item> </el-form-item>
<el-form-item label="统计方法:"> <el-form-item label="统计方法:">
<el-select <el-select
@@ -155,7 +155,7 @@
<div style="padding-left: 50px"> <div style="padding-left: 50px">
<el-divider content-position="center">拓展数据</el-divider> <el-divider content-position="center">拓展数据</el-divider>
</div> </div>
<el-form class="form-two" :model="form" label-width="140px" ref="formRef2"> <el-form class="form-two" :model="form" label-width="130px" ref="formRef2">
<el-form-item label="告警码(缺省值):"> <el-form-item label="告警码(缺省值):">
<el-input <el-input
maxlength="32" maxlength="32"
@@ -282,7 +282,7 @@ const phaseSelect = [
id: 'C' id: 'C'
}, },
{ {
name: 'T相', name: '无相别',
id: 'T' id: 'T'
}, },
{ {
@@ -297,10 +297,10 @@ const phaseSelect = [
name: 'CA相', name: 'CA相',
id: 'CA' id: 'CA'
}, },
{ // {
name: '无相别', // name: '无相别',
id: 'M' // id: 'M'
}, // },
] ]
const StatMethodSelect = [ const StatMethodSelect = [
{ {

Some files were not shown because too many files have changed in this diff Show More