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

feat(setting): 添加动态参数配置功能

- 新增 AdvancedConfig 组件实现动态参数设置界面
- 添加动态参数的获取、显示和保存功能
- 支持数值类型参数的精度控制和只读属性
- 在设置页面添加高级设置选项卡
- 集成 Socket 通信支持动态参数配置操作
- 更新权限配置支持新的动态参数接口
panqiuyao 12 часов назад
Родитель
Сommit
fe7adbd19e

+ 5 - 2
electron/utils/socket.js

@@ -11,7 +11,7 @@ const fs = require('fs');
 const typeToMessage = {
   run_mcu_single:['seeting','default'],
   get_device_info:['seeting','default'],
-  get_deviation_data:"developer",
+  get_deviation_data:["developer","seeting"],
   set_deviation:"developer",
   get_mcu_other_info:"developer",
   set_mcu_other_info:"developer",
@@ -22,7 +22,10 @@ const typeToMessage = {
   upper_footer_progress:"PhotographyDetail",
   scene_progress:"PhotographyDetail",
   upload_goods_progress:"PhotographyDetail",
-  detail_result_progress:"PhotographyDetail"
+  detail_result_progress:"PhotographyDetail",
+  "get_dynamic_config":"seeting",
+  "set_dynamic_config":"seeting",
+  "get_mcu_info":"seeting",
 }
 
 // MCU 错误日志配置(从 config 读取,默认100条)

+ 313 - 0
frontend/src/views/Setting/components/AdvancedConfig.vue

@@ -0,0 +1,313 @@
+<template>
+  <div class="advanced-config-wrap">
+    <div class="config-container">
+      <div class="config-header">
+        <span class="header-title">动态参数设置</span>
+        <el-button text @click="refreshAll" :loading="loading">
+          <el-icon><Refresh /></el-icon>
+          刷新
+        </el-button>
+      </div>
+
+      <div class="content-area" v-loading="loading">
+        <div v-if="dynamicParams.length === 0 && !loading" class="empty-tip">
+          <el-icon class="empty-icon"><FolderOpened /></el-icon>
+          <span>暂无可配置的动态参数</span>
+        </div>
+        <div class="param-grid" v-else>
+          <div v-for="(param, index) in dynamicParams" :key="index" class="param-card">
+            <div class="param-header">
+              <span class="param-name">{{ param.tips || `参数${param.addr}` }}</span>
+              <el-tag v-if="param.readonly" type="info" size="small" effect="plain">只读</el-tag>
+            </div>
+            <div class="param-body">
+              <el-input-number
+                v-if="param.type === 'float'"
+                v-model="param.value"
+                :precision="param.precision || 1"
+                :step="Math.pow(0.1, param.precision || 1)"
+                :disabled="param.readonly"
+                controls-position="right"
+                size="default"
+                placeholder="请输入"
+              />
+              <el-input
+                v-else
+                v-model="param.value"
+                :disabled="param.readonly"
+                size="default"
+                placeholder="请输入"
+              />
+            </div>
+          </div>
+        </div>
+        <div class="content-footer" v-if="dynamicParams.length > 0">
+          <span class="param-count">共 {{ dynamicParams.length }} 个参数</span>
+          <el-button type="primary" @click="saveDynamicParams" :loading="saving">
+            <el-icon v-if="!saving"><Check /></el-icon>
+            保存参数
+          </el-button>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue';
+import { ElMessage } from 'element-plus';
+import { Refresh, Check, FolderOpened } from '@element-plus/icons-vue';
+import socket from "@/stores/modules/socket";
+import client from "@/stores/modules/client";
+import icpList from '@/utils/ipc';
+
+const socketStore = socket();
+const clientStore = client();
+
+// 动态参数列表
+const dynamicParams = ref<any[]>([]);
+
+// 加载状态
+const loading = ref(false);
+const saving = ref(false);
+
+// 获取动态参数配置
+async function fetchDynamicConfig() {
+  return new Promise<void>((resolve) => {
+    socketStore.sendMessage({
+      type: 'get_dynamic_config',
+      data: {}
+    });
+
+    const handler = (event: any, result: any) => {
+      if (result.code === 0 && result.data) {
+        dynamicParams.value = Object.keys(result.data).map(key => ({
+          addr: result.data[key].addr,
+          tips: result.data[key].tips,
+          readonly: result.data[key].readonly,
+          type: result.data[key].type,
+          precision: result.data[key].precision,
+          value: result.data[key].value
+        }));
+      } else if (result.msg) {
+        ElMessage.error(result.msg);
+      }
+      clientStore.ipc.removeAllListeners(icpList.socket.message + '_get_dynamic_config');
+      resolve();
+    };
+
+    clientStore.ipc.once(icpList.socket.message + '_get_dynamic_config', handler);
+
+    setTimeout(() => {
+      clientStore.ipc.removeAllListeners(icpList.socket.message + '_get_dynamic_config');
+      resolve();
+    }, 10000);
+  });
+}
+
+// 保存动态参数
+async function saveDynamicParams() {
+  saving.value = true;
+
+  const paramsToSave = dynamicParams.value.filter(p => !p.readonly);
+
+  if (paramsToSave.length === 0) {
+    ElMessage.warning('没有可保存的参数');
+    saving.value = false;
+    return;
+  }
+
+  let successCount = 0;
+  let failCount = 0;
+
+  for (const param of paramsToSave) {
+    const success = await saveSingleParam(param);
+    if (success) {
+      successCount++;
+    } else {
+      failCount++;
+    }
+  }
+
+  if (failCount === 0) {
+    ElMessage.success(`保存成功 (${successCount}项)`);
+  } else {
+    ElMessage.warning(`部分保存成功 (成功${successCount}项, 失败${failCount}项)`);
+  }
+
+  saving.value = false;
+}
+
+// 保存单个参数
+function saveSingleParam(param: any): Promise<boolean> {
+  return new Promise((resolve) => {
+    socketStore.sendMessage({
+      type: 'set_dynamic_config',
+      data: {
+        addr: param.addr,
+        tips: param.tips,
+        readonly: param.readonly,
+        type: param.type,
+        precision: param.precision,
+        value: String(param.value)
+      }
+    });
+
+    const handler = (event: any, result: any) => {
+      clientStore.ipc.removeAllListeners(icpList.socket.message + '_set_dynamic_config');
+      resolve(result.code === 0);
+    };
+
+    clientStore.ipc.once(icpList.socket.message + '_set_dynamic_config', handler);
+
+    setTimeout(() => {
+      clientStore.ipc.removeAllListeners(icpList.socket.message + '_set_dynamic_config');
+      resolve(false);
+    }, 5000);
+  });
+}
+
+// 刷新数据
+async function refreshAll() {
+  loading.value = true;
+  await fetchDynamicConfig();
+  loading.value = false;
+}
+
+// 组件挂载
+onMounted(() => {
+  refreshAll();
+});
+</script>
+
+<style scoped lang="scss">
+$primary-color: #409eff;
+$primary-light: #ecf5ff;
+$bg-color: #f5f7fa;
+$border-color: #e4e7ed;
+$text-primary: #303133;
+$text-secondary: #909399;
+
+.advanced-config-wrap {
+  padding: 0;
+  min-height: calc(100vh - 120px);
+  border-radius: 8px;
+}
+
+.config-container {
+  padding: 0;
+}
+
+.config-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 16px 20px;
+  background: #fff;
+  border-bottom: 1px solid $border-color;
+
+  .header-title {
+    font-size: 15px;
+    font-weight: 600;
+    color: $text-primary;
+  }
+
+  .el-button {
+    color: $text-secondary;
+    font-size: 13px;
+
+    &:hover {
+      color: $primary-color;
+    }
+  }
+}
+
+.content-area {
+  background: #fff;
+  padding: 20px;
+}
+
+.content-footer {
+  margin-top: 24px;
+  padding-top: 20px;
+  border-top: 1px dashed $border-color;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+
+  .param-count {
+    font-size: 13px;
+    color: $text-secondary;
+  }
+
+  .el-button {
+    min-width: 120px;
+  }
+}
+
+.param-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+  gap: 16px;
+}
+
+.param-card {
+  background: #fff;
+  border: 1px solid $border-color;
+  border-radius: 10px;
+  padding: 16px;
+  transition: all 0.3s;
+
+  &:hover {
+    border-color: $primary-color;
+    box-shadow: 0 4px 12px rgba(64, 158, 255, 0.1);
+  }
+
+  .param-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 12px;
+
+    .param-name {
+      font-size: 14px;
+      font-weight: 500;
+      color: $text-primary;
+    }
+  }
+
+  .param-body {
+    :deep(.el-input-number) {
+      width: 100% !important;
+
+      .el-input__wrapper {
+        border-radius: 6px;
+      }
+    }
+
+    :deep(.el-input) {
+      width: 100%;
+
+      .el-input__wrapper {
+        border-radius: 6px;
+      }
+    }
+  }
+}
+
+.empty-tip {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 12px;
+  padding: 60px 20px;
+  color: $text-secondary;
+  font-size: 14px;
+
+  .empty-icon {
+    font-size: 48px;
+    color: #dcdfe6;
+  }
+}
+</style>

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

