diff --git a/pc-cattle-transportation/src/api/slaughter.js b/pc-cattle-transportation/src/api/slaughter.js new file mode 100644 index 0000000..e4d94ad --- /dev/null +++ b/pc-cattle-transportation/src/api/slaughter.js @@ -0,0 +1,126 @@ +import request from '@/utils/axios.ts'; + +// 屠宰场管理 +export function slaughterHouseList(data) { + return request({ + url: '/slaughter/house/list', + method: 'POST', + data, + }); +} + +export function slaughterHouseAdd(data) { + return request({ + url: '/slaughter/house/add', + method: 'POST', + data, + }); +} + +export function slaughterHouseEdit(data) { + return request({ + url: '/slaughter/house/edit', + method: 'POST', + data, + }); +} + +export function slaughterHouseDel(id) { + return request({ + url: `/slaughter/house/delete?id=${id}`, + method: 'GET', + }); +} + +export function slaughterHouseDetail(id) { + return request({ + url: `/slaughter/house/detail?id=${id}`, + method: 'GET', + }); +} + +export function slaughterHouseAll() { + return request({ + url: '/slaughter/house/all', + method: 'GET', + }); +} + +// 进场管理 +export function slaughterEntryList(data) { + return request({ + url: '/slaughter/entry/list', + method: 'POST', + data, + }); +} + +export function slaughterEntryAdd(data) { + return request({ + url: '/slaughter/entry/add', + method: 'POST', + data, + }); +} + +export function slaughterEntryEdit(data) { + return request({ + url: '/slaughter/entry/edit', + method: 'POST', + data, + }); +} + +export function slaughterEntryDel(id) { + return request({ + url: `/slaughter/entry/delete?id=${id}`, + method: 'GET', + }); +} + +export function slaughterEntryDetail(id) { + return request({ + url: `/slaughter/entry/detail?id=${id}`, + method: 'GET', + }); +} + +// 屠宰记录 +export function slaughterRecordList(data) { + return request({ + url: '/slaughter/record/list', + method: 'POST', + data, + }); +} + +export function slaughterRecordAdd(data) { + return request({ + url: '/slaughter/record/add', + method: 'POST', + data, + }); +} + +export function slaughterRecordEdit(data) { + return request({ + url: '/slaughter/record/edit', + method: 'POST', + data, + }); +} + +export function slaughterRecordDel(id) { + return request({ + url: `/slaughter/record/delete?id=${id}`, + method: 'GET', + }); +} + +export function slaughterRecordDetail(id) { + return request({ + url: `/slaughter/record/detail?id=${id}`, + method: 'GET', + }); +} + diff --git a/pc-cattle-transportation/src/router/index.ts b/pc-cattle-transportation/src/router/index.ts index 0e6b5e2..e7fc998 100644 --- a/pc-cattle-transportation/src/router/index.ts +++ b/pc-cattle-transportation/src/router/index.ts @@ -327,6 +327,48 @@ export const constantRoutes: Array = [ }, ], }, + // 屠宰场管理路由 + { + path: '/slaughter', + component: LayoutIndex, + meta: { + title: '屠宰场管理', + keepAlive: true, + requireAuth: true, + }, + children: [ + { + path: 'house', + name: 'house', + meta: { + title: '屠宰场管理', + keepAlive: true, + requireAuth: true, + }, + component: () => import('~/views/slaughter/house.vue'), + }, + { + path: 'entry', + name: 'entry', + meta: { + title: '进场管理', + keepAlive: true, + requireAuth: true, + }, + component: () => import('~/views/slaughter/entry.vue'), + }, + { + path: 'record', + name: 'record', + meta: { + title: '屠宰记录', + keepAlive: true, + requireAuth: true, + }, + component: () => import('~/views/slaughter/record.vue'), + }, + ], + }, { path: '/datav', name: 'DataV', diff --git a/pc-cattle-transportation/src/views/entry/attestation.vue b/pc-cattle-transportation/src/views/entry/attestation.vue index 710e109..e9a30b2 100644 --- a/pc-cattle-transportation/src/views/entry/attestation.vue +++ b/pc-cattle-transportation/src/views/entry/attestation.vue @@ -109,12 +109,13 @@ - + @@ -152,7 +191,8 @@ import Pagination from '@/components/Pagination/index.vue'; import createDeliveryDialog from '@/views/shipping/createDeliveryDialog.vue'; import OrderDialog from '@/views/shipping/orderDialog.vue'; import { inspectionList, downloadZip, pageDeviceList } from '@/api/abroad.js'; -import { updateDeliveryStatus, deleteDeliveryLogic, downloadDeliveryPackage, getDeliveryDetail, downloadAcceptanceForm } from '@/api/shipping.js'; +import { updateDeliveryStatus, deleteDeliveryLogic, downloadDeliveryPackage, getDeliveryDetail, downloadAcceptanceForm, orderPageQuery, orderGetDetail } from '@/api/shipping.js'; +import { sysUserList } from '@/api/sys.js'; const router = useRouter(); const route = useRoute(); @@ -169,6 +209,16 @@ const data = reactive({ rows: [], total: 10, deviceCounts: {}, // 存储每个订单的设备数量 {orderId: {host: 0, ear: 0, collar: 0, total: 0}} + userMap: {}, // 存储用户ID到用户名的映射 {userId: userName} +}); + +// 相关订单对话框数据 +const relatedOrdersDialog = reactive({ + visible: false, + loading: false, + orders: [], + deliveryId: null, + deliveryNumber: '' }); const formItemList = reactive([ { @@ -258,6 +308,94 @@ const getEarTagCount = (deliveryId) => { return counts ? counts.ear : 0; }; +// 映射创建人名称 +const mapCreatedByName = async () => { + if (!data.rows || data.rows.length === 0) { + return; + } + + // 检查是否所有记录都已经有 createByName 字段(后端可能已经返回) + const needMapping = data.rows.some(row => !row.createByName && row.createdBy); + + if (!needMapping) { + // 如果所有记录都有 createByName,不需要映射 + return; + } + + // 收集所有的 createdBy 字段值(去重) + const createdByIds = [...new Set(data.rows + .map(row => row.createdBy) + .filter(id => id != null && id !== '' && id !== undefined))]; + + if (createdByIds.length === 0) { + // 如果没有创建人ID,设置默认值 + data.rows.forEach(row => { + if (!row.createByName) { + row.createByName = '--'; + } + }); + return; + } + + try { + // 查询用户信息 + const userRes = await sysUserList({ + pageNum: 1, + pageSize: 1000 // 设置一个较大的值以获取所有用户 + }); + + if (userRes.code === 200) { + const userList = userRes.data?.rows || userRes.data || []; + + // 创建用户ID到用户名的映射(只映射需要的用户) + const idSet = new Set(createdByIds.map(id => String(id))); + userList.forEach(user => { + const userId = String(user.id); + if (idSet.has(userId) && user.name) { + data.userMap[userId] = user.name; + // 同时支持数字ID和字符串ID的映射 + data.userMap[user.id] = user.name; + } + }); + + // 为每条记录设置 createByName(只设置没有值的记录) + data.rows.forEach(row => { + if (!row.createByName) { + if (row.createdBy) { + const userId = String(row.createdBy); + // 尝试多种方式查找用户名 + row.createByName = data.userMap[row.createdBy] + || data.userMap[userId] + || data.userMap[Number(row.createdBy)] + || '--'; + } else { + row.createByName = '--'; + } + } + }); + } else { + // API返回错误,使用默认值 + data.rows.forEach(row => { + if (!row.createByName) { + row.createByName = row.createdBy ? String(row.createdBy) : '--'; + } + }); + } + } catch (error) { + console.error('获取用户信息失败:', error); + // 如果获取用户信息失败,显示ID或默认值 + data.rows.forEach(row => { + if (!row.createByName) { + if (row.createdBy) { + row.createByName = String(row.createdBy); + } else { + row.createByName = '--'; + } + } + }); + } +}; + const searchFrom = () => { form.pageNum = 1; getDataList(); @@ -326,6 +464,10 @@ const getDataList = () => { } console.log('[INSPECTION-LIST] 解析后的数据 - 总数:', data.total, '当前页数据:', data.rows.length); + + // 映射创建人名称 + await mapCreatedByName(); + dataListLoading.value = false; // 为每个订单获取设备数量 @@ -876,6 +1018,80 @@ const handleOrderSuccess = () => { getDataList(); }; +// 显示相关订单 +const showRelatedOrders = async (row) => { + relatedOrdersDialog.visible = true; + relatedOrdersDialog.loading = true; + relatedOrdersDialog.orders = []; + relatedOrdersDialog.deliveryId = row.id; + relatedOrdersDialog.deliveryNumber = row.deliveryNumber || ''; + + try { + // 从运送清单数据中获取 orderId 字段 + const orderId = row.orderId; + + if (!orderId) { + relatedOrdersDialog.orders = []; + ElMessage.info('该运送清单暂无关联订单'); + relatedOrdersDialog.loading = false; + return; + } + + // 解析 orderId(可能是字符串,包含逗号分隔的多个ID) + let orderIds = []; + if (typeof orderId === 'string') { + // 如果是字符串,按逗号分割 + orderIds = orderId.split(',').map(id => id.trim()).filter(id => id !== ''); + } else if (typeof orderId === 'number') { + // 如果是数字,转换为数组 + orderIds = [String(orderId)]; + } else if (Array.isArray(orderId)) { + // 如果是数组,直接使用 + orderIds = orderId.map(id => String(id)); + } else { + // 其他情况,尝试转换为字符串 + orderIds = [String(orderId)]; + } + + if (orderIds.length === 0) { + relatedOrdersDialog.orders = []; + ElMessage.info('该运送清单暂无关联订单'); + relatedOrdersDialog.loading = false; + return; + } + + // 根据订单ID列表逐个查询订单详情 + const orderPromises = orderIds.map(orderId => orderGetDetail(orderId)); + const orderResults = await Promise.allSettled(orderPromises); + + // 处理查询结果 + const orders = []; + orderResults.forEach((result, index) => { + if (result.status === 'fulfilled' && result.value.code === 200) { + const orderData = result.value.data || result.value; + if (orderData) { + orders.push(orderData); + } + } else { + console.warn(`获取订单 ${orderIds[index]} 详情失败:`, result.reason || result.value?.msg); + } + }); + + relatedOrdersDialog.orders = orders; + + if (orders.length === 0) { + ElMessage.info('该运送清单暂无关联订单或订单不存在'); + } else if (orders.length < orderIds.length) { + ElMessage.warning(`成功加载 ${orders.length}/${orderIds.length} 个订单`); + } + } catch (error) { + console.error('获取相关订单失败:', error); + ElMessage.error('获取相关订单失败,请稍后重试'); + } finally { + relatedOrdersDialog.loading = false; + } +}; + onMounted(() => { getDataList(); }); diff --git a/pc-cattle-transportation/src/views/entry/details.vue b/pc-cattle-transportation/src/views/entry/details.vue index 791c179..463cc75 100644 --- a/pc-cattle-transportation/src/views/entry/details.vue +++ b/pc-cattle-transportation/src/views/entry/details.vue @@ -43,6 +43,7 @@ {{ data.baseInfo.createTime || '' }} {{ data.baseInfo.freight ? data.baseInfo.freight + ' 元' : '-' }} {{ totalRegisteredDevices }} 个 + {{ data.baseInfo.ratedQuantity ||'-' }} 头 @@ -123,7 +124,19 @@ -
+
照片信息
@@ -147,83 +160,99 @@ />
-
+
检疫票
-
+
纸质磅单
-
+
空磅车头照片
-
+
过重磅车头照片
-
+
车辆重磅照片
-
+
驾驶员手持身份证
-
+
落地纸质磅单
-
+
落地过重磅车头
@@ -231,36 +260,82 @@
-
+
视频信息
-
+
空车过磅视频
-
-
+
装车过磅视频
-
-
+
装车视频
-
-
+
控槽视频
-
-
+
装完牛绕车一圈
-
-
+
卸牛视频
-
-
+
落地过磅视频
-
@@ -1193,12 +1268,27 @@ const hasValue = (value) => { if (value === null || value === undefined) { return false; } + if (Array.isArray(value)) { + return value.length > 0; + } if (typeof value === 'string') { return value.trim() !== ''; } return true; }; +// 将逗号分隔的字符串或数组统一为去空后的数组 +const parseMediaList = (value) => { + if (!value) return []; + if (Array.isArray(value)) { + return value.map((v) => (v || '').trim()).filter(Boolean); + } + return String(value) + .split(',') + .map((v) => v.trim()) + .filter(Boolean); +}; + // 获取卖方名称:如果是从订单页面进入的,使用订单的卖方;否则显示 '-' const getSupplierName = () => { // 检查是否从订单页面进入(通过路由参数 fromOrder 判断) diff --git a/pc-cattle-transportation/src/views/shipping/createDeliveryDialog.vue b/pc-cattle-transportation/src/views/shipping/createDeliveryDialog.vue index 3b9c571..b39385a 100644 --- a/pc-cattle-transportation/src/views/shipping/createDeliveryDialog.vue +++ b/pc-cattle-transportation/src/views/shipping/createDeliveryDialog.vue @@ -358,47 +358,20 @@
- - + +
拖拽或点击上传(最多2张)
- -
@@ -406,47 +379,20 @@
- - + +
拖拽或点击上传(最多2张)
- -
@@ -456,47 +402,20 @@
- - + +
拖拽或点击上传(最多2张)
- -
@@ -504,47 +423,20 @@
- - + +
拖拽或点击上传(最多2张)
- -
@@ -554,47 +446,20 @@
- - + +
拖拽或点击上传(最多2张)
- -
@@ -602,47 +467,20 @@
- - + +
拖拽或点击上传(最多2张)
- -
@@ -652,47 +490,20 @@
- - + +
拖拽或点击上传(最多2张)
- -
@@ -700,47 +511,20 @@
- - + +
拖拽或点击上传(最多2张)
- -
@@ -760,27 +544,19 @@ action="/api/common/upload" style="width: 100%" :headers="uploadHeaders" - :limit="1" - :on-success="handleEntruckWeightVideoSuccess" - :on-remove="() => handleRemoveVideo('entruckWeightVideo')" + :limit="2" + multiple + :file-list="videoLists.entruckWeightVideo" + :on-success="handleVideoSuccess('entruckWeightVideo')" + :on-remove="handleVideoRemove('entruckWeightVideo')" + :on-preview="(file) => handlePreviewVideo('entruckWeightVideo', file)" > -
将视频文件拖到此处,或点击上传
+
拖拽或点击上传(最多2个)
-
- - -
@@ -792,27 +568,19 @@ action="/api/common/upload" style="width: 100%" :headers="uploadHeaders" - :limit="1" - :on-success="handleEmptyWeightVideoSuccess" - :on-remove="() => handleRemoveVideo('emptyWeightVideo')" + :limit="2" + multiple + :file-list="videoLists.emptyWeightVideo" + :on-success="handleVideoSuccess('emptyWeightVideo')" + :on-remove="handleVideoRemove('emptyWeightVideo')" + :on-preview="(file) => handlePreviewVideo('emptyWeightVideo', file)" > -
将视频文件拖到此处,或点击上传
+
拖拽或点击上传(最多2个)
-
- - -
@@ -826,27 +594,19 @@ action="/api/common/upload" style="width: 100%" :headers="uploadHeaders" - :limit="1" - :on-success="handleCattleLoadingVideoSuccess" - :on-remove="() => handleRemoveVideo('cattleLoadingVideo')" + :limit="2" + multiple + :file-list="videoLists.cattleLoadingVideo" + :on-success="handleVideoSuccess('cattleLoadingVideo')" + :on-remove="handleVideoRemove('cattleLoadingVideo')" + :on-preview="(file) => handlePreviewVideo('cattleLoadingVideo', file)" > -
将视频文件拖到此处,或点击上传
+
拖拽或点击上传(最多2个)
-
- - -
@@ -858,27 +618,19 @@ action="/api/common/upload" style="width: 100%" :headers="uploadHeaders" - :limit="1" - :on-success="handleControlSlotVideoSuccess" - :on-remove="() => handleRemoveVideo('controlSlotVideo')" + :limit="2" + multiple + :file-list="videoLists.controlSlotVideo" + :on-success="handleVideoSuccess('controlSlotVideo')" + :on-remove="handleVideoRemove('controlSlotVideo')" + :on-preview="(file) => handlePreviewVideo('controlSlotVideo', file)" > -
将视频文件拖到此处,或点击上传
+
拖拽或点击上传(最多2个)
-
- - -
@@ -892,27 +644,19 @@ action="/api/common/upload" style="width: 100%" :headers="uploadHeaders" - :limit="1" - :on-success="handleCattleLoadingCircleVideoSuccess" - :on-remove="() => handleRemoveVideo('cattleLoadingCircleVideo')" + :limit="2" + multiple + :file-list="videoLists.cattleLoadingCircleVideo" + :on-success="handleVideoSuccess('cattleLoadingCircleVideo')" + :on-remove="handleVideoRemove('cattleLoadingCircleVideo')" + :on-preview="(file) => handlePreviewVideo('cattleLoadingCircleVideo', file)" > -
将视频文件拖到此处,或点击上传
+
拖拽或点击上传(最多2个)
-
- - -
@@ -924,27 +668,19 @@ action="/api/common/upload" style="width: 100%" :headers="uploadHeaders" - :limit="1" - :on-success="handleUnloadCattleVideoSuccess" - :on-remove="() => handleRemoveVideo('unloadCattleVideo')" + :limit="2" + multiple + :file-list="videoLists.unloadCattleVideo" + :on-success="handleVideoSuccess('unloadCattleVideo')" + :on-remove="handleVideoRemove('unloadCattleVideo')" + :on-preview="(file) => handlePreviewVideo('unloadCattleVideo', file)" > -
将视频文件拖到此处,或点击上传
+
拖拽或点击上传(最多2个)
-
- - -
@@ -958,27 +694,19 @@ action="/api/common/upload" style="width: 100%" :headers="uploadHeaders" - :limit="1" - :on-success="handleDestinationWeightVideoSuccess" - :on-remove="() => handleRemoveVideo('destinationWeightVideo')" + :limit="2" + multiple + :file-list="videoLists.destinationWeightVideo" + :on-success="handleVideoSuccess('destinationWeightVideo')" + :on-remove="handleVideoRemove('destinationWeightVideo')" + :on-preview="(file) => handlePreviewVideo('destinationWeightVideo', file)" > -
将视频文件拖到此处,或点击上传
+
拖拽或点击上传(最多2个)
-
- - -
@@ -1140,6 +868,64 @@ const formData = reactive({ destinationWeightVideo: '', }); +// 照片/视频字段列表(用于多文件收集与提交) +const photoFields = [ + 'quarantineTickeyUrl', + 'poundListImg', + 'emptyVehicleFrontPhoto', + 'loadedVehicleFrontPhoto', + 'loadedVehicleWeightPhoto', + 'driverIdCardPhoto', + 'destinationPoundListImg', + 'destinationVehicleFrontPhoto', +]; + +const videoFields = [ + 'entruckWeightVideo', + 'emptyWeightVideo', + 'cattleLoadingVideo', + 'controlSlotVideo', + 'cattleLoadingCircleVideo', + 'unloadCattleVideo', + 'destinationWeightVideo', +]; + +const photoLists = reactive( + photoFields.reduce((acc, key) => { + acc[key] = []; + return acc; + }, {}) +); + +const videoLists = reactive( + videoFields.reduce((acc, key) => { + acc[key] = []; + return acc; + }, {}) +); + +// 字段名称到中文名称的映射(用于错误提示) +const photoFieldNames = { + quarantineTickeyUrl: '检疫票', + poundListImg: '发车纸质磅单(双章)', + emptyVehicleFrontPhoto: '车辆空磅上磅车头照片', + loadedVehicleFrontPhoto: '车辆过重磅车头照片', + loadedVehicleWeightPhoto: '车辆重磅侧方照片', + driverIdCardPhoto: '驾驶员手持身份证站车头照片', + destinationPoundListImg: '落地纸质磅单(双章)', + destinationVehicleFrontPhoto: '落地车辆车头照片', +}; + +const videoFieldNames = { + entruckWeightVideo: '装车过磅视频', + emptyWeightVideo: '空车过磅视频', + cattleLoadingVideo: '装牛视频', + controlSlotVideo: '控槽视频', + cattleLoadingCircleVideo: '装完牛绕车一圈视频', + unloadCattleVideo: '卸牛视频', + destinationWeightVideo: '落地过磅视频', +}; + // 车牌号校验(已移除正则验证,直接传递字符串) // const validatePlateNumber = (rule, value, callback) => { // const plateReg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-Z0-9]{5}[A-Z0-9挂学警港澳]$/; @@ -1211,6 +997,10 @@ const collarDevices = computed(() => { // 完善提交数据 - 只提交DeliveryCreateDto需要的字段 const buildSubmitData = () => { + // 确保多文件列表已同步到表单字段 + syncPhotoFieldsToForm(); + syncVideoFieldsToForm(); + // 将司机ID转换为姓名 let driverNameStr = ''; if (formData.driverId) { @@ -1607,7 +1397,7 @@ const fillFormWithEditData = (editData) => { 'delivery对象': delivery }); - // 照片 + // 照片(逗号串) formData.quarantineTickeyUrl = delivery.quarantineTickeyUrl || ''; formData.poundListImg = delivery.poundListImg || ''; formData.emptyVehicleFrontPhoto = delivery.emptyVehicleFrontPhoto || ''; @@ -1617,7 +1407,7 @@ const fillFormWithEditData = (editData) => { formData.destinationPoundListImg = delivery.destinationPoundListImg || ''; formData.destinationVehicleFrontPhoto = delivery.destinationVehicleFrontPhoto || ''; - // 视频 + // 视频(逗号串) formData.entruckWeightVideo = delivery.entruckWeightVideo || ''; formData.emptyWeightVideo = delivery.emptyWeightVideo || ''; formData.cattleLoadingVideo = delivery.cattleLoadingVideo || ''; @@ -1625,6 +1415,27 @@ const fillFormWithEditData = (editData) => { formData.cattleLoadingCircleVideo = delivery.cattleLoadingCircleVideo || ''; formData.unloadCattleVideo = delivery.unloadCattleVideo || ''; formData.destinationWeightVideo = delivery.destinationWeightVideo || ''; + + // 回填多文件列表 + const syncListsFromForm = () => { + photoFields.forEach((field) => { + const urls = (formData[field] || '').split(',').map((u) => u.trim()).filter(Boolean); + photoLists[field] = urls.map((url, idx) => ({ + url, + name: `${field}-${idx + 1}`, + uid: `${field}-${idx + 1}`, + })); + }); + videoFields.forEach((field) => { + const urls = (formData[field] || '').split(',').map((u) => u.trim()).filter(Boolean); + videoLists[field] = urls.map((url, idx) => ({ + url, + name: `${field}-${idx + 1}`, + uid: `${field}-${idx + 1}`, + })); + }); + }; + syncListsFromForm(); // 保存编辑的ID,用于区分是新增还是编辑 formData.editId = delivery.id; @@ -1926,6 +1737,98 @@ const handleSubmit = () => { return; } + // 检查图片上传状态 + const failedPhotos = []; + const uploadingPhotos = []; + photoFields.forEach((field) => { + const list = photoLists[field] || []; + // 检查是否有正在上传中的文件 + const hasUploading = list.some((file) => file.status === 'uploading'); + if (hasUploading) { + uploadingPhotos.push(photoFieldNames[field] || field); + } + // 检查是否有文件但没有成功上传 + const hasUnsuccessfulUpload = list.some((file) => { + // 如果文件状态是 'fail',说明上传失败 + if (file.status === 'fail') { + return true; + } + // 如果文件存在但没有 url,或者 url 为空,说明上传失败 + // 排除正在上传中的文件(status === 'uploading') + if (file.status !== 'uploading' && (!file.url || file.url.trim() === '')) { + return true; + } + return false; + }); + if (hasUnsuccessfulUpload) { + failedPhotos.push(photoFieldNames[field] || field); + } + }); + + // 检查视频上传状态 + const failedVideos = []; + const uploadingVideos = []; + videoFields.forEach((field) => { + const list = videoLists[field] || []; + // 检查是否有正在上传中的文件 + const hasUploading = list.some((file) => file.status === 'uploading'); + if (hasUploading) { + uploadingVideos.push(videoFieldNames[field] || field); + } + // 检查是否有文件但没有成功上传 + const hasUnsuccessfulUpload = list.some((file) => { + // 如果文件状态是 'fail',说明上传失败 + if (file.status === 'fail') { + return true; + } + // 如果文件存在但没有 url,或者 url 为空,说明上传失败 + // 排除正在上传中的文件(status === 'uploading') + if (file.status !== 'uploading' && (!file.url || file.url.trim() === '')) { + return true; + } + return false; + }); + if (hasUnsuccessfulUpload) { + failedVideos.push(videoFieldNames[field] || field); + } + }); + + // 如果有正在上传中的文件,提示用户等待 + if (uploadingPhotos.length > 0 || uploadingVideos.length > 0) { + let errorMsg = '请等待以下文件上传完成后再提交:\n'; + if (uploadingPhotos.length > 0) { + errorMsg += `图片:${uploadingPhotos.join('、')}\n`; + } + if (uploadingVideos.length > 0) { + errorMsg += `视频:${uploadingVideos.join('、')}`; + } + ElMessage.warning(errorMsg); + return; + } + + // 如果有上传失败的文件,提示用户 + if (failedPhotos.length > 0 || failedVideos.length > 0) { + let errorMsg = '请确保以下文件上传成功后再提交:\n'; + if (failedPhotos.length > 0) { + errorMsg += `图片:${failedPhotos.join('、')}\n`; + } + if (failedVideos.length > 0) { + errorMsg += `视频:${failedVideos.join('、')}`; + } + ElMessage.error(errorMsg); + return; + } + + // 提交前,将多文件列表拼接到表单字段 + photoFields.forEach((field) => { + const urls = photoLists[field].map((f) => f.url).filter(Boolean).slice(0, 2); + formData[field] = urls.join(','); + }); + videoFields.forEach((field) => { + const urls = videoLists[field].map((f) => f.url).filter(Boolean).slice(0, 2); + formData[field] = urls.join(','); + }); + submitLoading.value = true; try { // 提交前详细日志 @@ -2215,93 +2118,121 @@ const resolveUploadUrl = (res) => { // 优先级顺序:url > data.src > data.url > data.fileUrl return res.url || res.data?.src || res.data?.url || res.data?.fileUrl || ''; }; -// 通用:创建成功回调 -const makeUploadSuccessSetter = (key) => (response) => { + +// 同步:将多文件列表写回表单(逗号拼接,限制2张) +const syncPhotoFieldsToForm = () => { + photoFields.forEach((field) => { + const urls = photoLists[field].map((f) => f.url).filter(Boolean).slice(0, 2); + formData[field] = urls.join(','); + }); +}; + +// 同步:将多视频列表写回表单(逗号拼接,限制2个) +const syncVideoFieldsToForm = () => { + videoFields.forEach((field) => { + const urls = videoLists[field].map((f) => f.url).filter(Boolean).slice(0, 2); + formData[field] = urls.join(','); + }); +}; + +// 照片:上传成功 +const handlePhotoSuccess = (field) => (response, file, fileList) => { const url = resolveUploadUrl(response); if (response?.code === 200 && url) { - formData[key] = url; - + // el-upload fileList 已包含新文件,但需确保 url 写入 + const targetList = photoLists[field]; + const existing = targetList.find((f) => f.uid === file.uid); + if (existing) { + existing.url = url; + existing.name = file.name; + } else { + targetList.push({ + url, + name: file.name, + uid: file.uid, + }); + } ElMessage.success('上传成功'); + syncPhotoFieldsToForm(); } else { console.warn(`[UPLOAD] 未识别的响应结构:`, response); ElMessage.error(response?.msg || '上传失败'); } }; -// 照片上传成功回调(统一使用工厂函数) -const handleQuarantinePhotoSuccess = makeUploadSuccessSetter('quarantineTickeyUrl'); -const handlePoundListPhotoSuccess = makeUploadSuccessSetter('poundListImg'); -const handleEmptyVehicleFrontPhotoSuccess = makeUploadSuccessSetter('emptyVehicleFrontPhoto'); -const handleLoadedVehicleFrontPhotoSuccess = makeUploadSuccessSetter('loadedVehicleFrontPhoto'); -const handleLoadedVehicleWeightPhotoSuccess = makeUploadSuccessSetter('loadedVehicleWeightPhoto'); -const handleDriverIdCardPhotoSuccess = makeUploadSuccessSetter('driverIdCardPhoto'); -const handleDestinationPoundListPhotoSuccess = makeUploadSuccessSetter('destinationPoundListImg'); -const handleDestinationVehicleFrontPhotoSuccess = makeUploadSuccessSetter('destinationVehicleFrontPhoto'); - -// 视频上传成功回调(统一使用工厂函数) -const handleEntruckWeightVideoSuccess = makeUploadSuccessSetter('entruckWeightVideo'); -const handleEmptyWeightVideoSuccess = makeUploadSuccessSetter('emptyWeightVideo'); -const handleCattleLoadingVideoSuccess = makeUploadSuccessSetter('cattleLoadingVideo'); -const handleControlSlotVideoSuccess = makeUploadSuccessSetter('controlSlotVideo'); -const handleCattleLoadingCircleVideoSuccess = makeUploadSuccessSetter('cattleLoadingCircleVideo'); -const handleUnloadCattleVideoSuccess = makeUploadSuccessSetter('unloadCattleVideo'); -const handleDestinationWeightVideoSuccess = makeUploadSuccessSetter('destinationWeightVideo'); - -// 失败回调(可选) -const handleUploadError = (err) => { - console.error('[UPLOAD] 失败:', err); - ElMessage.error('上传失败'); +// 照片:移除 +const handlePhotoRemove = (field) => (file, fileList) => { + const targetList = photoLists[field]; + const idx = targetList.findIndex((f) => f.uid === file.uid || f.url === file.url); + if (idx > -1) targetList.splice(idx, 1); + syncPhotoFieldsToForm(); }; -// 照片删除回调 -const handleDeletePhoto = (key) => { - if (formData.hasOwnProperty(key)) { - formData[key] = ''; - ElMessage.success('已删除照片'); +// 视频:上传成功 +const handleVideoSuccess = (field) => (response, file, fileList) => { + const url = resolveUploadUrl(response); + if (response?.code === 200 && url) { + const targetList = videoLists[field]; + const existing = targetList.find((f) => f.uid === file.uid); + if (existing) { + existing.url = url; + existing.name = file.name; + } else { + targetList.push({ + url, + name: file.name, + uid: file.uid, + }); + } + ElMessage.success('上传成功'); + syncVideoFieldsToForm(); + } else { + console.warn(`[UPLOAD] 未识别的响应结构:`, response); } }; +// 视频:移除 +const handleVideoRemove = (field) => (file, fileList) => { + const targetList = videoLists[field]; + const idx = targetList.findIndex((f) => f.uid === file.uid || f.url === file.url); + if (idx > -1) targetList.splice(idx, 1); + syncVideoFieldsToForm(); +}; + // 图片预览相关 const showImageViewer = ref(false); const imageViewerUrlList = ref([]); const imageViewerInitialIndex = ref(0); -// 照片预览回调 -const handlePreviewPhoto = (key) => { - // 根据key找到对应的图片URL - const urlMap = { - 'quarantineTickeyUrl': formData.quarantineTickeyUrl, - 'poundListImg': formData.poundListImg, - 'emptyVehicleFrontPhoto': formData.emptyVehicleFrontPhoto, - 'loadedVehicleFrontPhoto': formData.loadedVehicleFrontPhoto, - 'loadedVehicleWeightPhoto': formData.loadedVehicleWeightPhoto, - 'driverIdCardPhoto': formData.driverIdCardPhoto, - 'destinationPoundListImg': formData.destinationPoundListImg, - 'destinationVehicleFrontPhoto': formData.destinationVehicleFrontPhoto - }; - - const imageUrl = urlMap[key]; - if (imageUrl) { - imageViewerUrlList.value = [imageUrl]; - imageViewerInitialIndex.value = 0; +const handlePreviewPhoto = (field, file) => { + const urls = photoLists[field].map((f) => f.url).filter(Boolean); + if (urls.length > 0) { + imageViewerUrlList.value = urls; + if (file?.url) { + const idx = urls.indexOf(file.url); + imageViewerInitialIndex.value = idx > -1 ? idx : 0; + } else { + imageViewerInitialIndex.value = 0; + } showImageViewer.value = true; } }; -// 关闭图片预览 +// 视频预览(打开新窗口) +const handlePreviewVideo = (field, file) => { + const url = file?.url || videoLists[field]?.find((v) => v.url)?.url; + if (url) { + window.open(url, '_blank'); + } else { + ElMessage.warning('暂无可预览的视频'); + } +}; + const closeImageViewer = () => { showImageViewer.value = false; imageViewerUrlList.value = []; }; -// 视频移除回调 -const handleRemoveVideo = (key) => { - if (formData.hasOwnProperty(key)) { - formData[key] = ''; - ElMessage.success('已删除视频'); - } -}; - // 关闭弹窗 const handleClose = () => { // 清除防抖定时器,立即保存 @@ -2429,6 +2360,9 @@ const handleClose = () => { formData[key] = ''; } }); + // 重置文件列表 + photoFields.forEach((f) => (photoLists[f] = [])); + videoFields.forEach((f) => (videoLists[f] = [])); dialogVisible.value = false; showStartLocationMap.value = false; showEndLocationMap.value = false; diff --git a/pc-cattle-transportation/src/views/shipping/editDialog.vue b/pc-cattle-transportation/src/views/shipping/editDialog.vue index 2e8b981..4d6a7ed 100644 --- a/pc-cattle-transportation/src/views/shipping/editDialog.vue +++ b/pc-cattle-transportation/src/views/shipping/editDialog.vue @@ -164,9 +164,16 @@ - + - + @@ -287,7 +294,7 @@ const ruleForm = reactive({ buyerId: '', // 采购商 buyerPrice: '', // 采购单价 salePrice: '', // 销售单价 - firmPrice: '', // 约定单价 + firmPrice: null, // 约定单价 startLocation: '', // 起始地 startLat: '', startLon: '', diff --git a/pc-cattle-transportation/src/views/shipping/orderDialog.vue b/pc-cattle-transportation/src/views/shipping/orderDialog.vue index 6a097c8..de8ada6 100644 --- a/pc-cattle-transportation/src/views/shipping/orderDialog.vue +++ b/pc-cattle-transportation/src/views/shipping/orderDialog.vue @@ -72,9 +72,9 @@ @@ -728,36 +728,10 @@ const onShowDialog = async (orderData, deliveryId) => { // 设置deliveryId(如果提供) ruleForm.deliveryId = deliveryId || orderData?.deliveryId || null; - // 处理预付款自动填充 - // 1. 如果编辑订单,直接使用订单数据中的预付款 + // 处理预付款:编辑时使用订单值,新建时保持为空 if (orderData?.advancePayment != null) { ruleForm.advancePayment = orderData.advancePayment; - } - // 2. 如果是创建订单且有deliveryId,尝试从已关联的订单中获取预付款作为默认值 - else if (!ruleForm.id && ruleForm.deliveryId) { - try { - const res = await getOrdersByDeliveryId(ruleForm.deliveryId); - if (res.code === 200) { - const responseData = res.data || res; - let orders = []; - if (responseData && typeof responseData === 'object' && 'rows' in responseData) { - orders = responseData.rows || []; - } else if (responseData && responseData.data && 'rows' in responseData.data) { - orders = responseData.data.rows || []; - } - - // 如果有关联的订单,使用第一个订单的预付款作为默认值 - if (orders.length > 0 && orders[0].advancePayment != null) { - ruleForm.advancePayment = orders[0].advancePayment; - } - } - } catch (error) { - console.error('获取关联订单预付款失败:', error); - // 失败不影响继续操作,预付款保持为null - } - } - // 3. 其他情况,预付款为null - else { + } else { ruleForm.advancePayment = null; } diff --git a/pc-cattle-transportation/src/views/slaughter/entry.vue b/pc-cattle-transportation/src/views/slaughter/entry.vue new file mode 100644 index 0000000..79beeb9 --- /dev/null +++ b/pc-cattle-transportation/src/views/slaughter/entry.vue @@ -0,0 +1,534 @@ + + + + + + diff --git a/pc-cattle-transportation/src/views/slaughter/house.vue b/pc-cattle-transportation/src/views/slaughter/house.vue new file mode 100644 index 0000000..5f67603 --- /dev/null +++ b/pc-cattle-transportation/src/views/slaughter/house.vue @@ -0,0 +1,374 @@ + + + + + + diff --git a/pc-cattle-transportation/src/views/slaughter/record.vue b/pc-cattle-transportation/src/views/slaughter/record.vue new file mode 100644 index 0000000..41037d5 --- /dev/null +++ b/pc-cattle-transportation/src/views/slaughter/record.vue @@ -0,0 +1,364 @@ + + + + + + diff --git a/pc-cattle-transportation/src/views/userManage/driverDialog.vue b/pc-cattle-transportation/src/views/userManage/driverDialog.vue index 1e9dc53..5b70716 100644 --- a/pc-cattle-transportation/src/views/userManage/driverDialog.vue +++ b/pc-cattle-transportation/src/views/userManage/driverDialog.vue @@ -19,6 +19,11 @@ :file-list="ruleForm.driver_license" :headers="importHeaders" :on-exceed="() => handleExceed(2)" + @drop.native="(e) => handleDrop(e, 'driver_license', 2)" + @dragover.native.prevent + @dragenter.native="(e) => handleDragEnter(e)" + @dragleave.native="(e) => handleDragLeave(e)" + class="upload-drag-area" > @@ -35,6 +40,11 @@ :file-list="ruleForm.id_card" :headers="importHeaders" :on-exceed="() => handleExceed(2)" + @drop.native="(e) => handleDrop(e, 'id_card', 2)" + @dragover.native.prevent + @dragenter.native="(e) => handleDragEnter(e)" + @dragleave.native="(e) => handleDragLeave(e)" + class="upload-drag-area" > @@ -152,6 +162,105 @@ const handleExceed = (number) => { ElMessage.warning(`最多只能上传${number}张图片!`); }; +// 处理拖拽进入 +const handleDragEnter = (e) => { + e.preventDefault(); + e.stopPropagation(); + const target = e.currentTarget; + if (target) { + target.classList.add('drag-over'); + } +}; + +// 处理拖拽离开 +const handleDragLeave = (e) => { + e.preventDefault(); + e.stopPropagation(); + const target = e.currentTarget; + if (target) { + target.classList.remove('drag-over'); + } +}; + +// 处理拖拽上传 +const handleDrop = (e, type, limit = 2) => { + e.preventDefault(); + e.stopPropagation(); + // 移除拖拽样式 + const target = e.currentTarget; + if (target) { + target.classList.remove('drag-over'); + } + const files = Array.from(e.dataTransfer.files); + + // 过滤出图片文件 + const imageFiles = files.filter(file => file.type.startsWith('image/')); + + if (imageFiles.length === 0) { + ElMessage.error('请拖拽图片文件!'); + return; + } + + // 检查限制:只处理不超过限制的文件 + const currentCount = ruleForm[type].length; + const remainingSlots = limit - currentCount; + + if (remainingSlots <= 0) { + ElMessage.warning(`最多只能上传${limit}张图片!`); + return; + } + + // 只处理前 remainingSlots 个文件 + const filesToUpload = imageFiles.slice(0, remainingSlots); + + // 处理每个文件 + filesToUpload.forEach((file, index) => { + // 验证文件 + if (!beforeAvatarUpload(file)) { + return; + } + + // 创建 FormData 并上传 + const formData = new FormData(); + formData.append('file', file); + + // 使用 fetch 上传文件 + fetch('/api/common/upload', { + method: 'POST', + headers: { + 'Authorization': importHeaders.Authorization + }, + body: formData + }) + .then(response => response.json()) + .then(res => { + // 创建文件对象 + const fileObj = { + uid: Date.now() + Math.random() + index, + name: file.name, + status: 'success', + url: '' + }; + + // 获取当前文件列表并添加新文件 + const currentFileList = [...ruleForm[type]]; + currentFileList.push(fileObj); + + // 调用成功处理函数 + handleAvatarSuccess(res, fileObj, currentFileList, type); + }) + .catch(error => { + console.error('上传失败:', error); + ElMessage.error('上传失败,请稍后重试'); + }); + }); + + // 如果拖拽的文件超过限制,提示用户 + if (imageFiles.length > remainingSlots) { + ElMessage.warning(`只能上传${remainingSlots}张图片,已忽略多余的${imageFiles.length - remainingSlots}张`); + } +}; + const handlePreview = (file) => { data.dialogImageUrl = file.url; data.dialogVisibleImg = true; @@ -224,4 +333,39 @@ defineExpose({ onShowDialog }); display: flex; justify-content: flex-end; } + +/* 拖拽上传样式 */ +:deep(.upload-drag-area) { + position: relative; +} + +:deep(.upload-drag-area .el-upload--picture-card) { + border: 2px dashed #d9d9d9; + border-radius: 6px; + cursor: pointer; + position: relative; + overflow: hidden; + transition: all 0.3s; +} + +:deep(.upload-drag-area .el-upload--picture-card:hover) { + border-color: #409eff; + background-color: #f5f7fa; +} + +:deep(.upload-drag-area .el-upload-list--picture-card) { + display: inline-block; + vertical-align: top; +} + +/* 拖拽时的视觉反馈 */ +:deep(.upload-drag-area.drag-over .el-upload--picture-card) { + border-color: #409eff; + background-color: #ecf5ff; + border-style: solid; +} + +:deep(.upload-drag-area.drag-over .el-upload--picture-card .el-icon) { + color: #409eff; +} diff --git a/pc-cattle-transportation/src/views/userManage/vehicle.vue b/pc-cattle-transportation/src/views/userManage/vehicle.vue index 5236c25..394de79 100644 --- a/pc-cattle-transportation/src/views/userManage/vehicle.vue +++ b/pc-cattle-transportation/src/views/userManage/vehicle.vue @@ -97,6 +97,7 @@ + - + diff --git a/pc-cattle-transportation/src/views/warehouse/warehouseOutDialog.vue b/pc-cattle-transportation/src/views/warehouse/warehouseOutDialog.vue index b9da28a..04ed408 100644 --- a/pc-cattle-transportation/src/views/warehouse/warehouseOutDialog.vue +++ b/pc-cattle-transportation/src/views/warehouse/warehouseOutDialog.vue @@ -27,23 +27,120 @@ - - - - - + + + + + + +
+ + 添加进仓记录 + + + + + + + + + + + + + + + + + + + + + +
+ 总出仓数量:{{ getTotalOutCount() }} +
+
@@ -137,9 +234,13 @@ v-model="ruleForm.cattleCount" :min="1" :max="9999" - placeholder="请输入牛只数量" + placeholder="请输入牛只数量(自动计算)" style="width: 100%" + :disabled="true" /> +
+ 由关联的进仓记录自动计算 +
@@ -263,11 +364,11 @@