Переглянути джерело

feat(camera): 新增相机参数配置并重构拍照流程

- 新增ISO、光圈、快门、曝光补偿参数的读取与设置面板
- 重构测试拍照流程,支持移动后自动拍照及超时回退预览
- 删除步骤时自动清理对应的相机参数配置
- 修复camera_configs合并时step_camera数据被覆盖的问题
- 新增onTestPhoto和onBackPreview事件及对应处理逻辑
- 优化编辑面板布局,按钮改为弹性均分并限制面板宽度
- 注册run_mcu和smart_shooter_photo_take消息路由
panqiuyao 1 тиждень тому
батько
коміт
5a28f54595

+ 3 - 1
electron/utils/socket.js

@@ -10,13 +10,15 @@ const fs = require('fs');
 
 const typeToMessage = {
   run_mcu_single:['seeting','default'],
+  run_mcu:['seeting','default'],
+  smart_shooter_photo_take:['seeting','default'],
   get_device_info:['seeting','default'],
   get_deviation_data:["developer","seeting"],
   set_deviation:"developer",
   get_mcu_other_info:"developer",
   set_mcu_other_info:"developer",
   send_command:"developer",
-  smart_shooter_get_camera_property:"seeting",
+  smart_shooter_get_camera_property:['seeting','default'],
   detail_progress:"PhotographyDetail",
   segment_progress:"PhotographyDetail",
   upper_footer_progress:"PhotographyDetail",

+ 95 - 39
frontend/src/views/Photography/check.vue

@@ -72,10 +72,13 @@
       ref="editData"
       @onClose="onClose"
       @onRunMcuSingle="onRunMcuSingle"
+      @onTestPhoto="takePictures"
+      @onBackPreview="backToPreview"
       @onReady="onEditRowReady"
       @onPointChange="switchPreviewPoint"
       :addRowData="addRowData"
       :disablePointConfig="!isSetting"
+      :showingTestPhoto="isSetting && step === 2"
     />
 
   </div>
@@ -369,55 +372,81 @@ function handleAutoFocus() {
 const loading = ref(true)
 const editData = ref(null);
 const imageTplPath = ref(null)
+const expectTestPhoto = ref(false)
+let testPhotoToken = 0
+let shootAfterMove = false
+function buildSingleShotData() {
+  const row = editData.value?.editRowData || {}
+  const data: Record<string, any> = {
+    camera_height: Number(row.camera_height),
+    camera_angle: Number(row.camera_angle),
+    led_switch: row.led_switch,
+    id: 0,
+    mode_type: row.mode_type,
+    turntable_position: Number(row.turntable_position),
+    action_name: row.action_name || '测试',
+    turntable_angle: Number(row.turntable_angle),
+    shoe_upturn: Number(row.shoe_upturn),
+    camera_focal_distance: Number(row.camera_focal_distance || 0),
+    action_index: 1,
+    number_focus: isSetting.value ? Number(row.number_focus || 0) : 0,
+    take_picture: true,
+    pre_delay: 0,
+    after_delay: 0,
+    point_name: row.point_name || currentPointName.value || 'A',
+    is_move_device: row.is_move_device,
+  }
+  if (isSetting.value) {
+    const cameraConfigs = editData.value?.getCameraConfigs?.()
+    if (cameraConfigs) {
+      data.camera_configs = cameraConfigs
+    }
+  }
+  return data
+}
+
+function backToPreview() {
+  testPhotoToken += 1
+  expectTestPhoto.value = false
+  shootAfterMove = false
+  step.value = 1
+  imageTplPath.value = null
+  loading.value = false
+  showVideo(currentPointName.value)
+}
+
 function takePictures() {
   if(loading.value) return;
   console.log(editData);
-  console.log(editData.value.editRowData);
+  console.log(editData.value?.editRowData);
 
   if (clientStore.isClient) {
     loading.value = true;
+    if (isSetting.value) {
+      const token = ++testPhotoToken
+      expectTestPhoto.value = true
+      shootAfterMove = true
+      setTimeout(() => {
+        if (token !== testPhotoToken || !expectTestPhoto.value || step.value !== 1) return
+        expectTestPhoto.value = false
+        shootAfterMove = false
+        loading.value = false
+        showVideo(currentPointName.value)
+        ElMessage.warning('未收到测试照片,已返回预览')
+      }, 30000)
+    }
+    const shotData = buildSingleShotData()
+    if (isSetting.value) {
+      shotData.take_picture = false
+      shotData.number_focus = 0
+      delete shotData.camera_configs
+    }
     console.log('takePictures');
-    console.log({
-      camera_height: Number(editData.value.editRowData.camera_height),
-      camera_angle:  Number(editData.value.editRowData.camera_angle),
-      led_switch:editData.value.editRowData.led_switch,
-      id:0,
-      mode_type:editData.value.editRowData.mode_type,
-      turntable_position:Number(editData.value.editRowData.turntable_position),
-      action_name:editData.value.editRowData.action_name || '测试',
-      turntable_angle: Number(editData.value.editRowData.turntable_angle),
-      shoe_upturn: Number(editData.value.editRowData.shoe_upturn),
-      camera_focal_distance: Number(editData.value.editRowData.camera_focal_distance),
-      action_index:1,
-      number_focus:0,
-      take_picture:true,
-      pre_delay:0,
-      after_delay:0,
-      point_name:editData.value.editRowData.point_name || currentPointName.value  || 'A',
-      is_move_device:editData.value.editRowData.is_move_device
-    })
+    console.log(shotData)
     hideVideo(currentPointName.value).then(() => {
       socketStore.sendMessage({
         type: 'run_mcu_single',
-        data: {
-          camera_height: Number(editData.value.editRowData.camera_height),
-          camera_angle:  Number(editData.value.editRowData.camera_angle),
-          led_switch:editData.value.editRowData.led_switch,
-          id:0,
-          mode_type:editData.value.editRowData.mode_type,
-          turntable_position:Number(editData.value.editRowData.turntable_position),
-          action_name:editData.value.editRowData.action_name || '测试',
-          turntable_angle: Number(editData.value.editRowData.turntable_angle),
-          shoe_upturn: Number(editData.value.editRowData.shoe_upturn),
-          camera_focal_distance: Number(editData.value.editRowData.camera_focal_distance),
-          action_index:1,
-          number_focus:0,
-          take_picture:true,
-          pre_delay:0,
-          after_delay:0,
-          point_name:editData.value.editRowData.point_name || currentPointName.value  || 'A',
-          is_move_device:editData.value.editRowData.is_move_device
-        }
+        data: shotData,
       });
     })
   }
@@ -450,10 +479,22 @@ function  createMainImage (file_path){
 }
 
 
+clientStore.ipc.on(icpList.socket.message+'_run_mcu', async (event, result) => {
+  if (!expectTestPhoto.value) return
+  if (result?.code === 0) return
+  testPhotoToken += 1
+  expectTestPhoto.value = false
+  loading.value = false
+  showVideo(currentPointName.value)
+  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);
+  testPhotoToken += 1
+  expectTestPhoto.value = false
   if(result.code === 0 && result.data?.photo_file_name){
     imageTplPath.value  = result.data?.photo_file_name
     step.value = 2
@@ -490,6 +531,20 @@ clientStore.ipc.on(icpList.socket.message+'_run_mcu_single', async (event, resul
   }else {
     loading.value = false;
     console.log('_run_mcu_single');
+    if (shootAfterMove && result.code === 0) {
+      shootAfterMove = false
+      loading.value = true
+      socketStore.sendMessage({
+        type: 'smart_shooter_photo_take',
+        data: {
+          point_name: currentPointName.value || 'A',
+          goods_art_no: '',
+          id: 0,
+        }
+      })
+      return
+    }
+    expectTestPhoto.value = false
     showVideo(currentPointName.value)
     if(result.code !== 0 && result.msg) ElMessage.error(result.msg)
   }
@@ -531,6 +586,7 @@ onBeforeUnmount(() => {
   clientStore.ipc.removeAllListeners(icpList.camera.PreviewHide);
   clientStore.ipc.removeAllListeners(icpList.camera.PreviewShow);
   clientStore.ipc.removeAllListeners(icpList.socket.message+'_run_mcu_single');
+  clientStore.ipc.removeAllListeners(icpList.socket.message+'_run_mcu');
   clientStore.ipc.removeAllListeners(icpList.socket.message+'_smart_shooter_photo_take');
 })
 </script>

+ 225 - 12
frontend/src/views/Photography/components/editRow.vue

@@ -18,6 +18,31 @@
         <el-button size="small" plain style="margin-left: 8px;" @click="onRefreshPointList" v-log="{ describe: { action: '点击刷新点位列表' } }">刷新</el-button>
       </el-form-item>
 
+      <template v-if="!disablePointConfig">
+        <el-form-item v-for="item in cameraPropDefs" :key="item.key" :label="item.label">
+          <el-select
+            v-model="cameraForm[item.key]"
+            filterable
+            clearable
+            placeholder="不修改"
+            style="width: 108px;"
+            :loading="cameraPropLoading"
+          >
+            <el-option label="不修改" value="" />
+            <el-option v-for="opt in optionsFor(item.key)" :key="opt" :label="opt" :value="opt" />
+          </el-select>
+          <el-button
+            v-if="item.key === 'ISO'"
+            size="small"
+            plain
+            style="margin-left: 8px;"
+            :loading="cameraPropLoading"
+            @click="fetchCameraProperties"
+            v-log="{ describe: { action: '点击读取当前点位相机参数' } }"
+          >读取</el-button>
+        </el-form-item>
+      </template>
+
       <!-- 多相机模式 - 仅拍照模式 -->
 <!--      <el-form-item label="是否移动设备" v-if="isMultiCameraMode">
         <div class="flex-row">
@@ -109,15 +134,16 @@
     <div class="btn-row mar-top-20">
       <div class="normal-btn" @click="refreshData" v-if="id" v-log="{ describe: { action: '点击刷新配置' } }">刷新</div>
       <div class="normal-btn" @click="close" v-if="id" v-log="{ describe: { action: '点击关闭编辑行' } }">取消</div>
-      <div class="normal-btn"  v-loading="captureLoading"  v-if="!id && editRowData.is_system" @click="testShoesFlip" v-log="{ describe: { action: '点击运行测试拍照' } }">运行</div>
-      <div class="primary-btn"  v-loading="captureLoading" @click="saveRow" v-log="{ describe: { action: '点击保存设备配置' } }">{{ id ? '保存并关闭' : '保存' }}</div>
+      <div class="normal-btn" @click="emit('onBackPreview')" v-if="!disablePointConfig && showingTestPhoto" v-log="{ describe: { action: '点击返回预览' } }">回预览</div>
+      <div class="normal-btn"  v-loading="captureLoading"  v-if="showRunButton" @click="onRunClick" v-log="{ describe: { action: '点击运行测试拍照' } }">拍照</div>
+      <div class="primary-btn"  v-loading="captureLoading" @click="saveRow" v-log="{ describe: { action: '点击保存设备配置' } }">{{ id ? '保存关闭' : '保存' }}</div>
     </div>
   </div>
 
 </template>
 
 <script setup lang="ts">
-import { ref, defineProps, defineEmits , watch, onMounted, computed } from 'vue'
+import { ref, reactive, defineProps, defineEmits , watch, onMounted, computed } from 'vue'
 import icpList from '@/utils/ipc';
 import { digiCamControlWEB } from  '@/utils/appconfig'
 import {ElMessage} from "element-plus";
@@ -138,12 +164,149 @@ const isMultiCameraMode = computed(() => userInfoStore.isMultiCameraMode)
 
 // 已配置相机的点位列表
 const availablePoints = ref<string[]>(['A'])
+let cachedConfigs: any = null
+
+const cameraPropDefs = [
+  { key: 'ISO', label: 'ISO' },
+  { key: 'Aperture', label: '光圈' },
+  { key: 'ShutterSpeed', label: '快门' },
+  { key: 'Exposure', label: '曝光补偿' },
+]
+const cameraForm = reactive({
+  ISO: '',
+  Aperture: '',
+  ShutterSpeed: '',
+  Exposure: '',
+})
+const cameraRanges = reactive({
+  ISO: [] as string[],
+  Aperture: [] as string[],
+  ShutterSpeed: [] as string[],
+  Exposure: [] as string[],
+})
+const cameraPropLoading = ref(false)
+let cameraPropRequest = 0
+
+function isWritable(item) {
+  const flag = item?.CameraPropertyIsWriteable
+  if (flag == null) return true
+  return flag === true || flag === 1 || flag === '1' || flag === 'true' || flag === 'True'
+}
+
+function optionsFor(key) {
+  const range = cameraRanges[key] || []
+  const current = cameraForm[key]
+  if (current && !range.includes(current)) return [current, ...range]
+  return range
+}
+
+function parseCameraConfigs(raw) {
+  if (!raw) return {}
+  if (typeof raw === 'string') {
+    try {
+      const parsed = JSON.parse(raw)
+      return parsed && typeof parsed === 'object' ? parsed : {}
+    } catch (error) {
+      return {}
+    }
+  }
+  if (typeof raw === 'object') return raw
+  return {}
+}
+
+function applyCameraConfigs(raw) {
+  const saved = parseCameraConfigs(raw)
+  cameraPropDefs.forEach(({ key }) => {
+    cameraForm[key] = saved[key] != null && saved[key] !== '' ? String(saved[key]) : ''
+  })
+  return Object.keys(saved).length > 0
+}
+
+function getCameraConfigs() {
+  const payload = {}
+  cameraPropDefs.forEach(({ key }) => {
+    if (cameraForm[key] !== '' && cameraForm[key] != null) {
+      payload[key] = String(cameraForm[key])
+    }
+  })
+  return Object.keys(payload).length ? payload : undefined
+}
+
+function applySavedCamera(row, fallbackId) {
+  const nested = parseCameraConfigs(row?.camera_configs)
+  if (Object.keys(nested).length) {
+    applyCameraConfigs(nested)
+    return
+  }
+  const flat = {}
+  cameraPropDefs.forEach(({ key }) => {
+    if (row && row[key] != null && row[key] !== '') flat[key] = row[key]
+  })
+  if (Object.keys(flat).length) {
+    applyCameraConfigs(flat)
+    return
+  }
+  if (row && Object.prototype.hasOwnProperty.call(row, 'camera_configs')) {
+    applyCameraConfigs(null)
+    return
+  }
+  if (fallbackId) {
+    applyCameraConfigs(cachedConfigs?.camera_configs?.step_camera?.[String(fallbackId)])
+  }
+}
+
+function fetchCameraProperties() {
+  if (!clientStore.isClient || props.disablePointConfig) return
+  const requestId = ++cameraPropRequest
+  const point = editRowData.value.point_name || 'A'
+  cameraPropLoading.value = true
+  const channel = icpList.socket.message + '_smart_shooter_get_camera_property'
+  const timer = setTimeout(() => {
+    if (requestId !== cameraPropRequest) return
+    cameraPropLoading.value = false
+    clientStore.ipc.removeAllListeners(channel)
+    ElMessage.warning('读取相机参数超时,请再点一次读取')
+  }, 8000)
+  clientStore.ipc.removeAllListeners(channel)
+  clientStore.ipc.on(channel, (_event, result) => {
+    if (requestId !== cameraPropRequest) return
+    clearTimeout(timer)
+    cameraPropLoading.value = false
+    clientStore.ipc.removeAllListeners(channel)
+    if (result?.code !== 0 || !Array.isArray(result.data)) {
+      ElMessage.warning(typeof result?.msg === 'string' ? result.msg : '未获取到当前点位的相机参数')
+      return
+    }
+    const dropped = []
+    cameraPropDefs.forEach(({ key, label }) => {
+      const item = result.data.find((prop) => prop?.CameraPropertyType === key)
+      const range = item && isWritable(item) && Array.isArray(item.CameraPropertyRange)
+        ? item.CameraPropertyRange.map((opt) => String(opt))
+        : []
+      cameraRanges[key] = range
+      if (cameraForm[key] && range.length && !range.includes(String(cameraForm[key]))) {
+        cameraForm[key] = ''
+        dropped.push(label)
+      }
+    })
+    if (dropped.length) {
+      ElMessage.warning(`${dropped.join('、')}不在当前点位相机范围内,已清空`)
+    }
+  })
+  socketStore.sendMessage({
+    type: 'smart_shooter_get_camera_property',
+    data: { point_name: point }
+  })
+}
 
 // 刷新点位列表(从 getAllUserConfigs API 获取已绑定相机的点位)
 // 同时会校验当前选中的点位:若该点位已不再可用,自动回退到第一个可用点位
 const refreshPointList = async () => {
   try {
     const result = await getAllUserConfigs({})
+    if (result.code == 0) {
+      cachedConfigs = result.data?.configs || {}
+    }
     if (result.code == 0 && result.data?.configs?.camera_configs?.iso_config) {
       const isoConfig = result.data.configs.camera_configs.iso_config
       availablePoints.value = Object.keys(isoConfig).filter(key => {
@@ -186,6 +349,7 @@ const onRefreshPointList = async () => {
   } else if (previousPoint !== editRowData.value.point_name && availablePoints.value[0]) {
     emit('onPointChange', availablePoints.value[0])
   }
+  fetchCameraProperties()
 }
 
 
@@ -214,6 +378,10 @@ const props = defineProps({
   disablePointConfig: {
     type: Boolean,
     default: false
+  },
+  showingTestPhoto: {
+    type: Boolean,
+    default: false
   }
 })
 
@@ -226,8 +394,9 @@ const maxFocalDistance = ref(8000); // 相机焦段距离最大值,默认8000
 
 // 监听点位变化,切换预览
 watch(() => editRowData.value.point_name, (newPoint, oldPoint) => {
-  if (initStatus.value && newPoint && newPoint !== oldPoint) {
+  if (initStatus.value && newPoint && oldPoint && newPoint !== oldPoint) {
     emit('onPointChange', newPoint)
+    fetchCameraProperties()
   }
 })
 
@@ -266,7 +435,6 @@ onMounted(async ()=>{
   await refreshPointList()
   if(props.addRowData.mode_type){
     console.log(props.addRowData);
-    initStatus.value = true
     editRowData.value = props.addRowData;
     // 多相机模式默认值
     if (isMultiCameraMode.value) {
@@ -276,8 +444,13 @@ onMounted(async ()=>{
       }
       editRowData.value.is_move_device = editRowData.value.is_move_device ?? 1
     }
+    if (props.addRowData.copy_from_id || props.addRowData.camera_configs) {
+      applySavedCamera(props.addRowData, props.addRowData.copy_from_id)
+    }
+    initStatus.value = true
     testShoesFlip()
     emit('onReady', editRowData.value.point_name || 'A')
+    fetchCameraProperties()
     return
   }
   let fun = getDeviceConfigDetail;
@@ -308,9 +481,13 @@ onMounted(async ()=>{
       }
       editRowData.value.is_move_device = editRowData.value.is_move_device ?? 1
     }
+    if (props.id) {
+      applySavedCamera(editRowData.value, props.id)
+    }
     initStatus.value = true;
     testShoesFlip()
     emit('onReady', editRowData.value.point_name || 'A')
+    fetchCameraProperties()
   }
 
 })
@@ -359,6 +536,14 @@ async function changeNum(type, min, max) {
 
 /*测试拍照*/
 const captureLoading = ref(false)
+const showRunButton = computed(() => !props.disablePointConfig || (!props.id && editRowData.value.is_system))
+function onRunClick() {
+  if (!props.disablePointConfig) {
+    emit('onTestPhoto')
+    return
+  }
+  testShoesFlip()
+}
 function testShoesFlip(){
   if (captureLoading.value) {
     return
@@ -425,7 +610,7 @@ function testShoesFlip(){
   }
 }
 
-const emit = defineEmits([ 'confirm','onClose','onRunMcuSingle','onReady','onPointChange']);
+const emit = defineEmits([ 'confirm','onClose','onRunMcuSingle','onReady','onPointChange','onTestPhoto','onBackPreview']);
 const close = ()=>{
   console.log('onClose')
   emit('onClose')
@@ -454,6 +639,9 @@ const refreshData = async () => {
         }
         editRowData.value.is_move_device = editRowData.value.is_move_device ?? 1
       }
+      await refreshPointList()
+      applySavedCamera(editRowData.value, props.id)
+      fetchCameraProperties()
       ElMessage.success('刷新成功')
     } else {
       ElMessage.error('刷新失败')
@@ -493,6 +681,13 @@ const saveRow = async () => {
   const saveData = {
     ...editRowData.value,
   }
+  delete saveData.copy_from_id
+  cameraPropDefs.forEach(({ key }) => {
+    delete saveData[key]
+  })
+  if (!props.disablePointConfig) {
+    saveData.camera_configs = getCameraConfigs() || null
+  }
 
   // 多相机模式添加点位信息
   if (isMultiCameraMode.value) {
@@ -512,6 +707,7 @@ const saveRow = async () => {
 // 暴露给父组件
 defineExpose({
   editRowData, // 父组件可通过 editData.value.editRowData 访问
+  getCameraConfigs,
 });
 </script>
 <style>
@@ -519,6 +715,10 @@ defineExpose({
 
 <style lang="scss" scoped>
 .editrow_wrap {
+  width: 330px;
+  flex: 0 0 330px;
+  max-height: 100vh;
+  overflow-y: auto;
 }
 .config-type{
   font-size: 16px;
@@ -606,30 +806,43 @@ defineExpose({
 .btn-row{
   display: flex;
   align-items: center;
-  justify-content: center;
-  gap: 10px;
+  justify-content: stretch;
+  gap: 6px;
+  width: 100%;
+  padding: 0 8px;
+  box-sizing: border-box;
 }
 .primary-btn{
-  width: 100px;
+  flex: 1 1 0;
+  width: auto;
+  min-width: 0;
   height: 30px;
+  padding: 0 2px;
   background: linear-gradient( 135deg, #2FB0FF 0%, #B863FB 100%);
   border-radius: 4px;
   color: #fff;
-  font-size: 14px;
+  font-size: 12px;
   text-align: center;
   line-height: 30px;
+  white-space: nowrap;
   cursor: pointer;
+  box-sizing: border-box;
 }
 .normal-btn{
-  width: 100px;
+  flex: 1 1 0;
+  width: auto;
+  min-width: 0;
   height: 30px;
+  padding: 0 2px;
   background: #fff;
   border: 1px solid #CCCCCC;
   border-radius: 4px;
-  font-size: 14px;
+  font-size: 12px;
   text-align: center;
   line-height: 30px;
+  white-space: nowrap;
   cursor: pointer;
+  box-sizing: border-box;
 }
 
 .mode-hint {

+ 1 - 0
frontend/src/views/Setting/components/CameraConfig.vue

@@ -390,6 +390,7 @@ watch(multiConfig, (newVal) => {
     }
   })
   emit('update:camera_configs', {
+    ...(props.camera_configs || {}),
     iso_config: isoConfig,
   })
 }, { deep: true })

+ 21 - 2
frontend/src/views/Setting/components/action_config.vue

@@ -107,7 +107,7 @@ import socket from "@/stores/modules/socket";
 const socketStore = socket(); // WebSocket状态管理实例
 const tokenInfoStore = tokenInfo();
 
-import  { getTopTabs, getDeviceConfigs,setLeftRightConfig,restConfig,sortDeviceConfig,setTabName,delDviceConfig, copyDeviceConfig } from '@/apis/setting'
+import  { getTopTabs, getDeviceConfigs,setLeftRightConfig,restConfig,sortDeviceConfig,setTabName,delDviceConfig, copyDeviceConfig, getAllUserConfigs, setAllUserConfigs } from '@/apis/setting'
 
 // 表格数据和对话框状态
 const tableData = ref([]); // 配置表格数据
@@ -307,12 +307,30 @@ const copyRow = (row, index) => {
     number_focus: row.number_focus,
     pre_delay: row.pre_delay,
     after_delay: row.after_delay,
-    camera_focal_distance: row.camera_focal_distance
+    camera_focal_distance: row.camera_focal_distance,
+    point_name: row.point_name,
+    is_move_device: row.is_move_device,
+    camera_configs: row.camera_configs,
+    copy_from_id: row.id,
   }
   dialogVisible.value = true;
   editTitle.value = '新增步骤';
 };
 
+async function removeStepCamera(id) {
+  if (!id) return
+  try {
+    const result = await getAllUserConfigs({})
+    const configs = result?.data?.configs
+    const stepCamera = configs?.camera_configs?.step_camera
+    if (!configs || !stepCamera || stepCamera[String(id)] == null) return
+    delete stepCamera[String(id)]
+    await setAllUserConfigs({ configs })
+  } catch (error) {
+    console.error('删除步骤相机参数失败:', error)
+  }
+}
+
 /**
  * 删除指定行的配置。
  * @param {Object} row - 当前行的数据
@@ -333,6 +351,7 @@ const deleteRow = (row, index) => {
     })
 
     if (result.code == 0) {
+      await removeStepCamera(row.id)
       getList();
       ElMessage.success('删除成功');
     }

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

@@ -619,7 +619,11 @@ const handlePaddingInput = () => {
 
 // 添加更新camera_configs的方法
 const updateCameraConfigs = (configs) => {
-  formData.camera_configs = configs;
+  const prevStep = formData.camera_configs?.step_camera
+  formData.camera_configs = { ...(configs || {}) }
+  if (formData.camera_configs.step_camera == null && prevStep) {
+    formData.camera_configs.step_camera = prevStep
+  }
 };
 
 const toggleTab = async (item) => {
@@ -738,6 +742,20 @@ const saveSetting = async (index) => {
     submitData.basic_configs.is_flip_800image = flipValue;
 
   }
+  if (index === 3) {
+    try {
+      const latest = await getAllUserConfigs()
+      const stepCamera = latest?.data?.configs?.camera_configs?.step_camera
+      if (stepCamera) {
+        submitData.camera_configs = {
+          ...(submitData.camera_configs || {}),
+          step_camera: stepCamera,
+        }
+      }
+    } catch (error) {
+      console.error('合并步骤相机参数失败:', error)
+    }
+  }
   const params = JSON.parse(JSON.stringify({
     configs:submitData
   }))