初步完善2.0

This commit is contained in:
xuqiuyun
2025-12-10 16:49:35 +08:00
parent 980ffb39b9
commit ec9061fd82
61 changed files with 2638 additions and 2323 deletions

View File

@@ -85,3 +85,42 @@ export function slaughterEntryDetail(id) {
});
}
// 屠宰记录
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',
});
}

View File

@@ -357,6 +357,16 @@ export const constantRoutes: Array<RouteRecordRaw> = [
},
component: () => import('~/views/slaughter/entry.vue'),
},
{
path: 'record',
name: 'record',
meta: {
title: '屠宰记录',
keepAlive: true,
requireAuth: true,
},
component: () => import('~/views/slaughter/record.vue'),
},
],
},
{

View File

@@ -904,6 +904,28 @@ const videoLists = reactive(
}, {})
);
// 字段名称到中文名称的映射(用于错误提示)
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挂学警港澳]$/;
@@ -1715,6 +1737,88 @@ 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);

View File

@@ -6,20 +6,28 @@
</div>
<div class="main-container">
<el-table :data="data.rows" border v-loading="data.loading" element-loading-text="数据加载中..." style="width: 100%">
<el-table-column label="进场编码" prop="entryCode" width="150" />
<el-table-column label="屠宰场" prop="slaughterHouseId" min-width="140">
<template #default="scope">
{{ houseNameMap[scope.row.slaughterHouseId] || '-' }}
</template>
</el-table-column>
<el-table-column label="运送清单ID" prop="deliveryId" width="120" />
<el-table-column label="订单ID" prop="orderId" width="120" />
<el-table-column label="运送清单" prop="deliveryNumber" width="150" />
<el-table-column label="订单" min-width="200">
<template #default="scope">
<div v-if="scope.row.orderBuyerName || scope.row.orderSellerName">
<div v-if="scope.row.orderBuyerName">买方{{ scope.row.orderBuyerName }}</div>
<div v-if="scope.row.orderSellerName">卖方{{ scope.row.orderSellerName }}</div>
</div>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="起始地" prop="startLocation" min-width="150" />
<el-table-column label="目的地" prop="endLocation" min-width="150" />
<el-table-column label="司机" prop="driverName" width="120" />
<el-table-column label="联系方式" prop="driverMobile" width="140" />
<el-table-column label="车牌号" prop="licensePlate" width="120" />
<el-table-column label="出肉率(%)" prop="yieldRate" width="110" />
<el-table-column label="创建时间" prop="create_time" width="180" />
<el-table-column label="创建时间" prop="createTime" width="180" />
<el-table-column label="操作" width="200">
<template #default="scope">
<el-button link type="primary" @click="openDialog(scope.row)">编辑</el-button>
@@ -35,16 +43,51 @@
<el-dialog v-model="dialog.visible" :title="dialog.title" width="600px" :close-on-click-modal="false">
<el-form ref="dialogFormRef" :model="dialog.form" :rules="dialog.rules" label-width="120px">
<el-form-item label="进场编码" prop="entryCode">
<el-input
v-model="dialog.form.entryCode"
placeholder="请输入进场编码或点击自动生成"
style="width: calc(100% - 100px); margin-right: 10px;"
/>
<el-button type="primary" @click="generateEntryCode">自动生成</el-button>
</el-form-item>
<el-form-item label="屠宰场" prop="slaughterHouseId">
<el-select v-model="dialog.form.slaughterHouseId" placeholder="请选择屠宰场" filterable style="width: 100%">
<el-option v-for="item in houseOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="运送清单ID" prop="deliveryId">
<el-input v-model.number="dialog.form.deliveryId" placeholder="请输入运送清单ID" />
<el-form-item label="运送清单" prop="deliveryId">
<el-select
v-model="dialog.form.deliveryId"
placeholder="请选择运送清单"
clearable
filterable
style="width: 100%"
@change="handleDeliveryChange"
>
<el-option
v-for="item in deliveryList"
:key="item.id"
:label="`${item.deliveryNumber || '--'} - ${item.licensePlate || '--'}`"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="订单ID" prop="orderId">
<el-input v-model.number="dialog.form.orderId" placeholder="请输入订单ID可选" />
<el-form-item label="订单" prop="orderId">
<el-select
v-model="dialog.form.orderId"
placeholder="请选择订单(可选)"
clearable
filterable
style="width: 100%"
>
<el-option
v-for="item in orderList"
:key="item.id"
:label="`订单${item.id} -买方${item.buyerName}-卖方${item.sellerName}- 单价: ${item.firmPrice || '--'}元/斤`"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-row :gutter="12">
<el-col :span="12">
@@ -94,15 +137,30 @@
</el-form-item>
</el-col>
</el-row>
<el-form-item label="车牌号" prop="licensePlate">
<el-input v-model="dialog.form.licensePlate" placeholder="请输入车牌号" />
</el-form-item>
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="车牌号" prop="licensePlate">
<el-input v-model="dialog.form.licensePlate" placeholder="请输入车牌号" />
<el-form-item label="牛只数量" prop="capacity">
<el-input-number
v-model="dialog.form.capacity"
:min="0"
:max="9999"
placeholder="请输入牛只数量"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="出肉率(%)" prop="yieldRate">
<el-input-number v-model="dialog.form.yieldRate" :min="0" :max="100" :step="0.1" style="width: 100%" />
<el-form-item label="剩余牛只数量" prop="remainingCapacity">
<el-input-number
v-model="dialog.form.remainingCapacity"
:min="0"
:max="9999"
placeholder="请输入剩余牛只数量"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
@@ -128,37 +186,42 @@ import {
slaughterEntryEdit,
slaughterEntryDel,
} from '@/api/slaughter.js';
import { slaughterHouseAll } from '@/api/slaughter.js';
import { slaughterHouseAll, slaughterHouseList } from '@/api/slaughter.js';
import { shippingList, orderPageQuery } from '@/api/shipping.js';
const baseSearchRef = ref();
const dialogFormRef = ref();
const houseOptions = ref([]);
const houseNameMap = reactive({});
const deliveryList = ref([]);
const orderList = ref([]);
const formItemList = reactive([
{
label: '屠宰场',
param: 'slaughterHouseId',
type: 'select',
options: [],
selectOptions: [],
placeholder: '请选择屠宰场',
span: 8,
labelWidth: 100,
labelKey: 'label',
valueKey: 'value',
},
{
label: '运送清单ID',
param: 'deliveryId',
label: '运送清单',
param: 'deliveryNumber',
type: 'input',
placeholder: '请输入运送清单ID',
placeholder: '请输入运送清单',
span: 8,
labelWidth: 100,
},
{
label: '订单ID',
param: 'orderId',
label: '订单',
param: 'orderBuyerName'||'orderSellerName',
type: 'input',
placeholder: '请输入订单ID',
placeholder: '请输入买方或卖方公司名称',
span: 8,
labelWidth: 100,
},
@@ -180,6 +243,7 @@ const dialog = reactive({
title: '新增进场',
form: {
id: null,
entryCode: '',
slaughterHouseId: null,
deliveryId: null,
orderId: null,
@@ -192,18 +256,20 @@ const dialog = reactive({
driverName: '',
driverMobile: '',
licensePlate: '',
yieldRate: null,
capacity: null,
remainingCapacity: null,
remark: '',
},
rules: {
slaughterHouseId: [{ required: true, message: '请选择屠宰场', trigger: 'change' }],
deliveryId: [{ required: true, message: '请输入运送清单ID', trigger: 'blur' }],
deliveryId: [{ required: true, message: '请选择运送清单', trigger: 'change' }],
},
});
const resetDialogForm = () => {
dialog.form = {
id: null,
entryCode: '',
slaughterHouseId: null,
deliveryId: null,
orderId: null,
@@ -216,21 +282,28 @@ const resetDialogForm = () => {
driverName: '',
driverMobile: '',
licensePlate: '',
yieldRate: null,
capacity: null,
remainingCapacity: null,
remark: '',
};
dialog.title = '新增进场';
};
const loadHouseOptions = () => {
slaughterHouseAll()
// 调用列表接口,支持模糊查询
slaughterHouseList({ pageNum: 1, pageSize: 1000 })
.then((res) => {
const options = (res.data || []).map((item) => ({
let responseData = res.data;
if (responseData && responseData.data && typeof responseData.data === 'object') {
responseData = responseData.data;
}
const list = responseData?.rows || responseData || [];
const options = list.map((item) => ({
label: item.house_name || item.houseName,
value: item.id,
}));
houseOptions.value = options;
formItemList[0].options = options;
formItemList[0].selectOptions = options;
Object.keys(houseNameMap).forEach((key) => delete houseNameMap[key]);
options.forEach((opt) => {
houseNameMap[opt.value] = opt.label;
@@ -246,8 +319,14 @@ const getList = () => {
const params = { ...form, ...baseSearchRef.value.penetrateParams() };
slaughterEntryList(params)
.then((res) => {
data.rows = res.data.rows || [];
data.total = res.data.total || 0;
// 处理嵌套的数据结构res.data.data.rows 或 res.data.rows
let responseData = res.data;
if (responseData && responseData.data && typeof responseData.data === 'object') {
// 嵌套结构res.data.data
responseData = responseData.data;
}
data.rows = responseData?.rows || [];
data.total = responseData?.total || 0;
})
.catch((err) => {
console.error(err);
@@ -263,11 +342,137 @@ const handleSearch = () => {
getList();
};
// 加载订单列表
const loadOrderList = async () => {
try {
const res = await orderPageQuery({ pageNum: 1, pageSize: 1000 });
if (res.code === 200) {
const responseData = res.data || res;
if (responseData && typeof responseData === 'object' && 'rows' in responseData) {
orderList.value = responseData.rows || [];
} else if (responseData && responseData.data && 'rows' in responseData.data) {
orderList.value = responseData.data.rows || [];
} else {
orderList.value = [];
}
}
} catch (error) {
console.error('加载订单列表失败', error);
}
};
// 加载运送清单列表
const loadDeliveryList = async () => {
try {
const res = await shippingList({ pageNum: 1, pageSize: 1000 });
if (res.code === 200) {
const responseData = res.data || res;
if (responseData && typeof responseData === 'object' && 'rows' in responseData) {
deliveryList.value = responseData.rows || [];
} else if (responseData && responseData.data && 'rows' in responseData.data) {
deliveryList.value = responseData.data.rows || [];
} else {
deliveryList.value = [];
}
}
} catch (error) {
console.error('加载运送清单列表失败', error);
}
};
// 运送清单选择变化
const handleDeliveryChange = (deliveryId) => {
if (!deliveryId) {
// 清空相关字段
dialog.form.startLocation = '';
dialog.form.startLon = '';
dialog.form.startLat = '';
dialog.form.endLocation = '';
dialog.form.endLon = '';
dialog.form.endLat = '';
dialog.form.driverName = '';
dialog.form.driverMobile = '';
dialog.form.licensePlate = '';
dialog.form.capacity = null;
dialog.form.remainingCapacity = null;
return;
}
// 从 deliveryList 中找到对应的运送清单
const selectedDelivery = deliveryList.value.find(item => item.id === deliveryId);
if (selectedDelivery) {
// 自动填充起始地信息
if (selectedDelivery.startLocation) {
dialog.form.startLocation = selectedDelivery.startLocation;
}
if (selectedDelivery.startLon) {
dialog.form.startLon = selectedDelivery.startLon;
}
if (selectedDelivery.startLat) {
dialog.form.startLat = selectedDelivery.startLat;
}
// 自动填充目的地信息
if (selectedDelivery.endLocation) {
dialog.form.endLocation = selectedDelivery.endLocation;
}
if (selectedDelivery.endLon) {
dialog.form.endLon = selectedDelivery.endLon;
}
if (selectedDelivery.endLat) {
dialog.form.endLat = selectedDelivery.endLat;
}
// 自动填充司机信息
if (selectedDelivery.driverName) {
dialog.form.driverName = selectedDelivery.driverName;
}
// 兼容 driverPhone 和 driverMobile 两种字段名
if (selectedDelivery.driverPhone || selectedDelivery.driverMobile) {
dialog.form.driverMobile = selectedDelivery.driverPhone || selectedDelivery.driverMobile;
}
// 自动填充车牌号
if (selectedDelivery.licensePlate) {
dialog.form.licensePlate = selectedDelivery.licensePlate;
}
// 自动填充牛只数量(从运送清单的 ratedQuantity 填充)
if (selectedDelivery.ratedQuantity) {
dialog.form.capacity = selectedDelivery.ratedQuantity;
// 默认剩余牛只数量等于牛只数量
dialog.form.remainingCapacity = selectedDelivery.ratedQuantity;
}
}
};
// 自动生成进场编码
const generateEntryCode = () => {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const dateStr = `${year}${month}${day}`;
// 生成一个简单的编码SE + yyyyMMdd + 时间戳后4位
const timestamp = Date.now().toString();
const seq = timestamp.slice(-4);
dialog.form.entryCode = `SE${dateStr}${seq}`;
};
const openDialog = (row) => {
resetDialogForm();
// 加载运送清单列表和订单列表
loadDeliveryList();
loadOrderList();
if (row) {
dialog.title = '编辑进场';
Object.assign(dialog.form, row);
} else {
// 新增时,如果没有编码则自动生成
if (!dialog.form.entryCode) {
generateEntryCode();
}
}
dialog.visible = true;
};

View File

@@ -137,9 +137,9 @@ const formItemList = reactive([
label: '状态',
param: 'status',
type: 'select',
status: [
{ label: '启用', value: 1 },
{ label: '停用', value: 0 },
selectOptions: [
{ value: 1,text: '启用' },
{ value: 0 ,text: '停用' },
],
placeholder: '请选择状态',
span: 8,

View File

@@ -0,0 +1,364 @@
<template>
<div class="page-container">
<base-search :formItemList="formItemList" @search="handleSearch" ref="baseSearchRef" />
<div class="action-bar">
<el-button type="primary" @click="openDialog()">新增屠宰记录</el-button>
</div>
<div class="main-container">
<el-table :data="data.rows" border v-loading="data.loading" element-loading-text="数据加载中..." style="width: 100%">
<el-table-column label="屠宰记录编码" prop="recordCode" min-width="140" />
<el-table-column label="进场编码" prop="entryCode" min-width="140" />
<el-table-column label="屠宰场" prop="slaughterHouseName" min-width="140">
<template #default="scope">
{{ scope.row.slaughterHouseName || '-' }}
</template>
</el-table-column>
<el-table-column label="屠宰头数" prop="slaughterCount" width="120" />
<el-table-column label="出肉率(%)" prop="yieldRate" width="120">
<template #default="scope">
<span v-if="scope.row.yieldRate !== null && scope.row.yieldRate !== undefined">
{{ scope.row.yieldRate }}%
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="进场剩余头数" prop="remainingCapacity" width="140">
<template #default="scope">
{{ scope.row.remainingCapacity ?? '-' }}
</template>
</el-table-column>
<el-table-column label="创建时间" prop="createTime" width="180" />
<el-table-column label="操作" width="180">
<template #default="scope">
<el-button link type="primary" @click="openDialog(scope.row)">编辑</el-button>
<el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
<template #empty>
<div class="dataListOnEmpty">暂无数据</div>
</template>
</el-table>
<pagination v-model:limit="form.pageSize" v-model:page="form.pageNum" :total="data.total" @pagination="getList" />
</div>
<el-dialog v-model="dialog.visible" :title="dialog.title" width="520px" :close-on-click-modal="false">
<el-form ref="dialogFormRef" :model="dialog.form" :rules="dialog.rules" label-width="120px">
<el-form-item label="屠宰记录编码" prop="recordCode">
<el-input
v-model="dialog.form.recordCode"
placeholder="请输入屠宰记录编码或点击自动生成"
style="width: calc(100% - 100px); margin-right: 10px;"
/>
<el-button type="primary" @click="generateRecordCode">自动生成</el-button>
</el-form-item>
<el-form-item label="进场记录" prop="entryId">
<el-select
v-model="dialog.form.entryId"
placeholder="请选择进场记录"
filterable
style="width: 100%"
@change="handleEntryChange"
>
<el-option
v-for="item in entryOptions"
:key="item.id"
:label="`${item.entryCode || '--'}(剩余${item.remainingCapacity ?? '--'}头)`"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="屠宰头数" prop="slaughterCount">
<el-input-number
v-model="dialog.form.slaughterCount"
:min="1"
:max="999999"
placeholder="请输入屠宰头数"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="出肉率(%)" prop="yieldRate">
<el-input-number
v-model="dialog.form.yieldRate"
:min="0"
:max="100"
:step="0.1"
placeholder="请输入出肉率(0-100)"
style="width: 100%"
/>
</el-form-item>
<el-form-item>
<el-alert
type="info"
:closable="false"
show-icon
title="选中的进场记录"
description="进场编码:{{ dialog.state.entryCode || '--' }},当前剩余头数:{{ dialog.state.remaining ?? '--' }}"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialog.visible = false">取消</el-button>
<el-button type="primary" @click="submitDialog">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { reactive, ref, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import baseSearch from '@/components/common/searchCustom/index.vue';
import {
slaughterRecordList,
slaughterRecordAdd,
slaughterRecordEdit,
slaughterRecordDel,
slaughterRecordDetail,
slaughterEntryList,
slaughterHouseList,
} from '@/api/slaughter.js';
const baseSearchRef = ref();
const dialogFormRef = ref();
const houseOptions = ref([]);
const entryOptions = ref([]);
const entryMap = reactive({});
const formItemList = reactive([
{
label: '屠宰场',
param: 'slaughterHouseId',
type: 'select',
selectOptions: [],
placeholder: '请选择屠宰场',
span: 8,
labelWidth: 100,
labelKey: 'label',
valueKey: 'value',
},
{
label: '屠宰记录编码',
param: 'recordCode',
type: 'input',
placeholder: '请输入屠宰记录编码',
span: 8,
labelWidth: 120,
},
{
label: '进场编码',
param: 'entryCode',
type: 'input',
placeholder: '请输入进场编码',
span: 8,
labelWidth: 100,
},
]);
const data = reactive({
rows: [],
total: 0,
loading: false,
});
const form = reactive({
pageNum: 1,
pageSize: 10,
});
const dialog = reactive({
visible: false,
title: '新增屠宰记录',
form: {
id: null,
recordCode: '',
entryId: null,
slaughterCount: 1,
yieldRate: null,
},
state: {
remaining: null,
entryCode: '',
},
rules: {
entryId: [{ required: true, message: '请选择进场记录', trigger: 'change' }],
slaughterCount: [{ required: true, message: '请输入屠宰头数', trigger: 'blur' }],
},
});
const resetDialogForm = () => {
dialog.form = {
id: null,
recordCode: '',
entryId: null,
slaughterCount: 1,
yieldRate: null,
};
dialog.state = {
remaining: null,
entryCode: '',
};
dialog.title = '新增屠宰记录';
};
const loadHouseOptions = () => {
slaughterHouseList({ pageNum: 1, pageSize: 1000 })
.then((res) => {
let responseData = res.data;
if (responseData && responseData.data && typeof responseData.data === 'object') {
responseData = responseData.data;
}
const list = responseData?.rows || responseData || [];
const options = list.map((item) => ({
label: item.house_name || item.houseName,
value: item.id,
}));
houseOptions.value = options;
formItemList[0].selectOptions = options;
})
.catch((err) => console.error(err));
};
const loadEntryOptions = () => {
slaughterEntryList({ pageNum: 1, pageSize: 1000 })
.then((res) => {
let responseData = res.data;
if (responseData && responseData.data && typeof responseData.data === 'object') {
responseData = responseData.data;
}
const list = responseData?.rows || responseData || [];
entryOptions.value = list;
Object.keys(entryMap).forEach((key) => delete entryMap[key]);
list.forEach((item) => {
entryMap[item.id] = {
entryCode: item.entryCode,
remainingCapacity: item.remainingCapacity,
};
});
})
.catch((err) => console.error(err));
};
const getList = () => {
data.loading = true;
const params = { ...form, ...baseSearchRef.value.penetrateParams() };
slaughterRecordList(params)
.then((res) => {
let responseData = res.data;
if (responseData && responseData.data && typeof responseData.data === 'object') {
responseData = responseData.data;
}
data.rows = responseData?.rows || [];
data.total = responseData?.total || 0;
})
.catch((err) => {
console.error(err);
ElMessage.error('查询失败');
})
.finally(() => {
data.loading = false;
});
};
const handleSearch = () => {
form.pageNum = 1;
getList();
};
const generateRecordCode = () => {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const dateStr = `${year}${month}${day}`;
const seq = Date.now().toString().slice(-3);
dialog.form.recordCode = `SL${dateStr}${seq}`;
};
const handleEntryChange = (entryId) => {
if (!entryId) {
dialog.state.remaining = null;
dialog.state.entryCode = '';
return;
}
const info = entryMap[entryId];
dialog.state.remaining = info?.remainingCapacity ?? null;
dialog.state.entryCode = info?.entryCode || '';
};
const openDialog = (row) => {
resetDialogForm();
loadEntryOptions();
if (row) {
dialog.title = '编辑屠宰记录';
// 重新获取详情,保证剩余头数为最新
slaughterRecordDetail(row.id)
.then((res) => {
const responseData = res.data?.data || res.data || {};
Object.assign(dialog.form, responseData);
dialog.state.entryCode = responseData.entryCode;
// 可用剩余 = 后端剩余 + 当前记录屠宰头数
const remaining = (responseData.remainingCapacity ?? 0) + (responseData.slaughterCount ?? 0);
dialog.state.remaining = remaining;
})
.catch((err) => console.error(err));
} else {
if (!dialog.form.recordCode) {
generateRecordCode();
}
}
dialog.visible = true;
};
const submitDialog = () => {
dialogFormRef.value?.validate((valid) => {
if (!valid) return;
const payload = { ...dialog.form };
const api = payload.id ? slaughterRecordEdit : slaughterRecordAdd;
api(payload)
.then(() => {
ElMessage.success(payload.id ? '更新成功' : '新增成功');
dialog.visible = false;
getList();
loadEntryOptions();
})
.catch((err) => {
console.error(err);
ElMessage.error(payload.id ? '更新失败' : '新增失败');
});
});
};
const handleDelete = (row) => {
ElMessageBox.confirm('确认删除该屠宰记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => slaughterRecordDel(row.id))
.then(() => {
ElMessage.success('删除成功');
getList();
loadEntryOptions();
})
.catch((err) => {
if (err !== 'cancel') {
console.error(err);
ElMessage.error('删除失败');
}
});
};
onMounted(() => {
loadHouseOptions();
loadEntryOptions();
getList();
});
</script>
<style scoped>
.action-bar {
margin-bottom: 16px;
}
</style>

View File

@@ -62,7 +62,7 @@
<el-option
v-for="item in orderList"
:key="item.id"
:label="`订单${item.id} - 单价: ${item.firmPrice || '--'}元/斤`"
:label="`订单${item.id} -买方${item.buyerName}-卖方${item.sellerName}- 单价: ${item.firmPrice || '--'}元/斤`"
:value="item.id"
/>
</el-select>

View File

@@ -35,9 +35,23 @@
{{ scope.row.warehouseName || '--' }}
</template>
</el-table-column>
<el-table-column label="进仓单号" prop="warehouseInNumber" min-width="150" width="180">
<el-table-column label="关联进仓记录" prop="warehouseInRelations" min-width="200" width="250">
<template #default="scope">
{{ scope.row.warehouseInNumber || '--' }}
<div v-if="scope.row.warehouseInRelations && scope.row.warehouseInRelations.length > 0">
<el-tag
v-for="(relation, index) in scope.row.warehouseInRelations"
:key="index"
type="info"
size="small"
style="margin-right: 5px; margin-bottom: 5px;"
>
{{ relation.warehouseInNumber || '--' }} ({{ relation.outCount || 0 }})
</el-tag>
</div>
<span v-else-if="scope.row.warehouseInNumber">
{{ scope.row.warehouseInNumber || '--' }}
</span>
<span v-else>--</span>
</template>
</el-table-column>
<el-table-column label="运单号" prop="deliveryNumber" min-width="120" width="150">

View File

@@ -27,23 +27,120 @@
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="进仓记录" prop="warehouseInId">
<el-select
v-model="ruleForm.warehouseInId"
placeholder="请选择进仓记录(可选)"
clearable
filterable
style="width: 100%"
@change="handleWarehouseInChange"
>
<el-option
v-for="item in warehouseInList"
:key="item.id"
:label="`${item.inNumber || '--'} - ${item.cattleCount || 0}头`"
:value="item.id"
/>
</el-select>
</el-row>
<!-- 进仓记录关联表格 -->
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="关联进仓记录" prop="warehouseInRelations">
<div style="width: 100%">
<el-button
type="primary"
size="small"
@click="addWarehouseInRelation"
:disabled="data.isDetail || !ruleForm.warehouseId"
style="margin-bottom: 10px"
>
添加进仓记录
</el-button>
<el-table
:data="ruleForm.warehouseInRelations"
border
style="width: 100%"
:max-height="300"
:key="`warehouse-in-table-${warehouseInList.length}`"
>
<el-table-column label="进仓单号" min-width="150">
<template #default="scope">
<el-select
v-model="scope.row.warehouseInId"
placeholder="请选择进仓记录"
filterable
clearable
style="width: 100%"
:disabled="data.isDetail"
:loading="warehouseInList.length === 0 && ruleForm.warehouseId"
@change="handleWarehouseInRelationChange(scope.row, scope.$index)"
>
<el-option
v-for="item in getAvailableWarehouseInList(scope.$index)"
:key="`option-${item.id}-${item.remainingCount}`"
:label="`${item.inNumber || '--'} (总:${item.cattleCount || 0}头, 剩余:${item.remainingCount || 0}头)`"
:value="item.id"
>
{{ item.inNumber || '--' }} (:{{ item.cattleCount || 0 }}, 剩余:{{ item.remainingCount || 0 }})
</el-option>
<el-option
v-if="getAvailableWarehouseInList(scope.$index).length === 0 && warehouseInList.length === 0 && ruleForm.warehouseId && !data.isDetail"
disabled
value=""
>
正在加载进仓记录...
</el-option>
<el-option
v-if="getAvailableWarehouseInList(scope.$index).length === 0 && warehouseInList.length > 0 && !data.isDetail"
disabled
value=""
>
暂无可用进仓记录剩余数量为0或已被选择
</el-option>
<el-option
v-if="getAvailableWarehouseInList(scope.$index).length === 0 && !ruleForm.warehouseId && !data.isDetail"
disabled
value=""
>
请先选择中转仓
</el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="进仓总数量" prop="totalCount" min-width="100" align="center">
<template #default="scope">
{{ scope.row.totalCount || '--' }}
</template>
</el-table-column>
<el-table-column label="已出仓数量" prop="outCount" min-width="100" align="center">
<template #default="scope">
{{ scope.row.outCount || 0 }}
</template>
</el-table-column>
<el-table-column label="剩余数量" prop="remainingCount" min-width="100" align="center">
<template #default="scope">
<span :style="{ color: (scope.row.remainingCount || 0) <= 0 ? 'red' : 'green' }">
{{ scope.row.remainingCount || 0 }}
</span>
</template>
</el-table-column>
<el-table-column label="本次出仓数量" prop="outCountThisTime" min-width="150">
<template #default="scope">
<el-input-number
:model-value="scope.row.outCountThisTime || null"
@update:model-value="(val) => { scope.row.outCountThisTime = val || undefined; handleOutCountChange(); }"
:min="Math.min(1, scope.row.remainingCount || 0)"
:max="Math.max(scope.row.remainingCount || 0, 0)"
:disabled="data.isDetail || !scope.row.warehouseInId || (scope.row.remainingCount || 0) <= 0"
placeholder="请输入数量"
style="width: 100%"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="100" align="center" v-if="!data.isDetail">
<template #default="scope">
<el-button
link
type="danger"
@click="removeWarehouseInRelation(scope.$index)"
:disabled="!scope.row.warehouseInId"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div style="margin-top: 10px; color: #666; font-size: 12px;">
<span>总出仓数量:<strong style="color: #409EFF;">{{ getTotalOutCount() }}</strong> 头</span>
</div>
</div>
</el-form-item>
</el-col>
</el-row>
@@ -137,9 +234,13 @@
v-model="ruleForm.cattleCount"
:min="1"
:max="9999"
placeholder="请输入牛只数量"
placeholder="请输入牛只数量自动计算"
style="width: 100%"
:disabled="true"
/>
<div style="color: #909399; font-size: 12px; margin-top: 5px;">
由关联的进仓记录自动计算
</div>
</el-form-item>
</el-col>
<el-col :span="12">
@@ -263,11 +364,11 @@
</template>
<script setup>
import { ref, reactive, watch } from 'vue';
import { ref, reactive, watch, nextTick, computed } from 'vue';
import { ElMessage } from 'element-plus';
import { Plus } from '@element-plus/icons-vue';
import { BaiduMap, BmMapType, BmMarker } from 'vue-baidu-map-3x';
import { warehouseOutAdd, warehouseOutEdit, warehouseOutDetail } from '@/api/warehouseOut.js';
import { warehouseOutAdd, warehouseOutEdit, warehouseOutDetail, warehouseOutList } from '@/api/warehouseOut.js';
import { warehouseAll } from '@/api/warehouse.js';
import { warehouseInList as warehouseInListApi } from '@/api/warehouseIn.js';
import { orderPageQuery, shippingList } from '@/api/shipping.js';
@@ -281,7 +382,7 @@ const photoFileList = ref([]);
const videoFileList = ref([]);
const warehouseList = ref([]);
const warehouseInList = ref([]);
const warehouseInList = ref([]); // 所有可用的进仓记录
const orderList = ref([]);
const deliveryList = ref([]);
@@ -295,7 +396,8 @@ const data = reactive({
const ruleForm = reactive({
id: null,
warehouseId: null,
warehouseInId: null,
warehouseInId: null, // 保留用于兼容,但不再使用
warehouseInRelations: [], // 进仓记录关联数组每个元素包含warehouseInId, totalCount, outCount, remainingCount, outCountThisTime
orderId: [],
deliveryId: null,
destinationLocation: '',
@@ -317,6 +419,30 @@ const rules = reactive({
deliveryId: [
{ required: true, message: '请选择运送清单', trigger: 'change' },
],
warehouseInRelations: [
{
validator: (rule, value, callback) => {
if (!value || value.length === 0) {
callback(new Error('请至少添加一个进仓记录'));
} else {
// 检查每个关联记录是否都填写了出仓数量
const hasEmpty = value.some(item => !item.warehouseInId || !item.outCountThisTime || item.outCountThisTime <= 0);
if (hasEmpty) {
callback(new Error('请为每个进仓记录填写出仓数量'));
} else {
// 检查总出仓数量
const totalOutCount = value.reduce((sum, item) => sum + (item.outCountThisTime || 0), 0);
if (totalOutCount <= 0) {
callback(new Error('总出仓数量必须大于0'));
} else {
callback();
}
}
}
},
trigger: 'change'
},
],
cattleCount: [
{ required: true, message: '请输入牛只数量', trigger: 'blur' },
{ type: 'number', min: 1, message: '牛只数量必须大于0', trigger: 'blur' },
@@ -341,23 +467,119 @@ const loadWarehouseList = async () => {
// 加载进仓记录列表(不限制状态,避免过滤掉待进仓记录)
const loadWarehouseInList = async (warehouseId) => {
try {
console.log('开始加载进仓记录列表warehouseId:', warehouseId);
const params = { pageNum: 1, pageSize: 1000 };
if (warehouseId) {
params.warehouseId = warehouseId;
}
console.log('调用接口参数:', params);
const res = await warehouseInListApi(params);
console.log('进仓记录接口返回:', res);
if (res.code === 200) {
const responseData = res.data || res;
let list = [];
if (responseData && typeof responseData === 'object' && 'rows' in responseData) {
warehouseInList.value = responseData.rows || [];
list = responseData.rows || [];
} else if (responseData && responseData.data && 'rows' in responseData.data) {
warehouseInList.value = responseData.data.rows || [];
} else {
warehouseInList.value = [];
list = responseData.data.rows || [];
} else if (Array.isArray(responseData)) {
list = responseData;
}
console.log('解析后的进仓记录列表:', list);
// 直接使用数据库返回的 remainingCount 字段并过滤掉剩余数量为0的记录
// 在编辑模式下,需要将当前编辑的出仓记录关联的进仓记录的剩余数量加回去
const currentEditId = data.editId;
console.log('当前编辑的出仓记录ID:', currentEditId);
// 如果正在编辑,需要获取当前编辑的出仓记录详情,以便恢复已关联进仓记录的剩余数量
let currentEditOutRelations = new Map(); // key: 进仓记录ID, value: 当前编辑的出仓数量
if (currentEditId && ruleForm.warehouseInRelations && ruleForm.warehouseInRelations.length > 0) {
// 从表单中获取当前编辑的出仓记录关联的进仓记录和出仓数量
ruleForm.warehouseInRelations.forEach(rel => {
if (rel.warehouseInId && rel.outCountThisTime) {
const currentCount = currentEditOutRelations.get(rel.warehouseInId) || 0;
currentEditOutRelations.set(rel.warehouseInId, currentCount + rel.outCountThisTime);
}
});
console.log('当前编辑的出仓记录关联的进仓记录和数量:', Array.from(currentEditOutRelations.entries()));
}
// 处理进仓记录列表,使用数据库的 remainingCount 字段
// 如果 remainingCount 字段不存在(数据库未迁移),则使用 cattleCount 作为默认值
const processedList = list.map(item => {
// 使用数据库返回的 remainingCount如果没有则使用 cattleCount兼容旧数据
const totalCount = item.cattleCount || 0;
let remainingCount = item.remainingCount;
// 如果 remainingCount 为 undefined 或 null说明数据库字段可能不存在使用 cattleCount
if (remainingCount === undefined || remainingCount === null) {
remainingCount = totalCount;
console.warn(`进仓记录 ${item.inNumber} 没有 remainingCount 字段,使用 cattleCount: ${totalCount}`);
}
// 在编辑模式下,如果这个进仓记录已经被当前编辑的出仓记录关联,需要将当前编辑的出仓数量加回去
// 这样用户就可以编辑到进仓总数量(如果没有其他出仓记录关联的话)
if (currentEditId && currentEditOutRelations.has(item.id)) {
const currentEditOutCount = currentEditOutRelations.get(item.id) || 0;
remainingCount = remainingCount + currentEditOutCount;
console.log(`编辑模式 - 进仓记录 ${item.inNumber}: 恢复当前编辑的出仓数量 ${currentEditOutCount},剩余数量从 ${item.remainingCount} 调整为 ${remainingCount}`);
}
const outCount = totalCount - remainingCount;
console.log(`处理进仓记录 ${item.inNumber}: totalCount=${totalCount}, outCount=${outCount}, remainingCount=${remainingCount}`);
return {
...item,
outCount: Math.max(0, outCount),
remainingCount: Math.max(0, remainingCount),
// 标记是否为编辑模式下已关联的记录
isCurrentEditRelated: currentEditId && currentEditOutRelations.has(item.id),
};
});
// 在编辑模式下需要保留当前已选择的进仓记录即使剩余数量为0
// 这样用户可以看到已选择的记录但无法选择新的剩余数量为0的记录
const currentSelectedIds = new Set();
if (data.editId && ruleForm.warehouseInRelations) {
ruleForm.warehouseInRelations.forEach(rel => {
if (rel.warehouseInId) {
currentSelectedIds.add(rel.warehouseInId);
}
});
}
// 过滤进仓记录列表
// 1. 保留当前已选择的记录(编辑模式下,不受剩余数量限制)
// 2. 过滤掉剩余数量为0的记录新增模式下或未选择的记录
warehouseInList.value = processedList.filter(item => {
// 如果是当前已选择的记录,保留(编辑模式下需要显示,不受剩余数量限制)
if (currentSelectedIds.has(item.id)) {
return true;
}
// 否则,只显示剩余数量 > 0 的记录
return (item.remainingCount || 0) > 0;
});
console.log('处理后的进仓记录列表:', warehouseInList.value);
console.log('当前已选择的进仓记录ID:', Array.from(currentSelectedIds));
// 强制触发响应式更新
nextTick(() => {
console.log('nextTick - 进仓记录列表已更新,长度:', warehouseInList.value.length);
});
} else {
console.error('加载进仓记录列表失败,返回码:', res.code, '错误信息:', res.msg);
ElMessage.warning('加载进仓记录列表失败:' + (res.msg || '未知错误'));
warehouseInList.value = [];
}
} catch (error) {
console.error('加载进仓记录列表失败', error);
console.error('加载进仓记录列表异常:', error);
ElMessage.error('加载进仓记录列表失败:' + (error.message || '未知错误'));
warehouseInList.value = [];
}
};
@@ -407,6 +629,7 @@ const handleClose = () => {
id: null,
warehouseId: null,
warehouseInId: null,
warehouseInRelations: [],
orderId: [],
deliveryId: null,
destinationLocation: '',
@@ -448,9 +671,47 @@ const onClickSave = () => {
const photosStr = photoFileList.value.map(file => file.url || file.response?.data?.url || '').filter(url => url).join(',');
const videosStr = videoFileList.value.map(file => file.url || file.response?.data?.url || '').filter(url => url).join(',');
// 处理进仓记录关联(多对多关系)
console.log('========== 提交前数据检查 ==========');
console.log('ruleForm.warehouseInRelations:', ruleForm.warehouseInRelations);
console.log('ruleForm.warehouseInId:', ruleForm.warehouseInId);
const warehouseInRelations = ruleForm.warehouseInRelations
.filter(item => item.warehouseInId && item.outCountThisTime > 0)
.map(item => ({
warehouseInId: item.warehouseInId,
outCount: item.outCountThisTime,
}));
console.log('过滤后的 warehouseInRelations:', warehouseInRelations);
// 如果有关联的进仓记录将ID用逗号分隔设置到 warehouseInId用于兼容旧数据格式
// 如果只有一个,直接设置;如果有多个,用逗号分隔
let warehouseInIdValue = null;
if (warehouseInRelations.length > 0) {
if (warehouseInRelations.length === 1) {
warehouseInIdValue = String(warehouseInRelations[0].warehouseInId);
} else {
// 多个ID用逗号分隔
warehouseInIdValue = warehouseInRelations.map(item => String(item.warehouseInId)).join(',');
}
console.log('从 warehouseInRelations 提取 warehouseInIdValue:', warehouseInIdValue);
} else if (ruleForm.warehouseInId) {
// 如果没有关联记录但有旧的 warehouseInId使用旧值
warehouseInIdValue = String(ruleForm.warehouseInId);
console.log('使用旧的 warehouseInId:', warehouseInIdValue);
} else {
console.warn('警告warehouseInRelations 为空且 warehouseInId 也为空!');
}
console.log('最终提交参数 - warehouseInRelations:', warehouseInRelations);
console.log('最终提交参数 - warehouseInIdValue:', warehouseInIdValue);
console.log('==========================================');
const params = {
warehouseId: ruleForm.warehouseId,
warehouseInId: ruleForm.warehouseInId,
warehouseInId: warehouseInIdValue, // 设置为关联的进仓记录ID单个或逗号分隔的多个
warehouseInRelations: warehouseInRelations, // 新增:多对多关联数组
orderId: orderIdStr,
deliveryId: ruleForm.deliveryId,
destinationLocation: ruleForm.destinationLocation,
@@ -515,13 +776,19 @@ const onShowDialog = (row, isDetail = false) => {
// 如果是详情模式,先获取详情数据
if (isDetail) {
warehouseOutDetail(row.id)
.then((res) => {
.then(async (res) => {
if (res.code === 200 && res.data) {
const detailData = res.data;
// 先加载进仓记录列表,以便能够正确显示关联的进仓记录
if (detailData.warehouseId) {
await loadWarehouseInList(detailData.warehouseId);
}
Object.assign(ruleForm, {
id: detailData.id,
warehouseId: detailData.warehouseId,
warehouseInId: detailData.warehouseInId,
warehouseInRelations: detailData.warehouseInRelations || [], // 加载关联的进仓记录
orderId: detailData.orderId ? detailData.orderId.split(',').map(id => parseInt(id)) : [],
deliveryId: detailData.deliveryId,
destinationLocation: detailData.destinationLocation || '',
@@ -535,6 +802,30 @@ const onShowDialog = (row, isDetail = false) => {
remark: detailData.remark || '',
status: detailData.status !== undefined ? detailData.status : 1,
});
// 如果有旧的warehouseInId但没有warehouseInRelations则转换
if (detailData.warehouseInId && (!detailData.warehouseInRelations || detailData.warehouseInRelations.length === 0)) {
// 兼容旧数据将单个warehouseInId转换为关联数组
const warehouseIn = warehouseInList.value.find(item => item.id === detailData.warehouseInId);
ruleForm.warehouseInRelations = [{
warehouseInId: detailData.warehouseInId,
totalCount: warehouseIn ? warehouseIn.cattleCount : (detailData.warehouseInCattleCount || detailData.cattleCount || 0),
outCount: warehouseIn ? warehouseIn.outCount : 0,
remainingCount: warehouseIn ? warehouseIn.remainingCount : (detailData.warehouseInCattleCount || detailData.cattleCount || 0),
outCountThisTime: detailData.cattleCount || 0,
}];
} else if (detailData.warehouseInRelations && detailData.warehouseInRelations.length > 0) {
// 如果有warehouseInRelations需要更新每个关联记录的详细信息
ruleForm.warehouseInRelations = detailData.warehouseInRelations.map(relation => {
const warehouseIn = warehouseInList.value.find(item => item.id === relation.warehouseInId);
return {
warehouseInId: relation.warehouseInId,
totalCount: warehouseIn ? warehouseIn.cattleCount : (relation.totalCount || 0),
outCount: warehouseIn ? warehouseIn.outCount : (relation.outCount || 0),
remainingCount: warehouseIn ? warehouseIn.remainingCount : (relation.remainingCount || 0),
outCountThisTime: relation.outCount || relation.outCountThisTime || 0,
};
});
}
// 处理照片和视频文件列表
if (detailData.photos) {
photoFileList.value = detailData.photos.split(',').map(url => ({ url, name: 'photo' }));
@@ -548,30 +839,13 @@ const onShowDialog = (row, isDetail = false) => {
ElMessage.error('获取详情失败:' + (error.message || '未知错误'));
});
} else {
// 编辑模式,直接使用传入的数据
Object.assign(ruleForm, {
id: row.id,
warehouseId: row.warehouseId,
warehouseInId: row.warehouseInId,
orderId: row.orderId ? row.orderId.split(',').map(id => parseInt(id)) : [],
deliveryId: row.deliveryId,
destinationLocation: row.destinationLocation || '',
destinationLon: row.destinationLon || '',
destinationLat: row.destinationLat || '',
cattleCount: row.cattleCount,
weight: row.weight,
outTime: row.outTime || '',
photos: row.photos || '',
videos: row.videos || '',
remark: row.remark || '',
status: row.status !== undefined ? row.status : 1,
});
// 处理照片和视频文件列表
if (row.photos) {
photoFileList.value = row.photos.split(',').map(url => ({ url, name: 'photo' }));
}
if (row.videos) {
videoFileList.value = row.videos.split(',').map(url => ({ url, name: 'video' }));
// 编辑模式,先加载进仓记录列表,然后设置表单数据
if (row.warehouseId) {
loadWarehouseInList(row.warehouseId).then(() => {
setFormDataForEdit(row);
});
} else {
setFormDataForEdit(row);
}
}
} else {
@@ -580,6 +854,7 @@ const onShowDialog = (row, isDetail = false) => {
id: null,
warehouseId: null,
warehouseInId: null,
warehouseInRelations: [],
orderId: [],
deliveryId: null,
destinationLocation: '',
@@ -602,22 +877,139 @@ const onShowDialog = (row, isDetail = false) => {
// 中转仓选择变化
const handleWarehouseChange = (warehouseId) => {
// 加载该中转仓的进仓记录
loadWarehouseInList(warehouseId);
console.log('中转仓选择变化warehouseId:', warehouseId);
// 清空关联记录
ruleForm.warehouseInRelations = [];
// 清空进仓记录列表
warehouseInList.value = [];
// 如果选择了中转仓,加载该中转仓的进仓记录
if (warehouseId) {
loadWarehouseInList(warehouseId);
}
};
// 进仓记录选择变化
const handleWarehouseInChange = (warehouseInId) => {
// 如果选择了进仓记录,可以自动填充一些信息
if (warehouseInId) {
const warehouseIn = warehouseInList.value.find(item => item.id === warehouseInId);
if (warehouseIn) {
// 可以自动填充牛只数量等信息
if (!ruleForm.cattleCount && warehouseIn.cattleCount) {
ruleForm.cattleCount = warehouseIn.cattleCount;
}
}
// 添加进仓记录关联
const addWarehouseInRelation = async () => {
if (!ruleForm.warehouseId) {
ElMessage.warning('请先选择中转仓');
return;
}
// 如果进仓记录列表为空,尝试重新加载
if (warehouseInList.value.length === 0) {
console.log('进仓记录列表为空,重新加载');
await loadWarehouseInList(ruleForm.warehouseId);
}
console.log('添加进仓记录关联行,当前列表长度:', warehouseInList.value.length);
ruleForm.warehouseInRelations.push({
warehouseInId: null,
totalCount: 0,
outCount: 0,
remainingCount: 0,
outCountThisTime: undefined, // 使用 undefined 而不是 null避免 el-input-number 报错
});
console.log('添加后的关联行数:', ruleForm.warehouseInRelations.length);
console.log('添加后的 warehouseInList:', warehouseInList.value);
// 等待 nextTick 确保 computed 重新计算
nextTick(() => {
console.log('nextTick - availableWarehouseInLists:', availableWarehouseInLists.value);
});
};
// 删除进仓记录关联
const removeWarehouseInRelation = (index) => {
ruleForm.warehouseInRelations.splice(index, 1);
handleOutCountChange();
};
// 创建一个计算属性,为每一行生成可用的进仓记录列表
// 这样可以避免在模板中重复调用函数,提高性能
const availableWarehouseInLists = computed(() => {
const result = ruleForm.warehouseInRelations.map((_, currentIndex) => {
// 获取已选择的其他行的进仓ID
const selectedIds = ruleForm.warehouseInRelations
.map((item, index) => index !== currentIndex ? item.warehouseInId : null)
.filter(id => id != null);
// 过滤出可用的进仓记录
const available = warehouseInList.value.filter(item => {
// 只显示有剩余数量的进仓记录,且未被其他行选择
return (item.remainingCount || 0) > 0 && !selectedIds.includes(item.id);
});
console.log(`computed[${currentIndex}]: warehouseInList.length=${warehouseInList.value.length}, selectedIds=${JSON.stringify(selectedIds)}, available.length=${available.length}`);
return available;
});
console.log('availableWarehouseInLists computed:', result);
return result;
});
// 获取可用的进仓记录列表(排除已选择的)
// 使用缓存的计算属性,避免重复计算
const getAvailableWarehouseInList = (currentIndex) => {
// 确保索引有效
if (currentIndex < 0 || currentIndex >= availableWarehouseInLists.value.length) {
console.warn(`getAvailableWarehouseInList: 无效的索引 ${currentIndex}, 当前关联行数: ${ruleForm.warehouseInRelations.length}`);
// 如果索引无效,直接计算返回
const selectedIds = ruleForm.warehouseInRelations
.map((item, index) => index !== currentIndex ? item.warehouseInId : null)
.filter(id => id != null);
return warehouseInList.value.filter(item => {
return (item.remainingCount || 0) > 0 && !selectedIds.includes(item.id);
});
}
const result = availableWarehouseInLists.value[currentIndex] || [];
console.log(`getAvailableWarehouseInList(${currentIndex}):`, result.length, 'items', result);
return result;
};
// 进仓记录关联变化
const handleWarehouseInRelationChange = (row, index) => {
if (row.warehouseInId) {
const warehouseIn = warehouseInList.value.find(item => item.id === row.warehouseInId);
if (warehouseIn) {
row.totalCount = warehouseIn.cattleCount || 0;
row.outCount = warehouseIn.outCount || 0;
// 在编辑模式下,如果这是已关联的记录,使用调整后的剩余数量(已经加回了当前编辑的数量)
// 在新增模式下,使用正常的剩余数量
row.remainingCount = warehouseIn.remainingCount || 0;
// 自动设置本次出仓数量为剩余数量如果剩余数量大于0
// 在编辑模式下,如果这是已关联的记录,可以编辑到剩余数量(包括当前编辑的数量)
if (row.remainingCount > 0 && !row.outCountThisTime) {
row.outCountThisTime = row.remainingCount;
}
console.log(`进仓记录关联变化 - ${warehouseIn.inNumber}: totalCount=${row.totalCount}, remainingCount=${row.remainingCount}, isCurrentEditRelated=${warehouseIn.isCurrentEditRelated}`);
}
} else {
row.totalCount = 0;
row.outCount = 0;
row.remainingCount = 0;
row.outCountThisTime = undefined;
}
handleOutCountChange();
};
// 出仓数量变化
const handleOutCountChange = () => {
// 自动计算总出仓数量
const totalOutCount = getTotalOutCount();
ruleForm.cattleCount = totalOutCount;
// 触发表单验证
if (formDataRef.value) {
formDataRef.value.validateField('warehouseInRelations');
formDataRef.value.validateField('cattleCount');
}
};
// 计算总出仓数量
const getTotalOutCount = () => {
return ruleForm.warehouseInRelations.reduce((sum, item) => {
return sum + (item.outCountThisTime || 0);
}, 0);
};
// 运送清单选择变化
@@ -725,11 +1117,91 @@ const beforeVideoUpload = (file) => {
return false; // 阻止自动上传,手动处理
};
// 设置编辑模式的表单数据
const setFormDataForEdit = (row) => {
Object.assign(ruleForm, {
id: row.id,
warehouseId: row.warehouseId,
warehouseInId: row.warehouseInId,
warehouseInRelations: row.warehouseInRelations || [], // 加载关联的进仓记录
orderId: row.orderId ? row.orderId.split(',').map(id => parseInt(id)) : [],
deliveryId: row.deliveryId,
destinationLocation: row.destinationLocation || '',
destinationLon: row.destinationLon || '',
destinationLat: row.destinationLat || '',
cattleCount: row.cattleCount,
weight: row.weight,
outTime: row.outTime || '',
photos: row.photos || '',
videos: row.videos || '',
remark: row.remark || '',
status: row.status !== undefined ? row.status : 1,
});
// 如果有旧的warehouseInId但没有warehouseInRelations则转换
// 注意warehouseInId 现在可能是字符串多个ID用逗号分隔
if (row.warehouseInId && (!row.warehouseInRelations || row.warehouseInRelations.length === 0)) {
console.log('编辑模式 - 检测到 warehouseInId 但没有 warehouseInRelations开始转换');
console.log('row.warehouseInId:', row.warehouseInId, '类型:', typeof row.warehouseInId);
// 兼容旧数据:将 warehouseInId可能是单个ID或逗号分隔的多个ID转换为关联数组
const warehouseInIdStr = String(row.warehouseInId);
const warehouseInIds = warehouseInIdStr.split(',').map(id => id.trim()).filter(id => id);
console.log('解析出的进仓记录ID数组:', warehouseInIds);
ruleForm.warehouseInRelations = warehouseInIds.map(warehouseInIdStr => {
const warehouseInId = parseInt(warehouseInIdStr);
const warehouseIn = warehouseInList.value.find(item => item.id === warehouseInId);
console.log(`处理进仓记录ID ${warehouseInId}:`, warehouseIn ? '找到' : '未找到');
// 如果找不到对应的进仓记录,使用默认值
return {
warehouseInId: warehouseInId,
totalCount: warehouseIn ? warehouseIn.cattleCount : 0,
outCount: warehouseIn ? warehouseIn.outCount : 0,
remainingCount: warehouseIn ? warehouseIn.remainingCount : 0,
outCountThisTime: warehouseIn ? Math.min(warehouseIn.remainingCount || 0, row.cattleCount || 0) : (row.cattleCount || 0),
};
});
console.log('转换后的 warehouseInRelations:', ruleForm.warehouseInRelations);
} else if (row.warehouseInRelations && row.warehouseInRelations.length > 0) {
// 如果有warehouseInRelations需要更新每个关联记录的详细信息
ruleForm.warehouseInRelations = row.warehouseInRelations.map(relation => {
const warehouseIn = warehouseInList.value.find(item => item.id === relation.warehouseInId);
return {
warehouseInId: relation.warehouseInId,
totalCount: warehouseIn ? warehouseIn.cattleCount : (relation.totalCount || 0),
outCount: warehouseIn ? warehouseIn.outCount : (relation.outCount || 0),
remainingCount: warehouseIn ? warehouseIn.remainingCount : (relation.remainingCount || 0),
outCountThisTime: relation.outCount || relation.outCountThisTime || 0,
};
});
}
// 处理照片和视频文件列表
if (row.photos) {
photoFileList.value = row.photos.split(',').map(url => ({ url, name: 'photo' }));
}
if (row.videos) {
videoFileList.value = row.videos.split(',').map(url => ({ url, name: 'video' }));
}
};
// 监听对话框打开,加载数据
watch(() => data.dialogVisible, (newVal) => {
if (newVal) {
console.log('对话框打开,开始加载数据');
loadWarehouseList();
loadWarehouseInList();
// 如果已有中转仓ID则加载对应的进仓记录否则等待用户选择中转仓
if (ruleForm.warehouseId) {
console.log('已有中转仓ID加载进仓记录:', ruleForm.warehouseId);
loadWarehouseInList(ruleForm.warehouseId);
} else {
console.log('暂无中转仓ID等待用户选择');
// 清空进仓记录列表
warehouseInList.value = [];
}
loadOrderList();
loadDeliveryList();
}