绘制 运维版本管理页面

This commit is contained in:
guanj
2026-03-19 11:29:26 +08:00
parent 9f1fbf93cd
commit a30379ab01
11 changed files with 930 additions and 479 deletions

View File

@@ -35,6 +35,15 @@ export const adminBaseRoute = {
title: pageTitle('router.supplementaryRecruitment') title: pageTitle('router.supplementaryRecruitment')
} }
}, },
{
// 在线补召
path: '/versionMaintenance',
name: 'versionMaintenance',
component: () => import('@/views/govern/manage/programVersion/comp/versionMaintenance.vue'),
meta: {
title: pageTitle('router.versionMaintenance')
}
},
{ {
path: 'cockpit', path: 'cockpit',
name: '项目管理', name: '项目管理',

View File

@@ -12,7 +12,7 @@
</pane> </pane>
<pane style="background: #fff"> <pane style="background: #fff">
<div class="device-manage-right"> <div class="device-manage-right">
<el-form :inline="true" class="demo-form-inline" style="height: 42px"> <el-form :inline="true" class="demo-form-inline" style="height: 42px">
<el-form-item style="position: relative; z-index: 2"> <el-form-item style="position: relative; z-index: 2">
<el-button icon="el-icon-Plus" type="primary" @click="add" v-if="nodeLevel != 4"> <el-button icon="el-icon-Plus" type="primary" @click="add" v-if="nodeLevel != 4">
{{ {{
@@ -2154,6 +2154,8 @@ const onsubmit = async () => {
break break
} }
} }
} else {
ElMessage.warning('请检查表单数据是否填写完整!')
} }
}) })
} }

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

