瀏覽代碼

feat(photography): 添加演示模式功能

- 实现了演示模式全屏遮罩界面
- 添加了演示配置选择对话框
- 集成了货号、程序类型和配置选择功能
- 实现了演示模式下的拍摄流程控制
- 添加了拍摄完成后的自动货号删除功能
- 为惠利玛用户增加了演示模式按钮入口
- 实现了左右脚程序类型的动态切换
- 完善了演示模式的开始和停止逻辑
panqiuyao 1 周之前
父節點
當前提交
e35eefb700

+ 158 - 1
frontend/src/views/Photography/mixin/usePhotography.ts

@@ -12,7 +12,7 @@ import { useUuidStore } from '@/stores/modules/uuid'
 import useUserInfo from "@/stores/modules/user";
 import configInfo from '@/stores/modules/config';
 import tokenInfo from "@/stores/modules/token";
-import { getAllUserConfigs } from '@/apis/setting';
+import { getAllUserConfigs, getTopTabs } from '@/apis/setting';
 
 export default function usePhotography() {
     const loading = ref(false)
@@ -40,6 +40,15 @@ export default function usePhotography() {
     const searchGoodsArtNo = ref('')
     let smartShooterTimeout: ReturnType<typeof setTimeout> | null = null
 
+    // ==================== 演示模式 ====================
+    const isDemoMode = ref(false) // 是否处于演示模式
+    const demoGoodsArtNo = ref('演示货号1') // 演示货号
+    const demoProgramType = ref('执行左脚程序') // 演示程序类型
+    const demoConfigList = ref<{id: number, mode_name: string}[]>([]) // 演示配置列表
+    const demoSelectedConfigId = ref<number | null>(null) // 选中的演示配置ID
+    const demoDialogVisible = ref(false) // 演示配置对话框
+    // ==================== 演示模式 END ====================
+
     // 初始化 WebSocket 状态管理
     const socketStore = socket()
     const uuidStore = useUuidStore();
@@ -768,6 +777,20 @@ const onRemoteControl = async (type) => {
           if (runAction.value.goods_art_no) {
             scheduleSegment(runAction.value.goods_art_no)
           }
+
+          // 演示模式:拍摄完成后自动删除货号
+          if (isDemoMode.value) {
+            const demoGoodsNo = demoGoodsArtNo.value
+            setTimeout(async () => {
+              try {
+                await del({ goods_art_nos: [demoGoodsNo] })
+                ElMessage.info('演示拍摄完成,货号已自动删除')
+              } catch (e) {
+                console.error('自动删除演示货号失败:', e)
+              }
+            }, 2000)
+          }
+
           runLoading.value = false;
           runAction.value.goods_art_no = '';
           runAction.value.action = '';
@@ -943,6 +966,129 @@ const onRemoteControl = async (type) => {
     }
 
     /**
+     * 演示模式:获取配置列表
+     * @param type 0-左脚程序, 1-右脚程序
+     */
+    const fetchDemoConfigList = async (type: number) => {
+      demoConfigList.value = []
+      demoSelectedConfigId.value = null
+
+      const result = await getTopTabs({ type })
+      if (result.code == 0 && result.data?.tabs) {
+        demoConfigList.value = result.data.tabs
+        if (demoConfigList.value.length > 0) {
+          demoSelectedConfigId.value = demoConfigList.value[0].id
+        }
+      }
+    }
+
+    /**
+     * 演示模式:打开配置选择对话框
+     */
+    const openDemoMode = async () => {
+      demoGoodsArtNo.value = '演示货号1'
+      demoProgramType.value = '执行左脚程序'
+      demoSelectedConfigId.value = null
+      demoConfigList.value = []
+
+      // 获取左脚程序配置列表
+      await fetchDemoConfigList(0)
+
+      demoDialogVisible.value = true
+    }
+
+    /**
+     * 演示模式:监听程序类型变化,重新获取配置列表
+     */
+    const onDemoProgramTypeChange = async () => {
+      const type = demoProgramType.value === '执行左脚程序' ? 0 : 1
+      await fetchDemoConfigList(type)
+    }
+
+    /**
+     * 演示模式:开始演示
+     */
+    const startDemoMode = async () => {
+      if (!demoGoodsArtNo.value.trim()) {
+        ElMessage.error('请输入货号')
+        return
+      }
+      if (!demoSelectedConfigId.value) {
+        ElMessage.error('请选择配置')
+        return
+      }
+
+      // 设置演示模式标记
+      isDemoMode.value = true
+      // 设置货号
+      goods_art_no.value = demoGoodsArtNo.value
+      goods_art_no_tpl.value = demoGoodsArtNo.value
+
+      demoDialogVisible.value = false
+
+      // 直接启动拍摄
+      runGoods({
+        action: demoProgramType.value,
+        goods_art_no: demoGoodsArtNo.value
+      })
+    }
+
+    /**
+     * 演示模式:停止演示
+     * 执行完成后删除货号,然后退出演示模式
+     */
+    const stopDemoMode = async () => {
+      if (!isDemoMode.value) return
+
+      // 停止当前运行
+      if (runLoading.value || takePictureLoading.value) {
+        socketStore.sendMessage({
+          type: 'stop_action',
+        })
+      }
+
+      // 等待拍摄停止,然后删除货号
+      const demoGoodsNo = demoGoodsArtNo.value
+
+      // 延迟等待设备停止
+      setTimeout(async () => {
+        // 重置状态
+        resetStatus()
+
+        // 删除演示货号
+        try {
+          await del({ goods_art_nos: [demoGoodsNo] })
+        } catch (e) {
+          console.error('删除演示货号失败:', e)
+        }
+
+        // 退出演示模式
+        isDemoMode.value = false
+        goods_art_no.value = ''
+        goods_art_no_tpl.value = ''
+        ElMessage.success('已退出演示模式')
+      }, 500)
+    }
+
+    /**
+     * 演示模式:监听拍摄完成事件
+     * 演示模式下拍摄完成后自动删除货号
+     */
+    const handlePhotoTakeFinishForDemo = (goodsArtNo: string) => {
+      if (isDemoMode.value && goodsArtNo === demoGoodsArtNo.value) {
+        // 延迟删除,等待数据写入完成
+        setTimeout(async () => {
+          try {
+            await del({ goods_art_nos: [demoGoodsArtNo.value] })
+            ElMessage.info('演示拍摄完成,货号已自动删除')
+          } catch (e) {
+            console.error('自动删除演示货号失败:', e)
+          }
+        }, 2000)
+      }
+    }
+
+    /**
      * 删除所有货号。
      */
     const deleteAllGoods = function () {
@@ -993,6 +1139,17 @@ const onRemoteControl = async (type) => {
       initEventListeners,
       cleanupEventListeners,
       resetStatus,
+      // 演示模式
+      isDemoMode,
+      demoGoodsArtNo,
+      demoProgramType,
+      demoConfigList,
+      demoSelectedConfigId,
+      demoDialogVisible,
+      openDemoMode,
+      startDemoMode,
+      stopDemoMode,
+      onDemoProgramTypeChange,
     }
 }
 

+ 174 - 0
frontend/src/views/Photography/shot.vue

@@ -15,6 +15,60 @@
     @close="previewVisible = false"
   />
 
+  <!-- 演示模式全屏遮罩 -->
+  <div class="demo-mode-overlay" v-if="isDemoMode">
+    <div class="demo-mode-content">
+      <div class="demo-mode-header">
+        <span class="demo-mode-tag">演示模式</span>
+        <el-button type="danger" size="large" @click="stopDemoMode" :loading="runLoading || takePictureLoading">
+          停止演示
+        </el-button>
+      </div>
+      <div class="demo-mode-info">
+        <div class="demo-info-item">
+          <span class="demo-label">货号:</span>
+          <span class="demo-value">{{ demoGoodsArtNo }}</span>
+        </div>
+        <div class="demo-info-item">
+          <span class="demo-label">程序:</span>
+          <span class="demo-value">{{ demoProgramType }}</span>
+        </div>
+      </div>
+      <div class="demo-mode-hint">
+        拍摄中...
+      </div>
+    </div>
+  </div>
+
+  <!-- 演示配置选择对话框 -->
+  <el-dialog v-model="demoDialogVisible" title="选择演示配置" width="500px" :close-on-click-modal="false">
+    <el-form label-width="80px">
+      <el-form-item label="货号">
+        <el-input v-model="demoGoodsArtNo" placeholder="请输入演示货号" />
+      </el-form-item>
+      <el-form-item label="程序类型">
+        <el-radio-group v-model="demoProgramType" @change="onDemoProgramTypeChange">
+          <el-radio label="执行左脚程序">左脚程序</el-radio>
+          <el-radio label="执行右脚程序">右脚程序</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="选择配置">
+        <el-select v-model="demoSelectedConfigId" placeholder="请选择配置" style="width: 100%">
+          <el-option
+            v-for="item in demoConfigList"
+            :key="item.id"
+            :label="item.mode_name"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="demoDialogVisible = false">取消</el-button>
+      <el-button type="primary" @click="startDemoMode">开始演示</el-button>
+    </template>
+  </el-dialog>
+
   <div class="photography-page flex-col">
     <div class="main-container">
       <div class="content-wrapper flex-col">
@@ -38,6 +92,13 @@
 
         </div>
 
+        <!-- 演示模式按钮(仅惠利玛用户可见) -->
+        <div class="demo-mode-btn-wrap" v-if="isHLM">
+          <el-button type="warning" size="large" @click="openDemoMode" :disabled="isDemoMode">
+            演示模式
+          </el-button>
+        </div>
+
         <div class="step-section">
           <div class="step-number flex-col"><span class="text_22">2</span></div>
           <div class="step-two flex-col justify-between">
@@ -228,6 +289,10 @@ import { useThumbnails } from './composables/useThumbnails'
 import ImagePreview, { PreviewItem } from '@/components/PreviewImage/index.vue'
 import generate from '@/utils/menus/generate'
 import { ElMessage, ElMessageBox } from 'element-plus'
+import useUserInfo from '@/stores/modules/user'
+
+const userInfoStore = useUserInfo()
+const isHLM = computed(() => userInfoStore.isHLM)
 
 const {
   loading,
@@ -258,6 +323,17 @@ const {
   pageSize,
   currentPage,
   totalPages,
+  // 演示模式
+  isDemoMode,
+  demoGoodsArtNo,
+  demoProgramType,
+  demoConfigList,
+  demoSelectedConfigId,
+  demoDialogVisible,
+  openDemoMode,
+  startDemoMode,
+  stopDemoMode,
+  onDemoProgramTypeChange,
 } = usePhotography()
 
 // 搜索货号
@@ -457,6 +533,104 @@ watch(goodsList, () => {
 </style>
 
 <style scoped lang="scss">
+// 演示模式全屏遮罩
+.demo-mode-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.85);
+  z-index: 9999;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+
+  .demo-mode-content {
+    text-align: center;
+    color: #fff;
+
+    .demo-mode-header {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      gap: 30px;
+      margin-bottom: 40px;
+
+      .demo-mode-tag {
+        font-size: 48px;
+        font-weight: bold;
+        color: #FFD700;
+        text-shadow: 0 0 20px rgba(255, 215, 0, 0.5);
+        animation: pulse 2s infinite;
+      }
+
+      .el-button {
+        width: 200px;
+        height: 50px;
+        font-size: 18px;
+      }
+    }
+
+    .demo-mode-info {
+      display: flex;
+      flex-direction: column;
+      gap: 15px;
+      margin-bottom: 40px;
+      font-size: 20px;
+
+      .demo-info-item {
+        .demo-label {
+          color: #aaa;
+        }
+        .demo-value {
+          color: #fff;
+          font-weight: 500;
+        }
+      }
+    }
+
+    .demo-mode-hint {
+      font-size: 24px;
+      color: #67C23A;
+      animation: blink 1.5s infinite;
+    }
+  }
+}
+
+@keyframes pulse {
+  0%, 100% {
+    opacity: 1;
+    transform: scale(1);
+  }
+  50% {
+    opacity: 0.8;
+    transform: scale(1.05);
+  }
+}
+
+@keyframes blink {
+  0%, 100% {
+    opacity: 1;
+  }
+  50% {
+    opacity: 0.5;
+  }
+}
+
+// 演示模式按钮样式
+.demo-mode-btn-wrap {
+  display: flex;
+  justify-content: center;
+  margin: 20px 0 30px 50px;
+
+  .el-button {
+    width: 200px;
+    height: 44px;
+    font-size: 16px;
+  }
+}
+
 .photography-page {
   background-color: rgba(255, 255, 255, 1);
   position: relative;

+ 1 - 1
frontend/src/views/Setting/index.vue

@@ -20,7 +20,7 @@
         <img src="@/assets/images/setting/icon3a.png" class="nav-icon" v-else/>
         <span>其他设置</span>
       </div>
-      <div class="nav-item"  v-if="configInfoStore.appModel === 1"  :class="{'active': activeIndex === 4}"  @click="toggleTab(4)" v-log="{ describe: { action: '点击切换设置Tab', tab: '左右脚程序设置' } }">
+      <div class="nav-item"    :class="{'active': activeIndex === 4}"  @click="toggleTab(4)" v-log="{ describe: { action: '点击切换设置Tab', tab: '左右脚程序设置' } }">
         <img src="@/assets/images/setting/icon4.png" class="nav-icon" v-if="activeIndex !== 4"/>
         <img src="@/assets/images/setting/icon4a.png" class="nav-icon" v-else/>
         <span>左右脚程序设置</span>