976 lines
34 KiB
Vue
976 lines
34 KiB
Vue
|
|
<template>
|
|||
|
|
<el-dialog
|
|||
|
|
v-model="data.dialogVisible"
|
|||
|
|
:title="data.isDetail ? '进仓详情' : (data.editId ? '编辑进仓记录' : '新增进仓记录')"
|
|||
|
|
:before-close="handleClose"
|
|||
|
|
width="900px"
|
|||
|
|
:close-on-click-modal="false"
|
|||
|
|
>
|
|||
|
|
<el-form ref="formDataRef" :model="ruleForm" :rules="rules" label-width="120px" :disabled="data.isDetail">
|
|||
|
|
<el-row :gutter="20">
|
|||
|
|
<el-col :span="12">
|
|||
|
|
<el-form-item label="中转仓" prop="warehouseId">
|
|||
|
|
<el-select
|
|||
|
|
v-model="ruleForm.warehouseId"
|
|||
|
|
placeholder="请选择中转仓"
|
|||
|
|
clearable
|
|||
|
|
filterable
|
|||
|
|
style="width: 100%"
|
|||
|
|
@change="handleWarehouseChange"
|
|||
|
|
>
|
|||
|
|
<el-option
|
|||
|
|
v-for="item in warehouseList"
|
|||
|
|
:key="item.id"
|
|||
|
|
:label="item.warehouseName"
|
|||
|
|
:value="item.id"
|
|||
|
|
/>
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-col>
|
|||
|
|
<el-col :span="12">
|
|||
|
|
<el-form-item label="运送清单" prop="deliveryId">
|
|||
|
|
<el-select
|
|||
|
|
v-model="ruleForm.deliveryId"
|
|||
|
|
placeholder="请选择运送清单"
|
|||
|
|
clearable
|
|||
|
|
filterable
|
|||
|
|
style="width: 100%"
|
|||
|
|
@change="handleDeliveryChange"
|
|||
|
|
>
|
|||
|
|
<el-option
|
|||
|
|
v-for="item in deliveryList"
|
|||
|
|
:key="item.id"
|
|||
|
|
:label="`${item.deliveryNumber || '--'} - ${item.licensePlate || '--'}`"
|
|||
|
|
:value="item.id"
|
|||
|
|
/>
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-col>
|
|||
|
|
</el-row>
|
|||
|
|
|
|||
|
|
<el-row :gutter="20">
|
|||
|
|
<el-col :span="12">
|
|||
|
|
<el-form-item label="订单" prop="orderId">
|
|||
|
|
<el-select
|
|||
|
|
v-model="ruleForm.orderId"
|
|||
|
|
placeholder="请选择订单(可多选)"
|
|||
|
|
multiple
|
|||
|
|
clearable
|
|||
|
|
filterable
|
|||
|
|
style="width: 100%"
|
|||
|
|
>
|
|||
|
|
<el-option
|
|||
|
|
v-for="item in orderList"
|
|||
|
|
:key="item.id"
|
|||
|
|
:label="`订单${item.id} - 单价: ${item.firmPrice || '--'}元/斤`"
|
|||
|
|
:value="item.id"
|
|||
|
|
/>
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-col>
|
|||
|
|
<el-col :span="12">
|
|||
|
|
<el-form-item label="进仓时间" prop="inTime">
|
|||
|
|
<el-date-picker
|
|||
|
|
v-model="ruleForm.inTime"
|
|||
|
|
type="datetime"
|
|||
|
|
placeholder="请选择进仓时间"
|
|||
|
|
style="width: 100%"
|
|||
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|||
|
|
/>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-col>
|
|||
|
|
</el-row>
|
|||
|
|
|
|||
|
|
<el-row :gutter="20">
|
|||
|
|
<el-col :span="24">
|
|||
|
|
<el-form-item label="来源地" prop="sourceLocation">
|
|||
|
|
<el-input
|
|||
|
|
v-model="ruleForm.sourceLocation"
|
|||
|
|
placeholder="请输入来源地"
|
|||
|
|
maxlength="255"
|
|||
|
|
style="width: calc(100% - 100px); margin-right: 10px;"
|
|||
|
|
/>
|
|||
|
|
<el-button type="primary" @click="openSourceLocationMap">选择位置</el-button>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-col>
|
|||
|
|
</el-row>
|
|||
|
|
|
|||
|
|
<el-row :gutter="20">
|
|||
|
|
<el-col :span="12">
|
|||
|
|
<el-form-item label="来源地经度">
|
|||
|
|
<el-input v-model="ruleForm.sourceLon" placeholder="经度" disabled />
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-col>
|
|||
|
|
<el-col :span="12">
|
|||
|
|
<el-form-item label="来源地纬度">
|
|||
|
|
<el-input v-model="ruleForm.sourceLat" placeholder="纬度" disabled />
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-col>
|
|||
|
|
</el-row>
|
|||
|
|
|
|||
|
|
<el-row :gutter="20">
|
|||
|
|
<el-col :span="12">
|
|||
|
|
<el-form-item label="牛只数量(头)" prop="cattleCount">
|
|||
|
|
<el-input-number
|
|||
|
|
v-model="ruleForm.cattleCount"
|
|||
|
|
:min="1"
|
|||
|
|
:max="9999"
|
|||
|
|
placeholder="请输入牛只数量"
|
|||
|
|
style="width: 100%"
|
|||
|
|
/>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-col>
|
|||
|
|
<el-col :span="12">
|
|||
|
|
<el-form-item label="重量(公斤)" prop="weight">
|
|||
|
|
<el-input-number
|
|||
|
|
v-model="ruleForm.weight"
|
|||
|
|
:min="0"
|
|||
|
|
:precision="2"
|
|||
|
|
placeholder="请输入重量"
|
|||
|
|
style="width: 100%"
|
|||
|
|
>
|
|||
|
|
<template #append>kg</template>
|
|||
|
|
</el-input-number>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-col>
|
|||
|
|
</el-row>
|
|||
|
|
|
|||
|
|
<el-row :gutter="20">
|
|||
|
|
<el-col :span="12">
|
|||
|
|
<el-form-item label="状态" prop="status">
|
|||
|
|
<el-select v-model="ruleForm.status" placeholder="请选择状态" style="width: 100%">
|
|||
|
|
<el-option label="待进仓" :value="1" />
|
|||
|
|
<el-option label="已进仓" :value="2" />
|
|||
|
|
<el-option label="已出仓" :value="3" />
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-col>
|
|||
|
|
</el-row>
|
|||
|
|
|
|||
|
|
<el-row :gutter="20">
|
|||
|
|
<el-col :span="24">
|
|||
|
|
<el-form-item label="照片">
|
|||
|
|
<div style="display: flex; flex-direction: column; gap: 10px;">
|
|||
|
|
<!-- 拖拽上传区域 -->
|
|||
|
|
<el-upload
|
|||
|
|
drag
|
|||
|
|
action="#"
|
|||
|
|
:auto-upload="false"
|
|||
|
|
:before-upload="beforePhotoUpload"
|
|||
|
|
:limit="9"
|
|||
|
|
accept="image/*"
|
|||
|
|
:on-change="handlePhotoChange"
|
|||
|
|
:show-file-list="false"
|
|||
|
|
style="width: 100%"
|
|||
|
|
>
|
|||
|
|
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
|
|||
|
|
<div class="el-upload__text">将图片文件拖到此处,或<em>点击上传</em></div>
|
|||
|
|
<template #tip>
|
|||
|
|
<div class="el-upload__tip">支持 jpg/png/gif 格式,单个文件不超过 10MB,最多上传 9 张</div>
|
|||
|
|
</template>
|
|||
|
|
</el-upload>
|
|||
|
|
<!-- 图片预览列表 -->
|
|||
|
|
<div v-if="photoFileList.length > 0" class="photo-preview-list">
|
|||
|
|
<div
|
|||
|
|
v-for="(file, index) in photoFileList"
|
|||
|
|
:key="index"
|
|||
|
|
class="photo-preview-item"
|
|||
|
|
>
|
|||
|
|
<el-image
|
|||
|
|
:src="file.url || file.response?.data?.url || ''"
|
|||
|
|
fit="cover"
|
|||
|
|
class="photo-preview-image"
|
|||
|
|
:preview-src-list="photoFileList.map(f => f.url || f.response?.data?.url || '').filter(Boolean)"
|
|||
|
|
:initial-index="index"
|
|||
|
|
/>
|
|||
|
|
<el-button
|
|||
|
|
type="danger"
|
|||
|
|
:icon="Delete"
|
|||
|
|
circle
|
|||
|
|
size="small"
|
|||
|
|
class="photo-delete-btn"
|
|||
|
|
@click="handlePhotoRemove(file)"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-col>
|
|||
|
|
</el-row>
|
|||
|
|
|
|||
|
|
<el-row :gutter="20">
|
|||
|
|
<el-col :span="24">
|
|||
|
|
<el-form-item label="视频">
|
|||
|
|
<el-upload
|
|||
|
|
drag
|
|||
|
|
action="#"
|
|||
|
|
:auto-upload="false"
|
|||
|
|
:on-change="handleVideoChange"
|
|||
|
|
:on-remove="handleVideoRemove"
|
|||
|
|
:before-upload="beforeVideoUpload"
|
|||
|
|
:limit="3"
|
|||
|
|
accept="video/*"
|
|||
|
|
:show-file-list="false"
|
|||
|
|
style="width: 100%"
|
|||
|
|
>
|
|||
|
|
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
|
|||
|
|
<div class="el-upload__text">将视频文件拖到此处,或<em>点击上传</em></div>
|
|||
|
|
<template #tip>
|
|||
|
|
<div class="el-upload__tip">支持 mp4/avi/rmvb/mkv 格式,单个文件不超过 100MB,最多上传 3 个</div>
|
|||
|
|
</template>
|
|||
|
|
</el-upload>
|
|||
|
|
<!-- 视频预览列表 -->
|
|||
|
|
<div v-if="videoFileList.length > 0" class="video-preview-list">
|
|||
|
|
<div
|
|||
|
|
v-for="(file, index) in videoFileList"
|
|||
|
|
:key="index"
|
|||
|
|
class="video-preview-item"
|
|||
|
|
>
|
|||
|
|
<video
|
|||
|
|
:src="file.url || file.response?.data?.url || ''"
|
|||
|
|
controls
|
|||
|
|
class="video-preview-player"
|
|||
|
|
></video>
|
|||
|
|
<div class="video-name">{{ file.name || '视频' }}</div>
|
|||
|
|
<el-button
|
|||
|
|
type="danger"
|
|||
|
|
:icon="Delete"
|
|||
|
|
circle
|
|||
|
|
size="small"
|
|||
|
|
class="video-delete-btn"
|
|||
|
|
@click="handleVideoRemove(file)"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-col>
|
|||
|
|
</el-row>
|
|||
|
|
|
|||
|
|
<el-row :gutter="20">
|
|||
|
|
<el-col :span="24">
|
|||
|
|
<el-form-item label="备注">
|
|||
|
|
<el-input
|
|||
|
|
v-model="ruleForm.remark"
|
|||
|
|
type="textarea"
|
|||
|
|
:rows="3"
|
|||
|
|
placeholder="请输入备注"
|
|||
|
|
maxlength="500"
|
|||
|
|
show-word-limit
|
|||
|
|
/>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-col>
|
|||
|
|
</el-row>
|
|||
|
|
</el-form>
|
|||
|
|
|
|||
|
|
<template #footer>
|
|||
|
|
<span class="dialog-footer">
|
|||
|
|
<el-button v-if="!data.isDetail" :loading="data.saveLoading" type="primary" @click="onClickSave">
|
|||
|
|
保存
|
|||
|
|
</el-button>
|
|||
|
|
<el-button @click="handleClose">{{ data.isDetail ? '关闭' : '取消' }}</el-button>
|
|||
|
|
</span>
|
|||
|
|
</template>
|
|||
|
|
</el-dialog>
|
|||
|
|
|
|||
|
|
<!-- 来源地地址选择地图 -->
|
|||
|
|
<el-dialog v-model="showSourceLocationMap" title="选择来源地地址" width="900px">
|
|||
|
|
<baidu-map
|
|||
|
|
class="map"
|
|||
|
|
:center="ruleForm.sourceLon && ruleForm.sourceLat ? {lng: parseFloat(ruleForm.sourceLon), lat: parseFloat(ruleForm.sourceLat)} : {lng: 116.404, lat: 39.915}"
|
|||
|
|
:zoom="15"
|
|||
|
|
:scroll-wheel-zoom="true"
|
|||
|
|
@click="handleSourceLocationClick"
|
|||
|
|
style="height: 500px"
|
|||
|
|
>
|
|||
|
|
<bm-marker
|
|||
|
|
v-if="ruleForm.sourceLon && ruleForm.sourceLat"
|
|||
|
|
:position="{lng: parseFloat(ruleForm.sourceLon), lat: parseFloat(ruleForm.sourceLat)}"
|
|||
|
|
:dragging="true"
|
|||
|
|
@dragging="handleSourceMarkerDrag"
|
|||
|
|
/>
|
|||
|
|
<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="showSourceLocationMap = false">取消</el-button>
|
|||
|
|
<el-button type="primary" @click="showSourceLocationMap = false">确定</el-button>
|
|||
|
|
</span>
|
|||
|
|
</template>
|
|||
|
|
</el-dialog>
|
|||
|
|
|
|||
|
|
<!-- 图片预览 -->
|
|||
|
|
<el-dialog v-model="showImageViewer" title="图片预览" width="800px">
|
|||
|
|
<el-image :src="imageViewerUrl" style="width: 100%" fit="contain" />
|
|||
|
|
</el-dialog>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup>
|
|||
|
|
import { ref, reactive, watch } from 'vue';
|
|||
|
|
import { ElMessage } from 'element-plus';
|
|||
|
|
import { Plus, UploadFilled, Delete } from '@element-plus/icons-vue';
|
|||
|
|
import { BaiduMap, BmMapType, BmMarker } from 'vue-baidu-map-3x';
|
|||
|
|
import { warehouseInAdd, warehouseInEdit, warehouseInDetail } from '@/api/warehouseIn.js';
|
|||
|
|
import { warehouseAll } from '@/api/warehouse.js';
|
|||
|
|
import { orderPageQuery, shippingList } from '@/api/shipping.js';
|
|||
|
|
|
|||
|
|
const emits = defineEmits(['success']);
|
|||
|
|
const formDataRef = ref(null);
|
|||
|
|
const showSourceLocationMap = ref(false);
|
|||
|
|
const showImageViewer = ref(false);
|
|||
|
|
const imageViewerUrl = ref('');
|
|||
|
|
const photoFileList = ref([]);
|
|||
|
|
const videoFileList = ref([]);
|
|||
|
|
|
|||
|
|
const warehouseList = ref([]);
|
|||
|
|
const orderList = ref([]);
|
|||
|
|
const deliveryList = ref([]);
|
|||
|
|
|
|||
|
|
const data = reactive({
|
|||
|
|
dialogVisible: false,
|
|||
|
|
saveLoading: false,
|
|||
|
|
editId: null,
|
|||
|
|
isDetail: false,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const ruleForm = reactive({
|
|||
|
|
id: null,
|
|||
|
|
warehouseId: null,
|
|||
|
|
orderId: [],
|
|||
|
|
deliveryId: null,
|
|||
|
|
sourceLocation: '',
|
|||
|
|
sourceLon: '',
|
|||
|
|
sourceLat: '',
|
|||
|
|
cattleCount: null,
|
|||
|
|
weight: null,
|
|||
|
|
inTime: '',
|
|||
|
|
photos: '',
|
|||
|
|
videos: '',
|
|||
|
|
remark: '',
|
|||
|
|
status: 1,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const rules = reactive({
|
|||
|
|
warehouseId: [
|
|||
|
|
{ required: true, message: '请选择中转仓', trigger: 'change' },
|
|||
|
|
],
|
|||
|
|
deliveryId: [
|
|||
|
|
{ required: true, message: '请选择运送清单', trigger: 'change' },
|
|||
|
|
],
|
|||
|
|
cattleCount: [
|
|||
|
|
{ required: true, message: '请输入牛只数量', trigger: 'blur' },
|
|||
|
|
{ type: 'number', min: 1, message: '牛只数量必须大于0', trigger: 'blur' },
|
|||
|
|
],
|
|||
|
|
inTime: [
|
|||
|
|
{ required: true, message: '请选择进仓时间', trigger: 'change' },
|
|||
|
|
],
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 加载中转仓列表
|
|||
|
|
const loadWarehouseList = async () => {
|
|||
|
|
try {
|
|||
|
|
const res = await warehouseAll();
|
|||
|
|
if (res.code === 200) {
|
|||
|
|
warehouseList.value = res.data || [];
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('加载中转仓列表失败', error);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 加载订单列表
|
|||
|
|
const loadOrderList = async () => {
|
|||
|
|
try {
|
|||
|
|
const res = await orderPageQuery({ pageNum: 1, pageSize: 1000 });
|
|||
|
|
if (res.code === 200) {
|
|||
|
|
const responseData = res.data || res;
|
|||
|
|
if (responseData && typeof responseData === 'object' && 'rows' in responseData) {
|
|||
|
|
orderList.value = responseData.rows || [];
|
|||
|
|
} else if (responseData && responseData.data && 'rows' in responseData.data) {
|
|||
|
|
orderList.value = responseData.data.rows || [];
|
|||
|
|
} else {
|
|||
|
|
orderList.value = [];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('加载订单列表失败', error);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 加载运送清单列表
|
|||
|
|
const loadDeliveryList = async () => {
|
|||
|
|
try {
|
|||
|
|
const res = await shippingList({ pageNum: 1, pageSize: 1000 });
|
|||
|
|
if (res.code === 200) {
|
|||
|
|
const responseData = res.data || res;
|
|||
|
|
if (responseData && typeof responseData === 'object' && 'rows' in responseData) {
|
|||
|
|
deliveryList.value = responseData.rows || [];
|
|||
|
|
} else if (responseData && responseData.data && 'rows' in responseData.data) {
|
|||
|
|
deliveryList.value = responseData.data.rows || [];
|
|||
|
|
} else {
|
|||
|
|
deliveryList.value = [];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('加载运送清单列表失败', error);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleClose = () => {
|
|||
|
|
if (formDataRef.value) {
|
|||
|
|
formDataRef.value.resetFields();
|
|||
|
|
}
|
|||
|
|
Object.assign(ruleForm, {
|
|||
|
|
id: null,
|
|||
|
|
warehouseId: null,
|
|||
|
|
orderId: [],
|
|||
|
|
deliveryId: null,
|
|||
|
|
sourceLocation: '',
|
|||
|
|
sourceLon: '',
|
|||
|
|
sourceLat: '',
|
|||
|
|
cattleCount: null,
|
|||
|
|
weight: null,
|
|||
|
|
inTime: '',
|
|||
|
|
photos: '',
|
|||
|
|
videos: '',
|
|||
|
|
remark: '',
|
|||
|
|
status: 1,
|
|||
|
|
});
|
|||
|
|
photoFileList.value = [];
|
|||
|
|
videoFileList.value = [];
|
|||
|
|
data.editId = null;
|
|||
|
|
data.isDetail = false;
|
|||
|
|
data.dialogVisible = false;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const onClickSave = () => {
|
|||
|
|
if (!formDataRef.value) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
formDataRef.value.validate((valid) => {
|
|||
|
|
if (!valid) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
data.saveLoading = true;
|
|||
|
|
|
|||
|
|
// 处理订单ID(多个订单用逗号分隔)
|
|||
|
|
const orderIdStr = ruleForm.orderId && ruleForm.orderId.length > 0
|
|||
|
|
? ruleForm.orderId.join(',')
|
|||
|
|
: null;
|
|||
|
|
|
|||
|
|
// 处理照片和视频URL(多个用逗号分隔)
|
|||
|
|
const photosStr = photoFileList.value.map(file => file.url || file.response?.data?.url || '').filter(url => url).join(',');
|
|||
|
|
const videosStr = videoFileList.value.map(file => file.url || file.response?.data?.url || '').filter(url => url).join(',');
|
|||
|
|
|
|||
|
|
const params = {
|
|||
|
|
warehouseId: ruleForm.warehouseId,
|
|||
|
|
orderId: orderIdStr,
|
|||
|
|
deliveryId: ruleForm.deliveryId,
|
|||
|
|
sourceLocation: ruleForm.sourceLocation,
|
|||
|
|
sourceLon: ruleForm.sourceLon,
|
|||
|
|
sourceLat: ruleForm.sourceLat,
|
|||
|
|
cattleCount: ruleForm.cattleCount,
|
|||
|
|
weight: ruleForm.weight,
|
|||
|
|
inTime: ruleForm.inTime,
|
|||
|
|
photos: photosStr,
|
|||
|
|
videos: videosStr,
|
|||
|
|
remark: ruleForm.remark,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (data.editId) {
|
|||
|
|
// 编辑
|
|||
|
|
params.id = data.editId;
|
|||
|
|
params.status = ruleForm.status;
|
|||
|
|
warehouseInEdit(params)
|
|||
|
|
.then((res) => {
|
|||
|
|
if (res.code === 200) {
|
|||
|
|
ElMessage.success('编辑成功');
|
|||
|
|
handleClose();
|
|||
|
|
emits('success');
|
|||
|
|
} else {
|
|||
|
|
ElMessage.error(res.msg || '编辑失败');
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
.catch((error) => {
|
|||
|
|
ElMessage.error('编辑失败:' + (error.message || '未知错误'));
|
|||
|
|
})
|
|||
|
|
.finally(() => {
|
|||
|
|
data.saveLoading = false;
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
// 新增
|
|||
|
|
warehouseInAdd(params)
|
|||
|
|
.then((res) => {
|
|||
|
|
if (res.code === 200) {
|
|||
|
|
ElMessage.success('新增成功');
|
|||
|
|
handleClose();
|
|||
|
|
emits('success');
|
|||
|
|
} else {
|
|||
|
|
ElMessage.error(res.msg || '新增失败');
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
.catch((error) => {
|
|||
|
|
ElMessage.error('新增失败:' + (error.message || '未知错误'));
|
|||
|
|
})
|
|||
|
|
.finally(() => {
|
|||
|
|
data.saveLoading = false;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const onShowDialog = (row, isDetail = false) => {
|
|||
|
|
data.isDetail = isDetail || false;
|
|||
|
|
data.editId = null;
|
|||
|
|
|
|||
|
|
if (row) {
|
|||
|
|
data.editId = row.id;
|
|||
|
|
// 如果是详情模式,先获取详情数据
|
|||
|
|
if (isDetail) {
|
|||
|
|
warehouseInDetail(row.id)
|
|||
|
|
.then((res) => {
|
|||
|
|
if (res.code === 200 && res.data) {
|
|||
|
|
const detailData = res.data;
|
|||
|
|
Object.assign(ruleForm, {
|
|||
|
|
id: detailData.id,
|
|||
|
|
warehouseId: detailData.warehouseId,
|
|||
|
|
orderId: detailData.orderId ? detailData.orderId.split(',').map(id => parseInt(id)) : [],
|
|||
|
|
deliveryId: detailData.deliveryId,
|
|||
|
|
sourceLocation: detailData.sourceLocation || '',
|
|||
|
|
sourceLon: detailData.sourceLon || '',
|
|||
|
|
sourceLat: detailData.sourceLat || '',
|
|||
|
|
cattleCount: detailData.cattleCount,
|
|||
|
|
weight: detailData.weight,
|
|||
|
|
inTime: detailData.inTime || '',
|
|||
|
|
photos: detailData.photos || '',
|
|||
|
|
videos: detailData.videos || '',
|
|||
|
|
remark: detailData.remark || '',
|
|||
|
|
status: detailData.status !== undefined ? detailData.status : 1,
|
|||
|
|
});
|
|||
|
|
// 处理照片和视频文件列表
|
|||
|
|
if (detailData.photos) {
|
|||
|
|
photoFileList.value = detailData.photos.split(',').map(url => ({ url, name: 'photo' }));
|
|||
|
|
}
|
|||
|
|
if (detailData.videos) {
|
|||
|
|
videoFileList.value = detailData.videos.split(',').map(url => ({ url, name: 'video' }));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
.catch((error) => {
|
|||
|
|
ElMessage.error('获取详情失败:' + (error.message || '未知错误'));
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
// 编辑模式,直接使用传入的数据
|
|||
|
|
Object.assign(ruleForm, {
|
|||
|
|
id: row.id,
|
|||
|
|
warehouseId: row.warehouseId,
|
|||
|
|
orderId: row.orderId ? row.orderId.split(',').map(id => parseInt(id)) : [],
|
|||
|
|
deliveryId: row.deliveryId,
|
|||
|
|
sourceLocation: row.sourceLocation || '',
|
|||
|
|
sourceLon: row.sourceLon || '',
|
|||
|
|
sourceLat: row.sourceLat || '',
|
|||
|
|
cattleCount: row.cattleCount,
|
|||
|
|
weight: row.weight,
|
|||
|
|
inTime: row.inTime || '',
|
|||
|
|
photos: row.photos || '',
|
|||
|
|
videos: row.videos || '',
|
|||
|
|
remark: row.remark || '',
|
|||
|
|
status: row.status !== undefined ? row.status : 1,
|
|||
|
|
});
|
|||
|
|
// 处理照片和视频文件列表
|
|||
|
|
if (row.photos) {
|
|||
|
|
photoFileList.value = row.photos.split(',').map(url => ({ url, name: 'photo' }));
|
|||
|
|
}
|
|||
|
|
if (row.videos) {
|
|||
|
|
videoFileList.value = row.videos.split(',').map(url => ({ url, name: 'video' }));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 新增模式,重置表单
|
|||
|
|
Object.assign(ruleForm, {
|
|||
|
|
id: null,
|
|||
|
|
warehouseId: null,
|
|||
|
|
orderId: [],
|
|||
|
|
deliveryId: null,
|
|||
|
|
sourceLocation: '',
|
|||
|
|
sourceLon: '',
|
|||
|
|
sourceLat: '',
|
|||
|
|
cattleCount: null,
|
|||
|
|
weight: null,
|
|||
|
|
inTime: '',
|
|||
|
|
photos: '',
|
|||
|
|
videos: '',
|
|||
|
|
remark: '',
|
|||
|
|
status: 1,
|
|||
|
|
});
|
|||
|
|
photoFileList.value = [];
|
|||
|
|
videoFileList.value = [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
data.dialogVisible = true;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 中转仓选择变化
|
|||
|
|
const handleWarehouseChange = (warehouseId) => {
|
|||
|
|
// 可以在这里添加逻辑
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 运送清单选择变化
|
|||
|
|
const handleDeliveryChange = (deliveryId) => {
|
|||
|
|
if (!deliveryId) {
|
|||
|
|
// 清空相关字段
|
|||
|
|
ruleForm.sourceLocation = '';
|
|||
|
|
ruleForm.sourceLon = '';
|
|||
|
|
ruleForm.sourceLat = '';
|
|||
|
|
ruleForm.cattleCount = null;
|
|||
|
|
ruleForm.weight = null;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 从 deliveryList 中找到对应的运送清单
|
|||
|
|
const selectedDelivery = deliveryList.value.find(item => item.id === deliveryId);
|
|||
|
|
if (selectedDelivery) {
|
|||
|
|
// 自动填充来源地信息
|
|||
|
|
if (selectedDelivery.startLocation) {
|
|||
|
|
ruleForm.sourceLocation = selectedDelivery.startLocation;
|
|||
|
|
}
|
|||
|
|
if (selectedDelivery.startLon) {
|
|||
|
|
ruleForm.sourceLon = selectedDelivery.startLon;
|
|||
|
|
}
|
|||
|
|
if (selectedDelivery.startLat) {
|
|||
|
|
ruleForm.sourceLat = selectedDelivery.startLat;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 自动填充牛只数量
|
|||
|
|
if (selectedDelivery.ratedQuantity) {
|
|||
|
|
ruleForm.cattleCount = selectedDelivery.ratedQuantity;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 自动计算重量:entruckWeight - emptyWeight
|
|||
|
|
if (selectedDelivery.entruckWeight && selectedDelivery.emptyWeight) {
|
|||
|
|
const entruckWeight = parseFloat(selectedDelivery.entruckWeight) || 0;
|
|||
|
|
const emptyWeight = parseFloat(selectedDelivery.emptyWeight) || 0;
|
|||
|
|
const calculatedWeight = entruckWeight - emptyWeight;
|
|||
|
|
if (calculatedWeight > 0) {
|
|||
|
|
ruleForm.weight = parseFloat(calculatedWeight.toFixed(2));
|
|||
|
|
}
|
|||
|
|
} else if (selectedDelivery.entruckWeight) {
|
|||
|
|
// 如果只有装车重量,也可以填充
|
|||
|
|
ruleForm.weight = parseFloat(selectedDelivery.entruckWeight) || null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 打开来源地地图选择地址
|
|||
|
|
const openSourceLocationMap = () => {
|
|||
|
|
if (ruleForm.sourceLocation && ruleForm.sourceLocation.trim()) {
|
|||
|
|
showSourceLocationMap.value = true;
|
|||
|
|
setTimeout(() => {
|
|||
|
|
if (window.BMap && window.BMap.Geocoder) {
|
|||
|
|
const geocoder = new window.BMap.Geocoder();
|
|||
|
|
geocoder.getPoint(ruleForm.sourceLocation, (point) => {
|
|||
|
|
if (point) {
|
|||
|
|
ruleForm.sourceLon = point.lng;
|
|||
|
|
ruleForm.sourceLat = point.lat;
|
|||
|
|
ElMessage.success('已定位到该地址');
|
|||
|
|
} else {
|
|||
|
|
ElMessage.warning('未找到该地址,请在地图上手动选择');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}, 500);
|
|||
|
|
} else {
|
|||
|
|
showSourceLocationMap.value = true;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 地图点击事件
|
|||
|
|
const handleSourceLocationClick = (e) => {
|
|||
|
|
ruleForm.sourceLon = e.point.lng;
|
|||
|
|
ruleForm.sourceLat = e.point.lat;
|
|||
|
|
if (window.BMap && window.BMap.Geocoder) {
|
|||
|
|
const geocoder = new window.BMap.Geocoder();
|
|||
|
|
geocoder.getLocation(e.point, (res) => {
|
|||
|
|
if (res) {
|
|||
|
|
ruleForm.sourceLocation = res.address;
|
|||
|
|
ElMessage.success('已设置来源地地址');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 标记拖拽事件
|
|||
|
|
const handleSourceMarkerDrag = (e) => {
|
|||
|
|
ruleForm.sourceLon = e.point.lng;
|
|||
|
|
ruleForm.sourceLat = e.point.lat;
|
|||
|
|
if (window.BMap && window.BMap.Geocoder) {
|
|||
|
|
const geocoder = new window.BMap.Geocoder();
|
|||
|
|
geocoder.getLocation(e.point, (res) => {
|
|||
|
|
if (res) {
|
|||
|
|
ruleForm.sourceLocation = res.address;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 图片预览(保留用于兼容)
|
|||
|
|
const handlePictureCardPreview = (file) => {
|
|||
|
|
imageViewerUrl.value = file.url || file.response?.data?.url || '';
|
|||
|
|
showImageViewer.value = true;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 照片文件变化处理(拖拽上传时触发)
|
|||
|
|
const handlePhotoChange = (file, fileList) => {
|
|||
|
|
// 验证文件
|
|||
|
|
if (!beforePhotoUpload(file)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 手动上传文件
|
|||
|
|
uploadPhotoFile(file);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 手动上传照片文件
|
|||
|
|
const uploadPhotoFile = async (file) => {
|
|||
|
|
const formData = new FormData();
|
|||
|
|
formData.append('file', file.raw || file);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 获取 token
|
|||
|
|
let token = '';
|
|||
|
|
const userStore = localStorage.getItem('userStore');
|
|||
|
|
if (userStore) {
|
|||
|
|
const us = JSON.parse(userStore);
|
|||
|
|
token = us.token || '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const response = await fetch('/api/common/upload', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {
|
|||
|
|
'Authorization': token,
|
|||
|
|
},
|
|||
|
|
body: formData,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const result = await response.json();
|
|||
|
|
if (result.code === 200) {
|
|||
|
|
const photoUrl = result.data?.url || result.data;
|
|||
|
|
photoFileList.value.push({
|
|||
|
|
url: photoUrl,
|
|||
|
|
name: file.name,
|
|||
|
|
uid: file.uid || Date.now(),
|
|||
|
|
});
|
|||
|
|
ElMessage.success('照片上传成功');
|
|||
|
|
} else {
|
|||
|
|
ElMessage.error(result.msg || '照片上传失败');
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('照片上传失败:', error);
|
|||
|
|
ElMessage.error('照片上传失败');
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 删除照片
|
|||
|
|
const handlePhotoRemove = (file) => {
|
|||
|
|
const index = photoFileList.value.findIndex(item => item.uid === file.uid || item.url === file.url);
|
|||
|
|
if (index > -1) {
|
|||
|
|
photoFileList.value.splice(index, 1);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 视频文件变化处理(拖拽上传时触发)
|
|||
|
|
const handleVideoChange = (file, fileList) => {
|
|||
|
|
// 验证文件
|
|||
|
|
if (!beforeVideoUpload(file)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 手动上传文件
|
|||
|
|
uploadVideoFile(file);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 手动上传视频文件
|
|||
|
|
const uploadVideoFile = async (file) => {
|
|||
|
|
const formData = new FormData();
|
|||
|
|
formData.append('file', file.raw || file);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 获取 token
|
|||
|
|
let token = '';
|
|||
|
|
const userStore = localStorage.getItem('userStore');
|
|||
|
|
if (userStore) {
|
|||
|
|
const us = JSON.parse(userStore);
|
|||
|
|
token = us.token || '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const response = await fetch('/api/common/upload', {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {
|
|||
|
|
'Authorization': token,
|
|||
|
|
},
|
|||
|
|
body: formData,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const result = await response.json();
|
|||
|
|
if (result.code === 200) {
|
|||
|
|
const videoUrl = result.data?.url || result.data;
|
|||
|
|
videoFileList.value.push({
|
|||
|
|
url: videoUrl,
|
|||
|
|
name: file.name,
|
|||
|
|
uid: file.uid || Date.now(),
|
|||
|
|
});
|
|||
|
|
ElMessage.success('视频上传成功');
|
|||
|
|
} else {
|
|||
|
|
ElMessage.error(result.msg || '视频上传失败');
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('视频上传失败:', error);
|
|||
|
|
ElMessage.error('视频上传失败');
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 删除视频
|
|||
|
|
const handleVideoRemove = (file) => {
|
|||
|
|
const index = videoFileList.value.findIndex(item => item.uid === file.uid || item.url === file.url);
|
|||
|
|
if (index > -1) {
|
|||
|
|
videoFileList.value.splice(index, 1);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 上传前验证照片
|
|||
|
|
const beforePhotoUpload = (file) => {
|
|||
|
|
const isImage = file.type.startsWith('image/');
|
|||
|
|
const isLt10M = file.size / 1024 / 1024 < 10;
|
|||
|
|
|
|||
|
|
if (!isImage) {
|
|||
|
|
ElMessage.error('只能上传图片文件!');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
if (!isLt10M) {
|
|||
|
|
ElMessage.error('图片大小不能超过 10MB!');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查数量限制
|
|||
|
|
if (photoFileList.value.length >= 9) {
|
|||
|
|
ElMessage.error('最多只能上传 9 张照片!');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true; // 允许继续处理
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 上传前验证视频
|
|||
|
|
const beforeVideoUpload = (file) => {
|
|||
|
|
const isVideo = file.type.startsWith('video/');
|
|||
|
|
const isLt100M = file.size / 1024 / 1024 < 100;
|
|||
|
|
|
|||
|
|
if (!isVideo) {
|
|||
|
|
ElMessage.error('只能上传视频文件!');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
if (!isLt100M) {
|
|||
|
|
ElMessage.error('视频大小不能超过 100MB!');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查数量限制
|
|||
|
|
if (videoFileList.value.length >= 3) {
|
|||
|
|
ElMessage.error('最多只能上传 3 个视频!');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true; // 允许继续处理
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 监听对话框打开,加载数据
|
|||
|
|
watch(() => data.dialogVisible, (newVal) => {
|
|||
|
|
if (newVal) {
|
|||
|
|
loadWarehouseList();
|
|||
|
|
loadOrderList();
|
|||
|
|
loadDeliveryList();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 暴露方法给父组件调用
|
|||
|
|
defineExpose({
|
|||
|
|
onShowDialog,
|
|||
|
|
});
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped lang="scss">
|
|||
|
|
.map {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 500px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 照片预览列表样式 */
|
|||
|
|
.photo-preview-list {
|
|||
|
|
display: flex;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
gap: 10px;
|
|||
|
|
margin-top: 10px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.photo-preview-item {
|
|||
|
|
position: relative;
|
|||
|
|
width: 120px;
|
|||
|
|
height: 120px;
|
|||
|
|
border: 1px solid #dcdfe6;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
overflow: hidden;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.photo-preview-image {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.photo-delete-btn {
|
|||
|
|
position: absolute;
|
|||
|
|
top: 5px;
|
|||
|
|
right: 5px;
|
|||
|
|
z-index: 10;
|
|||
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 视频预览列表样式 */
|
|||
|
|
.video-preview-list {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
gap: 15px;
|
|||
|
|
margin-top: 15px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.video-preview-item {
|
|||
|
|
position: relative;
|
|||
|
|
border: 1px solid #dcdfe6;
|
|||
|
|
border-radius: 6px;
|
|||
|
|
padding: 10px;
|
|||
|
|
background-color: #f5f7fa;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.video-preview-player {
|
|||
|
|
width: 100%;
|
|||
|
|
max-height: 300px;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.video-name {
|
|||
|
|
margin-top: 8px;
|
|||
|
|
font-size: 12px;
|
|||
|
|
color: #606266;
|
|||
|
|
text-align: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.video-delete-btn {
|
|||
|
|
position: absolute;
|
|||
|
|
top: 15px;
|
|||
|
|
right: 15px;
|
|||
|
|
z-index: 10;
|
|||
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
|