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