From 72b025d6b43271ae88541ea23c92070b3b2acc96 Mon Sep 17 00:00:00 2001
From: sd <shidong@jhsoft.cc>
Date: 星期二, 05 八月 2025 16:15:44 +0800
Subject: [PATCH] 模型训练-批量批注、批量删除以及批量导入功能实现

---
 src/pages/modelTuning/components/imageCard.vue     |   39 ++
 src/pages/modelTuning/components/rightCardList.vue |  403 +++++++++++++++++++++++++++---
 src/api/modelTuning.ts                             |   21 +
 src/pages/modelTuning/components/batchImport.vue   |  308 +++++++++++++++++++++++
 4 files changed, 716 insertions(+), 55 deletions(-)

diff --git a/src/api/modelTuning.ts b/src/api/modelTuning.ts
index f86d395..7b0ecb2 100644
--- a/src/api/modelTuning.ts
+++ b/src/api/modelTuning.ts
@@ -50,3 +50,24 @@
     params: query
   });
 };
+export const batchUpdateTrainStatus = (params: any) => {
+  return request({
+    url: "/api-v1/v1/train/batchUpdate ",
+    method: "put",
+    data: params
+  });
+};
+export const batchDeleteTrains = (query: any) => {
+  return request({
+    url: "/api-v1/v1/train/batchDelete",
+    method: "delete",
+    params: query
+  });
+};
+export const uploadDataTrainTags = (params: any) => {
+  return request({
+    url: "/api-v1/v1/train/uploadData",
+    method: "post",
+    data: params
+  });
+};
diff --git a/src/pages/modelTuning/components/batchImport.vue b/src/pages/modelTuning/components/batchImport.vue
new file mode 100644
index 0000000..533cfe1
--- /dev/null
+++ b/src/pages/modelTuning/components/batchImport.vue
@@ -0,0 +1,308 @@
+<template>
+  <el-dialog :title="`瀵煎叆${importLabel}`" :visible.sync="visible" width="600px">
+    <div class="batch-import-container">
+      <!-- 瀵煎叆绫诲瀷閫夋嫨 -->
+      <el-form label-width="100px" v-if="showTypeSelector">
+        <el-form-item label="鏍锋湰绫诲瀷">
+          <el-radio-group v-model="importType">
+            <el-radio :label="1">姝f牱鏈�</el-radio>
+            <el-radio :label="2">璐熸牱鏈�</el-radio>
+            <el-radio :label="3">寰呮爣璁版牱鏈�</el-radio>
+          </el-radio-group>
+        </el-form-item>
+      </el-form>
+      
+      <!-- 涓婁紶鍖哄煙 -->
+      <div class="upload-area" @dragover.prevent @drop="onDrop">
+        <div class="upload-content">
+          <i class="el-icon-upload"></i>
+          <div class="upload-text">
+            <p>灏嗗浘鐗囨嫋鏀惧埌姝ゅ锛屾垨</p>
+            <el-button type="primary">閫夋嫨鍥剧墖</el-button>
+          </div>
+          <p class="upload-hint">鏀寔JPG/PNG鏍煎紡锛屾渶澶�100寮犲浘鐗囷紝鍗曞紶鏈�澶�5MB</p>
+        </div>
+        <input 
+          type="file" 
+          ref="fileInput" 
+          class="file-input" 
+          multiple 
+          accept="image/*" 
+          @change="handleFileChange"
+        >
+      </div>
+      
+      <!-- 鍥剧墖棰勮鍖� -->
+      <div class="image-preview">
+        <div class="preview-header">
+          <span>宸查�夋嫨鍥剧墖 ({{ fileList.length }})</span>
+          <el-button 
+            type="text" 
+            :disabled="fileList.length === 0" 
+            @click="fileList = []"
+          >
+            娓呯┖
+          </el-button>
+        </div>
+        
+        <div class="preview-content">
+          <div 
+            class="preview-item" 
+            v-for="(file, index) in fileList" 
+            :key="index"
+          >
+            <img :src="getPreviewUrl(file)" alt="棰勮鍥�">
+            <div class="image-info">
+              <span class="file-name">{{ file.name }}</span>
+              <span class="file-size">{{ formatSize(file.size) }}</span>
+            </div>
+            <i 
+              class="el-icon-delete" 
+              title="鍒犻櫎"
+              @click="removeFile(index)"
+            ></i>
+          </div>
+          
+          <div class="empty-hint" v-if="fileList.length === 0">
+            鏆傛湭閫夋嫨鍥剧墖
+          </div>
+        </div>
+      </div>
+    </div>
+    
+    <div slot="footer" class="dialog-footer">
+      <el-button @click="visible = false">鍙� 娑�</el-button>
+      <el-button 
+        type="primary" 
+        :disabled="fileList.length === 0" 
+        @click="submitImport"
+      >
+        瀵� 鍏�
+      </el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+export default {
+  name: 'BatchImport',
+  props: {
+    // 鏄惁鏄剧ず绫诲瀷閫夋嫨鍣紙鐢ㄤ簬鍖哄垎瀵煎叆鎸夐挳鍜屼笅鎷夊鍏ワ級
+    showTypeSelector: {
+      type: Boolean,
+      default: true
+    },
+    // 棰勮鐨勫鍏ョ被鍨嬶紙1-姝f牱鏈��2-璐熸牱鏈��3-寰呮爣璁帮級
+    presetType: {
+      type: Number,
+      default: 0
+    }
+  },
+  data() {
+    return {
+      visible: false,
+      importType: this.presetType || 1,
+      fileList: []
+    };
+  },
+  computed: {
+    importLabel() {
+      const labels = {1: '姝f牱鏈�', 2: '璐熸牱鏈�', 3: '寰呮爣璁版牱鏈�'};
+      return this.presetType ? labels[this.presetType] : labels[this.importType];
+    }
+  },
+  methods: {
+    open() {
+      this.visible = true;
+      this.fileList = [];
+      if (!this.presetType) this.importType = 1;
+    },
+    
+    close() {
+      this.visible = false;
+    },
+    
+    onDrop(e) {
+      e.preventDefault();
+      this.addFiles(e.dataTransfer.files);
+    },
+    
+    handleFileChange(e) {
+      this.addFiles(e.target.files);
+      // 閲嶇疆input浠ヤ究鍙互鍐嶆閫夋嫨鐩稿悓鏂囦欢
+      e.target.value = null;
+    },
+    
+    addFiles(fileList) {
+      const files = Array.from(fileList);
+      // 杩囨护闈炲浘鐗囨枃浠�
+      const validFiles = files.filter(file => file.type.startsWith('image/'));
+      
+      // 闄愬埗鏂囦欢鏁伴噺 (鏈�澶�100寮�)
+      if (this.fileList.length + validFiles.length > 100) {
+        this.$message.warning('鏈�澶氭敮鎸�100寮犲浘鐗囧鍏�');
+        return;
+      }
+      
+      validFiles.forEach(file => {
+        // 妫�鏌ユ枃浠跺ぇ灏� (5MB浠ュ唴)
+        if (file.size > 5 * 1024 * 1024) {
+          this.$message.warning(`鏂囦欢 ${file.name} 瓒呰繃5MB闄愬埗`);
+          return;
+        }
+        this.fileList.push(file);
+      });
+    },
+    
+    getPreviewUrl(file) {
+      return URL.createObjectURL(file);
+    },
+    
+    formatSize(size) {
+      if (size < 1024) return size + ' B';
+      if (size < 1024 * 1024) return (size / 1024).toFixed(1) + ' KB';
+      return (size / (1024 * 1024)).toFixed(1) + ' MB';
+    },
+    
+    removeFile(index) {
+      this.fileList.splice(index, 1);
+    },
+    
+    submitImport() {
+      const finalImportType = this.presetType || this.importType;
+      const fileNames = this.fileList.map(f => f.name);
+      
+      this.$emit('import', {
+        type: finalImportType,
+        files: this.fileList
+      });
+      
+    //   this.$message.success(`鎴愬姛瀵煎叆 ${fileNames.length} 寮犲浘鐗嘸);
+      this.close();
+    }
+  }
+};
+</script>
+
+<style scoped>
+.batch-import-container {
+  padding: 10px;
+}
+
+.upload-area {
+  position: relative;
+  border: 2px dashed #dcdfe6;
+  border-radius: 6px;
+  padding: 20px;
+  text-align: center;
+  margin-bottom: 20px;
+  cursor: pointer;
+  transition: border-color 0.3s;
+}
+
+.upload-area:hover {
+  border-color: #409EFF;
+}
+
+.upload-content {
+  pointer-events: none;
+}
+
+.upload-content i {
+  font-size: 48px;
+  color: #c0c4cc;
+  margin-bottom: 15px;
+}
+
+.upload-text {
+  margin-bottom: 10px;
+}
+
+.upload-hint {
+  color: #909399;
+  font-size: 12px;
+  margin-top: 10px;
+}
+
+.file-input {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  opacity: 0;
+  cursor: pointer;
+}
+
+.image-preview {
+  border: 1px solid #ebeef5;
+  border-radius: 4px;
+  max-height: 300px;
+  overflow-y: auto;
+}
+
+.preview-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 10px 15px;
+  background-color: #f5f7fa;
+  border-bottom: 1px solid #ebeef5;
+}
+
+.preview-content {
+  padding: 10px;
+}
+
+.preview-item {
+  position: relative;
+  display: flex;
+  align-items: center;
+  padding: 10px;
+  border-bottom: 1px solid #f1f1f1;
+}
+
+.preview-item:hover {
+  background-color: #f8f8f8;
+}
+
+.preview-item img {
+  width: 50px;
+  height: 50px;
+  object-fit: cover;
+  border-radius: 4px;
+  margin-right: 10px;
+}
+
+.image-info {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+}
+
+.file-name {
+  font-size: 14px;
+  margin-bottom: 5px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 380px;
+}
+
+.file-size {
+  color: #909399;
+  font-size: 12px;
+}
+
+.preview-item .el-icon-delete {
+  color: #f56c6c;
+  font-size: 16px;
+  cursor: pointer;
+}
+
+.empty-hint {
+  text-align: center;
+  padding: 30px;
+  color: #909399;
+}
+</style>
\ No newline at end of file
diff --git a/src/pages/modelTuning/components/imageCard.vue b/src/pages/modelTuning/components/imageCard.vue
index 5d7f900..fafcde0 100644
--- a/src/pages/modelTuning/components/imageCard.vue
+++ b/src/pages/modelTuning/components/imageCard.vue
@@ -2,13 +2,14 @@
     <el-col :xs="24" :sm="12" :md="8" :lg="6" class="image-card-container">
         <div class="image-card" :class="{ 'batch-selected': isBatchMode && selected }" @click="handleCardClick">
             <!-- 澶氶�夋 (浠呭湪鎵归噺妯″紡涓嬫樉绀�) -->
