Просмотр исходного кода

Merge remote-tracking branch 'origin/testing' into testing

zengjun 8 месяцев назад
Родитель
Сommit
32a41f6e30
2 измененных файлов с 938 добавлено и 64 удалено
  1. 13 1
      src/views/haikang/putaway/dispatch/index.vue
  2. 925 63
      src/views/piece/dashboard/index.vue

+ 13 - 1
src/views/haikang/putaway/dispatch/index.vue

@@ -341,7 +341,7 @@ onUnmounted(() => {
   closeListener()
 })
 
-window.onRefresh = loadData
+
 
 //删除分拨
 const onClickRight = () => {
@@ -355,6 +355,8 @@ const onClickRight = () => {
     finishTask(params).then(res=>{
       showNotify({ type: 'success', duration: 3000, message: `解绑成功` })
       scanSuccess()
+      reset()
+      loadData()
     }).catch(err=>{
       scanError()
     }).finally(() => {
@@ -362,6 +364,16 @@ const onClickRight = () => {
     })
   }).catch(() => {})
 }
+const reset=()=>{
+  containerNo.value=''
+  wallNo.value=''
+  scanType.value=3
+  searchBarcode.value = ''
+  bin.value=''
+  locationActive.value = []
+  tips.value='请扫描容器号'
+}
+window.onRefresh = loadData
 </script>
 <style scoped lang="sass">
 .container

+ 925 - 63
src/views/piece/dashboard/index.vue

@@ -1,99 +1,961 @@
 <template>
-  <div>
-    <button @click="_openCamera">打开相机</button>
-    <button @click="_openGallery">打开相册选择图片并返回路径</button>
-    <button @click="_openImageEditor">打开图片编辑器</button>
-
-    <!-- 显示图片的容器 -->
-    <div v-if="imageUrl" class="image-preview">
-      <img :src="imageUrl" alt="Selected Image" />
+  <van-overlay :show="!isOnline">
+    <div class="wrapper">
+      <!-- 断网提示(固定在顶部) -->
+      <div v-if="!isOnline" class="network-alert offline">
+        ⚠️ 网络已断开,请检查您的网络连接!
+      </div>
+      <!-- 网络恢复提示 -->
+      <Transition name="fade">
+        <div v-if="showReconnected" class="network-alert online">
+          ✅ 网络已恢复
+        </div>
+      </Transition>
     </div>
