map/ui/dasadmin/src/views/backend/malfunction/index.vue

975 lines
32 KiB
Vue
Raw Normal View History

<template>
<div class="malfunction">
<el-container class="container">
<el-aside class="aside">
<div class="searchTree">
<el-input v-model="searchTreeValue" clearable placeholder="搜索" :suffix-icon="Search" @change="searchTree"> </el-input>
</div>
<div class="treeMain">
<el-scrollbar>
<el-tree
ref="treeRef"
highlight-current
:data="treeData"
:props="defaultProps"
node-key="id"
:default-expanded-keys="defaultExpandKeys"
@node-click="handleNodeClick"
></el-tree>
</el-scrollbar>
</div>
</el-aside>
<el-container class="mainContainer">
<el-header class="header">
<div class="searchFileName">
<span>文件名称</span>
<el-input v-model="searchData.fileName"></el-input>
</div>
<div class="searchDate">
<span>日期</span>
<el-date-picker
v-model="searchData.date"
type="monthrange"
unlink-panels
start-placeholder="开始时间"
end-placeholder="结束时间"
format="YYYY-MM"
date-format="YYYY/MM"
:shortcuts="shortcuts"
@change="getListForAirBlower"
></el-date-picker>
</div>
<div class="btnPart">
<el-button v-show="curTreeData.id !== '0'" :icon="Setting" @click="openConfigDialog">配置</el-button>
<!--
<el-button type="primary" :icon="Download">下载</el-button>
<el-button type="primary" plain :icon="Delete">删除</el-button>
-->
</div>
</el-header>
<el-main class="main">
<el-tabs v-model="activeName" @tab-change="checkTab" class="tabs">
<el-tab-pane label="故障录波文件" name="malFunction" class="tabPane">
<div class="tableMain">
<el-table :data="tableData" class="tableClass">
<el-table-column type="selection" width="55" />
<el-table-column label="文件名称" prop="name" align="center"></el-table-column>
<el-table-column label="修改时间" prop="lastModified" align="center" width="250"></el-table-column>
<el-table-column label="文件大小" prop="size" align="center" width="120"></el-table-column>
<el-table-column label="操作" align="center" width="140">
<template #default="scope">
<div class="tableBtnPart">
<el-button text type="primary" @click="readFile(scope.row)">查看</el-button>
<el-button text type="danger" @click="downloadFile(scope.row)">下载</el-button>
</div>
</template>
</el-table-column>
</el-table>
</div>
<div class="footerPart">
<el-pagination
v-model:current-page="pageSetting.current"
v-model:page-size="pageSetting.pageSize"
:total="pageSetting.total"
:page-sizes="pageSetting.pageSizes"
background
:pager-count="7"
layout="total,prev, pager, next, jumper,sizes"
@change="changePageSetting"
></el-pagination>
</div>
</el-tab-pane>
<el-tab-pane label="运行日志文件" name="logManage" class="tabPane">
<div class="tableMain">
<el-table :data="logTableData" class="tableClass">
<el-table-column type="selection" width="55" />
<el-table-column label="文件名称" prop="name" align="center"></el-table-column>
<el-table-column label="修改时间" prop="lastModified" align="center" width="250"></el-table-column>
<el-table-column label="文件大小" prop="size" align="center" width="120"></el-table-column>
<el-table-column label="操作" align="center" width="140">
<template #default="scope">
<div class="tableBtnPart">
<el-button text type="primary" @click="readFile(scope.row)">查看</el-button>
<el-button text type="danger" @click="downloadFile(scope.row)">下载</el-button>
</div>
</template>
</el-table-column>
</el-table>
</div>
<div class="footerPart">
<el-pagination
v-model:current-page="logPageSetting.current"
v-model:page-size="logPageSetting.pageSize"
:total="logPageSetting.total"
:page-sizes="logPageSetting.pageSizes"
background
:pager-count="7"
layout="total,prev, pager, next, jumper,sizes"
@change="changePageSetting"
></el-pagination>
</div>
</el-tab-pane>
</el-tabs>
</el-main>
</el-container>
</el-container>
</div>
<el-dialog v-model="configDialogVisible" title="配置信息" width="500" @close="closeConfigDialog">
<el-form ref="configFormRef" :model="configFormData" label-width="120" :rules="configFormRules">
<el-form-item prop="timeFormat" label="时间格式">
<el-input v-model="configFormData.timeFormat"></el-input>
</el-form-item>
<el-form-item prop="delimiter" label="分隔符">
<el-input v-model="configFormData.delimiter"></el-input>
</el-form-item>
<el-form-item prop="validStartLine" label="有效起始行数">
<el-input-number v-model="configFormData.validStartLine"></el-input-number>
</el-form-item>
</el-form>
<template #footer>
<el-button type="primary" @click="submitConfig">保存</el-button>
<el-button @click="cancelConfig">取消</el-button>
</template>
</el-dialog>
<el-dialog v-model="previewFileDialogVisible" title="数据分析" width="1000" @close="closePreviewFileDialog">
<el-row class="previewFilePart">
<el-col :span="8" class="colPart leftCol" v-loading="previewTreeLoading">
<div class="search">
<el-input
v-model="previewSearchTreeVal"
@change="previewSearchTree"
class="previewSearchTree"
:suffix-icon="Search"
clearable
></el-input>
<el-button @click="clearPreviewTree">清空</el-button>
</div>
<el-scrollbar class="previewTreeScrollbar">
<el-tree
ref="previewTreeRef"
:data="previewTreeData"
:props="previewTreeProps"
show-checkbox
node-key="key"
@check="handleCheckChange"
></el-tree>
<!--
@node-click="previewNodeClick"
-->
</el-scrollbar>
</el-col>
<el-col :span="16" class="colPart">
<div class="title">图表</div>
<div class="chartPart">
<div ref="previewChartRef" class="previewChart"></div>
</div>
</el-col>
</el-row>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted, nextTick, onUnmounted } from 'vue'
import { dayjs, ElMessage, FormInstance, TreeInstance } from 'element-plus'
import { Search, Setting } from '@element-plus/icons-vue'
import { getMalFunctionListReq, setConfigReq, previewFileReq, downloadFileReq, getFileKeyEnumsReq } from '/@/api/backend/malfunction/request'
import {
getLogRecordListReq,
setConfigReq as setLogConfigReq,
previewFileReq as previewLogFileReq,
downloadFileReq as downloadLogFileReq,
getFileKeyEnumsReq as getLogFileKeyEnumsReq,
} from '/@/api/backend/logRecord/request'
import { equipList } from '/@/api/backend/temperature/request'
import * as echarts from 'echarts'
import { tableItemData } from './type'
const defaultProps = {
children: 'children',
label: 'label',
}
const treeRef = ref()
let originTreeChildData: {
label: string
id: string
code: string
children?: any[]
}[] = []
const treeData = ref<
{
label: string
id: string
code: string
options: any
children?: any[]
}[]
>([
{
label: '风机列表',
id: '0',
code: '0',
options: {},
children: [],
},
])
const defaultExpandKeys: string[] = ['0']
const searchTreeValue = ref('')
const searchTree = (val: string) => {
if (val == '') {
treeData.value[0].children = originTreeChildData
return
}
const reg = new RegExp(val, 'i')
treeData.value[0].children =
originTreeChildData.filter((item: any) => {
return reg.test(item.label)
}) ?? []
}
const curTreeData = ref<{
label: string
id: string
code: string
model: string
madeinFactory: string
options: any
children?: any[]
}>({
label: '风机列表',
id: '0',
code: '0',
model: '',
madeinFactory: '',
options: {},
})
const handleNodeClick = (target: { label: string; id: string; code: string; model: string; madeinFactory: string; options: any }) => {
curTreeData.value = target
if (target.id === '0') return
getListForAirBlower()
}
const getTreeDataList = () => {
return new Promise((resolve) => {
equipList({ objectType: 10002 }).then((res) => {
const data = res.data.map((item: any) => {
return {
label: item.name,
code: item.code,
id: item.id,
model: item.model,
madeinFactory: item.madeinFactory,
options: item.options ? JSON.parse(item.options) : {},
}
})
originTreeChildData = data
treeData.value[0].children = data
resolve(data[0])
})
})
}
const searchData = reactive<{ fileName: ''; date: Date[] }>({
fileName: '',
date: [dayjs().toDate(), dayjs().toDate()],
})
const shortcuts = [
{
text: '本月',
value: () => {
const start = dayjs().startOf('month').toDate()
const end = dayjs().endOf('month').toDate()
return [start, end]
},
},
{
text: '上月',
value: () => {
const start = dayjs().subtract(1, 'month').toDate()
const end = dayjs().subtract(1, 'month').toDate()
return [start, end]
},
},
{
text: '近三月',
value: () => {
const start = dayjs().subtract(2, 'month').toDate()
const end = dayjs().toDate()
return [start, end]
},
},
]
let originTableData = ref<tableItemData[]>([])
const tableData = computed(() => {
let data = originTableData.value
if (searchData.fileName) {
const reg = new RegExp(searchData.fileName, 'i')
data = data.filter((item) => reg.test(item.name))
}
const res = data.slice((pageSetting.current - 1) * pageSetting.pageSize, pageSetting.current * pageSetting.pageSize)
pageSetting.total = res.length
return res
})
const pageSetting = reactive({
current: 1,
pageSize: 20,
total: 0,
pageSizes: [20, 50, 100],
})
const changePageSetting = () => {}
const getListForAirBlower = () => {
console.log(activeName.value)
const data = {
deviceCode: curTreeData.value.code,
startTime: dayjs(searchData.date[0]).format('YYYY-MM'),
endTime: dayjs(searchData.date[1]).format('YYYY-MM'),
}
if (activeName.value === 'malFunction') {
getMalFunctionListReq(data).then((res) => {
if (res.success) {
pageSetting.total = res.data.length
originTableData.value = res.data
}
})
} else if (activeName.value === 'logManage') {
getLogRecordListReq(data).then((res) => {
if (res.success) {
logPageSetting.total = res.data.length
logOriginTableData.value = res.data
}
})
}
}
const configDialogVisible = ref(false)
const configFormRef = ref<FormInstance>()
const closeConfigDialog = () => {
configFormRef.value?.resetFields()
}
const configFormData = reactive({
timeFormat: '',
delimiter: '',
validStartLine: 4,
})
const openConfigDialog = () => {
configDialogVisible.value = true
if (activeName.value === 'malFunction') {
configFormData.delimiter = curTreeData.value.options.fdrFormat.delimiter
configFormData.timeFormat = curTreeData.value.options.fdrFormat.timeFormat
configFormData.validStartLine = curTreeData.value.options.fdrFormat.validStartLine
} else if (activeName.value === 'logManage') {
configFormData.delimiter = curTreeData.value.options.plcFormat.delimiter
configFormData.timeFormat = curTreeData.value.options.plcFormat.timeFormat
configFormData.validStartLine = curTreeData.value.options.plcFormat.validStartLine
}
}
const configFormRules = {
timeFormat: [
{
required: true,
message: '请输入时间格式',
trigger: 'blur',
},
],
delimiter: [
{
required: true,
message: '请输入分隔符',
trigger: 'blur',
},
],
validStartLine: [
{
required: true,
message: '请输入有效起始行',
trigger: 'change',
},
],
}
const submitConfig = () => {
configFormRef.value?.validate((valid) => {
if (valid) {
setAirBlowerConfig()
.then(() => {
ElMessage.success('配置成功')
configDialogVisible.value = false
})
.then(() => {
return getTreeDataList()
})
.then(() => {
nextTick(() => {
treeRef.value?.setCurrentKey(curTreeData.value.id, false)
curTreeData.value = treeRef.value?.getCurrentNode()
})
})
.catch((err) => {
ElMessage.error(err)
})
}
})
}
const cancelConfig = () => {
configDialogVisible.value = false
configFormRef.value?.resetFields()
}
const setAirBlowerConfig = () => {
return new Promise((resolve) => {
const stringify = JSON.stringify({ fdrFormat: configFormData })
if (activeName.value === 'malFunction') {
setConfigReq({
id: curTreeData.value.id,
options: stringify,
})
.then(() => {
resolve(true)
})
.catch(() => {
throw '配置失败'
})
} else if (activeName.value === 'logManage') {
setLogConfigReq({
id: curTreeData.value.id,
options: stringify,
})
.then(() => {
resolve(true)
})
.catch(() => {
throw '配置失败'
})
}
})
}
const previewFileDialogVisible = ref(false)
const readFile = (data: tableItemData) => {
previewFileDialogVisible.value = true
getFileKeyEnum().finally(() => {
getFileData(data.path).then((res) => {
previewChartData = res.data
const attrName = Object.keys(res.data)
const data: any = []
attrName.forEach((item) => {
if (item === 'TimeStamp') {
previewChartData.TimeStamp = previewChartData.TimeStamp.map((item: any) => {
return dayjs(item).format('YYYY-MM-DD HH:mm:ss.SSS')
})
} else if (item === 'TimeStampUTC') {
previewChartData.TimeStamp = previewChartData.TimeStampUTC.map((item: any) => {
return dayjs(item).format('YYYY-MM-DD HH:mm:ss.SSS')
})
} else {
data.push({
label: fileKeyEnums[item],
key: item,
isLeaf: true,
})
}
})
previewTreeData.value = data
originPreviewTreeData = data
previewTreeLoading.value = false
})
})
}
const downloadFile = (data: tableItemData) => {
runDownLoad(data)
.then((res) => {
const url = window.URL.createObjectURL(res.data)
const a = document.createElement('a')
a.style.display = 'none'
a.href = url
a.download = data.name
document.body.appendChild(a)
a.click()
window.URL.revokeObjectURL(url)
document.body.removeChild(a)
})
.catch((err) => {
ElMessage.error(err)
})
}
const runDownLoad = (data: tableItemData) => {
if (activeName.value === 'malFunction') {
return downloadFileReq({ url: data.path })
} else if (activeName.value === 'logManage') {
return downloadLogFileReq({ url: data.path })
}
return Promise.reject()
}
const closePreviewFileDialog = () => {
previewTreeData.value = []
previewChartData = []
selectPreviewTree = []
curSeries = []
previewChartInstance && previewChartInstance.clear()
}
const previewTreeLoading = ref(false)
const previewTreeRef = ref<TreeInstance>()
const previewTreeData = ref()
let originPreviewTreeData: any = []
const previewTreeProps = {
children: 'children',
label: 'label',
isLeaf: 'isLeaf',
}
const previewSearchTreeVal = ref('')
const previewSearchTree = (val: string) => {
nextTick(() => {
previewTreeRef.value?.setCheckedKeys(selectPreviewTree)
})
if (val === '') {
previewTreeData.value = originPreviewTreeData
return
}
const regex = new RegExp(val, 'i')
const filterData = originPreviewTreeData.filter((item: any) => regex.test(item.label))
previewTreeData.value = filterData
}
const clearPreviewTree = () => {
nextTick(() => {
selectPreviewTree = []
previewTreeRef.value?.setCheckedKeys(selectPreviewTree)
})
previewChartInstance && previewChartInstance.clear()
}
let selectPreviewTree: any = []
const handleCheckChange = (data: any, state: { checkedKeys: string[] }) => {
previewChartInstance && previewChartInstance.clear()
selectPreviewTree = state.checkedKeys
if (selectPreviewTree.length > 0) {
initPreviewChart()
} else {
previewChartInstance && previewChartInstance.clear()
}
}
// const previewNodeClick = (data: any) => {
// if (selectPreviewTree.includes(data.key)) {
// previewTreeRef.value?.setChecked(data.key, false, false)
// }else{
// previewTreeRef.value?.setChecked(data.key, true, false)
// }
// }
let previewChartData: any = {}
let curSeries: any = []
let previewChartInstance: any = null
const previewChartRef = ref()
const getRandomDarkColor = () => {
let r = Math.floor(Math.random() * 200) // 限制在0到127之间以生成较深的颜色
let g = Math.floor(Math.random() * 200)
let b = Math.floor(Math.random() * 200)
let color = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`
return color
}
const createSeriresData = () => {
const seriesData: any = []
const curAttr = curSeries.map((item: any) => item.id)
selectPreviewTree.forEach((item: string) => {
if (curAttr.includes(item)) {
const seriesItem = curSeries.find((cur: any) => cur.id === item)
seriesData.push(seriesItem)
} else {
const color = getRandomDarkColor()
const createName = fileKeyEnums[item] + (fileKeyUnit[item] ? `${fileKeyUnit[item] ?? ''}` : '')
const data = {
id: item,
name: createName,
type: 'line',
barWidth: 20,
itemStyle: {
color: color,
barBorderRadius: 2,
},
smooth: 0.6,
symbol: 'none',
data: previewChartData[item],
}
seriesData.push(data)
}
})
curSeries = seriesData
return seriesData
}
const initPreviewChart = () => {
const chart = previewChartInstance ?? echarts.init(previewChartRef.value)
const series = createSeriresData()
const option = {
grid: {
top: 50,
right: 23,
bottom: 50,
left: 18,
containLabel: true,
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line',
},
},
xAxis: {
type: 'category',
axisLine: {
show: true,
lineStyle: {
color: '#dadada',
width: 1,
type: 'solid',
},
},
axisLabel: {
//x轴文字的配置
show: true,
color: '#4E5969',
interval: 'auto',
formatter: function (value: any) {
return value.slice(11)
},
//rotate: 45
},
splitLine: {
//分割线配置
show: false,
lineStyle: {
color: '#999999',
},
},
data: previewChartData['TimeStamp'],
},
yAxis: [
{
type: 'value',
name: '历史值',
nameTextStyle: {
color: '#4E5969',
},
axisLine: {
show: false,
lineStyle: {
color: '#dadada',
width: 0,
type: 'solid',
},
},
axisLabel: {
//x轴文字的配置
show: true,
color: '#4E5969',
},
axisTick: { show: false },
splitLine: {
interval: 50,
lineStyle: {
type: 'dashed',
color: '#dadada',
},
},
},
],
dataZoom: [
{
type: 'inside',
start: 0,
end: 100,
},
{
start: 0,
end: 100,
},
],
legend: {
data: [],
textStyle: {
color: '#73767a',
},
},
series: series,
}
chart.setOption(option)
previewChartInstance = chart
}
const getFileData = (url: string) => {
previewTreeLoading.value = true
if (activeName.value === 'malFunction') {
return previewFileReq({
deviceCode: curTreeData.value.code,
url: url,
})
} else if (activeName.value === 'logManage') {
return previewLogFileReq({
deviceCode: curTreeData.value.code,
url: url,
})
}
return Promise.reject()
}
const fileKeyEnums: any = {}
const fileKeyUnit: any = {}
const getFileKeyEnum = () => {
return new Promise((resolve, reject) => {
if (activeName.value === 'malFunction') {
getFileKeyEnumsReq({
madeinfactory: curTreeData.value.madeinFactory,
model: curTreeData.value.model,
})
.then((res) => {
if (res.success) {
res.data.forEach((item: any) => {
fileKeyEnums[item.variable] = item?.description ?? item.variable
fileKeyUnit[item.variable] = item?.unit ?? ''
})
resolve(true)
}
})
.catch(() => {
reject(false)
})
} else if (activeName.value === 'logManage') {
getLogFileKeyEnumsReq({
madeinfactory: curTreeData.value.madeinFactory,
model: curTreeData.value.model,
})
.then((res) => {
if (res.success) {
res.data.forEach((item: any) => {
fileKeyEnums[item.variable] = item?.description ?? item.variable
fileKeyUnit[item.variable] = item?.unit ?? ''
})
resolve(true)
}
})
.catch(() => {
reject(false)
})
}
})
}
let logOriginTableData = ref<tableItemData[]>([])
const logTableData = computed(() => {
let data = logOriginTableData.value
if (searchData.fileName) {
const reg = new RegExp(searchData.fileName, 'i')
data = data.filter((item) => reg.test(item.name))
}
const res = data.slice((logPageSetting.current - 1) * logPageSetting.pageSize, logPageSetting.current * logPageSetting.pageSize)
logPageSetting.total = res.length
return res
})
const logPageSetting = reactive({
current: 1,
pageSize: 20,
total: 0,
pageSizes: [20, 50, 100],
})
const activeName = ref<'malFunction' | 'logManage'>('malFunction')
const checkTab = () => {
getListForAirBlower()
}
onMounted(() => {
getTreeDataList().then((data: any) => {
treeRef.value && treeRef.value.setCurrentKey(data.id, true)
curTreeData.value = data
getListForAirBlower()
})
})
onUnmounted(() => {
previewChartInstance && previewChartInstance.dispose()
previewChartInstance = null
})
</script>
<style lang="scss" scoped>
.malfunction {
width: 100%;
height: 100%;
.container {
width: 100%;
height: 100%;
.aside {
width: 260px;
height: 100%;
padding: 0 20px;
border-right: 1px solid #eaebed;
.searchTree {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 80px;
.el-input {
width: 220px;
height: 40px;
}
}
.treeMain {
width: 100%;
height: calc(100% - 80px);
}
}
.mainContainer {
width: calc(100% - 260px);
height: 100%;
.header {
display: flex;
align-items: center;
width: 100%;
height: 80px;
.searchFileName {
display: flex;
align-items: center;
width: 320px;
height: 40px;
span {
width: 70px;
}
.el-input {
width: 220px;
height: 40px;
}
}
.searchDate {
display: flex;
align-items: center;
width: 400px;
height: 40px;
span {
flex-shrink: 0;
width: 45px;
}
:deep(.el-date-editor) {
width: 400px;
height: 40px;
}
}
.btnPart {
margin-left: auto;
width: 360px;
text-align: right;
.el-button {
margin: 0 10px;
width: 100px;
height: 40px;
}
}
}
.main {
width: 100%;
height: calc(100% - 80px);
.tabs {
width: 100%;
height: 100%;
:deep(.el-tabs__content) {
width: 100%;
height: calc(100% - 60px);
.tabPane {
width: 100%;
height: 100%;
}
}
.tableMain {
width: 100%;
height: calc(100% - 32px);
.tableClass {
width: 100%;
height: 100%;
.tableBtnPart {
display: flex;
justify-content: center;
.el-button {
margin: 0;
}
}
}
}
.footerPart {
display: flex;
justify-content: right;
}
}
}
}
}
}
.el-input-number {
width: 100%;
}
.previewFilePart {
width: 100%;
height: 500px;
.colPart {
width: 100%;
height: 100%;
.title {
padding-left: 20px;
width: 100%;
height: 30px;
font-size: 20px;
}
.previewTreeScrollbar {
height: calc(100% - 40px);
}
.chartPart {
position: relative;
width: 100%;
height: calc(100% - 40px);
.label {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100px;
font-size: 20px;
}
.previewChart {
width: 100%;
height: 100%;
}
}
}
.leftCol {
border-right: 1px solid #eaebed;
.search {
display: flex;
}
.previewSearchTree {
width: calc(100% - 80px);
height: 40px;
padding: 0 10px 10px 20px;
}
.el-button {
margin: 0 10px 10px 0;
width: 70px;
height: 30px;
}
}
}
</style>