Files
bj_power/bj_power_mes/frontend/src/pages/Scan.vue
T
SunYF e852c7cd37 chore: 批量完成项目多模块迭代优化
1. 废弃半成品库存表并统一库存主表
2. 修复列表排序与搜索大小写不敏感问题
3. 新增用户最近登录时间、工单BOM名称字段
4. 完善角色管理、工位选择器功能
5. 增加拧紧数据补录、成品自动回流WMS能力
6. 统一导出时间范围校验规则
7. 丰富物料/备料单筛选条件
8. 调整菜单与权限命名适配产品型号业务
2026-09-08 11:44:04 +08:00

232 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
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.
<template>
<el-card>
<el-tabs v-model="tab" @tab-change="onTab">
<el-tab-pane label="报工录入" name="entry">
<el-form inline>
<el-form-item label="工位">
<el-select v-model="form.stationNo" placeholder="请选择工位" style="width:120px" @change="loadSteps">
<el-option v-for="i in 12" :key="i" :value="i" :label="`工位${i}`" />
</el-select>
</el-form-item>
<el-form-item label="工件SN"><el-input v-model="form.sn" placeholder="扫码或手输" style="width:190px" @change="loadSteps" /></el-form-item>
<el-form-item><el-tag v-if="steps.length" type="success" size="small">已自动载入当前工位应做步骤</el-tag></el-form-item>
</el-form>
<!-- 参数采集区:按该工位启用工艺流程的工序步骤动态渲染 -->
<el-card v-if="steps.length" shadow="never" style="margin-bottom:12px">
<div slot="header" style="display:flex;justify-content:space-between;align-items:center">
<b>工位 {{ form.stationNo }} 参数采集{{ flowName ? '(' + flowName + ')' : '' }}</b>
<el-button v-if="can('produce.scan')" type="primary" size="small" :loading="saving" @click="submit">提交报工</el-button>
</div>
<el-table :data="steps" border size="small">
<el-table-column prop="seq" label="步骤" width="60" align="center" />
<el-table-column prop="name" label="步骤名称" min-width="130" />
<el-table-column label="采集方式" width="130" align="center">
<template #default="{ row }">
<el-tag v-if="row.collectType === 'AUTO'" type="warning" size="small">拧紧枪自动</el-tag>
<el-tag v-else-if="row.collectType === 'MANUAL'" type="primary" size="small">手填</el-tag>
<el-tag v-else type="info" size="small">仅记录</el-tag>
</template>
</el-table-column>
<el-table-column label="采集值" min-width="180">
<template #default="{ row }">
<el-input v-if="row.collectType !== 'NONE'" v-model="row.value" placeholder="采集值(可空)" style="width:130px" />
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="判定标准" min-width="200">
<template #default="{ row }">
<template v-if="row.criteria.length">
<el-tag v-for="c in row.criteria" :key="c.id" size="small" style="margin-right:4px">
{{ c.name }}{{ critText(c) }}
</el-tag>
</template>
<span v-else style="color:#909399">未设标准</span>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 装机绑定区:该工件在本工序应装什么料(BOM 装配工序配置)→ 扫码/手录绑定,齐套后才允许报工 -->
<el-card v-if="bindPanel" shadow="never" style="margin-bottom:12px;border-color:var(--el-color-primary)">
<div slot="header" style="display:flex;justify-content:space-between;align-items:center">
<b>工序 {{ form.stationNo }} 装机绑定(装配物料)</b>
<el-alert v-if="bindPanel.enabled && !bindPanel.complete" type="warning" :closable="false" show-icon style="flex:1;margin-left:16px"
:title="'尚未绑齐:' + bindPanel.missing" />
<el-alert v-else-if="bindPanel.enabled && bindPanel.complete" type="success" :closable="false" show-icon style="flex:1;margin-left:16px"
title="装配物料已绑齐,可提交报工" />
<el-alert v-else type="info" :closable="false" style="flex:1;margin-left:16px"
title="该产品未在本工序配置需绑定物料(BOM「装配工序」为空),不校验" />
</div>
<el-table v-if="bindPanel.required && bindPanel.required.length" :data="bindPanel.required" border size="small">
<el-table-column prop="materialCode" label="物料编码" min-width="110" />
<el-table-column prop="materialName" label="物料名称" min-width="120" />
<el-table-column prop="manageMode" label="管理方式" width="120">
<template #default="{row}">{{ row.manageMode === '2' ? '精密件(SN)' : '结构件(批次)' }}</template>
</el-table-column>
<el-table-column label="已绑/需绑" width="100" align="center">
<template #default="{row}">
<b :style="{color: row.complete ? 'var(--el-color-success)' : 'var(--el-color-danger)'}">{{ row.boundCount }}/{{ row.need }}</b>
</template>
</el-table-column>
<el-table-column label="已绑定值" min-width="170">
<template #default="{row}">
<el-tag v-for="b in row.bound" :key="b" size="small" style="margin-right:4px">{{ b }}</el-tag>
<span v-if="!row.bound.length" style="color:#909399">未绑定</span>
</template>
</el-table-column>
<el-table-column label="绑定操作" width="230">
<template #default="{row}">
<div style="display:flex;gap:4px">
<el-input v-model="row._input" :placeholder="row.manageMode === '2' ? '扫码SN' : '扫码/输入批次号'" size="small" @keyup.enter="bindOne(row)" />
<el-button size="small" type="primary" :loading="binding" @click="bindOne(row)">绑定</el-button>
</div>
</template>
</el-table-column>
</el-table>
</el-card>
<el-alert v-if="!steps.length && !bindPanel" type="info" :closable="false" title="请选择工位、输入SN后点【载入工序步骤】;若提示未配置工序步骤,请到「工艺流程」为该工位启用流程并维护工序步骤" />
</el-tab-pane>
<el-tab-pane label="报工记录" name="records" lazy>
<el-form inline>
<el-form-item label="SN"><el-input v-model="query.sn" clearable style="width:160px" /></el-form-item>
<el-form-item label="工单号"><el-input v-model="query.orderNo" clearable style="width:160px" /></el-form-item>
<el-form-item><el-button @click="load">刷新</el-button></el-form-item>
</el-form>
<el-table :data="list" border size="small">
<el-table-column prop="sn" label="SN" min-width="130" />
<el-table-column prop="orderNo" label="工单号" min-width="130" />
<el-table-column prop="station" label="工位" width="80" />
<el-table-column prop="type" label="类型" width="110" />
<el-table-column prop="processCode" label="工位号" width="80" />
<el-table-column prop="operator" label="操作人" width="90" />
<el-table-column prop="time" label="时间" min-width="170" />
</el-table>
<el-pagination style="margin-top:12px;justify-content:flex-end" background layout="total, prev, pager, next"
:total="recordsTotal" :page-size="recordsSize" v-model:current-page="recordsPage" @current-change="load" />
</el-tab-pane>
</el-tabs>
</el-card>
<PageHelp :data="helpScan" page="Scan" />
</template>
<script setup>
import { reactive, ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import request from '../utils/request'
import { can } from '../utils/perm'
import PageHelp from '../components/PageHelp.vue'
import { helpScan } from '../help'
const tab = ref('entry')
const recordsPage = ref(1)
const recordsSize = ref(20)
const recordsTotal = ref(0)
const form = reactive({ stationNo: 1, sn: '' })
const steps = ref([])
const flowName = ref('')
const saving = ref(false)
const bindPanel = ref(null)
const binding = ref(false)
const query = reactive({ sn: '', orderNo: '' })
const list = ref([])
function critText(c) {
switch (c.logic) {
case 'GE': return ` ≥${c.target}${c.unit}`
case 'LE': return ` ≤${c.target}${c.unit}`
case 'GT': return ` >${c.target}${c.unit}`
case 'LT': return ` <${c.target}${c.unit}`
case 'RANGE': return ` [${c.min},${c.max}]${c.unit}`
case 'EQUAL': return ` =${c.target}${c.unit}`
default: return c.name || ''
}
}
// 从后端加载该工位启用工艺流程的工序步骤(value 挂在 row 上,提交时收集)
async function loadSteps() {
if (!form.sn) return ElMessage.warning('请先输入工件SN')
bindPanel.value = null
try {
const data = await request.get('/process-steps', { params: { stationNo: form.stationNo } }) || []
steps.value = data.map(s => ({ ...s, value: '', text: '' }))
flowName.value = ''
if (!steps.value.length) {
ElMessage.warning(`工位${form.stationNo} 未配置启用的工艺流程,请到「工艺流程」维护`)
}
loadBindPanel()
} catch { steps.value = [] }
}
// 拉取本工件本工序的装配物料绑定面板(应绑清单 + 已绑情况)
async function loadBindPanel() {
if (!form.sn) return
try {
const p = await request.get('/bind-panel', { params: { sn: form.sn, processCode: form.stationNo } }) || null
if (p?.required) {
p.required.forEach((r) => { r._input = '' })
}
bindPanel.value = p
} catch {
bindPanel.value = null
}
}
// 扫/录单个物料 → 立即绑定落库并刷新齐套状态(防错即时反馈)
async function bindOne(row) {
const v = (row._input || '').trim()
if (!v) return ElMessage.warning('请先输入绑定值(精密件扫码SN / 结构件批次号)')
binding.value = true
try {
await request.post('/binds', {
sn: form.sn,
orderNo: '',
processCode: form.stationNo,
stationNo: form.stationNo,
items: [{ materialCode: row.materialCode, bindValue: v }]
})
ElMessage.success('绑定成功')
loadBindPanel()
} catch { /* 错误已在拦截器提示 */ }
binding.value = false
}
async function submit() {
if (!form.sn) return ElMessage.warning('请输入SN')
if (!steps.value.length) return ElMessage.warning('该工位未配置工序步骤,无法报工')
// 报工前强校验:本工序装配物料需绑齐(后端 validateStationBinds)
if (bindPanel.value?.enabled && !bindPanel.value?.complete) {
return ElMessage.warning('装配物料未绑齐,请先完成装机绑定:' + (bindPanel.value.missing || ''))
}
const stepReq = steps.value
.filter(s => s.collectType !== 'NONE' && (s.text !== '' || s.value !== ''))
.map(s => ({
stepId: s.id,
name: s.name,
value: Number(s.value) || 0,
text: s.text || String(s.value || '')
}))
// 手动报工:processCode = 工位号(工位 1~12 固定)
await request.post('/workpiece/process/report', {
sn: form.sn,
processCode: form.stationNo,
stationNo: form.stationNo,
steps: stepReq
})
ElMessage.success('报工成功')
steps.value = []
bindPanel.value = null
Object.assign(form, { sn: '' })
load()
}
async function load() {
const data = await request.get('/scan/records', { params: { ...query, page: recordsPage.value, pageSize: recordsSize.value } })
list.value = data?.list || []
recordsTotal.value = data?.total || 0
}
function onTab(name) { if (name === 'records' && !list.value.length) load() }
onMounted(() => { if (form.stationNo) loadSteps() })
</script>