添加功能下载验收单

This commit is contained in:
xuqiuyun
2025-11-05 14:39:27 +08:00
parent eacb0453dd
commit 70d9db252a
11 changed files with 919 additions and 68 deletions

View File

@@ -284,6 +284,15 @@ export function downloadDeliveryPackage(id) {
});
}
// 下载验收单生成HTML格式的牛只发车验收单
export function downloadAcceptanceForm(id) {
return request({
url: `/delivery/downloadAcceptanceForm?id=${id}`,
method: 'GET',
responseType: 'blob', // 用于下载HTML文件
});
}
// 获取运送清单详情(用于编辑)
export function getDeliveryDetail(id) {
return request({

View File

@@ -52,15 +52,17 @@ axios.interceptors.response.use(
// 单独判断导出问题-start
if(toString.call(responseData) === '[object Blob]' && (responseData.type !== 'application/json')) {
download(response);
return ElMessage({
type: 'success',
message: '导出成功!',
});
ElMessage({
type: 'success',
message: '导出成功!',
});
return Promise.resolve(response.data); // 返回 Promise避免返回对象
}else if(toString.call(responseData) === '[object Blob]' && (responseData.type == 'application/json')){
setTimeout(() => {
window.location.href = '/login';
}, 1000);
return ElMessage.error(`登录过期,请重新登录!`);
ElMessage.error(`登录过期,请重新登录!`);
return Promise.reject('登录过期,请重新登录!'); // 返回 Promise.reject避免返回对象
}
// 单独判断导出问题-end
@@ -71,7 +73,8 @@ axios.interceptors.response.use(
setTimeout(() => {
window.location.href = '/login';
}, 1000);
return ElMessage.error(`登录过期,请重新登录!`);
ElMessage.error(`登录过期,请重新登录!`);
return Promise.reject('登录过期,请重新登录!'); // 返回 Promise.reject避免返回对象
}
// console.log(responseData);
// if (responseData && (responseData.code === 650)) {

View File

@@ -102,6 +102,7 @@
<el-button type="warning" link @click="editDelivery(scope.row)" v-hasPermi="['entry:edit']">编辑</el-button>
<el-button type="success" link @click="updateStatus(scope.row)" v-hasPermi="['entry:status']">修改状态</el-button>
<el-button type="info" link @click="downloadPackage(scope.row)" :loading="downLoading[scope.row.id]" v-hasPermi="['entry:download']">打包文件</el-button>
<el-button type="primary" link @click="downloadAcceptanceFormHandler(scope.row)" :loading="downLoading[scope.row.id]" v-hasPermi="['entry:export']">下载验收单</el-button>
<el-button type="danger" link @click="deleteDelivery(scope.row)" v-hasPermi="['entry:delete']">删除</el-button>
</div>
</template>
@@ -125,7 +126,7 @@ import { ElMessage, ElMessageBox } from 'element-plus';
import baseSearch from '@/components/common/searchCustom/index.vue';
import createDeliveryDialog from '@/views/shipping/createDeliveryDialog.vue';
import { inspectionList, downloadZip, pageDeviceList } from '@/api/abroad.js';
import { updateDeliveryStatus, deleteDeliveryLogic, downloadDeliveryPackage, getDeliveryDetail } from '@/api/shipping.js';
import { updateDeliveryStatus, deleteDeliveryLogic, downloadDeliveryPackage, getDeliveryDetail, downloadAcceptanceForm } from '@/api/shipping.js';
const router = useRouter();
const route = useRoute();
@@ -679,10 +680,52 @@ const downloadPackage = async (row) => {
ElMessage.info('正在打包文件,请稍候...');
// 调用后端API打包文件包含图片、视频和信息
// 注意:由于响应拦截器设置了 responseType: 'blob',返回的是 Blob 对象
const res = await downloadDeliveryPackage(row.id);
// 创建下载链接
const blob = new Blob([res], { type: 'application/zip' });
// 确保 res 是 Blob 对象,如果不是则转换为 Blob
let blob;
if (res instanceof Blob) {
blob = res;
} else {
// 如果不是 Blob尝试转换为 Blob
blob = new Blob([res], { type: 'application/zip' });
}
// 检查响应是否是有效的ZIP文件通过检查ZIP文件魔数
// ZIP 文件的魔数是50 4B 03 04 (PK\x03\x04)
const arrayBuffer = await blob.arrayBuffer();
const uint8Array = new Uint8Array(arrayBuffer);
// 检查是否是ZIP文件前4个字节是 50 4B 03 04
const isZipFile = uint8Array.length >= 4 &&
uint8Array[0] === 0x50 &&
uint8Array[1] === 0x4B &&
uint8Array[2] === 0x03 &&
uint8Array[3] === 0x04;
if (!isZipFile) {
// 响应是错误信息JSON或文本尝试读取并显示错误
try {
const decoder = new TextDecoder('utf-8');
const errorText = decoder.decode(uint8Array);
let errorMsg = '打包文件失败';
try {
// 尝试解析为 JSON
const errorObj = JSON.parse(errorText);
errorMsg = errorObj.msg || errorObj.message || errorText;
} catch (e) {
// 不是 JSON直接使用文本
errorMsg = errorText || '打包文件失败,请重试';
}
ElMessage.error(errorMsg);
} catch (e) {
ElMessage.error('打包文件失败,请重试');
}
return;
}
// 创建下载链接(使用验证后的 Blob
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
@@ -695,7 +738,69 @@ const downloadPackage = async (row) => {
ElMessage.success('文件打包成功');
} catch (error) {
console.error('打包文件失败:', error);
ElMessage.error('打包文件失败,请重试');
ElMessage.error(error.message || '打包文件失败,请重试');
} finally {
downLoading[row.id] = false;
}
};
// 下载验收单
const downloadAcceptanceFormHandler = async (row) => {
try {
downLoading[row.id] = true;
ElMessage.info('正在生成验收单,请稍候...');
// 调用后端API生成验收单
const res = await downloadAcceptanceForm(row.id);
// 确保 res 是 Blob 对象
let blob;
if (res instanceof Blob) {
blob = res;
} else {
blob = new Blob([res], { type: 'text/html;charset=UTF-8' });
}
// 检查是否是HTML文件通过检查内容
const arrayBuffer = await blob.arrayBuffer();
const uint8Array = new Uint8Array(arrayBuffer);
// 尝试解码为文本检查是否是HTML
const decoder = new TextDecoder('utf-8');
const htmlText = decoder.decode(uint8Array);
// 检查是否是错误响应JSON格式
if (htmlText.trim().startsWith('{') || htmlText.trim().startsWith('[')) {
try {
const errorObj = JSON.parse(htmlText);
ElMessage.error(errorObj.msg || errorObj.message || '生成验收单失败,请重试');
} catch (e) {
ElMessage.error('生成验收单失败,请重试');
}
return;
}
// 创建下载链接
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `牛只发车验收单_${row.deliveryNumber || row.id}_${new Date().getTime()}.html`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
// 同时在新窗口中打开,方便预览
const newWindow = window.open('', '_blank');
if (newWindow) {
newWindow.document.write(htmlText);
newWindow.document.close();
}
ElMessage.success('验收单生成成功');
} catch (error) {
console.error('生成验收单失败:', error);
ElMessage.error(error.message || '生成验收单失败,请重试');
} finally {
downLoading[row.id] = false;
}

View File

@@ -224,17 +224,6 @@
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="预估重量(kg)" prop="estimatedWeight">
<el-input-number
v-model="formData.estimatedWeight"
:min="0.01"
:precision="2"
placeholder="请输入预估重量"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
@@ -671,7 +660,6 @@ const formData = reactive({
endLat: '',
endLon: '',
cattleCount: 1,
estimatedWeight: null,
quarantineCertNo: '',
remark: '',
// 装车相关字段
@@ -746,7 +734,6 @@ const rules = {
startLocation: [{ required: true, message: '请输入起点地址', trigger: 'blur' }],
endLocation: [{ required: true, message: '请输入目的地地址', trigger: 'blur' }],
cattleCount: [{ required: true, message: '请输入牛只数量', trigger: 'blur' }],
estimatedWeight: [{ required: true, message: '请输入预估重量', trigger: 'blur' }],
};
// 计算耳标设备列表(包含体重输入)
@@ -817,7 +804,6 @@ const buildSubmitData = () => {
// 其他信息
cattleCount: formData.cattleCount,
estimatedWeight: formData.estimatedWeight,
quarantineCertNo: formData.quarantineCertNo,
remark: formData.remark,
@@ -850,12 +836,24 @@ const buildSubmitData = () => {
}
// 将所有undefined值转换为空字符串确保字段被提交
// 但是对于数值类型的字段如cattleCount保留null或原值不要转换为空字符串
Object.keys(data).forEach(key => {
if (data[key] === undefined) {
data[key] = '';
// 数值类型字段保留null其他字段转换为空字符串
if (key === 'cattleCount' ||
key === 'orderId' || key === 'shipperId' || key === 'buyerId' ||
key === 'driverId' || key === 'serverId') {
data[key] = null;
} else {
data[key] = '';
}
}
});
// 确保cattleCount不为空字符串如果是空字符串则转换为null
if (data.cattleCount === '') {
data.cattleCount = null;
}
return data;
};
@@ -955,6 +953,9 @@ const fillFormWithEditData = (editData) => {
formData.entruckWeight = delivery.entruckWeight || null;
formData.landingEntruckWeight = delivery.landingEntruckWeight || null;
// 牛只数量从ratedQuantity字段映射
formData.cattleCount = delivery.ratedQuantity || 1;
// 照片
formData.quarantineTickeyUrl = delivery.quarantineTickeyUrl || '';
formData.poundListImg = delivery.poundListImg || '';
@@ -1246,7 +1247,8 @@ const handleSubmit = () => {
// 判断是编辑还是新增
if (formData.editId) {
// 编辑模式:调用更新接口
// 将cattleCount映射为ratedQuantity后端DeliveryEditDto使用ratedQuantity字段
submitData.ratedQuantity = submitData.cattleCount;
submitData.deliveryId = formData.editId; // 添加deliveryId字段后端需要
res = await shippingApi.updateDeliveryInfo(submitData);
} else {

View File

@@ -76,6 +76,17 @@
<el-option label="按肉价结算" :value="3"></el-option>
</el-select>
</el-form-item>
<el-form-item label="约定价格(元/斤)" prop="firmPrice">
<el-input-number
v-model="ruleForm.firmPrice"
:precision="2"
:min="0"
:max="9999999.99"
placeholder="请输入约定价格"
style="width: 100%"
></el-input-number>
</el-form-item>
</el-form>
<template #footer>
@@ -89,6 +100,7 @@
<script setup>
import { ref, reactive } from 'vue';
import { ElMessage } from 'element-plus';
import { orderAddNew, orderUpdate } from '@/api/shipping.js';
import { memberListByType } from '@/api/userManage.js';
@@ -119,12 +131,17 @@ const ruleForm = reactive({
buyerId: [], // 买方ID数组
sellerId: [], // 卖方ID数组
settlementType: 1, // 结算方式1-上车重量2-下车重量3-按肉价结算
firmPrice: null, // 约定价格(元/斤)
});
const rules = reactive({
buyerId: [{ required: true, message: '请选择买方', trigger: 'change' }],
sellerId: [{ required: true, message: '请选择卖方', trigger: 'change' }],
settlementType: [{ required: true, message: '请选择结算方式', trigger: 'change' }],
firmPrice: [
{ required: true, message: '请输入约定价格', trigger: 'blur' },
{ type: 'number', min: 0, message: '约定价格不能小于0', trigger: 'blur' }
],
});
const handleClose = () => {
@@ -136,6 +153,7 @@ const handleClose = () => {
ruleForm.buyerId = [];
ruleForm.sellerId = [];
ruleForm.settlementType = 1;
ruleForm.firmPrice = null;
data.dialogVisible = false;
};
@@ -217,6 +235,7 @@ const onClickSave = () => {
buyerId: ruleForm.buyerId.join(','), // 将数组转为逗号分隔的字符串
sellerId: ruleForm.sellerId.join(','), // 将数组转为逗号分隔的字符串
settlementType: ruleForm.settlementType,
firmPrice: ruleForm.firmPrice, // 约定价格(元/斤)
};
data.saveLoading = true;
@@ -282,6 +301,7 @@ const onShowDialog = (orderData) => {
}
ruleForm.settlementType = orderData?.settlementType || 1;
ruleForm.firmPrice = orderData?.firmPrice != null ? orderData.firmPrice : null;
data.dialogVisible = true;
getSupplierList();