增加备注

This commit is contained in:
BianLzhaoMin 2025-11-25 17:16:35 +08:00
parent 5ed6e787bc
commit c7ac05a8ac
3 changed files with 947 additions and 871 deletions

View File

@ -20,19 +20,18 @@
:src="file.url"
alt=""
class="preview-image"
>
/>
<!-- 压缩包文件预览 -->
<div
v-else
class="zip-preview"
:title="file.name"
>
<div v-else class="zip-preview" :title="file.name">
<i class="el-icon-document"></i>
<div class="file-name">{{ file.name }}</div>
</div>
<i class="el-icon-close delete-icon" @click="$emit('remove-image', index)"></i>
<i
class="el-icon-close delete-icon"
@click="$emit('remove-image', index)"
></i>
</div>
</div>
</div>
@ -57,7 +56,9 @@
<el-button
type="primary"
@click="$emit('start-upload')"
:disabled="fileList.length === 0 || selectedTag.length === 0"
:disabled="
fileList.length === 0 || selectedTag.length === 0
"
>
开始标注
</el-button>
@ -68,76 +69,78 @@
<script>
export default {
name: "FileUploader",
name: 'FileUploader',
props: {
fileList: {
type: Array,
default: () => []
default: () => [],
},
selectedTag: {
type: Array,
default: () => []
}
default: () => [],
},
},
computed: {
uploadLimit() {
const hasZip = this.fileList.some(file => this.isCompressedFile(file.name));
return hasZip ? 1 : 50;
}
const hasZip = this.fileList.some((file) =>
this.isCompressedFile(file.name),
)
return hasZip ? 1 : 50
},
},
methods: {
isImageFile(filename) {
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
const extension = filename.split('.').pop().toLowerCase();
return imageExtensions.includes(extension);
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']
const extension = filename.split('.').pop().toLowerCase()
return imageExtensions.includes(extension)
},
handleFileChange(file, fileList) {
const isZip = this.isCompressedFile(file.name);
const hasImage = fileList.some(f => this.isImageFile(f.name));
const hasZip = fileList.some(f => this.isCompressedFile(f.name));
const isZip = this.isCompressedFile(file.name)
const hasImage = fileList.some((f) => this.isImageFile(f.name))
const hasZip = fileList.some((f) => this.isCompressedFile(f.name))
//
if (isZip && hasImage) {
this.$message.warning('不能同时上传压缩包');
return false;
this.$message.warning('不能同时上传压缩包')
return false
}
if (this.isImageFile(file.name) && hasZip) {
this.$message.warning('不能同时上传图片');
return false;
this.$message.warning('不能同时上传图片')
return false
}
if (file.raw) {
const reader = new FileReader();
const reader = new FileReader()
reader.onload = (e) => {
file.url = e.target.result;
};
reader.readAsDataURL(file.raw);
file.url = e.target.result
}
reader.readAsDataURL(file.raw)
}
// fileListfile.url
setTimeout(() => {
this.$emit('file-change', file, fileList);
}, 100);
this.$emit('file-change', file, fileList)
}, 100)
},
isCompressedFile(filename) {
const compressedExtensions = ['zip', 'rar', '7z'];
const extension = filename.split('.').pop().toLowerCase();
return compressedExtensions.includes(extension);
const compressedExtensions = ['zip', 'rar', '7z']
const extension = filename.split('.').pop().toLowerCase()
return compressedExtensions.includes(extension)
},
handleExceed(files, fileList) {
const hasZip = fileList.some(f => this.isCompressedFile(f.name));
const hasZip = fileList.some((f) => this.isCompressedFile(f.name))
if (hasZip) {
this.$message.warning('压缩包最多只能上传1个');
this.$message.warning('压缩包最多只能上传1个')
} else {
this.$message.warning(`最多只能上传50张图片`);
}
}
this.$message.warning(`最多只能上传50张图片`)
}
},
},
}
</script>

View File

