初步完成屠宰场管理

This commit is contained in:
xuqiuyun
2025-12-09 17:34:29 +08:00
parent 620975c04d
commit 980ffb39b9
30 changed files with 3428 additions and 522 deletions

View File

@@ -0,0 +1,87 @@
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',
});
}

View File

@@ -327,6 +327,38 @@ export const constantRoutes: Array<RouteRecordRaw> = [
},
],
},
// 屠宰场管理路由
{
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: '/datav',
name: 'DataV',

View File

@@ -109,12 +109,13 @@
<el-table-column label="司机姓名" prop="driverName"> </el-table-column>
<el-table-column label="创建时间" prop="createTime"></el-table-column>
<el-table-column label="创建人" prop="createByName"></el-table-column>
<el-table-column label="操作" width="400">
<el-table-column label="操作" width="480">
<template #default="scope">
<div class="table_column_operation">
<el-button type="primary" link @click="details(scope.row, scope.row.warningTypeList ? scope.row.warningTypeList.length : 0)" v-hasPermi="['entry:view']">详情</el-button>
<el-button type="primary" link @click="viewDevices(scope.row)" v-hasPermi="['entry:device']">查看设备</el-button>
<el-button type="primary" link @click="showAddOrderDialog(scope.row.id)" v-hasPermi="['entry:order:create']">新增订单</el-button>
<el-button type="primary" link @click="showRelatedOrders(scope.row)" v-hasPermi="['entry:view']">相关订单</el-button>
<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>
@@ -140,6 +141,44 @@
<createDeliveryDialog ref="editDialogRef" @success="getDataList" />
<!-- 订单对话框 -->
<OrderDialog ref="OrderDialogRef" @success="handleOrderSuccess" />
<!-- 相关订单对话框 -->
<el-dialog v-model="relatedOrdersDialog.visible" :title="`相关订单 - 运单号: ${relatedOrdersDialog.deliveryNumber || '--'}`" width="900px">
<el-table :data="relatedOrdersDialog.orders" border v-loading="relatedOrdersDialog.loading" style="width: 100%">
<el-table-column label="订单ID" prop="id" width="100"></el-table-column>
<el-table-column label="买方" prop="buyerName" width="150"></el-table-column>
<el-table-column label="卖方" prop="sellerName" width="150"></el-table-column>
<el-table-column label="结算方式" prop="settlementType" width="120">
<template #default="scope">
<span v-if="scope.row.settlementType === 1">上车重量</span>
<span v-else-if="scope.row.settlementType === 2">下车重量</span>
<span v-else-if="scope.row.settlementType === 3">按肉价结算</span>
<span v-else>--</span>
</template>
</el-table-column>
<el-table-column label="约定价格(元/斤)" prop="firmPrice" width="140">
<template #default="scope">
<span v-if="scope.row.firmPrice">{{ scope.row.firmPrice }}</span>
<span v-else>--</span>
</template>
</el-table-column>
<el-table-column label="预付款(元)" prop="advancePayment" width="120">
<template #default="scope">
<span v-if="scope.row.advancePayment">{{ scope.row.advancePayment }}</span>
<span v-else>--</span>
</template>
</el-table-column>
<el-table-column label="创建时间" prop="createTime" width="180"></el-table-column>
<template #empty>
<div style="text-align: center; padding: 20px; color: #999">暂无相关订单</div>
</template>
</el-table>
<template #footer>
<span class="dialog-footer">
<el-button @click="relatedOrdersDialog.visible = false">关闭</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
@@ -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();
});

View File

@@ -43,6 +43,7 @@
<el-descriptions-item label="创建时间">{{ data.baseInfo.createTime || '' }}</el-descriptions-item>
<el-descriptions-item label="司机运费">{{ data.baseInfo.freight ? data.baseInfo.freight + ' 元' : '-' }}</el-descriptions-item>
<el-descriptions-item label="登记设备数量">{{ totalRegisteredDevices }} </el-descriptions-item>
<el-descriptions-item label="牛只数量">{{ data.baseInfo.ratedQuantity ||'-' }} </el-descriptions-item>
</el-descriptions>
</el-card>
@@ -123,7 +124,19 @@
</div>
<!-- 照片信息分组 -->
<div v-if="hasValue(data.baseInfo.quarantineTickeyUrl) || hasValue(data.baseInfo.poundListImg) || hasValue(data.baseInfo.emptyVehicleFrontPhoto) || hasValue(data.baseInfo.loadedVehicleFrontPhoto) || hasValue(data.baseInfo.loadedVehicleWeightPhoto) || hasValue(data.baseInfo.driverIdCardPhoto) || hasValue(data.baseInfo.destinationPoundListImg) || hasValue(data.baseInfo.destinationVehicleFrontPhoto)" class="info-group">
<div
v-if="
parseMediaList(data.baseInfo.quarantineTickeyUrl).length ||
parseMediaList(data.baseInfo.poundListImg).length ||
parseMediaList(data.baseInfo.emptyVehicleFrontPhoto).length ||
parseMediaList(data.baseInfo.loadedVehicleFrontPhoto).length ||
parseMediaList(data.baseInfo.loadedVehicleWeightPhoto).length ||
parseMediaList(data.baseInfo.driverIdCardPhoto).length ||
parseMediaList(data.baseInfo.destinationPoundListImg).length ||
parseMediaList(data.baseInfo.destinationVehicleFrontPhoto).length
"
class="info-group"
>
<div class="sub-title">照片信息</div>
<div class="media-grid">
<div class="media-item" v-if="data.baseInfo.carFrontPhoto || data.baseInfo.carBehindPhoto">
@@ -147,83 +160,99 @@
/>
</div>
</div>
<div class="media-item" v-if="hasValue(data.baseInfo.quarantineTickeyUrl)">
<div class="media-item" v-if="parseMediaList(data.baseInfo.quarantineTickeyUrl).length">
<div class="media-label">检疫票</div>
<el-image
v-for="(url, idx) in parseMediaList(data.baseInfo.quarantineTickeyUrl)"
:key="`quarantine-${idx}`"
class="photo-img"
:src="data.baseInfo.quarantineTickeyUrl"
:src="url"
fit="cover"
:preview-src-list="[data.baseInfo.quarantineTickeyUrl]"
:preview-src-list="parseMediaList(data.baseInfo.quarantineTickeyUrl)"
preview-teleported
/>
</div>
<div class="media-item" v-if="hasValue(data.baseInfo.poundListImg)">
<div class="media-item" v-if="parseMediaList(data.baseInfo.poundListImg).length">
<div class="media-label">纸质磅单</div>
<el-image
v-for="(url, idx) in parseMediaList(data.baseInfo.poundListImg)"
:key="`pound-${idx}`"
class="photo-img"
:src="data.baseInfo.poundListImg"
:src="url"
fit="cover"
:preview-src-list="[data.baseInfo.poundListImg]"
:preview-src-list="parseMediaList(data.baseInfo.poundListImg)"
preview-teleported
/>
</div>
<div class="media-item" v-if="hasValue(data.baseInfo.emptyVehicleFrontPhoto)">
<div class="media-item" v-if="parseMediaList(data.baseInfo.emptyVehicleFrontPhoto).length">
<div class="media-label">空磅车头照片</div>
<el-image
v-for="(url, idx) in parseMediaList(data.baseInfo.emptyVehicleFrontPhoto)"
:key="`empty-front-${idx}`"
class="photo-img"
:src="data.baseInfo.emptyVehicleFrontPhoto"
:src="url"
fit="cover"
:preview-src-list="[data.baseInfo.emptyVehicleFrontPhoto]"
:preview-src-list="parseMediaList(data.baseInfo.emptyVehicleFrontPhoto)"
preview-teleported
/>
</div>
<div class="media-item" v-if="hasValue(data.baseInfo.loadedVehicleFrontPhoto)">
<div class="media-item" v-if="parseMediaList(data.baseInfo.loadedVehicleFrontPhoto).length">
<div class="media-label">过重磅车头照片</div>
<el-image
v-for="(url, idx) in parseMediaList(data.baseInfo.loadedVehicleFrontPhoto)"
:key="`loaded-front-${idx}`"
class="photo-img"
:src="data.baseInfo.loadedVehicleFrontPhoto"
:src="url"
fit="cover"
:preview-src-list="[data.baseInfo.loadedVehicleFrontPhoto]"
:preview-src-list="parseMediaList(data.baseInfo.loadedVehicleFrontPhoto)"
preview-teleported
/>
</div>
<div class="media-item" v-if="hasValue(data.baseInfo.loadedVehicleWeightPhoto)">
<div class="media-item" v-if="parseMediaList(data.baseInfo.loadedVehicleWeightPhoto).length">
<div class="media-label">车辆重磅照片</div>
<el-image
v-for="(url, idx) in parseMediaList(data.baseInfo.loadedVehicleWeightPhoto)"
:key="`loaded-weight-${idx}`"
class="photo-img"
:src="data.baseInfo.loadedVehicleWeightPhoto"
:src="url"
fit="cover"
:preview-src-list="[data.baseInfo.loadedVehicleWeightPhoto]"
:preview-src-list="parseMediaList(data.baseInfo.loadedVehicleWeightPhoto)"
preview-teleported
/>
</div>
<div class="media-item" v-if="hasValue(data.baseInfo.driverIdCardPhoto)">
<div class="media-item" v-if="parseMediaList(data.baseInfo.driverIdCardPhoto).length">
<div class="media-label">驾驶员手持身份证</div>
<el-image
v-for="(url, idx) in parseMediaList(data.baseInfo.driverIdCardPhoto)"
:key="`driver-id-${idx}`"
class="photo-img"
:src="data.baseInfo.driverIdCardPhoto"
:src="url"
fit="cover"
:preview-src-list="[data.baseInfo.driverIdCardPhoto]"
:preview-src-list="parseMediaList(data.baseInfo.driverIdCardPhoto)"
preview-teleported
/>
</div>
<div class="media-item" v-if="hasValue(data.baseInfo.destinationPoundListImg)">
<div class="media-item" v-if="parseMediaList(data.baseInfo.destinationPoundListImg).length">
<div class="media-label">落地纸质磅单</div>
<el-image
v-for="(url, idx) in parseMediaList(data.baseInfo.destinationPoundListImg)"
:key="`dest-pound-${idx}`"
class="photo-img"
:src="data.baseInfo.destinationPoundListImg"
:src="url"
fit="cover"
:preview-src-list="[data.baseInfo.destinationPoundListImg]"
:preview-src-list="parseMediaList(data.baseInfo.destinationPoundListImg)"
preview-teleported
/>
</div>
<div class="media-item" v-if="hasValue(data.baseInfo.destinationVehicleFrontPhoto)">
<div class="media-item" v-if="parseMediaList(data.baseInfo.destinationVehicleFrontPhoto).length">
<div class="media-label">落地过重磅车头</div>
<el-image
v-for="(url, idx) in parseMediaList(data.baseInfo.destinationVehicleFrontPhoto)"
:key="`dest-front-${idx}`"
class="photo-img"
:src="data.baseInfo.destinationVehicleFrontPhoto"
:src="url"
fit="cover"
:preview-src-list="[data.baseInfo.destinationVehicleFrontPhoto]"
:preview-src-list="parseMediaList(data.baseInfo.destinationVehicleFrontPhoto)"
preview-teleported
/>
</div>
@@ -231,36 +260,82 @@
</div>
<!-- 视频信息分组 -->
<div v-if="hasValue(data.baseInfo.emptyWeightVideo) || hasValue(data.baseInfo.entruckWeightVideo) || hasValue(data.baseInfo.entruckVideo) || hasValue(data.baseInfo.controlSlotVideo) || hasValue(data.baseInfo.cattleLoadingCircleVideo) || hasValue(data.baseInfo.unloadCattleVideo) || hasValue(data.baseInfo.destinationWeightVideo)" class="info-group">
<div
v-if="
parseMediaList(data.baseInfo.emptyWeightVideo).length ||
parseMediaList(data.baseInfo.entruckWeightVideo).length ||
parseMediaList(data.baseInfo.entruckVideo).length ||
parseMediaList(data.baseInfo.controlSlotVideo).length ||
parseMediaList(data.baseInfo.cattleLoadingCircleVideo).length ||
parseMediaList(data.baseInfo.unloadCattleVideo).length ||
parseMediaList(data.baseInfo.destinationWeightVideo).length
"
class="info-group"
>
<div class="sub-title">视频信息</div>
<div class="media-grid">
<div class="media-item video-item" v-if="hasValue(data.baseInfo.emptyWeightVideo)">
<div class="media-item video-item" v-if="parseMediaList(data.baseInfo.emptyWeightVideo).length">
<div class="media-label">空车过磅视频</div>
<video controls :src="data.baseInfo.emptyWeightVideo" />
<video
v-for="(url, idx) in parseMediaList(data.baseInfo.emptyWeightVideo)"
:key="`emptyWeightVideo-${idx}`"
controls
:src="url"
/>
</div>
<div class="media-item video-item" v-if="hasValue(data.baseInfo.entruckWeightVideo)">
<div class="media-item video-item" v-if="parseMediaList(data.baseInfo.entruckWeightVideo).length">
<div class="media-label">装车过磅视频</div>
<video controls :src="data.baseInfo.entruckWeightVideo" />
<video
v-for="(url, idx) in parseMediaList(data.baseInfo.entruckWeightVideo)"
:key="`entruckWeightVideo-${idx}`"
controls
:src="url"
/>
</div>
<div class="media-item video-item" v-if="hasValue(data.baseInfo.entruckVideo)">
<div class="media-item video-item" v-if="parseMediaList(data.baseInfo.entruckVideo).length">
<div class="media-label">装车视频</div>
<video controls :src="data.baseInfo.entruckVideo" />
<video
v-for="(url, idx) in parseMediaList(data.baseInfo.entruckVideo)"
:key="`entruckVideo-${idx}`"
controls
:src="url"
/>
</div>
<div class="media-item video-item" v-if="hasValue(data.baseInfo.controlSlotVideo)">
<div class="media-item video-item" v-if="parseMediaList(data.baseInfo.controlSlotVideo).length">
<div class="media-label">控槽视频</div>
<video controls :src="data.baseInfo.controlSlotVideo" />
<video
v-for="(url, idx) in parseMediaList(data.baseInfo.controlSlotVideo)"
:key="`controlSlotVideo-${idx}`"
controls
:src="url"
/>
</div>
<div class="media-item video-item" v-if="hasValue(data.baseInfo.cattleLoadingCircleVideo)">
<div class="media-item video-item" v-if="parseMediaList(data.baseInfo.cattleLoadingCircleVideo).length">
<div class="media-label">装完牛绕车一圈</div>
<video controls :src="data.baseInfo.cattleLoadingCircleVideo" />
<video
v-for="(url, idx) in parseMediaList(data.baseInfo.cattleLoadingCircleVideo)"
:key="`cattleLoadingCircleVideo-${idx}`"
controls
:src="url"
/>
</div>
<div class="media-item video-item" v-if="hasValue(data.baseInfo.unloadCattleVideo)">
<div class="media-item video-item" v-if="parseMediaList(data.baseInfo.unloadCattleVideo).length">
<div class="media-label">卸牛视频</div>
<video controls :src="data.baseInfo.unloadCattleVideo" />
<video
v-for="(url, idx) in parseMediaList(data.baseInfo.unloadCattleVideo)"
:key="`unloadCattleVideo-${idx}`"
controls
:src="url"
/>
</div>
<div class="media-item video-item" v-if="hasValue(data.baseInfo.destinationWeightVideo)">
<div class="media-item video-item" v-if="parseMediaList(data.baseInfo.destinationWeightVideo).length">
<div class="media-label">落地过磅视频</div>
<video controls :src="data.baseInfo.destinationWeightVideo" />
<video
v-for="(url, idx) in parseMediaList(data.baseInfo.destinationWeightVideo)"
:key="`destinationWeightVideo-${idx}`"
controls
:src="url"
/>
</div>
</div>
</div>
@@ -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 判断)

