From e304c012ce20fb13b32dbf7e109806485630a059 Mon Sep 17 00:00:00 2001 From: Lxq <19852720163@163.com> Date: Sun, 8 Feb 2026 17:02:10 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=9A=E5=8A=A1=E9=80=BB=E8=BE=91=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rc_autoplc_front/src/App.vue | 46 + rc_autoplc_front/src/api/tb/devparam.ts | 24 + rc_autoplc_front/src/router/index.ts | 5 + rc_autoplc_front/src/views/Layout.vue | 91 +- rc_autoplc_front/src/views/devinfo/index.vue | 1214 ++++++---------- rc_autoplc_front/src/views/devparam/index.vue | 1291 +++++++++++++++++ .../src/views/islandInfo/index.vue | 364 +---- rc_autoplc_front/src/views/stepinfo/index.vue | 1141 ++++++++++++--- 8 files changed, 2867 insertions(+), 1309 deletions(-) create mode 100644 rc_autoplc_front/src/views/devparam/index.vue diff --git a/rc_autoplc_front/src/App.vue b/rc_autoplc_front/src/App.vue index b7a6acd..09a0f74 100644 --- a/rc_autoplc_front/src/App.vue +++ b/rc_autoplc_front/src/App.vue @@ -1,5 +1,51 @@ diff --git a/rc_autoplc_front/src/api/tb/devparam.ts b/rc_autoplc_front/src/api/tb/devparam.ts index 75d0e18..f666e0a 100644 --- a/rc_autoplc_front/src/api/tb/devparam.ts +++ b/rc_autoplc_front/src/api/tb/devparam.ts @@ -44,4 +44,28 @@ export function devparamselect(data: any) { method: 'get', params: data, }) +} + +// 为参数增加设备的devId +export function devparamaddDevId(devId: number, paramId: number) { + return request({ + url: '/devParam/addDevId', + method: 'post', + params: { + devId, + paramId, + }, + }) +} + +// 为参数减少设备的devId +export function devparamdelDevId(devId: number, paramId: number) { + return request({ + url: '/devParam/delDevId', + method: 'post', + params: { + devId, + paramId, + }, + }) } \ No newline at end of file diff --git a/rc_autoplc_front/src/router/index.ts b/rc_autoplc_front/src/router/index.ts index 204128c..3b67437 100644 --- a/rc_autoplc_front/src/router/index.ts +++ b/rc_autoplc_front/src/router/index.ts @@ -60,6 +60,11 @@ const router = createRouter({ name: 'island-info', component: () => import('../views/islandInfo/index.vue'), }, + { + path: '/devparam', + name: 'devparam', + component: () => import('../views/devparam/index.vue'), + }, { path: '/devinfo', name: 'devinfo', diff --git a/rc_autoplc_front/src/views/Layout.vue b/rc_autoplc_front/src/views/Layout.vue index 66f462f..419f691 100644 --- a/rc_autoplc_front/src/views/Layout.vue +++ b/rc_autoplc_front/src/views/Layout.vue @@ -32,68 +32,72 @@ :collapse="false" > - + 首页 - + 系统管理 - + 用户管理 - + 角色管理 - + 部门管理 - + 职位管理 - + 操作日志管理 - + 用户角色管理 - + 业务管理 - + PLC设备管理 + + + 动作参数管理 + - - 设备管理 + + 动作管理 - + 功能岛管理 - + 流程管理 - + PLC设备控制 - + 样品管理 @@ -101,7 +105,7 @@ 标准流程管理 - + 流程创建 @@ -120,7 +124,26 @@ import { ref, computed } from 'vue' import { useRouter, useRoute } from 'vue-router' import { ElMessage, ElMessageBox } from 'element-plus' -import { User, Setting, Avatar, OfficeBuilding, Briefcase, Document, CaretBottom, UserFilled, Grid, Monitor, Connection, List, EditPen, Files, Tickets, Management, FolderOpened, Box, Filter, HomeFilled } from '@element-plus/icons-vue' +import { + User, + Setting, + Avatar, + OfficeBuilding, + Briefcase, + Document, + CaretBottom, + UserFilled, + Grid, + Monitor, + Connection, + List, + EditPen, + Files, + Box, + Operation, + Tools, + House, +} from '@element-plus/icons-vue' import { useAuthStore } from '@/stores/auth' const router = useRouter() @@ -140,17 +163,31 @@ const activeMenu = computed(() => { // 根据菜单名称获取图标 const getMenuIcon = (menuName: string) => { const iconMap: Record = { + // 一级 + 首页: House, + 系统管理: Setting, + 业务管理: Grid, + 流程管理: List, + + // 系统管理 + 用户管理: User, + 角色管理: Avatar, + 部门管理: OfficeBuilding, + 职位管理: Briefcase, + 操作日志管理: Document, + 用户角色管理: UserFilled, + + // 业务管理 + PLC设备管理: Connection, + 动作参数管理: Tools, + 动作管理: Monitor, + 功能岛管理: Grid, + + // 流程管理 + PLC设备控制: Operation, + 样品管理: Box, '标准流程管理': Files, // 文件图标,适合标准流程管理 '流程创建': EditPen, - '用户管理': User, - '角色管理': Avatar, - '部门管理': OfficeBuilding, - '职位管理': Briefcase, - '操作日志管理': Document, - '用户角色管理': UserFilled, - '功能岛管理': Setting, - '设备管理': Monitor, - 'PLC设备管理': Connection, } return iconMap[menuName] || EditPen } diff --git a/rc_autoplc_front/src/views/devinfo/index.vue b/rc_autoplc_front/src/views/devinfo/index.vue index a80d222..75a8010 100644 --- a/rc_autoplc_front/src/views/devinfo/index.vue +++ b/rc_autoplc_front/src/views/devinfo/index.vue @@ -3,24 +3,24 @@ 首页 业务管理 - 设备管理 + 动作管理 - + - + @@ -52,13 +52,13 @@ - 新增设备 + 新增动作 - + - + {{ row.devName || '暂无' }} - + - {{ row.devModel || '暂无' }} + {{ row.plcAddr ?? '暂无' }} @@ -160,23 +160,39 @@ ref="formRef" :model="formData" :rules="formRules" - label-width="100px" + label-width="120px" label-position="left" > - + - - + + + + + + - - - - - - 选项数据: - - 添加 - - - - - - {{ option.dicValue }} - - - - - - 暂无选项数据,请添加 - - - - - - - - - - - - - 添加一行 - + + + + + + + + + + 查询 + 重置 + + - - - - + + + - - {{ row.paramName || '暂无' }} + {{ row.plcAddr ?? '暂无' }} - - - + - - - - {{ row.paramType || '暂无' }} + {{ row.paramUnit || '暂无' }} - - - + - - {{ row.paramUnit || '暂无' }} + {{ row.paramValue ?? '暂无' }} - - - + - - - - - 下拉框 - - {{ getFormTypeLabel(row.formType) || '暂无' }} - - - - - - - - - 保存 - - - - 编辑 - - - - 删除 - + {{ getParamCategoryName(row.dicDataId) }} @@ -405,19 +299,28 @@ layout="total, sizes, prev, pager, next, jumper" :total="paramTotal" background - @current-change="handleParamCurrentChange" + @current-change="handleParamPageChange" @size-change="handleParamSizeChange" /> + + + + + + + diff --git a/rc_autoplc_front/src/views/islandInfo/index.vue b/rc_autoplc_front/src/views/islandInfo/index.vue index 0147f9b..dc4322d 100644 --- a/rc_autoplc_front/src/views/islandInfo/index.vue +++ b/rc_autoplc_front/src/views/islandInfo/index.vue @@ -108,7 +108,7 @@ v-model="drawerVisible" :title="drawerTitle" direction="rtl" - size="500px" + size="600px" :before-close="handleDrawerClose" > - - - - - 绑定设备 - - - - - - - {{ row.devName || '暂无' }} - - - - - - {{ getStatusText(row.status) }} - - - - - - {{ row.devDesc || '暂无' }} - - - + + + + + + + {{ row.devName || '暂无' }} + + + + + {{ row.plcAddr ?? '暂无' }} + + + + + + {{ getStatusText(row.status) }} + + + + + + 该功能岛暂无动作 - 暂无绑定设备 @@ -198,67 +196,6 @@ - - - - - - - - {{ row.devName || '暂无' }} - - - - - {{ row.devModel || '暂无' }} - - - - - {{ row.ipAddr || '暂无' }} - - - - - {{ row.port !== null && row.port !== undefined ? row.port : '暂无' }} - - - - - {{ row.protocolType || '暂无' }} - - - - - - {{ getStatusText(row.status) }} - - - - - - - - @@ -307,13 +244,12 @@ import { islandInfolist, islandInfobyid, } from '@/api/tb/islandinfo' -import { devselect, devInfoupd } from '@/api/tb/devinfo' +import { devselect } from '@/api/tb/devinfo' // 加载状态 const loading = ref(false) const submitting = ref(false) const deviceLoading = ref(false) -const savingDeviceBind = ref(false) // 功能岛列表 const islandList = ref([]) @@ -344,12 +280,8 @@ const formData = reactive({ islandDesc: '', }) -// 绑定设备相关 -const bindDeviceDialogVisible = ref(false) -const deviceListForBind = ref([]) -const deviceTableRef = ref() -const selectedDevices = ref([]) -const boundDevices = ref([]) +// 动作列表相关 +const deviceList = ref([]) // 表单验证规则 const formRules = { @@ -426,16 +358,11 @@ const getStatusText = (status: number | null | undefined) => { return '未知' } -// 获取设备序号 +// 获取动作序号 const getDeviceIndex = (index: number) => { return index + 1 } -// 获取已绑定设备序号 -const getBoundDeviceIndex = (index: number) => { - return index + 1 -} - // 获取功能岛列表 const getIslandList = async () => { try { @@ -513,8 +440,7 @@ const resetForm = () => { formData.islandName = '' formData.islandCode = '' formData.islandDesc = '' - boundDevices.value = [] - selectedDevices.value = [] + deviceList.value = [] formRef.value?.clearValidate() } @@ -540,9 +466,9 @@ const handleEdit = async (item: any) => { // 优先使用后端的 desc 字段,兼容其他可能的字段名 formData.islandDesc = data.desc ?? data.islandDesc ?? data.description ?? '' - // 加载已绑定的设备 + // 加载该功能岛下的动作列表 if (formData.id) { - await loadBoundDevices(formData.id) + await loadDeviceList(formData.id) } isEdit.value = true @@ -559,39 +485,51 @@ const handleEdit = async (item: any) => { } } -// 加载已绑定的设备 -const loadBoundDevices = async (islandId: number | string) => { +// 加载该功能岛下的动作列表 +const loadDeviceList = async (islandId: number | string) => { try { + deviceLoading.value = true const res: any = await devselect({}) const allDevices = res?.data ?? res ?? [] if (Array.isArray(allDevices)) { - boundDevices.value = allDevices.filter((device: any) => - device.islandId && (device.islandId === islandId || device.islandId === String(islandId)) - ) + // 过滤出该功能岛下的动作,并排除PLC动作 + deviceList.value = allDevices.filter((device: any) => { + const devModel = device.devModel || '' + const isNotPlc = devModel.toUpperCase() !== 'PLC' + const isBelongToIsland = device.islandId && (device.islandId === islandId || device.islandId === String(islandId)) + return isNotPlc && isBelongToIsland + }) + } else { + deviceList.value = [] } } catch (error) { - console.error('加载已绑定设备失败:', error) - boundDevices.value = [] + console.error('加载动作列表失败:', error) + deviceList.value = [] + } finally { + deviceLoading.value = false } } // 删除功能岛 const handleDelete = async (item: any) => { try { - // 检查是否绑定了设备 + // 检查是否绑定了动作 const res: any = await devselect({}) const allDevices = res?.data ?? res ?? [] const boundDevicesList = Array.isArray(allDevices) - ? allDevices.filter((device: any) => - device.islandId && (device.islandId === item.id || device.islandId === String(item.id)) - ) + ? allDevices.filter((device: any) => { + const devModel = device.devModel || '' + const isNotPlc = devModel.toUpperCase() !== 'PLC' + const isBelongToIsland = device.islandId && (device.islandId === item.id || device.islandId === String(item.id)) + return isNotPlc && isBelongToIsland + }) : [] if (boundDevicesList.length > 0) { - const deviceNames = boundDevicesList.map((d: any) => d.devName || '未命名设备').join('、') + const deviceNames = boundDevicesList.map((d: any) => d.devName || '未命名动作').join('、') const islandName = item.islandName || item.name || '未命名' await ElMessageBox.alert( - `${islandName}已绑定${deviceNames},请先完成设备解绑`, + `${islandName}下还有${deviceNames}等${boundDevicesList.length}个动作,请先完成动作解绑`, '提示', { confirmButtonText: '确定', @@ -674,164 +612,6 @@ const handleSubmit = async () => { } } -// 打开绑定设备对话框 -const handleBindDevice = async () => { - if (!isEdit.value || !formData.id) { - // 新增功能岛不允许绑定设备(仅新增功能岛信息) - return - } - try { - deviceLoading.value = true - const res: any = await devselect({}) - const allDevices = res?.data ?? res ?? [] - - if (Array.isArray(allDevices)) { - // 编辑模式:显示未绑定的设备 + 已绑定该功能岛的设备,排除设备型号为PLC的设备 - deviceListForBind.value = allDevices.filter((device: any) => { - // 排除设备型号为PLC的设备 - if (device.devModel && String(device.devModel).toUpperCase() === 'PLC') { - return false - } - // 筛选条件:islandId为0或空的设备,或者已绑定该功能岛的设备 - return ( - !device.islandId || - device.islandId === formData.id || - device.islandId === String(formData.id) || - device.islandId === 0 || - device.islandId === '0' - ) - }) - } else { - deviceListForBind.value = [] - } - - bindDeviceDialogVisible.value = true - - // 等待DOM更新后设置已选中的设备 - await new Promise(resolve => setTimeout(resolve, 100)) - if (deviceTableRef.value && isEdit.value && formData.id) { - // 编辑模式:默认选中已绑定的设备 - const boundDeviceIds = boundDevices.value.map((d: any) => d.id) - deviceListForBind.value.forEach((device: any) => { - if (boundDeviceIds.includes(device.id)) { - deviceTableRef.value.toggleRowSelection(device, true) - } - }) - } - } catch (error) { - console.error('获取设备列表失败:', error) - ElMessage.error('获取设备列表失败') - } finally { - deviceLoading.value = false - } -} - -// 设备选择变化 -const handleDeviceSelectionChange = (selection: any[]) => { - selectedDevices.value = selection -} - -// 保存设备绑定 -const handleSaveDeviceBind = async () => { - try { - savingDeviceBind.value = true - - // 获取当前选中的设备ID列表 - const selectedDeviceIds = selectedDevices.value.map((d: any) => d.id) - - if (!isEdit.value || !formData.id) return - - // 编辑模式:立即保存绑定关系 - // 获取之前已绑定的设备ID列表 - const previousBoundDeviceIds = boundDevices.value.map((d: any) => d.id) - - // 需要绑定的设备(新增的) - const devicesToBind = selectedDeviceIds.filter((id: any) => !previousBoundDeviceIds.includes(id)) - - // 需要解绑的设备(取消选中的) - const devicesToUnbind = previousBoundDeviceIds.filter((id: any) => !selectedDeviceIds.includes(id)) - - // 执行绑定操作 - const bindPromises = devicesToBind.map((deviceId: any) => { - const device = deviceListForBind.value.find((d: any) => d.id === deviceId) - if (device) { - // 包含设备名称等必填字段 - return devInfoupd({ - id: deviceId, - devName: device.devName, // 必填字段 - islandId: formData.id, - }) - } - return Promise.resolve() - }) - - // 执行解绑操作 - const unbindPromises = devicesToUnbind.map((deviceId: any) => { - const device = - deviceListForBind.value.find((d: any) => d.id === deviceId) || - boundDevices.value.find((d: any) => d.id === deviceId) - if (device) { - // 包含设备名称等必填字段 - return devInfoupd({ - id: deviceId, - devName: device.devName, // 必填字段 - islandId: 0, // 解绑时设置为0 - }) - } - return Promise.resolve() - }) - - // 执行所有操作 - const allPromises = [...bindPromises, ...unbindPromises] - if (allPromises.length > 0) { - const results = await Promise.all(allPromises) - // 检查是否有失败的请求 - const failedResults = results.filter((res: any) => { - if (!res) return false - // 检查响应数据(可能是直接返回的数据对象) - const data = res.data || res - return data && (data.code !== '0' && data.code !== 0 && data.code !== undefined && !data.success) - }) - if (failedResults.length > 0) { - const firstFailed = failedResults[0] - const errorData = firstFailed?.data || firstFailed - throw new Error(errorData?.message || errorData?.msg || '部分设备绑定/解绑失败') - } - } - - // 更新已绑定设备列表 - await loadBoundDevices(formData.id) - - ElMessage.success('保存成功') - - bindDeviceDialogVisible.value = false - } catch (error: any) { - console.error('保存设备绑定失败:', error) - // 处理不同类型的错误 - let errorMessage = '保存设备绑定失败' - if (error?.message) { - errorMessage = error.message - } else if (error?.response?.data?.message) { - errorMessage = error.response.data.message - } else if (error?.response?.data?.msg) { - errorMessage = error.response.data.msg - } else if (typeof error === 'string') { - errorMessage = error - } - ElMessage.error(errorMessage) - } finally { - savingDeviceBind.value = false - } -} - -// 关闭绑定设备对话框 -const handleBindDialogClose = () => { - bindDeviceDialogVisible.value = false - selectedDevices.value = [] - if (deviceTableRef.value) { - deviceTableRef.value.clearSelection() - } -} // 关闭抽屉 const handleDrawerClose = () => { @@ -1048,15 +828,11 @@ onMounted(() => { gap: 10px; } -.bind-device-wrapper { +.device-list-wrapper { width: 100%; } -.bound-devices-table-wrapper { - width: 100%; -} - -.no-bound-devices { +.no-devices { margin-top: 12px; padding: 12px; text-align: center; @@ -1067,12 +843,6 @@ onMounted(() => { border: 1px dashed #dcdfe6; } -.dialog-footer { - display: flex; - justify-content: flex-end; - gap: 10px; -} - // 功能岛卡片动画:从下往上滑动 @keyframes slideUpIn { from { diff --git a/rc_autoplc_front/src/views/stepinfo/index.vue b/rc_autoplc_front/src/views/stepinfo/index.vue index 1af6443..cca7801 100644 --- a/rc_autoplc_front/src/views/stepinfo/index.vue +++ b/rc_autoplc_front/src/views/stepinfo/index.vue @@ -6,26 +6,38 @@ 流程创建 - + - 功能岛 + 动作列表 - - - - - {{ index + 1 }}. {{ item.islandName || item.name }} - + + + {{ group.islandName }} + + + + + + {{ item.devName || '未命名动作' }} + + @@ -39,15 +51,49 @@ > - - {{ flowIndex }}、{{ flowName }} - - - {{ flowName }} - - - 请先选择标准流程 - + + + 标准流程序号 + + {{ flowIndex }} + + + + + + + 标准流程名称 + + {{ flowName }} + + + + + + + + + 确定 + + + 编辑流程 @@ -55,7 +101,7 @@ - 拖拽左侧功能岛到此处创建流程步骤 + 拖拽左侧动作到此处创建流程步骤 @@ -76,9 +122,9 @@ - + - {{ item.islandName || item.name }} + {{ item.devName || '未命名动作' }} @@ -96,7 +142,7 @@ - {{ item.description }} + {{ item.description || `第${chineseNumbers[item.sort] || item.sort + 1}次[${item.devName || '未命名动作'}]` }} @@ -112,6 +158,31 @@ + + + + + + {{ selectedWorkflowItem?.devName || '动作' }} 参数预览 + + 收起 + + + + {{ p.paramName || `参数${i + 1}` }}: + {{ formatPreviewValue(p) }} + + + + 暂无参数(请先点击“编辑”配置参数并保存) + + + + 退出 清空 @@ -204,7 +275,7 @@ @@ -1342,6 +1907,23 @@ onMounted(() => { padding: 5px; } + .island-group { + margin-bottom: 16px; + + .island-group-header { + font-size: 14px; + font-weight: 600; + color: #303133; + padding: 8px 12px; + background-color: #e6f7ff; + border-left: 3px solid #409eff; + margin-bottom: 8px; + border-radius: 2px; + animation: slideUpIn 0.4s ease-out forwards; + opacity: 0; + } + } + .component-item { display: flex; flex-direction: row; @@ -1412,6 +1994,36 @@ onMounted(() => { display: flex; gap: 6px; align-items: center; + flex: 1; + } + + .flow-input-group { + display: flex; + gap: 20px; + align-items: center; + flex: 1; + } + + .flow-input-item { + display: flex; + align-items: center; + gap: 8px; + } + + .flow-input-label { + font-size: 14px; + color: #303133; + white-space: nowrap; + + &::before { + content: '*'; + color: #f56c6c; + margin-right: 4px; + } + } + + .flow-input { + width: 200px; } .canvas-status { @@ -1610,9 +2222,100 @@ onMounted(() => { box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05); z-index: 2; } + + // 底部参数预览抽屉 + .param-preview-panel { + margin-top: 12px; + padding: 12px 14px; + background: #ffffff; + border-top: 1px solid #e4e7ed; + box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.06); + border-radius: 8px; + z-index: 1; + user-select: text; + } + + .param-preview-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; + } + + .param-preview-title { + font-size: 14px; + font-weight: 600; + color: #303133; + } + + .param-preview-list { + display: flex; + flex-wrap: wrap; + gap: 10px 18px; + max-height: 120px; + overflow: auto; + padding-right: 6px; + } + + .param-preview-item { + display: inline-flex; + align-items: baseline; + gap: 6px; + padding: 6px 10px; + border-radius: 6px; + background: #f5f7fa; + border: 1px solid #ebeef5; + color: #303133; + max-width: 100%; + } + + .param-preview-name { + color: #606266; + white-space: nowrap; + } + + .param-preview-value { + color: #303133; + word-break: break-all; + } + + .param-preview-empty { + color: #909399; + font-size: 13px; + padding: 8px 0; + } } } +// 抽屉滑入动画(从下往上) +.param-preview-slide-enter-active, +.param-preview-slide-leave-active { + transition: all 0.18s ease; +} +.param-preview-slide-enter-from, +.param-preview-slide-leave-to { + opacity: 0; + transform: translateY(10px); +} + +.flow-readonly { + display: inline-flex; + align-items: center; + height: 32px; + padding: 0 10px; + border: 1px solid #dcdfe6; + border-radius: 4px; + background: #f5f7fa; + color: #303133; + min-width: 180px; + box-sizing: border-box; +} + +.flow-confirm-item { + display: flex; + align-items: flex-end; +} + // 抽屉样式 .drawer-content { padding: 20px;