<template>
|
<div>
|
<vol-box
|
v-model="showDetailBox"
|
:lazy="true"
|
width="65%"
|
:padding="20"
|
title="无库存出库"
|
class="custom-vol-box"
|
>
|
<div>
|
<!-- 上方输入框 -->
|
<el-form :inline="true" :model="formData" ref="formData" style="margin-bottom: 20px; align-items: flex-end;">
|
<el-form-item
|
label="扫描条码:"
|
style="width: 80%"
|
prop="barcode"
|
>
|
<el-input
|
ref="barcodeInput"
|
v-model="formData.barcode"
|
placeholder="请使用扫码枪扫描条码,或手动输入"
|
@keyup.enter="handleScan"
|
autofocus
|
class="custom-input"
|
></el-input>
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary" size="small" @click="handleScan" class="custom-button">
|
<i class="el-icon-search"></i> 确认扫描
|
</el-button>
|
</el-form-item>
|
</el-form>
|
|
<!-- 下方显示框 -->
|
<div class="scan-list">
|
<el-card shadow="hover" style="margin-bottom: 10px; border: none;" class="custom-card">
|
<div class="card-header">
|
<span class="header-title">已扫描条码列表(共{{ scannedBarcodes.length }}条)</span>
|
<el-button
|
class="clear-all-btn"
|
@click="clearAll"
|
:disabled="scannedBarcodes.length === 0"
|
>清空所有</el-button>
|
</div>
|
<div class="card-body">
|
<el-scrollbar height="400px" class="custom-scrollbar">
|
<!-- 使用 transition-group 包裹以实现动画 -->
|
<transition-group name="barcode-item-transition">
|
<div class="barcode-item" v-for="(barcode, index) in scannedBarcodes" :key="barcode" :data-index="index">
|
<span class="barcode-text">{{ index + 1 }}. {{ barcode }}</span>
|
<el-button
|
class="delete-btn"
|
@click="removeItem(index)"
|
>删除</el-button>
|
</div>
|
</transition-group>
|
<div class="empty-tip" v-if="scannedBarcodes.length === 0">
|
<i class="el-icon-information"></i>
|
<span>暂无扫描记录,请扫描条码</span>
|
</div>
|
</el-scrollbar>
|
</div>
|
</el-card>
|
</div>
|
</div>
|
|
<template #footer>
|
<div class="footer-actions">
|
<el-button type="primary" size="small" @click="submit" :disabled="scannedBarcodes.length === 0" class="submit-btn">
|
<i class="el-icon-check"></i> 提交出库
|
</el-button>
|
<el-button type="text" size="small" @click="showDetailBox = false" class="cancel-btn">
|
取消
|
</el-button>
|
</div>
|
</template>
|
</vol-box>
|
</div>
|
</template>
|
|
<script>
|
import VolBox from "@/components/basic/VolBox.vue";
|
|
export default {
|
components: { VolBox },
|
data() {
|
return {
|
showDetailBox: false,
|
formData: {
|
barcode: "",
|
},
|
scannedBarcodes: [],
|
};
|
},
|
methods: {
|
open() {
|
this.showDetailBox = true;
|
this.scannedBarcodes = [];
|
this.formData.barcode = "";
|
this.$nextTick(() => {
|
this.$refs.barcodeInput.focus();
|
});
|
},
|
|
handleScan() {
|
const barcode = this.formData.barcode.trim();
|
if (!barcode) {
|
this.$refs.barcodeInput.focus();
|
return;
|
}
|
if (this.scannedBarcodes.includes(barcode)) {
|
this.$message.warning(`条码 ${barcode} 已扫描过,请勿重复扫描`);
|
this.formData.barcode = "";
|
this.$refs.barcodeInput.focus();
|
return;
|
}
|
|
this.scannedBarcodes.push(barcode);
|
this.formData.barcode = "";
|
|
this.$nextTick(() => {
|
this.$refs.barcodeInput.focus();
|
});
|
},
|
|
removeItem(index) {
|
this.scannedBarcodes.splice(index, 1);
|
},
|
|
clearAll() {
|
this.$confirm("确定要清空所有扫描记录吗?", "提示", {
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning",
|
}).then(() => {
|
this.scannedBarcodes = [];
|
}).catch(() => {
|
this.$refs.barcodeInput.focus();
|
});
|
},
|
|
submit() {
|
if (this.scannedBarcodes.length === 0) {
|
this.$message.warning("请先扫描至少一条条码");
|
this.$refs.barcodeInput.focus();
|
return;
|
}
|
|
const params = {
|
barcodes: this.scannedBarcodes,
|
};
|
|
this.http
|
.post("/api/OutboundOrder/NoStockOut", params, "数据处理中...")
|
.then((res) => {
|
if (!res.status) {
|
this.$message.error(res.message);
|
this.$refs.barcodeInput.focus();
|
return;
|
}
|
this.$message.success("出库成功");
|
this.showDetailBox = false;
|
this.$emit("parentCall", ($vue) => {
|
$vue.refresh();
|
});
|
})
|
.catch((err) => {
|
this.$message.error(`请求失败:${err.message || "未知错误"}`);
|
this.$refs.barcodeInput.focus();
|
});
|
},
|
},
|
};
|
</script>
|
|
<style scoped>
|
/* 关键:定义列表项的过渡动画 */
|
.barcode-item-transition-enter-active,
|
.barcode-item-transition-leave-active {
|
transition: all 0.3s ease;
|
}
|
.barcode-item-transition-enter-from {
|
opacity: 0;
|
transform: translateY(10px);
|
}
|
.barcode-item-transition-leave-to {
|
opacity: 0;
|
transform: translateX(30px);
|
}
|
/* 确保删除时其他元素平滑上移 */
|
.barcode-item-transition-move {
|
transition: transform 1s ease;
|
}
|
|
.scan-list {
|
width: 100%;
|
}
|
|
.custom-card {
|
border-radius: 12px;
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08) !important;
|
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
}
|
.custom-card:hover {
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12) !important;
|
}
|
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 15px;
|
padding-bottom: 10px;
|
border-bottom: 1px solid #f0f0f0;
|
}
|
.header-title {
|
font-weight: 600;
|
font-size: 15px;
|
color: #333;
|
}
|
.clear-all-btn {
|
color: #f56c6c;
|
font-size: 13px;
|
transition: color 0.2s;
|
}
|
.clear-all-btn:hover {
|
color: #e53e3e;
|
background: rgba(245, 108, 108, 0.1);
|
}
|
|
.card-body {
|
padding: 0;
|
}
|
|
/* 自定义滚动条 */
|
.custom-scrollbar ::v-deep .el-scrollbar__thumb {
|
background: rgba(0, 0, 0, 0.2);
|
border-radius: 4px;
|
width: 4px;
|
}
|
.custom-scrollbar ::v-deep .el-scrollbar__bar:hover .el-scrollbar__thumb {
|
background: rgba(0, 0, 0, 0.3);
|
width: 6px;
|
}
|
.custom-scrollbar ::v-deep .el-scrollbar__wrap {
|
overflow-x: hidden;
|
}
|
|
.barcode-item {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
padding: 10px 15px;
|
border-bottom: 1px solid #f7f7f7;
|
transition: background-color 0.2s ease;
|
}
|
.barcode-item:hover {
|
background-color: #fafafa;
|
}
|
/* 为奇数行添加轻微的背景色,增强可读性 */
|
.barcode-item:nth-child(odd) {
|
background-color: #e1e1e1;
|
}
|
.barcode-text {
|
flex: 1;
|
font-size: 14px;
|
color: #666;
|
transition: color 0.2s;
|
}
|
.barcode-item:hover .barcode-text {
|
color: #409eff;
|
}
|
|
.delete-btn {
|
color: #ea1919;
|
font-size: 20px;
|
transition: all 0.2s;
|
transform: scale(0.8);
|
}
|
.barcode-item:hover .delete-btn {
|
opacity: 1;
|
transform: scale(1);
|
}
|
.delete-btn:hover {
|
color: #f56c6c !important; /* 使用 !important 覆盖 Element UI 默认样式 */
|
background: rgba(245, 108, 108, 0.1);
|
}
|
|
.empty-tip {
|
text-align: center;
|
padding: 80px 0;
|
color: #999;
|
font-size: 14px;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
}
|
.empty-tip i {
|
font-size: 40px;
|
margin-bottom: 15px;
|
color: #dcdfe6;
|
}
|
|
/* 自定义输入框 */
|
.custom-input ::v-deep .el-input__inner {
|
border-radius: 6px;
|
border-color: #e4e7ed;
|
transition: all 0.3s;
|
height: 36px;
|
line-height: 36px;
|
}
|
.custom-input ::v-deep .el-input__inner:focus {
|
border-color: #409eff;
|
box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.1);
|
}
|
|
/* 自定义按钮 */
|
.custom-button {
|
border-radius: 6px;
|
height: 36px;
|
line-height: 36px;
|
font-size: 13px;
|
font-weight: 500;
|
transition: all 0.2s;
|
}
|
.custom-button:hover {
|
transform: translateY(-1px);
|
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
|
}
|
|
.footer-actions {
|
text-align: right;
|
}
|
.submit-btn {
|
border-radius: 6px;
|
font-weight: 500;
|
padding: 10px 20px;
|
transition: all 0.2s;
|
}
|
.submit-btn:hover {
|
transform: translateY(-1px);
|
box-shadow: 0 4px 12px rgba(46, 164, 79, 0.2);
|
}
|
.cancel-btn {
|
color: #666;
|
margin-right: 10px;
|
transition: color 0.2s;
|
}
|
.cancel-btn:hover {
|
color: #333;
|
background: #f5f5f5;
|
}
|
</style>
|
|
<style>
|
/* ... (全局样式部分保持不变) ... */
|
.text-button:hover {
|
background-color: #f0f9eb !important;
|
}
|
.el-table .warning-row {
|
background: oldlace;
|
}
|
.box-table .el-table tbody tr:hover > td {
|
background-color: #d8e0d4 !important;
|
}
|
.box-table .el-table tbody tr.current-row > td {
|
background-color: #f0f9eb !important;
|
}
|
.el-table .success-row {
|
background: #f0f9eb;
|
}
|
.box-table .el-table {
|
border: 1px solid #ebeef5;
|
}
|
.box-head .el-alert__content {
|
width: 100%;
|
}
|
</style>
|