-            <div class="checkbox-wrapper" v-if="isBatchMode">
-                <el-checkbox v-model="localSelected" @click.stop @change="emitSelection" />
-            </div>
+            <!-- <div class="checkbox-wrapper" v-if="isBatchMode">
+            </div> -->
+            <el-checkbox class="checkbox-wrapper" v-if="isBatchMode" v-model="localSelected" @click.stop
+                @change="emitSelection" />
             <!-- 鍥剧墖瀹瑰櫒 -->
             <div class="image-container">
                 <!-- <img :src="item.imagePath" class="gallery-image" alt="鐩戞帶鎴浘" /> -->
-                <el-image :src="item.imagePath" class="gallery-image" fit="scale-down"/>
+                <el-image :src="item.imagePath" class="gallery-image" fit="scale-down" />
             </div>
 
             <!-- 鍗$墖鎿嶄綔鎸夐挳 -->
@@ -65,8 +66,18 @@
         }
     },
     watch: {
-        selected(newVal) {
-            this.localSelected = newVal;
+        selected: {
+            immediate: true,
+            handler(newVal) {
+                this.localSelected = newVal;
+            }
+        },
+        // 娣诲姞瀵筰tem.selected鐨勬繁搴︾洃鍚�
+        'item.selected': {
+            immediate: true,
+            handler(newVal) {
+                this.localSelected = newVal;
+            }
         }
     },
     methods: {
@@ -82,19 +93,21 @@
         handleCardClick() {
             // 鎵归噺妯″紡涓嬬偣鍑诲崱鐗囪Е鍙戦�夋嫨
             if (this.isBatchMode) {
-                this.localSelected = !this.localSelected;
-                this.emitSelection();
             } else {
                 this.$emit('card-click', this.item);
             }
         },
         emitSelection() {
+            // console.info(this.selected)
+            this.localSelected = !this.localSelected;
             this.$emit('toggle-select');
         },
 
         // 鏇存敼鐘舵��
         changeStatus(status) {
-            this.$emit('status-change', { trainId: this.item.trainId, status });
+            if (!this.isBatchMode) {
+                this.$emit('status-change', { trainId: this.item.trainId, status });
+            }
         },
 
         // 鍒犻櫎
@@ -248,10 +261,14 @@
 }
 
 .checkbox-wrapper {
+    width: 18px;
+    /* height: 18px; */
     color: #FFFFFF;
     position: absolute;
-    top: 10px;
-    left: 20px;
+    /* top: 10px;
+    left: 20px; */
+    margin-top: 10px;
+    margin-left: 10px;
     z-index: 10;
 }
 </style>
\ No newline at end of file
diff --git a/src/pages/modelTuning/components/rightCardList.vue b/src/pages/modelTuning/components/rightCardList.vue
index b07c05d..0b83799 100644
--- a/src/pages/modelTuning/components/rightCardList.vue
+++ b/src/pages/modelTuning/components/rightCardList.vue
@@ -1,5 +1,39 @@
 <template>
     <div class="image-gallery">
+        <!-- 娣诲姞瀵煎叆缁勪欢 -->
+        <BatchImport ref="batchImport" :show-type-selector="false" @import="handleImportFiles" />
+        <!-- 妯″瀷璁粌寮圭獥 -->
+        <el-dialog title="妯″瀷璁粌" :visible.sync="trainDialogVisible" width="372px" top="10vh">
+            <div class="sample-info">
+                <div class="info-label">鏍锋湰淇℃伅</div>
+                <div class="sample-count">姝f牱鏈暟閲忥細{{ positiveCount }}</div>
+                <div class="sample-count">璐熸牱鏈暟閲忥細{{ negativeCount }}</div>
+            </div>
+            <div slot="footer" class="dialog-footer">
+                <el-button @click="trainDialogVisible = false">鍙栨秷</el-button>
+                <el-button type="primary" @click="startTraining">寮�濮嬭缁�</el-button>
+            </div>
+        </el-dialog>
+
+        <!-- 鎵归噺鏍囨敞寮圭獥 -->
+        <el-dialog title="鎵归噺鏍囨敞" :visible.sync="batchLabelDialogVisible" width="472px" top="10vh">
+            <div class="label-options">
+                <div class="label-option" :class="{ active: batchLabelStatus === 1 }" @click="batchLabelStatus = 1">
+                    姝g‘
+                </div>
+                <div class="label-option" :class="{ active: batchLabelStatus === 2 }" @click="batchLabelStatus = 2">
+                    閿欒
+                </div>
+                <div class="label-option" :class="{ active: batchLabelStatus === 0 }" @click="batchLabelStatus = 0">
+                    涓嶇‘瀹�
+                </div>
+            </div>
+            <div slot="footer" class="dialog-footer">
+                <el-button type="danger" @click="handleBatchDelete">鎵归噺鍒犻櫎</el-button>
+                <el-button @click="batchLabelDialogVisible = false">鍙栨秷</el-button>
+                <el-button type="primary" @click="confirmBatchLabeling">纭畾</el-button>
+            </div>
+        </el-dialog>
         <!-- 椤堕儴绛涢�夊尯鍩� -->
         <div class="filter-section">
             <el-form :inline="true" class="filter-form">
@@ -38,13 +72,13 @@
                         <el-button type="primary" class="action-btn">
                             <i class="el-icon-download"></i> 瀵煎叆
                         </el-button>
-                        <el-dropdown-menu slot="dropdown">
-                            <el-dropdown-item>姝f牱鏈�</el-dropdown-item>
-                            <el-dropdown-item>璐熸牱鏈�</el-dropdown-item>
-                            <el-dropdown-item>寰呮爣璁版牱鏈�</el-dropdown-item>
+                        <el-dropdown-menu slot="dropdown" v-if="!this.trainId == 0">
+                            <el-dropdown-item @click.native="openImportDialog(1)">姝f牱鏈�</el-dropdown-item>
+                            <el-dropdown-item @click.native="openImportDialog(2)">璐熸牱鏈�</el-dropdown-item>
+                            <el-dropdown-item @click.native="openImportDialog(3)">寰呮爣璁版牱鏈�</el-dropdown-item>
                         </el-dropdown-menu>
                     </el-dropdown>
-                    <el-button type="primary" class="action-btn">
+                    <el-button type="primary" @click="openTrainDialog" class="action-btn">
                         妯″瀷璁粌
                     </el-button>
                 </div>
@@ -54,7 +88,8 @@
         <!-- 鎵归噺鎿嶄綔鎺у埗鏍� -->
         <div class="batch-controls" v-if="isBatchMode">
             <div class="select-all">
-                <el-checkbox v-model="selectAll" @change="toggleSelectAll">鍏ㄩ��</el-checkbox>
+                <el-checkbox :indeterminate="isIndeterminate" v-model="selectAll"
+                    @change="toggleSelectAll">鍏ㄩ��</el-checkbox>
             </div>
             <!-- <div>宸查�夋嫨 {{ selectedCount }} 涓」鐩�</div> -->
             <div class="batch-actions">
@@ -79,20 +114,31 @@
 </template>
 
 <script>
+import BatchImport from './batchImport';
 import imageCard from './imageCard';
 import Pagination from '@/components/rightPagination';
-import { getTrains, updateTrainStatus, deleteTrains } from "@/api/modelTuning";
+import { getTrains, updateTrainStatus, deleteTrains, batchUpdateTrainStatus, batchDeleteTrains, uploadDataTrainTags } from "@/api/modelTuning";
 export default {
     components: {
         imageCard,
         Pagination,
+        BatchImport
 
     },
     name: 'ImageGallery',
     data() {
         return {
+            trainDialogVisible: false,       // 妯″瀷璁粌寮圭獥鍙鎬�
+            batchLabelDialogVisible: false,  // 鎵归噺鏍囨敞寮圭獥鍙鎬�
+            // 妯″瀷璁粌寮圭獥鏁版嵁
+            positiveCount: 0,       // 姝f牱鏈暟閲�
+            negativeCount: 0,       // 璐熸牱鏈暟閲�
+            // 鎵归噺鏍囨敞鐘舵��
+            batchLabelStatus: 0,    // 榛樿閫夋嫨"涓嶇‘瀹�"
+            isIndeterminate: false,
+            selectAll: false,
             pagination: {},
-            trainId: null,
+            trainId: 0,
             totalCount: 0,     // 鎬绘暟鎹噺
             currentPage: 1,    // 褰撳墠椤电爜
             pageSize: 12,     // 姣忛〉鏁伴噺
@@ -123,6 +169,67 @@
     mounted() {
     },
     methods: {
+        // 鎵撳紑瀵煎叆瀵硅瘽妗�
+        openImportDialog(type) {
+            this.$refs.batchImport.presetType = type;
+            this.$refs.batchImport.open();
+        },
+        // 澶勭悊瀵煎叆鐨勬枃浠�
+        async handleImportFiles({ type, files }) {
+            try {
+                this.$loading({ text: `瀵煎叆${this.getTypeName(type)}涓�...` });
+                // 1. 鍒涘缓FormData鐢ㄤ簬鏂囦欢涓婁紶
+                let formData = new FormData();
+                // 2. 娣诲姞瀹為檯鏂囦欢鍐呭鍒癋ormData
+                files.forEach(item => {
+                   formData.append('file', item);
+                });
+                formData.append('tagId', this.trainId);
+                formData.append('status', type === 3 ? 0 : type);
+                
+    //   console.log(formData)
+                // // 妯℃嫙涓婁紶璇锋眰锛堝疄闄呭簲璋冪敤API锛�
+                let rspc = await uploadDataTrainTags(formData)
+                if (rspc && rspc.status === 200) {
+                    this.$message({
+                        type: 'success',
+                        message: '鎴愬姛'
+                    });
+                    this.fetchTableData()
+                } else {
+                    this.$message({
+                        type: 'error',
+                        message: rspc.msg
+                    });
+
+                }
+            } catch (error) {
+                this.$message.error(`瀵煎叆澶辫触: ${error.message}`);
+            } finally {
+                this.$loading().close();
+            }
+        },
+        getTypeName(type) {
+            return {
+                1: '姝f牱鏈�',
+                2: '璐熸牱鏈�',
+                3: '寰呮爣璁版牱鏈�'
+            }[type];
+        },
+        // 妯℃嫙鏂囦欢涓婁紶鍑芥暟
+        async uploadFile(file, type) {
+            // 鍦ㄥ疄闄呭簲鐢ㄤ腑锛岃繖閲屽簲璇ユ槸涓�涓狝PI璋冪敤
+            return new Promise((resolve) => {
+                const formData = new FormData();
+                formData.append('file', file);
+                formData.append('type', type);
+                formData.append('trainId', this.trainId);
+
+                // 璋冪敤涓婁紶API锛屼緥濡�:
+                // await uploadSample(formData);
+                resolve();
+            });
+        },
         async paginationChange(params) {
             this.currentPage = params.page
             this.pageSize = params.pageSize
@@ -131,11 +238,12 @@
         async changeTrainId(trainId) {
             // console.info(trainId)
             this.trainId = trainId
+            this.isBatchMode = false
             await this.fetchTableData()
         },
         // 鑾峰彇琛ㄦ牸鏁版嵁鏂规硶
         async fetchTableData(params) {
-            console.info(this.currentPage)
+            // console.info(this.currentPage)
             this.galleryItems = []
             // params.tagId = this.trainId
             let rspc = await getTrains({
@@ -144,15 +252,20 @@
                 pageSize: this.pageSize,
             });
             if (rspc && rspc.status === 200) {
-                this.galleryItems = rspc.data.list;
+                if (rspc.data.list) {
+                    this.galleryItems = rspc.data.list.map(item => ({
+                        ...item,
+                        selected: false // 纭繚姣忎釜鍗$墖閮芥湁鍒濆鍊�
+                    }));
+                }
             }
             // console.log('trainId:', this.trainId);
             this.totalCount = rspc.data.pagination.total
             // 鏇存柊鍒嗛〉鏁版嵁鍓嶅厛鏍¢獙褰撳墠椤电爜
-            const totalPage = res.data.pagination.totalPage
+            const totalPage = rspc.data.pagination.totalPage
             const currentPage = this.currentPage > totalPage
                 ? totalPage
-                : res.data.pagination.page
+                : rspc.data.pagination.page
             this.currentPage = currentPage,
                 this.totalPage = totalPage
         },
@@ -190,13 +303,18 @@
         },
         //淇敼鍥剧墖鐘舵��
         async handleStatusChange(parm) {
-            console.log('淇敼鐘舵��', parm);
+            // console.log('淇敼鐘舵��', parm);
             let rspc = await updateTrainStatus(parm);
             if (rspc && rspc.status === 200) {
                 this.$message({
                     type: 'success',
                     message: '鎴愬姛'
                 });
+                // for (let i = 0; i < this.galleryItems.length; i++) {
+                //     if (parm.trainId === this.galleryItems[i].trainId) {
+                //         this.galleryItems[i].status = parm.status
+                //     }
+                // }
                 this.fetchTableData()
             } else {
                 this.$message({
@@ -230,20 +348,9 @@
         },
         // 杩涘叆鎵归噺妯″紡
         enterBatchMode() {
-            // this.isBatchMode = true;
-            // this.selectAll = false;
-            // this.batchSelected = [];
-
-            // // 娓呴櫎宸查�夌姸鎬�
-            // this.galleryItems.forEach(item => {
-            //     item.selected = false;
-            // });
-        },
-
-        // 閫�鍑烘壒閲忔ā寮�
-        exitBatchMode() {
-            this.isBatchMode = false;
+            this.isBatchMode = true;
             this.selectAll = false;
+            this.isIndeterminate = false;
             this.batchSelected = [];
 
             // 娓呴櫎宸查�夌姸鎬�
@@ -251,37 +358,174 @@
                 item.selected = false;
             });
         },
+
+        // 閫�鍑烘壒閲忔ā寮�
+        exitBatchMode() {
+            this.isBatchMode = false;
+            this.selectAll = false;
+            this.isIndeterminate = false;
+            this.batchSelected = [];
+
+            // 娓呴櫎宸查�夌姸鎬�
+            this.galleryItems.forEach(item => {
+                if (item.hasOwnProperty('selected')) {
+                    item.selected = false;
+                }
+            });
+        },
         toggleSelect(index) {
             if (!this.isBatchMode) return;
 
-            const position = this.batchSelected.indexOf(index);
-            if (position === -1) {
+            const isCurrentlySelected = this.galleryItems[index].selected;
+
+            // 鏇存柊閫変腑鐘舵��
+            this.$set(this.galleryItems[index], 'selected', !isCurrentlySelected);
+
+            // 鏇存柊batchSelected鏁扮粍
+            if (!isCurrentlySelected) {
+                // 娣诲姞閫夋嫨
                 this.batchSelected.push(index);
             } else {
-                this.batchSelected.splice(position, 1);
+                // 绉婚櫎閫夋嫨
+                const position = this.batchSelected.indexOf(index);
+                if (position !== -1) {
+                    this.batchSelected.splice(position, 1);
+                }
             }
             this.checkAllSelected();
         },
         checkAllSelected() {
-            this.isAllSelected = this.batchSelected.length === this.galleryItems.length;
+            this.selectAll = this.batchSelected.length === this.galleryItems.length;
+            this.isIndeterminate = this.batchSelected.length > 0 && this.batchSelected.length < this.galleryItems.length;
+            // console.info("batchSelected.length="+this.batchSelected.length)
+            // console.info("galleryItems.length="+this.galleryItems.length)
         },
         // 鍏ㄩ��/鍙栨秷鍏ㄩ��
         toggleSelectAll() {
-            this.galleryItems.forEach(item => {
-                item.selected = this.selectAll;
+            const allSelected = this.selectAll;
+            this.batchSelected = [];
+
+            this.galleryItems.forEach((_, index) => {
+                this.$set(this.galleryItems[index], 'selected', allSelected);
+                if (allSelected) {
+                    this.batchSelected.push(index);
+                }
             });
+            this.isIndeterminate = this.batchSelected.length > 0 && this.batchSelected.length < this.galleryItems.length;
         },
 
         // 纭鎵归噺鎿嶄綔
         confirmBatch() {
-            const selectedItems = this.galleryItems.filter(item => item.selected);
+            this.openBatchLabelDialog()
+        },
+        // 鎵撳紑妯″瀷璁粌寮圭獥
+        openTrainDialog() {
+            // 姝ゅ搴旇皟鐢ˋPI鑾峰彇瀹為檯鐨勬牱鏈暟閲�
+            // 杩欓噷浣跨敤绀轰緥鏁版嵁
+            this.positiveCount = 100;
+            this.negativeCount = 10;
+            // this.trainDialogVisible = true;
+        },
+        // 寮�濮嬭缁�
+        async startTraining() {
+            try {
+                this.$loading({ text: '妯″瀷璁粌涓�...' });
+                // 璋冪敤瀹為檯鐨勮缁傾PI
+                // await startModelTraining({
+                //   positive: this.positiveCount,
+                //   negative: this.negativeCount
+                // });
 
-            this.$message({
-                message: `宸插${selectedItems.length}涓」鎵ц鎵归噺鎿嶄綔`,
-                type: 'success'
+                // 妯℃嫙API寤惰繜
+                await new Promise(resolve => setTimeout(resolve, 2000));
+
+                this.$message.success('妯″瀷璁粌宸插紑濮�');
+                this.trainDialogVisible = false;
+            } catch (error) {
+                this.$message.error(`璁粌澶辫触: ${error.message}`);
+            } finally {
+                this.$loading().close();
+            }
+        },
+        // 鎵撳紑鎵归噺鏍囨敞寮圭獥
+        openBatchLabelDialog() {
+            if (this.batchSelected.length === 0) {
+                this.$message.warning('璇峰厛閫夋嫨鍥剧墖杩涜鏍囨敞');
+                return;
+            }
+            this.batchLabelStatus = 0; // 閲嶇疆涓轰笉纭畾鐘舵��
+            this.batchLabelDialogVisible = true;
+        },
+        // 纭鎵归噺鏍囨敞
+        async confirmBatchLabeling() {
+            if (this.batchSelected.length === 0) {
+                this.$message.warning('璇峰厛閫夋嫨鍥剧墖');
+                return;
+            }
+
+            try {
+                // this.$loading({ text: '鎵归噺鏍囨敞涓�...' });
+
+                const selectedItems = this.galleryItems.filter(item => item.selected);
+                let ids = []
+                for (let i = 0; i < selectedItems.length; i++) {
+                    ids.push(selectedItems[i].trainId)
+                }
+
+                // 璋冪敤鎵归噺鏇存柊鐘舵�佺殑API
+                let rspc = await batchUpdateTrainStatus({
+                    ids: ids,
+                    status: this.batchLabelStatus
+                })
+                if (rspc && rspc.status === 200) {
+                    this.$message.success(`宸叉垚鍔熸爣娉�${selectedItems.length}涓暟鎹甡);
+                    this.batchLabelDialogVisible = false;
+                    this.exitBatchMode(); // 閫�鍑烘壒閲忔ā寮�
+                    this.fetchTableData(); // 鍒锋柊鏁版嵁
+                } else {
+                    this.$message.error(`鏍囨敞澶辫触: ${rspc.msg}`);
+                }
+            } catch (error) {
+                this.$message.error(`鏍囨敞澶辫触: ${error.message}`);
+            } finally {
+                this.$loading().close();
+            }
+        },
+        // 鎵归噺鍒犻櫎
+        async handleBatchDelete() {
+            if (this.batchSelected.length === 0) {
+                this.$message.warning('璇峰厛閫夋嫨鍥剧墖');
+                return;
+            }
+
+            this.$confirm(`纭畾瑕佸垹闄ら�変腑鐨�${this.batchSelected.length}涓暟鎹悧锛焋, '璀﹀憡', {
+                confirmButtonText: '纭畾',
+                cancelButtonText: '鍙栨秷',
+                type: 'warning'
+            }).then(async () => {
+                try {
+                    // this.$loading({ text: '鍒犻櫎涓�...' });
+
+                    // 璋冪敤鎵归噺鍒犻櫎API
+                    let rspc = await batchDeleteTrains({
+                        ids: ids
+                    })
+                    if (rspc && rspc.status === 200) {
+                        this.$message.success(`宸叉垚鍔熷垹闄�${this.batchSelected.length}涓暟鎹甡);
+                        this.batchLabelDialogVisible = false;
+                        this.exitBatchMode(); // 閫�鍑烘壒閲忔ā寮�
+                        this.fetchTableData(); // 鍒锋柊鏁版嵁
+                    } else {
+                        this.$message.error(`鍒犻櫎澶辫触: ${rspc.msg}`);
+                    }
+                } catch (error) {
+                    this.$message.error(`鍒犻櫎澶辫触: ${error.message}`);
+                } finally {
+                    this.$loading().close();
+                }
+            }).catch(() => {
+                // 鐢ㄦ埛鍙栨秷鍒犻櫎
             });
-
-            this.exitBatchMode();
         },
     }
 }
@@ -298,8 +542,8 @@
 
 /* 绛涢�夊尯鍩熸牱寮� */
 .filter-section {
-    padding: 20px;
-    background-color: #f5f7fa;
+    /* padding: 20px; */
+    /* background-color: #f5f7fa; */
     border-radius: 4px;
     margin-bottom: 20px;
 }
@@ -349,11 +593,12 @@
     display: flex;
     align-items: center;
     background: #fff;
-    border-bottom: 1px solid #e6ebf5;
-    padding: 10px 20px;
+    /* border-bottom: 1px solid #e6ebf5; */
+    padding: 5px;
     margin-bottom: 20px;
-    border-radius: 4px;
-    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
+    margin-top: -10px;
+    /* border-radius: 4px; */
+    /* box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05); */
 
 }
 
@@ -366,4 +611,74 @@
     display: flex;
     gap: 10px;
 }
+
+/* 寮圭獥鏍峰紡 */
+/* 鏍囬宸﹀榻� */
+::v-deep .el-dialog__header {
+    text-align: left;
+}
+
+::v-deep .el-dialog__title {
+    font-weight: bold;
+    font-size: 16px;
+}
+
+::v-deep .el-dialog__body {
+    padding-top: 20px;
+    padding-bottom: 15px;
+}
+
+/* 妯″瀷璁粌寮圭獥鍐呭 */
+.sample-info {
+    padding: 20px;
+}
+
+.info-label {
+    font-weight: bold;
+    margin-bottom: 12px;
+    color: #606266;
+}
+
+.sample-count {
+    padding: 8px 0;
+    border-bottom: 1px solid #f0f2f5;
+}
+
+/* 鎵归噺鏍囨敞寮圭獥鍐呭 */
+.label-options {
+    display: flex;
+    flex-direction: column;
+}
+
+.label-option {
+    width: 100%;
+    padding: 15px 20px;
+    margin: 8px 0;
+    border: 1px solid #dcdfe6;
+    border-radius: 4px;
+    cursor: pointer;
+    transition: all 0.3s;
+    text-align: center;
+    box-sizing: border-box;
+}
+
+.label-option:hover {
+    border-color: #409eff;
+    color: #409eff;
+}
+
+.label-option.active {
+    border-color: #409eff;
+    background-color: #ecf5ff;
+    color: #409eff;
+}
+
+/* 寮圭獥搴曢儴鎸夐挳 */
+.dialog-footer {
+    /* display: flex; */
+    text-align: center;
+    /* justify-content: space-between; */
+    /* padding: 10px 20px; */
+    /* border-top: 1px solid #e6ebf5; */
+}
 </style>
\ No newline at end of file

--
Gitblit v1.8.0