Browse Source

AI知识图谱/文件上传bug修复

master
xuhuajiao 4 months ago
parent
commit
3c461b0f54
  1. 11
      src/views/AIAssistant/AIDigitalHuman/index.vue
  2. 294
      src/views/AIAssistant/AIIntelligentCoding/aiForm copy.vue
  3. 155
      src/views/AIAssistant/AIIntelligentCoding/aiForm.vue
  4. 262
      src/views/AIAssistant/AIIntelligentCoding/index.vue
  5. 35
      src/views/AIAssistant/AIknowledgeGraph/index.vue
  6. 7
      src/views/collectReorganizi/collectionLibrary/module/uploadOriginal/bigUpload.vue
  7. 185
      src/views/collectReorganizi/collectionLibrary/module/uploadOriginal/index.vue
  8. 180
      src/views/components/echarts/graph.vue

11
src/views/AIAssistant/AIDigitalHuman/index.vue

@ -29,14 +29,14 @@
</div>
</div>
<div class="chat-send">
<el-input v-model="inputMessage" type="textarea" placeholder="请输入你想咨询的问题" @input="updateSendButtonState" @keyup.enter="sendMessage" />
<el-input v-model="inputMessage" type="textarea" :autosize="{ minRows:4 , maxRows: 4 }" placeholder="请输入你想咨询的问题" @input="updateSendButtonState" @keyup.enter="sendMessage" />
<div class="send-button-container">
<div class="hot-word">
<span v-for="(word, index) in hotWords" :key="index" @click="handleHotWordClick(word)">{{ word }}</span>
</div>
<div style="display: flex; justify-content: flex-end; align-items: center;">
<div :class="isModelDeepseek ? 'model-select' : 'model-select active'" @click="selectModel"><i class="iconfont icon-shendusikao" />深度思考</div>
<span class="send-button" :class="{ 'send-disabled-button': isSendButtonDisabled }" :disabled="isSendButtonDisabled" @click="sendMessage">发送</span>
<span class="send-button" :class="{ 'send-disabled-button': isSendButtonDisabled }" :disabled="isSendBuonDisabled" @click="sendMessage">发送</span>
</div>
</div>
</div>
@ -244,7 +244,8 @@ export default {
font-size: 16px;
line-height: 24px;
border: none;
padding: 20px;
padding: 0 20px;
margin: 20px 0;
color: #fff;
background-color: transparent;
}
@ -464,4 +465,8 @@ export default {
border-radius: 12px;
padding: 0 !important;
}
::v-deep .el-textarea__inner {
resize: none;
border: none;
}
</style>

294
src/views/AIAssistant/AIIntelligentCoding/aiForm copy.vue

@ -1,294 +0,0 @@
<template>
<el-row :gutter="12" class="demo-radius">
<div
class="radius"
:style="{
borderRadius: 'base'
}"
>
<div id="messgebox" ref="scrollDiv" class="messge">
<ul>
<li
v-for="(item, index) in message"
:key="index"
style="list-style-type:none;"
>
<div
v-if="item.user == username"
class="mymsginfo"
style="float:right"
>
<div>
<el-avatar
style="float: right;margin-right: 30px;background: #01bd7e;"
>
<!-- {{ item.user.substring(0, 2) }} -->
<img :alt="item.user.substring(0, 2)" :src="userphoto">
</el-avatar>
</div>
<div
style="float: right;margin-right: 10px;margin-top:10px;width:80%;text-align: right;"
>
{{ item.msg }}
</div>
</div>
<div v-else class="chatmsginfo">
<div>
<el-avatar style="float: left;margin-right: 10px;">
{{ item.user }}
</el-avatar>
</div>
<div style="float: left;margin-top:10px;width:80%;">
<img
v-if="item.msg == ''"
alt="loading"
class="loading"
src=""
>
<MdPreview
style="margin-top:-20px;"
:auto-fold-threshold="9999"
:editor-id="id"
:model-value="item.msg + item.dot"
/>
<!-- {{ item.msg }} -->
</div>
</div>
</li>
</ul>
</div>
<div class="inputmsg">
<el-form :model="form">
<el-form-item>
<el-avatar
style="float: left;background: #01bd7e;margin-bottom: -44px;margin-left: 4px;z-index: 999;width: 30px;height: 30px;"
>
<img alt="jin" :src="userphoto">
</el-avatar>
<el-input
id="txt_suiwen"
v-model="form.desc"
:prefix-icon="userphoto"
resize="none"
autofocus="true"
:autosize="{ minRows: 1, maxRows: 2 }"
placeholder="说说你想问点啥....按Enter键可直接发送"
type="textarea"
@keydown.enter.native.prevent="startStreaming($event)"
/>
</el-form-item>
</el-form>
</div>
</div>
</el-row>
</template>
<script setup>
// import { MdPreview, MdCatalog } from 'md-editor-v3'
// import 'md-editor-v3/lib/preview.css'
// const id = 'preview-only'
</script>
<script>
export default {
data() {
return {
form: {
desc: ""
},
message: [],
username: sessionStorage.name,
userphoto: sessionStorage.photo,
loadingtype: false,
controller: null,
arequestData: {
model: "qwen2", //"llama3.1",
messages: []
}
};
},
mounted() {},
methods: {
scrollToBottom() {
let elscroll = this.$refs["scrollDiv"];
elscroll.scrollTop = elscroll.scrollHeight + 30;
},
clearForm(formName) {
this.form.desc = "";
},
async startStreaming(e) {
if (e.ctrkey && e.keyCode == 13) {
this.form.desc += "\n";
}
document.getElementById("txt_suiwen").disabled = "true";
//
if (this.controller) {
this.controller.abort();
}
setTimeout(() => {
this.scrollToBottom();
}, 50);
var mymsg = this.form.desc.trim();
if (mymsg.length > 0) {
this.form.desc = "";
this.message.push({
user: this.username,
msg: mymsg
});
this.message.push({
user: "GPT",
msg: "",
dot: ""
});
// AbortController
this.controller = new AbortController();
const signal = this.controller.signal;
this.arequestData.messages.push({ role: "user", content: mymsg });
try {
const response = await fetch("http://127.0.0.1:11434/api/chat", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(this.arequestData),
signal
});
if (!response.body) {
this.message[this.message.length - 1].msg =
"ReadableStream not yet supported in this browser.";
throw new Error(
"ReadableStream not yet supported in this browser."
);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let result = "";
this.message[this.message.length - 1].dot = "⚪";
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
result += decoder.decode(value, { stream: true });
// JSON
const jsonChunks = result.split("\n").filter(line => line.trim());
//console.log(result)
for (const chunk of jsonChunks) {
try {
const data = JSON.parse(chunk);
//console.log(data.message.content)
this.message[this.message.length - 1].msg +=
data.message.content;
setTimeout(() => {
this.scrollToBottom();
}, 50);
} catch (e) {
//this.message[this.message.length-1].msg=e;
// JSON
//console.error('Failed to parse JSON:', e);
}
}
// result 便
result = "";
}
} catch (error) {
if (error.name === "AbortError") {
console.log("Stream aborted");
this.message[this.message.length - 1].msg = "Stream aborted";
} else {
console.error("Streaming error:", error);
this.message[this.message.length - 1].msg = "Stream error" + error;
}
}
this.message[this.message.length - 1].dot = "";
this.arequestData.messages.push({
role: "assistant", //this.message[this.message.length-1].user,//"GPT",
content: this.message[this.message.length - 1].msg
});
setTimeout(() => {
this.scrollToBottom();
}, 50);
} else {
this.form.desc = "";
}
document.getElementById("txt_suiwen").disabled = "";
document.getElementById("txt_suiwen").focus();
}
},
beforeDestroy() {
//
if (this.controller) {
this.controller.abort();
}
}
};
</script>
<style scoped>
.radius {
margin: 0 auto;
}
.demo-radius .title {
color: var(--el-text-color-regular);
font-size: 18px;
margin: 10px 0;
}
.demo-radius .value {
color: var(--el-text-color-primary);
font-size: 16px;
margin: 10px 0;
}
.demo-radius .radius {
min-height: 580px;
height: 85vh;
width: 70%;
border: 1px solid var(--el-border-color);
border-radius: 14px;
margin-top: 10px;
}
.messge {
width: 96%;
height: 84%;
/* border:1px solid red; */
margin: 6px auto;
overflow: hidden;
overflow-y: auto;
}
.inputmsg {
width: 96%;
height: 12%;
/* border:1px solid blue; */
border-top: 2px solid #ccc;
margin: 4px auto;
padding-top: 10px;
}
.mymsginfo {
width: 100%;
height: auto;
min-height: 50px;
}
::-webkit-scrollbar {
width: 6px;
height: 5px;
}
::-webkit-scrollbar-track {
background-color: rgba(0, 0, 0, 0.2);
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.5);
border-radius: 10px;
}
::-webkit-scrollbar-button {
background-color: #7c2929;
height: 0;
width: 0px;
}
::-webkit-scrollbar-corner {
background-color: black;
}
</style>
<style>
.el-textarea__inner {
padding-left: 45px;
padding-top: 0.75rem;
padding-bottom: 0.75rem;
}
</style>

