Ver código fonte

feat:闸机系统优化
- 实现根据设备id去查是内侧闸机还是外侧
- 实现根据设备id去查仓库id且上料增加仓库id参数
- 增加闸机控制页且添加声音播报

liuyanpeng 6 meses atrás
pai
commit
47cba7df70

+ 10 - 0
public/index.html

@@ -14,6 +14,16 @@
             type="text/javascript"
             src="/android-device-sdk/FingerprintConfigApi.js"
         ></script>
+        <script
+            nonce="*NONCE_TOKEN*"
+            type="text/javascript"
+            src="/android-device-sdk/SettingApi.js"
+        ></script>
+        <script
+            nonce="*NONCE_TOKEN*"
+            type="text/javascript"
+            src="/android-device-sdk/SoundPlayApi.js"
+        ></script>
     </head>
     <body>
         <div id="app"></div>

+ 12 - 12
src/api/blankBox.js

@@ -1,33 +1,33 @@
 import request from '../util/request.js';
 
 // 空料箱上架
-export function checkBlankBox() {
+export function checkBlankBox(warehouseId) {
     return request({
-        url: '/api/FeedBoxResource/checkBlankBox',
+        url: '/api/FeedBoxResource/checkBlankBox?warehouseId=' + warehouseId,
         method: 'get',
     });
 }
 
 // 请求一个空料箱
-export function requestBlankBox() {
+export function requestBlankBox(warehouseId) {
     return request({
-        url: '/api/FeedBoxResource/requestBlankBox',
+        url: '/api/FeedBoxResource/requestBlankBox?warehouseId=' + warehouseId,
         method: 'get',
     });
 }
 
 // 请求校验一个空料箱和多个工装设备
-export function checkBlankBoxAndInventory() {
+export function checkBlankBoxAndInventory(warehouseId) {
     return request({
-        url: '/api/FeedBoxResource/checkBlankBoxAndInventory',
+        url: '/api/FeedBoxResource/checkBlankBoxAndInventory?warehouseId=' + warehouseId,
         method: 'get',
     });
 }
 
 // 成品上架校验料箱
-export function checkFeedBoxAndProduct() {
+export function checkFeedBoxAndProduct(warehouseId) {
     return request({
-        url: '/api/FeedBoxResource/checkFeedBoxAndProduct',
+        url: '/api/FeedBoxResource/checkFeedBoxAndProduct?warehouseId=' + warehouseId,
         method: 'get',
     });
 }
@@ -42,17 +42,17 @@ export function productRack(params) {
 }
 
 // 成品下架查询立体库成品
-export function findProductCanRemoved() {
+export function findProductCanRemoved(warehouseId) {
     return request({
-        url: '/api/FeedBoxResource/findProductCanRemoved',
+        url: '/api/FeedBoxResource/findProductCanRemoved?warehouseId=' + warehouseId,
         method: 'get',
     });
 }
 
 // 成品下架
-export function productRemoved(params) {
+export function productRemoved(params, warehouseId) {
     return request({
-        url: '/api/FeedBoxResource/productRemoved',
+        url: '/api/FeedBoxResource/productRemoved?warehouseId=' + warehouseId,
         method: 'post',
         data: params,
     });

+ 16 - 0
src/api/gate.js

@@ -0,0 +1,16 @@
+import request from '../util/request.js';
+
+export function controlGate(command) {
+    return request({
+        url: '/api/GateResource/controlGate?command=' + command,
+        method: 'get',
+    });
+}
+
+// 大件运输开关
+export function controlGateByBigInv(command) {
+    return request({
+        url: '/api/GateResource/controlGateByBigInv?command=' + command,
+        method: 'get',
+    });
+}

+ 16 - 0
src/api/login.js

@@ -31,4 +31,20 @@ export function queryAwayAbnormalCar() {
         url: '/api/LocatorResource/checkAwayAbnormalCar',
         method: 'get',
     });
+}
+
+// 查询用户权限
+export function getUserRole() {
+    return request({
+        url: '/api/RoleResourceV3/listUserRoleOrTemplate',
+        method: 'get',
+    });
+}
+
+// 根据设备ID获取仓库ID
+export function getWarehouseId(deviceId) {
+    return request({
+        url: `/api/WarehouseResource/findWarehouseByDeviceId?deviceId=${deviceId}`,
+        method: 'get',
+    });
 }

+ 10 - 7
src/box/FeedingArea.vue

@@ -446,6 +446,8 @@ const showProductOffShelfDialog = ref(false);
 const offShelfProductList = ref([]);
 const selectedOffShelfProducts = ref([]);
 
+const warehouseId = ref(localStorage.getItem('#warehouseId'));
+
 const goBack = () => {
     router.back();
 };
@@ -488,8 +490,9 @@ const handlePlaceEmptyBin = async isReverify => {
     dialogTitle.value = '上架结果';
     showVerificationStatus.value = true;
     loading.value = true;
+    
     try {
-        const res = await checkBlankBox();
+        const res = await checkBlankBox(warehouseId.value);
         handleDataProcessing(res, isReverify);
     } catch (error) {
         verificationSuccess.value = false;
@@ -506,7 +509,7 @@ const handleRequestEmptyBin = async isReverify => {
     showVerificationStatus.value = false;
     loading.value = true;
     try {
-        const res = await requestBlankBox();
+        const res = await requestBlankBox(warehouseId.value);
         handleDataProcessing(res, isReverify);
     } catch (error) {
         console.error('校验失败:', error);
@@ -523,7 +526,7 @@ const handleRequestVerification = async isReverify => {
     showVerificationStatus.value = true;
     loading.value = true;
     try {
-        const res = await checkBlankBoxAndInventory();
+        const res = await checkBlankBoxAndInventory(warehouseId.value);
         handleDataProcessing(res, isReverify);
     } catch (error) {
         console.error('校验失败:', error);
@@ -598,7 +601,7 @@ const handleProductShelfDataProcessing = (res, isReverify = false) => {
 const handleProductShelf = async (isReverify = false) => {
     loading.value = true;
     try {
-        const res = await checkFeedBoxAndProduct();
+        const res = await checkFeedBoxAndProduct(warehouseId.value);
         handleProductShelfDataProcessing(res, isReverify);
     } catch (error) {
         console.error('成品上架校验失败:', error);
@@ -661,7 +664,7 @@ const submitProductShelf = async () => {
     // 获取料箱ID和所选成品ID集合
     const feedBoxId = productShelfBinList.value[0].feedBoxId;
     const productIds = selectedProducts.value.map(p => p.productId);
-    const params = { feedBoxId, productIds };
+    const params = { feedBoxId, productIds,warehouseId: warehouseId.value };
 
     console.log('成品上架提交数据:', params);
 
@@ -697,7 +700,7 @@ const handleProductOffShelf = async () => {
     selectedOffShelfProducts.value = [];
     loading.value = true;
     try {
-        const res = await findProductCanRemoved();
+        const res = await findProductCanRemoved(warehouseId.value);
         if (res.errorCode === 0) {
             if (res.datas && res.datas.length > 0) {
                 offShelfProductList.value = res.datas;
@@ -766,7 +769,7 @@ const submitProductOffShelf = async () => {
 
     loading.value = true;
     try {
-        const res = await productRemoved(params);
+        const res = await productRemoved(params, warehouseId.value);
         if (res.errorCode === 0) {
             showNotify({ type: 'success', message: '成品下架成功', duration: 3000, zIndex: 9999999 });
             closeProductOffShelfDialog();

+ 2 - 7
src/common/PageHeader.vue

@@ -102,14 +102,9 @@ const handleLogout = () => {
     localStorage.removeItem('#LoginInfo');
     localStorage.removeItem('#token');
     localStorage.removeItem('#accountId');
+    localStorage.removeItem('#warehouseId');
 
-    // 检查localStorage中的isOut值,如果为true则在登录页面添加isOut参数
-    const isOut = localStorage.getItem('isOut') === 'true';
-    if (isOut) {
-        router.push({ path: '/fingerprint-login', query: { isOut: 'true' } });
-    } else {
-        router.push('/fingerprint-login');
-    }
+    router.push('/fingerprint-login');
 };
 
 // 切换设置下拉菜单

+ 690 - 0
src/gate/ControlGate.vue

@@ -0,0 +1,690 @@
+<template>
+  <!-- 闸机控制 - 智能仓储风格 -->
+  <div class="control-gate-page">
+    <!-- 背景层 -->
+    <div class="bg-layer" :style="{ backgroundImage: `url(${bgImg})` }" />
+
+    <!-- 顶部标题区域 -->
+    <div class="header-section">
+      <button class="logout-btn" @click="goHome">
+        <i class="fas fa-home" />
+        <span>主页</span>
+      </button>
+      <h1 class="page-title">闸机控制</h1>
+      <div class="header-actions" />
+    </div>
+
+    <!-- 主内容区域 -->
+    <main class="main-content">
+      <!-- 控制面板卡片 -->
+      <div class="control-panel">
+        <div class="panel-header">
+          <div class="panel-icon">
+            <i class="fas fa-door-open" />
+          </div>
+          <div class="panel-title">
+            <h2>闸机操作</h2>
+            <p>选择以下操作来控制闸机</p>
+          </div>
+        </div>
+
+        <!-- 操作按钮网格 -->
+        <div class="control-grid">
+          <!-- 闸机常开 -->
+          <button class="control-btn open-btn" :disabled="loading" @click="handleControl('OPEN', '闸机常开', false)">
+            <div class="btn-icon">
+              <i class="fas fa-unlock" />
+            </div>
+            <div class="btn-text">
+              <span class="btn-title">闸机常开</span>
+              <span class="btn-desc">保持闸机开启状态</span>
+            </div>
+          </button>
+
+          <!-- 闸机关门 -->
+          <button class="control-btn close-btn" :disabled="loading" @click="handleControl('CLOSE', '闸机关门', false)">
+            <div class="btn-icon">
+              <i class="fas fa-lock" />
+            </div>
+            <div class="btn-text">
+              <span class="btn-title">闸机关门</span>
+              <span class="btn-desc">关闭闸机通道</span>
+            </div>
+          </button>
+
+          <!-- 闸机报警 -->
+          <button class="control-btn alarm-btn" :disabled="loading" @click="handleControl('ALARM', '闸机报警', false)">
+            <div class="btn-icon">
+              <i class="fas fa-bell" />
+            </div>
+            <div class="btn-text">
+              <span class="btn-title">闸机报警</span>
+              <span class="btn-desc">触发闸机报警</span>
+            </div>
+          </button>
+
+          <!-- 左闸机开门 -->
+          <button class="control-btn left-btn" :disabled="loading" @click="handleControl('LEFTOPEN', '左闸机开门', false)">
+            <div class="btn-icon">
+              <i class="fas fa-arrow-left" />
+            </div>
+            <div class="btn-text">
+              <span class="btn-title">左闸机开门</span>
+              <span class="btn-desc">开启左侧闸机</span>
+            </div>
+          </button>
+
+          <!-- 右闸机开门 -->
+          <button class="control-btn right-btn" :disabled="loading" @click="handleControl('RIGHTOPEN', '右闸机开门', false)">
+            <div class="btn-icon">
+              <i class="fas fa-arrow-right" />
+            </div>
+            <div class="btn-text">
+              <span class="btn-title">右闸机开门</span>
+              <span class="btn-desc">开启右侧闸机</span>
+            </div>
+          </button>
+
+          <!-- 重启闸机 -->
+          <button class="control-btn reboot-btn" :disabled="loading" @click="handleControl('REBOOT', '重启闸机', false)">
+            <div class="btn-icon">
+              <i class="fas fa-sync-alt" />
+            </div>
+            <div class="btn-text">
+              <span class="btn-title">重启闸机</span>
+              <span class="btn-desc">重新启动闸机系统</span>
+            </div>
+          </button>
+
+          <button class="control-btn big-open-btn" :disabled="loading" @click="handleControl('OPEN', '大件运输常开', true)">
+            <div class="btn-icon">
+              <i class="fas fa-truck" />
+            </div>
+            <div class="btn-text">
+              <span class="btn-title">大件运输常开</span>
+              <span class="btn-desc">大件运输模式保持开启</span>
+            </div>
+          </button>
+
+          <button class="control-btn big-close-btn" :disabled="loading" @click="handleControl('CLOSE', '大件运输关闭', true)">
+            <div class="btn-icon">
+              <i class="fas fa-truck-loading" />
+            </div>
+            <div class="btn-text">
+              <span class="btn-title">关闭大件运输常开</span>
+              <span class="btn-desc">大件运输模式保持关闭</span>
+            </div>
+          </button>
+        </div>
+      </div>
+    </main>
+
+    <!-- Loading -->
+    <div v-if="loading" class="loading-overlay">
+      <div class="loading-dots">
+        <div class="dot" />
+        <div class="dot" />
+        <div class="dot" />
+      </div>
+      <span class="loading-text">执行中...</span>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { useRouter } from 'vue-router';
+import { showNotify } from 'vant';
+import { controlGate, controlGateByBigInv } from '../api/gate.js';
+
+// 图片资源
+import bgImg from '../assets/images/bj.png';
+
+const router = useRouter();
+
+// 加载状态
+const loading = ref(false);
+
+// 返回主页
+const goHome = () => {
+    router.push('/home');
+};
+
+// 执行闸机控制
+const handleControl = async (command, actionName, isBigInv) => {
+    loading.value = true;
+    try {
+        let res = null;
+        if (isBigInv) {
+            res = await controlGateByBigInv(command);
+        } else {
+            res = await controlGate(command);
+        }
+        if (res.errorCode === 0) {
+            showNotify({ type: 'success', message: `${actionName}操作成功`, duration: 2000 });
+            if (actionName.includes('开')) {
+                plugin.soundPlay.playOpen();
+            } else if (actionName.includes('关')) {
+                plugin.soundPlay.playClose();
+            } else if (actionName.includes('报')) {
+                plugin.soundPlay.playAlarm();
+            }
+        } else {
+            showNotify({ type: 'danger', message: res.errorMessage || `${actionName}操作失败`, duration: 3000 });
+        }
+    } catch (error) {
+        console.error('闸机控制失败:', error);
+        showNotify({ type: 'danger', message: `${actionName}操作失败`, duration: 3000 });
+    } finally {
+        loading.value = false;
+    }
+};
+</script>
+
+<style scoped>
+/* ========== 基础样式 ========== */
+.control-gate-page {
+  width: 100%;
+  height: 100vh;
+  max-height: 100vh;
+  position: relative;
+  font-family: 'Microsoft YaHei', sans-serif;
+  color: #fff;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+}
+
+/* 背景层 */
+.bg-layer {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: #041c3d;
+  background-size: cover;
+  background-position: center;
+  background-repeat: no-repeat;
+  z-index: 0;
+}
+
+/* ========== 顶部标题区域 ========== */
+.header-section {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 15px 30px;
+  position: relative;
+  z-index: 10;
+  flex-shrink: 0;
+}
+
+.page-title {
+  font-size: 28px;
+  font-weight: bold;
+  color: #fff;
+  text-shadow: 0 2px 10px rgba(0, 191, 255, 0.5);
+  margin: 0;
+  position: absolute;
+  left: 50%;
+  transform: translateX(-50%);
+}
+
+.logout-btn {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 10px 20px;
+  background: linear-gradient(90deg, #1a4a7a 0%, #0d3a5a 100%);
+  border: 1px solid #2a7fff;
+  border-radius: 8px;
+  color: #7ec8ff;
+  font-size: 16px;
+  cursor: pointer;
+  transition: all 0.3s;
+}
+
+.logout-btn:hover {
+  background: linear-gradient(90deg, #2a5a8a 0%, #1d4a6a 100%);
+  box-shadow: 0 0 15px rgba(42, 127, 255, 0.4);
+}
+
+.logout-btn i {
+  font-size: 18px;
+}
+
+.header-actions {
+  width: 100px;
+}
+
+/* ========== 主内容区域 ========== */
+.main-content {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: flex-start;
+  padding: 20px 30px;
+  position: relative;
+  z-index: 10;
+  min-height: 0;
+  overflow-y: auto;
+}
+
+/* ========== 控制面板卡片 ========== */
+.control-panel {
+  background: rgba(9, 61, 140, 0.5);
+  border: 1px solid #049FD8;
+  border-radius: 20px;
+  padding: 40px 50px;
+  width: 100%;
+  max-width: 1200px;
+}
+
+.panel-header {
+  display: flex;
+  align-items: center;
+  gap: 20px;
+  margin-bottom: 35px;
+}
+
+.panel-icon {
+  width: 70px;
+  height: 70px;
+  background: linear-gradient(135deg, #1e90ff 0%, #00bfff 100%);
+  border-radius: 16px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 32px;
+  color: #fff;
+  box-shadow: 0 8px 25px rgba(0, 191, 255, 0.3);
+}
+
+.panel-title h2 {
+  font-size: 28px;
+  font-weight: 600;
+  color: #fff;
+  margin: 0 0 8px 0;
+}
+
+.panel-title p {
+  font-size: 16px;
+  color: #7ec8ff;
+  margin: 0;
+}
+
+/* ========== 控制按钮网格 ========== */
+.control-grid {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 20px;
+}
+
+.control-btn {
+  background: rgba(13, 58, 106, 0.6);
+  border: 2px solid rgba(42, 127, 255, 0.4);
+  border-radius: 16px;
+  padding: 30px 25px;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 18px;
+}
+
+.control-btn:hover:not(:disabled) {
+  transform: translateY(-5px);
+  box-shadow: 0 10px 30px rgba(0, 191, 255, 0.3);
+}
+
+.control-btn:disabled {
+  opacity: 0.5;
+  cursor: not-allowed;
+}
+
+.btn-icon {
+  width: 70px;
+  height: 70px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 30px;
+  color: #fff;
+  transition: all 0.3s;
+}
+
+.btn-text {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 6px;
+}
+
+.btn-title {
+  font-size: 20px;
+  font-weight: 600;
+  color: #fff;
+}
+
+.btn-desc {
+  font-size: 14px;
+  color: #7ec8ff;
+}
+
+/* 闸机常开 - 绿色 */
+.open-btn {
+  border-color: rgba(16, 185, 129, 0.5);
+}
+
+.open-btn:hover:not(:disabled) {
+  border-color: #10b981;
+  background: rgba(16, 185, 129, 0.2);
+}
+
+.open-btn .btn-icon {
+  background: linear-gradient(135deg, #10b981 0%, #34d399 100%);
+}
+
+/* 闸机关门 - 红色 */
+.close-btn {
+  border-color: rgba(239, 68, 68, 0.5);
+}
+
+.close-btn:hover:not(:disabled) {
+  border-color: #ef4444;
+  background: rgba(239, 68, 68, 0.2);
+}
+
+.close-btn .btn-icon {
+  background: linear-gradient(135deg, #ef4444 0%, #f87171 100%);
+}
+
+.big-open-btn {
+  border-color: rgba(34, 211, 238, 0.5);
+}
+
+.big-open-btn:hover:not(:disabled) {
+  border-color: #22d3ee;
+  background: rgba(34, 211, 238, 0.2);
+}
+
+.big-open-btn .btn-icon {
+  background: linear-gradient(135deg, #06b6d4 0%, #22d3ee 100%);
+}
+
+.big-close-btn {
+  border-color: rgba(220, 38, 38, 0.55);
+}
+
+.big-close-btn:hover:not(:disabled) {
+  border-color: #dc2626;
+  background: rgba(220, 38, 38, 0.2);
+}
+
+.big-close-btn .btn-icon {
+  background: linear-gradient(135deg, #dc2626 0%, #f87171 100%);
+}
+
+/* 闸机报警 - 橙色 */
+.alarm-btn {
+  border-color: rgba(245, 158, 11, 0.5);
+}
+
+.alarm-btn:hover:not(:disabled) {
+  border-color: #f59e0b;
+  background: rgba(245, 158, 11, 0.2);
+}
+
+.alarm-btn .btn-icon {
+  background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%);
+}
+
+/* 左闸机开门 - 蓝色 */
+.left-btn {
+  border-color: rgba(59, 130, 246, 0.5);
+}
+
+.left-btn:hover:not(:disabled) {
+  border-color: #3b82f6;
+  background: rgba(59, 130, 246, 0.2);
+}
+
+.left-btn .btn-icon {
+  background: linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%);
+}
+
+/* 右闸机开门 - 紫色 */
+.right-btn {
+  border-color: rgba(139, 92, 246, 0.5);
+}
+
+.right-btn:hover:not(:disabled) {
+  border-color: #8b5cf6;
+  background: rgba(139, 92, 246, 0.2);
+}
+
+.right-btn .btn-icon {
+  background: linear-gradient(135deg, #8b5cf6 0%, #a78bfa 100%);
+}
+
+/* 重启闸机 - 青色 */
+.reboot-btn {
+  border-color: rgba(6, 182, 212, 0.5);
+}
+
+.reboot-btn:hover:not(:disabled) {
+  border-color: #06b6d4;
+  background: rgba(6, 182, 212, 0.2);
+}
+
+.reboot-btn .btn-icon {
+  background: linear-gradient(135deg, #06b6d4 0%, #22d3ee 100%);
+}
+
+/* ========== Loading ========== */
+.loading-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(4, 28, 61, 0.9);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  z-index: 2000;
+  gap: 20px;
+}
+
+.loading-dots {
+  display: flex;
+  gap: 10px;
+}
+
+.loading-dots .dot {
+  width: 16px;
+  height: 16px;
+  background: #00bfff;
+  border-radius: 50%;
+  animation: dotPulse 1.4s infinite ease-in-out both;
+}
+
+.loading-dots .dot:nth-child(1) {
+  animation-delay: -0.32s;
+}
+
+.loading-dots .dot:nth-child(2) {
+  animation-delay: -0.16s;
+}
+
+@keyframes dotPulse {
+
+  0%,
+  80%,
+  100% {
+    transform: scale(0);
+    opacity: 0.5;
+  }
+
+  40% {
+    transform: scale(1);
+    opacity: 1;
+  }
+}
+
+.loading-text {
+  font-size: 18px;
+  color: #7ec8ff;
+}
+
+/* ========== 响应式 - 横屏 1920*1080 ========== */
+@media screen and (orientation: landscape) {
+  .header-section {
+    padding: 42px 20px !important;
+  }
+
+  .page-title {
+    font-size: 24px;
+  }
+
+  .logout-btn {
+    padding: 8px 16px;
+    font-size: 14px;
+  }
+
+  .main-content {
+    padding: 20px 40px;
+    justify-content: center;
+  }
+
+  .control-panel {
+    padding: 35px 45px;
+    max-width: 95%;
+  }
+
+  .panel-header {
+    margin-bottom: 30px;
+    gap: 18px;
+  }
+
+  .panel-icon {
+    width: 60px;
+    height: 60px;
+    font-size: 28px;
+  }
+
+  .panel-title h2 {
+    font-size: 24px;
+  }
+
+  .panel-title p {
+    font-size: 15px;
+  }
+
+  .control-grid {
+    gap: 20px;
+  }
+
+  .control-btn {
+    padding: 28px 20px;
+    gap: 15px;
+  }
+
+  .btn-icon {
+    width: 65px;
+    height: 65px;
+    font-size: 28px;
+  }
+
+  .btn-title {
+    font-size: 18px;
+  }
+
+  .btn-desc {
+    font-size: 13px;
+  }
+}
+
+/* ========== 响应式 - 竖屏 1080*1920 ========== */
+@media screen and (orientation: portrait) {
+  .header-section {
+    padding: 64px 20px !important;
+  }
+
+  .page-title {
+    font-size: 32px;
+  }
+
+  .logout-btn {
+    padding: 12px 24px;
+    font-size: 18px;
+  }
+
+  .main-content {
+    padding: 30px;
+    justify-content: flex-start;
+  }
+
+  .control-panel {
+    padding: 40px 35px;
+    max-width: 100%;
+  }
+
+  .panel-header {
+    margin-bottom: 40px;
+    gap: 20px;
+  }
+
+  .panel-icon {
+    width: 70px;
+    height: 70px;
+    font-size: 32px;
+  }
+
+  .panel-title h2 {
+    font-size: 28px;
+  }
+
+  .panel-title p {
+    font-size: 16px;
+  }
+
+  .control-grid {
+    grid-template-columns: repeat(2, 1fr);
+    gap: 20px;
+  }
+
+  .control-btn {
+    padding: 30px 20px;
+    gap: 15px;
+  }
+
+  .btn-icon {
+    width: 65px;
+    height: 65px;
+    font-size: 28px;
+  }
+
+  .btn-title {
+    font-size: 20px;
+  }
+
+  .btn-desc {
+    font-size: 14px;
+  }
+
+  .loading-dots .dot {
+    width: 20px;
+    height: 20px;
+  }
+
+  .loading-text {
+    font-size: 22px;
+  }
+}
+</style>

+ 19 - 17
src/login/FingerprintLogin.vue

@@ -246,12 +246,14 @@ const handleRestart = () => {
 
 // 跳转到密码登录页面
 const goToPasswordLogin = () => {
-    const isOut = localStorage.getItem('isOut') === 'true';
-    if (isOut) {
-        router.push({ path: '/login', query: { isOut: 'true' } });
-    } else {
-        router.push('/login');
-    }
+    // const isOut = localStorage.getItem('isOut') === 'true';
+    // if (isOut) {
+    //     router.push({ path: '/login', query: { isOut: 'true' } });
+    // } else {
+    //     router.push('/login');
+    // }
+
+    router.push('/login');
 };
 
 const checkPasswordReminder = async () => {
@@ -275,17 +277,17 @@ const checkPasswordReminder = async () => {
 // 组件挂载
 onMounted(() => {
     // 检查URL参数中是否包含isOut=true,如果有则存储到localStorage
-    const isOutParam = route.query.isOut;
-    if (isOutParam !== undefined) {
-        const isOutValue = String(isOutParam) === 'true' ? 'true' : 'false';
-        if (isOutValue == 'true') {
-            localStorage.setItem('isOut', isOutValue);
-        } else {
-            localStorage.setItem('isOut', isOutValue);
-        }
-    } else {
-        localStorage.setItem('isOut', 'false');
-    }
+    // const isOutParam = route.query.isOut;
+    // if (isOutParam !== undefined) {
+    //     const isOutValue = String(isOutParam) === 'true' ? 'true' : 'false';
+    //     if (isOutValue == 'true') {
+    //         localStorage.setItem('isOut', isOutValue);
+    //     } else {
+    //         localStorage.setItem('isOut', isOutValue);
+    //     }
+    // } else {
+    //     localStorage.setItem('isOut', 'false');
+    // }
 
     // 设置指纹设备的响应处理函数
     if (plugin.fingerprintConfig) {

+ 95 - 21
src/login/UserHome.vue

@@ -1,4 +1,4 @@
-<!-- 智能仓储管理系统 - 大屏首页 (1080x1920 竖屏优化,支持其他设备滚动) -->
+<!-- 智能仓储管理系统 -->
 <template>
   <div class="home-page">
     <!-- 背景 -->
@@ -37,7 +37,13 @@
               <i class="fas fa-box mr-2" />
               <span>送料区管理</span>
             </div>
-            <div v-if="isOut" class="dropdown-item" @click="toggleMenu">
+            <div v-if="isAdmin" class="dropdown-item" @click="goControlGate">
+              <i class="fas fa-door-open mr-2" />
+              <span>闸机控制</span>
+            </div>
+
+            <!-- 调试使用 -->
+            <div v-if="isOutMenu" class="dropdown-item" @click="toggleMenu">
               <i class="fas fa-bars-staggered" />
               <span>内侧菜单</span>
             </div>
@@ -69,7 +75,7 @@
           </div>
         </div>
       </div>
-      
+
       <!-- 异常停泊区提示卡片 -->
       <div v-if="showAbnormalCard" class="abnormal-card">
         <div class="abnormal-card-header">
@@ -86,7 +92,9 @@
 
       <!-- 操作按钮区域 -->
       <div class="buttons-section">
-        <template v-if="isOut">
+        <!-- <template v-if="isOut"> -->
+        <!-- 调试使用 -->
+        <template v-if="isOutMenu">
           <div v-for="action in outButtons" :key="action.label" class="action-btn" @click="handleAction(action.action)">
             <img :src="buttonBg" alt="" class="btn-bg" />
             <span class="btn-text">{{ action.label }}</span>
@@ -136,8 +144,10 @@
 </template>
 
 <script setup>
-import { ref, reactive, onMounted, onUnmounted } from 'vue';
 import { useRouter } from 'vue-router';
+import { getQueryString } from '../util/common.js';
+import { ref, reactive, onMounted, onUnmounted } from 'vue';
+import { getWarehouseId } from '../api/login.js';
 
 // 图片资源 - 从 assets/images 目录加载
 import cardIcon1 from '../assets/images/card1.png';
@@ -152,13 +162,17 @@ import arrowDownIcon from '../assets/images/down.png';
 import headerImg from '../assets/images/header.png';
 import bottomImg from '../assets/images/bottom.png';
 
-import { getStaticInfo } from '../api/login.js';
+import { getStaticInfo, getUserRole } from '../api/login.js';
 
 const router = useRouter();
 const username = ref('admin');
 
 // 是否为外侧屏幕
 const isOut = ref(true);
+const isOutMenu = ref(true);
+
+// 是否为管理员
+const isAdmin = ref(false);
 
 // 设置下拉菜单状态
 const showSettings = ref(false);
@@ -190,12 +204,14 @@ onMounted(() => {
     }
 
     // 读取isOut状态
-    const storedIsOut = localStorage.getItem('isOut');
-    if (storedIsOut !== null) {
-        isOut.value = storedIsOut === 'true';
-    }
+    // const storedIsOut = localStorage.getItem('isOut');
+    // if (storedIsOut !== null) {
+    //     isOut.value = storedIsOut === 'true';
+    // }
 
     getInfo();
+    getNowUserRole();
+    getWarehouseInfo();
 
     infoTimer = setInterval(() => {
         getInfo();
@@ -203,7 +219,7 @@ onMounted(() => {
 
     // 添加点击外部关闭下拉菜单的事件监听
     document.addEventListener('click', handleClickOutside);
-    
+
     // 延时1秒检查异常信息,确保异常信息已经存储到localStorage
     setTimeout(() => {
         checkAbnormalMessage();
@@ -320,10 +336,17 @@ const goDeliveryManagement = () => {
     router.push('/delivery-management');
 };
 
+// 跳转到闸机控制页面
+const goControlGate = () => {
+    showSettings.value = false;
+    router.push('/control-gate');
+};
+
 // 切换菜单
 const toggleMenu = () => {
-    isOut.value = !isOut.value;
-    localStorage.setItem('isOut', isOut.value);
+    // isOut.value = !isOut.value;
+    // localStorage.setItem('isOut', isOut.value);
+    isOutMenu.value = !isOutMenu.value;
 };
 
 // 处理退出
@@ -331,14 +354,16 @@ const handleLogout = () => {
     localStorage.removeItem('#LoginInfo');
     localStorage.removeItem('#token');
     localStorage.removeItem('#accountId');
+    localStorage.removeItem('#warehouseId');
 
     // 检查localStorage中的isOut值,如果为true则在登录页面添加isOut参数
-    const isOutValue = localStorage.getItem('isOut') === 'true';
-    if (isOutValue) {
-        router.push({ path: '/fingerprint-login', query: { isOut: 'true' } });
-    } else {
-        router.push('/fingerprint-login');
-    }
+    // const isOutValue = localStorage.getItem('isOut') === 'true';
+    // if (isOutValue) {
+    //     router.push({ path: '/fingerprint-login', query: { isOut: 'true' } });
+    // } else {
+    //     router.push('/fingerprint-login');
+    // }
+    router.push('/fingerprint-login');
 };
 
 // 处理按钮操作
@@ -405,7 +430,6 @@ const getInfo = async () => {
         const res = await getStaticInfo();
         if (res.errorCode === 0) {
             const { inventoryQuantity, stockInQuantity, stockOutQuantity, cfInAndOutResponses } = res.data;
-            console.log(statistics.value[0].value);
             statistics.value[0].value = inventoryQuantity;
             statistics.value[1].value = stockInQuantity;
             statistics.value[2].value = stockOutQuantity;
@@ -424,6 +448,55 @@ const getInfo = async () => {
     }
 
 };
+
+// 获取当前用户是否为超级管理员权限
+const getNowUserRole = async () => {
+    try {
+        const loginInfo = localStorage.getItem('#LoginInfo');
+        if (!loginInfo) {
+            isAdmin.value = false;
+            return;
+        }
+        const loginInfoObj = JSON.parse(loginInfo);
+        const currentUserId = loginInfoObj.userId;
+
+        const res = await getUserRole();
+
+        if (res.errorCode === 0 && res.datas && res.datas.length > 0) {
+            const hasAdminRole = res.datas.some(
+                item => item.userId === currentUserId && item.roleOrTemplateName === '超级管理员',
+            );
+            isAdmin.value = hasAdminRole;
+        } else {
+            isAdmin.value = false;
+        }
+    } catch (error) {
+        console.error('获取用户权限失败:', error);
+        isAdmin.value = false;
+    }
+};
+
+// 如果不是设备从URL中获取,否则从设备中获取,并判断是内侧还是外侧设备
+const getWarehouseInfo = async () => {
+    let deviceId = null;
+    if (!plugin.settingConfig.getImei()) {
+        deviceId = getQueryString().deviceId;
+    } else {
+        deviceId = plugin.settingConfig.getImei();
+    }
+    try {
+        const res = await getWarehouseId(deviceId);
+        if (res.errorCode === 0 && res.data) {
+            localStorage.setItem('#warehouseId', res.data.warehouseId);
+            isOut.value = !res.data.inGate;
+            isOutMenu.value = !res.data.inGate;
+        } else {
+            console.log('获取仓库ID异常:', res.errorMessage);
+        }
+    } catch (error) {
+        console.error('获取仓库ID失败:', error);
+    }
+};
 </script>
 
 <style scoped>
@@ -735,7 +808,7 @@ const getInfo = async () => {
 /* 异常停泊区提示卡片样式 */
 .abnormal-card {
   position: absolute;
-  top: 150px; 
+  top: 150px;
   width: 1030px;
   background: linear-gradient(135deg, rgba(9, 45, 82, 0.95) 0%, rgba(5, 30, 60, 0.95) 100%);
   border: 1px solid rgba(30, 144, 255, 0.5);
@@ -816,6 +889,7 @@ const getInfo = async () => {
     opacity: 0;
     transform: translateY(30px);
   }
+
   to {
     opacity: 1;
     transform: translateY(0);

+ 19 - 18
src/login/UserLogin.vue

@@ -97,7 +97,7 @@
 <script setup>
 import { ref, onMounted } from 'vue';
 import { useRouter, useRoute } from 'vue-router';
-import { loginApi, queryRemindTime,queryAwayAbnormalCar } from '../api/login.js';
+import { loginApi, queryRemindTime, queryAwayAbnormalCar } from '../api/login.js';
 import { getFormattedDateTime } from '../common/Common.js';
 import { showNotify } from 'vant';
 
@@ -115,17 +115,17 @@ const showPassword = ref(false);
 
 onMounted(() => {
     // 检查URL参数中是否包含isOut=true,如果有则存储到localStorage
-    const isOutParam = route.query.isOut;
-    if (isOutParam !== undefined) {
-        const isOutValue = String(isOutParam) === 'true' ? 'true' : 'false';
-        if (isOutValue == 'true') {
-            localStorage.setItem('isOut', isOutValue);
-        } else {
-            localStorage.setItem('isOut', isOutValue);
-        }
-    } else {
-        localStorage.setItem('isOut', 'false');
-    }
+    // const isOutParam = route.query.isOut;
+    // if (isOutParam !== undefined) {
+    //     const isOutValue = String(isOutParam) === 'true' ? 'true' : 'false';
+    //     if (isOutValue == 'true') {
+    //         localStorage.setItem('isOut', isOutValue);
+    //     } else {
+    //         localStorage.setItem('isOut', isOutValue);
+    //     }
+    // } else {
+    //     localStorage.setItem('isOut', 'false');
+    // }
 });
 
 // 登录处理函数
@@ -218,12 +218,13 @@ const checkAwayAbnormalCar = async () => {
 };
 
 const handleFingerprintLogin = () => {
-    const isOut = localStorage.getItem('isOut') === 'true';
-    if (isOut) {
-        router.push({ path: '/fingerprint-login', query: { isOut: 'true' } });
-    } else {
-        router.push('/fingerprint-login');
-    }
+    // const isOut = localStorage.getItem('isOut') === 'true';
+    // if (isOut) {
+    //     router.push({ path: '/fingerprint-login', query: { isOut: 'true' } });
+    // } else {
+    //     router.push('/fingerprint-login');
+    // }
+    router.push('/fingerprint-login');
 };
 
 // 忘记密码处理函数

+ 4 - 0
src/router/routes.js

@@ -43,6 +43,9 @@ const FingerprintLogin = () => import('../login/FingerprintLogin.vue');
 // 上架区
 const FeedingArea = () => import('../box/FeedingArea.vue');
 
+// 闸机控制
+const ControlGate = () => import('../gate/ControlGate.vue');
+
 const routes = [
     { path: '/', redirect: '/login' },
     { path: '/login', component: UserLogin, meta: { title: '用户登录' } },
@@ -63,5 +66,6 @@ const routes = [
     { path: '/fingerprint-enroll', component: FingerprintEnroll, meta: { title: '指纹录入' } },
     { path: '/fingerprint-login', component: FingerprintLogin, meta: { title: '指纹登录' } },
     { path: '/feeding-area', component: FeedingArea, meta: { title: '上架区' } },
+    { path: '/control-gate', component: ControlGate, meta: { title: '闸机控制' } },
 ];
 export default routes;

+ 9 - 1
src/util/common.js

@@ -51,4 +51,12 @@ export function notificationSuccess(message, title) {
         message: title ? `${title}: ${message}` : message,
         duration: 3000,
     });
-}
+}
+
+// 获取设备ID
+export const getQueryString = () => {
+    let urlStr = window.location.href.split('?')[1];
+    const urlSearchParams = new URLSearchParams(urlStr);
+    const result = Object.fromEntries(urlSearchParams.entries());
+    return result;
+};

+ 7 - 5
src/util/request.js

@@ -68,11 +68,13 @@ const responseErrorHandler = error => {
             const isProduction = process.env.APP_ENV === 'production';
             const loginPath = isProduction ? 'board.html#/login' : '#/login';
             
-            if (isOut == 'true') {
-                window.location = Common.getRedirectUrl(loginPath + '?isOut=true&redirectUrl=' + encodeURIComponent(currentUrl));
-            } else {
-                window.location = Common.getRedirectUrl(loginPath + '?isOut=false&redirectUrl=' + encodeURIComponent(currentUrl));
-            }
+            // if (isOut == 'true') {
+            //     window.location = Common.getRedirectUrl(loginPath + '?isOut=true&redirectUrl=' + encodeURIComponent(currentUrl));
+            // } else {
+            //     window.location = Common.getRedirectUrl(loginPath + '?isOut=false&redirectUrl=' + encodeURIComponent(currentUrl));
+            // }
+
+            window.location = Common.getRedirectUrl(loginPath + '?redirectUrl=' + encodeURIComponent(currentUrl));
         }
     }
     if (error.response.status === 504) {