562 lines
12 KiB
Vue
562 lines
12 KiB
Vue
<template>
|
||
<el-config-provider size="large">
|
||
<div class="container">
|
||
<div class="left" v-if="!hideLeftMenu">
|
||
<left-menu ref="leftMenuRef" @connectWsHandle="initWebSocket()" />
|
||
</div>
|
||
<div :class="{ view: !hideLeftMenu }">
|
||
<!-- <div class="wrapper">
|
||
<div class="animation"> -->
|
||
<router-view v-slot="{ Component }">
|
||
<!-- <transition :name="transitionName"> -->
|
||
<keep-alive :include="includeList">
|
||
<component :is="Component"></component>
|
||
</keep-alive>
|
||
<!-- </transition> -->
|
||
</router-view>
|
||
<!-- </div>
|
||
</div> -->
|
||
</div>
|
||
</div>
|
||
</el-config-provider>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { v4 as uuidv4 } from 'uuid'
|
||
import { ref, reactive, watch, onMounted } from "vue";
|
||
import { useRouter, useRoute } from "vue-router";
|
||
import leftMenu from "@/components/leftMenu.vue";
|
||
import useStorage from '@/utils/useStorage'
|
||
import { useUser } from "@/store/user.js";
|
||
import { dayjs, ElMessage } from "element-plus";
|
||
import { scanSendMessage } from '@/api/order/index'
|
||
import { useGlobal } from '@/store/global.js'
|
||
import { useSocket } from '@/store/socket.js'
|
||
import { usePrint } from '@/store/print.js'
|
||
|
||
const global = useGlobal()
|
||
const socketStore = useSocket()
|
||
const printStore = usePrint()
|
||
|
||
const leftMenuRef = ref(null)
|
||
|
||
const uuid = ref('')
|
||
|
||
function createUUID() {
|
||
if (!useStorage.get('uuid')) {
|
||
useStorage.set('uuid', uuidv4())
|
||
uuid.value = useStorage.get('uuid')
|
||
} else {
|
||
uuid.value = useStorage.get('uuid')
|
||
}
|
||
}
|
||
|
||
const store = useUser();
|
||
|
||
const route = useRoute();
|
||
|
||
const includeList = reactive([]);
|
||
const hideLeftMenu = ref(false);
|
||
watch(route, (to) => {
|
||
// console.log(to);
|
||
if (to.meta.keepAlive) {
|
||
includeList.push(to.name);
|
||
}
|
||
let arr = ["/login", "/device_list", "/add_device", "/add_label"];
|
||
if (arr.includes(to.path)) {
|
||
hideLeftMenu.value = true;
|
||
} else {
|
||
hideLeftMenu.value = false;
|
||
}
|
||
if (to.fullPath == '/login') {
|
||
if (ws.value != null) {
|
||
console.log('关闭ws');
|
||
ws.value.close()
|
||
ws.value = null
|
||
wsIsClose.value = true
|
||
}
|
||
} else {
|
||
// 打开ws
|
||
openWs()
|
||
}
|
||
});
|
||
|
||
// 登录成功后开始连接ws
|
||
function openWs() {
|
||
if (store.userInfo && store.userInfo.shopId && ws.value == null) {
|
||
initWebSocket()
|
||
// 更新print
|
||
printStore.init()
|
||
}
|
||
}
|
||
|
||
let transitionName = ref();
|
||
let router = useRouter();
|
||
router.beforeEach((to, from) => {
|
||
// 根据路由标记判断触发哪个动画
|
||
if (to.meta.index > from.meta.index) {
|
||
// 从右往左动画
|
||
transitionName.value = "slide-right";
|
||
} else if (to.meta.index < from.meta.index) {
|
||
// 从左往右动画
|
||
transitionName.value = "slide-left";
|
||
} else {
|
||
transitionName.value = "";
|
||
}
|
||
});
|
||
|
||
|
||
let ws = ref(null)
|
||
let wsIsClose = ref(false)
|
||
// 初始化websocket
|
||
function initWebSocket(wsUrl = import.meta.env.VITE_API_WSS) {
|
||
createUUID()
|
||
|
||
wsIsClose.value = false
|
||
ws.value = new WebSocket(wsUrl);
|
||
// console.log("websocket:", ws.value);
|
||
|
||
ws.value.onopen = function () {
|
||
console.log('wss连接成功');
|
||
socketStore.changeOnline(true)
|
||
|
||
// 清除心跳
|
||
clearInterval(heartbeatTimer.value)
|
||
heartbeatTimer.value = null
|
||
startheartbeat()
|
||
|
||
// 清除重连
|
||
clearInterval(reConnectTimer.value)
|
||
reConnectTimer.value = null
|
||
reConnectCount.value = 0
|
||
|
||
ws.value.send(JSON.stringify({
|
||
type: "connect",
|
||
shopId: store.userInfo.shopId,
|
||
clientId: uuid.value
|
||
}))
|
||
};
|
||
|
||
// 接收消息
|
||
ws.value.onmessage = function (e) {
|
||
// websocketonmessage(e);
|
||
let data = JSON.parse(e.data)
|
||
if (data.type == 'order') {
|
||
console.log('接收消息', data);
|
||
// 接收订单消息,打印小票
|
||
// printBill(data)
|
||
|
||
// 检测是否需要打印标签小票
|
||
// checkLabelPrint(data)
|
||
printStore.labelPrint(data)
|
||
}
|
||
};
|
||
|
||
// 连接发生错误
|
||
ws.value.onerror = function () {
|
||
console.log("WebSocket连接发生错误");
|
||
socketStore.changeOnline(false)
|
||
|
||
// 清除心跳
|
||
clearInterval(heartbeatTimer.value)
|
||
heartbeatTimer.value = null
|
||
|
||
// 手动关闭后不在执行自动连接任务
|
||
if (!wsIsClose.value) reConnect(wsUrl);
|
||
};
|
||
|
||
// 关闭
|
||
ws.value.onclose = function (e) {
|
||
console.log('ws关闭了', e);
|
||
socketStore.changeOnline(false)
|
||
|
||
// 清除心跳
|
||
clearInterval(heartbeatTimer.value)
|
||
heartbeatTimer.value = null
|
||
|
||
// 手动关闭后不在执行自动连接任务
|
||
if (!wsIsClose.value) reConnect(wsUrl);
|
||
};
|
||
}
|
||
|
||
// 启动心跳连接
|
||
let heartbeatTimer = ref(null)
|
||
function startheartbeat() {
|
||
heartbeatTimer.value = setInterval(() => {
|
||
ws.value.send(JSON.stringify({ type: 'heartbeat' }))
|
||
}, 10000)
|
||
}
|
||
|
||
// 重连最大次数5次
|
||
let reConnectCount = ref(0)
|
||
let reConnectTimer = ref(null)
|
||
function reConnect(wsUrl) {
|
||
if (reConnectTimer.value != null) return
|
||
reConnectTimer.value = setInterval(() => {
|
||
// 自动连接超过5次不在连接,需手动出发
|
||
// console.log('reConnectCount.value===', reConnectCount.value);
|
||
// if (reConnectCount.value >= 100) {
|
||
// console.log('重连超过5次,不在连接');
|
||
// clearInterval(reConnectTimer.value)
|
||
// reConnectTimer.value = null
|
||
// reConnectCount.value = 0
|
||
// wsIsClose.value = true
|
||
// ws.value.close()
|
||
// } else {
|
||
// }
|
||
reConnectCount.value++
|
||
initWebSocket(wsUrl)
|
||
}, 5000)
|
||
}
|
||
|
||
const nextCodeRef = ref('')
|
||
const lastTimeRef = ref('')
|
||
const codeRef = ref('')
|
||
// 通过扫码枪获取条形码
|
||
async function getBarCode(e) {
|
||
let nextCode = ''
|
||
let nextTime = ''
|
||
const lastTime = lastTimeRef.value
|
||
let code = codeRef.value
|
||
if (window.event) {
|
||
// IE
|
||
nextCode = e.keyCode
|
||
} else if (e.which) {
|
||
// Netscape/Firefox/Opera
|
||
nextCode = e.which
|
||
}
|
||
nextTime = new Date().getTime()
|
||
// 字母上方 数字键0-9 对应键码值 48-57; 数字键盘 数字键0-9 对应键码值 96-105
|
||
if (
|
||
(nextCode >= 48 && nextCode <= 57) ||
|
||
(nextCode >= 96 && nextCode <= 105)
|
||
) {
|
||
const codes = {
|
||
'48': 48,
|
||
'49': 49,
|
||
'50': 50,
|
||
'51': 51,
|
||
'52': 52,
|
||
'53': 53,
|
||
'54': 54,
|
||
'55': 55,
|
||
'56': 56,
|
||
'57': 57,
|
||
'96': 48,
|
||
'97': 49,
|
||
'98': 50,
|
||
'99': 51,
|
||
'100': 52,
|
||
'101': 53,
|
||
'102': 54,
|
||
'103': 55,
|
||
'104': 56,
|
||
'105': 57
|
||
}
|
||
nextCode = codes[nextCode]
|
||
nextTime = new Date().getTime()
|
||
}
|
||
// 第二次输入延迟两秒,删除之前的数据重新计算
|
||
if (nextTime && lastTime && nextTime - lastTime > 2000) {
|
||
code = String.fromCharCode(nextCode)
|
||
} else {
|
||
code += String.fromCharCode(nextCode)
|
||
}
|
||
|
||
|
||
// 保存数据
|
||
nextCodeRef.value = nextCode
|
||
lastTimeRef.value = nextTime
|
||
codeRef.value = code
|
||
// 键入Enter
|
||
if (e.which === 13) {
|
||
// 判断 code 长度(这里就获取到条码值了,以下业务自由发挥)
|
||
code = code.trim()
|
||
if (code.length == 13) {
|
||
console.log('A类条码:' + code);
|
||
} else if (code.length == 23) {
|
||
console.log('B类条码:' + code);
|
||
} else if (code.length == 0) {
|
||
console.log('请输入条码');
|
||
} else {
|
||
console.log('条码不合法:' + code);
|
||
try {
|
||
if (!global.isCallNumber || !code.length) return
|
||
await scanSendMessage({
|
||
outNumber: code,
|
||
shopId: store.userInfo.shopId
|
||
})
|
||
ElMessage.success('叫号成功')
|
||
leftMenuRef.value.updateCallNumber()
|
||
} catch (error) {
|
||
console.log(error);
|
||
}
|
||
}
|
||
|
||
// console.log('code', code);
|
||
// 键入回车务必清空code值
|
||
codeRef.value = ''
|
||
return false
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
document.addEventListener('keydown', (e) => {
|
||
getBarCode(e)
|
||
})
|
||
})
|
||
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
user-select: none;
|
||
}
|
||
|
||
:root {
|
||
--r: 24;
|
||
--g: 124;
|
||
--b: 170;
|
||
--r-lighter: calc(var(--r) + (255 - var(--r)) * 0.2);
|
||
--g-lighter: calc(var(--g) + (255 - var(--g)) * 0.2);
|
||
--b-lighter: calc(var(--b) + (255 - var(--b)) * 0.2);
|
||
|
||
--r-lighter2: calc(var(--r) + (255 - var(--r)) * 0.5);
|
||
--g-lighter2: calc(var(--g) + (255 - var(--g)) * 0.5);
|
||
--b-lighter2: calc(var(--b) + (255 - var(--b)) * 0.5);
|
||
|
||
--r-lighter3: calc(var(--r) + (255 - var(--r)) * 0.9);
|
||
--g-lighter3: calc(var(--g) + (255 - var(--g)) * 0.9);
|
||
--b-lighter3: calc(var(--b) + (255 - var(--b)) * 0.9);
|
||
|
||
--r-darker: calc(var(--r) * 0.8);
|
||
--g-darker: calc(var(--g) * 0.8);
|
||
--b-darker: calc(var(--b) * 0.8);
|
||
|
||
--primary-color: rgb(var(--r), var(--g), var(--b));
|
||
--primary-color-hover: rgb(var(--r-lighter3),
|
||
var(--g-lighter3),
|
||
var(--b-lighter3));
|
||
--el-color-primary: var(--primary-color) !important;
|
||
--el-button-hover-bg-color: var(--primary-color) !important;
|
||
--el-color-primary-light-3: rgb(var(--r-lighter),
|
||
var(--g-lighter),
|
||
var(--b-lighter)) !important;
|
||
--el-color-primary-dark-2: rgb(var(--r-darker),
|
||
var(--g-darker),
|
||
var(--b-darker)) !important;
|
||
--el-color-primary-light-5: rgb(var(--r-lighter2),
|
||
var(--g-lighter2),
|
||
var(--b-lighter2)) !important;
|
||
|
||
--el-font-size-base: 16px !important;
|
||
--el-message-close-size: var(--el-font-size-base) !important;
|
||
--el-component-size-large: 40px !important;
|
||
--el-mask-color: rgba(255, 255, 255, 0.6) !important;
|
||
}
|
||
|
||
@font-face {
|
||
font-family: "num";
|
||
src: url("@/assets/font/Ignotum-Regular.ttf");
|
||
}
|
||
|
||
html {
|
||
font-size: var(--el-font-size-base);
|
||
color: #333;
|
||
}
|
||
|
||
.el-dialog__headerbtn {
|
||
top: 10px !important;
|
||
}
|
||
|
||
.el-pagination {
|
||
justify-content: center;
|
||
}
|
||
|
||
.el-drawer__header {
|
||
// padding: 0 !important;
|
||
margin-bottom: 5px !important;
|
||
}
|
||
|
||
.el-table .warning-row {
|
||
--el-table-tr-bg-color: var(--el-color-warning-light-9);
|
||
}
|
||
|
||
.el-table .success-row {
|
||
--el-table-tr-bg-color: var(--el-color-success-light-9);
|
||
}
|
||
|
||
.el-drawer__body {
|
||
padding: 0 var(--el-drawer-padding-primary) !important;
|
||
}
|
||
|
||
.el-textarea {
|
||
font-size: var(--el-font-size-base) !important;
|
||
}
|
||
|
||
.el-popover__title {
|
||
font-size: var(--el-font-size-base) !important;
|
||
}
|
||
|
||
.el-dialog__header {
|
||
background-color: #555;
|
||
margin-right: 0 !important;
|
||
padding-bottom: 20px !important;
|
||
border-radius: var(--el-dialog-border-radius) var(--el-dialog-border-radius) 0 0;
|
||
}
|
||
|
||
.el-dialog__title {
|
||
color: #fff !important;
|
||
}
|
||
|
||
.el-button--large {
|
||
--el-button-size: var(--el-component-size-large) !important;
|
||
font-size: var(--el-font-size-base) !important;
|
||
}
|
||
|
||
.el-input--large {
|
||
font-size: var(--el-font-size-base) !important;
|
||
}
|
||
|
||
.el-dialog {
|
||
padding: 0 !important;
|
||
}
|
||
|
||
.el-dialog__body {
|
||
padding: calc(var(--el-dialog-padding-primary) + 10px) var(--el-dialog-padding-primary);
|
||
}
|
||
|
||
.el-dialog__header {
|
||
padding: var(--el-dialog-padding-primary);
|
||
padding-bottom: 10px;
|
||
margin-right: 16px;
|
||
}
|
||
|
||
.el-input__suffix {
|
||
font-size: 20px !important;
|
||
}
|
||
|
||
.empty {
|
||
display: flex;
|
||
justify-content: center;
|
||
padding-top: 100px;
|
||
}
|
||
|
||
/*定义滚动条高宽及背景
|
||
高宽分别对应横竖滚动条的尺寸*/
|
||
::-webkit-scrollbar {
|
||
width: 4px;
|
||
height: 2px;
|
||
}
|
||
|
||
/*定义滚动条轨道
|
||
内阴影+圆角*/
|
||
::-webkit-scrollbar-track {
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
/*定义滑块
|
||
内阴影+圆角*/
|
||
::-webkit-scrollbar-thumb {
|
||
border-radius: 10px;
|
||
background-color: #d3d3d3;
|
||
}
|
||
|
||
.scroll-x {
|
||
&::-webkit-scrollbar {
|
||
width: 0;
|
||
height: 0;
|
||
}
|
||
|
||
&::-webkit-scrollbar-track {
|
||
background-color: transparent;
|
||
}
|
||
|
||
&::-webkit-scrollbar-thumb {
|
||
border-radius: 0;
|
||
background-color: transparent;
|
||
}
|
||
}
|
||
|
||
.mt50 {
|
||
margin-top: 50px;
|
||
}
|
||
|
||
.container {
|
||
display: flex;
|
||
|
||
.view {
|
||
flex: 1;
|
||
height: 100vh;
|
||
padding: var(--el-font-size-base);
|
||
background-color: #efefef;
|
||
}
|
||
}
|
||
|
||
.content {
|
||
display: flex;
|
||
height: 100%;
|
||
}
|
||
|
||
.card {
|
||
background-color: #fff;
|
||
border-radius: 10px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.wrapper {
|
||
overflow-x: hidden;
|
||
|
||
.animation {
|
||
display: flex;
|
||
width: 200%;
|
||
|
||
&>div {
|
||
width: 50%;
|
||
}
|
||
|
||
.slide-left-enter-active,
|
||
.slide-left-leave-active,
|
||
.slide-right-enter-active,
|
||
.slide-right-leave-active {
|
||
transition: transform 0.3s;
|
||
}
|
||
|
||
.slide-right-enter-from {
|
||
transform: translateX(0);
|
||
}
|
||
|
||
.slide-right-enter-to {
|
||
transform: translateX(-100%);
|
||
}
|
||
|
||
.slide-right-leave-from {
|
||
transform: translateX(0);
|
||
}
|
||
|
||
.slide-right-leave-to {
|
||
transform: translateX(-100%);
|
||
}
|
||
|
||
.slide-left-enter-from {
|
||
transform: translateX(-200%);
|
||
}
|
||
|
||
.slide-left-enter-to {
|
||
transform: translateX(-100%);
|
||
}
|
||
|
||
.slide-left-leave-from {
|
||
transform: translateX(0);
|
||
}
|
||
|
||
.slide-left-leave-to {
|
||
transform: translateX(100%);
|
||
}
|
||
}
|
||
}
|
||
</style>
|