<template>
|
<div>
|
<vol-box
|
v-model="showStockTakeBox"
|
:lazy="true"
|
:width="isMobile ? '95%' : '70%'"
|
:padding="24"
|
title="库存盘点操作(条码盘盈不需要进行盘点完成操作)"
|
class="custom-vol-box"
|
>
|
<div class="stock-take-container">
|
<div class="receipt-info">
|
<el-tag type="primary" size="small" class="receipt-tag">
|
当前盘点单据号:<span class="receipt-no">{{ orderNo }}</span>
|
</el-tag>
|
</div>
|
|
<!-- 核心输入表单:移除所有校验规则 -->
|
<el-form
|
:model="formData"
|
ref="formRef"
|
label-width="100px"
|
class="stock-take-form"
|
@submit.prevent
|
>
|
<!-- 料箱号输入框:移除校验规则 -->
|
<el-form-item label="料箱号:" name="boxNo" class="form-item">
|
<el-input
|
ref="boxNoInputRef"
|
v-model="formData.boxNo"
|
placeholder="请使用扫码枪扫描料箱号,或手动输入"
|
clearable
|
@keydown.enter="debouncedHandleBoxNoScan"
|
@blur="handleBoxNoBlur"
|
:disabled="loading"
|
class="custom-input"
|
:class="{ 'has-value': formData.boxNo.trim() }"
|
>
|
<template #append>
|
<el-button
|
icon="Search"
|
type="primary"
|
size="small"
|
@click="handleBoxNoScan"
|
:disabled="loading"
|
class="input-btn"
|
></el-button>
|
</template>
|
</el-input>
|
</el-form-item>
|
|
<!-- 站台选择下拉框:移除校验规则 -->
|
<el-form-item label="回库站台:" name="station" class="form-item">
|
<el-select
|
v-model="selectedStation"
|
placeholder="请选择回库站台"
|
:disabled="loading"
|
class="custom-input"
|
:class="{ 'has-value': selectedStation }"
|
>
|
<el-option
|
v-for="item in stations"
|
:key="item.key || item.value"
|
:label="item.label"
|
:value="item.value"
|
></el-option>
|
</el-select>
|
</el-form-item>
|
|
<!-- 条码输入框:移除校验规则 -->
|
<el-form-item label="盘点条码:" name="barcode" class="form-item">
|
<el-input
|
ref="barcodeInputRef"
|
v-model="formData.barcode"
|
placeholder="请使用扫码枪扫描条码,或手动输入"
|
clearable
|
@keydown.enter="debouncedHandleBarcodeScan"
|
:disabled="loading"
|
class="custom-input"
|
:class="{ 'has-value': formData.barcode.trim() }"
|
>
|
<template #append>
|
<el-button
|
icon="Search"
|
type="primary"
|
size="small"
|
@click="handleBarcodeScan"
|
:disabled="loading"
|
class="input-btn"
|
></el-button>
|
</template>
|
</el-input>
|
</el-form-item>
|
|
<!-- 库存数量框(只读) -->
|
<el-form-item label="库存数量:" name="stockQuantity" class="form-item">
|
<el-input
|
v-model="formData.stockQuantity"
|
placeholder="扫描条码后自动填充"
|
readonly
|
class="custom-input custom-readonly-input"
|
:class="{ 'has-value': formData.stockQuantity }"
|
></el-input>
|
</el-form-item>
|
|
<!-- 实际盘点数量:只读,默认0 -->
|
<el-form-item label="实际盘点数量:" name="actualQuantity" class="form-item">
|
<el-input
|
v-model="formData.actualQuantity"
|
placeholder="默认值为0"
|
readonly
|
class="custom-input custom-readonly-input"
|
:class="{ 'has-value': formData.actualQuantity !== '' }"
|
></el-input>
|
</el-form-item>
|
</el-form>
|
|
<!-- 操作按钮区域:简化禁用逻辑 -->
|
<div class="action-buttons">
|
<el-button
|
type="info"
|
size="small"
|
@click="handleBoxReturn"
|
:disabled="loading"
|
class="return-btn"
|
>
|
<Return /> 料箱回库
|
</el-button>
|
<el-button
|
type="primary"
|
size="small"
|
@click="handleStockTakeComplete"
|
:disabled="loading"
|
class="complete-btn"
|
>
|
<Check /> 盘点完成
|
</el-button>
|
<el-button
|
type="text"
|
size="small"
|
@click="handleCancel"
|
:disabled="loading"
|
class="cancel-btn"
|
>
|
取消
|
</el-button>
|
</div>
|
</div>
|
</vol-box>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, onMounted, nextTick, watch, defineEmits, computed } from "vue";
|
import { ElMessage, ElTag, ElMessageBox } from "element-plus";
|
|
import VolBox from "@/components/basic/VolBox.vue";
|
import http from "@/api/http";
|
import { stationManager, STATION_STORAGE_KEY } from "@/../src/uitils/stationManager";
|
|
// 响应式变量 - 单据号
|
const orderNo = ref("");
|
|
// 暴露事件
|
const emit = defineEmits(["close", "refresh", "box-returned"]);
|
|
// 响应式数据
|
const showStockTakeBox = ref(false);
|
const formData = reactive({
|
boxNo: "", // 料箱号
|
barcode: "", // 盘点条码
|
stockQuantity: "", // 库存数量
|
actualQuantity: 0, // 实际盘点数量默认0
|
});
|
const loading = ref(false);
|
const formRef = ref(null);
|
|
// 站台相关响应式数据
|
const stations = ref([
|
{ label: "站台2", value: "2-1" },
|
{ label: "站台3", value: "3-1" },
|
]);
|
const selectedStation = ref(stationManager.getStation() || "");
|
|
// 模板引用
|
const boxNoInputRef = ref(null);
|
const barcodeInputRef = ref(null);
|
|
// 检测是否为移动端
|
const isMobile = computed(() => {
|
return window.innerWidth < 768;
|
});
|
|
// 组件挂载时聚焦到料箱号输入框
|
onMounted(() => {
|
nextTick(() => {
|
boxNoInputRef.value?.focus();
|
});
|
window.addEventListener("resize", () => {});
|
});
|
|
// 监听料箱号变化,清空后续相关字段(仅保留基础清空逻辑)
|
watch(
|
() => formData.boxNo,
|
(newVal) => {
|
if (!newVal.trim()) {
|
formData.barcode = "";
|
formData.stockQuantity = "";
|
formData.actualQuantity = 0;
|
selectedStation.value = stationManager.getStation() || "";
|
orderNo.value = "";
|
}
|
},
|
{ immediate: true }
|
);
|
|
// 防抖函数
|
const debounce = (fn, delay = 100) => {
|
let timer = null;
|
return (...args) => {
|
if (timer) clearTimeout(timer);
|
timer = setTimeout(() => {
|
fn.apply(this, args);
|
}, delay);
|
};
|
};
|
|
// 打开弹窗
|
const open = () => {
|
showStockTakeBox.value = true;
|
// 重置表单
|
formData.boxNo = "";
|
formData.barcode = "";
|
formData.stockQuantity = "";
|
formData.actualQuantity = 0;
|
selectedStation.value = stationManager.getStation() || "";
|
orderNo.value = "";
|
nextTick(() => {
|
boxNoInputRef.value?.focus();
|
});
|
};
|
|
// 关闭弹窗
|
const handleCancel = (e) => {
|
e.stopPropagation();
|
e.preventDefault();
|
showStockTakeBox.value = false;
|
emit("close");
|
orderNo.value = "";
|
};
|
|
// 料箱号失焦处理
|
const handleBoxNoBlur = () => {
|
if (formData.boxNo.trim() && boxNoInputRef.value?.input) {
|
boxNoInputRef.value.input.select();
|
}
|
};
|
|
// 料箱号扫描处理:移除前端校验,直接调用接口
|
const handleBoxNoScan = async () => {
|
const boxNo = formData.boxNo.trim();
|
if (!boxNo) {
|
ElMessage.warning("请输入料箱号");
|
return;
|
}
|
|
try {
|
loading.value = true;
|
// 直接调用接口,不做前端校验
|
const res = await http.post(
|
`/api/TakeStockOrder/ValidateBoxNo?orderNo=${encodeURIComponent(orderNo.value)}&boxNo=${encodeURIComponent(boxNo)}`,
|
"验证料箱号中..."
|
);
|
|
if (!res.status) {
|
throw new Error(res.message || "料箱号验证失败");
|
}
|
if (res.data?.takeStockOrder) {
|
orderNo.value = res.data.takeStockOrder;
|
}
|
ElMessage.success(`料箱号 ${boxNo} 验证通过,单据号:${orderNo.value}`);
|
nextTick(() => {
|
barcodeInputRef.value?.focus();
|
});
|
} catch (error) {
|
ElMessage.error(error.message);
|
nextTick(() => {
|
if (boxNoInputRef.value?.input) {
|
boxNoInputRef.value.input.select();
|
}
|
});
|
} finally {
|
loading.value = false;
|
}
|
};
|
|
// 条码扫描处理:移除前端校验,直接调用接口
|
const handleBarcodeScan = async () => {
|
const boxNo = formData.boxNo.trim();
|
const barcode = formData.barcode.trim();
|
if (!boxNo || !barcode) {
|
ElMessage.warning("请输入料箱号和条码");
|
return;
|
}
|
|
try {
|
loading.value = true;
|
// 直接调用接口,不做前端校验
|
const res = await http.post(
|
`/api/TakeStockOrder/ValidateBarcode?boxNo=${encodeURIComponent(boxNo)}&barcode=${encodeURIComponent(barcode)}`,
|
"验证条码中..."
|
);
|
|
if (!res.status) {
|
throw new Error(res.message || "条码验证失败");
|
}
|
|
if (res.data?.stockQuantity !== undefined && res.data?.stockQuantity !== null) {
|
formData.stockQuantity = res.data.stockQuantity;
|
}
|
ElMessage.success(`条码 ${barcode} 验证通过,库存数量:${formData.stockQuantity}`);
|
nextTick(() => {
|
const actualQuantityInput = document.querySelector(".form-item:last-child .el-input__inner");
|
actualQuantityInput?.blur();
|
});
|
} catch (error) {
|
ElMessage.error(error.message);
|
nextTick(() => {
|
if (barcodeInputRef.value?.input) {
|
barcodeInputRef.value.input.select();
|
}
|
});
|
} finally {
|
loading.value = false;
|
}
|
};
|
|
// 盘点完成提交:完全移除前端校验,直接传值给后端
|
const handleStockTakeComplete = async () => {
|
// 仅做最基础的空提示(可选,也可以去掉)
|
if (!formData.boxNo.trim()) {
|
ElMessage.warning("请输入料箱号");
|
return;
|
}
|
if (!formData.barcode.trim()) {
|
ElMessage.warning("请输入条码");
|
return;
|
}
|
|
// 直接获取所有值,不做任何校验
|
const { boxNo, barcode, actualQuantity, stockQuantity } = formData;
|
const receiptNo = orderNo.value;
|
|
try {
|
loading.value = true;
|
|
// 直接传值给后端,不做任何前端校验
|
const res = await http.post(
|
"/api/TakeStockOrder/CompleteStockTake",
|
{
|
orderNo: receiptNo,
|
boxNo,
|
barcode,
|
actualQuantity, // 固定传0
|
stockQuantity,
|
},
|
{
|
loadingText: "提交盘点数据中...",
|
}
|
);
|
|
if (res.status) {
|
ElMessage.success("盘点完成,提交成功!");
|
formData.barcode = "";
|
formData.stockQuantity = "";
|
formData.actualQuantity = 0;
|
nextTick(() => {
|
barcodeInputRef.value?.focus();
|
});
|
emit("refresh");
|
} else {
|
throw new Error(res.message || "盘点提交失败");
|
}
|
} catch (error) {
|
ElMessage.error(error.message || "网络异常,提交失败");
|
} finally {
|
loading.value = false;
|
}
|
};
|
|
// 料箱回库功能:简化校验,直接传值
|
const handleBoxReturn = async () => {
|
const boxNo = formData.boxNo.trim();
|
if (!boxNo) {
|
ElMessage.warning("请输入料箱号");
|
return;
|
}
|
|
try {
|
await ElMessageBox.confirm(`确定将料箱【${boxNo}】回库至【${selectedStation.value}】站台吗?`, "回库确认", {
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "info",
|
center: true,
|
confirmButtonClass: "el-button--primary",
|
cancelButtonClass: "el-button--text",
|
});
|
|
loading.value = true;
|
|
// 直接传值给后端,不做严格校验
|
const res = await http.post(
|
`/api/TakeStockOrder/ReturnBox?boxNo=${encodeURIComponent(boxNo)}&orderNo=${encodeURIComponent(orderNo.value)}&sourceAddress=${encodeURIComponent(selectedStation.value)}`,
|
"料箱回库中..."
|
);
|
|
if (res.status) {
|
ElMessage.success(`料箱【${boxNo}】回库至【${selectedStation.value}】站台成功!`);
|
formData.boxNo = "";
|
formData.barcode = "";
|
formData.stockQuantity = "";
|
formData.actualQuantity = 0;
|
selectedStation.value = stationManager.getStation() || "";
|
orderNo.value = "";
|
nextTick(() => {
|
boxNoInputRef.value?.focus();
|
});
|
emit("box-returned", boxNo);
|
} else {
|
throw new Error(res.message || "料箱回库失败");
|
}
|
} catch (error) {
|
if (error.name !== "Cancel") {
|
ElMessage.error(error.message);
|
}
|
} finally {
|
loading.value = false;
|
}
|
};
|
|
// 带防抖的扫描处理
|
const debouncedHandleBoxNoScan = debounce(async (e) => {
|
e.stopPropagation();
|
e.preventDefault();
|
await handleBoxNoScan();
|
}, 100);
|
|
const debouncedHandleBarcodeScan = debounce(async (e) => {
|
e.stopPropagation();
|
e.preventDefault();
|
await handleBarcodeScan();
|
}, 100);
|
|
// 暴露方法
|
defineExpose({
|
open,
|
});
|
</script>
|
|
<style scoped>
|
/* 样式完全保留,无修改 */
|
.stock-take-container {
|
width: 100%;
|
display: flex;
|
flex-direction: column;
|
gap: 16px;
|
padding: 8px 0;
|
}
|
|
.receipt-info {
|
margin-bottom: 4px;
|
}
|
|
.receipt-tag {
|
padding: 8px 16px;
|
font-size: 14px;
|
background-color: #e8f4f8 !important;
|
border-color: #409eff !important;
|
color: #1989fa !important;
|
border-radius: 8px;
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
|
}
|
|
.receipt-no {
|
font-weight: 600;
|
color: #1989fa;
|
margin-left: 6px;
|
}
|
|
.stock-take-form {
|
width: 100%;
|
background-color: #ffffff;
|
padding: 20px;
|
border-radius: 12px;
|
box-shadow: 0 2px 12px rgba(64, 158, 255, 0.06);
|
border: 1px solid #f0f8fb;
|
}
|
|
.form-item {
|
margin-bottom: 16px;
|
}
|
|
.custom-input {
|
width: 100%;
|
position: relative;
|
}
|
|
.custom-input.has-value :deep(.el-input__inner),
|
.custom-input.has-value :deep(.el-select__wrapper) {
|
--el-input-placeholder-color: transparent;
|
border-color: #8cc5ff;
|
background-color: #ffffff;
|
}
|
|
.custom-input :deep(.el-input__inner),
|
.custom-input :deep(.el-select__wrapper) {
|
border-radius: 8px;
|
border-color: #e5f0fa;
|
transition: all 0.2s ease;
|
height: 42px;
|
line-height: 42px;
|
font-size: 14px;
|
background-color: #f8fbff;
|
padding: 0 12px;
|
}
|
|
.custom-input :deep(.el-input__inner:focus),
|
.custom-input :deep(.el-select__wrapper:focus) {
|
border-color: #409eff;
|
box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.1);
|
background-color: #ffffff;
|
outline: none;
|
}
|
|
.custom-input :deep(.el-input__clear) {
|
color: #91c9f7;
|
width: 18px;
|
height: 18px;
|
}
|
|
.custom-input :deep(.el-input__clear:hover) {
|
color: #409eff;
|
}
|
|
.custom-readonly-input :deep(.el-input__inner) {
|
background-color: #f0f8fb;
|
color: #1989fa;
|
font-weight: 500;
|
cursor: default;
|
border-color: #d1e7fd;
|
padding-right: 12px;
|
}
|
|
.input-btn {
|
border-radius: 0 8px 8px 0 !important;
|
height: 42px;
|
width: 48px;
|
background-color: #409eff;
|
border-color: #409eff;
|
transition: all 0.2s ease;
|
}
|
|
.input-btn:hover {
|
background-color: #1989fa !important;
|
border-color: #1989fa !important;
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
transform: translateY(-1px);
|
}
|
|
.form-item :deep(.el-form-item__error) {
|
font-size: 12px;
|
color: #f56c6c;
|
margin-top: 4px;
|
display: none;
|
}
|
|
.form-item:deep(.el-form-item--error) .custom-input:not(.has-value) + .el-form-item__error {
|
display: block;
|
}
|
|
.custom-input.has-value :deep(.el-input__inner.el-input__inner--error),
|
.custom-input.has-value :deep(.el-select__wrapper.el-select__wrapper--error) {
|
border-color: #8cc5ff;
|
box-shadow: none;
|
}
|
|
.action-buttons {
|
display: flex;
|
justify-content: flex-end;
|
gap: 10px;
|
margin-top: 20px;
|
flex-wrap: wrap;
|
}
|
|
.return-btn {
|
border-radius: 8px;
|
padding: 9px 20px;
|
font-size: 14px;
|
font-weight: 500;
|
background-color: #e8f4f8;
|
border-color: #409eff;
|
color: #1989fa;
|
transition: all 0.2s ease;
|
}
|
|
.return-btn:hover {
|
background-color: #d1e7fd !important;
|
border-color: #1989fa !important;
|
color: #1989fa !important;
|
transform: translateY(-1px);
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);
|
}
|
|
.return-btn:disabled {
|
background-color: #f0f8fb !important;
|
border-color: #b3d8ff !important;
|
color: #91c9f7 !important;
|
cursor: not-allowed;
|
transform: none;
|
box-shadow: none;
|
}
|
|
.complete-btn {
|
border-radius: 8px;
|
padding: 9px 20px;
|
font-size: 14px;
|
font-weight: 500;
|
background-color: #409eff;
|
border-color: #409eff;
|
transition: all 0.2s ease;
|
}
|
|
.complete-btn:hover {
|
background-color: #1989fa !important;
|
border-color: #1989fa !important;
|
transform: translateY(-1px);
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
}
|
|
.complete-btn:disabled {
|
background-color: #a0cfff !important;
|
border-color: #a0cfff !important;
|
color: #ffffff !important;
|
cursor: not-allowed;
|
transform: none;
|
box-shadow: none;
|
}
|
|
.cancel-btn {
|
color: #666666;
|
font-size: 14px;
|
transition: all 0.2s ease;
|
padding: 9px 20px;
|
border-radius: 8px;
|
min-width: 80px;
|
}
|
|
.cancel-btn:hover {
|
color: #1989fa !important;
|
background-color: #f0f8fb !important;
|
}
|
|
.custom-vol-box {
|
border-radius: 16px !important;
|
overflow: hidden;
|
box-shadow: 0 8px 24px rgba(64, 158, 255, 0.12);
|
}
|
|
.custom-vol-box :deep(.el-dialog__header) {
|
background-color: #e8f4f8;
|
border-bottom: 1px solid #d1e7fd;
|
padding: 16px 24px;
|
}
|
|
.custom-vol-box :deep(.el-dialog__title) {
|
color: #1989fa;
|
font-size: 18px;
|
font-weight: 600;
|
}
|
|
.custom-vol-box :deep(.el-dialog__body) {
|
padding: 16px 24px;
|
}
|
|
@media (max-width: 768px) {
|
.stock-take-form {
|
padding: 16px;
|
}
|
|
.form-item {
|
margin-bottom: 12px;
|
}
|
|
.action-buttons {
|
justify-content: center;
|
}
|
|
.custom-vol-box :deep(.el-dialog__header) {
|
padding: 12px 16px;
|
}
|
|
.custom-vol-box :deep(.el-dialog__body) {
|
padding: 12px 16px;
|
}
|
}
|
</style>
|
|
<style>
|
.el-tag--primary {
|
--el-tag-bg-color: #e8f4f8;
|
--el-tag-border-color: #409eff;
|
--el-tag-text-color: #1989fa;
|
}
|
|
.el-input__inner::-webkit-input-placeholder,
|
.el-select__placeholder {
|
color: #b3d8ff;
|
font-size: 13px;
|
}
|
|
.el-input__inner::-moz-placeholder,
|
.el-select__placeholder {
|
color: #b3d8ff;
|
font-size: 13px;
|
}
|
|
.el-input__inner:-ms-input-placeholder,
|
.el-select__placeholder {
|
color: #b3d8ff;
|
font-size: 13px;
|
}
|
|
.el-input__inner::placeholder,
|
.el-select__placeholder {
|
color: #b3d8ff;
|
font-size: 13px;
|
}
|
|
.el-button--info {
|
--el-button-bg-color: #e8f4f8;
|
--el-button-border-color: #409eff;
|
--el-button-text-color: #1989fa;
|
--el-button-hover-bg-color: #d1e7fd;
|
--el-button-hover-border-color: #1989fa;
|
--el-button-hover-text-color: #1989fa;
|
}
|
|
.el-message-box {
|
border-radius: 12px !important;
|
box-shadow: 0 4px 20px rgba(64, 158, 255, 0.15);
|
}
|
|
.el-message-box__header {
|
border-bottom: 1px solid #f0f8fb;
|
padding: 12px 20px;
|
}
|
|
.el-message-box__title {
|
color: #1989fa;
|
font-size: 16px;
|
font-weight: 600;
|
}
|
|
.el-message-box__content {
|
color: #666666;
|
font-size: 14px;
|
padding: 16px 20px;
|
}
|
|
.el-message-box__btns {
|
padding: 12px 20px;
|
border-top: 1px solid #f0f8fb;
|
}
|
</style>
|