| | |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column align="center" label="序号" type="index" width="60" /> |
| | | <el-table-column label="销售订单" prop="salesContractNo" show-overflow-tooltip /> |
| | | <el-table-column label="发货订单号" prop="shippingNo" show-overflow-tooltip /> |
| | | <el-table-column label="客户名称" prop="customerName" show-overflow-tooltip /> |
| | | <el-table-column label="发货时间" prop="shippingDate" show-overflow-tooltip /> |
| | | <el-table-column label="发货车牌号" prop="shippingCarNumber" show-overflow-tooltip /> |
| | | <el-table-column label="快递公司" prop="expressCompany" show-overflow-tooltip /> |
| | | <el-table-column label="快递单号" prop="expressNumber" show-overflow-tooltip /> |
| | | <el-table-column fixed="right" label="操作" width="150" align="center"> |
| | | <el-table-column label="审核状态" prop="status" align="center" width="120"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" @click="openForm('edit', scope.row)">编辑</el-button> |
| | | <el-button link type="danger" size="small" @click="handleDeleteSingle(scope.row)">删除</el-button> |
| | | <el-tag :type="getApprovalStatusType(scope.row.status)"> |
| | | {{ getApprovalStatusText(scope.row.status) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column fixed="right" label="操作" width="200" align="center"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | :disabled="!isApproved(scope.row.status)" |
| | | @click="openForm('edit', scope.row)">补充发货信息</el-button> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | @click="openDetail(scope.row)" |
| | | >详情</el-button> |
| | | <el-button |
| | | link |
| | | type="danger" |
| | | size="small" |
| | | :disabled="isApproving(scope.row.status)" |
| | | @click="handleDeleteSingle(scope.row)">删除</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 详情弹框 --> |
| | | <el-dialog v-model="detailDialogVisible" title="发货台账详情" width="55%" @close="closeDetail"> |
| | | <div v-if="detailRow" class="detail-wrapper"> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="销售订单">{{ detailRow.salesContractNo || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="发货订单号">{{ detailRow.shippingNo || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="客户名称">{{ detailRow.customerName || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="发货类型">{{ detailRow.type || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="发货日期">{{ detailRow.shippingDate || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="审核状态">{{ getApprovalStatusText(detailRow.status) }}</el-descriptions-item> |
| | | <el-descriptions-item label="发货车牌号">{{ detailRow.shippingCarNumber || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="快递公司">{{ detailRow.expressCompany || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="快递单号" :span="2">{{ detailRow.expressNumber || '--' }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div class="detail-images" v-if="detailImages.length"> |
| | | <div class="detail-images-title">发货图片</div> |
| | | <el-image |
| | | v-for="img in detailImages" |
| | | :key="img.url" |
| | | :src="img.url" |
| | | :preview-src-list="detailImages.map(i => i.url)" |
| | | fit="cover" |
| | | class="detail-image" |
| | | /> |
| | | </div> |
| | | <div v-else class="detail-images-empty">暂无发货图片</div> |
| | | </div> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDetail">关闭</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import { getToken } from "@/utils/auth"; |
| | | import { getCurrentDate } from "@/utils/index.js"; |
| | | import { |
| | | deliveryLedgerListPage, |
| | | addOrUpdateDeliveryLedger, |
| | | delDeliveryLedger, |
| | | deliveryLedgerListPage, |
| | | addOrUpdateDeliveryLedger, |
| | | delDeliveryLedger, deductStock, |
| | | } from "@/api/salesManagement/deliveryLedger.js"; |
| | | import { delLedgerFile } from "@/api/salesManagement/salesLedger.js"; |
| | | |
| | |
| | | const total = ref(0); |
| | | const deliveryFileList = ref([]); |
| | | const javaApi = proxy.javaApi; |
| | | // 详情弹框 |
| | | const detailDialogVisible = ref(false); |
| | | const detailRow = ref(null); |
| | | const detailImages = ref([]); |
| | | |
| | | const normalizeFileUrl = (rawUrl = '') => { |
| | | let fileUrl = rawUrl || ''; |
| | | // Windows 路径转 URL |
| | | if (fileUrl && fileUrl.indexOf('\\') > -1) { |
| | | const uploadsIndex = fileUrl.toLowerCase().indexOf('uploads'); |
| | | if (uploadsIndex > -1) { |
| | | const relativePath = fileUrl.substring(uploadsIndex).replace(/\\/g, '/'); |
| | | fileUrl = '/' + relativePath; |
| | | } else { |
| | | const parts = fileUrl.split('\\'); |
| | | const fileName = parts[parts.length - 1]; |
| | | fileUrl = '/uploads/' + fileName; |
| | | } |
| | | } |
| | | if (fileUrl && !fileUrl.startsWith('http')) { |
| | | if (!fileUrl.startsWith('/')) fileUrl = '/' + fileUrl; |
| | | fileUrl = javaApi + fileUrl; |
| | | } |
| | | return fileUrl; |
| | | }; |
| | | |
| | | // 上传配置 |
| | | const upload = reactive({ |
| | |
| | | |
| | | // 打开弹框 |
| | | const openForm = async (type, row) => { |
| | | // 补充发货信息:仅“审核通过”允许编辑 |
| | | if (type === 'edit' && row && !isApproved(row.status)) { |
| | | proxy.$modal.msgWarning("只有审核通过的数据才可以补充发货信息"); |
| | | return; |
| | | } |
| | | |
| | | operationType.value = type; |
| | | const baseUrl = import.meta.env.VITE_APP_BASE_API; |
| | | |
| | |
| | | // 如果有图片,将 commonFileList 转换为文件列表格式 |
| | | if (row.commonFileList && Array.isArray(row.commonFileList) && row.commonFileList.length > 0) { |
| | | deliveryFileList.value = row.commonFileList.map((file, index) => { |
| | | // 处理 URL:将 Windows 路径转换为可访问的 URL |
| | | let fileUrl = file.url || ''; |
| | | console.log('原始 URL:', fileUrl); |
| | | |
| | | // 如果 URL 是 Windows 路径格式(包含反斜杠),需要转换 |
| | | if (fileUrl && fileUrl.indexOf('\\') > -1) { |
| | | // 查找 uploads 关键字的位置,从那里开始提取相对路径 |
| | | const uploadsIndex = fileUrl.toLowerCase().indexOf('uploads'); |
| | | if (uploadsIndex > -1) { |
| | | // 从 uploads 开始提取路径,并将反斜杠替换为正斜杠 |
| | | const relativePath = fileUrl.substring(uploadsIndex).replace(/\\/g, '/'); |
| | | fileUrl = '/' + relativePath; |
| | | console.log('转换后的相对路径:', fileUrl); |
| | | } else { |
| | | // 如果没有找到 uploads,提取最后一个目录和文件名 |
| | | const parts = fileUrl.split('\\'); |
| | | const fileName = parts[parts.length - 1]; |
| | | fileUrl = '/uploads/' + fileName; |
| | | console.log('未找到 uploads,使用文件名:', fileUrl); |
| | | } |
| | | } |
| | | |
| | | // 确保所有非 http 开头的 URL 都拼接 baseUrl |
| | | if (fileUrl && !fileUrl.startsWith('http')) { |
| | | // 确保路径以 / 开头 |
| | | if (!fileUrl.startsWith('/')) { |
| | | fileUrl = '/' + fileUrl; |
| | | } |
| | | // 拼接 baseUrl |
| | | fileUrl = javaApi + fileUrl; |
| | | console.log('最终拼接的 URL:', fileUrl); |
| | | } |
| | | const fileUrl = normalizeFileUrl(file.url || ''); |
| | | |
| | | return { |
| | | uid: file.id || Date.now() + index, |
| | |
| | | dialogFormVisible.value = true; |
| | | }; |
| | | |
| | | // 打开详情弹框 |
| | | const openDetail = (row) => { |
| | | detailRow.value = row || null; |
| | | const list = Array.isArray(row?.commonFileList) ? row.commonFileList : []; |
| | | detailImages.value = list |
| | | .map((f) => ({ url: normalizeFileUrl(f?.url || '') })) |
| | | .filter((i) => !!i.url); |
| | | detailDialogVisible.value = true; |
| | | }; |
| | | const closeDetail = () => { |
| | | detailDialogVisible.value = false; |
| | | detailRow.value = null; |
| | | detailImages.value = []; |
| | | }; |
| | | |
| | | // 提交表单 |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate((valid) => { |
| | |
| | | expressNumber: form.value.type === "快递" ? form.value.expressNumber : "", |
| | | tempFileIds: tempFileIds, |
| | | }; |
| | | addOrUpdateDeliveryLedger(payload).then((res) => { |
| | | deductStock(payload).then((res) => { |
| | | proxy.$modal.msgSuccess("操作成功"); |
| | | closeDia(); |
| | | getList(); |
| | |
| | | |
| | | // 批量删除 |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("请选择数据"); |
| | | return; |
| | | } |
| | | |
| | | // 检查选中的行是否有"审核中"状态 |
| | | const approvingRows = selectedRows.value.filter(row => isApproving(row.status)); |
| | | if (approvingRows.length > 0) { |
| | | proxy.$modal.msgWarning("审核中的数据不能删除"); |
| | | return; |
| | | } |
| | | |
| | | const ids = selectedRows.value.map((item) => item.id); |
| | | ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | |
| | | |
| | | // 单个删除 |
| | | const handleDeleteSingle = (row) => { |
| | | // 检查是否为"审核中"状态 |
| | | if (isApproving(row.status)) { |
| | | proxy.$modal.msgWarning("审核中的数据不能删除"); |
| | | return; |
| | | } |
| | | |
| | | ElMessageBox.confirm("此操作将删除该记录,是否确认?", "删除", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | |
| | | } |
| | | }; |
| | | |
| | | // 获取审核状态文本 |
| | | const getApprovalStatusText = (status) => { |
| | | if (status === null || status === undefined || status === '') { |
| | | return '待审核'; |
| | | } |
| | | // 如果是数字 |
| | | if (typeof status === 'number') { |
| | | const statusMap = { |
| | | 0: '待审核', |
| | | 1: '审核中', |
| | | 2: '审核拒绝', |
| | | 3: '审核通过' |
| | | }; |
| | | return statusMap[status] || '待审核'; |
| | | } |
| | | // 如果是字符串,直接返回或映射 |
| | | const statusStr = String(status).trim(); |
| | | const statusTextMap = { |
| | | '待审核': '待审核', |
| | | '审核中': '审核中', |
| | | '审核拒绝': '审核拒绝', |
| | | '审核通过': '审核通过', |
| | | '0': '待审核', |
| | | '1': '审核中', |
| | | '2': '审核拒绝', |
| | | '3': '审核通过' |
| | | }; |
| | | return statusTextMap[statusStr] || statusStr || '待审核'; |
| | | }; |
| | | |
| | | // 获取审核状态标签类型(颜色) |
| | | const getApprovalStatusType = (status) => { |
| | | if (status === null || status === undefined || status === '') { |
| | | return 'info'; |
| | | } |
| | | // 如果是数字 |
| | | if (typeof status === 'number') { |
| | | const typeMap = { |
| | | 0: 'info', // 待审核 - 灰色 |
| | | 1: 'warning', // 审核中 - 黄色 |
| | | 2: 'danger', // 审核拒绝 - 红色 |
| | | 3: 'success' // 审核通过 - 绿色 |
| | | }; |
| | | return typeMap[status] || 'info'; |
| | | } |
| | | // 如果是字符串 |
| | | const statusStr = String(status).trim(); |
| | | const typeTextMap = { |
| | | '待审核': 'info', |
| | | '审核中': 'warning', |
| | | '审核拒绝': 'danger', |
| | | '审核通过': 'success', |
| | | '0': 'info', |
| | | '1': 'warning', |
| | | '2': 'danger', |
| | | '3': 'success' |
| | | }; |
| | | return typeTextMap[statusStr] || 'info'; |
| | | }; |
| | | |
| | | // 检查审核状态是否为"审核通过" |
| | | const isApproved = (status) => { |
| | | if (status === null || status === undefined || status === '') { |
| | | return false; |
| | | } |
| | | // 如果是数字,3 表示审核通过 |
| | | if (typeof status === 'number') { |
| | | return status === 3; |
| | | } |
| | | // 如果是字符串 |
| | | const statusStr = String(status).trim(); |
| | | return statusStr === '审核通过' || statusStr === '3'; |
| | | }; |
| | | |
| | | // 检查审核状态是否为"审核中" |
| | | const isApproving = (status) => { |
| | | if (status === null || status === undefined || status === '') { |
| | | return false; |
| | | } |
| | | // 如果是数字,1 表示审核中 |
| | | if (typeof status === 'number') { |
| | | return status === 1; |
| | | } |
| | | // 如果是字符串 |
| | | const statusStr = String(status).trim(); |
| | | return statusStr === '审核中' || statusStr === '1'; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | |
| | | display: none; |
| | | } |
| | | } |
| | | .detail-wrapper { |
| | | padding: 8px 0; |
| | | } |
| | | .detail-images { |
| | | margin-top: 16px; |
| | | } |
| | | .detail-images-title { |
| | | font-weight: 600; |
| | | margin-bottom: 10px; |
| | | color: #303133; |
| | | } |
| | | .detail-image { |
| | | width: 120px; |
| | | height: 120px; |
| | | margin-right: 10px; |
| | | margin-bottom: 10px; |
| | | border-radius: 6px; |
| | | } |
| | | .detail-images-empty { |
| | | margin-top: 16px; |
| | | color: #909399; |
| | | } |
| | | </style> |
| | | |