浏览代码

feat(photography): 新增图像处理功能并优化拍摄流程

- 添加新路由 /photography/processImage 并引入对应组件
- 修改首页跳转逻辑,从 PhotographyShot 跳转至 PhotographyProcessImage
- 重构 shot.vue 组件,移除冗余代码及注释,优化结构
- 移除 appModel 判断逻辑,统一拍摄界面标题为“拍摄商品”
- 更新拍摄记录展示逻辑,简化按钮控制与显示条件
- 优化图像路径绑定逻辑,增强类型安全性和渲染性能
- 整合 usePhotography mixin,提取核心业务逻辑至独立模块
- 清理无用样式和组件引用,提升代码可维护性
panqiuyao 1 周之前
父节点
当前提交
3c81cff6d7

+ 8 - 0
frontend/src/router/index.ts

@@ -45,6 +45,14 @@ const routes: RouteRecordRaw[] = [
         }
     },
     {
+        path: "/photography/processImage",
+        name: "PhotographyProcessImage",
+        component: () => import("@/views/Photography/processImage.vue"),
+        meta: {
+            title: '处理图像'
+        }
+    },
+    {
         path: "/photography/detail",
         name: "PhotographyDetail",
         component: () => import("@/views/Photography/detail.vue"),

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

@@ -120,7 +120,7 @@ const goShot = async () => {
   socketConnect();
   configInfoStore.updateAppModel(2);
   router.push({
-    name: 'PhotographyShot'
+    name: 'PhotographyProcessImage'
   });
 };
 

+ 860 - 0
frontend/src/views/Photography/mixin/usePhotography.ts

@@ -0,0 +1,860 @@
+import { ref, onMounted, onBeforeUnmount, watchEffect, computed } from 'vue'
+import icpList from '@/utils/ipc'
+import client from "@/stores/modules/client";
+import socket from "@/stores/modules/socket";
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { getFilePath, getRouterUrl } from '@/utils/appfun'
+import { useRouter, useRoute } from "vue-router";
+import checkInfo from "@/stores/modules/check";
+import generate from '@/utils/menus/generate'
+import { clickLog, setLogInfo } from '@/utils/log'
+import { useUuidStore } from '@/stores/modules/uuid'
+import useUserInfo from "@/stores/modules/user";
+import configInfo from '@/stores/modules/config';
+import tokenInfo from "@/stores/modules/token";
+
+export default function usePhotography() {
+    const loading = ref(false)
+    const runLoading = ref(false)
+    const takePictureLoading = ref(false)
+    const goodsList = ref([])
+    const goods_art_no_tpl = ref('')
+    const goods_art_no = ref('')
+    const runAction = ref({
+      "action": "",
+      "goods_art_no": ""
+    })
+    const lastPhoto = ref({})
+    const showlastPhoto = ref(false)
+    const isDelGoodsGetList = ref(false)
+    const reNosObj = ref({
+      goods_art_no: null,
+      action: null,
+    })
+    const goodsArtNo = ref()
+    let smartShooterTimeout: ReturnType<typeof setTimeout> | null = null
+
+    // 初始化 WebSocket 状态管理
+    const socketStore = socket()
+    const uuidStore = useUuidStore();
+    const clientStore = client();
+    const Router = useRouter()
+    const route = useRoute();
+    const useUserInfoStore = useUserInfo();
+    const configInfoStore = configInfo();
+    const tokenInfoStore = tokenInfo();
+    const checkInfoStore = checkInfo()
+
+    // 抠图请求去重与延迟队列(key: goods_art_no, value: timeoutId)
+    const segmentQueue = new Map<string, ReturnType<typeof setTimeout>>()
+
+    function isGoodsStillInList(goodsArtNo: string): boolean {
+      return goodsList.value?.some((g: any) => g.goods_art_no === goodsArtNo) || false
+    }
+
+    function scheduleSegment(goodsArtNo: string) {
+      if (!goodsArtNo) return
+      // 若已存在,则重置计时(重新插入)
+      if (segmentQueue.has(goodsArtNo)) {
+        const t = segmentQueue.get(goodsArtNo)
+        if (t) clearTimeout(t)
+        segmentQueue.delete(goodsArtNo)
+      }
+      const timeoutId = setTimeout(async () => {
+        segmentQueue.delete(goodsArtNo)
+        if (!isGoodsStillInList(goodsArtNo)) return
+        try {
+          await socketStore.connectSocket();
+
+          socketStore.sendMessage({
+            type: 'segment_progress',
+            data: {
+              token: tokenInfoStore.getToken,
+              uuid: uuidStore?.getUuid || '',
+              goods_art_no: [goodsArtNo],
+            }
+          })
+        } catch (e) {
+          // 忽略发送异常,避免打断主流程
+        }
+      }, 20000)
+      segmentQueue.set(goodsArtNo, timeoutId)
+    }
+
+    /**
+     * 保存货号模板到货号变量中。
+     */
+    function saveGoodsArtNo() {
+      if (goods_art_no_tpl.value) {
+        goods_art_no.value = goods_art_no_tpl.value
+        ElMessage.success('商品货号' + goods_art_no.value + '获取成功,请在遥控器上按下左或右脚按键,启动拍摄')
+      }
+    }
+
+    /**
+     * 获取拍照记录。
+     * @param params - 可选参数,用于分页或其他筛选条件。
+     */
+    async function getPhotoRecords(params?: {}) {
+      if (loading.value) return;
+      loading.value = true;
+      clientStore.ipc.send(icpList.takePhoto.getPhotoRecords, {
+        ...params,
+        page: 1,
+        size: 100,
+      });
+      clientStore.ipc.on(icpList.takePhoto.getPhotoRecords, (event, result) => {
+
+        loading.value = false;
+        if (result.code === 0) {
+
+          clientStore.ipc.removeAllListeners(icpList.takePhoto.getPhotoRecords);
+
+          console.log('getPhotoRecords  print_time:' + new Date().toLocaleString())
+          console.log('getPhotoRecords  print_time:' + JSON.stringify(result.data.list))
+          goodsList.value = result.data.list
+          if (isDelGoodsGetList.value) {
+            isDelGoodsGetList.value = false;
+            return;
+          }
+          getLastPhotoRecord()
+
+        } else if (result.msg) {
+          ElMessage.error(result.msg)
+        }
+      });
+    }
+
+    /**
+     * 执行拍照操作。
+     * @param data - 包含拍摄所需的数据对象。
+     */
+    async function runGoods(data) {
+      if (runLoading.value || takePictureLoading.value) {
+        ElMessage.error('拍摄程序正在运行,请稍候')
+        return
+      }
+
+      await socketStore.connectSocket();
+
+      socketStore.sendMessage({
+        type: 'run_mcu',
+        data,
+      })
+      runLoading.value = true;
+      runAction.value.action = data.action
+      runAction.value.goods_art_no = data.goods_art_no
+      goods_art_no.value = ''
+      goods_art_no_tpl.value = ''
+      reNosObj.value.goods_art_no = null;
+      reNosObj.value.action = null;
+
+      clientStore.ipc.on(icpList.socket.message + '_run_mcu', (event, result) => {
+
+        clientStore.ipc.removeAllListeners(icpList.socket.message + '_run_mcu');
+        console.log('_run_mcu');
+        console.log(result);
+        if (result.code !== 0 && result.msg) {
+          ElMessage.error(result.msg)
+          runLoading.value = false
+          return;
+        } else {
+
+          ElMessage.success('开始拍摄,请稍后')
+        }
+      })
+
+
+    }
+
+    /**
+     * 格式化时间字符串。
+     * @param time - 原始时间字符串。
+     * @returns 格式化后的时间字符串,若输入为空则返回 null。
+     */
+    const getTime = function (time) {
+      if (!time) return null
+      return time.replace('T', ' ').substr(5, 11)
+    }
+
+    /**
+     * 删除所有商品货号的历史记录。
+     */
+    async function delAll() {
+      let params = goodsList.value.map(item => item.goods_art_no)
+      try {
+        await ElMessageBox.confirm('确定要删除当下的历史记录吗?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+        })
+        await clickLog({ describe: { action: '点击确认一键删除', goods_art_nos: params } }, route)
+        del({ goods_art_nos: params })
+      } catch (e) {
+        await clickLog({ describe: { action: '点击取消一键删除' } }, route)
+      }
+    }
+
+    /**
+     * 删除指定的商品货号。
+     * @param params - 包含需要删除的货号列表的对象。
+     */
+    const delGoods = async function (params) {
+      try {
+        await ElMessageBox.confirm('确定要删除货号:' + params.goods_art_nos[0] + '的拍摄数据吗?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+        })
+        await clickLog({ describe: { action: '点击确认删除货号', goods_art_no: params.goods_art_nos?.[0] } }, route)
+        del(params)
+      } catch (e) {
+        await clickLog({ describe: { action: '点击取消删除货号', goods_art_no: params.goods_art_nos?.[0] } }, route)
+      }
+    }
+
+    /**
+     * 删除指定的商品货号。
+     * @param params - 包含需要删除的货号列表的对象。
+     */
+    const del = async function (params) {
+      console.log(icpList.takePhoto.delectGoodsArts, params);
+      clientStore.ipc.removeAllListeners(icpList.takePhoto.delectGoodsArts);
+      clientStore.ipc.send(icpList.takePhoto.delectGoodsArts, params);
+      clientStore.ipc.on(icpList.takePhoto.delectGoodsArts, (event, result) => {
+        clientStore.ipc.removeAllListeners(icpList.takePhoto.delectGoodsArts);
+        console.log("icpList.takePhoto.delectGoodsArts", params);
+        if (result.code === 0) {
+          isDelGoodsGetList.value = true
+          ElMessage.info('货号删除成功')
+          getPhotoRecords()
+          if (reNosObj.value.goods_art_no) {
+            runGoods(
+              {
+                "action": reNosObj.value.action,
+                "goods_art_no": reNosObj.value.goods_art_no
+              })
+
+          }
+        } else if (result.msg) {
+          ElMessage.error(result.msg)
+        }
+      });
+
+    }
+
+    //单个重拍
+
+    const reTakePicture = async (img) => {
+      if (!img.id) return;
+      if (img.image_path) {
+        try {
+          await ElMessageBox.confirm('此操作会先删除此数据,需要继续吗?', '提示', {
+            confirmButtonText: '确定',
+            cancelButtonText: '取消',
+          })
+          await clickLog({ describe: { action: '点击确认单张重拍', goods_art_no: img.goods_art_no, action_name: img.action_name } }, route)
+        } catch (e) {
+          await clickLog({ describe: { action: '点击取消单张重拍', goods_art_no: img.goods_art_no, action_name: img.action_name } }, route)
+          return
+        }
+
+      }
+
+      runLoading.value = true;
+      reNosObj.value.goods_art_no = img.goods_art_no
+      reNosObj.value.action = 're_take_picture'
+
+      let params = {
+        id: img.action_id
+      }
+
+      clientStore.ipc.removeAllListeners(icpList.setting.getDeviceConfigDetail);
+
+      clientStore.ipc.send(icpList.setting.getDeviceConfigDetail, params);
+
+
+      clientStore.ipc.on(icpList.setting.getDeviceConfigDetail, (event, result) => {
+
+        console.log('getDeviceConfigDetail')
+        console.log(result)
+        if (result.code == 0 && result.data) {
+          clientStore.ipc.removeAllListeners(icpList.setting.getDeviceConfigDetail);
+
+          this_run_mcu_single(result.data)
+        } else if (result.msg) {
+          runLoading.value = false;
+          reNosObj.value.goods_art_no = ''
+          reNosObj.value.action = ''
+          ElMessage.error(result.msg)
+        }
+      });
+
+      function this_run_mcu_single(data) {
+
+        clientStore.ipc.removeAllListeners(icpList.socket.message + '_run_mcu_single');
+        socketStore.sendMessage({
+          type: 'run_mcu_single',
+          data: {
+            camera_height: Number(data.camera_height),
+            camera_angle: Number(data.camera_angle),
+            led_switch: data.led_switch,
+            id: 0,
+            mode_type: data.mode_type,
+            turntable_position: Number(data.turntable_position),
+            action_name: data.action_name || '测试',
+            turntable_angle: Number(data.turntable_angle),
+            shoe_upturn: Number(data.shoe_upturn),
+            action_index: 1,
+            number_focus: 0,
+            take_picture: false,
+            pre_delay: 0,
+            after_delay: 0,
+          }
+        });
+
+
+        clientStore.ipc.on(icpList.socket.message + '_run_mcu_single', async (event, result) => {
+          console.log('_run_mcu_single_row')
+          clientStore.ipc.removeAllListeners(icpList.socket.message + '_run_mcu_single');
+          this_re_take_picture()
+        })
+      }
+      async function this_re_take_picture() {
+
+        await ElMessageBox.alert('已复位到该视图下,请把鞋子摆放完毕之后,点击按钮开始重拍', '提示', {
+          confirmButtonText: "开始重拍",
+          showClose: false,
+          closeOnClickModal: false,
+          closeOnPressEscape: false
+        })
+        await clickLog({ describe: { action: '点击开始重拍', goods_art_no: img.goods_art_no } }, route)
+
+
+        socketStore.sendMessage({
+          type: 'smart_shooter_photo_take',
+          "data": { "id": img.id, "goods_art_no": img.goods_art_no },
+        })
+      }
+
+
+    }
+
+    const resetStatus = () => {
+      runLoading.value = false;
+      reNosObj.value.goods_art_no = ''
+      reNosObj.value.action = ''
+      runAction.value.goods_art_no = '';
+      runAction.value.action = '';
+    }
+
+    //货号重拍
+    const reTakePictureNos = async (goods_art_no, item) => {
+
+      try {
+        await ElMessageBox.confirm('此操作会先删除删除货号:' + goods_art_no + '的拍摄数据吗,需要继续吗?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+        })
+        await clickLog({ describe: { action: '点击确认重拍货号', goods_art_no } }, route)
+      } catch (e) {
+        await clickLog({ describe: { action: '点击取消重拍货号', goods_art_no } }, route)
+        return
+      }
+      reNosObj.value.goods_art_no = goods_art_no
+      reNosObj.value.action = '执行左脚程序'
+      console.log(item);
+      if (item.items && typeof item.items === 'object' && item.items[0].PhotoRecord.image_deal_mode) {
+        reNosObj.value.action = '执行右脚程序'
+      }
+      del({ goods_art_nos: [goods_art_no] })
+    }
+
+    /**
+     * 检查是否可以进入下一步操作。
+     */
+    const next = async function () {
+      if (runLoading.value) {
+        ElMessage.error('正在拍摄中,请稍候')
+        return;
+      }
+      if (goodsList.length) {
+        ElMessage.error('请先拍摄商品。')
+        return;
+      }
+    }
+
+    const oneClickStop = () => {
+
+      if (!(runLoading.value || takePictureLoading.value)) {
+        ElMessage.error('拍摄程序已结束,不需要单独停止!')
+        return
+      } else {
+
+        socketStore.sendMessage({
+          type: 'stop_action',
+        })
+      }
+    }
+
+    /**
+     * 打开最近一张拍摄图
+     */
+    const getLastPhotoRecord = async () => {
+
+      return;
+
+      if (goodsList.value && goodsList.value.length === 0) return;
+      clientStore.ipc.removeAllListeners(icpList.takePhoto.getLastPhotoRecord);
+      clientStore.ipc.send(icpList.takePhoto.getLastPhotoRecord,);
+
+      clientStore.ipc.on(icpList.takePhoto.getLastPhotoRecord, (event, result) => {
+        console.log('getLastPhotoRecord');
+        console.log(result.data?.goods_art_no);
+        clientStore.ipc.removeAllListeners(icpList.takePhoto.getLastPhotoRecord);
+        if (result.code === 0) {
+          if (lastPhoto.value?.photo_file_name) {
+            //   if(  lastPhoto.value?.image_path == result.data?.image_path) return;
+
+            if (runAction.value.goods_art_no === result.data?.goods_art_no) {
+              showlastPhoto.value = true
+            }
+          }
+          lastPhoto.value = result.data
+        } else if (result.msg) {
+          ElMessage.error(result.msg)
+        }
+      });
+    }
+
+    /**
+     * 打开主图详情页面。
+     */
+    function openPhotographyDetail() {
+      // 埋点:开始生成
+      clickLog({ describe: { action: '开始生成', goods_count: goodsList.value.length, goods_art_nos: goodsList.value.map(item => item.goods_art_no) } }, route);
+
+      if (runLoading.value || takePictureLoading.value) {
+        ElMessage.error('正在拍摄中,请稍候')
+        return;
+      }
+      const { href } = Router.resolve({
+        name: 'PhotographyDetail',
+        query: {
+          goods_art_nos: goodsList.value.map(item => item.goods_art_no),
+        }
+      })
+
+      clientStore.ipc.removeAllListeners(icpList.utils.openMain);
+      let params = {
+        title: '主图与详情生成',
+        width: 3840,
+        height: 2160,
+        frame: true,
+        id: "PhotographyDetail",
+        url: getRouterUrl(href)
+      }
+      clientStore.ipc.send(icpList.utils.openMain, params);
+    }
+
+    /*高级生成*/
+    const onGenerateCLick = (menu, item) => {
+      if (menu.name === '历史记录') {
+
+        menu.click()
+        return
+      }
+      const firstWithImagePath = item.items.find(
+        (image) => image.PhotoRecord.image_path
+      );
+
+      if (firstWithImagePath) {
+        menu.click({
+          query: {
+            image_path: firstWithImagePath.PhotoRecord.image_path
+          }
+        })
+      } else {
+        menu.click()
+      }
+
+    }
+
+    // 打开输出目录:appConfig.appPath + '/build/extraResources/py/output'
+    const openOutputDir = () => {
+      try {
+
+        const appPath = configInfoStore?.appConfig?.appPath || ''
+        if (!appPath) {
+          ElMessage.error('未获取到应用目录 appPath')
+          return
+        }
+        const fullPath = `${appPath}\\output`
+        clientStore.ipc.removeAllListeners(icpList.utils.shellFun);
+        clientStore.ipc.send(icpList.utils.shellFun, {
+          action: 'openMkPath',
+          params: fullPath.replace(/\//g, '\\')
+        });
+      } catch (e) {
+        console.error(e)
+        ElMessage.error('打开目录失败')
+      }
+    }
+
+    const menu = computed(() => {
+      if (configInfoStore.appModel === 2) {
+        return [
+          {
+            type: 'setting'
+          },
+          {
+            name: '切换模式',
+            click() {
+              configInfoStore.updateAppModel(1)
+              Router.push({
+                name: 'PhotographyCheck'
+              })
+            }
+          },
+          {
+            name: '生成图目录',
+            click() {
+              openOutputDir()
+            }
+          },
+          {
+            ...generate
+          }
+        ]
+      }
+      if (useUserInfoStore.userInfo.brand_company_code === '1300' || configInfoStore.appConfig.debug) {
+        return [
+          {
+            type: 'setting'
+          },
+          {
+            type: 'developer'
+          },
+          {
+            name: '生成图目录',
+            click() {
+              openOutputDir()
+            }
+          },
+          {
+            ...generate,
+          }
+        ]
+      }
+
+
+      return [
+        {
+          type: 'setting'
+        },
+        {
+          name: '生成图目录',
+          click() {
+            openOutputDir()
+          }
+        },
+        {
+          ...generate
+        }
+      ]
+
+
+    })
+
+    const onRemoteControl = (type) => {
+      if (type == 'take_picture') {
+        // 埋点:手动拍照
+        clickLog({ describe: { action: '点击遥控器拍照按钮' } }, route);
+
+        if (runLoading.value || takePictureLoading.value) {
+          ElMessage.error('拍摄程序正在运行,请稍候')
+          return
+        }
+
+        ElMessage.success('正在拍摄中,请稍候')
+        socketStore.sendMessage({
+          type: 'handler_take_picture',
+        })
+        return;
+      }
+
+
+      if (!goods_art_no.value) {
+        ElMessage.error('请在左侧第一步中,先扫描货号或者手动输入货号!')
+        goodsArtNo.value?.focus() // 聚焦输入框
+        return;
+      }
+      let action = '执行左脚程序'
+      if (type === 'right') action = '执行右脚程序'
+
+      // 埋点:遥控器启动拍摄
+      clickLog({ describe: { action: `点击遥控器${type === 'left' ? '左脚' : '右脚'}按钮`, goods_art_no: goods_art_no.value } }, route);
+
+      runGoods({
+        "action": action,
+        "goods_art_no": goods_art_no.value,
+      })
+
+    }
+
+    // 初始化事件监听
+    const initEventListeners = () => {
+      // 监听蓝牙扫描事件
+      clientStore.ipc.on(icpList.socket.message + '_blue_tooth_scan', (event, result) => {
+
+        console.log('_blue_tooth_scan')
+        if (result.code === 0 && result.data?.data) {
+          console.log(goods_art_no.value);
+          if (!goods_art_no.value) {
+            ElMessage.error('请在左侧第一步中,先扫描货号或者手动输入货号!')
+            goodsArtNo.value?.focus() // 聚焦输入框
+            return;
+          }
+          runGoods({
+            ...result.data?.data,
+            goods_art_no: goods_art_no.value
+          })
+
+        }
+      });
+
+      // 监听图片处理完成事件
+      clientStore.ipc.on(icpList.socket.message + '_image_process', (event, result) => {
+        console.log('_image_process')
+        console.log(result)
+        getPhotoRecords()
+        // 延迟两秒再获取一遍数据
+        setTimeout(() => {
+          getPhotoRecords()
+        }, 3000)
+      })
+
+      // 监听拍照完成事件
+      clientStore.ipc.on(icpList.socket.message + '_photo_take', (event, result) => {
+        console.log('_photo_take')
+        console.log(result)
+        if (result.status === 2 && result.msg.includes('执行完成')) {
+          getPhotoRecords()
+          // 延迟两秒再获取一遍数据
+          setTimeout(() => {
+            getPhotoRecords()
+          }, 3000)
+          takePictureLoading.value = false;
+          return;
+        }
+        if (result.code !== 0 && result.msg) {
+          ElMessage.error(result.msg)
+          takePictureLoading.value = false;
+        }
+
+      })
+
+      // 监听一键停止
+      clientStore.ipc.on(icpList.socket.message + '_stop_action', (event, result) => {
+        console.log('_stop_action')
+        console.log(result)
+        oneClickStop()
+
+      })
+
+      // 监听一键停止结束
+      clientStore.ipc.on(icpList.socket.message + '_run_mcu_stop', (event, result) => {
+        console.log('_run_mcu_stop')
+
+        resetStatus()
+      })
+
+      // 监听拍照完成后的最终状态事件
+      clientStore.ipc.on(icpList.socket.message + '_photo_take_finish', (event, result) => {
+        console.log('_photo_take_finish')
+        console.log(result)
+        if (result.code === 0) {
+          setLogInfo(route, { action: '全部拍摄完成', goods_art_no: runAction.value.goods_art_no });
+          // 全部拍摄完成后,触发抠图队列
+          if (runAction.value.goods_art_no) {
+            scheduleSegment(runAction.value.goods_art_no)
+          }
+          runLoading.value = false;
+          runAction.value.goods_art_no = '';
+          runAction.value.action = '';
+          setTimeout(() => {
+            showlastPhoto.value = false
+          }, 3000)
+        }
+
+      })
+
+      // 监听手动触发拍照事件
+      clientStore.ipc.on(icpList.socket.message + '_handler_take_picture', async (event, result) => {
+        console.log('_handler_take_picture')
+        console.log(result)
+        if (result.code === 0) {
+          if (runLoading.value || takePictureLoading.value) {
+            ElMessage.error('拍摄程序正在运行,请稍候')
+            return
+          }
+
+          ElMessage.success('正在拍摄中,请稍候')
+
+          takePictureLoading.value = true;
+          await socketStore.connectSocket();
+          socketStore.sendMessage(result.data)
+
+          getPhotoRecords()
+          // 延迟两秒再获取一遍数据
+          setTimeout(() => {
+            getPhotoRecords()
+          }, 3000)
+
+        } else if (result.msg) {
+          ElMessage.error(result.msg)
+        }
+
+      })
+
+      //拍照成功  SmartShooter
+      clientStore.ipc.on(icpList.socket.message + '_smart_shooter_photo_take', async (event, result) => {
+        console.log('_smart_shooter_photo_take');
+        console.log(result);
+
+        if (result.code === 0) {
+          if (!result.data.goods_art_no) return;
+          setLogInfo(route, { action: '单张拍摄完成', goods_art_no: result.data.goods_art_no });
+          if (reNosObj.value?.goods_art_no === result.data.goods_art_no) {
+            runLoading.value = false;
+          }
+
+          // 单张重拍完成且存在重拍货号时,触发抠图队列
+          scheduleSegment(result.data.goods_art_no)
+          if (smartShooterTimeout) {
+            clearTimeout(smartShooterTimeout);
+          }
+          setTimeout(() => {
+            showlastPhoto.value = true;
+            lastPhoto.value = {
+              file_path: result.data.photo_file_name
+            };
+            setTimeout(() => {
+              if (!runAction.value.goods_art_no) {
+                showlastPhoto.value = false;
+              }
+            }, 3000)
+          }, 100);
+          smartShooterTimeout = setTimeout(() => {
+            getPhotoRecords();
+
+            if (!runAction.value.goods_art_no) {
+              showlastPhoto.value = false;
+            }
+          }, 2000);
+        } else if (result.msg) {
+
+          runLoading.value = false;
+          reNosObj.value.goods_art_no = ''
+          reNosObj.value.action = ''
+          ElMessage.error(result.msg)
+        }
+      })
+
+      // 监听拍照完成后的最终状态事件
+      clientStore.ipc.on(icpList.socket.message + '_run_mcu_update', (event, result) => {
+        console.log('run_mcu_updat print_time:' + new Date().toLocaleString())
+        console.log('run_mcu_update  print_time:' + JSON.stringify(result))
+
+        if (result.code === 0) {
+          if (result.data?.file_path) {
+            if (lastPhoto.value?.file_path == result.data?.file_path) return;
+            let goods_art_no = runAction.value.goods_art_no || reNosObj.value.goods_art_no
+            if (goods_art_no === result.data?.goods_art_no) {
+              showlastPhoto.value = true
+              goodsList.value.map(item => {
+                if (item.goods_art_no === result.data?.goods_art_no) {
+                  item.items[result.data.image_index].PhotoRecord.image_path = result.data?.file_path
+                  result.data.action_name = item.items[result.data.image_index].action_name
+                  setTimeout(() => {
+                    item.items[result.data.image_index].PhotoRecord.image_path = result.data?.file_path
+                  }, 1000)
+
+                  setTimeout(() => {
+                    showlastPhoto.value = false
+                  }, 3000)
+                }
+              })
+              setTimeout(() => {
+                getPhotoRecords()
+              }, 2000)
+            }
+            lastPhoto.value = result.data
+          }
+        } else if (result.msg) {
+          ElMessage.error(result.msg)
+        }
+        if (reNosObj.value.goods_art_no) {
+          resetStatus()
+        }
+      })
+    }
+
+    // 清理事件监听
+    const cleanupEventListeners = () => {
+      clientStore.ipc.removeAllListeners(icpList.socket.message + '_blue_tooth_scan');
+      clientStore.ipc.removeAllListeners(icpList.socket.message + '_image_process');
+      clientStore.ipc.removeAllListeners(icpList.socket.message + '_run_mcu');
+      clientStore.ipc.removeAllListeners(icpList.socket.message + '_photo_take');
+      clientStore.ipc.removeAllListeners(icpList.socket.message + '_photo_take_finish');
+      clientStore.ipc.removeAllListeners(icpList.socket.message + '_run_mcu_update');
+      clientStore.ipc.removeAllListeners(icpList.socket.message + '_stop_action');
+      clientStore.ipc.removeAllListeners(icpList.socket.message + '_smart_shooter_photo_take');
+      clientStore.ipc.removeAllListeners(icpList.socket.message + '_run_mcu_stop');
+      clientStore.ipc.removeAllListeners(icpList.socket.message + '_digicam_take_picture');
+      clientStore.ipc.removeAllListeners(icpList.socket.message + '_segment_progress');
+
+      // 清理抠图队列的定时器
+      try {
+        segmentQueue.forEach((t) => { if (t) clearTimeout(t) })
+        segmentQueue.clear()
+      } catch (e) { }
+    }
+
+    // 监听蓝牙扫描
+    checkInfoStore.set_blue_tooth_scan_NO('')
+    watchEffect(async () => {
+      if (checkInfoStore.blue_tooth_scan_NO) {
+        ElMessage.success('商品货号' + checkInfoStore.blue_tooth_scan_NO + '获取成功,请在遥控器上按下左或右脚按键,启动拍摄')
+        goods_art_no.value = checkInfoStore.blue_tooth_scan_NO
+        checkInfoStore.set_blue_tooth_scan_NO('')
+      }
+    })
+
+    return {
+      loading,
+      runLoading,
+      takePictureLoading,
+      goodsList,
+      goods_art_no_tpl,
+      goods_art_no,
+      runAction,
+      lastPhoto,
+      showlastPhoto,
+      goodsArtNo,
+      menu,
+      configInfoStore,
+      getTime,
+      getFilePath,
+      getPhotoRecords,
+      delAll,
+      delGoods,
+      reTakePicture,
+      reTakePictureNos,
+      oneClickStop,
+      onRemoteControl,
+      openPhotographyDetail,
+      onGenerateCLick,
+      initEventListeners,
+      cleanupEventListeners,
+    }
+}
+

+ 314 - 0
frontend/src/views/Photography/processImage.vue

@@ -0,0 +1,314 @@
+<template>
+  <headerBar
+      title="处理图像"
+      showUser
+      :menu="menu"
+  />
+
+  <hardware-check/>
+
+  <div class="photography-page flex-col">
+    <div class="main-container">
+      <div class="history-section flex-col koutu-section">
+          <span class="history-title flex between">
+            <div>拍摄记录</div>
+            <div class="c-666 fs-12" v-if="goodsList.length" >
+                    <el-button :disabled="runLoading || takePictureLoading" @click="delAll" class="input-button" type="danger" size="mini" v-log="{ describe: { action: '一键删除所有记录' } }">一键删除</el-button>
+            </div>
+          </span>
+          <img class="divider-line" referrerpolicy="no-referrer" src="@/assets/images/Photography/divider-line.png" />
+
+          <div class="history-warp">
+            <div v-if="!goodsList.length" class="fs-14 c-666 mar-top-50">
+              {{ loading ? '数据正在加载中,请稍候...' : '暂无数据,请先进行拍摄'}}
+            </div>
+              <div v-else class="history-item clearfix"  v-for="item in goodsList" :key="item.goods_art_no" style="padding:10px;">
+                <div class="flex  between flex-item  c-333">
+                  <div class="chaochu flex-item flex left">货号:{{ item.goods_art_no }}</div>
+                  <div >
+                    <el-dropdown :disabled="runLoading || takePictureLoading" >
+                      <el-button :disabled="runLoading || takePictureLoading" size="small"  plain style="margin-right: 5px" >高级生成</el-button>
+                      <template #dropdown>
+                        <el-dropdown-menu>
+                          <el-dropdown-item
+                              v-for="menu in generate.children"
+                              @click.native="onGenerateCLick(menu,item)">{{ menu.name }}</el-dropdown-item>
+                        </el-dropdown-menu>
+                      </template>
+                    </el-dropdown>
+
+                    <el-button size="small" :disabled="runLoading || takePictureLoading" @click="delGoods({goods_art_nos:[item.goods_art_no]})" v-log="{ describe: { action: '删除货号', goods_art_no: item.goods_art_no } }">删除</el-button>
+                  </div>
+                </div>
+                <div class="flex  between flex-item  c-333" style="margin-top: 5px">
+                  <div class="c-999 fs-12">{{ getTime(item.action_time) }}</div>
+                </div>
+                <div class="mar-top-10 clearfix history-item_image_wrap" style="width: 100%" >
+                    <component class="history-item_image"
+                         v-for="image in item.items"
+                               :key="image.action_id || image.action_name"
+                               v-loading="!image.PhotoRecord.image_path && runAction.goods_art_no == item.goods_art_no"
+                               :is="image.PhotoRecord.image_path ? 'div' : 'p'"
+                    >
+                      <span class="tag">{{ image.action_name }}</span>
+                      <el-popover
+                          popper-class="koutu-image-popper"
+                          placement="right"
+                          :hide-after="0"
+                          width="50%"
+                          offset="20"
+                          v-if="image.PhotoRecord.image_path"
+                      >
+                        <template #reference>
+                          <div class="flex el-image_view">
+                            <el-image  :src="getFilePath(image.PhotoRecord.image_path)"  fit="contain" >
+                              <template #error>
+                                <div class="image-slot"></div>
+                              </template>
+                            </el-image>
+                          </div>
+                        </template>
+
+                        <el-image  :src="getFilePath(image.PhotoRecord.image_path)"  fit="contain" >
+                          <template #error>
+                            <div class="image-slot"></div>
+                          </template>
+                        </el-image>
+                      </el-popover>
+
+                      <div class="flex el-image_view"  v-else>
+                      <el-image :src="getFilePath(image.PhotoRecord.image_path)"  fit="contain">
+                        <template #error>
+                          <div class="image-slot"></div>
+                        </template>
+                      </el-image>
+                      </div>
+                    </component>
+                    <div v-if="item.items.length < 5"
+                      v-for="n in (5 - item.items.length)"
+                         :key="n"
+                         class="history-item_image"
+                    >
+                      <span class="tag" style="font-size: 12px;">暂未配置</span>
+
+                    </div>
+                </div>
+              </div>
+
+          </div>
+
+          <div
+            class="next-step button--primary1 flex-col"
+            :class="{ 'is-disabled': !goodsList.length || runLoading || takePictureLoading }"
+            @click="(!goodsList.length || runLoading || takePictureLoading) ? null : openPhotographyDetail()"
+            v-log="{ describe: { action: '点击开始生成' } }"
+          >
+            <span class="next-step-text">开始生成</span>
+          </div>
+        </div>
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+import headerBar from '@/components/header-bar/index.vue'
+import { onMounted, onBeforeUnmount } from 'vue'
+import HardwareCheck from '@/components/check/index.vue'
+import usePhotography from './mixin/usePhotography'
+import generate from '@/utils/menus/generate'
+
+const {
+  loading,
+  runLoading,
+  takePictureLoading,
+  goodsList,
+  runAction,
+  menu,
+  getTime,
+  getFilePath,
+  getPhotoRecords,
+  delAll,
+  delGoods,
+  oneClickStop,
+  openPhotographyDetail,
+  onGenerateCLick,
+  initEventListeners,
+  cleanupEventListeners,
+} = usePhotography()
+
+onMounted(async () => {
+  await getPhotoRecords()
+  initEventListeners()
+})
+
+onBeforeUnmount(() => {
+  cleanupEventListeners()
+})
+</script>
+
+<style lang="scss">
+.koutu-image-popper {
+  width: calc(100vw - 470px) !important;
+  right: 70px !important;
+  top: 100px !important;
+  height: calc(100vh - 170px) !important;
+  transform: translate(0px, 0px) !important;
+
+  .el-image {
+    width: 100%;
+    height:100%;
+    display: block;
+
+    .el-image__inner {
+      width: 100%;
+      height:100%;
+      display: block;
+
+    }
+  }
+}
+</style>
+
+<style scoped lang="scss">
+.photography-page {
+  background-color: rgba(255, 255, 255, 1);
+  position: relative;
+  .main-container {
+    position: relative;
+    display: flex;
+  }
+}
+
+.history-section {
+        background-color: rgba(234, 236, 237, 1);
+        width: 100%;
+        height: calc(100vh - 30px);
+
+        .history-title {
+          width: calc(100% - 20px);
+          height: 22px;
+          overflow-wrap: break-word;
+          color: rgba(51, 51, 51, 1);
+          font-size: 16px;
+          font-family: PingFangSC-Semibold;
+          font-weight: 600;
+          text-align: center;
+          white-space: nowrap;
+          line-height: 22px;
+          margin: 9px 0 0 10px;
+        }
+
+        .divider-line {
+          width: 100%;
+          height: 1px;
+          margin-top: 9px;
+        }
+
+        .history-warp {
+          flex-grow: 1;
+          overflow: auto;
+          height: calc(100% - 125px);
+
+          .history-item {
+            .history-item_image_wrap {
+              padding-bottom: 20px;
+              border-bottom: 1px solid #CCCCCC;
+            }
+            .history-item_image {
+              .tag {
+                color: #bbb;
+                position: absolute;
+                left: 0;
+                right: 0;
+                top:50%;
+                margin-top: -15px;
+                line-height: 20px;
+              }
+
+              position: relative;
+              width: 70px;
+              height: 70px;
+              float: left;
+              margin: 6px 0 0 6px;
+              background: #F7F7F7;
+              .el-image {
+                display: block;
+                width:100%;
+                height: 100%;
+              }
+              &:first-child {
+                width: 146px;
+                height: 146px;
+              }
+              &.el-loading-parent--relative{
+                ::v-deep {
+                  .el-loading-mask { display: none}
+                }
+              }
+
+            }
+
+
+            .el-image_view {
+              display: flex;
+              width: 100%;
+              height: 100%;
+
+              .reset-button {
+                width: 40px;
+                text-align: center;
+                height: 20px;
+                position: absolute;
+                left:50%;
+                top:50%;
+                padding: 0px;
+                margin-left:-20px;
+                margin-top:-10px;
+                color: #ffffff;
+                font-size: 14px;
+                background: rgba(0,0,0,0.6);
+                border-radius: 12px;
+                display: none;
+                cursor: pointer;
+              }
+              &:hover {
+                .reset-button {
+                  display: block;
+                }
+              }
+            }
+            p:first-of-type {
+              ::v-deep {
+                .el-loading-mask { display: block !important;}
+              }
+            }
+
+
+          }
+        }
+
+        .next-step {
+          height: 50px;
+          background-size: 100% 100%;
+          width: 100%;
+          line-height: 50px;
+          cursor: pointer;
+
+          &.is-disabled {
+            opacity: 0.5;
+            cursor: not-allowed;
+            pointer-events: none;
+          }
+
+          .next-step-text {
+            width: 100%;
+            overflow-wrap: break-word;
+            color: rgba(255, 255, 255, 1);
+            text-align: center;
+            white-space: nowrap;
+
+          }
+        }
+
+      }
+</style>
+

文件差异内容过多而无法显示
+ 89 - 1006
frontend/src/views/Photography/shot.vue


部分文件因为文件数量过多而无法显示