@@ -25,6 +25,11 @@
         <img src="@/assets/images/setting/icon4a.png" class="nav-icon" v-else/>
         <span>左右脚程序设置</span>
       </div>
+      <div class="nav-item"  :class="{'active': activeIndex === 5}" @click="toggleTab(5)" v-log="{ describe: { action: '点击切换设置Tab', tab: '高级设置' } }">
+        <img src="@/assets/images/setting/icon4.png" class="nav-icon" v-if="activeIndex !== 5"/>
+        <img src="@/assets/images/setting/icon4a.png" class="nav-icon" v-else/>
+        <span>高级设置</span>
+      </div>
     </nav>
 
     <div class="form-container">
@@ -205,9 +210,12 @@
             <actionConfig/>
 
           </div>
+          <div v-if="activeIndex === 5">
+            <AdvancedConfig />
+          </div>
     </div>
       <div class="text-center mt-8">
-        <button class="bg-gradient-to-r from-primary" @click="onSava(activeIndex)" v-if="activeIndex !== 4" v-log="{ describe: { action: '点击保存设置', tabIndex: activeIndex } }">
+        <button class="bg-gradient-to-r from-primary" @click="onSava(activeIndex)" v-if="activeIndex !== 4 && activeIndex !== 5" v-log="{ describe: { action: '点击保存设置', tabIndex: activeIndex } }">
           保存
         </button>
       </div>