-  </div>
+  </van-overlay>
+  <van-row class="container">
+    <van-col span="6" class="user-panel">
+      <div class="user-header">
+        <h3>用户信息</h3>
+      </div>
+
+      <div class="info-grid">
+<!--        <div class="info-item">-->
+<!--          <div class="info-label">用户ID</div>-->
+<!--          <div class="info-value">{{ userInfo.userId }}</div>-->
+<!--        </div>-->
+
+        <div class="info-item">
+          <div class="info-label">用户姓名</div>
+          <div class="info-value" :style="{ color: !userInfo.name ? 'red' : '#1e1f1f' }">{{ userInfo.name || '请登录' }}</div>
+        </div>
+
+        <div class="info-item">
+          <div class="info-label">MAC地址</div>
+          <div class="info-value">{{ mac }}</div>
+        </div>
+
+        <div class="info-item">
+          <div class="info-label">仓库</div>
+          <div class="info-value">{{ frogPosition.warehouse }}</div>
+        </div>
+
+        <div class="info-item">
+          <div class="info-label">位置编号</div>
+          <div class="info-value">{{ frogPosition.code }}</div>
+        </div>
+
+        <div class="info-item">
+          <div class="info-label">用途类型</div>
+          <div class="info-value">{{ frogPosition.type }}</div>
+        </div>
+      </div>
+
+      <div class="error-section">
+        <div class="error-display">
+          <div class="error-label" @click="logout">退出登录</div>
+          <!--          <div class="error-count">{{ errNum }}</div>-->
+        </div>
+      </div>
+
+    </van-col>
+    <van-col span="18" class="table-container">
+      <van-cell-group class="custom-cell-group">
+        <van-cell title="系统提示:" :value="message" :value-class="{'success-message': message && message.includes('扫描成功')}"/>
+      </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-pull-refresh
+          ref="pullRefreshRef"
+          v-model="refreshing"
+          success-text="刷新成功"
+          @refresh="onRefresh"
+          class="table-body"
+        >
+          <!-- 表格主体 -->
+          <van-list
+            v-model:loading="loading"
+            :finished="finished"
+            finished-text="没有更多了"
+            @load="onLoad">
+            <div
+              v-for="(item, index) in list"
+              :key="item.id"
+              class="table-row"
+            >
+              <div class="col index">{{ index + 1 }}</div>
+              <div class="col time">{{ item.operationTime.replace(/^\d{4}-/, '') }}</div>
+              <div class="col operator">{{ item.operatorName }}</div>
+              <div class="col content">{{ item.deliveryNo }}</div>
+            </div>
+            <van-back-top @click="scrollTop"/>
+          </van-list>
+        </van-pull-refresh>
+      </div>
+    </van-col>
+  </van-row>
+  <van-button class="add" icon="setting-o" type="primary" @click="_openSetting" />
+  <van-sticky>
+    <van-dialog
+      v-model:show="setting.show"
+      title="设置"
+      class="setting-dialog"
+      show-cancel-button
+      :before-close="_verifyMac"
+      @confirm="_saveMac"
+      @closed="_closeSetting"
+      width="380px">
+
+      <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-notice-bar color="#1989fa" background="#ecf9ff" @click="checkUpdate"
+                      style="font-size: 16px; padding-left: 10px" left-icon="info-o">
+        版本号:{{ versionName }} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(点击检查更新)
+      </van-notice-bar>
+    </van-dialog>
+  </van-sticky>
 </template>
 
 <script setup>
-import { ref } from 'vue'
+import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
+import { closeListener, openListener, scanInit } from '@/utils/keydownListener'
+import { scanSuccess, scanError } from '@/utils/android'
+import { getVersionName, checkUpdate, saveUserId, getUserId, scanRepeat, saveMacAddress, listErrRecords, reLoginTip,
+   readMacAddress, pageDelivery, addDelivery, isDeliveryNoExists, markAsPushed, getErrRecordsCount, removeUserId, scanErr } from '@/utils/androidPiece'
+import { showLoadingToast, showNotify } from 'vant'
+import { getUserIdByCert, getUserNameById } from '@/api/login/index'
+import { receive, getScanDriverInfo } from '@/api/scan/index'
+import { formatDateTime } from '@/utils/date'
+
+// 在输入对话框或界面中添加提示文本
+const hintMessage = ref(`请前往:设置 → 关于平板电脑 → 设备WLAN MAC 地址 长按复制并粘贴到此处。`);
+const mac = ref('')
+const userInfo = ref({
+  userId: '',
+  name: ''
+})
+const setting = ref({
+  show: false,
+  mac: '',
+  password: ''
+})
+const message = ref('')
+const list = ref([])
+const loading = ref(false)
+const finished = ref(false)
+const errNum = ref(0)
+const preDeliveryNo = ref('')
 
-const imagePath = ref('')
-const imageUrl = ref('')
+// 小青蛙位置属性
+const frogPosition = ref({
+  warehouse: '',
+  type:'',
+  code:''
+})
 
-const _openCamera = () => {
-  if (!window.android) {
-    alert('Android 接口不可用')
+
+const isOnline = ref(navigator.onLine)
+// 更新网络状态
+const updateNetworkStatus = () => {
+  isOnline.value = navigator.onLine
+}
+
+// 退出登录
+const logout = () => {
+  if (!userInfo.value.userId) {
+    scanError()
+    showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '请先登录!' });
     return
   }
-  window.android.openCamera()
+  userInfo.value = {}
+  removeUserId()
+  scanSuccess()
+  showNotify({ type: 'success', style: 'font-size: 30px !important;height:50px', message: '已退出登录!' });
 }
 
