bug修改

This commit is contained in:
LHD_HY 2026-02-05 15:06:56 +08:00
parent 58ace303d2
commit 2ff53cae29
11 changed files with 462 additions and 208 deletions

View File

@ -75,3 +75,12 @@ export function getSampleImageListAPI(params) {
params
})
}
// 数据管理->样本库管理->获取样本图片列表
export function getSampleListByVersionIdAPI(params) {
return request({
url: '/smartPlatform/data/sample/getSampleListByVersionId',
method: 'get',
params
})
}

View File

@ -447,6 +447,7 @@ export default {
},
//
//
async submitAuditResult(auditStatus) {
if (this.isAuditing) return
if (!this.currentImage.id) {
@ -474,9 +475,17 @@ export default {
if (res.code === 200) {
this.$message.success(auditStatus === 1 ? '审核通过提交成功!' : '审核不通过提交成功!')
// ===== =====
await this.loadAuditImages(); //
//
this.currentImageIndex = Math.min(this.currentImageIndex, this.imageList.length - 1);
//
if (this.imageList.length === 0) {
this.currentImage = {};
this.$message.info('暂无剩余审批图片数据');
}
//
if (this.currentImageIndex < this.imageList.length - 1) {
if (this.imageList.length > 0 && this.currentImageIndex < this.imageList.length - 1) {
this.nextImage()
}
} else {
@ -498,7 +507,7 @@ export default {
//
.labeling-page-container {
width: 100%;
height: 92vh;
height: 90.5vh;
display: flex;
flex-direction: column;
overflow: hidden;

View File

@ -56,6 +56,7 @@ export default {
data() {
return {
labelingTaskId: this.convertToNumber(decryptWithSM4(this.$route.query.labelingTaskId || '')),
labelingTemplate: decryptWithSM4(this.$route.query.labelingTemplate || ''),
//
formLabel,
columnsList,
@ -107,12 +108,29 @@ export default {
// /
handleView() {
const labelingTaskIdStr = String(this.labelingTaskId)
this.$router.push({
name: 'LabelingDetail',
query: {
labelingTaskId : encryptWithSM4(labelingTaskIdStr)
}
});
console.log('123123123131',this.labelingTemplate)
if (this.labelingTemplate === '1' || this.labelingTemplate === '2') {
this.$router.push({
name: 'ClassificationDetail',
query: {
labelingTaskId: encryptWithSM4(labelingTaskIdStr),
}
});
} else if (this.labelingTemplate === '3' || this.labelingTemplate === '4' || this.labelingTemplate === '5') {
this.$router.push({
name: 'LabelingDetail',
query: {
labelingTaskId: encryptWithSM4(labelingTaskIdStr),
}
});
} else if (this.labelingTemplate === '8' || this.labelingTemplate === '9') {
this.$router.push({
name: 'OcrLabelingDetail',
query: {
labelingTaskId: encryptWithSM4(labelingTaskIdStr),
}
});
}
},
}
}

View File

@ -139,12 +139,13 @@ export default {
methods: {
//
handleProgressClick(data) {
console.log('data',data)
const labelingTaskIdStr = String(data.labelingTaskId)
this.$router.push({
name: 'auditList',
query: {
labelingTaskId : encryptWithSM4(labelingTaskIdStr)
labelingTaskId : encryptWithSM4(labelingTaskIdStr),
labelingTemplate: encryptWithSM4(data.labelingTemplateValue)
}
});
},

View File

@ -6,6 +6,7 @@
width="1200px"
:close-on-click-modal="false"
@close="handleClose"
class="data-select-dialog"
>
<div class="data-select-container">
<!-- 左侧三层样本树 -->
@ -738,6 +739,7 @@ export default {
this.fullScreenImageName = item.fileName || item.label || '样本图片';
this.fullScreenImageVisible = true;
document.body.style.overflow = 'hidden';
document.body.style.paddingRight = '17px';
},
/** 关闭全屏预览 */
@ -746,6 +748,7 @@ export default {
this.fullScreenImageUrl = "";
this.fullScreenImageName = "";
document.body.style.overflow = 'auto';
document.body.style.paddingRight = '0';
},
/** 页码大小变化 */
@ -799,174 +802,227 @@ export default {
.data-select-root {
width: 100%;
height: 100%;
overflow: hidden; /* 兜底:禁用根容器滚动 */
}
/* 主容器 */
// 穿el-dialogdialog
::v-deep .data-select-dialog {
.el-dialog__body {
padding: 15px 20px !important; /* 微调内边距,减少高度浪费 */
height: 700px !important; /* 固定dialog内容区高度包含标题/内容/按钮 */
overflow: hidden !important; /* 彻底禁用dialog默认的内容区滚动核心修复 */
display: flex;
flex-direction: column;
}
}
/* 主容器 - 核心移除外层滚动弹性占满dialog内容区 */
.data-select-container {
display: flex;
height: 600px;
gap: 20px;
box-sizing: border-box;
padding-bottom: 10px;
padding-bottom: 0; /* 修复1删掉主容器底部内边距避免间距放大双横线 */
overflow: hidden; /* 禁用自身滚动 */
flex: 1; /* 弹性占满dialog body的剩余高度抵消footer和内边距 */
height: 100%; /* 兜底高度 */
}
.sample-tree-panel {
width: 250px;
border-right: 1px solid #ebeef5;
padding-right: 10px;
box-sizing: border-box;
// - 100%
.sample-tree-panel {
width: 250px;
border-right: 1px solid #ebeef5;
padding-right: 10px;
box-sizing: border-box;
height: 100%; /* 占满主容器高度 */
display: flex;
flex-direction: column; /* 弹性列布局,标题固定+树滚动 */
overflow: hidden; /* 禁用面板自身滚动 */
.panel-title {
font-size: 14px;
font-weight: 500;
color: #333;
margin: 0 0 10px 0;
padding-bottom: 8px;
border-bottom: 1px solid #ebeef5;
}
.tree-empty-tip {
text-align: center;
padding: 20px;
color: #999;
font-size: 14px;
}
::v-deep .el-tree {
height: calc(100% - 30px);
overflow-y: auto;
.tree-tooltip {
display: inline-block;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// Element UI
.el-tree-node.is-disabled {
color: #ccc !important;
.el-checkbox {
pointer-events: none;
.el-checkbox__inner {
background-color: #f5f7fa !important;
border-color: #e4e7ed !important;
color: #c0c4cc !important;
cursor: not-allowed !important;
}
.el-checkbox__label {
color: #ccc !important;
cursor: not-allowed !important;
}
}
}
//
.el-tree-node.is-disabled .el-checkbox {
display: inline-block !important;
cursor: not-allowed !important;
}
}
.panel-title {
font-size: 14px;
font-weight: 500;
color: #333;
margin: 0 0 10px 0;
padding-bottom: 8px;
border-bottom: 1px solid #ebeef5;
flex-shrink: 0; /* 标题不收缩,固定高度 */
}
.sample-image-panel {
flex: 1;
overflow-y: auto;
box-sizing: border-box;
padding-bottom: 10px;
.tree-empty-tip {
text-align: center;
padding: 20px;
color: #999;
font-size: 14px;
flex: 1; /* 占满剩余高度,居中显示 */
display: flex;
align-items: center;
justify-content: center;
}
.select-header {
::v-deep .el-tree {
flex: 1; /* 弹性占满剩余高度 */
height: 100%;
overflow-y: auto; /* 仅树组件自身滚动,唯一滚动点 */
overflow-x: hidden; /* 禁用横向滚动 */
.tree-tooltip {
display: inline-block;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
//
.el-tree-node.is-disabled {
color: #ccc !important;
.el-checkbox {
pointer-events: none;
.el-checkbox__inner {
background-color: #f5f7fa !important;
border-color: #e4e7ed !important;
color: #c0c4cc !important;
cursor: not-allowed !important;
}
.el-checkbox__label {
color: #ccc !important;
cursor: not-allowed !important;
}
}
}
.el-tree-node.is-disabled .el-checkbox {
display: inline-block !important;
cursor: not-allowed !important;
}
}
}
// - 100%
.sample-image-panel {
flex: 1; /* 占满剩余宽度 */
height: 100%; /* 占满主容器高度 */
box-sizing: border-box;
padding-bottom: 0; /* 修复2删掉面板底部内边距避免冗余间距 */
display: flex;
flex-direction: column; /* 弹性列布局:头部+图片滚动+分页固定 */
overflow: hidden; /* 禁用面板自身滚动,核心 */
.select-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 15px;
padding-bottom: 8px;
border-bottom: 1px solid #ebeef5;
flex-shrink: 0; /* 头部固定,不收缩 */
}
.select-count {
font-size: 12px;
color: #666;
}
.image-empty-tip {
text-align: center;
padding: 50px;
color: #999;
font-size: 14px;
flex: 1; /* 占满剩余高度,居中 */
display: flex;
align-items: center;
justify-content: center;
}
.image-grid {
display: flex;
flex-wrap: wrap;
gap: 15px;
min-height: 100px;
flex: 1; /* 弹性占满头部和分页之间的所有高度 */
overflow-y: auto; /* 仅图片网格自身滚动,唯一滚动点 */
overflow-x: hidden; /* 禁用横向滚动 */
padding-right: 6px; /* 为滚动条预留空间,避免内容遮挡 */
box-sizing: border-box;
}
.image-item {
width: calc(33.33% - 10px);
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 8px;
transition: all 0.2s;
box-sizing: border-box;
display: flex;
flex-direction: column;
height: 280px;
&:hover {
border-color: #1f72ea;
box-shadow: 0 2px 8px rgba(31, 114, 234, 0.1);
}
.image-wrapper {
flex: 1;
margin-bottom: 8px;
overflow: hidden;
border-radius: 4px;
.sample-image {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.image-footer {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 15px;
padding-bottom: 8px;
border-bottom: 1px solid #ebeef5;
font-size: 12px;
color: #666;
height: 30px;
.select-count {
font-size: 12px;
color: #666;
.image-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 60%;
}
}
.image-empty-tip {
text-align: center;
padding: 50px;
color: #999;
font-size: 14px;
}
.image-grid {
display: flex;
flex-wrap: wrap;
gap: 15px;
min-height: 100px;
.image-item {
width: calc(33.33% - 10px);
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 8px;
transition: all 0.2s;
box-sizing: border-box;
.image-actions {
display: flex;
flex-direction: column;
height: 280px;
align-items: center;
gap: 8px;
&:hover {
border-color: #1f72ea;
box-shadow: 0 2px 8px rgba(31, 114, 234, 0.1);
}
.preview-btn {
padding: 0;
margin: 0;
color: #1f72ea;
.image-wrapper {
flex: 1;
margin-bottom: 8px;
overflow: hidden;
border-radius: 4px;
.sample-image {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.image-footer {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 12px;
color: #666;
height: 30px;
.image-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 60%;
}
.image-actions {
display: flex;
align-items: center;
gap: 8px;
.preview-btn {
padding: 0;
margin: 0;
color: #1f72ea;
&:hover {
color: #0e5bc8;
}
}
&:hover {
color: #0e5bc8;
}
}
}
}
}
//
::v-deep .el-pagination {
flex-shrink: 0; /* 分页固定,不收缩 */
margin-top: 15px !important;
text-align: right;
//
border: none !important;
}
}
// -
.dialog-footer {
text-align: right;
padding-top: 10px;
flex-shrink: 0; /* 按钮区固定,不收缩 */
border-top: 1px solid #ebeef5; /* 唯一保留的分隔线 */
margin-top: 10px; /* 微调间距,让分隔线和分页的距离更美观 */
border-bottom: none !important; /* 兜底:确保无底部边框 */
}
/* 全屏预览样式 */
@ -983,6 +1039,7 @@ export default {
justify-content: center;
padding: 20px;
box-sizing: border-box;
overflow: hidden; /* 禁用预览弹窗滚动 */
}
.full-screen-image-content {

View File

@ -37,9 +37,9 @@
type="text"
v-hasPermi="['data:dataset:version:add']"
class="action-btn version-add-btn"
@click="handleVersionAdd(data)"
@click="handleExport(data)"
>
新增版本
导出
</el-button>
<el-button
type="text"
@ -60,6 +60,7 @@ import TableModel from '@/components/TableModel2'
import { columnsList, formLabel } from './config'
import { datasetVersionListAPI, delDatasetVersionDataAPI } from '@/api/data/dataset'
import { decryptWithSM4, encryptWithSM4 } from '@/utils/sm'
import {exportImagesAPI} from "@/api/data/labeling";
export default {
name: 'DatasetVersionList', //
@ -181,8 +182,41 @@ export default {
this.queryVersionList()
},
handleExport(row) {
this.$message.info(`正在导出数据集:${row.datasetName}`)
/** 导出操作 */
async handleExport(row) {
const datasetVersionId = row.datasetVersionId
//
try {
let blob, fileName;
blob = await exportImagesAPI({ datasetVersionId });
fileName = `数据集图片.zip`;
//
this.downloadFile(blob, fileName);
this.$message.success('导出成功,正在下载...');
} catch (error) {
// blob
console.error('导出失败:', error);
if (error.response?.data instanceof Blob) {
const text = await new Response(error.response.data).text();
const res = JSON.parse(text);
this.$message.error(res.msg || '导出失败,无可用数据');
} else {
this.$message.error(error.msg || '导出失败,请稍后重试');
}
}
},
downloadFile(blob, fileName) {
//
const url = window.URL.createObjectURL(new Blob([blob]));
const a = document.createElement('a');
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
// URL
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
},
handleQuery() {

View File

@ -57,7 +57,11 @@ export default {
watch: {
isAdd: {
handler(newVal) {
if (newVal === CONSTANT_PARAMS.type) {
console.log('newVal',newVal)
console.log('123',CONSTANT_PARAMS.type)
if (newVal === CONSTANT_PARAMS.type) {
console.log('1231')
this.initFormData();
}else{
this.form.labelGroupName = this.rowData.labelGroupName
@ -75,6 +79,7 @@ export default {
methods: {
/** 初始化表单数据 */
initFormData() {
console.log('初始化表单数据:', this.rowData);
if (this.rowData) {
//
this.form = {
@ -229,4 +234,4 @@ export default {
::v-deep .el-dialog__footer {
text-align: center;
}
</style>
</style>

View File

@ -29,7 +29,7 @@
<script>
import TableModel from '@/components/TableModel2'
import { columnsList, formLabel } from './config'
import { labelListAPI, delLabelAPI } from '@/api/data/label'
import { labelListAPI, delDataAPI } from '@/api/data/label'
import LabelForm from './LabelForm.vue'
export default {
@ -101,8 +101,9 @@ export default {
/** 修改操作 */
handleUpdate(row) {
this.rowData = {
labelGroupName: this.activeCategoryName,
...row
...row,
labelGroupName: this.activeCategoryName
}
this.title = '编辑标签'
this.isAdd = 'edit'
@ -126,7 +127,7 @@ export default {
dangerouslyUseHTMLString: true,
customClass: 'delete-confirm-dialog'
}).then(() => {
delLabelAPI(
delDataAPI(
{
labelId: raw.labelId
}

View File

@ -15,8 +15,8 @@
<!-- 保留缩放/全屏/重置 -->
<el-button icon="el-icon-zoom-in" @click="zoomIn" title="放大"></el-button>
<el-button icon="el-icon-zoom-out" @click="zoomOut" title="缩小"></el-button>
<el-button icon="el-icon-full-screen" @click="toggleFullScreen" title="页面内全屏" :class="{ active: isFullScreen }"></el-button>
<el-button icon="el-icon-refresh" @click="resetZoom" title="重置视图"></el-button>
<!-- <el-button icon="el-icon-full-screen" @click="toggleFullScreen" title="页面内全屏" :class="{ active: isFullScreen }"></el-button>-->
<!-- <el-button icon="el-icon-refresh" @click="resetZoom" title="重置视图"></el-button>-->
</el-button-group>
</div>
@ -460,16 +460,16 @@ export default {
//
async switchImage(index) {
//
if (this.selectedLabelIds.length > 0 || this.isInvalidData) {
try {
await this.saveLabel() //
} catch (err) {
const confirm = await this.$confirm('当前图片分类未保存,是否放弃修改并切换?', '提示', {
type: 'warning'
})
if (!confirm) return
}
}
// if (this.selectedLabelIds.length > 0 || this.isInvalidData) {
// try {
// await this.saveLabel() //
// } catch (err) {
// const confirm = await this.$confirm('', '', {
// type: 'warning'
// })
// if (!confirm) return
// }
// }
// ****
this.currentImageIndex = index
@ -536,6 +536,16 @@ export default {
if (res.code === 200) {
this.$message.success('分类保存成功!');
this.saveCurrentLabelData(); //
// ===== =====
await this.loadTreeData(); //
//
this.currentImageIndex = Math.min(this.currentImageIndex, this.imageList.length - 1);
//
this.currentImage = this.imageList[this.currentImageIndex] || {};
//
this.selectedLabelIds = this.currentImage.selectedLabelIds || [];
this.isInvalidData = this.currentImage.isInvalid || false;
// ===== =====
return Promise.resolve(res);
} else {
const errorMsg = '保存失败:' + (res.msg || '接口返回异常');

View File

@ -74,9 +74,9 @@
<div
class="label-item"
v-for="(label, labelIndex) in group.labels"
:key="label.id || labelIndex"
:key="label.labelId || labelIndex"
>
<span class="label-name">#{{ labelIndex + 1 }} {{ label.name }} </span>
<span class="label-name">#{{ labelIndex + 1 }} {{ label.labelName }} </span>
<span class="shape-count" v-if="label.shapeIds && label.shapeIds.length">
({{ label.shapeIds.length }}个标注)
</span>
@ -109,6 +109,23 @@ export default {
name: 'LabelingDetail',
data() {
return {
//
labelDrawConfig: {
baseLineWidth: 3,
minLineWidth: 1,
maxLineWidth: 6,
textFontSize: 16,
textPadding: 3,
shapeMainColor: {
rect: '#00ff00', //
polygon: '#0099ff', //
circle: '#ff9900' //
},
selectedColor: '#ff0000', // /
bgOpacity: 0.4, //
shapeFillOpacity: 0.4, //
textColor: "#ffffff"
},
shapeColorMap: {
rect: '#00ff00', // - 绿
polygon: '#0099ff', // -
@ -157,6 +174,58 @@ export default {
return isNaN(num) ? '' : num;
},
// 116RGBA#fff/#ffffff
hexToRgba(hex, opacity) {
const cleanHex = hex.replace('#', '');
const fullHex = cleanHex.length === 3
? cleanHex.split('').map(c => c + c).join('')
: cleanHex;
const r = parseInt(fullHex.substring(0, 2), 16);
const g = parseInt(fullHex.substring(2, 4), 16);
const b = parseInt(fullHex.substring(4, 6), 16);
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
},
// 216>0.5
isLightColor(hex) {
const cleanHex = hex.replace('#', '');
const fullHex = cleanHex.length === 3
? cleanHex.split('').map(c => c + c).join('')
: cleanHex;
const r = parseInt(fullHex.substring(0, 2), 16) / 255;
const g = parseInt(fullHex.substring(2, 4), 16) / 255;
const b = parseInt(fullHex.substring(4, 6), 16) / 255;
//
const luminance = (0.299 * r + 0.587 * g + 0.114 * b);
return luminance > 0.5;
},
// 3线
getDynamicLineWidth() {
const { baseLineWidth, minLineWidth, maxLineWidth } = this.labelDrawConfig
// 线/
const dynamicWidth = baseLineWidth / this.zoomScale
return Math.max(minLineWidth, Math.min(maxLineWidth, dynamicWidth))
},
// 4
getDynamicFontSize() {
const { textFontSize } = this.labelDrawConfig
//
const dynamicSize = textFontSize / this.zoomScale
return Math.max(10, Math.min(24, dynamicSize)) // /
},
// ID
findLabelById(labelId) {
for (const group of this.labelGroups) {
for (const label of group.labels) {
if (label.labelId === labelId) return label
}
}
return null
},
//
async loadLabelTreeData() {
try {
@ -226,7 +295,7 @@ export default {
}
// ID
const shapeId = `shape_${item.labelingSampleId}_${item.labelId}_${Date.now()}`
const shapeId = `shape_${item.labelingSampleId}_${item.id || Date.now()}`
//
const shape = {
@ -357,7 +426,8 @@ export default {
} else if (window.AILabel) {
instance = window.AILabel({ el: container, image: image })
} else {
instance = this.createSimpleLabelTool(container, image)
//
instance = this.createSimpleLabelTool(container, image, this)
}
if (instance) {
@ -383,21 +453,21 @@ export default {
}
},
//
createSimpleLabelTool(container, image) {
//
createSimpleLabelTool(container, image, parentInstance) {
const tool = {
el: container,
image,
shapes: [],
selectedShape: null,
mainCanvas: null,
tempCanvas: null,
mainCtx: null,
tempCtx: null,
zoomScale: 1.0,
//
parent: parentInstance,
init() {
//
// +
const mainCanvas = document.createElement('canvas')
mainCanvas.style.cssText = 'position:absolute;top:0;left:0;pointer-events:none;z-index:10;width:100%;height:100%;'
mainCanvas.width = container.offsetWidth
@ -422,15 +492,6 @@ export default {
this.redrawMainCanvas()
},
//
drawTempShape() {},
//
cancelDrawing() {},
//
setDrawType() {},
//
setScale(scale) {
this.zoomScale = scale
@ -470,12 +531,12 @@ export default {
return [...this.shapes]
},
//
// +
redrawMainCanvas() {
this.mainCtx.clearRect(0, 0, this.mainCanvas.width, this.mainCanvas.height)
this.mainCtx.save()
//
// +
this.mainCtx.translate(
this.el.offsetWidth / 2,
this.el.offsetHeight / 2
@ -486,7 +547,7 @@ export default {
-this.image.naturalWidth / 2,
-this.image.naturalHeight / 2
)
//
//
this.mainCtx.drawImage(
this.image,
0, 0,
@ -495,24 +556,34 @@ export default {
)
}
//
//
const dynamicLineWidth = this.parent.getDynamicLineWidth()
const dynamicFontSize = this.parent.getDynamicFontSize()
const { textPadding, bgOpacity, shapeFillOpacity, shapeMainColor } = this.parent.labelDrawConfig
// +
this.shapes.forEach(s => {
// 1.
const shapeColor = s.color || shapeMainColor[s.type] || '#000000'
const labelId = s.labelId || ''
const label = this.parent.findLabelById(labelId)
// 2.
this.mainCtx.lineWidth = dynamicLineWidth
this.mainCtx.strokeStyle = shapeColor
this.mainCtx.fillStyle = this.parent.hexToRgba(shapeColor, shapeFillOpacity)
// 3.
if (s.type === 'rect') {
this.mainCtx.strokeStyle = s.color || '#00ff00'
this.mainCtx.fillStyle = 'rgba(0,255,0,0.1)'
this.mainCtx.strokeRect(s.x, s.y, s.width, s.height)
this.mainCtx.fillRect(s.x, s.y, s.width, s.height)
} else if (s.type === 'circle') {
this.mainCtx.strokeStyle = s.color || '#ff9900'
this.mainCtx.fillStyle = 'rgba(255,153,0,0.1)'
this.mainCtx.beginPath()
this.mainCtx.arc(s.x, s.y, s.radius, 0, Math.PI * 2)
this.mainCtx.closePath()
this.mainCtx.fill()
this.mainCtx.stroke()
} else if (s.type === 'polygon') {
this.mainCtx.strokeStyle = s.color || '#0099ff'
this.mainCtx.fillStyle = 'rgba(0,153,255,0.1)'
this.mainCtx.beginPath()
s.points.forEach((point, idx) => {
idx === 0 ? this.mainCtx.moveTo(point.x, point.y) : this.mainCtx.lineTo(point.x, point.y)
@ -521,15 +592,53 @@ export default {
this.mainCtx.fill()
this.mainCtx.stroke()
}
// 4.
if (!label) return
//
let textX, textY
if (s.type === 'rect') {
textX = s.x + s.width / 2
textY = s.y + s.height / 2
} else if (s.type === 'circle') {
textX = s.x
textY = s.y
} else if (s.type === 'polygon') {
const totalX = s.points.reduce((sum, p) => sum + p.x, 0)
const totalY = s.points.reduce((sum, p) => sum + p.y, 0)
textX = totalX / s.points.length
textY = totalY / s.points.length
}
// 5.
this.mainCtx.font = `${dynamicFontSize}px Microsoft YaHei`
this.mainCtx.textAlign = 'center' //
this.mainCtx.textBaseline = 'middle' //
// 6.
const bgRgba = this.parent.hexToRgba(shapeColor, bgOpacity)
const textColor = this.parent.isLightColor(shapeColor) ? '#000000' : '#ffffff'
// 7.
const textWidth = this.mainCtx.measureText(label.labelName).width
const textHeight = dynamicFontSize
this.mainCtx.fillStyle = bgRgba
this.mainCtx.fillRect(
textX - (textWidth + 2 * textPadding) / 2,
textY - (textHeight + 2 * textPadding) / 2,
textWidth + 2 * textPadding,
textHeight + 2 * textPadding
)
// 8.
this.mainCtx.fillStyle = textColor
this.mainCtx.fillText(label.labelName, textX, textY)
})
this.mainCtx.restore()
},
//
on() {},
//
// labelId/color
setShapeData(id, data) {
const shape = this.shapes.find(s => s.id === id)
if (shape) Object.assign(shape, data)
@ -559,7 +668,7 @@ export default {
}
},
//
// labelId
loadLabelData() {
if (!this.ailabelInstance || !this.currentImage) return
@ -579,6 +688,7 @@ export default {
shapes.forEach(shapeData => {
try {
let shapeId = ''
//
if (shapeData.type === 'rect' && shapeData.x !== undefined) {
shapeId = this.ailabelInstance.drawRect?.(shapeData.x, shapeData.y, shapeData.width, shapeData.height) || shapeData.id
} else if (shapeData.type === 'polygon' && shapeData.points && shapeData.points.length >= 3) {
@ -587,10 +697,11 @@ export default {
shapeId = this.ailabelInstance.drawCircle?.(shapeData.x, shapeData.y, shapeData.radius) || shapeData.id
}
//
// labelIdcolor
if (shapeId) {
this.ailabelInstance.setShapeData?.(shapeId, {
color: shapeData.color || this.shapeColorMap[shapeData.type]
labelId: shapeData.labelId, // ID
color: shapeData.color || this.shapeColorMap[shapeData.type] //
})
}
} catch (e) {
@ -663,7 +774,6 @@ export default {
handleShapeDeleted() {},
selectLabel() {},
findShapeIdsByLabelId() { return [] },
findLabelById() { return null },
removeLabelById() {},
confirmAddLabel() {},
deleteSelected() {},

View File

@ -19,7 +19,7 @@
<el-button plain size="mini" type="success" icon="el-icon-download" v-hasPermi="['system:user:import']"
@click="handleModelExport">
导入模板
模板下载
</el-button>
<el-button plain size="mini" type="warning" icon="el-icon-upload2" v-hasPermi="['system:user:import']"