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/rightCardList.vue |  403 +++++++++++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 359 insertions(+), 44 deletions(-)

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