Files
shzg_projectManage/src/views/Inventory/task/autoInventory.vue
2026-02-28 09:35:39 +08:00

560 lines
18 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>
<div class="app-container">
<el-header style="display: flex;align-items: center;justify-content: space-between;background-color: #9ebee5;">
<div>{{ dataList[0] || '等待连接' }}</div>
</el-header>
<div style="display: flex;">
<div style="width: 300px;display: flex;flex-direction: column;align-items: center;padding: 20px 0;">
<el-avatar :size="100" :src="avatar" />
<el-button type="primary" style="margin-top: 20px;" @click="startScan" v-show="matchStatus == 0">开始盘点</el-button>
<el-button type="success" style="margin-top: 20px;" @click="startMatch" v-show="matchStatus == 1">开始匹配</el-button>
<el-button type="primary" style="margin-top: 20px;" @click="stopTask" v-show="matchStatus == 1">停止盘点</el-button>
<el-button type="info" style="margin-top: 20px;" @click="resetMatch" v-show="matchStatus == 2">重新盘点</el-button>
</div>
<div style="width: 300px;font-size: 18px;padding: 30px 0;">
<div>
设备IP{{ deviceObj.ipAddress || "暂无" }}
</div>
<div style="margin-top: 20px;">
设备端口{{ deviceObj.port || "暂无" }}
</div>
<div style="margin-top: 20px;">
总条目{{ scanTotal || '暂无' }}
</div>
</div>
<div style="width: 300px;font-size: 18px;padding: 30px 0;">
<div>
所属仓库{{ deviceObj.warehouseName || "暂无" }}
</div>
<div style="margin-top: 20px;">
所属场景{{ deviceObj.sceneName || "暂无" }}
</div>
</div>
<div style="flex: 1;">
<div id="chartsMain" style="height: 300px;padding: 20px 0;"></div>
</div>
</div>
<el-divider style="margin-top: 0;" />
<el-tabs type="border-card" v-model="activeTabName" @tab-click="changeTab">
<el-tab-pane label="扫码列表" name="扫码">
<div>共扫描到{{ dataList.length > 0 ? dataList.length - 1 : 0 }}</div>
<div v-for="(item, index) in dataList" :key="index" style="text-align: center;">
<div v-show="index != 0">
{{ item }}
</div>
</div>
<div v-show="dataList.length == 1">
<el-empty description="暂无数据" />
</div>
</el-tab-pane>
<el-tab-pane label="匹配列表" name="匹配" :disabled="matchStatus < 2">
<el-select
v-model="status"
class="m-2"
placeholder="Select"
size="large"
style="width: 200px"
@change="changeStatus"
>
<el-option
v-for="item in statusList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<div style="margin: 20px 0">
<el-text v-show="status == 0">当前为正常数据</el-text>
<el-text v-show="status == 1">当前为未扫描到但数据库里有的数据</el-text>
<el-text v-show="status == 2">当前为已扫描到但数据库里没有的数据</el-text>
</div>
<el-table :data="matchData" border empty-text="暂无数据" show-overflow-tooltip show-summary
:summary-method="getSummaries" v-if="status == 0 || status == 1">
<el-table-column label="序号" align="center" type="index" width="70" />
<el-table-column label="单据号" align="center" prop="billNo" width="180" />
<el-table-column label="库存类型" align="center" prop="operationTypeName" width="150" />
<el-table-column label="订单编号" align="center" prop="sapNo" width="150" />
<el-table-column label="项目号" align="center" prop="xmNo" width="150" />
<el-table-column label="项目描述" align="center" prop="xmMs" width="150" />
<el-table-column label="物料号" align="center" prop="wlNo" width="100" />
<el-table-column label="物料描述" align="center" prop="wlMs" width="150" />
<el-table-column label="供应商名称" align="center" prop="gysMc" width="150" />
<el-table-column label="合同单价" align="center" prop="htDj" />
<el-table-column label="总计" align="center" prop="totalAmount" />
<el-table-column label="单位" align="center" prop="dw" />
<el-table-column label="实际入库数量" align="center" prop="realQty" width="120" />
<el-table-column label="库位码" align="center" prop="pcode" width="120" />
<el-table-column label="托盘码" align="center" prop="trayCode" />
<el-table-column label="身份码" align="center" prop="entityId" width="150" />
<el-table-column label="物资类型" align="center" prop="wlTypeName" />
<el-table-column label="场景" align="center" prop="sceneName" width="150" />
<el-table-column label="所属大库" align="center" prop="parentWarehouseName" width="150" />
<el-table-column label="所属小库" align="center" prop="warehouseName" width="150" />
<el-table-column label="入库类型" align="center" prop="operationTypeName" width="150" />
<el-table-column label="入库时间" align="center" prop="operationTime" width="150" />
<el-table-column label="还料时间" align="center" prop="returnTime" width="150">
<template #default="scope">
<span>{{ parseTime(scope.row.returnTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
</template>
</el-table-column>
<el-table-column label="库龄" align="center" prop="stockAge" />
<el-table-column label="备注" align="center" prop="remark" width="150" />
<el-table-column label="一次封样号" align="center" prop="fycde1" width="150" />
<el-table-column label="二次封样号" align="center" prop="fycde2" width="150" />
<!-- <el-table-column prop="count" label="操作" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="View" @click="handleView(scope.row)">查看</el-button>
</template>
</el-table-column> -->
</el-table>
<el-table :data="matchData" border empty-text="暂无数据" v-if="status == 2">
<el-table-column prop="rkPcode" label="存放位置" />
</el-table>
<div style="margin-top: 20px;width: 100%;display: flex;align-items: center;justify-content: flex-end;">
<div style="margin-right: 20px;color: #606266;"> {{ matchTotal }}</div>
<el-pagination background layout="prev, pager, next" :total="matchTotal" @change="chagePage" />
</div>
</el-tab-pane>
</el-tabs>
<el-dialog v-model="taskView" title="详情" width="85%" draggable>
<el-table :data="viewData" border empty-text="暂无数据">
<el-table-column prop="sapNo" label="订单" />
<el-table-column prop="wlNo" label="物料号" />
<el-table-column prop="wlMs" label="物料描述" />
<el-table-column prop="realQty" label="数量">
<template #default="scope">{{ scope.row.realQty }}({{ scope.row.dw }})</template>
</el-table-column>
<el-table-column prop="xmMs" label="项目名称" />
<el-table-column prop="gysMc" label="供应商" />
<el-table-column prop="cangkuName" label="所属仓库" />
<el-table-column prop="rkTime" label="入库时间">
<template #default="scope">{{ parseTime(scope.row.rkTime, '{y}-{m}-{d}') }}</template>
</el-table-column>
</el-table>
<template #footer>
<span class="dialog-footer">
<el-button @click="taskView = false">关闭</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup name="AutoInventory">
import { ElLoading } from 'element-plus';
import { getTaskCount, getScan, getMatch, getChart, stopScan, getTotalStatistics, getStockList, inventoryList } from "@/api/Inventory/autoInventory"
import { ref, reactive, onMounted, onBeforeUnmount, markRaw } from "vue";
import avatar from "@/assets/images/avatar.jpg"
import { useRoute } from 'vue-router';
import * as echarts from 'echarts';
const taskId = ref(null)
const taskView = ref(false)
const viewData = ref([])
const handleView = (row) => {
// let obj = {
// pageNum: 1,
// pageSize: 100,
// pcode: row.rkPcode
// }
console.log(row.rkPcode)
getStockList(row.rkPcode).then(res => {
console.log(res)
viewData.value = res.data
taskView.value = true
})
}
// 获取总条数
const scanTotal = ref("")
const getTotal = () => {
let obj = {
taskId: taskId.value
}
getTaskCount(obj).then(res => {
scanTotal.value = res.data
})
}
// 开始盘点
const startScan = async (formEl) => {
let obj = {
deviceId: deviceObj.value.deviceId
}
getScan(obj).then(res => {
console.log('开始盘点')
localStorage.setItem("scanTotal", scanTotal.value)
})
}
let chartInt = ref("")
const status = ref(0) // 盘点状态
const statusList = ref(
[
{
label: '正常',
value: 0
},
{
label: '未扫到',
value: 1
},
{
label: '无数据',
value: 2
}
]
) // 盘点状态
const isSend = ref(false) // 是否正在发送
const activeTabName = ref("扫码") // tab高亮
const matchStatus = ref(0) //是否可以点击匹配列表
const matchData = ref([]) // 匹配列表
const isConnect = ref(true) // 是否自动重新重连websocket
const deviceObj = ref({
deviceId: 0,
ipAddress: 0,
port: 0,
warehouseId: 0,
});
const matchTotal = ref(0);
const queryParams = ref({
pageNum: 1,
pageSize: 10,
});
let loadingInstance = null; //
const warehouseList = ref([
{value: 1, label: '1号仓库'},
{value: 2, label: '2号仓库'},
{value: 3, label: '3号仓库'},
{value: 4, label: '4号仓库'},
{value: 5, label: '5号仓库'}
]);
// 开始匹配
const startMatch = () => {
queryParams.value.pageNum = 1
status.value = 0
activeTabName.value = "匹配"
getMatchData()
}
// 重新盘点
const resetMatch = () => {
activeTabName.value = "扫码"
matchStatus.value = 0
scanTotal.value = ""
let text = dataList.value[0]
dataList.value = [text]
}
// 开始匹配/结束盘点/获取盘点列表
const getMatchData = () => {
let list = JSON.parse(JSON.stringify(dataList.value))
let arr = list.splice(1)
let obj = {
ids: arr,
deviceId: deviceObj.value.deviceId,
taskId: taskId.value
}
getMatch(obj).then(res => {
let text = dataList.value[0]
dataList.value = [text]
localStorage.removeItem("scanTotal")
localStorage.setItem("dataList", JSON.stringify(dataList.value))
matchStatus.value = 2
getResultList()
// 渲染图表
let chartObj = {
ids: arr,
}
getChart(chartObj).then(res => {
console.log(res.data)
chartInt.value.setOption({
title: {
text: '本次共计盘点到' + res.data.total + "个库位(库位货品数如下表)",
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: res.data.xdata,
axisTick: {
alignWithLabel: true
}
},
yAxis: {
type: 'value'
},
series: [
{
data: res.data.ydata,
type: 'bar',
showBackground: true,
backgroundStyle: {
color: 'rgba(180, 180, 180, 0.2)'
}
}
]
})
})
})
}
// 匹配结果
const getResultList = () => {
let obj = {
pageNum: queryParams.value.pageNum,
pageSize: queryParams.value.pageSize,
status: status.value,
warehouseCode: deviceObj.value.warehouseCode,
sceneId: deviceObj.value.sceneId,
}
if (status.value != 0) {
matchData.value = null
matchTotal.value = 0
return;
}
inventoryList(obj).then(res => {
matchData.value = res.rows
matchTotal.value = res.total
})
}
// 切换tab
const changeTab = (tab) => {
if (tab.props.label == '匹配列表') {
queryParams.value.pageNum = 1
status.value = 0
getResultList()
}
}
// 点击翻页
const chagePage = (e) => {
queryParams.value.pageNum = e
getResultList()
}
// 切换盘点状态
const changeStatus = function (e) {
queryParams.value.pageNum = 1
status.value = e
getResultList()
}
// websocket连接
let webSocketPort = import.meta.env.VITE_APP_BASE_API + '/ws/socket'
const dataList = ref([]); // 用于展示从 WebSocket 获取的消息
let ws = null; // WebSocket 实例
let reconnectTimeout = null; // 重连超时控制
// 建立 WebSocket 连接
const connectWebSocket = () => {
ws = new WebSocket(webSocketPort); // 替换为实际的 WebSocket 服务 URL
ws.onopen = () => {
console.log("WebSocket 连接已建立");
send()
};
ws.onmessage = (event) => {
console.log(event.data); // 接收到服务器推送的消息
if (localStorage.getItem("scanTotal")) {
scanTotal.value = localStorage.getItem("scanTotal")
dataList.value = JSON.parse(localStorage.getItem("dataList"))
console.log("获取", dataList.value)
}
if (!(dataList.value.some(e => e == event.data))) {
if (dataList.value.findIndex(item => item.includes("连接")) >= 0 && event.data.includes("连接")) {
let signIndex = dataList.value.findIndex(item => item.includes("连接"))
dataList.value[signIndex] = event.data
} else {
dataList.value.push(event.data)
}
localStorage.setItem("dataList", JSON.stringify(dataList.value))
console.log("存储", dataList.value)
}
if (dataList.value.length > 1) {
matchStatus.value = 1
}
};
ws.onerror = (error) => {
console.error("WebSocket 发生错误", error);
};
ws.onclose = (event) => {
console.log("WebSocket 连接已关闭", event.code);
if (isConnect.value) {
handleReconnect(); // WebSocket 连接关闭时尝试重连
}
};
};
// 处理 WebSocket 连接断开后的重连
const handleReconnect = () => {
// 如果已经在重连,避免重复重连
if (reconnectTimeout) {
return;
}
reconnectTimeout = setTimeout(() => {
console.log("正在尝试重新连接 WebSocket...");
connectWebSocket();
reconnectTimeout = null; // 重连后清除重连超时
}, 5000); // 5 秒后重连
};
// 获取缓存数据
const deviceInfo = localStorage.getItem("deviceInfo")
if (deviceInfo) {
deviceObj.value = JSON.parse(deviceInfo)
loadingInstance = ElLoading.service({
background: 'rgba(0, 0, 0, 0.7)',
text: '正在连接设备...',
});
connectWebSocket(); // 组件加载时连接 WebSocket
}
// 获取总计
const totalMoney = ref(null)
const pcodeCount = ref(null)
const sumQty = ref(null)
function getSummaries(param) {
const { columns, data } = param;
const sums = [];
columns.forEach((column, index) => {
if (index === 1) { // 第一列不进行合计操作,通常是序列号或选择框等非数值列。
sums[index] = '总计'; // 这里可以设置为其他文字或空字符串。
return;
} else if (index === 2) { // 第二列是金额列,进行求和操作。
sums[index] = '总金额:' + totalMoney.value;
} else if (index === 3) {
sums[index] = '共存于:' + pcodeCount.value + '个库位'
} else if (index === 4) {
sums[index] = '总数:' + sumQty.value
}
})
return sums
}
function getSumInfo() {
//统计信息
console.log(deviceObj.value,'deviceObj');
let rkInfo = JSON.parse(JSON.stringify({
warehouseCode: deviceObj.value.warehouseCode,
sceneId: deviceObj.value.sceneId,
}))
getTotalStatistics(rkInfo).then(response => {
totalMoney.value = response.data.totalAmount
pcodeCount.value = response.data.locationCount
sumQty.value = response.data.totalQuantity
});
}
getSumInfo()
// 使用 Websocket 发送消息
const send = () => {
let obj = {
deviceId: deviceObj.value.deviceId,
ip: deviceObj.value.ipAddress,
port: deviceObj.value.port
}
loadingInstance.close();
isSend.value = true
ws.send(JSON.stringify(obj))
}
// 停止盘点
const stopTask = () => {
let obj = {
deviceId: deviceObj.value.deviceId
}
stopScan(obj).then(res => {
console.log("停止盘点")
matchStatus.value = 0
let text = dataList.value[0]
dataList.value = [text]
localStorage.removeItem("scanTotal")
localStorage.setItem("dataList", JSON.stringify(dataList.value))
})
}
const route = useRoute()
onMounted(() => {
chartInt.value = markRaw(echarts.init(document.getElementById('chartsMain')))
taskId.value = route.query.taskId
getTotal()
// chartInt.value.setOption({
// title: {
// text: '本次共计盘点到' + "10个库位(库位货品数如下表)",
// },
// tooltip: {
// trigger: 'axis',
// axisPointer: {
// type: 'shadow'
// }
// },
// grid: {
// left: '3%',
// right: '4%',
// bottom: '3%',
// containLabel: true
// },
// xAxis: {
// type: 'category',
// // data: res.data.xdata,
// data: ['G02-0102', 'G02-0101', 'G02-0103', 'G02-0104', 'G02-0105', 'G02-0106', 'G02-0107'],
// axisTick: {
// alignWithLabel: true
// }
// },
// yAxis: {
// type: 'value'
// },
// series: [
// {
// // data: res.data.ydata,
// data: [120, 200, 150, 80, 70, 110, 130],
// type: 'bar',
// showBackground: true,
// backgroundStyle: {
// color: 'rgba(180, 180, 180, 0.2)'
// }
// }
// ]
// })
});
onBeforeUnmount(() => {
isConnect.value = false
if (ws) {
ws.close(); // 组件销毁时关闭 WebSocket 连接
ws = null
}
});
</script>
<style scoped>
.text{
font-size: 18px;
padding: 30px 0;
}
.demo-tabs > .el-tabs__content {
padding: 32px;
color: #6b778c;
font-size: 32px;
font-weight: 600;
}
</style>