This commit is contained in:
lizhenhua 2024-12-01 18:13:04 +08:00
parent 578b0f7b8d
commit 27534f574c
4 changed files with 790 additions and 1 deletions

View File

@ -23,6 +23,13 @@ export function participant(query) {
params: query
})
}
export function agreement(query) {
return request({
url: '/ai/annotationTask/audit',
method: 'post',
data: query
})
}
//创建数据集
export function add(data) {

View File

@ -0,0 +1,68 @@
import request from '@/utils/request'
//查询数据集
export function list(query) {
return request({
url: '/ai/annotationTask/list/all',
method: 'get',
params: query
})
}
export function creation(query) {
return request({
url: '/ai/annotationTask/list/creation',
method: 'get',
params: query
})
}
export function participant(query) {
return request({
url: '/ai/annotationTask/list/participant',
method: 'get',
params: query
})
}
//创建数据集
export function add(data) {
return request({
url: '/ai/annotationTask/create',
method: 'post',
data: data
})
}
// 获取标注任务文件详情
export function getAnnotationDetails(data) {
return request({
url: '/ai/annotationTask/create',
method: 'post',
data: data
})
}
export function getMyNoAnnotatedTask(data) {
return request({
url: '/ai/annotationTask/getMyNoAuditedTask',
method: 'get',
params: data
})
}
export function getMyAuditFiles(annotationStatus,taskId) {
return request({
url: '/ai/annotationTask/getMyAuditFiles/'+annotationStatus+'/'+taskId,
method: 'get',
})
}
export function manualAnnotate(data) {
return request({
url: '/ai/annotationTask/manualAnnotate',
method: 'post',
data:data
})
}

View File

@ -0,0 +1,257 @@
<template>
<div class="label-studio-annotator">
<div id="label-studio" class="annotation-container"></div>
<div class="button-container">
<button class="agree-button" @click="agreement(1)">同意</button>
<button class="disagree-button" @click="showDisagreement = true">不同意</button>
</div>
<!-- 弹出层 -->
<div v-if="showDisagreement" class="layui-layer">
<div class="layui-layer-content">
<h2><span STYLE="color: red">*</span>填写说明</h2>
<textarea v-model="disagreementReason" placeholder="填写不同意的原因"></textarea>
<div class="layui-layer-buttons">
<button class="layui-btn layui-btn-primary" @click="sendDisagreement">提交</button>
<button class="layui-btn layui-btn-danger" @click="closeDisagreementModal">取消</button>
</div>
</div>
</div>
</div>
</template>
<script>
import LabelStudio from 'label-studio';
import 'label-studio/build/static/css/main.css';
import {agreement, manualAnnotate } from '../../../../api/dataCenter/annotationTask';
export default {
name: 'LabelStudioAnnotator',
props: {
fileUrl: { type: String, required: true },
taskId: { type: Number, required: true },
config: { type: String, required: true },
id: { type: Number, required: true },
itemIndex: { type: Number, required: true },
annotations: {
type: Array,
default: () => [] //
}
},
computed: {
index: {
get() {
return this.itemIndex;
},
set(value) {
this.$parent.updateItemIndex(value); //
}
}
},
data() {
return {
labelStudio: null,
annotationsList: [] ,
showDisagreement: false, //
disagreementReason: '' //
};
},
watch: {
fileUrl: 'resetLabelStudio' // LabelStudio
},
mounted() {
this.initLabelStudio(); // LabelStudio
},
methods: {
//
closeDisagreementModal() {
this.showDisagreement = false;
this.disagreementReason = ''; //
},
//
sendDisagreement() {
if(this.disagreementReason === ""){
this.$modal.msgSuccess('说明原因不能为空');
return;
}
agreement({
taskId: Number(this.taskId),
fileId: Number(this.id),
auditFailedReason: this.disagreementReason
}).then(response => {
this.$modal.msgSuccess('已提交不同意原因');
this.closeDisagreementModal(); //
}).catch(error => {
console.log(error);
});
},
//
getAnnotations() {
return [{
result: this.annotations.map(annotation => ({
type: "rectanglelabels",
from_name: "label",
to_name: "image",
value: {
rectanglelabels: [annotation.label],
x: annotation.x,
y: annotation.y,
width: annotation.width,
height: annotation.height
}
}))
}];
},
//
disagreement(){
},
//
agreement(){
agreement({
taskId : Number(this.taskId),
fileId : Number(this.id)
}).then(response => {
this.$modal.msgSuccess('已同意标注')
}).catch(error => {
console.log(error);
});
},
// LabelStudio
initLabelStudio() {
this.cleanupLabelStudio(); //
const task = {
id: this.id,
data: { image: this.fileUrl },
};
if (this.annotations.length) {
task.annotations = this.getAnnotations();
}
this.$nextTick(() => {
const labelStudioElement = document.getElementById('label-studio');
if (!labelStudioElement) {
console.error('Label Studio element not found');
return;
}
this.labelStudio = new LabelStudio('label-studio', {
config: this.config,
interfaces: [],
user: { pk: 1, firstName: '标注者', lastName: '用户' },
task,
onLabelStudioLoad: (LS) => {
if (!LS.annotationStore.selectedAnnotation) {
const annotation = LS.annotationStore.addAnnotation({ userGenerate: true });
LS.annotationStore.selectAnnotation(annotation.id);
}
},
});
});
},
// LabelStudio
resetLabelStudio() {
this.cleanupLabelStudio();
this.initLabelStudio(); //
},
// LabelStudio
cleanupLabelStudio() {
if (this.labelStudio && this.labelStudio.destroy) {
this.labelStudio.destroy();
}
const labelStudioElement = document.getElementById('label-studio');
if (labelStudioElement) {
labelStudioElement.innerHTML = ''; //
}
this.labelStudio = null; //
}
}
};
</script>
<style scoped>
.label-studio-annotator {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
}
.annotation-container {
flex-direction: column;
width: 100%;
height: 100%;
min-height: 500px; /* 设置最小高度 */
}
.button-container {
position: absolute;
float: right;
justify-content: center;
margin-left: 28%;
top: 53%;
}
.agree-button, .disagree-button {
padding: 10px 20px;
margin: 0 5px;
border: none;
border-radius: 5px;
cursor: pointer;
}
.agree-button {
background-color: #4CAF50;
color: white;
}
.disagree-button {
background-color: #F44336;
color: white;
}
/* 弹出层样式 */
.layui-layer {
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5); /* 半透明背景 */
display: flex;
justify-content: center;
align-items: center;
}
.layui-layer-content {
background-color: #fff;
padding: 20px;
border-radius: 5px;
width: 400px; /* 宽度 */
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.layui-layer-buttons {
display: flex;
justify-content: space-between;
margin-top: 10px;
}
.layui-btn {
padding: 10px 15px;
border: none;
border-radius: 5px;
cursor: pointer;
}
.layui-btn-primary {
background-color: #4CAF50;
color: white;
}
.layui-btn-danger {
background-color: #F44336;
color: white;
}
</style>

View File

@ -1,9 +1,466 @@
<template>
<div class="div-main">
<!-- 顶部区域 -->
<div class="top-part">
<div class="top-content">
<!-- 右侧选择框和设置按钮 -->
<div class="top-content-right">
<el-select
v-model="taskId"
placeholder="请选择审核任务"
clearable
class="select-task"
@change="selectTask"
>
<el-option
v-for="dict in taskList"
:key="dict.taskId"
:label="dict.taskName"
:value="dict.taskId"
/>
</el-select>
<!-- <span class="settings-icon">
<i class="el-icon-setting"></i>
</span>-->
</div>
</div>
</div>
<!-- 底部区域 -->
<div class="bottom-part">
<div class="bottom-content">
<div class="bottom-content-left">
<div class="bottom-content-left-top">
<div>
<el-button
type="primary"
:class="{'is-selected': annotationType===0}"
plain
size="mini"
@click="toggleSelected(0)"
>全部
</el-button>
<el-button
type="primary"
:class="{'is-selected': annotationType===1}"
plain
size="mini"
@click="toggleSelected(1)"
>已审核
</el-button>
<el-button
type="primary"
:class="{'is-selected': annotationType===2}"
plain
size="mini"
@click="toggleSelected(2)"
>未审核
</el-button>
<el-button
type="primary"
:class="{'is-selected': annotationType===3}"
plain
size="mini"
@click="toggleSelected(3)"
>审核驳回
</el-button>
</div>
</div>
<div class="bottom-content-left-bottom">
<div>
<ul>
<li v-for="(item, index) in images" :key="item.fileId" class="list-item">
<div @click="setItem(item, index)">
<input type="checkbox" :checked="item.fileAnnotationStatus==='1'" disabled>
<span :class="{'highlighted': itemIndex === index}" style="font-size: 14px; margin-left: 5px;"
>{{ item.fileName }}</span>
</div>
</li>
</ul>
</div>
</div>
</div>
<div class="bottom-content-center">
<div>
<custom-label-studio :annotations="annotationResult" :taskId="taskId" :item-index="itemIndex"
:config="labelConfig" :id="task.id" :file-url="task.data.image"
@update-itemIndex="updateItemIndex"
></custom-label-studio>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
<script>
import customLabelStudio from './examLabelStudio.vue'
import { getMyNoAnnotatedTask, getMyAuditFiles } from '../../../../api/dataCenter/examine'
export default {
dicts: ['ai_annotate_type'],
components: { customLabelStudio },
data() {
return {
itemIndex: 0,
annotationResult: [],
item: {},
taskList: [],
task: {
id: 0,
data: {
image: ''
}
},
labelConfig: `
<View>
<Image name="image" value="$image"/>
<RectangleLabels name="label" toName="image">
</RectangleLabels>
</View>
`,
taskId: undefined, // ID
leftIcons: [
'el-icon-circle-plus-outline',
'el-icon-remove-outline',
'el-icon-search',
'el-icon-search',
'el-icon-search',
'el-icon-search'
], //
count: 600,
annotationType: 0,
images: []
}
},
created() {
this.loadTaskList(); //
},
methods: {
//
updateItemIndex(val) {
const item = this.images[val];
this.images[val - 1].fileAnnotationStatus = '1';
this.itemIndex = val;
this.updateTaskData(item);
},
//
loadTaskList() {
getMyNoAnnotatedTask().then(res => {
this.taskList = res.data;
});
},
// ID
selectTask(id) {
this.resetImages();
this.taskId = id;
this.fetchImages();
},
//
toggleSelected(type) {
if (this.annotationType === type) return;
this.annotationType = type;
this.resetImages();
this.fetchImages();
},
//
setItem(item, index) {
this.annotationResult = JSON.parse(item.annotationResult);
this.itemIndex = index;
this.updateTaskData(item);
},
//
resetImages() {
this.images = [];
//this.annotationType = 0;
},
// ID
fetchImages() {
if (!this.taskId) return;
getMyAuditFiles(this.annotationType, this.taskId).then(response => {
//alert(JSON.stringify(response))
this.itemIndex = 0;
this.images = response.data;
this.updateTaskData(
this.images[this.itemIndex]);
});
},
//
updateTaskData(item) {
if (item !== undefined){
this.task.data.image = `http://192.168.0.14:9090/bonus/${item.fileUrl}`;
this.task.id = item.fileId;
this.annotationResult = JSON.parse(item.annotationResult);
}
}
}
}
</script>
<style scoped lang="scss">
/* 默认按钮样式 */
.el-button {
transition: all 0.3s ease;
}
/* 选中时的样式 */
.el-button.is-selected {
background-color: #007bff !important; /* 设置选中背景色 */
border-color: #007bff !important; /* 设置选中边框色 */
color: white !important; /* 设置字体颜色 */
}
.div-main {
width: 100%;
height: calc(100vh - 84px);
/* 顶部样式 */
.top-part {
width: 100%;
height: 60px;
padding: 10px;
.top-content {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
width: 100%;
padding-left: 10px;
padding-right: 10px;
background-color: #f5f7fa;
border: 1px solid #dfe4ed;
.top-content-left {
display: flex;
align-items: center;
gap: 10px; /* 按钮之间的间距 */
}
.top-content-right {
display: flex;
align-items: center;
gap: 10px;
.select-task {
flex-shrink: 0; /* 防止选择框缩小 */
}
.settings-icon {
font-size: 24px;
display: flex;
align-items: center;
}
}
}
}
/* 底部样式 */
.bottom-part {
width: 100%;
height: calc(100% - 60px);
padding-bottom: 10px;
padding-left: 10px;
padding-right: 10px;
.bottom-content {
display: flex;
align-items: center;
height: 100%;
width: 100%;
background-color: #ffffff;
border: 1px solid #dfe4ed;
.bottom-content-left,
.bottom-content-center,
.bottom-content-right {
height: 100%;
}
.bottom-content-left {
width: 20%;
.bottom-content-left-bottom,
.bottom-content-left-top {
width: 100%;
padding: 5px;
}
.bottom-content-left-top {
height: 50px;
div {
width: 100%;
height: 100%;
background-color: #f5f7fa;
border: 1px solid #dfe4ed;
display: flex;
align-items: center;
justify-content: space-evenly;
}
}
.bottom-content-left-bottom {
height: calc(100% - 50px);
> div {
width: 100%;
height: 100%;
background-color: #f5f7fa;
border: 1px solid #dfe4ed;
display: flex;
align-items: center;
justify-content: center;
ul {
width: 100%;
height: 100%;
overflow: auto;
padding: 0;
list-style: none; /* 移除默认的点样式 */
li {
width: 100%;
padding: 1px;
background-color: #FFFFFF;
div {
height: 40px;
padding-left: 2px;
display: flex;
cursor: pointer; /* 手势效果 */
align-items: center;
background-color: #f5f7fa;
}
input[type="checkbox"] {
appearance: none; /* 隐藏默认复选框样式 */
width: 15px;
height: 15px;
border: 1px solid #4d4e51;
border-radius: 4px; /* 圆角方框 */
cursor: pointer;
}
input[type="checkbox"]:checked::after {
content: '✔'; /* 显示勾符号 */
color: white;
font-size: 10px;
display: flex;
align-items: center;
justify-content: center;
}
input[type="checkbox"]:checked {
background-color: rgb(64, 158, 255); /* 改变选中颜色 */
border-color: rgb(64, 158, 255);
}
}
}
/* 针对自定义滚动条样式 */
ul::-webkit-scrollbar {
cursor: default; /* 对滚动条自定义光标 */
width: 5px;
}
ul::-webkit-scrollbar-thumb {
background-color: #888;
cursor: pointer; /* 手势效果 */
}
ul::-webkit-scrollbar-thumb:hover {
background-color: #555;
}
}
}
}
.bottom-content-center {
width: 80%;
padding: 5px;
div {
width: 100%;
height: 100%;
background-color: #f5f7fa;
border: 1px solid #dfe4ed;
}
}
.bottom-content-right {
width: 20%;
padding: 5px;
.bottom-content-right-center {
width: 100%;
height: 100%;
background-color: #f5f7fa;
border: 1px solid #dfe4ed;
display: flex;
align-items: center;
flex-direction: column;
.bottom-content-right-top {
width: 100%;
height: calc(50% - 25px);
padding: 5px;
div {
width: 100%;
height: 100%;
background-color: #f5f7fa;
border: 1px solid #dfe4ed;
}
}
.bottom-content-right-middle {
width: 100%;
height: calc(50% - 25px);
padding: 5px;
div {
width: 100%;
height: 100%;
background-color: #f5f7fa;
border: 1px solid #dfe4ed;
}
}
.bottom-content-right-bottom {
width: 100%;
height: 50px;
padding: 5px;
div {
width: 100%;
height: 100%;
background-color: #f5f7fa;
border: 1px solid #dfe4ed;
display: flex;
align-items: center;
justify-content: space-evenly;
}
}
}
}
}
}
}
.highlighted {
color: rgb(64, 158, 255); /* 选中项文本颜色 */
font-size: 16px; /* 选中项字体大小 */
}
</style>