将半成品成品同样进行新增所属公司以及内容修改
This commit is contained in:
@ -2,16 +2,28 @@
|
||||
<div class="product-module">
|
||||
<div class="header-tools">
|
||||
<div class="left-tools">
|
||||
<el-select
|
||||
v-model="queryParams.company"
|
||||
placeholder="所属公司"
|
||||
class="filter-item-select"
|
||||
clearable
|
||||
filterable
|
||||
@change="fetchData"
|
||||
style="width: 160px;"
|
||||
>
|
||||
<el-option v-for="item in companyOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
|
||||
<el-input
|
||||
v-model="queryParams.keyword"
|
||||
placeholder="🔍 搜索物料 / SN / 工单 / 订单号..."
|
||||
class="search-input"
|
||||
placeholder="🔍 搜索物料 / SN / 工单..."
|
||||
class="filter-item-input"
|
||||
clearable
|
||||
@clear="fetchData"
|
||||
@keyup.enter="fetchData"
|
||||
style="width: 300px; margin-right: 10px;"
|
||||
style="width: 260px;"
|
||||
>
|
||||
<template #append><el-button :icon="Search" @click="fetchData" /></template>
|
||||
<template #prefix><el-icon><Search /></el-icon></template>
|
||||
</el-input>
|
||||
|
||||
<el-select
|
||||
@ -66,6 +78,11 @@
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #default="scope" v-else-if="col.prop === 'company_name'">
|
||||
<el-tag v-if="scope.row.company_name" type="info" effect="plain" size="small" style="font-weight: bold;">{{ scope.row.company_name }}</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
<template #default="scope" v-else-if="['serial_number'].includes(col.prop)">
|
||||
<div v-if="scope.row.serial_number" class="id-cell">
|
||||
<span class="prefix-tag sn">SN</span>
|
||||
@ -133,21 +150,27 @@
|
||||
|
||||
<el-pagination class="pagination-bar" v-model:current-page="queryParams.page" v-model:page-size="queryParams.pageSize" :total="total" layout="total, sizes, prev, pager, next" background @change="fetchData" />
|
||||
|
||||
<el-dialog v-model="visible" :title="dialogStatus === 'create' ? '成品入库' : '编辑成品'" width="1100px" top="5vh" :close-on-click-modal="false" class="stylish-dialog">
|
||||
<el-dialog v-model="visible" :title="dialogStatus === 'create' ? '成品入库' : '编辑成品'" width="1100px" top="5vh" :close-on-click-modal="false" class="stylish-dialog compact-layout">
|
||||
<div class="dialog-scroll-container">
|
||||
<el-form :model="form" label-width="110px" ref="formRef" :rules="rules" size="default" class="stylish-form">
|
||||
|
||||
<div class="form-card basic-card">
|
||||
<div class="card-title"><el-icon class="icon"><Box /></el-icon><span>1. 基础信息</span></div>
|
||||
<div class="card-title">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<el-icon class="icon"><Box /></el-icon>
|
||||
<span>1. 基础信息</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<el-row :gutter="24" v-if="dialogStatus === 'create'" style="margin-bottom: 20px;">
|
||||
<el-col :span="10">
|
||||
<el-form-item label="物料搜索" prop="base_id">
|
||||
<el-form-item label="物料搜索" prop="base_id" class="highlight-label">
|
||||
<el-select
|
||||
v-model="form.base_id"
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
clearable
|
||||
placeholder="搜名称/规格..."
|
||||
:remote-method="handleSearchMaterial"
|
||||
@visible-change="handleMaterialDropdownVisible"
|
||||
@ -155,15 +178,28 @@
|
||||
style="width: 100%"
|
||||
@change="onMaterialSelected"
|
||||
default-first-option
|
||||
v-loadmore="loadMoreMaterials"
|
||||
popper-class="long-dropdown"
|
||||
>
|
||||
<template #prefix><el-icon><Search /></el-icon></template>
|
||||
<el-option v-for="item in materialOptions" :key="item.id" :label="item.name" :value="item.id">
|
||||
<div class="option-item">
|
||||
<span class="opt-name">{{ item.name }}</span>
|
||||
<span class="opt-spec">{{ item.spec }}</span>
|
||||
<el-tag v-if="item.isHistory" size="small" type="info" effect="plain">历史</el-tag>
|
||||
<el-tag v-else size="small" type="success" effect="plain">系统</el-tag>
|
||||
<div class="opt-main">
|
||||
<span class="opt-name" :title="item.name">{{ item.name }}</span>
|
||||
</div>
|
||||
<div class="opt-meta">
|
||||
<span class="opt-spec" :title="item.spec">{{ item.spec || '-' }}</span>
|
||||
</div>
|
||||
<div class="opt-tags">
|
||||
<el-tag size="small" type="info" effect="light" class="company-tag">{{ item.company_name }}</el-tag>
|
||||
<el-tag v-if="item.isHistory" size="small" type="info" effect="plain">历史</el-tag>
|
||||
<el-tag v-else size="small" type="success" effect="plain">系统</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</el-option>
|
||||
<div v-if="loadingMore" style="text-align: center; color: #999; font-size: 12px; padding: 8px; background: #f9f9f9;">
|
||||
<el-icon class="is-loading"><Refresh /></el-icon> 加载更多中...
|
||||
</div>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@ -175,11 +211,12 @@
|
||||
</el-row>
|
||||
<div class="read-only-grid">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8"><el-form-item label="名称"><el-input v-model="form.material_name" disabled class="is-text-view" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="规格"><el-input v-model="form.spec_model" disabled class="is-text-view" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="单位"><el-input v-model="form.unit" disabled class="is-text-view" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="类型"><el-input v-model="form.material_type" disabled class="is-text-view" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="类别"><el-input v-model="form.category" disabled class="is-text-view" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="所属公司"><el-input v-model="form.company_name" readonly class="is-text-view" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="名称"><el-input v-model="form.material_name" readonly class="is-text-view" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="规格"><el-input v-model="form.spec_model" readonly class="is-text-view" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="单位"><el-input v-model="form.unit" readonly class="is-text-view" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="类型"><el-input v-model="form.material_type" readonly class="is-text-view" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="类别"><el-input v-model="form.category" readonly class="is-text-view" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
@ -190,8 +227,8 @@
|
||||
<div class="card-content">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="6"><el-form-item label="SKU" prop="sku"><el-input v-model="form.sku" placeholder="自动生成" disabled /></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="条码" prop="barcode"><el-input v-model="form.barcode" placeholder="自动生成" /></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="库位" prop="warehouse_location"><el-input v-model="form.warehouse_location" /></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="条码" prop="barcode"><el-input v-model="form.barcode" placeholder="自动生成" clearable /></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="库位" prop="warehouse_location"><el-input v-model="form.warehouse_location" placeholder="例如: B-01-01" clearable /></el-form-item></el-col>
|
||||
<el-col :span="6"><el-form-item label="入库日期"><el-date-picker v-model="form.in_date" type="date" value-format="YYYY-MM-DD" style="width:100%" disabled /></el-form-item></el-col>
|
||||
</el-row>
|
||||
|
||||
@ -389,22 +426,53 @@ import {
|
||||
updateProductInbound,
|
||||
deleteProductInbound,
|
||||
searchMaterialBase,
|
||||
searchBom // [新增]
|
||||
searchBom,
|
||||
getFilterOptions // [新增]
|
||||
} from '@/api/inbound/product'
|
||||
import { uploadFile, deleteFile } from '@/api/inbound/buy'
|
||||
import WebRtcCamera from '@/components/Camera/WebRtcCamera.vue'
|
||||
import { getLabelPreview, executePrint } from '@/api/common/print'
|
||||
|
||||
// ------------------------------------
|
||||
// v-loadmore
|
||||
// ------------------------------------
|
||||
const vLoadmore = {
|
||||
mounted(el: any, binding: any) {
|
||||
const checkAndBind = () => {
|
||||
const dropDownWrap = document.querySelector('.long-dropdown .el-select-dropdown__wrap')
|
||||
if (dropDownWrap && !dropDownWrap.getAttribute('data-loadmore-bound')) {
|
||||
dropDownWrap.setAttribute('data-loadmore-bound', 'true')
|
||||
dropDownWrap.addEventListener('scroll', function (this: any) {
|
||||
const condition = this.scrollHeight - this.scrollTop <= this.clientHeight + 1
|
||||
if (condition) {
|
||||
binding.value()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
setTimeout(checkAndBind, 500)
|
||||
el.addEventListener('click', () => setTimeout(checkAndBind, 300))
|
||||
}
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const submitting = ref(false)
|
||||
const visible = ref(false)
|
||||
const searchLoading = ref(false)
|
||||
const loadingMore = ref(false)
|
||||
const dialogStatus = ref<'create' | 'update'>('create')
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const formRef = ref()
|
||||
const queryParams = reactive({ page: 1, pageSize: 100, keyword: '', statuses: ['在库', '借库'] })
|
||||
const queryParams = reactive({ page: 1, pageSize: 100, keyword: '', statuses: ['在库', '借库'], company: '' })
|
||||
const categoryOptions = ref<string[]>([])
|
||||
const typeOptions = ref<string[]>([])
|
||||
const companyOptions = ref<string[]>([]) // [新增]
|
||||
const materialOptions = ref<any[]>([])
|
||||
const searchPage = ref(1)
|
||||
const searchKeyword = ref('')
|
||||
const hasNextPage = ref(true)
|
||||
let searchTimer: any = null
|
||||
|
||||
// BOM 搜索相关
|
||||
const bomSearchLoading = ref(false)
|
||||
@ -432,6 +500,7 @@ const inspection_url = ref('')
|
||||
|
||||
// [核心优化] 所有列定义
|
||||
const allColumns = [
|
||||
{ prop: 'company_name', label: '所属公司', minWidth: '100' }, // [新增]
|
||||
{ prop: 'material_name', label: '名称', minWidth: '140' },
|
||||
{ prop: 'sku', label: 'SKU', minWidth: '110' },
|
||||
{ prop: 'serial_number', label: '序列号', minWidth: '130' },
|
||||
@ -454,11 +523,13 @@ const allColumns = [
|
||||
{ prop: 'detail_link', label: '详情', minWidth: '100' }
|
||||
]
|
||||
|
||||
const defaultVisibleCols = ['material_name', 'sku', 'serial_number', 'qty_stock', 'status', 'quality_status', 'product_photo', 'sale_price', 'order_id']
|
||||
const defaultVisibleCols = ['company_name', 'material_name', 'sku', 'serial_number', 'qty_stock', 'status', 'quality_status', 'product_photo', 'sale_price', 'order_id']
|
||||
const visibleColumnProps = ref(defaultVisibleCols)
|
||||
|
||||
const form = reactive({
|
||||
id: undefined, base_id: undefined, material_name: '', spec_model: '', material_type: '', category: '', unit: '',
|
||||
id: undefined, base_id: undefined,
|
||||
company_name: '', // [新增]
|
||||
material_name: '', spec_model: '', material_type: '', category: '', unit: '',
|
||||
sku: '', barcode: '', serial_number: '', in_date: '',
|
||||
in_quantity: 1, stock_quantity: 1, available_quantity: 1,
|
||||
warehouse_location: '', status: '在库', quality_status: '合格',
|
||||
@ -513,18 +584,53 @@ const rules = {
|
||||
// ------------------------------------
|
||||
// Material Search & Population Logic
|
||||
// ------------------------------------
|
||||
const handleMaterialDropdownVisible = (visible: boolean) => { if (visible && materialOptions.value.length === 0) handleSearchMaterial('') }
|
||||
const handleMaterialDropdownVisible = (visible: boolean) => { if (visible && materialOptions.value.length === 0) handleSearchMaterialDebounced('') }
|
||||
|
||||
const handleSearchMaterialDebounced = (query: string) => {
|
||||
if (searchTimer) clearTimeout(searchTimer)
|
||||
searchTimer = setTimeout(() => {
|
||||
handleSearchMaterial(query)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
const handleSearchMaterial = async (query: string) => {
|
||||
searchLoading.value = true
|
||||
searchKeyword.value = query
|
||||
searchPage.value = 1
|
||||
materialOptions.value = []
|
||||
|
||||
try {
|
||||
const res: any = await searchMaterialBase(query)
|
||||
const res: any = await searchMaterialBase(query, 1)
|
||||
const apiResults = (res.data || []).map((i: any) => ({ ...i, isHistory: false }))
|
||||
materialOptions.value = apiResults
|
||||
hasNextPage.value = res.has_next
|
||||
} finally { searchLoading.value = false }
|
||||
}
|
||||
|
||||
const loadMoreMaterials = async () => {
|
||||
if (searchLoading.value || loadingMore.value || !hasNextPage.value) return
|
||||
loadingMore.value = true
|
||||
searchPage.value += 1
|
||||
try {
|
||||
const res: any = await searchMaterialBase(searchKeyword.value, searchPage.value)
|
||||
if (res.data && res.data.length > 0) {
|
||||
const newItems = res.data.map((i: any) => ({...i, isHistory: false}))
|
||||
materialOptions.value.push(...newItems)
|
||||
hasNextPage.value = res.has_next
|
||||
} else {
|
||||
hasNextPage.value = false
|
||||
}
|
||||
} catch (e) {
|
||||
searchPage.value -= 1
|
||||
} finally {
|
||||
loadingMore.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const onMaterialSelected = (val: number) => {
|
||||
const item = materialOptions.value.find(i => i.id === val)
|
||||
if (item) {
|
||||
form.company_name = item.company_name // [新增]
|
||||
form.material_name = item.name
|
||||
form.spec_model = item.spec
|
||||
form.material_type = item.type
|
||||
@ -552,6 +658,28 @@ const fetchData = async () => {
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
const fetchOptions = async () => {
|
||||
try {
|
||||
const res: any = await getFilterOptions()
|
||||
if (res.code === 200) {
|
||||
categoryOptions.value = res.data.categories
|
||||
typeOptions.value = res.data.types
|
||||
companyOptions.value = res.data.companies // [新增]
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Fetch options failed", e)
|
||||
}
|
||||
}
|
||||
|
||||
const resetQuery = () => {
|
||||
queryParams.keyword = ''
|
||||
queryParams.category = ''
|
||||
queryParams.material_type = ''
|
||||
queryParams.company = ''
|
||||
queryParams.page = 1
|
||||
fetchData()
|
||||
}
|
||||
|
||||
const handleCreate = () => {
|
||||
dialogStatus.value = 'create'
|
||||
resetForm()
|
||||
@ -582,7 +710,7 @@ const handleUpdate = (row: any) => {
|
||||
inspectionFileList.value = iReports.filter(r => !isExternalLink(r)).map(url => ({ name: url.split('/').pop(), url: getImageUrl(url) }))
|
||||
const iLinks = iReports.filter(r => isExternalLink(r))
|
||||
inspection_url.value = iLinks.length > 0 ? iLinks[0] : ''
|
||||
materialOptions.value = [{ id: row.base_id, name: row.material_name, spec: row.spec_model, category: row.category, isHistory: false }]
|
||||
materialOptions.value = [{ id: row.base_id, name: row.material_name, spec: row.spec_model, category: row.category, company_name: row.company_name, isHistory: false }]
|
||||
// 回显BOM
|
||||
if (form.bom_code) {
|
||||
bomOptions.value = [{ bom_no: form.bom_code, version: form.bom_version }]
|
||||
@ -705,7 +833,10 @@ const resetForm = () => {
|
||||
const getStatusType = (s:string) => ({'在库':'success','出库':'info','借库':'warning','损耗':'danger'}[s]||'warning')
|
||||
const getQualityType = (s:string) => ({'合格':'success','不合格':'danger','待检':'info'}[s]||'info')
|
||||
const formatMoney = (val:any) => isNaN(Number(val)) ? '-' : `¥ ${Number(val).toFixed(2)}`
|
||||
onMounted(() => fetchData())
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
fetchOptions()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@ -747,4 +878,31 @@ onMounted(() => fetchData())
|
||||
.camera-card:hover { border-color: #409EFF; color: #409EFF; }
|
||||
.camera-card .text { font-size: 12px; margin-top: 5px; }
|
||||
.camera-card .el-icon { font-size: 24px; }
|
||||
|
||||
/* [重点] 下拉框 Flex 布局 */
|
||||
.option-item { display: flex; align-items: center; padding: 8px 0; width: 100%; }
|
||||
.opt-main { flex: 1; min-width: 0; margin-right: 10px; }
|
||||
.opt-name { font-weight: 600; font-size: 14px; color: #333; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.opt-meta { width: 100px; text-align: right; flex-shrink: 0; margin-right: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.opt-spec { color: #999; font-size: 12px; }
|
||||
.opt-tags { display: flex; gap: 5px; flex-shrink: 0; }
|
||||
.company-tag { font-weight: bold; }
|
||||
|
||||
/* [新增] 修复 filter-item-select/input 样式 */
|
||||
.filter-item-select { /* 宽度已在行内样式控制 */ }
|
||||
.filter-item-input { /* 宽度已在行内样式控制 */ }
|
||||
.action-btn { font-weight: 500; }
|
||||
|
||||
/* [新增] 修复弹窗最小高度 */
|
||||
.dialog-scroll-container { min-height: 450px; }
|
||||
|
||||
/* [新增] 纯文本样式 */
|
||||
.is-text-view :deep(.el-input__wrapper) { box-shadow: none !important; background-color: transparent !important; border-bottom: 1px dashed #dcdfe6; border-radius: 0; padding-left: 0; }
|
||||
.is-text-view :deep(.el-input__inner) { color: #303133; font-weight: 600; font-size: 14px; cursor: text; }
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.long-dropdown { width: 580px !important; }
|
||||
.long-dropdown .el-select-dropdown__wrap { max-height: 320px !important; }
|
||||
.long-dropdown .el-input__suffix { z-index: 10; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user