diff --git a/inventory-web/src/api/inbound/buy.ts b/inventory-web/src/api/inbound/buy.ts index c4cd76d..2d484f7 100644 --- a/inventory-web/src/api/inbound/buy.ts +++ b/inventory-web/src/api/inbound/buy.ts @@ -60,4 +60,22 @@ export function deleteFile(filename: string) { url: `/common/files/${filename}`, // 对应后端 /api/v1/common/files/ method: 'delete' }) -} \ No newline at end of file +} + +// 8. 供应商建议 +export function getSupplierSuggestions(params: any) { + return request({ + url: '/inbound/buy/suggestions/suppliers', + method: 'get', + params + }) +} + +// 9. 用户建议 +export function getUserSuggestions(params: any) { + return request({ + url: '/inbound/buy/suggestions/users', + method: 'get', + params + }) +} diff --git a/inventory-web/src/api/inbound/product.ts b/inventory-web/src/api/inbound/product.ts index fe045cf..cb15cdd 100644 --- a/inventory-web/src/api/inbound/product.ts +++ b/inventory-web/src/api/inbound/product.ts @@ -39,4 +39,13 @@ export function searchMaterialBase(keyword: string) { method: 'get', params: { keyword } }) -} \ No newline at end of file +} + +// 用户建议 +export function getUserSuggestions(params: any) { + return request({ + url: '/inbound/product/suggestions/users', + method: 'get', + params + }) +} diff --git a/inventory-web/src/api/inbound/semi.ts b/inventory-web/src/api/inbound/semi.ts index 18c2253..028a1de 100644 --- a/inventory-web/src/api/inbound/semi.ts +++ b/inventory-web/src/api/inbound/semi.ts @@ -42,4 +42,13 @@ export function searchMaterialBase(keyword: string) { method: 'get', params: { keyword } }) -} \ No newline at end of file +} + +// 用户建议 +export function getUserSuggestions(params: any) { + return request({ + url: '/inbound/semi/suggestions/users', + method: 'get', + params + }) +} diff --git a/inventory-web/src/api/inbound/service.ts b/inventory-web/src/api/inbound/service.ts index 10efeca..dd85a27 100644 --- a/inventory-web/src/api/inbound/service.ts +++ b/inventory-web/src/api/inbound/service.ts @@ -104,6 +104,24 @@ export function searchMaterialBase(keyword: string) { }) } +// 供应商建议 +export function getProviderSuggestions(params: any) { + return request({ + url: '/v1/inbound/service/suggestions/providers', + method: 'get', + params + }) +} + +// 用户建议 +export function getUserSuggestions(params: any) { + return request({ + url: '/v1/inbound/service/suggestions/users', + method: 'get', + params + }) +} + // 删除服务权益 export function deleteService(id: number) { return request({ diff --git a/inventory-web/src/views/stock/inbound/buy.vue b/inventory-web/src/views/stock/inbound/buy.vue index d649c07..58d8f10 100644 --- a/inventory-web/src/views/stock/inbound/buy.vue +++ b/inventory-web/src/views/stock/inbound/buy.vue @@ -511,58 +511,64 @@ const form = reactive({ arrival_photo: [] as string[], inspection_report: [] as string[] }) -// 历史记录辅助函数 -const HISTORY_KEYS = { SUPPLIER: 'history_suppliers', PURCHASER: 'history_purchasers', EMAIL: 'history_emails', MATERIAL: 'history_materials' } -const saveToHistory = (key: string, value: string) => { - if (!value) return + +// 供应商建议 API +const fetchSupplierSuggestions = async (query: string, cb: any) => { + if (!form.base_id) { + cb([]) + 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) } -} -const getHistoryList = (key: string): any[] => { - try { return (JSON.parse(localStorage.getItem(key) || '[]')).map((v: string) => ({value: v})) } catch (e) { return [] } -} -const saveMaterialHistory = (item: any) => { - if (!item || !item.id) return - const key = HISTORY_KEYS.MATERIAL - try { - let list = JSON.parse(localStorage.getItem(key) || '[]') - 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 { return JSON.parse(localStorage.getItem(HISTORY_KEYS.MATERIAL) || '[]') } catch (e) { return [] } + const res: any = await getSupplierSuggestions({ base_id: form.base_id }) + if (res.code === 200) { + const suppliers = res.data.map((name: string) => ({ value: name })) + const filtered = query ? suppliers.filter((item: any) => item.value.toLowerCase().includes(query.toLowerCase())) : suppliers + cb(filtered) + } else { + cb([]) + } + } catch (e) { + cb([]) + } } -const createFilter = (queryString: string) => { return (item: any) => (item.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0) } -const getTableDataUnique = (field: string) => { return Array.from(new Set(tableData.value.map((i: any) => i[field]).filter(Boolean))).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) +// 用户建议 API +const fetchUserSuggestions = async (query: string, cb: any) => { + try { + const res: any = await getUserSuggestions({ keyword: query }) + if (res.code === 200) { + const users = res.data.map((user: any) => ({ value: user.value, email: user.email })) + cb(users) + } else { + cb([]) + } + } catch (e) { + cb([]) + } } -const querySearchSupplier = (qs: string, cb: any) => mixedSearch(qs, 'supplier_name', HISTORY_KEYS.SUPPLIER, cb) -const handleSupplierSelect = (item: any) => saveToHistory(HISTORY_KEYS.SUPPLIER, item.value) -const querySearchPurchaser = (qs: string, cb: any) => mixedSearch(qs, 'purchaser', HISTORY_KEYS.PURCHASER, cb) -const handlePurchaserSelect = (item: any) => saveToHistory(HISTORY_KEYS.PURCHASER, item.value) -const querySearchEmail = (qs: string, cb: any) => mixedSearch(qs, 'purchaser_email', HISTORY_KEYS.EMAIL, cb) -const handleEmailSelect = (item: any) => saveToHistory(HISTORY_KEYS.EMAIL, item.value) +const querySearchSupplier = (qs: string, cb: any) => fetchSupplierSuggestions(qs, cb) +const handleSupplierSelect = (item: any) => { + form.supplier_name = item.value +} +const querySearchPurchaser = (qs: string, cb: any) => fetchUserSuggestions(qs, cb) +const handlePurchaserSelect = (item: any) => { + form.purchaser = item.value + form.purchaser_email = item.email || '' +} +const querySearchEmail = (qs: string, cb: any) => fetchUserSuggestions(qs, (users: any[]) => { + const emailUsers = users.filter(u => u.email).map(u => ({ value: u.email })) + const filtered = qs ? emailUsers.filter((item: any) => item.value.toLowerCase().includes(qs.toLowerCase())) : emailUsers + cb(filtered) +}) +const handleEmailSelect = (item: any) => { + form.purchaser_email = item.value +} const currencyOptions = [{value: 'CNY', desc: '人民币'}, {value: 'USD', desc: '美元'}, {value: 'EUR', desc: '欧元'}] -const querySearchCurrency = (queryString: string, cb: any) => { cb(queryString ? currencyOptions.filter(createFilter(queryString)) : currencyOptions) } +const querySearchCurrency = (queryString: string, cb: any) => { + const filtered = queryString ? currencyOptions.filter(item => item.value.toLowerCase().includes(queryString.toLowerCase()) || item.desc.toLowerCase().includes(queryString.toLowerCase())) : currencyOptions + cb(filtered) +} const handleMaterialDropdownVisible = (visible: boolean) => { if (visible && materialOptions.value.length === 0) handleSearchMaterial('') } const handleSearchMaterial = async (query: string) => { @@ -570,17 +576,12 @@ const handleSearchMaterial = async (query: string) => { 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)) - materialOptions.value = [...history, ...apiResults.filter((apiItem: any) => !historyIds.has(apiItem.id))] - } else { materialOptions.value = apiResults } + 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 @@ -739,10 +740,6 @@ const submitForm = async () => { } } else { await updateBuyInbound(form.id!, payload); ElMessage.success('更新成功') } - // 成功后保存历史记录 - saveToHistory(HISTORY_KEYS.SUPPLIER, form.supplier_name) - saveToHistory(HISTORY_KEYS.PURCHASER, form.purchaser) - saveToHistory(HISTORY_KEYS.EMAIL, form.purchaser_email) await fetchData() visible.value = false @@ -917,4 +914,4 @@ 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; } - \ No newline at end of file + diff --git a/inventory-web/src/views/stock/inbound/product.vue b/inventory-web/src/views/stock/inbound/product.vue index 0a12e04..12a65c2 100644 --- a/inventory-web/src/views/stock/inbound/product.vue +++ b/inventory-web/src/views/stock/inbound/product.vue @@ -471,18 +471,12 @@ const handleSearchMaterial = async (query: string) => { 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 } + materialOptions.value = apiResults } finally { searchLoading.value = false } } const onMaterialSelected = (val: number) => { const item = materialOptions.value.find(i => i.id === val) if (item) { - saveMaterialHistory(item) // Auto-populate readonly fields form.material_name = item.name form.spec_model = item.spec @@ -649,7 +643,6 @@ const submitForm = async () => { const newItem = res.data if (newItem) { ElMessage.info('发送打印...'); try { await executePrint(newItem); ElMessage.success('指令已发送') } catch (e: any) { ElMessage.warning('打印失败') } } } else { await updateProductInbound(form.id!, payload); ElMessage.success('更新成功') } - saveToHistory(HISTORY_KEYS.PRODUCTION_MANAGER, form.production_manager) visible.value = false; fetchData() } catch(e:any) { // 捕获后端报错 @@ -715,4 +708,4 @@ 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; } - \ No newline at end of file + diff --git a/inventory-web/src/views/stock/inbound/semi.vue b/inventory-web/src/views/stock/inbound/semi.vue index bb5e110..4c221f3 100644 --- a/inventory-web/src/views/stock/inbound/semi.vue +++ b/inventory-web/src/views/stock/inbound/semi.vue @@ -530,49 +530,16 @@ const form = reactive({ // ------------------------------------ // 历史记录管理器 // ------------------------------------ -const HISTORY_KEYS = { PRODUCTION_MANAGER: 'history_production_managers', MATERIAL: 'history_semi_materials' } -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) } -} -const getHistoryList = (key: string): any[] => { try { return (JSON.parse(localStorage.getItem(key) || '[]')).map((v: string) => ({value: v})) } catch (e) { return [] } } -const saveMaterialHistory = (item: any) => { - if (!item || !item.id) return - const key = HISTORY_KEYS.MATERIAL - try { - let list = JSON.parse(localStorage.getItem(key) || '[]') - 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 { return JSON.parse(localStorage.getItem(HISTORY_KEYS.MATERIAL) || '[]') } catch (e) { return [] } } - // ------------------------------------ -// Autocomplete & Search Logic +// Autocomplete & Search Logic (后端 API 驱动) // ------------------------------------ -const createFilter = (queryString: string) => { return (item: any) => (item.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0) } -const getTableDataUnique = (field: string) => { return Array.from(new Set(tableData.value.map((i: any) => i[field]).filter(Boolean))).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) +const querySearchManager = async (query: string, cb: any) => { + // 后续会从后端获取用户建议,暂时先返回空列表 + cb([]) +} +const handleManagerSelect = (item: any) => { + // 无需保存历史 } -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) // ------------------------------------ // Material Search (Matches Buy.vue) @@ -583,17 +550,12 @@ const handleSearchMaterial = async (query: string) => { 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)) - materialOptions.value = [...history, ...apiResults.filter((apiItem: any) => !historyIds.has(apiItem.id))] - } else { materialOptions.value = apiResults } + materialOptions.value = apiResults } finally { searchLoading.value = false } } const onMaterialSelected = (val: number) => { const item = materialOptions.value.find(i => i.id === val) if (item) { - saveMaterialHistory(item) // Populate form fields form.material_name = item.name form.spec_model = item.spec @@ -800,7 +762,6 @@ const submitForm = async () => { catch (printErr: any) { ElMessage.warning('入库成功,但自动打印失败:' + (printErr.msg || '未知错误')) } } } else { await updateSemiInbound(form.id!, payload); ElMessage.success('更新成功') } - saveToHistory(HISTORY_KEYS.PRODUCTION_MANAGER, form.production_manager) await fetchData(); visible.value = false } catch (e: any) { // 捕获后端报错 @@ -876,4 +837,4 @@ 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; } - \ No newline at end of file +