Files
admin-govern/src/views/govern/setting/frontManagement/index.vue
2025-11-05 15:10:44 +08:00

646 lines
22 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="default-main">
<div class="default">
<div style="flex: 1">
<TableHeader>
<template v-slot:select>
<el-form-item label="前置等级">
<el-select
v-model="tableStore.table.params.nodeGrade"
clearable
placeholder="请选择前置等级"
>
<el-option
v-for="item in fontdveoption"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="tableStore.table.params.searchState" clearable placeholder="请选择状态">
<el-option
v-for="item in statusoption"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</template>
<template v-slot:operation>
<el-button type="primary" class="ml10" @click="add" icon="el-icon-Plus">新增</el-button>
</template>
</TableHeader>
<Table
ref="tableRef"
:row-config="{ isCurrent: true, isHover: true }"
@current-change="currentChangeEvent"
></Table>
</div>
<div class="pd10" style="width: 400px" v-loading="loading">
<el-input v-model="filterText" placeholder="请输入内容" clearable show-word-limit @input="change">
<template #prefix>
<Icon name="el-icon-Search" style="font-size: 16px" />
</template>
</el-input>
<el-tree
:style="height"
style="overflow-y: auto"
:data="dataSource"
node-key="id"
ref="treeRef"
default-expand-all
:props="defaultProps"
:filter-node-method="filterNode"
>
<template #default="{ node, data }">
<div class="custom-tree-node">
<span>
<span v-if="data.processState != null">
{{ node.label }}
</span>
<span v-else>{{ data.name }}</span>
<span
v-if="data.processState != null"
class="iconSpan"
:style="{ background: data.processState == 0 ? '#ff0000' : '#00b07d' }"
></span>
</span>
<div>
<template v-if="data.id">
<el-tooltip content="编辑" placement="top">
<el-button
style="margin-left: 4px"
icon="el-icon-Edit"
type="primary"
@click="edit(data)"
link
></el-button>
</el-tooltip>
</template>
<template v-else>
<el-popconfirm title="确定重启吗?" placement="bottom" @confirm="restart(data)">
<template #reference>
<el-tooltip content="重启" placement="top">
<el-button
style="margin-left: 4px"
icon="el-icon-Refresh"
type="warning"
link
/>
</el-tooltip>
</template>
<template #actions="{ confirm, cancel }">
<el-button size="small" @click="cancel">取消</el-button>
<el-button type="warning" size="small" @click="confirm">确认</el-button>
</template>
</el-popconfirm>
</template>
</div>
</div>
</template>
</el-tree>
</div>
</div>
<el-dialog
draggable
:title="dialogTitle"
v-model="dialogFormVisible"
:close-on-click-modal="false"
width="700px"
:before-close="resetForm"
>
<el-form :model="formData" label-width="120px" :rules="rules" ref="ruleFormRef">
<el-form-item label="名称:" prop="name">
<el-input
v-model="formData.name"
placeholder="请输入名称"
maxlength="32"
show-word-limit
@input="handleInput"
></el-input>
</el-form-item>
<el-form-item label="IP:" prop="ip" class="top">
<el-input v-model="formData.ip" placeholder="请输入Ip"></el-input>
</el-form-item>
<el-form-item label="等级:" prop="nodeGrade" class="top">
<el-select v-model="formData.nodeGrade" placeholder="请选择等级" style="width: 100%">
<el-option
v-for="item in fontdveoption"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="最大终端数:" prop="nodeDevNum" class="top">
<el-input
v-model="formData.nodeDevNum"
onkeyup="value = value.replace(/[^0-9]/g,'')"
maxlength="5"
placeholder="请输入最大终端数"
></el-input>
</el-form-item>
<el-form-item label="最大进程数:" prop="maxProcessNum" class="top">
<el-input
v-model="formData.maxProcessNum"
onkeyup="value = value.replace(/[^0-9]/g,'')"
maxlength="5"
placeholder="请根据监测点规模填写合适进程数1个进程最大可承载200个监测点"
></el-input>
<!-- <el-select v-model="formData.maxProcessNum" placeholder="请选择等级" style="width: 100%">
<el-option
v-for="item in NumList"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select> -->
</el-form-item>
<el-form-item label="排序:" prop="sort" class="top">
<el-input v-model="formData.sort" placeholder="请输入排序"></el-input>
</el-form-item>
<el-form-item label="描述:" prop="remark" class="top">
<el-input
v-model="formData.remark"
:autosize="{ minRows: 2, maxRows: 4 }"
type="textarea"
placeholder="请输入描述"
></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="resetForm"> </el-button>
<el-button type="primary" @click="onSubmit"> </el-button>
</template>
</el-dialog>
<!-- 绑定进程号 -->
<el-dialog draggable title="绑定进程号" v-model="popUps" :close-on-click-modal="false" width="400px">
<el-form :model="bindProcessForm" ref="bindProcessFormRef" label-width="80px" :rules="rules2" >
<el-form-item label="前置机" prop="nodeId">
<el-select v-model="bindProcessForm.nodeId" placeholder="请选择前置机" style="width: 100%" clearable @change="handleNodeChange">
<el-option
v-for="item in tableStore.table.data"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="进程号" prop="processNo">
<el-select v-model="bindProcessForm.processNo" placeholder="请选择进程号" style="width: 100%" clearable>
<el-option
v-for="item in processOptions"
:key="item.name"
:label="item.name"
:value="item.name"
></el-option>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="popUps = false"> </el-button>
<el-button type="primary" @click="bindTheProcess"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { mainHeight } from '@/utils/layout'
import TableStore from '@/utils/tableStore'
import { ElMessage } from 'element-plus'
import { ref, onMounted, provide, reactive, nextTick } from 'vue'
import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue'
import { ElButton } from 'element-plus'
import {addNode,updateNode,delNode,nodeDeviceTree,restartProcess,updateProcess} from '@/api/cs-device-boot/frontManagement'
defineOptions({
name: 'govern/setting/frontManagement'
})
const filterText = ref('')
const fontdveoption: any = ref([
{ id: 0, name: '极重要' },
{ id: 1, name: '普通' },
{ id: 2, name: '备用' }
])
const statusoption: any = ref([
{ id: 0, name: '未启用' },
{ id: 1, name: '启用' }
])
const height = mainHeight(70)
const loading = ref(false)
const popUps = ref(false)
const tableRef = ref()
const ruleFormRef = ref()
const dataSource: any = ref([])
const defaultProps = {
children: 'deviceInfoList',
label: 'name'
}
const bindProcessFormRef = ref()
const bindProcessForm = ref({
nodeId: '',
processNo: ''
})
const processOptions = ref<Array<{ name: string; processNo: string }>>([])
const formData: any = ref({
name: '',
ip: '',
nodeGrade: '',
nodeDevNum: '',
sort: '',
remark: '',
maxProcessNum: ''
})
const rules = reactive({
name: [{ required: true, message: '名称不可为空', trigger: 'blur' }],
ip: [{ required: true, message: 'ip不可为空', trigger: 'blur' }],
nodeGrade: [{ required: true, message: '等级不可为空', trigger: 'blur' }],
nodeDevNum: [{ required: true, message: '最大终端数不可为空', trigger: 'blur' }],
maxProcessNum: [{ required: true, message: '最大进程数不可为空', trigger: 'blur' }],
sort: [{ required: true, message: '排序不可为空', trigger: 'blur' }],
remark: [{ required: true, message: '描述不可为空', trigger: 'blur' }]
})
const rules2 = reactive({
nodeId: [{ required: true, message: '请选择前置机', trigger: 'change' }],
processNo: [{ required: true, message: '请选择进程号', trigger: 'change' }]
})
const dialogFormVisible = ref(false)
const dialogTitle = ref('新增前置机')
const processId = ref('')
const tableStore = new TableStore({
url: '/cs-device-boot/node/nodeList',
method: 'POST',
column: [
{ title: '名称', field: 'name' },
{ title: 'IP', field: 'ip' },
{
title: '等级',
field: 'nodeGrade',
render: 'tag',
custom: {
0: 'success',
1: 'warning',
2: 'info'
},
replaceValue: {
0: '极重要',
1: '普通',
2: '备用'
}
},
{
title: '最大监测点数量',
field: 'nodeDevNum'
},
{
title: '最大进程数',
field: 'maxProcessNum'
},
{
title: '排序',
field: 'sort'
},
{
title: '状态',
field: 'state',
render: 'tag',
custom: {
0: 'warning',
1: 'success'
},
replaceValue: {
0: '未启用',
1: '启用'
}
},
{ title: '描述', field: 'remark' },
{
title: '操作',
align: 'center',
width: '180',
render: 'buttons',
buttons: [
{
name: 'edit',
title: '编辑',
type: 'primary',
icon: 'el-icon-EditPen',
render: 'basicButton',
click: async row => {
dialogFormVisible.value = true
dialogTitle.value = '前置机修改'
formData.value = JSON.parse(JSON.stringify(row))
}
},
{
name: 'del',
title: '删除',
type: 'danger',
icon: 'el-icon-Delete',
render: 'confirmButton',
popconfirm: {
confirmButtonText: '确认',
cancelButtonText: '取消',
confirmButtonType: 'danger',
title: '确定删除吗?'
},
click: row => {
delNode(row.id).then(res => {
ElMessage.success('删除成功')
tableStore.index()
})
}
}
]
}
],
beforeSearchFun: () => {
for (let key in tableStore.table.params) {
if (tableStore.table.params[key] === '' && key !== 'nodeGrade' && key !== 'searchState') {
delete tableStore.table.params[key]
}
}
},
loadCallback: () => {
// 确保数据存在且不为空
if (tableStore.table.data && tableStore.table.data.length > 0) {
// 使用 nextTick 确保 DOM 更新完成后再设置当前行
nextTick(() => {
tableRef.value.getRef().setCurrentRow(tableStore.table.data[0])
// 延迟执行 currentChangeEvent 确保 setCurrentRow 生效
setTimeout(() => {
currentChangeEvent()
}, 0)
})
}
}
})
const nodeId = ref('')
// 点击行
const currentChangeEvent = () => {
// 确保 tableRef 和当前记录存在
if (!tableRef.value || !tableRef.value.getRef().getCurrentRecord()) {
loading.value = false
dataSource.value = []
return
}
loading.value = true
dataSource.value = []
nodeDeviceTree({
id: tableRef.value.getRef().getCurrentRecord().id
})
.then(res => {
nodeId.value = tableRef.value.getRef().getCurrentRecord().id
// 检查返回的数据是否存在且不为空
if (res.data && res.data.processDeviceList) {
dataSource.value = res.data.processDeviceList.filter(item => (item.name = item.processNo + ''))
} else {
dataSource.value = []
}
loading.value = false
})
.catch(() => {
// 添加错误处理,确保 loading 状态也能关闭
dataSource.value = []
loading.value = false
})
}
// 重启进程
const restart = (data: any) => {
restartProcess({
nodeId: nodeId.value,
processNo: data.processNo
}).then(res => {
ElMessage.success('重启成功')
currentChangeEvent()
})
}
const treeRef = ref()
// 树过滤
const change = (val: any) => {
treeRef.value!.filter(filterText.value)
}
// 修改 edit 方法
const edit = (data: any) => {
bindProcessForm.value.processNo = data.nodeProcess
bindProcessForm.value.nodeId = nodeId.value // 默认选中当前节点
processId.value = data.id
popUps.value = true
// 首次加载当前前置机的进程号选项
loadProcessOptionsForNode(nodeId.value)
}
const loadProcessOptionsForNode = (nodeId: string) => {
// 请求该前置机下的进程列表
nodeDeviceTree({ id: nodeId }).then(res => {
if (res.data && res.data.processDeviceList) {
// 处理进程列表数据
processOptions.value = res.data.processDeviceList.map(item => ({
...item,
name: item.processNo + '' // 保持与原逻辑一致
}))
} else {
processOptions.value = []
}
})
}
// 前置机切换
const handleNodeChange = (nodeId: any) => {
if (!nodeId) {
processOptions.value = []
bindProcessForm.value.processNo = ''
return
}
// 清除之前选中的进程号
bindProcessForm.value.processNo = ''
// 加载新选中前置机的进程号选项
loadProcessOptionsForNode(nodeId)
}
// 更新进程号
const bindTheProcess = () => {
bindProcessFormRef.value.validate((valid: any) => {
if (valid) {
updateProcess({
id: processId.value,
processNo: bindProcessForm.value.processNo,
nodeId: bindProcessForm.value.nodeId
}).then((res: any) => {
ElMessage.success('修改成功!')
popUps.value = false
currentChangeEvent()
})
}
})
}
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
}
tableStore.table.params.orderBy = 'desc'
tableStore.table.params.nodeGrade = ''
tableStore.table.params.searchState = ''
provide('tableStore', tableStore)
// 新增
const add = () => {
dialogFormVisible.value = true
dialogTitle.value = '新增前置机'
}
// 确认
const onSubmit = () => {
ruleFormRef.value.validate((valid: any) => {
if (valid) {
if (dialogTitle.value == '新增前置机') {
addNode(formData.value).then(res => {
ElMessage.success('新增前置机')
resetForm()
tableStore.onTableAction('search', {})
})
} else {
updateNode(formData.value).then(res => {
ElMessage.success('修改成功')
resetForm()
tableStore.onTableAction('search', {})
})
}
}
})
}
// 清空表格
const resetForm = () => {
dialogFormVisible.value = false
formData.value = {
name: '',
ip: '',
nodeGrade: '',
nodeDevNum: '',
sort: '',
remark: ''
}
}
const handleInput = (val: string) => {
formData.value.name = val.replace(/\s+/g, '')
}
onMounted(() => {
setTimeout(() => {
tableStore.index()
}, 100)
})
</script>
<style lang="scss" scoped>
.qrcode-label {
position: absolute;
top: 0;
right: 0;
z-index: -99;
display: flex;
align-items: center;
justify-content: center;
width: 180px;
height: 180px;
padding: 10px;
background: #fff;
flex-direction: column;
.qrcode-label-title {
margin-bottom: 10px;
font-size: 14px;
font-weight: bold;
}
.qrcode-label-img {
width: 140px;
height: 140px;
}
}
:deep(.default) {
display: flex;
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
width: 300px;
}
.iconSpan {
display: inline-block;
width: 8px;
height: 8px;
margin-left: 3px;
border-radius: 50%;
margin-bottom: 2px;
}
</style>