This commit is contained in:
BianLzhaoMin 2025-11-26 16:11:54 +08:00
parent 651342597b
commit 88c29b9dae
5 changed files with 487 additions and 341 deletions

View File

@ -1,109 +1,135 @@
<template>
<div class="history-content">
<!-- 可滚动区域 -->
<div class="scrollable-content">
<ImageResults
:is-evaluate="1"
:upload-info="uploadInfo"
:image-results="imageResults"
:selected-images="selectedImages"
@hand-click="handleHandClickFromChild"
@confirm-results="$emit('confirm-results')"
/>
</div>
<div class="history-content">
<!-- 可滚动区域 -->
<div class="scrollable-content">
<ImageResults
:is-evaluate="1"
:upload-info="uploadInfo"
:image-results="imageResults"
:selected-images="selectedImages"
@hand-click="handleHandClickFromChild"
@confirm-results="$emit('confirm-results')"
/>
</div>
<!-- 固定在底部的区域 -->
<div class="fixed-bottom-area">
<!-- 标签选择区域 -->
<TagSelector
:tags="tags"
:visible-tags="visibleTags"
:selected-tag="selectedTag"
@toggle-tag="$emit('toggle-tag', $event)"
@toggle-all-tags="$emit('toggle-all-tags')"
/>
<!-- 固定在底部的区域 -->
<div class="fixed-bottom-area">
<!-- 标签选择区域 -->
<TagSelector
:tags="tags"
:visible-tags="visibleTags"
:selected-tag="selectedTag"
@toggle-tag="$emit('toggle-tag', $event)"
@toggle-all-tags="$emit('toggle-all-tags')"
/>
<!-- 图片上传区域 -->
<FileUploader
:file-list="fileList"
:selected-tag="selectedTag"
@file-change="(file, fileList) => $emit('file-change', file, fileList)"
@remove-image="(index) => $emit('remove-image', index)"
@start-upload="$emit('start-upload')"
/>
<!-- 输入备注 -->
<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)
"
@remove-image="(index) => $emit('remove-image', index)"
@start-upload="$emit('start-upload', remarkInfo)"
/>
</div>
</div>
</div>
</template>
<script>
import ImageResults from './ImageResultsUn.vue';
import TagSelector from './TagSelector.vue';
import FileUploader from './FileUploaderUn.vue';
import ImageResults from './ImageResultsUn.vue'
import TagSelector from './TagSelector.vue'
import FileUploader from './FileUploaderUn.vue'
import axios from 'axios'
import {getToken} from '@/utils/auth'
import { getToken } from '@/utils/auth'
export default {
name: "HistoryView",
components: {
ImageResults,
TagSelector,
FileUploader
},
props: {
uploadInfo: {
type: String,
default: ''
name: 'HistoryView',
components: {
ImageResults,
TagSelector,
FileUploader,
},
imageResults: {
type: Array,
default: () => []
data() {
return {
remarkInfo: '',
}
},
selectedImages: {
type: Object,
default: () => ({})
props: {
uploadInfo: {
type: String,
default: '',
},
imageResults: {
type: Array,
default: () => [],
},
selectedImages: {
type: Object,
default: () => ({}),
},
tags: {
type: Array,
default: () => [],
},
visibleTags: {
type: Array,
default: () => [],
},
selectedTag: {
type: Array,
default: () => [],
},
fileList: {
type: Array,
default: () => [],
},
},
tags: {
type: Array,
default: () => []
methods: {
handleHandClickFromChild(result, index) {
this.$emit('hand-click', result, index)
},
},
visibleTags: {
type: Array,
default: () => []
watch: {
imageResults: {
handler(newVal) {
if (newVal.length > 0) {
this.remarkInfo = newVal[0].remark
}
},
immediate: true,
},
},
selectedTag: {
type: Array,
default: () => []
},
fileList: {
type: Array,
default: () => []
}
},
methods: {
handleHandClickFromChild(result, index) {
this.$emit('hand-click', result, index);
}
}
}
</script>
<style scoped>
.history-content {
height: 100%;
width: 60%;
margin-left: 22%;
display: flex;
flex-direction: column;
height: 100%;
width: 60%;
margin-left: 22%;
display: flex;
flex-direction: column;
}
.scrollable-content {
flex: 1;
overflow-y: auto;
flex: 1;
overflow-y: auto;
}
.fixed-bottom-area {
position: sticky;
bottom: 0;
padding-top: 20px;
position: sticky;
bottom: 0;
padding-top: 20px;
}
</style>

View File

@ -44,7 +44,7 @@
"
:src="result.url"
fit="contain"
@click="onClickImage(result)"
@click="onClickImage(result, group.isSure)"
>
<div slot="error" class="image-slot">
<i class="el-icon-picture-outline"></i>
@ -248,7 +248,7 @@
:before-close="handleCloseImageDialog"
>
<template #title>
<span>图片标注</span>
<span>{{ isSure == 1 ? '图片查看' : '图片标注' }}</span>
</template>
<div v-if="currentImage" class="annotation-dialog">
<div class="annotation-preview">
@ -309,14 +309,14 @@
</div>
<div class="annotation-buttons">
<el-button
v-if="!isReAnnotating"
v-if="!isReAnnotating && isSure != 1"
type="primary"
size="small"
@click="startReAnnotation"
>
重新标注
</el-button>
<template v-else>
<template v-if="isReAnnotating && isSure != 1">
<el-button size="small" @click="cancelReAnnotation">
取消标注
</el-button>
@ -445,6 +445,7 @@ export default {
],
imgSrc: '',
isSure: null,
}
},
created() {
@ -485,11 +486,12 @@ export default {
},
//
onClickImage(image) {
onClickImage(image, isSure = null) {
console.log('image', image)
if (!image || !image.url) {
return
}
this.isSure = isSure
this.currentImage = image
this.imgSrc = image.url
this.imageDialogVisible = true
@ -506,6 +508,10 @@ export default {
},
startReAnnotation() {
if (this.isSure == 1) {
this.$message.warning('该图片已确认,无法重新标注')
return
}
this.isReAnnotating = true
this.annotationCoords = null
this.annotationType = ''
@ -779,6 +785,28 @@ export default {
finalCanvas.height = this.originalSize.height
const finalCtx = finalCanvas.getContext('2d')
//
// 使
const avgSize =
(this.originalSize.width + this.originalSize.height) / 2
const baseSize = 1000 // 1000px
// 使
// 0.8 - 3.0
const scaleFactor = Math.max(
0.8,
Math.min(3.0, Math.sqrt(avgSize / baseSize)),
)
// 线
const baseLineWidth = 1.5
const lineWidth = baseLineWidth * scaleFactor
const baseFontSize = 12
const fontSize = baseFontSize * scaleFactor
const baseMargin = 2
const margin = baseMargin * scaleFactor
const basePadding = 2
const padding = basePadding * scaleFactor
//
const img = new Image()
img.crossOrigin = 'Anonymous'
@ -795,15 +823,50 @@ export default {
//
this.annotations.forEach((annotation) => {
const { coords } = annotation
const { coords, typeName } = annotation
//
finalCtx.strokeStyle = '#03FB02'
finalCtx.lineWidth = 1.5
finalCtx.lineWidth = lineWidth
finalCtx.strokeRect(
coords.x1,
coords.y1,
coords.x2 - coords.x1,
coords.y2 - coords.y1,
)
//
if (typeName) {
//
finalCtx.font = `${fontSize}px Arial`
finalCtx.textBaseline = 'top'
//
const textMetrics =
finalCtx.measureText(typeName)
const textWidth = textMetrics.width
const textHeight = fontSize
// + margin
const labelX = coords.x1 + margin
const labelY = coords.y1 + margin
//
finalCtx.fillStyle = '#01CB04'
finalCtx.fillRect(
labelX,
labelY,
textWidth + padding * 2,
textHeight + padding * 2,
)
//
finalCtx.fillStyle = '#ffffff'
finalCtx.fillText(
typeName,
labelX + padding,
labelY + padding,
)
}
})
resolve()

View File

@ -265,6 +265,8 @@ export default {
isSure: record.isSure,
operId: record.operaId,
id: record.id,
isSure: record.isSure,
remark: record.remark,
}),
)
// - isActive
@ -528,6 +530,7 @@ export default {
operId: record.operaId,
id: record.id,
remark: record.remark,
isSure: record.isSure,
}),
)

View File

@ -1,48 +1,86 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" >
<el-form-item prop="date">
<el-date-picker
v-model="queryParams.operaTime"
type="date"
placeholder="选择日期"
<div class="app-container">
<el-form
:model="queryParams"
ref="queryForm"
size="small"
:inline="true"
>
</el-date-picker>
</el-form-item>
<el-form-item prop="date">
<el-date-picker
v-model="queryParams.operaTime"
type="date"
placeholder="选择日期"
>
</el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery" style="background:transparent;border-color: #B1C0CB;">重置</el-button>
<el-button icon="el-icon-download" size="mini" @click="handleExport" style="background:transparent;border-color: #B1C0CB;">下载</el-button>
</el-form-item>
</el-form>
<el-form-item prop="remark">
<el-input
v-model="queryParams.remark"
placeholder="请输入备注"
clearable
style="width: 240px"
/>
</el-form-item>
<div class="content-area" ref="servicesGrid">
<div
:key="item.id"
class="services-grid"
v-for="item in showProMaterialsDuctList"
>
<!-- 图片区域 -->
<div class="card-image">
<ImagePreview
style="margin-top: 10px"
:height="200"
:borderRadius="10"
:width="itemWidth"
:src="item.bjUrl"
/>
</div>
<el-form-item>
<el-button
type="primary"
icon="el-icon-search"
size="mini"
@click="handleQuery"
>搜索</el-button
>
<el-button
icon="el-icon-refresh"
size="mini"
@click="resetQuery"
style="background: transparent; border-color: #b1c0cb"
>重置</el-button
>
<el-button
icon="el-icon-download"
size="mini"
@click="handleExport"
style="background: transparent; border-color: #b1c0cb"
>下载</el-button
>
</el-form-item>
</el-form>
<!-- 卡片内容 -->
<div class="card-content">
<div class="card-footer">
<div class="person-info">
<span class="name">综合得分:{{ item.overallScore }}</span>
<span class="name">{{ item.dictValue }}</span>
<span class="date">{{ item.operaName }}</span>
</div>
<!-- <div class="tags">
<div class="content-area" ref="servicesGrid">
<div
:key="item.id"
class="services-grid"
v-for="item in showProMaterialsDuctList"
>
<!-- 图片区域 -->
<div class="card-image">
<ImagePreview
style="margin-top: 10px"
:height="200"
:borderRadius="10"
:width="itemWidth"
:src="item.bjUrl"
/>
</div>
<!-- 卡片内容 -->
<div class="card-content">
<div class="card-footer">
<div class="person-info">
<span class="name"
>综合得分:{{ item.overallScore }}</span
>
<span class="name">{{ item.dictValue }}</span>
<span class="date">{{ item.operaName }}</span>
</div>
<div class="person-info">
<span class="name">备注:{{ item.remark }}</span>
</div>
<!-- <div class="tags">
<div class="tag-row">
<span class="tag-item" style="padding-left: 16px;">清晰度: {{ item.clarity }}</span>
<span class="tag-item" style="padding-left: 16px;">干净度: {{ item.cleanliness }}</span>
@ -54,277 +92,290 @@
<span class="tag-item">细节体验: {{ item.detail }}</span>
</div>
</div>-->
<div style="border-top: 1px solid #CFD4D7;margin-top: 10px"></div>
<div class="action-buttons">
<el-button
type="danger"
size="mini"
@click="handleDelete(item)"
plain
icon="el-icon-delete"
>
删除
</el-button>
<div
style="
border-top: 1px solid #cfd4d7;
margin-top: 10px;
"
></div>
<div class="action-buttons">
<el-button
type="danger"
size="mini"
@click="handleDelete(item)"
plain
icon="el-icon-delete"
>
删除
</el-button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
:page-sizes="[8, 16, 32, 64]"
@pagination="getList"
/>
</div>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
:page-sizes="[8, 16, 32, 64]"
@pagination="getList"
/>
</div>
</template>
<script>
import {deleteFile, exportImages, getAllImagelist} from "@/api/imageCaptioning/imageLibrary";
import {
deleteFile,
exportImages,
getAllImagelist,
} from '@/api/imageCaptioning/imageLibrary'
export default {
name: "imageCaptioningLibrary",
data() {
return {
//
total: 0,
//
queryParams: {
pageNum: 1,
pageSize: 8,
date: '',
operaType: 2,
operaTime:''
},
//
form: {},
proMaterialsListAll: [], //
showProMaterialsDuctList: [], //
itemWidth: 0, //
}
},
created() {
this.getList();
},
mounted() {
this.getItemWidth()
},
methods: {
//
getItemWidth() {
this.itemWidth = (this.$refs.servicesGrid?.clientWidth - 320) / 4
name: 'imageCaptioningLibrary',
data() {
return {
//
total: 0,
//
queryParams: {
pageNum: 1,
pageSize: 8,
date: '',
operaType: 2,
operaTime: '',
remark: '',
},
//
form: {},
proMaterialsListAll: [], //
showProMaterialsDuctList: [], //
itemWidth: 0, //
}
},
/** 查询角色列表 */
getList() {
getAllImagelist(this.queryParams).then(response => {
this.proMaterialsListAll = response.rows;
this.total = response.total || 0;
console.log(this.proMaterialsListAll)
this.showProMaterialsDuctList = this.proMaterialsListAll;
})
created() {
this.getList()
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
//
if (this.queryParams.operaTime) {
const date = new Date(this.queryParams.operaTime)
// 使getFullYeargetMonthgetDate
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
this.queryParams.operaTime = `${year}-${month}-${day}`
}
this.getList()
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm")
// null
this.queryParams.operaTime = null
this.handleQuery()
mounted() {
this.getItemWidth()
},
/** 导出按钮操作 */
handleExport() {
const imageIds = this.showProMaterialsDuctList.map(item => item.imageId);
exportImages({
imageId: imageIds
}).then(response => {
//
const blob = new Blob([response]);
const link = document.createElement('a');
const fileName = `图片压缩包_${new Date().getTime()}.zip`;
link.href = URL.createObjectURL(blob);
link.download = fileName;
link.click();
URL.revokeObjectURL(link.href);
}).catch(error => {
console.error('下载失败:', error);
this.$message.error('下载失败');
});
},
//
handleDelete(item) {
this.$confirm('确认要删除该记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
//
deleteFile({"imageId":item.imageId}).then(response => {
if (response.code === 200) {
this.$message.success('删除成功')
methods: {
//
getItemWidth() {
this.itemWidth = (this.$refs.servicesGrid?.clientWidth - 320) / 4
},
/** 查询角色列表 */
getList() {
getAllImagelist(this.queryParams).then((response) => {
this.proMaterialsListAll = response.rows
this.total = response.total || 0
console.log(this.proMaterialsListAll)
this.showProMaterialsDuctList = this.proMaterialsListAll
})
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
//
if (this.queryParams.operaTime) {
const date = new Date(this.queryParams.operaTime)
// 使getFullYeargetMonthgetDate
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
this.queryParams.operaTime = `${year}-${month}-${day}`
}
this.getList()
} else {
this.$message.error('删除失败')
}
})
}).catch(() => {
this.$message.info('已取消删除')
})
}
}
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm('queryForm')
// null
this.queryParams.operaTime = null
this.handleQuery()
},
/** 导出按钮操作 */
handleExport() {
const imageIds = this.showProMaterialsDuctList.map(
(item) => item.imageId,
)
exportImages({
imageId: imageIds,
})
.then((response) => {
//
const blob = new Blob([response])
const link = document.createElement('a')
const fileName = `图片压缩包_${new Date().getTime()}.zip`
link.href = URL.createObjectURL(blob)
link.download = fileName
link.click()
URL.revokeObjectURL(link.href)
})
.catch((error) => {
console.error('下载失败:', error)
this.$message.error('下载失败')
})
},
//
handleDelete(item) {
this.$confirm('确认要删除该记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
//
deleteFile({ imageId: item.imageId }).then((response) => {
if (response.code === 200) {
this.$message.success('删除成功')
this.getList()
} else {
this.$message.error('删除失败')
}
})
})
.catch(() => {
this.$message.info('已取消删除')
})
},
},
}
</script>
<style scoped>
.app-container {
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%;
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%;
}
.content-area {
flex: 1;
height: calc(100vh - 218px);
/*padding-bottom: 20px;*/
padding-right: 12px;
padding-top: 2px;
overflow-y: auto;
display: flex;
flex-wrap: wrap;
row-gap: 20px;
flex: 1;
height: calc(100vh - 218px);
/*padding-bottom: 20px;*/
padding-right: 12px;
padding-top: 2px;
overflow-y: auto;
display: flex;
flex-wrap: wrap;
row-gap: 20px;
}
.services-grid {
width: 22%;
margin-left: 2.5%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-sizing: border-box;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
border: 1px solid #d6d1d1;
border-radius: 10px;
align-content: flex-start;
justify-items: center;
height: 43%;
width: 22%;
margin-left: 2.5%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-sizing: border-box;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
border: 1px solid #d6d1d1;
border-radius: 10px;
align-content: flex-start;
justify-items: center;
height: 43%;
}
.card-image {
height: 191px;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
height: 191px;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
}
.card-content {
width: 100%;
display: flex;
flex-direction: column;
font-size: 14px;
width: 100%;
display: flex;
flex-direction: column;
font-size: 14px;
}
.card-footer {
display: flex;
flex-direction: column;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
min-height: 24px;
display: flex;
flex-direction: column;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
min-height: 24px;
}
.person-info {
display: flex;
justify-content: space-between;
margin: 10px 18px 0 18px;
align-items: center;
display: flex;
justify-content: space-between;
margin: 10px 18px 0 18px;
align-items: center;
}
.name {
font-weight: bold;
color: #333;
font-weight: bold;
color: #333;
}
.date {
color: #666;
/*font-size: 12px;*/
color: #666;
/*font-size: 12px;*/
}
.tags {
display: flex;
flex-direction: column;
margin: 10px 18px 0 18px;
gap: 6px;
display: flex;
flex-direction: column;
margin: 10px 18px 0 18px;
gap: 6px;
}
.tag-row {
display: table;
width: 100%;
table-layout: fixed;
margin:3px 0 0 0;
display: table;
width: 100%;
table-layout: fixed;
margin: 3px 0 0 0;
}
.tag-item {
display: table-cell;
width: 33.33%;
text-align: left;
font-size: 12px;
color: #333;
white-space: nowrap;
padding: 0 4px;
display: table-cell;
width: 33.33%;
text-align: left;
font-size: 12px;
color: #333;
white-space: nowrap;
padding: 0 4px;
}
.action-buttons {
display: flex;
justify-content: flex-end;
margin: 10px 18px 0px 18px;
display: flex;
justify-content: flex-end;
margin: 10px 18px 0px 18px;
}
>>>.el-input__inner{
background: transparent !important;
color: #333333 !important;
border-color: #B5C4CC;
.el-input__inner {
background: transparent !important;
color: #333333 !important;
border-color: #b5c4cc;
}
.pagination-container{
background: transparent !important;
.pagination-container {
background: transparent !important;
}
.title-desc {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 8px;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 8px;
}
</style>

View File

@ -87,6 +87,7 @@ export default {
selectedImages: {}, //
isSidebarVisible: true, // /
addId: '',
remarkInfo: '',
}
},
@ -123,7 +124,7 @@ export default {
this.addId = ''
},
startUpload() {
startUpload(remarkInfo = '') {
console.log('开始上传')
if (this.fileList.length === 0) {
@ -156,7 +157,7 @@ export default {
})
const params = {
id: this.addId,
remark: this.remarkInfo,
remark: remarkInfo || this.remarkInfo,
}
formData.append('params', JSON.stringify(params))
// formData.append("id", this.addId)
@ -190,6 +191,7 @@ export default {
balance: item.balance,
detail: item.detail,
dictValue: item.dictValue,
remark: record.remark,
}),
)
allImageResults =
@ -316,6 +318,7 @@ export default {
balance: item.balance,
detail: item.detail,
dictValue: item.dictValue,
remark: record.remark,
}),
)
allImageResults = allImageResults.concat(fileResults)