6 changed files with 781 additions and 148 deletions
-
453src/views/AIAssistant/AIKeywords/archivesList.vue
-
2src/views/AIAssistant/AIKeywords/index.vue
-
210src/views/AIAssistant/AIKeywords/module/editOcrContent.vue
-
256src/views/AIAssistant/AIKeywords/module/keywordMark.vue
-
6src/views/collectReorganizi/collectionLibrary/module/uploadOriginal/bigUpload.vue
-
2src/views/login.vue
@ -0,0 +1,210 @@ |
|||||
|
<template> |
||||
|
<!-- 编辑解析 --> |
||||
|
<el-dialog class="aiFile-dialog" title="编辑解析" :close-on-click-modal="false" :modal-append-to-body="false" append-to-body :visible.sync="ocrVisible" :before-close="handleCloseDialog"> |
||||
|
<div class="setting-dialog"> |
||||
|
<swiper |
||||
|
ref="mySwiper" |
||||
|
class="swiper-server" |
||||
|
:options="swiperOptionServer" |
||||
|
:auto-update="true" |
||||
|
:auto-destroy="true" |
||||
|
:delete-instance-on-destroy="true" |
||||
|
:cleanup-styles-on-destroy="true" |
||||
|
> |
||||
|
<swiper-slide v-for="(item, index) in swiperImg" :key="index" class="swiper-slide-server"> |
||||
|
<div class="aiImg"> |
||||
|
<img src="~@/assets/images/test/4.png"> |
||||
|
</div> |
||||
|
<div |
||||
|
class="aiText" |
||||
|
:class="{ editable: editingIndex === index }" |
||||
|
contenteditable="false" |
||||
|
@click="startEditing(index)" |
||||
|
@blur="saveEditing(index)" |
||||
|
v-html="item.textWithBr" |
||||
|
/> |
||||
|
</swiper-slide> |
||||
|
<div slot="pagination" class="swiper-pagination" /> |
||||
|
</swiper> |
||||
|
</div> |
||||
|
<div slot="footer" class="dialog-footer"> |
||||
|
<el-button type="text" @click="handleCloseDialog">取消</el-button> |
||||
|
<el-button type="primary" @click.native="handleComfiredMark">保存</el-button> |
||||
|
</div> |
||||
|
</el-dialog> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { mapGetters } from 'vuex' |
||||
|
import { swiper, swiperSlide } from 'vue-awesome-swiper' |
||||
|
import 'swiper/css/swiper.css' |
||||
|
|
||||
|
export default { |
||||
|
name: 'EditOcrContent', |
||||
|
components: { swiper, swiperSlide }, |
||||
|
props: {}, |
||||
|
data() { |
||||
|
return { |
||||
|
editingIndex: -1, |
||||
|
ocrLoading: false, |
||||
|
ocrVisible: false, |
||||
|
displayedText: '', |
||||
|
isTxtEditing: new Array(3).fill(false), |
||||
|
swiperImg: [ |
||||
|
{ |
||||
|
'file_path': '~@/assets/images/test/1.png', |
||||
|
'ocr_path': 'D:/ProjectsFTZN/Python/Project_AI_dangan/service_AI_archives_fileParsing/files\\img\\ocr_1.jpg', |
||||
|
'content': 'ICS 01.140.30\nA13\nGB\n中华人民共和国国家标准\nGB/T7408-2005/ISO8601:2000\n代替GB/T7408-1994\n数据元和交换格式\n信息交换\n日期和时间表示法\nDataelementsandinterchangeformats-\nInformation interchange-Representation of dates and times\n(ISO8601:2000,IDT)\n2005-03-28发布\n2005-10-01实施\n中华人民共和国国家质量监督检验检疫总局\n发布\n中国国家标准化管理委员会' |
||||
|
}, |
||||
|
{ |
||||
|
'file_path': '~@/assets/images/test/2.png', |
||||
|
'ocr_path': 'D:/ProjectsFTZN/Python/Project_AI_dangan/service_AI_archives_fileParsing/files\\img\\ocr_2.jpg', |
||||
|
'content': 'GB/T7408--2005/ISO8601:2000\n目\n次\n前言\n1范围\n2规范性引用文件\n3术语和定义\n4基本原则\n5表示法\n....\nX\n附录A(资料性附录)举例\n20\nA.1日期(见表A.1)\n20\nA.2\n日的时间(见表A.2)\n20\nA.3\n日期和该日的时间的组合(见表A.3)\n21\nA.4\n时间间隔(见表A.4)\n22\nA.5\n循环时间间隔(见表A.5)\n22' |
||||
|
}, |
||||
|
{ |
||||
|
'file_path': '~@/assets/images/test/3.png', |
||||
|
'ocr_path': 'D:/ProjectsFTZN/Python/Project_AI_dangan/service_AI_archives_fileParsing/files\\img\\ocr_3.jpg', |
||||
|
'content': 'GB/T7408-2005/ISO8601:2000\n前言\n本标准等同采用国际标准ISO8601一2000《数据元和交换格式信息交换日期和时间表示法》\n(英文版),并且代替GB/T7408一1994《数据元和交换格式信息交换日期和时间表示法》。\n本标准与GB/T7408-1994相比主要变化如下:\n增加日期和时间历法系统(本版的4.3);\n一一完善1994年版本的日期和时间的各种表示法(本版本的5.1,5.2,5.3,5.4);\n一增加时间间隔和循环时间间隔的表示法(本版本的5.5,5.6)。\n本标准的附录A为资料性附录。\n本标准由中国标准化研究院提出。\n本标准由全国电子业务标准化技术委员会归口。\n本标准起草单位:中国标准化研究院。\n本标准主要起草人:章建方、刘碧松、魏宏、孙文峰、刘颖。' |
||||
|
} |
||||
|
], |
||||
|
swiperOptionServer: { |
||||
|
autoplay: false, |
||||
|
allowTouchMove: false, |
||||
|
pagination: { |
||||
|
el: '.swiper-pagination', |
||||
|
clickable: true, // 分页点可点击 |
||||
|
bulletActiveClass: 'swiper-pagination-bullet-active-custom', // 自定义激活状态的类名 |
||||
|
bulletClass: 'swiper-pagination-bullet-custom', |
||||
|
renderBullet: function(index, className) { |
||||
|
return '<span class="' + className + '">' + (index + 1) + '</span>' |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
...mapGetters([ |
||||
|
'baseApi' |
||||
|
]), |
||||
|
swiper() { |
||||
|
return this.$refs.mySwiper.swiper |
||||
|
}, |
||||
|
processedSwiperImg() { |
||||
|
return this.swiperImg.map(item => { |
||||
|
const textWithBr = item.content.replace(/\n/g, '<br>') |
||||
|
return { ...item, textWithBr } |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
watch: { |
||||
|
swiperImg: { |
||||
|
deep: true, |
||||
|
handler() { |
||||
|
this.processedSwiperImg |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
created() { |
||||
|
this.swiperImg = this.processedSwiperImg |
||||
|
}, |
||||
|
mounted() { |
||||
|
}, |
||||
|
methods: { |
||||
|
startEditing(index) { |
||||
|
this.editingIndex = index |
||||
|
const slide = this.$el.querySelectorAll('.swiper-slide-server')[index] |
||||
|
const editableDiv = slide.querySelector('div[contenteditable]') |
||||
|
editableDiv.setAttribute('contenteditable', 'true') |
||||
|
}, |
||||
|
saveEditing(index) { |
||||
|
const slide = this.$el.querySelectorAll('.swiper-slide-server')[index] |
||||
|
const editableDiv = slide.querySelector('div[contenteditable]') |
||||
|
const editedHtml = editableDiv.innerHTML |
||||
|
// 只保留必要的替换,将 <br> 替换为 \n |
||||
|
let editedText = editedHtml.replace(/<br>/g, '\n') |
||||
|
editedText = editedText.trim() |
||||
|
this.swiperImg[index].content = editedText |
||||
|
this.swiperImg[index].textWithBr = editedText.replace(/\n/g, '<br>') |
||||
|
this.editingIndex = -1 |
||||
|
editableDiv.setAttribute('contenteditable', 'false') |
||||
|
}, |
||||
|
handleCloseDialog() { |
||||
|
this.ocrVisible = false |
||||
|
}, |
||||
|
handleComfiredMark() { |
||||
|
// 处理保存逻辑 |
||||
|
console.log('保存数据:', this.swiperImg) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
<style> |
||||
|
.swiper-container{ |
||||
|
padding-bottom: 40px; |
||||
|
} |
||||
|
.swiper-pagination-fraction, |
||||
|
.swiper-pagination-custom, .swiper-container-horizontal > .swiper-pagination-bullets{ |
||||
|
bottom: 0; |
||||
|
} |
||||
|
.swiper-pagination-bullet-custom { |
||||
|
display: inline-block; |
||||
|
width: 20px; |
||||
|
height: 20px; |
||||
|
line-height: 20px; |
||||
|
background-color: rgba(0, 0, 0, 0.2); |
||||
|
opacity: 1; |
||||
|
margin: 0 5px; |
||||
|
border-radius: 50%; |
||||
|
color: #000; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.swiper-pagination-bullet-active-custom { |
||||
|
background-color: #ff6600; |
||||
|
} |
||||
|
</style> |
||||
|
<style lang='scss' scoped> |
||||
|
.aiFile-dialog { |
||||
|
::v-deep .el-dialog { |
||||
|
width: 1400px !important; |
||||
|
.el-dialog__body{ |
||||
|
padding: 10px 0 0 0 !important; |
||||
|
} |
||||
|
} |
||||
|
::v-deep .swiper-server { |
||||
|
.swiper-slide-server { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
height: 700px; |
||||
|
.aiImg { |
||||
|
width: 800px; |
||||
|
overflow: hidden; |
||||
|
overflow-y: scroll; |
||||
|
margin-right: 20px; |
||||
|
img { |
||||
|
display: block; |
||||
|
width: 100%; |
||||
|
} |
||||
|
} |
||||
|
.aiText { |
||||
|
flex: 1; |
||||
|
padding: 0 10px; |
||||
|
white-space: pre-wrap; |
||||
|
word-wrap: break-word; |
||||
|
font-family: monospace; |
||||
|
background-color: #f4f4f4; |
||||
|
padding: 10px; |
||||
|
border: 1px solid #ccc; |
||||
|
border-radius: 4px; |
||||
|
height: calc(100vh - 245px); |
||||
|
overflow: hidden; |
||||
|
overflow-y: auto; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.dialog-footer{ |
||||
|
margin-top: 0; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,256 @@ |
|||||
|
<template> |
||||
|
<!-- 编辑标注 --> |
||||
|
<el-dialog class="aiMark-dialog" title="编辑标注" :close-on-click-modal="false" :modal-append-to-body="false" append-to-body :visible.sync="markVisible" :before-close="handleCloseDialog"> |
||||
|
<div class="setting-dialog"> |
||||
|
<div style="display: flex; justify-content: space-between;"> |
||||
|
<div v-loading="markLoading" style="width: calc(55%);"> |
||||
|
<pre ref="typingContainer" v-highlightjs="displayedText">{{ displayedText }}</pre> |
||||
|
<div style="text-align: right; margin-top: 10px;"> |
||||
|
<el-button type="primary" @click="handleAllPick"><i class="iconfont icon-huoqu" />AI智能提取</el-button> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="mark-list" style="width: calc(45%); margin-left: 20px; "> |
||||
|
<div class="mark-ai-list"> |
||||
|
<h3 class="item-title">AI提取标注</h3> |
||||
|
<el-button size="mini" class="check-btn btn-add-all" @click="handleAllAdd"><i class="iconfont icon-liuchengfaqi" />一键加入</el-button> |
||||
|
<div style="height:280px; padding: 0 10px; overflow: hidden; overflow-y: scroll; background-color: #f0f9eb;"> |
||||
|
<div v-for="(item, index) in aiPickData" :key="index" class="keywords-list"> |
||||
|
<div class="keywords-item ai-item-text"> |
||||
|
<el-checkbox v-model="selectedItems[index]" @change="handleCheckboxChange(index)" /> |
||||
|
<div class="keywords-text"> |
||||
|
<el-tooltip class="item" effect="dark" :content="item" :enterable="false" placement="top"> |
||||
|
<p>{{ item }}</p> |
||||
|
</el-tooltip> |
||||
|
</div> |
||||
|
<div class="keywords-handle"> |
||||
|
<i class="el-icon-circle-plus" @click="handleSingleAdd(index)" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="manual-add"> |
||||
|
<el-input v-model="manualVal" type="text" placeholder="可手动添加关键词" /> |
||||
|
<el-button @click="handleManualAdd">手动加入</el-button> |
||||
|
</div> |
||||
|
<div class="marked-list"> |
||||
|
<h3 class="item-title">已标注关键词</h3> |
||||
|
<div style="height: 260px; padding: 0 10px; overflow: hidden; overflow-y: scroll; background-color: #f0f9eb;"> |
||||
|
<div v-for="(item, index) in keywords" :key="index" class="keywords-list"> |
||||
|
<div class="keywords-item"> |
||||
|
<div v-if="isEditing[index]" class="keywords-input"> |
||||
|
<el-input v-model="keywords[index]" style="width: 320px;" @blur="handleBlur(index)" /> |
||||
|
</div> |
||||
|
<div v-else class="keywords-text"> |
||||
|
<el-tooltip class="item" effect="dark" :content="item" :enterable="false" placement="left"> |
||||
|
<p>{{ item }}</p> |
||||
|
</el-tooltip> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="keywords-handle"> |
||||
|
<i class="iconfont icon-shanchu" @click="handleDelete(index)" /> |
||||
|
<i class="iconfont icon-bianji" @click="handleEdit(index)" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div slot="footer" class="dialog-footer"> |
||||
|
<el-button type="text" @click="handleCloseDialog">取消</el-button> |
||||
|
<el-button type="primary" @click.native="handleComfiredMark">保存</el-button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</el-dialog> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { mapGetters } from 'vuex' |
||||
|
|
||||
|
export default { |
||||
|
name: 'EditMark', |
||||
|
components: { }, |
||||
|
mixins: [], |
||||
|
props: { |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
markLoading: false, |
||||
|
markVisible: false, |
||||
|
displayedText: '', |
||||
|
keywords: ['永久', '武汉飞天', '2024'], |
||||
|
isEditing: [], |
||||
|
aiPickData: ['数字人', '多媒体技术在档案信息存贮与检索中的应用中的应用中的应用中的应用', '2024'], |
||||
|
selectedItems: [], |
||||
|
manualVal: '' |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
...mapGetters([ |
||||
|
'baseApi' |
||||
|
]) |
||||
|
}, |
||||
|
created() { |
||||
|
}, |
||||
|
mounted() { |
||||
|
}, |
||||
|
methods: { |
||||
|
handleAllPick() { |
||||
|
|
||||
|
}, |
||||
|
handleEdit(index) { |
||||
|
this.$set(this.isEditing, index, !this.isEditing[index]) |
||||
|
}, |
||||
|
handleBlur(index) { |
||||
|
this.$set(this.isEditing, index, false) |
||||
|
}, |
||||
|
handleDelete(index) { |
||||
|
this.keywords.splice(index, 1) |
||||
|
this.isEditing.splice(index, 1) |
||||
|
}, |
||||
|
handleCheckboxChange(index) { |
||||
|
const allSelectedItems = this.aiPickData.filter((_, i) => this.selectedItems[i]) |
||||
|
console.log('所有选中项:', allSelectedItems) |
||||
|
}, |
||||
|
handleComfiredMark() { |
||||
|
|
||||
|
}, |
||||
|
handleCloseDialog() { |
||||
|
this.markVisible = false |
||||
|
}, |
||||
|
// 一键加入所有AI提取标注到已标注关键词列表 |
||||
|
handleAllAdd() { |
||||
|
let itemsToAdd = [] |
||||
|
// 检查是否有选中项 |
||||
|
if (this.selectedItems.some(item => item)) { |
||||
|
itemsToAdd = this.aiPickData.filter((_, i) => this.selectedItems[i]) |
||||
|
} else { |
||||
|
itemsToAdd = this.aiPickData |
||||
|
} |
||||
|
// 过滤掉已存在于keywords中的项 |
||||
|
const newKeywords = itemsToAdd.filter(item => !this.keywords.includes(item)) |
||||
|
this.keywords = [...this.keywords, ...newKeywords] |
||||
|
// 更新aiPickData,移除已添加的项 |
||||
|
this.aiPickData = this.aiPickData.filter(item => !newKeywords.includes(item)) |
||||
|
// 清空选中状态 |
||||
|
this.selectedItems = this.selectedItems.map(() => false) |
||||
|
}, |
||||
|
// 单个加入AI提取标注到已标注关键词列表 |
||||
|
handleSingleAdd(index) { |
||||
|
const item = this.aiPickData[index] |
||||
|
if (this.keywords.includes(item)) { |
||||
|
this.$message({ message: '已标注关键词列表已有相关关键词,不可重复加入', type: 'warning', offset: 8 }) |
||||
|
} else { |
||||
|
this.keywords.push(item) |
||||
|
this.aiPickData.splice(index, 1) |
||||
|
} |
||||
|
}, |
||||
|
// 手动加入关键词到已标注关键词列表 |
||||
|
handleManualAdd() { |
||||
|
const keyword = this.manualVal.trim() |
||||
|
if (keyword) { |
||||
|
if (this.keywords.includes(keyword)) { |
||||
|
this.$message({ message: '已标注关键词列表已有相关关键词,不可重复加入', type: 'warning', offset: 8 }) |
||||
|
} else { |
||||
|
this.keywords.push(keyword) |
||||
|
this.manualVal = '' |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang='scss' scoped> |
||||
|
.aiMark-dialog{ |
||||
|
::v-deep .el-dialog{ |
||||
|
width: 1100px !important; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.mark-list{ |
||||
|
.mark-ai-list{ |
||||
|
position: relative; |
||||
|
.btn-add-all{ |
||||
|
position: absolute; |
||||
|
right: 0; |
||||
|
top: 12px; |
||||
|
} |
||||
|
} |
||||
|
.manual-add{ |
||||
|
display:flex; |
||||
|
justify-content: space-between; |
||||
|
margin: 20px 0 0 0; |
||||
|
.el-button{ |
||||
|
margin-left: 10px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.item-title{ |
||||
|
position: relative; |
||||
|
padding: 18px 0 18px 15px; |
||||
|
font-size: 16px; |
||||
|
color: #0C0E1E; |
||||
|
&::before{ |
||||
|
position: absolute; |
||||
|
left: 0; |
||||
|
top: 50%; |
||||
|
content: ""; |
||||
|
width: 3px; |
||||
|
height: 16px; |
||||
|
background-color: #0348F3; |
||||
|
transform: translateY(-50%); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.keywords-list{ |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
width: 100%; |
||||
|
line-height: 30px; |
||||
|
border-bottom: 1px solid #e6e8ed; |
||||
|
.keywords-text { |
||||
|
flex: 1; |
||||
|
p{ |
||||
|
// width: 400px; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
display: -webkit-box; |
||||
|
-webkit-line-clamp: 1; |
||||
|
-webkit-box-orient: vertical; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.ai-item-text{ |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
width: 100%; |
||||
|
p{ |
||||
|
margin-left: 10px; |
||||
|
} |
||||
|
.keywords-handle{ |
||||
|
font-size: 16px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.keywords-item{ |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
pre { |
||||
|
background-color: #f4f4f4; |
||||
|
padding: 10px; |
||||
|
border: 1px solid #ccc; |
||||
|
border-radius: 4px; |
||||
|
white-space: pre-wrap; |
||||
|
word-wrap: break-word; |
||||
|
height: calc(100vh - 264px); |
||||
|
overflow: hidden; |
||||
|
overflow-y: auto; |
||||
|
} |
||||
|
.dialog-footer{ |
||||
|
margin-top: 20px; |
||||
|
} |
||||
|
</style> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue