chore: 保存盘点开发前改动

This commit is contained in:
zgw
2026-06-25 17:42:39 +08:00
parent 1af5b3cbd3
commit 4b8bc03343
114 changed files with 10005 additions and 1392 deletions

View File

@@ -455,29 +455,38 @@ export default {
hidden.style.cssText = 'position:fixed;left:-9999px;top:0;'
const svgs = []
rows.forEach(row => {
const fmt = this.getFormatById(row.formatId)
const barcodeType = (fmt && fmt.barcodeType) ? fmt.barcodeType : 'CODE128'
const lineWidth = (fmt && fmt.width) ? Number(fmt.width) : 2
const barHeight = (fmt && fmt.height) ? Number(fmt.height) : 60
const displayValue = fmt ? Number(fmt.showText) === 1 : true
const fontSize = (fmt && fmt.fontSize) ? Number(fmt.fontSize) : 14
const printNum = Math.max(1, Number(row.printNum || 1))
try {
rows.forEach(row => {
const fmt = this.getFormatById(row.formatId)
const barcodeType = this.normalizeBarcodeType(fmt && fmt.barcodeType)
const lineWidth = this.getBarcodeOptionNumber(fmt && fmt.width, 2, 1, 10)
const barHeight = this.getBarcodeOptionNumber(fmt && fmt.height, 60, 20, 300)
const displayValue = fmt ? Number(fmt.showText) !== 0 : true
const fontSize = this.getBarcodeOptionNumber(fmt && fmt.fontSize, 14, 8, 72)
const printNum = Math.max(1, Number(row.printNum || 1))
for (let i = 0; i < printNum; i++) {
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
hidden.appendChild(svg)
JsBarcode(svg, row.barcodeNo, {
format: barcodeType,
width: lineWidth,
height: barHeight,
displayValue: displayValue,
fontSize: fontSize,
margin: 0
})
svgs.push(svg.outerHTML)
for (let i = 0; i < printNum; i++) {
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
hidden.appendChild(svg)
JsBarcode(svg, String(row.barcodeNo), {
format: barcodeType,
width: lineWidth,
height: barHeight,
displayValue: displayValue,
fontSize: fontSize,
margin: 0
})
svgs.push(svg.outerHTML)
}
})
} catch (error) {
if (hidden.parentNode) {
hidden.parentNode.removeChild(hidden)
}
})
console.error('生成条码打印内容失败', error)
this.$modal.msgError('生成条码打印内容失败,请检查条码类型和打印值')
return
}
document.body.appendChild(hidden)
document.body.removeChild(hidden)
@@ -488,6 +497,29 @@ export default {
return this.formatOptions.find(item => String(item.id) === String(formatId)) || null
},
normalizeBarcodeType(type) {
const barcodeTypes = {
'1': 'CODE128',
CODE128: 'CODE128',
CODE39: 'CODE39',
EAN13: 'EAN13',
EAN8: 'EAN8',
UPC: 'UPC',
UPCA: 'UPC',
'UPC-A': 'UPC'
}
const value = type == null ? '' : String(type).trim().toUpperCase()
return barcodeTypes[value] || 'CODE128'
},
getBarcodeOptionNumber(value, fallback, min, max) {
const numberValue = Number(value)
if (!Number.isFinite(numberValue) || numberValue < min || numberValue > max) {
return fallback
}
return numberValue
},
buildPrintHtml(svgs) {
return `<!DOCTYPE html>
<html>

View File

@@ -28,6 +28,7 @@
<el-select v-model="queryParams.pointSize" placeholder="请选择点位大小" clearable>
<el-option label="大点位" value="1" />
<el-option label="小点位" value="2" />
<el-option label="通用点位" value="3" />
</el-select>
</el-form-item>
<el-form-item label="托盘类型" prop="trayType">
@@ -130,7 +131,7 @@
</el-select>
</el-form-item>
<el-form-item label="点位类型" prop="pointType">
<el-select v-model="form.pointType" placeholder="请选择点位类型">
<el-select v-model="form.pointType" placeholder="请选择点位类型" style="width:100%">
<el-option label="入库" value="1" />
<el-option label="出库" value="2" />
<el-option label="入出库" value="3" />
@@ -140,6 +141,7 @@
<el-select v-model="form.pointSize" placeholder="请选择点位大小" style="width:100%">
<el-option label="大点位" value="1" />
<el-option label="小点位" value="2" />
<el-option label="通用点位" value="3" />
</el-select>
</el-form-item>
<el-form-item label="托盘类型" prop="trayType">
@@ -249,6 +251,7 @@ export default {
formatPointSize(size) {
if (size === '1') return '大点位'
if (size === '2') return '小点位'
if (size === '3') return '通用点位'
return size || '-'
},
getList() {

View File

@@ -242,9 +242,9 @@
<dict-tag :options="dict.type.in_stockin_in_type" :value="scope.row.inType"/>
</template>
</el-table-column>
<el-table-column label="用户名称" align="center" prop="userName" show-overflow-tooltip/>
<el-table-column label="部门名称" align="center" prop="deptName" show-overflow-tooltip/>
<el-table-column label="供应商名称" align="center" prop="suplierName" width="100" show-overflow-tooltip/>
<!-- <el-table-column label="用户名称" align="center" prop="userName" show-overflow-tooltip/>-->
<!-- <el-table-column label="部门名称" align="center" prop="deptName" show-overflow-tooltip/>-->
<!-- <el-table-column label="供应商名称" align="center" prop="suplierName" width="100" show-overflow-tooltip/>-->
<!-- <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button

View File

@@ -37,13 +37,15 @@
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="仓库名称" prop="warehouseName">
<el-input
v-model="queryParams.warehouseName"
placeholder="请输入仓库名称"
clearable
@keyup.enter.native="handleQuery"
/>
<el-form-item label="仓库名称" prop="warehouseId">
<el-select v-model="queryParams.warehouseId" placeholder="请选择仓库" clearable filterable>
<el-option
v-for="warehouse in warehouseOptions"
:key="warehouse.warehouseId"
:label="warehouse.warehouseName"
:value="warehouse.warehouseId"
/>
</el-select>
</el-form-item>
<el-form-item label="巷道ID" prop="roadwayId">
<el-input
@@ -82,7 +84,7 @@
plain
icon="el-icon-close"
size="mini"
:disabled="single"
:disabled="!canOperateSelectedJob"
@click="handleCancel"
v-hasPermi="['in:job:cancel']"
>手动作废</el-button>
@@ -93,7 +95,7 @@
plain
icon="el-icon-check"
size="mini"
:disabled="single"
:disabled="!canOperateSelectedJob"
@click="handleComplete"
v-hasPermi="['in:job:complete']"
>手动完成</el-button>
@@ -104,11 +106,15 @@
<el-table v-loading="loading" :data="jobList" @selection-change="handleSelectionChange" @row-click="handleRowClick" highlight-current-row>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="任务ID" align="center" prop="jobId" />
<el-table-column label="任务状态" align="center" prop="status" />
<el-table-column label="任务状态" align="center" prop="status">
<template slot-scope="scope">
<dict-tag :options="dict.type.mydo_job_status" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="任务号" align="center" prop="taskNo" />
<el-table-column label="任务类型" align="center" prop="inOrOut">
<template slot-scope="scope">
<dict-tag :options="dict.type.mydo_job_status" :value="scope.row.inOrOut"/>
<dict-tag :options="dict.type.in_job_type" :value="scope.row.inOrOut"/>
</template>
</el-table-column>
<el-table-column label="执行阶段" align="center" prop="executionPhase">
@@ -203,16 +209,18 @@
<script>
import { listJob, cancelJob, completeJob } from "@/api/in/job"
import { listJobdetail } from "@/api/in/jobdetail"
import { listWarehouse } from "@/api/bas/warehouse"
export default {
name: "Job",
dicts: ['mydo_job_execution_phase', 'mydo_job_status'],
dicts: ['mydo_job_execution_phase', 'mydo_job_status', 'in_job_type'],
data() {
return {
// 遮罩层
loading: true,
// 选中数组
ids: [],
selectedRows: [],
// 非单个禁用
single: true,
// 显示搜索条件
@@ -221,6 +229,8 @@ export default {
total: 0,
// 作业信息表格数据
jobList: [],
// 仓库下拉选项
warehouseOptions: [],
// 查询参数
queryParams: {
pageNum: 1,
@@ -230,6 +240,7 @@ export default {
inOrOut: null,
executionPhase: null,
trayCode: null,
warehouseId: null,
warehouseName: null,
roadwayId: null,
cargoSpaceId: null,
@@ -254,8 +265,14 @@ export default {
}
},
created() {
this.getWarehouseOptions()
this.getList()
},
computed: {
canOperateSelectedJob() {
return this.selectedRows.length === 1 && !this.isFinalJobStatus(this.selectedRows[0].status)
}
},
methods: {
/** 查询作业信息列表 */
getList() {
@@ -266,6 +283,13 @@ export default {
this.loading = false
})
},
getWarehouseOptions() {
listWarehouse({ pageNum: 1, pageSize: 1000 }).then(response => {
this.warehouseOptions = response.rows || []
}).catch(() => {
this.warehouseOptions = []
})
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
@@ -287,10 +311,15 @@ export default {
// 多选框选中数据
handleSelectionChange(selection) {
this.ids = selection.map(item => item.jobId)
this.selectedRows = selection
this.single = selection.length !== 1
},
isFinalJobStatus(status) {
return ['4', '5'].includes(String(status))
},
/** 手动作废按钮操作 */
handleCancel() {
if (!this.canOperateSelectedJob) return
const jobId = this.ids[0]
this.$modal.confirm('是否确认作废选中的作业任务?').then(() => {
return cancelJob(jobId)
@@ -301,6 +330,7 @@ export default {
},
/** 手动完成按钮操作 */
handleComplete() {
if (!this.canOperateSelectedJob) return
const jobId = this.ids[0]
this.$modal.confirm('是否确认手动完成选中的作业任务?').then(() => {
return completeJob(jobId)

View File

@@ -9,10 +9,20 @@
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="任务状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择任务状态" clearable>
<el-option
v-for="dict in dict.type.mydo_job_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="任务类型" prop="inOrOut">
<el-select v-model="queryParams.inOrOut" placeholder="请选择任务类型" clearable>
<el-option
v-for="dict in dict.type.mydo_job_status"
v-for="dict in dict.type.out_job_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
@@ -115,11 +125,15 @@
<el-table v-loading="loading" :data="jobList" @selection-change="handleSelectionChange" @row-click="handleRowClick" highlight-current-row>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="任务ID" align="center" prop="jobId" />
<el-table-column label="任务状态" align="center" prop="status" />
<el-table-column label="任务状态" align="center" prop="status">
<template slot-scope="scope">
<dict-tag :options="dict.type.mydo_job_status" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="任务号" align="center" prop="taskNo" />
<el-table-column label="任务类型" align="center" prop="inOrOut">
<template slot-scope="scope">
<dict-tag :options="dict.type.mydo_job_status" :value="scope.row.inOrOut"/>
<dict-tag :options="dict.type.out_job_type" :value="scope.row.inOrOut"/>
</template>
</el-table-column>
<el-table-column label="执行阶段" align="center" prop="executionPhase">
@@ -137,7 +151,7 @@
<!-- <el-table-column label="重量" align="center" prop="weight" />-->
<el-table-column label="创建时间" align="center" prop="createTime" width="160">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createDate, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="开始时间" align="center" prop="startTime" width="160">
@@ -218,7 +232,7 @@ import { listJobdetail } from "@/api/out/jobdetail"
export default {
name: "Job",
dicts: ['mydo_job_execution_phase', 'mydo_job_status'],
dicts: ['mydo_job_execution_phase', 'mydo_job_status', 'out_job_type'],
data() {
return {
// 遮罩层
@@ -260,7 +274,8 @@ export default {
detailQueryParams: {
pageNum: 1,
pageSize: 10,
jobId: null
jobId: null,
status: null
}
}
},
@@ -334,6 +349,7 @@ export default {
handleRowClick(row) {
this.currentJob = row
this.detailQueryParams.jobId = row.jobId
this.detailQueryParams.status = row.status
this.detailQueryParams.pageNum = 1
this.getJobDetailList()
},

View File

@@ -0,0 +1,472 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="88px">
<el-form-item label="仓库名称" prop="warehouseId">
<el-select v-model="queryParams.warehouseId" placeholder="请选择仓库" clearable filterable>
<el-option
v-for="warehouse in warehouseOptions"
:key="warehouse.warehouseId"
:label="warehouse.warehouseName"
:value="warehouse.warehouseId"
/>
</el-select>
</el-form-item>
<el-form-item label="货位编码" prop="cargoSpaceId">
<el-input
v-model="queryParams.cargoSpaceId"
placeholder="请输入货位编码"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="货位名称" prop="cargoSpaceName">
<el-input
v-model="queryParams.cargoSpaceName"
placeholder="请输入货位名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="货位状态" prop="cargoSpaceStatus">
<el-select v-model="queryParams.cargoSpaceStatus" placeholder="请选择货位状态" clearable>
<el-option
v-for="dict in dict.type.bas_cargo_space_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="托盘条码" prop="trayCode">
<el-input
v-model="queryParams.trayCode"
placeholder="请输入托盘条码"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="物料编码" prop="productId">
<el-input
v-model="queryParams.productId"
placeholder="请输入物料编码"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="物料名称" prop="productName">
<el-input
v-model="queryParams.productName"
placeholder="请输入物料名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="批次号" prop="lotNo">
<el-input
v-model="queryParams.lotNo"
placeholder="请输入批次号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="货位范围" prop="showEmpty">
<el-switch v-model="queryParams.showEmpty" active-text="所有货位" inactive-text="有库存" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-position"
size="mini"
:disabled="multiple"
@click="openBatchGenerateDialog"
v-hasPermi="['out:locationOutbound:generate']"
>批量生成作业</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table
v-loading="loading"
:data="locationList"
border
stripe
highlight-current-row
row-key="cargoSpaceId"
@selection-change="handleSelectionChange"
@row-click="handleRowClick"
>
<el-table-column type="selection" width="55" align="center" :selectable="canGenerate" />
<el-table-column label="货位编码" align="center" prop="cargoSpaceId" width="150" show-overflow-tooltip />
<el-table-column label="货位名称" align="center" prop="cargoSpaceName" min-width="150" show-overflow-tooltip />
<el-table-column label="货位状态" align="center" prop="cargoSpaceStatus" width="110">
<template slot-scope="scope">
<dict-tag :options="dict.type.bas_cargo_space_status" :value="scope.row.cargoSpaceStatus" />
</template>
</el-table-column>
<el-table-column label="仓库" align="center" prop="warehouseName" width="120" show-overflow-tooltip />
<el-table-column label="巷道" align="center" prop="roadwayId" width="80" />
<el-table-column label="排" align="center" prop="thePlatoon" width="70" />
<el-table-column label="列" align="center" prop="theColumn" width="70" />
<el-table-column label="层" align="center" prop="theFloor" width="70" />
<el-table-column label="库存ID" align="center" prop="stockId" width="160" show-overflow-tooltip />
<el-table-column label="托盘条码" align="center" prop="trayCode" width="140" show-overflow-tooltip />
<el-table-column label="托盘类型" align="center" prop="trayType" width="90" />
<el-table-column label="明细数" align="center" prop="detailCount" width="80" />
<el-table-column label="总库存" align="center" prop="quantity" width="90" />
<el-table-column label="已分配" align="center" prop="outQuantity" width="90" />
<el-table-column label="可用库存" align="center" prop="availableQuantity" width="100" />
<el-table-column label="入库时间" align="center" prop="inDate" width="160">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.inDate, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="120" fixed="right">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-position"
:disabled="!canGenerate(scope.row)"
@click="openGenerateDialog(scope.row)"
v-hasPermi="['out:locationOutbound:generate']"
>生成作业</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<div class="detail-panel">
<div class="detail-header">
<span class="detail-title">库存明细</span>
<el-tag v-if="currentLocation" type="primary" size="small">
货位:{{ currentLocation.cargoSpaceName || currentLocation.cargoSpaceId }}
</el-tag>
<span v-else class="detail-hint">(请点击上方货位行查看明细)</span>
</div>
<el-table
:data="locationDetailList"
border
stripe
size="small"
style="width: 100%;"
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
>
<el-table-column label="库存明细ID" align="center" prop="detailId" width="180" show-overflow-tooltip />
<el-table-column label="物料编码" align="center" prop="productId" width="130" show-overflow-tooltip />
<el-table-column label="物料名称" align="center" prop="productName" min-width="160" show-overflow-tooltip />
<el-table-column label="批次号" align="center" prop="lotNo" width="130" show-overflow-tooltip />
<el-table-column label="库存数量" align="center" prop="quantity" width="100" />
<el-table-column label="已分配" align="center" prop="outQuantity" width="100" />
<el-table-column label="可用库存" align="center" prop="availableQuantity" width="110" />
<el-table-column label="入库时间" align="center" prop="inDate" width="160">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.inDate, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<template slot="empty">
<div class="detail-empty">
{{ currentLocation ? (currentLocation.stockId ? '暂无库存明细' : '当前货位无库存') : '请先选择上方货位行' }}
</div>
</template>
</el-table>
</div>
<el-dialog
title="指定货位出库"
:visible.sync="generateDialogVisible"
width="520px"
append-to-body
>
<el-descriptions v-if="currentRow" :column="1" size="small" border class="mb12">
<el-descriptions-item label="货位">{{ currentRow.cargoSpaceName || currentRow.cargoSpaceId }}</el-descriptions-item>
<el-descriptions-item label="托盘">{{ currentRow.trayCode || '-' }}</el-descriptions-item>
<el-descriptions-item label="可用库存">{{ currentRow.availableQuantity || 0 }}</el-descriptions-item>
</el-descriptions>
<el-form label-width="90px" size="small">
<el-form-item label="出库口" required>
<el-select
v-model="selectedExitId"
placeholder="请选择出库口"
filterable
clearable
style="width: 100%;"
:loading="exitLoading"
>
<el-option
v-for="point in exitOptions"
:key="point.pointId"
:label="formatExitLabel(point)"
:value="point.pointId"
/>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="generateDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="generating" @click="confirmGenerate">确认生成</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listLocationOutbound, generateLocationOutbound } from '@/api/out/locationOutbound'
import { listStationPoint } from '@/api/bas/stationpoint'
import { listWarehouse } from '@/api/bas/warehouse'
export default {
name: 'LocationOutbound',
dicts: ['bas_cargo_space_status'],
data() {
return {
loading: true,
showSearch: true,
total: 0,
locationList: [],
currentLocation: null,
locationDetailList: [],
selectedRows: [],
multiple: true,
isBatchGenerate: false,
batchGenerating: false,
warehouseOptions: [],
queryParams: {
pageNum: 1,
pageSize: 10,
warehouseId: null,
warehouseName: null,
cargoSpaceId: null,
cargoSpaceName: null,
cargoSpaceStatus: null,
trayCode: null,
productId: null,
productName: null,
lotNo: null,
showEmpty: false
},
generateDialogVisible: false,
generating: false,
exitLoading: false,
currentRow: null,
selectedExitId: null,
exitOptions: []
}
},
created() {
this.loadWarehouseOptions()
this.getList()
},
methods: {
loadWarehouseOptions() {
listWarehouse({ pageNum: 1, pageSize: 1000 }).then(response => {
this.warehouseOptions = response.rows || []
}).catch(() => {
this.warehouseOptions = []
})
},
getList() {
this.loading = true
listLocationOutbound(this.queryParams).then(response => {
this.locationList = response.rows || []
this.total = response.total || 0
this.refreshCurrentLocation()
this.loading = false
}).catch(() => {
this.loading = false
})
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.resetForm('queryForm')
this.queryParams.showEmpty = false
this.resetDetailQuery()
this.handleQuery()
},
resetDetailQuery() {
this.currentLocation = null
this.locationDetailList = []
},
refreshCurrentLocation() {
if (!this.currentLocation) {
this.locationDetailList = []
return
}
const current = this.locationList.find(row => row.cargoSpaceId === this.currentLocation.cargoSpaceId)
if (!current) {
this.resetDetailQuery()
return
}
this.handleRowClick(current)
},
handleRowClick(row) {
this.currentLocation = row
this.locationDetailList = row && row.details ? row.details : []
},
handleSelectionChange(selection) {
this.selectedRows = selection
this.multiple = selection.length === 0
},
canGenerate(row) {
if (!row) {
return false
}
if (!row.stockId) {
return ['1', '6'].includes(String(row.cargoSpaceStatus))
}
const availableQuantity = Number(row.availableQuantity || 0)
return String(row.cargoSpaceStatus) === '2' && availableQuantity > 0
},
openGenerateDialog(row) {
if (!this.canGenerate(row)) {
this.$modal.msgWarning('当前货位不能生成出库作业')
return
}
this.isBatchGenerate = false
this.currentRow = row
this.selectedExitId = null
this.generateDialogVisible = true
this.loadExitOptions(row)
},
openBatchGenerateDialog() {
if (this.selectedRows.length === 0) {
this.$modal.msgWarning('请选择可生成作业的货位')
return
}
const invalidRow = this.selectedRows.find(row => !this.canGenerate(row))
if (invalidRow) {
this.$modal.msgWarning('选中货位包含无可用库存或已生成出库作业的记录')
return
}
const rows = this.selectedRows
const warehouseIds = Array.from(new Set(rows.map(row => row.warehouseId).filter(Boolean)))
if (warehouseIds.length > 1) {
this.$modal.msgWarning('批量生成作业请选择同一仓库的货位')
return
}
this.isBatchGenerate = true
this.currentRow = rows[0]
this.selectedExitId = null
this.generateDialogVisible = true
this.loadExitOptions(rows[0])
},
loadExitOptions(row) {
this.exitLoading = true
listStationPoint({
warehouseId: row.warehouseId,
status: '0'
}).then(response => {
const rows = response.rows || response.data || []
this.exitOptions = rows.filter(point => {
return ['2', '3'].includes(String(point.pointType)) &&
String(point.pointStatus || '0') === '0' &&
this.pointSupportsTrayType(point, row.trayType)
})
if (this.exitOptions.length === 1) {
this.selectedExitId = this.exitOptions[0].pointId
}
this.exitLoading = false
}).catch(() => {
this.exitOptions = []
this.exitLoading = false
})
},
pointSupportsTrayType(point, trayType) {
if (!trayType || !point.trayType) {
return true
}
return String(point.trayType).split(',').map(item => item.trim()).includes(String(trayType))
},
formatExitLabel(point) {
const typeName = String(point.pointType) === '3' ? '入出库' : '出库'
const trayType = point.trayType ? `,${point.trayType}类托盘` : ''
return `${point.pointName || point.pointId}(${point.pointId},${typeName}${trayType})`
},
confirmGenerate() {
if (!this.currentRow || !this.currentRow.cargoSpaceId) {
this.$modal.msgWarning('请选择要出库的货位')
return
}
if (!this.selectedExitId) {
this.$modal.msgWarning('请选择出库口')
return
}
const rows = this.isBatchGenerate ? this.selectedRows.filter(row => this.canGenerate(row)) : [this.currentRow]
const confirmText = this.isBatchGenerate
? `确认将选中的 ${rows.length} 个货位生成出库作业?`
: this.currentRow.stockId
? `确认将货位 ${this.currentRow.cargoSpaceName || this.currentRow.cargoSpaceId} 的整托库存生成出库作业?`
: `确认将空货位 ${this.currentRow.cargoSpaceName || this.currentRow.cargoSpaceId} 生成出库作业?`
this.$modal.confirm(confirmText).then(() => {
this.generating = true
return Promise.all(rows.map(row => generateLocationOutbound({
stockId: row.stockId,
cargoSpaceId: row.cargoSpaceId,
exitId: this.selectedExitId
})))
}).then(response => {
const first = Array.isArray(response) && response.length > 0 ? response[0] : response
const message = this.isBatchGenerate ? '批量生成成功' : (first.msg || '生成成功')
this.$modal.msgSuccess(message)
this.generateDialogVisible = false
this.generating = false
this.getList()
}).catch(() => {
this.generating = false
})
}
}
}
</script>
<style scoped>
.detail-panel {
margin-top: 20px;
}
.detail-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
}
.detail-title {
font-size: 14px;
font-weight: 600;
color: #303133;
}
.detail-hint {
margin-left: 4px;
color: #909399;
font-size: 12px;
}
.detail-empty {
padding: 18px 24px;
color: #909399;
background: #f8fafc;
}
.mb12 {
margin-bottom: 12px;
}
</style>

View File

@@ -155,7 +155,7 @@
icon="el-icon-magic-stick"
size="mini"
:disabled="single"
@click="handleAutoAssign"
@click="openAutoAssignDialog"
v-hasPermi="['out:stockout:autoAssign']"
>自动分配
</el-button>
@@ -251,8 +251,9 @@
<el-table-column label="物料编码" align="center" prop="productId" width="150"/>
<el-table-column label="物料名称" align="center" prop="productName" min-width="150" show-overflow-tooltip/>
<el-table-column label="库存明细" align="center" prop="stockDetailId" width="160" show-overflow-tooltip/>
<el-table-column label="托盘类型" align="center" prop="trayType" width="90"/>
<el-table-column label="计划数量" align="center" prop="quantity" width="100"/>
<el-table-column label="扫码数量" align="center" prop="allotNum" width="100"/>
<el-table-column label="分配数量" align="center" prop="allotNum" width="100"/>
<el-table-column label="实际数量" align="center" prop="assignNum" width="100"/>
<el-table-column label="备注" align="center" prop="remark" min-width="120" show-overflow-tooltip/>
<template slot="empty">
@@ -273,12 +274,72 @@
</div>
<!-- ===================== 手动分配弹窗 ===================== -->
<el-dialog
title="选择出库口"
:visible.sync="exitDialogVisible"
width="420px"
append-to-body
>
<el-form label-width="110px" size="small">
<el-form-item
v-for="trayType in autoAssignTrayTypes"
:key="trayType"
:label="formatTrayTypeLabel(trayType)"
required
>
<el-select
v-model="selectedExitByTrayType[trayType]"
placeholder="请选择出库口"
clearable
filterable
style="width: 100%;"
:loading="exitLoading"
>
<el-option
v-for="point in getExitOptionsByTrayType(trayType)"
:key="point.pointId"
:label="formatExitLabel(point)"
:value="point.pointId"
/>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="exitDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmAutoAssign">确认分配</el-button>
</div>
</el-dialog>
<el-dialog
title="选择出库库存"
:visible.sync="assignDialogVisible"
width="80%"
append-to-body
>
<el-form v-if="manualAssignTrayTypes.length > 0" label-width="110px" size="small" style="margin-bottom: 12px;">
<el-form-item
v-for="trayType in manualAssignTrayTypes"
:key="trayType"
:label="formatTrayTypeLabel(trayType)"
required
>
<el-select
v-model="selectedExitByTrayType[trayType]"
placeholder="请选择出库口"
clearable
filterable
style="width: 320px;"
:loading="exitLoading"
>
<el-option
v-for="point in getExitOptionsByTrayType(trayType)"
:key="point.pointId"
:label="formatExitLabel(point)"
:value="point.pointId"
/>
</el-select>
</el-form-item>
</el-form>
<el-table
v-loading="stockLoading"
:data="stockList"
@@ -295,6 +356,7 @@
<el-table-column label="批次" prop="lotNo" align="center"/>
<el-table-column label="货位" prop="cargoSpaceName" align="center"/>
<el-table-column label="托盘号" prop="trayCode" align="center"/>
<el-table-column label="托盘类型" prop="trayType" align="center" width="90"/>
<el-table-column label="可用数量" prop="availableQuantity" align="center"/>
</el-table>
@@ -317,6 +379,7 @@ import {
} from "@/api/out/stockout"
import { listStockoutdetail } from "@/api/out/stockoutdetail"
import { listStockDetail } from "@/api/bas/stock"
import { listStationPoint } from "@/api/bas/stationpoint"
export default {
name: "Stockout",
@@ -325,6 +388,7 @@ export default {
return {
loading: true,
ids: [],
selectedRows: [],
single: true,
multiple: true,
showSearch: true,
@@ -356,17 +420,33 @@ export default {
detailQueryParams: {
pageNum: 1,
pageSize: 10,
stockoutId: null
stockoutId: null,
status: null
},
// 子单选中ID
detailIds: [],
detailSingle: true,
// 手动分配弹窗
exitDialogVisible: false,
assignDialogVisible: false,
exitLoading: false,
stockLoading: false,
exitOptions: [],
stockList: [],
selectedStockList: []
selectedExitId: null,
selectedExitByTrayType: {},
selectedStockList: [],
autoAssignDetails: [],
defaultTrayTypeKey: '__DEFAULT__'
}
},
computed: {
autoAssignTrayTypes() {
return this.collectTrayTypes(this.autoAssignDetails)
},
manualAssignTrayTypes() {
return this.collectTrayTypes(this.selectedStockList)
}
},
created() {
@@ -409,16 +489,19 @@ export default {
resetDetailList() {
this.detailList = []
this.detailQueryParams.stockoutId = null
this.detailQueryParams.status = null
this.currentStockout = null
this.detailTotal = 0
},
handleSelectionChange(selection) {
this.ids = selection.map(item => item.stockoutId)
this.selectedRows = selection
this.single = selection.length !== 1
},
handleRowClick(row) {
this.currentStockout = row
this.detailQueryParams.stockoutId = row.stockoutId
this.detailQueryParams.status = row.status
this.detailQueryParams.pageNum = 1
this.getDetailList()
},
@@ -444,23 +527,126 @@ export default {
this.$modal.msgWarning("请选择子单")
return
}
if (!this.currentStockout) {
this.$modal.msgWarning("请先选择出库主单")
return
}
const detail = this.detailList.find(item => item.stockoutDetailId === this.detailIds[0])
this.selectedExitId = null
this.selectedExitByTrayType = {}
this.selectedStockList = []
this.assignDialogVisible = true
this.stockLoading = true
this.loadExitOptions(this.currentStockout.warehouseId)
// 根据物料编码查询可用库存明细
listStockDetail({
productId: detail.productId,
warehouseId: this.currentStockout ? this.currentStockout.warehouseId : null,
stockStatus: '1'
}).then(res => {
this.stockList = res.rows
this.stockList = res.rows || []
this.stockLoading = false
})
},
loadExitOptions(warehouseId) {
if (!warehouseId) {
this.exitOptions = []
return Promise.resolve()
}
this.exitLoading = true
return listStationPoint({
warehouseId: warehouseId,
status: '0'
}).then(res => {
const rows = res.rows || res.data || []
this.exitOptions = rows.filter(point => {
return ['2', '3'].includes(String(point.pointType)) && String(point.pointStatus || '0') === '0'
})
this.exitLoading = false
}).catch(() => {
this.exitOptions = []
this.exitLoading = false
})
},
formatExitLabel(point) {
const typeName = String(point.pointType) === '3' ? '入出库' : '出库'
const trayType = point.trayType ? `,${point.trayType}类托盘` : ''
return `${point.pointName || point.pointId}(${point.pointId},${typeName}${trayType})`
},
getTrayTypeKey(row) {
return row && row.trayType ? String(row.trayType) : this.defaultTrayTypeKey
},
collectTrayTypes(rows) {
const types = []
;(rows || []).forEach(row => {
const key = this.getTrayTypeKey(row)
if (!types.includes(key)) {
types.push(key)
}
})
return types
},
formatTrayTypeLabel(trayType) {
return trayType === this.defaultTrayTypeKey ? '默认出库口' : `${trayType}类托盘`
},
pointSupportsTrayType(point, trayType) {
if (trayType === this.defaultTrayTypeKey) {
return true
}
if (!point.trayType) {
return true
}
return String(point.trayType).split(',').map(item => item.trim()).includes(trayType)
},
getExitOptionsByTrayType(trayType) {
return this.exitOptions.filter(point => this.pointSupportsTrayType(point, trayType))
},
resetExitSelections(trayTypes) {
this.selectedExitByTrayType = {}
;(trayTypes || []).forEach(trayType => {
this.$set(this.selectedExitByTrayType, trayType, null)
})
},
ensureExitSelections(trayTypes) {
;(trayTypes || []).forEach(trayType => {
if (!Object.prototype.hasOwnProperty.call(this.selectedExitByTrayType, trayType)) {
this.$set(this.selectedExitByTrayType, trayType, null)
}
})
},
validateExitSelections(trayTypes) {
const missingType = (trayTypes || []).find(trayType => !this.selectedExitByTrayType[trayType])
if (missingType) {
this.$modal.msgWarning(`请选择${this.formatTrayTypeLabel(missingType)}的出库口`)
return false
}
return true
},
buildExitByTrayType(trayTypes) {
const exitByTrayType = {}
;(trayTypes || []).forEach(trayType => {
if (trayType !== this.defaultTrayTypeKey) {
exitByTrayType[trayType] = this.selectedExitByTrayType[trayType]
}
})
return exitByTrayType
},
// 库存选中事件
handleStockSelectionChange(val) {
this.selectedStockList = val
this.ensureExitSelections(this.manualAssignTrayTypes)
},
// 确认分配
@@ -469,13 +655,25 @@ export default {
this.$modal.msgWarning("请选择要出库的库存")
return
}
const trayTypes = this.manualAssignTrayTypes
if (!this.validateExitSelections(trayTypes)) {
return
}
const stockIdList = Array.from(new Set(this.selectedStockList.map(item => item.detailId || item.stockDetailId || item.stockId)))
const exitByStockId = {}
this.selectedStockList.forEach(item => {
const stockKey = item.detailId || item.stockDetailId || item.stockId
exitByStockId[stockKey] = this.selectedExitByTrayType[this.getTrayTypeKey(item)]
})
const detailId = this.detailIds[0]
this.$modal.confirm("确认选中库存进行分配?").then(() => {
// 调用子单手动分配接口
return manualAssignStockout({
stockoutDetailId: detailId,
exitId: this.selectedExitByTrayType[this.defaultTrayTypeKey] || null,
exitByTrayType: this.buildExitByTrayType(trayTypes),
exitByStockId: exitByStockId,
stockIdList: stockIdList
})
}).then(() => {
@@ -503,9 +701,40 @@ export default {
this.getList()
})
},
handleAutoAssign() {
autoAssignStockout(this.ids[0]).then(() => {
openAutoAssignDialog() {
const stockout = this.selectedRows[0]
if (!stockout) {
this.$modal.msgWarning("请选择出库单据")
return
}
this.selectedExitId = null
this.selectedExitByTrayType = {}
this.autoAssignDetails = []
this.exitDialogVisible = true
this.loadExitOptions(stockout.warehouseId)
const detailPromise = this.currentStockout && this.currentStockout.stockoutId === stockout.stockoutId && this.detailList.length > 0
? Promise.resolve(this.detailList)
: listStockoutdetail({ stockoutId: stockout.stockoutId, status: stockout.status, pageNum: 1, pageSize: 9999 }).then(res => res.rows || [])
detailPromise.then(rows => {
this.autoAssignDetails = rows
this.resetExitSelections(this.autoAssignTrayTypes)
})
},
confirmAutoAssign() {
const trayTypes = this.autoAssignTrayTypes
if (trayTypes.length === 0) {
this.$modal.msgWarning("未加载到出库明细")
return
}
if (!this.validateExitSelections(trayTypes)) {
return
}
autoAssignStockout(this.ids[0], {
exitId: this.selectedExitByTrayType[this.defaultTrayTypeKey] || null,
exitByTrayType: this.buildExitByTrayType(trayTypes)
}).then(() => {
this.$modal.msgSuccess("自动分配成功")
this.exitDialogVisible = false
this.getDetailList()
})
}

View File

@@ -0,0 +1,213 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="任务号" prop="taskNo">
<el-input v-model="queryParams.taskNo" placeholder="请输入任务号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="任务状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择任务状态" clearable>
<el-option v-for="dict in dict.type.mydo_job_status" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="任务类型" prop="inOrOut">
<el-select v-model="queryParams.inOrOut" placeholder="请选择任务类型" clearable>
<el-option v-for="dict in dict.type.in_job_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="托盘码" prop="trayCode">
<el-input v-model="queryParams.trayCode" placeholder="请输入托盘码" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="仓库名称" prop="warehouseName">
<el-input v-model="queryParams.warehouseName" placeholder="请输入仓库名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="货位名称" prop="cargoSpaceName">
<el-input v-model="queryParams.cargoSpaceName" placeholder="请输入货位名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker v-model="daterangeCreateTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange" range-separator="-" start-placeholder="开始时间" end-placeholder="结束时间" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['in:job:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="jobList" @row-click="handleRowClick" highlight-current-row>
<el-table-column label="任务ID" align="center" prop="jobId" width="160" show-overflow-tooltip />
<el-table-column label="任务号" align="center" prop="taskNo" width="140" show-overflow-tooltip />
<el-table-column label="任务状态" align="center" prop="status" width="100">
<template slot-scope="scope"><dict-tag :options="dict.type.mydo_job_status" :value="scope.row.status" /></template>
</el-table-column>
<el-table-column label="任务类型" align="center" prop="inOrOut" width="100">
<template slot-scope="scope"><dict-tag :options="dict.type.in_job_type" :value="scope.row.inOrOut" /></template>
</el-table-column>
<el-table-column label="托盘码" align="center" prop="trayCode" width="140" show-overflow-tooltip />
<el-table-column label="仓库名称" align="center" prop="warehouseName" width="120" show-overflow-tooltip />
<el-table-column label="巷道ID" align="center" prop="roadwayId" width="100" />
<el-table-column label="货位名称" align="center" prop="cargoSpaceName" width="140" show-overflow-tooltip />
<el-table-column label="创建时间" align="center" prop="createTime" width="160">
<template slot-scope="scope">{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</template>
</el-table-column>
<el-table-column label="开始时间" align="center" prop="startTime" width="160">
<template slot-scope="scope">{{ parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</template>
</el-table-column>
<el-table-column label="结束时间" align="center" prop="finishTime" width="160">
<template slot-scope="scope">{{ parseTime(scope.row.finishTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
<div class="detail-panel" style="margin-top: 20px;">
<div class="detail-header" style="margin-bottom: 10px; display: flex; align-items: center; gap: 8px;">
<span style="font-size: 14px; font-weight: 600; color: #303133;">子作业详情</span>
<el-tag v-if="currentJob" type="primary" size="small" style="margin-left: 4px;">
主任务:{{ currentJob.taskNo || currentJob.jobId }}
</el-tag>
<span v-else style="font-size: 12px; color: #909399; margin-left: 4px;">(请点击上方主作业行查看对应子作业数据)</span>
</div>
<el-table
v-loading="detailLoading"
:data="jobDetailList"
border
stripe
size="small"
style="width: 100%;"
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
>
<el-table-column label="子任务ID" align="center" prop="jobDetailId" width="180" show-overflow-tooltip />
<el-table-column label="物料编码" align="center" prop="productId" width="120" />
<el-table-column label="物料名称" align="center" prop="productName" min-width="150" show-overflow-tooltip />
<el-table-column label="批次" align="center" prop="lotNo" width="120" />
<el-table-column label="子单编号" align="center" prop="inOrOutDId" width="160" show-overflow-tooltip />
<el-table-column label="入库数量" align="center" prop="inQuantity" width="100" />
<el-table-column label="库存数量" align="center" prop="stockQuantity" width="100" />
<el-table-column label="出库数量" align="center" prop="outQuantity" width="100" />
<el-table-column label="供应商名称" align="center" prop="supplierName" width="120" show-overflow-tooltip />
<template slot="empty">
<div style="padding: 30px 0; color: #909399; font-size: 13px;">
<i class="el-icon-document" style="font-size: 28px; display: block; margin-bottom: 8px;"></i>
{{ currentJob ? '暂无子作业数据' : '请先选择上方主作业行' }}
</div>
</template>
</el-table>
<pagination
v-show="detailTotal > 0"
:total="detailTotal"
:page.sync="detailQueryParams.pageNum"
:limit.sync="detailQueryParams.pageSize"
@pagination="getJobDetailList"
style="margin-top: 0;"
/>
</div>
</div>
</template>
<script>
import { listJob } from "@/api/in/job"
import { listJobdetail } from "@/api/in/jobdetail"
export default {
name: "SearchInJobFlow",
dicts: ['mydo_job_status', 'in_job_type'],
data() {
return {
loading: true,
showSearch: true,
total: 0,
jobList: [],
currentJob: null,
detailLoading: false,
detailTotal: 0,
jobDetailList: [],
daterangeCreateTime: [],
queryParams: {
pageNum: 1,
pageSize: 10,
status: '4',
taskNo: null,
inOrOut: null,
trayCode: null,
warehouseName: null,
cargoSpaceName: null
},
detailQueryParams: {
pageNum: 1,
pageSize: 10,
jobId: null,
status: null
}
}
},
created() {
this.getList()
},
methods: {
buildQuery() {
const query = { ...this.queryParams, params: { history: 'true' } }
if (this.daterangeCreateTime && this.daterangeCreateTime.length === 2) {
query.params.beginCreateTime = this.daterangeCreateTime[0]
query.params.endCreateTime = this.daterangeCreateTime[1]
}
return query
},
getList() {
this.resetDetailList()
this.loading = true
listJob(this.buildQuery()).then(response => {
this.jobList = response.rows || []
this.total = response.total || 0
this.loading = false
}).catch(() => {
this.loading = false
})
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.daterangeCreateTime = []
this.resetForm("queryForm")
this.queryParams.status = '4'
this.handleQuery()
},
resetDetailList() {
this.currentJob = null
this.jobDetailList = []
this.detailTotal = 0
this.detailQueryParams.jobId = null
this.detailQueryParams.status = null
this.detailQueryParams.pageNum = 1
},
handleRowClick(row) {
this.currentJob = row
this.detailQueryParams.jobId = row.jobId
this.detailQueryParams.status = row.status
this.detailQueryParams.pageNum = 1
this.getJobDetailList()
},
getJobDetailList() {
if (!this.detailQueryParams.jobId) return
this.detailLoading = true
listJobdetail(this.detailQueryParams).then(response => {
this.jobDetailList = response.rows || []
this.detailTotal = response.total || 0
this.detailLoading = false
}).catch(() => {
this.detailLoading = false
})
},
handleExport() {
this.download('in/job/export', this.buildQuery(), `in_job_flow_${new Date().getTime()}.xlsx`)
}
}
}
</script>

View File

@@ -0,0 +1,204 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="入库类型" prop="inType">
<el-select v-model="queryParams.inType" placeholder="请选择入库类型" clearable>
<el-option v-for="dict in dict.type.in_stockin_in_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="单据状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择单据状态" clearable>
<el-option v-for="dict in dict.type.in_stockin_status" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="上位单号" prop="erpBill">
<el-input v-model="queryParams.erpBill" placeholder="请输入上位单号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="仓库名称" prop="warehouseName">
<el-input v-model="queryParams.warehouseName" placeholder="请输入仓库名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="供应商" prop="supplierName">
<el-input v-model="queryParams.supplierName" placeholder="请输入供应商" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="完成时间">
<el-date-picker v-model="daterangeFinishDate" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange" range-separator="-" start-placeholder="开始时间" end-placeholder="结束时间" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['in:stockin:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="stockinList" @row-click="handleRowClick" highlight-current-row>
<el-table-column label="入库单号" align="center" prop="stockinId" width="160" show-overflow-tooltip />
<el-table-column label="入库类型" align="center" prop="inType" width="100">
<template slot-scope="scope"><dict-tag :options="dict.type.in_stockin_in_type" :value="scope.row.inType" /></template>
</el-table-column>
<el-table-column label="单据状态" align="center" prop="status" width="100">
<template slot-scope="scope"><dict-tag :options="dict.type.in_stockin_status" :value="scope.row.status" /></template>
</el-table-column>
<el-table-column label="上位单号" align="center" prop="erpBill" width="140" show-overflow-tooltip />
<el-table-column label="仓库名称" align="center" prop="warehouseName" width="120" show-overflow-tooltip />
<el-table-column label="供应商" align="center" prop="supplierName" min-width="140" show-overflow-tooltip />
<el-table-column label="创建时间" align="center" prop="createDate" width="160">
<template slot-scope="scope">{{ parseTime(scope.row.createDate, '{y}-{m}-{d} {h}:{i}:{s}') }}</template>
</el-table-column>
<el-table-column label="开始时间" align="center" prop="startDate" width="160">
<template slot-scope="scope">{{ parseTime(scope.row.startDate, '{y}-{m}-{d} {h}:{i}:{s}') }}</template>
</el-table-column>
<el-table-column label="完成时间" align="center" prop="finishDate" width="160">
<template slot-scope="scope">{{ parseTime(scope.row.finishDate, '{y}-{m}-{d} {h}:{i}:{s}') }}</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
<div class="detail-panel" style="margin-top: 20px;">
<div class="detail-header" style="margin-bottom: 10px; display: flex; align-items: center; gap: 8px;">
<span style="font-size: 14px; font-weight: 600; color: #303133;">入库子单详情</span>
<el-tag v-if="currentStockin" type="primary" size="small" style="margin-left: 4px;">
主单:{{ currentStockin.stockinId }}
</el-tag>
<span v-else style="font-size: 12px; color: #909399; margin-left: 4px;">(请点击上方主单行查看对应子单数据)</span>
</div>
<el-table
v-loading="detailLoading"
:data="detailList"
border
stripe
size="small"
style="width: 100%;"
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
>
<el-table-column label="子单ID" align="center" prop="stockinDetailId" width="180" show-overflow-tooltip />
<el-table-column label="批次" align="center" prop="lotNo" width="150" />
<el-table-column label="物料编码" align="center" prop="productId" width="150" />
<el-table-column label="物料名称" align="center" prop="productName" min-width="150" show-overflow-tooltip />
<el-table-column label="计划数量" align="center" prop="quantity" width="100" />
<el-table-column label="扫码数量" align="center" prop="scanNum" width="100" />
<el-table-column label="实际数量" align="center" prop="assignNum" width="100" />
<el-table-column label="备注" align="center" prop="remark" min-width="120" show-overflow-tooltip />
<template slot="empty">
<div style="padding: 30px 0; color: #909399; font-size: 13px;">
<i class="el-icon-document" style="font-size: 28px; display: block; margin-bottom: 8px;"></i>
{{ currentStockin ? '暂无子单数据' : '请先选择上侧主单行' }}
</div>
</template>
</el-table>
<pagination
v-show="detailTotal > 0"
:total="detailTotal"
:page.sync="detailQueryParams.pageNum"
:limit.sync="detailQueryParams.pageSize"
@pagination="getDetailList"
style="margin-top: 0;"
/>
</div>
</div>
</template>
<script>
import { listStockin, listStockinDetail } from "@/api/in/stockin"
export default {
name: "SearchInStockinHistory",
dicts: ['in_stockin_in_type', 'in_stockin_status'],
data() {
return {
loading: true,
showSearch: true,
total: 0,
stockinList: [],
currentStockin: null,
detailLoading: false,
detailTotal: 0,
detailList: [],
daterangeFinishDate: [],
queryParams: {
pageNum: 1,
pageSize: 10,
inType: null,
status: null,
erpBill: null,
warehouseName: null,
supplierName: null
},
detailQueryParams: {
pageNum: 1,
pageSize: 10,
stockinId: null,
status: null
}
}
},
created() {
this.getList()
},
methods: {
buildQuery() {
const query = { ...this.queryParams, params: { history: 'true' } }
if (this.daterangeFinishDate && this.daterangeFinishDate.length === 2) {
query.params.beginFinishDate = this.daterangeFinishDate[0]
query.params.endFinishDate = this.daterangeFinishDate[1]
}
return query
},
getList() {
this.resetDetailList()
this.loading = true
listStockin(this.buildQuery()).then(response => {
this.stockinList = response.rows || []
this.total = response.total || 0
this.loading = false
}).catch(() => {
this.loading = false
})
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.daterangeFinishDate = []
this.resetForm("queryForm")
this.handleQuery()
},
resetDetailList() {
this.currentStockin = null
this.detailList = []
this.detailTotal = 0
this.detailQueryParams.stockinId = null
this.detailQueryParams.status = null
this.detailQueryParams.pageNum = 1
},
handleRowClick(row) {
this.currentStockin = row
this.detailQueryParams.stockinId = row.stockinId
this.detailQueryParams.status = row.status
this.detailQueryParams.pageNum = 1
this.getDetailList()
},
getDetailList() {
if (!this.detailQueryParams.stockinId) return
this.detailLoading = true
listStockinDetail(this.detailQueryParams).then(response => {
this.detailList = response.rows || []
this.detailTotal = response.total || 0
this.detailLoading = false
}).catch(() => {
this.detailLoading = false
})
},
handleExport() {
this.download('in/stockin/export', this.buildQuery(), `in_stockin_history_${new Date().getTime()}.xlsx`)
}
}
}
</script>

View File

@@ -0,0 +1,133 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="任务号" prop="taskNo">
<el-input v-model="queryParams.taskNo" placeholder="请输入任务号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="任务状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择任务状态" clearable>
<el-option v-for="dict in dict.type.mydo_job_status" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="任务类型" prop="inOrOut">
<el-select v-model="queryParams.inOrOut" placeholder="请选择任务类型" clearable>
<el-option v-for="dict in dict.type.out_job_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="托盘码" prop="trayCode">
<el-input v-model="queryParams.trayCode" placeholder="请输入托盘码" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="仓库名称" prop="warehouseName">
<el-input v-model="queryParams.warehouseName" placeholder="请输入仓库名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="货位名称" prop="cargoSpaceName">
<el-input v-model="queryParams.cargoSpaceName" placeholder="请输入货位名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker v-model="daterangeCreateTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange" range-separator="-" start-placeholder="开始时间" end-placeholder="结束时间" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['out:job:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="jobList">
<el-table-column label="任务ID" align="center" prop="jobId" width="160" show-overflow-tooltip />
<el-table-column label="任务号" align="center" prop="taskNo" width="140" show-overflow-tooltip />
<el-table-column label="任务状态" align="center" prop="status" width="100">
<template slot-scope="scope"><dict-tag :options="dict.type.mydo_job_status" :value="scope.row.status" /></template>
</el-table-column>
<el-table-column label="任务类型" align="center" prop="inOrOut" width="100">
<template slot-scope="scope"><dict-tag :options="dict.type.out_job_type" :value="scope.row.inOrOut" /></template>
</el-table-column>
<el-table-column label="托盘码" align="center" prop="trayCode" width="140" show-overflow-tooltip />
<el-table-column label="仓库名称" align="center" prop="warehouseName" width="120" show-overflow-tooltip />
<el-table-column label="巷道ID" align="center" prop="roadwayId" width="100" />
<el-table-column label="货位名称" align="center" prop="cargoSpaceName" width="140" show-overflow-tooltip />
<el-table-column label="出口" align="center" prop="exitId" width="100" />
<el-table-column label="创建时间" align="center" prop="createTime" width="160">
<template slot-scope="scope">{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</template>
</el-table-column>
<el-table-column label="开始时间" align="center" prop="startTime" width="160">
<template slot-scope="scope">{{ parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</template>
</el-table-column>
<el-table-column label="结束时间" align="center" prop="finishTime" width="160">
<template slot-scope="scope">{{ parseTime(scope.row.finishTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
</div>
</template>
<script>
import { listJob } from "@/api/out/job"
export default {
name: "SearchOutJobFlow",
dicts: ['mydo_job_status', 'out_job_type'],
data() {
return {
loading: true,
showSearch: true,
total: 0,
jobList: [],
daterangeCreateTime: [],
queryParams: {
pageNum: 1,
pageSize: 10,
status: '4',
taskNo: null,
inOrOut: null,
trayCode: null,
warehouseName: null,
cargoSpaceName: null
}
}
},
created() {
this.getList()
},
methods: {
buildQuery() {
const query = { ...this.queryParams, params: { history: 'true' } }
if (this.daterangeCreateTime && this.daterangeCreateTime.length === 2) {
query.params.beginCreateTime = this.daterangeCreateTime[0]
query.params.endCreateTime = this.daterangeCreateTime[1]
}
return query
},
getList() {
this.loading = true
listJob(this.buildQuery()).then(response => {
this.jobList = response.rows || []
this.total = response.total || 0
this.loading = false
}).catch(() => {
this.loading = false
})
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.daterangeCreateTime = []
this.resetForm("queryForm")
this.queryParams.status = '4'
this.handleQuery()
},
handleExport() {
this.download('out/job/export', this.buildQuery(), `out_job_flow_${new Date().getTime()}.xlsx`)
}
}
}
</script>

View File

@@ -0,0 +1,125 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="出库类型" prop="outType">
<el-select v-model="queryParams.outType" placeholder="请选择出库类型" clearable>
<el-option v-for="dict in dict.type.out_stockout_out_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="单据状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择单据状态" clearable>
<el-option v-for="dict in dict.type.out_stockout_status" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="上位单号" prop="erpBill">
<el-input v-model="queryParams.erpBill" placeholder="请输入上位单号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="仓库名称" prop="warehouseName">
<el-input v-model="queryParams.warehouseName" placeholder="请输入仓库名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="客户名称" prop="customerName">
<el-input v-model="queryParams.customerName" placeholder="请输入客户名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="完成时间">
<el-date-picker v-model="daterangeFinishDate" style="width: 240px" value-format="yyyy-MM-dd" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['out:stockout:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="stockoutList">
<el-table-column label="出库单号" align="center" prop="stockoutId" width="160" show-overflow-tooltip />
<el-table-column label="出库类型" align="center" prop="outType" width="100">
<template slot-scope="scope"><dict-tag :options="dict.type.out_stockout_out_type" :value="scope.row.outType" /></template>
</el-table-column>
<el-table-column label="单据状态" align="center" prop="status" width="100">
<template slot-scope="scope"><dict-tag :options="dict.type.out_stockout_status" :value="scope.row.status" /></template>
</el-table-column>
<el-table-column label="上位单号" align="center" prop="erpBill" width="140" show-overflow-tooltip />
<el-table-column label="仓库名称" align="center" prop="warehouseName" width="120" show-overflow-tooltip />
<el-table-column label="客户名称" align="center" prop="customerName" min-width="140" show-overflow-tooltip />
<el-table-column label="创建时间" align="center" prop="createDate" width="160">
<template slot-scope="scope">{{ parseTime(scope.row.createDate, '{y}-{m}-{d} {h}:{i}:{s}') }}</template>
</el-table-column>
<el-table-column label="开始时间" align="center" prop="startDate" width="160">
<template slot-scope="scope">{{ parseTime(scope.row.startDate, '{y}-{m}-{d} {h}:{i}:{s}') }}</template>
</el-table-column>
<el-table-column label="完成时间" align="center" prop="finishDate" width="160">
<template slot-scope="scope">{{ parseTime(scope.row.finishDate, '{y}-{m}-{d} {h}:{i}:{s}') }}</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
</div>
</template>
<script>
import { listStockout } from "@/api/out/stockout"
export default {
name: "SearchOutStockoutHistory",
dicts: ['out_stockout_out_type', 'out_stockout_status'],
data() {
return {
loading: true,
showSearch: true,
total: 0,
stockoutList: [],
daterangeFinishDate: [],
queryParams: {
pageNum: 1,
pageSize: 10,
outType: null,
status: null,
erpBill: null,
warehouseName: null,
customerName: null
}
}
},
created() {
this.getList()
},
methods: {
buildQuery() {
const query = { ...this.queryParams, params: { history: 'true' } }
if (this.daterangeFinishDate && this.daterangeFinishDate.length === 2) {
query.params.beginFinishDate = this.daterangeFinishDate[0]
query.params.endFinishDate = this.daterangeFinishDate[1]
}
return query
},
getList() {
this.loading = true
listStockout(this.buildQuery()).then(response => {
this.stockoutList = response.rows || []
this.total = response.total || 0
this.loading = false
}).catch(() => {
this.loading = false
})
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.daterangeFinishDate = []
this.resetForm("queryForm")
this.handleQuery()
},
handleExport() {
this.download('out/stockout/export', this.buildQuery(), `out_stockout_history_${new Date().getTime()}.xlsx`)
}
}
}
</script>

View File

@@ -0,0 +1,139 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="货位ID" prop="cargoSpaceId">
<el-input v-model="queryParams.cargoSpaceId" placeholder="请输入货位ID" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="托盘条码" prop="trayCode">
<el-input v-model="queryParams.trayCode" placeholder="请输入托盘条码" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="仓库名称" prop="warehouseName">
<el-input v-model="queryParams.warehouseName" placeholder="请输入仓库名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="库存状态" prop="stockStatus">
<el-select v-model="queryParams.stockStatus" placeholder="请选择库存状态" clearable>
<el-option v-for="dict in dict.type.bas_stock_stock_status" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="物料状态" prop="productStatus">
<el-select v-model="queryParams.productStatus" placeholder="请选择物料状态" clearable>
<el-option v-for="dict in dict.type.bas_stock_product_status" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="批次号" prop="lotNo">
<el-input v-model="queryParams.lotNo" placeholder="请输入批次号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="物料编码" prop="productId">
<el-input v-model="queryParams.productId" placeholder="请输入物料编码" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="物料名称" prop="productName">
<el-input v-model="queryParams.productName" placeholder="请输入物料名称" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="入库时间">
<el-date-picker v-model="daterangeInDate" style="width: 240px" value-format="yyyy-MM-dd" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['bas:stock:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="detailList">
<el-table-column label="明细ID" align="center" prop="detailId" width="160" show-overflow-tooltip />
<el-table-column label="库存ID" align="center" prop="stockId" width="160" show-overflow-tooltip />
<el-table-column label="仓库名称" align="center" prop="warehouseName" width="120" show-overflow-tooltip />
<el-table-column label="货位ID" align="center" prop="cargoSpaceId" width="120" />
<el-table-column label="货位名称" align="center" prop="cargoSpaceName" width="120" show-overflow-tooltip />
<el-table-column label="托盘条码" align="center" prop="trayCode" width="140" show-overflow-tooltip />
<el-table-column label="库存状态" align="center" prop="stockStatus" width="100">
<template slot-scope="scope"><dict-tag :options="dict.type.bas_stock_stock_status" :value="scope.row.stockStatus" /></template>
</el-table-column>
<el-table-column label="物料状态" align="center" prop="productStatus" width="100">
<template slot-scope="scope"><dict-tag :options="dict.type.bas_stock_product_status" :value="scope.row.productStatus" /></template>
</el-table-column>
<el-table-column label="批次号" align="center" prop="lotNo" width="120" show-overflow-tooltip />
<el-table-column label="物料编码" align="center" prop="productId" width="130" show-overflow-tooltip />
<el-table-column label="物料名称" align="center" prop="productName" min-width="160" show-overflow-tooltip />
<el-table-column label="库存数量" align="center" prop="quantity" width="90" />
<el-table-column label="出库数量" align="center" prop="outQuantity" width="90" />
<el-table-column label="可用库存" align="center" prop="availableQuantity" width="90" />
<el-table-column label="入库时间" align="center" prop="inDate" width="120">
<template slot-scope="scope">{{ parseTime(scope.row.inDate, '{y}-{m}-{d}') }}</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
</div>
</template>
<script>
import { listStockDetail } from "@/api/bas/stock"
export default {
name: "SearchStockDetail",
dicts: ['bas_stock_stock_status', 'bas_stock_product_status'],
data() {
return {
loading: true,
showSearch: true,
total: 0,
detailList: [],
daterangeInDate: [],
queryParams: {
pageNum: 1,
pageSize: 10,
cargoSpaceId: null,
trayCode: null,
warehouseName: null,
stockStatus: null,
productStatus: null,
lotNo: null,
productId: null,
productName: null
}
}
},
created() {
this.getList()
},
methods: {
buildQuery() {
const query = { ...this.queryParams, params: {} }
if (this.daterangeInDate && this.daterangeInDate.length === 2) {
query.params.beginInDate = this.daterangeInDate[0]
query.params.endInDate = this.daterangeInDate[1]
}
return query
},
getList() {
this.loading = true
listStockDetail(this.buildQuery()).then(response => {
this.detailList = response.rows || []
this.total = response.total || 0
this.loading = false
}).catch(() => {
this.loading = false
})
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.daterangeInDate = []
this.resetForm("queryForm")
this.handleQuery()
},
handleExport() {
this.download('bas/stock/detail/export', this.buildQuery(), `stock_detail_${new Date().getTime()}.xlsx`)
}
}
}
</script>

View File

@@ -618,8 +618,11 @@ $lighterBlue: #409EFF;
font-size: 17px;
white-space: nowrap;
> img{
width: 30px;
width: 110px;
height: 30px;
padding: 4px 6px;
box-sizing: border-box;
object-fit: contain;
vertical-align: top;
}
.github{