@@ -59,13 +59,13 @@
placeholder="填写特殊类型(不填默认通用类型)" placeholder="填写特殊类型(不填默认通用类型)"
></el-input> ></el-input>
</el-form-item> </el-form-item>
<el-form-item label="版本协议:" prop="versionAgreement"> <el-form-item label="协议版本:" prop="versionAgreement">
<el-input <el-input
maxlength="32" maxlength="32"
show-word-limit show-word-limit
v-model.trim="form.versionAgreement" v-model.trim="form.versionAgreement"
autocomplete="off" autocomplete="off"
placeholder="请输入版本协议" placeholder="请输入协议版本"
></el-input> ></el-input>
</el-form-item> </el-form-item>
<el-form-item label="版本日期:" prop="versionDate"> <el-form-item label="版本日期:" prop="versionDate">
@@ -158,7 +158,7 @@ const rules = {
devType: [{ required: true, message: '请选择装置型号', trigger: 'change' }], devType: [{ required: true, message: '请选择装置型号', trigger: 'change' }],
versionNo: [{ required: true, message: '请输入版本号', trigger: 'blur' }], versionNo: [{ required: true, message: '请输入版本号', trigger: 'blur' }],
versionType: [{ required: true, message: '请输入版本类型', trigger: 'blur' }], versionType: [{ required: true, message: '请输入版本类型', trigger: 'blur' }],
versionAgreement: [{ required: true, message: '请输入版本协议', trigger: 'blur' }], versionAgreement: [{ required: true, message: '请输入协议版本', trigger: 'blur' }],
versionDate: [{ required: true, message: '请输入版本日期', trigger: 'blur' }], versionDate: [{ required: true, message: '请输入版本日期', trigger: 'blur' }],
description: [{ required: true, message: '请输入描述', trigger: 'blur' }], description: [{ required: true, message: '请输入描述', trigger: 'blur' }],
crcInfo: [{ required: true, message: '请输入CRC校验', trigger: 'blur' }], crcInfo: [{ required: true, message: '请输入CRC校验', trigger: 'blur' }],

View File

@@ -3,7 +3,7 @@
<TableHeader ref="tableHeaderRef" :showReset="false" showExport> <TableHeader ref="tableHeaderRef" :showReset="false" showExport>
<template #select> <template #select>
<el-form-item label="装置型号:"> <el-form-item label="装置型号:">
<el-select v-model.trim="tableStore.table.params.devType" placeholder="请选择装置型号" clearable> <el-select v-model.trim="tableStore.table.params.devType" filterable placeholder="请选择装置型号" clearable>
<el-option <el-option
v-for="item in DevTypeOptions" v-for="item in DevTypeOptions"
:key="item.id" :key="item.id"
@@ -57,7 +57,7 @@ const tableStore = new TableStore({
}, },
{ title: '装置型号', field: 'devTypeName' ,minWidth: '100'}, { title: '装置型号', field: 'devTypeName' ,minWidth: '100'},
{ title: '版本号', field: 'versionNo' ,minWidth: '100'}, { title: '版本号', field: 'versionNo' ,minWidth: '100'},
{ title: '版本协议', field: 'versionAgreement' ,minWidth: '100'}, { title: '协议版本', field: 'versionAgreement' ,minWidth: '100'},
{ title: '版本日期', field: 'versionDate' ,minWidth: '100'}, { title: '版本日期', field: 'versionDate' ,minWidth: '100'},
{ title: '归档日期', field: 'updateTime',minWidth: '150' }, { title: '归档日期', field: 'updateTime',minWidth: '150' },
{ title: '描述', field: 'description',minWidth: '200' }, { title: '描述', field: 'description',minWidth: '200' },

View File

@@ -0,0 +1,157 @@
<template>
<el-dialog class="cn-operate-dialog" width="600px" v-model.trim="dialogVisible" :title="title">
<el-form :model="form" class="form-one" label-width="auto" ref="formRef" :rules="rules">
<el-form-item label="版本号" prop="substationName">
<el-input v-model.trim="form.substationName" placeholder="请输入版本号"></el-input>
</el-form-item>
<el-form-item label="协议版本" prop="name">
<el-input v-model.trim="form.name" placeholder="请输入协议版本"></el-input>
</el-form-item>
<el-form-item label="版本日期" prop="name">
<el-input v-model.trim="form.name" placeholder="请输入版本日期"></el-input>
</el-form-item>
<el-form-item label="装置系列" prop="loadType">
<el-select v-model.trim="form.loadType" filterable clearable placeholder="请选择装置系列">
<el-option
v-for="item in DataTypeSelect"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="版本类型" prop="name">
<el-input v-model.trim="form.name" placeholder="请输入版本类型"></el-input>
</el-form-item>
<el-form-item label="描述" prop="name">
<el-input
maxlength="32"
:autosize="{ minRows: 2, maxRows: 4 }"
type="textarea"
show-word-limit
v-model.trim="form.remark"
placeholder="请输入描述"
></el-input>
</el-form-item>
<el-form-item label="上传升级文件" prop="name">
<el-upload
:limit="1"
accept=".bin"
:auto-upload="false"
:on-change="fileChange"
:on-exceed="fileExceed"
:on-remove="fileRemove"
:file-list="fileList"
>
<el-button type="primary">点击上传</el-button>
</el-upload>
</el-form-item>
<el-form-item label="CRC校验" prop="name">
<el-input v-model.trim="form.name" placeholder="请输入版本日期"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submit">确认</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, inject } from 'vue'
import { reactive } from 'vue'
import { ElMessage } from 'element-plus'
import TableStore from '@/utils/tableStore'
import { queryByCode, queryCsDictTree } from '@/api/system-boot/dictTree'
import { saveUser, updateUser } from '@/api/cs-device-boot/sensitiveLoadMange'
import { useDictData } from '@/stores/dictData'
const TypeOptions = ref()
const dictData = useDictData()
const DataTypeSelect = dictData.getBasicData('Interference_Source')
const tableStore = inject('tableStore') as TableStore
const fileList: any = ref([])
const formRef = ref()
const form = reactive<any>({
loadType: [],
substationName: null,
installedCapacity: null,
name: null,
userAgreementCapacity: null,
id: null,
sort: 100
})
const rules = {
substationName: [{ required: true, message: '请输入所属厂站名称', trigger: 'blur' }],
name: [{ required: true, message: '请输入敏感用户名称', trigger: 'blur' }],
loadType: [{ required: true, message: '请输入请选择敏感负荷类型', trigger: 'blur' }],
installedCapacity: [{ required: true, message: '请输入用户协议容量', trigger: 'blur' }],
userAgreementCapacity: [{ required: true, message: '请输入装机容量', trigger: 'blur' }],
sort: [{ required: true, message: '请输入排序', trigger: 'blur' }]
}
const dialogVisible = ref(false)
const title = ref('新增')
const open = (text: string, data?: anyObj) => {
formRef.value?.resetFields()
title.value = text
dialogVisible.value = true
if (data) {
for (let key in form) {
form[key] = data[key]
}
} else {
for (let key in form) {
form[key] = null
form.sort = 100
}
}
}
const fileRemove = (e: any) => {
form.file = null
fileList.value = []
}
const fileChange = (e: any) => {
if (!beforeUpload(e.raw)) return
form.file = e.raw
fileList.value = [e.raw]
}
const fileExceed = (e: any) => {
ElMessage.error('只能上传一个文件')
}
// 处理上传前检查
const beforeUpload = (file: any) => {
const isWord = file.name.endsWith('.bin')
if (!isWord) {
ElMessage.error('请上传(.bin)格式文件!')
fileList.value = []
return false
}
// 校验通过后允许上传,交由 http-request 处理
return true
}
const submit = () => {
formRef.value.validate(async (valid: boolean) => {
if (valid) {
if (form.id) {
await updateUser(form)
} else {
await saveUser(form)
}
ElMessage.success('操作成功')
tableStore.index()
dialogVisible.value = false
}
})
}
defineExpose({ open })
</script>

View File

@@ -0,0 +1,171 @@
<template>
<div class="default-main">
<TableHeader ref="tableHeaderRef" :showReset="false" showExport>
<template #select>
<el-form-item label="关键字筛选">
<el-input v-model="tableStore.table.params.keywords" clearable placeholder="请输入关键字" />
</el-form-item>
<el-form-item label="装置系列">
<el-select
v-model.trim="tableStore.table.params.devType"
filterable
placeholder="请选择装置系列"
clearable
>
<el-option
v-for="item in DevTypeOptions"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
</template>
<template #operation>
<el-button :icon="Plus" type="primary" @click="add">新增</el-button>
<el-button :icon="Back" @click="go(-1)">返回</el-button>
</template>
</TableHeader>
<Table ref="tableRef" />
<!-- 新增 -->
<Form ref="formRef" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide } 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 { queryByCode, queryByid } from '@/api/system-boot/dictTree'
import { useDictData } from '@/stores/dictData'
import { Plus, EditPen } from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'
import { Back, Setting, Search } from '@element-plus/icons-vue'
import Form from './form.vue'
defineOptions({
name: 'govern/manage/programVersion'
})
const dictData = useDictData()
const DevTypeOptions = ref()
const tableHeaderRef = ref()
const { push, go } = useRouter()
const formRef = ref()
const tableStore = new TableStore({
url: '/cs-device-boot/edData/queryEdDataPage',
method: 'POST',
column: [
{
field: 'index',
title: '序号',
width: '80',
formatter: (row: any) => {
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{ title: '装置系列', field: 'devTypeName', minWidth: '100' },
{ title: '版本号', field: 'versionNo', minWidth: '100' },
{ title: '协议版本', field: 'versionAgreement', minWidth: '100' },
{ title: '版本日期', field: 'versionDate', minWidth: '100' },
{ title: '修改人员', field: 'versionDate', minWidth: '100' },
{ title: '修改日期', field: 'versionDate', minWidth: '100' },
{ title: '描述', field: 'versionDate', minWidth: '100' },
{ title: '版本类型', field: 'versionDate', minWidth: '100' },
{
title: '状态',
render: 'switch',
width: 100,
field: 'usageStatus',
activeText: '启用',
inactiveText: '停用',
inactiveValue: '0',
activeValue: '1',
onChangeField: (row: any, value: any) => {
// console.log("🚀 ~ row:", row)
// ElMessageBox.prompt('二次校验密码确认', '', {
// confirmButtonText: '确认',
// cancelButtonText: '取消',
// customClass: 'customInput',
// inputType: 'text',
// beforeClose: (action, instance, done) => {
// if (action === 'confirm') {
// if (instance.inputValue == null) {
// return ElMessage.warning('请输入密码')
// } else if (instance.inputValue?.length > 32) {
// return ElMessage.warning(
// '密码长度不能超过32位,当前密码长度为' + instance.inputValue.length
// )
// } else {
// done()
// }
// } else {
// done()
// }
// }
// }).then(({ value }) => {
// passwordConfirm(value).then(res => {
// editEquipmentDelivery({
// ...row,
// status: row.status == 5 ? 1 : row.status == 6 ? 2 : row.status,
// usageStatus: row.usageStatus == 1 ? 0 : 1
// }).then(res => {
// ElMessage.success(row.usageStatus == 1 ? '设备停用成功!' : '设备启用成功!')
// tableStore.index()
// })
// })
// })
}
},
{
title: '操作',
fixed: 'right',
align: 'center',
width: '120',
render: 'buttons',
buttons: [
{
name: 'productSetting',
title: '修改',
type: 'primary',
icon: 'el-icon-EditPen',
render: 'basicButton'
},
{
name: 'productSetting',
title: '删除',
type: 'primary',
icon: 'el-icon-EditPen',
render: 'basicButton'
}
]
}
],
loadCallback: () => {}
})
queryByCode('Device_Type').then(res => {
const id = res.data.id
queryByid(id).then(res1 => {
res1.data.map((item: any, index: any) => {
if (item.pid == id) {
res1.data.splice(index, 1)
}
})
DevTypeOptions.value = res1.data
})
})
tableStore.table.params.devType = ''
provide('tableStore', tableStore)
const add = () => {
formRef.value.open('新增版本')
}
onMounted(() => {
tableHeaderRef.value.onComSearch()
})
</script>

View File

@@ -0,0 +1,182 @@
<template>
<div class="default-main">
<TableHeader ref="tableHeaderRef" :showReset="false" showExport>
<template #select>
<el-form-item label="关键字筛选">
<el-input v-model="tableStore.table.params.keywords" clearable placeholder="请输入关键字" />
</el-form-item>
<el-form-item label="通讯状态">
<el-select
v-model.trim="tableStore.table.params.devType"
filterable
placeholder="请选择通讯状态"
clearable
>
<el-option
v-for="item in DevTypeOptions"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="装置型号">
<el-select
v-model.trim="tableStore.table.params.devType"
filterable
placeholder="请选择装置型号"
clearable
>
<el-option
v-for="item in DevTypeOptions"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="装置系列">
<el-select
v-model.trim="tableStore.table.params.devType"
filterable
placeholder="请选择装置系列"
clearable
>
<el-option
v-for="item in DevTypeOptions"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="升级装置筛选">
<el-select
v-model.trim="tableStore.table.params.devType"
filterable
placeholder="请选择升级装置筛选"
clearable
>
<el-option
v-for="item in DevTypeOptions"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
</template>
<template #operation>
<el-button :icon="EditPen" type="primary" class="ml10" @click="maintenance">终端版本维护</el-button>
<el-button :icon="Share" type="primary" class="ml10">批量升级</el-button>
</template>
</TableHeader>
<Table ref="tableRef" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide } 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 { queryByCode, queryByid } from '@/api/system-boot/dictTree'
import { useDictData } from '@/stores/dictData'
import { Share, EditPen } from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'
defineOptions({
name: 'govern/manage/programVersion'
})
const dictData = useDictData()
const DevTypeOptions = ref()
const tableHeaderRef = ref()
const { push, options, currentRoute } = useRouter()
const tableStore = new TableStore({
url: '/cs-device-boot/edData/queryEdDataPage',
method: 'POST',
column: [
{ type: 'checkbox', width: '60' },
{
field: 'index',
title: '序号',
width: '80',
formatter: (row: any) => {
return (tableStore.table.params.pageNum - 1) * tableStore.table.params.pageSize + row.rowIndex + 1
}
},
{ title: '装置名称', field: 'devTypeName', minWidth: '100' },
{ title: '版本号', field: 'versionNo', minWidth: '100' },
{ title: '协议版本', field: 'versionAgreement', minWidth: '100' },
{ title: '版本日期', field: 'versionDate', minWidth: '100' },
{ title: '装置系列', field: 'versionDate', minWidth: '100' },
{ title: '装置型号', field: 'versionDate', minWidth: '100' },
{ title: '供电公司', field: 'versionDate', minWidth: '100' },
{ title: '变电站', field: 'versionDate', minWidth: '100' },
{
title: '状态',
field: 'status',
render: 'tag',
minWidth: '80',
custom: {
0: 'error',
1: 'success'
},
replaceValue: {
0: '禁用',
1: '启用'
}
},
{ title: '更新时间', field: 'versionDate', minWidth: '100' },
{ title: '修改人员', field: 'versionDate', minWidth: '100' },
{
title: '操作',
fixed: 'right',
align: 'center',
width: '120',
render: 'buttons',
buttons: [
{
name: 'productSetting',
title: '升级',
type: 'primary',
icon: 'el-icon-EditPen',
render: 'basicButton'
},
{
name: 'productSetting',
title: '升级日志',
type: 'primary',
icon: 'el-icon-EditPen',
render: 'basicButton'
}
]
}
],
loadCallback: () => {}
})
queryByCode('Device_Type').then(res => {
const id = res.data.id
queryByid(id).then(res1 => {
res1.data.map((item: any, index: any) => {
if (item.pid == id) {
res1.data.splice(index, 1)
}
})
DevTypeOptions.value = res1.data
})
})
// 版本维护
const maintenance = () => {
push({
path: '/versionMaintenance'
})
}
tableStore.table.params.devType = ''
provide('tableStore', tableStore)
onMounted(() => {
tableHeaderRef.value.onComSearch()
})
</script>

View File

@@ -1,67 +1,134 @@
<template>
<div class="default-main manage-realTime" :style="{ height: pageHeight.height }">
<DeviceTree @node-click="nodeClick" @init="" @deviceTypeChange=""></DeviceTree>
<div class="manage-realTime-right">
<div class="time-container">
<div>
<div>系统时间{{ realTime }}</div>
<div>终端时间{{ deviceTime }}</div>
</div>
<el-button icon="el-icon-RefreshLeft" type="primary" @click="synchronizeTime">对时</el-button>
</div>
<div :style="echartHeight" class="pl10 pr10">
<MyEchart :options="echartsData" />
</div>
<div class="pl10 pr10">
<el-table :data="tableData" border stripe :show-header="false" class="custom-table">
<el-table-column prop="label1" />
<el-table-column prop="label2" />
<el-table-column prop="value1" />
<el-table-column prop="label3" />
<el-table-column prop="value2" />
<el-table-column prop="label4" />
<el-table-column prop="value3" />
</el-table>
</div>
</div>
</div>
</template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive } from 'vue' import { ref, reactive } from 'vue'
import { mainHeight } from '@/utils/layout' import { mainHeight } from '@/utils/layout'
import DeviceTree from '@/components/tree/govern/deviceTree.vue' import DeviceTree from '@/components/tree/govern/deviceTree.vue'
import { queryByCode, queryCsDictTree } from '@/api/system-boot/dictTree'
import { getDevCapacity } from '@/api/cs-device-boot/capacity'
import { queryCommonStatisticalByTime } from '@/api/cs-harmonic-boot/stable'
import DatePicker from '@/components/form/datePicker/index.vue'
import MyEchart from '@/components/echarts/MyEchart.vue' import MyEchart from '@/components/echarts/MyEchart.vue'
import { yMethod, completeTimeSeries } from '@/utils/echartMethod'
import TableHeader from '@/components/table/header/index.vue'
import { RefreshLeft } from '@element-plus/icons-vue'
import { formatToDateTime } from '@/utils/dateUtil' import { formatToDateTime } from '@/utils/dateUtil'
defineOptions({ defineOptions({
name: 'govern/manage/realTime' name: 'govern/manage/realTime'
}) })
//页面属性 //页面属性
const pageHeight = mainHeight(20) const pageHeight = mainHeight(20)
const echartHeight = (mainHeight(180)) const echartHeight = mainHeight(180)
const realTime = ref<string>('')
const deviceTime = ref<string>('')
const timer = ref<any>(null)
const echartsData = ref<any>({ const echartsData = ref<any>({
title: { title: {
text: '终端性能' text: '终端性能'
}, },
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
},
legend: { legend: {
bottom: 0, bottom: 0,
data: ['CPU1使用率', 'CPU2使用率', '内存使用率', '磁盘使用率'] data: ['CPU1使用率', 'CPU2使用率', '内存使用率', '磁盘使用率']
}, },
xAxis: { xAxis: {
type: 'category', type: 'time',
data: ['2023-05-01', '2023-05-02', '2023-05-03', '2023-05-04', '2023-05-05', '2023-05-06'] name: '时间',
axisLabel: {
formatter: {
day: '{MM}-{dd}',
month: '{MM}',
year: '{yyyy}'
}
}
}, },
yAxis: { yAxis: {
name: '使用率', name: '%',
type: 'value', type: 'value',
min: 0, min: 0,
max: 100, max: 100
interval: 10,
axisLabel: {
formatter: '{value} %'
}
}, },
series: [ series: [
{ {
name: 'CPU1使用率', name: 'CPU1使用率',
type: 'line', type: 'line',
data: [10, 52, 20, 33, 39, 89] showSymbol: false,
smooth: true,
data: [
['2025-01-01 08:00:00', 10],
['2025-01-01 09:00:00', 10],
['2025-01-01 010:00:00', 10]
]
}, },
{ {
name: 'CPU2使用率', name: 'CPU2使用率',
type: 'line', type: 'line',
data: [9, 52, 10, 33, 69, 79] showSymbol: false,
smooth: true,
data: [
['2025-01-01 08:00:00', 11],
['2025-01-01 09:00:00', 11],
['2025-01-01 010:00:00', 11]
]
}, },
{ {
name: '内存使用率', name: '内存使用率',
type: 'line', type: 'line',
data: [10, 62, 80, 13, 29, 39] showSymbol: false,
smooth: true,
data: [
['2025-01-01 08:00:00', 12],
['2025-01-01 09:00:00', 12],
['2025-01-01 010:00:00', 12]
]
}, },
{ {
name: '磁盘使用率', name: '磁盘使用率',
type: 'line', type: 'line',
data: [50, 52, 40, 55, 49, 61] showSymbol: false,
smooth: true,
data: [
['2025-01-01 08:00:00', 13],
['2025-01-01 09:00:00', 13],
['2025-01-01 010:00:00', 13]
]
} }
] ]
}) })
@@ -125,38 +192,19 @@ const handleClose = () => {
const synchronizeTime = async () => { const synchronizeTime = async () => {
console.log('对时') console.log('对时')
} }
onMounted(() => {
timer.value = setInterval(() => {
realTime.value = formatToDateTime(new Date())
}, 1000)
})
// 在组件卸载时清除定时器
onUnmounted(() => {
if (timer.value) {
clearInterval(timer.value)
timer.value = null
}
})
</script> </script>
<template>
<div class="default-main manage-realTime" :style="{ height: pageHeight.height }">
<DeviceTree @node-click="nodeClick" @init="" @deviceTypeChange=""></DeviceTree>
<div class="manage-realTime-right">
<div class="time-container">
<span>系统时间{{ formatToDateTime(new Date()) }}</span>
<span>终端时间{{ formatToDateTime(new Date()) }}</span>
<el-button size="small" type="primary" @click="synchronizeTime">
<RefreshLeft style="width: 15px;margin-right: 5px" />
<span>对时</span>
</el-button>
</div>
<div :style="echartHeight" style="padding: 10px 20px 0 20px;">
<MyEchart :options="echartsData" />
</div>
<div style="padding: 0 20px 0 20px;">
<el-table :data="tableData" border :show-header="false" class="custom-table">
<el-table-column prop="label1" />
<el-table-column prop="label2" />
<el-table-column prop="value1" />
<el-table-column prop="label3" />
<el-table-column prop="value2" />
<el-table-column prop="label4" />
<el-table-column prop="value3" />
</el-table>
</div>
</div>
</div>
</template>
<style scoped lang="scss"> <style scoped lang="scss">
.manage-realTime { .manage-realTime {
display: flex; display: flex;
@@ -195,14 +243,11 @@ const synchronizeTime = async () => {
} }
:deep(.custom-table .el-table__cell) { :deep(.custom-table .el-table__cell) {
border: 1px solid #dcdfe6; border: 0.5px solid #dcdfe6;
} }
/* 2. 第 1、2、4、6 列显示深色背景 */ /* 2. 第 1、2、4、6 列显示深色背景 */
:deep(.custom-table .el-table__row .el-table__cell:nth-child(1)), :deep(.custom-table .el-table__row .el-table__cell:nth-child(1)) {
:deep(.custom-table .el-table__row .el-table__cell:nth-child(2)),
:deep(.custom-table .el-table__row .el-table__cell:nth-child(4)),
:deep(.custom-table .el-table__row .el-table__cell:nth-child(6)) {
background-color: #f5f7fa; background-color: #f5f7fa;
font-weight: bold; font-weight: bold;
} }
@@ -211,8 +256,8 @@ const synchronizeTime = async () => {
:deep(.custom-table .el-table__row .el-table__cell:nth-child(3)), :deep(.custom-table .el-table__row .el-table__cell:nth-child(3)),
:deep(.custom-table .el-table__row .el-table__cell:nth-child(5)), :deep(.custom-table .el-table__row .el-table__cell:nth-child(5)),
:deep(.custom-table .el-table__row .el-table__cell:nth-child(7)) { :deep(.custom-table .el-table__row .el-table__cell:nth-child(7)) {
background-color: #fff; // background-color: #fff;
} }
} }
} }
</style> </style>

View File

@@ -8,7 +8,7 @@
<el-input v-model.trim="form.name" placeholder="请输入敏感用户名称"></el-input> <el-input v-model.trim="form.name" placeholder="请输入敏感用户名称"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="敏感负荷类型" prop="loadType"> <el-form-item label="敏感负荷类型" prop="loadType">
<el-select v-model.trim="form.loadType" filterable clearable placeholder="请选择数据分类"> <el-select v-model.trim="form.loadType" filterable clearable placeholder="请选择敏感负荷类型">
<el-option <el-option
v-for="item in DataTypeSelect" v-for="item in DataTypeSelect"
:key="item.id" :key="item.id"

File diff suppressed because one or more lines are too long