View File

@@ -358,47 +358,20 @@
<el-form-item label="检疫票" prop="quarantineTickeyUrl" label-width="180px">
<div class="photo-upload-wrapper">
<el-upload
class="avatar-uploader"
drag
action="/api/common/upload"
:show-file-list="false"
:on-success="handleQuarantinePhotoSuccess"
:file-list="photoLists.quarantineTickeyUrl"
:limit="2"
multiple
:on-success="handlePhotoSuccess('quarantineTickeyUrl')"
:on-remove="handlePhotoRemove('quarantineTickeyUrl')"
:on-preview="(file) => handlePreviewPhoto('quarantineTickeyUrl', file)"
:headers="uploadHeaders"
accept="image/*"
>
<el-image
v-if="formData.quarantineTickeyUrl"
ref="quarantineImageRef"
:src="formData.quarantineTickeyUrl"
class="avatar"
fit="cover"
:preview-src-list="[formData.quarantineTickeyUrl]"
preview-teleported
preview-disabled
/>
<template v-else>
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
</template>
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<div class="el-upload__text">拖拽或点击上传最多2张</div>
</el-upload>
<el-button
v-if="formData.quarantineTickeyUrl"
type="primary"
:icon="ZoomIn"
circle
size="small"
class="preview-btn"
@click.stop="handlePreviewPhoto('quarantineTickeyUrl')"
/>
<el-button
v-if="formData.quarantineTickeyUrl"
type="danger"
:icon="Delete"
circle
size="small"
class="delete-btn"
@click.stop="handleDeletePhoto('quarantineTickeyUrl')"
/>
</div>
</el-form-item>
</el-col>
@@ -406,47 +379,20 @@
<el-form-item label="发车纸质磅单(双章)" prop="poundListImg" label-width="180px">
<div class="photo-upload-wrapper">
<el-upload
class="avatar-uploader"
drag
action="/api/common/upload"
:show-file-list="false"
:on-success="handlePoundListPhotoSuccess"
:file-list="photoLists.poundListImg"
:limit="2"
multiple
:on-success="handlePhotoSuccess('poundListImg')"
:on-remove="handlePhotoRemove('poundListImg')"
:on-preview="(file) => handlePreviewPhoto('poundListImg', file)"
:headers="uploadHeaders"
accept="image/*"
>
<el-image
v-if="formData.poundListImg"
ref="poundListImageRef"
:src="formData.poundListImg"
class="avatar"
fit="cover"
:preview-src-list="[formData.poundListImg]"
preview-teleported
preview-disabled
/>
<template v-else>
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
</template>
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<div class="el-upload__text">拖拽或点击上传最多2张</div>
</el-upload>
<el-button
v-if="formData.poundListImg"
type="primary"
:icon="ZoomIn"
circle
size="small"
class="preview-btn"
@click.stop="handlePreviewPhoto('poundListImg')"
/>
<el-button
v-if="formData.poundListImg"
type="danger"
:icon="Delete"
circle
size="small"
class="delete-btn"
@click.stop="handleDeletePhoto('poundListImg')"
/>
</div>
</el-form-item>
</el-col>
@@ -456,47 +402,20 @@
<el-form-item label="车辆空磅上磅车头照片" prop="emptyVehicleFrontPhoto" label-width="180px">
<div class="photo-upload-wrapper">
<el-upload
class="avatar-uploader"
drag
action="/api/common/upload"
:show-file-list="false"
:on-success="handleEmptyVehicleFrontPhotoSuccess"
:file-list="photoLists.emptyVehicleFrontPhoto"
:limit="2"
multiple
:on-success="handlePhotoSuccess('emptyVehicleFrontPhoto')"
:on-remove="handlePhotoRemove('emptyVehicleFrontPhoto')"
:on-preview="(file) => handlePreviewPhoto('emptyVehicleFrontPhoto', file)"
:headers="uploadHeaders"
accept="image/*"
>
<el-image
v-if="formData.emptyVehicleFrontPhoto"
ref="emptyVehicleFrontImageRef"
:src="formData.emptyVehicleFrontPhoto"
class="avatar"
fit="cover"
:preview-src-list="[formData.emptyVehicleFrontPhoto]"
preview-teleported
preview-disabled
/>
<template v-else>
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
</template>
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<div class="el-upload__text">拖拽或点击上传最多2张</div>
</el-upload>
<el-button
v-if="formData.emptyVehicleFrontPhoto"
type="primary"
:icon="ZoomIn"
circle
size="small"
class="preview-btn"
@click.stop="handlePreviewPhoto('emptyVehicleFrontPhoto')"
/>
<el-button
v-if="formData.emptyVehicleFrontPhoto"
type="danger"
:icon="Delete"
circle
size="small"
class="delete-btn"
@click.stop="handleDeletePhoto('emptyVehicleFrontPhoto')"
/>
</div>
</el-form-item>
</el-col>
@@ -504,47 +423,20 @@
<el-form-item label="车辆过重磅车头照片" prop="loadedVehicleFrontPhoto" label-width="180px">
<div class="photo-upload-wrapper">
<el-upload
class="avatar-uploader"
drag
action="/api/common/upload"
:show-file-list="false"
:on-success="handleLoadedVehicleFrontPhotoSuccess"
:file-list="photoLists.loadedVehicleFrontPhoto"
:limit="2"
multiple
:on-success="handlePhotoSuccess('loadedVehicleFrontPhoto')"
:on-remove="handlePhotoRemove('loadedVehicleFrontPhoto')"
:on-preview="(file) => handlePreviewPhoto('loadedVehicleFrontPhoto', file)"
:headers="uploadHeaders"
accept="image/*"
>
<el-image
v-if="formData.loadedVehicleFrontPhoto"
ref="loadedVehicleFrontImageRef"
:src="formData.loadedVehicleFrontPhoto"
class="avatar"
fit="cover"
:preview-src-list="[formData.loadedVehicleFrontPhoto]"
preview-teleported
preview-disabled
/>
<template v-else>
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
</template>
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<div class="el-upload__text">拖拽或点击上传最多2张</div>
</el-upload>
<el-button
v-if="formData.loadedVehicleFrontPhoto"
type="primary"
:icon="ZoomIn"
circle
size="small"
class="preview-btn"
@click.stop="handlePreviewPhoto('loadedVehicleFrontPhoto')"
/>
<el-button
v-if="formData.loadedVehicleFrontPhoto"
type="danger"
:icon="Delete"
circle
size="small"
class="delete-btn"
@click.stop="handleDeletePhoto('loadedVehicleFrontPhoto')"
/>
</div>
</el-form-item>
</el-col>
@@ -554,47 +446,20 @@
<el-form-item label="车辆重磅侧方照片" prop="loadedVehicleWeightPhoto" label-width="180px">
<div class="photo-upload-wrapper">
<el-upload
class="avatar-uploader"
drag
action="/api/common/upload"
:show-file-list="false"
:on-success="handleLoadedVehicleWeightPhotoSuccess"
:file-list="photoLists.loadedVehicleWeightPhoto"
:limit="2"
multiple
:on-success="handlePhotoSuccess('loadedVehicleWeightPhoto')"
:on-remove="handlePhotoRemove('loadedVehicleWeightPhoto')"
:on-preview="(file) => handlePreviewPhoto('loadedVehicleWeightPhoto', file)"
:headers="uploadHeaders"
accept="image/*"
>
<el-image
v-if="formData.loadedVehicleWeightPhoto"
ref="loadedVehicleWeightImageRef"
:src="formData.loadedVehicleWeightPhoto"
class="avatar"
fit="cover"
:preview-src-list="[formData.loadedVehicleWeightPhoto]"
preview-teleported
preview-disabled
/>
<template v-else>
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
</template>
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<div class="el-upload__text">拖拽或点击上传最多2张</div>
</el-upload>
<el-button
v-if="formData.loadedVehicleWeightPhoto"
type="primary"
:icon="ZoomIn"
circle
size="small"
class="preview-btn"
@click.stop="handlePreviewPhoto('loadedVehicleWeightPhoto')"
/>
<el-button
v-if="formData.loadedVehicleWeightPhoto"
type="danger"
:icon="Delete"
circle
size="small"
class="delete-btn"
@click.stop="handleDeletePhoto('loadedVehicleWeightPhoto')"
/>
</div>
</el-form-item>
</el-col>
@@ -602,47 +467,20 @@
<el-form-item label="驾驶员手持身份证站车头照片" prop="driverIdCardPhoto" label-width="180px">
<div class="photo-upload-wrapper">
<el-upload
class="avatar-uploader"
drag
action="/api/common/upload"
:show-file-list="false"
:on-success="handleDriverIdCardPhotoSuccess"
:file-list="photoLists.driverIdCardPhoto"
:limit="2"
multiple
:on-success="handlePhotoSuccess('driverIdCardPhoto')"
:on-remove="handlePhotoRemove('driverIdCardPhoto')"
:on-preview="(file) => handlePreviewPhoto('driverIdCardPhoto', file)"
:headers="uploadHeaders"
accept="image/*"
>
<el-image
v-if="formData.driverIdCardPhoto"
ref="driverIdCardImageRef"
:src="formData.driverIdCardPhoto"
class="avatar"
fit="cover"
:preview-src-list="[formData.driverIdCardPhoto]"
preview-teleported
preview-disabled
/>
<template v-else>
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
</template>
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<div class="el-upload__text">拖拽或点击上传最多2张</div>
</el-upload>
<el-button
v-if="formData.driverIdCardPhoto"
type="primary"
:icon="ZoomIn"
circle
size="small"
class="preview-btn"
@click.stop="handlePreviewPhoto('driverIdCardPhoto')"
/>
<el-button
v-if="formData.driverIdCardPhoto"
type="danger"
:icon="Delete"
circle
size="small"
class="delete-btn"
@click.stop="handleDeletePhoto('driverIdCardPhoto')"
/>
</div>
</el-form-item>
</el-col>
@@ -652,47 +490,20 @@
<el-form-item label="落地纸质磅单(双章)" prop="destinationPoundListImg" label-width="180px">
<div class="photo-upload-wrapper">
<el-upload
class="avatar-uploader"
drag
action="/api/common/upload"
:show-file-list="false"
:on-success="handleDestinationPoundListPhotoSuccess"
:file-list="photoLists.destinationPoundListImg"
:limit="2"
multiple
:on-success="handlePhotoSuccess('destinationPoundListImg')"
:on-remove="handlePhotoRemove('destinationPoundListImg')"
:on-preview="(file) => handlePreviewPhoto('destinationPoundListImg', file)"
:headers="uploadHeaders"
accept="image/*"
>
<el-image
v-if="formData.destinationPoundListImg"
ref="destinationPoundListImageRef"
:src="formData.destinationPoundListImg"
class="avatar"
fit="cover"
:preview-src-list="[formData.destinationPoundListImg]"
preview-teleported
preview-disabled
/>
<template v-else>
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
</template>
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<div class="el-upload__text">拖拽或点击上传最多2张</div>
</el-upload>
<el-button
v-if="formData.destinationPoundListImg"
type="primary"
:icon="ZoomIn"
circle
size="small"
class="preview-btn"
@click.stop="handlePreviewPhoto('destinationPoundListImg')"
/>
<el-button
v-if="formData.destinationPoundListImg"
type="danger"
:icon="Delete"
circle
size="small"
class="delete-btn"
@click.stop="handleDeletePhoto('destinationPoundListImg')"
/>
</div>
</el-form-item>
</el-col>
@@ -700,47 +511,20 @@
<el-form-item label="落地车辆过重磅车头照片" prop="destinationVehicleFrontPhoto" label-width="180px">
<div class="photo-upload-wrapper">
<el-upload
class="avatar-uploader"
drag
action="/api/common/upload"
:show-file-list="false"
:on-success="handleDestinationVehicleFrontPhotoSuccess"
:file-list="photoLists.destinationVehicleFrontPhoto"
:limit="2"
multiple
:on-success="handlePhotoSuccess('destinationVehicleFrontPhoto')"
:on-remove="handlePhotoRemove('destinationVehicleFrontPhoto')"
:on-preview="(file) => handlePreviewPhoto('destinationVehicleFrontPhoto', file)"
:headers="uploadHeaders"
accept="image/*"
>
<el-image
v-if="formData.destinationVehicleFrontPhoto"
ref="destinationVehicleFrontImageRef"
:src="formData.destinationVehicleFrontPhoto"
class="avatar"
fit="cover"
:preview-src-list="[formData.destinationVehicleFrontPhoto]"
preview-teleported
preview-disabled
/>
<template v-else>
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
</template>
<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
<div class="el-upload__text">拖拽或点击上传最多2张</div>
</el-upload>
<el-button
v-if="formData.destinationVehicleFrontPhoto"
type="primary"
:icon="ZoomIn"
circle
size="small"
class="preview-btn"
@click.stop="handlePreviewPhoto('destinationVehicleFrontPhoto')"
/>
<el-button
v-if="formData.destinationVehicleFrontPhoto"
type="danger"
:icon="Delete"
circle
size="small"
class="delete-btn"
@click.stop="handleDeletePhoto('destinationVehicleFrontPhoto')"
/>
</div>
</el-form-item>
</el-col>
@@ -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)"
>
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
<div class="el-upload__text">将视频文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__text">拖拽或点击上传最多2个</div>
<template #tip>
<div class="el-upload__tip">支持 mp4/avi/rmvb/mkv 格式</div>
</template>
</el-upload>
<div v-if="formData.entruckWeightVideo" class="video-preview-wrapper">
<video :src="formData.entruckWeightVideo" style="width: 100%; max-height: 300px; margin-top: 10px" controls></video>
<el-button
type="danger"
:icon="Delete"
circle
size="small"
class="delete-btn"
@click="handleRemoveVideo('entruckWeightVideo')"
/>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
@@ -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)"
>
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
<div class="el-upload__text">将视频文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__text">拖拽或点击上传最多2个</div>
<template #tip>
<div class="el-upload__tip">支持 mp4/avi/rmvb/mkv 格式</div>
</template>
</el-upload>
<div v-if="formData.emptyWeightVideo" class="video-preview-wrapper">
<video :src="formData.emptyWeightVideo" style="width: 100%; max-height: 300px; margin-top: 10px" controls></video>
<el-button
type="danger"
:icon="Delete"
circle
size="small"
class="delete-btn"
@click="handleRemoveVideo('emptyWeightVideo')"
/>
</div>
</el-form-item>
</el-col>
</el-row>
@@ -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)"
>
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
<div class="el-upload__text">将视频文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__text">拖拽或点击上传最多2个</div>
<template #tip>
<div class="el-upload__tip">支持 mp4/avi/rmvb/mkv 格式</div>
</template>
</el-upload>
<div v-if="formData.cattleLoadingVideo" class="video-preview-wrapper">
<video :src="formData.cattleLoadingVideo" style="width: 100%; max-height: 300px; margin-top: 10px" controls></video>
<el-button
type="danger"
:icon="Delete"
circle
size="small"
class="delete-btn"
@click="handleRemoveVideo('cattleLoadingVideo')"
/>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
@@ -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)"
>
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
<div class="el-upload__text">将视频文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__text">拖拽或点击上传最多2个</div>
<template #tip>
<div class="el-upload__tip">支持 mp4/avi/rmvb/mkv 格式</div>
</template>
</el-upload>
<div v-if="formData.controlSlotVideo" class="video-preview-wrapper">
<video :src="formData.controlSlotVideo" style="width: 100%; max-height: 300px; margin-top: 10px" controls></video>
<el-button
type="danger"
:icon="Delete"
circle
size="small"
class="delete-btn"
@click="handleRemoveVideo('controlSlotVideo')"
/>
</div>
</el-form-item>
</el-col>
</el-row>
@@ -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)"
>
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
<div class="el-upload__text">将视频文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__text">拖拽或点击上传最多2个</div>
<template #tip>
<div class="el-upload__tip">支持 mp4/avi/rmvb/mkv 格式</div>
</template>
</el-upload>
<div v-if="formData.cattleLoadingCircleVideo" class="video-preview-wrapper">
<video :src="formData.cattleLoadingCircleVideo" style="width: 100%; max-height: 300px; margin-top: 10px" controls></video>
<el-button
type="danger"
:icon="Delete"
circle
size="small"
class="delete-btn"
@click="handleRemoveVideo('cattleLoadingCircleVideo')"
/>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
@@ -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)"
>
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
<div class="el-upload__text">将视频文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__text">拖拽或点击上传最多2个</div>
<template #tip>
<div class="el-upload__tip">支持 mp4/avi/rmvb/mkv 格式</div>
</template>
</el-upload>
<div v-if="formData.unloadCattleVideo" class="video-preview-wrapper">
<video :src="formData.unloadCattleVideo" style="width: 100%; max-height: 300px; margin-top: 10px" controls></video>
<el-button
type="danger"
:icon="Delete"
circle
size="small"
class="delete-btn"
@click="handleRemoveVideo('unloadCattleVideo')"
/>
</div>
</el-form-item>
</el-col>
</el-row>
@@ -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)"
>
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
<div class="el-upload__text">将视频文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__text">拖拽或点击上传最多2个</div>
<template #tip>
<div class="el-upload__tip">支持 mp4/avi/rmvb/mkv 格式</div>
</template>
</el-upload>
<div v-if="formData.destinationWeightVideo" class="video-preview-wrapper">
<video :src="formData.destinationWeightVideo" style="width: 100%; max-height: 300px; margin-top: 10px" controls></video>
<el-button
type="danger"
:icon="Delete"
circle
size="small"
class="delete-btn"
@click="handleRemoveVideo('destinationWeightVideo')"
/>
</div>
</el-form-item>
</el-col>
</el-row>
@@ -1140,6 +868,42 @@ 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 validatePlateNumber = (rule, value, callback) => {
// const plateReg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-Z0-9]{5}[A-Z0-9挂学警港澳]$/;
@@ -1211,6 +975,10 @@ const collarDevices = computed(() => {
// 完善提交数据 - 只提交DeliveryCreateDto需要的字段
const buildSubmitData = () => {
// 确保多文件列表已同步到表单字段
syncPhotoFieldsToForm();
syncVideoFieldsToForm();
// 将司机ID转换为姓名
let driverNameStr = '';
if (formData.driverId) {
@@ -1607,7 +1375,7 @@ const fillFormWithEditData = (editData) => {
'delivery对象': delivery
});
// 照片
// 照片(逗号串)
formData.quarantineTickeyUrl = delivery.quarantineTickeyUrl || '';
formData.poundListImg = delivery.poundListImg || '';
formData.emptyVehicleFrontPhoto = delivery.emptyVehicleFrontPhoto || '';
@@ -1617,7 +1385,7 @@ const fillFormWithEditData = (editData) => {
formData.destinationPoundListImg = delivery.destinationPoundListImg || '';
formData.destinationVehicleFrontPhoto = delivery.destinationVehicleFrontPhoto || '';
// 视频
// 视频(逗号串)
formData.entruckWeightVideo = delivery.entruckWeightVideo || '';
formData.emptyWeightVideo = delivery.emptyWeightVideo || '';
formData.cattleLoadingVideo = delivery.cattleLoadingVideo || '';
@@ -1626,6 +1394,27 @@ const fillFormWithEditData = (editData) => {
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 +1715,16 @@ const handleSubmit = () => {
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 +2014,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 +2256,9 @@ const handleClose = () => {
formData[key] = '';
}
});
// 重置文件列表
photoFields.forEach((f) => (photoLists[f] = []));
videoFields.forEach((f) => (videoLists[f] = []));
dialogVisible.value = false;
showStartLocationMap.value = false;
showEndLocationMap.value = false;

View File

@@ -164,9 +164,16 @@
<el-row :gutter="40">
<el-col :span="12">
<el-form-item label="约定单价" prop="firmPrice">
<el-input v-model="ruleForm.firmPrice" placeholder="请输入约定单价" clearable>
<el-input-number
v-model="ruleForm.firmPrice"
:precision="4"
:min="0"
:max="999999.9999"
placeholder="请输入约定单价"
style="width: 100%"
>
<template #append>/公斤</template>
</el-input>
</el-input-number>
</el-form-item>
</el-col>
</el-row>
@@ -287,7 +294,7 @@ const ruleForm = reactive({
buyerId: '', // 采购商
buyerPrice: '', // 采购单价
salePrice: '', // 销售单价
firmPrice: '', // 约定单价
firmPrice: null, // 约定单价
startLocation: '', // 起始地
startLat: '',
startLon: '',

View File

@@ -72,9 +72,9 @@
<el-form-item label="约定价格(元/斤)" prop="firmPrice">
<el-input-number
v-model="ruleForm.firmPrice"
:precision="2"
:precision="4"
:min="0"
:max="9999999.99"
:max="999999.9999"
placeholder="请输入约定价格"
style="width: 100%"
></el-input-number>
@@ -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;
}

View File

@@ -0,0 +1,329 @@
<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="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="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="操作" width="200">
<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="600px" :close-on-click-modal="false">
<el-form ref="dialogFormRef" :model="dialog.form" :rules="dialog.rules" label-width="120px">
<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>
<el-form-item label="订单ID" prop="orderId">
<el-input v-model.number="dialog.form.orderId" placeholder="请输入订单ID可选" />
</el-form-item>
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="起始地" prop="startLocation">
<el-input v-model="dialog.form.startLocation" placeholder="请输入起始地" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="目的地" prop="endLocation">
<el-input v-model="dialog.form.endLocation" placeholder="请输入目的地" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="起始经度" prop="startLon">
<el-input v-model="dialog.form.startLon" placeholder="请输入起始经度" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="起始纬度" prop="startLat">
<el-input v-model="dialog.form.startLat" placeholder="请输入起始纬度" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="目的地经度" prop="endLon">
<el-input v-model="dialog.form.endLon" placeholder="请输入目的地经度" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="目的地纬度" prop="endLat">
<el-input v-model="dialog.form.endLat" placeholder="请输入目的地纬度" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="司机姓名" prop="driverName">
<el-input v-model="dialog.form.driverName" placeholder="请输入司机姓名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系方式" prop="driverMobile">
<el-input v-model="dialog.form.driverMobile" placeholder="请输入联系方式" />
</el-form-item>
</el-col>
</el-row>
<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>
</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>
</el-col>
</el-row>
<el-form-item label="备注" prop="remark">
<el-input v-model="dialog.form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
</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 {
slaughterEntryList,
slaughterEntryAdd,
slaughterEntryEdit,
slaughterEntryDel,
} from '@/api/slaughter.js';
import { slaughterHouseAll } from '@/api/slaughter.js';
const baseSearchRef = ref();
const dialogFormRef = ref();
const houseOptions = ref([]);
const houseNameMap = reactive({});
const formItemList = reactive([
{
label: '屠宰场',
param: 'slaughterHouseId',
type: 'select',
options: [],
placeholder: '请选择屠宰场',
span: 8,
labelWidth: 100,
},
{
label: '运送清单ID',
param: 'deliveryId',
type: 'input',
placeholder: '请输入运送清单ID',
span: 8,
labelWidth: 100,
},
{
label: '订单ID',
param: 'orderId',
type: 'input',
placeholder: '请输入订单ID',
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,
slaughterHouseId: null,
deliveryId: null,
orderId: null,
startLocation: '',
startLon: '',
startLat: '',
endLocation: '',
endLon: '',
endLat: '',
driverName: '',
driverMobile: '',
licensePlate: '',
yieldRate: null,
remark: '',
},
rules: {
slaughterHouseId: [{ required: true, message: '请选择屠宰场', trigger: 'change' }],
deliveryId: [{ required: true, message: '请输入运送清单ID', trigger: 'blur' }],
},
});
const resetDialogForm = () => {
dialog.form = {
id: null,
slaughterHouseId: null,
deliveryId: null,
orderId: null,
startLocation: '',
startLon: '',
startLat: '',
endLocation: '',
endLon: '',
endLat: '',
driverName: '',
driverMobile: '',
licensePlate: '',
yieldRate: null,
remark: '',
};
dialog.title = '新增进场';
};
const loadHouseOptions = () => {
slaughterHouseAll()
.then((res) => {
const options = (res.data || []).map((item) => ({
label: item.house_name || item.houseName,
value: item.id,
}));
houseOptions.value = options;
formItemList[0].options = options;
Object.keys(houseNameMap).forEach((key) => delete houseNameMap[key]);
options.forEach((opt) => {
houseNameMap[opt.value] = opt.label;
});
})
.catch((err) => {
console.error(err);
});
};
const getList = () => {
data.loading = true;
const params = { ...form, ...baseSearchRef.value.penetrateParams() };
slaughterEntryList(params)
.then((res) => {
data.rows = res.data.rows || [];
data.total = res.data.total || 0;
})
.catch((err) => {
console.error(err);
ElMessage.error('查询失败');
})
.finally(() => {
data.loading = false;
});
};
const handleSearch = () => {
form.pageNum = 1;
getList();
};
const openDialog = (row) => {
resetDialogForm();
if (row) {
dialog.title = '编辑进场';
Object.assign(dialog.form, row);
}
dialog.visible = true;
};
const submitDialog = () => {
dialogFormRef.value?.validate((valid) => {
if (!valid) return;
const payload = { ...dialog.form };
const api = payload.id ? slaughterEntryEdit : slaughterEntryAdd;
api(payload)
.then(() => {
ElMessage.success(payload.id ? '更新成功' : '新增成功');
dialog.visible = false;
getList();
})
.catch((err) => {
console.error(err);
ElMessage.error(payload.id ? '更新失败' : '新增失败');
});
});
};
const handleDelete = (row) => {
ElMessageBox.confirm('确认删除该进场记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => slaughterEntryDel(row.id))
.then(() => {
ElMessage.success('删除成功');
getList();
})
.catch((err) => {
if (err !== 'cancel') {
console.error(err);
ElMessage.error('删除失败');
}
});
};
onMounted(() => {
loadHouseOptions();
getList();
});
</script>
<style scoped>
.page-container {
padding: 0 0 16px;
}
.action-bar {
display: flex;
padding: 10px;
background: #fff;
margin-bottom: 10px;
}
</style>

View File

@@ -0,0 +1,374 @@
<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="houseName" min-width="150" />
<el-table-column label="屠宰场编码" prop="slaughterCode" min-width="150" />
<el-table-column label="容量" prop="capacity" width="90" />
<el-table-column label="地址" prop="address" min-width="200" />
<el-table-column label="负责人" prop="person" min-width="120" />
<el-table-column label="联系电话" prop="phone" min-width="140" />
<el-table-column label="状态" prop="status" width="90">
<template #default="scope">
<el-tag v-if="scope.row.status === 1" type="success">启用</el-tag>
<el-tag v-else type="info">停用</el-tag>
</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="primary" @click="locateOnMap(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="110px">
<el-form-item label="屠宰场名称" prop="houseName">
<el-input v-model="dialog.form.houseName" placeholder="请输入屠宰场名称" />
</el-form-item>
<el-form-item label="屠宰场编码" prop="slaughterCode">
<el-input v-model="dialog.form.slaughterCode" placeholder="不填将自动生成" />
</el-form-item>
<el-form-item label="容量" prop="capacity">
<el-input-number v-model="dialog.form.capacity" :min="0" :step="1" style="width: 200px" />
</el-form-item>
<el-form-item label="地址" prop="address">
<el-input
v-model="dialog.form.address"
placeholder="请输入地址"
style="width: calc(100% - 100px); margin-right: 10px"
/>
<el-button type="primary" @click="openLocationMap">选择位置</el-button>
</el-form-item>
<el-form-item label="经度" prop="longitude">
<el-input v-model="dialog.form.longitude" placeholder="经度" disabled />
</el-form-item>
<el-form-item label="纬度" prop="latitude">
<el-input v-model="dialog.form.latitude" placeholder="纬度" disabled />
</el-form-item>
<el-form-item label="负责人姓名" prop="person">
<el-input v-model="dialog.form.person" placeholder="请输入负责人姓名" />
</el-form-item>
<el-form-item label="联系电话" prop="phone">
<el-input v-model="dialog.form.phone" placeholder="请输入联系电话" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-switch v-model="dialog.form.status" :active-value="1" :inactive-value="0" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="dialog.form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
</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>
<!-- 地址选择地图 -->
<el-dialog v-model="showLocationMap" title="选择地址" width="900px">
<baidu-map
class="map"
:center="mapCenter"
:zoom="15"
:scroll-wheel-zoom="true"
@click="handleLocationClick"
style="height: 500px"
>
<bm-marker
v-if="dialog.form.longitude && dialog.form.latitude"
:position="{ lng: parseFloat(dialog.form.longitude), lat: parseFloat(dialog.form.latitude) }"
:dragging="true"
@dragging="handleMarkerDrag"
/>
<bm-map-type :map-types="['BMAP_NORMAL_MAP', 'BMAP_HYBRID_MAP']"></bm-map-type>
</baidu-map>
<template #footer>
<span class="dialog-footer">
<el-button @click="showLocationMap = false">取消</el-button>
<el-button type="primary" @click="showLocationMap = false">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { reactive, ref, onMounted, computed } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { BaiduMap, BmMapType, BmMarker } from 'vue-baidu-map-3x';
import baseSearch from '@/components/common/searchCustom/index.vue';
import { slaughterHouseList, slaughterHouseAdd, slaughterHouseEdit, slaughterHouseDel } from '@/api/slaughter.js';
const baseSearchRef = ref();
const dialogFormRef = ref();
const showLocationMap = ref(false);
const formItemList = reactive([
{
label: '屠宰场名称',
param: 'houseName',
type: 'input',
placeholder: '请输入屠宰场名称',
span: 8,
labelWidth: 100,
},
{
label: '屠宰场编码',
param: 'slaughterCode',
type: 'input',
placeholder: '请输入屠宰场编码',
span: 8,
labelWidth: 100,
},
{
label: '状态',
param: 'status',
type: 'select',
status: [
{ label: '启用', value: 1 },
{ label: '停用', value: 0 },
],
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,
houseName: '',
slaughterCode: '',
capacity: null,
address: '',
longitude: '',
latitude: '',
person: '',
phone: '',
status: 1,
remark: '',
},
rules: {
houseName: [{ required: true, message: '请输入屠宰场名称', trigger: 'blur' }],
capacity: [{ required: true, message: '请输入容量', trigger: 'change' }],
address: [{ required: true, message: '请输入地址', trigger: 'blur' }],
phone: [{ pattern: /^1[3-9]\d{9}$/, message: '请输入有效手机号', trigger: 'blur' }],
},
});
const resetDialogForm = () => {
dialog.form = {
id: null,
houseName: '',
slaughterCode: '',
capacity: null,
address: '',
longitude: '',
latitude: '',
person: '',
phone: '',
status: 1,
remark: '',
};
dialog.title = '新增屠宰场';
};
const getList = () => {
data.loading = true;
const params = { ...form, ...baseSearchRef.value.penetrateParams() };
slaughterHouseList(params)
.then((res) => {
// 兼容结构:
// axiosRes.data = { code, msg, data: { code, msg, data: { total, rows } } }
// 或 axiosRes.data = { code, msg, data: { total, rows } }
// 或 axiosRes.data = { total, rows }
const resp = res?.data || {};
const inner = resp?.data?.data || resp?.data || resp;
data.rows = inner.rows || inner.data?.rows || [];
data.total = inner.total || inner.data?.total || 0;
})
.catch((err) => {
console.error(err);
ElMessage.error('查询失败');
})
.finally(() => {
data.loading = false;
});
};
const handleSearch = () => {
form.pageNum = 1;
getList();
};
const openDialog = (row) => {
resetDialogForm();
if (row) {
dialog.title = '编辑屠宰场';
Object.assign(dialog.form, row);
}
dialog.visible = true;
};
const locateOnMap = (row) => {
if (row && row.longitude && row.latitude) {
dialog.form.longitude = row.longitude.toString();
dialog.form.latitude = row.latitude.toString();
dialog.form.address = row.address || dialog.form.address;
showLocationMap.value = true;
} else {
ElMessage.warning('该记录无有效经纬度');
}
};
const openLocationMap = () => {
// 如果输入了地址,先尝试地理编码定位
if (dialog.form.address && dialog.form.address.trim()) {
showLocationMap.value = true;
setTimeout(() => {
geocodeAddress(dialog.form.address, true);
}, 300);
} else {
showLocationMap.value = true;
}
};
const geocodeAddress = (address, showToast = false) => {
if (!address || !window.BMap || !window.BMap.Geocoder) {
return;
}
try {
const geocoder = new window.BMap.Geocoder();
geocoder.getPoint(address, (point) => {
if (point) {
dialog.form.longitude = point.lng.toFixed(6);
dialog.form.latitude = point.lat.toFixed(6);
showToast && ElMessage.success('已定位到该地址');
} else {
showToast && ElMessage.warning('未找到该地址,请在地图上手动选择');
}
});
} catch (e) {
// ignore geocode errors
}
};
const handleLocationClick = (e) => {
if (e && e.point) {
dialog.form.longitude = e.point.lng.toFixed(6);
dialog.form.latitude = e.point.lat.toFixed(6);
// 反向地理编码回填地址
if (window.BMap && window.BMap.Geocoder) {
const geocoder = new window.BMap.Geocoder();
geocoder.getLocation(e.point, (res) => {
if (res && res.address) {
dialog.form.address = res.address;
}
});
}
}
};
const handleMarkerDrag = (e) => {
if (e && e.point) {
dialog.form.longitude = e.point.lng.toFixed(6);
dialog.form.latitude = e.point.lat.toFixed(6);
if (window.BMap && window.BMap.Geocoder) {
const geocoder = new window.BMap.Geocoder();
geocoder.getLocation(e.point, (res) => {
if (res && res.address) {
dialog.form.address = res.address;
}
});
}
}
};
const mapCenter = computed(() => {
if (dialog.form.longitude && dialog.form.latitude) {
return { lng: parseFloat(dialog.form.longitude), lat: parseFloat(dialog.form.latitude) };
}
return { lng: 116.404, lat: 39.915 };
});
const submitDialog = () => {
dialogFormRef.value?.validate((valid) => {
if (!valid) return;
const payload = { ...dialog.form };
const requestApi = payload.id ? slaughterHouseEdit : slaughterHouseAdd;
requestApi(payload)
.then(() => {
ElMessage.success(payload.id ? '更新成功' : '新增成功');
dialog.visible = false;
getList();
})
.catch((err) => {
console.error(err);
ElMessage.error(payload.id ? '更新失败' : '新增失败');
});
});
};
const handleDelete = (row) => {
ElMessageBox.confirm('确认删除该屠宰场吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => slaughterHouseDel(row.id))
.then(() => {
ElMessage.success('删除成功');
getList();
})
.catch((err) => {
if (err !== 'cancel') {
console.error(err);
ElMessage.error('删除失败');
}
});
};
onMounted(() => {
getList();
});
</script>
<style scoped>
.page-container {
padding: 0 0 16px;
}
.action-bar {
display: flex;
padding: 10px;
background: #fff;
margin-bottom: 10px;
}
</style>

View File

@@ -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"
>
<el-icon><Plus /></el-icon>
</el-upload>
@@ -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"
>
<el-icon><Plus /></el-icon>
</el-upload>
@@ -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;
}
</style>

View File

@@ -97,6 +97,7 @@
</el-table-column>
<el-table-column label="备注" prop="remark" show-overflow-tooltip></el-table-column>
<el-table-column label="创建时间" prop="createTime" width="180"></el-table-column>
<el-table-column label="创建人" prop="createByName" width="120"></el-table-column>
<el-table-column label="操作" width="160">
<template #default="scope">
<el-button link type="primary" @click="showAddDialog(scope.row)">编辑</el-button>
@@ -119,6 +120,7 @@ import { ElMessage, ElMessageBox } from 'element-plus';
import { Picture } from '@element-plus/icons-vue';
import baseSearch from '@/components/common/searchCustom/index.vue';
import { vehicleList, vehicleDel } from '@/api/userManage.js';
import { sysUserList } from '@/api/sys.js';
import VehicleDialog from './vehicleDialog.vue';
const baseSearchRef = ref();
@@ -143,6 +145,7 @@ const data = reactive({
rows: [],
total: 0,
dataListLoading: false,
userMap: {}, // 存储用户ID到用户名的映射 {userId: userName}
});
const form = reactive({
@@ -191,6 +194,8 @@ const getDataList = async () => {
data.rows = dataInfo?.rows || [];
data.total = dataInfo?.total || 0;
// 映射创建人名称
await mapCreatedByName();
} else {
ElMessage.error(res.msg || '查询失败');
}
@@ -265,6 +270,94 @@ const getImageCount = (value) => {
return 1;
};
// 映射创建人名称
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 = '--';
}
}
});
}
};
onMounted(() => {
getDataList();
});

View File

@@ -16,6 +16,11 @@
:file-list="ruleForm.carFrontPhoto"
:headers="importHeaders"
:on-exceed="handleExceed"
@drop.native="(e) => handleDrop(e, 'carFrontPhoto')"
@dragover.native.prevent
@dragenter.native="(e) => handleDragEnter(e)"
@dragleave.native="(e) => handleDragLeave(e)"
class="upload-drag-area"
>
<el-icon><Plus /></el-icon>
</el-upload>
@@ -32,6 +37,11 @@
:file-list="ruleForm.carRearPhoto"
:headers="importHeaders"
:on-exceed="handleExceed"
@drop.native="(e) => handleDrop(e, 'carRearPhoto')"
@dragover.native.prevent
@dragenter.native="(e) => handleDragEnter(e)"
@dragleave.native="(e) => handleDragLeave(e)"
class="upload-drag-area"
>
<el-icon><Plus /></el-icon>
</el-upload>
@@ -46,6 +56,11 @@
:before-upload="beforeAvatarUpload"
:file-list="ruleForm.drivingLicensePhoto"
:headers="importHeaders"
@drop.native="(e) => handleDrop(e, 'drivingLicensePhoto')"
@dragover.native.prevent
@dragenter.native="(e) => handleDragEnter(e)"
@dragleave.native="(e) => handleDragLeave(e)"
class="upload-drag-area"
>
<el-icon><Plus /></el-icon>
</el-upload>
@@ -60,6 +75,11 @@
:before-upload="beforeAvatarUpload"
:file-list="ruleForm.recordCode"
:headers="importHeaders"
@drop.native="(e) => handleDrop(e, 'recordCode')"
@dragover.native.prevent
@dragenter.native="(e) => handleDragEnter(e)"
@dragleave.native="(e) => handleDragLeave(e)"
class="upload-drag-area"
>
<el-icon><Plus /></el-icon>
</el-upload>
@@ -178,6 +198,132 @@ const handleExceed = () => {
// 已移除单图片限制,此方法不再需要
};
// 处理拖拽进入
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) => {
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 limit = type === 'carFrontPhoto' || type === 'carRearPhoto' ? 1 : undefined;
if (limit) {
// 单图片:如果已有图片,先清空
if (ruleForm[type].length > 0) {
ruleForm[type] = [];
}
// 只处理第一个文件
const file = imageFiles[0];
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(),
name: file.name,
status: 'success',
url: ''
};
// 调用成功处理函数
handleAvatarSuccess(res, fileObj, [fileObj], type);
})
.catch(error => {
console.error('上传失败:', error);
ElMessage.error('上传失败,请稍后重试');
});
} else {
// 多图片:处理所有文件
imageFiles.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('上传失败,请稍后重试');
});
});
}
};
const onClickSave = async () => {
await formDataRef.value.validate(async (valid) => {
if (valid) {
@@ -334,5 +480,40 @@ defineExpose({ open });
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;
}
</style>

View File

@@ -0,0 +1,99 @@
package com.aiotagro.cattletrade.business.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.aiotagro.cattletrade.business.dto.SlaughterEntryCreateDto;
import com.aiotagro.cattletrade.business.dto.SlaughterEntryDto;
import com.aiotagro.cattletrade.business.dto.SlaughterEntryEditDto;
import com.aiotagro.cattletrade.business.entity.SlaughterEntry;
import com.aiotagro.cattletrade.business.service.ISlaughterEntryService;
import com.aiotagro.common.core.web.domain.AjaxResult;
import com.aiotagro.common.core.web.domain.PageResultResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 屠宰场进场管理控制器
*
* @author System
* @date 2025-12-09
*/
@RestController
@RequestMapping("/slaughter/entry")
public class SlaughterEntryController {
@Autowired
private ISlaughterEntryService slaughterEntryService;
/**
* 分页查询进场记录
*/
@SaCheckPermission("slaughterEntry:query")
@PostMapping("/list")
public AjaxResult list(@RequestBody SlaughterEntryDto dto) {
try {
PageResultResponse<SlaughterEntry> result = slaughterEntryService.pageQuery(dto);
return AjaxResult.success(result);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("查询进场记录失败:" + e.getMessage());
}
}
/**
* 新增进场记录
*/
@SaCheckPermission("slaughterEntry:add")
@PostMapping("/add")
public AjaxResult add(@Validated @RequestBody SlaughterEntryCreateDto dto) {
try {
return slaughterEntryService.addEntry(dto);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("新增进场记录失败:" + e.getMessage());
}
}
/**
* 编辑进场记录
*/
@SaCheckPermission("slaughterEntry:edit")
@PostMapping("/edit")
public AjaxResult edit(@Validated @RequestBody SlaughterEntryEditDto dto) {
try {
return slaughterEntryService.updateEntry(dto);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("编辑进场记录失败:" + e.getMessage());
}
}
/**
* 删除进场记录
*/
@SaCheckPermission("slaughterEntry:delete")
@GetMapping("/delete")
public AjaxResult delete(@RequestParam Integer id) {
try {
return slaughterEntryService.deleteEntry(id);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("删除进场记录失败:" + e.getMessage());
}
}
/**
* 详情
*/
@SaCheckPermission("slaughterEntry:query")
@GetMapping("/detail")
public AjaxResult detail(@RequestParam Integer id) {
try {
return slaughterEntryService.detail(id);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("查询进场记录详情失败:" + e.getMessage());
}
}
}

View File

@@ -0,0 +1,115 @@
package com.aiotagro.cattletrade.business.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.aiotagro.cattletrade.business.dto.SlaughterHouseCreateDto;
import com.aiotagro.cattletrade.business.dto.SlaughterHouseDto;
import com.aiotagro.cattletrade.business.dto.SlaughterHouseEditDto;
import com.aiotagro.cattletrade.business.entity.SlaughterHouse;
import com.aiotagro.cattletrade.business.service.ISlaughterHouseService;
import com.aiotagro.common.core.web.domain.AjaxResult;
import com.aiotagro.common.core.web.domain.PageResultResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 屠宰场管理控制器
*
* @author System
* @date 2025-12-09
*/
@RestController
@RequestMapping("/slaughter/house")
public class SlaughterHouseController {
@Autowired
private ISlaughterHouseService slaughterHouseService;
/**
* 分页查询屠宰场
*/
@SaCheckPermission("slaughterHouse:query")
@PostMapping("/list")
public AjaxResult list(@RequestBody SlaughterHouseDto dto) {
try {
PageResultResponse<SlaughterHouse> result = slaughterHouseService.pageQuery(dto);
return AjaxResult.success(result);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("查询屠宰场列表失败:" + e.getMessage());
}
}
/**
* 新增屠宰场
*/
@SaCheckPermission("slaughterHouse:add")
@PostMapping("/add")
public AjaxResult add(@Validated @RequestBody SlaughterHouseCreateDto dto) {
try {
return slaughterHouseService.addHouse(dto);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("新增屠宰场失败:" + e.getMessage());
}
}
/**
* 编辑屠宰场
*/
@SaCheckPermission("slaughterHouse:edit")
@PostMapping("/edit")
public AjaxResult edit(@Validated @RequestBody SlaughterHouseEditDto dto) {
try {
return slaughterHouseService.updateHouse(dto);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("编辑屠宰场失败:" + e.getMessage());
}
}
/**
* 删除屠宰场
*/
@SaCheckPermission("slaughterHouse:delete")
@GetMapping("/delete")
public AjaxResult delete(@RequestParam Integer id) {
try {
return slaughterHouseService.deleteHouse(id);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("删除屠宰场失败:" + e.getMessage());
}
}
/**
* 详情
*/
@SaCheckPermission("slaughterHouse:query")
@GetMapping("/detail")
public AjaxResult detail(@RequestParam Integer id) {
try {
return slaughterHouseService.detail(id);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("查询屠宰场详情失败:" + e.getMessage());
}
}
/**
* 启用列表(下拉)
*/
@GetMapping("/all")
public AjaxResult allEnabled() {
try {
List<SlaughterHouse> list = slaughterHouseService.getAllEnabled();
return AjaxResult.success("查询成功", list);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("查询屠宰场列表失败:" + e.getMessage());
}
}
}

View File

@@ -0,0 +1,89 @@
package com.aiotagro.cattletrade.business.dto;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
/**
* 进场记录创建DTO
*
* @author System
* @date 2025-12-09
*/
@Data
public class SlaughterEntryCreateDto {
/**
* 屠宰场ID
*/
@NotNull(message = "屠宰场ID不能为空")
private Integer slaughterHouseId;
/**
* 运送清单ID
*/
@NotNull(message = "运送清单ID不能为空")
private Integer deliveryId;
/**
* 订单ID
*/
private Integer orderId;
/**
* 起始地
*/
private String startLocation;
/**
* 起始经度
*/
private String startLon;
/**
* 起始纬度
*/
private String startLat;
/**
* 目的地
*/
private String endLocation;
/**
* 目的地经度
*/
private String endLon;
/**
* 目的地纬度
*/
private String endLat;
/**
* 司机姓名
*/
private String driverName;
/**
* 联系方式
*/
private String driverMobile;
/**
* 车牌号
*/
private String licensePlate;
/**
* 出肉率(%
*/
private BigDecimal yieldRate;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,31 @@
package com.aiotagro.cattletrade.business.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 进场记录查询DTO
*
* @author System
* @date 2025-12-09
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class SlaughterEntryDto extends BaseDto {
/**
* 屠宰场ID
*/
private Integer slaughterHouseId;
/**
* 运送清单ID
*/
private Integer deliveryId;
/**
* 订单ID
*/
private Integer orderId;
}

View File

@@ -0,0 +1,95 @@
package com.aiotagro.cattletrade.business.dto;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
/**
* 进场记录编辑DTO
*
* @author System
* @date 2025-12-09
*/
@Data
public class SlaughterEntryEditDto {
/**
* 主键ID
*/
@NotNull(message = "主键ID不能为空")
private Integer id;
/**
* 屠宰场ID
*/
@NotNull(message = "屠宰场ID不能为空")
private Integer slaughterHouseId;
/**
* 运送清单ID
*/
@NotNull(message = "运送清单ID不能为空")
private Integer deliveryId;
/**
* 订单ID
*/
private Integer orderId;
/**
* 起始地
*/
private String startLocation;
/**
* 起始经度
*/
private String startLon;
/**
* 起始纬度
*/
private String startLat;
/**
* 目的地
*/
private String endLocation;
/**
* 目的地经度
*/
private String endLon;
/**
* 目的地纬度
*/
private String endLat;
/**
* 司机姓名
*/
private String driverName;
/**
* 联系方式
*/
private String driverMobile;
/**
* 车牌号
*/
private String licensePlate;
/**
* 出肉率(%
*/
private BigDecimal yieldRate;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,70 @@
package com.aiotagro.cattletrade.business.dto;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 屠宰场创建DTO
*
* @author System
* @date 2025-12-09
*/
@Data
public class SlaughterHouseCreateDto {
/**
* 屠宰场名称
*/
@NotBlank(message = "屠宰场名称不能为空")
private String houseName;
/**
* 屠宰场编码(为空时自动生成)
*/
private String slaughterCode;
/**
* 容量(头数)
*/
@NotNull(message = "容量不能为空")
private Integer capacity;
/**
* 负责人
*/
private String person;
/**
* 联系电话
*/
private String phone;
/**
* 地址
*/
@NotBlank(message = "地址不能为空")
private String address;
/**
* 经度
*/
private String longitude;
/**
* 纬度
*/
private String latitude;
/**
* 状态1-启用0-禁用
*/
private Integer status;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,31 @@
package com.aiotagro.cattletrade.business.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 屠宰场查询DTO
*
* @author System
* @date 2025-12-09
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class SlaughterHouseDto extends BaseDto {
/**
* 屠宰场名称
*/
private String houseName;
/**
* 屠宰场编码
*/
private String slaughterCode;
/**
* 状态1-启用0-禁用
*/
private Integer status;
}

View File

@@ -0,0 +1,77 @@
package com.aiotagro.cattletrade.business.dto;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 屠宰场编辑DTO
*
* @author System
* @date 2025-12-09
*/
@Data
public class SlaughterHouseEditDto {
/**
* 主键ID
*/
@NotNull(message = "主键ID不能为空")
private Integer id;
/**
* 屠宰场名称
*/
@NotBlank(message = "屠宰场名称不能为空")
private String houseName;
/**
* 屠宰场编码
*/
@NotBlank(message = "屠宰场编码不能为空")
private String slaughterCode;
/**
* 容量(头数)
*/
@NotNull(message = "容量不能为空")
private Integer capacity;
/**
* 负责人
*/
private String person;
/**
* 联系电话
*/
private String phone;
/**
* 地址
*/
@NotBlank(message = "地址不能为空")
private String address;
/**
* 经度
*/
private String longitude;
/**
* 纬度
*/
private String latitude;
/**
* 状态1-启用0-禁用
*/
private Integer status;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,150 @@
package com.aiotagro.cattletrade.business.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* 屠宰场进场记录实体
*
* @author System
* @date 2025-12-09
*/
@Data
@TableName("slaughter_entry")
public class SlaughterEntry implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 屠宰场ID
*/
@TableField("slaughter_house_id")
private Integer slaughterHouseId;
/**
* 运送清单ID
*/
@TableField("delivery_id")
private Integer deliveryId;
/**
* 订单ID
*/
@TableField("order_id")
private Integer orderId;
/**
* 起始地
*/
@TableField("start_location")
private String startLocation;
/**
* 起始经度
*/
@TableField("start_lon")
private String startLon;
/**
* 起始纬度
*/
@TableField("start_lat")
private String startLat;
/**
* 目的地
*/
@TableField("end_location")
private String endLocation;
/**
* 目的地经度
*/
@TableField("end_lon")
private String endLon;
/**
* 目的地纬度
*/
@TableField("end_lat")
private String endLat;
/**
* 司机姓名
*/
@TableField("driver_name")
private String driverName;
/**
* 联系方式
*/
@TableField("driver_mobile")
private String driverMobile;
/**
* 车牌号
*/
@TableField("license_plate")
private String licensePlate;
/**
* 出肉率(%
*/
@TableField("yield_rate")
private BigDecimal yieldRate;
/**
* 备注
*/
@TableField("remark")
private String remark;
/**
* 逻辑删除标记(0-正常,1-已删除)
*/
@TableLogic(value = "0", delval = "1")
@TableField("is_delete")
private Integer isDelete;
/**
* 创建时间
*/
@TableField("create_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
/**
* 创建人ID
*/
@TableField("created_by")
private Integer createdBy;
/**
* 更新时间
*/
@TableField("update_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
/**
* 更新人ID
*/
@TableField("updated_by")
private Integer updatedBy;
}

View File

@@ -0,0 +1,125 @@
package com.aiotagro.cattletrade.business.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 屠宰场实体
*
* @author System
* @date 2025-12-09
*/
@Data
@TableName("slaughter_house")
public class SlaughterHouse implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 屠宰场名称
*/
@TableField("house_name")
private String houseName;
/**
* 屠宰场编码
*/
@TableField("slaughter_code")
private String slaughterCode;
/**
* 容量(头数)
*/
@TableField("capacity")
private Integer capacity;
/**
* 负责人
*/
@TableField("person")
private String person;
/**
* 联系电话
*/
@TableField("phone")
private String phone;
/**
* 地址
*/
@TableField("address")
private String address;
/**
* 经度
*/
@TableField("longitude")
private String longitude;
/**
* 纬度
*/
@TableField("latitude")
private String latitude;
/**
* 状态1-启用0-禁用
*/
@TableField("status")
private Integer status;
/**
* 备注
*/
@TableField("remark")
private String remark;
/**
* 逻辑删除标记(0-正常,1-已删除)
*/
@TableLogic(value = "0", delval = "1")
@TableField("is_delete")
private Integer isDelete;
/**
* 创建时间
*/
@TableField("create_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
/**
* 创建人ID
*/
@TableField("created_by")
private Integer createdBy;
/**
* 更新时间
*/
@TableField("update_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
/**
* 更新人ID
*/
@TableField("updated_by")
private Integer updatedBy;
}

View File

@@ -0,0 +1,16 @@
package com.aiotagro.cattletrade.business.mapper;
import com.aiotagro.cattletrade.business.entity.SlaughterEntry;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 进场记录 Mapper
*
* @author System
* @date 2025-12-09
*/
@Mapper
public interface SlaughterEntryMapper extends BaseMapper<SlaughterEntry> {
}

View File

@@ -0,0 +1,38 @@
package com.aiotagro.cattletrade.business.mapper;
import com.aiotagro.cattletrade.business.entity.SlaughterHouse;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* 屠宰场管理 Mapper
*
* @author System
* @date 2025-12-09
*/
@Mapper
public interface SlaughterHouseMapper extends BaseMapper<SlaughterHouse> {
/**
* 根据编码查询
*/
@Select("SELECT * FROM slaughter_house WHERE slaughter_code = #{slaughterCode} AND is_delete = 0")
SlaughterHouse selectByCode(@Param("slaughterCode") String slaughterCode);
/**
* 查询指定前缀下的最新编码
*/
@Select("SELECT * FROM slaughter_house WHERE slaughter_code LIKE #{prefixPattern} AND is_delete = 0 ORDER BY slaughter_code DESC LIMIT 1")
SlaughterHouse selectLastCode(@Param("prefixPattern") String prefixPattern);
/**
* 查询所有启用的屠宰场
*/
@Select("SELECT id, house_name, slaughter_code FROM slaughter_house WHERE status = 1 AND is_delete = 0 ORDER BY create_time DESC")
List<SlaughterHouse> selectAllEnabled();
}

View File

@@ -0,0 +1,44 @@
package com.aiotagro.cattletrade.business.service;
import com.aiotagro.cattletrade.business.dto.SlaughterEntryCreateDto;
import com.aiotagro.cattletrade.business.dto.SlaughterEntryDto;
import com.aiotagro.cattletrade.business.dto.SlaughterEntryEditDto;
import com.aiotagro.cattletrade.business.entity.SlaughterEntry;
import com.aiotagro.common.core.web.domain.AjaxResult;
import com.aiotagro.common.core.web.domain.PageResultResponse;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* 进场管理服务接口
*
* @author System
* @date 2025-12-09
*/
public interface ISlaughterEntryService extends IService<SlaughterEntry> {
/**
* 分页查询
*/
PageResultResponse<SlaughterEntry> pageQuery(SlaughterEntryDto dto);
/**
* 新增进场记录
*/
AjaxResult addEntry(SlaughterEntryCreateDto dto);
/**
* 编辑进场记录
*/
AjaxResult updateEntry(SlaughterEntryEditDto dto);
/**
* 删除进场记录(逻辑删除)
*/
AjaxResult deleteEntry(Integer id);
/**
* 获取详情
*/
AjaxResult detail(Integer id);
}

View File

@@ -0,0 +1,51 @@
package com.aiotagro.cattletrade.business.service;
import com.aiotagro.cattletrade.business.dto.SlaughterHouseCreateDto;
import com.aiotagro.cattletrade.business.dto.SlaughterHouseDto;
import com.aiotagro.cattletrade.business.dto.SlaughterHouseEditDto;
import com.aiotagro.cattletrade.business.entity.SlaughterHouse;
import com.aiotagro.common.core.web.domain.AjaxResult;
import com.aiotagro.common.core.web.domain.PageResultResponse;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* 屠宰场管理服务接口
*
* @author System
* @date 2025-12-09
*/
public interface ISlaughterHouseService extends IService<SlaughterHouse> {
/**
* 分页查询
*/
PageResultResponse<SlaughterHouse> pageQuery(SlaughterHouseDto dto);
/**
* 新增
*/
AjaxResult addHouse(SlaughterHouseCreateDto dto);
/**
* 编辑
*/
AjaxResult updateHouse(SlaughterHouseEditDto dto);
/**
* 删除(逻辑)
*/
AjaxResult deleteHouse(Integer id);
/**
* 详情
*/
AjaxResult detail(Integer id);
/**
* 下拉列表
*/
List<SlaughterHouse> getAllEnabled();
}

View File

@@ -0,0 +1,178 @@
package com.aiotagro.cattletrade.business.service.impl;
import com.aiotagro.cattletrade.business.dto.SlaughterEntryCreateDto;
import com.aiotagro.cattletrade.business.dto.SlaughterEntryDto;
import com.aiotagro.cattletrade.business.dto.SlaughterEntryEditDto;
import com.aiotagro.cattletrade.business.entity.Delivery;
import com.aiotagro.cattletrade.business.entity.Order;
import com.aiotagro.cattletrade.business.entity.SlaughterEntry;
import com.aiotagro.cattletrade.business.entity.SlaughterHouse;
import com.aiotagro.cattletrade.business.mapper.DeliveryMapper;
import com.aiotagro.cattletrade.business.mapper.OrderMapper;
import com.aiotagro.cattletrade.business.mapper.SlaughterEntryMapper;
import com.aiotagro.cattletrade.business.mapper.SlaughterHouseMapper;
import com.aiotagro.cattletrade.business.service.ISlaughterEntryService;
import com.aiotagro.common.core.utils.SecurityUtil;
import com.aiotagro.common.core.utils.bean.BeanUtils;
import com.aiotagro.common.core.web.domain.AjaxResult;
import com.aiotagro.common.core.web.domain.PageResultResponse;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
/**
* 进场管理服务实现
*
* @author System
* @date 2025-12-09
*/
@Service
public class SlaughterEntryServiceImpl extends ServiceImpl<SlaughterEntryMapper, SlaughterEntry> implements ISlaughterEntryService {
@Autowired
private SlaughterEntryMapper slaughterEntryMapper;
@Autowired
private SlaughterHouseMapper slaughterHouseMapper;
@Autowired
private DeliveryMapper deliveryMapper;
@Autowired
private OrderMapper orderMapper;
@Override
public PageResultResponse<SlaughterEntry> pageQuery(SlaughterEntryDto dto) {
Integer pageNum = dto.getPageNum() != null ? dto.getPageNum() : 1;
Integer pageSize = dto.getPageSize() != null ? dto.getPageSize() : 10;
Page<SlaughterEntry> page = PageHelper.startPage(pageNum, pageSize);
LambdaQueryWrapper<SlaughterEntry> wrapper = new LambdaQueryWrapper<>();
wrapper.and(q -> q.eq(SlaughterEntry::getIsDelete, 0).or().isNull(SlaughterEntry::getIsDelete));
if (dto.getSlaughterHouseId() != null) {
wrapper.eq(SlaughterEntry::getSlaughterHouseId, dto.getSlaughterHouseId());
}
if (dto.getDeliveryId() != null) {
wrapper.eq(SlaughterEntry::getDeliveryId, dto.getDeliveryId());
}
if (dto.getOrderId() != null) {
wrapper.eq(SlaughterEntry::getOrderId, dto.getOrderId());
}
wrapper.orderByDesc(SlaughterEntry::getCreateTime);
List<SlaughterEntry> list = slaughterEntryMapper.selectList(wrapper);
return new PageResultResponse<>(page.getTotal(), list);
}
@Override
@Transactional
public AjaxResult addEntry(SlaughterEntryCreateDto dto) {
AjaxResult validateResult = validateRelation(dto.getSlaughterHouseId(), dto.getDeliveryId(), dto.getOrderId());
if (validateResult != null) {
return validateResult;
}
SlaughterEntry entry = new SlaughterEntry();
BeanUtils.copyProperties(dto, entry);
Integer userId = SecurityUtil.getCurrentUserId();
entry.setCreatedBy(userId);
entry.setCreateTime(new Date());
int inserted = slaughterEntryMapper.insert(entry);
return inserted > 0 ? AjaxResult.success("新增进场记录成功") : AjaxResult.error("新增进场记录失败");
}
@Override
@Transactional
public AjaxResult updateEntry(SlaughterEntryEditDto dto) {
if (dto.getId() == null) {
return AjaxResult.error("进场记录ID不能为空");
}
SlaughterEntry existing = slaughterEntryMapper.selectById(dto.getId());
if (existing == null || (existing.getIsDelete() != null && existing.getIsDelete() == 1)) {
return AjaxResult.error("进场记录不存在");
}
AjaxResult validateResult = validateRelation(dto.getSlaughterHouseId(), dto.getDeliveryId(), dto.getOrderId());
if (validateResult != null) {
return validateResult;
}
SlaughterEntry entry = new SlaughterEntry();
BeanUtils.copyProperties(dto, entry);
Integer userId = SecurityUtil.getCurrentUserId();
entry.setUpdatedBy(userId);
entry.setUpdateTime(new Date());
int updated = slaughterEntryMapper.updateById(entry);
return updated > 0 ? AjaxResult.success("更新进场记录成功") : AjaxResult.error("更新进场记录失败");
}
@Override
@Transactional
public AjaxResult deleteEntry(Integer id) {
if (id == null) {
return AjaxResult.error("进场记录ID不能为空");
}
SlaughterEntry entry = slaughterEntryMapper.selectById(id);
if (entry == null || (entry.getIsDelete() != null && entry.getIsDelete() == 1)) {
return AjaxResult.error("进场记录不存在");
}
Integer userId = SecurityUtil.getCurrentUserId();
UpdateWrapper<SlaughterEntry> wrapper = new UpdateWrapper<>();
wrapper.eq("id", id).eq("is_delete", 0)
.set("is_delete", 1)
.set("updated_by", userId)
.set("update_time", new Date());
int result = slaughterEntryMapper.update(null, wrapper);
return result > 0 ? AjaxResult.success("删除进场记录成功") : AjaxResult.error("删除进场记录失败");
}
@Override
public AjaxResult detail(Integer id) {
if (id == null) {
return AjaxResult.error("进场记录ID不能为空");
}
SlaughterEntry entry = slaughterEntryMapper.selectById(id);
if (entry == null || (entry.getIsDelete() != null && entry.getIsDelete() == 1)) {
return AjaxResult.error("进场记录不存在");
}
return AjaxResult.success("查询成功", entry);
}
/**
* 校验关联的屠宰场、运送清单、订单是否存在
*/
private AjaxResult validateRelation(Integer houseId, Integer deliveryId, Integer orderId) {
SlaughterHouse house = slaughterHouseMapper.selectById(houseId);
if (house == null || (house.getIsDelete() != null && house.getIsDelete() == 1)) {
return AjaxResult.error("屠宰场不存在");
}
Delivery delivery = deliveryMapper.selectById(deliveryId);
if (delivery == null) {
return AjaxResult.error("运送清单不存在");
}
if (orderId != null) {
Order order = orderMapper.selectById(orderId);
if (order == null || (order.getIsDelete() != null && order.getIsDelete() == 1)) {
return AjaxResult.error("订单不存在");
}
}
return null;
}
}

View File

@@ -0,0 +1,198 @@
package com.aiotagro.cattletrade.business.service.impl;
import com.aiotagro.cattletrade.business.dto.SlaughterHouseCreateDto;
import com.aiotagro.cattletrade.business.dto.SlaughterHouseDto;
import com.aiotagro.cattletrade.business.dto.SlaughterHouseEditDto;
import com.aiotagro.cattletrade.business.entity.SlaughterHouse;
import com.aiotagro.cattletrade.business.mapper.SlaughterHouseMapper;
import com.aiotagro.cattletrade.business.service.ISlaughterHouseService;
import com.aiotagro.common.core.utils.SecurityUtil;
import com.aiotagro.common.core.utils.bean.BeanUtils;
import com.aiotagro.common.core.web.domain.AjaxResult;
import com.aiotagro.common.core.web.domain.PageResultResponse;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
/**
* 屠宰场管理服务实现
*
* @author System
* @date 2025-12-09
*/
@Service
public class SlaughterHouseServiceImpl extends ServiceImpl<SlaughterHouseMapper, SlaughterHouse> implements ISlaughterHouseService {
@Autowired
private SlaughterHouseMapper slaughterHouseMapper;
@Override
public PageResultResponse<SlaughterHouse> pageQuery(SlaughterHouseDto dto) {
Integer pageNum = dto.getPageNum() != null ? dto.getPageNum() : 1;
Integer pageSize = dto.getPageSize() != null ? dto.getPageSize() : 10;
Page<SlaughterHouse> page = PageHelper.startPage(pageNum, pageSize);
LambdaQueryWrapper<SlaughterHouse> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.and(wrapper -> wrapper.eq(SlaughterHouse::getIsDelete, 0).or().isNull(SlaughterHouse::getIsDelete));
if (StringUtils.hasText(dto.getHouseName())) {
queryWrapper.like(SlaughterHouse::getHouseName, dto.getHouseName());
}
if (StringUtils.hasText(dto.getSlaughterCode())) {
queryWrapper.like(SlaughterHouse::getSlaughterCode, dto.getSlaughterCode());
}
if (dto.getStatus() != null) {
queryWrapper.eq(SlaughterHouse::getStatus, dto.getStatus());
}
queryWrapper.orderByDesc(SlaughterHouse::getCreateTime);
List<SlaughterHouse> list = slaughterHouseMapper.selectList(queryWrapper);
return new PageResultResponse<>(page.getTotal(), list);
}
@Override
@Transactional
public AjaxResult addHouse(SlaughterHouseCreateDto dto) {
String code = dto.getSlaughterCode();
if (!StringUtils.hasText(code)) {
code = generateSlaughterCode();
} else {
SlaughterHouse exist = slaughterHouseMapper.selectByCode(code);
if (exist != null) {
return AjaxResult.error("屠宰场编码已存在");
}
}
SlaughterHouse house = new SlaughterHouse();
BeanUtils.copyProperties(dto, house);
house.setSlaughterCode(code);
if (house.getStatus() == null) {
house.setStatus(1);
}
Integer userId = SecurityUtil.getCurrentUserId();
house.setCreatedBy(userId);
house.setCreateTime(new Date());
try {
int inserted = slaughterHouseMapper.insert(house);
return inserted > 0 ? AjaxResult.success("新增屠宰场成功") : AjaxResult.error("新增屠宰场失败");
} catch (DuplicateKeyException e) {
return AjaxResult.error("屠宰场编码已存在,请使用其他编码");
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("新增屠宰场失败:" + e.getMessage());
}
}
@Override
@Transactional
public AjaxResult updateHouse(SlaughterHouseEditDto dto) {
if (dto.getId() == null) {
return AjaxResult.error("屠宰场ID不能为空");
}
SlaughterHouse exist = slaughterHouseMapper.selectById(dto.getId());
if (exist == null || (exist.getIsDelete() != null && exist.getIsDelete() == 1)) {
return AjaxResult.error("屠宰场不存在");
}
SlaughterHouse codeHouse = slaughterHouseMapper.selectByCode(dto.getSlaughterCode());
if (codeHouse != null && !codeHouse.getId().equals(dto.getId())) {
return AjaxResult.error("屠宰场编码已存在");
}
SlaughterHouse house = new SlaughterHouse();
BeanUtils.copyProperties(dto, house);
Integer userId = SecurityUtil.getCurrentUserId();
house.setUpdatedBy(userId);
house.setUpdateTime(new Date());
try {
int updated = slaughterHouseMapper.updateById(house);
return updated > 0 ? AjaxResult.success("更新屠宰场成功") : AjaxResult.error("更新屠宰场失败");
} catch (DuplicateKeyException e) {
return AjaxResult.error("屠宰场编码已存在,请使用其他编码");
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("更新屠宰场失败:" + e.getMessage());
}
}
@Override
@Transactional
public AjaxResult deleteHouse(Integer id) {
if (id == null) {
return AjaxResult.error("屠宰场ID不能为空");
}
SlaughterHouse house = slaughterHouseMapper.selectById(id);
if (house == null || (house.getIsDelete() != null && house.getIsDelete() == 1)) {
return AjaxResult.error("屠宰场不存在");
}
Integer userId = SecurityUtil.getCurrentUserId();
UpdateWrapper<SlaughterHouse> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id", id).eq("is_delete", 0)
.set("is_delete", 1)
.set("updated_by", userId)
.set("update_time", new Date());
int result = slaughterHouseMapper.update(null, updateWrapper);
return result > 0 ? AjaxResult.success("删除成功") : AjaxResult.error("删除失败");
}
@Override
public AjaxResult detail(Integer id) {
if (id == null) {
return AjaxResult.error("屠宰场ID不能为空");
}
SlaughterHouse house = slaughterHouseMapper.selectById(id);
if (house == null || (house.getIsDelete() != null && house.getIsDelete() == 1)) {
return AjaxResult.error("屠宰场不存在");
}
return AjaxResult.success("查询成功", house);
}
@Override
public List<SlaughterHouse> getAllEnabled() {
return slaughterHouseMapper.selectAllEnabled();
}
/**
* 生成屠宰场编码SLyyyyMMdd + 4位流水
*/
private String generateSlaughterCode() {
String dateStr = new SimpleDateFormat("yyyyMMdd").format(new Date());
String prefix = "SL" + dateStr;
SlaughterHouse last = slaughterHouseMapper.selectLastCode(prefix + "%");
int next = 1;
if (last != null && StringUtils.hasText(last.getSlaughterCode())) {
String lastCode = last.getSlaughterCode();
if (lastCode.startsWith(prefix)) {
String seq = lastCode.substring(prefix.length());
try {
next = Integer.parseInt(seq) + 1;
} catch (NumberFormatException ignored) {
next = 1;
}
}
}
return String.format("%s%04d", prefix, next);
}
}

View File

@@ -0,0 +1,68 @@
USE cattletrade;
-- 创建屠宰场管理相关表
-- 1. 屠宰场表slaughter_house
-- 2. 进场表slaughter_entry
-- ============================================
-- 1. 屠宰场表slaughter_house
-- ============================================
CREATE TABLE IF NOT EXISTS `slaughter_house` (
`id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`house_name` VARCHAR(100) NOT NULL COMMENT '屠宰场名称',
`slaughter_code` VARCHAR(50) NOT NULL COMMENT '屠宰场编码格式SLyyyyMMddXXXX',
`capacity` INT(11) DEFAULT NULL COMMENT '容量(头数)',
`address` VARCHAR(255) DEFAULT NULL COMMENT '地址',
`longitude` VARCHAR(50) DEFAULT NULL COMMENT '经度',
`latitude` VARCHAR(50) DEFAULT NULL COMMENT '纬度',
`status` TINYINT(1) DEFAULT 1 COMMENT '状态1-启用0-禁用',
`remark` TEXT COMMENT '备注',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`created_by` INT(11) DEFAULT NULL COMMENT '创建人ID',
`updated_by` INT(11) DEFAULT NULL COMMENT '更新人ID',
`is_delete` TINYINT(1) DEFAULT 0 COMMENT '逻辑删除0-未删除1-已删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_slaughter_code` (`slaughter_code`),
KEY `idx_house_name` (`house_name`),
KEY `idx_status` (`status`),
KEY `idx_is_delete` (`is_delete`),
KEY `idx_create_time` (`create_time`),
KEY `idx_status_is_delete` (`status`, `is_delete`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='屠宰场表';
-- ============================================
-- 2. 进场表slaughter_entry
-- ============================================
CREATE TABLE IF NOT EXISTS `slaughter_entry` (
`id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`slaughter_house_id` INT(11) NOT NULL COMMENT '屠宰场ID',
`delivery_id` INT(11) NOT NULL COMMENT '运送清单ID关联delivery表',
`order_id` INT(11) DEFAULT NULL COMMENT '订单ID关联order表',
`start_location` VARCHAR(255) DEFAULT NULL COMMENT '起始地',
`start_lon` VARCHAR(50) DEFAULT NULL COMMENT '起始经度',
`start_lat` VARCHAR(50) DEFAULT NULL COMMENT '起始纬度',
`end_location` VARCHAR(255) DEFAULT NULL COMMENT '目的地',
`end_lon` VARCHAR(50) DEFAULT NULL COMMENT '目的地经度',
`end_lat` VARCHAR(50) DEFAULT NULL COMMENT '目的地纬度',
`driver_name` VARCHAR(50) DEFAULT NULL COMMENT '司机姓名',
`driver_mobile` VARCHAR(20) DEFAULT NULL COMMENT '联系方式',
`license_plate` VARCHAR(20) DEFAULT NULL COMMENT '车牌号',
`yield_rate` DECIMAL(5,2) DEFAULT NULL COMMENT '出肉率(%',
`remark` TEXT COMMENT '备注',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`created_by` INT(11) DEFAULT NULL COMMENT '创建人ID',
`updated_by` INT(11) DEFAULT NULL COMMENT '更新人ID',
`is_delete` TINYINT(1) DEFAULT 0 COMMENT '逻辑删除0-未删除1-已删除',
PRIMARY KEY (`id`),
KEY `idx_slaughter_house_id` (`slaughter_house_id`),
KEY `idx_delivery_id` (`delivery_id`),
KEY `idx_order_id` (`order_id`),
KEY `idx_is_delete` (`is_delete`),
KEY `idx_create_time` (`create_time`),
CONSTRAINT `fk_slaughter_entry_house` FOREIGN KEY (`slaughter_house_id`) REFERENCES `slaughter_house` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `fk_slaughter_entry_delivery` FOREIGN KEY (`delivery_id`) REFERENCES `delivery` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `fk_slaughter_entry_order` FOREIGN KEY (`order_id`) REFERENCES `order` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='屠宰场进场表';

View File

@@ -0,0 +1,74 @@
USE cattletrade;
-- 插入屠宰场管理菜单(一级目录 + 两个二级菜单 + 按钮权限)
-- 菜单类型0-目录1-菜单2-按钮
-- 1) 一级菜单:屠宰场管理
INSERT INTO sys_menu (parent_id, type, icon, name, sort, route_url, page_url, org_type, authority, create_time, update_time, is_delete)
VALUES (
0,
0,
'el-icon-knife-fork',
'屠宰场管理',
70,
NULL,
NULL,
2,
NULL,
NOW(),
NOW(),
0
);
SET @slaughter_parent_id = LAST_INSERT_ID();
-- 2) 二级菜单:屠宰场管理(列表页)
INSERT INTO sys_menu (parent_id, type, icon, name, sort, route_url, page_url, org_type, authority, create_time, update_time, is_delete)
VALUES (
@slaughter_parent_id,
1,
'el-icon-office-building',
'屠宰场管理',
1,
'house',
'slaughter/house',
2,
'slaughterHouse:list',
NOW(),
NOW(),
0
);
SET @slaughter_house_menu_id = LAST_INSERT_ID();
INSERT INTO sys_menu (parent_id, type, icon, name, sort, route_url, page_url, org_type, authority, create_time, update_time, is_delete)
VALUES
(@slaughter_house_menu_id, 2, NULL, '新增', 1, NULL, NULL, 2, 'slaughterHouse:add', NOW(), NOW(), 0),
(@slaughter_house_menu_id, 2, NULL, '编辑', 2, NULL, NULL, 2, 'slaughterHouse:edit', NOW(), NOW(), 0),
(@slaughter_house_menu_id, 2, NULL, '删除', 3, NULL, NULL, 2, 'slaughterHouse:delete', NOW(), NOW(), 0),
(@slaughter_house_menu_id, 2, NULL, '查询', 4, NULL, NULL, 2, 'slaughterHouse:query', NOW(), NOW(), 0);
-- 3) 二级菜单:进场管理
INSERT INTO sys_menu (parent_id, type, icon, name, sort, route_url, page_url, org_type, authority, create_time, update_time, is_delete)
VALUES (
@slaughter_parent_id,
1,
'el-icon-truck',
'进场管理',
2,
'entry',
'slaughter/entry',
2,
'slaughterEntry:list',
NOW(),
NOW(),
0
);
SET @slaughter_entry_menu_id = LAST_INSERT_ID();
INSERT INTO sys_menu (parent_id, type, icon, name, sort, route_url, page_url, org_type, authority, create_time, update_time, is_delete)
VALUES
(@slaughter_entry_menu_id, 2, NULL, '新增', 1, NULL, NULL, 2, 'slaughterEntry:add', NOW(), NOW(), 0),
(@slaughter_entry_menu_id, 2, NULL, '编辑', 2, NULL, NULL, 2, 'slaughterEntry:edit', NOW(), NOW(), 0),
(@slaughter_entry_menu_id, 2, NULL, '删除', 3, NULL, NULL, 2, 'slaughterEntry:delete', NOW(), NOW(), 0),
(@slaughter_entry_menu_id, 2, NULL, '查询', 4, NULL, NULL, 2, 'slaughterEntry:query', NOW(), NOW(), 0);