@ -24,11 +24,21 @@
@toggle-all-tags="$emit('toggle-all-tags')"
/>
<!-- 输入备注 -->
<el-input
:rows="1"
placeholder="请输入备注"
type="textarea"
v-model="remarkInfo"
/>
<!-- 图片上传区域 -->
<FileUploader
:file-list="fileList"
:selected-tag="selectedTag"
@file-change="(file, fileList) => $emit('file-change', file, fileList)"
@file-change="
(file, fileList) => $emit('file-change', file, fileList)
"
@remove-image="(index) => $emit('remove-image', index)"
@start-upload="$emit('start-upload')"
/>
@ -37,62 +47,67 @@
</template>
<script>
import ImageResults from './ImageResults.vue';
import TagSelector from './TagSelector.vue';
import FileUploader from './FileUploader.vue';
import ImageResults from './ImageResults.vue'
import TagSelector from './TagSelector.vue'
import FileUploader from './FileUploader.vue'
import axios from 'axios'
import { getToken } from '@/utils/auth'
export default {
name: "HistoryView",
name: 'HistoryView',
components: {
ImageResults,
TagSelector,
FileUploader
FileUploader,
},
data() {
return {
remarkInfo: '', //
}
},
props: {
uploadInfo: {
type: String,
default: ''
default: '',
},
imageResults: {
type: Array,
default: () => []
default: () => [],
},
groupedImageResults: {
type: Array,
default: () => []
default: () => [],
},
selectedImages: {
type: Object,
default: () => ({})
default: () => ({}),
},
tags: {
type: Array,
default: () => []
default: () => [],
},
visibleTags: {
type: Array,
default: () => []
default: () => [],
},
selectedTag: {
type: Array,
default: () => []
default: () => [],
},
fileList: {
type: Array,
default: () => []
default: () => [],
},
isSure: {
type: String,
default: ''
}
default: '',
},
},
methods: {
handleHandClickFromChild(result, index) {
this.$emit('hand-click', result, index);
}
}
this.$emit('hand-click', result, index)
},
},
}
</script>

View File

