From 8d90b3c7749b060a758b3e0d57c47ae65c650244 Mon Sep 17 00:00:00 2001 From: yueli Date: Mon, 31 Aug 2026 13:10:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(purchase):=20=E9=87=87=E8=B4=AD=E7=94=B3?= =?UTF-8?q?=E8=AF=B7=E5=BC=B9=E7=AA=97=E5=8A=A0=E5=A4=A7=20+=20=E6=AF=8F?= =?UTF-8?q?=E8=A1=8C=E7=8B=AC=E7=AB=8B=E5=A4=87=E6=B3=A8/=E7=85=A7?= =?UTF-8?q?=E7=89=87=20+=20=E5=A4=87=E6=B3=A8=E4=B8=80=E9=94=AE=E5=90=8C?= =?UTF-8?q?=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 弹窗加大到 1180px、表格 max-height 400 减少滚动 - 明细表新增「备注」列和「照片」列(每行独立上传,最多3张) - 新增「备注同步所有」按钮:把第一行备注复制到所有行 - 提交时行备注优先、行照片优先,无则回退公共备注/公共图片 - 图片校验改为逐行(行内或公共至少其一) --- inventory-web/src/views/purchase/index.vue | 120 ++++++++++++++++++--- 1 file changed, 103 insertions(+), 17 deletions(-) diff --git a/inventory-web/src/views/purchase/index.vue b/inventory-web/src/views/purchase/index.vue index c470a76..845c9bb 100644 --- a/inventory-web/src/views/purchase/index.vue +++ b/inventory-web/src/views/purchase/index.vue @@ -71,7 +71,7 @@ /> - + @@ -79,11 +79,14 @@
物料明细({{ formItems.length }} 项) 添加一行 + + 备注同步所有 +
- - - + + + - + - + - + - + - + - + - + + + + + + + @@ -172,7 +195,8 @@ - + + 未上传行内照片时使用此公共凭证 import { ref, computed, onMounted } from 'vue' import { useRoute } from 'vue-router' -import { Refresh, Plus, Delete } from '@element-plus/icons-vue' +import { Refresh, Plus, Delete, Copy } from '@element-plus/icons-vue' import { ElMessage, ElMessageBox } from 'element-plus' import { useUserStore } from '@/stores/user' import { searchMaterialPurchase } from '@/api/purchase' @@ -347,6 +371,9 @@ interface FormItemRow { total_price: number | undefined tax_rate: number totalPriceManuallyEdited: boolean + remark: string + images: string[] + fileList: any[] } const formItems = ref([]) @@ -361,7 +388,10 @@ const createEmptyFormItem = (): FormItemRow => ({ unit_price: undefined, total_price: undefined, tax_rate: 0, - totalPriceManuallyEdited: false + totalPriceManuallyEdited: false, + remark: '', + images: [], + fileList: [] }) // 添加一行 @@ -619,6 +649,54 @@ const handleRemoveImage = async (uploadFile: any) => { } } +// ★ 行内照片上传 +const customUploadRow = async (options: any, row: any) => { + const { file, onSuccess, onError } = options + const formData = new FormData() + formData.append('file', file) + isUploading.value = true + try { + const res: any = await uploadFile(formData) + if (res.code === 200) { + const newUrl = res.data.url + row.images = row.images || [] + row.images.push(newUrl) + row.fileList = row.fileList || [] + row.fileList.push({ name: file.name || newUrl.split('/').pop(), url: getImageUrl(newUrl) }) + onSuccess({ url: getImageUrl(newUrl) }) + } else { + ElMessage.error(res.msg || '上传失败') + onError(new Error(res.msg)) + } + } catch (e) { + ElMessage.error('网络错误') + onError(e) + } finally { + isUploading.value = false + } +} + +// ★ 行内照片删除 +const handleRemoveRowImage = async (uploadFile: any, row: any) => { + const urlToRemove = (row.images || []).find(u => getImageUrl(u) === uploadFile.url) || uploadFile.url + row.images = (row.images || []).filter(u => u !== urlToRemove) + row.fileList = (row.fileList || []).filter(f => f.url !== uploadFile.url) + const filename = urlToRemove.split('/').pop() + if (filename && !urlToRemove.startsWith('http')) { + await deleteFile(filename).catch(() => {}) + } +} + +// ★ 备注一键同步所有行:取第一行的备注复制到所有行 +const syncRemarkToAll = () => { + if (formItems.value.length === 0) return + const source = formItems.value[0].remark || '' + formItems.value.forEach((row, idx) => { + if (idx > 0) row.remark = source + }) + ElMessage.success('已将第一行备注同步到所有行') +} + // 粘贴上传处理器(PC 端:鼠标悬停 + Ctrl+V 直接粘贴图片) const handlePasteLink = (link: string, field: string) => { // 采购单没有独立的外链输入框,暂不支持网页图片链接自动填入 @@ -629,7 +707,6 @@ usePasteUpload(customUpload, 'images', '#upload-purchase-images', handlePasteLin const submitForm = async () => { // 1. 公共信息校验 if (!form.value.approver_id) { ElMessage.warning('请选择审批人'); return } - if (!form.value.images || form.value.images.length === 0) { ElMessage.warning('请上传至少一张图片'); return } // 2. 逐行校验 const validItems = formItems.value.filter(i => i.name.trim()) @@ -642,6 +719,13 @@ const submitForm = async () => { if (!row.name.trim()) { ElMessage.warning(`第 ${idx + 1} 行请选择采购物品`); return } if (!row.purchase_date) { ElMessage.warning(`第 ${idx + 1} 行请选择采购日期`); return } if (!row.quantity || row.quantity <= 0) { ElMessage.warning(`第 ${idx + 1} 行采购数量必须大于 0`); return } + // 图片校验:每行必须有行内照片或公共照片 + const hasRowImg = (row.images && row.images.length > 0) + const hasCommonImg = (form.value.images && form.value.images.length > 0) + if (!hasRowImg && !hasCommonImg) { + ElMessage.warning(`第 ${idx + 1} 行请上传照片(或使用公共图片凭证)`) + return + } } // 3. 逐行提交(每行携带公共信息) @@ -659,8 +743,10 @@ const submitForm = async () => { tax_rate: row.tax_rate, approver_id: form.value.approver_id, supplier_link: form.value.supplier_link, - remark: form.value.remark, - images: form.value.images + // 行备注优先;若空则用公共备注 + remark: row.remark || form.value.remark, + // 行照片优先;若空则用公共图片凭证 + images: (row.images && row.images.length > 0) ? row.images : form.value.images } await createPurchase(payload) successCount++