-const _openGallery = () => {
-  if (!window.android) {
-    alert('Android 接口不可用')
-    return
+// 网络提示
+const showReconnected = ref(false)
+let reconnectTimer = null
+watch(isOnline, (newVal, oldVal) => {
+  // 当网络从离线变为在线时
+  if (newVal && !oldVal) {
+    scanSuccess()
+    showReconnected.value = true
+
+    // 3秒后自动隐藏恢复提示
+    reconnectTimer = setTimeout(() => {
+      showReconnected.value = false
+    }, 3000)
+  }
+
+  // 当网络从在线变为离线时,隐藏恢复提示
+  if (!newVal && oldVal) {
+    scanError()
+    showReconnected.value = false
+  }
+})
+
+onUnmounted(() => {
+  window.removeEventListener('online', updateNetworkStatus)
+  window.removeEventListener('offline', updateNetworkStatus)
+  closeListener()
+})
+
+onMounted(() => {
+  window.addEventListener('online', updateNetworkStatus)
+  window.addEventListener('offline', updateNetworkStatus)
+
+  versionName.value = getVersionName()
+  mac.value = readMacAddress()
+  // 获取用户信息
+  if (!userInfo.value.userId) {
+    const userId = getUserId()
+    if (userId) {
+      userInfo.value.userId = userId
+      getUserName(userId)
+    }
+  }
+  // 获取机器位置信息
+  if (!frogPosition.value.warehouse && mac.value) {
+    getFrogPosition()
+  }
+
+  if (mac.value) {
+    _openScan()
+  } else {
+    _openSetting()
+  }
+  // ip
+  // fetchIP()
+  // err数量
+  errNum.value = getErrNum()
+})
+
+const getFrogPosition = () => {
+  getScanDriverInfo(mac.value).then(res => {
+    if (res && res.data) {
+      frogPosition.value.warehouse = res.data.warehouseCode
+      frogPosition.value.type = res.data.type
+      frogPosition.value.code = res.data.code
+    }
+  })
+}
+
+const _openScan = () => {
+  openListener()
+  setTimeout(() => {
+    scanInit(debounceScan)
+  },300)
+}
+
+const pushing = ref(false)
+const pullRefreshRef = ref(null)
+const scanDebounceTimeout = ref(null)
+
+const debounceScan = (code) => {
+  // 清除之前的防抖计时器
+  if (scanDebounceTimeout.value) {
+    clearTimeout(scanDebounceTimeout.value)
+  }
+
+  // 设置新的防抖计时器
+  scanDebounceTimeout.value = setTimeout(() => {
+    _handlerScan(code)
+  }, 300) // 300ms 防抖时间
+}
+const _handlerScan = (code) => {
+  code = fixDuplicateText(code)
+  // 校验扫描的是否为登录二维码
+  const regex = /\{"sign":"([^"]*)",\s*"identity":(\d+)\}/;
+  if (code !== null && regex.test(code)) {
+    const match = regex.exec(code)
+    const sign = match[1]
+    const identity = match[2]
+    getUserIdByCert({sign: sign, identity: identity}).then(res => {
+      if (res && res.data) {
+        userInfo.value.userId = res.data
+        getUserName(res.data)
+      } else {
+        showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '登录失败!' });
+      }
+    })
+  } else {
+    // 检查长度是否在11到25个字符之间
+    if (typeof code !== 'string' || code.length < 11 || code.length > 25) {
+      console.log(code)
+      scanErr()
+      showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '请扫描正确的快递单号!' });
+      return
+    }
+    // 数字0-9,大写字母A-Z,部分特殊字符(空格、! " % & ' ( ) * + , - . / : ; < = > ? _)
+    const code128BPattern = /^[\x20-\x7F]+$/;
+    // 新增:过滤以430300开头的正则
+    const startsWithPattern = /^430300.*/;
+    // 检查是否只包含有效字符
+    if (!code128BPattern.test(code) || startsWithPattern.test(code)) {
+      console.log(code)
+      scanErr()
+      showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '请扫描正确的快递单号!' });
+      return
+    }
+    scrollTop()
+    // 校验是否已登录
+    if (!userInfo.value.userId) {
+      scanError()
+      showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '请先登录!' });
+      return
+    }
+    // 校验是否为空
+    if (!code) {
+      scanErr()
+      showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '请先扫描快递单号!' });
+      return
+    }
+
+    if (pushing.value) {
+      scanErr()
+      showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '正在提交中,请稍后扫描!' });
+      return
+    }
+    pushing.value =  true  // 请求状态锁
+
+    // 校验是否已扫描
+    if (list.value.some(item => item.code === code)) {
+      scanRepeat()
+      pushing.value = false
+      message.value = '该快递单已扫描!'
+      showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '该快递单已扫描!' });
+      return
+    }
+
+    const exists = isDeliveryNoExists(code)
+    if (!exists) {
+      scanRepeat()
+      pushing.value = false
+      message.value = '该快递单已扫描!'
+      showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '该快递单已扫描!' });
+      return
+    }
+
+    const toast = showLoadingToast({
+      message: '上传中...',
+      forbidClick: true,
+      duration: 0 // 禁止自动关闭
+    });
+    // 校验是否已存在
+    const currentTime = formatDateTime(new Date())
+    message.value = ''
+    const dto = {
+      deliveryNo: code,
+      machine: mac.value,
+      operator: userInfo.value.userId,
+      operationTime: currentTime,
+      operatorName: userInfo.value.name,
+      isPush: 1,
+      preDeliveryNo: preDeliveryNo.value,
+      version: versionName.value }
+
+    receive(dto).then(res => {
+      if (res && res.code === 200) {
+        const result = addDelivery(mac.value, code, userInfo.value.userId, userInfo.value.name)
+        if (result === -1) {
+          scanRepeat()
+          message.value = '该快递单已扫描!'
+          showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '该快递单已扫描!' });
+          return
+        }
+        if (result === -2) {
+          scanErr()
+          showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '添加失败,请联系开发人员!' });
+          return
+        }
+        preDeliveryNo.value = code
+        scanSuccess()
+        message.value = code + '扫描成功!'
+        list.value.unshift(dto)
+        showNotify({ type: 'success', style: 'font-size: 30px !important;height:50px', message: '扫描成功!' });
+      }
+    }).catch(err => {
+      if (err.code === 700) {
+        userInfo.value = {}
+        reLoginTip()
+      } else {
+        scanErr()
+      }
+      showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: `扫描失败!${err.message}` });
+    }).finally(() => {
+      toast.close();
+      pushing.value = false
+    })
   }
