修改内容

This commit is contained in:
xuqiuyun
2025-10-27 17:38:20 +08:00
parent a40ce28318
commit 42e0abcbe3
50 changed files with 4240 additions and 1276 deletions

View File

@@ -15,12 +15,26 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="发货方" prop="shipper">
<el-input v-model="formData.shipper" placeholder="请输入发货方" />
<el-select v-model="formData.shipper" placeholder="请选择发货方" clearable filterable style="width: 100%">
<el-option
v-for="item in supplierList"
:key="item.id"
:label="item.username || item.mobile"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="采购方" prop="buyer">
<el-input v-model="formData.buyer" placeholder="请输入采购方" />
<el-select v-model="formData.buyer" placeholder="请选择采购方" clearable filterable style="width: 100%">
<el-option
v-for="item in buyerList"
:key="item.id"
:label="item.username || item.mobile"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
@@ -28,12 +42,26 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="车牌号" prop="plateNumber">
<el-input v-model="formData.plateNumber" placeholder="京A12345" />
<el-select v-model="formData.plateNumber" placeholder="请选择车牌号" clearable filterable style="width: 100%">
<el-option
v-for="item in vehicleOptions"
:key="item.id"
:label="item.licensePlate"
:value="item.licensePlate"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="司机姓名" prop="driverName">
<el-input v-model="formData.driverName" placeholder="请输入司机姓名" />
<el-select v-model="formData.driverName" placeholder="请选择司机" clearable filterable style="width: 100%" @change="handleDriverChange">
<el-option
v-for="item in driverOptions"
:key="item.id"
:label="item.username"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
@@ -55,9 +83,9 @@
>
<el-option
v-for="item in serverList"
:key="item.id"
:label="item.deviceNo || item.deviceId"
:value="item.id"
:key="item.deviceId"
:label="item.deviceId + (item.name ? ' - ' + item.name : '')"
:value="item.deviceId"
/>
</el-select>
</el-form-item>
@@ -77,9 +105,9 @@
>
<el-option
v-for="item in eartagList"
:key="item.id"
:label="item.deviceNo || item.deviceId"
:value="item.id"
:key="item.deviceId"
:label="item.deviceId + (item.name ? ' - ' + item.name : '')"
:value="item.deviceId"
/>
</el-select>
</el-form-item>
@@ -96,9 +124,9 @@
>
<el-option
v-for="item in collarList"
:key="item.id"
:label="item.deviceNo || item.deviceId"
:value="item.id"
:key="item.deviceId"
:label="item.deviceId + (item.name ? ' - ' + item.name : '')"
:value="item.deviceId"
/>
</el-select>
</el-form-item>
@@ -131,14 +159,36 @@
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-col :span="24">
<el-form-item label="起点地址" prop="startLocation">
<el-input v-model="formData.startLocation" placeholder="请输入起点地址" />
<el-input
v-model="formData.startLocation"
placeholder="请输入或在地图上选择起点地址"
@click="showStartLocationMap = true"
readonly
style="cursor: pointer;"
>
<template #append>
<el-button @click="showStartLocationMap = true">在地图上选择</el-button>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="目的地地址" prop="endLocation">
<el-input v-model="formData.endLocation" placeholder="请输入目的地地址" />
<el-input
v-model="formData.endLocation"
placeholder="请输入或在地图上选择目的地地址"
@click="showEndLocationMap = true"
readonly
style="cursor: pointer;"
>
<template #append>
<el-button @click="showEndLocationMap = true">在地图上选择</el-button>
</template>
</el-input>
</el-form-item>
</el-col>
</el-row>
@@ -197,12 +247,57 @@
</span>
</template>
</el-dialog>
<!-- 起点地址选择地图 -->
<el-dialog v-model="showStartLocationMap" title="选择起点地址" width="900px">
<baidu-map
class="map"
:center="{lng: 116.404, lat: 39.915}"
:zoom="15"
:scroll-wheel-zoom="true"
@click="handleStartLocationClick"
style="height: 500px"
>
<bm-marker v-if="formData.startLon && formData.startLat" :position="{lng: parseFloat(formData.startLon), lat: parseFloat(formData.startLat)}" :dragging="true" @dragging="handleStartMarkerDrag" />
<bm-map-type :map-types="['BMAP_NORMAL_MAP', 'BMAP_HYBRID_MAP']"></bm-map-type>
</baidu-map>
<template #footer>
<span class="dialog-footer">
<el-button @click="showStartLocationMap = false">取消</el-button>
<el-button type="primary" @click="showStartLocationMap = false">确定</el-button>
</span>
</template>
</el-dialog>
<!-- 目的地地址选择地图 -->
<el-dialog v-model="showEndLocationMap" title="选择目的地地址" width="900px">
<baidu-map
class="map"
:center="{lng: 116.404, lat: 39.915}"
:zoom="15"
:scroll-wheel-zoom="true"
@click="handleEndLocationClick"
style="height: 500px"
>
<bm-marker v-if="formData.endLon && formData.endLat" :position="{lng: parseFloat(formData.endLon), lat: parseFloat(formData.endLat)}" :dragging="true" @dragging="handleEndMarkerDrag" />
<bm-map-type :map-types="['BMAP_NORMAL_MAP', 'BMAP_HYBRID_MAP']"></bm-map-type>
</baidu-map>
<template #footer>
<span class="dialog-footer">
<el-button @click="showEndLocationMap = false">取消</el-button>
<el-button type="primary" @click="showEndLocationMap = false">确定</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { ElMessage } from 'element-plus';
import { createDelivery, getAvailableServers, getAvailableEartags, getAvailableCollars } from '@/api/shipping.js';
import { createDelivery, updateDeviceDeliveryId } from '@/api/shipping.js';
import { memberListByType, driverList as fetchDriverList, vehicleList as fetchVehicleList } from '@/api/userManage.js';
import { iotDeviceQueryList } from '@/api/hardware.js';
import { BaiduMap, BmMapType, BmMarker } from 'vue-baidu-map-3x';
const dialogVisible = ref(false);
const formRef = ref(null);
@@ -210,12 +305,18 @@ const submitLoading = ref(false);
const serverList = ref([]);
const eartagList = ref([]);
const collarList = ref([]);
const supplierList = ref([]);
const buyerList = ref([]);
const driverOptions = ref([]);
const vehicleOptions = ref([]);
const showStartLocationMap = ref(false);
const showEndLocationMap = ref(false);
const formData = reactive({
shipper: '',
buyer: '',
plateNumber: '',
driverName: '',
shipper: null,
buyer: null,
plateNumber: null,
driverName: null,
driverPhone: '',
serverId: null,
eartagIds: [],
@@ -224,6 +325,10 @@ const formData = reactive({
estimatedArrivalTime: '',
startLocation: '',
endLocation: '',
startLat: '',
startLon: '',
endLat: '',
endLon: '',
cattleCount: 1,
estimatedWeight: null,
quarantineCertNo: '',
@@ -266,8 +371,8 @@ const validateArrivalTime = (rule, value, callback) => {
};
const rules = {
shipper: [{ required: true, message: '请输入发货方', trigger: 'blur' }],
buyer: [{ required: true, message: '请输入采购方', trigger: 'blur' }],
shipper: [{ required: true, message: '请选择发货方', trigger: 'change' }],
buyer: [{ required: true, message: '请选择采购方', trigger: 'change' }],
plateNumber: [{ required: true, validator: validatePlateNumber, trigger: 'blur' }],
driverName: [{ required: true, message: '请输入司机姓名', trigger: 'blur' }],
driverPhone: [{ required: true, validator: validatePhone, trigger: 'blur' }],
@@ -279,45 +384,173 @@ const rules = {
estimatedWeight: [{ required: true, message: '请输入预估重量', trigger: 'blur' }],
};
// 完善提交数据
const buildSubmitData = () => {
const data = { ...formData };
// 确保经纬度是字符串格式
if (data.startLat) data.startLat = String(data.startLat);
if (data.startLon) data.startLon = String(data.startLon);
if (data.endLat) data.endLat = String(data.endLat);
if (data.endLon) data.endLon = String(data.endLon);
return data;
};
// 打开弹窗
const open = () => {
dialogVisible.value = true;
loadSupplierAndBuyerList();
loadDeviceOptions();
loadDriverList();
loadVehicleList();
};
// 加载供应商和采购方列表
const loadSupplierAndBuyerList = async () => {
try {
// 加载供应商列表 (type=2)
const supplierRes = await memberListByType({ type: 2, pageNum: 1, pageSize: 9999 });
if (supplierRes.code === 200) {
supplierList.value = supplierRes.data?.rows || supplierRes.data || [];
}
// 加载采购方列表 (type=4)
const buyerRes = await memberListByType({ type: 4, pageNum: 1, pageSize: 9999 });
if (buyerRes.code === 200) {
buyerList.value = buyerRes.data?.rows || buyerRes.data || [];
}
} catch (error) {
console.error('加载供应商/采购方列表失败', error);
}
};
// 加载设备选项
const loadDeviceOptions = async () => {
try {
// 加载主机设备
const serverRes = await getAvailableServers({ pageNum: 1, pageSize: 9999 });
// 统一使用 iotDeviceQueryList 接口,通过 type 参数区分设备类型
// type: 1-主机, 2-耳标, 4-项圈
const [serverRes, eartagRes, collarRes] = await Promise.all([
iotDeviceQueryList({ pageNum: 1, pageSize: 9999, type: 1 }), // 主机
iotDeviceQueryList({ pageNum: 1, pageSize: 9999, type: 2 }), // 耳标
iotDeviceQueryList({ pageNum: 1, pageSize: 9999, type: 4 }) // 项圈
]);
if (serverRes.code === 200) {
serverList.value = serverRes.data?.rows || serverRes.data || [];
// 过滤出主机类型设备 (type === 1)
const allServers = serverRes.data?.rows || serverRes.data || [];
serverList.value = allServers.filter(item => item.type === 1 || item.type === '1');
}
// 加载耳标设备
const eartagRes = await getAvailableEartags({ pageNum: 1, pageSize: 9999 });
if (eartagRes.code === 200) {
eartagList.value = eartagRes.data?.rows || eartagRes.data || [];
// 过滤出耳标类型设备 (type === 2)
const allEartags = eartagRes.data?.rows || eartagRes.data || [];
eartagList.value = allEartags.filter(item => item.type === 2 || item.type === '2');
}
// 加载项圈设备
const collarRes = await getAvailableCollars({ pageNum: 1, pageSize: 9999 });
if (collarRes.code === 200) {
collarList.value = collarRes.data?.rows || collarRes.data || [];
// 过滤出项圈类型设备 (type === 4)
const allCollars = collarRes.data?.rows || collarRes.data || [];
collarList.value = allCollars.filter(item => item.type === 4 || item.type === '4');
}
} catch (error) {
console.error('加载设备列表失败', error);
}
};
// 加载司机列表
const loadDriverList = async () => {
try {
const res = await fetchDriverList({ pageNum: 1, pageSize: 9999 });
if (res.code === 200) {
driverOptions.value = res.data?.rows || res.data || [];
}
} catch (error) {
console.error('加载司机列表失败', error);
}
};
// 加载车辆列表
const loadVehicleList = async () => {
try {
const res = await fetchVehicleList({ pageNum: 1, pageSize: 9999 });
if (res.code === 200) {
vehicleOptions.value = res.data?.data?.rows || res.data?.rows || res.data || [];
}
} catch (error) {
console.error('加载车辆列表失败', error);
}
};
// 司机选择变化时自动填充电话
const handleDriverChange = (driverId) => {
if (driverId) {
const driver = driverOptions.value.find(item => item.id === driverId);
if (driver && driver.mobile) {
formData.driverPhone = driver.mobile;
}
} else {
formData.driverPhone = '';
}
};
// 更新选中设备的delivery_id
const updateSelectedDevicesDeliveryId = async (deliveryId) => {
try {
const devicesToUpdate = [];
// 收集所有选中的设备
if (formData.serverId) {
devicesToUpdate.push(formData.serverId);
}
if (formData.eartagIds && formData.eartagIds.length > 0) {
devicesToUpdate.push(...formData.eartagIds);
}
if (formData.collarIds && formData.collarIds.length > 0) {
devicesToUpdate.push(...formData.collarIds);
}
// 批量更新设备的delivery_id
for (const deviceId of devicesToUpdate) {
await updateDeviceDeliveryId({
deviceId: deviceId,
deliveryId: deliveryId
});
}
console.log(`成功更新 ${devicesToUpdate.length} 个设备的delivery_id`);
} catch (error) {
console.error('更新设备delivery_id失败:', error);
// 不阻止流程,只记录错误
}
};
// 提交表单
const handleSubmit = () => {
formRef.value.validate(async (valid) => {
if (valid) {
// 验证地址经纬度
if (!formData.startLat || !formData.startLon) {
ElMessage.warning('请在地图上选择起点位置');
return;
}
if (!formData.endLat || !formData.endLon) {
ElMessage.warning('请在地图上选择目的地位置');
return;
}
submitLoading.value = true;
try {
const res = await createDelivery(formData);
const submitData = buildSubmitData();
console.log('提交的数据:', submitData);
const res = await createDelivery(submitData);
if (res.code === 200) {
// 获取新创建的运送清单ID
const newDeliveryId = res.data?.id;
if (newDeliveryId) {
// 更新设备的delivery_id
await updateSelectedDevicesDeliveryId(newDeliveryId);
}
ElMessage.success('创建成功');
dialogVisible.value = false;
emit('success');
@@ -325,6 +558,7 @@ const handleSubmit = () => {
ElMessage.error(res.msg || '创建失败');
}
} catch (error) {
console.error('创建失败:', error);
ElMessage.error('创建失败,请稍后重试');
} finally {
submitLoading.value = false;
@@ -333,10 +567,64 @@ const handleSubmit = () => {
});
};
// 地图点击事件 - 起点
const handleStartLocationClick = (e) => {
formData.startLon = e.point.lng;
formData.startLat = e.point.lat;
// 反向地理编码获取地址
const geocoder = new window.BMap.Geocoder();
geocoder.getLocation(e.point, (res) => {
if (res) {
formData.startLocation = res.address;
ElMessage.success('已设置起点地址');
}
});
};
// 起点标记拖拽事件
const handleStartMarkerDrag = (e) => {
formData.startLon = e.point.lng;
formData.startLat = e.point.lat;
const geocoder = new window.BMap.Geocoder();
geocoder.getLocation(e.point, (res) => {
if (res) {
formData.startLocation = res.address;
}
});
};
// 地图点击事件 - 目的地
const handleEndLocationClick = (e) => {
formData.endLon = e.point.lng;
formData.endLat = e.point.lat;
// 反向地理编码获取地址
const geocoder = new window.BMap.Geocoder();
geocoder.getLocation(e.point, (res) => {
if (res) {
formData.endLocation = res.address;
ElMessage.success('已设置目的地地址');
}
});
};
// 目的地标记拖拽事件
const handleEndMarkerDrag = (e) => {
formData.endLon = e.point.lng;
formData.endLat = e.point.lat;
const geocoder = new window.BMap.Geocoder();
geocoder.getLocation(e.point, (res) => {
if (res) {
formData.endLocation = res.address;
}
});
};
// 关闭弹窗
const handleClose = () => {
formRef.value?.resetFields();
dialogVisible.value = false;
showStartLocationMap.value = false;
showEndLocationMap.value = false;
};
// 暴露方法给父组件
@@ -348,6 +636,11 @@ const emit = defineEmits(['success']);
</script>
<style scoped>
.map {
width: 100%;
height: 500px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;