1.每次 点击手动上线时 记录 物料编码与版本号关系到数据库

2.当人工扫码时 收到MQTT消息 弹出窗口显示该物料号历史程序号 可人工选择或自行输入新版本号
3.点击上线按钮 需要二次确认信息弹窗 弹窗 显示产品或者工件的详细信息 需要二次确认或者取消
4.二次确认弹窗 中增加 程序号 版本号+物料编码
5.版本号 和 物料编码 放在一行显示 分两个框
This commit is contained in:
XingCheng3
2026-05-28 17:45:38 +08:00
parent cf6831dfbe
commit ec0e9a96ab
4 changed files with 2581 additions and 249 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -17,63 +17,55 @@
</div> </div>
<div class="section-title">扫码上线验证信息</div> <div class="section-title">扫码上线验证信息</div>
<el-form :inline="true" label-width="50px" label-position="right" style="width: 100%;"> <el-form :inline="true" label-width="70px" label-position="right" class="upline-form" style="width: 100%;">
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :span="14"> <el-col :span="12">
<el-form-item label="扫码值" style="width: 100%;" class="SmallLabel"> <el-form-item label="扫码值" style="width: 100%;">
<el-input v-model="ws1Result.扫码值" readonly size="small" style="width: 100%;" /> <el-input v-model="ws1Result.扫码值" readonly size="small" style="width: 100%;" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="10"> <el-col :span="12">
<el-form-item label="物料编码" style="width: 100%;">
<el-input v-model="ws1Result.扫码物料编码" readonly size="small" style="width: 100%;" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="14">
<el-form-item label="工单号" style="width: 100%;">
<el-input v-model="ws1Result.工单号" readonly size="small" style="width: 100%;" />
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="序列号" style="width: 100%;"> <el-form-item label="序列号" style="width: 100%;">
<el-input v-model="ws1Result.扫码序列号" readonly size="small" style="width: 100%;" /> <el-input v-model="ws1Result.扫码序列号" readonly size="small" style="width: 100%;" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :span="14"> <el-col :span="12">
<el-form-item label="工单号" style="width: 100%;">
<el-input v-model="ws1Result.工单号" readonly size="small" style="width: 100%;" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="错误消息" style="width: 100%;"> <el-form-item label="错误消息" style="width: 100%;">
<el-input v-model="ws1Result.错误消息" readonly size="small" style="width: 100%;color:red;" /> <el-input v-model="ws1Result.错误消息" readonly size="small" style="width: 100%;color:red;" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="10">
<el-form-item label="程序版本" style="width: 100%;">
<el-select v-model="ws1Result.程序版本" filterable allow-create default-first-option size="small" style="width: 100%;">
<el-option v-for="item in programVersionOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="14">
<div style="height: 32px;"></div>
</el-col>
<el-col :span="10">
<el-form-item label="验证结果" style="width: 100%;">
<div v-if="ws1Result.验证结果 == 1"
style="background:#aaff33;color:#222;font-weight:bold;font-size:14px;padding:2px 8px;border-radius:4px;width:100%;text-align:center;">
验证通过</div>
<div v-else-if="ws1Result.验证结果 == 2"
style="background:#ff3b3b;color:#fff;font-weight:bold;font-size:14px;padding:2px 8px;border-radius:4px;width:100%;text-align:center;">
验证失败</div>
<div v-else-if="ws1Result.验证结果 == -1"
style="background:#ffe066;color:#222;font-weight:bold;font-size:14px;padding:2px 8px;border-radius:4px;width:100%;text-align:center;">
解析错误</div>
</el-form-item>
</el-col>
</el-row> </el-row>
<div class="material-version-row">
<div class="mv-version">
<div class="top-label">程序版本</div>
<el-input
v-model="ws1Result.程序版本"
readonly
size="small"
placeholder="双击选择"
class="version-input"
@dblclick.native="openVersionDialog(opNameList[0])" />
</div>
<div class="mv-material">
<div class="top-label">物料编码</div>
<el-input v-model="ws1Result.扫码物料编码" readonly size="small" />
</div>
<div class="mv-result">
<div class="top-label">验证结果</div>
<div class="result-display">
<span v-if="ws1Result.验证结果 == 1" class="result-pass">验证通过</span>
<span v-else-if="ws1Result.验证结果 == 2" class="result-fail">验证失败</span>
<span v-else-if="ws1Result.验证结果 == -1" class="result-error">解析错误</span>
</div>
</div>
</div>
</el-form> </el-form>
<div style="display: flex; align-items: center; margin: 8px 0;"> <div style="display: flex; align-items: center; margin: 8px 0;">
<el-button type="primary" style="width: 200px;height: 50px; font-size: 14px;" <el-button type="primary" style="width: 200px;height: 50px; font-size: 14px;"
@@ -192,63 +184,55 @@
</div> </div>
<div class="section-title">扫码上线验证信息</div> <div class="section-title">扫码上线验证信息</div>
<el-form :inline="true" label-width="50px" label-position="right" style="width: 100%;"> <el-form :inline="true" label-width="70px" label-position="right" class="upline-form" style="width: 100%;">
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :span="14"> <el-col :span="12">
<el-form-item label="扫码值" style="width: 100%;" class="SmallLabel"> <el-form-item label="扫码值" style="width: 100%;">
<el-input v-model="ws2Result.扫码值" readonly size="small" style="width: 100%;" /> <el-input v-model="ws2Result.扫码值" readonly size="small" style="width: 100%;" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="10"> <el-col :span="12">
<el-form-item label="物料编码" style="width: 100%;">
<el-input v-model="ws2Result.扫码物料编码" readonly size="small" style="width: 100%;" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="14">
<el-form-item label="工单号" style="width: 100%;">
<el-input v-model="ws2Result.工单号" readonly size="small" style="width: 100%;" />
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="序列号" style="width: 100%;"> <el-form-item label="序列号" style="width: 100%;">
<el-input v-model="ws2Result.扫码序列号" readonly size="small" style="width: 100%;" /> <el-input v-model="ws2Result.扫码序列号" readonly size="small" style="width: 100%;" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :span="14"> <el-col :span="12">
<el-form-item label="工单号" style="width: 100%;">
<el-input v-model="ws2Result.工单号" readonly size="small" style="width: 100%;" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="错误消息" style="width: 100%;"> <el-form-item label="错误消息" style="width: 100%;">
<el-input v-model="ws2Result.错误消息" readonly size="small" style="width: 100%;color:red;" /> <el-input v-model="ws2Result.错误消息" readonly size="small" style="width: 100%;color:red;" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="10">
<el-form-item label="程序版本" style="width: 100%;">
<el-select v-model="ws2Result.程序版本" filterable allow-create default-first-option size="small" style="width: 100%;">
<el-option v-for="item in programVersionOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="14">
<div style="height: 32px;"></div>
</el-col>
<el-col :span="10">
<el-form-item label="验证结果" style="width: 100%;">
<div v-if="ws2Result.验证结果 == 1"
style="background:#aaff33;color:#222;font-weight:bold;font-size:14px;padding:2px 8px;border-radius:4px;width:100%;text-align:center;">
验证通过</div>
<div v-else-if="ws2Result.验证结果 == 2"
style="background:#ff3b3b;color:#fff;font-weight:bold;font-size:14px;padding:2px 8px;border-radius:4px;width:100%;text-align:center;">
验证失败</div>
<div v-else-if="ws2Result.验证结果 == -1"
style="background:#ffe066;color:#222;font-weight:bold;font-size:14px;padding:2px 8px;border-radius:4px;width:100%;text-align:center;">
解析错误</div>
</el-form-item>
</el-col>
</el-row> </el-row>
<div class="material-version-row">
<div class="mv-version">
<div class="top-label">程序版本</div>
<el-input
v-model="ws2Result.程序版本"
readonly
size="small"
placeholder="双击选择"
class="version-input"
@dblclick.native="openVersionDialog(opNameList[1])" />
</div>
<div class="mv-material">
<div class="top-label">物料编码</div>
<el-input v-model="ws2Result.扫码物料编码" readonly size="small" />
</div>
<div class="mv-result">
<div class="top-label">验证结果</div>
<div class="result-display">
<span v-if="ws2Result.验证结果 == 1" class="result-pass">验证通过</span>
<span v-else-if="ws2Result.验证结果 == 2" class="result-fail">验证失败</span>
<span v-else-if="ws2Result.验证结果 == -1" class="result-error">解析错误</span>
</div>
</div>
</div>
</el-form> </el-form>
<div style="display: flex; align-items: center; margin: 8px 0;"> <div style="display: flex; align-items: center; margin: 8px 0;">
<el-button type="primary" style="width: 200px;height: 50px; font-size: 14px;" <el-button type="primary" style="width: 200px;height: 50px; font-size: 14px;"
@@ -352,6 +336,113 @@
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
<el-dialog
:visible.sync="versionDialog.visible"
width="520px"
:close-on-click-modal="false"
append-to-body
custom-class="version-dialog"
:show-close="true">
<div slot="title" class="vd-header">
<div class="vd-title">选择程序版本</div>
<div class="vd-subtitle">物料编码 · {{ versionDialog.partCode || '—' }}</div>
</div>
<div class="vd-body">
<div class="vd-section-title">
<span>历史程序版本</span>
<span class="vd-count" v-if="versionDialog.historyList.length"> {{ versionDialog.historyList.length }} </span>
</div>
<div v-if="versionDialog.historyList.length" class="vd-tag-list">
<div
v-for="item in versionDialog.historyList"
:key="item"
class="vd-tag"
:class="{ 'is-active': versionDialog.selectedVersion === item }"
@click="versionDialog.selectedVersion = item">
{{ item }}
</div>
</div>
<div v-else class="vd-empty">
<div class="vd-empty-text">该物料暂无历史程序版本</div>
<div class="vd-empty-hint">请在下方手动输入新版本号</div>
</div>
<div class="vd-section-title vd-section-title-spaced">手动输入版本号</div>
<el-input
v-model="versionDialog.selectedVersion"
size="medium"
maxlength="32"
placeholder=""
clearable />
</div>
<div slot="footer" class="vd-footer">
<el-button @click="versionDialog.visible = false"> </el-button>
<el-button type="primary" :disabled="!versionDialog.selectedVersion" @click="confirmVersionDialog"> </el-button>
</div>
</el-dialog>
<el-dialog
:visible.sync="loadConfirmDialog.visible"
width="560px"
:close-on-click-modal="false"
append-to-body
custom-class="load-confirm-dialog"
:show-close="true">
<div slot="title" class="lcd-header">
<div class="lcd-title">上线信息确认</div>
<div class="lcd-subtitle">请确认以下信息无误后再点击"确认上线"</div>
</div>
<div class="lcd-body">
<div class="lcd-card lcd-card-primary">
<div class="lcd-card-tag">关键信息</div>
<div class="lcd-row-2col lcd-row-2col-key">
<div class="lcd-field">
<div class="lcd-label">程序版本</div>
<div class="lcd-value lcd-value-strong lcd-value-right">{{ loadConfirmDialog.detail.程序版本 || '—' }}</div>
</div>
<div class="lcd-field">
<div class="lcd-label">物料编码</div>
<div class="lcd-value lcd-value-strong">{{ loadConfirmDialog.detail.扫码物料编码 || '—' }}</div>
</div>
</div>
<div class="lcd-field lcd-field-program-no">
<div class="lcd-label">程序号</div>
<div class="lcd-value lcd-value-mono">{{ loadConfirmProgramNo || '—' }}</div>
</div>
</div>
<div class="lcd-card">
<div class="lcd-info-grid">
<div class="lcd-info-item">
<div class="lcd-info-label">工位号</div>
<div class="lcd-info-value">{{ loadConfirmDialog.detail.工位号 || '—' }}</div>
</div>
<div class="lcd-info-item">
<div class="lcd-info-label">序列号</div>
<div class="lcd-info-value">{{ loadConfirmDialog.detail.扫码序列号 || '—' }}</div>
</div>
<div class="lcd-info-item lcd-info-item-full">
<div class="lcd-info-label">工单号</div>
<div class="lcd-info-value">{{ loadConfirmDialog.detail.工单号 || '—' }}</div>
</div>
<div class="lcd-info-item lcd-info-item-full">
<div class="lcd-info-label">扫码值</div>
<div class="lcd-info-value lcd-info-value-mono">{{ loadConfirmDialog.detail.扫码值 || '—' }}</div>
</div>
</div>
</div>
</div>
<div slot="footer" class="lcd-footer">
<el-button @click="loadConfirmDialog.visible = false"> </el-button>
<el-button type="primary" :loading="loadConfirmDialog.loading" @click="confirmLoadDialog"> 线</el-button>
</div>
</el-dialog>
</div> </div>
</template> </template>
@@ -366,10 +457,14 @@ import { mqttIp, mqttTopic, mqttInitRequestTopic, writePlcApi_BtnType, selectOpN
const PROGRAM_VERSION_TAG_ID = '208640' const PROGRAM_VERSION_TAG_ID = '208640'
/** 程序版本下拉选项 */ /** 程序版本下拉选项 */
const PROGRAM_VERSION_OPTIONS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('') /** 程序版本可选项PLC 实际可写的字符值) */
const PROGRAM_VERSION_OPTIONS = []
/** 轮询读取的所有 PLC 信号标识 */ /** 轮询读取的所有 PLC 信号标识 */
const POLL_TAG_IDS = ['200004', '200005', '200006', '200007', '200380', '203240', '200440', PROGRAM_VERSION_TAG_ID] const POLL_TAG_IDS = ['200004', '200005', '200006', '200007', '200380', '203240', '200440']
/** 上次使用的程序版本 localStorage key 前缀(按工位独立) */
const LAST_VERSION_STORAGE_PREFIX = 'ZL_DJ_LastProgramVersion_'
/** 状态灯信号 → data 属性映射 */ /** 状态灯信号 → data 属性映射 */
const STATUS_TAG_MAP = { const STATUS_TAG_MAP = {
@@ -404,6 +499,9 @@ export default {
// 双工位扫码验证数据 // 双工位扫码验证数据
ws1Result: { 扫码值: '', 工单号: '', 扫码物料编码: '', 扫码序列号: '', 错误消息: '', 验证结果: '', 程序版本: '' }, ws1Result: { 扫码值: '', 工单号: '', 扫码物料编码: '', 扫码序列号: '', 错误消息: '', 验证结果: '', 程序版本: '' },
ws2Result: { 扫码值: '', 工单号: '', 扫码物料编码: '', 扫码序列号: '', 错误消息: '', 验证结果: '', 程序版本: '' }, ws2Result: { 扫码值: '', 工单号: '', 扫码物料编码: '', 扫码序列号: '', 错误消息: '', 验证结果: '', 程序版本: '' },
// 是否已处理过首次扫码(首次:自动取最近一次版本,不弹窗;之后:弹窗手动选)
ws1FirstVerifyHandled: false,
ws2FirstVerifyHandled: false,
programVersionOptions: PROGRAM_VERSION_OPTIONS, programVersionOptions: PROGRAM_VERSION_OPTIONS,
// 双工位托盘信息 // 双工位托盘信息
ws1Tray: { 托盘号: '', 托盘物料编码: '', 托盘工件状态: '' }, ws1Tray: { 托盘号: '', 托盘物料编码: '', 托盘工件状态: '' },
@@ -424,10 +522,41 @@ export default {
isResetSerialLoading: false, isResetSerialLoading: false,
// 轮询定时器(按工位分别存储,支持双工位同时轮询) // 轮询定时器(按工位分别存储,支持双工位同时轮询)
pollTimers: {}, pollTimers: {},
// 历史程序版本选择弹窗
versionDialog: {
visible: false,
opName: '',
partCode: '',
historyList: [],
selectedVersion: ''
},
// 手动上线二次确认弹窗
loadConfirmDialog: {
visible: false,
loading: false,
opName: '',
detail: {
工位号: '', 扫码值: '', 工单号: '', 扫码序列号: '', 扫码物料编码: '', 程序版本: ''
}
},
} }
}, },
computed: { computed: {
...mapGetters(['id', 'token']) ...mapGetters(['id', 'token', 'name']),
versionDialogOptions() {
const merged = [...this.versionDialog.historyList]
this.programVersionOptions.forEach(v => {
if (!merged.includes(v)) merged.push(v)
})
return merged
},
/** 上线信息确认弹窗 - 程序号 = 程序版本 + 物料编码 */
loadConfirmProgramNo() {
const ver = this.loadConfirmDialog.detail.程序版本 || ''
const code = this.loadConfirmDialog.detail.扫码物料编码 || ''
if (!ver && !code) return ''
return `${ver}${code}`
}
}, },
created() { created() {
if (this.opNameType === '转台上线') { if (this.opNameType === '转台上线') {
@@ -518,7 +647,6 @@ export default {
if (valueMap['200380'] !== undefined) tray.托盘号 = valueMap['200380'] if (valueMap['200380'] !== undefined) tray.托盘号 = valueMap['200380']
if (valueMap['203240'] !== undefined) this.convertMaterialCode(opName, valueMap['203240']) if (valueMap['203240'] !== undefined) this.convertMaterialCode(opName, valueMap['203240'])
if (valueMap['200440'] !== undefined) tray.托盘工件状态 = TRAY_STATUS_TEXT[valueMap['200440']] || '' if (valueMap['200440'] !== undefined) tray.托盘工件状态 = TRAY_STATUS_TEXT[valueMap['200440']] || ''
if (valueMap[PROGRAM_VERSION_TAG_ID] !== undefined) result.程序版本 = valueMap[PROGRAM_VERSION_TAG_ID] || ''
}, },
/** 将PLC读取到的物料号通过存储过程转换为物料编码 */ /** 将PLC读取到的物料号通过存储过程转换为物料编码 */
@@ -715,7 +843,7 @@ export default {
switch (interFaceType) { switch (interFaceType) {
case 'VerifyResult': { case 'VerifyResult': {
if (arrays_signal.length < 11) { if (arrays_signal.length < 9) {
this.$message.warning('收到扫码结果,但是数据异常,不予处理!') this.$message.warning('收到扫码结果,但是数据异常,不予处理!')
console.log(msg) console.log(msg)
return return
@@ -737,10 +865,22 @@ export default {
targetResult.扫码物料编码 = arrays_signal[6] targetResult.扫码物料编码 = arrays_signal[6]
targetResult.扫码序列号 = arrays_signal[7] targetResult.扫码序列号 = arrays_signal[7]
targetResult.工单号 = arrays_signal[8] targetResult.工单号 = arrays_signal[8]
targetResult.工单物料编号 = arrays_signal[9] // targetResult.工单物料编号 = arrays_signal[9]
targetResult.工单序列号 = arrays_signal[10] // targetResult.工单序列号 = arrays_signal[10]
if (targetResult.验证结果 == 1) { if (targetResult.验证结果 == 1) {
this.$message.success('收到校验结果,校验通过!' + targetResult.错误消息) this.$message.success('收到校验结果,校验通过!' + targetResult.错误消息)
if (pos === this.clickOpName) {
const isFirst = pos === this.opNameList[0]
? !this.ws1FirstVerifyHandled
: !this.ws2FirstVerifyHandled
if (isFirst) {
this.autoFillFirstVersion(pos)
if (pos === this.opNameList[0]) this.ws1FirstVerifyHandled = true
else this.ws2FirstVerifyHandled = true
} else {
this.openVersionDialog(pos)
}
}
if (this.isAutoPartLoad && this.clickOpName === pos) { if (this.isAutoPartLoad && this.clickOpName === pos) {
this.partLoadOpName(targetResult.扫码工位) this.partLoadOpName(targetResult.扫码工位)
} }
@@ -804,17 +944,50 @@ export default {
return return
} }
try { this.loadConfirmDialog.opName = opName
await this.$confirm('确定要执行手动请求上线操作吗?', '确认操作', { this.loadConfirmDialog.detail = {
confirmButtonText: '确定', 工位号: opName,
cancelButtonText: '取消', 扫码值: result.扫码值 || '',
type: 'warning' 工单号: result.工单号 || '',
}) 扫码序列号: result.扫码序列号 || '',
} catch (error) { 扫码物料编码: result.扫码物料编码 || '',
程序版本: programVersion
}
this.loadConfirmDialog.loading = false
this.loadConfirmDialog.visible = true
},
/** 二次确认弹窗"确定":把弹窗里的程序版本写回扫码结果,再走 PLC 写值流程 */
async confirmLoadDialog() {
const opName = this.loadConfirmDialog.opName
const version = this.loadConfirmDialog.detail.程序版本
? this.loadConfirmDialog.detail.程序版本.toString()
: ''
if (!version) {
this.$message.warning('请选择或输入程序版本!')
return return
} }
const result = opName === this.opNameList[0] ? this.ws1Result : this.ws2Result
result.程序版本 = version
this.setLastUsedVersion(opName, version)
this.loadConfirmDialog.loading = true
try {
await this.doPartLoad(opName)
} finally {
this.loadConfirmDialog.loading = false
this.loadConfirmDialog.visible = false
}
},
/** 实际执行 PLC 写值上线流程;成功后写入手动上线记录 */
async doPartLoad(opName) {
const result = opName === this.opNameList[0] ? this.ws1Result : this.ws2Result
const partLoadStatus = opName === this.opNameList[0] ? this.ws1PartLoadStatus : this.ws2PartLoadStatus const partLoadStatus = opName === this.opNameList[0] ? this.ws1PartLoadStatus : this.ws2PartLoadStatus
const programVersion = result.程序版本 ? result.程序版本.toString() : ''
const partCode = result.扫码物料编码 || ''
const plcValue = await this.getPLCValue(opName, '205015') const plcValue = await this.getPLCValue(opName, '205015')
if (plcValue === 'True') { if (plcValue === 'True') {
partLoadStatus.success = false partLoadStatus.success = false
@@ -824,34 +997,112 @@ export default {
return return
} }
this.writePLCValue(opName, PROGRAM_VERSION_TAG_ID, programVersion) try {
.then(response => { const r1 = await this.writePLCValue(opName, PROGRAM_VERSION_TAG_ID, programVersion)
if (response.data.code != 0) { if (r1.data.code != 0) throw new Error(r1.data.msg || '程序版本写入失败')
return Promise.reject(new Error(response.data.msg || '程序版本写入失败')) const r2 = await this.writePLCValue(opName, '205015', 'true')
} if (r2.data.code == 0) {
return this.writePLCValue(opName, '205015', 'true')
})
.then(response => {
if (response.data.code == 0) {
partLoadStatus.success = true partLoadStatus.success = true
partLoadStatus.message = '' partLoadStatus.message = ''
partLoadStatus.show = true partLoadStatus.show = true
this.$message.success('申请上线成功!') this.$message.success('申请上线成功!')
this.recordManualLoad(opName, partCode, programVersion)
} else { } else {
partLoadStatus.success = false partLoadStatus.success = false
partLoadStatus.message = response.data.msg || '申请失败' partLoadStatus.message = r2.data.msg || '申请失败'
partLoadStatus.show = true partLoadStatus.show = true
this.$message.error(response.data.msg) this.$message.error(r2.data.msg)
} }
}) } catch (error) {
.catch(error => {
partLoadStatus.success = false partLoadStatus.success = false
partLoadStatus.message = error.message partLoadStatus.message = error.message
partLoadStatus.show = true partLoadStatus.show = true
this.$message.error('申请上线失败' + error.message) this.$message.error('申请上线失败' + error.message)
}
},
/** 写入手动上线 (物料编码,程序版本) 记录 */
recordManualLoad(opName, partCode, version) {
if (!partCode || !version) return
const param = []
param[0] = ['物料编码', partCode]
param[1] = ['程序版本', version]
param[2] = ['工位号', opName]
param[3] = ['操作人', this.name || '']
const Data = this.CreateData('12', 'ZL_物料程序版本_手动上线记录_Insert', param)
this.ExecDatabase(Data).catch(err => console.error('记录手动上线失败:', err))
},
// ==================== 历史程序版本弹窗 ====================
/** 查询某物料编码的历史程序版本(按最近使用时间倒序) */
loadHistoryVersions(partCode) {
if (!partCode) return Promise.resolve([])
const param = [['物料编码', partCode]]
const Data = this.CreateData('11', 'ZL_物料程序版本_手动上线记录_查询历史版本', param)
return this.ExecDatabase(Data)
.then(resp => Array.isArray(resp.data) ? resp.data.map(r => r.程序版本).filter(v => !!v) : [])
.catch(err => {
console.error('查询历史程序版本失败:', err)
return []
}) })
}, },
/** 打开"选择程序版本"弹窗:扫码验证通过且为当前工位时触发 */
async openVersionDialog(opName) {
const result = opName === this.opNameList[0] ? this.ws1Result : this.ws2Result
const partCode = result.扫码物料编码 || ''
const historyList = await this.loadHistoryVersions(partCode)
const lastUsed = this.getLastUsedVersion(opName)
this.versionDialog.opName = opName
this.versionDialog.partCode = partCode
this.versionDialog.historyList = historyList
this.versionDialog.selectedVersion = (result.程序版本 || lastUsed || historyList[0] || '')
this.versionDialog.visible = true
},
/** 首次扫码:从数据库取该物料最近一次的程序版本,直接回填,不弹窗 */
async autoFillFirstVersion(opName) {
const result = opName === this.opNameList[0] ? this.ws1Result : this.ws2Result
const partCode = result.扫码物料编码 || ''
if (!partCode) return
const historyList = await this.loadHistoryVersions(partCode)
if (historyList && historyList.length > 0) {
result.程序版本 = historyList[0]
this.setLastUsedVersion(opName, historyList[0])
}
},
/** "选择程序版本"弹窗确定:把版本回填到对应工位,不自动上线 */
confirmVersionDialog() {
const version = this.versionDialog.selectedVersion ? this.versionDialog.selectedVersion.toString() : ''
if (!version) {
this.$message.warning('请选择或输入程序版本!')
return
}
const opName = this.versionDialog.opName
const result = opName === this.opNameList[0] ? this.ws1Result : this.ws2Result
result.程序版本 = version
this.setLastUsedVersion(opName, version)
this.versionDialog.visible = false
},
/** 读取该工位上一次使用的程序版本localStorage */
getLastUsedVersion(opName) {
try {
return localStorage.getItem(LAST_VERSION_STORAGE_PREFIX + opName) || ''
} catch (e) {
return ''
}
},
/** 持久化保存该工位本次使用的程序版本localStorage */
setLastUsedVersion(opName, version) {
try {
localStorage.setItem(LAST_VERSION_STORAGE_PREFIX + opName, version || '')
} catch (e) { /* ignore */ }
},
// ==================== 读码 / 写码 / 清码 ==================== // ==================== 读码 / 写码 / 清码 ====================
/** 写码 - 发送写码请求并启动10秒轮询 */ /** 写码 - 发送写码请求并启动10秒轮询 */
@@ -1035,4 +1286,400 @@ export default {
font-size: inherit !important; font-size: inherit !important;
} }
} }
.material-version-row {
display: flex;
align-items: flex-end;
gap: 8px;
margin-top: 4px;
.mv-version {
flex: 0 0 90px;
}
.mv-material {
flex: 2 1 0;
min-width: 0;
}
.mv-result {
flex: 1 1 0;
min-width: 120px;
}
.top-label {
font-size: 14px;
color: #606266;
font-weight: bold;
line-height: 1.4;
padding: 0 0 2px 2px;
}
.version-input {
::v-deep .el-input__inner {
text-align: right;
cursor: pointer;
background-color: #fff;
}
}
.result-display {
height: 32px;
display: flex;
align-items: center;
justify-content: center;
.result-pass,
.result-fail,
.result-error {
display: inline-block;
width: 100%;
font-weight: bold;
font-size: 14px;
padding: 4px 8px;
border-radius: 4px;
text-align: center;
}
.result-pass { background: #aaff33; color: #222; }
.result-fail { background: #ff3b3b; color: #fff; }
.result-error { background: #ffe066; color: #222; }
}
}
.upline-form {
::v-deep .el-form-item__label {
width: 70px !important;
padding-right: 6px;
}
::v-deep .el-form-item__content {
width: calc(100% - 70px) !important;
}
}
</style>
<style lang="scss">
.version-dialog {
border-radius: 12px;
overflow: hidden;
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.18);
.el-dialog__header {
padding: 18px 24px 14px;
background: linear-gradient(135deg, #f5f9ff 0%, #ffffff 100%);
border-bottom: 1px solid #ebeef5;
}
.el-dialog__headerbtn {
top: 18px;
right: 20px;
}
.el-dialog__body {
padding: 18px 24px 4px;
}
.el-dialog__footer {
padding: 12px 24px 18px;
}
.vd-header {
.vd-title {
font-size: 17px;
font-weight: 600;
color: #303133;
line-height: 1.3;
}
.vd-subtitle {
margin-top: 4px;
font-size: 12px;
color: #909399;
line-height: 1.4;
word-break: break-all;
}
}
.vd-body {
.vd-section-title {
font-size: 13px;
font-weight: 600;
color: #606266;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
.vd-count {
font-size: 12px;
font-weight: normal;
color: #909399;
}
}
.vd-section-title-spaced {
margin-top: 18px;
}
.vd-tag-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
max-height: 180px;
overflow-y: auto;
padding: 4px 4px 4px 0;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #dcdfe6;
border-radius: 3px;
}
}
.vd-tag {
display: inline-flex;
align-items: center;
padding: 6px 14px;
min-width: 56px;
justify-content: center;
border-radius: 16px;
background: #f4f6fa;
color: #606266;
font-size: 13px;
font-weight: 500;
cursor: pointer;
border: 1px solid transparent;
transition: all 0.18s ease;
user-select: none;
&:hover {
background: #e8f0ff;
color: #409eff;
}
&.is-active {
background: #409eff;
color: #fff;
border-color: #409eff;
box-shadow: 0 4px 10px rgba(64, 158, 255, 0.28);
}
}
.vd-empty {
padding: 18px 12px;
background: #fafbfc;
border: 1px dashed #dcdfe6;
border-radius: 8px;
text-align: center;
.vd-empty-text {
font-size: 13px;
color: #909399;
}
.vd-empty-hint {
margin-top: 4px;
font-size: 12px;
color: #c0c4cc;
}
}
}
.vd-footer {
text-align: right;
}
}
.load-confirm-dialog {
border-radius: 14px;
overflow: hidden;
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.18);
.el-dialog__header {
padding: 18px 24px 14px;
background: linear-gradient(135deg, #fff7eb 0%, #ffffff 100%);
border-bottom: 1px solid #ebeef5;
}
.el-dialog__headerbtn {
top: 18px;
right: 20px;
}
.el-dialog__body {
padding: 18px 22px 6px;
background: #f7f9fc;
}
.el-dialog__footer {
padding: 14px 22px 18px;
background: #fff;
border-top: 1px solid #ebeef5;
}
.lcd-header {
.lcd-title {
font-size: 18px;
font-weight: 600;
color: #303133;
line-height: 1.3;
}
.lcd-subtitle {
margin-top: 4px;
font-size: 12px;
color: #909399;
line-height: 1.4;
}
}
.lcd-body {
display: flex;
flex-direction: column;
gap: 12px;
}
.lcd-card {
background: #ffffff;
border: 1px solid #ebeef5;
border-radius: 10px;
padding: 14px 16px;
position: relative;
&.lcd-card-primary {
background: linear-gradient(180deg, #ffffff 0%, #fafcff 100%);
border-color: #d9e7ff;
padding-top: 22px;
}
}
.lcd-card-tag {
position: absolute;
top: -10px;
left: 14px;
background: #ff9d00;
color: #fff;
font-size: 11px;
font-weight: 600;
padding: 3px 10px;
border-radius: 10px;
letter-spacing: 0.5px;
box-shadow: 0 2px 6px rgba(255, 157, 0, 0.35);
}
.lcd-row-2col {
display: grid;
grid-template-columns: 1.4fr 1fr;
gap: 14px;
align-items: end;
}
.lcd-row-2col-key {
grid-template-columns: 0.5fr 1.5fr;
margin-bottom: 12px;
}
.lcd-field {
.lcd-label {
font-size: 12px;
color: #909399;
margin-bottom: 6px;
font-weight: 500;
}
.lcd-value {
font-size: 15px;
color: #303133;
font-weight: 500;
word-break: break-all;
line-height: 1.4;
padding: 7px 12px;
background: #f4f6fa;
border-radius: 6px;
min-height: 32px;
display: flex;
align-items: center;
}
.lcd-value-strong {
font-weight: 600;
color: #ff7a00;
background: #fff5e6;
border: 1px solid #ffe1b3;
}
.lcd-value-right {
justify-content: flex-end;
text-align: right;
}
.lcd-value-mono {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 14px;
letter-spacing: 0.3px;
}
}
.lcd-field-program-no {
.lcd-value {
background: #f0f7ff;
border: 1px dashed #b3d4ff;
color: #1f6feb;
font-weight: 600;
}
}
.lcd-select {
width: 100%;
.el-input__inner {
height: 32px;
line-height: 32px;
}
}
.lcd-info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px 14px;
}
.lcd-info-item {
display: flex;
flex-direction: column;
&.lcd-info-item-full {
grid-column: 1 / -1;
}
}
.lcd-info-label {
font-size: 12px;
color: #909399;
margin-bottom: 4px;
}
.lcd-info-value {
font-size: 13px;
color: #303133;
word-break: break-all;
line-height: 1.5;
font-weight: 500;
}
.lcd-info-value-mono {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 12.5px;
color: #606266;
background: #f4f6fa;
padding: 6px 10px;
border-radius: 4px;
}
.lcd-footer {
text-align: right;
.el-button {
min-width: 90px;
}
.el-button--primary {
min-width: 130px;
font-weight: 600;
}
}
}
</style> </style>

View File

@@ -17,63 +17,55 @@
</div> </div>
<div class="section-title">扫码上线验证信息</div> <div class="section-title">扫码上线验证信息</div>
<el-form :inline="true" label-width="50px" label-position="right" style="width: 100%;"> <el-form :inline="true" label-width="70px" label-position="right" class="upline-form" style="width: 100%;">
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :span="14"> <el-col :span="12">
<el-form-item label="扫码值" style="width: 100%;" class="SmallLabel"> <el-form-item label="扫码值" style="width: 100%;">
<el-input v-model="ws1Result.扫码值" readonly size="small" style="width: 100%;" /> <el-input v-model="ws1Result.扫码值" readonly size="small" style="width: 100%;" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="10"> <el-col :span="12">
<el-form-item label="物料编码" style="width: 100%;">
<el-input v-model="ws1Result.扫码物料编码" readonly size="small" style="width: 100%;" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="14">
<el-form-item label="工单号" style="width: 100%;">
<el-input v-model="ws1Result.工单号" readonly size="small" style="width: 100%;" />
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="序列号" style="width: 100%;"> <el-form-item label="序列号" style="width: 100%;">
<el-input v-model="ws1Result.扫码序列号" readonly size="small" style="width: 100%;" /> <el-input v-model="ws1Result.扫码序列号" readonly size="small" style="width: 100%;" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :span="14"> <el-col :span="12">
<el-form-item label="工单号" style="width: 100%;">
<el-input v-model="ws1Result.工单号" readonly size="small" style="width: 100%;" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="错误消息" style="width: 100%;"> <el-form-item label="错误消息" style="width: 100%;">
<el-input v-model="ws1Result.错误消息" readonly size="small" style="width: 100%;color:red;" /> <el-input v-model="ws1Result.错误消息" readonly size="small" style="width: 100%;color:red;" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="10">
<el-form-item label="程序版本" style="width: 100%;">
<el-select v-model="ws1Result.程序版本" filterable allow-create default-first-option size="small" style="width: 100%;">
<el-option v-for="item in programVersionOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="14">
<div style="height: 32px;"></div>
</el-col>
<el-col :span="10">
<el-form-item label="验证结果" style="width: 100%;">
<div v-if="ws1Result.验证结果 == 1"
style="background:#aaff33;color:#222;font-weight:bold;font-size:14px;padding:2px 8px;border-radius:4px;width:100%;text-align:center;">
验证通过</div>
<div v-else-if="ws1Result.验证结果 == 2"
style="background:#ff3b3b;color:#fff;font-weight:bold;font-size:14px;padding:2px 8px;border-radius:4px;width:100%;text-align:center;">
验证失败</div>
<div v-else-if="ws1Result.验证结果 == -1"
style="background:#ffe066;color:#222;font-weight:bold;font-size:14px;padding:2px 8px;border-radius:4px;width:100%;text-align:center;">
解析错误</div>
</el-form-item>
</el-col>
</el-row> </el-row>
<div class="material-version-row">
<div class="mv-version">
<div class="top-label">程序版本</div>
<el-input
v-model="ws1Result.程序版本"
readonly
size="small"
placeholder="双击选择"
class="version-input"
@dblclick.native="openVersionDialog(opNameList[0])" />
</div>
<div class="mv-material">
<div class="top-label">物料编码</div>
<el-input v-model="ws1Result.扫码物料编码" readonly size="small" />
</div>
<div class="mv-result">
<div class="top-label">验证结果</div>
<div class="result-display">
<span v-if="ws1Result.验证结果 == 1" class="result-pass">验证通过</span>
<span v-else-if="ws1Result.验证结果 == 2" class="result-fail">验证失败</span>
<span v-else-if="ws1Result.验证结果 == -1" class="result-error">解析错误</span>
</div>
</div>
</div>
</el-form> </el-form>
<div style="display: flex; align-items: center; margin: 8px 0;"> <div style="display: flex; align-items: center; margin: 8px 0;">
<el-button type="primary" style="width: 200px;height: 50px; font-size: 14px;" <el-button type="primary" style="width: 200px;height: 50px; font-size: 14px;"
@@ -192,63 +184,55 @@
</div> </div>
<div class="section-title">扫码上线验证信息</div> <div class="section-title">扫码上线验证信息</div>
<el-form :inline="true" label-width="50px" label-position="right" style="width: 100%;"> <el-form :inline="true" label-width="70px" label-position="right" class="upline-form" style="width: 100%;">
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :span="14"> <el-col :span="12">
<el-form-item label="扫码值" style="width: 100%;" class="SmallLabel"> <el-form-item label="扫码值" style="width: 100%;">
<el-input v-model="ws2Result.扫码值" readonly size="small" style="width: 100%;" /> <el-input v-model="ws2Result.扫码值" readonly size="small" style="width: 100%;" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="10"> <el-col :span="12">
<el-form-item label="物料编码" style="width: 100%;">
<el-input v-model="ws2Result.扫码物料编码" readonly size="small" style="width: 100%;" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="14">
<el-form-item label="工单号" style="width: 100%;">
<el-input v-model="ws2Result.工单号" readonly size="small" style="width: 100%;" />
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="序列号" style="width: 100%;"> <el-form-item label="序列号" style="width: 100%;">
<el-input v-model="ws2Result.扫码序列号" readonly size="small" style="width: 100%;" /> <el-input v-model="ws2Result.扫码序列号" readonly size="small" style="width: 100%;" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="10"> <el-row :gutter="10">
<el-col :span="14"> <el-col :span="12">
<el-form-item label="工单号" style="width: 100%;">
<el-input v-model="ws2Result.工单号" readonly size="small" style="width: 100%;" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="错误消息" style="width: 100%;"> <el-form-item label="错误消息" style="width: 100%;">
<el-input v-model="ws2Result.错误消息" readonly size="small" style="width: 100%;color:red;" /> <el-input v-model="ws2Result.错误消息" readonly size="small" style="width: 100%;color:red;" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="10">
<el-form-item label="程序版本" style="width: 100%;">
<el-select v-model="ws2Result.程序版本" filterable allow-create default-first-option size="small" style="width: 100%;">
<el-option v-for="item in programVersionOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="14">
<div style="height: 32px;"></div>
</el-col>
<el-col :span="10">
<el-form-item label="验证结果" style="width: 100%;">
<div v-if="ws2Result.验证结果 == 1"
style="background:#aaff33;color:#222;font-weight:bold;font-size:14px;padding:2px 8px;border-radius:4px;width:100%;text-align:center;">
验证通过</div>
<div v-else-if="ws2Result.验证结果 == 2"
style="background:#ff3b3b;color:#fff;font-weight:bold;font-size:14px;padding:2px 8px;border-radius:4px;width:100%;text-align:center;">
验证失败</div>
<div v-else-if="ws2Result.验证结果 == -1"
style="background:#ffe066;color:#222;font-weight:bold;font-size:14px;padding:2px 8px;border-radius:4px;width:100%;text-align:center;">
解析错误</div>
</el-form-item>
</el-col>
</el-row> </el-row>
<div class="material-version-row">
<div class="mv-version">
<div class="top-label">程序版本</div>
<el-input
v-model="ws2Result.程序版本"
readonly
size="small"
placeholder="双击选择"
class="version-input"
@dblclick.native="openVersionDialog(opNameList[1])" />
</div>
<div class="mv-material">
<div class="top-label">物料编码</div>
<el-input v-model="ws2Result.扫码物料编码" readonly size="small" />
</div>
<div class="mv-result">
<div class="top-label">验证结果</div>
<div class="result-display">
<span v-if="ws2Result.验证结果 == 1" class="result-pass">验证通过</span>
<span v-else-if="ws2Result.验证结果 == 2" class="result-fail">验证失败</span>
<span v-else-if="ws2Result.验证结果 == -1" class="result-error">解析错误</span>
</div>
</div>
</div>
</el-form> </el-form>
<div style="display: flex; align-items: center; margin: 8px 0;"> <div style="display: flex; align-items: center; margin: 8px 0;">
<el-button type="primary" style="width: 200px;height: 50px; font-size: 14px;" <el-button type="primary" style="width: 200px;height: 50px; font-size: 14px;"
@@ -352,6 +336,113 @@
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
<el-dialog
:visible.sync="versionDialog.visible"
width="520px"
:close-on-click-modal="false"
append-to-body
custom-class="version-dialog"
:show-close="true">
<div slot="title" class="vd-header">
<div class="vd-title">选择程序版本</div>
<div class="vd-subtitle">物料编码 · {{ versionDialog.partCode || '—' }}</div>
</div>
<div class="vd-body">
<div class="vd-section-title">
<span>历史程序版本</span>
<span class="vd-count" v-if="versionDialog.historyList.length"> {{ versionDialog.historyList.length }} </span>
</div>
<div v-if="versionDialog.historyList.length" class="vd-tag-list">
<div
v-for="item in versionDialog.historyList"
:key="item"
class="vd-tag"
:class="{ 'is-active': versionDialog.selectedVersion === item }"
@click="versionDialog.selectedVersion = item">
{{ item }}
</div>
</div>
<div v-else class="vd-empty">
<div class="vd-empty-text">该物料暂无历史程序版本</div>
<div class="vd-empty-hint">请在下方手动输入新版本号</div>
</div>
<div class="vd-section-title vd-section-title-spaced">手动输入版本号</div>
<el-input
v-model="versionDialog.selectedVersion"
size="medium"
maxlength="32"
placeholder=""
clearable />
</div>
<div slot="footer" class="vd-footer">
<el-button @click="versionDialog.visible = false"> </el-button>
<el-button type="primary" :disabled="!versionDialog.selectedVersion" @click="confirmVersionDialog"> </el-button>
</div>
</el-dialog>
<el-dialog
:visible.sync="loadConfirmDialog.visible"
width="560px"
:close-on-click-modal="false"
append-to-body
custom-class="load-confirm-dialog"
:show-close="true">
<div slot="title" class="lcd-header">
<div class="lcd-title">上线信息确认</div>
<div class="lcd-subtitle">请确认以下信息无误后再点击"确认上线"</div>
</div>
<div class="lcd-body">
<div class="lcd-card lcd-card-primary">
<div class="lcd-card-tag">关键信息</div>
<div class="lcd-row-2col lcd-row-2col-key">
<div class="lcd-field">
<div class="lcd-label">程序版本</div>
<div class="lcd-value lcd-value-strong lcd-value-right">{{ loadConfirmDialog.detail.程序版本 || '—' }}</div>
</div>
<div class="lcd-field">
<div class="lcd-label">物料编码</div>
<div class="lcd-value lcd-value-strong">{{ loadConfirmDialog.detail.扫码物料编码 || '—' }}</div>
</div>
</div>
<div class="lcd-field lcd-field-program-no">
<div class="lcd-label">程序号</div>
<div class="lcd-value lcd-value-mono">{{ loadConfirmProgramNo || '—' }}</div>
</div>
</div>
<div class="lcd-card">
<div class="lcd-info-grid">
<div class="lcd-info-item">
<div class="lcd-info-label">工位号</div>
<div class="lcd-info-value">{{ loadConfirmDialog.detail.工位号 || '—' }}</div>
</div>
<div class="lcd-info-item">
<div class="lcd-info-label">序列号</div>
<div class="lcd-info-value">{{ loadConfirmDialog.detail.扫码序列号 || '—' }}</div>
</div>
<div class="lcd-info-item lcd-info-item-full">
<div class="lcd-info-label">工单号</div>
<div class="lcd-info-value">{{ loadConfirmDialog.detail.工单号 || '—' }}</div>
</div>
<div class="lcd-info-item lcd-info-item-full">
<div class="lcd-info-label">扫码值</div>
<div class="lcd-info-value lcd-info-value-mono">{{ loadConfirmDialog.detail.扫码值 || '—' }}</div>
</div>
</div>
</div>
</div>
<div slot="footer" class="lcd-footer">
<el-button @click="loadConfirmDialog.visible = false"> </el-button>
<el-button type="primary" :loading="loadConfirmDialog.loading" @click="confirmLoadDialog"> 线</el-button>
</div>
</el-dialog>
</div> </div>
</template> </template>
@@ -366,10 +457,14 @@ import { mqttIp, mqttTopic, mqttInitRequestTopic, writePlcApi_BtnType, selectOpN
const PROGRAM_VERSION_TAG_ID = '208640' const PROGRAM_VERSION_TAG_ID = '208640'
/** 程序版本下拉选项 */ /** 程序版本下拉选项 */
const PROGRAM_VERSION_OPTIONS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('') /** 程序版本可选项PLC 实际可写的字符值) */
const PROGRAM_VERSION_OPTIONS = []
/** 轮询读取的所有 PLC 信号标识 */ /** 轮询读取的所有 PLC 信号标识 */
const POLL_TAG_IDS = ['200004', '200005', '200006', '200007', '200380', '203240', '200440', PROGRAM_VERSION_TAG_ID] const POLL_TAG_IDS = ['200004', '200005', '200006', '200007', '200380', '203240', '200440']
/** 上次使用的程序版本 localStorage key 前缀(按工位独立) */
const LAST_VERSION_STORAGE_PREFIX = 'ZL_DJ_LastProgramVersion_'
/** 状态灯信号 → data 属性映射 */ /** 状态灯信号 → data 属性映射 */
const STATUS_TAG_MAP = { const STATUS_TAG_MAP = {
@@ -404,6 +499,9 @@ export default {
// 双工位扫码验证数据 // 双工位扫码验证数据
ws1Result: { 扫码值: '', 工单号: '', 扫码物料编码: '', 扫码序列号: '', 错误消息: '', 验证结果: '', 程序版本: '' }, ws1Result: { 扫码值: '', 工单号: '', 扫码物料编码: '', 扫码序列号: '', 错误消息: '', 验证结果: '', 程序版本: '' },
ws2Result: { 扫码值: '', 工单号: '', 扫码物料编码: '', 扫码序列号: '', 错误消息: '', 验证结果: '', 程序版本: '' }, ws2Result: { 扫码值: '', 工单号: '', 扫码物料编码: '', 扫码序列号: '', 错误消息: '', 验证结果: '', 程序版本: '' },
// 是否已处理过首次扫码(首次:自动取最近一次版本,不弹窗;之后:弹窗手动选)
ws1FirstVerifyHandled: false,
ws2FirstVerifyHandled: false,
programVersionOptions: PROGRAM_VERSION_OPTIONS, programVersionOptions: PROGRAM_VERSION_OPTIONS,
// 双工位托盘信息 // 双工位托盘信息
ws1Tray: { 托盘号: '', 托盘物料编码: '', 托盘工件状态: '' }, ws1Tray: { 托盘号: '', 托盘物料编码: '', 托盘工件状态: '' },
@@ -424,10 +522,41 @@ export default {
isResetSerialLoading: false, isResetSerialLoading: false,
// 轮询定时器(按工位分别存储,支持双工位同时轮询) // 轮询定时器(按工位分别存储,支持双工位同时轮询)
pollTimers: {}, pollTimers: {},
// 历史程序版本选择弹窗
versionDialog: {
visible: false,
opName: '',
partCode: '',
historyList: [],
selectedVersion: ''
},
// 手动上线二次确认弹窗
loadConfirmDialog: {
visible: false,
loading: false,
opName: '',
detail: {
工位号: '', 扫码值: '', 工单号: '', 扫码序列号: '', 扫码物料编码: '', 程序版本: ''
}
},
} }
}, },
computed: { computed: {
...mapGetters(['id', 'token']) ...mapGetters(['id', 'token', 'name']),
versionDialogOptions() {
const merged = [...this.versionDialog.historyList]
this.programVersionOptions.forEach(v => {
if (!merged.includes(v)) merged.push(v)
})
return merged
},
/** 上线信息确认弹窗 - 程序号 = 程序版本 + 物料编码 */
loadConfirmProgramNo() {
const ver = this.loadConfirmDialog.detail.程序版本 || ''
const code = this.loadConfirmDialog.detail.扫码物料编码 || ''
if (!ver && !code) return ''
return `${ver}${code}`
}
}, },
created() { created() {
if (this.opNameType === '转台上线') { if (this.opNameType === '转台上线') {
@@ -518,7 +647,6 @@ export default {
if (valueMap['200380'] !== undefined) tray.托盘号 = valueMap['200380'] if (valueMap['200380'] !== undefined) tray.托盘号 = valueMap['200380']
if (valueMap['203240'] !== undefined) this.convertMaterialCode(opName, valueMap['203240']) if (valueMap['203240'] !== undefined) this.convertMaterialCode(opName, valueMap['203240'])
if (valueMap['200440'] !== undefined) tray.托盘工件状态 = TRAY_STATUS_TEXT[valueMap['200440']] || '' if (valueMap['200440'] !== undefined) tray.托盘工件状态 = TRAY_STATUS_TEXT[valueMap['200440']] || ''
if (valueMap[PROGRAM_VERSION_TAG_ID] !== undefined) result.程序版本 = valueMap[PROGRAM_VERSION_TAG_ID] || ''
}, },
/** 将PLC读取到的物料号通过存储过程转换为物料编码 */ /** 将PLC读取到的物料号通过存储过程转换为物料编码 */
@@ -715,7 +843,7 @@ export default {
switch (interFaceType) { switch (interFaceType) {
case 'VerifyResult': { case 'VerifyResult': {
if (arrays_signal.length < 11) { if (arrays_signal.length < 9) {
this.$message.warning('收到扫码结果,但是数据异常,不予处理!') this.$message.warning('收到扫码结果,但是数据异常,不予处理!')
console.log(msg) console.log(msg)
return return
@@ -737,10 +865,22 @@ export default {
targetResult.扫码物料编码 = arrays_signal[6] targetResult.扫码物料编码 = arrays_signal[6]
targetResult.扫码序列号 = arrays_signal[7] targetResult.扫码序列号 = arrays_signal[7]
targetResult.工单号 = arrays_signal[8] targetResult.工单号 = arrays_signal[8]
targetResult.工单物料编号 = arrays_signal[9] // targetResult.工单物料编号 = arrays_signal[9]
targetResult.工单序列号 = arrays_signal[10] // targetResult.工单序列号 = arrays_signal[10]
if (targetResult.验证结果 == 1) { if (targetResult.验证结果 == 1) {
this.$message.success('收到校验结果,校验通过!' + targetResult.错误消息) this.$message.success('收到校验结果,校验通过!' + targetResult.错误消息)
if (pos === this.clickOpName) {
const isFirst = pos === this.opNameList[0]
? !this.ws1FirstVerifyHandled
: !this.ws2FirstVerifyHandled
if (isFirst) {
this.autoFillFirstVersion(pos)
if (pos === this.opNameList[0]) this.ws1FirstVerifyHandled = true
else this.ws2FirstVerifyHandled = true
} else {
this.openVersionDialog(pos)
}
}
if (this.isAutoPartLoad && this.clickOpName === pos) { if (this.isAutoPartLoad && this.clickOpName === pos) {
this.partLoadOpName(targetResult.扫码工位) this.partLoadOpName(targetResult.扫码工位)
} }
@@ -804,17 +944,50 @@ export default {
return return
} }
try { this.loadConfirmDialog.opName = opName
await this.$confirm('确定要执行手动请求上线操作吗?', '确认操作', { this.loadConfirmDialog.detail = {
confirmButtonText: '确定', 工位号: opName,
cancelButtonText: '取消', 扫码值: result.扫码值 || '',
type: 'warning' 工单号: result.工单号 || '',
}) 扫码序列号: result.扫码序列号 || '',
} catch (error) { 扫码物料编码: result.扫码物料编码 || '',
程序版本: programVersion
}
this.loadConfirmDialog.loading = false
this.loadConfirmDialog.visible = true
},
/** 二次确认弹窗"确定":把弹窗里的程序版本写回扫码结果,再走 PLC 写值流程 */
async confirmLoadDialog() {
const opName = this.loadConfirmDialog.opName
const version = this.loadConfirmDialog.detail.程序版本
? this.loadConfirmDialog.detail.程序版本.toString()
: ''
if (!version) {
this.$message.warning('请选择或输入程序版本!')
return return
} }
const result = opName === this.opNameList[0] ? this.ws1Result : this.ws2Result
result.程序版本 = version
this.setLastUsedVersion(opName, version)
this.loadConfirmDialog.loading = true
try {
await this.doPartLoad(opName)
} finally {
this.loadConfirmDialog.loading = false
this.loadConfirmDialog.visible = false
}
},
/** 实际执行 PLC 写值上线流程;成功后写入手动上线记录 */
async doPartLoad(opName) {
const result = opName === this.opNameList[0] ? this.ws1Result : this.ws2Result
const partLoadStatus = opName === this.opNameList[0] ? this.ws1PartLoadStatus : this.ws2PartLoadStatus const partLoadStatus = opName === this.opNameList[0] ? this.ws1PartLoadStatus : this.ws2PartLoadStatus
const programVersion = result.程序版本 ? result.程序版本.toString() : ''
const partCode = result.扫码物料编码 || ''
const plcValue = await this.getPLCValue(opName, '205015') const plcValue = await this.getPLCValue(opName, '205015')
if (plcValue === 'True') { if (plcValue === 'True') {
partLoadStatus.success = false partLoadStatus.success = false
@@ -824,34 +997,112 @@ export default {
return return
} }
this.writePLCValue(opName, PROGRAM_VERSION_TAG_ID, programVersion) try {
.then(response => { const r1 = await this.writePLCValue(opName, PROGRAM_VERSION_TAG_ID, programVersion)
if (response.data.code != 0) { if (r1.data.code != 0) throw new Error(r1.data.msg || '程序版本写入失败')
return Promise.reject(new Error(response.data.msg || '程序版本写入失败')) const r2 = await this.writePLCValue(opName, '205015', 'true')
} if (r2.data.code == 0) {
return this.writePLCValue(opName, '205015', 'true')
})
.then(response => {
if (response.data.code == 0) {
partLoadStatus.success = true partLoadStatus.success = true
partLoadStatus.message = '' partLoadStatus.message = ''
partLoadStatus.show = true partLoadStatus.show = true
this.$message.success('申请上线成功!') this.$message.success('申请上线成功!')
this.recordManualLoad(opName, partCode, programVersion)
} else { } else {
partLoadStatus.success = false partLoadStatus.success = false
partLoadStatus.message = response.data.msg || '申请失败' partLoadStatus.message = r2.data.msg || '申请失败'
partLoadStatus.show = true partLoadStatus.show = true
this.$message.error(response.data.msg) this.$message.error(r2.data.msg)
} }
}) } catch (error) {
.catch(error => {
partLoadStatus.success = false partLoadStatus.success = false
partLoadStatus.message = error.message partLoadStatus.message = error.message
partLoadStatus.show = true partLoadStatus.show = true
this.$message.error('申请上线失败' + error.message) this.$message.error('申请上线失败' + error.message)
}
},
/** 写入手动上线 (物料编码,程序版本) 记录 */
recordManualLoad(opName, partCode, version) {
if (!partCode || !version) return
const param = []
param[0] = ['物料编码', partCode]
param[1] = ['程序版本', version]
param[2] = ['工位号', opName]
param[3] = ['操作人', this.name || '']
const Data = this.CreateData('12', 'ZL_物料程序版本_手动上线记录_Insert', param)
this.ExecDatabase(Data).catch(err => console.error('记录手动上线失败:', err))
},
// ==================== 历史程序版本弹窗 ====================
/** 查询某物料编码的历史程序版本(按最近使用时间倒序) */
loadHistoryVersions(partCode) {
if (!partCode) return Promise.resolve([])
const param = [['物料编码', partCode]]
const Data = this.CreateData('11', 'ZL_物料程序版本_手动上线记录_查询历史版本', param)
return this.ExecDatabase(Data)
.then(resp => Array.isArray(resp.data) ? resp.data.map(r => r.程序版本).filter(v => !!v) : [])
.catch(err => {
console.error('查询历史程序版本失败:', err)
return []
}) })
}, },
/** 打开"选择程序版本"弹窗:扫码验证通过且为当前工位时触发 */
async openVersionDialog(opName) {
const result = opName === this.opNameList[0] ? this.ws1Result : this.ws2Result
const partCode = result.扫码物料编码 || ''
const historyList = await this.loadHistoryVersions(partCode)
const lastUsed = this.getLastUsedVersion(opName)
this.versionDialog.opName = opName
this.versionDialog.partCode = partCode
this.versionDialog.historyList = historyList
this.versionDialog.selectedVersion = (result.程序版本 || lastUsed || historyList[0] || '')
this.versionDialog.visible = true
},
/** 首次扫码:从数据库取该物料最近一次的程序版本,直接回填,不弹窗 */
async autoFillFirstVersion(opName) {
const result = opName === this.opNameList[0] ? this.ws1Result : this.ws2Result
const partCode = result.扫码物料编码 || ''
if (!partCode) return
const historyList = await this.loadHistoryVersions(partCode)
if (historyList && historyList.length > 0) {
result.程序版本 = historyList[0]
this.setLastUsedVersion(opName, historyList[0])
}
},
/** "选择程序版本"弹窗确定:把版本回填到对应工位,不自动上线 */
confirmVersionDialog() {
const version = this.versionDialog.selectedVersion ? this.versionDialog.selectedVersion.toString() : ''
if (!version) {
this.$message.warning('请选择或输入程序版本!')
return
}
const opName = this.versionDialog.opName
const result = opName === this.opNameList[0] ? this.ws1Result : this.ws2Result
result.程序版本 = version
this.setLastUsedVersion(opName, version)
this.versionDialog.visible = false
},
/** 读取该工位上一次使用的程序版本localStorage */
getLastUsedVersion(opName) {
try {
return localStorage.getItem(LAST_VERSION_STORAGE_PREFIX + opName) || ''
} catch (e) {
return ''
}
},
/** 持久化保存该工位本次使用的程序版本localStorage */
setLastUsedVersion(opName, version) {
try {
localStorage.setItem(LAST_VERSION_STORAGE_PREFIX + opName, version || '')
} catch (e) { /* ignore */ }
},
// ==================== 读码 / 写码 / 清码 ==================== // ==================== 读码 / 写码 / 清码 ====================
/** 写码 - 发送写码请求并启动10秒轮询 */ /** 写码 - 发送写码请求并启动10秒轮询 */
@@ -1035,4 +1286,400 @@ export default {
font-size: inherit !important; font-size: inherit !important;
} }
} }
.material-version-row {
display: flex;
align-items: flex-end;
gap: 8px;
margin-top: 4px;
.mv-version {
flex: 0 0 90px;
}
.mv-material {
flex: 2 1 0;
min-width: 0;
}
.mv-result {
flex: 1 1 0;
min-width: 120px;
}
.top-label {
font-size: 14px;
color: #606266;
font-weight: bold;
line-height: 1.4;
padding: 0 0 2px 2px;
}
.version-input {
::v-deep .el-input__inner {
text-align: right;
cursor: pointer;
background-color: #fff;
}
}
.result-display {
height: 32px;
display: flex;
align-items: center;
justify-content: center;
.result-pass,
.result-fail,
.result-error {
display: inline-block;
width: 100%;
font-weight: bold;
font-size: 14px;
padding: 4px 8px;
border-radius: 4px;
text-align: center;
}
.result-pass { background: #aaff33; color: #222; }
.result-fail { background: #ff3b3b; color: #fff; }
.result-error { background: #ffe066; color: #222; }
}
}
.upline-form {
::v-deep .el-form-item__label {
width: 70px !important;
padding-right: 6px;
}
::v-deep .el-form-item__content {
width: calc(100% - 70px) !important;
}
}
</style>
<style lang="scss">
.version-dialog {
border-radius: 12px;
overflow: hidden;
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.18);
.el-dialog__header {
padding: 18px 24px 14px;
background: linear-gradient(135deg, #f5f9ff 0%, #ffffff 100%);
border-bottom: 1px solid #ebeef5;
}
.el-dialog__headerbtn {
top: 18px;
right: 20px;
}
.el-dialog__body {
padding: 18px 24px 4px;
}
.el-dialog__footer {
padding: 12px 24px 18px;
}
.vd-header {
.vd-title {
font-size: 17px;
font-weight: 600;
color: #303133;
line-height: 1.3;
}
.vd-subtitle {
margin-top: 4px;
font-size: 12px;
color: #909399;
line-height: 1.4;
word-break: break-all;
}
}
.vd-body {
.vd-section-title {
font-size: 13px;
font-weight: 600;
color: #606266;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
.vd-count {
font-size: 12px;
font-weight: normal;
color: #909399;
}
}
.vd-section-title-spaced {
margin-top: 18px;
}
.vd-tag-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
max-height: 180px;
overflow-y: auto;
padding: 4px 4px 4px 0;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #dcdfe6;
border-radius: 3px;
}
}
.vd-tag {
display: inline-flex;
align-items: center;
padding: 6px 14px;
min-width: 56px;
justify-content: center;
border-radius: 16px;
background: #f4f6fa;
color: #606266;
font-size: 13px;
font-weight: 500;
cursor: pointer;
border: 1px solid transparent;
transition: all 0.18s ease;
user-select: none;
&:hover {
background: #e8f0ff;
color: #409eff;
}
&.is-active {
background: #409eff;
color: #fff;
border-color: #409eff;
box-shadow: 0 4px 10px rgba(64, 158, 255, 0.28);
}
}
.vd-empty {
padding: 18px 12px;
background: #fafbfc;
border: 1px dashed #dcdfe6;
border-radius: 8px;
text-align: center;
.vd-empty-text {
font-size: 13px;
color: #909399;
}
.vd-empty-hint {
margin-top: 4px;
font-size: 12px;
color: #c0c4cc;
}
}
}
.vd-footer {
text-align: right;
}
}
.load-confirm-dialog {
border-radius: 14px;
overflow: hidden;
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.18);
.el-dialog__header {
padding: 18px 24px 14px;
background: linear-gradient(135deg, #fff7eb 0%, #ffffff 100%);
border-bottom: 1px solid #ebeef5;
}
.el-dialog__headerbtn {
top: 18px;
right: 20px;
}
.el-dialog__body {
padding: 18px 22px 6px;
background: #f7f9fc;
}
.el-dialog__footer {
padding: 14px 22px 18px;
background: #fff;
border-top: 1px solid #ebeef5;
}
.lcd-header {
.lcd-title {
font-size: 18px;
font-weight: 600;
color: #303133;
line-height: 1.3;
}
.lcd-subtitle {
margin-top: 4px;
font-size: 12px;
color: #909399;
line-height: 1.4;
}
}
.lcd-body {
display: flex;
flex-direction: column;
gap: 12px;
}
.lcd-card {
background: #ffffff;
border: 1px solid #ebeef5;
border-radius: 10px;
padding: 14px 16px;
position: relative;
&.lcd-card-primary {
background: linear-gradient(180deg, #ffffff 0%, #fafcff 100%);
border-color: #d9e7ff;
padding-top: 22px;
}
}
.lcd-card-tag {
position: absolute;
top: -10px;
left: 14px;
background: #ff9d00;
color: #fff;
font-size: 11px;
font-weight: 600;
padding: 3px 10px;
border-radius: 10px;
letter-spacing: 0.5px;
box-shadow: 0 2px 6px rgba(255, 157, 0, 0.35);
}
.lcd-row-2col {
display: grid;
grid-template-columns: 1.4fr 1fr;
gap: 14px;
align-items: end;
}
.lcd-row-2col-key {
grid-template-columns: 0.5fr 1.5fr;
margin-bottom: 12px;
}
.lcd-field {
.lcd-label {
font-size: 12px;
color: #909399;
margin-bottom: 6px;
font-weight: 500;
}
.lcd-value {
font-size: 15px;
color: #303133;
font-weight: 500;
word-break: break-all;
line-height: 1.4;
padding: 7px 12px;
background: #f4f6fa;
border-radius: 6px;
min-height: 32px;
display: flex;
align-items: center;
}
.lcd-value-strong {
font-weight: 600;
color: #ff7a00;
background: #fff5e6;
border: 1px solid #ffe1b3;
}
.lcd-value-right {
justify-content: flex-end;
text-align: right;
}
.lcd-value-mono {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 14px;
letter-spacing: 0.3px;
}
}
.lcd-field-program-no {
.lcd-value {
background: #f0f7ff;
border: 1px dashed #b3d4ff;
color: #1f6feb;
font-weight: 600;
}
}
.lcd-select {
width: 100%;
.el-input__inner {
height: 32px;
line-height: 32px;
}
}
.lcd-info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px 14px;
}
.lcd-info-item {
display: flex;
flex-direction: column;
&.lcd-info-item-full {
grid-column: 1 / -1;
}
}
.lcd-info-label {
font-size: 12px;
color: #909399;
margin-bottom: 4px;
}
.lcd-info-value {
font-size: 13px;
color: #303133;
word-break: break-all;
line-height: 1.5;
font-weight: 500;
}
.lcd-info-value-mono {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 12.5px;
color: #606266;
background: #f4f6fa;
padding: 6px 10px;
border-radius: 4px;
}
.lcd-footer {
text-align: right;
.el-button {
min-width: 90px;
}
.el-button--primary {
min-width: 130px;
font-weight: 600;
}
}
}
</style> </style>

View File

@@ -6,14 +6,14 @@ export const name4_config = `OP9999`
// 工厂网段 192.168.0.180 生产环境需使用 // 工厂网段 192.168.0.180 生产环境需使用
// 开发环境使用 127.0.0.1 // 开发环境使用 127.0.0.1
// var url = `http://192.168.0.180:10140` var url = `http://192.168.0.180:10140`
// var api = `http://192.168.0.180:9981` var api = `http://192.168.0.180:9981`
var url = `http://127.0.0.1:10140` // var url = `http://127.0.0.1:10140`
var api = `http://127.0.0.1:9981` // var api = `http://127.0.0.1:9981`
// var url = `http://192.168.0.180:10140` // var url = `http://192.168.0.180:10140`
// export const mqttIp = `192.168.0.180` // export const mqttIp = `127.0.0.1`
export const mqttIp = `192.168.0.180` export const mqttIp = `192.168.0.180`
export const mqttTopic = `VerifyResult` export const mqttTopic = `VerifyResult`
export const mqttInitRequestTopic = `MesServer` export const mqttInitRequestTopic = `MesServer`