优化项目细节和sql查询
This commit is contained in:
@@ -327,3 +327,27 @@ export function updateDeliveryInfo(data) {
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 订单关联管理 API ====================
|
||||
|
||||
// 解除订单与运送清单的关联
|
||||
export function unbindOrderFromDelivery(orderId) {
|
||||
return request({
|
||||
url: '/order/unbindFromDelivery',
|
||||
method: 'POST',
|
||||
data: { orderId },
|
||||
});
|
||||
}
|
||||
|
||||
// 查询运送清单关联的订单列表
|
||||
export function getOrdersByDeliveryId(deliveryId) {
|
||||
return request({
|
||||
url: '/order/list',
|
||||
method: 'POST',
|
||||
data: {
|
||||
deliveryId: deliveryId,
|
||||
pageNum: 1,
|
||||
pageSize: 1000, // 获取所有关联的订单
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -98,7 +98,7 @@ app.use(JsonViewer);
|
||||
|
||||
app.use(BaiduMap, {
|
||||
// ak 是在百度地图开发者平台申请的密钥 详见 http://lbsyun.baidu.com/apiconsole/key */
|
||||
ak: 'fLz8UwJSM3ayYl6dtsWYp7TQ8993R6kC', //
|
||||
ak: 'xITbC7jegaAAuu4m9jC2Zx6eFbQJ29Rj', //
|
||||
// v: '2.0', // 默认使用3.0
|
||||
// type: 'WebGL' // ||API 默认API (使用此模式 BMap=BMapGL)
|
||||
// type: 'WebGL', // ||API 默认API (使用此模式 BMap=BMapGL)
|
||||
|
||||
@@ -237,18 +237,6 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
requireAuth: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'loadingOrder', // ✅ 修复:使用相对路径
|
||||
name: 'LoadingOrder',
|
||||
meta: {
|
||||
title: '装车订单',
|
||||
keepAlive: true,
|
||||
requireAuth: true,
|
||||
},
|
||||
component: () => import('~/views/shipping/loadingOrder.vue'),
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
path: 'shippinglist', // ✅ 修复:使用相对路径
|
||||
name: 'ShippingList',
|
||||
@@ -259,6 +247,19 @@ export const constantRoutes: Array<RouteRecordRaw> = [
|
||||
},
|
||||
component: () => import('~/views/entry/attestation.vue'),
|
||||
},
|
||||
{
|
||||
path: 'loadingOrder', // ✅ 修复:使用相对路径
|
||||
name: 'LoadingOrder',
|
||||
meta: {
|
||||
title: '订单信息',
|
||||
keepAlive: true,
|
||||
requireAuth: true,
|
||||
},
|
||||
component: () => import('~/views/shipping/loadingOrder.vue'),
|
||||
},
|
||||
|
||||
|
||||
|
||||
],
|
||||
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ export function BMPGL(ak) {
|
||||
};
|
||||
const script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.src = `http://api.map.baidu.com/api?v=3.0&type=webgl&ak=${ak}&callback=init`;
|
||||
script.src = `https://api.map.baidu.com/api?v=3.0&type=webgl&ak=${ak}&callback=init`;
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
|
||||
@@ -749,7 +749,7 @@ const loadDeviceLogs = async (deviceId, deviceType, deliveryId) => {
|
||||
const initMap = async () => {
|
||||
try {
|
||||
// 使用百度地图 API Key
|
||||
const BMapGL = await BMPGL('fLz8UwJSM3ayYl6dtsWYp7TQ8993R6kC');
|
||||
const BMapGL = await BMPGL('xITbC7jegaAAuu4m9jC2Zx6eFbQJ29Rj');
|
||||
const lat = parseFloat(warningData.latitude);
|
||||
const lon = parseFloat(warningData.longitude);
|
||||
|
||||
@@ -1054,7 +1054,7 @@ const initTrackMap = async () => {
|
||||
}
|
||||
|
||||
try {
|
||||
const BMapGL = await BMPGL('fLz8UwJSM3ayYl6dtsWYp7TQ8993R6kC');
|
||||
const BMapGL = await BMPGL('xITbC7jegaAAuu4m9jC2Zx6eFbQJ29Rj');
|
||||
trackBMapGL.value = BMapGL; // 保存 BMapGL 实例
|
||||
|
||||
// 创建地图实例
|
||||
@@ -1147,7 +1147,7 @@ const drawStaticTrack = (BMapGL) => {
|
||||
// 使用简单的圆形标记作为起点(绿色)
|
||||
const startMarker = new BMapGL.Marker(startPoint, {
|
||||
icon: new BMapGL.Icon(
|
||||
'http://api.map.baidu.com/images/marker_red.png',
|
||||
'https://api.map.baidu.com/images/marker_red.png',
|
||||
new BMapGL.Size(32, 32),
|
||||
{ anchor: new BMapGL.Size(16, 32) }
|
||||
)
|
||||
@@ -1174,7 +1174,7 @@ const drawStaticTrack = (BMapGL) => {
|
||||
// 使用红色标记作为终点
|
||||
const endMarker = new BMapGL.Marker(endPoint, {
|
||||
icon: new BMapGL.Icon(
|
||||
'http://api.map.baidu.com/images/marker_red.png',
|
||||
'https://api.map.baidu.com/images/marker_red.png',
|
||||
new BMapGL.Size(32, 32),
|
||||
{ anchor: new BMapGL.Size(16, 32) }
|
||||
)
|
||||
@@ -1219,7 +1219,7 @@ const drawDynamicTrack = (BMapGL) => {
|
||||
const startPoint = new BMapGL.Point(trackPath.value[0].lng, trackPath.value[0].lat);
|
||||
const startMarker = new BMapGL.Marker(startPoint, {
|
||||
icon: new BMapGL.Icon(
|
||||
'http://api.map.baidu.com/images/marker_red.png',
|
||||
'https://api.map.baidu.com/images/marker_red.png',
|
||||
new BMapGL.Size(32, 32),
|
||||
{ anchor: new BMapGL.Size(16, 32) }
|
||||
)
|
||||
@@ -1232,7 +1232,7 @@ const drawDynamicTrack = (BMapGL) => {
|
||||
const currentPoint = new BMapGL.Point(trackPath.value[0].lng, trackPath.value[0].lat);
|
||||
const playMarker = new BMapGL.Marker(currentPoint, {
|
||||
icon: new BMapGL.Icon(
|
||||
'http://api.map.baidu.com/images/marker_red.png',
|
||||
'https://api.map.baidu.com/images/marker_red.png',
|
||||
new BMapGL.Size(20, 20),
|
||||
{ anchor: new BMapGL.Size(10, 20) }
|
||||
)
|
||||
|
||||
@@ -2,6 +2,21 @@
|
||||
<div>
|
||||
<base-search :formItemList="formItemList" @search="searchFrom" ref="baseSearchRef"> </base-search>
|
||||
|
||||
<!-- 横向滚动操作栏 -->
|
||||
<div class="operation-scroll-bar">
|
||||
<div class="operation-scroll-container">
|
||||
<!-- <el-button type="primary" v-hasPermi="['loading:create']" @click="showAddOrderDialog(null)">创建订单</el-button> -->
|
||||
<el-button
|
||||
type="primary"
|
||||
v-hasPermi="['loading:add']"
|
||||
@click="showCreateDeliveryDialog"
|
||||
style="margin-left: 10px"
|
||||
>
|
||||
新增运送清单
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main-container">
|
||||
<el-table v-loading="dataListLoading" :data="data.rows" border element-loading-text="数据加载中...">
|
||||
<el-table-column label="运单号" prop="deliveryNumber"></el-table-column>
|
||||
@@ -94,11 +109,12 @@
|
||||
<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="350">
|
||||
<el-table-column label="操作" width="400">
|
||||
<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="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>
|
||||
@@ -122,6 +138,8 @@
|
||||
|
||||
<!-- 编辑对话框 -->
|
||||
<createDeliveryDialog ref="editDialogRef" @success="getDataList" />
|
||||
<!-- 订单对话框 -->
|
||||
<OrderDialog ref="OrderDialogRef" @success="handleOrderSuccess" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -132,6 +150,7 @@ import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import baseSearch from '@/components/common/searchCustom/index.vue';
|
||||
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';
|
||||
|
||||
@@ -139,6 +158,7 @@ const router = useRouter();
|
||||
const route = useRoute();
|
||||
const baseSearchRef = ref();
|
||||
const editDialogRef = ref();
|
||||
const OrderDialogRef = ref();
|
||||
const dataListLoading = ref(false);
|
||||
const downLoading = reactive({});
|
||||
const form = reactive({
|
||||
@@ -837,9 +857,87 @@ const downloadAcceptanceFormHandler = async (row) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 显示创建订单对话框
|
||||
const showAddOrderDialog = (deliveryId) => {
|
||||
if (OrderDialogRef.value) {
|
||||
OrderDialogRef.value.onShowDialog(null, deliveryId);
|
||||
}
|
||||
};
|
||||
|
||||
// 显示新增运送清单对话框
|
||||
const showCreateDeliveryDialog = () => {
|
||||
if (editDialogRef.value) {
|
||||
editDialogRef.value.open();
|
||||
}
|
||||
};
|
||||
|
||||
// 订单创建成功回调
|
||||
const handleOrderSuccess = () => {
|
||||
getDataList();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getDataList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped>
|
||||
/* 横向滚动操作栏样式 */
|
||||
.operation-scroll-bar {
|
||||
background: #fff;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
padding: 12px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.operation-scroll-container {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
gap: 10px;
|
||||
padding-bottom: 5px;
|
||||
|
||||
/* 自定义滚动条样式 */
|
||||
&::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
}
|
||||
|
||||
/* 确保按钮不会被压缩 */
|
||||
.el-button {
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.operation-scroll-bar {
|
||||
padding: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.operation-scroll-container {
|
||||
gap: 8px;
|
||||
|
||||
.el-button {
|
||||
font-size: 12px;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
</template>
|
||||
<el-descriptions :column="4" border size="large">
|
||||
<el-descriptions-item label="运单号">{{ data.baseInfo.deliveryNumber || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="卖方">{{ data.baseInfo.supplierName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="买方">{{ data.baseInfo.buyerName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="卖方">{{ getSupplierName() }}</el-descriptions-item>
|
||||
<el-descriptions-item label="买方">{{ getBuyerName() }}</el-descriptions-item>
|
||||
<el-descriptions-item label="车牌号">{{ data.baseInfo.licensePlate || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="司机姓名">{{ data.baseInfo.driverName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="起始地">{{ data.baseInfo.startLocation || '-' }}</el-descriptions-item>
|
||||
@@ -85,8 +85,13 @@
|
||||
|
||||
<!-- 重量信息分组 -->
|
||||
<div v-if="hasValue(data.baseInfo.emptyWeight) || hasValue(data.baseInfo.entruckWeight) || hasValue(data.baseInfo.landingEntruckWeight)" class="info-group">
|
||||
<div class="sub-title">重量信息</div>
|
||||
<el-descriptions :column="3" border>
|
||||
<div class="card-header">
|
||||
<span class="header-title">重量信息</span>
|
||||
</div>
|
||||
<div class="weight-info-container">
|
||||
<!-- 基础重量信息 -->
|
||||
<div class="weight-basic-info">
|
||||
<el-descriptions :column="3" border size="large">
|
||||
<el-descriptions-item v-if="hasValue(data.baseInfo.emptyWeight)" label="空车过磅重量">
|
||||
{{ data.baseInfo.emptyWeight }}kg
|
||||
</el-descriptions-item>
|
||||
@@ -98,6 +103,24 @@
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<!-- 计算重量信息 -->
|
||||
<div class="weight-calculated-info">
|
||||
<el-descriptions :column="3" border size="large">
|
||||
<el-descriptions-item v-if="departureCattleWeight !== null" label="发车牛只重量">
|
||||
<span class="calculated-value">{{ departureCattleWeight }}kg</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="landingCattleWeight !== null" label="落地牛只重量">
|
||||
<span class="calculated-value">{{ landingCattleWeight }}kg</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="weightLoss !== null" label="损耗">
|
||||
<span class="calculated-value loss-value" :class="{ 'positive-loss': weightLoss < 0, 'negative-loss': weightLoss > 0 }">
|
||||
{{ weightLoss > 0 ? '-' : weightLoss < 0 ? '+' : '' }}{{ Math.abs(weightLoss) }}kg
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</div>
|
||||
</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">
|
||||
@@ -185,7 +208,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="media-item" v-if="hasValue(data.baseInfo.destinationPoundListImg)">
|
||||
<div class="media-label">到地纸质磅单</div>
|
||||
<div class="media-label">落地纸质磅单</div>
|
||||
<el-image
|
||||
class="photo-img"
|
||||
:src="data.baseInfo.destinationPoundListImg"
|
||||
@@ -195,7 +218,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="media-item" v-if="hasValue(data.baseInfo.destinationVehicleFrontPhoto)">
|
||||
<div class="media-label">到地过重磅车头</div>
|
||||
<div class="media-label">落地过重磅车头</div>
|
||||
<el-image
|
||||
class="photo-img"
|
||||
:src="data.baseInfo.destinationVehicleFrontPhoto"
|
||||
@@ -236,7 +259,7 @@
|
||||
<video controls :src="data.baseInfo.unloadCattleVideo" />
|
||||
</div>
|
||||
<div class="media-item video-item" v-if="hasValue(data.baseInfo.destinationWeightVideo)">
|
||||
<div class="media-label">到地过磅视频</div>
|
||||
<div class="media-label">落地过磅视频</div>
|
||||
<video controls :src="data.baseInfo.destinationWeightVideo" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -1119,6 +1142,40 @@ const getStatusType = (status) => {
|
||||
};
|
||||
|
||||
// 判断字段是否有有效值(用于隐藏空值字段)
|
||||
// 计算发车牛只重量(装车过磅重量 - 空车过磅重量)
|
||||
const departureCattleWeight = computed(() => {
|
||||
const emptyWeight = parseFloat(data.baseInfo.emptyWeight);
|
||||
const entruckWeight = parseFloat(data.baseInfo.entruckWeight);
|
||||
if (!isNaN(emptyWeight) && !isNaN(entruckWeight) && emptyWeight > 0 && entruckWeight > 0) {
|
||||
const result = entruckWeight - emptyWeight;
|
||||
return result > 0 ? result.toFixed(2) : null;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
// 计算落地牛只重量(落地过磅重量 - 空车过磅重量)
|
||||
const landingCattleWeight = computed(() => {
|
||||
const emptyWeight = parseFloat(data.baseInfo.emptyWeight);
|
||||
const landingWeight = parseFloat(data.baseInfo.landingEntruckWeight);
|
||||
if (!isNaN(emptyWeight) && !isNaN(landingWeight) && emptyWeight > 0 && landingWeight > 0) {
|
||||
const result = landingWeight - emptyWeight;
|
||||
return result > 0 ? result.toFixed(2) : null;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
// 计算损耗(发车牛只重量 - 落地牛只重量)
|
||||
// 正数表示损耗(减少),负数表示增重(增加)
|
||||
const weightLoss = computed(() => {
|
||||
const departure = departureCattleWeight.value;
|
||||
const landing = landingCattleWeight.value;
|
||||
if (departure !== null && landing !== null) {
|
||||
const result = parseFloat(departure) - parseFloat(landing);
|
||||
return result.toFixed(2);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const hasValue = (value) => {
|
||||
if (value === null || value === undefined) {
|
||||
return false;
|
||||
@@ -1129,6 +1186,26 @@ const hasValue = (value) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
// 获取卖方名称:如果是从订单页面进入的,使用订单的卖方;否则显示 '-'
|
||||
const getSupplierName = () => {
|
||||
// 检查是否从订单页面进入(通过路由参数 fromOrder 判断)
|
||||
if (route.query.fromOrder === 'true' && route.query.sellerName) {
|
||||
return route.query.sellerName;
|
||||
}
|
||||
// 不是从订单页面进入的,直接显示 '-'
|
||||
return '-';
|
||||
};
|
||||
|
||||
// 获取买方名称:如果是从订单页面进入的,使用订单的买方;否则显示 '-'
|
||||
const getBuyerName = () => {
|
||||
// 检查是否从订单页面进入(通过路由参数 fromOrder 判断)
|
||||
if (route.query.fromOrder === 'true' && route.query.buyerName) {
|
||||
return route.query.buyerName;
|
||||
}
|
||||
// 不是从订单页面进入的,直接显示 '-'
|
||||
return '-';
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
data.id = route.query.id;
|
||||
data.status = route.query.status;
|
||||
@@ -1260,6 +1337,44 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.weight-info-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.weight-basic-info,
|
||||
.weight-calculated-info {
|
||||
flex: 1;
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
.weight-calculated-info {
|
||||
.calculated-value {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.loss-value {
|
||||
&.positive-loss {
|
||||
color: #67c23a; // 绿色表示增重
|
||||
}
|
||||
|
||||
&.negative-loss {
|
||||
color: #f56c6c; // 红色表示损耗
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.weight-basic-info,
|
||||
.weight-calculated-info {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.media-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
|
||||
@@ -22,12 +22,13 @@
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
:key="`order-select-${orderList.length}`"
|
||||
@change="handleOrderChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in orderList"
|
||||
:key="item.id"
|
||||
:label="`订单${item.id} - 买方: ${item.buyerName} | 卖方: ${item.sellerName} | 单价: ${item.firmPrice}元/斤`"
|
||||
:key="`order-${item.id}`"
|
||||
:label="`订单${item.id} - 买方: ${item.buyerName || '--'} | 卖方: ${item.sellerName || '--'} | 单价: ${item.firmPrice ? (item.firmPrice + '元/斤') : '--'}`"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
@@ -35,6 +36,54 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 已关联订单管理区域 -->
|
||||
<el-row :gutter="20" v-if="formData.editId && associatedOrders.length > 0">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="已关联订单">
|
||||
<el-table :data="associatedOrders" border size="small" style="width: 100%">
|
||||
<el-table-column label="订单ID" prop="id" width="100">
|
||||
<template #default="scope">
|
||||
{{ scope.row.id || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="买方" prop="buyerName" width="150">
|
||||
<template #default="scope">
|
||||
{{ scope.row.buyerName || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="卖方" prop="sellerName" width="150">
|
||||
<template #default="scope">
|
||||
{{ scope.row.sellerName || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价" prop="firmPrice" width="120">
|
||||
<template #default="scope">
|
||||
{{ scope.row.firmPrice ? (scope.row.firmPrice + ' 元/斤') : '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结算方式" prop="settlementTypeDesc" width="120">
|
||||
<template #default="scope">
|
||||
{{ scope.row.settlementTypeDesc || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="unbindOrder(scope.row.id)"
|
||||
:loading="unbindingOrderId === scope.row.id"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="车牌号" prop="plateNumber">
|
||||
@@ -219,26 +268,53 @@
|
||||
<el-input v-model="formData.quarantineCertNo" placeholder="请输入检疫证号(可选)" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="空车过磅重量" prop="emptyWeight">
|
||||
<el-input-number
|
||||
v-model="formData.emptyWeight"
|
||||
placeholder="请输入空车过磅重量(kg)"
|
||||
:precision="2"
|
||||
:min="0"
|
||||
:step="0.01"
|
||||
:controls-position="'right'"
|
||||
style="width: 100%"
|
||||
class="weight-input-number"
|
||||
>
|
||||
<template #append>kg</template>
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="空车过磅重量" prop="emptyWeight">
|
||||
<el-input-number v-model="formData.emptyWeight" placeholder="请输入空车过磅重量" :precision="2" style="width: 100%">
|
||||
<template #append>kg</template>
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="装车过磅重量" prop="entruckWeight">
|
||||
<el-input-number v-model="formData.entruckWeight" placeholder="请输入装车过磅重量" :precision="2" style="width: 100%">
|
||||
<el-input-number
|
||||
v-model="formData.entruckWeight"
|
||||
placeholder="请输入装车过磅重量(kg)"
|
||||
:precision="2"
|
||||
:min="0"
|
||||
:step="0.01"
|
||||
:controls-position="'right'"
|
||||
style="width: 100%"
|
||||
class="weight-input-number"
|
||||
>
|
||||
<template #append>kg</template>
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="落地过磅重量" prop="landingEntruckWeight">
|
||||
<el-input-number v-model="formData.landingEntruckWeight" placeholder="请输入落地过磅重量" :precision="2" style="width: 100%">
|
||||
<el-input-number
|
||||
v-model="formData.landingEntruckWeight"
|
||||
placeholder="请输入落地过磅重量(kg)"
|
||||
:precision="2"
|
||||
:min="0"
|
||||
:step="0.01"
|
||||
:controls-position="'right'"
|
||||
style="width: 100%"
|
||||
class="weight-input-number"
|
||||
>
|
||||
<template #append>kg</template>
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
@@ -279,7 +355,7 @@
|
||||
</el-divider>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="检疫票" prop="quarantineTickeyUrl">
|
||||
<el-form-item label="检疫票" prop="quarantineTickeyUrl" label-width="180px">
|
||||
<div class="photo-upload-wrapper">
|
||||
<el-upload
|
||||
class="avatar-uploader"
|
||||
@@ -322,7 +398,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="传纸质磅单(双章)" prop="poundListImg">
|
||||
<el-form-item label="发车纸质磅单(双章)" prop="poundListImg" label-width="180px">
|
||||
<div class="photo-upload-wrapper">
|
||||
<el-upload
|
||||
class="avatar-uploader"
|
||||
@@ -367,7 +443,7 @@
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="车辆空磅上磅车头照片" prop="emptyVehicleFrontPhoto">
|
||||
<el-form-item label="车辆空磅上磅车头照片" prop="emptyVehicleFrontPhoto" label-width="180px">
|
||||
<div class="photo-upload-wrapper">
|
||||
<el-upload
|
||||
class="avatar-uploader"
|
||||
@@ -410,7 +486,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="车辆过重磅车头照片" prop="loadedVehicleFrontPhoto">
|
||||
<el-form-item label="车辆过重磅车头照片" prop="loadedVehicleFrontPhoto" label-width="180px">
|
||||
<div class="photo-upload-wrapper">
|
||||
<el-upload
|
||||
class="avatar-uploader"
|
||||
@@ -455,7 +531,7 @@
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="车辆重磅照片" prop="loadedVehicleWeightPhoto">
|
||||
<el-form-item label="车辆重磅侧方照片" prop="loadedVehicleWeightPhoto" label-width="180px">
|
||||
<div class="photo-upload-wrapper">
|
||||
<el-upload
|
||||
class="avatar-uploader"
|
||||
@@ -498,7 +574,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="驾驶员手持身份证站车头照片" prop="driverIdCardPhoto">
|
||||
<el-form-item label="驾驶员手持身份证站车头照片" prop="driverIdCardPhoto" label-width="180px">
|
||||
<div class="photo-upload-wrapper">
|
||||
<el-upload
|
||||
class="avatar-uploader"
|
||||
@@ -543,7 +619,7 @@
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="到地纸质磅单(双章)" prop="destinationPoundListImg">
|
||||
<el-form-item label="落地纸质磅单(双章)" prop="destinationPoundListImg" label-width="180px">
|
||||
<div class="photo-upload-wrapper">
|
||||
<el-upload
|
||||
class="avatar-uploader"
|
||||
@@ -586,7 +662,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="到地车辆过重磅车头照片" prop="destinationVehicleFrontPhoto">
|
||||
<el-form-item label="落地车辆过重磅车头照片" prop="destinationVehicleFrontPhoto" label-width="180px">
|
||||
<div class="photo-upload-wrapper">
|
||||
<el-upload
|
||||
class="avatar-uploader"
|
||||
@@ -636,7 +712,7 @@
|
||||
</el-divider>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="装车过磅视频" prop="entruckWeightVideo">
|
||||
<el-form-item label="装车过磅视频" prop="entruckWeightVideo" label-width="180px">
|
||||
<el-upload
|
||||
accept=".mp4,.avi,.rmvb,.mkv,.MP4,.AVI,.RMVB,.MKV"
|
||||
class="upload-demo"
|
||||
@@ -663,7 +739,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="空车过磅视频" prop="emptyWeightVideo">
|
||||
<el-form-item label="空车过磅视频" prop="emptyWeightVideo" label-width="180px">
|
||||
<el-upload
|
||||
accept=".mp4,.avi,.rmvb,.mkv,.MP4,.AVI,.RMVB,.MKV"
|
||||
class="upload-demo"
|
||||
@@ -692,7 +768,7 @@
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="装牛视频" prop="cattleLoadingVideo">
|
||||
<el-form-item label="装牛视频" prop="cattleLoadingVideo" label-width="180px">
|
||||
<el-upload
|
||||
accept=".mp4,.avi,.rmvb,.mkv,.MP4,.AVI,.RMVB,.MKV"
|
||||
class="upload-demo"
|
||||
@@ -719,7 +795,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="控槽视频" prop="controlSlotVideo">
|
||||
<el-form-item label="控槽视频" prop="controlSlotVideo" label-width="180px">
|
||||
<el-upload
|
||||
accept=".mp4,.avi,.rmvb,.mkv,.MP4,.AVI,.RMVB,.MKV"
|
||||
class="upload-demo"
|
||||
@@ -748,7 +824,7 @@
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="装完牛绕车一圈视频" prop="cattleLoadingCircleVideo">
|
||||
<el-form-item label="装完牛绕车一圈视频" prop="cattleLoadingCircleVideo" label-width="180px">
|
||||
<el-upload
|
||||
accept=".mp4,.avi,.rmvb,.mkv,.MP4,.AVI,.RMVB,.MKV"
|
||||
class="upload-demo"
|
||||
@@ -775,7 +851,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="卸牛视频" prop="unloadCattleVideo">
|
||||
<el-form-item label="卸牛视频" prop="unloadCattleVideo" label-width="180px">
|
||||
<el-upload
|
||||
accept=".mp4,.avi,.rmvb,.mkv,.MP4,.AVI,.RMVB,.MKV"
|
||||
class="upload-demo"
|
||||
@@ -804,7 +880,7 @@
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="到地过磅视频" prop="destinationWeightVideo">
|
||||
<el-form-item label="落地过磅视频" prop="destinationWeightVideo" label-width="180px">
|
||||
<el-upload
|
||||
accept=".mp4,.avi,.rmvb,.mkv,.MP4,.AVI,.RMVB,.MKV"
|
||||
class="upload-demo"
|
||||
@@ -832,7 +908,7 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-form-item label="备注" prop="remark" label-width="180px">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
type="textarea"
|
||||
@@ -907,11 +983,11 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, watch } from 'vue';
|
||||
import { ref, reactive, computed, watch, nextTick } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { ElImageViewer } from 'element-plus';
|
||||
import { Plus, UploadFilled, Delete, ZoomIn } from '@element-plus/icons-vue';
|
||||
import { createDelivery, updateDeviceDeliveryId, orderPageQuery, getDeliveryDetail, updateOrderDeliveryId } from '@/api/shipping.js';
|
||||
import { createDelivery, updateDeviceDeliveryId, orderPageQuery, getDeliveryDetail, updateOrderDeliveryId, getOrdersByDeliveryId, unbindOrderFromDelivery } from '@/api/shipping.js';
|
||||
import * as shippingApi from '@/api/shipping.js';
|
||||
import { driverList as fetchDriverList, vehicleList as fetchVehicleList } from '@/api/userManage.js';
|
||||
import { iotDeviceQueryList } from '@/api/hardware.js';
|
||||
@@ -934,6 +1010,8 @@ const collarList = ref([]);
|
||||
const driverOptions = ref([]);
|
||||
const vehicleOptions = ref([]);
|
||||
const orderList = ref([]);
|
||||
const associatedOrders = ref([]); // 已关联的订单列表
|
||||
const unbindingOrderId = ref(null); // 正在解除关联的订单ID
|
||||
const showStartLocationMap = ref(false);
|
||||
const showEndLocationMap = ref(false);
|
||||
|
||||
@@ -973,7 +1051,7 @@ const formData = reactive({
|
||||
loadedVehicleFrontPhoto: '',
|
||||
loadedVehicleWeightPhoto: '',
|
||||
driverIdCardPhoto: '',
|
||||
// 到地相关照片
|
||||
// 落地相关照片
|
||||
destinationPoundListImg: '',
|
||||
destinationVehicleFrontPhoto: '',
|
||||
// 视频
|
||||
@@ -1169,6 +1247,44 @@ const open = async (editData = null) => {
|
||||
loadOrderList()
|
||||
]);
|
||||
|
||||
// 如果是编辑模式,加载已关联的订单
|
||||
if (editData && editData.id) {
|
||||
await loadAssociatedOrders(editData.id);
|
||||
|
||||
// 将已关联的订单补充到订单列表中(确保下拉框能正确显示)
|
||||
// 必须在填充表单之前完成,这样formData.orderIds填充时,orderList中已有完整信息
|
||||
if (associatedOrders.value.length > 0) {
|
||||
associatedOrders.value.forEach(associatedOrder => {
|
||||
// 检查订单列表中是否已存在该订单
|
||||
const exists = orderList.value.find(order => order.id === associatedOrder.id);
|
||||
if (!exists) {
|
||||
// 如果不存在,添加到订单列表中
|
||||
orderList.value.push(associatedOrder);
|
||||
console.log('[ORDER-LIST] 补充订单到列表:', associatedOrder);
|
||||
} else {
|
||||
// 如果存在但信息不完整,更新订单信息
|
||||
let updated = false;
|
||||
if (!exists.buyerName && associatedOrder.buyerName) {
|
||||
exists.buyerName = associatedOrder.buyerName;
|
||||
updated = true;
|
||||
}
|
||||
if (!exists.sellerName && associatedOrder.sellerName) {
|
||||
exists.sellerName = associatedOrder.sellerName;
|
||||
updated = true;
|
||||
}
|
||||
if (!exists.firmPrice && associatedOrder.firmPrice) {
|
||||
exists.firmPrice = associatedOrder.firmPrice;
|
||||
updated = true;
|
||||
}
|
||||
if (updated) {
|
||||
console.log('[ORDER-LIST] 更新订单信息:', exists);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
associatedOrders.value = [];
|
||||
}
|
||||
|
||||
// 标记开始恢复缓存,避免watch触发
|
||||
isRestoringFromCache.value = true;
|
||||
@@ -1176,6 +1292,38 @@ const open = async (editData = null) => {
|
||||
// 如果传入了编辑数据,则填充表单(编辑模式)
|
||||
if (editData) {
|
||||
fillFormWithEditData(editData);
|
||||
|
||||
// 填充表单后,再次确保orderList中有完整的订单信息(使用nextTick确保DOM已更新)
|
||||
await nextTick();
|
||||
if (formData.orderIds && formData.orderIds.length > 0) {
|
||||
formData.orderIds.forEach(orderId => {
|
||||
const existsInOrderList = orderList.value.find(order => order.id === orderId);
|
||||
if (!existsInOrderList) {
|
||||
// 从associatedOrders中查找并补充
|
||||
const associatedOrder = associatedOrders.value.find(order => order.id === orderId);
|
||||
if (associatedOrder) {
|
||||
orderList.value.push(associatedOrder);
|
||||
console.log('[AFTER-FILL] 补充订单到orderList:', associatedOrder);
|
||||
}
|
||||
} else {
|
||||
// 如果存在但信息不完整,从associatedOrders中更新
|
||||
const associatedOrder = associatedOrders.value.find(order => order.id === orderId);
|
||||
if (associatedOrder) {
|
||||
if (!existsInOrderList.buyerName && associatedOrder.buyerName) {
|
||||
existsInOrderList.buyerName = associatedOrder.buyerName;
|
||||
}
|
||||
if (!existsInOrderList.sellerName && associatedOrder.sellerName) {
|
||||
existsInOrderList.sellerName = associatedOrder.sellerName;
|
||||
}
|
||||
if (!existsInOrderList.firmPrice && associatedOrder.firmPrice) {
|
||||
existsInOrderList.firmPrice = associatedOrder.firmPrice;
|
||||
}
|
||||
console.log('[AFTER-FILL] 更新订单信息:', existsInOrderList);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 编辑模式:尝试从缓存恢复数据(如果缓存存在,用于补充未填充的字段)
|
||||
const delivery = editData.delivery || editData;
|
||||
if (delivery.id) {
|
||||
@@ -1239,16 +1387,58 @@ const fillFormWithEditData = (editData) => {
|
||||
|
||||
// 基本信息(将逗号分隔的订单ID字符串转换为数组)
|
||||
if (delivery.orderId) {
|
||||
let orderIdArray = [];
|
||||
if (typeof delivery.orderId === 'string') {
|
||||
// 如果是字符串,按逗号分割
|
||||
formData.orderIds = delivery.orderId.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id));
|
||||
// 如果是字符串,按逗号分割并转换为整数数组
|
||||
orderIdArray = delivery.orderId.split(',').map(id => parseInt(id.trim())).filter(id => !isNaN(id));
|
||||
} else if (Array.isArray(delivery.orderId)) {
|
||||
// 如果已经是数组,直接使用
|
||||
formData.orderIds = delivery.orderId;
|
||||
// 如果已经是数组,确保转换为整数数组
|
||||
orderIdArray = delivery.orderId.map(id => {
|
||||
if (typeof id === 'number') {
|
||||
return id;
|
||||
} else if (typeof id === 'string') {
|
||||
const parsed = parseInt(id);
|
||||
return isNaN(parsed) ? null : parsed;
|
||||
}
|
||||
return null;
|
||||
}).filter(id => id !== null);
|
||||
} else {
|
||||
// 如果是单个数字,转换为数组
|
||||
formData.orderIds = [delivery.orderId];
|
||||
const id = typeof delivery.orderId === 'number' ? delivery.orderId : parseInt(delivery.orderId);
|
||||
orderIdArray = isNaN(id) ? [] : [id];
|
||||
}
|
||||
|
||||
formData.orderIds = orderIdArray;
|
||||
|
||||
// 确保orderList中包含这些订单的完整信息
|
||||
// 如果orderList中没有对应的订单,从associatedOrders中查找并补充
|
||||
orderIdArray.forEach(orderId => {
|
||||
const existsInOrderList = orderList.value.find(order => order.id === orderId);
|
||||
if (!existsInOrderList) {
|
||||
// 从associatedOrders中查找
|
||||
const associatedOrder = associatedOrders.value.find(order => order.id === orderId);
|
||||
if (associatedOrder) {
|
||||
orderList.value.push(associatedOrder);
|
||||
console.log('[FILL-FORM] 从associatedOrders补充订单到orderList:', associatedOrder);
|
||||
} else {
|
||||
console.warn('[FILL-FORM] 订单ID', orderId, '在orderList和associatedOrders中都不存在');
|
||||
}
|
||||
} else {
|
||||
// 如果存在但信息不完整,尝试从associatedOrders中更新
|
||||
const associatedOrder = associatedOrders.value.find(order => order.id === orderId);
|
||||
if (associatedOrder) {
|
||||
if (!existsInOrderList.buyerName && associatedOrder.buyerName) {
|
||||
existsInOrderList.buyerName = associatedOrder.buyerName;
|
||||
}
|
||||
if (!existsInOrderList.sellerName && associatedOrder.sellerName) {
|
||||
existsInOrderList.sellerName = associatedOrder.sellerName;
|
||||
}
|
||||
if (!existsInOrderList.firmPrice && associatedOrder.firmPrice) {
|
||||
existsInOrderList.firmPrice = associatedOrder.firmPrice;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
formData.orderIds = [];
|
||||
}
|
||||
@@ -1318,6 +1508,30 @@ const fillFormWithEditData = (editData) => {
|
||||
// 司机运费
|
||||
formData.freight = delivery.freight || null;
|
||||
|
||||
// 检疫证号(从quarantineTickey字段读取)
|
||||
// 支持多种可能的数据结构和字段名
|
||||
// 1. 从 delivery 对象中读取
|
||||
// 2. 从 editData 根级读取(如果 delivery 中没有)
|
||||
// 3. 支持多种字段名:quarantineTickey、quarantine_tickey、quarantineCertNo
|
||||
const quarantineValue = delivery.quarantineTickey
|
||||
|| delivery.quarantine_tickey
|
||||
|| delivery.quarantineCertNo
|
||||
|| (editData && editData.quarantineTickey)
|
||||
|| (editData && editData.quarantine_tickey)
|
||||
|| (editData && editData.quarantineCertNo)
|
||||
|| '';
|
||||
formData.quarantineCertNo = quarantineValue;
|
||||
console.log('[FILL-FORM] 检疫证号填充:', {
|
||||
'delivery.quarantineTickey': delivery.quarantineTickey,
|
||||
'delivery.quarantine_tickey': delivery.quarantine_tickey,
|
||||
'delivery.quarantineCertNo': delivery.quarantineCertNo,
|
||||
'editData.quarantineTickey': editData?.quarantineTickey,
|
||||
'editData.quarantine_tickey': editData?.quarantine_tickey,
|
||||
'editData.quarantineCertNo': editData?.quarantineCertNo,
|
||||
'finalValue': formData.quarantineCertNo,
|
||||
'delivery对象': delivery
|
||||
});
|
||||
|
||||
// 照片
|
||||
formData.quarantineTickeyUrl = delivery.quarantineTickeyUrl || '';
|
||||
formData.poundListImg = delivery.poundListImg || '';
|
||||
@@ -1430,7 +1644,18 @@ const loadOrderList = async () => {
|
||||
// 兼容运行时找不到函数引用的问题,优先从命名空间调用
|
||||
const res = await (orderPageQuery ? orderPageQuery({ pageNum: 1, pageSize: 1000 }) : shippingApi.orderPageQuery({ pageNum: 1, pageSize: 1000 }));
|
||||
if (res.code === 200) {
|
||||
orderList.value = res.data?.rows || [];
|
||||
const responseData = res.data || res;
|
||||
if (responseData && typeof responseData === 'object' && 'rows' in responseData) {
|
||||
orderList.value = responseData.rows || [];
|
||||
} else if (responseData && responseData.data && 'rows' in responseData.data) {
|
||||
orderList.value = responseData.data.rows || [];
|
||||
} else {
|
||||
orderList.value = [];
|
||||
}
|
||||
console.log('[ORDER-LIST] 加载订单列表成功,数量:', orderList.value.length);
|
||||
if (orderList.value.length > 0) {
|
||||
console.log('[ORDER-LIST] 第一个订单示例:', orderList.value[0]);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载订单列表失败', error);
|
||||
@@ -1445,6 +1670,69 @@ const handleOrderChange = async (orderIds) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 加载已关联的订单列表
|
||||
const loadAssociatedOrders = async (deliveryId) => {
|
||||
try {
|
||||
const res = await getOrdersByDeliveryId(deliveryId);
|
||||
if (res.code === 200) {
|
||||
const responseData = res.data || res;
|
||||
if (responseData && typeof responseData === 'object' && 'rows' in responseData) {
|
||||
associatedOrders.value = responseData.rows || [];
|
||||
} else if (responseData && responseData.data && 'rows' in responseData.data) {
|
||||
associatedOrders.value = responseData.data.rows || [];
|
||||
} else {
|
||||
associatedOrders.value = [];
|
||||
}
|
||||
console.log('[ASSOCIATED-ORDERS] 加载已关联订单成功,数量:', associatedOrders.value.length);
|
||||
if (associatedOrders.value.length > 0) {
|
||||
console.log('[ASSOCIATED-ORDERS] 第一个订单示例:', associatedOrders.value[0]);
|
||||
console.log('[ASSOCIATED-ORDERS] 订单信息检查:', {
|
||||
id: associatedOrders.value[0].id,
|
||||
buyerName: associatedOrders.value[0].buyerName,
|
||||
sellerName: associatedOrders.value[0].sellerName,
|
||||
firmPrice: associatedOrders.value[0].firmPrice
|
||||
});
|
||||
}
|
||||
} else {
|
||||
associatedOrders.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载已关联订单失败:', error);
|
||||
associatedOrders.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 解除订单关联
|
||||
const unbindOrder = async (orderId) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要解除该订单与运送清单的关联吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
});
|
||||
|
||||
unbindingOrderId.value = orderId;
|
||||
const res = await unbindOrderFromDelivery(orderId);
|
||||
unbindingOrderId.value = null;
|
||||
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('解除关联成功');
|
||||
// 刷新已关联订单列表
|
||||
if (formData.editId) {
|
||||
await loadAssociatedOrders(formData.editId);
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(res.msg || '解除关联失败');
|
||||
}
|
||||
} catch (error) {
|
||||
unbindingOrderId.value = null;
|
||||
if (error !== 'cancel') {
|
||||
console.error('解除订单关联失败:', error);
|
||||
ElMessage.error('解除关联失败,请重试');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 司机选择变化时自动填充电话
|
||||
const handleDriverChange = (driverId) => {
|
||||
if (driverId) {
|
||||
@@ -1610,7 +1898,38 @@ const handleSubmit = () => {
|
||||
|
||||
if (res.code === 200) {
|
||||
// 获取运送清单ID(新增返回data.id,编辑直接用editId)
|
||||
const deliveryId = formData.editId || res.data?.id;
|
||||
let deliveryId = formData.editId || res.data?.id;
|
||||
|
||||
// 如果res.data是Delivery对象,从对象中获取id
|
||||
if (!deliveryId && res.data && typeof res.data === 'object' && res.data.id) {
|
||||
deliveryId = res.data.id;
|
||||
}
|
||||
|
||||
// 确保deliveryId是整数
|
||||
if (deliveryId) {
|
||||
if (typeof deliveryId === 'string') {
|
||||
// 如果是逗号分隔的字符串,取第一个(这种情况不应该发生,但做保护)
|
||||
if (deliveryId.includes(',')) {
|
||||
console.warn('[CREATE-DELIVERY] deliveryId包含逗号,可能是orderId:', deliveryId);
|
||||
deliveryId = deliveryId.split(',')[0];
|
||||
}
|
||||
const parsed = parseInt(deliveryId);
|
||||
if (isNaN(parsed)) {
|
||||
console.error('[CREATE-DELIVERY] deliveryId格式不正确:', deliveryId);
|
||||
ElMessage.error('获取运送清单ID失败');
|
||||
return;
|
||||
}
|
||||
deliveryId = parsed;
|
||||
} else if (typeof deliveryId !== 'number') {
|
||||
deliveryId = parseInt(deliveryId);
|
||||
if (isNaN(deliveryId)) {
|
||||
console.error('[CREATE-DELIVERY] deliveryId格式不正确:', deliveryId);
|
||||
ElMessage.error('获取运送清单ID失败');
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.log('[CREATE-DELIVERY] 获取到的deliveryId:', deliveryId, '类型:', typeof deliveryId);
|
||||
}
|
||||
|
||||
if (deliveryId) {
|
||||
// 更新设备的delivery_id
|
||||
@@ -1619,9 +1938,20 @@ const handleSubmit = () => {
|
||||
// 更新关联订单的delivery_id
|
||||
if (formData.orderIds && formData.orderIds.length > 0) {
|
||||
try {
|
||||
// 确保orderIds是整数数组,而不是字符串数组
|
||||
const orderIdsArray = formData.orderIds.map(id => {
|
||||
if (typeof id === 'string') {
|
||||
const parsed = parseInt(id);
|
||||
return isNaN(parsed) ? null : parsed;
|
||||
}
|
||||
return typeof id === 'number' ? id : parseInt(id);
|
||||
}).filter(id => id !== null);
|
||||
|
||||
console.log('[ORDER-UPDATE] 准备更新订单关联, deliveryId:', deliveryId, 'orderIds:', orderIdsArray);
|
||||
|
||||
const updateOrderRes = await updateOrderDeliveryId({
|
||||
deliveryId: deliveryId,
|
||||
orderIds: formData.orderIds
|
||||
orderIds: orderIdsArray
|
||||
});
|
||||
if (updateOrderRes.code === 200) {
|
||||
console.log('[ORDER-UPDATE] 成功更新订单delivery_id,订单数量:', formData.orderIds.length);
|
||||
@@ -2170,6 +2500,35 @@ let watchTimer = null;
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
/* 重量输入框样式优化 */
|
||||
:deep(.weight-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.weight-input-number .el-input__inner) {
|
||||
font-size: 14px;
|
||||
padding: 0 15px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
:deep(.weight-input-number .el-input-number__decrease),
|
||||
:deep(.weight-input-number .el-input-number__increase) {
|
||||
width: 32px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
:deep(.weight-input-number .el-input-group__append) {
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
background-color: #f5f7fa;
|
||||
color: #606266;
|
||||
border-left: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@@ -4,19 +4,9 @@
|
||||
<!-- 横向滚动操作栏 -->
|
||||
<div class="operation-scroll-bar">
|
||||
<div class="operation-scroll-container">
|
||||
<el-button type="primary" v-hasPermi="['loading:create']" @click="showAddDialog(null)">创建订单</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
v-hasPermi="['loading:add']"
|
||||
@click="showCreateDeliveryDialog"
|
||||
style="margin-left: 10px"
|
||||
>
|
||||
新增运送清单
|
||||
</el-button>
|
||||
<el-dropdown
|
||||
v-hasPermi="['order:import']"
|
||||
@command="handleImportCommand"
|
||||
style="margin-left: 10px"
|
||||
>
|
||||
<el-button type="success">
|
||||
导入数据
|
||||
@@ -34,58 +24,66 @@
|
||||
</div>
|
||||
|
||||
<div class="main-container">
|
||||
<el-table :data="rows" :key="data.tableKey" border v-loading="data.dataListLoading" element-loading-text="数据加载中..." style="width: 100%">
|
||||
<el-table-column label="订单ID" prop="id" width="100">
|
||||
<el-table
|
||||
:data="rows"
|
||||
:key="data.tableKey"
|
||||
border
|
||||
v-loading="data.dataListLoading"
|
||||
element-loading-text="数据加载中..."
|
||||
style="width: 100%"
|
||||
:show-overflow-tooltip="true"
|
||||
>
|
||||
<el-table-column label="订单ID" prop="id" min-width="80" width="100">
|
||||
<template #default="scope">
|
||||
{{ scope.row.id || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="买方" prop="buyerName" width="200">
|
||||
<el-table-column label="买方" prop="buyerName" min-width="120" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ scope.row.buyerName || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="卖方" prop="sellerName" width="200">
|
||||
<el-table-column label="卖方" prop="sellerName" min-width="120" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ scope.row.sellerName || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结算方式" prop="settlementTypeDesc" width="150">
|
||||
<el-table-column label="结算方式" prop="settlementTypeDesc" min-width="100" width="120">
|
||||
<template #default="scope">
|
||||
{{ scope.row.settlementTypeDesc || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="运单号" prop="deliveryNumber" width="150">
|
||||
<el-table-column label="运单号" prop="deliveryNumber" min-width="120" width="150" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ scope.row.deliveryNumber || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="起始地" prop="startLocation" width="200">
|
||||
<el-table-column label="起始地" prop="startLocation" min-width="150" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ scope.row.startLocation || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="目的地" prop="endLocation" width="200">
|
||||
<el-table-column label="目的地" prop="endLocation" min-width="150" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ scope.row.endLocation || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单价" prop="firmPrice" width="120">
|
||||
<el-table-column label="单价" prop="firmPrice" min-width="100" width="120">
|
||||
<template #default="scope">
|
||||
{{ (scope.row.firmPriceFromDelivery || scope.row.firmPrice) ? ((scope.row.firmPriceFromDelivery || scope.row.firmPrice) + ' 元/斤') : '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建人" prop="createdByName" width="120">
|
||||
<el-table-column label="创建人" prop="createdByName" min-width="100" width="120" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ scope.row.createdByName || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" prop="createTime" width="180">
|
||||
<el-table-column label="创建时间" prop="createTime" min-width="160" width="180">
|
||||
<template #default="scope">
|
||||
{{ scope.row.createTime || '--' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="300" fixed="right">
|
||||
<el-table-column label="操作" min-width="280" width="300" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" v-hasPermi="['order:edit']" @click="showEditDialog(scope.row)">编辑</el-button>
|
||||
<el-button link type="primary" v-hasPermi="['order:delete']" @click="del(scope.row.id)">删除</el-button>
|
||||
@@ -93,7 +91,7 @@
|
||||
link
|
||||
type="primary"
|
||||
v-if="scope.row.deliveryId"
|
||||
@click="viewDeliveryDetail(scope.row.deliveryId)"
|
||||
@click="viewDeliveryDetail(scope.row)"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
@@ -361,16 +359,26 @@ const showCreateDeliveryDialog = () => {
|
||||
};
|
||||
|
||||
// 查看运送清单详情
|
||||
const viewDeliveryDetail = (deliveryId) => {
|
||||
if (!deliveryId) {
|
||||
const viewDeliveryDetail = (row) => {
|
||||
if (!row || !row.deliveryId) {
|
||||
ElMessage.warning('运单ID不存在');
|
||||
return;
|
||||
}
|
||||
const query = {
|
||||
id: row.deliveryId
|
||||
};
|
||||
// 如果是从订单页面进入,传递订单的买方和卖方信息
|
||||
if (row.buyerName) {
|
||||
query.buyerName = row.buyerName;
|
||||
}
|
||||
if (row.sellerName) {
|
||||
query.sellerName = row.sellerName;
|
||||
}
|
||||
// 标记是从订单页面进入的
|
||||
query.fromOrder = 'true';
|
||||
router.push({
|
||||
path: '/entry/details',
|
||||
query: {
|
||||
id: deliveryId
|
||||
}
|
||||
query: query
|
||||
});
|
||||
};
|
||||
|
||||
@@ -439,7 +447,7 @@ const initTrackMap = async () => {
|
||||
}
|
||||
|
||||
try {
|
||||
const BMapGL = await BMPGL('fLz8UwJSM3ayYl6dtsWYp7TQ8993R6kC');
|
||||
const BMapGL = await BMPGL('xITbC7jegaAAuu4m9jC2Zx6eFbQJ29Rj');
|
||||
|
||||
// 创建地图实例
|
||||
trackMapInstance.value = new BMapGL.Map('trackMap');
|
||||
@@ -907,6 +915,21 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
/* 表格容器优化 */
|
||||
.main-container {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
|
||||
:deep(.el-table) {
|
||||
width: 100%;
|
||||
min-width: 1400px; /* 确保所有列都能显示 */
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper) {
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.operation-scroll-bar {
|
||||
@@ -922,6 +945,12 @@ onMounted(() => {
|
||||
padding: 6px 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.main-container {
|
||||
:deep(.el-table) {
|
||||
min-width: 1200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { orderAddNew, orderUpdate } from '@/api/shipping.js';
|
||||
import { orderAddNew, orderUpdate, updateDeliveryInfo } from '@/api/shipping.js';
|
||||
import { memberListByType } from '@/api/userManage.js';
|
||||
|
||||
const emits = defineEmits();
|
||||
@@ -120,6 +120,7 @@ const ruleForm = reactive({
|
||||
sellerId: null, // 卖方ID(单选)
|
||||
settlementType: 1, // 结算方式:1-上车重量,2-下车重量,3-按肉价结算
|
||||
firmPrice: null, // 约定价格(元/斤)
|
||||
deliveryId: null, // 运送清单ID(关联时使用)
|
||||
});
|
||||
|
||||
// 验证买方和卖方不能是同一用户
|
||||
@@ -165,6 +166,7 @@ const handleClose = () => {
|
||||
ruleForm.sellerId = null;
|
||||
ruleForm.settlementType = 1;
|
||||
ruleForm.firmPrice = null;
|
||||
ruleForm.deliveryId = null;
|
||||
data.dialogVisible = false;
|
||||
};
|
||||
|
||||
@@ -297,18 +299,64 @@ const onClickSave = () => {
|
||||
firmPrice: ruleForm.firmPrice, // 约定价格(元/斤)
|
||||
};
|
||||
|
||||
// 如果是新增订单且有deliveryId,传递deliveryId参数
|
||||
if (!ruleForm.id && ruleForm.deliveryId) {
|
||||
params.deliveryId = ruleForm.deliveryId;
|
||||
}
|
||||
|
||||
data.saveLoading = true;
|
||||
|
||||
// 根据是否有ID判断是新增还是编辑
|
||||
const savePromise = ruleForm.id ? orderUpdate(params) : orderAddNew(params);
|
||||
|
||||
savePromise.then((res) => {
|
||||
savePromise.then(async (res) => {
|
||||
data.saveLoading = false;
|
||||
if (res.code === 200) {
|
||||
// 如果是新增订单且有deliveryId,需要更新delivery表的order_id字段
|
||||
if (!ruleForm.id && ruleForm.deliveryId) {
|
||||
// 尝试从响应中获取订单ID
|
||||
let orderId = null;
|
||||
if (res.data) {
|
||||
// 如果返回的是订单对象
|
||||
if (res.data.id) {
|
||||
orderId = res.data.id;
|
||||
}
|
||||
// 如果返回的是订单ID(数字或字符串)
|
||||
else if (typeof res.data === 'number' || typeof res.data === 'string') {
|
||||
orderId = res.data;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果获取到了订单ID,更新delivery表的order_id字段
|
||||
if (orderId) {
|
||||
try {
|
||||
// 更新delivery表的order_id字段
|
||||
const updateRes = await updateDeliveryInfo({
|
||||
deliveryId: ruleForm.deliveryId, // 使用deliveryId而不是id
|
||||
orderId: String(orderId) // 转为字符串格式
|
||||
});
|
||||
if (updateRes.code === 200) {
|
||||
ElMessage({
|
||||
message: '订单创建成功,已关联运送清单',
|
||||
type: 'success',
|
||||
});
|
||||
} else {
|
||||
ElMessage.warning('订单创建成功,但关联运送清单失败:' + (updateRes.msg || '未知错误'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('更新运送清单关联失败:', error);
|
||||
ElMessage.warning('订单创建成功,但关联运送清单失败,请手动关联');
|
||||
}
|
||||
} else {
|
||||
// 如果无法获取订单ID,提示用户
|
||||
ElMessage.warning('订单创建成功,但无法自动关联运送清单,请手动关联');
|
||||
}
|
||||
} else {
|
||||
ElMessage({
|
||||
message: res.msg,
|
||||
type: 'success',
|
||||
});
|
||||
}
|
||||
emits('success');
|
||||
handleClose();
|
||||
} else {
|
||||
@@ -317,6 +365,7 @@ const onClickSave = () => {
|
||||
})
|
||||
.catch((err) => {
|
||||
data.saveLoading = false;
|
||||
ElMessage.error('保存失败:' + (err.message || '未知错误'));
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -325,7 +374,7 @@ const onClickSave = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const onShowDialog = (orderData) => {
|
||||
const onShowDialog = (orderData, deliveryId) => {
|
||||
if (formDataRef.value) {
|
||||
formDataRef.value.resetFields();
|
||||
}
|
||||
@@ -367,6 +416,8 @@ const onShowDialog = (orderData) => {
|
||||
|
||||
ruleForm.settlementType = orderData?.settlementType || 1;
|
||||
ruleForm.firmPrice = orderData?.firmPrice != null ? orderData.firmPrice : null;
|
||||
// 设置deliveryId(如果提供)
|
||||
ruleForm.deliveryId = deliveryId || orderData?.deliveryId || null;
|
||||
|
||||
data.dialogVisible = true;
|
||||
// 初始化时加载列表
|
||||
|
||||
@@ -180,8 +180,47 @@ public class OrderController {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理orderIds参数:支持整数数组和字符串数组
|
||||
List<Integer> orderIds = new ArrayList<>();
|
||||
Object orderIdsObj = params.get("orderIds");
|
||||
if (orderIdsObj != null) {
|
||||
if (orderIdsObj instanceof List) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Integer> orderIds = (List<Integer>) params.get("orderIds");
|
||||
List<Object> orderIdsList = (List<Object>) orderIdsObj;
|
||||
for (Object item : orderIdsList) {
|
||||
if (item instanceof Integer) {
|
||||
orderIds.add((Integer) item);
|
||||
} else if (item instanceof String) {
|
||||
try {
|
||||
orderIds.add(Integer.parseInt((String) item));
|
||||
} catch (NumberFormatException e) {
|
||||
logger.error("订单ID格式不正确:{}", item);
|
||||
return AjaxResult.error("订单ID格式不正确:" + item);
|
||||
}
|
||||
} else if (item instanceof Number) {
|
||||
orderIds.add(((Number) item).intValue());
|
||||
} else {
|
||||
logger.error("订单ID类型不支持:{}", item.getClass().getName());
|
||||
return AjaxResult.error("订单ID类型不支持");
|
||||
}
|
||||
}
|
||||
} else if (orderIdsObj instanceof String) {
|
||||
// 如果是逗号分隔的字符串,按逗号分割
|
||||
String orderIdsStr = (String) orderIdsObj;
|
||||
String[] ids = orderIdsStr.split(",");
|
||||
for (String id : ids) {
|
||||
try {
|
||||
orderIds.add(Integer.parseInt(id.trim()));
|
||||
} catch (NumberFormatException e) {
|
||||
logger.error("订单ID格式不正确:{}", id);
|
||||
return AjaxResult.error("订单ID格式不正确:" + id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.error("orderIds参数类型不正确:{}", orderIdsObj.getClass().getName());
|
||||
return AjaxResult.error("orderIds参数类型不正确");
|
||||
}
|
||||
}
|
||||
|
||||
if (deliveryId == null) {
|
||||
logger.error("更新失败:运送清单ID不能为空");
|
||||
@@ -199,5 +238,41 @@ public class OrderController {
|
||||
return AjaxResult.error("批量更新订单delivery_id失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解除订单与运送清单的关联
|
||||
*/
|
||||
@SaCheckPermission("order:edit")
|
||||
@PostMapping("/unbindFromDelivery")
|
||||
public AjaxResult unbindFromDelivery(@RequestBody Map<String, Object> params) {
|
||||
try {
|
||||
logger.info("解除订单与运送清单关联,参数:{}", params);
|
||||
|
||||
Integer orderId = null;
|
||||
if (params.get("orderId") != null) {
|
||||
Object orderIdObj = params.get("orderId");
|
||||
if (orderIdObj instanceof Integer) {
|
||||
orderId = (Integer) orderIdObj;
|
||||
} else if (orderIdObj instanceof String) {
|
||||
try {
|
||||
orderId = Integer.parseInt((String) orderIdObj);
|
||||
} catch (NumberFormatException e) {
|
||||
logger.error("orderId格式不正确:{}", orderIdObj);
|
||||
return AjaxResult.error("orderId格式不正确");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (orderId == null) {
|
||||
logger.error("解除关联失败:订单ID不能为空");
|
||||
return AjaxResult.error("订单ID不能为空");
|
||||
}
|
||||
|
||||
return orderService.unbindOrderFromDelivery(orderId);
|
||||
} catch (Exception e) {
|
||||
logger.error("解除订单与运送清单关联失败:{}", e.getMessage(), e);
|
||||
return AjaxResult.error("解除关联失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ public class DeliveryEditDto {
|
||||
@NotNull(message = "运送清单ID不能为空")
|
||||
private Integer deliveryId;
|
||||
|
||||
/** 关联订单ID(多个订单ID用逗号分隔的字符串,如:"1,2,3") */
|
||||
private String orderId;
|
||||
|
||||
private String deliveryTitle;
|
||||
|
||||
private Integer ratedQuantity;
|
||||
@@ -72,6 +75,9 @@ public class DeliveryEditDto {
|
||||
/** 司机运费(元) */
|
||||
private java.math.BigDecimal freight;
|
||||
|
||||
/** 检疫证号 */
|
||||
private String quarantineCertNo;
|
||||
|
||||
/** 检疫票照片 */
|
||||
private String quarantineTickeyUrl;
|
||||
/** 传纸质磅单(双章) */
|
||||
|
||||
@@ -285,6 +285,12 @@ public class Delivery implements Serializable {
|
||||
@TableField("freight")
|
||||
private java.math.BigDecimal freight;
|
||||
|
||||
/**
|
||||
* 检疫证号
|
||||
*/
|
||||
@TableField("quarantine_tickey")
|
||||
private String quarantineTickey;
|
||||
|
||||
/**
|
||||
* 检疫票
|
||||
*/
|
||||
|
||||
@@ -90,6 +90,23 @@ public interface MemberDriverMapper {
|
||||
"WHERE md.id = #{driverId}")
|
||||
Map<String, Object> selectDriverById(@Param("driverId") Integer driverId);
|
||||
|
||||
/**
|
||||
* 批量根据司机ID查询司机信息(关联member表获取手机号)
|
||||
* 用于优化性能,避免 N+1 查询问题
|
||||
*/
|
||||
@Select("<script>" +
|
||||
"SELECT md.id, md.member_id, md.username, " +
|
||||
"md.driving_license, md.driver_license, md.record_code, " +
|
||||
"md.car_img, md.id_card, md.remark, md.create_time, m.mobile, m.status " +
|
||||
"FROM member_driver md " +
|
||||
"LEFT JOIN member m ON md.member_id = m.id " +
|
||||
"WHERE md.id IN " +
|
||||
"<foreach collection='driverIds' item='id' open='(' separator=',' close=')'>" +
|
||||
" #{id}" +
|
||||
"</foreach>" +
|
||||
"</script>")
|
||||
List<Map<String, Object>> selectDriverByIds(@Param("driverIds") List<Integer> driverIds);
|
||||
|
||||
/**
|
||||
* 根据车牌号查询司机信息(已废弃:司机表不再有车牌号字段)
|
||||
* 该方法保留是为了兼容性,但会返回 null
|
||||
|
||||
@@ -38,5 +38,20 @@ public interface VehicleMapper extends BaseMapper<Vehicle> {
|
||||
"ORDER BY create_time DESC " +
|
||||
"</script>")
|
||||
List<Vehicle> selectVehicleList(@Param("licensePlate") String licensePlate);
|
||||
|
||||
/**
|
||||
* 批量根据车牌号查询车辆信息
|
||||
* 用于优化性能,避免 N+1 查询问题
|
||||
* @param licensePlates 车牌号列表
|
||||
* @return 车辆列表
|
||||
*/
|
||||
@Select("<script>" +
|
||||
"SELECT * FROM vehicle WHERE is_delete = 0 " +
|
||||
"AND license_plate IN " +
|
||||
"<foreach collection='licensePlates' item='plate' open='(' separator=',' close=')'>" +
|
||||
" #{plate}" +
|
||||
"</foreach>" +
|
||||
"</script>")
|
||||
List<Vehicle> selectByLicensePlates(@Param("licensePlates") List<String> licensePlates);
|
||||
}
|
||||
|
||||
|
||||
@@ -72,5 +72,13 @@ public interface IOrderService extends IService<Order> {
|
||||
* @return AjaxResult
|
||||
*/
|
||||
AjaxResult updateOrderDeliveryId(Integer deliveryId, List<Integer> orderIds);
|
||||
|
||||
/**
|
||||
* 解除订单与运送清单的关联(将订单的deliveryId设置为null)
|
||||
*
|
||||
* @param orderId 订单ID
|
||||
* @return AjaxResult
|
||||
*/
|
||||
AjaxResult unbindOrderFromDelivery(Integer orderId);
|
||||
}
|
||||
|
||||
|
||||
@@ -136,228 +136,9 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
// 需要权限过滤:先查询所有数据,填充信息,过滤后再分页
|
||||
list = this.list(wrapper);
|
||||
|
||||
// 填充关联信息(供应商、资金方、采购商、司机等)
|
||||
// 使用批量查询填充关联信息(优化性能,避免 N+1 查询)
|
||||
if(CollectionUtils.isNotEmpty(list)){
|
||||
list.forEach(delivery -> {
|
||||
if(userId.equals(delivery.getCheckBy())){
|
||||
//判断是否需要核验,1:需要核验,2:不需要核验
|
||||
delivery.setIfCheck(1);
|
||||
}
|
||||
|
||||
// 查询四个角色的手机号(供应商、资金方、采购商、司机)
|
||||
try {
|
||||
// 1. 查询供应商信息(supplierId是逗号分隔的字符串)
|
||||
if (StringUtils.isNotEmpty(delivery.getSupplierId())) {
|
||||
String[] supplierIds = delivery.getSupplierId().split(",");
|
||||
List<String> supplierNames = new ArrayList<>();
|
||||
List<String> supplierMobiles = new ArrayList<>();
|
||||
for (String supplierId : supplierIds) {
|
||||
if (StringUtils.isNotEmpty(supplierId.trim())) {
|
||||
try {
|
||||
Integer sid = Integer.parseInt(supplierId.trim());
|
||||
// 查询member和member_user表关联数据
|
||||
Map<String, Object> supplierInfo = memberMapper.selectMemberUserById(sid);
|
||||
if (supplierInfo != null) {
|
||||
String username = (String) supplierInfo.get("username");
|
||||
String mobile = (String) supplierInfo.get("mobile");
|
||||
if (StringUtils.isNotEmpty(username)) {
|
||||
supplierNames.add(username);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(mobile)) {
|
||||
supplierMobiles.add(mobile);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!supplierNames.isEmpty()) {
|
||||
delivery.setSupplierName(String.join(",", supplierNames));
|
||||
} else if (!supplierMobiles.isEmpty()) {
|
||||
// 如果用户名为空,使用手机号作为备选
|
||||
delivery.setSupplierName(String.join(",", supplierMobiles));
|
||||
}
|
||||
if (!supplierMobiles.isEmpty()) {
|
||||
delivery.setSupplierMobile(String.join(",", supplierMobiles));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 查询资金方信息
|
||||
if (delivery.getFundId() != null) {
|
||||
Map<String, Object> fundInfo = memberMapper.selectMemberUserById(delivery.getFundId());
|
||||
if (fundInfo != null) {
|
||||
String username = (String) fundInfo.get("username");
|
||||
String mobile = (String) fundInfo.get("mobile");
|
||||
if (StringUtils.isNotEmpty(username)) {
|
||||
delivery.setFundName(username);
|
||||
} else if (StringUtils.isNotEmpty(mobile)) {
|
||||
// 如果用户名为空,使用手机号作为备选
|
||||
delivery.setFundName(mobile);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(mobile)) {
|
||||
delivery.setFundMobile(mobile);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 查询采购商信息
|
||||
if (delivery.getBuyerId() != null) {
|
||||
Map<String, Object> buyerInfo = memberMapper.selectMemberUserById(delivery.getBuyerId());
|
||||
if (buyerInfo != null) {
|
||||
String username = (String) buyerInfo.get("username");
|
||||
String mobile = (String) buyerInfo.get("mobile");
|
||||
if (StringUtils.isNotEmpty(username)) {
|
||||
delivery.setBuyerName(username);
|
||||
} else if (StringUtils.isNotEmpty(mobile)) {
|
||||
// 如果用户名为空,使用手机号作为备选
|
||||
delivery.setBuyerName(mobile);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(mobile)) {
|
||||
delivery.setBuyerMobile(mobile);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 查询司机手机号(如果有司机ID)
|
||||
if (delivery.getDriverId() != null) {
|
||||
try {
|
||||
Map<String, Object> driverInfo = memberDriverMapper.selectDriverById(delivery.getDriverId());
|
||||
if (driverInfo != null) {
|
||||
String driverName = (String) driverInfo.get("username");
|
||||
String driverMobile = (String) driverInfo.get("mobile");
|
||||
String carImg = (String) driverInfo.get("car_img");
|
||||
|
||||
if (StringUtils.isNotEmpty(driverMobile)) {
|
||||
delivery.setDriverMobile(driverMobile);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(driverName)) {
|
||||
delivery.setDriverName(driverName);
|
||||
}
|
||||
|
||||
// 优先从车辆表获取车身照片(根据车牌号)
|
||||
// 如果车辆表中没有,再从司机信息中获取作为后备
|
||||
boolean vehiclePhotoSet = false;
|
||||
if (delivery.getLicensePlate() != null && StringUtils.isNotEmpty(delivery.getLicensePlate())) {
|
||||
try {
|
||||
Vehicle vehicle = vehicleMapper.selectByLicensePlate(delivery.getLicensePlate());
|
||||
if (vehicle != null) {
|
||||
String carFrontPhoto = vehicle.getCarFrontPhoto();
|
||||
String carRearPhoto = vehicle.getCarRearPhoto();
|
||||
if (StringUtils.isNotEmpty(carFrontPhoto) || StringUtils.isNotEmpty(carRearPhoto)) {
|
||||
delivery.setCarFrontPhoto(StringUtils.isNotEmpty(carFrontPhoto) ? carFrontPhoto : null);
|
||||
delivery.setCarBehindPhoto(StringUtils.isNotEmpty(carRearPhoto) ? carRearPhoto : null);
|
||||
vehiclePhotoSet = true;
|
||||
logger.debug("从车辆表获取照片: 车牌号={}, 车头照片={}, 车尾照片={}",
|
||||
delivery.getLicensePlate(), delivery.getCarFrontPhoto(), delivery.getCarBehindPhoto());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("从车辆表获取照片失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果车辆表中没有照片,从司机信息中获取作为后备
|
||||
if (!vehiclePhotoSet && carImg != null && !carImg.isEmpty()) {
|
||||
// 按逗号分割car_img字段,分别映射到车头和车尾照片
|
||||
String[] carImgUrls = carImg.split(",");
|
||||
|
||||
if (carImgUrls.length >= 2) {
|
||||
// 逗号前面的URL作为车尾照片
|
||||
String carBehindPhoto = carImgUrls[0].trim();
|
||||
// 逗号后面的URL作为车头照片
|
||||
String carFrontPhoto = carImgUrls[1].trim();
|
||||
|
||||
delivery.setCarBehindPhoto(carBehindPhoto);
|
||||
delivery.setCarFrontPhoto(carFrontPhoto);
|
||||
} else if (carImgUrls.length == 1) {
|
||||
// 只有一个URL时,同时设置为车头和车尾照片
|
||||
String singlePhoto = carImgUrls[0].trim();
|
||||
delivery.setCarFrontPhoto(singlePhoto);
|
||||
delivery.setCarBehindPhoto(singlePhoto);
|
||||
} else {
|
||||
// 没有有效URL,设置为null
|
||||
delivery.setCarFrontPhoto(null);
|
||||
delivery.setCarBehindPhoto(null);
|
||||
}
|
||||
} else if (!vehiclePhotoSet) {
|
||||
// 如果车辆表和司机信息中都没有照片,设置为null
|
||||
delivery.setCarFrontPhoto(null);
|
||||
delivery.setCarBehindPhoto(null);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// 统计登记设备数量(耳标+项圈)
|
||||
Integer currentDeliveryId = delivery.getId();
|
||||
if (currentDeliveryId != null) {
|
||||
try {
|
||||
// 统计耳标设备数量
|
||||
LambdaQueryWrapper<DeliveryDevice> earTagWrapper = new LambdaQueryWrapper<>();
|
||||
earTagWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId)
|
||||
.eq(DeliveryDevice::getDeviceType, 2);
|
||||
long earTagCount = deliveryDeviceService.count(earTagWrapper);
|
||||
|
||||
// 统计项圈设备数量
|
||||
LambdaQueryWrapper<DeliveryDevice> collarWrapper = new LambdaQueryWrapper<>();
|
||||
collarWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId)
|
||||
.eq(DeliveryDevice::getDeviceType, 3);
|
||||
long collarCount = deliveryDeviceService.count(collarWrapper);
|
||||
|
||||
// 设置总设备数量和耳标数量
|
||||
int totalDeviceCount = (int) (earTagCount + collarCount);
|
||||
delivery.setRegisteredJbqCount(totalDeviceCount);
|
||||
delivery.setEarTagCount((int) earTagCount);
|
||||
|
||||
// 设置已分配设备数量,与登记设备数量保持一致
|
||||
delivery.setBindJbqCount(totalDeviceCount);
|
||||
|
||||
// 统计已佩戴设备数量(bandge_status = 1)
|
||||
int wornDeviceCount = 0;
|
||||
try {
|
||||
// 统计已佩戴的耳标设备数量(通过delivery_device表关联)
|
||||
LambdaQueryWrapper<DeliveryDevice> wornEarTagWrapper = new LambdaQueryWrapper<>();
|
||||
wornEarTagWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId)
|
||||
.eq(DeliveryDevice::getDeviceType, 2)
|
||||
.eq(DeliveryDevice::getIsWare, 1); // 1表示已佩戴
|
||||
long wornEarTagCount = deliveryDeviceService.count(wornEarTagWrapper);
|
||||
|
||||
// 统计已佩戴的项圈设备数量(通过delivery_device表关联xq_client表)
|
||||
LambdaQueryWrapper<DeliveryDevice> wornCollarWrapper = new LambdaQueryWrapper<>();
|
||||
wornCollarWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId)
|
||||
.eq(DeliveryDevice::getDeviceType, 3);
|
||||
List<DeliveryDevice> collarDevices = deliveryDeviceService.list(wornCollarWrapper);
|
||||
|
||||
int wornCollarCount = 0;
|
||||
for (DeliveryDevice device : collarDevices) {
|
||||
// 查询xq_client表中的bandge_status
|
||||
LambdaQueryWrapper<XqClient> xqWrapper = new LambdaQueryWrapper<>();
|
||||
xqWrapper.eq(XqClient::getSn, device.getDeviceId());
|
||||
XqClient xqClient = xqClientService.getOne(xqWrapper);
|
||||
if (xqClient != null && xqClient.getBandgeStatus() != null && xqClient.getBandgeStatus() == 1) {
|
||||
wornCollarCount++;
|
||||
}
|
||||
}
|
||||
|
||||
wornDeviceCount = (int) (wornEarTagCount + wornCollarCount);
|
||||
delivery.setWareCount(wornDeviceCount);
|
||||
} catch (Exception e) {
|
||||
delivery.setWareCount(0);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
delivery.setRegisteredJbqCount(0);
|
||||
delivery.setBindJbqCount(0);
|
||||
delivery.setWareCount(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
batchFillDeliveryRelations(list, userId);
|
||||
}
|
||||
|
||||
// 数据权限过滤:非超级管理员只能看到与自己手机号相关的订单
|
||||
@@ -413,223 +194,9 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
list = this.list(wrapper);
|
||||
total = result.getTotal();
|
||||
|
||||
// 填充关联信息(供应商、资金方、采购商、司机等)
|
||||
// 使用批量查询填充关联信息(优化性能,避免 N+1 查询)
|
||||
if(CollectionUtils.isNotEmpty(list)){
|
||||
list.forEach(delivery -> {
|
||||
if(userId.equals(delivery.getCheckBy())){
|
||||
//判断是否需要核验,1:需要核验,2:不需要核验
|
||||
delivery.setIfCheck(1);
|
||||
}
|
||||
|
||||
// 查询四个角色的手机号(供应商、资金方、采购商、司机)
|
||||
try {
|
||||
// 1. 查询供应商信息(supplierId是逗号分隔的字符串)
|
||||
if (StringUtils.isNotEmpty(delivery.getSupplierId())) {
|
||||
String[] supplierIds = delivery.getSupplierId().split(",");
|
||||
List<String> supplierNames = new ArrayList<>();
|
||||
List<String> supplierMobiles = new ArrayList<>();
|
||||
for (String supplierId : supplierIds) {
|
||||
if (StringUtils.isNotEmpty(supplierId.trim())) {
|
||||
try {
|
||||
Integer sid = Integer.parseInt(supplierId.trim());
|
||||
// 查询member和member_user表关联数据
|
||||
Map<String, Object> supplierInfo = memberMapper.selectMemberUserById(sid);
|
||||
if (supplierInfo != null) {
|
||||
String username = (String) supplierInfo.get("username");
|
||||
String mobile = (String) supplierInfo.get("mobile");
|
||||
if (StringUtils.isNotEmpty(username)) {
|
||||
supplierNames.add(username);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(mobile)) {
|
||||
supplierMobiles.add(mobile);
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!supplierNames.isEmpty()) {
|
||||
delivery.setSupplierName(String.join(",", supplierNames));
|
||||
} else if (!supplierMobiles.isEmpty()) {
|
||||
// 如果用户名为空,使用手机号作为备选
|
||||
delivery.setSupplierName(String.join(",", supplierMobiles));
|
||||
}
|
||||
if (!supplierMobiles.isEmpty()) {
|
||||
delivery.setSupplierMobile(String.join(",", supplierMobiles));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 查询资金方信息
|
||||
if (delivery.getFundId() != null) {
|
||||
Map<String, Object> fundInfo = memberMapper.selectMemberUserById(delivery.getFundId());
|
||||
if (fundInfo != null) {
|
||||
String username = (String) fundInfo.get("username");
|
||||
String mobile = (String) fundInfo.get("mobile");
|
||||
if (StringUtils.isNotEmpty(username)) {
|
||||
delivery.setFundName(username);
|
||||
} else if (StringUtils.isNotEmpty(mobile)) {
|
||||
// 如果用户名为空,使用手机号作为备选
|
||||
delivery.setFundName(mobile);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(mobile)) {
|
||||
delivery.setFundMobile(mobile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 查询采购商信息
|
||||
if (delivery.getBuyerId() != null) {
|
||||
Map<String, Object> buyerInfo = memberMapper.selectMemberUserById(delivery.getBuyerId());
|
||||
if (buyerInfo != null) {
|
||||
String username = (String) buyerInfo.get("username");
|
||||
String mobile = (String) buyerInfo.get("mobile");
|
||||
if (StringUtils.isNotEmpty(username)) {
|
||||
delivery.setBuyerName(username);
|
||||
} else if (StringUtils.isNotEmpty(mobile)) {
|
||||
// 如果用户名为空,使用手机号作为备选
|
||||
delivery.setBuyerName(mobile);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(mobile)) {
|
||||
delivery.setBuyerMobile(mobile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 查询司机手机号(如果有司机ID)
|
||||
if (delivery.getDriverId() != null) {
|
||||
try {
|
||||
Map<String, Object> driverInfo = memberDriverMapper.selectDriverById(delivery.getDriverId());
|
||||
if (driverInfo != null) {
|
||||
String driverName = (String) driverInfo.get("username");
|
||||
String driverMobile = (String) driverInfo.get("mobile");
|
||||
String carImg = (String) driverInfo.get("car_img");
|
||||
|
||||
if (StringUtils.isNotEmpty(driverMobile)) {
|
||||
delivery.setDriverMobile(driverMobile);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(driverName)) {
|
||||
delivery.setDriverName(driverName);
|
||||
}
|
||||
|
||||
// 优先从车辆表获取车身照片(根据车牌号)
|
||||
// 如果车辆表中没有,再从司机信息中获取作为后备
|
||||
boolean vehiclePhotoSet = false;
|
||||
if (delivery.getLicensePlate() != null && StringUtils.isNotEmpty(delivery.getLicensePlate())) {
|
||||
try {
|
||||
Vehicle vehicle = vehicleMapper.selectByLicensePlate(delivery.getLicensePlate());
|
||||
if (vehicle != null) {
|
||||
String carFrontPhoto = vehicle.getCarFrontPhoto();
|
||||
String carRearPhoto = vehicle.getCarRearPhoto();
|
||||
if (StringUtils.isNotEmpty(carFrontPhoto) || StringUtils.isNotEmpty(carRearPhoto)) {
|
||||
delivery.setCarFrontPhoto(StringUtils.isNotEmpty(carFrontPhoto) ? carFrontPhoto : null);
|
||||
delivery.setCarBehindPhoto(StringUtils.isNotEmpty(carRearPhoto) ? carRearPhoto : null);
|
||||
vehiclePhotoSet = true;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("从车辆表获取照片失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果车辆表中没有照片,从司机信息中获取作为后备
|
||||
if (!vehiclePhotoSet && carImg != null && !carImg.isEmpty()) {
|
||||
// 按逗号分割car_img字段,分别映射到车头和车尾照片
|
||||
String[] carImgUrls = carImg.split(",");
|
||||
|
||||
if (carImgUrls.length >= 2) {
|
||||
// 逗号前面的URL作为车尾照片
|
||||
String carBehindPhoto = carImgUrls[0].trim();
|
||||
// 逗号后面的URL作为车头照片
|
||||
String carFrontPhoto = carImgUrls[1].trim();
|
||||
|
||||
delivery.setCarBehindPhoto(carBehindPhoto);
|
||||
delivery.setCarFrontPhoto(carFrontPhoto);
|
||||
} else if (carImgUrls.length == 1) {
|
||||
// 只有一个URL时,同时设置为车头和车尾照片
|
||||
String singlePhoto = carImgUrls[0].trim();
|
||||
delivery.setCarFrontPhoto(singlePhoto);
|
||||
delivery.setCarBehindPhoto(singlePhoto);
|
||||
} else {
|
||||
// 没有有效URL,设置为null
|
||||
delivery.setCarFrontPhoto(null);
|
||||
delivery.setCarBehindPhoto(null);
|
||||
}
|
||||
} else if (!vehiclePhotoSet) {
|
||||
// 如果车辆表和司机信息中都没有照片,设置为null
|
||||
delivery.setCarFrontPhoto(null);
|
||||
delivery.setCarBehindPhoto(null);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// 统计登记设备数量(耳标+项圈)
|
||||
Integer currentDeliveryId = delivery.getId();
|
||||
if (currentDeliveryId != null) {
|
||||
try {
|
||||
// 统计耳标设备数量
|
||||
LambdaQueryWrapper<DeliveryDevice> earTagWrapper = new LambdaQueryWrapper<>();
|
||||
earTagWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId)
|
||||
.eq(DeliveryDevice::getDeviceType, 2);
|
||||
long earTagCount = deliveryDeviceService.count(earTagWrapper);
|
||||
|
||||
// 统计项圈设备数量
|
||||
LambdaQueryWrapper<DeliveryDevice> collarWrapper = new LambdaQueryWrapper<>();
|
||||
collarWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId)
|
||||
.eq(DeliveryDevice::getDeviceType, 3);
|
||||
long collarCount = deliveryDeviceService.count(collarWrapper);
|
||||
|
||||
// 设置总设备数量和耳标数量
|
||||
int totalDeviceCount = (int) (earTagCount + collarCount);
|
||||
delivery.setRegisteredJbqCount(totalDeviceCount);
|
||||
delivery.setEarTagCount((int) earTagCount);
|
||||
|
||||
// 设置已分配设备数量,与登记设备数量保持一致
|
||||
delivery.setBindJbqCount(totalDeviceCount);
|
||||
|
||||
// 统计已佩戴设备数量(bandge_status = 1)
|
||||
int wornDeviceCount = 0;
|
||||
try {
|
||||
// 统计已佩戴的耳标设备数量(通过delivery_device表关联)
|
||||
LambdaQueryWrapper<DeliveryDevice> wornEarTagWrapper = new LambdaQueryWrapper<>();
|
||||
wornEarTagWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId)
|
||||
.eq(DeliveryDevice::getDeviceType, 2)
|
||||
.eq(DeliveryDevice::getIsWare, 1); // 1表示已佩戴
|
||||
long wornEarTagCount = deliveryDeviceService.count(wornEarTagWrapper);
|
||||
|
||||
// 统计已佩戴的项圈设备数量(通过delivery_device表关联xq_client表)
|
||||
LambdaQueryWrapper<DeliveryDevice> wornCollarWrapper = new LambdaQueryWrapper<>();
|
||||
wornCollarWrapper.eq(DeliveryDevice::getDeliveryId, currentDeliveryId)
|
||||
.eq(DeliveryDevice::getDeviceType, 3);
|
||||
List<DeliveryDevice> collarDevices = deliveryDeviceService.list(wornCollarWrapper);
|
||||
|
||||
int wornCollarCount = 0;
|
||||
for (DeliveryDevice device : collarDevices) {
|
||||
// 查询xq_client表中的bandge_status
|
||||
LambdaQueryWrapper<XqClient> xqWrapper = new LambdaQueryWrapper<>();
|
||||
xqWrapper.eq(XqClient::getSn, device.getDeviceId());
|
||||
XqClient xqClient = xqClientService.getOne(xqWrapper);
|
||||
if (xqClient != null && xqClient.getBandgeStatus() != null && xqClient.getBandgeStatus() == 1) {
|
||||
wornCollarCount++;
|
||||
}
|
||||
}
|
||||
|
||||
wornDeviceCount = (int) (wornEarTagCount + wornCollarCount);
|
||||
delivery.setWareCount(wornDeviceCount);
|
||||
} catch (Exception e) {
|
||||
delivery.setWareCount(0);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
delivery.setRegisteredJbqCount(0);
|
||||
delivery.setBindJbqCount(0);
|
||||
delivery.setWareCount(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
batchFillDeliveryRelations(list, userId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -799,6 +366,10 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
delivery.setLandingEntruckWeight(StringUtils.isNotEmpty(dto.getLandingEntruckWeight()) ? dto.getLandingEntruckWeight() : null);
|
||||
// 司机运费
|
||||
delivery.setFreight(dto.getFreight());
|
||||
// 检疫证号
|
||||
if (dto.getQuarantineCertNo() != null && !dto.getQuarantineCertNo().trim().isEmpty()) {
|
||||
delivery.setQuarantineTickey(dto.getQuarantineCertNo().trim());
|
||||
}
|
||||
// 照片
|
||||
delivery.setQuarantineTickeyUrl(dto.getQuarantineTickeyUrl());
|
||||
delivery.setPoundListImg(dto.getPoundListImg());
|
||||
@@ -1046,6 +617,53 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
delivery.setFirmPrice(dto.getFirmPrice());
|
||||
}
|
||||
|
||||
// 更新订单ID关联(追加模式:如果已有订单ID,则追加;如果没有,则直接设置)
|
||||
if (dto.getOrderId() != null && StringUtils.isNotEmpty(dto.getOrderId())) {
|
||||
String newOrderIdStr = dto.getOrderId().trim();
|
||||
String existingOrderId = delivery.getOrderId();
|
||||
|
||||
// 将新订单ID字符串按逗号分割为列表(支持多个订单ID)
|
||||
List<String> newOrderIdList = Arrays.asList(newOrderIdStr.split(","));
|
||||
newOrderIdList = newOrderIdList.stream()
|
||||
.map(String::trim)
|
||||
.filter(id -> !id.isEmpty())
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
|
||||
if (StringUtils.isNotEmpty(existingOrderId)) {
|
||||
// 如果已有订单ID,将现有订单ID也按逗号分割
|
||||
List<String> orderIdList = new ArrayList<>(Arrays.asList(existingOrderId.split(",")));
|
||||
// 去除空白并过滤空字符串
|
||||
orderIdList = orderIdList.stream()
|
||||
.map(String::trim)
|
||||
.filter(id -> !id.isEmpty())
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
|
||||
// 追加新订单ID(如果不存在)
|
||||
for (String newOrderId : newOrderIdList) {
|
||||
if (!orderIdList.contains(newOrderId)) {
|
||||
orderIdList.add(newOrderId);
|
||||
}
|
||||
}
|
||||
|
||||
// 重新排序并去重
|
||||
orderIdList = orderIdList.stream()
|
||||
.distinct()
|
||||
.sorted((a, b) -> Integer.compare(Integer.parseInt(a), Integer.parseInt(b)))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
delivery.setOrderId(String.join(",", orderIdList));
|
||||
logger.info("追加订单ID到运送清单: deliveryId={}, 原有订单ID={}, 新增订单ID={}, 更新后订单ID={}",
|
||||
dto.getDeliveryId(), existingOrderId, newOrderIdStr, delivery.getOrderId());
|
||||
} else {
|
||||
// 如果没有现有订单ID,直接设置(排序并去重)
|
||||
List<String> orderIdList = newOrderIdList.stream()
|
||||
.distinct()
|
||||
.sorted((a, b) -> Integer.compare(Integer.parseInt(a), Integer.parseInt(b)))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
delivery.setOrderId(String.join(",", orderIdList));
|
||||
logger.info("设置运送清单的订单ID: deliveryId={}, orderId={}", dto.getDeliveryId(), delivery.getOrderId());
|
||||
}
|
||||
}
|
||||
|
||||
// 更新重量字段:将空字符串转换为null,避免数据库DECIMAL字段类型错误
|
||||
// 注意:前端可能传递空字符串"",需要处理这种情况
|
||||
if (dto.getEmptyWeight() != null) {
|
||||
@@ -1062,6 +680,15 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
delivery.setFreight(dto.getFreight());
|
||||
}
|
||||
|
||||
// 更新检疫证号
|
||||
if (dto.getQuarantineCertNo() != null) {
|
||||
if (StringUtils.isNotEmpty(dto.getQuarantineCertNo())) {
|
||||
delivery.setQuarantineTickey(dto.getQuarantineCertNo().trim());
|
||||
} else {
|
||||
delivery.setQuarantineTickey(null);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新照片字段:将空字符串转换为null,避免前端显示问题
|
||||
// 注意:前端总是传递字段(即使是空字符串),只有在传递了有效URL时才更新
|
||||
if (dto.getQuarantineTickeyUrl() != null) {
|
||||
@@ -2266,5 +1893,275 @@ public class DeliveryServiceImpl extends ServiceImpl<DeliveryMapper, Delivery> i
|
||||
return unique;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量填充 Delivery 列表的关联信息(优化性能,避免 N+1 查询)
|
||||
* @param deliveries Delivery 列表
|
||||
* @param userId 当前用户ID
|
||||
*/
|
||||
private void batchFillDeliveryRelations(List<Delivery> deliveries, Integer userId) {
|
||||
if (CollectionUtils.isEmpty(deliveries)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 收集所有需要查询的 ID
|
||||
Set<Integer> allMemberIds = new HashSet<>();
|
||||
Set<Integer> driverIds = new HashSet<>();
|
||||
Set<String> licensePlates = new HashSet<>();
|
||||
Set<Integer> deliveryIds = new HashSet<>();
|
||||
|
||||
for (Delivery delivery : deliveries) {
|
||||
// 收集供应商ID(可能是逗号分隔的字符串)
|
||||
if (StringUtils.isNotEmpty(delivery.getSupplierId())) {
|
||||
String[] supplierIds = delivery.getSupplierId().split(",");
|
||||
for (String supplierId : supplierIds) {
|
||||
if (StringUtils.isNotEmpty(supplierId.trim())) {
|
||||
try {
|
||||
allMemberIds.add(Integer.parseInt(supplierId.trim()));
|
||||
} catch (NumberFormatException e) {
|
||||
// 忽略无效的ID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 收集资金方ID
|
||||
if (delivery.getFundId() != null) {
|
||||
allMemberIds.add(delivery.getFundId());
|
||||
}
|
||||
// 收集采购商ID
|
||||
if (delivery.getBuyerId() != null) {
|
||||
allMemberIds.add(delivery.getBuyerId());
|
||||
}
|
||||
// 收集司机ID
|
||||
if (delivery.getDriverId() != null) {
|
||||
driverIds.add(delivery.getDriverId());
|
||||
}
|
||||
// 收集车牌号
|
||||
if (StringUtils.isNotEmpty(delivery.getLicensePlate())) {
|
||||
licensePlates.add(delivery.getLicensePlate());
|
||||
}
|
||||
// 收集运单ID(用于设备统计)
|
||||
if (delivery.getId() != null) {
|
||||
deliveryIds.add(delivery.getId());
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 批量查询 member 信息
|
||||
Map<Integer, Map<String, Object>> memberMap = new HashMap<>();
|
||||
if (!allMemberIds.isEmpty()) {
|
||||
List<Map<String, Object>> memberList = memberMapper.selectMemberUserByIds(new ArrayList<>(allMemberIds));
|
||||
for (Map<String, Object> member : memberList) {
|
||||
Object idObj = member.get("id");
|
||||
Integer id = null;
|
||||
if (idObj instanceof Integer) {
|
||||
id = (Integer) idObj;
|
||||
} else if (idObj instanceof Long) {
|
||||
id = ((Long) idObj).intValue();
|
||||
} else if (idObj instanceof Number) {
|
||||
id = ((Number) idObj).intValue();
|
||||
}
|
||||
if (id != null) {
|
||||
memberMap.put(id, member);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 批量查询司机信息
|
||||
Map<Integer, Map<String, Object>> driverMap = new HashMap<>();
|
||||
if (!driverIds.isEmpty()) {
|
||||
List<Map<String, Object>> driverList = memberDriverMapper.selectDriverByIds(new ArrayList<>(driverIds));
|
||||
for (Map<String, Object> driver : driverList) {
|
||||
Object idObj = driver.get("id");
|
||||
Integer id = null;
|
||||
if (idObj instanceof Integer) {
|
||||
id = (Integer) idObj;
|
||||
} else if (idObj instanceof Long) {
|
||||
id = ((Long) idObj).intValue();
|
||||
} else if (idObj instanceof Number) {
|
||||
id = ((Number) idObj).intValue();
|
||||
}
|
||||
if (id != null) {
|
||||
driverMap.put(id, driver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 批量查询车辆信息
|
||||
Map<String, Vehicle> vehicleMap = new HashMap<>();
|
||||
if (!licensePlates.isEmpty()) {
|
||||
List<Vehicle> vehicleList = vehicleMapper.selectByLicensePlates(new ArrayList<>(licensePlates));
|
||||
for (Vehicle vehicle : vehicleList) {
|
||||
if (vehicle != null && StringUtils.isNotEmpty(vehicle.getLicensePlate())) {
|
||||
vehicleMap.put(vehicle.getLicensePlate(), vehicle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 批量查询设备数量统计(使用 SQL 聚合查询)
|
||||
Map<Integer, Map<String, Integer>> deviceCountMap = new HashMap<>();
|
||||
if (!deliveryIds.isEmpty()) {
|
||||
// 批量查询耳标和项圈设备数量
|
||||
for (Integer deliveryId : deliveryIds) {
|
||||
try {
|
||||
LambdaQueryWrapper<DeliveryDevice> earTagWrapper = new LambdaQueryWrapper<>();
|
||||
earTagWrapper.eq(DeliveryDevice::getDeliveryId, deliveryId)
|
||||
.eq(DeliveryDevice::getDeviceType, 2);
|
||||
long earTagCount = deliveryDeviceService.count(earTagWrapper);
|
||||
|
||||
LambdaQueryWrapper<DeliveryDevice> collarWrapper = new LambdaQueryWrapper<>();
|
||||
collarWrapper.eq(DeliveryDevice::getDeliveryId, deliveryId)
|
||||
.eq(DeliveryDevice::getDeviceType, 3);
|
||||
long collarCount = deliveryDeviceService.count(collarWrapper);
|
||||
|
||||
Map<String, Integer> counts = new HashMap<>();
|
||||
counts.put("earTag", (int) earTagCount);
|
||||
counts.put("collar", (int) collarCount);
|
||||
counts.put("total", (int) (earTagCount + collarCount));
|
||||
deviceCountMap.put(deliveryId, counts);
|
||||
} catch (Exception e) {
|
||||
logger.error("查询设备数量失败,deliveryId: {}", deliveryId, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 填充每个 Delivery 的关联信息
|
||||
for (Delivery delivery : deliveries) {
|
||||
// 设置核验标识
|
||||
if (userId != null && userId.equals(delivery.getCheckBy())) {
|
||||
delivery.setIfCheck(1);
|
||||
}
|
||||
|
||||
// 填充供应商信息
|
||||
if (StringUtils.isNotEmpty(delivery.getSupplierId())) {
|
||||
String[] supplierIds = delivery.getSupplierId().split(",");
|
||||
List<String> supplierNames = new ArrayList<>();
|
||||
List<String> supplierMobiles = new ArrayList<>();
|
||||
for (String supplierId : supplierIds) {
|
||||
if (StringUtils.isNotEmpty(supplierId.trim())) {
|
||||
try {
|
||||
Integer sid = Integer.parseInt(supplierId.trim());
|
||||
Map<String, Object> supplierInfo = memberMap.get(sid);
|
||||
if (supplierInfo != null) {
|
||||
String username = (String) supplierInfo.get("username");
|
||||
String mobile = (String) supplierInfo.get("mobile");
|
||||
if (StringUtils.isNotEmpty(username)) {
|
||||
supplierNames.add(username);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(mobile)) {
|
||||
supplierMobiles.add(mobile);
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
// 忽略无效的ID
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!supplierNames.isEmpty()) {
|
||||
delivery.setSupplierName(String.join(",", supplierNames));
|
||||
} else if (!supplierMobiles.isEmpty()) {
|
||||
delivery.setSupplierName(String.join(",", supplierMobiles));
|
||||
}
|
||||
if (!supplierMobiles.isEmpty()) {
|
||||
delivery.setSupplierMobile(String.join(",", supplierMobiles));
|
||||
}
|
||||
}
|
||||
|
||||
// 填充资金方信息
|
||||
if (delivery.getFundId() != null) {
|
||||
Map<String, Object> fundInfo = memberMap.get(delivery.getFundId());
|
||||
if (fundInfo != null) {
|
||||
String username = (String) fundInfo.get("username");
|
||||
String mobile = (String) fundInfo.get("mobile");
|
||||
if (StringUtils.isNotEmpty(username)) {
|
||||
delivery.setFundName(username);
|
||||
} else if (StringUtils.isNotEmpty(mobile)) {
|
||||
delivery.setFundName(mobile);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(mobile)) {
|
||||
delivery.setFundMobile(mobile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 填充采购商信息
|
||||
if (delivery.getBuyerId() != null) {
|
||||
Map<String, Object> buyerInfo = memberMap.get(delivery.getBuyerId());
|
||||
if (buyerInfo != null) {
|
||||
String username = (String) buyerInfo.get("username");
|
||||
String mobile = (String) buyerInfo.get("mobile");
|
||||
if (StringUtils.isNotEmpty(username)) {
|
||||
delivery.setBuyerName(username);
|
||||
} else if (StringUtils.isNotEmpty(mobile)) {
|
||||
delivery.setBuyerName(mobile);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(mobile)) {
|
||||
delivery.setBuyerMobile(mobile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 填充司机信息
|
||||
if (delivery.getDriverId() != null) {
|
||||
Map<String, Object> driverInfo = driverMap.get(delivery.getDriverId());
|
||||
if (driverInfo != null) {
|
||||
String driverName = (String) driverInfo.get("username");
|
||||
String driverMobile = (String) driverInfo.get("mobile");
|
||||
String carImg = (String) driverInfo.get("car_img");
|
||||
|
||||
if (StringUtils.isNotEmpty(driverMobile)) {
|
||||
delivery.setDriverMobile(driverMobile);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(driverName)) {
|
||||
delivery.setDriverName(driverName);
|
||||
}
|
||||
|
||||
// 优先从车辆表获取车身照片
|
||||
boolean vehiclePhotoSet = false;
|
||||
if (StringUtils.isNotEmpty(delivery.getLicensePlate())) {
|
||||
Vehicle vehicle = vehicleMap.get(delivery.getLicensePlate());
|
||||
if (vehicle != null) {
|
||||
String carFrontPhoto = vehicle.getCarFrontPhoto();
|
||||
String carRearPhoto = vehicle.getCarRearPhoto();
|
||||
if (StringUtils.isNotEmpty(carFrontPhoto) || StringUtils.isNotEmpty(carRearPhoto)) {
|
||||
delivery.setCarFrontPhoto(StringUtils.isNotEmpty(carFrontPhoto) ? carFrontPhoto : null);
|
||||
delivery.setCarBehindPhoto(StringUtils.isNotEmpty(carRearPhoto) ? carRearPhoto : null);
|
||||
vehiclePhotoSet = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果车辆表中没有照片,从司机信息中获取
|
||||
if (!vehiclePhotoSet && StringUtils.isNotEmpty(carImg)) {
|
||||
String[] carImgUrls = carImg.split(",");
|
||||
if (carImgUrls.length >= 2) {
|
||||
delivery.setCarBehindPhoto(carImgUrls[0].trim());
|
||||
delivery.setCarFrontPhoto(carImgUrls[1].trim());
|
||||
} else if (carImgUrls.length == 1) {
|
||||
String singlePhoto = carImgUrls[0].trim();
|
||||
delivery.setCarFrontPhoto(singlePhoto);
|
||||
delivery.setCarBehindPhoto(singlePhoto);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 填充设备数量统计
|
||||
if (delivery.getId() != null) {
|
||||
Map<String, Integer> counts = deviceCountMap.get(delivery.getId());
|
||||
if (counts != null) {
|
||||
delivery.setRegisteredJbqCount(counts.get("total"));
|
||||
delivery.setEarTagCount(counts.get("earTag"));
|
||||
delivery.setBindJbqCount(counts.get("total"));
|
||||
// 已佩戴设备数量暂时设为0(需要额外查询)
|
||||
delivery.setWareCount(0);
|
||||
} else {
|
||||
delivery.setRegisteredJbqCount(0);
|
||||
delivery.setEarTagCount(0);
|
||||
delivery.setBindJbqCount(0);
|
||||
delivery.setWareCount(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ 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.extension.service.impl.ServiceImpl;
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -24,6 +22,7 @@ import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -93,25 +92,18 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
||||
|
||||
queryWrapper.orderByDesc(Order::getCreateTime);
|
||||
|
||||
// 判断是否需要先过滤再分页(如果提供了买方、卖方、运单号、起始地、目的地、单价搜索)
|
||||
boolean needFilter = (buyerName != null && !buyerName.trim().isEmpty()) ||
|
||||
(sellerName != null && !sellerName.trim().isEmpty()) ||
|
||||
(deliveryNumber != null && !deliveryNumber.trim().isEmpty()) ||
|
||||
(startLocation != null && !startLocation.trim().isEmpty()) ||
|
||||
(endLocation != null && !endLocation.trim().isEmpty()) ||
|
||||
(firmPrice != null && !firmPrice.trim().isEmpty());
|
||||
|
||||
List<Order> filteredList;
|
||||
long total;
|
||||
|
||||
if (needFilter) {
|
||||
// 需要过滤的情况:先查询所有数据,填充信息,过滤,然后手动分页
|
||||
// 为了保持买卖关系链的完整性,统一采用先查询所有数据、排序、再分页的方式
|
||||
// 这样可以确保同一deliveryId的订单链条不会被分页截断
|
||||
// 先查询所有符合条件的数据(不包含需要填充信息的过滤条件)
|
||||
List<Order> allList = orderMapper.selectList(queryWrapper);
|
||||
|
||||
// 批量填充关联信息(优化性能)
|
||||
fillOrderInfoBatch(allList);
|
||||
|
||||
// 进行过滤
|
||||
// 进行过滤(针对需要填充信息后才能过滤的字段)
|
||||
filteredList = allList;
|
||||
if (buyerName != null && !buyerName.trim().isEmpty()) {
|
||||
filteredList = filteredList.stream()
|
||||
@@ -150,74 +142,64 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
// 获取总数(排序前)
|
||||
total = filteredList.size();
|
||||
|
||||
// 手动分页
|
||||
int startIndex = (pageNum - 1) * pageSize;
|
||||
int endIndex = Math.min(startIndex + pageSize, filteredList.size());
|
||||
if (startIndex < filteredList.size()) {
|
||||
filteredList = filteredList.subList(startIndex, endIndex);
|
||||
} else {
|
||||
filteredList = new ArrayList<>();
|
||||
}
|
||||
logger.info("查询到{}条订单记录,过滤后{}条", allList.size(), total);
|
||||
|
||||
logger.info("查询到{}条订单记录,过滤后{}条,分页后{}条", allList.size(), total, filteredList.size());
|
||||
} else {
|
||||
// 不需要过滤的情况:直接使用PageHelper分页
|
||||
Page<Order> page = PageHelper.startPage(pageNum, pageSize);
|
||||
|
||||
// 执行查询
|
||||
List<Order> list = orderMapper.selectList(queryWrapper);
|
||||
|
||||
// 批量填充关联信息(优化性能)
|
||||
fillOrderInfoBatch(list);
|
||||
|
||||
// 获取总数和分页数据
|
||||
total = page.getTotal();
|
||||
filteredList = list;
|
||||
|
||||
logger.info("查询到{}条订单记录,分页后{}条", total, filteredList.size());
|
||||
}
|
||||
|
||||
// 优化排序:按运送清单分组排序
|
||||
// 优化排序:按运送清单分组,同链路内按买卖关系链排序
|
||||
// 排序规则:1. 有deliveryId的订单按deliveryId分组,相同deliveryId的订单放在一起
|
||||
// 2. 按deliveryId排序(null值放在最后)
|
||||
// 3. 同一deliveryId内的订单按创建时间排序
|
||||
filteredList.sort((o1, o2) -> {
|
||||
Integer deliveryId1 = o1.getDeliveryId();
|
||||
Integer deliveryId2 = o2.getDeliveryId();
|
||||
// 3. 同一deliveryId内的订单按买卖关系链排序:
|
||||
// - 如果订单A的卖方等于订单B的买方,则B排在A后面
|
||||
// - 形成链条:订单1 -> 订单2 -> 订单3
|
||||
List<Order> sortedList = new ArrayList<>();
|
||||
|
||||
// 如果两个订单都没有deliveryId,按创建时间倒序
|
||||
if (deliveryId1 == null && deliveryId2 == null) {
|
||||
if (o1.getCreateTime() != null && o2.getCreateTime() != null) {
|
||||
return o2.getCreateTime().compareTo(o1.getCreateTime());
|
||||
// 按deliveryId分组
|
||||
java.util.Map<Integer, List<Order>> deliveryGroups = new java.util.HashMap<>();
|
||||
List<Order> noDeliveryOrders = new ArrayList<>();
|
||||
|
||||
for (Order order : filteredList) {
|
||||
Integer deliveryId = order.getDeliveryId();
|
||||
if (deliveryId == null) {
|
||||
noDeliveryOrders.add(order);
|
||||
} else {
|
||||
deliveryGroups.computeIfAbsent(deliveryId, k -> new ArrayList<>()).add(order);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 如果只有一个订单有deliveryId,有deliveryId的排在前面
|
||||
if (deliveryId1 == null) {
|
||||
return 1; // o1排在后面
|
||||
}
|
||||
if (deliveryId2 == null) {
|
||||
return -1; // o2排在后面
|
||||
// 对每个deliveryId组内的订单按买卖关系链排序
|
||||
List<Integer> sortedDeliveryIds = new ArrayList<>(deliveryGroups.keySet());
|
||||
sortedDeliveryIds.sort(Integer::compareTo);
|
||||
|
||||
for (Integer deliveryId : sortedDeliveryIds) {
|
||||
List<Order> groupOrders = deliveryGroups.get(deliveryId);
|
||||
List<Order> sortedGroup = sortOrdersByChain(groupOrders);
|
||||
sortedList.addAll(sortedGroup);
|
||||
}
|
||||
|
||||
// 两个订单都有deliveryId,先按deliveryId排序
|
||||
int deliveryIdCompare = deliveryId1.compareTo(deliveryId2);
|
||||
if (deliveryIdCompare != 0) {
|
||||
return deliveryIdCompare;
|
||||
}
|
||||
|
||||
// deliveryId相同,按创建时间倒序
|
||||
// 没有deliveryId的订单按创建时间倒序,放在最后
|
||||
noDeliveryOrders.sort((o1, o2) -> {
|
||||
if (o1.getCreateTime() != null && o2.getCreateTime() != null) {
|
||||
return o2.getCreateTime().compareTo(o1.getCreateTime());
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
sortedList.addAll(noDeliveryOrders);
|
||||
|
||||
logger.info("订单列表排序完成,共{}条记录", filteredList.size());
|
||||
filteredList = sortedList;
|
||||
logger.info("订单列表排序完成,共{}条记录,已按买卖关系链排序", filteredList.size());
|
||||
|
||||
// 排序后再进行分页,确保同一链条的订单不会被分到不同页
|
||||
int startIndex = (pageNum - 1) * pageSize;
|
||||
int endIndex = Math.min(startIndex + pageSize, filteredList.size());
|
||||
if (startIndex < filteredList.size()) {
|
||||
filteredList = filteredList.subList(startIndex, endIndex);
|
||||
logger.info("分页后{}条记录(第{}页,每页{}条)", filteredList.size(), pageNum, pageSize);
|
||||
} else {
|
||||
filteredList = new ArrayList<>();
|
||||
logger.info("分页后0条记录(超出范围)");
|
||||
}
|
||||
|
||||
// 构建分页结果
|
||||
return new PageResultResponse<>(total, filteredList);
|
||||
@@ -229,8 +211,8 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
||||
@Override
|
||||
@Transactional
|
||||
public AjaxResult addOrder(Order order) {
|
||||
logger.info("开始新增订单,买方:{},卖方:{},结算方式:{},约定价格:{}",
|
||||
order.getBuyerId(), order.getSellerId(), order.getSettlementType(), order.getFirmPrice());
|
||||
logger.info("开始新增订单,买方:{},卖方:{},结算方式:{},约定价格:{},运送清单ID:{}",
|
||||
order.getBuyerId(), order.getSellerId(), order.getSettlementType(), order.getFirmPrice(), order.getDeliveryId());
|
||||
|
||||
try {
|
||||
// 验证结算方式
|
||||
@@ -250,6 +232,16 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
||||
return AjaxResult.error("约定价格不能小于0");
|
||||
}
|
||||
|
||||
// 如果提供了deliveryId,验证运送清单是否存在
|
||||
if (order.getDeliveryId() != null) {
|
||||
com.aiotagro.cattletrade.business.entity.Delivery delivery = deliveryMapper.selectById(order.getDeliveryId());
|
||||
if (delivery == null) {
|
||||
logger.error("运送清单不存在,ID:{}", order.getDeliveryId());
|
||||
return AjaxResult.error("运送清单不存在");
|
||||
}
|
||||
logger.info("订单将关联到运送清单,运送清单ID:{},运单号:{}", order.getDeliveryId(), delivery.getDeliveryNumber());
|
||||
}
|
||||
|
||||
// 设置创建人和创建时间
|
||||
Integer userId = SecurityUtil.getCurrentUserId();
|
||||
order.setCreatedBy(userId);
|
||||
@@ -259,8 +251,15 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
||||
int result = orderMapper.insert(order);
|
||||
|
||||
if (result > 0) {
|
||||
if (order.getDeliveryId() != null) {
|
||||
logger.info("新增订单成功并关联运送清单,订单ID:{},运送清单ID:{}", order.getId(), order.getDeliveryId());
|
||||
} else {
|
||||
logger.info("新增订单成功,订单ID:{}", order.getId());
|
||||
return AjaxResult.success("新增订单成功");
|
||||
}
|
||||
// 返回订单ID,方便前端更新关联关系
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("id", order.getId());
|
||||
return AjaxResult.success("新增订单成功", data);
|
||||
} else {
|
||||
logger.error("新增订单失败");
|
||||
return AjaxResult.error("新增订单失败");
|
||||
@@ -341,6 +340,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
||||
|
||||
/**
|
||||
* 删除订单(逻辑删除)
|
||||
* 删除订单时,同时更新delivery表中order_id字段,移除对应的订单ID
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
@@ -360,6 +360,75 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
||||
return AjaxResult.error("订单不存在");
|
||||
}
|
||||
|
||||
// 在删除订单之前,先更新delivery表中order_id字段,移除该订单ID
|
||||
String orderIdStr = String.valueOf(id);
|
||||
try {
|
||||
// 查询所有order_id字段包含该订单ID的delivery记录
|
||||
// 使用LIKE查询匹配:开头、中间、结尾或单独存在
|
||||
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<com.aiotagro.cattletrade.business.entity.Delivery> wrapper =
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
|
||||
wrapper.and(w -> w
|
||||
.eq(com.aiotagro.cattletrade.business.entity.Delivery::getOrderId, orderIdStr) // 单独存在
|
||||
.or()
|
||||
.like(com.aiotagro.cattletrade.business.entity.Delivery::getOrderId, orderIdStr + ",") // 开头
|
||||
.or()
|
||||
.like(com.aiotagro.cattletrade.business.entity.Delivery::getOrderId, "," + orderIdStr + ",") // 中间
|
||||
.or()
|
||||
.like(com.aiotagro.cattletrade.business.entity.Delivery::getOrderId, "," + orderIdStr) // 结尾
|
||||
);
|
||||
wrapper.isNotNull(com.aiotagro.cattletrade.business.entity.Delivery::getOrderId);
|
||||
wrapper.ne(com.aiotagro.cattletrade.business.entity.Delivery::getOrderId, "");
|
||||
|
||||
List<com.aiotagro.cattletrade.business.entity.Delivery> deliveryList = deliveryMapper.selectList(wrapper);
|
||||
|
||||
if (deliveryList != null && !deliveryList.isEmpty()) {
|
||||
logger.info("找到{}条运送清单包含订单ID:{}", deliveryList.size(), id);
|
||||
|
||||
// 更新每条delivery记录的order_id字段
|
||||
for (com.aiotagro.cattletrade.business.entity.Delivery delivery : deliveryList) {
|
||||
String currentOrderId = delivery.getOrderId();
|
||||
if (currentOrderId != null && !currentOrderId.trim().isEmpty()) {
|
||||
// 将order_id字符串按逗号分割
|
||||
List<String> orderIdList = new ArrayList<>(Arrays.asList(currentOrderId.split(",")));
|
||||
// 去除空白并过滤空字符串
|
||||
orderIdList = orderIdList.stream()
|
||||
.map(String::trim)
|
||||
.filter(oid -> !oid.isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 移除该订单ID
|
||||
orderIdList.remove(orderIdStr);
|
||||
|
||||
// 如果还有订单ID,重新组合;否则设置为null
|
||||
if (!orderIdList.isEmpty()) {
|
||||
// 排序并去重
|
||||
orderIdList = orderIdList.stream()
|
||||
.distinct()
|
||||
.sorted((a, b) -> Integer.compare(Integer.parseInt(a), Integer.parseInt(b)))
|
||||
.collect(Collectors.toList());
|
||||
String newOrderId = String.join(",", orderIdList);
|
||||
delivery.setOrderId(newOrderId);
|
||||
logger.info("更新运送清单order_id:deliveryId={}, 原值={}, 新值={}",
|
||||
delivery.getId(), currentOrderId, newOrderId);
|
||||
} else {
|
||||
delivery.setOrderId(null);
|
||||
logger.info("更新运送清单order_id:deliveryId={}, 原值={}, 新值=null(已清空)",
|
||||
delivery.getId(), currentOrderId);
|
||||
}
|
||||
|
||||
// 更新delivery记录
|
||||
deliveryMapper.updateById(delivery);
|
||||
}
|
||||
}
|
||||
logger.info("成功更新{}条运送清单的order_id字段", deliveryList.size());
|
||||
} else {
|
||||
logger.info("未找到包含订单ID {} 的运送清单记录", id);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("更新delivery表order_id字段失败,订单ID:{},错误:{}", id, e.getMessage(), e);
|
||||
// 不阻止删除流程,只记录错误
|
||||
}
|
||||
|
||||
// 逻辑删除(通过update方法设置is_delete=1)
|
||||
// MyBatis-Plus的@TableLogic会自动处理
|
||||
int result = orderMapper.deleteById(id);
|
||||
@@ -607,6 +676,176 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按买卖关系链排序订单
|
||||
* 规则:如果订单A的卖方等于订单B的买方,则B排在A后面
|
||||
* 支持买方/卖方为多个名称(逗号分隔)的情况
|
||||
*
|
||||
* @param orders 同一deliveryId的订单列表
|
||||
* @return 排序后的订单列表
|
||||
*/
|
||||
private List<Order> sortOrdersByChain(List<Order> orders) {
|
||||
if (orders == null || orders.size() <= 1) {
|
||||
return orders;
|
||||
}
|
||||
|
||||
List<Order> result = new ArrayList<>();
|
||||
List<Order> remaining = new ArrayList<>(orders);
|
||||
|
||||
// 辅助方法:检查两个名称是否匹配(支持逗号分隔的多个名称)
|
||||
java.util.function.BiFunction<String, String, Boolean> namesMatch = (name1, name2) -> {
|
||||
if (name1 == null || name2 == null) {
|
||||
return false;
|
||||
}
|
||||
// 分割并trim,支持 ", " 或 "," 分隔
|
||||
String[] names1 = name1.split(",\\s*");
|
||||
String[] names2 = name2.split(",\\s*");
|
||||
for (String n1 : names1) {
|
||||
String trimmed1 = n1.trim();
|
||||
if (trimmed1.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
for (String n2 : names2) {
|
||||
String trimmed2 = n2.trim();
|
||||
if (trimmed1.equals(trimmed2)) {
|
||||
logger.debug("名称匹配:{} == {}", trimmed1, trimmed2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// 记录所有订单的买卖方信息用于调试
|
||||
logger.info("开始排序订单,共{}条", remaining.size());
|
||||
for (Order order : remaining) {
|
||||
logger.info("订单ID:{},买方:{},卖方:{}", order.getId(), order.getBuyerName(), order.getSellerName());
|
||||
}
|
||||
|
||||
// 找到链条的起点:没有其他订单的卖方等于这个订单的买方
|
||||
Order startOrder = null;
|
||||
for (Order order : remaining) {
|
||||
String buyerName = order.getBuyerName();
|
||||
if (buyerName == null || buyerName.trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean isStart = true;
|
||||
// 检查是否有其他订单的卖方等于这个订单的买方
|
||||
for (Order other : remaining) {
|
||||
if (order == other) {
|
||||
continue;
|
||||
}
|
||||
String otherSellerName = other.getSellerName();
|
||||
if (otherSellerName != null && namesMatch.apply(buyerName, otherSellerName)) {
|
||||
logger.info("订单{}不是起点,因为订单{}的卖方({})等于订单{}的买方({})",
|
||||
order.getId(), other.getId(), otherSellerName, order.getId(), buyerName);
|
||||
isStart = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isStart) {
|
||||
logger.info("找到起点订单:{},买方:{}", order.getId(), buyerName);
|
||||
startOrder = order;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到起点(可能是循环链或没有匹配关系),使用第一个订单作为起点
|
||||
if (startOrder == null) {
|
||||
logger.warn("未找到起点订单,使用第一个订单作为起点");
|
||||
startOrder = remaining.get(0);
|
||||
}
|
||||
|
||||
// 从起点开始构建链条
|
||||
result.add(startOrder);
|
||||
remaining.remove(startOrder);
|
||||
logger.info("添加起点订单:{}", startOrder.getId());
|
||||
|
||||
// 按照买卖关系链依次添加订单,支持多个独立链条
|
||||
while (!remaining.isEmpty()) {
|
||||
Order currentOrder = result.get(result.size() - 1); // 获取当前链条的最后一个订单
|
||||
boolean found = false;
|
||||
String currentSellerName = currentOrder.getSellerName();
|
||||
|
||||
if (currentSellerName != null && !currentSellerName.trim().isEmpty()) {
|
||||
// 查找下一个订单:买方等于当前订单的卖方
|
||||
for (Order nextOrder : remaining) {
|
||||
String nextBuyerName = nextOrder.getBuyerName();
|
||||
if (nextBuyerName != null && namesMatch.apply(currentSellerName, nextBuyerName)) {
|
||||
logger.info("找到下一个订单:{}(当前订单{}的卖方{} == 订单{}的买方{})",
|
||||
nextOrder.getId(), currentOrder.getId(), currentSellerName, nextOrder.getId(), nextBuyerName);
|
||||
result.add(nextOrder);
|
||||
remaining.remove(nextOrder);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果当前链条无法继续,尝试从剩余订单中找新的起点构建新链条
|
||||
if (!found) {
|
||||
logger.info("当前链条无法继续,当前订单{}的卖方:{},剩余{}个订单,尝试构建新链条",
|
||||
currentOrder.getId(), currentSellerName, remaining.size());
|
||||
|
||||
// 从剩余订单中找新的起点
|
||||
Order newStartOrder = null;
|
||||
for (Order order : remaining) {
|
||||
String buyerName = order.getBuyerName();
|
||||
if (buyerName == null || buyerName.trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean isStart = true;
|
||||
// 检查是否有其他剩余订单的卖方等于这个订单的买方
|
||||
for (Order other : remaining) {
|
||||
if (order == other) {
|
||||
continue;
|
||||
}
|
||||
String otherSellerName = other.getSellerName();
|
||||
if (otherSellerName != null && namesMatch.apply(buyerName, otherSellerName)) {
|
||||
isStart = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isStart) {
|
||||
logger.info("找到新链条起点订单:{},买方:{}", order.getId(), buyerName);
|
||||
newStartOrder = order;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果找到新起点,开始构建新链条
|
||||
if (newStartOrder != null) {
|
||||
result.add(newStartOrder);
|
||||
remaining.remove(newStartOrder);
|
||||
logger.info("添加新链条起点订单:{}", newStartOrder.getId());
|
||||
// 继续循环,从新起点开始构建链条
|
||||
continue;
|
||||
} else {
|
||||
// 如果找不到新起点,将剩余订单按创建时间倒序添加
|
||||
logger.info("未找到新链条起点,将剩余订单按创建时间排序");
|
||||
for (Order rem : remaining) {
|
||||
logger.info("剩余订单{},买方:{},卖方:{}", rem.getId(), rem.getBuyerName(), rem.getSellerName());
|
||||
}
|
||||
remaining.sort((o1, o2) -> {
|
||||
if (o1.getCreateTime() != null && o2.getCreateTime() != null) {
|
||||
return o2.getCreateTime().compareTo(o1.getCreateTime());
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
result.addAll(remaining);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("排序完成,最终顺序:{}", result.stream().map(o -> String.valueOf(o.getId())).collect(Collectors.joining(" -> ")));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充订单关联信息(买方名称、卖方名称、创建人名称、结算方式描述)
|
||||
* 用于单个订单详情查询
|
||||
@@ -854,6 +1093,36 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
||||
|
||||
logger.info("批量更新订单delivery_id完成,成功:{}条,失败:{}条", successCount, failCount);
|
||||
|
||||
// 同步更新delivery表的order_id字段(将所有关联的订单ID用逗号连接)
|
||||
if (successCount > 0) {
|
||||
try {
|
||||
// 查询该运送清单当前关联的所有订单ID
|
||||
LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<Order>()
|
||||
.eq(Order::getDeliveryId, deliveryId)
|
||||
.select(Order::getId)
|
||||
.orderByAsc(Order::getId);
|
||||
List<Order> associatedOrders = orderMapper.selectList(queryWrapper);
|
||||
|
||||
// 将所有订单ID用逗号连接
|
||||
String orderIdStr = associatedOrders.stream()
|
||||
.map(order -> String.valueOf(order.getId()))
|
||||
.collect(Collectors.joining(","));
|
||||
|
||||
// 更新delivery表的order_id字段
|
||||
com.aiotagro.cattletrade.business.entity.Delivery delivery = deliveryMapper.selectById(deliveryId);
|
||||
if (delivery != null) {
|
||||
delivery.setOrderId(orderIdStr);
|
||||
deliveryMapper.updateById(delivery);
|
||||
logger.info("同步更新运送清单order_id成功,运送清单ID:{},订单ID列表:{}", deliveryId, orderIdStr);
|
||||
} else {
|
||||
logger.warn("运送清单不存在,无法更新order_id,运送清单ID:{}", deliveryId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("同步更新运送清单order_id失败,运送清单ID:{},错误:{}", deliveryId, e.getMessage(), e);
|
||||
// 不抛出异常,只记录日志,因为订单的delivery_id已经更新成功
|
||||
}
|
||||
}
|
||||
|
||||
if (failCount == 0) {
|
||||
return AjaxResult.success("批量更新成功,共更新" + successCount + "条订单");
|
||||
} else {
|
||||
@@ -868,5 +1137,88 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
|
||||
return AjaxResult.error("批量更新订单delivery_id失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解除订单与运送清单的关联(将订单的deliveryId设置为null)
|
||||
*
|
||||
* @param orderId 订单ID
|
||||
* @return AjaxResult
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public AjaxResult unbindOrderFromDelivery(Integer orderId) {
|
||||
try {
|
||||
logger.info("解除订单与运送清单的关联,订单ID:{}", orderId);
|
||||
|
||||
if (orderId == null) {
|
||||
logger.error("订单ID不能为空");
|
||||
return AjaxResult.error("订单ID不能为空");
|
||||
}
|
||||
|
||||
// 查询订单是否存在
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
logger.error("订单不存在,ID:{}", orderId);
|
||||
return AjaxResult.error("订单不存在");
|
||||
}
|
||||
|
||||
// 如果订单没有关联运送清单,直接返回成功
|
||||
if (order.getDeliveryId() == null) {
|
||||
logger.info("订单未关联运送清单,无需解除,订单ID:{}", orderId);
|
||||
return AjaxResult.success("订单未关联运送清单,无需解除");
|
||||
}
|
||||
|
||||
Integer oldDeliveryId = order.getDeliveryId();
|
||||
|
||||
// 将deliveryId设置为null
|
||||
order.setDeliveryId(null);
|
||||
order.setUpdateTime(new Date());
|
||||
try {
|
||||
order.setUpdatedBy(SecurityUtil.getCurrentUserId());
|
||||
} catch (Exception e) {
|
||||
logger.warn("获取当前用户失败,使用订单原有创建人:{}", e.getMessage());
|
||||
if (order.getCreatedBy() != null) {
|
||||
order.setUpdatedBy(order.getCreatedBy());
|
||||
}
|
||||
}
|
||||
|
||||
int result = orderMapper.updateById(order);
|
||||
if (result > 0) {
|
||||
// 同步更新delivery表的order_id字段(移除该订单ID)
|
||||
if (oldDeliveryId != null) {
|
||||
try {
|
||||
com.aiotagro.cattletrade.business.entity.Delivery delivery = deliveryMapper.selectById(oldDeliveryId);
|
||||
if (delivery != null && delivery.getOrderId() != null && !delivery.getOrderId().trim().isEmpty()) {
|
||||
// 从order_id字符串中移除当前订单ID
|
||||
String orderIdStr = delivery.getOrderId();
|
||||
List<String> orderIdList = new ArrayList<>(Arrays.asList(orderIdStr.split(",")));
|
||||
orderIdList.remove(String.valueOf(orderId));
|
||||
|
||||
// 更新order_id字段
|
||||
if (orderIdList.isEmpty()) {
|
||||
delivery.setOrderId(null);
|
||||
} else {
|
||||
delivery.setOrderId(String.join(",", orderIdList));
|
||||
}
|
||||
deliveryMapper.updateById(delivery);
|
||||
logger.info("同步更新运送清单order_id成功,移除订单ID:{},运送清单ID:{}", orderId, oldDeliveryId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("同步更新运送清单order_id失败,运送清单ID:{},错误:{}", oldDeliveryId, e.getMessage(), e);
|
||||
// 不抛出异常,只记录日志
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("解除订单与运送清单关联成功,订单ID:{},原运送清单ID:{}", orderId, oldDeliveryId);
|
||||
return AjaxResult.success("解除关联成功");
|
||||
} else {
|
||||
logger.error("解除订单与运送清单关联失败,订单ID:{}", orderId);
|
||||
return AjaxResult.error("解除关联失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("解除订单与运送清单关联异常,订单ID:{},错误:{}", orderId, e.getMessage(), e);
|
||||
return AjaxResult.error("解除关联失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import org.springframework.context.annotation.Configuration;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* Druid 数据源配置类
|
||||
*
|
||||
* @author Carson
|
||||
* @package_name com.aiotagro.payinfo.config
|
||||
* @date 2024/2/6 17:00
|
||||
@@ -15,21 +17,22 @@ import javax.sql.DataSource;
|
||||
@Configuration
|
||||
public class DruidDataSourceConfig {
|
||||
|
||||
//编写方法,注入DruidDataSource
|
||||
//为什么我们注入自己的DataSource , 默认的HiKariDatasource失效?
|
||||
//1. 默认的数据源是如配置? @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
|
||||
// 解读通过@ConditionalOnMissingBean({ DataSource.class}) 判断如果容器有DataSource Bean 就不注入默认的HiKariDatasource
|
||||
/**
|
||||
* 配置 Druid 数据源
|
||||
*
|
||||
* 说明:
|
||||
* 1. 使用 @ConfigurationProperties("spring.datasource") 自动读取配置文件中的连接池参数
|
||||
* 2. Spring Boot 默认使用 HikariCP,通过注入自定义 DataSource Bean 来使用 Druid
|
||||
* 3. Druid 连接池参数通过 application.yml 中的 spring.datasource.druid.* 配置
|
||||
*
|
||||
* @return DataSource 数据源实例
|
||||
*/
|
||||
@ConfigurationProperties("spring.datasource")
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
//1. 配置了 @ConfigurationProperties("spring.datasource")
|
||||
// 就可以读取到application.yml的配置,注意:我们需要将bean注入到spring ioc容器中、bean中提供get\set方法
|
||||
//2. 我们就不需要调用DruidDataSource 对象的setXxx, 会自动关联
|
||||
|
||||
DruidDataSource druidDataSource = new DruidDataSource();
|
||||
//druidDataSource.setUrl();
|
||||
//druidDataSource.setUsername();
|
||||
//druidDataSource.setPassword();
|
||||
// 连接池参数通过 @ConfigurationProperties 自动从配置文件注入
|
||||
// 配置文件位置:application.yml 中的 spring.datasource.druid.*
|
||||
return druidDataSource;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,46 @@ spring:
|
||||
max-wait: -1ms
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://129.211.213.226:3306/cattletrade?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
# 在 URL 中添加连接超时和 Socket 超时参数,以及自动重连
|
||||
url: jdbc:mysql://129.211.213.226:3306/cattletrade?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&connectTimeout=60000&socketTimeout=60000&autoReconnect=true
|
||||
username: root
|
||||
password: Aiotagro@741
|
||||
# Druid 连接池配置
|
||||
druid:
|
||||
# 初始连接数
|
||||
initial-size: 5
|
||||
# 最小空闲连接数
|
||||
min-idle: 5
|
||||
# 最大活跃连接数
|
||||
max-active: 20
|
||||
# 获取连接的最大等待时间(毫秒),60秒
|
||||
max-wait: 60000
|
||||
# 连接池中的连接空闲多久后会被回收(毫秒)
|
||||
time-between-eviction-runs-millis: 60000
|
||||
# 连接在池中最小生存时间(毫秒),5分钟
|
||||
min-evictable-idle-time-millis: 300000
|
||||
# 连接在池中最大生存时间(毫秒),15分钟
|
||||
max-evictable-idle-time-millis: 900000
|
||||
# 验证连接的 SQL 查询
|
||||
validation-query: SELECT 1
|
||||
# 获取连接时是否验证连接有效性
|
||||
test-on-borrow: false
|
||||
# 归还连接时是否验证连接有效性
|
||||
test-on-return: false
|
||||
# 空闲时是否验证连接有效性(推荐开启)
|
||||
test-while-idle: true
|
||||
# 连接超时时间(毫秒)
|
||||
connection-timeout: 60000
|
||||
# Socket 超时时间(毫秒)
|
||||
socket-timeout: 60000
|
||||
# 查询超时时间(毫秒)
|
||||
query-timeout: 60000
|
||||
# 连接失败后是否中断
|
||||
break-after-acquire-failure: false
|
||||
# 连接失败重试次数
|
||||
connection-error-retry-attempts: 3
|
||||
# 是否在连接断开时自动重连
|
||||
keep-alive: true
|
||||
|
||||
sa-token:
|
||||
# token 名称(同时也是 cookie 名称)
|
||||
|
||||
@@ -26,9 +26,46 @@ spring:
|
||||
# username: iot-plateform
|
||||
# password: 3qJ7$bV%N9mE
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://129.211.213.226:3306/cattletrade?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
# 在 URL 中添加连接超时和 Socket 超时参数,以及自动重连
|
||||
url: jdbc:mysql://129.211.213.226:3306/cattletrade?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&connectTimeout=60000&socketTimeout=60000&autoReconnect=true
|
||||
username: root
|
||||
password: Aiotagro@741
|
||||
# Druid 连接池配置
|
||||
druid:
|
||||
# 初始连接数
|
||||
initial-size: 5
|
||||
# 最小空闲连接数
|
||||
min-idle: 5
|
||||
# 最大活跃连接数
|
||||
max-active: 20
|
||||
# 获取连接的最大等待时间(毫秒),60秒
|
||||
max-wait: 60000
|
||||
# 连接池中的连接空闲多久后会被回收(毫秒)
|
||||
time-between-eviction-runs-millis: 60000
|
||||
# 连接在池中最小生存时间(毫秒),5分钟
|
||||
min-evictable-idle-time-millis: 300000
|
||||
# 连接在池中最大生存时间(毫秒),15分钟
|
||||
max-evictable-idle-time-millis: 900000
|
||||
# 验证连接的 SQL 查询
|
||||
validation-query: SELECT 1
|
||||
# 获取连接时是否验证连接有效性
|
||||
test-on-borrow: false
|
||||
# 归还连接时是否验证连接有效性
|
||||
test-on-return: false
|
||||
# 空闲时是否验证连接有效性(推荐开启)
|
||||
test-while-idle: true
|
||||
# 连接超时时间(毫秒)
|
||||
connection-timeout: 60000
|
||||
# Socket 超时时间(毫秒)
|
||||
socket-timeout: 60000
|
||||
# 查询超时时间(毫秒)
|
||||
query-timeout: 60000
|
||||
# 连接失败后是否中断
|
||||
break-after-acquire-failure: false
|
||||
# 连接失败重试次数
|
||||
connection-error-retry-attempts: 3
|
||||
# 是否在连接断开时自动重连
|
||||
keep-alive: true
|
||||
|
||||
sa-token:
|
||||
# token 名称(同时也是 cookie 名称)
|
||||
|
||||
@@ -23,9 +23,46 @@ spring:
|
||||
# max-wait: -1ms
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://129.211.213.226:3306/cattletrade?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
# 在 URL 中添加连接超时和 Socket 超时参数,以及自动重连
|
||||
url: jdbc:mysql://129.211.213.226:3306/cattletrade?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&connectTimeout=60000&socketTimeout=60000&autoReconnect=true
|
||||
username: root
|
||||
password: Aiotagro@741
|
||||
# Druid 连接池配置
|
||||
druid:
|
||||
# 初始连接数
|
||||
initial-size: 5
|
||||
# 最小空闲连接数
|
||||
min-idle: 5
|
||||
# 最大活跃连接数
|
||||
max-active: 20
|
||||
# 获取连接的最大等待时间(毫秒),60秒
|
||||
max-wait: 60000
|
||||
# 连接池中的连接空闲多久后会被回收(毫秒)
|
||||
time-between-eviction-runs-millis: 60000
|
||||
# 连接在池中最小生存时间(毫秒),5分钟
|
||||
min-evictable-idle-time-millis: 300000
|
||||
# 连接在池中最大生存时间(毫秒),15分钟
|
||||
max-evictable-idle-time-millis: 900000
|
||||
# 验证连接的 SQL 查询
|
||||
validation-query: SELECT 1
|
||||
# 获取连接时是否验证连接有效性
|
||||
test-on-borrow: false
|
||||
# 归还连接时是否验证连接有效性
|
||||
test-on-return: false
|
||||
# 空闲时是否验证连接有效性(推荐开启)
|
||||
test-while-idle: true
|
||||
# 连接超时时间(毫秒)
|
||||
connection-timeout: 60000
|
||||
# Socket 超时时间(毫秒)
|
||||
socket-timeout: 60000
|
||||
# 查询超时时间(毫秒)
|
||||
query-timeout: 60000
|
||||
# 连接失败后是否中断
|
||||
break-after-acquire-failure: false
|
||||
# 连接失败重试次数
|
||||
connection-error-retry-attempts: 3
|
||||
# 是否在连接断开时自动重连
|
||||
keep-alive: true
|
||||
|
||||
sa-token:
|
||||
# token 名称(同时也是 cookie 名称)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
USE cattletrade;
|
||||
|
||||
-- 为 warning_log 表添加索引以优化预警列表查询性能
|
||||
-- 优化目标:getPageWarningLog 查询中的派生表查询和 JOIN 操作
|
||||
--
|
||||
-- 执行说明:
|
||||
-- 1. 先执行检查脚本查看现有索引:SHOW INDEX FROM warning_log;
|
||||
-- 2. 如果索引已存在,会报错 "Duplicate key name",可以忽略该错误或注释掉对应语句
|
||||
-- 3. 建议按顺序执行,遇到已存在的索引错误时跳过即可
|
||||
|
||||
-- ============================================
|
||||
-- 第一步:检查现有索引(可选)
|
||||
-- ============================================
|
||||
-- 执行以下命令查看 warning_log 表的所有索引:
|
||||
-- SHOW INDEX FROM warning_log;
|
||||
|
||||
-- ============================================
|
||||
-- 第二步:创建索引(所有索引已存在,已全部注释)
|
||||
-- ============================================
|
||||
-- ✅ 状态:所有需要的索引已经存在,无需再次创建
|
||||
-- 如需重新创建,请先删除现有索引:DROP INDEX idx_name ON warning_log;
|
||||
|
||||
-- 1. 添加 delivery_id 和 warning_type 的联合索引(用于派生表的 GROUP BY 查询)
|
||||
-- 这个索引可以大幅提升 "SELECT delivery_id, warning_type, MAX(id) FROM warning_log WHERE warning_type IN (...) GROUP BY delivery_id, warning_type" 的性能
|
||||
-- ✅ 索引已存在:idx_delivery_warning_type
|
||||
-- ALTER TABLE warning_log
|
||||
-- ADD INDEX idx_delivery_warning_type (delivery_id, warning_type, id);
|
||||
|
||||
-- 2. 添加 warning_time 索引(用于 ORDER BY 排序)
|
||||
-- ✅ 索引已存在:idx_warning_time
|
||||
-- ALTER TABLE warning_log
|
||||
-- ADD INDEX idx_warning_time (warning_time DESC);
|
||||
|
||||
-- 3. 如果 delivery_id 还没有单独索引,添加一个(用于 JOIN 操作)
|
||||
-- 注意:如果 delivery_id 已经是主键或唯一索引的一部分,可以跳过
|
||||
-- 检查现有索引:SHOW INDEX FROM warning_log;
|
||||
-- 如果 delivery_id 没有索引,执行下面的语句
|
||||
-- ALTER TABLE warning_log ADD INDEX idx_delivery_id (delivery_id);
|
||||
|
||||
-- ============================================
|
||||
-- 索引说明和性能优化效果
|
||||
-- ============================================
|
||||
--
|
||||
-- 已存在的索引:
|
||||
-- 1. idx_delivery_warning_type: 覆盖索引,支持 WHERE warning_type IN (...) GROUP BY delivery_id, warning_type 的查询
|
||||
-- 2. idx_warning_time: 支持 ORDER BY warning_time DESC 的排序操作
|
||||
--
|
||||
-- 性能提升效果:
|
||||
-- ✅ 派生表查询从全表扫描优化为索引扫描
|
||||
-- ✅ GROUP BY 操作可以使用索引,避免临时表排序
|
||||
-- ✅ ORDER BY 可以使用索引,避免文件排序
|
||||
--
|
||||
-- ============================================
|
||||
-- 验证索引是否生效
|
||||
-- ============================================
|
||||
-- 执行以下 SQL 验证索引:
|
||||
-- SHOW INDEX FROM warning_log WHERE Key_name IN ('idx_delivery_warning_type', 'idx_warning_time');
|
||||
--
|
||||
-- 使用 EXPLAIN 分析查询性能:
|
||||
-- EXPLAIN SELECT ... (使用优化后的 getPageWarningLog SQL)
|
||||
--
|
||||
-- 注意:所有索引已创建完成,脚本保留作为文档参考
|
||||
|
||||
@@ -36,6 +36,12 @@
|
||||
id, delivery_number, buyer_price, sale_price, firm_price, status, license_plate, car_front_photo, car_behind_photo, car_video, start_location, start_lat, start_lon, end_location, end_lat, end_lon, estimated_delivery_time, registered_jbq_count, driver_name, driver_mobile, create_time, created_by, check_by, check_time, check_video
|
||||
</sql>
|
||||
|
||||
<!--
|
||||
优化说明:
|
||||
1. 使用派生表(Derived Table)替代关联子查询,避免对每一行执行子查询
|
||||
2. 先找出每个 delivery_id 和 warning_type 的最大 id,然后通过 JOIN 连接
|
||||
3. 性能提升:从 O(n*m) 降低到 O(n+m),其中 n 是 delivery 数量,m 是 warning_log 数量
|
||||
-->
|
||||
<select id="getPageWarningLog" resultType="com.aiotagro.cattletrade.business.entity.Delivery">
|
||||
SELECT
|
||||
d.id,
|
||||
@@ -65,17 +71,15 @@
|
||||
wl.inventory_jbq_count,
|
||||
su.name as createByName
|
||||
FROM delivery d
|
||||
INNER JOIN warning_log wl ON d.id = wl.delivery_id
|
||||
INNER JOIN (
|
||||
SELECT delivery_id, warning_type, MAX(id) as max_id
|
||||
FROM warning_log
|
||||
WHERE warning_type IN (2,3,4,5,6,7,8,9)
|
||||
GROUP BY delivery_id, warning_type
|
||||
) latest_wl ON d.id = latest_wl.delivery_id
|
||||
INNER JOIN warning_log wl ON wl.id = latest_wl.max_id
|
||||
LEFT JOIN sys_user su ON d.created_by = su.id
|
||||
<where>
|
||||
wl.warning_type IN (2,3,4,5,6,7,8,9)
|
||||
AND wl.id IN (
|
||||
SELECT MAX(id)
|
||||
FROM warning_log
|
||||
WHERE delivery_id = d.id
|
||||
AND warning_type IN (2,3,4,5,6,7,8,9)
|
||||
GROUP BY warning_type
|
||||
)
|
||||
<if test="dto.deliveryNumber != null and '' != dto.deliveryNumber">
|
||||
AND d.delivery_number LIKE CONCAT('%', #{dto.deliveryNumber}, '%')
|
||||
</if>
|
||||
|
||||
Reference in New Issue
Block a user