9 changed files with 1299 additions and 667 deletions
-
12src/api/collect/collect.js
-
14src/assets/styles/yxk-admin.scss
-
25src/views/AIAssistant/AICataloging/history/index.vue
-
664src/views/AIAssistant/AICataloging/index copy.vue
-
646src/views/AIAssistant/AICataloging/index.vue
-
387src/views/AIAssistant/AICataloging/running/index.vue
-
93src/views/AIAssistant/AICataloging/running/module/detail.vue
-
77src/views/collectReorganizi/collectionLibrary/module/collectHeader.vue
-
40src/views/components/echarts/graph.vue
@ -0,0 +1,25 @@ |
|||
<template> |
|||
<div> |
|||
<Running ref="running" :is-histroy="true" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import Running from '../running/index' |
|||
|
|||
export default { |
|||
name: 'History', |
|||
components: { Running }, |
|||
data() { |
|||
return { |
|||
} |
|||
}, |
|||
mounted() { |
|||
}, |
|||
methods: { |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
</style> |
@ -0,0 +1,664 @@ |
|||
<template> |
|||
<div class="app-container category-container" style="height: calc(100vh - 140px);"> |
|||
<!-- 门类列表 --> |
|||
<div class="container-main"> |
|||
<div class="elect-cont-left"> |
|||
<div class="container-left"> |
|||
<span class="right-top-line" /> |
|||
<span class="left-bottom-line" /> |
|||
<!--门类树状结构--> |
|||
<div class="tree-scroll"> |
|||
<el-scrollbar style="height: calc(100vh - 230px);"> |
|||
<el-tree ref="categroyTree" v-loading="crud.loading" class="arc-tree arc-tree-01" :data="crud.data" :props="defaultProps" node-key="id" :expand-on-click-node="false" highlight-current @node-click="handleNodeClick"> |
|||
<span slot-scope="{ node, data }" class="custom-tree-node"> |
|||
<el-tooltip :content="node.label" placement="left" :enterable="false" effect="dark"> |
|||
<span v-if="data.isType === 0"> |
|||
{{ data.label }} |
|||
</span> |
|||
<span v-if="data.isType === 1" class="iconFolder tree-text"> |
|||
{{ data.label }} |
|||
</span> |
|||
<span v-if="data.isType === 2" class="iconArch tree-text"> |
|||
{{ data.label }} |
|||
</span> |
|||
<span v-if="data.isType === 3" class="iconFile tree-text"> |
|||
{{ data.label }} |
|||
</span> |
|||
</el-tooltip> |
|||
</span> |
|||
</el-tree> |
|||
</el-scrollbar> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<!-- 门类管理tab --> |
|||
<div class="elect-cont-right"> |
|||
<div v-if="selectedCategory.isType === 2" class="container-right"> |
|||
<span class="right-top-line" /> |
|||
<span class="left-bottom-line" /> |
|||
<div style="display: flex; justify-content: flex-start;"> |
|||
<div class="upload-btn"> |
|||
<input id="upFile" type="file" name="upFile" multiple @change="changeAiFile($event)"> |
|||
<el-button :loading="aiLoading" size="small" type="primary"><i :class="['iconfont', aiLoading ? 'icon-huoqu' : 'icon-shangchuan']" />{{ aiLoading ? 'AI辅助著录识别中' : '选择文件' }}</el-button> |
|||
</div> |
|||
<!-- margin-left: 10px; line-height: 34px; height: 106px; overflow: hidden; overflow-y: scroll; --> |
|||
<div style="flex: 1; font-size: 12px; display: flex; line-height: 34px; "> |
|||
<div v-for="item in fileList" :key="item.name" class="file-list" style="margin-left: 10px;"> |
|||
<i class="iconfont icon-xiaowenjian" style="font-size: 14px;" /> |
|||
{{ item.name }} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="ai-category-main"> |
|||
<div class="ai-des-left"> |
|||
<div class="des-title"> |
|||
<p>著录项</p> |
|||
</div> |
|||
<div class="ai-des-form"> |
|||
<PreviewForm |
|||
ref="previewForm" |
|||
:is-has-code="true" |
|||
:is-disabled="false" |
|||
:form-preview-data.sync="formPreviewData" |
|||
:selected-category="selectedCategory" |
|||
:is-des-form-type="isDesFormType" |
|||
:collect-level="collectLevel" |
|||
:category-menu="categoryMenu" |
|||
:is-ai-category="true" |
|||
/> |
|||
</div> |
|||
</div> |
|||
<div class="ai-des-right"> |
|||
<div class="des-title"> |
|||
<p>AI识别数据</p> |
|||
</div> |
|||
<div class="ai-des-json"> |
|||
<pre v-if="aiJsonData" ref="typingContainer" v-highlightjs="displayedText">{{ displayedText }}</pre> |
|||
</div> |
|||
<div v-if="typingFinished" style="text-align: right;"> |
|||
<el-button @click="saveAiData">保存</el-button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
<script> |
|||
import crudCategory from '@/api/system/category/category' |
|||
import CRUD, { presenter, header } from '@crud/crud' |
|||
import PreviewForm from '@/views/components/category/PreviewForm' |
|||
import { FetchInitCategoryInputFieldByPid, FetchCategoryMenu } from '@/api/system/category/category' |
|||
import { FetchAIResultZhulu } from '@/api/collect/collect' |
|||
import { getCurrentTime } from '@/utils/index' |
|||
import { archivesUpload } from '@/utils/upload' |
|||
import { mapGetters } from 'vuex' |
|||
|
|||
export default { |
|||
name: 'AICataloging', |
|||
components: { PreviewForm }, |
|||
cruds() { |
|||
return [ |
|||
CRUD({ |
|||
title: 'AI辅助著录', url: 'api/category/fondMenu', |
|||
crudMethod: { ...crudCategory }, |
|||
optShow: { |
|||
add: false, |
|||
edit: false, |
|||
del: false, |
|||
download: false, |
|||
group: false |
|||
} |
|||
}) |
|||
] |
|||
}, |
|||
mixins: [presenter(), header()], |
|||
provide() { |
|||
return { |
|||
parentsData: this |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
defaultProps: { |
|||
children: 'children', |
|||
label: 'label' |
|||
}, |
|||
selectedCategory: {}, |
|||
formPreviewData: [], |
|||
isDesFormType: 'arcives', |
|||
collectLevel: 3, |
|||
categoryMenu: [], |
|||
nowDate: '', // 当前时间 |
|||
aiLoading: false, |
|||
fileList: [], |
|||
aiJsonData: null, |
|||
displayedText: '', |
|||
typingInterval: null, |
|||
typingFinished: false, |
|||
currentLineIndex: 0 |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapGetters([ |
|||
'baseApi' |
|||
]) |
|||
}, |
|||
mounted() { |
|||
this.getCategoryDataTree() |
|||
}, |
|||
methods: { |
|||
getCategoryDataTree() { |
|||
FetchCategoryMenu().then(res => { |
|||
this.categoryMenu = res |
|||
}) |
|||
}, |
|||
filterData(data) { |
|||
return data.filter(node => { |
|||
if (node.children && node.children.length > 0) { |
|||
node.children = this.filterData(node.children) // 递归处理子节点 |
|||
} |
|||
return node.isType !== 3 // 过滤掉isType为3的节点 |
|||
}) |
|||
}, |
|||
// 逆归实现 获取指定元素 |
|||
findNode(tree, func) { |
|||
for (const node of tree) { |
|||
if (func(node)) return node |
|||
if (node.children) { |
|||
const res = this.findNode(node.children, func) |
|||
if (res) return res |
|||
} |
|||
} |
|||
return null |
|||
}, |
|||
// 根据父级展开全部子级 |
|||
expandAllChildren(node, targetElement) { |
|||
node.expanded = true |
|||
// 递归展开当前节点的每个子节点 |
|||
if (node.childNodes && node.childNodes.length > 0) { |
|||
for (let i = 0; i < node.childNodes.length; i++) { |
|||
if (node.childNodes[i].data.id === targetElement.id) { |
|||
this.$refs.categroyTree.setCurrentKey(node.childNodes[i]) |
|||
} |
|||
this.expandAllChildren(node.childNodes[i], targetElement) |
|||
} |
|||
} |
|||
}, |
|||
// 转换函数,将原始数据转换为el-tree所需格式 |
|||
transformData(rawData) { |
|||
return rawData.map(item => { |
|||
return { |
|||
label: item.fondName, |
|||
isType: 0, |
|||
id: item.fondsId, |
|||
fondsNo: item.fondsNo, |
|||
children: item.categoryList.map(category => { |
|||
return { |
|||
label: category.cnName, |
|||
cnName: category.cnName, |
|||
id: category.id, |
|||
arrangeType: category.arrangeType, |
|||
isType: category.isType, |
|||
fondsId: item.fondsId, |
|||
fondName: item.fondName, |
|||
fondsNo: item.fondsNo, |
|||
children: this.transformChildren(category.children, item.fondsId, item.fondName, item.fondsNo) |
|||
} |
|||
}) |
|||
} |
|||
}) |
|||
}, |
|||
// 递归函数,用于处理数据的子节点 |
|||
transformChildren(children, fondsId, fondName, fondsNo) { |
|||
return children.map(child => { |
|||
return { |
|||
label: child.cnName, |
|||
cnName: child.cnName, |
|||
id: child.id, |
|||
isType: child.isType, |
|||
pid: child.pid, |
|||
code: child.code, |
|||
arrangeType: child.arrangeType, |
|||
fondsId: fondsId, |
|||
fondName: fondName, |
|||
fondsNo: fondsNo, |
|||
children: child.children.length ? this.transformChildren(child.children, fondsId, fondName, fondsNo) : [] |
|||
} |
|||
}) |
|||
}, |
|||
// 找顶级节点 |
|||
findTopLevelNode(data, fondsId) { |
|||
for (let i = 0; i < data.length; i++) { |
|||
if (data[i].id === fondsId) { |
|||
return data[i] |
|||
} |
|||
} |
|||
return null |
|||
}, |
|||
[CRUD.HOOK.afterRefresh]() { |
|||
this.crud.data = this.filterData(this.transformData(this.crud.data)) |
|||
this.$nextTick(() => { |
|||
let currentKey |
|||
if (localStorage.getItem('currentArchivesKey') !== null) { |
|||
currentKey = JSON.parse(localStorage.getItem('currentArchivesKey')) |
|||
// 删除门类节点后 |
|||
if (this.$refs.categroyTree.getCurrentKey(currentKey.id) == null) { |
|||
localStorage.removeItem('currentArchivesKey') |
|||
} |
|||
this.topLevelNode = this.findTopLevelNode(this.crud.data, currentKey.fondsId) |
|||
// 如果找到了顶级节点,则从该节点开始递归查找指定元素 |
|||
if (this.topLevelNode) { |
|||
if (currentKey) { |
|||
// 展开顶级节点的子节点 |
|||
if (currentKey.isType === 1) { |
|||
currentKey = this.findNode(this.crud.data[0].children, (node) => { |
|||
return node.isType !== 1 |
|||
}) |
|||
} |
|||
this.expandAllChildren(this.$refs.categroyTree.getNode(this.topLevelNode), currentKey) |
|||
} else { |
|||
this.defaultSetting(currentKey) |
|||
} |
|||
} else { |
|||
this.defaultSetting(currentKey) |
|||
} |
|||
} else { |
|||
this.defaultSetting(currentKey) |
|||
} |
|||
if (currentKey && currentKey.id) { |
|||
this.$nextTick(() => { |
|||
// 选中节点的门类详情 |
|||
this.handleNodeClick(currentKey) |
|||
}) |
|||
} |
|||
}) |
|||
}, |
|||
defaultSetting(currentKey) { |
|||
if (this.crud.data[0].isType === 0) { |
|||
currentKey = this.findNode(this.crud.data[0].children, (node) => { |
|||
return node.isType !== 1 |
|||
}) |
|||
this.expandAllChildren(this.$refs.categroyTree.getNode(this.crud.data[0]), currentKey) |
|||
} else { |
|||
currentKey = this.crud.data[0] |
|||
this.expandAllChildren(this.$refs.categroyTree.getNode(this.crud.data[0]), currentKey) |
|||
} |
|||
}, |
|||
// 选中门类后,设置门类详情数据 |
|||
handleNodeClick(val) { |
|||
if (val) { |
|||
localStorage.setItem('currentArchivesKey', JSON.stringify(val)) |
|||
this.selectedCategory = val |
|||
this.$nextTick(() => { |
|||
this.getFormInfo() |
|||
}) |
|||
} |
|||
}, |
|||
getFormInfo() { |
|||
// console.log('this.selectedCategory.arrangeType', this.selectedCategory.arrangeType) |
|||
// if (this.selectedCategory.arrangeType === 1) { |
|||
// this.collectLevel = 3 |
|||
// } else if (this.selectedCategory.arrangeType === 2) { |
|||
// this.collectLevel = 2 |
|||
// } else { |
|||
// this.collectLevel = 1 |
|||
// } |
|||
const params = { |
|||
'categoryId': this.selectedCategory.id, |
|||
'categoryLevel': this.collectLevel |
|||
} |
|||
FetchInitCategoryInputFieldByPid(params).then(data => { |
|||
this.formPreviewData = data |
|||
console.log('formPreviewData', this.formPreviewData) |
|||
this.isDesFormType = 'arcives' |
|||
this.$nextTick(() => { |
|||
this.$refs.previewForm.archivesType = 'add' |
|||
this.$refs.previewForm.FetchNoFormatField(this.selectedCategory.id) |
|||
}) |
|||
}) |
|||
}, |
|||
// 选择附件 |
|||
async changeAiFile(e) { |
|||
// 替换文件时清空 aiJsonData |
|||
this.aiJsonData = null |
|||
this.currentLineIndex = 0 |
|||
this.displayedText = '' |
|||
this.typingFinished = false |
|||
this.typingInterval = null |
|||
// if (this.typingInterval) { |
|||
// clearInterval(this.typingInterval); |
|||
// } |
|||
|
|||
const selectedFiles = Array.from(e.target.files) |
|||
const imageFiles = selectedFiles.filter(file => file.type.startsWith('image/')) |
|||
const nonImageFiles = selectedFiles.filter(file => !file.type.startsWith('image/')) |
|||
|
|||
// 不允许同时选择图片和非图片文件 |
|||
if (imageFiles.length > 0 && nonImageFiles.length > 0) { |
|||
this.$message.error('不能同时选择图片和其他类型文件,请重新选择') |
|||
return |
|||
} |
|||
|
|||
const existingImageFiles = this.fileList.filter(item => item.formatType === 'image') |
|||
const existingNonImageFiles = this.fileList.filter(item => item.formatType !== 'image') |
|||
|
|||
if (imageFiles.length > 0) { |
|||
// if (existingImageFiles.length > 0) { |
|||
// if (existingImageFiles.length + imageFiles.length > 3) { |
|||
// // 若加入新图片会超过 3 张,清空已有图片 |
|||
// this.fileList = this.fileList.filter(item => item.formatType !== 'image') |
|||
// } |
|||
// } else if (existingNonImageFiles.length > 0) { |
|||
// // 若已有非图片文件,清空已有非图片文件 |
|||
// this.fileList = this.fileList.filter(item => item.formatType === 'image') |
|||
// } |
|||
|
|||
// // 检查图片文件数量 |
|||
// if (imageFiles.length > 3) { |
|||
// this.$message.error('图片文件最多只能选择 3 个,请重新选择') |
|||
// return |
|||
// } |
|||
if (existingNonImageFiles.length > 0) { |
|||
// 若已有非图片文件,清空已有非图片文件 |
|||
this.fileList = this.fileList.filter(item => item.formatType === 'image') |
|||
} |
|||
|
|||
for (const file of imageFiles) { |
|||
// 检查文件是否已存在 |
|||
if (this.fileList.some(item => item.name === file.name)) { |
|||
this.$message.warning(`文件 ${file.name} 已存在,请勿重复上传`) |
|||
continue |
|||
} |
|||
|
|||
const fileInfo = { |
|||
file: file, |
|||
size: file.size, |
|||
formatType: file.type.substring(0, file.type.indexOf('/')), |
|||
name: file.name, |
|||
postfix: file.name.substring( |
|||
file.name.lastIndexOf('.') + 1, |
|||
file.name.length |
|||
), |
|||
px: '' |
|||
} |
|||
|
|||
const fileBase64 = await this.getBase64(file) |
|||
const res = await this.getImgPx(fileBase64) |
|||
fileInfo.px = res.width + 'px*' + res.height + 'px' |
|||
|
|||
this.fileList.push(fileInfo) |
|||
} |
|||
this.FetchAiFileUplaod(this.fileList) |
|||
} else if (nonImageFiles.length > 0) { |
|||
if (existingNonImageFiles.length > 0) { |
|||
// 若已有非图片文件,直接替换 |
|||
this.fileList = this.fileList.filter(item => item.formatType === 'image') |
|||
} else if (existingImageFiles.length > 0) { |
|||
// 若已有图片文件,清空已有图片文件 |
|||
this.fileList = this.fileList.filter(item => item.formatType !== 'image') |
|||
} |
|||
|
|||
// 检查非图片文件数量 |
|||
if (nonImageFiles.length > 1) { |
|||
this.$message.error('非图片文件最多只能选择 1 个,请重新选择') |
|||
return |
|||
} |
|||
|
|||
for (const file of nonImageFiles) { |
|||
// 检查文件是否已存在 |
|||
if (this.fileList.some(item => item.name === file.name)) { |
|||
this.$message.warning(`文件 ${file.name} 已存在,请勿重复上传`) |
|||
continue |
|||
} |
|||
|
|||
const fileInfo = { |
|||
file: file, |
|||
size: file.size, |
|||
formatType: file.type.substring(0, file.type.indexOf('/')), |
|||
name: file.name, |
|||
postfix: file.name.substring( |
|||
file.name.lastIndexOf('.') + 1, |
|||
file.name.length |
|||
), |
|||
px: '' |
|||
} |
|||
this.fileList.push(fileInfo) |
|||
} |
|||
this.FetchAiFileUplaod(this.fileList) |
|||
} |
|||
}, |
|||
FetchAiFileUplaod(files) { |
|||
this.aiLoading = true |
|||
this.nowDate = getCurrentTime() |
|||
const promiseArray = files.map(async(item, index) => { |
|||
const json = {} |
|||
json.file_name = item.name |
|||
json.file_size = item.size |
|||
json.file_type = item.postfix |
|||
json.file_path = '' |
|||
json.archive_id = this.arcId |
|||
json.file_dpi = item.px |
|||
json.file_thumbnail = '' |
|||
json.create_time = this.nowDate |
|||
json.id = null |
|||
json.last_modified = item.file.lastModified |
|||
return json |
|||
}) |
|||
console.log('promiseArray', promiseArray) |
|||
const fileDefault = files.map(item => item.file) |
|||
|
|||
Promise.all(promiseArray) |
|||
.then((arrayUpload) => { |
|||
archivesUpload(this.baseApi + '/api/collect/uploadAssistEnterFiles', |
|||
fileDefault, |
|||
this.selectedCategory.id, |
|||
this.arcId, |
|||
JSON.stringify(arrayUpload) |
|||
).then(res => { |
|||
if (res.data.data !== null) { |
|||
const params = { |
|||
'code': res.data.data.code, |
|||
'id': res.data.data.id |
|||
} |
|||
const fetchAiZhuluResult = () => { |
|||
// 检查是否应该继续请求 |
|||
if (!this.shouldContinueFetching) return |
|||
|
|||
FetchAIResultZhulu(params).then((res) => { |
|||
const data = JSON.parse(res) |
|||
console.log('data', data) |
|||
if (data.result !== null && data.status === 'success') { |
|||
this.$message({ message: '解析著录附件成功', type: 'success', offset: 8 }) |
|||
this.aiJsonData = data.result |
|||
this.startTypingEffect() |
|||
} else { |
|||
setTimeout(fetchAiZhuluResult, 3000) |
|||
} |
|||
}).catch(err => { |
|||
console.log(err) |
|||
}) |
|||
} |
|||
fetchAiZhuluResult() |
|||
} else { |
|||
this.$message({ message: '著录附件传输失败', type: 'error', offset: 8 }) |
|||
} |
|||
}) |
|||
}) |
|||
.catch((error) => { |
|||
console.error(error) |
|||
}) |
|||
}, |
|||
startTypingEffect() { |
|||
const lines = this.aiJsonData.split('\n') |
|||
this.currentLineIndex = 0 |
|||
this.displayedText = '' |
|||
this.typingFinished = false |
|||
|
|||
this.typingInterval = setInterval(() => { |
|||
if (this.currentLineIndex < lines.length) { |
|||
this.displayedText += lines[this.currentLineIndex] + '\n' |
|||
this.currentLineIndex++ |
|||
} else { |
|||
clearInterval(this.typingInterval) |
|||
|
|||
setTimeout(() => { |
|||
this.typingFinished = true |
|||
if (this.aiJsonData) { |
|||
// this.$refs.previewForm.archivesType = 'add' |
|||
this.$refs.previewForm.addOrUpdateForm = JSON.parse(this.aiJsonData) |
|||
this.aiLoading = false |
|||
} |
|||
}, 1000) |
|||
} |
|||
|
|||
// 滚动条始终保持在底部 |
|||
const container = this.$refs.typingContainer |
|||
if (container) { |
|||
container.scrollTop = container.scrollHeight |
|||
} |
|||
}, 200) |
|||
}, |
|||
saveAiData() { |
|||
this.$router.push({ path: '/collectReorganizi/collectionLibrary' }) |
|||
}, |
|||
// 将上传的图片转为base64 |
|||
getBase64(file) { |
|||
const reader = new FileReader() |
|||
reader.readAsDataURL(file) |
|||
return new Promise((resolve) => { |
|||
reader.onload = () => { |
|||
resolve(reader.result) |
|||
} |
|||
}) |
|||
}, |
|||
// 获取图片的分辨率 |
|||
getImgPx(img) { |
|||
const image = new Image() |
|||
image.src = img |
|||
return new Promise((resolve) => { |
|||
image.onload = () => { |
|||
const width = image.width |
|||
const height = image.height |
|||
resolve({ width, height }) |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
@import "~@/assets/styles/mixin.scss"; |
|||
@import "~@/assets/styles/variables.scss"; |
|||
.category-container { |
|||
.elect-cont-left{ |
|||
width: 296px !important; |
|||
} |
|||
} |
|||
.openSidebar .category-container .elect-cont-right{ |
|||
width: calc(100vw - 614px) !important; |
|||
} |
|||
.hideSidebar .category-container .elect-cont-right { |
|||
width: calc(100vw - 412px) !important; |
|||
} |
|||
.tree-scroll{ |
|||
font-size: 14px; |
|||
} |
|||
.ai-category-main{ |
|||
display: flex; |
|||
justify-content: flex-start; |
|||
margin-top: 10px; |
|||
|
|||
.ai-des-left{ |
|||
width: 740px; |
|||
margin-right: 20px; |
|||
} |
|||
.ai-des-right{ |
|||
flex: 1; |
|||
} |
|||
|
|||
.ai-des-form{ |
|||
height: calc(100vh - 262px); |
|||
overflow-y: auto; |
|||
padding-right: 10px; |
|||
} |
|||
|
|||
.ai-des-json{ |
|||
height: calc(100vh - 300px); |
|||
margin-bottom: 10px; |
|||
} |
|||
} |
|||
|
|||
.prearch-upload{ |
|||
margin-right: 0 !important; |
|||
::v-deep .el-form-item__label{ |
|||
position: relative; |
|||
&::before{ |
|||
position: absolute; |
|||
top: -2px; |
|||
right: 70px; |
|||
content: "*"; |
|||
font-size: 10px; |
|||
color: #ff4949; |
|||
font-style: normal; |
|||
} |
|||
} |
|||
::v-deep .el-form-item__content{ |
|||
position: relative; |
|||
width: 540px !important; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
.input-style{ |
|||
width: 500px; |
|||
height: 34px; |
|||
line-height: 34px; |
|||
padding: 0 20px; |
|||
border: 1px solid #e6e8ed; |
|||
border-radius: 3px; |
|||
// &.error-box{ |
|||
// border-color: #ed4a41; |
|||
// } |
|||
} |
|||
// .error-tip{ |
|||
// position: absolute; |
|||
// left: 0; |
|||
// bottom: -26px; |
|||
// font-size: 12px; |
|||
// color: #ff4949; |
|||
// } |
|||
.upload-btn{ |
|||
position: relative; |
|||
width:96px; |
|||
margin-right: 0 !important; |
|||
margin-left: 10px; |
|||
overflow: initial !important; |
|||
#upFile{ |
|||
position: absolute; |
|||
left: 0; |
|||
top: 0; |
|||
// opacity: 0; |
|||
width: 84px; |
|||
height: 34px; |
|||
} |
|||
.el-button{ |
|||
margin-top: -2px; |
|||
font-weight: bold; |
|||
border-color: #0348f3; |
|||
color: #0348f3; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
pre { |
|||
background-color: #f4f4f4; |
|||
padding: 10px; |
|||
border: 1px solid #ccc; |
|||
border-radius: 4px; |
|||
white-space: pre-wrap; |
|||
word-wrap: break-word; |
|||
height: calc(100vh - 300px); |
|||
overflow-y: auto; |
|||
} |
|||
</style> |
@ -1,645 +1,55 @@ |
|||
<template> |
|||
<div class="app-container category-container" style="height: calc(100vh - 140px);"> |
|||
<!-- 门类列表 --> |
|||
<div class="container-main"> |
|||
<div class="elect-cont-left"> |
|||
<div class="container-left"> |
|||
<div class="app-container tab-container"> |
|||
<div class="tab-content"> |
|||
<span class="right-top-line" /> |
|||
<span class="left-bottom-line" /> |
|||
<!--门类树状结构--> |
|||
<div class="tree-scroll"> |
|||
<el-scrollbar style="height: calc(100vh - 230px);"> |
|||
<el-tree ref="categroyTree" v-loading="crud.loading" class="arc-tree arc-tree-01" :data="crud.data" :props="defaultProps" node-key="id" :expand-on-click-node="false" highlight-current @node-click="handleNodeClick"> |
|||
<span slot-scope="{ node, data }" class="custom-tree-node"> |
|||
<el-tooltip :content="node.label" placement="left" :enterable="false" effect="dark"> |
|||
<span v-if="data.isType === 0"> |
|||
{{ data.label }} |
|||
</span> |
|||
<span v-if="data.isType === 1" class="iconFolder tree-text"> |
|||
{{ data.label }} |
|||
</span> |
|||
<span v-if="data.isType === 2" class="iconArch tree-text"> |
|||
{{ data.label }} |
|||
</span> |
|||
<span v-if="data.isType === 3" class="iconFile tree-text"> |
|||
{{ data.label }} |
|||
</span> |
|||
</el-tooltip> |
|||
</span> |
|||
</el-tree> |
|||
</el-scrollbar> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<!-- 门类管理tab --> |
|||
<div class="elect-cont-right"> |
|||
<div v-if="selectedCategory.isType === 2" class="container-right"> |
|||
<span class="right-top-line" /> |
|||
<span class="left-bottom-line" /> |
|||
<div style="display: flex; justify-content: flex-start;"> |
|||
<div class="upload-btn"> |
|||
<input id="upFile" type="file" name="upFile" multiple @change="changeAiFile($event)"> |
|||
<el-button :loading="aiLoading" size="small" type="primary"><i :class="['iconfont', aiLoading ? 'icon-huoqu' : 'icon-shangchuan']" />{{ aiLoading ? 'AI辅助著录识别中' : '选择文件' }}</el-button> |
|||
</div> |
|||
<!-- margin-left: 10px; line-height: 34px; height: 106px; overflow: hidden; overflow-y: scroll; --> |
|||
<div style="flex: 1; font-size: 12px; display: flex; line-height: 34px; "> |
|||
<div v-for="item in fileList" :key="item.name" class="file-list" style="margin-left: 10px;"> |
|||
<i class="iconfont icon-xiaowenjian" style="font-size: 14px;" /> |
|||
{{ item.name }} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="ai-category-main"> |
|||
<div class="ai-des-left"> |
|||
<div class="des-title"> |
|||
<p>著录项</p> |
|||
</div> |
|||
<div class="ai-des-form"> |
|||
<PreviewForm |
|||
ref="previewForm" |
|||
:is-has-code="true" |
|||
:is-disabled="false" |
|||
:form-preview-data.sync="formPreviewData" |
|||
:selected-category="selectedCategory" |
|||
:is-des-form-type="isDesFormType" |
|||
:collect-level="collectLevel" |
|||
:category-menu="categoryMenu" |
|||
:is-ai-category="true" |
|||
/> |
|||
</div> |
|||
</div> |
|||
<div class="ai-des-right"> |
|||
<div class="des-title"> |
|||
<p>AI识别数据</p> |
|||
</div> |
|||
<div class="ai-des-json"> |
|||
<pre v-if="aiJsonData" ref="typingContainer" v-highlightjs="displayedText">{{ displayedText }}</pre> |
|||
</div> |
|||
<div v-if="typingFinished" style="text-align: right;"> |
|||
<el-button @click="saveAiData">保存</el-button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<span class="right-bottom-line" /> |
|||
<ul class="tab-nav"> |
|||
<li :class="{ 'active-tab-nav': activeIndex == 0 }" @click="changeActiveTab(0)">处理中<i /></li> |
|||
<li :class="{ 'active-tab-nav': activeIndex == 1 }" @click="changeActiveTab(1)">已完成<i /></li> |
|||
<!-- 最右侧装饰img --> |
|||
<span class="tab-right-img" /> |
|||
</ul> |
|||
<component :is="comName" /> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import crudCategory from '@/api/system/category/category' |
|||
import CRUD, { presenter, header } from '@crud/crud' |
|||
import PreviewForm from '@/views/components/category/PreviewForm' |
|||
import { FetchInitCategoryInputFieldByPid, FetchCategoryMenu } from '@/api/system/category/category' |
|||
import { getCurrentTime } from '@/utils/index' |
|||
import { archivesUpload } from '@/utils/upload' |
|||
import { mapGetters } from 'vuex' |
|||
import running from './running/index' |
|||
import history from './history/index' |
|||
|
|||
export default { |
|||
name: 'AICataloging', |
|||
components: { PreviewForm }, |
|||
cruds() { |
|||
return [ |
|||
CRUD({ |
|||
title: 'AI辅助著录', url: 'api/category/fondMenu', |
|||
crudMethod: { ...crudCategory }, |
|||
optShow: { |
|||
add: false, |
|||
edit: false, |
|||
del: false, |
|||
download: false, |
|||
group: false |
|||
} |
|||
}) |
|||
] |
|||
}, |
|||
mixins: [presenter(), header()], |
|||
provide() { |
|||
return { |
|||
parentsData: this |
|||
} |
|||
}, |
|||
components: { running, history }, |
|||
data() { |
|||
return { |
|||
defaultProps: { |
|||
children: 'children', |
|||
label: 'label' |
|||
}, |
|||
selectedCategory: {}, |
|||
formPreviewData: [], |
|||
isDesFormType: 'arcives', |
|||
collectLevel: 3, |
|||
categoryMenu: [], |
|||
nowDate: '', // 当前时间 |
|||
aiLoading: false, |
|||
fileList: [], |
|||
aiJsonData: null, |
|||
displayedText: '', |
|||
typingInterval: null, |
|||
typingFinished: false, |
|||
currentLineIndex: 0 |
|||
activeIndex: 0 |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapGetters([ |
|||
'baseApi' |
|||
]) |
|||
}, |
|||
mounted() { |
|||
this.getCategoryDataTree() |
|||
}, |
|||
methods: { |
|||
getCategoryDataTree() { |
|||
FetchCategoryMenu().then(res => { |
|||
this.categoryMenu = res |
|||
}) |
|||
}, |
|||
filterData(data) { |
|||
return data.filter(node => { |
|||
if (node.children && node.children.length > 0) { |
|||
node.children = this.filterData(node.children) // 递归处理子节点 |
|||
} |
|||
return node.isType !== 3 // 过滤掉isType为3的节点 |
|||
}) |
|||
}, |
|||
// 逆归实现 获取指定元素 |
|||
findNode(tree, func) { |
|||
for (const node of tree) { |
|||
if (func(node)) return node |
|||
if (node.children) { |
|||
const res = this.findNode(node.children, func) |
|||
if (res) return res |
|||
} |
|||
} |
|||
return null |
|||
}, |
|||
// 根据父级展开全部子级 |
|||
expandAllChildren(node, targetElement) { |
|||
node.expanded = true |
|||
// 递归展开当前节点的每个子节点 |
|||
if (node.childNodes && node.childNodes.length > 0) { |
|||
for (let i = 0; i < node.childNodes.length; i++) { |
|||
if (node.childNodes[i].data.id === targetElement.id) { |
|||
this.$refs.categroyTree.setCurrentKey(node.childNodes[i]) |
|||
} |
|||
this.expandAllChildren(node.childNodes[i], targetElement) |
|||
} |
|||
} |
|||
}, |
|||
// 转换函数,将原始数据转换为el-tree所需格式 |
|||
transformData(rawData) { |
|||
return rawData.map(item => { |
|||
return { |
|||
label: item.fondName, |
|||
isType: 0, |
|||
id: item.fondsId, |
|||
fondsNo: item.fondsNo, |
|||
children: item.categoryList.map(category => { |
|||
return { |
|||
label: category.cnName, |
|||
cnName: category.cnName, |
|||
id: category.id, |
|||
arrangeType: category.arrangeType, |
|||
isType: category.isType, |
|||
fondsId: item.fondsId, |
|||
fondName: item.fondName, |
|||
fondsNo: item.fondsNo, |
|||
children: this.transformChildren(category.children, item.fondsId, item.fondName, item.fondsNo) |
|||
} |
|||
}) |
|||
} |
|||
}) |
|||
}, |
|||
// 递归函数,用于处理数据的子节点 |
|||
transformChildren(children, fondsId, fondName, fondsNo) { |
|||
return children.map(child => { |
|||
return { |
|||
label: child.cnName, |
|||
cnName: child.cnName, |
|||
id: child.id, |
|||
isType: child.isType, |
|||
pid: child.pid, |
|||
code: child.code, |
|||
arrangeType: child.arrangeType, |
|||
fondsId: fondsId, |
|||
fondName: fondName, |
|||
fondsNo: fondsNo, |
|||
children: child.children.length ? this.transformChildren(child.children, fondsId, fondName, fondsNo) : [] |
|||
} |
|||
}) |
|||
}, |
|||
// 找顶级节点 |
|||
findTopLevelNode(data, fondsId) { |
|||
for (let i = 0; i < data.length; i++) { |
|||
if (data[i].id === fondsId) { |
|||
return data[i] |
|||
} |
|||
} |
|||
return null |
|||
}, |
|||
[CRUD.HOOK.afterRefresh]() { |
|||
this.crud.data = this.filterData(this.transformData(this.crud.data)) |
|||
this.$nextTick(() => { |
|||
let currentKey |
|||
if (localStorage.getItem('currentArchivesKey') !== null) { |
|||
currentKey = JSON.parse(localStorage.getItem('currentArchivesKey')) |
|||
// 删除门类节点后 |
|||
if (this.$refs.categroyTree.getCurrentKey(currentKey.id) == null) { |
|||
localStorage.removeItem('currentArchivesKey') |
|||
} |
|||
this.topLevelNode = this.findTopLevelNode(this.crud.data, currentKey.fondsId) |
|||
// 如果找到了顶级节点,则从该节点开始递归查找指定元素 |
|||
if (this.topLevelNode) { |
|||
if (currentKey) { |
|||
// 展开顶级节点的子节点 |
|||
if (currentKey.isType === 1) { |
|||
currentKey = this.findNode(this.crud.data[0].children, (node) => { |
|||
return node.isType !== 1 |
|||
}) |
|||
} |
|||
this.expandAllChildren(this.$refs.categroyTree.getNode(this.topLevelNode), currentKey) |
|||
} else { |
|||
this.defaultSetting(currentKey) |
|||
} |
|||
} else { |
|||
this.defaultSetting(currentKey) |
|||
} |
|||
} else { |
|||
this.defaultSetting(currentKey) |
|||
} |
|||
if (currentKey && currentKey.id) { |
|||
this.$nextTick(() => { |
|||
// 选中节点的门类详情 |
|||
this.handleNodeClick(currentKey) |
|||
}) |
|||
} |
|||
}) |
|||
}, |
|||
defaultSetting(currentKey) { |
|||
if (this.crud.data[0].isType === 0) { |
|||
currentKey = this.findNode(this.crud.data[0].children, (node) => { |
|||
return node.isType !== 1 |
|||
}) |
|||
this.expandAllChildren(this.$refs.categroyTree.getNode(this.crud.data[0]), currentKey) |
|||
} else { |
|||
currentKey = this.crud.data[0] |
|||
this.expandAllChildren(this.$refs.categroyTree.getNode(this.crud.data[0]), currentKey) |
|||
} |
|||
}, |
|||
// 选中门类后,设置门类详情数据 |
|||
handleNodeClick(val) { |
|||
if (val) { |
|||
localStorage.setItem('currentArchivesKey', JSON.stringify(val)) |
|||
this.selectedCategory = val |
|||
this.$nextTick(() => { |
|||
this.getFormInfo() |
|||
}) |
|||
} |
|||
}, |
|||
getFormInfo() { |
|||
// console.log('this.selectedCategory.arrangeType', this.selectedCategory.arrangeType) |
|||
// if (this.selectedCategory.arrangeType === 1) { |
|||
// this.collectLevel = 3 |
|||
// } else if (this.selectedCategory.arrangeType === 2) { |
|||
// this.collectLevel = 2 |
|||
// } else { |
|||
// this.collectLevel = 1 |
|||
// } |
|||
const params = { |
|||
'categoryId': this.selectedCategory.id, |
|||
'categoryLevel': this.collectLevel |
|||
comName: function() { |
|||
if (this.activeIndex === 0) { |
|||
return 'running' |
|||
} else if (this.activeIndex === 1) { |
|||
return 'history' |
|||
} |
|||
FetchInitCategoryInputFieldByPid(params).then(data => { |
|||
this.formPreviewData = data |
|||
console.log('formPreviewData', this.formPreviewData) |
|||
this.isDesFormType = 'arcives' |
|||
this.$nextTick(() => { |
|||
this.$refs.previewForm.archivesType = 'add' |
|||
this.$refs.previewForm.FetchNoFormatField(this.selectedCategory.id) |
|||
}) |
|||
}) |
|||
}, |
|||
// 选择附件 |
|||
async changeAiFile(e) { |
|||
// 替换文件时清空 aiJsonData |
|||
this.aiJsonData = null |
|||
this.currentLineIndex = 0 |
|||
this.displayedText = '' |
|||
this.typingFinished = false |
|||
this.typingInterval = null |
|||
// if (this.typingInterval) { |
|||
// clearInterval(this.typingInterval); |
|||
// } |
|||
|
|||
const selectedFiles = Array.from(e.target.files) |
|||
const imageFiles = selectedFiles.filter(file => file.type.startsWith('image/')) |
|||
const nonImageFiles = selectedFiles.filter(file => !file.type.startsWith('image/')) |
|||
|
|||
// 不允许同时选择图片和非图片文件 |
|||
if (imageFiles.length > 0 && nonImageFiles.length > 0) { |
|||
this.$message.error('不能同时选择图片和其他类型文件,请重新选择') |
|||
return |
|||
} |
|||
|
|||
const existingImageFiles = this.fileList.filter(item => item.formatType === 'image') |
|||
const existingNonImageFiles = this.fileList.filter(item => item.formatType !== 'image') |
|||
|
|||
if (imageFiles.length > 0) { |
|||
if (existingImageFiles.length > 0) { |
|||
if (existingImageFiles.length + imageFiles.length > 3) { |
|||
// 若加入新图片会超过 3 张,清空已有图片 |
|||
this.fileList = this.fileList.filter(item => item.formatType !== 'image') |
|||
} |
|||
} else if (existingNonImageFiles.length > 0) { |
|||
// 若已有非图片文件,清空已有非图片文件 |
|||
this.fileList = this.fileList.filter(item => item.formatType === 'image') |
|||
} |
|||
|
|||
// 检查图片文件数量 |
|||
if (imageFiles.length > 3) { |
|||
this.$message.error('图片文件最多只能选择 3 个,请重新选择') |
|||
return |
|||
} |
|||
|
|||
for (const file of imageFiles) { |
|||
// 检查文件是否已存在 |
|||
if (this.fileList.some(item => item.name === file.name)) { |
|||
this.$message.warning(`文件 ${file.name} 已存在,请勿重复上传`) |
|||
continue |
|||
} |
|||
|
|||
const fileInfo = { |
|||
file: file, |
|||
size: file.size, |
|||
formatType: file.type.substring(0, file.type.indexOf('/')), |
|||
name: file.name, |
|||
postfix: file.name.substring( |
|||
file.name.lastIndexOf('.') + 1, |
|||
file.name.length |
|||
), |
|||
px: '' |
|||
} |
|||
|
|||
const fileBase64 = await this.getBase64(file) |
|||
const res = await this.getImgPx(fileBase64) |
|||
fileInfo.px = res.width + 'px*' + res.height + 'px' |
|||
|
|||
this.fileList.push(fileInfo) |
|||
return 'running' |
|||
} |
|||
this.FetchAiFileUplaod(this.fileList) |
|||
} else if (nonImageFiles.length > 0) { |
|||
if (existingNonImageFiles.length > 0) { |
|||
// 若已有非图片文件,直接替换 |
|||
this.fileList = this.fileList.filter(item => item.formatType === 'image') |
|||
} else if (existingImageFiles.length > 0) { |
|||
// 若已有图片文件,清空已有图片文件 |
|||
this.fileList = this.fileList.filter(item => item.formatType !== 'image') |
|||
} |
|||
|
|||
// 检查非图片文件数量 |
|||
if (nonImageFiles.length > 1) { |
|||
this.$message.error('非图片文件最多只能选择 1 个,请重新选择') |
|||
return |
|||
} |
|||
|
|||
for (const file of nonImageFiles) { |
|||
// 检查文件是否已存在 |
|||
if (this.fileList.some(item => item.name === file.name)) { |
|||
this.$message.warning(`文件 ${file.name} 已存在,请勿重复上传`) |
|||
continue |
|||
} |
|||
|
|||
const fileInfo = { |
|||
file: file, |
|||
size: file.size, |
|||
formatType: file.type.substring(0, file.type.indexOf('/')), |
|||
name: file.name, |
|||
postfix: file.name.substring( |
|||
file.name.lastIndexOf('.') + 1, |
|||
file.name.length |
|||
), |
|||
px: '' |
|||
} |
|||
this.fileList.push(fileInfo) |
|||
} |
|||
this.FetchAiFileUplaod(this.fileList) |
|||
} |
|||
}, |
|||
FetchAiFileUplaod(files) { |
|||
this.nowDate = getCurrentTime() |
|||
const promiseArray = files.map(async(item, index) => { |
|||
const json = {} |
|||
json.file_name = item.name |
|||
json.file_size = item.size |
|||
json.file_type = item.postfix |
|||
// json.file_path = this.filePath[index].path |
|||
json.file_path = '' |
|||
json.archive_id = this.arcId |
|||
json.file_dpi = item.px |
|||
json.file_thumbnail = '' |
|||
json.create_time = this.nowDate |
|||
json.id = null |
|||
json.last_modified = item.file.lastModified |
|||
// json.digital_summary = null |
|||
// json.public_key = null |
|||
// json.private_key = null |
|||
return json |
|||
}) |
|||
console.log('promiseArray', promiseArray) |
|||
// 原始得上传文件二进制 |
|||
const fileDefault = files.map(item => item.file) |
|||
|
|||
Promise.all(promiseArray) |
|||
.then((arrayUpload) => { |
|||
// 上传附件 |
|||
archivesUpload(this.baseApi + '/api/collect/uploadAssistEnterFiles', |
|||
fileDefault, |
|||
this.selectedCategory.id, |
|||
this.arcId, |
|||
JSON.stringify(arrayUpload) |
|||
).then(res => { |
|||
if (res.data.code === 200) { |
|||
this.$message({ message: res.data.data, type: 'success', offset: 8 }) |
|||
// this.aiJsonData = res.data; |
|||
// this.startTypingEffect(); |
|||
} else { |
|||
this.$message({ message: '上传附件失败', type: 'error', offset: 8 }) |
|||
} |
|||
// this.handleClose() |
|||
}) |
|||
}) |
|||
.catch((error) => { |
|||
console.error(error) |
|||
}) |
|||
}, |
|||
startTypingEffect() { |
|||
const lines = this.aiJsonData.split('\n') |
|||
this.currentLineIndex = 0 |
|||
this.displayedText = '' |
|||
this.typingFinished = false |
|||
|
|||
this.typingInterval = setInterval(() => { |
|||
if (this.currentLineIndex < lines.length) { |
|||
this.displayedText += lines[this.currentLineIndex] + '\n' |
|||
this.currentLineIndex++ |
|||
} else { |
|||
clearInterval(this.typingInterval) |
|||
|
|||
setTimeout(() => { |
|||
this.typingFinished = true |
|||
if (this.aiJsonData) { |
|||
// this.$refs.previewForm.archivesType = 'add' |
|||
this.$refs.previewForm.addOrUpdateForm = JSON.parse(this.aiJsonData) |
|||
this.aiLoading = false |
|||
} |
|||
}, 1000) |
|||
} |
|||
|
|||
// 滚动条始终保持在底部 |
|||
const container = this.$refs.typingContainer |
|||
if (container) { |
|||
container.scrollTop = container.scrollHeight |
|||
} |
|||
}, 200) |
|||
}, |
|||
saveAiData() { |
|||
this.$router.push({ path: '/collectReorganizi/collectionLibrary' }) |
|||
}, |
|||
// 将上传的图片转为base64 |
|||
getBase64(file) { |
|||
const reader = new FileReader() |
|||
reader.readAsDataURL(file) |
|||
return new Promise((resolve) => { |
|||
reader.onload = () => { |
|||
resolve(reader.result) |
|||
} |
|||
}) |
|||
}, |
|||
// 获取图片的分辨率 |
|||
getImgPx(img) { |
|||
const image = new Image() |
|||
image.src = img |
|||
return new Promise((resolve) => { |
|||
image.onload = () => { |
|||
const width = image.width |
|||
const height = image.height |
|||
resolve({ width, height }) |
|||
} |
|||
}) |
|||
methods: { |
|||
changeActiveTab(data) { |
|||
this.activeIndex = data |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
@import "~@/assets/styles/mixin.scss"; |
|||
@import "~@/assets/styles/variables.scss"; |
|||
.category-container { |
|||
.elect-cont-left{ |
|||
width: 296px !important; |
|||
} |
|||
} |
|||
.openSidebar .category-container .elect-cont-right{ |
|||
width: calc(100vw - 614px) !important; |
|||
} |
|||
.hideSidebar .category-container .elect-cont-right { |
|||
width: calc(100vw - 412px) !important; |
|||
} |
|||
.tree-scroll{ |
|||
font-size: 14px; |
|||
} |
|||
.ai-category-main{ |
|||
display: flex; |
|||
justify-content: flex-start; |
|||
margin-top: 10px; |
|||
|
|||
.ai-des-left{ |
|||
width: 740px; |
|||
margin-right: 20px; |
|||
} |
|||
.ai-des-right{ |
|||
flex: 1; |
|||
} |
|||
|
|||
.ai-des-form{ |
|||
height: calc(100vh - 262px); |
|||
overflow-y: auto; |
|||
padding-right: 10px; |
|||
} |
|||
|
|||
.ai-des-json{ |
|||
height: calc(100vh - 300px); |
|||
margin-bottom: 10px; |
|||
} |
|||
} |
|||
|
|||
.prearch-upload{ |
|||
margin-right: 0 !important; |
|||
::v-deep .el-form-item__label{ |
|||
position: relative; |
|||
&::before{ |
|||
position: absolute; |
|||
top: -2px; |
|||
right: 70px; |
|||
content: "*"; |
|||
font-size: 10px; |
|||
color: #ff4949; |
|||
font-style: normal; |
|||
} |
|||
} |
|||
::v-deep .el-form-item__content{ |
|||
position: relative; |
|||
width: 540px !important; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
.input-style{ |
|||
width: 500px; |
|||
height: 34px; |
|||
line-height: 34px; |
|||
padding: 0 20px; |
|||
border: 1px solid #e6e8ed; |
|||
border-radius: 3px; |
|||
// &.error-box{ |
|||
// border-color: #ed4a41; |
|||
// } |
|||
} |
|||
// .error-tip{ |
|||
// position: absolute; |
|||
// left: 0; |
|||
// bottom: -26px; |
|||
// font-size: 12px; |
|||
// color: #ff4949; |
|||
// } |
|||
.upload-btn{ |
|||
position: relative; |
|||
width:96px; |
|||
margin-right: 0 !important; |
|||
margin-left: 10px; |
|||
overflow: initial !important; |
|||
#upFile{ |
|||
position: absolute; |
|||
left: 0; |
|||
top: 0; |
|||
// opacity: 0; |
|||
width: 84px; |
|||
height: 34px; |
|||
} |
|||
.el-button{ |
|||
margin-top: -2px; |
|||
font-weight: bold; |
|||
border-color: #0348f3; |
|||
color: #0348f3; |
|||
} |
|||
} |
|||
} |
|||
[data-theme=dark] .tab-content{ |
|||
height: calc(100vh - 200px) !important; |
|||
} |
|||
pre { |
|||
background-color: #f4f4f4; |
|||
padding: 10px; |
|||
border: 1px solid #ccc; |
|||
border-radius: 4px; |
|||
white-space: pre-wrap; |
|||
word-wrap: break-word; |
|||
height: calc(100vh - 300px); |
|||
overflow-y: auto; |
|||
[data-theme=light] .tab-content{ |
|||
height: calc(100vh - 179px); |
|||
} |
|||
</style> |
@ -0,0 +1,387 @@ |
|||
<template> |
|||
<div> |
|||
<!--工具栏--> |
|||
<div class="head-container" style="text-align: right;"> |
|||
<el-button v-if="!isHistroy" size="mini" @click="uploadVisible=true"> |
|||
<i class="iconfont icon-shangchuan" /> |
|||
AI著录文件上传 |
|||
</el-button> |
|||
</div> |
|||
<!--表格渲染--> |
|||
<el-table |
|||
ref="table" |
|||
v-loading="crud.loading" |
|||
:data="crud.data" |
|||
row-key="id" |
|||
@select="crud.selectChange" |
|||
@select-all="crud.selectAllChange" |
|||
@cell-dblclick="tableDoubleClick" |
|||
@selection-change="crud.selectionChangeHandler" |
|||
> |
|||
<el-table-column label="文件id" prop="id" /> |
|||
<!-- <el-table-column label="结束时间" prop="endTime" align="center" min-width="160"> |
|||
<template slot-scope="scope"> |
|||
<div v-if="scope.row.endTime">{{ scope.row.endTime | parseTime }}</div> |
|||
<div v-else>-</div> |
|||
</template> |
|||
</el-table-column> --> |
|||
<el-table-column label="解析状态" prop="status" align="center" width="140"> |
|||
<template slot-scope="scope"> |
|||
<div v-if="!isHistroy"> |
|||
<span class="row-state soon-state">解析中</span> |
|||
<!-- <span class="row-state end-state">解析完成</span> --> |
|||
</div> |
|||
<div v-else> |
|||
<span v-if="scope.row.endTime" class="row-state end-state">已处理</span> |
|||
</div> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="操作" prop="status" align="center" width="140"> |
|||
<!-- slot-scope="scope" --> |
|||
<template> |
|||
<!-- v-if="scope.row.status === 1" --> |
|||
<el-button size="mini" class="check-btn" style="padding: 5px;"> |
|||
<i class="iconfont icon-tianjiawenjian" /> |
|||
新增档案 |
|||
</el-button> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
<!--分页组件--> |
|||
<pagination v-if="crud.data.length!==0" /> |
|||
<Detail ref="aiCatalogingFile" :is-histroy="isHistroy" /> |
|||
|
|||
<el-dialog class="fileUpload-dialog" title="文件上传" :close-on-click-modal="false" :modal-append-to-body="false" append-to-body :visible.sync="uploadVisible"> |
|||
<div class="setting-dialog"> |
|||
<div class="upload-container"> |
|||
<i v-if="fileList.length === 0" class="iconfont icon-tianjiawenjian upload-icon" /> |
|||
<div v-for="item in fileList" :key="item.name" class="file-list"> |
|||
<i class="iconfont icon-xiaowenjian" /> |
|||
{{ item.name }} |
|||
<i class="el-icon-close" @click="deleteFile(item)" /> |
|||
</div> |
|||
<div class="upload-input"> |
|||
<input ref="fileInput" type="file" multiple @change="changeAiFile"> |
|||
<div class="upload-zip"><i class="iconfont icon-shangchuan2" />点击上传</div> |
|||
</div> |
|||
</div> |
|||
<div slot="footer" class="dialog-footer"> |
|||
<el-button type="text" @click="uploadVisible = false">取消</el-button> |
|||
<el-button :loading="aiLoading" type="primary" @click="handleUploadConfirm">保存</el-button> |
|||
</div> |
|||
</div> |
|||
</el-dialog> |
|||
|
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import CRUD, { presenter, header, crud } from '@crud/crud' |
|||
import pagination from '@crud/Pagination' |
|||
import Detail from './module/detail' |
|||
|
|||
import { archivesUpload } from '@/utils/upload' |
|||
import { getCurrentTime } from '@/utils/index' |
|||
import { mapGetters } from 'vuex' |
|||
import { FetchAIResultZhulu } from '@/api/collect/collect' |
|||
|
|||
export default { |
|||
name: 'Running', |
|||
components: { pagination, Detail }, |
|||
cruds() { |
|||
return CRUD({ title: '处理中', url: 'api/flowable/getFlowList', crudMethod: {}, |
|||
optShow: { |
|||
add: false, |
|||
edit: false, |
|||
del: false, |
|||
reset: false, |
|||
download: false, |
|||
group: false |
|||
}}) |
|||
}, |
|||
mixins: [presenter(), header(), crud()], |
|||
|
|||
props: { |
|||
isHistroy: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
aiLoading: false, |
|||
permission: {}, |
|||
uploadVisible: false, |
|||
nowDate: '', |
|||
fileList: [], |
|||
aiJsonData: null, |
|||
displayedText: '', |
|||
typingInterval: null, |
|||
typingFinished: false, |
|||
currentLineIndex: 0, |
|||
shouldContinueFetching: true |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapGetters([ |
|||
'baseApi' |
|||
]) |
|||
}, |
|||
mounted() { |
|||
}, |
|||
methods: { |
|||
[CRUD.HOOK.beforeRefresh]() { |
|||
if (this.isHistroy) { |
|||
this.crud.query.isEnd = true |
|||
} else { |
|||
this.crud.query.isEnd = false |
|||
} |
|||
}, |
|||
// table - 双击查看详情 |
|||
tableDoubleClick(row) { |
|||
this.$refs.aiCatalogingFile.detailVisible = true |
|||
this.$refs.aiCatalogingFile.getFileList() |
|||
}, |
|||
async changeAiFile(e) { |
|||
// 替换文件时清空 aiJsonData |
|||
this.aiJsonData = null |
|||
this.currentLineIndex = 0 |
|||
this.displayedText = '' |
|||
this.typingFinished = false |
|||
this.typingInterval = null |
|||
// if (this.typingInterval) { |
|||
// clearInterval(this.typingInterval); |
|||
// } |
|||
|
|||
const selectedFiles = Array.from(e.target.files) |
|||
const imageFiles = selectedFiles.filter(file => file.type.startsWith('image/')) |
|||
const nonImageFiles = selectedFiles.filter(file => !file.type.startsWith('image/')) |
|||
|
|||
// 不允许同时选择图片和非图片文件 |
|||
if (imageFiles.length > 0 && nonImageFiles.length > 0) { |
|||
this.$message.error('不能同时选择图片和其他类型文件,请重新选择') |
|||
return |
|||
} |
|||
|
|||
const existingImageFiles = this.fileList.filter(item => item.formatType === 'image') |
|||
const existingNonImageFiles = this.fileList.filter(item => item.formatType !== 'image') |
|||
|
|||
if (imageFiles.length > 0) { |
|||
// if (existingImageFiles.length > 0) { |
|||
// if (existingImageFiles.length + imageFiles.length > 3) { |
|||
// // 若加入新图片会超过 3 张,清空已有图片 |
|||
// this.fileList = this.fileList.filter(item => item.formatType !== 'image') |
|||
// } |
|||
// } else if (existingNonImageFiles.length > 0) { |
|||
// // 若已有非图片文件,清空已有非图片文件 |
|||
// this.fileList = this.fileList.filter(item => item.formatType === 'image') |
|||
// } |
|||
|
|||
// // 检查图片文件数量 |
|||
// if (imageFiles.length > 3) { |
|||
// this.$message.error('图片文件最多只能选择 3 个,请重新选择') |
|||
// return |
|||
// } |
|||
if (existingNonImageFiles.length > 0) { |
|||
// 若已有非图片文件,清空已有非图片文件 |
|||
this.fileList = this.fileList.filter(item => item.formatType === 'image') |
|||
} |
|||
|
|||
for (const file of imageFiles) { |
|||
// 检查文件是否已存在 |
|||
if (this.fileList.some(item => item.name === file.name)) { |
|||
this.$message.warning(`文件 ${file.name} 已存在,请勿重复上传`) |
|||
continue |
|||
} |
|||
|
|||
const fileInfo = { |
|||
file: file, |
|||
size: file.size, |
|||
formatType: file.type.substring(0, file.type.indexOf('/')), |
|||
name: file.name, |
|||
postfix: file.name.substring( |
|||
file.name.lastIndexOf('.') + 1, |
|||
file.name.length |
|||
), |
|||
px: '' |
|||
} |
|||
|
|||
const fileBase64 = await this.getBase64(file) |
|||
const res = await this.getImgPx(fileBase64) |
|||
fileInfo.px = res.width + 'px*' + res.height + 'px' |
|||
|
|||
this.fileList.push(fileInfo) |
|||
} |
|||
this.FetchAiFileUplaod(this.fileList) |
|||
} else if (nonImageFiles.length > 0) { |
|||
if (existingNonImageFiles.length > 0) { |
|||
// 若已有非图片文件,直接替换 |
|||
this.fileList = this.fileList.filter(item => item.formatType === 'image') |
|||
} else if (existingImageFiles.length > 0) { |
|||
// 若已有图片文件,清空已有图片文件 |
|||
this.fileList = this.fileList.filter(item => item.formatType !== 'image') |
|||
} |
|||
|
|||
// 检查非图片文件数量 |
|||
if (nonImageFiles.length > 1) { |
|||
this.$message.error('非图片文件最多只能选择 1 个,请重新选择') |
|||
return |
|||
} |
|||
|
|||
for (const file of nonImageFiles) { |
|||
// 检查文件是否已存在 |
|||
if (this.fileList.some(item => item.name === file.name)) { |
|||
this.$message.warning(`文件 ${file.name} 已存在,请勿重复上传`) |
|||
continue |
|||
} |
|||
|
|||
const fileInfo = { |
|||
file: file, |
|||
size: file.size, |
|||
formatType: file.type.substring(0, file.type.indexOf('/')), |
|||
name: file.name, |
|||
postfix: file.name.substring( |
|||
file.name.lastIndexOf('.') + 1, |
|||
file.name.length |
|||
), |
|||
px: '' |
|||
} |
|||
this.fileList.push(fileInfo) |
|||
} |
|||
} |
|||
}, |
|||
handleUploadConfirm() { |
|||
this.FetchAiFileUplaod(this.fileList) |
|||
}, |
|||
FetchAiFileUplaod(files) { |
|||
this.aiLoading = true |
|||
this.nowDate = getCurrentTime() |
|||
const promiseArray = files.map(async(item, index) => { |
|||
const json = {} |
|||
json.file_name = item.name |
|||
json.file_size = item.size |
|||
json.file_type = item.postfix |
|||
json.file_path = '' |
|||
json.archive_id = null |
|||
json.file_dpi = item.px |
|||
json.file_thumbnail = '' |
|||
json.create_time = this.nowDate |
|||
json.id = null |
|||
json.last_modified = item.file.lastModified |
|||
return json |
|||
}) |
|||
console.log('promiseArray', promiseArray) |
|||
const fileDefault = files.map(item => item.file) |
|||
// this.selectedCategory.id, |
|||
// this.arcId, |
|||
Promise.all(promiseArray) |
|||
.then((arrayUpload) => { |
|||
archivesUpload(this.baseApi + '/api/collect/uploadAssistEnterFiles', |
|||
fileDefault, |
|||
JSON.stringify(arrayUpload) |
|||
).then(res => { |
|||
if (res.data.data !== null) { |
|||
const params = { |
|||
'code': res.data.data.code, |
|||
'id': res.data.data.id |
|||
} |
|||
const fetchAiZhuluResult = () => { |
|||
// 检查是否应该继续请求 |
|||
if (!this.shouldContinueFetching) return |
|||
|
|||
FetchAIResultZhulu(params).then((res) => { |
|||
const data = JSON.parse(res) |
|||
console.log('data', data) |
|||
if (data.result !== null && data.status === 'success') { |
|||
this.$message({ message: '解析著录附件成功', type: 'success', offset: 8 }) |
|||
this.aiJsonData = data.result |
|||
this.startTypingEffect() |
|||
} else { |
|||
setTimeout(fetchAiZhuluResult, 3000) |
|||
} |
|||
}).catch(err => { |
|||
console.log(err) |
|||
}) |
|||
} |
|||
fetchAiZhuluResult() |
|||
} else { |
|||
this.$message({ message: '著录附件传输失败', type: 'error', offset: 8 }) |
|||
} |
|||
}) |
|||
}) |
|||
.catch((error) => { |
|||
console.error(error) |
|||
}) |
|||
}, |
|||
startTypingEffect() { |
|||
const lines = this.aiJsonData.split('\n') |
|||
this.currentLineIndex = 0 |
|||
this.displayedText = '' |
|||
this.typingFinished = false |
|||
|
|||
this.typingInterval = setInterval(() => { |
|||
if (this.currentLineIndex < lines.length) { |
|||
this.displayedText += lines[this.currentLineIndex] + '\n' |
|||
this.currentLineIndex++ |
|||
} else { |
|||
clearInterval(this.typingInterval) |
|||
|
|||
setTimeout(() => { |
|||
this.typingFinished = true |
|||
if (this.aiJsonData) { |
|||
// this.$refs.previewForm.archivesType = 'add' |
|||
// this.$refs.previewForm.addOrUpdateForm = JSON.parse(this.aiJsonData) |
|||
this.aiLoading = false |
|||
} |
|||
}, 1000) |
|||
} |
|||
|
|||
// 滚动条始终保持在底部 |
|||
const container = this.$refs.typingContainer |
|||
if (container) { |
|||
container.scrollTop = container.scrollHeight |
|||
} |
|||
}, 200) |
|||
}, |
|||
// 附件旁边的X |
|||
deleteFile(file) { |
|||
const index = this.fileList.indexOf(file) |
|||
this.fileList.splice(index, 1) |
|||
if (this.fileList.length !== 0) { |
|||
// archivesUpload(this.baseApi + '/api/collect/uploadFiles', this.fileList, this.selectedCategory.id).then(res => { |
|||
// if (res.data.code === 200) { |
|||
// this.filePath = res.data.data |
|||
// } |
|||
// }) |
|||
} else { |
|||
this.$message({ message: '已清空所有要上传的附件', offset: 8 }) |
|||
} |
|||
}, |
|||
// 将上传的图片转为base64 |
|||
getBase64(file) { |
|||
const reader = new FileReader() |
|||
reader.readAsDataURL(file) |
|||
return new Promise((resolve) => { |
|||
reader.onload = () => { |
|||
resolve(reader.result) |
|||
} |
|||
}) |
|||
}, |
|||
// 获取图片的分辨率 |
|||
getImgPx(img) { |
|||
const image = new Image() |
|||
image.src = img |
|||
return new Promise((resolve) => { |
|||
image.onload = () => { |
|||
const width = image.width |
|||
const height = image.height |
|||
resolve({ width, height }) |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
</style> |
@ -0,0 +1,93 @@ |
|||
<template> |
|||
<el-dialog class="detail-dialog" :visible.sync="detailVisible" :close-on-click-modal="false" :modal-append-to-body="false" append-to-body title="AI著录文件"> |
|||
<div class="setting-dialog"> |
|||
<el-table |
|||
ref="table" |
|||
:data="tableData" |
|||
style="min-width: 100%" |
|||
height="calc(100vh - 382px)" |
|||
> |
|||
<el-table-column type="selection" width="55" align="center" /> |
|||
<el-table-column type="index" label="序号" width="55" align="center" /> |
|||
<el-table-column prop="file_name" label="文件名称" show-overflow-tooltip min-width="140" /> |
|||
<el-table-column prop="file_type" label="格式" min-width="60" align="center" /> |
|||
<el-table-column prop="file_size" label="大小" min-width="85" align="center"> |
|||
<template slot-scope="scope"> |
|||
{{ getFileSize(scope.row.file_size) }} |
|||
</template> |
|||
</el-table-column> |
|||
<!-- <el-table-column prop="file_dpi" label="分辨率" min-width="120" align="center"> |
|||
<template slot-scope="scope"> |
|||
<div v-if="!scope.row.file_dpi || scope.row.file_dpi === 'null'"> - </div> |
|||
<div v-else> {{ scope.row.file_dpi }} </div> |
|||
</template> |
|||
</el-table-column> --> |
|||
<el-table-column prop="file_thumbnail" label="缩略图" min-width="60" align="center"> |
|||
<template slot-scope="scope"> |
|||
<div v-if="scope.row.file_type === 'jpg' || scope.row.file_type === 'jpeg' || scope.row.file_type === 'png' || scope.row.file_type === 'bmp'|| scope.row.file_type === 'gif'"> |
|||
<i class="fileIcon icon-image" /> |
|||
</div> |
|||
<div v-else-if="scope.row.file_type === 'xlsx' || scope.row.file_type === 'xls'"> |
|||
<i class="fileIcon icon-excel" /> |
|||
</div> |
|||
<div v-else-if="scope.row.file_type === 'docx' || scope.row.file_type === 'doc'"> |
|||
<i class="fileIcon icon-word" /> |
|||
</div> |
|||
<div v-else-if="scope.row.file_type === 'pdf'"> |
|||
<i class="fileIcon icon-pdf" /> |
|||
</div> |
|||
<div v-else-if="scope.row.file_type === 'ppt' || scope.row.file_type === 'pptx'"> |
|||
<i class="fileIcon icon-ppt" /> |
|||
</div> |
|||
<div v-else-if="scope.row.file_type === 'zip' || scope.row.file_type === 'rar'"> |
|||
<i class="fileIcon icon-zip" /> |
|||
</div> |
|||
<div v-else-if="scope.row.file_type === 'txt'"> |
|||
<i class="fileIcon icon-txt" /> |
|||
</div> |
|||
<div v-else> |
|||
<i class="fileIcon icon-other" /> |
|||
</div> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column prop="create_time" label="创建时间" min-width="130" align="center" /> |
|||
</el-table> |
|||
<div slot="footer" class="dialog-footer"> |
|||
<el-button type="primary" @click="detailVisible=false">确定</el-button> |
|||
</div> |
|||
</div> |
|||
</el-dialog> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'Detail', |
|||
components: { |
|||
}, |
|||
props: { |
|||
isHistroy: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
tableData: [], |
|||
detailVisible: false |
|||
} |
|||
}, |
|||
computed: { |
|||
}, |
|||
mounted() { |
|||
|
|||
}, |
|||
methods: { |
|||
getFileList() { |
|||
console.log('附件列表') |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang='scss' scoped> |
|||
</style> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue