feat(purchase): 弹窗加大 + 移除公共图片 + 每行照片必填
- 弹窗宽度 1180px → 1350px,缓解表格输入框拥挤 - 移除底部公共「图片凭证」上传区,照片统一在每行上传 - 照片列表头加红星必填标记 - 提交时逐行校验照片必填:某行无照片则阻断并提示该行名称 - 清理废弃的公共图片逻辑(fileList/customUpload/handleRemoveImage/usePasteUpload)
This commit is contained in:
@ -71,7 +71,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- ========== 新建/编辑弹窗 ========== -->
|
<!-- ========== 新建/编辑弹窗 ========== -->
|
||||||
<el-dialog v-model="formDialogVisible" :title="dialogTitle" width="1180px" top="4vh" destroy-on-close :close-on-click-modal="false" :close-on-press-escape="false" class="purchase-dialog">
|
<el-dialog v-model="formDialogVisible" :title="dialogTitle" width="1350px" top="4vh" destroy-on-close :close-on-click-modal="false" :close-on-press-escape="false" class="purchase-dialog">
|
||||||
<el-form ref="formRef" :model="form" label-width="100px">
|
<el-form ref="formRef" :model="form" label-width="100px">
|
||||||
|
|
||||||
<!-- ── 物料明细表(动态多行) ── -->
|
<!-- ── 物料明细表(动态多行) ── -->
|
||||||
@ -143,7 +143,11 @@
|
|||||||
<el-input v-model="row.remark" type="textarea" :rows="1" placeholder="行备注" resize="none" />
|
<el-input v-model="row.remark" type="textarea" :rows="1" placeholder="行备注" resize="none" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="照片" width="110" align="center">
|
<el-table-column width="130" align="center">
|
||||||
|
<!-- ★ 表头加红星:照片必填 -->
|
||||||
|
<template #header>
|
||||||
|
<span style="color: #F56C6C; margin-right: 3px;">*</span>照片
|
||||||
|
</template>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-upload
|
<el-upload
|
||||||
:http-request="(opt: any) => customUploadRow(opt, row)"
|
:http-request="(opt: any) => customUploadRow(opt, row)"
|
||||||
@ -185,32 +189,16 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ── 备注与图片 ── -->
|
<!-- ── 公共备注 ── -->
|
||||||
<div class="form-section">
|
<div class="form-section">
|
||||||
<div class="section-title"><span>备注与凭证</span></div>
|
<div class="section-title"><span>公共备注</span></div>
|
||||||
<el-row :gutter="16">
|
<el-row :gutter="16">
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<el-form-item label="备注">
|
<el-form-item label="备注">
|
||||||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="备注信息(选填)" />
|
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="公共备注(未填写行备注时使用)" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24">
|
</el-row>
|
||||||
<el-form-item label="公共图片" id="upload-purchase-images">
|
|
||||||
<span style="font-size: 12px; color: #909399; margin-right: 8px;">未上传行内照片时使用此公共凭证</span>
|
|
||||||
<el-upload
|
|
||||||
v-model:file-list="fileList"
|
|
||||||
:http-request="customUpload"
|
|
||||||
:on-remove="handleRemoveImage"
|
|
||||||
:before-upload="beforeAvatarUpload"
|
|
||||||
accept="image/jpeg,image/png,image/gif,image/webp"
|
|
||||||
multiple
|
|
||||||
list-type="picture-card"
|
|
||||||
>
|
|
||||||
<el-icon><Plus /></el-icon>
|
|
||||||
</el-upload>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</el-form>
|
</el-form>
|
||||||
@ -297,7 +285,6 @@ import {
|
|||||||
approvePurchase, getPurchaseApprovers, autoFillPurchase
|
approvePurchase, getPurchaseApprovers, autoFillPurchase
|
||||||
} from '@/api/purchase'
|
} from '@/api/purchase'
|
||||||
import { uploadFile, deleteFile } from '@/api/common/upload'
|
import { uploadFile, deleteFile } from '@/api/common/upload'
|
||||||
import { usePasteUpload } from '@/hooks/usePasteUpload'
|
|
||||||
import type { FormInstance } from 'element-plus'
|
import type { FormInstance } from 'element-plus'
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
@ -311,7 +298,6 @@ const pageSize = ref(20)
|
|||||||
const filterStatus = ref<number | ''>(0) // 默认筛选待审批
|
const filterStatus = ref<number | ''>(0) // 默认筛选待审批
|
||||||
const submitLoading = ref(false)
|
const submitLoading = ref(false)
|
||||||
const rejectLoading = ref(false)
|
const rejectLoading = ref(false)
|
||||||
const fileList = ref<any[]>([])
|
|
||||||
|
|
||||||
// 驳回
|
// 驳回
|
||||||
const rejectDialogVisible = ref(false)
|
const rejectDialogVisible = ref(false)
|
||||||
@ -355,8 +341,7 @@ const vLoadmore = {
|
|||||||
const form = ref({
|
const form = ref({
|
||||||
approver_id: undefined as number | undefined,
|
approver_id: undefined as number | undefined,
|
||||||
supplier_link: '',
|
supplier_link: '',
|
||||||
remark: '',
|
remark: ''
|
||||||
images: [] as string[]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// ★ 动态明细行
|
// ★ 动态明细行
|
||||||
@ -473,8 +458,7 @@ const openCreateDialog = (prefill?: { materialId?: number; name?: string; spec?:
|
|||||||
form.value = {
|
form.value = {
|
||||||
approver_id: undefined,
|
approver_id: undefined,
|
||||||
supplier_link: '',
|
supplier_link: '',
|
||||||
remark: '',
|
remark: ''
|
||||||
images: []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ★ 初始化明细行:若有预填物料则第一行带入,否则给一行空行
|
// ★ 初始化明细行:若有预填物料则第一行带入,否则给一行空行
|
||||||
@ -493,7 +477,6 @@ const openCreateDialog = (prefill?: { materialId?: number; name?: string; spec?:
|
|||||||
}
|
}
|
||||||
formItems.value = [firstRow]
|
formItems.value = [firstRow]
|
||||||
|
|
||||||
fileList.value = []
|
|
||||||
formDialogVisible.value = true
|
formDialogVisible.value = true
|
||||||
|
|
||||||
// 异步加载默认审批人(取当前用户上一次采购申请的审批人)
|
// 异步加载默认审批人(取当前用户上一次采购申请的审批人)
|
||||||
@ -615,40 +598,6 @@ const beforeAvatarUpload = (rawFile: any) => {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const customUpload = async (options: 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
|
|
||||||
form.value.images!.push(newUrl)
|
|
||||||
// ★ v-model:file-list 已自动管理列表,onSuccess 通知 el-upload 更新状态即可
|
|
||||||
// 传入 { url } 让 el-upload 正确设置文件预览 URL
|
|
||||||
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 handleRemoveImage = async (uploadFile: any) => {
|
|
||||||
const urlToRemove = form.value.images!.find(u => getImageUrl(u) === uploadFile.url) || uploadFile.url
|
|
||||||
form.value.images = form.value.images!.filter(u => u !== urlToRemove)
|
|
||||||
const filename = urlToRemove.split('/').pop()
|
|
||||||
if (filename && !urlToRemove.startsWith('http')) {
|
|
||||||
await deleteFile(filename).catch(() => {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ★ 行内照片上传
|
// ★ 行内照片上传
|
||||||
const customUploadRow = async (options: any, row: any) => {
|
const customUploadRow = async (options: any, row: any) => {
|
||||||
const { file, onSuccess, onError } = options
|
const { file, onSuccess, onError } = options
|
||||||
@ -697,12 +646,6 @@ const syncRemarkToAll = () => {
|
|||||||
ElMessage.success('已将第一行备注同步到所有行')
|
ElMessage.success('已将第一行备注同步到所有行')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 粘贴上传处理器(PC 端:鼠标悬停 + Ctrl+V 直接粘贴图片)
|
|
||||||
const handlePasteLink = (link: string, field: string) => {
|
|
||||||
// 采购单没有独立的外链输入框,暂不支持网页图片链接自动填入
|
|
||||||
}
|
|
||||||
usePasteUpload(customUpload, 'images', '#upload-purchase-images', handlePasteLink)
|
|
||||||
|
|
||||||
// --- 提交(循环逐行调用单条接口) ---
|
// --- 提交(循环逐行调用单条接口) ---
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
// 1. 公共信息校验
|
// 1. 公共信息校验
|
||||||
@ -719,11 +662,9 @@ const submitForm = async () => {
|
|||||||
if (!row.name.trim()) { ElMessage.warning(`第 ${idx + 1} 行请选择采购物品`); return }
|
if (!row.name.trim()) { ElMessage.warning(`第 ${idx + 1} 行请选择采购物品`); return }
|
||||||
if (!row.purchase_date) { 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 }
|
if (!row.quantity || row.quantity <= 0) { ElMessage.warning(`第 ${idx + 1} 行采购数量必须大于 0`); return }
|
||||||
// 图片校验:每行必须有行内照片或公共照片
|
// ★ 每行照片必填
|
||||||
const hasRowImg = (row.images && row.images.length > 0)
|
if (!row.images || row.images.length === 0) {
|
||||||
const hasCommonImg = (form.value.images && form.value.images.length > 0)
|
ElMessage.warning(`请为第 ${idx + 1} 行【${row.name || '未命名物品'}】上传至少一张照片凭证!`)
|
||||||
if (!hasRowImg && !hasCommonImg) {
|
|
||||||
ElMessage.warning(`第 ${idx + 1} 行请上传照片(或使用公共图片凭证)`)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -745,8 +686,8 @@ const submitForm = async () => {
|
|||||||
supplier_link: form.value.supplier_link,
|
supplier_link: form.value.supplier_link,
|
||||||
// 行备注优先;若空则用公共备注
|
// 行备注优先;若空则用公共备注
|
||||||
remark: row.remark || form.value.remark,
|
remark: row.remark || form.value.remark,
|
||||||
// 行照片优先;若空则用公共图片凭证
|
// ★ 每行必填照片
|
||||||
images: (row.images && row.images.length > 0) ? row.images : form.value.images
|
images: row.images || []
|
||||||
}
|
}
|
||||||
await createPurchase(payload)
|
await createPurchase(payload)
|
||||||
successCount++
|
successCount++
|
||||||
|
|||||||
Reference in New Issue
Block a user