feat: 通信管理模块开发

master
JINGYJ 3 weeks ago
parent 9949a3ff0b
commit b6a613a9be

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

@ -1,184 +1,88 @@
<template>
<!-- 通信管理弹框 -->
<el-dialog
title="通信管理"
v-model="dialogVisible"
width="800px"
:before-close="handleClose"
class="custom-dialog"
>
<!-- 内容容器左侧菜单 + 右侧表单 -->
<div class="dialog-container">
<!-- 左侧导航菜单 -->
<div class="left-menu">
<DSDialog title="通信管理" v-model="show" width="1200" class="commManagement-dialog ds-dialog">
<div class="commManagement-container">
<div class="commManagement-siderBar">
<div
class="menu-item"
:class="{ active: activeMenu === 'device' }"
@click="activeMenu = 'device'"
class="commManagement-sidebar-item"
:class="{ active: activeTab === v.value }"
@click="setActiveTab(v)"
v-for="(v, k) in commBars"
:key="k"
>
<i class="el-icon-setting"></i> 设备管理
</div>
<div
class="menu-item"
:class="{ active: activeMenu === 'receive' }"
@click="activeMenu = 'receive'"
>
<i class="el-icon-s-data"></i> 接收事件
</div>
<div
class="menu-item"
:class="{ active: activeMenu === 'send' }"
@click="activeMenu = 'send'"
>
<i class="el-icon-s-opportunity"></i> 发送事件
</div>
<div
class="menu-item"
:class="{ active: activeMenu === 'heartbeat' }"
@click="activeMenu = 'heartbeat'"
>
<i class="el-icon-heart-rate"></i> 心跳管理
</div>
<div
class="menu-item"
:class="{ active: activeMenu === 'response' }"
@click="activeMenu = 'response'"
>
<i class="el-icon-switch-button"></i> 响应配置
<!-- // TODO icon -->
<img :src="activeTab === v.value ? v.iconSelected : v.icon" alt="" class="w-[32px]" />
<span>{{ v.label }}</span>
</div>
</div>
<!-- 右侧内容区域 -->
<div class="right-content">
<!-- 设备列表 + 协议配置 -->
<div class="device-section" v-if="activeMenu === 'device'">
<!-- 设备列表 -->
<div class="device-list">
<div class="device-title">设备列表</div>
<el-button icon="el-icon-plus" size="mini" @click="addDevice"></el-button>
<div class="device-item" v-for="(item, index) in deviceList" :key="index">
<span>{{ item.name }}</span>
<el-switch
v-model="item.enabled"
active-text="开启"
inactive-text="关闭"
@change="handleSwitch(index)"
/>
</div>
</div>
<!-- 通信协议配置 -->
<div class="protocol-config">
<div class="config-title">通信协议</div>
<el-form label-width="80px">
<el-form-item label="协议类型">
<el-select v-model="form.protocolType" placeholder="选择协议">
<el-option label="TCP客户端" value="tcp"></el-option>
<el-option label="UDP客户端" value="udp"></el-option>
</el-select>
</el-form-item>
<el-form-item label="设备名称">
<el-input v-model="form.deviceName"></el-input>
</el-form-item>
<el-form-item label="目标IP">
<el-input v-model="form.targetIp"></el-input>
</el-form-item>
<el-form-item label="目标端口">
<el-input v-model="form.targetPort"></el-input>
</el-form-item>
<el-form-item label="数据上传">
<el-switch v-model="form.dataUpload"></el-switch>
</el-form-item>
<el-form-item label="自动重连">
<el-switch v-model="form.autoReconnect"></el-switch>
</el-form-item>
<el-form-item label="接受结束符">
<el-switch v-model="form.acceptTerminator"></el-switch>
</el-form-item>
</el-form>
</div>
</div>
<!-- 接收事件 -->
<div class="event-section" v-else-if="activeMenu === 'receive'">
<div class="config-title">接收事件</div>
<el-input
type="textarea"
v-model="receiveData"
placeholder="接收事件内容"
rows="6"
class="event-textarea"
></el-input>
<div class="event-actions">
<el-checkbox v-model="hexDisplay">16</el-checkbox>
<el-button type="primary" size="mini" @click="clearReceive"></el-button>
<el-button type="primary" size="mini" @click="fetchReceive"></el-button>
</div>
<div class="commManagement-line-first"></div>
<div class="commManagement-infoList">
<div class="commManagement-infoList-title">
<span>{{ currentTabLabel }}</span>
<div class="infoList-title-btn">+</div>
</div>
<!-- 发送事件 -->
<div class="event-section" v-else-if="activeMenu === 'send'">
<div class="config-title">发送事件</div>
<el-input
type="textarea"
v-model="sendData"
placeholder="发送事件内容"
rows="6"
class="event-textarea"
></el-input>
<div class="event-actions">
<el-button type="primary" size="mini" @click="sendDataAction"></el-button>
</div>
</div>
<!-- 心跳管理 -->
<div class="heartbeat-section" v-else-if="activeMenu === 'heartbeat'">
<div class="config-title">心跳管理</div>
<el-form label-width="100px">
<el-form-item label="心跳间隔">
<el-input v-model="heartbeatInterval" placeholder="单位:秒"></el-input>
</el-form-item>
<el-form-item label="心跳内容">
<el-input v-model="heartbeatContent"></el-input>
</el-form-item>
</el-form>
</div>
<!-- 响应配置 -->
<div class="response-section" v-else-if="activeMenu === 'response'">
<div class="config-title">响应配置</div>
<el-form label-width="100px">
<el-form-item label="响应超时">
<el-input v-model="responseTimeout" placeholder="单位:秒"></el-input>
</el-form-item>
<el-form-item label="重试次数">
<el-input v-model="retryCount"></el-input>
</el-form-item>
</el-form>
</div>
<!-- 底部操作按钮 -->
<div class="bottom-actions">
<el-button type="danger" @click="deleteDevice"></el-button>
<div class="commManagement-infoList-content">
<div
class="commManagement-infoList-item"
:class="{ active: item.enabled }"
v-for="(item, index) in deviceList"
:key="index"
/>
<el-empty :image-size="54" :image="NoDataIcon" style="margin-top: 130px" />
</div>
</div>
<div class="commManagement-line-second"></div>
<div class="commManagement-content">3</div>
</div>
</el-dialog>
<!-- 触发弹框按钮用于测试 -->
<el-button type="primary" @click="dialogVisible = true" style="margin: 20px"
>打开通信管理</el-button
>
</DSDialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { DSButton } from '@/components/Button'
import { DSDialog } from '@/components/Dialog'
import DeviceIcon from '@/assets/images/common/device.png'
import DeviceSelectedIcon from '@/assets/images/common/device_selected.png'
import SendIcon from '@/assets/images/common/send.png'
import SendSelectedIcon from '@/assets/images/common/send_selected.png'
import ReceiveIcon from '@/assets/images/common/receive.png'
import ReceiveSelectedIcon from '@/assets/images/common/receive_selected.png'
import HeartbeatIcon from '@/assets/images/common/heartbeat.png'
import HeartbeatSelectedIcon from '@/assets/images/common/heartbeat_selected.png'
import ResponseIcon from '@/assets/images/common/response.png'
import ResponseSelectedIcon from '@/assets/images/common/response_selected.png'
import NoDataIcon from '@/assets/images/common/no_data.png'
interface Props {
/** 弹窗显隐 */
value: boolean
}
interface Emits {
(e: 'update:value', val: boolean): void
}
const props = withDefaults(defineProps<Props>(), {
value: false
})
const emit = defineEmits<Emits>()
//
const dialogVisible = ref(false)
const show = computed({
get() {
return props.value
},
set(val: boolean) {
emit('update:value', val)
}
})
//
const activeMenu = ref('device') // device/receive/send/heartbeat/response
const activeTab = ref('device') // device/receive/send/heartbeat/response
// activeTab label
const currentTabLabel = computed(() => {
// commBars value activeTab label
const matched = commBars.find((item) => item.value === activeTab.value)
//
return matched ? matched.label : ''
})
//
const deviceList = reactive([
@ -186,6 +90,29 @@ const deviceList = reactive([
{ name: '2.UDP客户端', enabled: false }
])
interface CommBar {
label: string
value: 'device' | 'receive' | 'send' | 'heartbeat' | 'response'
icon: string
iconSelected: string
}
const commBars: CommBar[] = [
{ label: '设备列表', value: 'device', icon: DeviceIcon, iconSelected: DeviceSelectedIcon },
{ label: '接收事件', value: 'receive', icon: ReceiveIcon, iconSelected: ReceiveSelectedIcon },
{ label: '发送事件', value: 'send', icon: SendIcon, iconSelected: SendSelectedIcon },
{
label: '心跳管理',
value: 'heartbeat',
icon: HeartbeatIcon,
iconSelected: HeartbeatSelectedIcon
},
{ label: '响应配置', value: 'response', icon: ResponseIcon, iconSelected: ResponseSelectedIcon }
]
const setActiveTab = (tab: SettingItem) => {
activeTab.value = tab.value
console.log(activeTab.value)
}
//
const form = reactive({
protocolType: 'tcp', // tcp/udp
@ -203,222 +130,74 @@ const receiveData = ref('')
const sendData = ref('')
// 16
const hexDisplay = ref(true)
//
const heartbeatInterval = ref('')
const heartbeatContent = ref('')
//
const responseTimeout = ref('')
const retryCount = ref('')
//
const addDevice = () => {
deviceList.push({ name: `新设备${deviceList.length + 1}`, enabled: false })
}
//
const handleSwitch = (index: number) => {
console.log(`设备 ${deviceList[index].name} 状态:`, deviceList[index].enabled)
}
//
const clearReceive = () => {
receiveData.value = ''
}
//
const fetchReceive = () => {
receiveData.value = '模拟接收数据0A 0B 0C 0D' //
}
//
const sendDataAction = () => {
console.log('发送数据:', sendData.value)
//
}
//
const deleteDevice = () => {
if (deviceList.length > 0) {
deviceList.pop()
}
}
//
const handleClose = (done: () => void) => {
//
form.protocolType = 'tcp'
form.deviceName = 'TCP客户端'
form.targetIp = ''
form.targetPort = ''
form.dataUpload = false
form.autoReconnect = false
form.acceptTerminator = true
//
receiveData.value = ''
sendData.value = ''
done()
}
</script>
<style scoped lang="scss">
/* 弹框整体样式 */
.custom-dialog {
.el-dialog__header {
border-bottom: 1px solid #444;
}
.el-dialog__body {
padding: 0;
background: #2c2c2c;
color: #fff;
}
}
/* 内容容器:左侧菜单 + 右侧表单 */
.dialog-container {
.commManagement-container {
position: relative;
width: 100%;
height: 766px;
box-sizing: border-box;
display: flex;
height: 500px;
padding: 20px;
}
/* 左侧菜单 */
.left-menu {
width: 180px;
background: #373737;
border-radius: 4px;
padding: 20px 0;
margin-right: 20px;
.menu-item {
color: #fff;
padding: 12px 20px;
cursor: pointer;
transition: background 0.3s;
.commManagement-siderBar {
box-sizing: border-box;
width: 120px;
height: 100%;
padding: 24px;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
&:hover {
background: #4d4d4d;
}
&.active {
background: #4d4d4d;
font-weight: bold;
}
}
}
/* 右侧内容区域 */
.right-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
/* 设备管理区域 */
.device-section {
display: flex;
gap: 20px;
.device-list {
width: 220px;
background: #373737;
border-radius: 4px;
padding: 16px;
.device-title {
font-size: 14px;
font-weight: bold;
margin-bottom: 12px;
}
.device-item {
.commManagement-sidebar-item {
margin-bottom: 16px;
height: 62px;
display: flex;
justify-content: space-between;
flex-direction: column;
align-items: center;
margin-bottom: 8px;
justify-content: space-between;
}
}
.protocol-config {
flex: 1;
background: #373737;
border-radius: 4px;
padding: 16px;
.config-title {
.commManagement-infoList {
box-sizing: border-box;
width: 252px;
.commManagement-infoList-title {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 50px;
padding: 0 16px;
font-size: 14px;
font-weight: bold;
margin-bottom: 12px;
.infoList-title-btn {
width: 24px;
height: 24px;
background: #154ddd;
border: none;
color: #fff;
font-size: 24px;
text-align: center;
line-height: 20px;
border-radius: 50%;
cursor: pointer;
}
}
}
}
/* 事件区域(接收/发送) */
.event-section {
background: #373737;
border-radius: 4px;
padding: 16px;
.config-title {
font-size: 14px;
font-weight: bold;
margin-bottom: 12px;
}
.event-textarea {
width: 100%;
background: #2c2c2c;
color: #fff;
border: 1px solid #444;
border-radius: 4px;
padding: 8px;
resize: none;
}
.event-actions {
display: flex;
align-items: center;
gap: 16px;
margin-top: 8px;
.commManagement-content {
flex: 1;
}
}
/* 心跳管理区域 */
.heartbeat-section {
background: #373737;
border-radius: 4px;
padding: 16px;
.config-title {
font-size: 14px;
font-weight: bold;
margin-bottom: 12px;
.commManagement-line-first,
.commManagement-line-second {
position: absolute;
left: 120px;
width: 1px;
height: 100vh;
background-color: #fff;
opacity: 0.3;
}
}
/* 响应配置区域 */
.response-section {
background: #373737;
border-radius: 4px;
padding: 16px;
.config-title {
font-size: 14px;
font-weight: bold;
margin-bottom: 12px;
.commManagement-line-second {
left: 372px;
}
}
/* 底部操作按钮 */
.bottom-actions {
display: flex;
justify-content: flex-end;
margin-top: 20px;
}
</style>

@ -62,8 +62,8 @@ defineOptions({
name: 'TitleBar'
})
interface Emits {
(e: 'setting'): void;
(e: 'open-log'): void;
(e: 'setting'): void
(e: 'system-click', command: string): void
}
const emit = defineEmits<Emits>()
@ -73,22 +73,7 @@ const mouseY = ref(0)
const handleMenuClick = (command) => {
console.log('点击的菜单项:', command)
// command
switch (command) {
case 'log':
//
emit('open-log')
break
case 'communication':
//
break
case 'controller':
//
break
case 'camera':
//
break
}
emit('system-click', command)
}
const mousedown = (event) => {
dragging.value = true

@ -9,7 +9,11 @@
<template>
<div class="design-wrap">
<div class="design-header">
<HeadCtrl @setting="() => (isOpenSetting = true)" @open-log="() => (isOpenLog = true)" />
<HeadCtrl
@setting="() => (isOpenSetting = true)"
@open-log="() => (isOpenLog = true)"
@system-click="handleSystemClick"
/>
<NavCtrl />
</div>
<div class="flex design-content">
@ -28,6 +32,7 @@
<!-- 弹窗 -->
<SettingList v-model:value="isOpenSetting" />
<LogList v-model:value="isOpenLog" />
<CommManagement v-model:value="isOpenCommManagement" />
</template>
<script setup lang="ts">
@ -39,6 +44,27 @@ import OutputView from './Workflow/outputView.vue'
import ResultsView from './Workflow/resultsView.vue'
import SettingList from './Settings/settingList.vue'
import LogList from './LogManagement/logList.vue'
import CommManagement from './CommManagement/index.vue'
const isOpenSetting = ref<boolean>(false) //
const isOpenLog = ref<boolean>(false) //
const isOpenCommManagement = ref<boolean>(false) //
// TitleBar
const handleSystemClick = (command: string) => {
//
switch (command) {
case 'log':
// open
isOpenLog.value = true
break
case 'communication':
//
isOpenCommManagement.value = true
break
case 'controller':
break
case 'camera':
break
}
}
</script>

Loading…
Cancel
Save