三个基础入库页面修改新增弹窗内容展示,下拉框以及弹窗屏幕大小自适应性
This commit is contained in:
@ -71,15 +71,37 @@
|
||||
<div class="card-title"><el-icon class="icon"><Box /></el-icon><span>1. 基础信息</span></div>
|
||||
<div class="card-content">
|
||||
<el-row :gutter="24" v-if="dialogStatus === 'create'" style="margin-bottom: 20px;">
|
||||
<el-col :span="14">
|
||||
<el-col :span="10">
|
||||
<el-form-item label="物料搜索" prop="base_id">
|
||||
<el-select v-model="form.base_id" filterable remote reserve-keyword placeholder="搜名称/规格..." :remote-method="handleSearchMaterial" :loading="searchLoading" style="width: 100%" @change="onMaterialSelected">
|
||||
<el-select
|
||||
v-model="form.base_id"
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
placeholder="搜名称/规格..."
|
||||
:remote-method="handleSearchMaterial"
|
||||
@visible-change="handleMaterialDropdownVisible"
|
||||
:loading="searchLoading"
|
||||
style="width: 100%"
|
||||
@change="onMaterialSelected"
|
||||
default-first-option
|
||||
>
|
||||
<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></div>
|
||||
<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>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="14" style="display: flex; align-items: center;">
|
||||
<span class="search-tip">
|
||||
<el-icon><InfoFilled /></el-icon> 未输入时展示最新物料;输入关键词进行精确搜索。
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="read-only-grid">
|
||||
<el-row :gutter="24">
|
||||
@ -140,7 +162,19 @@
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8"><el-form-item label="订单号"><el-input v-model="form.order_id" placeholder="关联销售订单" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="负责人"><el-input v-model="form.production_manager" /></el-form-item></el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="负责人">
|
||||
<el-autocomplete
|
||||
v-model="form.production_manager"
|
||||
:fetch-suggestions="querySearchManager"
|
||||
placeholder="输入或选择负责人"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
:trigger-on-focus="true"
|
||||
@select="handleManagerSelect"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8"><el-form-item label="产品定价"><el-input-number v-model="form.sale_price" :precision="2" style="width:100%"><template #prefix>¥</template></el-input-number></el-form-item></el-col>
|
||||
</el-row>
|
||||
|
||||
@ -172,7 +206,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, watch } from 'vue'
|
||||
import { Plus, Setting, Refresh, Search, Box, House, Link } from '@element-plus/icons-vue'
|
||||
import { Plus, Setting, Refresh, Search, Box, House, Link, InfoFilled } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import dayjs from 'dayjs'
|
||||
// 引用更新后的 product.ts
|
||||
@ -228,6 +262,87 @@ const rules = {
|
||||
in_quantity: [{ required: true, message: '必填', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// 历史记录管理器 (Local Storage)
|
||||
// ------------------------------------
|
||||
const HISTORY_KEYS = {
|
||||
PRODUCTION_MANAGER: 'history_product_managers',
|
||||
MATERIAL: 'history_product_materials'
|
||||
}
|
||||
|
||||
// 保存历史 (String 类型)
|
||||
const saveToHistory = (key: string, value: string) => {
|
||||
if (!value) return
|
||||
try {
|
||||
const existing = localStorage.getItem(key)
|
||||
let list = existing ? JSON.parse(existing) : []
|
||||
list = list.filter((i: string) => i !== value)
|
||||
list.unshift(value)
|
||||
if (list.length > 20) list = list.slice(0, 20)
|
||||
localStorage.setItem(key, JSON.stringify(list))
|
||||
} catch (e) { console.error('save history failed', e) }
|
||||
}
|
||||
|
||||
// 获取历史 (String 类型)
|
||||
const getHistoryList = (key: string): any[] => {
|
||||
try {
|
||||
const existing = localStorage.getItem(key)
|
||||
const list = existing ? JSON.parse(existing) : []
|
||||
return list.map((v: string) => ({ value: v }))
|
||||
} catch (e) { return [] }
|
||||
}
|
||||
|
||||
// 保存物料历史 (Object 类型)
|
||||
const saveMaterialHistory = (item: any) => {
|
||||
if (!item || !item.id) return
|
||||
const key = HISTORY_KEYS.MATERIAL
|
||||
try {
|
||||
const existing = localStorage.getItem(key)
|
||||
let list = existing ? JSON.parse(existing) : []
|
||||
list = list.filter((i: any) => i.id !== item.id)
|
||||
list.unshift({ ...item, isHistory: true })
|
||||
if (list.length > 10) list = list.slice(0, 10)
|
||||
localStorage.setItem(key, JSON.stringify(list))
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const getMaterialHistory = () => {
|
||||
try {
|
||||
const existing = localStorage.getItem(HISTORY_KEYS.MATERIAL)
|
||||
return existing ? JSON.parse(existing) : []
|
||||
} catch (e) { return [] }
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// Autocomplete 建议逻辑 (混合模式)
|
||||
// ------------------------------------
|
||||
const createFilter = (queryString: string) => {
|
||||
return (item: any) => {
|
||||
return (item.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0)
|
||||
}
|
||||
}
|
||||
|
||||
const getTableDataUnique = (field: string) => {
|
||||
const uniqueItems = Array.from(new Set(tableData.value.map((i: any) => i[field]).filter(Boolean)))
|
||||
return uniqueItems.map(i => ({ value: i }))
|
||||
}
|
||||
|
||||
const mixedSearch = (queryString: string, tableField: string, storageKey: string, cb: any) => {
|
||||
const tableList = getTableDataUnique(tableField)
|
||||
const historyList = getHistoryList(storageKey)
|
||||
const map = new Map()
|
||||
historyList.forEach(i => map.set(i.value, i))
|
||||
tableList.forEach(i => map.set(i.value, i))
|
||||
const allList = Array.from(map.values())
|
||||
const results = queryString ? allList.filter(createFilter(queryString)) : allList
|
||||
cb(results)
|
||||
}
|
||||
|
||||
// 1. 负责人
|
||||
const querySearchManager = (qs: string, cb: any) => mixedSearch(qs, 'production_manager', HISTORY_KEYS.PRODUCTION_MANAGER, cb)
|
||||
const handleManagerSelect = (item: any) => saveToHistory(HISTORY_KEYS.PRODUCTION_MANAGER, item.value)
|
||||
|
||||
|
||||
const fetchData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
@ -237,19 +352,37 @@ const fetchData = async () => {
|
||||
} finally { loading.value = false }
|
||||
}
|
||||
|
||||
const handleSearchMaterial = async (query: string) => {
|
||||
if (query) {
|
||||
searchLoading.value = true
|
||||
try {
|
||||
const res: any = await searchMaterialBase(query)
|
||||
materialOptions.value = res.data || []
|
||||
} finally { searchLoading.value = false }
|
||||
// ------------------------------------
|
||||
// 物料搜索逻辑 (优化)
|
||||
// ------------------------------------
|
||||
const handleMaterialDropdownVisible = (visible: boolean) => {
|
||||
if (visible) {
|
||||
if (materialOptions.value.length === 0) {
|
||||
handleSearchMaterial('')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearchMaterial = async (query: string) => {
|
||||
searchLoading.value = true
|
||||
try {
|
||||
const res: any = await searchMaterialBase(query)
|
||||
const apiResults = (res.data || []).map((i: any) => ({ ...i, isHistory: false }))
|
||||
if (!query) {
|
||||
const history = getMaterialHistory()
|
||||
const historyIds = new Set(history.map((h: any) => h.id))
|
||||
const filteredApi = apiResults.filter((apiItem: any) => !historyIds.has(apiItem.id))
|
||||
materialOptions.value = [...history, ...filteredApi]
|
||||
} else {
|
||||
materialOptions.value = apiResults
|
||||
}
|
||||
} finally { searchLoading.value = false }
|
||||
}
|
||||
|
||||
const onMaterialSelected = (val: number) => {
|
||||
const item = materialOptions.value.find(i => i.id === val)
|
||||
if (item) {
|
||||
saveMaterialHistory(item)
|
||||
form.material_name = item.name
|
||||
form.spec_model = item.spec
|
||||
form.material_type = item.type
|
||||
@ -261,6 +394,7 @@ const handleCreate = () => {
|
||||
resetForm()
|
||||
form.in_date = dayjs().format('YYYY-MM-DD')
|
||||
visible.value = true
|
||||
materialOptions.value = []
|
||||
}
|
||||
|
||||
const handleUpdate = (row: any) => {
|
||||
@ -272,6 +406,13 @@ const handleUpdate = (row: any) => {
|
||||
} else {
|
||||
form.production_time_range = []
|
||||
}
|
||||
// 编辑模式下填充当前物料
|
||||
materialOptions.value = [{
|
||||
id: row.base_id,
|
||||
name: row.material_name,
|
||||
spec: row.spec_model,
|
||||
isHistory: false
|
||||
}]
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
@ -286,6 +427,10 @@ const submitForm = async () => {
|
||||
}
|
||||
if(dialogStatus.value === 'create') await createProductInbound(payload)
|
||||
else await updateProductInbound(form.id!, payload)
|
||||
|
||||
// 保存历史
|
||||
saveToHistory(HISTORY_KEYS.PRODUCTION_MANAGER, form.production_manager)
|
||||
|
||||
ElMessage.success('操作成功')
|
||||
visible.value = false
|
||||
fetchData()
|
||||
@ -334,7 +479,9 @@ onMounted(() => fetchData())
|
||||
.production-card { border-left: 4px solid #E6A23C; }
|
||||
.identity-panel { background: #fffbf0; border: 1px dashed #e6a23c; padding: 15px; margin: 10px 0; border-radius: 6px; }
|
||||
.prefix-tag.sn { color: #409EFF; background: #ecf5ff; padding: 0 5px; font-weight: bold; }
|
||||
.option-item { display: flex; justify-content: space-between; width: 100%; }
|
||||
.opt-spec { color: #8492a6; font-size: 12px; }
|
||||
.option-item { display: flex; justify-content: space-between; width: 100%; align-items: center; }
|
||||
.opt-name { font-weight: bold; }
|
||||
.opt-spec { color: #8492a6; font-size: 12px; margin-right: 10px; }
|
||||
.is-text-view :deep(.el-input__wrapper) { box-shadow: none !important; background: #f5f7fa; border-bottom: 1px solid #dcdfe6; }
|
||||
.search-tip { color: #909399; font-size: 12px; margin-left: 10px; display: flex; align-items: center; gap: 4px; }
|
||||
</style>
|
||||
@ -141,38 +141,42 @@
|
||||
|
||||
<div class="card-content">
|
||||
<el-row :gutter="24" v-if="dialogStatus === 'create'" style="margin-bottom: 20px;">
|
||||
<el-col :span="14">
|
||||
<el-col :span="10">
|
||||
<el-form-item label="物料搜索" prop="base_id" class="highlight-label">
|
||||
<el-select
|
||||
v-model="form.base_id"
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
placeholder="输入名称 / 规格型号进行模糊搜索..."
|
||||
placeholder="输入名称或规格..."
|
||||
:remote-method="handleSearchMaterial"
|
||||
@visible-change="handleMaterialDropdownVisible"
|
||||
:loading="searchLoading"
|
||||
style="width: 100%"
|
||||
@change="onMaterialSelected"
|
||||
size="large"
|
||||
default-first-option
|
||||
>
|
||||
<el-option
|
||||
v-for="item in materialOptions"
|
||||
:key="item.id"
|
||||
:label="item.name + ' [' + item.spec + ']'"
|
||||
: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>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<div class="info-alert">
|
||||
<el-icon><InfoFilled /></el-icon> 仅展示状态为“启用”的基础物料
|
||||
</div>
|
||||
<el-col :span="14" style="display: flex; align-items: center;">
|
||||
<span class="search-tip">
|
||||
<el-icon><InfoFilled /></el-icon> 未输入时展示最新物料;输入关键词进行精确搜索。
|
||||
</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
@ -303,7 +307,19 @@
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8"><el-form-item label="生产负责人"><el-input v-model="form.production_manager" /></el-form-item></el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="生产负责人">
|
||||
<el-autocomplete
|
||||
v-model="form.production_manager"
|
||||
:fetch-suggestions="querySearchManager"
|
||||
placeholder="输入或选择负责人"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
:trigger-on-focus="true"
|
||||
@select="handleManagerSelect"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<el-form-item label="生产时间">
|
||||
<el-date-picker
|
||||
@ -481,6 +497,93 @@ const form = reactive({
|
||||
detail_link: ''
|
||||
})
|
||||
|
||||
// ------------------------------------
|
||||
// 历史记录管理器 (Local Storage)
|
||||
// ------------------------------------
|
||||
const HISTORY_KEYS = {
|
||||
PRODUCTION_MANAGER: 'history_production_managers',
|
||||
MATERIAL: 'history_semi_materials'
|
||||
}
|
||||
|
||||
// 保存历史 (String 类型)
|
||||
const saveToHistory = (key: string, value: string) => {
|
||||
if (!value) return
|
||||
try {
|
||||
const existing = localStorage.getItem(key)
|
||||
let list = existing ? JSON.parse(existing) : []
|
||||
// 移除旧的,添加到前面
|
||||
list = list.filter((i: string) => i !== value)
|
||||
list.unshift(value)
|
||||
if (list.length > 20) list = list.slice(0, 20) // 最多存20条
|
||||
localStorage.setItem(key, JSON.stringify(list))
|
||||
} catch (e) { console.error('save history failed', e) }
|
||||
}
|
||||
|
||||
// 获取历史 (String 类型)
|
||||
const getHistoryList = (key: string): any[] => {
|
||||
try {
|
||||
const existing = localStorage.getItem(key)
|
||||
const list = existing ? JSON.parse(existing) : []
|
||||
return list.map((v: string) => ({ value: v }))
|
||||
} catch (e) { return [] }
|
||||
}
|
||||
|
||||
// 保存物料历史 (Object 类型)
|
||||
const saveMaterialHistory = (item: any) => {
|
||||
if (!item || !item.id) return
|
||||
const key = HISTORY_KEYS.MATERIAL
|
||||
try {
|
||||
const existing = localStorage.getItem(key)
|
||||
let list = existing ? JSON.parse(existing) : []
|
||||
list = list.filter((i: any) => i.id !== item.id)
|
||||
list.unshift({ ...item, isHistory: true })
|
||||
if (list.length > 10) list = list.slice(0, 10)
|
||||
localStorage.setItem(key, JSON.stringify(list))
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const getMaterialHistory = () => {
|
||||
try {
|
||||
const existing = localStorage.getItem(HISTORY_KEYS.MATERIAL)
|
||||
return existing ? JSON.parse(existing) : []
|
||||
} catch (e) { return [] }
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// Autocomplete 建议逻辑 (混合模式:历史+当前表格)
|
||||
// ------------------------------------
|
||||
const createFilter = (queryString: string) => {
|
||||
return (item: any) => {
|
||||
return (item.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0)
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:从当前表格提取
|
||||
const getTableDataUnique = (field: string) => {
|
||||
const uniqueItems = Array.from(new Set(tableData.value.map((i: any) => i[field]).filter(Boolean)))
|
||||
return uniqueItems.map(i => ({ value: i }))
|
||||
}
|
||||
|
||||
// 通用查询: 历史记录 + 当前页面数据
|
||||
const mixedSearch = (queryString: string, tableField: string, storageKey: string, cb: any) => {
|
||||
const tableList = getTableDataUnique(tableField)
|
||||
const historyList = getHistoryList(storageKey)
|
||||
|
||||
// 合并去重
|
||||
const map = new Map()
|
||||
historyList.forEach(i => map.set(i.value, i))
|
||||
tableList.forEach(i => map.set(i.value, i))
|
||||
|
||||
const allList = Array.from(map.values())
|
||||
const results = queryString ? allList.filter(createFilter(queryString)) : allList
|
||||
cb(results)
|
||||
}
|
||||
|
||||
// 1. 生产负责人
|
||||
const querySearchManager = (qs: string, cb: any) => mixedSearch(qs, 'production_manager', HISTORY_KEYS.PRODUCTION_MANAGER, cb)
|
||||
const handleManagerSelect = (item: any) => saveToHistory(HISTORY_KEYS.PRODUCTION_MANAGER, item.value)
|
||||
|
||||
|
||||
// ------------------------------------
|
||||
// 逻辑校验规则
|
||||
// ------------------------------------
|
||||
@ -573,23 +676,41 @@ const handleEntryModeChange = (val: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearchMaterial = async (query: string) => {
|
||||
if (query) {
|
||||
searchLoading.value = true
|
||||
try {
|
||||
const res: any = await searchMaterialBase(query)
|
||||
materialOptions.value = res.data || []
|
||||
} finally {
|
||||
searchLoading.value = false
|
||||
// ------------------------------------
|
||||
// 物料搜索逻辑 (优化:支持空查询加载默认值)
|
||||
// ------------------------------------
|
||||
const handleMaterialDropdownVisible = (visible: boolean) => {
|
||||
if (visible) {
|
||||
if (materialOptions.value.length === 0) {
|
||||
handleSearchMaterial('')
|
||||
}
|
||||
} else {
|
||||
materialOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearchMaterial = async (query: string) => {
|
||||
searchLoading.value = true
|
||||
try {
|
||||
const res: any = await searchMaterialBase(query)
|
||||
const apiResults = (res.data || []).map((i: any) => ({ ...i, isHistory: false }))
|
||||
|
||||
if (!query) {
|
||||
const history = getMaterialHistory()
|
||||
const historyIds = new Set(history.map((h: any) => h.id))
|
||||
const filteredApi = apiResults.filter((apiItem: any) => !historyIds.has(apiItem.id))
|
||||
materialOptions.value = [...history, ...filteredApi]
|
||||
} else {
|
||||
materialOptions.value = apiResults
|
||||
}
|
||||
} finally {
|
||||
searchLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const onMaterialSelected = (val: number) => {
|
||||
const item = materialOptions.value.find(i => i.id === val)
|
||||
if (item) {
|
||||
saveMaterialHistory(item)
|
||||
|
||||
form.material_name = item.name
|
||||
form.spec_model = item.spec
|
||||
form.category = item.category
|
||||
@ -621,6 +742,8 @@ const handleCreate = () => {
|
||||
entryMode.value = 'batch'
|
||||
form.batch_number = ''
|
||||
visible.value = true
|
||||
// 每次打开弹窗时,先清空选项,让下拉时触发“历史加载”
|
||||
materialOptions.value = []
|
||||
}
|
||||
|
||||
const handleUpdate = (row: any) => {
|
||||
@ -673,6 +796,14 @@ const handleUpdate = (row: any) => {
|
||||
form.quality_report_link = row.quality_report_link
|
||||
form.detail_link = row.detail_link
|
||||
|
||||
// 编辑模式下,把当前物料塞入选项,防止显示为 ID
|
||||
materialOptions.value = [{
|
||||
id: row.base_id,
|
||||
name: row.material_name,
|
||||
spec: row.spec_model,
|
||||
category: row.category
|
||||
}]
|
||||
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
@ -700,6 +831,10 @@ const submitForm = async () => {
|
||||
await updateSemiInbound(form.id!, payload)
|
||||
ElMessage.success('更新成功')
|
||||
}
|
||||
|
||||
// 保存生产负责人信息到历史记录
|
||||
saveToHistory(HISTORY_KEYS.PRODUCTION_MANAGER, form.production_manager)
|
||||
|
||||
await fetchData()
|
||||
visible.value = false
|
||||
} catch (e: any) {
|
||||
@ -824,6 +959,7 @@ onMounted(() => fetchData())
|
||||
|
||||
/* 基础信息卡片 (蓝色调,示读) */
|
||||
.basic-card { border-left: 4px solid #409EFF; }
|
||||
.search-tip { color: #909399; font-size: 12px; margin-left: 10px; display: flex; align-items: center; gap: 4px; }
|
||||
.is-text-view :deep(.el-input__wrapper) {
|
||||
box-shadow: none !important;
|
||||
background-color: #f5f7fa;
|
||||
@ -876,8 +1012,8 @@ onMounted(() => fetchData())
|
||||
/* 底部按钮 */
|
||||
.dialog-footer { display: flex; justify-content: flex-end; gap: 15px; margin-top: 20px; }
|
||||
.info-alert { font-size: 12px; color: #909399; margin-top: 10px; display: flex; align-items: center; gap: 5px; }
|
||||
.option-item { display: flex; justify-content: space-between; width: 100%; }
|
||||
.option-item { display: flex; justify-content: space-between; width: 100%; align-items: center;}
|
||||
.opt-name { font-weight: bold; }
|
||||
.opt-spec { color: #8492a6; font-size: 13px; }
|
||||
.opt-spec { color: #8492a6; font-size: 13px; margin-right: 10px; }
|
||||
.total-price-input :deep(.el-input__inner) { color: #F56C6C; font-weight: bold; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user