692 lines
26 KiB
Vue
692 lines
26 KiB
Vue
<template>
|
||
<div class="image-captioning-container">
|
||
<!-- 左侧导航栏 -->
|
||
<Sidebar
|
||
:is-sidebar-visible="isSidebarVisible"
|
||
:label-list="labelList"
|
||
:selected-item="selectedItem"
|
||
:current-index="1"
|
||
@toggle-sidebar="toggleSidebar"
|
||
@create-new-label="createNewLabel"
|
||
@select-item="selectItem"
|
||
/>
|
||
|
||
<!-- 主内容区域 -->
|
||
<div class="main-content" v-if="showNewAnnotation">
|
||
<!-- 主内容区域-新增的 -->
|
||
<div class="add-content">
|
||
<!-- 标题 -->
|
||
<div class="welcome-message"> 嗨!我是你的图像标注助理 </div>
|
||
|
||
<!-- 标签选择区域 -->
|
||
<TagSelector
|
||
:tags="tags"
|
||
:visible-tags="visibleTags"
|
||
:selected-tag="selectedTag"
|
||
@toggle-tag="toggleTag"
|
||
@toggle-all-tags="toggleAllTags"
|
||
/>
|
||
|
||
<!-- 输入备注 -->
|
||
<el-input
|
||
:rows="1"
|
||
placeholder="请输入备注"
|
||
type="textarea"
|
||
v-model="remarkInfo"
|
||
/>
|
||
|
||
<!-- 图片上传区域 -->
|
||
<FileUploader
|
||
:file-list="fileList"
|
||
:selected-tag="selectedTag"
|
||
@file-change="handleFileChange"
|
||
@remove-image="removeImage"
|
||
@start-upload="startUpload"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 主内容区域-历史 -->
|
||
<div class="main-content" v-if="showNewAnnotationAdd">
|
||
<HistoryView
|
||
:upload-info="uploadInfo"
|
||
:image-results="imageResults"
|
||
:grouped-image-results="groupedImageResults"
|
||
:selected-images="selectedImages"
|
||
:tags="tags"
|
||
:visible-tags="visibleTags"
|
||
:selected-tag="selectedTag"
|
||
:file-list="fileList"
|
||
:is-sure="isSure"
|
||
@hand-click="handleHandClick"
|
||
@confirm-results="confirmResults"
|
||
@toggle-tag="toggleTag"
|
||
@toggle-all-tags="toggleAllTags"
|
||
@file-change="handleFileChange"
|
||
@remove-image="removeImage"
|
||
@start-upload="startUpload"
|
||
@update-image-annotation="updateImageAnnotation"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
getSelectedAPI,
|
||
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'
|
||
|
||
export default {
|
||
name: 'imageCaptioning',
|
||
components: {
|
||
Sidebar,
|
||
TagSelector,
|
||
FileUploader,
|
||
HistoryView,
|
||
},
|
||
data() {
|
||
return {
|
||
showNewAnnotation: true, // 控制新建标注界面的显示状态
|
||
showNewAnnotationAdd: false, // 控制历史界面的显示状态
|
||
tags: [], // 标签数据
|
||
visibleTags: [], // 默认显示前3个
|
||
selectedTag: [], // 当前选中的标签
|
||
showAllTags: false, // 控制是否显示所有标签
|
||
|
||
// 导航栏数据
|
||
labelList: [],
|
||
selectedItem: null,
|
||
// 文件上传相关
|
||
fileList: [],
|
||
showImageResults: false,
|
||
uploadInfo: '',
|
||
|
||
// 图片识别结果
|
||
imageResults: [],
|
||
selectedImages: {}, // 用于跟踪选中的图片
|
||
isSidebarVisible: true, // 控制左侧导航栏显示/隐藏
|
||
isSure: '', //小图标,确定按钮 0-未确定 1-已确定
|
||
addId: '', //新增id
|
||
|
||
groupedImageResults: [], // 新增分组数据
|
||
|
||
remarkInfo: '', // 备注信息
|
||
}
|
||
},
|
||
|
||
created() {
|
||
// 获取标签列表数据
|
||
this.getSelectedAPI()
|
||
//查询左侧历史记录
|
||
this.getImageListAPI()
|
||
},
|
||
|
||
methods: {
|
||
async getSelectedAPI() {
|
||
try {
|
||
const res = await getSelectedAPI()
|
||
// 判断返回码和数据
|
||
if (res.code === 200 && res.data && Array.isArray(res.data)) {
|
||
// 将返回的数据转换为包含 id 和 name 的对象数组
|
||
this.tags = res.data.map((item) => ({
|
||
id: item.id,
|
||
name: item.name,
|
||
}))
|
||
this.visibleTags = this.tags.slice(0, 3)
|
||
} else {
|
||
console.error('获取标签列表失败:', res)
|
||
this.tags = []
|
||
}
|
||
} catch (error) {
|
||
console.error('获取标签列表异常:', error)
|
||
this.tags = []
|
||
}
|
||
},
|
||
|
||
async getImageListAPI() {
|
||
try {
|
||
const res = await getImageListAPI({ operaType: 1 })
|
||
// 判断返回码和数据
|
||
if (res.code === 200 && res.data && Array.isArray(res.data)) {
|
||
this.labelList = res.data.map((item) => ({
|
||
id: item.id,
|
||
date: item.operaTime,
|
||
name: item.operaName,
|
||
}))
|
||
}
|
||
} catch (error) {
|
||
console.error('获取列表异常:', error)
|
||
}
|
||
},
|
||
|
||
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 = ''
|
||
},
|
||
|
||
startUpload(remarkInfo = '') {
|
||
console.log('开始上传')
|
||
if (this.selectedTag.length === 0) {
|
||
this.$message.warning('请选择需要标注的内容标签')
|
||
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)
|
||
if (this.fileList.length === 0) {
|
||
this.$message.warning('请上传图片')
|
||
return
|
||
}
|
||
// 初始化 selectedImages
|
||
this.selectedImages = {}
|
||
// 显示加载状态
|
||
const loading = this.$loading({
|
||
lock: true,
|
||
text: '正在上传和识别图片...',
|
||
spinner: 'el-icon-loading',
|
||
background: 'rgba(0, 0, 0, 0.7)',
|
||
})
|
||
// 保存原始文件列表用于显示结果
|
||
const originalFileList = [...this.fileList]
|
||
// 清空上传预览区域
|
||
this.fileList = []
|
||
|
||
// 创建 FormData 对象
|
||
const formData = new FormData()
|
||
// 添加文件
|
||
originalFileList.forEach((file) => {
|
||
formData.append('files', file.raw) // 使用 raw 字段获取原始文件对象
|
||
})
|
||
// formData.append('param', selectedTagNames);
|
||
// formData.append('id', this.addId);
|
||
|
||
// 修改参数处理方式,与之前的params 保持一致
|
||
const params = {
|
||
param: selectedTagNames,
|
||
remark: remarkInfo || this.remarkInfo,
|
||
id: this.addId,
|
||
}
|
||
formData.append('params', JSON.stringify(params))
|
||
|
||
//调用上传的接口,传递文件列表和标签IDs,name
|
||
addImageInfoAPI(formData)
|
||
.then((res) => {
|
||
loading.close()
|
||
if (
|
||
res.code === 200 &&
|
||
res.data &&
|
||
Array.isArray(res.data)
|
||
) {
|
||
// 处理成功响应 - 获取全部记录
|
||
const records = res.data
|
||
this.addId = records[0]?.operaId || ''
|
||
console.log('this.addId:', this.addId)
|
||
|
||
// 按下标分组存储数据
|
||
this.groupedImageResults = records.map(
|
||
(record, index) => ({
|
||
index: index, // 下标
|
||
operaName: record.operaName,
|
||
images: record.fileVoList.map((item) => ({
|
||
url: item.bjUrl || '未找到图片地址',
|
||
originUrl: item.imageUrl,
|
||
id: item.imageId || item.id,
|
||
name: item.originalName,
|
||
contentImage: item.contentImage,
|
||
fileSize: item.fileSize,
|
||
operId: record.id,
|
||
})),
|
||
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.isSure = record.isSure;
|
||
|
||
// 刷新左侧标签文本并选中对应标签
|
||
if (records.length > 0) {
|
||
this.refreshSidebarLabel(
|
||
records[0].operaName,
|
||
records[0].id,
|
||
)
|
||
}
|
||
|
||
this.showImageResults = true
|
||
this.showNewAnnotation = false
|
||
this.showNewAnnotationAdd = true
|
||
this.$message.success('上传和识别完成')
|
||
} else {
|
||
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,
|
||
)
|
||
if (index !== -1) {
|
||
this.labelList[index].name = operaName
|
||
}
|
||
// 选中对应的标签
|
||
this.selectedItem = recordId
|
||
}, 500) // 延迟确保数据加载完成
|
||
},
|
||
|
||
selectItem(item) {
|
||
this.selectedItem = item.id
|
||
// 这里可以添加加载历史记录数据的逻辑
|
||
this.loadRecordData(item.id)
|
||
},
|
||
|
||
loadRecordData(recordId) {
|
||
// 模拟加载历史记录数据
|
||
const record = this.labelList.find((r) => r.id === recordId)
|
||
if (record) {
|
||
this.showNewAnnotation = false
|
||
this.showNewAnnotationAdd = true
|
||
// 这里可以添加实际的数据加载逻辑
|
||
this.fileList = [] // 清空文件列表
|
||
this.selectedTag = [] // 清空标签选择
|
||
this.showImageResults = true // 显示识别结果
|
||
this.selectedImages = {} // 初始化选中状态
|
||
// 生成数据
|
||
this.generateTestData(recordId)
|
||
}
|
||
},
|
||
|
||
toggleTag(tag) {
|
||
const index = this.selectedTag.findIndex(
|
||
(item) => item.id === tag.id,
|
||
)
|
||
if (index === -1) {
|
||
this.selectedTag.push(tag)
|
||
} else {
|
||
this.selectedTag.splice(index, 1)
|
||
}
|
||
|
||
if (!this.showAllTags) {
|
||
this.updateVisibleTagsInCollapsedState()
|
||
}
|
||
},
|
||
|
||
updateVisibleTagsInCollapsedState() {
|
||
const selectedTags = this.selectedTag || []
|
||
const defaultTags = this.tags.slice(0, 3)
|
||
|
||
if (selectedTags.length > 0) {
|
||
if (selectedTags.length >= 3) {
|
||
this.visibleTags = selectedTags.slice(0, 3)
|
||
} else {
|
||
const tags = defaultTags
|
||
.filter(
|
||
(tag) =>
|
||
!selectedTags.some(
|
||
(selected) => selected.id === tag.id,
|
||
),
|
||
)
|
||
.slice(0, 3 - selectedTags.length)
|
||
this.visibleTags = [...selectedTags, ...tags]
|
||
}
|
||
} else {
|
||
this.visibleTags = defaultTags
|
||
}
|
||
},
|
||
|
||
toggleAllTags() {
|
||
this.showAllTags = !this.showAllTags
|
||
if (this.showAllTags) {
|
||
this.visibleTags = this.tags
|
||
} else {
|
||
const selectedTags = this.selectedTag || []
|
||
const defaultTags = this.tags.slice(0, 3)
|
||
|
||
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),
|
||
)
|
||
this.visibleTags = uniqueTags.slice(0, 3)
|
||
} else {
|
||
this.visibleTags = selectedTags
|
||
}
|
||
} else {
|
||
this.visibleTags = defaultTags
|
||
}
|
||
}
|
||
},
|
||
|
||
handleFileChange(file, fileList) {
|
||
if (file.raw) {
|
||
const reader = new FileReader()
|
||
reader.onload = (e) => {
|
||
file.url = e.target.result
|
||
}
|
||
reader.readAsDataURL(file.raw)
|
||
}
|
||
// 延迟设置fileList,确保file.url已正确设置
|
||
setTimeout(() => {
|
||
this.fileList = fileList
|
||
}, 100)
|
||
},
|
||
|
||
removeImage(index) {
|
||
this.fileList.splice(index, 1)
|
||
this.$message.success('已删除文件')
|
||
},
|
||
|
||
// 处理手图标点击事件
|
||
// 处理手图标点击事件
|
||
handleHandClick(result, index) {
|
||
// 判断是分组模式还是普通模式
|
||
if (
|
||
typeof result === 'object' &&
|
||
result.hasOwnProperty('groupIndex') &&
|
||
result.hasOwnProperty('imageIndex')
|
||
) {
|
||
// 分组模式
|
||
const { groupIndex, imageIndex, key } = result
|
||
|
||
console.log('点击了分组图片:', groupIndex, imageIndex)
|
||
|
||
// 检查是否已有其他未确认分组的选中项
|
||
const hasOtherUnconfirmedGroupSelection = Object.keys(
|
||
this.selectedImages,
|
||
).some((k) => {
|
||
if (k.includes('-')) {
|
||
const [otherGroupIndex, otherImageIndex] = k
|
||
.split('-')
|
||
.map(Number)
|
||
// 获取其他分组的信息
|
||
const otherGroup =
|
||
this.groupedImageResults[otherGroupIndex]
|
||
// 只有当其他分组未确认且与当前分组不同时,才限制选择
|
||
if (
|
||
otherGroup &&
|
||
otherGroup.isSure !== '1' &&
|
||
otherGroupIndex !== groupIndex &&
|
||
this.selectedImages[k]
|
||
) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
})
|
||
|
||
// 如果已有其他未确认分组的选中项,提示用户
|
||
if (hasOtherUnconfirmedGroupSelection) {
|
||
this.$message.warning(
|
||
'当前版本不支持跨组选择,请先取消其他组的选择',
|
||
)
|
||
return
|
||
}
|
||
|
||
if (this.selectedImages[key]) {
|
||
console.log('取消选中分组图片:', groupIndex, imageIndex)
|
||
this.$set(this.selectedImages, key, false)
|
||
} else {
|
||
console.log('选中分组图片:', groupIndex, imageIndex)
|
||
this.$set(this.selectedImages, key, true)
|
||
}
|
||
} else if (
|
||
typeof result === 'object' &&
|
||
result.hasOwnProperty('index') &&
|
||
result.isSingle
|
||
) {
|
||
// 普通模式
|
||
const { index } = result
|
||
console.log('点击了图片:', index)
|
||
|
||
if (this.selectedImages[index]) {
|
||
console.log('取消选中:', index)
|
||
this.$set(this.selectedImages, index, false)
|
||
} else {
|
||
console.log('选中:', index)
|
||
this.$set(this.selectedImages, index, true)
|
||
}
|
||
} else {
|
||
// 兼容旧的处理方式
|
||
console.log(result)
|
||
console.log('点击了图片:', index)
|
||
|
||
if (this.selectedImages[index]) {
|
||
console.log('取消选中:', index)
|
||
this.$set(this.selectedImages, index, false)
|
||
} else {
|
||
console.log('选中:', index)
|
||
this.$set(this.selectedImages, index, true)
|
||
}
|
||
}
|
||
},
|
||
|
||
async generateTestData(recordId) {
|
||
try {
|
||
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.groupedImageResults = res.data.map(
|
||
(record, index) => ({
|
||
index: index, // 下标
|
||
operaName: record.operaName,
|
||
images: record.fileVoList.map((item) => ({
|
||
url: item.bjUrl,
|
||
originUrl: item.imageUrl,
|
||
id: item.imageId || item.id,
|
||
name: item.originalName,
|
||
contentImage: item.contentImage,
|
||
fileSize: item.fileSize,
|
||
operId: record.id,
|
||
})),
|
||
totalCount: record.imageNum || 0,
|
||
recognizedCount: record.bzNum || 0,
|
||
unrecognizedCount: record.wbzNum || 0,
|
||
isSure: record.isSure,
|
||
operId: record.operaId,
|
||
id: record.id,
|
||
remark: record.remark,
|
||
}),
|
||
)
|
||
|
||
// 初始化选中状态 - 根据 isActive 字段
|
||
this.initializeSelectedImages(res.data)
|
||
// 设置第一条记录的状态用于界面显示
|
||
this.isSure = res.data[0]?.isSure || ''
|
||
}
|
||
} catch (error) {
|
||
console.error('获取列表异常:', error)
|
||
}
|
||
},
|
||
|
||
// 获取选中图片的ID和operId
|
||
getSelectedImageInfo() {
|
||
const selectedImageInfo = []
|
||
|
||
// 遍历选中的图片
|
||
for (let key in this.selectedImages) {
|
||
if (this.selectedImages[key]) {
|
||
// 判断是分组模式还是普通模式
|
||
if (key.includes('-')) {
|
||
// 分组模式: key格式为 "groupIndex-imageIndex"
|
||
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,
|
||
})
|
||
}
|
||
} else {
|
||
// 普通模式: 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,
|
||
})
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return selectedImageInfo
|
||
},
|
||
|
||
confirmResults(id) {
|
||
console.log('确认结果', id)
|
||
const selectedInfo = this.getSelectedImageInfo()
|
||
|
||
if (selectedInfo.length === 0) {
|
||
// this.$message.warning('请至少选择一张图片');
|
||
// return;
|
||
this.handleConfirm(this.addId, null, id)
|
||
} else {
|
||
// 获取图片ID数组
|
||
const imageIds = selectedInfo.map((item) => item.imageId)
|
||
|
||
// 获取operId(假设所有选中图片属于同一组,使用第一个)
|
||
const operId = selectedInfo[0].operId
|
||
|
||
console.log('选中的图片IDs:', imageIds)
|
||
console.log('操作ID:', operId)
|
||
|
||
// 调用确认接口
|
||
this.handleConfirm(operId, imageIds, id)
|
||
}
|
||
},
|
||
|
||
async handleConfirm(operId, imageIds, id) {
|
||
try {
|
||
// 调用后端接口
|
||
const res = await updateImageSureAPI({
|
||
operaId: operId,
|
||
imageId: imageIds,
|
||
id: id,
|
||
})
|
||
if (res.code === 200) {
|
||
this.$message.success('确认成功')
|
||
// 使用 nextTick 确保 DOM 更新后再延迟
|
||
this.$nextTick(() => {
|
||
setTimeout(async () => {
|
||
await this.generateTestData(this.addId)
|
||
}, 8000) // 延迟0.8秒
|
||
})
|
||
} else {
|
||
this.$message.error('确认失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('确认异常:', error)
|
||
this.$message.error('确认过程中出现错误')
|
||
}
|
||
},
|
||
|
||
toggleSidebar() {
|
||
this.isSidebarVisible = !this.isSidebarVisible
|
||
},
|
||
|
||
initializeSelectedImages(records) {
|
||
// 清空当前选中状态
|
||
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)
|
||
}
|
||
})
|
||
}
|
||
})
|
||
},
|
||
|
||
updateImageAnnotation() {
|
||
this.generateTestData(this.selectedItem)
|
||
},
|
||
},
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.image-captioning-container {
|
||
display: flex;
|
||
overflow: hidden;
|
||
height: calc(100vh - 84px);
|
||
width: 100%;
|
||
background: url('../../../assets/images/imageCaptioning/login-bg-background.png')
|
||
no-repeat center center;
|
||
background-size: 100% 100%;
|
||
}
|
||
|
||
.main-content {
|
||
flex: 1;
|
||
padding: 40px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.add-content {
|
||
height: 55%;
|
||
width: 60%;
|
||
margin-top: 15%;
|
||
margin-left: 22%;
|
||
}
|
||
|
||
.welcome-message {
|
||
text-align: center;
|
||
font-size: 20px;
|
||
margin-bottom: 50px;
|
||
font-weight: 600;
|
||
color: #334249;
|
||
font-style: normal;
|
||
text-transform: none;
|
||
}
|
||
</style>
|