Files
xgy-oa/klp-ui/src/views/wms/print/scaner.vue

481 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<el-row class="container" :gutter="20">
<el-col :span="4" class="device-panel">
<div class="panel-header">
<h3>设备列表</h3>
<el-button size="mini" type="primary" @click="refreshDevices">刷新</el-button>
</div>
<div class="device-stats">
<span>总设备: {{ deviceStats.totalCount || 0 }}</span>
<span>活跃设备: {{ deviceStats.activeCount || 0 }}</span>
</div>
<ul class="device-list">
<li v-for="item in deviceList" :key="item.id" class="device-item" :class="{ active: item.isActive }">
<div class="device-status" :class="{ online: item.isActive }"></div>
<div class="device-info">
<span class="device-name">{{ item.name }}</span>
<span class="device-id">{{ item.id }}</span>
<span class="device-ip">{{ item.ip }}</span>
</div>
</li>
</ul>
</el-col>
<!-- <el-col :span="20" class="main-panel">
<div class="form-panel">
<el-form inline>
<el-form-item label="目标库位" class="form-item">
<WarehouseSelect size="mini" style="width: 200px;" v-model="defaultForm.warehouseId" />
</el-form-item>
<el-form-item label="挂载单据" class="form-item">
<el-select size="mini" filterable v-model="defaultForm.stockIoId" placeholder="请选择挂载单据" clearable class="form-input">
<el-option
v-for="item in masterList"
:key="item.stockIoId"
:label="item.stockIoCode"
:value="item.stockIoId"
/>
</el-select>
</el-form-item>
<el-form-item label="数量" class="form-item">
<el-input size="mini" v-model="defaultForm.quantity" class="form-input" />
</el-form-item>
<el-form-item label="记录类型" class="form-item">
<el-select size="mini" v-model="defaultForm.ioType" placeholder="请选择操作类型" clearable class="form-input">
<el-option label="入库" value="in" />
<el-option label="出库" value="out" />
</el-select>
</el-form-item>
<el-form-item label="批次号" class="form-item">
<el-input size="mini" v-model="defaultForm.batchNo" class="form-input" />
</el-form-item>
<el-button type="primary" :disabled="selectedList.length === 0" @click="handleBatchConfirm">批量确认</el-button>
</el-form>
</div>
<div class="table-panel">
<KLPTable height="100%" :data="messageList" style="width: 100%" class="message-table" stripe @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column prop="time" label="时间" width="150" align="center" />
<el-table-column prop="itemId" label="物料" align="center">
<template #default="scope">
<ProductInfo v-if="scope.row.itemType == 'product' || scope.row.itemType == 'semi'" :productId="scope.row.itemId" />
<RawMaterialInfo v-else :materialId="scope.row.itemId" />
</template>
</el-table-column>
<el-table-column label="存储位置" align="center">
<template #default="scope">
<ELWarehouseSelect v-model="scope.row.warehouseId" />
</template>
</el-table-column>
<el-table-column prop="stockIoId" label="挂载单据" align="center">
<template #default="scope">
<el-select v-model="scope.row.stockIoId" filterable placeholder="请选择挂载单据" clearable class="table-select">
<el-option
v-for="item in masterList"
:key="item.stockIoId"
:label="item.stockIoCode"
:value="item.stockIoId"
/>
</el-select>
</template>
</el-table-column>
<el-table-column prop="quantity" label="数量" align="center">
<template #default="scope">
<el-input v-model="scope.row.quantity" class="table-input" />
</template>
</el-table-column>
<el-table-column prop="ioType" label="操作类型" align="center">
<template #default="scope">
<el-select v-model="scope.row.ioType" placeholder="请选择操作类型" clearable class="table-select">
<el-option
v-for="dict in dict.type.stock_io_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column prop="batchNo" label="批次号" align="center">
<template #default="scope">
<el-input v-model="scope.row.batchNo" class="table-input" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="140" fixed="right">
<template #default="scope">
<el-button size="mini" type="text" @click="handleDelete(scope.row)">删除</el-button>
<el-button size="mini" type="text" @click="handleConfirm(scope.row)">确认</el-button>
</template>
</el-table-column>
</KLPTable>
</div>
</el-col> -->
</el-row>
</template>
<script>
import { listStockIo, scanInStock, scanOutStock } from '@/api/wms/stockIo';
import { addStockIoDetail } from '@/api/wms/stockIoDetail';
import WarehouseSelect from '@/components/WarehouseSelect/index.vue';
import ELWarehouseSelect from '@/components/KLPService/WarehouseSelect/index.vue';
import ProductInfo from '@/components/KLPService/Renderer/ProductInfo.vue';
import RawMaterialInfo from '@/components/KLPService/Renderer/RawMaterialInfo.vue';
export default {
components: {
WarehouseSelect,
ELWarehouseSelect,
ProductInfo,
RawMaterialInfo,
},
data() {
return {
deviceList: [],
deviceStats: {
totalCount: 0,
activeCount: 0
},
messageList: [],
socket: null,
defaultForm: {
ioType: '',
stockIoId: '',
quantity: 1,
warehouseId: '',
batchNo: '',
},
masterList: [],
selectedList: [],
};
},
dicts: ['stock_io_type'],
mounted() {
this.initSocket();
this.fetchMaster();
},
beforeDestroy() {
// 组件销毁时关闭WebSocket连接
if (this.socket) {
this.socket.close();
}
},
methods: {
initSocket() {
// 处理WebSocket连接
this.socket = new WebSocket("ws://localhost:9000/ws");
this.socket.onopen = () => {
console.log("Socket 连接已建立");
};
this.socket.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
// 处理设备列表数据
if (data.type === "allDevices") {
console.log("获取设备列表", data);
this.deviceList = data.devices || [];
this.deviceStats = {
totalCount: data.totalCount || 0,
activeCount: data.activeCount || 0
};
}
// 处理扫描消息
else if (data.type === "scanMessage") {
console.log("获取扫描消息", data);
this.messageList.push({
time: new Date().toLocaleString(),
itemId: data.itemId,
itemType: data.itemType,
stockIoId: this.defaultForm.stockIoId,
quantity: this.defaultForm.quantity,
ioType: this.defaultForm.ioType,
warehouseId: this.defaultForm.warehouseId,
batchNo: this.defaultForm.batchNo,
unit: '个'
});
}
} catch (error) {
console.error("解析WebSocket消息失败", error);
}
};
this.socket.onclose = () => {
console.log("Socket 连接已关闭");
// 连接关闭后尝试重连
setTimeout(() => {
this.initSocket();
}, 5000);
};
this.socket.onerror = (error) => {
console.error("Socket 错误", error);
};
},
// 刷新设备列表
refreshDevices() {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify({ type: "refreshDevices" }));
} else {
this.$message.warning("WebSocket连接未建立无法刷新设备列表");
// 尝试重新连接
this.initSocket();
}
},
fetchMaster() {
listStockIo({ pageSize: 9999, pageNum: 1 }).then(res => {
console.log("获取挂载单据", res);
this.masterList = res.rows || [];
}).catch(error => {
console.error("获取挂载单据失败", error);
this.$message.error("获取挂载单据失败");
});
},
handleDeviceChange(item) {
this.socket.send(
JSON.stringify({
type: "toggleDevice",
deviceId: item.id,
})
);
},
handleBatchConfirm() {
// 汇总会导致的库存变更,需要确认
console.log("批量确认", this.selectedList);
if (this.selectedList.length === 0) {
this.$message.warning("请选择需要确认的记录");
return;
}
// 批量处理逻辑
Promise.all(this.selectedList.map(item => this.processRecord(item)))
.then(() => {
this.$message.success("批量确认成功");
// 从列表中移除已确认的项
this.messageList = this.messageList.filter(
item => !this.selectedList.some(selected => selected.time === item.time)
);
this.selectedList = [];
})
.catch(error => {
console.error("批量确认失败", error);
this.$message.error("批量确认失败");
});
},
handleSelectionChange(selection) {
this.selectedList = selection;
},
handleDelete(row) {
this.messageList = this.messageList.filter(item => item.time !== row.time);
},
async handleConfirm(row) {
try {
await this.processRecord(row);
this.handleDelete(row);
this.$message.success('确认成功');
} catch (error) {
console.error("确认失败", error);
this.$message.error('确认失败');
}
},
// 处理单条记录的确认逻辑
async processRecord(row) {
// 插入记录
await this.insertRecord({...row, recordType: 1});
// 更新库存
await this.updateStock(row);
},
insertRecord(row) {
return addStockIoDetail(row);
},
updateStock(row) {
if (row.ioType === 'in') {
return scanInStock(row);
} else {
return scanOutStock(row);
}
}
},
};
</script>
<style scoped>
.container {
height: calc(100vh - 100px);
padding: 20px;
background-color: #f5f7fa;
}
.device-panel {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
padding: 0 !important;
height: calc(100vh - 40px);
display: flex;
flex-direction: column;
}
.panel-header {
padding: 15px 20px;
border-bottom: 1px solid #ebeef5;
background-color: #f8fafc;
border-radius: 8px 8px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.panel-header h3 {
margin: 0;
font-size: 16px;
color: #303133;
font-weight: 500;
}
/* 设备统计信息 */
.device-stats {
padding: 10px 20px;
border-bottom: 1px solid #ebeef5;
display: flex;
justify-content: space-between;
font-size: 13px;
color: #606266;
}
.device-list {
flex: 1;
overflow-y: auto;
margin: 0;
padding: 0;
}
.device-item {
display: flex;
align-items: center;
padding: 12px 20px;
border-bottom: 1px solid #ebeef5;
transition: background-color 0.3s;
cursor: pointer;
}
.device-item:hover {
background-color: #f5f7fa;
}
/* 设备状态指示器 */
.device-status {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #ccc;
margin-right: 12px;
}
.device-status.online {
background-color: #42b983; /* 绿色表示在线 */
}
.device-info {
flex: 1;
}
.device-name {
display: block;
font-weight: 500;
color: #303133;
margin-bottom: 4px;
}
.device-id {
display: block;
font-size: 12px;
color: #606266;
margin-bottom: 2px;
}
.device-ip {
display: block;
font-size: 12px;
color: #909399;
}
/* 活跃设备高亮 */
.device-item.active {
background-color: #f0f9eb;
}
.form-panel {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
padding: 15px 20px;
margin-bottom: 20px;
display: flex;
flex-wrap: wrap;
}
.filter-form {
display: flex;
align-items: center;
}
.form-item {
margin-bottom: 0 !important;
margin-right: 15px;
}
.form-input {
width: 180px;
}
.table-panel {
flex: 1;
height: calc(100vh - 220px);
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
overflow: hidden;
display: flex;
flex-direction: column;
}
.message-table {
flex: 1;
border-radius: 8px;
overflow: hidden;
}
.table-select {
width: 100%;
}
.table-input {
width: 100%;
}
.el-table::before {
height: 0;
}
.el-table th {
background-color: #f8fafc !important;
color: #606266;
font-weight: 500;
}
.el-table td, .el-table th {
padding: 12px 0;
}
</style>