zh 10 mesi fa
parent
commit
6e1129c8d5

+ 16 - 0
src/api/login/index.ts

@@ -17,3 +17,19 @@ export function getUserInfo() {
     method: 'get',
   })
 }
+
+export function getUserIdByCert(code : string) {
+  return request({
+    url: '/api/user/getUserIdByCert',
+    method: 'post',
+    data:JSON.stringify(code)
+  })
+}
+
+export function getUserNameById(userId : number) {
+  return request({
+    url: '/api/user/getUserNameById',
+    method: 'post',
+    params: { userId: userId }
+  })
+}

+ 15 - 0
src/api/scan/index.ts

@@ -0,0 +1,15 @@
+// @ts-ignore
+import request from '@/utils/request'
+// @ts-ignore
+import { messageType } from '@/types/scan.ts'
+/**
+ * 接收扫描设备发送的扫描记录
+ * @param userId
+ */
+export function receive(message : messageType) {
+  return request({
+    url: '/api/erp/scan-log/receive',
+    method: 'post',
+    data: JSON.stringify(message)
+  })
+}

+ 6 - 2
src/router/index.ts

@@ -49,8 +49,12 @@ const routes: RouteRecordRaw[] = [
     meta:{title:'复核还库'},
     component: () => import('@/views/outbound/check/moveStock/index.vue')
   },
-
-
+  {
+    path: '/piece-dashboard',
+    name: 'PieceDashboard',
+    meta:{title:'计件面板'},
+    component: () => import('@/views/piece/dashboard/index.vue')
+  },
 ];
 
 // 创建路由实例

+ 17 - 0
src/types/scan.ts

@@ -0,0 +1,17 @@
+/**
+ * 接收扫描设备发送的扫描记录
+ *
+ * @param warehouse 仓库
+ * @param deliveryNo 快递单号
+ * @param machine mac地址
+ * @param operator 操作员
+ * @param operationTime 操作时间
+ * @returns
+ */
+export interface messageType {
+  warehouse: string;
+  deliveryNo: string;
+  machine: string;
+  operator: string;
+  operationTime: string;
+}

+ 22 - 0
src/utils/android.ts

@@ -103,4 +103,26 @@ export function scanError(){
 
   }
 }
+/**
+ * 保存mac地址 - piece
+ */
+export function saveMacAddress(mac : string) : boolean {
+  try {
+    // @ts-ignore
+    return window.android.saveMacAddress(mac)
+  }catch (e){
+    return false
+  }
+}
+/**
+ * 读取mac地址 - piece
+ */
+export function readMacAddress(): string {
+  try {
+    // @ts-ignore
+    return window.android.readMacAddress()
+  }catch (e){
+    return ''
+  }
+}
 

+ 1 - 0
src/views/index.vue

@@ -6,6 +6,7 @@
     <div class="home" @click="onRouter('picking-aisle')">进入巷道拣货</div>
     <div class="home" @click="onRouter('blind-receiving')">进入盲收</div>
     <div class="home" @click="onRouter('check-move-stock')">进入反拣还库</div>
+    <div class="home" @click="onRouter('piece-dashboard')">进入计件面板</div>
   </div>
 </template>
 <script setup>

+ 316 - 0
src/views/piece/dashboard/index.vue