@@ -239,8 +247,12 @@ import { preview } from '@planckdev/element-plus/utils'
 import actionConfig from './components/action_config.vue'
 import otherConfig from './components/otherConfig'
 import CameraConfig from './components/CameraConfig';
+import AdvancedConfig from './components/AdvancedConfig.vue';
+import useUserInfo from '@/stores/modules/user';
 useCheckInfo();
 
+const userInfoStore = useUserInfo();
+
 //点击三次  打开资源目录
 
 import DebugPanel from './components/DebugPanel.vue';
@@ -591,7 +603,7 @@ const updateCameraConfigs = (configs) => {
 
 const toggleTab = async (item) => {
   const oldType = activeIndex.value;
-  // 切换前保存当前 Tab 配置(包含相机配置 3)
+  // 切换前保存当前 Tab 配置(包含相机配置 3),高级设置(5)有独立保存逻辑,跳过自动保存
   if ([0,1,2,3].includes(oldType)) {
     const next = await saveSetting(oldType);
     if (next === false) return false;
@@ -601,6 +613,11 @@ const toggleTab = async (item) => {
 };
 
 const onSava = async (index)=>{
+  // 高级设置(5)有独立保存逻辑
+  if (index === 5) {
+    ElMessage.info('请在高级设置中使用保存按钮保存');
+    return;
+  }
   const next =  await  saveSetting(index)
 
   if(next !== false){