Commit 7103082c by yubin

inventory

parent c5a04317
import request from '@/utils/request'
// 查询库存列表
export function listInventory(query) {
return request({
url: '/inventory/inventory/list',
method: 'get',
params: query
})
}
// 查询库存详细
export function getInventory(id) {
return request({
url: '/inventory/inventory/' + id,
method: 'get'
})
}
// 新增库存
export function addInventory(data) {
return request({
url: '/inventory/inventory',
method: 'post',
data: data
})
}
// 修改库存
export function updateInventory(data) {
return request({
url: '/inventory/inventory',
method: 'put',
data: data
})
}
// 删除库存
export function delInventory(id) {
return request({
url: '/inventory/inventory/' + id,
method: 'delete'
})
}
// 批量删除库存
export function delInventoryByIds(ids) {
return request({
url: '/inventory/inventory/' + ids,
method: 'delete'
})
}
// 根据物料ID查询库存
export function listInventoryByMaterialId(materialId) {
return request({
url: '/inventory/inventory/listByMaterialId',
method: 'get',
params: { materialId: materialId }
})
}
<template>
<div class="material-selector-container" style="overflow: hidden;">
<splitpanes class="default-theme">
<pane size="16" style="overflow: auto;">
<TreeComponent
ref="treeComponent"
:tree-data="categoryTreeData"
:tree-props="treeProps"
:node-key="nodeKey"
:show-search="true"
search-placeholder="请输入分类名称"
:default-expand-all="true"
:highlight-current="true"
:loading="loadingTree"
@node-click="handleTreeClick"
>
<template #node-content="{ node, data }">
<span class="custom-tree-node">{{ node.label }} ({{ data.categoryCode || '-' }})</span>
</template>
</TreeComponent>
</pane>
<pane size="84" style="overflow: auto;">
<div style="padding: 10px; display: flex; flex-direction: column;">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="88px">
<el-form-item label="物料编码" prop="materialCode">
<el-input
v-model="queryParams.materialCode"
placeholder="请输入物料编码"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="物料名称" prop="materialName">
<el-input
v-model="queryParams.materialName"
placeholder="请输入物料名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="SAP物料号" prop="sapNo">
<el-input
v-model="queryParams.sapNo"
placeholder="请输入SAP物料号"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="物料分类" prop="categoryNameInput">
<el-input
v-model="queryParams.categoryNameInput"
placeholder="请输入物料分类"
clearable
@keyup.enter.native="handleQuery"
/>
</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-table
ref="table"
v-loading="loading"
:data="materialsList"
@selection-change="handleSelectionChange"
:scroll-x="true"
:row-key="row => row.materialCode"
@row-click="handleRowClick"
:select-on-indeterminate="false"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" align="center"/>
<el-table-column label="物料编码" align="center" prop="materialCode" width="120"/>
<el-table-column label="物料名称" align="center" prop="materialName" width="150"/>
<el-table-column label="SAP物料号" align="center" prop="sapNo" />
<el-table-column label="TS Code" align="center" prop="tsCode" />
<el-table-column label="物料分类编码(原始)" align="center" prop="categoryCode" width="150" />
<el-table-column label="物料分类编码(格式化)" align="center" width="180">
<template slot-scope="scope">
{{ categoryCodeFormatMap[scope.row.categoryCode] || scope.row.categoryCode || '-' }}
</template>
</el-table-column>
<el-table-column label="物料分类名称" align="center" width="150">
<template slot-scope="scope">
{{ scope.row.displayCategory || categoryMap[scope.row.categoryCode] || '未匹配' }}
</template>
</el-table-column>
<el-table-column label="规格型号" align="center" prop="specification" />
<el-table-column label="计量单位" align="center" prop="materialUnit" />
<el-table-column label="是否批次管理" align="center" prop="isBatchManaged">
<template slot-scope="scope">
<el-tag :type="scope.row.isBatchManaged === 1 ? 'success' : 'info'" size="mini">
{{ scope.row.isBatchManaged === 1 ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</div>
</pane>
</splitpanes>
</div>
</template>
<script>
import { listMaterials } from "@/api/inventory/materials"
import { listMaterials_category } from "@/api/inventory/materials_category"
import TreeComponent from '@/views/inventory/materials_category/treeComponent.vue'
import { Splitpanes, Pane } from 'splitpanes'
import 'splitpanes/dist/splitpanes.css'
export default {
name: "MaterialSelector",
components: { TreeComponent, Splitpanes, Pane },
props: {
selectedMaterialCodes: {
type: Array,
default: () => []
},
defaultCategoryCodes: {
type: Array,
default: () => []
},
multiple: {
type: Boolean,
default: true
}
},
data() {
return {
categoryTreeData: [],
treeProps: { children: 'children', label: 'label', value: 'sid' },
nodeKey: 'sid',
loadingTree: false,
categoryMap: {},
categoryNameToCodeMap: {},
categoryCodeToSidMap: {},
categoryCodeFormatMap: {},
currentNodeId: null,
queryParams: {
pageNum: 1,
pageSize: 10,
materialCode: null,
materialName: null,
sapNo: null,
tsCode: null,
categoryCode: null,
categoryNameInput: null,
specification: null,
},
materialsList: [],
total: 0,
loading: true,
selectedRows: []
}
},
watch: {
/**
* 监听选中的物料编码变化,强制同步表格选中状态
*/
selectedMaterialCodes: {
immediate: true,
deep: true,
handler(val) {
console.log('选择器接收的选中编码:', val)
if (!val || !val.length || !this.materialsList.length) return
this.$nextTick(() => {
if (this.$refs.table) {
// 先清空所有选中状态
this.$refs.table.clearSelection()
// 统一编码格式后再匹配
const formattedCodes = val.map(code => code.trim().toUpperCase())
// 重新选中指定编码的物料
this.materialsList.forEach(row => {
const rowCode = row.materialCode.trim().toUpperCase()
if (formattedCodes.includes(rowCode)) {
this.$refs.table.toggleRowSelection(row, true)
}
})
}
})
}
},
defaultCategoryCodes: {
immediate: true,
handler(val) {
if (val && val.length && this.categoryTreeData.length) {
this.$nextTick(() => {
this.selectCategoryNodes(val)
})
}
}
}
},
async created() {
await Promise.all([
this.getCategoryTreeData(),
this.getCategoryList()
])
this.getList()
},
methods: {
async getCategoryList() {
try {
const response = await listMaterials_category({ pageNum: 1, pageSize: 1000 })
if (response.rows && response.rows.length > 0) {
this.categoryMap = {}
this.categoryNameToCodeMap = {}
this.categoryCodeFormatMap = {}
response.rows.forEach(item => {
if (item.isUsed !== 0 && item.isUsed !== '0') {
const code = item.categoryCode
this.categoryMap[code] = item.categoryName
// 格式化分类编码
let formattedCode = code
if (code && code.length === 6) {
formattedCode = `${code.substring(0, 4)}-${code.substring(4)}`
} else if (code && code.length === 8) {
formattedCode = `${code.substring(0, 4)}-${code.substring(4, 6)}-${code.substring(6)}`
}
this.categoryCodeFormatMap[code] = formattedCode
if (!this.categoryNameToCodeMap[item.categoryName]) {
this.categoryNameToCodeMap[item.categoryName] = code
} else if (!Array.isArray(this.categoryNameToCodeMap[item.categoryName])) {
this.categoryNameToCodeMap[item.categoryName] = [this.categoryNameToCodeMap[item.categoryName], code]
} else {
this.categoryNameToCodeMap[item.categoryName].push(code)
}
}
})
}
} catch (error) {
console.error('获取分类列表失败:', error)
}
},
async getCategoryTreeData() {
this.loadingTree = true
try {
const response = await listMaterials_category({ pageNum: 1, pageSize: 1000 })
if (response.rows && response.rows.length > 0) {
const activeCategories = response.rows.filter(item => item.isUsed !== 0 && item.isUsed !== '0')
this.categoryTreeData = this.buildTreeData(activeCategories)
this.buildCategoryCodeToSidMap(this.categoryTreeData)
}
} catch (error) {
console.error('获取分类树数据失败:', error)
} finally {
this.loadingTree = false
}
},
buildTreeData(list, parentId = null) {
return list
.filter(item => parentId === null
? (!item.parentId || item.parentId === 0 || item.parentId === '0')
: item.parentId == parentId
)
.map(item => ({
...item,
sid: String(item.id),
label: item.categoryName,
children: this.buildTreeData(list, item.id).length
? this.buildTreeData(list, item.id)
: undefined
}))
},
buildCategoryCodeToSidMap(treeData) {
treeData.forEach(node => {
if (node.categoryCode) {
this.categoryCodeToSidMap[node.categoryCode] = node.sid
const formattedCode = this.categoryCodeFormatMap[node.categoryCode]
if (formattedCode) {
this.categoryCodeToSidMap[formattedCode] = node.sid
}
}
if (node.children && node.children.length) {
this.buildCategoryCodeToSidMap(node.children)
}
})
},
selectCategoryNodes(categoryCodes) {
if (!this.$refs.treeComponent || !this.$refs.treeComponent.$refs.tree) return
const tree = this.$refs.treeComponent.$refs.tree
categoryCodes.forEach(code => {
const rawCode = code.replace(/-/g, '')
const sid = this.categoryCodeToSidMap[rawCode] || this.categoryCodeToSidMap[code]
if (sid) {
tree.setCurrentKey(sid)
this.currentNodeId = sid
const node = tree.getNode(sid)
if (node) {
tree.expandNode(node)
}
}
})
},
handleTreeClick(data) {
this.currentNodeId = data.sid
this.queryParams.categoryCode = data.categoryCode
this.queryParams.categoryNameInput = null
this.queryParams.pageNum = 1
this.getList()
},
getList() {
this.loading = true
listMaterials(this.queryParams).then(response => {
this.materialsList = (response.rows || []).filter(item => item.isUsed !== 0 && item.isUsed !== '0').map(item => ({
...item,
displayCategory: this.categoryMap[item.categoryCode] || `${this.categoryCodeFormatMap[item.categoryCode] || item.categoryCode}(未匹配分类)`
}))
this.total = response.total
console.log('物料选择器加载列表:', this.materialsList)
// 强制同步选中状态
this.$nextTick(() => {
if (this.selectedMaterialCodes.length && this.$refs.table) {
this.$refs.table.clearSelection()
const formattedCodes = this.selectedMaterialCodes.map(code => code.trim().toUpperCase())
this.materialsList.forEach(row => {
const rowCode = row.materialCode.trim().toUpperCase()
if (formattedCodes.includes(rowCode)) {
this.$refs.table.toggleRowSelection(row, true)
}
})
}
})
}).finally(() => {
this.loading = false
})
},
handleQuery() {
const inputName = this.queryParams.categoryNameInput
if (inputName) {
const matchedCode = this.categoryNameToCodeMap[inputName]
if (matchedCode) {
this.queryParams.categoryCode = Array.isArray(matchedCode) ? matchedCode[0] : matchedCode
} else {
const matchedCodes = Object.entries(this.categoryMap)
.filter(([code, name]) => name.includes(inputName))
.map(([code]) => code)
if (matchedCodes.length > 0) {
this.queryParams.categoryCode = matchedCodes[0]
}
}
}
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.queryParams = {
pageNum: 1,
pageSize: 10,
materialCode: null,
materialName: null,
sapNo: null,
tsCode: null,
categoryCode: null,
categoryNameInput: null,
specification: null,
}
this.currentNodeId = null
if (this.$refs.treeComponent && this.$refs.treeComponent.$refs.tree) {
this.$refs.treeComponent.$refs.tree.setCurrentKey(null)
}
this.getList()
},
handleSelectionChange(selection) {
this.selectedRows = selection
const categoryCodes = [...new Set(selection.map(item => item.categoryCode).filter(c => c))]
const formattedCategoryIds = categoryCodes.map(code => this.categoryCodeFormatMap[code] || code)
const selectionData = {
materialCodes: selection.map(item => item.materialCode || ''),
names: selection.map(item => item.materialName || ''),
formattedCategoryIds: formattedCategoryIds
}
console.log('选择器选中数据:', selectionData)
this.$emit('selection-change', selectionData)
this.$emit('change', selectionData.materialCodes)
this.$emit('input', selectionData.materialCodes)
},
handleRowClick(row) {
if (!this.multiple) {
this.$refs.table.clearSelection()
this.$refs.table.toggleRowSelection(row, true)
const categoryCode = row.categoryCode || ''
const formattedCategoryId = this.categoryCodeFormatMap[categoryCode] || categoryCode
const selectionData = {
materialCodes: [row.materialCode || ''],
names: [row.materialName || ''],
formattedCategoryIds: [formattedCategoryId]
}
this.$emit('selection-change', selectionData)
this.$emit('input', selectionData.materialCodes)
this.$emit('select', row.materialCode)
}
},
getSelection() {
const categoryCodes = [...new Set(this.selectedRows.map(item => item.categoryCode).filter(c => c))]
const formattedCategoryIds = categoryCodes.map(code => this.categoryCodeFormatMap[code] || code)
return {
materialCodes: this.selectedRows.map(item => item.materialCode || ''),
names: this.selectedRows.map(item => item.materialName || ''),
formattedCategoryIds: formattedCategoryIds
}
},
getSelectedMaterials() {
return this.selectedRows.map(item => item.materialCode || '')
}
}
}
</script>
<style scoped>
.material-selector-container {
height: 100%;
min-height: 500px;
}
.custom-tree-node {
font-size: 14px;
}
</style>
\ No newline at end of file
<template>
<el-dialog
:title="title"
:visible.sync="open"
width="950px"
append-to-body
@close="handleClose"
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<el-form ref="detailForm" :model="form" label-width="100px" :rules="rules">
<!-- 通用信息:保留货物ID、计划总数量(全局字段) -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="货物ID" prop="materialId">
<el-input
v-model="form.materialId"
placeholder="请选择或输入货物ID"
readonly
@click.native="openMaterialSelector = true"
style="cursor: pointer"
>
<template slot="suffix">
<el-button
icon="el-icon-search"
size="mini"
@click.native="openMaterialSelector = true"
></el-button>
</template>
</el-input>
<!-- 物料选择器弹窗 -->
<el-dialog
title="选择物料"
:visible.sync="openMaterialSelector"
width="90%"
append-to-body
:close-on-click-modal="false"
:close-on-press-escape="false"
:modal="true"
:modal-append-to-body="true"
>
<div style="height: 70vh; overflow: auto; padding: 0 10px;">
<MaterialSelector
ref="materialsSeletor"
@selection-change="handleMaterialSelectionChange"
:selected-material-codes="form.materialUuids ? [form.materialUuids] : []"
:multiple="true"
/>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click.native="openMaterialSelector = false">取消</el-button>
<el-button type="primary" @click.native="confirmMaterialSelect">确认选择</el-button>
</div>
</el-dialog>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="计划总数量" prop="totalPlannedQuantity">
<el-input
v-model.number="form.totalPlannedQuantity"
placeholder="请输入计划总数量"
type="number"
min="1"
/>
</el-form-item>
</el-col>
</el-row>
<!-- 库存信息列表 -->
<el-row v-if="form.materialId && form.materialId.trim()" style="margin: 10px 0;">
<el-col :span="24">
<div style="margin-bottom: 8px; font-weight: 600; color: #1989fa;">
库存信息列表
</div>
<el-table
ref="inventoryTable"
:data="inventoryList"
border
size="small"
max-height="400"
highlight-current-row
stripe
empty-text="暂无库存数据"
@selection-change="handleSelectionChange"
@row-click="handleRowClick"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="id" label="库存ID" width="100" />
<el-table-column prop="materialId" label="货物ID" width="100" />
<el-table-column prop="batchId" label="批次ID" width="100" />
<el-table-column prop="orderId" label="订单ID" width="120" />
<el-table-column prop="warehouseId" label="仓库ID" width="100" />
<el-table-column prop="locationId" label="库位ID" width="100" />
<el-table-column
prop="inventoryType"
label="库存类型"
width="100"
>
<template slot-scope="scope">
<el-tag size="mini" :type="scope.row.inventoryType === 1 ? 'primary' : 'success'">
{{ scope.row.inventoryType === 1 ? '正品' : '次品' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="可用数量" width="100">
<template slot-scope="scope">
{{ (scope.row.quantity || 0) - (scope.row.lockedQuantity || 0) }}
</template>
</el-table-column>
<el-table-column label="选择数量" width="120">
<template slot-scope="scope">
<el-input
v-model.number="scope.row.selectedQty"
type="number"
size="mini"
min="1"
:max="(scope.row.quantity || 0) - (scope.row.lockedQuantity || 0)"
@input="handleRowQtyInput(scope.row)"
placeholder="输入数量"
/>
</template>
</el-table-column>
<el-table-column label="计划件数" width="120">
<template slot-scope="scope">
<el-input
v-model.number="scope.row.plannedPackages"
type="number"
size="mini"
min="0"
@input="handleRowPackagesInput(scope.row)"
placeholder="输入件数"
/>
</template>
</el-table-column>
</el-table>
<!-- 选中行的扩展字段填写区域 -->
<div
v-if="currentSelectedRow"
style="margin-top: 10px; padding: 10px; border: 1px solid #e6e6e6; border-radius: 4px;"
>
<div style="margin-bottom: 8px; font-weight: 600; color: #1989fa;">
库存【ID: {{ currentSelectedRow.id }}】明细信息
</div>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="约数" prop="divisor">
<el-input
v-model.number="currentSelectedRow.divisor"
placeholder="请输入约数"
type="number"
min="0"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="标签颜色" prop="labelColor">
<el-select v-model="currentSelectedRow.labelColor" placeholder="请选择标签颜色">
<el-option label="红色" value="1"></el-option>
<el-option label="蓝色" value="2"></el-option>
<el-option label="绿色" value="3"></el-option>
<el-option label="黄色" value="4"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="单价" prop="unitPrice">
<el-input
v-model.number="currentSelectedRow.unitPrice"
placeholder="请输入单价"
type="number"
min="0"
step="0.01"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 10px;">
<el-col :span="12">
<el-form-item label="收货时间" prop="receivedAt">
<el-date-picker
v-model="currentSelectedRow.receivedAt"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择收货时间"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="收货人" prop="receivedBy">
<el-input
v-model="currentSelectedRow.receivedBy"
placeholder="请输入收货人"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 10px;">
<el-col :span="24">
<el-form-item label="凭证号" prop="voucherNumber">
<el-input
v-model="currentSelectedRow.voucherNumber"
placeholder="请输入凭证号"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" style="margin-top: 10px;">
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input
v-model="currentSelectedRow.remark"
placeholder="请输入备注"
type="textarea"
rows="2"
/>
</el-form-item>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
<!-- 已生成的明细预览 -->
<el-row v-if="details.length > 0" style="margin: 10px 0;">
<el-col :span="24">
<div style="margin-bottom: 8px; font-weight: 600; color: #1989fa;">
已生成明细项
</div>
<el-table
:data="details"
border
size="small"
max-height="200"
>
<el-table-column prop="batchId" label="批次ID" />
<el-table-column prop="warehouseId" label="仓库ID" />
<el-table-column prop="locationId" label="库位ID" />
<el-table-column prop="plannedQuantity" label="计划数量" />
<el-table-column prop="actualQuantity" label="实际数量" />
<el-table-column prop="plannedPackages" label="计划件数" />
<el-table-column prop="actualPackages" label="实际件数" />
<el-table-column prop="divisor" label="约数" />
<el-table-column prop="unitPrice" label="单价" />
<el-table-column prop="labelColor" label="标签颜色">
<template slot-scope="scope">
{{ getLabelColorText(scope.row.labelColor) }}
</template>
</el-table-column>
<el-table-column prop="receivedAt" label="收货时间" />
<el-table-column prop="receivedBy" label="收货人" />
<el-table-column prop="voucherNumber" label="凭证号" />
<el-table-column prop="remark" label="备注" />
<el-table-column label="操作" width="80">
<template slot-scope="scope">
<el-button
type="text"
size="mini"
@click.native="removeDetail(scope.row)"
>删除</el-button>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click.native="handleClose">取消</el-button>
<el-button type="primary" @click.native="handleSubmit">确定</el-button>
</div>
</el-dialog>
</template>
<script>
import { listInventoryByMaterialId } from "@/api/inventory/inventory"
import MaterialSelector from '../../../components/materialsSeletor.vue'
// 防抖函数(适配Vue2 this上下文)
function debounce(fn, delay = 500) {
let timer = null
return function(...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
export default {
name: 'OutboundOrderFormWithItems',
components: {
MaterialSelector
},
props: {
title: {
type: String,
default: '新增明细项'
},
open: {
type: Boolean,
default: false
},
initForm: {
type: Object,
default: () => ({})
}
},
data() {
return {
// 仅保留全局字段:货物ID、计划总数量
form: {
materialUuids: '',
materialId: '',
totalPlannedQuantity: null,
itemStatus: '1' // 对应数据库状态:1-待收货
},
details: [],
inventoryList: [],
loadingInstance: null,
rules: {
materialId: [{
required: true,
message: '请选择货物ID',
trigger: ['change', 'blur']
}]
// totalPlannedQuantity: [{
// required: true,
// message: '请输入计划总数量',
// trigger: 'blur',
// type: 'number'
// }, {
// validator: (rule, value, callback) => {
// if (value && value < 1) {
// callback(new Error('计划总数量不能小于1'))
// } else {
// callback()
// }
// },
// trigger: 'blur'
// }]
},
openMaterialSelector: false,
selectedMaterialId: '',
selectedMaterialInfo: null,
currentSelectedRow: null // 存储当前点击的库存行
}
},
created() {
this.debounceMaterialIdChange = debounce(this.handleMaterialIdChange.bind(this), 500)
},
watch: {
open: {
handler(newVal) {
if (newVal) {
this.form = { ...this.$options.data().form, ...this.initForm }
this.selectedMaterialId = this.form.materialId || ''
this.selectedMaterialInfo = null
this.currentSelectedRow = null // 重置选中行
if (this.form.materialId && this.form.materialId.trim()) {
this.handleMaterialIdChange()
}
} else {
this.$nextTick(() => {
this.closeLoading()
if (this.$refs.detailForm) {
this.$refs.detailForm.resetFields()
}
})
}
},
immediate: true
},
initForm: {
handler(val) {
if (this.open) {
this.form = { ...this.$options.data().form, ...val }
this.selectedMaterialId = this.form.materialId || ''
this.selectedMaterialInfo = null
this.currentSelectedRow = null // 重置选中行
if (this.form.materialId && this.form.materialId.trim()) {
this.handleMaterialIdChange()
}
}
},
immediate: true,
deep: true
}
},
methods: {
// 标签颜色值转文本
getLabelColorText(value) {
const colorMap = {
'1': '红色',
'2': '蓝色',
'3': '绿色',
'4': '黄色'
}
return colorMap[value] || '-'
},
closeLoading() {
if (this.loadingInstance) {
this.loadingInstance.close()
this.loadingInstance = null
}
},
async handleMaterialIdChange() {
const materialId = this.form.materialId?.trim() || ''
if (!materialId) {
this.inventoryList = []
this.closeLoading()
return
}
try {
this.loadingInstance = this.$loading({
text: '查询中...',
target: this.$el,
lock: true,
background: 'rgba(0, 0, 0, 0.7)'
})
const res = await listInventoryByMaterialId(materialId)
if (res.code === 200) {
// 为每个库存行初始化扩展字段
this.inventoryList = (res.rows || []).map(item => ({
...item,
selectedQty: null,
plannedPackages: null,
divisor: null, // 约数
labelColor: '', // 标签颜色
unitPrice: null, // 单价
receivedAt: '', // 收货时间
receivedBy: '', // 收货人
voucherNumber: '', // 凭证号
remark: '', // 备注
quantity: item.quantity || 0,
lockedQuantity: item.lockedQuantity || 0
}))
if (this.inventoryList.length === 0) {
this.$message.warning('未查询到该物料的库存信息')
}
} else {
this.inventoryList = []
this.$message.error(res.msg || '查询库存失败')
}
} catch (error) {
this.$message.error('查询库存失败:' + (error.message || '网络异常'))
this.inventoryList = []
} finally {
this.closeLoading()
}
},
// 点击库存行触发 - 显示扩展字段填写区域
handleRowClick(row) {
this.currentSelectedRow = row
// 自动选中当前行
this.$refs.inventoryTable.toggleRowSelection(row, true)
},
handleRowQtyInput(row) {
if (isNaN(row.selectedQty) || row.selectedQty === '') {
row.selectedQty = null
return
}
const availableQty = (row.quantity || 0) - (row.lockedQuantity || 0)
if (row.selectedQty > availableQty) {
this.$message.warning(`选择数量不能超过可用数量${availableQty}`)
row.selectedQty = availableQty
} else if (row.selectedQty < 1) {
this.$message.warning('选择数量不能小于1')
row.selectedQty = 1
}
},
handleRowPackagesInput(row) {
if (isNaN(row.plannedPackages) || row.plannedPackages === '') {
row.plannedPackages = null
return
}
if (row.plannedPackages < 0) {
this.$message.warning('计划件数不能小于0')
row.plannedPackages = 0
}
},
handleSelectionChange(rows) {
// 过滤已取消选择的行
this.details = this.details.filter(detail =>
rows.some(row => row.id === detail.inventoryId)
)
// 处理选中行,携带行级扩展字段
rows.forEach(row => {
// 选择数量为空则跳过
if (row.selectedQty === null || row.selectedQty === '') return
const availableQty = (row.quantity || 0) - (row.lockedQuantity || 0)
if (row.selectedQty > availableQty || row.selectedQty < 1) {
this.$message.warning(`行${row.id}的选择数量不合法,请重新输入`)
row.selectedQty = Math.min(Math.max(row.selectedQty, 1), availableQty)
}
// 校验行级必填字段(可根据实际需求调整)
if (!row.divisor && row.divisor !== 0) {
this.$message.warning(`库存ID:${row.id} 请填写约数`)
return
}
if (!row.labelColor) {
this.$message.warning(`库存ID:${row.id} 请选择标签颜色`)
return
}
if (!row.unitPrice && row.unitPrice !== 0) {
this.$message.warning(`库存ID:${row.id} 请填写单价`)
return
}
if (!row.receivedAt) {
this.$message.warning(`库存ID:${row.id} 请选择收货时间`)
return
}
if (!row.receivedBy) {
this.$message.warning(`库存ID:${row.id} 请填写收货人`)
return
}
const existingIndex = this.details.findIndex(d => d.inventoryId === row.id)
const newDetail = {
inventoryId: row.id,
materialId: this.form.materialId,
batchId: row.batchId || '',
warehouseId: row.warehouseId || '',
locationId: row.locationId || '',
plannedQuantity: row.selectedQty,
actualQuantity: row.selectedQty,
plannedPackages: row.plannedPackages || 0,
actualPackages: row.plannedPackages || 0, // 实际件数默认等于计划件数
divisor: row.divisor, // 行级约数
labelColor: row.labelColor, // 行级标签颜色
unitPrice: row.unitPrice, // 行级单价
itemStatus: this.form.itemStatus || '1',
receivedAt: row.receivedAt, // 行级收货时间
receivedBy: row.receivedBy, // 行级收货人
voucherNumber: row.voucherNumber || '', // 行级凭证号
remark: row.remark || '' // 行级备注
}
if (existingIndex > -1) {
this.details.splice(existingIndex, 1, newDetail)
} else {
this.details.push(newDetail)
}
})
},
removeDetail(row) {
this.details = this.details.filter(d => d.inventoryId !== row.inventoryId)
if (this.$refs.inventoryTable && row.id) {
const inventoryRow = this.inventoryList.find(item => item.id === row.inventoryId)
if (inventoryRow) {
this.$refs.inventoryTable.toggleRowSelection(inventoryRow, false)
// 清空该行的填写内容
inventoryRow.selectedQty = null
inventoryRow.plannedPackages = null
inventoryRow.divisor = null
inventoryRow.labelColor = ''
inventoryRow.unitPrice = null
inventoryRow.receivedAt = ''
inventoryRow.receivedBy = ''
inventoryRow.voucherNumber = ''
inventoryRow.remark = ''
// 如果删除的是当前选中行,重置currentSelectedRow
if (this.currentSelectedRow && this.currentSelectedRow.id === row.inventoryId) {
this.currentSelectedRow = null
}
}
}
},
handleSubmit() {
if (!this.form.materialId?.trim()) {
this.$message.error('请先选择物料ID')
return
}
this.$refs.detailForm.validate(async (valid) => {
if (!valid) {
this.$message.error('表单校验失败,请检查必填项')
return
}
if (this.details.length === 0) {
this.$message.error('请选择库存并填写数量生成明细')
return
}
// 计算实际总数量
const totalActual = this.details.reduce((sum, d) => sum + (d.actualQuantity || 0), 0)
if (this.form.totalPlannedQuantity && this.form.totalPlannedQuantity !== totalActual) {
try {
await this.$confirm(
`实际总数量(${totalActual})与计划总数量(${this.form.totalPlannedQuantity})不一致,是否继续提交?`,
'提示',
{
confirmButtonText: '继续',
cancelButtonText: '取消',
type: 'warning'
}
)
} catch (e) {
return
}
}
this.$emit('submit', this.details)
this.$emit('update:open', false)
})
},
handleClose() {
this.closeLoading()
this.$nextTick(() => {
this.form = this.$options.data().form
this.details = []
this.inventoryList = []
this.selectedMaterialId = ''
this.selectedMaterialInfo = null
this.openMaterialSelector = false
this.currentSelectedRow = null // 重置选中行
if (this.$refs.detailForm) {
this.$refs.detailForm.resetFields()
}
})
this.$emit('update:open', false)
},
handleMaterialSelectionChange() {
const selectedData = this.$refs.materialsSeletor?.getSelectedMaterials?.() || this.$refs.materialsSeletor?.selectedList || []
if (selectedData && selectedData.length > 0) {
this.selectedMaterialInfo = selectedData[0]
this.selectedMaterialId = this.selectedMaterialInfo || ''
} else {
this.selectedMaterialInfo = null
this.selectedMaterialId = ''
}
},
confirmMaterialSelect() {
this.handleMaterialSelectionChange()
if (!this.selectedMaterialId) {
this.$message.warning('请选择物料后再确认')
return
}
this.$set(this.form, 'materialId', this.selectedMaterialId)
this.$set(this.form, 'materialUuids', this.selectedMaterialInfo.materialCode || this.selectedMaterialId)
this.openMaterialSelector = false
this.handleMaterialIdChange()
this.$nextTick(() => {
if (this.$refs.detailForm) {
this.$refs.detailForm.validateField('materialId')
}
})
}
},
beforeDestroy() {
this.closeLoading()
}
}
</script>
<style scoped>
.dialog-footer {
text-align: center;
}
/deep/ .el-table {
--el-table-header-text-color: #303133;
--el-table-row-hover-bg-color: #f8f9fa;
}
/deep/ .el-table th {
background-color: #f8f9fa;
font-weight: 600;
}
/deep/ .el-table .el-tag {
margin: 0;
}
/deep/ .el-table .el-input {
width: 100px;
}
/deep/ .material-selector-container {
height: 100%;
min-height: 500px;
}
/* 修复模态框遮罩层点击穿透问题 */
/deep/ .el-dialog__wrapper {
.el-modal__mask {
pointer-events: auto !important;
}
.el-dialog {
pointer-events: auto !important;
}
}
</style>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论