@@ -0,0 +1,316 @@
+<template>
+  <van-row class="container">
+    <van-col style="background-color: #b0b2b2" span="6">
+      <van-row justify="center" :gutter="[0, 23]" style="margin-top: 23px">
+        <van-col span="24"><span>用户ID:</span> {{ userInfo.userId }}</van-col>
+        <van-col span="24"><span>用户姓名:</span> {{ userInfo.name }}</van-col>
+        <van-col span="24"><span>IP:</span> {{ ipAddress || '获取中...' }}</van-col>
+        <van-col span="24"><span>MAC:</span> {{ mac }}</van-col>
+      </van-row>
+    </van-col>
+    <van-col span="18" class="table-container">
+      <van-cell-group class="custom-cell-group">
+        <van-cell title="信息提示:" :value="message" />
+      </van-cell-group>
+      <div class="waterfall-table">
+        <!-- 固定表头 -->
+        <div class="table-header">
+          <div class="col index">序号</div>
+          <div class="col time">时间</div>
+          <div class="col operator">操作人</div>
+          <div class="col content">内容</div>
+        </div>
+
+        <!-- 表格主体 -->
+        <van-list
+          v-model:loading="loading"
+          :finished="finished"
+          finished-text="没有更多了"
+          @load="onLoad"
+          class="table-body">
+          <van-cell
+            v-for="(item, index) in list"
+            :key="item.id"
+            class="table-row"
+          >
+            <span class="col index">{{ index + 1 }}</span>
+            <span class="col time">{{ item.time }}</span>
+            <span class="col operator">{{ item.operator }}</span>
+            <span class="col content">{{ item.content }}</span>
+          </van-cell>
+        </van-list>
+      </div>
+    </van-col>
+  </van-row>
+  <van-button class="add" icon="setting-o" type="primary" @click="_openSetting" />
+  <van-dialog v-model:show="setting.show" title="设置" width="410px" :before-close="_verifyMac"
+              show-cancel-button @confirm="_saveMac" @closed="_closeSetting">
+    <van-notice-bar
+      wrapable
+      left-icon="volume-o"
+      :scrollable="false"
+      :text="hintMessage"
+    />
+    <van-cell-group inset>
+      <van-field
+        v-model="setting.mac"
+        label="MAC地址"
+        placeholder="请输入MAC地址" />
+      <van-field
+        v-model="setting.password"
+        type="password"
+        label="密码"
+        placeholder="设置密码"
+      />
+    </van-cell-group>
+  </van-dialog>
+</template>
+
+<script setup>
+import { onMounted, onUnmounted, ref } from 'vue'
+import { closeListener, openListener, scanInit } from '@/utils/keydownListener'
+import { saveMacAddress, readMacAddress } from '@/utils/android'
+import { showNotify } from 'vant'
+import { getUserIdByCert, getUserNameById } from '@/api/login/index.ts'
+
+// 在输入对话框或界面中添加提示文本
+const hintMessage = ref(`请前往:设置 → 关于平板电脑 → 设备WLAN MAC 地址 长按复制并粘贴到此处。`);
+const mac = ref('')
+const userInfo = ref({
+  userId: '',
+  name: ''
+})
+const setting = ref({
+  show: false,
+  mac: '',
+  password: ''
+})
+
+const message = ref('')
+onUnmounted(() => {
+  closeListener()
+})
+onMounted(() => {
+  mac.value = readMacAddress()
+  if (mac.value) {
+    _openScan()
+  } else {
+    setting.value.show =  true
+  }
+
+  // ip
+  fetchIP()
+})
+
+const _openScan = () => {
+  openListener()
+  setTimeout(() => {
+    scanInit(_handlerScan)
+  },300)
+}
+const _handlerScan = (code) => {
+  console.log(code)
+  // 校验扫描的是否为登录二维码
+  const regex = /\{"sign":"([^"]*)",\s*"identity":(\d+)\}/;
+  if (code !== null && regex.test(code)) {
+    const match = regex.exec(code)
+    const sign = match[1]
+    const [userId, token] = sign.split('#');
+    const identity = match[2]
+    getUserIdByCert({sign: sign, identity: identity}).then(res => {
+      if (res && res.data) {
+        userInfo.value.userId = res.data
+        getUserNameById(res.data).then(res => {
+          if (res && res.data) {
+            userInfo.value.name = res.data
+            showNotify({ type: 'success', message: '登录成功!' });
+          }
+        })
+      } else {
+        showNotify({ type: 'danger', message: '登录失败!' });
+      }
+    })
+  } else {
+
+  }
+}
+
+const _openSetting = () => {
+  closeListener()
+  setting.value.show = true
+}
+
+// 校验mac地址是否已配置
+const _verifyMac = (done) => {
+  if (!mac.value) {
+    setting.value.show = true
+    return false
+  }
+  return true
+}
+
+// 关闭设置
+const _closeSetting = () => {
+  _openScan()
+}
+
+const _saveMac = () => {
+  if(!isValidMacAddress(setting.value.mac)) {
+    showNotify({ type: 'danger', message: '请输入正确的MAC地址!' });
+    return
+  }
+  if (!isValidPassword(setting.value.password)) {
+    showNotify({ type: 'danger', message: '请输入正确的密码!' });
+    return
+  }
+  // if(!saveMacAddress(setting.value.mac)) {
+  //   showNotify({ type: 'danger', message: 'MAC保存失败!' });
+  //   return
+  // }
+  showNotify({ type: 'success', message: 'MAC保存成功!' });
+  mac.value = setting.value.mac
+}
+
+function isValidMacAddress(mac) {
+  const regex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
+  return mac !== null && regex.test(mac);
+}
+function isValidPassword(password) {
+  return password === '123456';
+}
+
+// 网络状态和ip
+const ipAddress = ref(null)
+
+// 获取 IP 地址
+const fetchIP = async () => {
+  const RTCPeerConnection = window.RTCPeerConnection;
+  if (RTCPeerConnection) {
+    const pc = new RTCPeerConnection({ iceServers: [] });
+    pc.createDataChannel('');
+    pc.createOffer()
+      .then(sdp => pc.setLocalDescription(sdp))
+      .catch(console.error);
+    pc.onicecandidate = e => {
+      if (!e.candidate) return;
+      const ip = /([0-9]{1,3}(\.[0-9]{1,3}){3})/.exec(e.candidate.candidate)?.[1];
+      if (ip) ipAddress.value = ip;
+    };
+  }
+}
+
+const list = ref([]);
+const loading = ref(false);
+const finished = ref(false);
+
+// 模拟数据加载
+const onLoad = () => {
+  setTimeout(() => {
+    const newData = Array.from({ length: 100 }, (_, i) => ({
+      id: list.value.length + i + 1,
+      time: `2023-06-${Math.floor(Math.random() * 30) + 1} ${Math.floor(Math.random() * 24)}:${Math.floor(Math.random() * 60)}`,
+      operator: `用户${Math.floor(Math.random() * 1000)}`,
+      content: `${list.value.length + i + 1} - ${'这是一段动换行显示'}`
+    }));
+
+    list.value.push(...newData);
+    loading.value = false;
+
+    // 模拟数据加载完成
+    if (list.value.length >= 50) {
+      finished.value = true;
+    }
+  }, 1000);
+};
+
+</script>
+
+<style scoped lang="sass">
+.container
+  height: 100vh
+  display: flex
+  overflow: hidden
+  font-size: large
+
+.table-container
+  display: flex
+  flex-direction: column
+  height: 100%
+  overflow: hidden
+
+.add
+  position: fixed
+  bottom: 10px
+  left: 10px
+  z-index: 1000
+
+span
+  font-size: 7px
+
+/* 深度选择器穿透组件作用域 */
+:deep(.custom-cell-group)
+  /* 调整标题宽度 */
+  .van-cell__title
+    flex: 0 0 50px !important /* 固定宽度 */
+    font-size: x-large
+    max-width: 50px /* 最大宽度 */
+    min-width: 50px /* 最小宽度 */
+  /* 调整内容区域 */
+  .van-cell__value
+    flex: 1
+    color: red
+    font-size: large
+    text-align: left /* 左对齐 */
+    margin-left: 1px /* 可选:增加左边距 */
+
+.waterfall-table
+  position: relative
+  flex: 1
+  display: flex
+  flex-direction: column
+  overflow: hidden
+  height: 100%
+
+.table-header
+  display: flex
+  background: #f2f3f5
+  font-weight: bold
+  z-index: 100
+  box-shadow: 0 2px 4px rgba(0,0,0,0.05)
+  flex-shrink: 0  // 防止表头被压缩
+
+.table-body
+  flex: 1
+  overflow-y: auto
+  position: relative
+  padding-top: 0  // 移除之前的padding-top
+
+.table-row
+  display: flex
+  box-sizing: border-box
+
+.col
+  padding: 8px 4px
+  overflow: hidden
+  text-overflow: ellipsis
+  box-sizing: border-box
+
+/* 列宽设置 - 确保表头和表体使用相同的宽度 */
+.index
+  flex: 0 0 30px
+  width: 30px
+  text-align: center
+
+.time
+  flex: 0 0 70px
+  width: 70px
+
+.operator
+  flex: 0 0 50px
+  width: 50px
+
+.content
+  flex: 1
+  min-width: 0 /* 允许内容换行 */
+  white-space: normal /* 允许换行 */
+</style>