|
@@ -0,0 +1,756 @@
|
|
|
+<template>
|
|
|
+ <div class="app-container">
|
|
|
+ <div ref="box" class="markPage">
|
|
|
+ <div class="left">
|
|
|
+ <div class="table">
|
|
|
+ <el-table ref="table" :data="dataList" style="width: 100%" row-key="id" highlight-current-row @current-change="handleCurrentChange">
|
|
|
+ <el-table-column show-overflow-tooltip prop="text" align="center" label="标注文本" />
|
|
|
+ <el-table-column prop="state" align="center" label="状态" width="70">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-tag size="mini" :type="scope.row.state?'success':'danger'">{{ scope.row.state?'已标注':'未标注' }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ <div class="pagination">
|
|
|
+ <pagination
|
|
|
+ v-show="total > 0"
|
|
|
+ :layout="layout"
|
|
|
+ :total="total"
|
|
|
+ :page.sync="queryParams.page"
|
|
|
+ :limit.sync="queryParams.size"
|
|
|
+ :background="false"
|
|
|
+ @pagination="getList"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="content">
|
|
|
+ <el-card class="box-card main">
|
|
|
+ <div id="text-container">{{ markData.text }}</div>
|
|
|
+ <el-cascader-panel
|
|
|
+ v-if="tagInfo.visible"
|
|
|
+ ref="tagLabel"
|
|
|
+ :class="['tag-box']"
|
|
|
+ :options="tagLabelTreeList"
|
|
|
+ :style="{ top: tagInfo.top + 'px', left: tagInfo.left + 'px' }"
|
|
|
+ :props="{ label: 'name', value: 'entityId', expandTrigger: 'hover' }"
|
|
|
+ @change="handleSelect"
|
|
|
+ />
|
|
|
+ <!-- :style="{ top: cancelTagInfo.top + 'px', left: cancelTagInfo.left + 'px' }" -->
|
|
|
+ <div
|
|
|
+ v-if="cancelTagInfo.visible"
|
|
|
+ :class="['cancel-tag']"
|
|
|
+ :style="{ top: cancelTagInfo.top + 'px', left: cancelTagInfo.left + 'px' }"
|
|
|
+ @click="handleCancel"
|
|
|
+ >
|
|
|
+ 取 消
|
|
|
+ </div>
|
|
|
+ <header>
|
|
|
+ <el-button type="primary" size="mini" @click="clearMark()"> 清空标记 </el-button>
|
|
|
+ <el-button type="primary" size="mini" @click="handlePrev()"> 上一条 </el-button>
|
|
|
+ <!-- <el-button type="primary" size="mini" @click="addRelation"> 增加关系 </el-button> -->
|
|
|
+ <el-button type="primary" size="mini" @click="handleNext()"> 下一条 </el-button>
|
|
|
+ <el-button type="primary" size="mini" @click="handleSave()"> 保存 </el-button>
|
|
|
+ </header>
|
|
|
+ </el-card>
|
|
|
+ <div class="footer">
|
|
|
+ <div class="select">
|
|
|
+ <el-select v-model="labelQueryParams.parentId" size="mini" clearable placeholder="请选择" @change="changeParentLabel">
|
|
|
+ <el-option v-for="item in parentLabel" :key="item.entityId" :label="item.name" :value="item.entityId" />
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ <div class="tag">
|
|
|
+ <el-tag
|
|
|
+ v-for="label in childLabel"
|
|
|
+ :key="label.entityId"
|
|
|
+ :color="label.is_show ? label.color : '#f6f4f490'"
|
|
|
+ class="labelTag"
|
|
|
+ @click="handleTag(label)"
|
|
|
+ >
|
|
|
+ {{ label.name }}
|
|
|
+ <i v-if="!label.is_show" class="el-icon-circle-close closeIcon" />
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="right">
|
|
|
+ <h4>关系</h4>
|
|
|
+ <div v-if="markRelationList.length !== 0" class="relationResult">
|
|
|
+ <el-table :data="markRelationList" style="width: 100%" row-key="id">
|
|
|
+ <el-table-column prop="subject" align="center" label="主体">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <span style="margin-right: 8px;">{{ scope.row.subject }}</span>
|
|
|
+ <!-- <el-button icon="el-icon-plus" size="mini" circle /> -->
|
|
|
+ <i class="el-icon-circle-plus-outline addIcon" @click="addRelation(scope.row)" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="object" align="center" label="客体">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-select v-model="scope.row.object" clearable placeholder="请选择客体" size="mini" @change="((value)=>{handleObject(value, scope.row)})">
|
|
|
+ <el-option
|
|
|
+ v-for="item in markTextList"
|
|
|
+ :key="item.mark_id"
|
|
|
+ :label="item.mark_content"
|
|
|
+ :value="item.mark_content"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="relation" align="center" label="关系">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-select v-model="scope.row.relation" clearable placeholder="请选择关系" size="mini">
|
|
|
+ <el-option
|
|
|
+ v-for="item in relationOption"
|
|
|
+ :key="item.relationshipId"
|
|
|
+ :label="item.name"
|
|
|
+ :value="item.name"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column
|
|
|
+ fixed="right"
|
|
|
+ label="操作"
|
|
|
+ width="100"
|
|
|
+ >
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-button type="text" size="small" @click="handleDeleteRelation(scope.row)">删除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ <!-- <img v-else class="img" src="@/assets/images/none.png" alt="暂无标注结果" title="暂无标注结果"> -->
|
|
|
+ <el-empty v-else description="暂无标注结果" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { getDataListApi, PNDataApi } from '@/api/dataMark/dataManage'
|
|
|
+import { getEntityListApi, updataEntityApi, getChildListApi } from '@/api/dataMark/entity'
|
|
|
+import { getRelationshipListApi } from '@/api/dataMark/relationship'
|
|
|
+import { updataMarkInfoApi } from '@/api/dataMark/dataMark'
|
|
|
+import Mark from 'mark.js'
|
|
|
+import { v4 as uuidv4 } from 'uuid'
|
|
|
+export default {
|
|
|
+ name: 'MarkPage',
|
|
|
+ components: {},
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ queryParams: {
|
|
|
+ page: 1,
|
|
|
+ size: 10,
|
|
|
+ dataset_id: null,
|
|
|
+ id: null,
|
|
|
+ text: null,
|
|
|
+ is_mark: null,
|
|
|
+ state: null
|
|
|
+ },
|
|
|
+ labelQueryParams: {
|
|
|
+ parentId: null
|
|
|
+ },
|
|
|
+ layout: 'prev, pager, next',
|
|
|
+ // 总条数
|
|
|
+ total: 0,
|
|
|
+ // 表格List
|
|
|
+ dataList: [],
|
|
|
+ // 正在标注的文本信息
|
|
|
+ markData: {},
|
|
|
+ parentLabel: [],
|
|
|
+ childLabel: [],
|
|
|
+ selectParentLabel: null,
|
|
|
+ TAG_WIDTH: 208,
|
|
|
+ // 选择的文字以及文字位置
|
|
|
+ selectedText: {
|
|
|
+ start: 0,
|
|
|
+ end: 0,
|
|
|
+ content: ''
|
|
|
+ },
|
|
|
+ // 实体标签信息
|
|
|
+ tagInfo: {
|
|
|
+ visible: false,
|
|
|
+ top: 0,
|
|
|
+ left: 0
|
|
|
+ },
|
|
|
+ // 关系、取消标签
|
|
|
+ cancelTagInfo: {
|
|
|
+ visible: false,
|
|
|
+ top: 0,
|
|
|
+ left: 0
|
|
|
+ },
|
|
|
+ markContent: {},
|
|
|
+ tagLabelList: [],
|
|
|
+ tagLabelTreeList: [],
|
|
|
+ // 所有标注的list
|
|
|
+ markTextList: [],
|
|
|
+ relationOption: [],
|
|
|
+ markRelationList: [],
|
|
|
+ relationForm: {},
|
|
|
+ selectMarkedId: null,
|
|
|
+ editMarkItem: {},
|
|
|
+ strNum: null
|
|
|
+ }
|
|
|
+ },
|
|
|
+ created() {
|
|
|
+ this.queryParams.dataset_id = Number(this.$route.params.dataset_id)
|
|
|
+ this.queryParams.id = Number(this.$route.params.id)
|
|
|
+ this.getLabelList(true)
|
|
|
+ this.init()
|
|
|
+ // this.getList()
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ getList() {
|
|
|
+ this.loading = true
|
|
|
+ getDataListApi(this.queryParams).then((response) => {
|
|
|
+ this.dataList = response.data
|
|
|
+
|
|
|
+ if (!this.queryParams.id) {
|
|
|
+ const newData = this.dataList[0]
|
|
|
+ this.setNewData(newData)
|
|
|
+ } else {
|
|
|
+ const flag = this.dataList.findIndex(item => {
|
|
|
+ return item.id === Number(this.queryParams.id)
|
|
|
+ })
|
|
|
+ if (flag !== -1) {
|
|
|
+ const newData = this.dataList[flag]
|
|
|
+ this.setNewData(newData)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.total = response.total
|
|
|
+ this.loading = false
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取实体类和关系(是否重新获取List)
|
|
|
+ getLabelList(flag) {
|
|
|
+ const data = {}
|
|
|
+ getEntityListApi(data).then((res) => {
|
|
|
+ this.tagLabelTreeList = this.handleTree(res.data, 'entityId')
|
|
|
+ this.tagLabelList = res.data
|
|
|
+ // 获取标注文本
|
|
|
+ flag ? this.getList() : ''
|
|
|
+ this.parentLabel = []
|
|
|
+ this.childLabel = []
|
|
|
+ res.data.forEach((item) => {
|
|
|
+ if (item.parentId === 0) this.parentLabel.push(item)
|
|
|
+ else this.childLabel.push(item)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ getRelationshipListApi(data).then((response) => {
|
|
|
+ this.relationOption = response.data
|
|
|
+ this.loading = false
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 行高亮
|
|
|
+ tableRowClassName({ row }) {
|
|
|
+ if (row.id === Number(this.queryParams.id)) {
|
|
|
+ return 'warning-row'
|
|
|
+ }
|
|
|
+ return ''
|
|
|
+ },
|
|
|
+
|
|
|
+ // 表格单选
|
|
|
+ handleCurrentChange(val) {
|
|
|
+ // this.queryParams.id = val.id
|
|
|
+ // this.loading = true
|
|
|
+ document.getElementById('text-container').innerHTML = ''
|
|
|
+ const flag = this.dataList.findIndex(item => {
|
|
|
+ return item.id === val.id
|
|
|
+ })
|
|
|
+ if (flag !== -1) {
|
|
|
+ const newData = this.dataList[flag]
|
|
|
+ this.setNewData(newData)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ changeParentLabel() {
|
|
|
+ if (this.labelQueryParams.parentId) {
|
|
|
+ getChildListApi(this.labelQueryParams).then((res) => {
|
|
|
+ this.childLabel = []
|
|
|
+ this.childLabel = res.data
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ this.getLabelList(false)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 控制实体类的展示
|
|
|
+ handleTag(row) {
|
|
|
+ row.is_show = !row.is_show
|
|
|
+ updataEntityApi(row).then((res) => {
|
|
|
+ this.getLabelList(false)
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 给标注区域绑定鼠标事件
|
|
|
+ init() {
|
|
|
+ // 从后端获取标注数据,进行初始化标注
|
|
|
+ // this.init1()
|
|
|
+ this.$nextTick(() => {
|
|
|
+ const el = document.getElementById('text-container')
|
|
|
+ el.addEventListener('mouseup', this.handleSelection)
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 选择
|
|
|
+ handleSelection(event) {
|
|
|
+ const selectedText = window?.getSelection()?.toString() || ''
|
|
|
+ if (selectedText.length > 0) {
|
|
|
+ this.tagInfo = {
|
|
|
+ visible: true,
|
|
|
+ top: event.offsetY + 50,
|
|
|
+ left: event.offsetX - 50
|
|
|
+ }
|
|
|
+ this.getSelectedTextData()
|
|
|
+ } else {
|
|
|
+ this.tagInfo.visible = false
|
|
|
+ this.cancelTagInfo.visible = false
|
|
|
+ }
|
|
|
+ // this.resetEditTag()
|
|
|
+ },
|
|
|
+
|
|
|
+ // 得到标注的文本的位置
|
|
|
+ getSelectedTextData() {
|
|
|
+ const select = window?.getSelection()
|
|
|
+ // 用正则将空格和回车键去除
|
|
|
+ const nodeValue = select.focusNode?.nodeValue.replace(/(^\s*)|(\s*$)/g, '')
|
|
|
+ // 开始位置和结束位置
|
|
|
+ const anchorOffset = select.anchorOffset
|
|
|
+ const focusOffset = select.focusOffset
|
|
|
+ const nodeValueStartIndex = this.markData.text?.indexOf(nodeValue)
|
|
|
+ this.selectedText.content = select.toString()
|
|
|
+ if (anchorOffset < focusOffset) {
|
|
|
+ // 从左到右标注
|
|
|
+ this.selectedText.start = nodeValueStartIndex + anchorOffset
|
|
|
+ this.selectedText.end = nodeValueStartIndex + focusOffset
|
|
|
+ } else {
|
|
|
+ // 从右到左
|
|
|
+ this.selectedText.start = nodeValueStartIndex + focusOffset
|
|
|
+ this.selectedText.end = nodeValueStartIndex + anchorOffset
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 标注文本
|
|
|
+ handleSelect() {
|
|
|
+ const tagData = this.$refs.tagLabel.getCheckedNodes(true)[0].data
|
|
|
+ const marker = new Mark(document.getElementById('text-container'))
|
|
|
+ const { color, name } = tagData
|
|
|
+ const markId = uuidv4()
|
|
|
+ this.strNum = null
|
|
|
+
|
|
|
+ const repeatFlag = this.markTextList.findIndex(item => {
|
|
|
+ return item.mark_content === this.selectedText.content
|
|
|
+ })
|
|
|
+ if (repeatFlag !== -1) {
|
|
|
+ this.$message({
|
|
|
+ message: '该标注内容已经存在,请重新选择',
|
|
|
+ type: 'warning'
|
|
|
+ })
|
|
|
+ this.tagInfo.visible = false
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.markTextList.forEach((item) => {
|
|
|
+ if (item.start <= this.selectedText.start) {
|
|
|
+ this.strNum += item.entityName.length
|
|
|
+ }
|
|
|
+ })
|
|
|
+ marker.markRanges(
|
|
|
+ [
|
|
|
+ {
|
|
|
+ start: this.selectedText.start + this.strNum, // 必填
|
|
|
+ length: this.selectedText.content.length // 必填
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ {
|
|
|
+ className: 'text-selected',
|
|
|
+ element: 'span',
|
|
|
+ each: (element) => {
|
|
|
+ // 为元素添加样式和属性
|
|
|
+ // element.setAttribute('id', markId)
|
|
|
+ element.style.borderBottom = `2px solid ${tagData.color}`
|
|
|
+ element.style.color = tagData.color
|
|
|
+
|
|
|
+ // 创建右上角标签元素
|
|
|
+ const labelElement = document.createElement('span')
|
|
|
+ labelElement.textContent = name
|
|
|
+ labelElement.style.backgroundColor = color
|
|
|
+ labelElement.style.float = 'right'
|
|
|
+ labelElement.style.color = '#fff'
|
|
|
+ labelElement.style.padding = '2px 4px'
|
|
|
+ labelElement.style.borderRadius = '3px'
|
|
|
+ labelElement.style.fontSize = '12px'
|
|
|
+ labelElement.style.lineHeight = '12px'
|
|
|
+ labelElement.style.cursor = 'pointer'
|
|
|
+
|
|
|
+ const container = document.createElement('div')
|
|
|
+ container.setAttribute('id', markId)
|
|
|
+ container.style.display = 'inline-block'
|
|
|
+ element.parentNode.insertBefore(container, element)
|
|
|
+ container.appendChild(element)
|
|
|
+ container.appendChild(labelElement)
|
|
|
+ // 绑定事件
|
|
|
+ element.onclick = (e) => {
|
|
|
+ this.cancelTagInfo = {
|
|
|
+ visible: true,
|
|
|
+ top: e.offsetY + 50,
|
|
|
+ left: e.offsetX + 50
|
|
|
+ }
|
|
|
+ this.selectMarkedId = markId
|
|
|
+ }
|
|
|
+ element.oncontextmenu = (e) => {
|
|
|
+ e.preventDefault() // 阻止默认右击菜单
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ this.markTextList.push({
|
|
|
+ entityColor: color,
|
|
|
+ entityName: name,
|
|
|
+ start: this.selectedText.start,
|
|
|
+ end: this.selectedText.end,
|
|
|
+ mark_content: this.selectedText.content,
|
|
|
+ mark_id: markId
|
|
|
+ })
|
|
|
+ console.log('this.markTextList', this.markTextList)
|
|
|
+
|
|
|
+ this.markRelationList.push({
|
|
|
+ subject: this.selectedText.content,
|
|
|
+ subjectClass: name,
|
|
|
+ subjectPosition: [this.selectedText.start, this.selectedText.end],
|
|
|
+ object: '',
|
|
|
+ objectClass: '',
|
|
|
+ objectPosition: [],
|
|
|
+ relation: ''
|
|
|
+ })
|
|
|
+ this.tagInfo.visible = false
|
|
|
+ },
|
|
|
+
|
|
|
+ // 取消标注
|
|
|
+ handleCancel() {
|
|
|
+ this.cancelTagInfo.visible = false
|
|
|
+
|
|
|
+ if (!this.selectMarkedId) return
|
|
|
+ const selectMarkItem = this.markTextList.find(list => list.mark_id === this.selectMarkedId)
|
|
|
+ // console.log('item', item)
|
|
|
+
|
|
|
+ // const textContainer = document.getElementById('text-container')
|
|
|
+ const bigContainer = document.getElementById(this.selectMarkedId)
|
|
|
+
|
|
|
+ const textSpan = bigContainer.firstElementChild.innerText
|
|
|
+ const markEl = new Mark(bigContainer)
|
|
|
+ markEl.unmark()
|
|
|
+ bigContainer.insertAdjacentText('beforebegin', textSpan)
|
|
|
+ bigContainer.remove()
|
|
|
+
|
|
|
+ this.markTextList.splice(
|
|
|
+ this.markTextList?.findIndex((t) => t.mark_id === this.selectMarkedId),
|
|
|
+ 1
|
|
|
+ )
|
|
|
+ this.markRelationList.forEach((item, index) => {
|
|
|
+ if (item.subject === selectMarkItem.mark_content) {
|
|
|
+ // this.markRelationList.splice(index, 1)
|
|
|
+ this.markRelationList = this.markRelationList.filter(function(item) {
|
|
|
+ return item.subject !== selectMarkItem.mark_content
|
|
|
+ })
|
|
|
+ }
|
|
|
+ if (item.object === selectMarkItem.mark_content) {
|
|
|
+ item.object = item.objectClass = ''
|
|
|
+ item.objectPosition = []
|
|
|
+ item.relation = ''
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 选择客体
|
|
|
+ handleObject(selectVal, row) {
|
|
|
+ if (selectVal === row.subject) {
|
|
|
+ this.$message({
|
|
|
+ message: '主体与客体不能相同,请重新选择',
|
|
|
+ type: 'warning'
|
|
|
+ })
|
|
|
+ this.markRelationList.forEach(item => {
|
|
|
+ if (item === row) {
|
|
|
+ item.object = ''
|
|
|
+ }
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const selectOptions = this.markTextList.filter((item) =>
|
|
|
+ selectVal.includes(item.mark_content)
|
|
|
+ )
|
|
|
+ const selectItem = selectOptions[0]
|
|
|
+ this.markRelationList.forEach(item => {
|
|
|
+ if (item === row) {
|
|
|
+ item.objectClass = selectItem.entityName
|
|
|
+ item.objectPosition = [selectItem.start, selectItem.end]
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ addRelation(row) {
|
|
|
+ let selectOptions = {}; let indexPos
|
|
|
+ this.markRelationList.forEach((item, index) => {
|
|
|
+ if (item === row) {
|
|
|
+ selectOptions = { ...row }
|
|
|
+ indexPos = index
|
|
|
+ }
|
|
|
+ })
|
|
|
+ selectOptions.object = selectOptions.objectClass = ''
|
|
|
+ selectOptions.objectPosition = []
|
|
|
+ selectOptions.relation = ''
|
|
|
+ this.markRelationList.splice(indexPos + 1, 0, selectOptions)
|
|
|
+ },
|
|
|
+
|
|
|
+ // 更改文本
|
|
|
+ setNewData(newData) {
|
|
|
+ this.$set(this, 'markData', newData)
|
|
|
+ this.markTextList = [] // 标注的文本清空
|
|
|
+ this.markRelationList = [] // 清空关系
|
|
|
+ document.getElementById('text-container').innerHTML = this.markData.text
|
|
|
+ if (this.markData.markInfo) {
|
|
|
+ const markData = JSON.parse(this.markData.markInfo)
|
|
|
+ this.markRelationList = markData
|
|
|
+ // 回显时将重复的去除
|
|
|
+ const map = new Map()
|
|
|
+ for (const item of markData) {
|
|
|
+ if (!map.has(item.subject)) {
|
|
|
+ map.set(item.subject, item)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const arr = [...map.values()]
|
|
|
+ arr.forEach((item, index) => {
|
|
|
+ this.handleBackShow(item, index)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ this.$refs.table.setCurrentRow(newData)
|
|
|
+ },
|
|
|
+
|
|
|
+ // 标注回显
|
|
|
+ handleBackShow(itemMarkInfo, index) {
|
|
|
+ const marker = new Mark(document.getElementById('text-container'))
|
|
|
+ const entityData = this.tagLabelList.find(item => {
|
|
|
+ return item.name === itemMarkInfo.subjectClass
|
|
|
+ })
|
|
|
+ const markId = uuidv4()
|
|
|
+ marker.markRanges(
|
|
|
+ [
|
|
|
+ {
|
|
|
+ start: itemMarkInfo.subjectPosition[0] + index * 2, // 增加标注文本会增加”“两个字符,因此起始位置需要加上index*2
|
|
|
+ length: itemMarkInfo.subjectPosition[1] - itemMarkInfo.subjectPosition[0] // 必填
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ {
|
|
|
+ className: 'text-selected',
|
|
|
+ element: 'span',
|
|
|
+ each: (element) => {
|
|
|
+ // 为元素添加样式和属性
|
|
|
+ // element.setAttribute('id', markId)
|
|
|
+ element.style.borderBottom = `2px solid ${entityData ? entityData.color : 'red'}`
|
|
|
+ element.style.color = entityData ? entityData.color : 'red'
|
|
|
+
|
|
|
+ // 创建右上角标签元素
|
|
|
+ const labelElement = document.createElement('span')
|
|
|
+ labelElement.textContent = itemMarkInfo.subjectClass
|
|
|
+ labelElement.style.backgroundColor = entityData ? entityData.color : 'red'
|
|
|
+ labelElement.style.float = 'right'
|
|
|
+ labelElement.style.color = '#fff'
|
|
|
+ labelElement.style.padding = '2px 4px'
|
|
|
+ labelElement.style.borderRadius = '3px'
|
|
|
+ labelElement.style.fontSize = '12px'
|
|
|
+ labelElement.style.lineHeight = '12px'
|
|
|
+ labelElement.style.cursor = 'pointer'
|
|
|
+
|
|
|
+ const container = document.createElement('div')
|
|
|
+ container.setAttribute('id', markId)
|
|
|
+ container.style.display = 'inline-block'
|
|
|
+ element.parentNode.insertBefore(container, element)
|
|
|
+ container.appendChild(element)
|
|
|
+ container.appendChild(labelElement)
|
|
|
+ // 绑定事件
|
|
|
+ element.onclick = (e) => {
|
|
|
+ this.cancelTagInfo = {
|
|
|
+ visible: true,
|
|
|
+ top: e.offsetY + 50,
|
|
|
+ left: e.offsetX + 50
|
|
|
+ }
|
|
|
+ this.selectMarkedId = markId
|
|
|
+ }
|
|
|
+ element.oncontextmenu = (e) => {
|
|
|
+ e.preventDefault() // 阻止默认右击菜单
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ this.markTextList.push({
|
|
|
+ entityColor: entityData ? entityData.color : 'red',
|
|
|
+ entityName: itemMarkInfo.subjectClass,
|
|
|
+ start: itemMarkInfo.subjectPosition[0],
|
|
|
+ end: itemMarkInfo.subjectPosition[1],
|
|
|
+ mark_content: itemMarkInfo.subject,
|
|
|
+ mark_id: markId
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 调用上一条、下一条接口
|
|
|
+ handlePNChange(type) {
|
|
|
+ const data = { id: this.markData.id, dataset_id: this.markData.dataset_id, type }
|
|
|
+ PNDataApi(data).then((response) => {
|
|
|
+ if (response.code === 2) {
|
|
|
+ this.$message({
|
|
|
+ message: response.msg,
|
|
|
+ type: 'warning'
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ this.handleCurrentChange(response.data[0])
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 上一条
|
|
|
+ handlePrev() {
|
|
|
+ const flag = this.dataList.findIndex(item => {
|
|
|
+ return item.id === this.markData.id
|
|
|
+ })
|
|
|
+ if (flag !== -1 && flag === 0) {
|
|
|
+ if (this.queryParams.page === 1) {
|
|
|
+ this.$message({
|
|
|
+ message: '已经是第一条数据',
|
|
|
+ type: 'warning'
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ this.loading = true
|
|
|
+ this.queryParams.page = this.queryParams.page - 1
|
|
|
+ getDataListApi(this.queryParams).then((response) => {
|
|
|
+ this.dataList = response.data
|
|
|
+ this.total = response.total
|
|
|
+ this.handlePNChange('previous')
|
|
|
+ this.loading = false
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this.handlePNChange('previous')
|
|
|
+ }
|
|
|
+
|
|
|
+ // const isSave = JSON.stringify(this.markRelationList) === this.markData.markInfo
|
|
|
+ // if (!isSave) {
|
|
|
+ // this.$confirm(`标注内容未保存,是否保存`, '提示', {
|
|
|
+ // confirmButtonText: '确定',
|
|
|
+ // cancelButtonText: '取消',
|
|
|
+ // type: 'warning'
|
|
|
+ // }).then(() => {
|
|
|
+ // this.handleSave()
|
|
|
+ // })
|
|
|
+ // } else {
|
|
|
+ // this.loading = true
|
|
|
+ // this.queryParams.page = this.queryParams.page - 1
|
|
|
+ // getDataListApi(this.queryParams).then((response) => {
|
|
|
+ // this.dataList = response.data
|
|
|
+ // this.total = response.total
|
|
|
+ // this.handlePNChange('previous')
|
|
|
+ // this.loading = false
|
|
|
+ // })
|
|
|
+ // }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 下一条
|
|
|
+ handleNext() {
|
|
|
+ const flag = this.dataList.findIndex(item => {
|
|
|
+ return item.id === this.markData.id
|
|
|
+ })
|
|
|
+ if (flag !== -1 && flag === this.dataList.length - 1) {
|
|
|
+ if (Math.ceil(this.total / this.queryParams.size) === this.queryParams.page) {
|
|
|
+ this.$message({
|
|
|
+ message: '已经是最后一条数据',
|
|
|
+ type: 'warning'
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ this.loading = true
|
|
|
+ this.queryParams.page = this.queryParams.page + 1
|
|
|
+ getDataListApi(this.queryParams).then((response) => {
|
|
|
+ this.dataList = response.data
|
|
|
+ this.total = response.total
|
|
|
+ this.handlePNChange('next')
|
|
|
+ this.loading = false
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this.handlePNChange('next')
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 清空标记
|
|
|
+ clearMark() {
|
|
|
+ this.$confirm(`是否清空该数据的所有标记内容?该操作不可逆`, '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }).then(() => {
|
|
|
+ const textContainer = document.getElementById('text-container')
|
|
|
+ const markEl = new Mark(textContainer)
|
|
|
+ // 清除所有标注
|
|
|
+ markEl.unmark()
|
|
|
+ document.getElementById('text-container').innerHTML = this.markData.text
|
|
|
+ this.markTextList = [] // 标注的文本清空
|
|
|
+ this.markRelationList = [] // 清空关系
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 保存
|
|
|
+ handleSave() {
|
|
|
+ const data = {
|
|
|
+ id: this.markData.id,
|
|
|
+ markInfo: this.markRelationList,
|
|
|
+ state: 1
|
|
|
+ }
|
|
|
+ updataMarkInfoApi(data).then((response) => {
|
|
|
+ this.$modal.msgSuccess('保存成功')
|
|
|
+ getDataListApi(this.queryParams).then((response) => {
|
|
|
+ this.dataList = response.data
|
|
|
+ })
|
|
|
+ this.handleNext()
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 删除该条关系
|
|
|
+ handleDeleteRelation(row) {
|
|
|
+ console.log('row', row)
|
|
|
+
|
|
|
+ const delIndex = this.markRelationList.findIndex(item => {
|
|
|
+ return item === row
|
|
|
+ })
|
|
|
+ if (delIndex !== -1) {
|
|
|
+ this.$confirm(`是否删除该数据?`, '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning'
|
|
|
+ }).then(() => {
|
|
|
+ this.markRelationList.splice(delIndex, 1)
|
|
|
+ // 判断删除后,标注中还有没有该主体,没有的话在回显中也取消标注
|
|
|
+ const lastFlag = this.markTextList.forEach(item => {
|
|
|
+ if (item.mark_content === row.subject) {
|
|
|
+ // true代表还有
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ })
|
|
|
+ if (lastFlag !== true) {
|
|
|
+ const selectMarkItem = this.markTextList.find(list => list.mark_content === row.subject)
|
|
|
+ const bigContainer = document.getElementById(selectMarkItem.mark_id)
|
|
|
+
|
|
|
+ const textSpan = bigContainer.firstElementChild.innerText
|
|
|
+ const markEl = new Mark(bigContainer)
|
|
|
+ markEl.unmark()
|
|
|
+ bigContainer.insertAdjacentText('beforebegin', textSpan)
|
|
|
+ bigContainer.remove()
|
|
|
+ this.markTextList = this.markTextList.filter(item => item.mark_content !== row.subject)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+@import './markPage.scss';
|
|
|
+</style>
|