975 lines
27 KiB
Vue
975 lines
27 KiB
Vue
|
|
<script setup lang="ts">
|
|||
|
|
import { ref, onMounted, onBeforeUnmount, reactive, nextTick } from 'vue';
|
|||
|
|
import { useRealTime } from '@/utils/dateUtils';
|
|||
|
|
import { checkOrderNumberApi, addProcessInfoApi } from '@/api/detect';
|
|||
|
|
import type { Rule } from 'ant-design-vue/es/form';
|
|||
|
|
|
|||
|
|
// 检测设备表单规则
|
|||
|
|
const rules: Record<string, Rule[]> = {
|
|||
|
|
workOrderCode: [{ required: true, message: '请输入工单号', trigger: 'change' }],
|
|||
|
|
productCode: [{ required: true, message: '请输入产品编码', trigger: 'change' }],
|
|||
|
|
employeeCode: [{ required: true, message: '请输入员工号', trigger: 'change' }],
|
|||
|
|
processName: [{ required: true, message: '请输入工序名称', trigger: 'change' }],
|
|||
|
|
resourceName: [{ required: true, message: '请输入资源名称', trigger: 'change' }],
|
|||
|
|
equipmentCode: [{ required: true, message: '请输入设备编码', trigger: 'change' }],
|
|||
|
|
fixtureCode: [{ required: true, message: '请输入治具编码', trigger: 'change' }],
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 包装设备表单规则
|
|||
|
|
const packageRules: Record<string, Rule[]> = {
|
|||
|
|
customerSelection: [{ required: true, message: '请选择客户', trigger: 'change' }],
|
|||
|
|
productModel: [{ required: true, message: '请输入产品型号', trigger: 'change' }],
|
|||
|
|
employeeCode: [{ required: true, message: '请输入员工号', trigger: 'change' }],
|
|||
|
|
processName: [{ required: true, message: '请输入工序名称', trigger: 'change' }],
|
|||
|
|
resourceName: [{ required: true, message: '请输入资源名称', trigger: 'change' }],
|
|||
|
|
equipmentCode: [{ required: true, message: '请输入设备编码', trigger: 'change' }],
|
|||
|
|
fixtureCode: [{ required: true, message: '请输入治具编码', trigger: 'change' }],
|
|||
|
|
packingResult: [{ required: true, message: '请选择装箱结果', trigger: 'change' }],
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 实时时间
|
|||
|
|
const { currentTime } = useRealTime('HH:mm:ss');
|
|||
|
|
const currentDate = ref('');
|
|||
|
|
|
|||
|
|
// 可编辑表单状态管理
|
|||
|
|
const isDetectingEditMode = ref(false);
|
|||
|
|
const isPackageEditMode = ref(false);
|
|||
|
|
|
|||
|
|
// 初始副本存储
|
|||
|
|
let detectFormBackup: any = null;
|
|||
|
|
let packageFormBackup: any = null;
|
|||
|
|
|
|||
|
|
// 表单引用
|
|||
|
|
const detectFormRef = ref();
|
|||
|
|
const packageFormRef = ref();
|
|||
|
|
|
|||
|
|
// 执行结果日志
|
|||
|
|
const executionLogs = ref([
|
|||
|
|
'14:25:32 - 开始检测流程 SN-A538-09-2023-00018',
|
|||
|
|
'14:25:33 - 读取产品参数完成',
|
|||
|
|
'14:25:36 - 尺寸检测: 通过 (0.15mm)',
|
|||
|
|
'14:25:36 - 电压检测: 通过 (3.25V)',
|
|||
|
|
'14:25:37 - 精度检测: 通过 (98.7%)',
|
|||
|
|
'14:25:38 - 检测结果: 合格'
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
const packageLogs = ref([
|
|||
|
|
'14:25:45 - 包装流程: SN-A538-09-2023-00018',
|
|||
|
|
'14:25:46 - 读取产品标签完成',
|
|||
|
|
'14:25:49 - 包装材料准备完成',
|
|||
|
|
'14:25:52 - 产品包装完成',
|
|||
|
|
'14:25:53 - 更新库存: 已包装 (18/25)',
|
|||
|
|
'14:25:54 - 包装结果: 正常'
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
// 滚动到检测设备日志底部的函数
|
|||
|
|
const scrollToExecutionBottom = () => {
|
|||
|
|
const logsContainer = document.querySelector('.execution-logs-content');
|
|||
|
|
if (logsContainer) {
|
|||
|
|
logsContainer.scrollTop = logsContainer.scrollHeight;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 底部操作按钮配置
|
|||
|
|
const actionButtons = ref([
|
|||
|
|
{ label: '工序过站', handler: 'handleProcessPass', type: 'primary' as const, status: 'idle' as const },
|
|||
|
|
{ label: '复检序号检查', handler: 'handleRecheckSequence', type: 'default' as const, status: 'idle' as const },
|
|||
|
|
{ label: '扫码', handler: 'handleScanCode', type: 'default' as const, status: 'idle' as const },
|
|||
|
|
{ label: '序号检查', handler: 'handleSequenceCheck', type: 'default' as const, status: 'idle' as const },
|
|||
|
|
{ label: '包装过站', handler: 'handlePackagePass', type: 'primary' as const, status: 'idle' as const }
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
// 执行按钮操作
|
|||
|
|
const executeButtonAction = (handlerName: string) => {
|
|||
|
|
const handlers: { [key: string]: () => void } = {
|
|||
|
|
handleOrderCheck,
|
|||
|
|
handleProcessPass,
|
|||
|
|
handleRecheckSequence,
|
|||
|
|
handleSequenceCheck,
|
|||
|
|
handlePackagePass
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handler = handlers[handlerName];
|
|||
|
|
if (handler) {
|
|||
|
|
handler();
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 日志记录工具函数
|
|||
|
|
const addDetectionLog = (message: string) => {
|
|||
|
|
const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false });
|
|||
|
|
const logEntry = `${timestamp} - ${message}`;
|
|||
|
|
executionLogs.value.push(logEntry);
|
|||
|
|
|
|||
|
|
// 自动滚动到底部
|
|||
|
|
nextTick(() => {
|
|||
|
|
scrollToExecutionBottom();
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const addPackageLog = (message: string) => {
|
|||
|
|
const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false });
|
|||
|
|
const logEntry = `${timestamp} - ${message}`;
|
|||
|
|
packageLogs.value.push(logEntry);
|
|||
|
|
|
|||
|
|
// 自动滚动到底部
|
|||
|
|
nextTick(() => {
|
|||
|
|
const logContainers = document.querySelectorAll('.log-container');
|
|||
|
|
logContainers.forEach(container => {
|
|||
|
|
container.scrollTop = container.scrollHeight;
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 清空检测设备执行日志
|
|||
|
|
const clearExecutionLogs = () => {
|
|||
|
|
executionLogs.value = [];
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 清空自动包装执行日志
|
|||
|
|
const clearPackageLogs = () => {
|
|||
|
|
packageLogs.value = [];
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 单号检查
|
|||
|
|
const handleOrderCheck = () => {
|
|||
|
|
checkOrderNumberApi({
|
|||
|
|
workOrderCode: detectForm.workOrderCode,
|
|||
|
|
productCode: detectForm.productCode
|
|||
|
|
}).then(res => {
|
|||
|
|
if (res) {
|
|||
|
|
addDetectionLog('单号检查成功');
|
|||
|
|
} else {
|
|||
|
|
addDetectionLog('单号检查失败');
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
addDetectionLog('开始执行单号检查操作');
|
|||
|
|
console.log('单号检查');
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 工序过站
|
|||
|
|
const handleProcessPass = () => {
|
|||
|
|
addDetectionLog('开始执行工序过站操作');
|
|||
|
|
console.log('工序过站');
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 复检序号检查
|
|||
|
|
const handleRecheckSequence = () => {
|
|||
|
|
addPackageLog('开始执行复检序号检查操作');
|
|||
|
|
console.log('复检序号检查');
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 序号检查
|
|||
|
|
const handleSequenceCheck = () => {
|
|||
|
|
addPackageLog('开始执行序号检查操作');
|
|||
|
|
console.log('序号检查');
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 包装过站
|
|||
|
|
const handlePackagePass = () => {
|
|||
|
|
addPackageLog('开始执行包装过站操作');
|
|||
|
|
console.log('包装过站');
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// SSE事件处理函数
|
|||
|
|
const handleL1Event = (data: any) => {
|
|||
|
|
// L1_EVENT放到自动包装日志区
|
|||
|
|
addPackageLog(`L1事件: ${JSON.stringify(data)}`);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleL4Event = (data: any) => {
|
|||
|
|
// L4_EVENT放到检测设备日志区
|
|||
|
|
addDetectionLog(`L4事件: ${JSON.stringify(data)}`);
|
|||
|
|
if(data.code === 201) {
|
|||
|
|
detectForm.confirmSequence = data.data
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleMESEvent = (data: any) => {
|
|||
|
|
// MES_EVENT放到检测设备日志区
|
|||
|
|
addDetectionLog(`MES事件: ${JSON.stringify(data)}`);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleSseMessage = (data: string) => {
|
|||
|
|
// 处理SSE原始消息(如果需要的话)
|
|||
|
|
console.log('收到SSE消息:', data);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Status handlers are now managed by individual modal components
|
|||
|
|
|
|||
|
|
// 检测设备表单数据
|
|||
|
|
const detectForm = reactive({
|
|||
|
|
workOrderCode: 'SKT202507220001',
|
|||
|
|
productCode: 'JH1008611',
|
|||
|
|
employeeCode: 'ZDXTEST',
|
|||
|
|
processName: 'T-自动组装测试',
|
|||
|
|
resourceName: 'T-A自动组装测试',
|
|||
|
|
equipmentCode: 'JH.02.101.13-219',
|
|||
|
|
fixtureCode: 'JH0001',
|
|||
|
|
confirmSequence: '',
|
|||
|
|
detectionData: '',
|
|||
|
|
sequenceCount: 1
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 包装设备表单数据
|
|||
|
|
const packageForm = reactive({
|
|||
|
|
customerSelection: '上海电子科技有限公司',
|
|||
|
|
productModel: 'A538-09-高精度传感器',
|
|||
|
|
employeeCode: 'OP-7853',
|
|||
|
|
processName: '自动包装',
|
|||
|
|
resourceName: '包装台2号',
|
|||
|
|
equipmentCode: 'EQ-PK-002',
|
|||
|
|
fixtureCode: 'FX-PK-002-B',
|
|||
|
|
packingResult: '正常',
|
|||
|
|
sequenceNumber: 'SN-A538-09-2023-00018',
|
|||
|
|
passStationCount: 18,
|
|||
|
|
fullBoxCount: 25,
|
|||
|
|
passStationSequence: 'SN-A538-09-2023-00018'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 编辑模式切换
|
|||
|
|
const toggleDetectingEditMode = () => {
|
|||
|
|
if (!isDetectingEditMode.value) {
|
|||
|
|
// 进入编辑模式,保存初始副本
|
|||
|
|
detectFormBackup = JSON.parse(JSON.stringify(detectForm));
|
|||
|
|
}
|
|||
|
|
isDetectingEditMode.value = !isDetectingEditMode.value;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const togglePackageEditMode = () => {
|
|||
|
|
if (!isPackageEditMode.value) {
|
|||
|
|
// 进入编辑模式,保存初始副本
|
|||
|
|
packageFormBackup = JSON.parse(JSON.stringify(packageForm));
|
|||
|
|
}
|
|||
|
|
isPackageEditMode.value = !isPackageEditMode.value;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 保存检测设备编辑
|
|||
|
|
const saveDetectingEdit = () => {
|
|||
|
|
detectFormRef.value.validate().then(() => {
|
|||
|
|
console.log('保存检测设备数据:', detectForm);
|
|||
|
|
isDetectingEditMode.value = false;
|
|||
|
|
// 保存成功,清除备份
|
|||
|
|
detectFormBackup = null;
|
|||
|
|
|
|||
|
|
addProcessInfoApi({
|
|||
|
|
workOrderCode: detectForm.workOrderCode,
|
|||
|
|
productCode: detectForm.productCode,
|
|||
|
|
employeeCode: detectForm.employeeCode,
|
|||
|
|
processName: detectForm.processName,
|
|||
|
|
resourceName: detectForm.resourceName,
|
|||
|
|
equipmentCode: detectForm.equipmentCode,
|
|||
|
|
fixtureCode: detectForm.fixtureCode
|
|||
|
|
}).then(res => {
|
|||
|
|
console.log(res)
|
|||
|
|
})
|
|||
|
|
}).catch(() => {
|
|||
|
|
console.log('检测设备表单验证失败');
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 保存包装设备编辑
|
|||
|
|
const savePackageEdit = () => {
|
|||
|
|
packageFormRef.value.validate().then(() => {
|
|||
|
|
console.log('保存包装设备数据:', packageForm);
|
|||
|
|
isPackageEditMode.value = false;
|
|||
|
|
// 保存成功,清除备份
|
|||
|
|
packageFormBackup = null;
|
|||
|
|
}).catch(() => {
|
|||
|
|
console.log('包装设备表单验证失败');
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 取消检测设备编辑
|
|||
|
|
const cancelDetectingEdit = () => {
|
|||
|
|
isDetectingEditMode.value = false;
|
|||
|
|
if (detectFormBackup) {
|
|||
|
|
// 恢复到初始副本
|
|||
|
|
Object.assign(detectForm, detectFormBackup);
|
|||
|
|
detectFormBackup = null;
|
|||
|
|
}
|
|||
|
|
// 清除表单验证状态
|
|||
|
|
detectFormRef.value?.clearValidate();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 取消包装设备编辑
|
|||
|
|
const cancelPackageEdit = () => {
|
|||
|
|
isPackageEditMode.value = false;
|
|||
|
|
if (packageFormBackup) {
|
|||
|
|
// 恢复到初始副本
|
|||
|
|
Object.assign(packageForm, packageFormBackup);
|
|||
|
|
packageFormBackup = null;
|
|||
|
|
}
|
|||
|
|
// 清除表单验证状态
|
|||
|
|
packageFormRef.value?.clearValidate();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
// 初始化
|
|||
|
|
onMounted(() => {
|
|||
|
|
// 设置当前日期
|
|||
|
|
const now = new Date();
|
|||
|
|
currentDate.value = `${now.getFullYear()}年${String(now.getMonth() + 1).padStart(2, '0')}月${String(now.getDate()).padStart(2, '0')}日`;
|
|||
|
|
|
|||
|
|
// SSE连接逻辑已迁移到SseStatus组件中
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 清理事件监听
|
|||
|
|
onBeforeUnmount(() => {
|
|||
|
|
// SSE断开连接逻辑已迁移到SseStatus组件中
|
|||
|
|
});
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<template>
|
|||
|
|
<div class="hmi-container">
|
|||
|
|
<!-- 顶部状态栏 -->
|
|||
|
|
<div class="header-status">
|
|||
|
|
<div class="left-info">
|
|||
|
|
<span class="app-title">
|
|||
|
|
<i-lucide-cpu class="app-icon" /> 工业控制系统 HMI
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div class="center-status">
|
|||
|
|
<!-- MES状态 -->
|
|||
|
|
<MesStatus />
|
|||
|
|
|
|||
|
|
<!-- 网络通讯状态 -->
|
|||
|
|
<NetworkStatus />
|
|||
|
|
|
|||
|
|
<!-- PLC通讯状态 -->
|
|||
|
|
<PlcStatus />
|
|||
|
|
|
|||
|
|
<!-- 摄像头状态 -->
|
|||
|
|
<CameraStatus />
|
|||
|
|
|
|||
|
|
<!-- SSE 日志 -->
|
|||
|
|
<SseStatus
|
|||
|
|
:on-l1-event="handleL1Event"
|
|||
|
|
:on-l4-event="handleL4Event"
|
|||
|
|
:on-mes-event="handleMESEvent"
|
|||
|
|
:on-sse-message="handleSseMessage"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<SettingsModal />
|
|||
|
|
<!-- <div class="right-info" @click="showSettings">
|
|||
|
|
<span class="time-info">{{ currentTime }}</span>
|
|||
|
|
<span class="date-info">{{ currentDate }}</span>
|
|||
|
|
</div> -->
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 主要内容区域 -->
|
|||
|
|
<div class="main-content">
|
|||
|
|
<!-- 检测设备区域 -->
|
|||
|
|
<div class="device-section detecting-section">
|
|||
|
|
<div class="section-header">
|
|||
|
|
<h3 class="section-title">
|
|||
|
|
<i-lucide-monitor class="title-icon" />
|
|||
|
|
检测设备
|
|||
|
|
</h3>
|
|||
|
|
<div class="edit-controls" v-if="!isDetectingEditMode">
|
|||
|
|
<a-button size="small" @click="toggleDetectingEditMode" class="edit-btn">
|
|||
|
|
<i-lucide-edit class="btn-icon" />
|
|||
|
|
编辑
|
|||
|
|
</a-button>
|
|||
|
|
</div>
|
|||
|
|
<div class="edit-controls" v-else>
|
|||
|
|
<a-button size="small" type="primary" @click="saveDetectingEdit" class="save-btn">
|
|||
|
|
<i-lucide-save class="btn-icon" />
|
|||
|
|
保存
|
|||
|
|
</a-button>
|
|||
|
|
<a-button size="small" @click="cancelDetectingEdit" class="cancel-btn">
|
|||
|
|
<i-lucide-x class="btn-icon" />
|
|||
|
|
取消
|
|||
|
|
</a-button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<a-form :model="detectForm" :rules="rules" class="device-info" ref="detectFormRef" :colon="false"
|
|||
|
|
hideRequiredMark :labelCol="{ span: 4 }" :wrapperCol="{ span: 20 }">
|
|||
|
|
<a-row :gutter="32">
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="工单号" name="workOrderCode">
|
|||
|
|
<a-input-group compact>
|
|||
|
|
<a-input v-model:value="detectForm.workOrderCode" class="edit-input"
|
|||
|
|
:disabled="!isDetectingEditMode" />
|
|||
|
|
<a-button type="primary" @click="handleOrderCheck">单号检查</a-button>
|
|||
|
|
</a-input-group>
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="产品编码" name="productCode">
|
|||
|
|
<a-input v-model:value="detectForm.productCode" class="edit-input"
|
|||
|
|
:disabled="!isDetectingEditMode" />
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="员工工号" name="employeeCode">
|
|||
|
|
<a-input v-model:value="detectForm.employeeCode" class="edit-input"
|
|||
|
|
:disabled="!isDetectingEditMode" />
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="工序名称" name="processName">
|
|||
|
|
<a-input v-model:value="detectForm.processName" class="edit-input"
|
|||
|
|
:disabled="!isDetectingEditMode" />
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="资源名称" name="resourceName">
|
|||
|
|
<a-input v-model:value="detectForm.resourceName" class="edit-input"
|
|||
|
|
:disabled="!isDetectingEditMode" />
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="设备编码" name="equipmentCode">
|
|||
|
|
<a-input v-model:value="detectForm.equipmentCode" class="edit-input"
|
|||
|
|
:disabled="!isDetectingEditMode" />
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="治具编码" name="fixtureCode">
|
|||
|
|
<a-input v-model:value="detectForm.fixtureCode" class="edit-input"
|
|||
|
|
:disabled="!isDetectingEditMode" />
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="序号数量">
|
|||
|
|
<a-input v-model:value="detectForm.sequenceCount" class="edit-input" disabled />
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="24">
|
|||
|
|
<a-form-item label="确认序号" name="confirmSequence" :labelCol="{ span: 2 }" :wrapperCol="{ span: 22 }">
|
|||
|
|
<a-input-group compact>
|
|||
|
|
<a-input v-model:value="detectForm.confirmSequence" class="edit-input" disabled />
|
|||
|
|
<a-button type="primary" @click="handleSequenceCheck">序号检查</a-button>
|
|||
|
|
<a-button type="primary" @click="handleRecheckSequence" style="border-left: 1px solid #8395B6;">复检序号检查</a-button>
|
|||
|
|
</a-input-group>
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="24">
|
|||
|
|
<a-form-item label="检测数据" name="fixtureCode" :labelCol="{ span: 2 }" :wrapperCol="{ span: 22 }">
|
|||
|
|
<a-input v-model:value="detectForm.detectionData" class="edit-input"
|
|||
|
|
disabled />
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
</a-row>
|
|||
|
|
</a-form>
|
|||
|
|
<ExecutionResult title="执行结果" :logs="executionLogs" @clear="clearExecutionLogs" />
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 自动包装区域 -->
|
|||
|
|
<div class="device-section package-section">
|
|||
|
|
<div class="section-header">
|
|||
|
|
<h3 class="section-title">
|
|||
|
|
<i-lucide-package class="title-icon" />
|
|||
|
|
自动包装
|
|||
|
|
</h3>
|
|||
|
|
<div class="edit-controls" v-if="!isPackageEditMode">
|
|||
|
|
<a-button size="small" @click="togglePackageEditMode" class="edit-btn">
|
|||
|
|
<i-lucide-square-pen class="btn-icon" />
|
|||
|
|
编辑
|
|||
|
|
</a-button>
|
|||
|
|
</div>
|
|||
|
|
<div class="edit-controls" v-else>
|
|||
|
|
<a-button size="small" type="primary" @click="savePackageEdit" class="save-btn">
|
|||
|
|
<i-lucide-save class="btn-icon" />
|
|||
|
|
保存
|
|||
|
|
</a-button>
|
|||
|
|
<a-button size="small" @click="cancelPackageEdit" class="cancel-btn">
|
|||
|
|
<i-lucide-x class="btn-icon" />
|
|||
|
|
取消
|
|||
|
|
</a-button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<a-form :model="packageForm" :rules="packageRules" class="device-info" ref="packageFormRef" :colon="false"
|
|||
|
|
hideRequiredMark :labelCol="{ span: 4 }" :wrapperCol="{ span: 20 }">
|
|||
|
|
<a-row :gutter="32">
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="客户选择" name="customerSelection">
|
|||
|
|
<a-input v-model:value="packageForm.customerSelection" class="edit-input"
|
|||
|
|
:disabled="!isPackageEditMode" />
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="产品型号" name="productModel">
|
|||
|
|
<a-input v-model:value="packageForm.productModel" class="edit-input"
|
|||
|
|
:disabled="!isPackageEditMode" />
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="工号" name="employeeCode">
|
|||
|
|
<a-input v-model:value="packageForm.employeeCode" class="edit-input"
|
|||
|
|
:disabled="!isPackageEditMode" />
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="工序名称" name="processName">
|
|||
|
|
<a-input v-model:value="packageForm.processName" class="edit-input"
|
|||
|
|
:disabled="!isPackageEditMode" />
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="资源名称" name="resourceName">
|
|||
|
|
<a-input v-model:value="packageForm.resourceName" class="edit-input"
|
|||
|
|
:disabled="!isPackageEditMode" />
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="设备编码" name="equipmentCode">
|
|||
|
|
<a-input v-model:value="packageForm.equipmentCode" class="edit-input"
|
|||
|
|
:disabled="!isPackageEditMode" />
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="治具编码" name="fixtureCode">
|
|||
|
|
<a-input v-model:value="packageForm.fixtureCode" class="edit-input"
|
|||
|
|
:disabled="!isPackageEditMode" />
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="装箱结果" name="packingResult">
|
|||
|
|
<a-select v-model:value="packageForm.packingResult" class="edit-input"
|
|||
|
|
:disabled="!isPackageEditMode">
|
|||
|
|
<a-select-option value="正常">正常</a-select-option>
|
|||
|
|
<a-select-option value="异常">异常</a-select-option>
|
|||
|
|
<a-select-option value="待检">待检</a-select-option>
|
|||
|
|
</a-select>
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="序号" name="fixtureCode">
|
|||
|
|
<a-input v-model:value="packageForm.sequenceNumber" class="edit-input" disabled />
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="过站数量" name="fixtureCode">
|
|||
|
|
<a-input v-model:value="packageForm.passStationCount" class="edit-input" disabled />
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="满箱数量" name="fixtureCode">
|
|||
|
|
<a-input v-model:value="packageForm.fullBoxCount" class="edit-input" disabled />
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col :span="12">
|
|||
|
|
<a-form-item label="过站序号" name="fixtureCode">
|
|||
|
|
<a-input v-model:value="packageForm.passStationSequence" class="edit-input" disabled />
|
|||
|
|
</a-form-item>
|
|||
|
|
</a-col>
|
|||
|
|
</a-row>
|
|||
|
|
</a-form>
|
|||
|
|
<ExecutionResult title="执行结果" :logs="packageLogs" @clear="clearPackageLogs" />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 底部操作按钮区域 -->
|
|||
|
|
<!-- <ActionButtons :buttons="actionButtons" @execute="executeButtonAction" /> -->
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<style scoped lang="scss">
|
|||
|
|
@import '@/assets/styles/_variables.scss';
|
|||
|
|
|
|||
|
|
.hmi-container {
|
|||
|
|
width: 100vw;
|
|||
|
|
height: 100vh;
|
|||
|
|
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
|
|||
|
|
color: $white;
|
|||
|
|
font-family: $font-family;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
overflow: hidden;
|
|||
|
|
font-size: clamp(14px, 1.4vw, 18px);
|
|||
|
|
|
|||
|
|
/* 响应式字体大小 */
|
|||
|
|
@media (max-width: 1024px) {
|
|||
|
|
font-size: clamp(12px, 1.7vw, 16px);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (min-width: 1025px) and (max-width: 1366px) {
|
|||
|
|
font-size: clamp(14px, 1.5vw, 17px);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (min-width: 1367px) {
|
|||
|
|
font-size: clamp(16px, 1.3vw, 20px);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 响应式字体大小的具体元素
|
|||
|
|
@media (max-width: 1024px) {
|
|||
|
|
.app-title {
|
|||
|
|
font-size: clamp(16px, 2.8vw, 20px) !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.status-label,
|
|||
|
|
.status-value {
|
|||
|
|
font-size: clamp(12px, 1.4vw, 14px) !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.section-header h3 {
|
|||
|
|
font-size: clamp(16px, 2.0vw, 18px) !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-item .label,
|
|||
|
|
.info-item .value {
|
|||
|
|
font-size: clamp(12px, 1.4vw, 14px) !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.action-button {
|
|||
|
|
font-size: clamp(12px, 1.4vw, 14px) !important;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (min-width: 1025px) and (max-width: 1366px) {
|
|||
|
|
.app-title {
|
|||
|
|
font-size: clamp(18px, 2.4vw, 20px) !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.status-label,
|
|||
|
|
.status-value {
|
|||
|
|
font-size: clamp(13px, 1.3vw, 15px) !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.section-header h3 {
|
|||
|
|
font-size: clamp(17px, 1.8vw, 19px) !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-item .label,
|
|||
|
|
.info-item .value {
|
|||
|
|
font-size: clamp(13px, 1.3vw, 15px) !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.action-button {
|
|||
|
|
font-size: clamp(14px, 1.3vw, 16px) !important;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (min-width: 1367px) {
|
|||
|
|
.app-title {
|
|||
|
|
font-size: clamp(20px, 2.0vw, 22px) !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.status-label,
|
|||
|
|
.status-value {
|
|||
|
|
font-size: clamp(14px, 1.2vw, 16px) !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.section-header h3 {
|
|||
|
|
font-size: clamp(18px, 1.6vw, 20px) !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-item .label,
|
|||
|
|
.info-item .value {
|
|||
|
|
font-size: clamp(14px, 1.2vw, 16px) !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.action-button {
|
|||
|
|
font-size: clamp(15px, 1.2vw, 17px) !important;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 顶部状态栏 */
|
|||
|
|
.header-status {
|
|||
|
|
height: 50px;
|
|||
|
|
background: $bg-dark;
|
|||
|
|
border-bottom: 2px solid $primary-color;
|
|||
|
|
@include flex-between;
|
|||
|
|
padding: 0 $spacing-lg;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
|
|||
|
|
.left-info {
|
|||
|
|
.app-title {
|
|||
|
|
font-size: 18px;
|
|||
|
|
font-weight: bold;
|
|||
|
|
color: $white;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.center-status {
|
|||
|
|
display: flex;
|
|||
|
|
gap: $spacing-xxl;
|
|||
|
|
align-items: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.status-button {
|
|||
|
|
padding: $spacing-sm $spacing-md;
|
|||
|
|
@include button-base;
|
|||
|
|
@include button-hover($bg-overlay, rgba(255, 255, 255, 0.2), $white);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 主要内容区域 */
|
|||
|
|
.main-content {
|
|||
|
|
flex: 1;
|
|||
|
|
display: flex;
|
|||
|
|
gap: $spacing-lg;
|
|||
|
|
padding: $spacing-lg;
|
|||
|
|
overflow: hidden;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.device-section {
|
|||
|
|
flex: 1;
|
|||
|
|
background: $bg-overlay;
|
|||
|
|
border: 2px solid $primary-color;
|
|||
|
|
border-radius: $border-radius-lg;
|
|||
|
|
padding: $spacing-lg;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
overflow: hidden;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.section-header {
|
|||
|
|
margin-bottom: $spacing-md;
|
|||
|
|
border-bottom: 1px solid $primary-color;
|
|||
|
|
padding-bottom: 6px;
|
|||
|
|
@include flex-between;
|
|||
|
|
|
|||
|
|
h3 {
|
|||
|
|
margin: 0;
|
|||
|
|
color: $white;
|
|||
|
|
font-size: 18px;
|
|||
|
|
font-weight: bold;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.section-title {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: $spacing-sm;
|
|||
|
|
margin: 0;
|
|||
|
|
color: $white;
|
|||
|
|
font-size: 18px;
|
|||
|
|
font-weight: bold;
|
|||
|
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.title-icon {
|
|||
|
|
width: 20px;
|
|||
|
|
height: 20px;
|
|||
|
|
filter: drop-shadow(0 2px 4px rgba(74, 144, 226, 0.3));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.app-icon {
|
|||
|
|
width: 18px;
|
|||
|
|
height: 18px;
|
|||
|
|
color: $primary-color;
|
|||
|
|
margin-right: $spacing-sm;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.status-icon {
|
|||
|
|
width: 14px;
|
|||
|
|
height: 14px;
|
|||
|
|
margin-right: 6px;
|
|||
|
|
color: $text-light;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.btn-icon {
|
|||
|
|
width: 12px;
|
|||
|
|
height: 12px;
|
|||
|
|
margin-right: $spacing-xs;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.rotate {
|
|||
|
|
animation: rotation 2s infinite linear;
|
|||
|
|
/* 添加旋转动画 */
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes rotation {
|
|||
|
|
from {
|
|||
|
|
transform: rotate(0deg);
|
|||
|
|
/* 从 0 度开始 */
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
to {
|
|||
|
|
transform: rotate(360deg);
|
|||
|
|
/* 旋转到 360 度 */
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 编辑控制按钮 */
|
|||
|
|
.edit-controls {
|
|||
|
|
display: flex;
|
|||
|
|
gap: $spacing-sm;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.edit-btn {
|
|||
|
|
@include status-button($bg-primary, $bg-primary-hover, $primary-color, $primary-light, $text-light, $white);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.save-btn {
|
|||
|
|
@include status-button($success-bg, $success-bg-hover, $success-color, $success-light, $text-success, $white);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.cancel-btn {
|
|||
|
|
@include status-button($error-bg, $error-bg-hover, $error-color, $error-light, $text-error, $white);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 编辑输入框样式 */
|
|||
|
|
.edit-input {
|
|||
|
|
background: $bg-input !important;
|
|||
|
|
border: $border-light !important;
|
|||
|
|
color: $white !important;
|
|||
|
|
width: 100%;
|
|||
|
|
|
|||
|
|
&.bn {
|
|||
|
|
border: none !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
&:focus {
|
|||
|
|
background: $bg-input-focus !important;
|
|||
|
|
border-color: #fff !important;
|
|||
|
|
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2) !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
&.ant-input-disabled {
|
|||
|
|
color: $text-light !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
input {
|
|||
|
|
background: transparent !important;
|
|||
|
|
color: $white !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
&.ant-select.ant-select-in-form-item {
|
|||
|
|
border: none !important;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
:deep(.ant-select.ant-select-disabled) {
|
|||
|
|
.ant-select-selection-item {
|
|||
|
|
color: $white;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ant-select-selector {
|
|||
|
|
border: $border-light !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ant-select-arrow {
|
|||
|
|
color: #bdbdbd;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.device-info {
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
margin-bottom: $spacing-xl;
|
|||
|
|
|
|||
|
|
:deep(.ant-form-item-label > label) {
|
|||
|
|
color: $text-light;
|
|||
|
|
font-size: 16px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-item {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
margin-bottom: 5px;
|
|||
|
|
min-height: 20px;
|
|||
|
|
|
|||
|
|
|
|||
|
|
.value {
|
|||
|
|
color: $white;
|
|||
|
|
font-size: 14px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
|
|||
|
|
&.success {
|
|||
|
|
color: $success-color;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
/* SSE日志对话框样式 */
|
|||
|
|
.sse-logs-dialog {
|
|||
|
|
:deep(.ant-modal-content) {
|
|||
|
|
background: rgba(30, 30, 45, 0.95);
|
|||
|
|
border: 1px solid $primary-color;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sse-controls {
|
|||
|
|
display: flex;
|
|||
|
|
gap: $spacing-sm;
|
|||
|
|
margin-bottom: $spacing-md;
|
|||
|
|
|
|||
|
|
.sse-control-btn {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
|
|||
|
|
.btn-icon {
|
|||
|
|
margin-right: 4px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
/* 响应式设计 */
|
|||
|
|
@media (max-width: 1600px) {
|
|||
|
|
.main-content {
|
|||
|
|
gap: $spacing-lg;
|
|||
|
|
padding: $spacing-lg;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.device-section {
|
|||
|
|
padding: $spacing-lg;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-item {
|
|||
|
|
.label {
|
|||
|
|
min-width: 70px;
|
|||
|
|
font-size: 13px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.value {
|
|||
|
|
font-size: 13px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (max-width: 1366px) {
|
|||
|
|
.center-status {
|
|||
|
|
gap: $spacing-xl;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.status-label,
|
|||
|
|
.status-value {
|
|||
|
|
font-size: 13px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.main-content {
|
|||
|
|
gap: 10px;
|
|||
|
|
padding: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.device-section {
|
|||
|
|
padding: $spacing-md;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@media (max-width: 1024px) {
|
|||
|
|
.main-content {
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.device-section {
|
|||
|
|
flex: 1;
|
|||
|
|
height: calc(50vh - 100px);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Dialog样式已迁移到各个子组件中 */
|
|||
|
|
|
|||
|
|
:deep(.ant-input-group.ant-input-group-compact) {
|
|||
|
|
display: flex;
|
|||
|
|
|
|||
|
|
.ant-btn[disabled] {
|
|||
|
|
border-color: #8396B7;
|
|||
|
|
color: #b4b4b4;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</style>
|