扫码枪结果读取改用网页

This commit is contained in:
砂糖
2025-08-23 10:35:43 +08:00
parent ef15eaa853
commit f00c31e606
4 changed files with 986 additions and 424 deletions

View File

@@ -0,0 +1,344 @@
<template>
<div class="qr-parser-container">
<div class="card">
<el-alert title="请将当前输入法切换到英文输入后再进行扫码操作" type="warning"></el-alert>
<div class="card-header">
<h2 class="title">二维码解析器</h2>
<p class="subtitle">扫描或输入二维码内容进行解析</p>
</div>
<div class="input-section">
<el-input
v-model="text"
@change="handleChange"
@blur="handleBlur"
ref="textarea"
placeholder="请扫描二维码或粘贴二维码文本内容..."
:rows="3"
type="textarea"
:class="{ 'invalid-input': !isValid && text.length > 0 }"
/>
<div class="input-hint">
<i class="el-icon-info-circle"></i>
<span>{{ inputHint }}</span>
</div>
</div>
<!-- 加载状态 -->
<el-loading
v-if="isLoading"
text="正在解析数据..."
class="loading-overlay"
></el-loading>
<!-- 二维码解析结果 - 只有数据有效时才显示 -->
<div v-if="isValid" class="result-section fade-in">
<div class="result-header">
<h3>解析结果</h3>
<el-tag
:type="result.ioType === 'in' ? 'success' : 'warning'"
size="small"
>
{{ result.ioType === 'in' ? '入库' : '出库' }}
</el-tag>
</div>
<el-descriptions :column="1" border class="result-table">
<el-descriptions-item label="出入库类型" :span="1">
{{ result.ioType === 'in' ? '入库' : '出库' }}
</el-descriptions-item>
<el-descriptions-item label="物料类型" :span="1">
{{ formatItemType(result.itemType) }}
</el-descriptions-item>
<el-descriptions-item label="物料信息" :span="1">
<ProductInfo v-if="result.itemType === 'product' || result.itemType === 'semi'" :productId="result.itemId" />
<RawMaterialInfo v-else-if="result.itemType === 'raw_material'" :materialId="result.itemId" />
<el-input v-else disabled v-model="result.itemId" placeholder="未知物料" :disabled="true" style="width: 100%;" />
</el-descriptions-item>
<el-descriptions-item label="数量" :span="1">
{{ result.quantity }}
</el-descriptions-item>
<el-descriptions-item label="计量单位" :span="1">
{{ result.unit }}
</el-descriptions-item>
</el-descriptions>
<div class="action-buttons">
<el-button
type="primary"
@click="handleSubmit"
:loading="isSubmitting"
icon="el-icon-check"
>
确认执行
</el-button>
<el-button
type="default"
@click="handleReset"
icon="el-icon-refresh-right"
>
重置
</el-button>
</div>
</div>
</div>
</div>
</template>
<script>
import ProductInfo from '@/components/KLPService/Renderer/ProductInfo';
import RawMaterialInfo from '@/components/KLPService/Renderer/RawMaterialInfo';
import { addStockIoDetail } from '@/api/wms/stockIoDetail';
export default {
data() {
return {
text: '',
isLoading: false,
isSubmitting: false,
inputHint: '请输入二维码内容,系统将自动解析',
};
},
components: {
ProductInfo,
RawMaterialInfo,
},
computed: {
result() {
try {
return JSON.parse(this.text);
} catch (error) {
return {};
}
},
// 验证数据是否有效
isValid() {
const requiredFields = ['ioType', 'itemType', 'itemId', 'quantity', 'unit'];
// 检查是否包含所有必要字段
const hasAllFields = requiredFields.every(field => this.result.hasOwnProperty(field));
// 检查出入库类型是否有效
const isIoTypeValid = this.result.ioType === 'in' || this.result.ioType === 'out';
// 检查物料类型是否有效
const isItemTypeValid = ['product', 'semi', 'raw_material'].includes(this.result.itemType);
// 检查数量是否为有效数字
const isQuantityValid = !isNaN(Number(this.result.quantity)) && Number(this.result.quantity) > 0;
return hasAllFields && isIoTypeValid && isItemTypeValid && isQuantityValid;
}
},
methods: {
handleChange(value) {
if (!value) {
this.inputHint = '请输入二维码内容,系统将自动解析';
return;
}
this.isLoading = true;
// 模拟解析延迟,提升用户体验
setTimeout(() => {
try {
const parsed = JSON.parse(value);
if (this.isValid) {
this.inputHint = '数据解析成功,可以提交操作';
this.$message.success({
message: '数据解析成功',
duration: 1500
});
// 解析成功后禁用输入框
// this.$refs.textarea.disabled = true;
console.log(this.$refs.textarea.disabled, '禁用输入框');
} else {
this.inputHint = '解析的数据格式不完整,请检查二维码是否正确';
this.$message.warning({
message: '数据格式不完整',
duration: 2000
});
}
} catch (error) {
this.inputHint = '无法解析数据,请确保输入的是有效的二维码内容';
this.$message.error({
message: '解析失败,请检查输入内容',
duration: 2000
});
} finally {
this.isLoading = false;
}
}, 500);
},
handleBlur() {
// 自动重新聚焦,方便连续扫描
this.$nextTick(() => {
this.$refs.textarea.focus();
});
},
handleSubmit() {
if (!this.isValid) return;
this.isSubmitting = true;
addStockIoDetail(this.result)
.then(res => {
this.$message.success({
message: '操作成功',
duration: 2000
});
// 提交成功后重置表单,方便下一次操作
this.handleReset();
})
.catch(err => {
this.$message.error({
message: '操作失败: ' + (err.message || '未知错误'),
duration: 3000
});
})
.finally(() => {
this.isSubmitting = false;
});
},
handleReset() {
this.text = '';
this.inputHint = '请输入二维码内容,系统将自动解析';
// 重置后重新聚焦
this.$nextTick(() => {
this.$refs.textarea.focus();
});
},
// 格式化物料类型显示文本
formatItemType(type) {
const typeMap = {
'product': '成品',
'semi': '半成品',
'raw_material': '原材料'
};
return typeMap[type] || '未知类型';
}
},
mounted() {
// 确保组件挂载后自动聚焦
this.$nextTick(() => {
this.$refs.textarea.focus();
});
}
}
</script>
<style scoped>
.qr-parser-container {
display: flex;
justify-content: center;
padding: 20px;
background-color: #f5f7fa;
min-height: 100vh;
}
.card {
width: 100%;
background: #fff;
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
overflow: hidden;
}
.card-header {
padding: 20px 24px;
border-bottom: 1px solid #e8e8e8;
}
.title {
margin: 0;
font-size: 20px;
color: #1f2329;
font-weight: 600;
}
.subtitle {
margin: 5px 0 0;
font-size: 14px;
color: #6b7280;
}
.input-section {
padding: 24px;
}
.el-input {
transition: all 0.3s ease;
}
.el-input.invalid-input .el-textarea__inner {
border-color: #f56c6c;
box-shadow: 0 0 0 2px rgba(245, 108, 108, 0.2);
}
.input-hint {
margin-top: 8px;
font-size: 12px;
color: #6b7280;
display: flex;
align-items: center;
}
.input-hint i {
margin-right: 4px;
font-size: 14px;
}
.result-section {
padding: 0 24px 24px;
}
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.result-header h3 {
margin: 0;
font-size: 16px;
color: #1f2329;
font-weight: 500;
}
.result-table {
margin-bottom: 24px;
}
.action-buttons {
display: flex;
gap: 12px;
justify-content: flex-end;
}
.loading-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
/* 淡入动画 */
.fade-in {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>