You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

363 lines
8.9 KiB

<template>
<div>
<div class="status-bar">
<div class="goBack" @click="handleBack"><i class="el-icon-arrow-left"></i>上一页</div>
<div class="goBack">{{ functionTitle }}</div>
<div class="network" style="color: #fff" @click="$router.push({ path: '/' })">🏠首页</div>
</div>
<div class="input-section">
<!-- PO号输入 -->
<div v-if="processFlag === 1">
<div class="input-group">
<div class="input-group">
<label>PO号</label>
<div class="input-with-scan">
<input v-model="poNo" placeholder="请输入或扫描PO号" @keyup.enter="loadMaterials" />
</div>
</div>
<div class="input-group">
<button @click="loadMaterials" class="scan-btn">确认</button>
</div>
</div>
<div class="materials-section" v-if="materialList.length">
<div class="material-list">
<div v-for="(material, index) in materialList" :key="index" class="material-item"
:class="{ selected: selectedMaterial && selectedMaterial.partNo === material.partNo }"
@click="goToDetail(material)">
<div class="material-info">
<div class="part-no">{{ material.partNo }}</div>
<div class="part-desc">{{ material.desc }}</div>
<div class="qty-info">
需求: {{ material.qty }} | 已发: {{ material.recvQty }} | 剩余: {{ material.thisRecvQty }}
</div>
</div>
<div class="material-status">
<span v-if="material.thisRecvQty > 0" class="status-pending">待发料</span>
<span v-else class="status-complete">已完成</span>
</div>
</div>
</div>
</div>
</div>
<!-- 扫描标签/发料详情 -->
<div class="scan-section" v-if="processFlag === 2">
<div class="label-info" v-if="labelInfo">
<div class="info-row"><span class="label">物料编码:</span><span class="value">{{ labelInfo.partNo }}</span></div>
<div class="info-row"><span class="label">批次号:</span><span class="value">{{ labelInfo.batchNo }}</span></div>
<div class="info-row"><span class="label">可用数量:</span><span class="value">{{ labelInfo.availableQty }}</span>
</div>
</div>
<div class="qty-input" v-if="labelInfo">
<div class="input-group">
<label>发料数量</label>
<input v-model="issueQty" type="number"
:max="Math.min(selectedMaterial.thisRecvQty, labelInfo.availableQty)" placeholder="请输入发料数量" />
</div>
<div class="input-group">
<label>备注</label>
<el-input type="textarea" v-model="remark" placeholder="可选" />
</div>
<button @click="confirmIssue" class="confirm-btn" :disabled="!issueQty">确认发料</button>
</div>
</div>
<!-- 消息提示 -->
<div class="message" v-if="message" :class="messageType">{{ message }}</div>
</div>
</div>
</template>
<script>
import { getPoList } from '@/api/po/po.js'
export default {
name: 'DirectIssue',
props: {
functionTitle: {
type: String,
default: '',
},
},
data() {
return {
processFlag: 1, // 1=PO号输入, 2=物料列表, 3=扫描标签/发料详情
poNo: '',
materialList: [],
selectedMaterial: null,
scannedLabel: '',
labelInfo: null,
issueQty: null,
remark: '',
message: '',
messageType: 'info',
}
},
methods: {
handleBack() {
if (this.processFlag === 2) {
this.processFlag = 1
} else if (this.processFlag === 1) {
this.$emit('back')
}
},
async loadMaterials() {
if (!this.poNo) {
this.showMessage('请输入PO号', 'error')
return
}
// 调用API获取PO物料
try {
const { data } = await getPoList({
poNumber: this.poNo,
site: localStorage.getItem('site'),
})
if (data.code === 0 && data.rows && data.rows.length > 0) {
this.materialList = data.rows
} else {
this.showMessage(data.msg || '未找到PO物料', 'warning')
}
} catch (e) {
this.showMessage('网络错误', 'error')
}
},
goToDetail(material) {
if (material.thisRecvQty <= 0) {
this.showMessage('该物料已发料完成', 'warning')
return
}
this.selectedMaterial = material
this.scannedLabel = ''
this.labelInfo = {
partNo: this.selectedMaterial.partNo,
batchNo: 'BATCH001',
availableQty: 100,
}
this.issueQty = null
this.processFlag = 2
},
resetPO() {
this.poNo = ''
this.materialList = []
this.selectedMaterial = null
this.scannedLabel = ''
this.labelInfo = null
this.issueQty = null
this.processFlag = 1
},
parseMaterialLabel() {
// TODO: 调用解析标签API,获取labelInfo
this.labelInfo = {
partNo: this.selectedMaterial.partNo,
batchNo: 'BATCH001',
availableQty: 100,
}
this.issueQty = null
this.showMessage('标签解析成功', 'success')
},
confirmIssue() {
if (!this.issueQty || this.issueQty <= 0) {
this.showMessage('请输入有效的发料数量', 'error')
return
}
// TODO: 调用发料API
this.showMessage('发料成功', 'success')
// 刷新物料列表
this.loadMaterials()
this.selectedMaterial = null
this.scannedLabel = ''
this.labelInfo = null
this.issueQty = null
this.processFlag = 1
},
showMessage(text, type = 'info') {
this.message = text
this.messageType = type
setTimeout(() => {
this.message = ''
}, 2000)
},
},
}
</script>
<style scoped>
.input-section {
background: white;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.reset-btn {
background: #17b3a3;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.input-group {
margin-bottom: 15px;
}
.input-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #333;
}
.input-with-scan {
display: flex;
gap: 10px;
}
.input-with-scan input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.scan-btn,
.confirm-btn {
background: #17b3a3;
color: white;
border: none;
padding: 10px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
white-space: nowrap;
}
.scan-btn:hover,
.confirm-btn:hover {
background: #13998c;
}
.confirm-btn:disabled {
background: #6c757d;
cursor: not-allowed;
}
.material-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.material-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border: 1px solid #ddd;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s;
}
.material-item.selected {
border-color: #007bff;
background-color: #e3f2fd;
}
.material-info {
flex: 1;
}
.part-no {
font-weight: bold;
font-size: 16px;
color: #333;
margin-bottom: 4px;
}
.part-desc {
color: #666;
font-size: 14px;
margin-bottom: 4px;
}
.qty-info {
font-size: 12px;
color: #666;
}
.material-status {
text-align: right;
}
.status-pending {
background: #ffc107;
color: #212529;
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
}
.status-complete {
background: #28a745;
color: white;
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
}
.label-info {
background: #f8f9fa;
border-radius: 6px;
padding: 15px;
margin-bottom: 15px;
}
.info-row {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
padding: 5px 0;
border-bottom: 1px solid #eee;
}
.info-row:last-child {
border-bottom: none;
margin-bottom: 0;
}
.info-row .label {
font-weight: bold;
color: #666;
}
.info-row .value {
color: #333;
}
.qty-input {
margin-top: 15px;
}
.qty-input input[type='number'] {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.message {
margin-top: 10px;
padding: 8px 12px;
border-radius: 4px;
font-weight: bold;
}
.message.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.message.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.message.warning {
background: #fff3cd;
color: #856404;
border: 1px solid #ffeaa7;
}
.message.info {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
.confirm-btn {
width: 100%;
}
.scan-btn {
width: 100%;
}
</style>