155
src/views/AIAssistant/AIIntelligentCoding/aiForm.vue

@ -14,11 +14,11 @@
<el-option v-for="item in options" :key="item.id" :label="item.dictionaryName" :value="item.dictionaryName" />
</el-select>
</el-form-item>
<el-button class="check-btn" :loading="crud.status.cu === 2" type="primary" @click="handleComfiredEditing">保存</el-button>
</el-row>
<el-button class="check-btn" :loading="crud.status.cu === 2" type="primary" @click="handleComfiredEditing">保存</el-button>
</el-form>
<div class="content-container">
<div class="left-panel" :class="{ 'collapsed': isCollapsed }">
<div v-if="formType===1" class="left-panel" :class="{ 'collapsed': isCollapsed }">
<div class="ai-talk">
<div ref="talkContent" class="ai-talk-content">
<div v-for="(item, index) in message" :key="index" class="message">
@ -49,7 +49,7 @@
</div>
<div class="ai-talk-form">
<!-- 选择的文件 -->
<div class="upload-use-file">
<div class="upload-use-file" :class="{ 'animate-after': isUploading }" :style="{ '--animation-duration': animationDuration + 's' }">
<div v-if="fileName" class="file-content">
<span class="delt-file" @click="deleteFile"> <i class="el-icon-close" /></span>
<div v-if="fileType === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || fileType === 'application/vnd.ms-excel'">
@ -73,7 +73,14 @@
<div v-else>
<i class="fileIcon icon-other" />
</div>
<p>{{ fileName }}</p>
<div class="file-right-info">
<p>{{ fileName }}</p>
<span v-if="isUploading">解析中...</span>
<div v-else class="file-info">
<span>600KB</span>
<span>约2.9万字</span>
</div>
</div>
</div>
</div>
<el-input
@ -85,7 +92,7 @@
/>
<div class="ai-form-btn">
<div style="position: relative;">
<input type="file" style="display: block; width: 40px; position: absolute; top: 0; left: 0; opacity: 0;" @change="handleFileChange">
<input ref="fileInput" type="file" style="display: block; width: 40px; position: absolute; top: 0; left: 0; opacity: 0;" @change="handleFileChange">
<i class="iconfont icon-attachment" />
</div>
<span class="line" />
@ -94,14 +101,14 @@
</div>
</div>
</div>
<div class="toggle-btn" @click="toggleCollapse">
<div v-if="formType===1" class="toggle-btn" @click="toggleCollapse">
<span class="closed-btn" />
</div>
<div class="right-panel">
<mavon-editor
:value="editorContent"
:subfield="true"
:default-open="['edit', 'preview']"
:default-open="'edit,preview'"
:editable="true"
:ishljs="true"
/>
@ -129,6 +136,7 @@ export default {
data() {
return {
formTitle: '',
formType: 1,
formVisible: false,
editorRef: 'test',
editorContent: '',
@ -140,16 +148,17 @@ export default {
},
isCollapsed: false,
inputValue: '',
imageUrl: '',
fileName: '',
isImage: false,
fileType: '',
message: [],
controller: null,
arequestData: {
model: 'deepseek-r1:14b',
messages: []
}
},
isUploading: false,
progress: 0,
animationDuration: 0
}
},
computed: {
@ -191,11 +200,42 @@ export default {
}
this.fileType = file.type
this.isImage = false
this.fileName = file.name
this.imageUrl = ''
this.isUploading = true
this.progress = 0
//
// const startTime = Date.now()
// const formData = new FormData()
// formData.append('file', file)
// try {
// //
// const response = await axios.post('/upload', formData, {
// headers: {
// 'Content-Type': 'multipart/form-data'
// }
// })
// const endTime = Date.now()
// const uploadTime = (endTime - startTime) / 1000
// this.animationDuration = uploadTime
// //
// this.$message.success('')
// } catch (error) {
// //
// this.$message.error('')
// } finally {
// this.isUploading = false
// }
const uploadTime = 2
this.animationDuration = uploadTime
setTimeout(() => {
this.isUploading = false
}, uploadTime * 1000)
}
},
async handleMessage(e) {
@ -326,10 +366,12 @@ export default {
this.editorContent = talkContent
},
deleteFile() {
this.imageUrl = ''
this.fileName = ''
this.fileType = ''
this.isImage = false
// input type="file"
if (this.$refs.fileInput) {
this.$refs.fileInput.value = ''
}
},
handleCloseForm() {
this.inputValue = ''
@ -341,7 +383,32 @@ export default {
this.$refs.form.validate(valid => {
if (valid) {
console.log('保存数据', this.form)
this.handleCloseForm()
this.$confirm('此操作将发布当前编研主题' + '<span>你是否还要继续?</span>', '提示', {
confirmButtonText: '继续',
cancelButtonText: '取消',
type: 'warning',
dangerouslyUseHTMLString: true
}).then(() => {
// const ids = data.map(item => item.id)
// const params = {
// 'ids': ids,
// 'operator': this.user.username
// }
// crudEditing.del(params).then((res) => {
// console.log(res)
// if (res.code !== 500) {
// this.$message({ message: '', type: 'success', offset: 8 })
// } else {
// this.$message({ message: res.message, type: 'error', offset: 8 })
// }
// this.initData()
// }).catch(err => {
// console.log(err)
// })
this.handleCloseForm()
}).catch(() => {
})
}
})
},
@ -459,10 +526,22 @@ export default {
}
}
.upload-use-file{
position: relative;
width: 200px;
background-color: rgba(0,0,0,.04);
border-radius: 6px;
margin-bottom: 10px;
&::after{
content: '';
position: absolute;
top: 0;
left: 0;
width: 0;
height: 100%;
background-color: rgba(0,0,0,.06);
border-radius: 6px;
}
img{
display: block;
width: 60px;
@ -478,8 +557,28 @@ export default {
font-size: 12px;
padding: 10px;
cursor: pointer;
p{
.file-right-info{
margin-left: 10px;
line-height: 26px;
p{
font-size: 14px;
color: #000;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
span{
color: rgba(0,0,0,.3);
}
.file-info{
span{
font-size: 12px;
padding-right: 4px;
}
}
}
.delt-file{
display: none;
@ -514,7 +613,6 @@ export default {
padding: 0 !important;
resize: none;
border: none;
}
.ai-form-btn{
display: flex;
@ -542,7 +640,11 @@ export default {
::-webkit-scrollbar-thumb {
background-color:rgba(0,0,0,.1);
}
}
::v-deep .v-note-wrapper{
width: 100% !important;
min-height: auto !important;
}
}
.get-content-style{
@ -564,4 +666,23 @@ export default {
cursor: pointer;
}
}
.upload-use-file.animate-after{
&::after{
animation: load var(--animation-duration) forwards;
}
}
@keyframes load {
from {
width: 0;
}
to {
width: 100%;
}
}
.el-form{
display: flex;
justify-content: space-between;
align-items: flex-start;
}
</style>

262
src/views/AIAssistant/AIIntelligentCoding/index.vue

@ -49,98 +49,41 @@
<el-table-column prop="title" label="编研主题" />
<el-table-column prop="researchType" label="编研类型" align="center" />
<el-table-column prop="editor1" label="主编人" align="center" />
<el-table-column prop="editor2" label="其他编研人员" align="center" />
<el-table-column prop="startTime" label="开始时间" width="200">
<el-table-column prop="startTime" label="创建时间" width="200">
<template slot-scope="scope">
<div>{{ scope.row.startTime | parseTime }}</div>
</template>
</el-table-column>
<el-table-column prop="endTime" label="结束时间" width="200">
<el-table-column prop="startTime" label="最后更新时间" width="200">
<template slot-scope="scope">
<div>{{ scope.row.endTime | parseTime }}</div>
<div>{{ scope.row.startTime | parseTime }}</div>
</template>
</el-table-column>
</el-table>
<pagination v-if="crud.data.length !== 0" />
<aiForm ref="aiForm" />
<el-dialog class="detail-dialog" title="AI编研详情" :close-on-click-modal="false" :modal-append-to-body="false" append-to-body :visible.sync="onlineEditDetail" :before-close="handleCloseDialog">
<el-dialog class="detail-dialog ai-detial-dialog" title="编研详情" :close-on-click-modal="false" :modal-append-to-body="false" append-to-body :visible.sync="onlineEditDetail" :before-close="handleCloseDialog">
<div class="setting-dialog">
<div class="detail-tab tab-content">
<!-- tab -->
<ul class="tab-nav">
<li :class="{'active-tab-nav': onlineTabIndex == 0}" @click="changeOnlineTab(0)">AI编研正文</li>
<li :class="{'active-tab-nav': onlineTabIndex == 1}" @click="changeOnlineTab(1)">AI文档附件</li>
</ul>
<div v-if="onlineTabIndex===0" style="width: 100%; height: 500px; overflow: hidden; overflow-y: scroll;">
<!-- <p>文档名称{{ onlineContent && onlineContent.title }}</p> -->
<!-- <div class="context-wrapper" v-html="onlineContent" /> -->
<mavon-editor
class="md"
:value="onlineContent"
:subfield="false"
:default-open="'preview'"
:toolbars-flag="false"
:editable="false"
:scroll-style="true"
:ishljs="true"
/>
<div style="width: 100%; height: 500px; overflow: hidden; overflow-y: scroll;">
<div style="margin: 20px; line-height: 30px;">
<p>专题名称{{ onlineContent && onlineContent.title }}</p>
<p>编研类型{{ onlineContent && onlineContent.type }}</p>
</div>
<el-table
v-if="onlineTabIndex===1"
ref="table"
:data="detailArcData"
row-key="id"
style="width: 100%;"
height="calc(100vh - 560px)"
>
<el-table-column type="index" label="序号" align="center" width="55" />
<el-table-column prop="fileName" label="附件名称" />
<el-table-column prop="fileType" label="格式" align="center" min-width="60">
<template slot-scope="scope">
<div v-if="scope.row.fileType === 'jpg' || scope.row.fileType === 'jpeg' || scope.row.fileType === 'png' || scope.row.fileType === 'bmp'|| scope.row.fileType === 'gif'">
<i class="fileIcon icon-image" />
</div>
<div v-else-if="scope.row.fileType === 'xlsx' || scope.row.fileType === 'xls'">
<i class="fileIcon icon-excel" />
</div>
<div v-else-if="scope.row.fileType === 'docx' || scope.row.fileType === 'doc'">
<i class="fileIcon icon-word" />
</div>
<div v-else-if="scope.row.fileType === 'pdf'">
<i class="fileIcon icon-pdf" />
</div>
<div v-else-if="scope.row.fileType === 'ppt' || scope.row.fileType === 'pptx'">
<i class="fileIcon icon-ppt" />
</div>
<div v-else-if="scope.row.fileType === 'zip' || scope.row.fileType === 'rar'">
<i class="fileIcon icon-zip" />
</div>
<div v-else-if="scope.row.fileType === 'txt'">
<i class="fileIcon icon-txt" />
</div>
<div v-else>
<i class="fileIcon icon-other" />
</div>
</template>
</el-table-column>
<el-table-column prop="fileSize" label="大小">
<template slot-scope="scope">
{{ getFileSize(scope.row.fileSize) }}
</template>
</el-table-column>
<el-table-column prop="create_by" label="操作者" />
<el-table-column prop="create_time" label="加入时间">
<template slot-scope="scope">
<div>{{ scope.row.create_time | parseTime }}</div>
</template>
</el-table-column>
</el-table>
<mavon-editor
class="md"
:value="onlineContent"
:subfield="false"
:default-open="'preview'"
:toolbars-flag="false"
:editable="false"
:scroll-style="true"
:ishljs="true"
/>
</div>
</div>
</el-dialog>
<!-- <eForm ref="eform" @refresh="initData" /> -->
<!-- <EditingDetail ref="editingDetail" :current-research="currentResearch" /> -->
</div>
</template>
@ -148,11 +91,8 @@
import CRUD, { presenter, crud } from '@crud/crud'
import crudEditing from '@/api/archiveUtilize/archiveEditing'
import pagination from '@crud/Pagination'
// import DateRangePicker from '@/components/DateRangePicker'
import crudOperation from '@crud/CRUD.operation'
// import eForm from '../../archiveUtilize/archiveEditing/form.vue'
import aiForm from './aiForm'
// import EditingDetail from './module/detail'
import { mapGetters } from 'vuex'
export default {
@ -238,38 +178,38 @@ export default {
if (type === 1) {
this.$refs.aiForm.formTitle = '新增AI编研'
} else {
this.$refs.aiForm.formTitle = '新增AI编研'
this.$refs.aiForm.form = {
'id': row.id,
'title': row.title,
'editor1': row.editor1,
'editor2': row.editor2,
'researchType': row.researchType,
'startTime': row.startTime,
'endTime': row.endTime,
'remarks': row.remarks
}
if (row.editor1) {
var usernames = row.editor1.split(',')
var matchedItems = []
for (var i = 0; i < this.$refs.aiForm.userTable.length; i++) {
if (usernames.includes(this.$refs.aiForm.userTable[i].username)) {
matchedItems.push(this.$refs.aiForm.userTable[i])
}
}
this.$refs.aiForm.userMainSelected = matchedItems
}
this.$refs.aiForm.formTitle = '编辑AI编研'
// this.$refs.aiForm.form = {
// 'id': row.id,
// 'title': row.title,
// 'editor1': row.editor1,
// 'editor2': row.editor2,
// 'researchType': row.researchType,
// 'startTime': row.startTime,
// 'endTime': row.endTime,
// 'remarks': row.remarks
// }
// if (row.editor1) {
// var usernames = row.editor1.split(',')
// var matchedItems = []
// for (var i = 0; i < this.$refs.aiForm.userTable.length; i++) {
// if (usernames.includes(this.$refs.aiForm.userTable[i].username)) {
// matchedItems.push(this.$refs.aiForm.userTable[i])
// }
// }
// this.$refs.aiForm.userMainSelected = matchedItems
// }
if (row.editor2) {
var usernames2 = row.editor2.split(',')
var matchedItems2 = []
for (var j = 0; j < this.$refs.aiForm.userTable.length; j++) {
if (usernames2.includes(this.$refs.aiForm.userTable[j].username)) {
matchedItems2.push(this.$refs.aiForm.userTable[j])
}
}
this.$refs.aiForm.userOtherSelected = matchedItems2
}
// if (row.editor2) {
// var usernames2 = row.editor2.split(',')
// var matchedItems2 = []
// for (var j = 0; j < this.$refs.aiForm.userTable.length; j++) {
// if (usernames2.includes(this.$refs.aiForm.userTable[j].username)) {
// matchedItems2.push(this.$refs.aiForm.userTable[j])
// }
// }
// this.$refs.aiForm.userOtherSelected = matchedItems2
// }
}
this.$refs.aiForm.formType = type
this.$refs.aiForm.formVisible = true
@ -281,39 +221,32 @@ export default {
type: 'warning',
dangerouslyUseHTMLString: true
}).then(() => {
const ids = data.map(item => item.id)
const params = {
'ids': ids,
'operator': this.user.username
}
crudEditing.del(params).then((res) => {
console.log(res)
if (res.code !== 500) {
this.$message({ message: '删除成功', type: 'success', offset: 8 })
} else {
this.$message({ message: res.message, type: 'error', offset: 8 })
}
this.initData()
}).catch(err => {
console.log(err)
})
// const ids = data.map(item => item.id)
// const params = {
// 'ids': ids,
// 'operator': this.user.username
// }
// crudEditing.del(params).then((res) => {
// console.log(res)
// if (res.code !== 500) {
// this.$message({ message: '', type: 'success', offset: 8 })
// } else {
// this.$message({ message: res.message, type: 'error', offset: 8 })
// }
// this.initData()
// }).catch(err => {
// console.log(err)
// })
}).catch(() => {
})
},
clickRowHandler(row) {
this.$refs.table.toggleRowSelection(row)
},
// tableDoubleClick(row) {
// // this.currentResearch = row
// // this.$refs.editingDetail.archivesTabIndex = 0
// // this.$refs.editingDetail.getDetail(row)
// // this.$refs.editingDetail.editingDetailVisible = true
// },
tableDoubleClick(row) {
this.onlineTabIndex = 0
this.currentResearchOnline = row
this.onlineEditDetail = true
this.onlineContent = ``
this.onlineContent = ''
// const params = {
// 'researchOnlineId': row.id
// }
@ -325,68 +258,15 @@ export default {
// })
},
handleCloseDialog(done) {
this.lendDetail = false
this.onlineEditDetail = false
},
// changeMy(val) {
// if (val) {
// this.crud.query.participants = this.user.username
// } else {
// this.crud.query.participants = ''
// }
// this.crud.toQuery()
// },
changeOnlineTab(data) {
this.onlineTabIndex = data
if (this.onlineTabIndex === 1) {
// const paramsFile = {
// 'researchOnlineId': this.currentResearchOnline.id,
// 'page': 0,
// 'size': 10
// }
// console.log('paramsFile', paramsFile)
// FetchInitResearchOnlineFileList(paramsFile).then((res) => {
// console.log(res)
// this.detailArcData = res.content
// }).catch(err => {
// console.log(err)
// })
this.detailArcData = [
{
'id': '4028e3c38d67ac83018d680836c2000f',
'onlineId': '4028e3c38d67ac83018d6807660b000d',
'fileName': 'AI赋能档案管理的研究与展望.docx',
'fileType': 'docx',
'fileSize': 147535,
'filePath': '/researchOnlineFile/4028e3c38d67ac83018d680560230004/4028e3c38d67ac83018d6807660b000d/d2d41cf5-9b2d-4f2f-bc1b-f00e7c1a3962.zip',
'create_by': 'admin',
'update_by': 'admin',
'create_time': 1706847385000,
'update_time': 1706847385000
},
{
'id': '4028e3c38d67ac83018d680817fa000e',
'onlineId': '4028e3c38d67ac83018d6807660b000d',
'fileName': 'AI赋能档案管理的研究与展望.pdf',
'fileType': 'pdf',
'fileSize': 176996,
'filePath': '/researchOnlineFile/4028e3c38d67ac83018d680560230004/4028e3c38d67ac83018d6807660b000d/3e308a02-a7e9-4e45-97b3-cb659c994221.zip',
'create_by': 'admin',
'update_by': 'admin',
'create_time': 1706847377000,
'update_time': 1706847377000
}
]
}
},
getFileSize(fileSize) {
const fileSizeInKB = (fileSize / 1024).toFixed(2) + 'kB'
const fileSizeInB = fileSize + 'B'
return (fileSize / 1024) <= 0.01 ? fileSizeInB : fileSizeInKB
}
}
}
</script>
<style lang="scss" scoped>
::v-deep .v-note-wrapper{
width: 100% !important;
min-height: auto !important;
}
</style>

35
src/views/AIAssistant/AIknowledgeGraph/index.vue

@ -35,8 +35,7 @@
<div v-if="selectedCategory.isType === 2" class="container-right">
<span class="right-top-line" />
<span class="left-bottom-line" />
<!-- <img style="display: block; height: calc(100vh - 184px);" src="@/assets/images/graph-img.png" alt=" "> -->
<graph :graph-data="graphData" />
<graph ref="graphRefs" />
</div>
</div>
</div>
@ -139,22 +138,26 @@ export default {
if (val) {
localStorage.setItem('currentArchivesKey', JSON.stringify(val))
this.selectedCategory = val
// this.selectedCategory.cnName
const parmas = {
'searchKey': 'id', // id / title
'searchValue': 'E0B3627CBED49E53609245',
'tableName': 'Category' //
}
FetchInitShowByCategory(parmas).then((res) => {
console.log(res)
this.graphData = res
}).catch(err => {
console.log(err)
})
// this.$nextTick(() => {
// })
// this.getOrigionGraphData('E0B3627CBED49E53609245', 'Category')
this.$refs.graphRefs.getOrigionGraphData(null, 'E0B3627CBED49E53609245', 'Category')
}
},
getOrigionGraphData(id, tableName) {
// this.selectedCategory.cnName
// E0B3627CBED49E53609245
const parmas = {
'searchKey': 'id', // id / title
'searchValue': id,
'tableName': tableName //
}
FetchInitShowByCategory(parmas).then((res) => {
console.log(res)
this.graphData = res
}).catch(err => {
console.log(err)
})
},
filterData(data) {
return data.filter(node => {
if (node.children && node.children.length > 0) {

7
src/views/collectReorganizi/collectionLibrary/module/uploadOriginal/bigUpload.vue

@ -1,6 +1,10 @@
<template>
<!--上传组件-->
<el-dialog class="big-file" title="大文件上传" :close-on-click-modal="false" :modal-append-to-body="false" append-to-body :visible.sync="uploadBigVisible">
<el-dialog class="big-file" :close-on-click-modal="false" :modal-append-to-body="false" append-to-body :visible.sync="uploadBigVisible">
<template #title>
{{ uploadTitle }}
<span style="color: red;font-size: 12px; ">单个文件不可超过10GB</span>
</template>
<div class="setting-dialog">
<div class="uploader-big">
<uploader
@ -75,6 +79,7 @@ export default {
},
data() {
return {
uploadTitle: '大文件上传',
btnLoading: false,
uploadBigVisible: false,
skip: false,

185
src/views/collectReorganizi/collectionLibrary/module/uploadOriginal/index.vue

@ -1,14 +1,20 @@
<template>
<div>
<!-- 原文上传 -->
<el-dialog class="fileUpload-dialog" :title="uploadTitle" :close-on-click-modal="false" :modal-append-to-body="false" append-to-body :visible.sync="uploadVisible">
<el-dialog class="fileUpload-dialog" :close-on-click-modal="false" :modal-append-to-body="false" append-to-body :visible.sync="uploadVisible">
<template #title>
{{ uploadTitle }}
<span v-if="uploadType===0" style="color: red;font-size: 12px; ">单个文件超过10M请用大文件上传</span>
</template>
<div class="setting-dialog">
<div class="upload-container">
<i v-if="uploadType !== 1 && 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 style=" max-height: 300px; padding-right: 20px; overflow-y: scroll;">
<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>
<div v-if="uploadType !== 1" class="upload-input">
<input ref="fileInput" :key="key" type="file" :multiple="isMultiple" :accept="uploadType === 2 ? '.zip' :''" @change="handleFileChange">
@ -16,49 +22,34 @@
</div>
<BigUpload v-else-if="uploadType === 1" :selected-category="selectedCategory" :arc-id="arcId" />
<div v-if="uploadType === 2" class="el-upload__tip">上传限制文件类型zip</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button type="text" @click="uploadVisible = false">取消</el-button>
<el-button type="primary" @click="handleUploadConfirm">保存</el-button>
<el-button type="primary" :loading="uplaodLoading" @click="handleUploadConfirm">保存</el-button>
</div>
</div>
</el-dialog>
<!-- 上传详情 -->
<el-dialog class="collectUpload-dialog" :title="detailUploadTitle" :close-on-click-modal="false" :modal-append-to-body="false" append-to-body :visible.sync="uploadDetialVisible">
<!-- 判断是否有重复上传的文件 -->
<el-dialog class="collectUpload-dialog" title="文件上传" :close-on-click-modal="false" :modal-append-to-body="false" append-to-body :visible.sync="repeatFileVisible">
<div class="setting-dialog">
<div class="upload-detail">
<el-table :data="uploadDetailData" style="width: 100%">
<el-table-column prop="number" label="编号" width="120" />
<el-table-column prop="operator" label="操作人" width="120" />
<el-table-column prop="operationType" label="操作类型" width="120" />
<el-table-column prop="file" label="文件" width="160" />
<el-table-column prop="createDate" label="操作时间" width="200" />
<el-table-column prop="conclusion" label="结论" width="200">
<template slot-scope="scope">
<div>成功 {{ scope.row.successNumber }} ; 失败 {{ scope.row.failNumber }} </div>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<el-pagination
:current-page="page.page"
:total="page.total"
:page-size="page.size"
:pager-count="5"
layout="total, prev, pager, next, sizes"
@size-change="handleSizeChange"
@current-change="handleCurrentPage"
/>
<p style="color:#f00;margin-bottom: 20px;display:block">提示以下所选文件在当前档案文件列表已存在</p>
<div v-for="item in repeatFileData" :key="item.name" class="file-list" style="margin-bottom: 10px;">
<i class="iconfont icon-xiaowenjian" />
{{ item.name }}
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="handleRepeatFile(0)">直接上传</el-button>
<el-button style="width: 85px;" type="primary" @click="handleRepeatFile(1)">去重后上传</el-button>
<el-button type="text" @click="repeatFileVisible = false">取消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
// import { FetchAddArchivesFile } from '@/api/collect/collect'
import { FetchInitFileCategoryView } from '@/api/collect/collect'
import { getCurrentTime } from '@/utils/index'
import { upload, archivesUpload } from '@/utils/upload'
import { mapGetters } from 'vuex'
@ -81,6 +72,7 @@ export default {
},
data() {
return {
uplaodLoading: false,
uploadVisible: false,
uploadTitle: '普通上传',
detailUploadTitle: '上传详情',
@ -95,14 +87,10 @@ export default {
nowDate: '', //
fileList: [],
uploadType: 0,
//
uploadDetialVisible: false,
uploadDetailData: [],
page: {
page: 1,
size: 10,
total: 0
}
//
repeatFileVisible: false,
repeatFileData: [],
originFileData: []
}
},
computed: {
@ -119,40 +107,76 @@ export default {
mounted() {
},
methods: {
//
getFileList() {
const params = {
'categoryId': this.selectedCategory.id,
'archivesId': this.arcId
}
return FetchInitFileCategoryView(params).then(data => {
this.originFileData = data.returnlist
})
},
handleFileChange(e) {
const files = e.target.files
this.file = files[0]
this.key++
const maxMessage = '上传文件大小不能超过 10MB,可使用大文件上传!'
// const maxMessage = ' 10MB使!'
const singleMaxMessage = '文件 {filename} 超过 10MB,无法上传!'
const maxSize = 10 * 1024 * 1024
if (this.file && this.file.size > maxSize) {
this.$message({ message: maxMessage, type: 'warning', offset: 8 })
this.fileList = []
e.target.value = ''
return false
}
if (this.fileList.length !== 0) {
const existingFile = this.fileList.some(file => file.name === this.file.name)
if (existingFile) {
this.$message({ message: '文件已存在', type: 'warning', offset: 8 })
return false
}
}
// let totalSize = 0
const newFileList = []
for (let i = 0; i < files.length; i++) {
if (this.uploadType === 2) {
this.fileList = []
this.fileList.push(files[i])
} else {
this.fileList.push(files[i])
const file = files[i]
if (file.size > maxSize) {
this.$message({
message: singleMaxMessage.replace('{filename}', file.name),
type: 'warning',
offset: 8
})
continue
}
newFileList.push(file)
// totalSize += file.size
}
// if (totalSize > maxSize) {
// this.$message({
// message: maxMessage,
// type: 'warning',
// offset: 8
// })
// }
if (this.uploadType === 2) {
this.fileList = newFileList.slice(-1)
} else {
this.fileList = newFileList
}
this.key++
if (newFileList.length < files.length) {
e.target.value = ''
const dataTransfer = new DataTransfer()
newFileList.forEach(file => dataTransfer.items.add(file))
e.target.files = dataTransfer.files
}
},
handleUploadConfirm() {
async handleUploadConfirm() {
//
await this.getFileList()
if (this.fileList.length === 0) {
this.$message({ message: '请先选择相关文件!', offset: 8 })
return false
}
const existingFileNames = this.originFileData.map(file => file.file_name)
this.repeatFileData = this.fileList.filter(file => existingFileNames.includes(file.name))
console.log('filteredFileList', this.repeatFileData)
if (this.uploadType === 2) {
this.uplaodLoading = true
//
upload(this.baseApi + '/api/collect/catalogUpload',
this.fileList[0]
@ -166,12 +190,34 @@ export default {
} else {
this.$message({ message: '操作失败', type: 'error', offset: 8 })
}
this.uplaodLoading = false
})
} else {
if (this.repeatFileData.length === 0) {
this.uplaodToList(this.fileList)
} else {
this.repeatFileVisible = true
}
}
},
handleRepeatFile(type) {
if (type === 0) {
//
this.uplaodToList(this.fileList)
} else {
//
const nonRepeatFileData = this.fileList.filter(file => !this.repeatFileData.some(repeatFile => repeatFile.name === file.name))
this.uplaodToList(nonRepeatFileData)
}
this.repeatFileVisible = false
},
handleRepeatFileCancel() {
this.repeatFileVisible = false
this.repeatFileData = []
this.originFileData = []
},
uplaodToList(files) {
this.uplaodLoading = true
//
this.nowDate = getCurrentTime()
const promiseArray = files.map(async(item, index) => {
@ -214,6 +260,7 @@ export default {
} else {
this.$message({ message: '上传附件失败', type: 'error', offset: 8 })
}
this.uplaodLoading = false
})
})
.catch((error) => {
@ -232,6 +279,9 @@ export default {
})
} else {
this.$message({ message: '已清空所有要上传的附件', offset: 8 })
if (this.$refs.fileInput) {
this.$refs.fileInput.value = ''
}
}
},
// base64
@ -255,13 +305,6 @@ export default {
resolve({ width, height })
}
})
},
handleSizeChange(size) {
this.page.size = size
this.page.page = 1
},
handleCurrentPage(val) {
this.page.page = val
}
}
}
@ -271,7 +314,7 @@ export default {
.collectUpload-dialog{
::v-deep .el-dialog{
width: 970px;
width: 500px;
}
}

180
src/views/components/echarts/graph.vue

@ -21,7 +21,6 @@
<template #graph-plug>
<div v-if="isShowNodeTipsPanel" :style="{left: nodeMenuPanelPosition.x + 'px', top: nodeMenuPanelPosition.y + 'px' }" style="z-index: 999;padding:10px;background-color: #ffffff;border:#eeeeee solid 1px;box-shadow: 0px 0px 8px #cccccc;position: absolute;">
<div style="line-height: 25px;padding-left: 10px;color: #888888;font-size: 12px;">节点名称{{ currentNode.text }}</div>
<div class="c-node-menu-item">id:{{ currentNode.id }}</div>
</div>
</template>
</RelationGraph>
@ -30,20 +29,14 @@
</template>
<script>
import { getGraphData } from '../../../neo4j.js'
// import { getGraphData } from '../../../neo4j.js'
import RelationGraph from 'relation-graph'
import { FetchInitShowByCategory } from '@/api/ai/ai'
export default {
name: 'Demo',
components: { RelationGraph },
props: {
graphData: {
type: Object,
require: true,
default: function() {
return {}
}
}
},
data() {
return {
@ -100,17 +93,32 @@ export default {
// const query = "MATCH path = (target:Archives {id: '0047B3542CE88B895E41DE'})-[rels:HAS_KEYWORD*1..4]-(related:Archives) WHERE target <> related UNWIND relationships(path) AS r WITH startNode(r) AS n, r, endNode(r) AS m, length(path)/2 AS depth RETURN DISTINCT n, r, m;"
// this.fetchData(query)
// this.showGraph()
this.setData()
},
beforeDestroy() {
clearInterval(this.resizeTimer)
},
methods: {
setData(selectedNodeId = null) {
const { nodes, lines } = this.graphData
getOrigionGraphData(nodeId, id, tableName) {
// this.selectedCategory.cnName
// E0B3627CBED49E53609245
const parmas = {
'searchKey': 'id', // id / title
'searchValue': id,
'tableName': tableName //
}
FetchInitShowByCategory(parmas).then((res) => {
console.log(res)
this.graphData2 = res
this.setData(this.graphData2, nodeId)
}).catch(err => {
console.log(err)
})
},
setData(data, selectedNodeId = null) {
const { nodes, lines } = data
// ArchivesCategoryCategoryClassFondsKeywordsRetentionSecrecyPeriodSecurityClass
const newNodes = nodes.map(item => {
const newItem = { ...item, id: String(item.id), text: item.properties.title, type: item.labels[0] }
const newItem = { ...item, id: String(item.id), originId: item.properties.id, text: item.properties.title, type: item.labels[0] }
const typeToColorMap = {
Archives: '#0348F3',
@ -124,8 +132,6 @@ export default {
}
const defaultColor = '#000000' //
console.log('item.labels[0]', item.labels[0])
console.log('typeToColorMap[item.labels[0]]', typeToColorMap[item.labels[0]])
if (typeToColorMap[item.labels[0]]) {
newItem.color = typeToColorMap[item.labels[0]]
newItem.borderColor = typeToColorMap[item.labels[0]]
@ -165,8 +171,8 @@ export default {
}
}
// const originalNodesLength = this.allData.nodes.length
// const originalLinesLength = this.allData.lines.length
const originalNodesLength = this.allData.nodes.length
const originalLinesLength = this.allData.lines.length
this.allData.nodes = [...this.allData.nodes, ...uniqueNewNodes]
this.allData.lines = [...this.allData.lines, ...validNewEdges]
@ -175,98 +181,11 @@ export default {
return parseInt(a.id) - parseInt(b.id)
})
console.log('this.allData', this.allData)
this.showGraph()
//
// if (this.allData.nodes.length > originalNodesLength || this.allData.lines.length > originalLinesLength) {
// console.log('this.allData', this.allData)
// this.showGraph()
// } else {
// console.log('')
// }
},
async fetchData(query, selectedNodeId = null) {
this.isLoading = true
this.errorMessage = ''
try {
const { nodes, edges } = await getGraphData(query)
// ArchivesCategoryCategoryClassFondsKeywordsRetentionSecrecyPeriodSecurityClass
const newNodes = nodes.map(item => {
const newItem = { ...item, id: String(item.id) }
const typeToColorMap = {
Archives: '#0348F3',
Category: '#14C9C9',
CategoryClass: '#F8B722',
Fonds: '#722ED1',
Keywords: '#F4647B',
Retention: '#018BFF',
SecrecyPeriod: '#FEBD98',
SecurityClass: '#B1EBDF'
}
const defaultColor = '#000000' //
if (typeToColorMap[item.type]) {
newItem.color = typeToColorMap[item.type]
newItem.borderColor = typeToColorMap[item.type]
} else {
newItem.color = defaultColor
newItem.borderColor = defaultColor
}
return newItem
})
const newEdges = edges.map(item => {
return {
...item,
from: String(item.from),
to: String(item.to)
}
})
const existingNodeIds = new Set(this.allData.nodes.map(node => node.id))
const allNodesExist = newNodes.every(node => existingNodeIds.has(node.id))
let validNewEdges = []
let uniqueNewNodes = []
if (!allNodesExist) {
uniqueNewNodes = newNodes.filter(node => !existingNodeIds.has(node.id))
const existingLineKeys = new Set(this.allData.lines.map(line => `${line.from}-${line.to}-${line.text}`))
// to
if (selectedNodeId) {
const relatedNodeIds = [selectedNodeId, ...this.getRelatedNodeIds(selectedNodeId)]
validNewEdges = newEdges.filter(edge => {
return !relatedNodeIds.includes(edge.to) && !existingLineKeys.has(`${edge.from}-${edge.to}-${edge.text}`)
})
} else {
validNewEdges = newEdges.filter(edge => !existingLineKeys.has(`${edge.from}-${edge.to}-${edge.text}`))
}
}
const originalNodesLength = this.allData.nodes.length
const originalLinesLength = this.allData.lines.length
this.allData.nodes = [...this.allData.nodes, ...uniqueNewNodes]
this.allData.lines = [...this.allData.lines, ...validNewEdges]
this.allData.nodes.sort((a, b) => {
return parseInt(a.id) - parseInt(b.id)
})
//
if (this.allData.nodes.length > originalNodesLength || this.allData.lines.length > originalLinesLength) {
console.log('this.allData', this.allData)
this.showGraph()
} else {
console.log('数据无变化,不更新图表')
}
} catch (error) {
this.errorMessage = `Error fetching data from Neo4j: ${error.message}`
} finally {
this.isLoading = false
if (this.allData.nodes.length > originalNodesLength || this.allData.lines.length > originalLinesLength) {
this.showGraph()
} else {
console.log('数据无变化,不更新图表')
}
},
getRelatedNodeIds(nodeId) {
@ -276,8 +195,8 @@ export default {
},
async showGraph() {
console.log('this.allData', this.allData)
const rootId = this.allData.nodes[0].id
this.allData.rootId = rootId
// const rootId = this.allData.nodes[0].id
// this.allData.rootId = rootId
const graphInstance = this.$refs.graphRef.getInstance()
// await this.stopForceIfNeed()
@ -304,39 +223,22 @@ export default {
}, 500)
},
onNodeClick(nodeObject, $event) {
console.log('this.allData', this.allData)
console.log('节点数据:', nodeObject)
// const nodeId = nodeObject.id
const nodeId = nodeObject.id
const targetNode = this.allData.nodes.find(node => node.id === nodeId)
if (targetNode) {
const originId = targetNode.originId
console.log(originId)
// this.$emit('getGraphData', originId, targetNode.type)
this.getOrigionGraphData(nodeId, originId, targetNode.type)
} else {
console.log('未找到对应的节点')
}
// const query = `MATCH (n) WHERE id(n) = ${nodeId} MATCH (n)-[r]-(m) RETURN n, r,m;`
// this.fetchData(query, nodeId)
// console.log('query', query)
// const _all_nodes = this.$refs.graphRef.getInstance().getNodes()
// const { lot } = nodeObject
// if (lot.childs && lot.childs.length) {
// _all_nodes.forEach((thisNode) => {
// let _isHideThisNode = false
// lot.childs.forEach((childNode) => {
// if (thisNode.id === childNode.id || thisNode.id === nodeObject.id) {
// _isHideThisNode = true
// }
// if (lot.parent && thisNode.id === lot.parent.id) {
// _isHideThisNode = true
// }
// })
// thisNode.opacity = _isHideThisNode ? 1 : 0.1
// })
// } else {
// _all_nodes.forEach((thisNode) => {
// let _isHideThisNode = false
// if (thisNode.id === nodeObject.id) {
// _isHideThisNode = true
// }
// if (lot.parent && thisNode.id === lot.parent.id) {
// _isHideThisNode = true
// }
// thisNode.opacity = _isHideThisNode ? 1 : 0.1
// })
// }
},
onCanvasClick($event) {
const _all_nodes = this.$refs.graphRef.getInstance().getNodes()

Loading…
Cancel
Save