522 lines
26 KiB
Vue
522 lines
26 KiB
Vue
<script setup>
|
||
defineOptions({ name: 'Outbound' })
|
||
import { computed, onMounted, reactive, ref } from 'vue'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import request from '../utils/request'
|
||
import { getRealName } from '../utils/auth'
|
||
import { fmtTime } from '../utils/format'
|
||
import { exportXlsxFetch } from '../utils/export'
|
||
import { can } from '../utils/perm'
|
||
import PageHelp from '../components/PageHelp.vue'
|
||
import MaterialSelect from '../components/MaterialSelect.vue'
|
||
import { helpOutbound } from '../help'
|
||
|
||
const activeTab = ref('prep') // prep=备料出库 / general=通用出库 / records=出库记录
|
||
const operator = getRealName()
|
||
|
||
/* ===================== 备料出库(MES 工单台账) ===================== */
|
||
const queryForm = reactive({ orderNo: '' })
|
||
// 目标工位:AGV 配送 toDock 参数,DOCK01~DOCK20 下拉选填(不选默认产线入口 DOCK01)
|
||
const targetDock = ref('')
|
||
const dockOptions = Array.from({ length: 20 }, (_, i) => 'DOCK' + String(i + 1).padStart(2, '0'))
|
||
const loadingPrep = ref(false)
|
||
const queried = ref(false)
|
||
const rows = ref([])
|
||
|
||
onMounted(loadOrders)
|
||
async function loadOrders() {
|
||
try {
|
||
const data = await request.get('/orders', { params: { page: 1, pageSize: 200 } })
|
||
orders.value = Array.isArray(data) ? data : data?.list || []
|
||
} catch { orders.value = [] }
|
||
}
|
||
const orders = ref([])
|
||
|
||
const pendingRows = computed(() => rows.value.filter(r => r.status !== '领料完结'))
|
||
function gapOf(row) { return Math.max((row.totalQty || 0) - (row.outQty || 0), 0) }
|
||
|
||
// 一键自动分配批次:合格区 Z02,batchNo 正序 FIFO 凑够缺口
|
||
async function autoAllocate(row) {
|
||
const gap = gapOf(row)
|
||
if (gap <= 0) return
|
||
const stock = await request.get('/stock/query', { params: { type: 'batch', materialCode: row.materialCode, page: 1, pageSize: 200 } })
|
||
const batches = (stock?.batch || [])
|
||
.filter(b => b.qualityStatus === '合格' && b.zoneCode === 'Z02')
|
||
.sort((a, b) => String(a.batchNo).localeCompare(String(b.batchNo)))
|
||
|
||
const plan = []
|
||
let remain = gap
|
||
for (const b of batches) {
|
||
if (remain <= 0) break
|
||
const avail = (b.quantity || 0) - (b.lockedQty || 0)
|
||
if (avail <= 0) continue
|
||
const take = Math.min(avail, remain)
|
||
plan.push({ batchNo: b.batchNo, qty: take, zoneCode: b.zoneCode })
|
||
remain -= take
|
||
}
|
||
if (!plan.length) {
|
||
ElMessage.warning(`物料 ${row.materialCode} 在合格区(Z02)无可用批次库存`)
|
||
return
|
||
}
|
||
if (remain > 0) {
|
||
ElMessage.error(`可用量不足:缺口 ${gap},Z02 仅可凑 ${gap - remain},还差 ${remain}`)
|
||
return
|
||
}
|
||
|
||
const lines = plan.map(p => `· 批次 ${p.batchNo} 出库 ${p.qty}`).join('\n')
|
||
await ElMessageBox.confirm(
|
||
`工单 ${row.orderNo} 物料 ${row.materialCode}\n缺口 ${gap},按 FIFO 分配如下:\n${lines}\n\n操作人:${operator}${targetDock.value ? `\n目标口:${targetDock.value}` : ''}`,
|
||
'确认自动分配出库',
|
||
{ type: 'warning', confirmButtonText: '确认出库', cancelButtonText: '取消' }
|
||
)
|
||
|
||
const outboundNos = []
|
||
let lastStatus = ''
|
||
for (const p of plan) {
|
||
const res = await request.post('/outbound/create', {
|
||
orderNo: row.orderNo, materialCode: row.materialCode, batchNo: p.batchNo,
|
||
qty: p.qty, operator, targetDock: targetDock.value.trim() || undefined
|
||
})
|
||
if (res?.outboundNo) outboundNos.push(res.outboundNo)
|
||
lastStatus = res?.status || lastStatus
|
||
}
|
||
const done = lastStatus === '领料完结'
|
||
ElMessage.success(`出库完成:出库单号 ${outboundNos.join('、')};该物料${done ? '已' : '未'}领料完结`)
|
||
await queryPrep()
|
||
}
|
||
|
||
function statusTag(s) { return s === '领料完结' ? 'success' : s === '进行中' ? 'primary' : 'info' }
|
||
|
||
async function queryPrep() {
|
||
if (!queryForm.orderNo.trim()) { ElMessage.warning('请输入工单号'); return }
|
||
loadingPrep.value = true
|
||
try {
|
||
const data = await request.get('/ledger/query', { params: { orderNo: queryForm.orderNo.trim(), page: 1, pageSize: 100 } })
|
||
rows.value = data?.list || []
|
||
queried.value = true
|
||
if (!rows.value.length) ElMessage.info('该工单暂无台账记录')
|
||
} finally { loadingPrep.value = false }
|
||
}
|
||
|
||
/* ===================== 通用出库(手动,不依赖工单) ===================== */
|
||
// 出库类别:退料/退货/样品/报废/发货/其他(客户诉求 L96-108 + U5)
|
||
const CATEGORY_OPTIONS = [
|
||
{ value: 'return', label: '退料' },
|
||
{ value: 'supplier_return', label: '退货' },
|
||
{ value: 'sample', label: '样品' },
|
||
{ value: 'scrap', label: '报废' },
|
||
{ value: 'deliver', label: '发货' },
|
||
{ value: 'other', label: '其他' }
|
||
]
|
||
const OWNERSHIP_OPTIONS = [
|
||
{ value: 'none', label: '无' },
|
||
{ value: 'workorder', label: '工单号' },
|
||
{ value: 'project', label: '科技项目号' },
|
||
{ value: 'other', label: '其他' }
|
||
]
|
||
// 物料下拉由 MaterialSelect 组件按需远程搜索(最近 100 条)
|
||
const zones = ref([])
|
||
const genForm = reactive({
|
||
materialCode: '', mode: 1, batchNo: '', qty: 1, zoneCode: '',
|
||
boxNo: '', contractNo: '', remark: '',
|
||
outboundCategory: 'other', ownershipType: 'none', ownershipNo: '', targetStation: ''
|
||
})
|
||
const snInput = ref('')
|
||
const snList = ref([])
|
||
const savingGen = ref(false)
|
||
const genRows = ref([])
|
||
const loadingGen = ref(false)
|
||
const genPage = reactive({ current: 1, size: 10 })
|
||
const genTotal = ref(0)
|
||
|
||
const SN_SEP = /[,,;;、\s]+/
|
||
function addSnLines() {
|
||
for (let raw of String(snInput.value).split(SN_SEP)) {
|
||
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 loadDicts() {
|
||
if (zones.value.length) return
|
||
// 物料下拉由 MaterialSelect 组件按需远程搜索(最近 100 条),此处只加载区域
|
||
const zs = await request.get('/zone/picker')
|
||
zones.value = Array.isArray(zs) ? zs : zs?.list || []
|
||
}
|
||
// 区域下拉选项:picker 每行是一个货位,同一区域会重复出现,按区域编码去重后只展示一条
|
||
const zoneOptions = computed(() => {
|
||
const map = new Map()
|
||
zones.value.forEach(z => {
|
||
if (!z.zoneCode) return
|
||
if (!map.has(z.zoneCode)) map.set(z.zoneCode, { zoneCode: z.zoneCode, zoneName: z.zoneName || '' })
|
||
})
|
||
return [...map.values()]
|
||
})
|
||
|
||
async function loadGenRows() {
|
||
loadingGen.value = true
|
||
try {
|
||
const data = await request.get('/outbound/query', { params: { outboundType: 'general', page: genPage.current, pageSize: genPage.size } })
|
||
genRows.value = (data?.list || []).map(r => ({ ...r, snCodes: (r.details || []).map(d => d.snCode).filter(Boolean).join(',') }))
|
||
genTotal.value = data?.total || 0
|
||
} finally { loadingGen.value = false }
|
||
}
|
||
|
||
async function submitGen() {
|
||
if (!genForm.materialCode) { ElMessage.warning('请选择物料'); return }
|
||
if (!genForm.outboundCategory) { ElMessage.warning('请选择出库类别'); return }
|
||
if (genForm.ownershipType !== 'none' && !genForm.ownershipNo.trim()) {
|
||
ElMessage.warning('请填写归属编号(工单号 / 科技项目号 / 其他)'); return
|
||
}
|
||
if (genForm.outboundCategory === 'other' && !genForm.remark.trim()) {
|
||
ElMessage.warning('出库类别选「其他」时,请在备注中填写具体原因'); return
|
||
}
|
||
const payload = {
|
||
materialCode: genForm.materialCode, operator,
|
||
outboundCategory: genForm.outboundCategory,
|
||
ownershipType: genForm.ownershipType,
|
||
ownershipNo: genForm.ownershipType !== 'none' ? genForm.ownershipNo.trim() : undefined,
|
||
targetStation: genForm.targetStation.trim() || undefined,
|
||
zoneCode: genForm.zoneCode || undefined,
|
||
boxNo: genForm.boxNo.trim() || undefined,
|
||
contractNo: genForm.contractNo.trim() || undefined,
|
||
remark: genForm.remark.trim() || undefined
|
||
}
|
||
if (genForm.mode === 1) {
|
||
if (!genForm.batchNo.trim()) { ElMessage.warning('请输入批次号'); return }
|
||
if (!genForm.qty || genForm.qty <= 0) { ElMessage.warning('请输入有效数量'); return }
|
||
payload.batchNo = genForm.batchNo.trim()
|
||
payload.qty = genForm.qty
|
||
} else {
|
||
if (!snList.value.length) { ElMessage.warning('请录入至少一个 SN'); return }
|
||
payload.snList = [...snList.value]
|
||
payload.qty = snList.value.length
|
||
}
|
||
savingGen.value = true
|
||
try {
|
||
const res = await request.post('/outbound/general', payload)
|
||
ElMessage.success(`通用出库成功:${res?.outboundNo}${res?.boxNo ? ' 箱号 ' + res.boxNo : ''}`)
|
||
Object.assign(genForm, { materialCode: '', mode: 1, batchNo: '', qty: 1, zoneCode: '', boxNo: '', contractNo: '', remark: '', outboundCategory: 'other', ownershipType: 'none', ownershipNo: '', targetStation: '' })
|
||
snInput.value = ''
|
||
snList.value = []
|
||
genPage.current = 1
|
||
await loadGenRows()
|
||
if (activeTab.value === 'records') await loadRecords()
|
||
} finally { savingGen.value = false }
|
||
}
|
||
|
||
/* ===================== 出库记录(统一主表,全部 + 导出) ===================== */
|
||
const recFilters = reactive({ outboundType: '', materialCode: '', boxNo: '', ownershipType: '', dateRange: [] })
|
||
const recRows = ref([])
|
||
const recTotal = ref(0)
|
||
const loadingRec = ref(false)
|
||
const recPage = reactive({ current: 1, size: 10 })
|
||
|
||
function snOf(row) { return (row.details || []).map(d => d.snCode).filter(Boolean).join(',') }
|
||
|
||
async function loadRecords() {
|
||
loadingRec.value = true
|
||
try {
|
||
const params = {
|
||
outboundType: recFilters.outboundType,
|
||
materialCode: recFilters.materialCode.trim(),
|
||
boxNo: recFilters.boxNo.trim(),
|
||
ownershipType: recFilters.ownershipType,
|
||
page: recPage.current, pageSize: recPage.size
|
||
}
|
||
if (Array.isArray(recFilters.dateRange) && recFilters.dateRange.length === 2) {
|
||
params.startAt = new Date(recFilters.dateRange[0]).getTime()
|
||
params.endAt = new Date(recFilters.dateRange[1]).getTime() + 86399999
|
||
}
|
||
const data = await request.get('/outbound/query', { params })
|
||
recRows.value = (data?.list || []).map(r => ({ ...r, snCodes: snOf(r) }))
|
||
recTotal.value = data?.total || 0
|
||
} finally { loadingRec.value = false }
|
||
}
|
||
function searchRec() { recPage.current = 1; loadRecords() }
|
||
function resetRec() { Object.assign(recFilters, { outboundType: '', materialCode: '', boxNo: '', dateRange: [] }); searchRec() }
|
||
|
||
function downloadCSV() {
|
||
exportXlsxFetch('/api/outbound/export', {
|
||
outboundType: recFilters.outboundType,
|
||
materialCode: recFilters.materialCode.trim(),
|
||
boxNo: recFilters.boxNo.trim()
|
||
}, '出库管理.xlsx')
|
||
.then(() => ElMessage.success('导出成功'))
|
||
.catch(() => ElMessage.error('导出失败'))
|
||
}
|
||
|
||
function onTabChange(tab) {
|
||
if (tab === 'general') { loadDicts(); loadGenRows() }
|
||
else if (tab === 'records') { loadDicts(); loadRecords() }
|
||
}
|
||
|
||
onMounted(loadDicts)
|
||
</script>
|
||
|
||
<template>
|
||
<div>
|
||
<el-tabs v-model="activeTab" @tab-change="onTabChange">
|
||
<!-- ============ 备料出库(MES 工单台账驱动) ============ -->
|
||
<el-tab-pane label="工单备料出库(MES)" name="prep">
|
||
<el-alert type="info" :closable="false" class="mb12"
|
||
title="本页对接 MES 工单台账:输入 MES 工单号查询其物料台账并领料。"
|
||
description="未启用 MES 或工单未同步时,工单下拉可能为空——此时可直接手输工单号(前提是台账已同步),或改用「通用出库」处理退料/样品/报废/发货等不挂靠工单的场景。" />
|
||
<el-card shadow="never" class="mb12">
|
||
<el-form inline label-width="90px" @submit.prevent>
|
||
<el-form-item label="工单号">
|
||
<el-input v-model="queryForm.orderNo" placeholder="输入工单号回车查询" clearable autofocus
|
||
style="width:260px" @keyup.enter="queryPrep" />
|
||
</el-form-item>
|
||
<el-form-item label="操作人">
|
||
<el-input :model-value="operator" disabled style="width:160px" />
|
||
</el-form-item>
|
||
<el-form-item label="目标工位">
|
||
<el-select v-model="targetDock" placeholder="选填,默认 DOCK01" clearable filterable
|
||
style="width:180px">
|
||
<el-option v-for="d in dockOptions" :key="d" :value="d" :label="d" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" :loading="loadingPrep" @click="queryPrep">查询备料台账</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<el-card v-if="queried" shadow="never">
|
||
<template #header>
|
||
台账明细(共 {{ rows.length }} 行,待补 {{ pendingRows.length }} 行)
|
||
</template>
|
||
<el-table :data="rows" border v-loading="loadingPrep">
|
||
<el-table-column prop="orderNo" label="工单号" min-width="140" />
|
||
<el-table-column prop="materialCode" label="图号" min-width="150" />
|
||
<el-table-column prop="materialName" label="物料名称" min-width="140">
|
||
<template #default="{ row }">{{ row.materialName || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="totalQty" label="需求总量" width="100" align="center" />
|
||
<el-table-column prop="outQty" label="已出库" width="90" align="center" />
|
||
<el-table-column label="缺口" width="90" align="center">
|
||
<template #default="{ row }"><span :class="{ gap: gapOf(row) > 0 }">{{ gapOf(row) }}</span></template>
|
||
</el-table-column>
|
||
<el-table-column label="状态" width="110" align="center">
|
||
<template #default="{ row }"><el-tag :type="statusTag(row.status)" size="small">{{ row.status }}</el-tag></template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="140" align="center">
|
||
<template #default="{ row }">
|
||
<el-button v-if="row.status !== '领料完结'" type="primary" size="small" @click="autoAllocate(row)">一键分配出库</el-button>
|
||
<span v-else style="color:#67c23a">已完结</span>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-card>
|
||
<el-empty v-else description="请先按工单号查询备料台账" />
|
||
</el-tab-pane>
|
||
|
||
<!-- ============ 通用出库 ============ -->
|
||
<el-tab-pane label="通用出库" name="general">
|
||
<el-card shadow="never" class="mb12" header="通用出库(不依赖工单:退料 / 样品 / 报废 / 发货)">
|
||
<el-form label-width="96px" style="max-width:880px">
|
||
<el-form-item label="物料" required>
|
||
<MaterialSelect v-model="genForm.materialCode" placeholder="选择物料" />
|
||
</el-form-item>
|
||
<el-form-item label="出库类型">
|
||
<el-radio-group v-model="genForm.mode">
|
||
<el-radio :value="1">结构件(批次)</el-radio>
|
||
<el-radio :value="2">电气件(SN)</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="出库类别" required>
|
||
<el-select v-model="genForm.outboundCategory" style="width:100%">
|
||
<el-option v-for="c in CATEGORY_OPTIONS" :key="c.value" :value="c.value" :label="c.label" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="归属">
|
||
<el-select v-model="genForm.ownershipType" style="width:160px;margin-right:8px">
|
||
<el-option v-for="o in OWNERSHIP_OPTIONS" :key="o.value" :value="o.value" :label="o.label" />
|
||
</el-select>
|
||
<el-input v-if="genForm.ownershipType !== 'none'" v-model="genForm.ownershipNo"
|
||
placeholder="归属编号(工单号/科技项目号/其他)" clearable style="width:260px" />
|
||
</el-form-item>
|
||
<el-form-item label="目标工位">
|
||
<el-input v-model="genForm.targetStation" placeholder="选填:如 1~12 或 DOCK01" clearable style="width:100%" />
|
||
</el-form-item>
|
||
|
||
<template v-if="genForm.mode === 1">
|
||
<el-form-item label="批次号" required>
|
||
<el-input v-model="genForm.batchNo" placeholder="结构件批次号" style="width:100%" />
|
||
</el-form-item>
|
||
<el-form-item label="数量" required>
|
||
<el-input-number v-model="genForm.qty" :min="1" style="width:160px" />
|
||
</el-form-item>
|
||
</template>
|
||
|
||
<template v-else>
|
||
<el-form-item label="SN 录入" required>
|
||
<div style="width:100%">
|
||
<el-input v-model="snInput" type="textarea" :rows="3"
|
||
placeholder="连续扫描 SN,支持 逗号/分号/顿号/空格/回车 分隔,一次可录入多条" />
|
||
<div style="margin-top:6px">
|
||
<el-button type="primary" plain :disabled="!snInput.trim()" @click="addSnLines">录入到列表</el-button>
|
||
<span style="margin-left:8px;color:#909399;font-size:12px">已录入 {{ snList.length }} 条</span>
|
||
</div>
|
||
<el-table :data="snList.map((v, i) => ({ i, v }))" size="small" border max-height="180"
|
||
empty-text="暂无 SN" style="margin-top:8px;width:100%">
|
||
<el-table-column type="index" label="#" width="56" align="center" />
|
||
<el-table-column prop="v" label="SN 序列号" min-width="200" />
|
||
<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>
|
||
</el-form-item>
|
||
</template>
|
||
|
||
<el-form-item label="出库区域">
|
||
<el-select v-model="genForm.zoneCode" filterable clearable placeholder="可选" style="width:100%">
|
||
<el-option v-for="z in zoneOptions" :key="z.zoneCode" :value="z.zoneCode" :label="`${z.zoneCode} ${z.zoneName}`" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="箱号">
|
||
<el-input v-model="genForm.boxNo" placeholder="选填:整箱发货时填写,装箱信息随出库单记录(无需单独录入装箱)" clearable style="width:100%" />
|
||
</el-form-item>
|
||
<el-form-item label="合同号">
|
||
<el-input v-model="genForm.contractNo" placeholder="选填" clearable style="width:100%" />
|
||
</el-form-item>
|
||
<el-form-item label="备注">
|
||
<el-input v-model="genForm.remark" type="textarea" :rows="2" placeholder="选填" />
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" :loading="savingGen" @click="submitGen">确认通用出库</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<el-card shadow="never" header="通用出库记录">
|
||
<el-table :data="genRows" border size="small" v-loading="loadingGen" empty-text="暂无通用出库记录">
|
||
<el-table-column prop="outboundNo" label="出库单号" min-width="150" />
|
||
<el-table-column prop="materialCode" label="物料" min-width="130" />
|
||
<el-table-column prop="batchNo" label="批次号" min-width="120">
|
||
<template #default="{ row }">{{ row.batchNo || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="SN" min-width="180">
|
||
<template #default="{ row }">{{ row.snCodes || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="quantity" label="数量" width="80" align="center" />
|
||
<el-table-column label="归属" min-width="130">
|
||
<template #default="{ row }">
|
||
<span v-if="row.ownershipType === 'workorder'">工单号 {{ row.ownershipNo || '' }}</span>
|
||
<span v-else-if="row.ownershipType === 'project'">科技项目号 {{ row.ownershipNo || '' }}</span>
|
||
<span v-else-if="row.ownershipType === 'other'">其他 {{ row.ownershipNo || '' }}</span>
|
||
<span v-else>无</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="targetStation" label="目标工位" width="100" align="center">
|
||
<template #default="{ row }">{{ row.targetStation || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="boxNo" label="箱号" min-width="120">
|
||
<template #default="{ row }">{{ row.boxNo || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="operator" label="操作人" width="100" align="center" />
|
||
<el-table-column label="时间" width="150" align="center">
|
||
<template #default="{ row }">{{ fmtTime(row.createdAt) }}</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="pager">
|
||
<el-pagination background layout="total, sizes, prev, pager, next" :total="genTotal"
|
||
:current-page="genPage.current" :page-size="genPage.size" :page-sizes="[10, 20, 50, 100]"
|
||
@current-change="(p) => { genPage.current = p; loadGenRows() }"
|
||
@size-change="(s) => { genPage.size = s; genPage.current = 1; loadGenRows() }" />
|
||
</div>
|
||
</el-card>
|
||
</el-tab-pane>
|
||
|
||
<!-- ============ 出库记录(统一主表) ============ -->
|
||
<el-tab-pane label="出库记录" name="records">
|
||
<el-card shadow="never" class="mb12">
|
||
<el-form inline label-width="80px" @submit.prevent>
|
||
<el-form-item label="类型">
|
||
<el-select v-model="recFilters.outboundType" clearable placeholder="全部" style="width:140px">
|
||
<el-option label="备料出库" value="workorder" />
|
||
<el-option label="通用出库" value="general" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="物料">
|
||
<MaterialSelect v-model="recFilters.materialCode" placeholder="物料" width="180px" />
|
||
</el-form-item>
|
||
<el-form-item label="箱号">
|
||
<el-input v-model="recFilters.boxNo" placeholder="按箱号查询" clearable style="width:160px" @keyup.enter="searchRec" />
|
||
</el-form-item>
|
||
<el-form-item label="时间">
|
||
<el-date-picker v-model="recFilters.dateRange" type="daterange" value-format="YYYY-MM-DD"
|
||
range-separator="至" start-placeholder="开始" end-placeholder="结束" style="width:240px" />
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" :loading="loadingRec" @click="searchRec">查询</el-button>
|
||
<el-button @click="resetRec">重置</el-button>
|
||
<el-button v-if="can('outbound:export')" type="success" @click="downloadCSV">导出</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<el-card shadow="never" header="统一出库主表(备料 + 通用,装箱信息同表记录)">
|
||
<el-table :data="recRows" border size="small" v-loading="loadingRec" empty-text="暂无出库记录">
|
||
<el-table-column prop="outboundNo" label="出库单号" min-width="150" />
|
||
<el-table-column label="类型" width="100" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.outboundType === 'general' ? 'warning' : 'primary'" size="small">
|
||
{{ row.outboundType === 'general' ? '通用' : '备料' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="orderNo" label="工单号" min-width="130">
|
||
<template #default="{ row }">{{ row.orderNo || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="materialCode" label="物料" min-width="120" />
|
||
<el-table-column prop="batchNo" label="批次号" min-width="110">
|
||
<template #default="{ row }">{{ row.batchNo || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="SN" min-width="160">
|
||
<template #default="{ row }">{{ row.snCodes || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="quantity" label="数量" width="70" align="center" />
|
||
<el-table-column label="归属" min-width="130">
|
||
<template #default="{ row }">
|
||
<span v-if="row.ownershipType === 'workorder'">工单号 {{ row.ownershipNo || '' }}</span>
|
||
<span v-else-if="row.ownershipType === 'project'">科技项目号 {{ row.ownershipNo || '' }}</span>
|
||
<span v-else-if="row.ownershipType === 'other'">其他 {{ row.ownershipNo || '' }}</span>
|
||
<span v-else>无</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="targetStation" label="目标工位" width="100" align="center">
|
||
<template #default="{ row }">{{ row.targetStation || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="boxNo" label="箱号" min-width="120">
|
||
<template #default="{ row }">{{ row.boxNo || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="operator" label="操作人" width="100" align="center" />
|
||
<el-table-column label="时间" width="150" align="center">
|
||
<template #default="{ row }">{{ fmtTime(row.createdAt) }}</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="pager">
|
||
<el-pagination background layout="total, sizes, prev, pager, next" :total="recTotal"
|
||
:current-page="recPage.current" :page-size="recPage.size" :page-sizes="[10, 20, 50, 100]"
|
||
@current-change="(p) => { recPage.current = p; loadRecords() }"
|
||
@size-change="(s) => { recPage.size = s; recPage.current = 1; loadRecords() }" />
|
||
</div>
|
||
</el-card>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</div>
|
||
<PageHelp :data="helpOutbound" page="Outbound" />
|
||
</template>
|
||
|
||
<style scoped>
|
||
.mb12 { margin-bottom: 12px; }
|
||
.gap { color: #f56c6c; font-weight: 700; }
|
||
.pager { display: flex; justify-content: flex-end; margin-top: 10px; }
|
||
</style>
|