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

feat(setting): 添加配置复制功能和标签页滚动优化

- 新增复制配置功能,支持创建配置副本
- 优化标签页容器样式,支持水平滚动
- 添加选中标签页自动滚动到视图中心的功能
- 引入 nextTick 确保 DOM 更新后执行滚动
- 修复标签页切换时的滚动定位问题
panqiuyao 16 часов назад
Родитель
Сommit
f437d2f51c

+ 26 - 0
frontend/src/apis/setting.ts

@@ -139,6 +139,32 @@ export async function setTabName(data){
     return result;
 }
 
+
+
+
+//拷贝配置
+export async function copyDeviceConfig(data){
+    const result = await POST('/api/ai_image/camera_machine/copy_device_config',data);
+
+    // 同步到Python
+    try {
+        const clientStore = client();
+        const tokenInfoStore = tokenInfo();
+        const token = tokenInfoStore.getToken;
+        await clientStore.ipc.invoke(icpList.setting.syncActions, {
+            token: token || '',
+            action: 'copy_device_config',
+            id: data.tab_id,
+            mode_name: data.tab_name
+        });
+    } catch (error) {
+        console.error('同步重命名配置到Python失败:', error);
+        // 同步失败不影响主流程
+    }
+
+    return result;
+}
+
 //删除可执行命令
 export async function delDviceConfig(data){
     const result = await POST('/api/ai_image/camera_machine/remove_device_config',data);

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

@@ -7,12 +7,14 @@
   </el-tabs>
 
   <div class="two_tabs">
+    <div class="two_tabs_wrap">
       <div class="item"
            :class="item.id === activeTab.id ? 'active' : ''"
            v-for="item in tabs" :key="item.id"
            @click="toggleTab(item)" v-log="{ describe: { action: '点击切换动作Tab', tabName: item.mode_name, tabId: item.id } }"
            :style="{ cursor: isSortMode ? 'not-allowed' : 'pointer', opacity: isSortMode ? 0.5 : 1 }"
       >{{item.mode_name}}</div>
+    </div>
   </div>
   <div class="form-table">
     <div v-if="isSortMode" class="sort-tip">
@@ -23,6 +25,7 @@
       <div class="primary-btn" @click="addRow" v-log="{ describe: { action: '点击新增一行' } }" :class="{ disabled: isSortMode }" :style="{ opacity: isSortMode ? 0.5 : 1, cursor: isSortMode ? 'not-allowed' : 'pointer' }">新增一行</div>
       <div class="primary-btn" @click="resetConfig" v-log="{ describe: { action: '点击重新初始化', tab: topsTab } }" :class="{ disabled: isSortMode }" :style="{ opacity: isSortMode ? 0.5 : 1, cursor: isSortMode ? 'not-allowed' : 'pointer' }">重新初始化</div>
       <div class="primary-btn" @click="reName" v-log="{ describe: { action: '点击重命名配置' } }" :class="{ disabled: isSortMode }" :style="{ opacity: isSortMode ? 0.5 : 1, cursor: isSortMode ? 'not-allowed' : 'pointer' }">重命名配置</div>
+      <div class="primary-btn" @click="copySetting" v-log="{ describe: { action: '点击复制配置' } }" :class="{ disabled: isSortMode }" :style="{ opacity: isSortMode ? 0.5 : 1, cursor: isSortMode ? 'not-allowed' : 'pointer' }">复制配置</div>
       <div class="primary-btn" @click="toggleSortMode" v-log="{ describe: { action: isSortMode ? '点击保存排序' : '点击排序' } }">
         {{ isSortMode ? '保存排序' : '排序' }}
       </div>
@@ -89,7 +92,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, defineProps, defineEmits , watch,onMounted, reactive,onBeforeUnmount } from 'vue'
+import { ref, defineProps, defineEmits , watch,onMounted, reactive,onBeforeUnmount, nextTick } from 'vue'
 import EditDialog from "./EditDialog.vue";
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { Rank, Warning } from '@element-plus/icons-vue';
@@ -101,7 +104,7 @@ import socket from "@/stores/modules/socket";
 const socketStore = socket(); // WebSocket状态管理实例
 const tokenInfoStore = tokenInfo();
 
-import  { getTopTabs, getDeviceConfigs,setLeftRightConfig,restConfig,sortDeviceConfig,setTabName,delDviceConfig } from '@/apis/setting'
+import  { getTopTabs, getDeviceConfigs,setLeftRightConfig,restConfig,sortDeviceConfig,setTabName,delDviceConfig, copyDeviceConfig } from '@/apis/setting'
 
 // 表格数据和对话框状态
 const tableData = ref([]); // 配置表格数据
@@ -199,6 +202,11 @@ const getTopList = async ()=>{
     }
 
     getList()
+    
+    // 等待DOM更新后滚动到选中标签
+    nextTick(() => {
+      scrollToActiveTab();
+    });
   }
 
 }
@@ -209,6 +217,11 @@ const toggleTab = (item) => {
 
   activeTab.value = item
   getList()
+
+  // 滚动到选中元素的位置
+  nextTick(() => {
+    scrollToActiveTab();
+  });
 };
 
 
@@ -351,6 +364,55 @@ const reName = ()=>{
         }
       })
 }
+const copySetting = ()=>{
+  if (isSortMode.value) return; // 排序模式下禁用
+
+  ElMessageBox.prompt('', '复制配置', {
+    confirmButtonText: '保存',
+    cancelButtonText: '取消',
+    inputValue: "",
+    inputPlaceholder:'请输入配置名称',
+    inputValidator: (value) => {
+      if (value === '') {
+        return '请输入配置名称';
+      }
+      return true;
+    },
+  })
+      .then(async ({ value }) => {
+
+
+
+        const result =  await copyDeviceConfig({
+          tab_id: activeTab.value.id,
+          tab_name:value,
+        })
+        if (result.code == 0) {
+          getTopList()
+        }
+      })
+}
+
+// 滚动到选中标签的位置
+const scrollToActiveTab = () => {
+  const activeElement = document.querySelector('.two_tabs_wrap .item.active');
+  if (activeElement) {
+    const container = document.querySelector('.two_tabs_wrap');
+    if (container) {
+      const containerRect = container.getBoundingClientRect();
+      const elementRect = activeElement.getBoundingClientRect();
+      
+      // 计算需要滚动的位置
+      const scrollLeft = container.scrollLeft + elementRect.left - containerRect.left - containerRect.width / 2 + elementRect.width / 2;
+      
+      // 执行平滑滚动
+      container.scrollTo({
+        left: scrollLeft,
+        behavior: 'smooth'
+      });
+    }
+  }
+};
 
 /**
  * 新增一行配置。
@@ -585,6 +647,13 @@ const exitSortMode = () => {
   background: #fff;
   border: 1px solid #c8c8c8;
   border-top: none;
+
+  .two_tabs_wrap {
+     overflow-x: auto;
+     overflow-y: hidden;
+     display: flex;
+     min-height: 30px;
+  }
   .item {
     float: left;
     padding: 0 15px;