Files
bj_power/bj_power_wms_client/frontend/src/pages/Semi.vue
T
SunYF 47573e44b0 feat&fix: 完善半成品/序列号闭环流转逻辑
1. 调整物料编码字段为可空,优化注释说明
2. 修复Vite构建自动清空产物目录冲突问题
3. 优化前端提示文案与表单占位符
4. 为各前端项目添加内联SVG图标
5. 重构MES半成品流转逻辑,不再静默吞掉WMS错误并记录日志
6. 生成并添加空的WMS客户端静态文件
7. 完善Ent ORM模型的空值支持与查询谓词
8. 优化半成品入库接口,支持doneProcessCodes数组格式,新增SN反查物料编码逻辑
9. 更新帮助文档与操作手册
2026-09-01 14:30:26 +08:00

234 lines
8.6 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import request from '../utils/request'
import { fmtTime } from '../utils/format'
import { getRealName } from '../utils/auth'
import PageHelp from '../components/PageHelp.vue'
import { helpSemi } from '../help'
/* ---------- 半成品/成品入库(携带已完成工序,连续扫码) ---------- */
const materials = ref([])
onMounted(async () => {
try {
const ms = await request.get('/material/list')
materials.value = Array.isArray(ms) ? ms : ms?.list || []
} catch { /* 字典加载失败不阻塞入库 */ }
})
const operator = getRealName()
const inForm = reactive({ materialCode: '', zoneCode: '', orderNo: '', completedProcess: '' })
const snInput = ref('')
const snList = ref([])
const inbounding = ref(false)
function addSnLines() {
for (let raw of String(snInput.value).split(/\s+/)) {
const sn = raw.trim()
if (sn && !snList.value.includes(sn)) snList.value.push(sn)
}
snInput.value = ''
}
function removeSn(i) {
snList.value.splice(i, 1)
}
async function submitInbound() {
if (!inForm.materialCode) {
ElMessage.warning('请选择物料')
return
}
if (!snList.value.length) {
ElMessage.warning('请扫码录入 SN(回车追加)')
return
}
inbounding.value = true
let ok = 0
try {
for (const sn of snList.value) {
try {
await request.post('/semi/inbound', {
sn,
materialCode: inForm.materialCode,
completedProcess: inForm.completedProcess.trim(),
zoneCode: inForm.zoneCode || undefined,
orderNo: inForm.orderNo.trim() || undefined,
operator
})
ok++
} catch { /* 单条失败不中断后续 */ }
}
ElMessage.success(`半成品入库完成:成功 ${ok} / ${snList.value.length} 条`)
snList.value = []
snInput.value = ''
} finally {
inbounding.value = false
}
}
/* ---------- 半成品出库(重上线) ---------- */
const outForm = reactive({ sn: '' })
const outbounding = ref(false)
async function submitOutbound() {
const sn = outForm.sn.trim()
if (!sn) {
ElMessage.warning('请输入 SN')
return
}
outbounding.value = true
try {
await request.post('/semi/outbound', { sn, operator })
ElMessage.success(`半成品出库成功:${sn}`)
outForm.sn = ''
await loadList()
} finally {
outbounding.value = false
}
}
/* ---------- 半成品查询 ---------- */
const qForm = reactive({ materialCode: '', status: '' })
const list = ref([])
const total = ref(0)
const loading = ref(false)
const page = reactive({ current: 1, size: 10 })
async function loadList() {
loading.value = true
try {
const data = await request.get('/semi/query', {
params: {
materialCode: qForm.materialCode.trim(),
status: qForm.status,
page: page.current,
pageSize: page.size
}
})
list.value = data?.list || []
total.value = data?.total || 0
} finally {
loading.value = false
}
}
function search() {
page.current = 1
loadList()
}
</script>
<template>
<el-card shadow="never">
<el-tabs>
<el-tab-pane label="半成品/成品入库">
<el-form label-width="110px" style="max-width:680px">
<el-form-item label="物料">
<el-select v-model="inForm.materialCode" filterable placeholder="选择物料编码" style="width:100%">
<el-option v-for="m in materials" :key="m.id" :value="m.code"
:label="`${m.code} ${m.name || ''}`" />
</el-select>
</el-form-item>
<el-form-item label="已完成工序">
<el-input v-model="inForm.completedProcess" placeholder="如:1,3,5(可空)" clearable />
</el-form-item>
<el-form-item label="工单号">
<el-input v-model="inForm.orderNo" placeholder="来源工单号(可空)" clearable />
</el-form-item>
<el-form-item label="区域">
<el-input v-model="inForm.zoneCode" placeholder="库区(可空)" clearable />
</el-form-item>
<el-form-item label="操作人">
<el-input :model-value="operator" disabled />
</el-form-item>
<el-form-item label="扫码录入">
<el-input v-model="snInput" type="textarea" :rows="3" autofocus
placeholder="连续扫描 SN,每次回车追加(可一次多条,空白分隔)"
@keydown.enter.prevent="addSnLines" />
</el-form-item>
</el-form>
<div style="margin-bottom:8px">待入库 SN<b>{{ snList.length }}</b> </div>
<el-table :data="snList.map((v, i) => ({ i, v }))" size="small" border max-height="260"
empty-text="暂无 SN" style="max-width:760px">
<el-table-column type="index" label="#" width="56" align="center" />
<el-table-column prop="v" label="SN 序列号" min-width="220" />
<el-table-column label="操作" width="80" align="center">
<template #default="{ row }">
<el-button link type="danger" size="small" @click="removeSn(row.i)">移除</el-button>
</template>
</el-table-column>
</el-table>
<div style="margin-top:12px">
<el-button type="primary" :loading="inbounding" @click="submitInbound">提交入库</el-button>
<el-button @click="snList = []">清空</el-button>
</div>
</el-tab-pane>
<el-tab-pane label="半成品出库">
<el-form label-width="110px" style="max-width:560px" @submit.prevent="submitOutbound">
<el-form-item label="SN">
<el-input v-model="outForm.sn" placeholder="扫描或输入 SN,回车出库" clearable autofocus
@keyup.enter="submitOutbound" />
</el-form-item>
<el-form-item label="操作人">
<el-input :model-value="operator" disabled />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="outbounding" @click="submitOutbound">出库重上线</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="半成品/成品查询">
<el-form inline @submit.prevent="search">
<el-form-item label="物料编码">
<el-input v-model="qForm.materialCode" placeholder="物料编码" clearable style="width:200px" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="qForm.status" clearable placeholder="请选择状态" style="width:120px">
<el-option value="在库" label="在库" />
<el-option value="出库" label="出库" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="loading" @click="search">查询</el-button>
</el-form-item>
</el-form>
<el-table :data="list" border size="small" v-loading="loading" empty-text="暂无记录">
<el-table-column prop="sn" label="SN" min-width="200" />
<el-table-column prop="materialCode" label="物料编码" min-width="140" />
<el-table-column prop="completedProcess" label="已完成工序" min-width="120">
<template #default="{ row }">{{ row.completedProcess || '-' }}</template>
</el-table-column>
<el-table-column prop="zoneCode" label="区域" width="90" align="center">
<template #default="{ row }">{{ row.zoneCode || '-' }}</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="90" align="center">
<template #default="{ row }">
<el-tag :type="row.status === '在库' ? 'success' : 'info'" size="small">{{ row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="orderNo" label="工单号" min-width="140">
<template #default="{ row }">{{ row.orderNo || '-' }}</template>
</el-table-column>
<el-table-column label="创建时间" width="160" align="center">
<template #default="{ row }">{{ fmtTime(row.createdAt) }}</template>
</el-table-column>
</el-table>
<div class="pager">
<el-pagination background layout="total, prev, pager, next" :total="total"
:current-page="page.current" :page-size="page.size"
@current-change="(p) => { page.current = p; loadList() }" />
</div>
</el-tab-pane>
</el-tabs>
</el-card>
<PageHelp :data="helpSemi" />
</template>
<style scoped>
.pager { display: flex; justify-content: flex-end; margin-top: 10px; }
</style>