<template>
|
<el-dialog v-model="isShow"
|
title="到货记录"
|
width="70%"
|
@close="closeModal">
|
<div v-if="ledgerLabel"
|
class="ledger-tip">
|
销售台账:{{ ledgerLabel }}
|
</div>
|
<div class="actions">
|
<el-button type="primary"
|
@click="openForm()">新增到货</el-button>
|
</div>
|
<el-table :data="tableData"
|
border
|
v-loading="tableLoading"
|
style="width: 100%">
|
<el-table-column align="center"
|
label="序号"
|
type="index"
|
width="60" />
|
<el-table-column label="出库单号"
|
prop="outboundBatches"
|
min-width="150"
|
show-overflow-tooltip />
|
<el-table-column label="车牌号"
|
min-width="110"
|
show-overflow-tooltip>
|
<!-- 出库单没登记车辆时到货记录车辆为空,不能渲染成空白格 -->
|
<template #default="scope">
|
{{ scope.row.truckPlateNo || "--" }}
|
</template>
|
</el-table-column>
|
<el-table-column label="到货数量(吨)"
|
prop="arrivalQuantity"
|
min-width="110"
|
align="center" />
|
<el-table-column label="到货日期"
|
prop="arrivalDate"
|
min-width="120"
|
show-overflow-tooltip />
|
<el-table-column label="附件"
|
align="center"
|
min-width="120">
|
<template #default="scope">
|
<el-image v-if="toPhotoArray(scope.row.attachments).length"
|
:src="toPhotoArray(scope.row.attachments)[0]"
|
:preview-src-list="toPhotoArray(scope.row.attachments)"
|
preview-teleported
|
fit="cover"
|
style="width: 40px; height: 40px; border-radius: 4px" />
|
<span v-else>--</span>
|
</template>
|
</el-table-column>
|
<el-table-column label="备注"
|
prop="remark"
|
min-width="140"
|
show-overflow-tooltip />
|
<el-table-column fixed="right"
|
label="操作"
|
width="140"
|
align="center">
|
<template #default="scope">
|
<el-button link
|
type="primary"
|
@click="openForm(scope.row)">编辑
|
</el-button>
|
<el-button link
|
type="danger"
|
@click="handleDelete(scope.row)">删除
|
</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
<pagination v-show="total > 0"
|
:total="total"
|
layout="total, sizes, prev, pager, next, jumper"
|
:page="page.current"
|
:limit="page.size"
|
@pagination="paginationChange" />
|
|
<!-- 新增 / 编辑到货 -->
|
<el-dialog v-model="formVisible"
|
:title="form.id ? '编辑到货记录' : '新增到货记录'"
|
width="55%"
|
append-to-body
|
@close="closeForm">
|
<el-form ref="formRef"
|
:model="form"
|
label-width="140px"
|
label-position="top">
|
<el-row :gutter="30">
|
<el-col :span="12">
|
<el-form-item label="出库单号"
|
prop="stockOutRecordId"
|
:rules="[
|
{
|
required: true,
|
message: '请选择出库单',
|
trigger: 'change',
|
}
|
]">
|
<!-- 出库单 + 台账是这条记录的身份,编辑时不允许改 -->
|
<el-select v-model="form.stockOutRecordId"
|
placeholder="请选择出库单"
|
clearable
|
filterable
|
:disabled="!!form.id"
|
style="width: 100%"
|
@change="handleOutboundChange">
|
<el-option v-for="item in outboundOptions"
|
:key="item.stockOutRecordId"
|
:label="outboundLabel(item)"
|
:value="item.stockOutRecordId" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<!-- 车辆不强制:出库单没登记车辆(代储/客存出库)时留空即可 -->
|
<el-form-item label="车辆"
|
prop="vehicleId">
|
<el-select v-model="form.vehicleId"
|
:placeholder="vehiclePlaceholder"
|
clearable
|
filterable
|
:disabled="!form.stockOutRecordId || !vehicleOptions.length"
|
style="width: 100%"
|
@change="handleVehicleChange">
|
<el-option v-for="item in vehicleOptions"
|
:key="item.vehicleId"
|
:label="item.truckPlateNo"
|
:value="item.vehicleId" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-col :span="12">
|
<el-form-item label="到货数量(吨)"
|
prop="arrivalQuantity"
|
:rules="[
|
{
|
validator: validateArrivalQuantity,
|
trigger: 'blur',
|
}
|
]">
|
<el-input-number v-model="form.arrivalQuantity"
|
:min="0"
|
:max="arrivalMax"
|
:precision="2"
|
:controls="false"
|
placeholder="请输入数字,最多保留两位小数"
|
style="width: 100%" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="到货日期"
|
prop="arrivalDate">
|
<el-date-picker v-model="form.arrivalDate"
|
type="date"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
placeholder="请选择"
|
style="width: 100%" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-col :span="24">
|
<el-form-item label="附件">
|
<!-- 到货附件要永久可看,必须走 /common/public/upload(previewURL) -->
|
<ImageUpload v-model:file-list="form.attachmentList"
|
:permanent="true"
|
:limit="9" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-col :span="24">
|
<el-form-item label="备注">
|
<el-input v-model="form.remark"
|
type="textarea"
|
:rows="3"
|
maxlength="500"
|
show-word-limit
|
placeholder="请输入" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<div v-if="arrivalTip"
|
class="arrival-tip">
|
{{ arrivalTip }}
|
</div>
|
</el-form>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button type="primary"
|
@click="submitForm">确认</el-button>
|
<el-button @click="closeForm">取消</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
</el-dialog>
|
</template>
|
|
<script setup>
|
import { ref, reactive, computed, watch, getCurrentInstance, toRefs } from "vue";
|
import { ElMessageBox } from "element-plus";
|
import pagination from "@/components/PIMTable/Pagination.vue";
|
import ImageUpload from "@/components/AttachmentUpload/image/index.vue";
|
import {
|
salesLedgerArrivalListPage,
|
bindableArrivalVehicles,
|
addSalesLedgerArrival,
|
updateSalesLedgerArrival,
|
delSalesLedgerArrival,
|
} from "@/api/salesManagement/salesLedgerArrival.js";
|
|
const props = defineProps({
|
visible: {
|
type: Boolean,
|
required: true,
|
},
|
// 当前销售台账 id:到货记录挂在「出库单 × 台账」上,台账由外面带进来
|
ledgerId: {
|
type: [String, Number],
|
default: undefined,
|
},
|
ledgerLabel: {
|
type: String,
|
default: "",
|
},
|
});
|
|
const emit = defineEmits(["update:visible", "changed"]);
|
|
const { proxy } = getCurrentInstance();
|
|
const isShow = computed({
|
get() {
|
return props.visible;
|
},
|
set(val) {
|
emit("update:visible", val);
|
},
|
});
|
|
const tableData = ref([]);
|
const tableLoading = ref(false);
|
const total = ref(0);
|
const page = reactive({
|
current: 1,
|
size: 10,
|
});
|
|
// 可到货的「出库单 + 车辆」候选:一次拉回来,出库单去重、车辆按出库单联动
|
const bindableRows = ref([]);
|
const formVisible = ref(false);
|
const formRef = ref(null);
|
|
const buildEmptyForm = () => ({
|
id: null,
|
stockOutRecordId: undefined,
|
vehicleId: undefined,
|
truckPlateNo: "",
|
arrivalQuantity: null,
|
arrivalDate: "",
|
attachmentList: [],
|
remark: "",
|
});
|
|
const form = ref(buildEmptyForm());
|
|
// 出库单候选:同一个 CK 号只出一个选项
|
const outboundOptions = computed(() => {
|
const map = new Map();
|
bindableRows.value.forEach(item => {
|
if (!item.stockOutRecordId || map.has(item.stockOutRecordId)) {
|
return;
|
}
|
map.set(item.stockOutRecordId, item);
|
});
|
return [...map.values()];
|
});
|
|
// 没登记车辆的出库单(代储/客存不强制选车)拿不出车辆候选,不该当成一个空选项
|
const vehicleOptions = computed(() =>
|
bindableRows.value.filter(
|
item =>
|
item.stockOutRecordId === form.value.stockOutRecordId &&
|
item.vehicleId !== null &&
|
item.vehicleId !== undefined
|
)
|
);
|
|
const vehiclePlaceholder = computed(() => {
|
if (!form.value.stockOutRecordId) return "请先选择出库单";
|
return vehicleOptions.value.length
|
? "请选择车辆"
|
: "该出库单未登记车辆,可留空";
|
});
|
|
const selectedOutbound = computed(() =>
|
outboundOptions.value.find(
|
item => item.stockOutRecordId === form.value.stockOutRecordId
|
)
|
);
|
|
const outboundLabel = item =>
|
[item.outboundBatches, `已到货 ${item.arrivedQuantity ?? 0} / ${item.outQuantity ?? 0} 吨`]
|
.filter(Boolean)
|
.join("|");
|
|
// 编辑时本条已占的量要从「已到货」里加回来,否则数量改不大于原值
|
const originalArrivalQuantity = ref(0);
|
|
// 0 在 required 眼里算「已填」,拦不住,这里按后端口径要求 > 0
|
const validateArrivalQuantity = (rule, value, callback) => {
|
if (value === null || value === undefined || value === "") {
|
callback(new Error("请输入到货数量"));
|
return;
|
}
|
if (!(Number(value) > 0)) {
|
callback(new Error("到货数量必须大于0"));
|
return;
|
}
|
callback();
|
};
|
|
// 到货上限 = 该台账在这张出库单上分摊到的数量
|
const arrivalMax = computed(() => {
|
const base = selectedOutbound.value;
|
if (!base) return undefined;
|
const remain =
|
(Number(base.outQuantity) || 0) - (Number(base.arrivedQuantity) || 0);
|
return Number((remain + originalArrivalQuantity.value).toFixed(2));
|
});
|
|
const arrivalTip = computed(() => {
|
const base = selectedOutbound.value;
|
if (!base) return "";
|
return `该台账在这张出库单上分摊 ${base.outQuantity ?? 0} 吨,已到货 ${
|
base.arrivedQuantity ?? 0
|
} 吨,本次最多可登记 ${arrivalMax.value} 吨`;
|
});
|
|
const getList = () => {
|
if (!props.ledgerId) {
|
tableData.value = [];
|
total.value = 0;
|
return;
|
}
|
tableLoading.value = true;
|
salesLedgerArrivalListPage({
|
salesLedgerId: props.ledgerId,
|
...page,
|
})
|
.then(res => {
|
tableData.value = res.data?.records || [];
|
total.value = res.data?.total || 0;
|
})
|
.finally(() => {
|
tableLoading.value = false;
|
});
|
};
|
|
const paginationChange = obj => {
|
page.current = obj.page;
|
page.size = obj.limit;
|
getList();
|
};
|
|
const loadBindableRows = () => {
|
if (!props.ledgerId) {
|
bindableRows.value = [];
|
return Promise.resolve([]);
|
}
|
return bindableArrivalVehicles({ salesLedgerId: props.ledgerId })
|
.then(res => {
|
bindableRows.value = Array.isArray(res?.data) ? res.data : [];
|
return bindableRows.value;
|
})
|
.catch(() => {
|
bindableRows.value = [];
|
return [];
|
});
|
};
|
|
// 附件后端存 JSON 字符串,出参可能是数组也可能是串
|
const toPhotoArray = value => {
|
if (!value) return [];
|
if (Array.isArray(value)) {
|
return value.map(getPhotoUrl).filter(Boolean);
|
}
|
if (typeof value === "string") {
|
try {
|
const parsed = JSON.parse(value);
|
return Array.isArray(parsed)
|
? parsed.map(getPhotoUrl).filter(Boolean)
|
: [];
|
} catch (e) {
|
return [];
|
}
|
}
|
return [];
|
};
|
|
const getPhotoUrl = item =>
|
typeof item === "string" ? item : item?.previewURL || item?.url || "";
|
|
const openForm = row => {
|
form.value = buildEmptyForm();
|
originalArrivalQuantity.value = 0;
|
loadBindableRows().then(() => {
|
if (row) {
|
form.value.id = row.id ?? null;
|
form.value.stockOutRecordId = row.stockOutRecordId;
|
// 车辆/车牌以出库单登记为准,但列表行里带回的是当时的值,直接回显
|
form.value.vehicleId = row.vehicleId ?? undefined;
|
form.value.truckPlateNo = row.truckPlateNo || "";
|
form.value.arrivalQuantity = row.arrivalQuantity ?? null;
|
form.value.arrivalDate = row.arrivalDate || "";
|
form.value.remark = row.remark || "";
|
form.value.attachmentList = toPhotoArray(row.attachments).map(url => ({
|
url,
|
}));
|
originalArrivalQuantity.value = Number(row.arrivalQuantity) || 0;
|
}
|
formVisible.value = true;
|
});
|
};
|
|
const handleOutboundChange = () => {
|
// 车辆随出库单联动:换了出库单,原来选的车辆可能不在候选里
|
form.value.vehicleId = undefined;
|
form.value.truckPlateNo = "";
|
// 一张出库单只登记一个车辆,能定就直接带出来(代储/客存没登记车辆则留空)
|
const only = vehicleOptions.value[0];
|
if (vehicleOptions.value.length === 1) {
|
form.value.vehicleId = only.vehicleId;
|
form.value.truckPlateNo = only.truckPlateNo || "";
|
}
|
};
|
|
const handleVehicleChange = id => {
|
const vehicle = vehicleOptions.value.find(item => item.vehicleId === id);
|
form.value.truckPlateNo = vehicle?.truckPlateNo || "";
|
};
|
|
const closeForm = () => {
|
proxy.resetForm("formRef");
|
form.value = buildEmptyForm();
|
originalArrivalQuantity.value = 0;
|
formVisible.value = false;
|
};
|
|
const submitForm = () => {
|
proxy.$refs["formRef"].validate(valid => {
|
if (!valid) return;
|
const payload = {
|
stockOutRecordId: form.value.stockOutRecordId,
|
salesLedgerId: props.ledgerId,
|
arrivalQuantity: form.value.arrivalQuantity,
|
arrivalDate: form.value.arrivalDate,
|
// 只存永久链接,2 小时失效的签名 URL 过期后附件会打不开
|
attachments: form.value.attachmentList.map(getPhotoUrl).filter(Boolean),
|
remark: form.value.remark,
|
};
|
// 车辆/车牌以出库单为准,只有选了车才传,空值不参与后端的比对
|
if (form.value.vehicleId !== undefined && form.value.vehicleId !== null) {
|
payload.vehicleId = form.value.vehicleId;
|
payload.truckPlateNo = form.value.truckPlateNo;
|
}
|
const submit = form.value.id
|
? updateSalesLedgerArrival({ ...payload, id: form.value.id })
|
: addSalesLedgerArrival(payload);
|
submit.then(() => {
|
proxy.$modal.msgSuccess("操作成功");
|
closeForm();
|
getList();
|
emit("changed");
|
});
|
});
|
};
|
|
const handleDelete = row => {
|
ElMessageBox.confirm("此操作将删除该到货记录,是否确认?", "删除", {
|
confirmButtonText: "确认",
|
cancelButtonText: "取消",
|
type: "warning",
|
})
|
.then(() => {
|
delSalesLedgerArrival([row.id]).then(() => {
|
proxy.$modal.msgSuccess("删除成功");
|
getList();
|
emit("changed");
|
});
|
})
|
.catch(() => {
|
proxy.$modal.msg("已取消");
|
});
|
};
|
|
const closeModal = () => {
|
isShow.value = false;
|
};
|
|
watch(
|
() => props.visible,
|
visible => {
|
if (visible) {
|
page.current = 1;
|
getList();
|
}
|
}
|
);
|
</script>
|
|
<style scoped lang="scss">
|
.actions {
|
display: flex;
|
justify-content: flex-end;
|
margin-bottom: 10px;
|
}
|
|
.ledger-tip,
|
.arrival-tip {
|
margin-bottom: 10px;
|
padding: 6px 14px;
|
font-size: 13px;
|
color: #303133;
|
background: rgba(64, 158, 255, 0.08);
|
border-left: 3px solid #409eff;
|
border-radius: 3px;
|
}
|
|
.arrival-tip {
|
margin-bottom: 0;
|
}
|
</style>
|