Browse Source

init

master
han\hanst 1 week ago
parent
commit
ab39091ad4
  1. 20
      src/api/rack/closedLoop.js
  2. 97
      src/views/modules/rack/execution-control.vue
  3. 220
      src/views/modules/rack/external-device-config.vue
  4. 428
      src/views/modules/rack/package-label-management.vue

20
src/api/rack/closedLoop.js

@ -79,5 +79,25 @@ export const productionComplete = (jobCode, data) => createAPI('/rack/closedLoop
export const listManualRecord = (data) => createAPI('/rack/closedLoop/manual/list', 'post', data || {}) export const listManualRecord = (data) => createAPI('/rack/closedLoop/manual/list', 'post', data || {})
export const saveManualRecord = (data) => createAPI('/rack/closedLoop/manual/save', 'post', data) export const saveManualRecord = (data) => createAPI('/rack/closedLoop/manual/save', 'post', data)
export const listExternalDevice = (data) => createAPI('/rack/closedLoop/device/list', 'post', data || {})
export const saveExternalDevice = (data) => createAPI('/rack/closedLoop/device/save', 'post', data)
export const updateExternalDevice = (data) => createAPI('/rack/closedLoop/device/update', 'post', data)
export const deleteExternalDevice = (deviceId) => createAPI(`/rack/closedLoop/device/delete/${deviceId}`, 'post', {})
export const deleteExternalDeviceByCode = (deviceCode) => createAPI('/rack/closedLoop/device/delete-by-code', 'post', { deviceCode })
export const ingestPlcEvent = (data) => createAPI('/rack/closedLoop/plc/ingest', 'post', data || {})
export const listPlcStatus = () => createAPI('/rack/closedLoop/plc/status', 'get', {})
export const listPackageOrder = (data) => createAPI('/rack/closedLoop/package/order/list', 'post', data || {})
export const savePackageOrder = (data) => createAPI('/rack/closedLoop/package/order/save', 'post', data || {})
export const updatePackageOrder = (data) => createAPI('/rack/closedLoop/package/order/update', 'post', data || {})
export const deletePackageOrderByNo = (packageNo) => createAPI('/rack/closedLoop/package/order/delete-by-no', 'post', { packageNo })
export const listLabelTemplate = (data) => createAPI('/rack/closedLoop/package/template/list', 'post', data || {})
export const saveLabelTemplate = (data) => createAPI('/rack/closedLoop/package/template/save', 'post', data || {})
export const updateLabelTemplate = (data) => createAPI('/rack/closedLoop/package/template/update', 'post', data || {})
export const deleteLabelTemplateByCode = (templateCode) => createAPI('/rack/closedLoop/package/template/delete-by-code', 'post', { templateCode })
export const printPackageLabel = (data) => createAPI('/rack/closedLoop/package/label/print', 'post', data || {})
export const listLabelPrintLog = (data) => createAPI('/rack/closedLoop/package/print-log/list', 'post', data || {})
export const traceQuery = (data) => createAPI('/rack/closedLoop/trace/query', 'post', data || {}) export const traceQuery = (data) => createAPI('/rack/closedLoop/trace/query', 'post', data || {})
export const reportOverview = () => createAPI('/rack/closedLoop/report/overview', 'get', {}) export const reportOverview = () => createAPI('/rack/closedLoop/report/overview', 'get', {})

97
src/views/modules/rack/execution-control.vue

@ -37,8 +37,24 @@
style="width: 240px" style="width: 240px"
@keyup.enter.native="onRackScanned" /> @keyup.enter.native="onRackScanned" />
</el-form-item> </el-form-item>
<el-form-item label="批次下挂扫码(可选)">
<el-input
v-model.trim="batchDownHangCode"
clearable
placeholder="可扫码批次号后直接下挂"
style="width: 240px" />
</el-form-item>
</el-form> </el-form>
<el-alert
v-if="plcOfflineCount > 0"
title="PLC采集告警"
:description="plcStatusSummary"
type="warning"
:closable="false"
show-icon
style="margin-bottom: 8px;" />
<el-card shadow="never" style="margin-top: 8px;"> <el-card shadow="never" style="margin-top: 8px;">
<el-table class="data-table" :data="production.detailRows || []" border style="width:100%" height="180"> <el-table class="data-table" :data="production.detailRows || []" border style="width:100%" height="180">
<el-table-column type="index" width="50" /> <el-table-column type="index" width="50" />
@ -66,6 +82,32 @@
<el-button plain class="search-btn" :loading="simulateSubmitting" :disabled="!canSimulatePass" @click="simulateStationPassAll">模拟全部剩余池</el-button> <el-button plain class="search-btn" :loading="simulateSubmitting" :disabled="!canSimulatePass" @click="simulateStationPassAll">模拟全部剩余池</el-button>
<el-button plain class="reset-btn" :loading="completeSubmitting" :disabled="!canComplete" @click="completeAction">任务完工</el-button> <el-button plain class="reset-btn" :loading="completeSubmitting" :disabled="!canComplete" @click="completeAction">任务完工</el-button>
</div> </div>
<el-table
class="data-table"
:data="plcStatusRows"
border
stripe
style="width:100%; margin-top: 10px;"
height="150"
v-loading="plcStatusLoading">
<el-table-column type="index" width="45" />
<el-table-column prop="deviceCode" label="PLC设备" width="140" />
<el-table-column prop="lineId" label="线体" width="100" />
<el-table-column prop="stepCode" label="工序" width="110" />
<el-table-column label="在线状态" width="100">
<template slot-scope="scope">
<a :style="{ color: scope.row.online ? '#67C23A' : '#F56C6C' }">{{ scope.row.online ? '在线' : '离线' }}</a>
</template>
</el-table-column>
<el-table-column label="最近心跳" min-width="160">
<template slot-scope="scope">{{ formatDateTime(scope.row.heartbeatTime) }}</template>
</el-table-column>
<el-table-column label="最近过站" min-width="160">
<template slot-scope="scope">{{ formatDateTime(scope.row.latestEventTime) }}</template>
</el-table-column>
<el-table-column prop="offlineReason" label="异常原因" min-width="140" show-overflow-tooltip />
</el-table>
</el-card> </el-card>
<el-card style="margin-top: 12px;"> <el-card style="margin-top: 12px;">
@ -122,6 +164,7 @@
import { import {
listJob, listJob,
listRack, listRack,
listPlcStatus,
productionView, productionView,
productionStart, productionStart,
productionUpHang, productionUpHang,
@ -139,6 +182,7 @@ export default {
jobScanCode: '', jobScanCode: '',
materialScanCode: '', materialScanCode: '',
rackScanCode: '', rackScanCode: '',
batchDownHangCode: '',
selectedJobCode: '', selectedJobCode: '',
selectedRackCode: '', selectedRackCode: '',
selectedBindingRackCode: '', selectedBindingRackCode: '',
@ -150,7 +194,10 @@ export default {
simulateSubmitting: false, simulateSubmitting: false,
completeSubmitting: false, completeSubmitting: false,
autoRefresh: true, autoRefresh: true,
timer: null
timer: null,
plcStatusRows: [],
plcStatusLoading: false,
plcStatusTimer: null
} }
}, },
computed: { computed: {
@ -168,7 +215,9 @@ export default {
}, },
canDownHang () { canDownHang () {
const hasAnyActiveRack = this.getActiveRackCodes().length > 0 const hasAnyActiveRack = this.getActiveRackCodes().length > 0
return !!this.selectedJobCode && hasAnyActiveRack && !!(this.selectedRackCode || this.rackScanCode) && !this.downHangSubmitting
const hasRackCode = !!(this.selectedRackCode || this.rackScanCode)
const hasBatchCode = !!this.batchDownHangCode
return !!this.selectedJobCode && hasAnyActiveRack && (hasRackCode || hasBatchCode) && !this.downHangSubmitting
}, },
canComplete () { canComplete () {
const hasAnyActiveRack = this.getActiveRackCodes().length > 0 const hasAnyActiveRack = this.getActiveRackCodes().length > 0
@ -223,14 +272,29 @@ export default {
return '先点“开始生产”下达任务,再扫码来料和挂具,校验通过后自动上挂。' return '先点“开始生产”下达任务,再扫码来料和挂具,校验通过后自动上挂。'
} }
return '请先扫码来料,再扫码挂具;校验通过后会自动上挂。全部来料完成后再点“任务完工”。' return '请先扫码来料,再扫码挂具;校验通过后会自动上挂。全部来料完成后再点“任务完工”。'
},
plcOfflineCount () {
return (this.plcStatusRows || []).filter(item => item && !item.online).length
},
plcStatusSummary () {
const rows = (this.plcStatusRows || []).filter(item => item && !item.online)
if (!rows.length) {
return ''
}
const names = rows.slice(0, 3).map(item => `${item.deviceCode || '未知设备'}${item.offlineReason ? `(${item.offlineReason})` : ''}`)
const extra = rows.length > 3 ? `${rows.length}` : ''
return `${names.join('、')}${extra},请检查采集链路或使用手工补录兜底。`
} }
}, },
mounted () { mounted () {
this.refreshBaseOptions() this.refreshBaseOptions()
this.startTimer() this.startTimer()
this.loadPlcStatus()
this.startPlcStatusTimer()
}, },
beforeDestroy () { beforeDestroy () {
this.stopTimer() this.stopTimer()
this.stopPlcStatusTimer()
}, },
methods: { methods: {
async refreshBaseOptions () { async refreshBaseOptions () {
@ -254,6 +318,7 @@ export default {
this.selectedJobCode = this.jobScanCode this.selectedJobCode = this.jobScanCode
this.materialScanCode = '' this.materialScanCode = ''
this.rackScanCode = '' this.rackScanCode = ''
this.batchDownHangCode = ''
this.selectedRackCode = '' this.selectedRackCode = ''
await this.onJobChange() await this.onJobChange()
}, },
@ -380,11 +445,12 @@ export default {
return return
} }
this.syncScanCodes() this.syncScanCodes()
const hasBatchCode = !!String(this.batchDownHangCode || '').trim()
if (!this.selectedRackCode && this.getActiveRackCodes().length === 1) { if (!this.selectedRackCode && this.getActiveRackCodes().length === 1) {
this.selectedRackCode = this.getActiveRackCodes()[0] this.selectedRackCode = this.getActiveRackCodes()[0]
} }
if (!this.selectedRackCode) {
this.$message.warning('请先扫码要下挂的挂具')
if (!this.selectedRackCode && !hasBatchCode) {
this.$message.warning('请先扫码挂具码,或扫码批次编码执行下挂')
return return
} }
const downRackCode = this.selectedRackCode const downRackCode = this.selectedRackCode
@ -393,6 +459,7 @@ export default {
const resp = await productionDownHang({ const resp = await productionDownHang({
jobCode: this.selectedJobCode, jobCode: this.selectedJobCode,
rackCode: downRackCode, rackCode: downRackCode,
batchCode: hasBatchCode ? this.batchDownHangCode : '',
operatorName: this.operatorName operatorName: this.operatorName
}) })
this.assertApiSuccess(resp, '下挂失败') this.assertApiSuccess(resp, '下挂失败')
@ -400,6 +467,7 @@ export default {
this.$message.success('下挂成功') this.$message.success('下挂成功')
this.selectedRackCode = '' this.selectedRackCode = ''
this.rackScanCode = '' this.rackScanCode = ''
this.batchDownHangCode = ''
} catch (e) { } catch (e) {
this.$message.error(this.resolveErrorMessage(e)) this.$message.error(this.resolveErrorMessage(e))
} finally { } finally {
@ -694,6 +762,27 @@ export default {
this.timer = null this.timer = null
} }
}, },
async loadPlcStatus () {
this.plcStatusLoading = true
try {
const { data } = await listPlcStatus()
this.plcStatusRows = data.rows || []
} finally {
this.plcStatusLoading = false
}
},
startPlcStatusTimer () {
this.stopPlcStatusTimer()
this.plcStatusTimer = setInterval(() => {
this.loadPlcStatus()
}, 10000)
},
stopPlcStatusTimer () {
if (this.plcStatusTimer) {
clearInterval(this.plcStatusTimer)
this.plcStatusTimer = null
}
},
formatDateTime (v) { formatDateTime (v) {
if (!v) return '-' if (!v) return '-'
let date = null let date = null

220
src/views/modules/rack/external-device-config.vue

@ -0,0 +1,220 @@
<template>
<div class="mod-config">
<el-card>
<div slot="header" class="card-title-row">
<span>外采设备配置</span>
<el-button plain class="add-btn" @click="openDialog()">新增设备</el-button>
</div>
<el-form :inline="true" label-position="top" class="query-form">
<el-form-item label="设备编码">
<el-input v-model.trim="searchData.deviceCode" clearable placeholder="例如 PLC-L01-01" style="width: 180px" />
</el-form-item>
<el-form-item label="设备名称">
<el-input v-model.trim="searchData.deviceName" clearable placeholder="模糊查询" style="width: 180px" />
</el-form-item>
<el-form-item label="线体">
<el-input v-model.trim="searchData.lineId" clearable placeholder="例如 LINE-01" style="width: 140px" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="searchData.status" clearable placeholder="全部" style="width: 120px">
<el-option label="启用" value="启用" />
<el-option label="停用" value="停用" />
</el-select>
</el-form-item>
<el-form-item label=" ">
<el-button plain class="search-btn" :loading="loading" @click="loadList">查询</el-button>
<el-button plain class="reset-btn" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table class="data-table" :data="dataList" border stripe style="width:100%" v-loading="loading" :height="tableHeight">
<el-table-column type="index" width="50" />
<el-table-column prop="deviceCode" label="设备编码" width="160" />
<el-table-column prop="deviceName" label="设备名称" width="170" />
<el-table-column prop="deviceType" label="设备类型" width="90" />
<el-table-column prop="sourceType" label="数据源" width="90" />
<el-table-column prop="lineId" label="线体" width="100" />
<el-table-column prop="stationId" label="站点ID" width="140" />
<el-table-column prop="stepCode" label="工序编码" width="130" />
<el-table-column label="地址" min-width="160">
<template slot-scope="scope">{{ `${scope.row.ip || '-'}:${scope.row.port || '-'}` }}</template>
</el-table-column>
<el-table-column prop="unitId" label="UnitId" width="90" />
<el-table-column prop="status" label="状态" width="90" />
<el-table-column label="最近心跳" min-width="170">
<template slot-scope="scope">{{ formatDateTime(scope.row.lastHeartbeatTime) }}</template>
</el-table-column>
<el-table-column label="操作" width="160" fixed="right">
<template slot-scope="scope">
<a @click="openDialog(scope.row)">修改</a>
<a style="color:#F56C6C;margin-left:10px" @click="handleDelete(scope.row)">删除</a>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog
:title="form.deviceId ? '修改外采设备' : '新增外采设备'"
:visible.sync="dialogVisible"
width="760px"
:close-on-click-modal="false"
v-drag>
<el-form :model="form" label-position="top" class="device-form">
<el-row :gutter="12">
<el-col :span="8"><el-form-item label="设备编码" required><el-input v-model.trim="form.deviceCode" :disabled="!!form.deviceId" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="设备名称"><el-input v-model.trim="form.deviceName" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="设备类型"><el-input v-model.trim="form.deviceType" /></el-form-item></el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="8"><el-form-item label="数据源类型"><el-input v-model.trim="form.sourceType" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="线体"><el-input v-model.trim="form.lineId" placeholder="例如 LINE-01" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="状态"><el-select v-model="form.status" style="width:100%"><el-option label="启用" value="启用" /><el-option label="停用" value="停用" /></el-select></el-form-item></el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="8"><el-form-item label="站点ID"><el-input v-model.trim="form.stationId" placeholder="例如 PLC-POOL-001" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="工序编码" required><el-input v-model.trim="form.stepCode" placeholder="例如 POOL-001" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="UnitId"><el-input-number v-model="form.unitId" :min="1" :precision="0" style="width:100%" /></el-form-item></el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="12"><el-form-item label="IP"><el-input v-model.trim="form.ip" placeholder="例如 192.168.1.10" /></el-form-item></el-col>
<el-col :span="12"><el-form-item label="端口"><el-input-number v-model="form.port" :min="1" :precision="0" style="width:100%" /></el-form-item></el-col>
</el-row>
<el-form-item label="备注"><el-input v-model.trim="form.remark" type="textarea" :rows="3" /></el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button plain class="reset-btn" @click="dialogVisible = false">取消</el-button>
<el-button plain class="add-btn" :loading="submitLoading" @click="submit">保存</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import {
listExternalDevice,
saveExternalDevice,
updateExternalDevice,
deleteExternalDevice
} from '@/api/rack/closedLoop'
const emptyForm = () => ({
deviceId: null,
deviceCode: '',
deviceName: '',
deviceType: 'PLC',
sourceType: 'PLC',
lineId: '',
stationId: '',
stepCode: '',
ip: '',
port: 502,
unitId: 1,
status: '启用',
remark: ''
})
export default {
data () {
return {
tableHeight: window.innerHeight - 290,
loading: false,
submitLoading: false,
dialogVisible: false,
dataList: [],
searchData: {
deviceCode: '',
deviceName: '',
lineId: '',
status: ''
},
form: emptyForm()
}
},
mounted () {
this.loadList()
},
methods: {
async loadList () {
this.loading = true
try {
const { data } = await listExternalDevice(this.searchData)
this.dataList = data.rows || []
} finally {
this.loading = false
}
},
resetQuery () {
this.searchData = {
deviceCode: '',
deviceName: '',
lineId: '',
status: ''
}
this.loadList()
},
openDialog (row) {
this.form = row
? {
...emptyForm(),
...row
}
: emptyForm()
this.dialogVisible = true
},
async submit () {
if (!this.form.deviceCode || !this.form.stepCode) {
this.$message.warning('请填写设备编码和工序编码')
return
}
this.submitLoading = true
try {
if (this.form.deviceId) {
await updateExternalDevice(this.form)
this.$message.success('修改成功')
} else {
await saveExternalDevice(this.form)
this.$message.success('新增成功')
}
this.dialogVisible = false
await this.loadList()
} finally {
this.submitLoading = false
}
},
async handleDelete (row) {
try {
await this.$confirm(`确认删除外采设备:${row.deviceCode} 吗?`, '提示', { type: 'warning' })
await deleteExternalDevice(row.deviceId)
this.$message.success('删除成功')
await this.loadList()
} catch (e) {}
},
formatDateTime (value) {
if (!value) {
return '-'
}
const date = new Date(value)
if (Number.isNaN(date.getTime())) {
return value
}
const pad = n => String(n).padStart(2, '0')
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
}
}
}
</script>
<style scoped>
.card-title-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.query-form {
margin-bottom: 8px;
}
.dialog-footer {
display: inline-flex;
gap: 8px;
}
</style>

428
src/views/modules/rack/package-label-management.vue

@ -0,0 +1,428 @@
<template>
<div class="mod-config">
<el-card>
<div slot="header" class="card-title-row">
<span>扫码包装与标签打印</span>
</div>
<el-tabs v-model="activeTab">
<el-tab-pane label="包装作业" name="package">
<el-form :inline="true" label-position="top" class="query-form">
<el-form-item label="包装单号">
<el-input v-model.trim="packageQuery.packageNo" clearable style="width: 180px" />
</el-form-item>
<el-form-item label="任务单号">
<el-input v-model.trim="packageQuery.jobCode" clearable style="width: 180px" />
</el-form-item>
<el-form-item label="批次编码">
<el-input v-model.trim="packageQuery.batchCode" clearable style="width: 180px" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="packageQuery.status" clearable style="width: 120px">
<el-option label="待打印" value="待打印" />
<el-option label="已打印" value="已打印" />
</el-select>
</el-form-item>
<el-form-item label=" ">
<el-button plain class="search-btn" :loading="packageLoading" @click="loadPackageList">查询</el-button>
<el-button plain class="add-btn" @click="openPackageDialog()">新增包装单</el-button>
</el-form-item>
</el-form>
<el-table class="data-table" :data="packageList" border stripe style="width:100%" :height="tableHeight" v-loading="packageLoading">
<el-table-column type="index" width="50" />
<el-table-column prop="packageNo" label="包装单号" width="170" />
<el-table-column prop="jobCode" label="任务单号" width="160" />
<el-table-column prop="batchCode" label="批次编码" width="150" />
<el-table-column prop="rackCode" label="挂具码" width="120" />
<el-table-column prop="qty" label="数量" width="100" />
<el-table-column prop="status" label="状态" width="100" />
<el-table-column prop="labelTemplateCode" label="标签模板" width="140" />
<el-table-column label="最近打印" min-width="170">
<template slot-scope="scope">{{ formatDateTime(scope.row.lastPrintTime) }}</template>
</el-table-column>
<el-table-column label="操作" width="260" fixed="right">
<template slot-scope="scope">
<a @click="openPackageDialog(scope.row)">修改</a>
<a style="margin-left: 10px" @click="openPrintDialog(scope.row)">打印标签</a>
<a style="margin-left: 10px" @click="loadPrintLogs(scope.row.packageNo)">打印记录</a>
<a style="color:#F56C6C;margin-left:10px" @click="deletePackage(scope.row)">删除</a>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="标签模板" name="template">
<el-form :inline="true" label-position="top" class="query-form">
<el-form-item label="模板编码">
<el-input v-model.trim="templateQuery.templateCode" clearable style="width: 180px" />
</el-form-item>
<el-form-item label="模板名称">
<el-input v-model.trim="templateQuery.templateName" clearable style="width: 180px" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="templateQuery.status" clearable style="width: 120px">
<el-option label="启用" value="启用" />
<el-option label="停用" value="停用" />
</el-select>
</el-form-item>
<el-form-item label=" ">
<el-button plain class="search-btn" :loading="templateLoading" @click="loadTemplateList">查询</el-button>
<el-button plain class="add-btn" @click="openTemplateDialog()">新增模板</el-button>
</el-form-item>
</el-form>
<el-table class="data-table" :data="templateList" border stripe style="width:100%" :height="tableHeight" v-loading="templateLoading">
<el-table-column type="index" width="50" />
<el-table-column prop="templateCode" label="模板编码" width="160" />
<el-table-column prop="templateName" label="模板名称" width="180" />
<el-table-column prop="status" label="状态" width="90" />
<el-table-column prop="templateContent" label="模板内容" min-width="320" show-overflow-tooltip />
<el-table-column label="操作" width="160" fixed="right">
<template slot-scope="scope">
<a @click="openTemplateDialog(scope.row)">修改</a>
<a style="color:#F56C6C;margin-left:10px" @click="deleteTemplate(scope.row)">删除</a>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</el-card>
<el-card style="margin-top: 12px;">
<div slot="header" class="card-title-row">
<span>标签打印日志</span>
<span class="sub-text">{{ printLogKeyword ? `当前筛选:${printLogKeyword}` : '默认显示最近记录' }}</span>
</div>
<el-table class="data-table" :data="printLogList" border stripe style="width:100%" max-height="260" v-loading="printLogLoading">
<el-table-column type="index" width="50" />
<el-table-column prop="printNo" label="打印流水号" width="190" />
<el-table-column prop="packageNo" label="包装单号" width="170" />
<el-table-column prop="templateCode" label="模板编码" width="130" />
<el-table-column prop="printSeq" label="序号" width="80" />
<el-table-column prop="printStatus" label="状态" width="90" />
<el-table-column prop="operatorName" label="操作人" width="120" />
<el-table-column label="打印时间" min-width="170">
<template slot-scope="scope">{{ formatDateTime(scope.row.printTime) }}</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog
:title="packageForm.packageId ? '修改包装单' : '新增包装单'"
:visible.sync="packageDialogVisible"
width="720px"
:close-on-click-modal="false"
v-drag>
<el-form :model="packageForm" label-position="top">
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="任务单号" required>
<el-select v-model="packageForm.jobCode" filterable clearable placeholder="请选择任务单号" style="width:100%">
<el-option v-for="item in jobOptions" :key="item.jobCode" :label="`${item.jobCode} / ${item.status || '-'}`" :value="item.jobCode" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12"><el-form-item label="批次编码"><el-input v-model.trim="packageForm.batchCode" /></el-form-item></el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="12"><el-form-item label="挂具码"><el-input v-model.trim="packageForm.rackCode" /></el-form-item></el-col>
<el-col :span="12"><el-form-item label="数量"><el-input-number v-model="packageForm.qty" :min="0" :precision="0" style="width:100%" /></el-form-item></el-col>
</el-row>
<el-form-item label="备注"><el-input v-model.trim="packageForm.remark" type="textarea" :rows="3" /></el-form-item>
</el-form>
<span slot="footer">
<el-button plain class="reset-btn" @click="packageDialogVisible = false">取消</el-button>
<el-button plain class="add-btn" :loading="packageSubmitLoading" @click="submitPackage">保存</el-button>
</span>
</el-dialog>
<el-dialog
:title="templateForm.templateId ? '修改标签模板' : '新增标签模板'"
:visible.sync="templateDialogVisible"
width="720px"
:close-on-click-modal="false"
v-drag>
<el-form :model="templateForm" label-position="top">
<el-row :gutter="12">
<el-col :span="8"><el-form-item label="模板编码" required><el-input v-model.trim="templateForm.templateCode" :disabled="!!templateForm.templateId" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="模板名称" required><el-input v-model.trim="templateForm.templateName" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="状态"><el-select v-model="templateForm.status" style="width:100%"><el-option label="启用" value="启用" /><el-option label="停用" value="停用" /></el-select></el-form-item></el-col>
</el-row>
<el-form-item label="模板内容" required>
<el-input
v-model.trim="templateForm.templateContent"
type="textarea"
:rows="6"
placeholder="支持占位符:{{packageNo}} {{jobCode}} {{batchCode}} {{rackCode}} {{qty}}" />
</el-form-item>
<el-form-item label="备注"><el-input v-model.trim="templateForm.remark" /></el-form-item>
</el-form>
<span slot="footer">
<el-button plain class="reset-btn" @click="templateDialogVisible = false">取消</el-button>
<el-button plain class="add-btn" :loading="templateSubmitLoading" @click="submitTemplate">保存</el-button>
</span>
</el-dialog>
<el-dialog
title="标签打印"
:visible.sync="printDialogVisible"
width="520px"
:close-on-click-modal="false"
v-drag>
<el-form :model="printForm" label-position="top">
<el-form-item label="包装单号"><el-input v-model="printForm.packageNo" disabled /></el-form-item>
<el-form-item label="标签模板" required>
<el-select v-model="printForm.templateCode" filterable style="width:100%">
<el-option v-for="item in enabledTemplateList" :key="item.templateCode" :label="`${item.templateCode} / ${item.templateName}`" :value="item.templateCode" />
</el-select>
</el-form-item>
<el-form-item label="打印份数"><el-input-number v-model="printForm.copies" :min="1" :precision="0" style="width:100%" /></el-form-item>
</el-form>
<span slot="footer">
<el-button plain class="reset-btn" @click="printDialogVisible = false">取消</el-button>
<el-button plain class="add-btn" :loading="printSubmitting" @click="submitPrint">执行打印</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import {
listPackageOrder,
savePackageOrder,
updatePackageOrder,
deletePackageOrderByNo,
listLabelTemplate,
saveLabelTemplate,
updateLabelTemplate,
deleteLabelTemplateByCode,
printPackageLabel,
listLabelPrintLog,
listJob
} from '@/api/rack/closedLoop'
const emptyPackageForm = () => ({
packageId: null,
jobCode: '',
batchCode: '',
rackCode: '',
qty: 0,
remark: ''
})
const emptyTemplateForm = () => ({
templateId: null,
templateCode: '',
templateName: '',
templateContent: '',
status: '启用',
remark: ''
})
export default {
data () {
return {
activeTab: 'package',
tableHeight: window.innerHeight - 330,
jobOptions: [],
packageLoading: false,
templateLoading: false,
printLogLoading: false,
packageSubmitLoading: false,
templateSubmitLoading: false,
printSubmitting: false,
packageDialogVisible: false,
templateDialogVisible: false,
printDialogVisible: false,
packageQuery: { packageNo: '', jobCode: '', batchCode: '', status: '' },
templateQuery: { templateCode: '', templateName: '', status: '' },
packageList: [],
templateList: [],
printLogList: [],
printLogKeyword: '',
packageForm: emptyPackageForm(),
templateForm: emptyTemplateForm(),
printForm: {
packageNo: '',
templateCode: '',
copies: 1
}
}
},
computed: {
enabledTemplateList () {
return (this.templateList || []).filter(item => item && item.status === '启用')
}
},
mounted () {
this.bootstrap()
},
methods: {
async bootstrap () {
await Promise.all([
this.loadJobOptions(),
this.loadPackageList(),
this.loadTemplateList(),
this.loadPrintLogs('')
])
},
async loadJobOptions () {
const { data } = await listJob({})
this.jobOptions = data.rows || []
},
async loadPackageList () {
this.packageLoading = true
try {
const { data } = await listPackageOrder(this.packageQuery)
this.packageList = data.rows || []
} finally {
this.packageLoading = false
}
},
async loadTemplateList () {
this.templateLoading = true
try {
const { data } = await listLabelTemplate(this.templateQuery)
this.templateList = data.rows || []
} finally {
this.templateLoading = false
}
},
async loadPrintLogs (packageNo) {
this.printLogLoading = true
try {
this.printLogKeyword = packageNo || ''
const { data } = await listLabelPrintLog(packageNo ? { packageNo } : {})
this.printLogList = data.rows || []
} finally {
this.printLogLoading = false
}
},
openPackageDialog (row) {
this.packageForm = row
? {
...emptyPackageForm(),
...row
}
: emptyPackageForm()
this.packageDialogVisible = true
},
openTemplateDialog (row) {
this.templateForm = row
? {
...emptyTemplateForm(),
...row
}
: emptyTemplateForm()
this.templateDialogVisible = true
},
openPrintDialog (row) {
this.printForm = {
packageNo: row.packageNo,
templateCode: row.labelTemplateCode || ((this.enabledTemplateList[0] && this.enabledTemplateList[0].templateCode) || ''),
copies: 1
}
this.printDialogVisible = true
},
async submitPackage () {
if (!this.packageForm.jobCode) {
this.$message.warning('请先选择任务单号')
return
}
this.packageSubmitLoading = true
try {
if (this.packageForm.packageId) {
await updatePackageOrder(this.packageForm)
this.$message.success('修改成功')
} else {
await savePackageOrder(this.packageForm)
this.$message.success('新增成功')
}
this.packageDialogVisible = false
await this.loadPackageList()
} finally {
this.packageSubmitLoading = false
}
},
async submitTemplate () {
if (!this.templateForm.templateCode || !this.templateForm.templateName || !this.templateForm.templateContent) {
this.$message.warning('请完整填写模板信息')
return
}
this.templateSubmitLoading = true
try {
if (this.templateForm.templateId) {
await updateLabelTemplate(this.templateForm)
this.$message.success('修改成功')
} else {
await saveLabelTemplate(this.templateForm)
this.$message.success('新增成功')
}
this.templateDialogVisible = false
await this.loadTemplateList()
} finally {
this.templateSubmitLoading = false
}
},
async submitPrint () {
if (!this.printForm.packageNo || !this.printForm.templateCode) {
this.$message.warning('请选择打印模板')
return
}
this.printSubmitting = true
try {
const { data } = await printPackageLabel(this.printForm)
const result = data.result || {}
this.$message.success(`打印完成,份数:${result.copies || this.printForm.copies}`)
this.printDialogVisible = false
await Promise.all([
this.loadPackageList(),
this.loadPrintLogs(this.printForm.packageNo)
])
} finally {
this.printSubmitting = false
}
},
async deletePackage (row) {
try {
await this.$confirm(`确认删除包装单:${row.packageNo} 吗?`, '提示', { type: 'warning' })
await deletePackageOrderByNo(row.packageNo)
this.$message.success('删除成功')
await this.loadPackageList()
} catch (e) {}
},
async deleteTemplate (row) {
try {
await this.$confirm(`确认删除模板:${row.templateCode} 吗?`, '提示', { type: 'warning' })
await deleteLabelTemplateByCode(row.templateCode)
this.$message.success('删除成功')
await this.loadTemplateList()
} catch (e) {}
},
formatDateTime (value) {
if (!value) {
return '-'
}
const date = new Date(value)
if (Number.isNaN(date.getTime())) {
return value
}
const pad = n => String(n).padStart(2, '0')
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
}
}
}
</script>
<style scoped>
.card-title-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.query-form {
margin-bottom: 8px;
}
.sub-text {
color: #909399;
font-size: 12px;
}
</style>
Loading…
Cancel
Save