@ -16,9 +16,7 @@
<!-- 主内容区域-新增的 -->
<div class="add-content">
<!-- 标题 -->
<div class="welcome-message">
我是你的图像标注助理
</div>
<div class="welcome-message"> 我是你的图像标注助理 </div>
<!-- 标签选择区域 -->
<TagSelector
@ -29,6 +27,14 @@
@toggle-all-tags="toggleAllTags"
/>
<!-- 输入备注 -->
<el-input
:rows="1"
placeholder="请输入备注"
type="textarea"
v-model="remarkInfo"
/>
<!-- 图片上传区域 -->
<FileUploader
:file-list="fileList"
@ -70,20 +76,20 @@ import {
getImageListAPI,
getImageListDetailsAPI,
addImageInfoAPI,
updateImageSureAPI
} from "@/api/imageCaptioning/imageCaptioning";
import Sidebar from "./components/Sidebar";
import TagSelector from "./components/TagSelector";
import FileUploader from "./components/FileUploader";
import HistoryView from "./components/HistoryView";
updateImageSureAPI,
} from '@/api/imageCaptioning/imageCaptioning'
import Sidebar from './components/Sidebar'
import TagSelector from './components/TagSelector'
import FileUploader from './components/FileUploader'
import HistoryView from './components/HistoryView'
export default {
name: "imageCaptioning",
name: 'imageCaptioning',
components: {
Sidebar,
TagSelector,
FileUploader,
HistoryView
HistoryView,
},
data() {
return {
@ -110,6 +116,8 @@ export default {
addId: '', //id
groupedImageResults: [], //
remarkInfo: '', //
}
},
@ -127,12 +135,11 @@ export default {
//
if (res.code === 200 && res.data && Array.isArray(res.data)) {
// id name
this.tags = res.data.map(item => ({
this.tags = res.data.map((item) => ({
id: item.id,
name: item.name
name: item.name,
}))
this.visibleTags = this.tags.slice(0, 3);
this.visibleTags = this.tags.slice(0, 3)
} else {
console.error('获取标签列表失败:', res)
this.tags = []
@ -148,12 +155,11 @@ export default {
const res = await getImageListAPI({ operaType: 1 })
//
if (res.code === 200 && res.data && Array.isArray(res.data)) {
this.labelList = res.data.map(item => ({
this.labelList = res.data.map((item) => ({
id: item.id,
date: item.operaTime,
name: item.operaName
name: item.operaName,
}))
}
} catch (error) {
console.error('获取列表异常:', error)
@ -161,16 +167,16 @@ export default {
},
createNewLabel() {
this.selectedItem = null;
this.fileList = [];
this.selectedTag = [];
this.showImageResults = false;
this.uploadInfo = '';
this.imageResults = [];
this.selectedImages = {};
this.showNewAnnotation = true;
this.showNewAnnotationAdd = false;
this.addId ='';
this.selectedItem = null
this.fileList = []
this.selectedTag = []
this.showImageResults = false
this.uploadInfo = ''
this.imageResults = []
this.selectedImages = {}
this.showNewAnnotation = true
this.showNewAnnotationAdd = false
this.addId = ''
},
startUpload() {
@ -180,134 +186,153 @@ export default {
return
}
//
const selectedTagIds = this.selectedTag.map(tag => tag.id).join(',');
const selectedTagNames = this.selectedTag.map(tag => tag.name).join(',');
console.log('选中的标签IDs:', selectedTagIds);
console.log('选中的标签名称:', selectedTagNames);
const selectedTagIds = this.selectedTag
.map((tag) => tag.id)
.join(',')
const selectedTagNames = this.selectedTag
.map((tag) => tag.name)
.join(',')
console.log('选中的标签IDs:', selectedTagIds)
console.log('选中的标签名称:', selectedTagNames)
if (this.fileList.length === 0) {
this.$message.warning('请上传图片')
return
}
// selectedImages
this.selectedImages = {};
this.selectedImages = {}
//
const loading = this.$loading({
lock: true,
text: '正在上传和识别图片...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
background: 'rgba(0, 0, 0, 0.7)',
})
//
const originalFileList = [...this.fileList];
const originalFileList = [...this.fileList]
//
this.fileList = [];
this.fileList = []
// FormData
const formData = new FormData();
const formData = new FormData()
//
originalFileList.forEach(file => {
formData.append('files', file.raw); // 使 raw
});
originalFileList.forEach((file) => {
formData.append('files', file.raw) // 使 raw
})
// formData.append('param', selectedTagNames);
// formData.append('id', this.addId);
// params
const params = {
param: selectedTagNames,
id: this.addId
};
formData.append('params', JSON.stringify(params));
remark: '备注996',
id: this.addId,
}
formData.append('params', JSON.stringify(params))
//IDsname
addImageInfoAPI(formData)
.then(res => {
loading.close();
if (res.code === 200 && res.data && Array.isArray(res.data)) {
.then((res) => {
loading.close()
if (
res.code === 200 &&
res.data &&
Array.isArray(res.data)
) {
// -
const records = res.data;
this.addId = records[0]?.operaId || '';
const records = res.data
this.addId = records[0]?.operaId || ''
console.log('this.addId:', this.addId)
//
this.groupedImageResults = records.map((record, index) => ({
this.groupedImageResults = records.map(
(record, index) => ({
index: index, //
operaName: record.operaName,
images: record.fileVoList.map(item => ({
url: item.bjUrl || "未找到图片地址",
images: record.fileVoList.map((item) => ({
url: item.bjUrl || '未找到图片地址',
id: item.imageId || item.id,
name: item.originalName,
contentImage: item.contentImage,
fileSize: item.fileSize,
operId: record.id,
})),
totalCount: record.imageNum || originalFileList.length,
totalCount:
record.imageNum || originalFileList.length,
recognizedCount: record.bzNum || 0,
unrecognizedCount: record.wbzNum || 0,
isSure: record.isSure,
operId: record.operaId,
id: record.id,
}));
}),
)
// - isActive
this.initializeSelectedImages(records);
this.initializeSelectedImages(records)
// this.isSure = record.isSure;
//
if (records.length > 0) {
this.refreshSidebarLabel(records[0].operaName, records[0].id);
this.refreshSidebarLabel(
records[0].operaName,
records[0].id,
)
}
this.showImageResults = true;
this.showNewAnnotation = false;
this.showNewAnnotationAdd = true;
this.$message.success('上传和识别完成');
this.showImageResults = true
this.showNewAnnotation = false
this.showNewAnnotationAdd = true
this.$message.success('上传和识别完成')
} else {
this.$message.error('上传失败');
this.$message.error('上传失败')
}
})
.catch(error => {
loading.close();
console.error('上传异常:', error);
this.$message.error('上传过程中出现错误');
});
.catch((error) => {
loading.close()
console.error('上传异常:', error)
this.$message.error('上传过程中出现错误')
})
},
refreshSidebarLabel(operaName, recordId) {
this.getImageListAPI()
setTimeout(() => {
const index = this.labelList.findIndex(item => item.id === recordId);
const index = this.labelList.findIndex(
(item) => item.id === recordId,
)
if (index !== -1) {
this.labelList[index].name = operaName;
this.labelList[index].name = operaName
}
//
this.selectedItem = recordId;
}, 500); //
this.selectedItem = recordId
}, 500) //
},
selectItem(item) {
this.selectedItem = item.id;
this.selectedItem = item.id
//
this.loadRecordData(item.id);
this.loadRecordData(item.id)
},
loadRecordData(recordId) {
//
const record = this.labelList.find(r => r.id === recordId);
const record = this.labelList.find((r) => r.id === recordId)
if (record) {
this.showNewAnnotation = false;
this.showNewAnnotationAdd = true;
this.showNewAnnotation = false
this.showNewAnnotationAdd = true
//
this.fileList = []; //
this.selectedTag = []; //
this.showImageResults = true; //
this.selectedImages = {}; //
this.fileList = [] //
this.selectedTag = [] //
this.showImageResults = true //
this.selectedImages = {} //
//
this.generateTestData(recordId);
this.generateTestData(recordId)
}
},
toggleTag(tag) {
const index = this.selectedTag.findIndex(item => item.id === tag.id)
const index = this.selectedTag.findIndex(
(item) => item.id === tag.id,
)
if (index === -1) {
this.selectedTag.push(tag)
} else {
@ -315,7 +340,7 @@ export default {
}
if (!this.showAllTags) {
this.updateVisibleTagsInCollapsedState();
this.updateVisibleTagsInCollapsedState()
}
},
@ -327,8 +352,13 @@ export default {
if (selectedTags.length >= 3) {
this.visibleTags = selectedTags.slice(0, 3)
} else {
const tags = defaultTags.filter(tag =>
!selectedTags.some(selected => selected.id === tag.id))
const tags = defaultTags
.filter(
(tag) =>
!selectedTags.some(
(selected) => selected.id === tag.id,
),
)
.slice(0, 3 - selectedTags.length)
this.visibleTags = [...selectedTags, ...tags]
}
@ -348,8 +378,11 @@ export default {
if (selectedTags.length > 0) {
if (selectedTags.length <= 3) {
const allTags = [...selectedTags, ...defaultTags]
const uniqueTags = allTags.filter((tag, index, self) =>
index === self.findIndex(t => t.id === tag.id))
const uniqueTags = allTags.filter(
(tag, index, self) =>
index ===
self.findIndex((t) => t.id === tag.id),
)
this.visibleTags = uniqueTags.slice(0, 3)
} else {
this.visibleTags = selectedTags
@ -362,104 +395,128 @@ export default {
handleFileChange(file, fileList) {
if (file.raw) {
const reader = new FileReader();
const reader = new FileReader()
reader.onload = (e) => {
file.url = e.target.result;
};
reader.readAsDataURL(file.raw);
file.url = e.target.result
}
reader.readAsDataURL(file.raw)
}
// fileListfile.url
setTimeout(() => {
this.fileList = fileList;
}, 100);
this.fileList = fileList
}, 100)
},
removeImage(index) {
this.fileList.splice(index, 1);
this.$message.success('已删除文件');
this.fileList.splice(index, 1)
this.$message.success('已删除文件')
},
//
//
handleHandClick(result, index) {
//
if (typeof result === 'object' && result.hasOwnProperty('groupIndex') && result.hasOwnProperty('imageIndex')) {
if (
typeof result === 'object' &&
result.hasOwnProperty('groupIndex') &&
result.hasOwnProperty('imageIndex')
) {
//
const { groupIndex, imageIndex, key } = result;
const { groupIndex, imageIndex, key } = result
console.log("点击了分组图片:", groupIndex, imageIndex);
console.log('点击了分组图片:', groupIndex, imageIndex)
//
const hasOtherUnconfirmedGroupSelection = Object.keys(this.selectedImages).some(k => {
const hasOtherUnconfirmedGroupSelection = Object.keys(
this.selectedImages,
).some((k) => {
if (k.includes('-')) {
const [otherGroupIndex, otherImageIndex] = k.split('-').map(Number);
const [otherGroupIndex, otherImageIndex] = k
.split('-')
.map(Number)
//
const otherGroup = this.groupedImageResults[otherGroupIndex];
const otherGroup =
this.groupedImageResults[otherGroupIndex]
//
if (otherGroup && otherGroup.isSure !== '1' && otherGroupIndex !== groupIndex && this.selectedImages[k]) {
return true;
if (
otherGroup &&
otherGroup.isSure !== '1' &&
otherGroupIndex !== groupIndex &&
this.selectedImages[k]
) {
return true
}
}
return false;
});
return false
})
//
if (hasOtherUnconfirmedGroupSelection) {
this.$message.warning('当前版本不支持跨组选择,请先取消其他组的选择');
return;
this.$message.warning(
'当前版本不支持跨组选择,请先取消其他组的选择',
)
return
}
if (this.selectedImages[key]) {
console.log("取消选中分组图片:", groupIndex, imageIndex);
this.$set(this.selectedImages, key, false);
console.log('取消选中分组图片:', groupIndex, imageIndex)
this.$set(this.selectedImages, key, false)
} else {
console.log("选中分组图片:", groupIndex, imageIndex);
this.$set(this.selectedImages, key, true);
console.log('选中分组图片:', groupIndex, imageIndex)
this.$set(this.selectedImages, key, true)
}
} else if (typeof result === 'object' && result.hasOwnProperty('index') && result.isSingle) {
} else if (
typeof result === 'object' &&
result.hasOwnProperty('index') &&
result.isSingle
) {
//
const { index } = result;
console.log("点击了图片:", index);
const { index } = result
console.log('点击了图片:', index)
if (this.selectedImages[index]) {
console.log("取消选中:", index);
this.$set(this.selectedImages, index, false);
console.log('取消选中:', index)
this.$set(this.selectedImages, index, false)
} else {
console.log("选中:", index);
this.$set(this.selectedImages, index, true);
console.log('选中:', index)
this.$set(this.selectedImages, index, true)
}
} else {
//
console.log(result);
console.log("点击了图片:", index);
console.log(result)
console.log('点击了图片:', index)
if (this.selectedImages[index]) {
console.log("取消选中:", index);
this.$set(this.selectedImages, index, false);
console.log('取消选中:', index)
this.$set(this.selectedImages, index, false)
} else {
console.log("选中:", index);
this.$set(this.selectedImages, index, true);
console.log('选中:', index)
this.$set(this.selectedImages, index, true)
}
}
},
async generateTestData(recordId) {
try {
const res = await getImageListDetailsAPI({id: recordId, operaType: 1})
const res = await getImageListDetailsAPI({
id: recordId,
operaType: 1,
})
if (res.code === 200 && res.data && Array.isArray(res.data)) {
this.addId = res.data[0].operaId;
console.log('addId:', this.addId);
this.addId = res.data[0].operaId
console.log('addId:', this.addId)
//
this.groupedImageResults = res.data.map((record, index) => ({
this.groupedImageResults = res.data.map(
(record, index) => ({
index: index, //
operaName: record.operaName,
images: record.fileVoList.map(item => ({
images: record.fileVoList.map((item) => ({
url: item.bjUrl || item.url,
id: item.imageId || item.id,
name: item.originalName,
contentImage: item.contentImage,
fileSize: item.fileSize,
operId: record.id
operId: record.id,
})),
totalCount: record.imageNum || 0,
recognizedCount: record.bzNum || 0,
@ -467,12 +524,13 @@ export default {
isSure: record.isSure,
operId: record.operaId,
id: record.id,
}));
}),
)
// - isActive
this.initializeSelectedImages(res.data);
this.initializeSelectedImages(res.data)
//
this.isSure = res.data[0]?.isSure || '';
this.isSure = res.data[0]?.isSure || ''
}
} catch (error) {
console.error('获取列表异常:', error)
@ -481,7 +539,7 @@ export default {
// IDoperId
getSelectedImageInfo() {
const selectedImageInfo = [];
const selectedImageInfo = []
//
for (let key in this.selectedImages) {
@ -489,52 +547,54 @@ export default {
//
if (key.includes('-')) {
// : key "groupIndex-imageIndex"
const [groupIndex, imageIndex] = key.split('-').map(Number);
const group = this.groupedImageResults[groupIndex];
const [groupIndex, imageIndex] = key
.split('-')
.map(Number)
const group = this.groupedImageResults[groupIndex]
if (group && group.images[imageIndex]) {
selectedImageInfo.push({
imageId: group.images[imageIndex].id,
operId: group.operId,
imageName: group.images[imageIndex].name
});
imageName: group.images[imageIndex].name,
})
}
} else {
// : key
const index = parseInt(key);
const index = parseInt(key)
if (this.imageResults[index]) {
selectedImageInfo.push({
imageId: this.imageResults[index].id,
operId: this.imageResults[index].operId,
imageName: this.imageResults[index].name
});
imageName: this.imageResults[index].name,
})
}
}
}
}
return selectedImageInfo;
return selectedImageInfo
},
confirmResults(id) {
console.log('确认结果', id)
const selectedInfo = this.getSelectedImageInfo();
const selectedInfo = this.getSelectedImageInfo()
if (selectedInfo.length === 0) {
// this.$message.warning('');
// return;
this.handleConfirm(this.addId,null,id );
this.handleConfirm(this.addId, null, id)
} else {
// ID
const imageIds = selectedInfo.map(item => item.imageId);
const imageIds = selectedInfo.map((item) => item.imageId)
// operId使
const operId = selectedInfo[0].operId;
const operId = selectedInfo[0].operId
console.log('选中的图片IDs:', imageIds);
console.log('操作ID:', operId);
console.log('选中的图片IDs:', imageIds)
console.log('操作ID:', operId)
//
this.handleConfirm(operId, imageIds,id);
this.handleConfirm(operId, imageIds, id)
}
},
@ -544,48 +604,45 @@ export default {
const res = await updateImageSureAPI({
operaId: operId,
imageId: imageIds,
id:id
});
id: id,
})
if (res.code === 200) {
this.$message.success('确认成功');
this.$message.success('确认成功')
// 使 nextTick DOM
this.$nextTick(() => {
setTimeout(async () => {
await this.generateTestData(this.addId);
}, 8000); // 0.8
});
await this.generateTestData(this.addId)
}, 8000) // 0.8
})
} else {
this.$message.error('确认失败');
this.$message.error('确认失败')
}
} catch (error) {
console.error('确认异常:', error);
this.$message.error('确认过程中出现错误');
console.error('确认异常:', error)
this.$message.error('确认过程中出现错误')
}
},
toggleSidebar() {
this.isSidebarVisible = !this.isSidebarVisible;
this.isSidebarVisible = !this.isSidebarVisible
},
initializeSelectedImages(records) {
//
this.selectedImages = {};
this.selectedImages = {}
//
records.forEach((record, groupIndex) => {
if (record.fileVoList && Array.isArray(record.fileVoList)) {
record.fileVoList.forEach((fileItem, imageIndex) => {
// isActive "1"
if (fileItem.isActive === "1") {
const key = `${groupIndex}-${imageIndex}`;
this.$set(this.selectedImages, key, true);
if (fileItem.isActive === '1') {
const key = `${groupIndex}-${imageIndex}`
this.$set(this.selectedImages, key, true)
}
});
})
}
});
})
},
},
}
</script>
@ -596,7 +653,8 @@ export default {
overflow: hidden;
height: calc(100vh - 84px);
width: 100%;
background: url('../../../assets/images/imageCaptioning/login-bg-background.png') no-repeat center center;
background: url('../../../assets/images/imageCaptioning/login-bg-background.png')
no-repeat center center;
background-size: 100% 100%;
}