-  window.android.openGallery()
 }
 
-const _openImageEditor = () => {
-  if (!window.android) {
-    alert('Android 接口不可用')
+const scrollTop = () => {
+  // 添加滚动到顶部的逻辑
+  nextTick(() => {
+    const el = pullRefreshRef.value?.$el;
+    if (el && el.scrollTo) {
+      el.scrollTo({ top: 0, behavior: 'smooth' });
+    } else if (el) {
+      // 回退方案
+      el.scrollTop = 0;
+    }
+  })
+}
+function fixDuplicateText(input) {
+  // 改进版正则表达式(支持任意字符且精确匹配完全重复)
+  const regex = /^(.+?)\1$/; // 非贪婪模式防止误匹配
+  const match = input.match(regex);
+  // 返回处理逻辑
+  return match ? match[1] : input;
+}
+
+
+// 在外部定义一个防抖函数映射表,用于跟踪每个单据的防抖状态
+const debounceMap = new Map();
+
+// 防抖函数封装
+const debounce = (func, wait = 500) => {
+  let timer;
+  return (...args) => {
+    clearTimeout(timer);
+    timer = setTimeout(() => {
+      func.apply(this, args);
+    }, wait);
+  };
+};
+
+// 修改后的推送函数
+const _rePush = (item) => {
+  if (!item || item.isPush === 1) return;
+  let status = false
+  showLoadingToast({
+    message: '上传中...',
+    forbidClick: true,
+    closeToast: status
+  });
+  // 初始化当前单据的防抖状态
+  if (!debounceMap.has(item.deliveryNo)) {
+    debounceMap.set(item.deliveryNo, {
+      isPushing: false,  // 请求状态锁
+      debouncedFn: debounce(() => {
+        const entry = debounceMap.get(item.deliveryNo);
+        if (!entry || entry.isPushing) return;
+
+        entry.isPushing = true;  // 加锁
+
+        const dto = {
+          deliveryNo: item.deliveryNo,
+          machine: item.machine,
+          operator: item.operator,
+          operationTime: item.operationTime
+        };
+
+        receive(dto).then(res => {
+          const result = markAsPushed(item.deliveryNo);
+          entry.isPushing = false;  // 请求完成解锁
+          item.isPush = 1;
+          scanSuccess();
+          status = true
+          errNum.value -= 1
+          showNotify({
+            type: 'success',
+            style: 'font-size: 30px !important;height:50px',
+            message: '推送成功!'
+          });
+        }).catch(err => {
+          entry.isPushing = false;  // 请求失败解锁
+          scanError();
+          status = true
+          showNotify({
+            type: 'danger',
+            style: 'font-size: 30px !important;height:50px',
+            message: `推单失败!${err.message}`
+          });
+        });
+      }, 500)  // 500ms防抖时间
+    });
+  }
+
+  // 执行防抖函数
+  const { debouncedFn } = debounceMap.get(item.deliveryNo);
+  debouncedFn();
+};
+
+const getUserName = (userId) => {
+  getUserNameById(userId, mac.value).then(res => {
+    if (res && res.data) {
+      saveUserId(userInfo.value.userId)
+      userInfo.value.name = res.data
+      scanSuccess()
+      showNotify({ type: 'success', style: 'font-size: 30px !important;height:50px', message: '登录成功!' });
+    }
+  })
+}
+
+const versionName = ref('')
+const _openSetting = () => {
+  closeListener()
+  setting.value.show = true
+  versionName.value = getVersionName()
+}
+
+
+// 校验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', style: 'font-size: 30px !important;height:50px', message: '请输入正确的MAC地址!' });
+    return
+  }
+  if (!isValidPassword(setting.value.password)) {
+    showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '请输入正确的密码!' });
     return
   }
