2026-08-28 15:06:01 +08:00
|
|
|
|
<script setup>
|
2026-09-03 14:20:37 +08:00
|
|
|
|
defineOptions({ name: 'Inbound' })
|
2026-08-28 15:06:01 +08:00
|
|
|
|
import { onMounted, reactive, ref } from 'vue'
|
2026-09-03 14:20:37 +08:00
|
|
|
|
import { useRoute } from 'vue-router'
|
2026-08-28 15:06:01 +08:00
|
|
|
|
import request from '../utils/request'
|
2026-09-03 14:20:37 +08:00
|
|
|
|
import { fmtTime } from '../utils/format'
|
|
|
|
|
|
import { exportXlsxFetch } from '../utils/export'
|
2026-08-28 17:23:44 +08:00
|
|
|
|
import PageHelp from '../components/PageHelp.vue'
|
|
|
|
|
|
import { helpInbound } from '../help'
|
2026-08-28 15:06:01 +08:00
|
|
|
|
|
2026-09-03 14:20:37 +08:00
|
|
|
|
const route = useRoute()
|
2026-08-28 15:06:01 +08:00
|
|
|
|
|
2026-09-03 14:20:37 +08:00
|
|
|
|
// 查询条件
|
|
|
|
|
|
const filters = reactive({ inboundNo: '', materialCode: '', manageMode: '', keyword: '' })
|
|
|
|
|
|
const dateRange = ref([]) // [开始日期, 结束日期] YYYY-MM-DD
|
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
|
const list = ref([])
|
|
|
|
|
|
const total = ref(0)
|
|
|
|
|
|
const page = reactive({ current: 1, size: 10 })
|
|
|
|
|
|
const tableRef = ref()
|
2026-08-28 15:06:01 +08:00
|
|
|
|
|
2026-09-03 14:20:37 +08:00
|
|
|
|
// 列表数据:结构件(manageMode=1)与精密件(manageMode=2)同主表(inbound_order)
|
|
|
|
|
|
async function load() {
|
|
|
|
|
|
loading.value = true
|
2026-08-28 15:06:01 +08:00
|
|
|
|
try {
|
2026-09-03 14:20:37 +08:00
|
|
|
|
const data = await request.get('/inbound/query', {
|
|
|
|
|
|
params: {
|
|
|
|
|
|
inboundNo: filters.inboundNo.trim(),
|
|
|
|
|
|
materialCode: filters.materialCode.trim(),
|
|
|
|
|
|
manageMode: filters.manageMode,
|
|
|
|
|
|
keyword: filters.keyword.trim(),
|
|
|
|
|
|
startDate: dateRange.value?.[0] || '',
|
|
|
|
|
|
endDate: dateRange.value?.[1] || '',
|
|
|
|
|
|
page: page.current,
|
|
|
|
|
|
pageSize: page.size
|
|
|
|
|
|
}
|
2026-08-28 15:06:01 +08:00
|
|
|
|
})
|
2026-09-03 14:20:37 +08:00
|
|
|
|
list.value = data?.list || []
|
|
|
|
|
|
total.value = data?.total || 0
|
2026-08-28 15:06:01 +08:00
|
|
|
|
} finally {
|
2026-09-03 14:20:37 +08:00
|
|
|
|
loading.value = false
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-03 14:20:37 +08:00
|
|
|
|
function search() {
|
|
|
|
|
|
page.current = 1
|
|
|
|
|
|
load()
|
|
|
|
|
|
}
|
|
|
|
|
|
function resetFilters() {
|
|
|
|
|
|
filters.inboundNo = ''
|
|
|
|
|
|
filters.materialCode = ''
|
|
|
|
|
|
filters.manageMode = ''
|
|
|
|
|
|
filters.keyword = ''
|
|
|
|
|
|
dateRange.value = []
|
|
|
|
|
|
search()
|
|
|
|
|
|
}
|
2026-08-28 15:06:01 +08:00
|
|
|
|
|
2026-09-03 14:20:37 +08:00
|
|
|
|
// 导出:按当前查询条件导出全部(后端 /inbound/export?format=xlsx 返回 .xlsx)
|
|
|
|
|
|
async function exportCsv() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await exportXlsxFetch('/api/inbound/export', {
|
|
|
|
|
|
inboundNo: filters.inboundNo.trim(),
|
|
|
|
|
|
materialCode: filters.materialCode.trim(),
|
|
|
|
|
|
manageMode: filters.manageMode,
|
|
|
|
|
|
keyword: filters.keyword.trim(),
|
|
|
|
|
|
startDate: dateRange.value?.[0] || '',
|
|
|
|
|
|
endDate: dateRange.value?.[1] || ''
|
|
|
|
|
|
}, '入库管理.xlsx')
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
/* 错误提示由请求层统一处理 */
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-03 14:20:37 +08:00
|
|
|
|
// 批量入库:新标签页打开录入页(单一职责,本页不含录入)
|
|
|
|
|
|
function openCreate() {
|
|
|
|
|
|
window.open(location.origin + '/inbound-create', '_blank')
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-03 14:20:37 +08:00
|
|
|
|
// 入库单明晰:按需懒加载 + 分页(不在列表里一次性拉全量,避免精密件上万条 SN 撑爆前端)
|
|
|
|
|
|
// detailState[inboundNo] = { list, total, page, size, loading, loaded }
|
|
|
|
|
|
const detailState = reactive({})
|
|
|
|
|
|
async function openDetail(row) {
|
|
|
|
|
|
const no = row.inboundNo
|
|
|
|
|
|
if (!detailState[no]) {
|
|
|
|
|
|
detailState[no] = { list: [], total: 0, page: 1, size: 50, loading: false, loaded: false }
|
2026-09-02 08:21:11 +08:00
|
|
|
|
}
|
2026-09-03 14:20:37 +08:00
|
|
|
|
const st = detailState[no]
|
|
|
|
|
|
if (st.loaded || st.loading) return
|
|
|
|
|
|
st.loading = true
|
2026-08-28 15:06:01 +08:00
|
|
|
|
try {
|
2026-09-03 14:20:37 +08:00
|
|
|
|
const d = await request.get('/inbound/details', {
|
|
|
|
|
|
params: { inboundNo: no, page: st.page, pageSize: st.size }
|
2026-08-28 15:06:01 +08:00
|
|
|
|
})
|
2026-09-03 14:20:37 +08:00
|
|
|
|
st.list = d?.list || []
|
|
|
|
|
|
st.total = d?.total || 0
|
|
|
|
|
|
st.loaded = true
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
/* 拦截器统一提示 */
|
2026-08-28 15:06:01 +08:00
|
|
|
|
} finally {
|
2026-09-03 14:20:37 +08:00
|
|
|
|
st.loading = false
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-09-03 14:20:37 +08:00
|
|
|
|
async function changeDetailPage(row, p) {
|
|
|
|
|
|
const st = detailState[row.inboundNo]
|
|
|
|
|
|
if (!st) return
|
|
|
|
|
|
st.page = p
|
|
|
|
|
|
st.loaded = false
|
|
|
|
|
|
await openDetail(row)
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-03 14:20:37 +08:00
|
|
|
|
function typeLabel(m) {
|
|
|
|
|
|
return m === 1 ? '结构件' : m === 2 ? '精密件' : '-'
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-03 14:20:37 +08:00
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
// 库存页点入库单号跳转过来 → 自动带入单号并查询(闭环)
|
|
|
|
|
|
if (route.query.inboundNo) {
|
|
|
|
|
|
filters.inboundNo = String(route.query.inboundNo)
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
2026-09-03 14:20:37 +08:00
|
|
|
|
load()
|
|
|
|
|
|
})
|
2026-08-28 15:06:01 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<el-card shadow="never">
|
2026-09-03 14:20:37 +08:00
|
|
|
|
<!-- 查询条件 -->
|
|
|
|
|
|
<el-form inline @submit.prevent="search" class="mb12">
|
|
|
|
|
|
<el-form-item label="入库单号">
|
|
|
|
|
|
<el-input v-model="filters.inboundNo" placeholder="入库单号(模糊)" clearable style="width:180px" @keyup.enter="search" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="物料编码">
|
|
|
|
|
|
<el-input v-model="filters.materialCode" placeholder="物料编码(模糊)" clearable style="width:160px" @keyup.enter="search" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="类型">
|
|
|
|
|
|
<el-select v-model="filters.manageMode" placeholder="全部" clearable style="width:120px" @change="search">
|
|
|
|
|
|
<el-option label="全部" value="" />
|
|
|
|
|
|
<el-option label="结构件" value="1" />
|
|
|
|
|
|
<el-option label="精密件" value="2" />
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="关键字">
|
|
|
|
|
|
<el-input v-model="filters.keyword" placeholder="物料名称/单号" clearable style="width:180px" @keyup.enter="search" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="入库时间">
|
|
|
|
|
|
<el-date-picker v-model="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="loading" @click="search">查询</el-button>
|
|
|
|
|
|
<el-button @click="resetFilters">重置</el-button>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item>
|
|
|
|
|
|
<el-button type="success" @click="openCreate">批量入库</el-button>
|
|
|
|
|
|
<el-button :disabled="!total" @click="exportCsv">导出</el-button>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 入库单列表(结构件/精密件同表) -->
|
|
|
|
|
|
<el-table ref="tableRef" :data="list" border size="small" v-loading="loading" empty-text="暂无入库数据">
|
|
|
|
|
|
<el-table-column type="expand" label="明细" width="60">
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<div style="padding:8px 24px">
|
|
|
|
|
|
<div v-if="!detailState[row.inboundNo]?.loaded && !detailState[row.inboundNo]?.loading" style="color:#909399">
|
|
|
|
|
|
点击右侧「明细」按钮加载(共 {{ row.detailCount || 0 }} 条)
|
2026-08-29 15:30:25 +08:00
|
|
|
|
</div>
|
2026-09-03 14:20:37 +08:00
|
|
|
|
<template v-else>
|
|
|
|
|
|
<el-table :data="detailState[row.inboundNo].list" size="small" border v-loading="detailState[row.inboundNo].loading">
|
|
|
|
|
|
<el-table-column label="批次号" min-width="180">
|
|
|
|
|
|
<template #default="{ row: d }">{{ d.batchNo || '-' }}</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column label="SN 序列号" min-width="200">
|
|
|
|
|
|
<template #default="{ row: d }">{{ d.snCode || '-' }}</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="quantity" label="数量" width="90" align="center" />
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
<div v-if="detailState[row.inboundNo].total > detailState[row.inboundNo].size"
|
|
|
|
|
|
style="display:flex;justify-content:flex-end;margin-top:8px">
|
|
|
|
|
|
<el-pagination small background layout="total, prev, pager, next"
|
|
|
|
|
|
:total="detailState[row.inboundNo].total"
|
|
|
|
|
|
:current-page="detailState[row.inboundNo].page"
|
|
|
|
|
|
:page-size="detailState[row.inboundNo].size"
|
|
|
|
|
|
@current-change="(p) => changeDetailPage(row, p)" />
|
|
|
|
|
|
</div>
|
2026-08-28 15:06:01 +08:00
|
|
|
|
</template>
|
2026-09-03 14:20:37 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="inboundNo" label="入库单号" min-width="160" />
|
|
|
|
|
|
<el-table-column label="类型" width="90" align="center">
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<el-tag size="small" :type="row.manageMode === 1 ? 'primary' : row.manageMode === 2 ? 'warning' : 'info'">
|
|
|
|
|
|
{{ typeLabel(row.manageMode) }}
|
|
|
|
|
|
</el-tag>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="materialCode" label="物料编码" min-width="140" />
|
|
|
|
|
|
<el-table-column prop="materialName" label="物料名称" min-width="140">
|
|
|
|
|
|
<template #default="{ row }">{{ row.materialName || '-' }}</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="quantity" label="数量" width="80" align="center" />
|
|
|
|
|
|
<el-table-column prop="batchNo" label="批次号" min-width="150">
|
|
|
|
|
|
<template #default="{ row }">{{ row.batchNo || '-' }}</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="operator" label="操作人" width="100">
|
|
|
|
|
|
<template #default="{ row }">{{ row.operator || '-' }}</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column label="入库时间" width="160" align="center">
|
|
|
|
|
|
<template #default="{ row }">{{ fmtTime(row.createdAt) }}</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column label="操作" width="120" align="center" fixed="right">
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<el-button link type="primary" size="small"
|
|
|
|
|
|
@click="tableRef.toggleRowExpansion(row); openDetail(row)">
|
|
|
|
|
|
明细{{ row.detailCount ? '(' + row.detailCount + ')' : '' }}
|
|
|
|
|
|
</el-button>
|
2026-08-28 15:06:01 +08:00
|
|
|
|
</template>
|
2026-09-03 14:20:37 +08:00
|
|
|
|
</el-table-column>
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="pager">
|
|
|
|
|
|
<el-pagination background layout="total, sizes, prev, pager, next" :total="total"
|
|
|
|
|
|
:current-page="page.current" :page-size="page.size" :page-sizes="[10, 20, 50, 100]"
|
|
|
|
|
|
@current-change="(p) => { page.current = p; load() }"
|
|
|
|
|
|
@size-change="(s) => { page.size = s; page.current = 1; load() }" />
|
|
|
|
|
|
</div>
|
2026-08-28 15:06:01 +08:00
|
|
|
|
</el-card>
|
2026-08-28 17:23:44 +08:00
|
|
|
|
<PageHelp :data="helpInbound" />
|
2026-08-28 15:06:01 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
2026-09-03 14:20:37 +08:00
|
|
|
|
.mb12 { margin-bottom: 12px; }
|
|
|
|
|
|
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
|
|
|
|
|
</style>
|