-  if (!imagePath.value) {
-    alert('请先选择图片')
+  if(!saveMacAddress(setting.value.mac)) {
+    showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: 'MAC保存失败!' });
     return
   }
-  window.android.openImageEditor(imagePath.value)
+  showNotify({ type: 'success', style: 'font-size: 30px !important;height:50px', message: 'MAC保存成功!' });
+  mac.value = setting.value.mac
+  getFrogPosition()
 }
 
-// 这个函数应该由 Android 调用
-window.handleImageResult = (path) => {
-  imagePath.value = path
-  // 如果是文件路径,直接使用
-  if (path.startsWith('file://') || path.startsWith('/storage')) {
-    imageUrl.value = path
-  }
-  // 如果是 base64,直接使用
-  else if (path.startsWith('data:image')) {
-    imageUrl.value = path
+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;
+    };
   }
 }
 
-// 处理 Blob 数据
-window.handleImageBlob = (base64) => {
-  const blob = base64ToBlob(base64, 'image/jpeg')
-  imageUrl.value = URL.createObjectURL(blob)
+const getErrNum = () => {
+  return getErrRecordsCount()
 }
 
-// 将 base64 转换为 Blob
-const base64ToBlob = (base64, mimeType) => {
-  const byteCharacters = atob(base64.split(',')[1])
-  const byteNumbers = new Array(byteCharacters.length)
-  for (let i = 0; i < byteCharacters.length; i++) {
-    byteNumbers[i] = byteCharacters.charCodeAt(i)
+const page = ref(1)
+const size = ref(10)
+// 模拟数据加载
+const onLoad = () => {
+  if (refreshing.value) {
+    list.value = [];
+    refreshing.value = false;
+  }
+  const result = pageDelivery(page.value, size.value)
+  if (result.size < size.value) {
+    finished.value = true;
+  } else {
+    page.value += 1
+  }
+  list.value.push(...result)
+  loading.value = false;
+};
+const refreshing = ref(false);
+const onRefresh = () => {
+  // 清空列表数据
+  finished.value = false;
+
+  // 重新加载数据
+  // 将 loading 设置为 true,表示处于加载状态
+  loading.value = true;
+  page.value = 1
+  onLoad();
+};
+const isOnePush = ref(false)
+const _one_click_push = () => {
+  isOnePush.value = true
+  const result = listErrRecords()
+  if (result && result != '[]') {
+    const records = JSON.parse(result);
+    const promises = []; // 收集所有请求的Promise
+
+    records.forEach(item => {
+      // 初始化当前单据的防抖状态
+      if (!debounceMap.has(item.deliveryNo)) {
+        const debounceEntry = {
+          isPushing: false,
+          debouncedFn: debounce(() => {
+            const entry = debounceMap.get(item.deliveryNo);
+            if (!entry || entry.isPushing) return;
+
+            entry.isPushing = true;  // 加锁
+
+            const dto = {
+              deliveryNo: item.deliveryNo,
+              machine: item.machine,
+              operator: item.operator,
+              operationTime: item.operationTime
+            };
+
+            // 将请求包装成Promise并收集
+            const promise = receive(dto)
+              .then(res => {
+                const result = markAsPushed(item.deliveryNo);
+                list.value.filter(entry => entry.deliveryNo === item.deliveryNo).forEach(entry => {
+                  entry.isPush = 1;
+                })
+                errNum.value -= 1
+              })
+              .finally(() => {
+                entry.isPushing = false; // 无论成功失败都解锁
+              });
+
+            promises.push(promise); // 添加到Promise数组
+            return promise;
+          }, 500)
+        };
+        debounceMap.set(item.deliveryNo, debounceEntry);
+      }
+
+      // 立即执行防抖函数并收集Promise
+      const promise = debounceMap.get(item.deliveryNo).debouncedFn();
+      if (promise) promises.push(promise);
+    });
+
+    // 所有请求完成后重置状态
+    Promise.allSettled(promises)
+      .then(_ => {
+        scanSuccess();
+        showNotify({ type: 'success', style: 'font-size: 30px !important;height:50px', message: '数据已推送!' });
+      })
+      .catch(_ => {
+        scanError();
+        showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '存在数据推送失败!' });
+      }).finally(() => {
+      isOnePush.value = false;
+    });
+  } else {
+    showNotify({ type: 'danger', style: 'font-size: 30px !important;height:50px', message: '暂无异常数据!' });
+    isOnePush.value = false; // 没有数据时立即重置
   }
-  const byteArray = new Uint8Array(byteNumbers)
-  return new Blob([byteArray], { type: mimeType })
 }
 </script>
 
 <style scoped lang="sass">
-button
-  margin: 10px
-  padding: 8px 16px
-  background: #42b983
+*
+  margin: 0
+  padding: 0
+  box-sizing: border-box
+
+
+body
+  font-family: "PingFang SC", "Microsoft YaHei", sans-serif
+  background: #f5f7fa
+  color: #333
+  overflow: hidden
+  height: 100vh
+
+.container
+  height: 100vh
+  display: flex
+  overflow: hidden
+  font-size: large
+  background: #fff
+
+span
+  font-size: 7px
+
+.table-container
+  display: flex
+  flex-direction: column
+  height: 100%
+  overflow: hidden
+  background: #fff
+
+.waterfall-table
+  position: relative
+  flex: 1
+  display: flex
+  flex-direction: column
+  overflow: hidden
+  height: 100%
+
+.table-header
+  font-size: 8px
+  display: flex
+  background: #f2f3f5
+  font-weight: bold
+  z-index: 0
+  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
+  font-size: 10px
+  height: 21px
+  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 75px
+  width: 75px
+
+.operator
+  flex: 0 0 55px
+  width: 55px
+
+.content
+  flex: 1
+  min-width: 0 /* 允许内容换行 */
+  white-space: normal /* 允许换行 */
+
+.status
+  flex: 0 0 40px
+  width: 40px
+
+/* 确保表体中的单元格与表头对齐 */
+:deep(.van-list)
+  .van-cell
+    padding: 0
+    width: 100%
+    box-sizing: border-box
+  .van-cell__title, .van-cell__value
+    flex: none
+
+.add
+  position: fixed
+  bottom: 10px
+  left: 10px
+  width: 20px
+  height: 20px
+  border-radius: 50%
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15)
+
+:deep(.custom-cell-group)
+  height: auto  // 移除固定高度
+  display: flex
+  align-items: center
+  min-height: 24px  // 确保有足够高度
+  .van-cell
+    display: flex
+    align-items: center
+    height: 100%
+  .van-cell__title
+    flex: 0 0 30px !important
+    font-size: 10px
+    line-height: 24px
+    max-width: 70px
+    min-width: 70px
+  .van-cell__value
+    flex: 1
+    line-height: 24px
+    color: red
+    font-size: 13px
+    text-align: left
+  .success-message
+    font-size: 12px
+    color: #2ed573 !important
+
+.van-notice-bar
+  background-color: #f8f9fa
+  margin: 5px 0 5px 0
+  border: 1px solid #eaeaea
+
+.van-field
+  padding: 5px 16px
+  transition: border-color 0.3s
+
+:deep(.custom-notify)
+  font-size: 100px
+
+.network-alert
+  position: fixed
+  top: 0
+  left: 0
+  right: 0
+  padding: 15px
+  text-align: center
+  font-weight: bold
+  z-index: 9999
+  animation: slideIn 0.3s ease-out
+
+
+@keyframes slideIn
+  from
+    opacity: 0
+    transform: translateY(-100%)
+  to
+    opacity: 1
+    transform: translateY(0)
+
+.network-alert.offline
+  background-color: #ff4757
   color: white
-  border: none
-  border-radius: 4px
-  cursor: pointer
 
+.network-alert.online
+  background-color: #2ed573
+  color: white
+  top: 0px
+
+.fade-leave-active
+  transition: opacity 0.5s ease
+
+.fade-leave-to
+  opacity: 0
+
+/* 左侧用户信息面板样式优化 */
+.user-panel
+  background-color: #f5f7fa
+  padding: 10px 10px
+  height: 100%
+  display: flex
+  flex-direction: column
+
+.user-header
+  text-align: center
+  margin-bottom: 6px
+  position: relative
+  padding-bottom: 6px
+
+.user-header h3
+  color: #3498db
+  font-weight: 500
+  font-size: 10px
+
+.user-header::after
+  content: ''
+  position: absolute
+  bottom: 0
+  left: 0
+  right: 0
+  height: 2px
+  background: linear-gradient(to right, transparent, #3498db, transparent)
+
+.info-grid
+  display: grid
+  grid-template-columns: 1fr
+  padding-bottom: 5px
+  gap: 0px
+  flex: 1
 
-.image-preview
-  margin-top: 20px
-  img
-    max-width: 100%
-    max-height: 400px
-    border: 1px solid #ddd
-    border-radius: 4px
+.info-item
+  display: flex
+  flex-direction: column
 
+.info-label
+  font-size: 7px
+  color: #7f8c8d
+  margin-bottom: 1px
+  display: flex
+  align-items: center
 
+.info-label::before
+  content: ''
+  display: inline-block
+  width: 3px
+  height: 10px
+  background-color: #3498db
+  margin-right: 3px
+  border-radius: 2px
+
+.info-value
+  font-size: 8px
+  font-weight: 400
+  color: #1e1f1f
+  padding-left: 8px
+
+.error-section
+  padding-bottom: 25px
+
+.error-display
+  background: #fff5f5
+  padding: 3px 4px
+  border-radius: 3px
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1)
+
+.error-label
+  font-size: 8px
+  color: #778181
+
+.error-count
+  font-size: 10px
+  font-weight: bold
+  color: #ff4757
+
+.retry-btn
+  width: 45px
+  height: 18px
+  font-size: 7px
+  font-weight: bold
+  border-radius: 4px
+  box-shadow: 0 4px 10px rgba(231, 76, 60, 0.3)
+  transition: all 0.3s
+  margin-top: 5px
+  margin-left: 28px
 </style>