[前端] 研读模块改造为三栏布局:文件树+预览+问答
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<Operates v-show="url != '/login' && url != '/writing/edit'&& url != '/knowledgeBase/fileDetail'" />
|
<Operates v-show="url != '/login' && url != '/writing/edit'" />
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -59,11 +59,11 @@
|
|||||||
import {copyToClip} from "@/utils";
|
import {copyToClip} from "@/utils";
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
import {fileGuidInfo} from "@/api";
|
import {fileGuidInfo} from "@/api";
|
||||||
import {onMounted, ref} from "vue";
|
import {computed, inject, ref, type Ref} from "vue";
|
||||||
import MarkdownIt from "markdown-it";
|
import MarkdownIt from "markdown-it";
|
||||||
import {transforMd} from "@/utils/markdown";
|
import {transforMd} from "@/utils/markdown";
|
||||||
|
|
||||||
const state=history.state;
|
const selectedFile = inject('selectedFile') as Ref<any>;
|
||||||
|
|
||||||
const emits=defineEmits(["refreshAbs","refreshCon"]);
|
const emits=defineEmits(["refreshAbs","refreshCon"]);
|
||||||
const mardown=new MarkdownIt();
|
const mardown=new MarkdownIt();
|
||||||
@@ -73,18 +73,29 @@ const refreshAbs=()=>{
|
|||||||
const refreshCon=()=>{
|
const refreshCon=()=>{
|
||||||
emits('refreshCon');
|
emits('refreshCon');
|
||||||
}
|
}
|
||||||
const historyParams=history.state;
|
|
||||||
const articleAbstract=ref(historyParams.articleAbstract);
|
|
||||||
const articleKeywords=ref(historyParams.articleKeywords);
|
|
||||||
const articleParagraph=ref(historyParams.articleParagraph);
|
|
||||||
|
|
||||||
//总结提炼
|
const articleAbstract=ref('');
|
||||||
onMounted(async () => {
|
const articleKeywords=ref('');
|
||||||
if(articleKeywords.value&&(articleKeywords.value.indexOf('关键词:')>-1||articleKeywords.value.indexOf('关键词:')>-1)){
|
const articleParagraph=ref('');
|
||||||
articleKeywords.value=articleKeywords.value.substring(articleKeywords.value.indexOf('关键词:')+4,articleKeywords.value.length);
|
|
||||||
articleKeywords.value=articleKeywords.value.substring(articleKeywords.value.indexOf('关键词:')+4,articleKeywords.value.length);
|
// 监听选中文件变化,更新导读内容
|
||||||
|
import {watch} from "vue";
|
||||||
|
watch(() => selectedFile.value, (newFile) => {
|
||||||
|
if (newFile) {
|
||||||
|
articleAbstract.value = newFile.articleAbstract || '';
|
||||||
|
let kw = newFile.articleKeywords || '';
|
||||||
|
if (kw && (kw.indexOf('关键词:') > -1 || kw.indexOf('关键词:') > -1)) {
|
||||||
|
kw = kw.substring(kw.indexOf('关键词:') + 4, kw.length);
|
||||||
|
kw = kw.substring(kw.indexOf('关键词:') + 4, kw.length);
|
||||||
}
|
}
|
||||||
})
|
articleKeywords.value = kw;
|
||||||
|
articleParagraph.value = newFile.articleParagraph || '';
|
||||||
|
} else {
|
||||||
|
articleAbstract.value = '';
|
||||||
|
articleKeywords.value = '';
|
||||||
|
articleParagraph.value = '';
|
||||||
|
}
|
||||||
|
}, {immediate: true});
|
||||||
const copyText=(text)=>{
|
const copyText=(text)=>{
|
||||||
copyToClip(text);
|
copyToClip(text);
|
||||||
ElMessage.success("复制成功");
|
ElMessage.success("复制成功");
|
||||||
@@ -95,7 +106,7 @@ const refreshGuide=async (type:string)=>{
|
|||||||
let param = {
|
let param = {
|
||||||
type: type,
|
type: type,
|
||||||
context: fileBox.innerText,
|
context: fileBox.innerText,
|
||||||
fileId: historyParams.fileId,
|
fileId: selectedFile.value?.fileId,
|
||||||
}
|
}
|
||||||
if(type==='6'){
|
if(type==='6'){
|
||||||
articleAbstract.value=''
|
articleAbstract.value=''
|
||||||
|
|||||||
@@ -27,9 +27,9 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {getPaper} from "@/api";
|
import {getPaper} from "@/api";
|
||||||
import {onMounted, ref} from "vue";
|
import {inject, onMounted, ref, watch, type Ref} from "vue";
|
||||||
|
|
||||||
const historyParams=history.state;
|
const selectedFile = inject('selectedFile') as Ref<any>;
|
||||||
|
|
||||||
type Literature = {
|
type Literature = {
|
||||||
doc_id: string,
|
doc_id: string,
|
||||||
@@ -53,9 +53,9 @@ const showNoData = ref(false);
|
|||||||
const getLiteratureList = async () => {
|
const getLiteratureList = async () => {
|
||||||
try {
|
try {
|
||||||
let res = await getPaper({
|
let res = await getPaper({
|
||||||
fileName: historyParams.fileName,
|
fileName: selectedFile.value?.fileName,
|
||||||
keywords: historyParams.articleKeywords,
|
keywords: selectedFile.value?.articleKeywords,
|
||||||
id: historyParams.fileId,
|
id: selectedFile.value?.fileId,
|
||||||
retry: 0
|
retry: 0
|
||||||
})
|
})
|
||||||
if (res.code === 200 && res.data && res.data.articles && res.data.articles.length > 0) {
|
if (res.code === 200 && res.data && res.data.articles && res.data.articles.length > 0) {
|
||||||
@@ -147,8 +147,17 @@ const publishDateFormat = (articles: Literature[]) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
// 监听文件切换,重新加载文献
|
||||||
|
watch(() => selectedFile.value?.fileId, () => {
|
||||||
|
if (selectedFile.value?.fileId) {
|
||||||
getLiteratureList();
|
getLiteratureList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (selectedFile.value?.fileId) {
|
||||||
|
getLiteratureList();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang='ts'>
|
<script setup lang='ts'>
|
||||||
import {inject, onMounted, reactive, ref} from "vue";
|
import {inject, onMounted, reactive, ref, watch, type Ref} from "vue";
|
||||||
import {
|
import {
|
||||||
deleteChatMessageById,
|
deleteChatMessageById,
|
||||||
delHistoryChatById,
|
delHistoryChatById,
|
||||||
@@ -62,6 +62,9 @@ import ReadingCreateMessage from "@/components/ReadingCreateMessage.vue";
|
|||||||
import {withLoading} from "@/utils/loading";
|
import {withLoading} from "@/utils/loading";
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
import {debounce} from "@/utils";
|
import {debounce} from "@/utils";
|
||||||
|
|
||||||
|
const selectedFile = inject('selectedFile') as Ref<any>;
|
||||||
|
|
||||||
// 引用内容
|
// 引用内容
|
||||||
const quoteMsg = inject("quoteMsg");
|
const quoteMsg = inject("quoteMsg");
|
||||||
const updateQuoteMsg = inject("updateQuoteMsg");
|
const updateQuoteMsg = inject("updateQuoteMsg");
|
||||||
@@ -79,7 +82,6 @@ const sendStatus = ref(false);
|
|||||||
const chatStatus = ref(false);
|
const chatStatus = ref(false);
|
||||||
const chatNumber = ref(0);
|
const chatNumber = ref(0);
|
||||||
const chatId = ref('');
|
const chatId = ref('');
|
||||||
const historyParam = history.state;
|
|
||||||
const conversationId = ref('');
|
const conversationId = ref('');
|
||||||
const chatInfo = reactive([]);
|
const chatInfo = reactive([]);
|
||||||
let controller = null;
|
let controller = null;
|
||||||
@@ -143,7 +145,7 @@ const getFetchChatAPI = async (prompt) => {
|
|||||||
const chatData = {
|
const chatData = {
|
||||||
prompt,
|
prompt,
|
||||||
type: 99,
|
type: 99,
|
||||||
fileId: historyParam.fileId
|
fileId: selectedFile.value?.fileId
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const fetchChatAPIData = await fetchChatAPI(chatData);
|
const fetchChatAPIData = await fetchChatAPI(chatData);
|
||||||
@@ -196,10 +198,10 @@ const getFetchChatAPIProcess = async (type: string) => {
|
|||||||
headers: headers,
|
headers: headers,
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
fileNames: [historyParam.embeddingId],
|
fileNames: [selectedFile.value?.embeddingId],
|
||||||
conversationId: conversationId.value,
|
conversationId: conversationId.value,
|
||||||
promptName: "default",
|
promptName: "default",
|
||||||
knowledgeBaseIdList: [historyParam.folderId],
|
knowledgeBaseIdList: [selectedFile.value?.folderId],
|
||||||
chatType: type,
|
chatType: type,
|
||||||
quote: quoteMsg.value
|
quote: quoteMsg.value
|
||||||
}),
|
}),
|
||||||
@@ -313,15 +315,21 @@ const autoPositionToBottom = () => {
|
|||||||
element.scrollTop = element.scrollHeight + 55;
|
element.scrollTop = element.scrollHeight + 55;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
const loadChatHistory = async () => {
|
||||||
//请求文件对话的dataId
|
// 重置对话状态
|
||||||
|
chatInfo.length = 0;
|
||||||
|
chatId.value = '';
|
||||||
|
firstChat.value = true;
|
||||||
|
chatNumber.value = 0;
|
||||||
|
conversationId.value = '';
|
||||||
|
|
||||||
|
if (!selectedFile.value?.fileId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let hisResponse = await listFileChatHistory(historyParam.fileId);
|
let hisResponse = await listFileChatHistory(selectedFile.value.fileId);
|
||||||
//根据dataId请求获取历史记录
|
|
||||||
if (hisResponse.code == 200 && hisResponse.data) {
|
if (hisResponse.code == 200 && hisResponse.data) {
|
||||||
chatNumber.value = hisResponse.data;
|
chatNumber.value = hisResponse.data;
|
||||||
firstChat.value = false;
|
firstChat.value = false;
|
||||||
//请求文件对话的dataId
|
|
||||||
let historyList = await listChatMessage({
|
let historyList = await listChatMessage({
|
||||||
chatNumber: chatNumber.value,
|
chatNumber: chatNumber.value,
|
||||||
fileType: 1
|
fileType: 1
|
||||||
@@ -358,9 +366,17 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
ElMessage.error(error && error.message ? error.message : '未知错误');
|
// 新文件没有历史记录,不报错
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听文件切换,重新加载对话历史
|
||||||
|
watch(() => selectedFile.value?.fileId, () => {
|
||||||
|
loadChatHistory();
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadChatHistory();
|
||||||
handleReadingMessageContentScroll();
|
handleReadingMessageContentScroll();
|
||||||
})
|
})
|
||||||
let messageInstance = null;
|
let messageInstance = null;
|
||||||
|
|||||||
@@ -34,16 +34,6 @@ const routes: RouteRecordRaw[] = [
|
|||||||
name: 'KnowledgeBase',
|
name: 'KnowledgeBase',
|
||||||
component: () => import('@/views/reading/index.vue'),
|
component: () => import('@/views/reading/index.vue'),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/knowledgeBase/fileList',
|
|
||||||
name: 'FileList',
|
|
||||||
component: () => import('@/views/reading/fileList.vue'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/knowledgeBase/fileDetail',
|
|
||||||
name: 'FileDetail',
|
|
||||||
component: () => import('@/views/reading/fileDetail.vue'),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/applications',
|
path: '/applications',
|
||||||
name: 'Application',
|
name: 'Application',
|
||||||
|
|||||||
@@ -1,883 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="body">
|
|
||||||
<div class="header">
|
|
||||||
<img @click="goback" src="@/assets/images/writing/back.png" alt="">
|
|
||||||
<div class="title" v-text="fileName"></div>
|
|
||||||
</div>
|
|
||||||
<div class="file-content-container">
|
|
||||||
<div class="file-content" ref="fileContent" id="file-content">
|
|
||||||
<div class="view-md" id="file-html-content" v-html="docHtml" ></div><!--@click="handleAnchorClick"-->
|
|
||||||
|
|
||||||
<div id="note-content" :title="noteContent" class="file-note"></div>
|
|
||||||
</div>
|
|
||||||
<div class="doc-right">
|
|
||||||
<ReadingBox ref="readingBox"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="click-bar-button" id="shortMenuDom" ref="shortMenuDom" v-show="shortMenuShow">
|
|
||||||
<template v-for="(item, index) in contentMenuList" :key="index">
|
|
||||||
<div class="menu-name" @click="fileContentMenu(index)">{{ item }}</div>
|
|
||||||
<div class="vertical-line" v-if="index < contentMenuList.length - 1"></div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 添加笔记弹框 -->
|
|
||||||
<el-dialog v-model="showAddNoteDialog" class="add-note-dialog" title="写下你的想法吧"
|
|
||||||
width="715" style="padding: 0; box-shadow: 0px 0px 8px 1px rgba(180,189,221,0.56); border-radius: 16px;"
|
|
||||||
:close-on-click-modal="false" :modal-append-to-body="false" :close-on-press-escape="false"
|
|
||||||
@open="openAddNoteDialog">
|
|
||||||
<el-input ref="noteMsgRef" v-model="noteMsg" type="textarea" :rows="10" placeholder="请输入笔记内容"/>
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer" style="text-align: center;">
|
|
||||||
<el-button type="primary" @click="submitNote()">
|
|
||||||
发表
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import {nextTick, onBeforeUnmount, onMounted, provide, reactive, ref,getCurrentInstance} from 'vue';
|
|
||||||
|
|
||||||
import ReadingBox from "@/components/ReadingBox.vue";
|
|
||||||
import {addFileNote, getFileContent} from "@/api";
|
|
||||||
import MarkdownIt from "markdown-it";
|
|
||||||
import {copyToClip, escapeRegExp, getGlobalSelectionPosition} from "@/utils";
|
|
||||||
import {withLoading} from "@/utils/loading";
|
|
||||||
import {ElMessage} from "element-plus";
|
|
||||||
import {transforMd} from "@/utils/markdown";
|
|
||||||
import Mark from "mark.js";
|
|
||||||
|
|
||||||
const readingBox = ref(null);
|
|
||||||
const docxFlow = ref<any>(null)
|
|
||||||
const pdfFlow = ref<any>(null)
|
|
||||||
const historyParams = history.state;
|
|
||||||
const fileContent = ref(null);
|
|
||||||
const shortMenuDom = ref(null);
|
|
||||||
const shortMenuShow = ref(false);
|
|
||||||
const selectText = ref('');
|
|
||||||
|
|
||||||
|
|
||||||
const docHtml = ref('');
|
|
||||||
|
|
||||||
const fileName = historyParams.fileName;
|
|
||||||
|
|
||||||
const suffix = fileName.slice(fileName.lastIndexOf('.') + 1);
|
|
||||||
|
|
||||||
const goback = () => {
|
|
||||||
history.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentMenuList = ['引用', '复制', '名词解释', '添加笔记','翻译'];
|
|
||||||
|
|
||||||
// 引用内容
|
|
||||||
const quoteMsg = ref<string>('');
|
|
||||||
provide('quoteMsg', quoteMsg);
|
|
||||||
provide('updateQuoteMsg', (newValue) => {
|
|
||||||
quoteMsg.value = newValue;
|
|
||||||
});
|
|
||||||
|
|
||||||
const fileContentMenu = (index) => {
|
|
||||||
disShowShortMenu();
|
|
||||||
if (index === 0) {
|
|
||||||
// 引用内容
|
|
||||||
quoteMsg.value = selectText.value;
|
|
||||||
if (quoteMsg.value && quoteMsg.value.length > 1000) {
|
|
||||||
quoteMsg.value.slice(0, 1000);
|
|
||||||
}
|
|
||||||
readingBox.value.changeHead(2);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (index === 1) {
|
|
||||||
//复制内容
|
|
||||||
copyToClip(selectText.value);
|
|
||||||
ElMessage.success("复制成功");
|
|
||||||
}
|
|
||||||
if (index === 2) {
|
|
||||||
//名词解释
|
|
||||||
let sendText = "我想请你解释一下这个词语的含义:" + selectText.value;
|
|
||||||
if (readingBox.value) {
|
|
||||||
readingBox.value.changeHead(2);
|
|
||||||
readingBox.value.readingBoxSend(sendText, '1');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (index === 3) {
|
|
||||||
// 添加笔记
|
|
||||||
readingBox.value.changeHead(3);
|
|
||||||
showAddNoteDialog.value = true;
|
|
||||||
}
|
|
||||||
if (index === 4) {
|
|
||||||
let sendText = selectText.value;
|
|
||||||
// 添加笔记
|
|
||||||
readingBox.value.changeHead(4);
|
|
||||||
readingBox.value.translateQuery(sendText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 隐藏
|
|
||||||
*/
|
|
||||||
const disShowShortMenu = () => {
|
|
||||||
shortMenuShow.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取文件html格式的内容
|
|
||||||
*/
|
|
||||||
const fileNote = reactive({
|
|
||||||
notes: []
|
|
||||||
});
|
|
||||||
const noteContent = ref('');
|
|
||||||
provide('fileNote', fileNote);
|
|
||||||
|
|
||||||
// LaTeX 公式正则表达式
|
|
||||||
|
|
||||||
const pattern = /(\$\$.*?\$\$|\$.*?\$)/g;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取文件内容
|
|
||||||
*/
|
|
||||||
const getFileHtmlContent = async () => {
|
|
||||||
let offsets = [];
|
|
||||||
try {
|
|
||||||
let res = await withLoading(getFileContent)({
|
|
||||||
fileId: historyParams.fileId,
|
|
||||||
embeddingId: historyParams.embeddingId,
|
|
||||||
knowledgeBaseId: historyParams.folderId
|
|
||||||
});
|
|
||||||
if (res && res.code === 200 && res.data && res.data.content) {
|
|
||||||
let content = res.data.content;
|
|
||||||
// 处理公式
|
|
||||||
content = content.replace(pattern, (match, captureGroup, offset, originalString) => {
|
|
||||||
offsets.push(offset);
|
|
||||||
return transforMd(match);
|
|
||||||
});
|
|
||||||
docHtml.value = content.replace(/<p>(.*?<span class="katex">.*?<\/span>.*?)<\/p>/g, '$1');
|
|
||||||
fileNote.notes = res.data.notes;
|
|
||||||
} else {
|
|
||||||
ElMessage.error('文件内容获取失败');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error(error && error.message ? error.message : '未知错误');
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
// 给公式标签加id
|
|
||||||
try {
|
|
||||||
await nextTick();
|
|
||||||
handelLaTeXElements(offsets);
|
|
||||||
handelCommentElements();
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 给LaTeX公式标签加id
|
|
||||||
const handelLaTeXElements = (offsets) => {
|
|
||||||
let katexElements = document.querySelectorAll('span[class="katex"]');
|
|
||||||
for (let i = 0; i < katexElements.length; i++) {
|
|
||||||
katexElements[i].setAttribute('id', 'katex-'+ offsets[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 给批注的a标签添加点击事件
|
|
||||||
const handelCommentElements = () => {
|
|
||||||
document.querySelectorAll('a[href^="#comment-"]').forEach(element => {
|
|
||||||
let targetId = element.getAttribute('href');
|
|
||||||
element.onclick = function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const targetElement = document.querySelector(targetId);
|
|
||||||
if (targetElement) {
|
|
||||||
targetElement.scrollIntoView({ behavior: 'smooth' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 笔记
|
|
||||||
const showAddNoteDialog = ref(false);
|
|
||||||
const noteMsg = ref('');
|
|
||||||
const noteMsgRef = ref(null);
|
|
||||||
type NoteParam = {
|
|
||||||
id:string,
|
|
||||||
fileId: number,
|
|
||||||
pstartId: string,
|
|
||||||
pendId: string,
|
|
||||||
wordContent: string,
|
|
||||||
startIndex: number,
|
|
||||||
endIndex: number,
|
|
||||||
noteContent: string
|
|
||||||
}
|
|
||||||
type PositionInfo = {
|
|
||||||
start: number,
|
|
||||||
end: number,
|
|
||||||
parentStart: string | null,
|
|
||||||
parentEnd: string | null
|
|
||||||
}
|
|
||||||
const noteParam = ref<NoteParam>({
|
|
||||||
id: '',
|
|
||||||
fileId: 0,
|
|
||||||
pstartId: '',
|
|
||||||
pendId: '',
|
|
||||||
wordContent: '',
|
|
||||||
startIndex: 0,
|
|
||||||
endIndex: 0,
|
|
||||||
noteContent: ''
|
|
||||||
});
|
|
||||||
provide('noteParam', noteParam);
|
|
||||||
const positionInfo = ref<PositionInfo>({
|
|
||||||
start: -1,
|
|
||||||
end: -1,
|
|
||||||
parentStart: '',
|
|
||||||
parentEnd: ''
|
|
||||||
})
|
|
||||||
const activeNoteIndex = ref(-1);
|
|
||||||
provide('activeNoteIndex', activeNoteIndex);
|
|
||||||
provide('highlightContentFlag', (note: any, newIndex: number) => {
|
|
||||||
activeNoteIndex.value = newIndex;
|
|
||||||
// 显示笔记
|
|
||||||
showFileNote(note, true);
|
|
||||||
});
|
|
||||||
// 显示笔记的方式,mouseover | click
|
|
||||||
const showFileNoteEvent = ref('');
|
|
||||||
/**
|
|
||||||
* 打开添加笔记弹框的监听钩子方法
|
|
||||||
*/
|
|
||||||
const openAddNoteDialog = async () => {
|
|
||||||
await nextTick();
|
|
||||||
if (noteMsgRef.value) {
|
|
||||||
noteMsgRef.value.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 发表笔记
|
|
||||||
*/
|
|
||||||
const submitNote = async () => {
|
|
||||||
if(noteParam.value.id){
|
|
||||||
noteParam.value.noteContent = noteMsg.value;
|
|
||||||
try {
|
|
||||||
let res = await withLoading(addFileNote)(noteParam.value);
|
|
||||||
if (res.code == 200) {
|
|
||||||
ElMessage.success('笔记修改成功');
|
|
||||||
showAddNoteDialog.value = false;
|
|
||||||
noteMsg.value = '';
|
|
||||||
await updateFileContent();
|
|
||||||
} else {
|
|
||||||
ElMessage.error('笔记修改失败');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error(error && error.message ? error.message : '未知错误');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
noteParam.value.fileId = historyParams.fileId;
|
|
||||||
noteParam.value.pstartId = positionInfo.value.parentStart ? positionInfo.value.parentStart : '';
|
|
||||||
noteParam.value.pendId = positionInfo.value.parentEnd ? positionInfo.value.parentEnd : '';
|
|
||||||
noteParam.value.wordContent = selectText.value;
|
|
||||||
noteParam.value.startIndex = positionInfo.value.start;
|
|
||||||
noteParam.value.endIndex = positionInfo.value.end;
|
|
||||||
noteParam.value.noteContent = noteMsg.value;
|
|
||||||
|
|
||||||
try {
|
|
||||||
let res = await withLoading(addFileNote)(noteParam.value);
|
|
||||||
if (res.code == 200) {
|
|
||||||
ElMessage.success('笔记添加成功');
|
|
||||||
showAddNoteDialog.value = false;
|
|
||||||
noteMsg.value = '';
|
|
||||||
await updateFileContent();
|
|
||||||
} else {
|
|
||||||
ElMessage.error('笔记添加失败');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error(error && error.message ? error.message : '未知错误');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 更新文章内容
|
|
||||||
*/
|
|
||||||
const updateFileContent = async () => {
|
|
||||||
await getFileHtmlContent();
|
|
||||||
handelNoteFlagMouseEvent();
|
|
||||||
}
|
|
||||||
provide('updateFileContent', updateFileContent);
|
|
||||||
/**
|
|
||||||
* 隐藏笔记效果
|
|
||||||
*/
|
|
||||||
const displayFileNote = () => {
|
|
||||||
// 隐藏笔记弹框
|
|
||||||
const noteElements = document.querySelectorAll('span[id^="note-"]');
|
|
||||||
noteElements.forEach((element) => {
|
|
||||||
element.classList.remove('note-flag-active');
|
|
||||||
});
|
|
||||||
let noteContentElement = document.getElementById('note-content');
|
|
||||||
if (noteContentElement) {
|
|
||||||
noteContentElement.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移除高亮
|
|
||||||
const previousHighlights = document.querySelectorAll('.highlight');
|
|
||||||
previousHighlights.forEach(highlight => {
|
|
||||||
const parent = highlight.parentNode;
|
|
||||||
const textNode = document.createTextNode(highlight.textContent);
|
|
||||||
parent.replaceChild(textNode, highlight);
|
|
||||||
parent.normalize();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 显示笔记内容
|
|
||||||
* @param note
|
|
||||||
*/
|
|
||||||
const showFileNote = (note: any, isPosition: boolean = false) => {
|
|
||||||
displayFileNote();
|
|
||||||
if (!note) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let noteElement = document.getElementById('note-' + (activeNoteIndex.value + 1));
|
|
||||||
let noteContentElement = document.getElementById('note-content');
|
|
||||||
let fileContentElement = document.getElementById("file-content");
|
|
||||||
const rectFileContent = fileContentElement.getBoundingClientRect();
|
|
||||||
if (noteElement) {
|
|
||||||
// 选中正文中的笔记标号
|
|
||||||
noteElement.classList.add('note-flag-active');
|
|
||||||
|
|
||||||
// 高亮显示
|
|
||||||
highlightSelectedWord(note);
|
|
||||||
|
|
||||||
// 是否需要定位
|
|
||||||
if (isPosition) {
|
|
||||||
let selectedElements = document.querySelectorAll('mark[class^="highlight"]');
|
|
||||||
const fileContentElement = document.getElementById('file-content');
|
|
||||||
if (selectedElements.length > 0 && fileContentElement) {
|
|
||||||
selectedElements[0].scrollIntoView({
|
|
||||||
behavior:'smooth',
|
|
||||||
block: 'center'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 控制笔记弹框的位置及内容
|
|
||||||
noteContent.value = note.noteContent;
|
|
||||||
const rectNoteElement = noteElement.getBoundingClientRect();
|
|
||||||
if (noteContentElement) {
|
|
||||||
noteContentElement.innerHTML = noteContent.value;
|
|
||||||
noteContentElement.style.display = 'block';
|
|
||||||
let rectNoteContentElement = noteContentElement.getBoundingClientRect();
|
|
||||||
let left = rectNoteElement.left - 10;
|
|
||||||
if (left + rectNoteContentElement.width > rectFileContent.width) {
|
|
||||||
left = rectFileContent.width - rectNoteContentElement.width;
|
|
||||||
}
|
|
||||||
noteContentElement.style.left = left + 'px';
|
|
||||||
noteContentElement.style.top = rectNoteElement.top - rectNoteElement.height - 8 + 'px';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 对指定的文字高亮显示
|
|
||||||
* @param word
|
|
||||||
* @param pstartId
|
|
||||||
* @param pendId
|
|
||||||
*/
|
|
||||||
const highlightSelectedWord = (note: any = null) => {
|
|
||||||
if (!note) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let marked=new Mark('#file-html-content');
|
|
||||||
marked.markRanges([{start:note.startIndex,length:note.endIndex-note.startIndex}],{
|
|
||||||
className:'highlight',
|
|
||||||
exclude:['.note-flag']
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 获取p标签id为note-span标签以外的文字
|
|
||||||
* @param p
|
|
||||||
*/
|
|
||||||
const getPContent = (p: HTMLElement) => {
|
|
||||||
const clone = p.cloneNode(true) as HTMLElement;
|
|
||||||
const spans = clone.querySelectorAll('span[id^="note-"]');
|
|
||||||
spans.forEach(span => {
|
|
||||||
clone.removeChild(span);
|
|
||||||
});
|
|
||||||
return clone.textContent?.trim();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 移除高亮
|
|
||||||
*/
|
|
||||||
const removeHighlightSelectedWord = () => {
|
|
||||||
const previousHighlights = document.querySelectorAll('.highlight');
|
|
||||||
previousHighlights.forEach(highlight => {
|
|
||||||
const parent = highlight.parentNode;
|
|
||||||
const textNode = document.createTextNode(highlight.textContent);
|
|
||||||
parent.replaceChild(textNode, highlight);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 正文中笔记标记的鼠标事件
|
|
||||||
* 包括:移入、移出、点击
|
|
||||||
*/
|
|
||||||
const handelNoteFlagMouseEvent = () => {
|
|
||||||
const noteElements = document.querySelectorAll('span[id^="note-"]');
|
|
||||||
// 鼠标移入
|
|
||||||
noteElements.forEach((element) => {
|
|
||||||
element.addEventListener('mouseover', (e) => {
|
|
||||||
showFileNoteEvent.value = 'mouseover';
|
|
||||||
let elementId = Number(element.id.replace('note-', ''));
|
|
||||||
for (let i = 0; i < fileNote.notes.length; i++) {
|
|
||||||
if (i + 1 == elementId) {
|
|
||||||
activeNoteIndex.value = i;
|
|
||||||
showFileNote(fileNote.notes[i]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 鼠标移出
|
|
||||||
element.addEventListener('mouseleave', (e) => {
|
|
||||||
if (showFileNoteEvent.value === 'mouseover') {
|
|
||||||
activeNoteIndex.value = -1;
|
|
||||||
displayFileNote();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 点击事件
|
|
||||||
element.addEventListener('click', async (e) => {
|
|
||||||
showFileNoteEvent.value = 'click';
|
|
||||||
let elementId = Number(element.id.replace('note-', ''));
|
|
||||||
for (let i = 0; i < fileNote.notes.length; i++) {
|
|
||||||
if (i + 1 == elementId) {
|
|
||||||
activeNoteIndex.value = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 打开笔记tab页面
|
|
||||||
if (readingBox.value) {
|
|
||||||
readingBox.value.changeHead(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
// 右侧笔记定位
|
|
||||||
let rightNoteElement = document.getElementById('right-note-' + elementId);
|
|
||||||
if (rightNoteElement) {
|
|
||||||
rightNoteElement.scrollIntoView({ behavior:'smooth' });
|
|
||||||
}
|
|
||||||
// 左侧显示笔记
|
|
||||||
showFileNote(fileNote.notes[activeNoteIndex.value]);
|
|
||||||
e.stopPropagation();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
docxFlow.value = null;
|
|
||||||
pdfFlow.value = null;
|
|
||||||
try {
|
|
||||||
await getFileHtmlContent();
|
|
||||||
} catch (error: any) {
|
|
||||||
ElMessage.error(error && error.message ? error.message : '未知错误');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileContent.value) {
|
|
||||||
// 绑定笔记标记的鼠标事件
|
|
||||||
handelNoteFlagMouseEvent();
|
|
||||||
|
|
||||||
//绑定mouseup事件
|
|
||||||
fileContent.value.addEventListener('mouseup', function (event) {
|
|
||||||
activeNoteIndex.value = -1;
|
|
||||||
displayFileNote();
|
|
||||||
removeHighlightSelectedWord();
|
|
||||||
// 实现右侧笔记的定位
|
|
||||||
setTimeout(() => {
|
|
||||||
const selection = window.getSelection();
|
|
||||||
selectText.value = selection.toString();
|
|
||||||
let position = getGlobalSelectionPosition(selection);
|
|
||||||
positionInfo.value = position;
|
|
||||||
if (selectText.value && shortMenuDom.value && fileContent.value.contains(selection.anchorNode)) {
|
|
||||||
//给selection赋值,然后弹出菜单,再添加一个菜单隐藏的事件
|
|
||||||
let offset = getViewportOffsetByEvent(event);
|
|
||||||
shortMenuShow.value = true;
|
|
||||||
shortMenuDom.value.style.left = offset.left + 'px';
|
|
||||||
shortMenuDom.value.style.top = offset.top + 'px';
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
// 绑定滚动条滚动事件
|
|
||||||
fileContent.value.addEventListener('scroll', () => {
|
|
||||||
// 滚动滚动条时,更新笔记弹框的位置并控制其显隐
|
|
||||||
let noteElement = document.getElementById('note-' + ( activeNoteIndex.value + 1));
|
|
||||||
let noteContentElement = document.getElementById('note-content');
|
|
||||||
if (noteElement && noteContentElement) {
|
|
||||||
const rectNoteElement = noteElement.getBoundingClientRect();
|
|
||||||
let top = rectNoteElement.top - rectNoteElement.height - 8;
|
|
||||||
noteContentElement.style.top = rectNoteElement.top - rectNoteElement.height - 8 + 'px';
|
|
||||||
if (top < 75) {
|
|
||||||
noteContentElement.style.display = 'none';
|
|
||||||
} else if (activeNoteIndex.value != -1) {
|
|
||||||
noteContentElement.style.display = 'block';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('mousedown', function (event) {
|
|
||||||
let execlute = document.getElementById('shortMenuDom');
|
|
||||||
if (execlute && execlute.contains(event.target)) {
|
|
||||||
} else {
|
|
||||||
disShowShortMenu();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('resize', handleResize);
|
|
||||||
})
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
window.removeEventListener('resize', handleResize);
|
|
||||||
})
|
|
||||||
|
|
||||||
// 用于触发重新渲染的响应式变量
|
|
||||||
const reRenderDocKey = ref(0);
|
|
||||||
const reRenderPdfKey = ref(0);
|
|
||||||
// 处理窗口大小改变的函数
|
|
||||||
const handleResize = async () => {
|
|
||||||
if (suffix == 'docx') {
|
|
||||||
reRenderDocKey.value++;
|
|
||||||
} else if (suffix == 'pdf') {
|
|
||||||
reRenderPdfKey.value++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算笔记弹框的位置
|
|
||||||
let noteElement = document.getElementById('note-' + (activeNoteIndex.value + 1));
|
|
||||||
let noteContentElement = document.getElementById('note-content');
|
|
||||||
let fileContentElement = document.getElementById("file-content");
|
|
||||||
|
|
||||||
if (noteElement && noteContentElement && fileContentElement) {
|
|
||||||
const rectFileContent = fileContentElement.getBoundingClientRect();
|
|
||||||
const rectNoteElement = noteElement.getBoundingClientRect();
|
|
||||||
let rectNoteContentElement = noteContentElement.getBoundingClientRect();
|
|
||||||
let left = rectNoteElement.left - 10;
|
|
||||||
if (left + rectNoteContentElement.width > rectFileContent.width) {
|
|
||||||
left = rectFileContent.width - rectNoteContentElement.width;
|
|
||||||
}
|
|
||||||
noteContentElement.style.left = left + 'px';
|
|
||||||
let top = rectNoteElement.top - rectNoteElement.height - 8;
|
|
||||||
noteContentElement.style.top = rectNoteElement.top - rectNoteElement.height - 8 + 'px';
|
|
||||||
if (top < 75) {
|
|
||||||
noteContentElement.style.display = 'none';
|
|
||||||
} else if (activeNoteIndex.value != -1) {
|
|
||||||
noteContentElement.style.display = 'block';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getViewportOffsetByEvent = (evt) => {
|
|
||||||
var el = evt.target || evt.srcElement;
|
|
||||||
var frameEl = getWindow(el).frameElement;
|
|
||||||
var offset = {
|
|
||||||
left: evt.clientX,
|
|
||||||
top: evt.clientY
|
|
||||||
};
|
|
||||||
if (frameEl && el.ownerDocument !== document) {
|
|
||||||
var rect = getClientRect(frameEl);
|
|
||||||
offset.left += rect.left;
|
|
||||||
offset.top += rect.top;
|
|
||||||
}
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getClientRect = (element) => {
|
|
||||||
var bcr;
|
|
||||||
//trace IE6下在控制编辑器显隐时可能会报错,catch一下
|
|
||||||
try {
|
|
||||||
bcr = element.getBoundingClientRect();
|
|
||||||
} catch (e) {
|
|
||||||
bcr = {left: 0, top: 0, height: 0, width: 0}
|
|
||||||
}
|
|
||||||
var rect = {
|
|
||||||
left: Math.round(bcr.left),
|
|
||||||
top: Math.round(bcr.top),
|
|
||||||
height: Math.round(bcr.bottom - bcr.top),
|
|
||||||
width: Math.round(bcr.right - bcr.left)
|
|
||||||
};
|
|
||||||
var doc;
|
|
||||||
while ((doc = element.ownerDocument) !== document &&
|
|
||||||
(element = getWindow(doc).frameElement)) {
|
|
||||||
bcr = element.getBoundingClientRect();
|
|
||||||
rect.left += bcr.left;
|
|
||||||
rect.top += bcr.top;
|
|
||||||
}
|
|
||||||
return rect;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getWindow = (node) => {
|
|
||||||
var doc = node.ownerDocument || node;
|
|
||||||
return doc.defaultView || doc.parentWindow;
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleAnchorClick = (event) => {
|
|
||||||
// 检查点击的是否是 a 标签
|
|
||||||
if (event.target.tagName === 'A') {
|
|
||||||
const href = event.target.getAttribute('href');
|
|
||||||
|
|
||||||
// 检查是否是锚点链接(以 # 开头)
|
|
||||||
if (href && href.startsWith('#')) {
|
|
||||||
event.preventDefault();
|
|
||||||
const anchorId = href.substring(1);
|
|
||||||
scrollToAnchor(anchorId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const scrollToAnchor = (id) => {
|
|
||||||
const element = document.getElementById(id);
|
|
||||||
if (element) {
|
|
||||||
element.scrollIntoView({ behavior: 'smooth' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
provide('noteMsg',noteMsg);
|
|
||||||
provide('showAddNoteDialog',showAddNoteDialog);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="less">
|
|
||||||
.body {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.header {
|
|
||||||
margin-top: 10px;
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
height: 65px;
|
|
||||||
background: #FFFFFF;
|
|
||||||
border-radius: 0px 0px 0px 0px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-left: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
width: calc(100% - 40px);
|
|
||||||
line-height: 65px;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 18px;
|
|
||||||
color: #000000;
|
|
||||||
font-style: normal;
|
|
||||||
text-transform: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-content-container {
|
|
||||||
margin-top: 20px;
|
|
||||||
height: calc(100% - 95px);
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.file-content {
|
|
||||||
overflow: auto;
|
|
||||||
width: calc(100% - 580px);
|
|
||||||
height: calc(100% - 30px);
|
|
||||||
background: #FFFFFF;
|
|
||||||
box-shadow: 0px 0px 8px 1px rgba(180, 189, 221, 0.56);
|
|
||||||
border-radius: 16px 16px 16px 16px;
|
|
||||||
margin-left: 20px;
|
|
||||||
.mark{
|
|
||||||
background: #D0EAC8;
|
|
||||||
}
|
|
||||||
textarea {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding: 35px 35px;
|
|
||||||
border: none; /* 去掉边框 */
|
|
||||||
resize: none; /* 禁止调整大小 */
|
|
||||||
//overflow: hidden; /* 隐藏溢出的内容,如果内容超出可视区域 */
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-md {
|
|
||||||
padding: 30px;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
::v-deep(p) {
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 1.8rem;
|
|
||||||
margin-block-start: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep(.highlight) {
|
|
||||||
background: #D0EAC8;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep(.table-container) {
|
|
||||||
display: flex;
|
|
||||||
align-items: end;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep(.note-flag) {
|
|
||||||
width: 23px;
|
|
||||||
height: 28px;
|
|
||||||
line-height: 28px;
|
|
||||||
display: inline-block;
|
|
||||||
text-align: center;
|
|
||||||
font-family: DIN Alternate, DIN Alternate;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 10px;
|
|
||||||
margin-left: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
background: url("@/assets/images/reading/note.png");
|
|
||||||
color: #004EA0;
|
|
||||||
background-size: contain !important;
|
|
||||||
background-repeat: no-repeat !important;
|
|
||||||
background-position: center bottom !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep(.note-flag:hover) {
|
|
||||||
color: #FAFBFF;
|
|
||||||
background: url("@/assets/images/reading/note_active.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep(.note-flag-active) {
|
|
||||||
color: #FAFBFF;
|
|
||||||
background: url("@/assets/images/reading/note_active.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep(#page-container-1) {
|
|
||||||
.note-flag {
|
|
||||||
width: 69px !important;
|
|
||||||
height: 84px !important;
|
|
||||||
font-size: 42px !important;
|
|
||||||
line-height: 84px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-note {
|
|
||||||
max-width: 240px;
|
|
||||||
height: 36px;
|
|
||||||
line-height: 36px;
|
|
||||||
padding: 0 12px;
|
|
||||||
position: absolute;
|
|
||||||
display: none;
|
|
||||||
background: #004EA0;
|
|
||||||
box-shadow: 0px 0px 8px 1px rgba(180, 189, 221, 0.56);
|
|
||||||
border-radius: 8px 8px 8px 8px;
|
|
||||||
border: 1px solid #D5DDFF;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #FFFFFF;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep(.docx-wrapper) {
|
|
||||||
background: transparent;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
span::selection {
|
|
||||||
background: #D0EAC8;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea::selection {
|
|
||||||
background: #D0EAC8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep(.docx-wrapper .docx) {
|
|
||||||
width: 100% !important;
|
|
||||||
padding: 20px 40px !important;
|
|
||||||
min-height: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep(.docx-wrapper > section.docx) {
|
|
||||||
box-shadow: none !important;
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep(.docx *) {
|
|
||||||
font-size: 16px !important;
|
|
||||||
font-family: PingFangSC !important;
|
|
||||||
line-height: 30px !important;
|
|
||||||
color: #333639 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep(.vue-office-pdf) {
|
|
||||||
padding: 20px 40px;
|
|
||||||
height: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep(.vue-office-pdf-wrapper) {
|
|
||||||
padding: 0px !important;
|
|
||||||
background: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep(.vue-office-pdf-wrapper > canvas) {
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep(p::selection) {
|
|
||||||
background: #D0EAC8;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-right {
|
|
||||||
margin-left: 20px;
|
|
||||||
margin-right: 20px;
|
|
||||||
width: 510px;
|
|
||||||
height: calc(100% - 30px);
|
|
||||||
background: linear-gradient(180deg, rgba(178, 226, 255, 0.4) 0%, rgba(106, 148, 255, 0.4) 100%);
|
|
||||||
box-shadow: 0 0 8px 1px rgba(180, 189, 221, 0.56);
|
|
||||||
border-radius: 16px;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.click-bar-button {
|
|
||||||
height: 36px;
|
|
||||||
padding: 0 4px;
|
|
||||||
background: #FFFFFF;
|
|
||||||
box-shadow: 0px 0px 8px 1px rgba(180, 189, 221, 0.56);
|
|
||||||
border-radius: 8px 8px 8px 8px;
|
|
||||||
border: 1px solid #D5DDFF;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 9999;
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
.menu-name {
|
|
||||||
line-height: 36px;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-name:hover {
|
|
||||||
color: #004EA0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vertical-line {
|
|
||||||
height: 16px;
|
|
||||||
width: 2px;
|
|
||||||
background-color: #D5DDFF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-note-dialog {
|
|
||||||
box-shadow: 0px 0px 8px 1px rgba(180, 189, 221, 0.56);
|
|
||||||
border-radius: 16px 16px 16px 16px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,766 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="doc-writing">
|
|
||||||
|
|
||||||
<!-- 文件列表 -->
|
|
||||||
<div class="doc-list">
|
|
||||||
<div class="doc-list-top">
|
|
||||||
<div style="display: flex;">
|
|
||||||
<img @click="goBack" src="../../assets/images/writing/back.png" alt="" style="margin-top:5px">
|
|
||||||
<div class="doc-list-title" style="margin-left:15px" v-text="pageTitle"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="doc-list-operate">
|
|
||||||
<el-input style="width: 316px;height: 32px" v-model="searchWord" placeholder="请输入文件名称关键字搜索">
|
|
||||||
<template #suffix>
|
|
||||||
<img src="@/assets/images/search.png" class="doc-search">
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
|
|
||||||
<div class="doc-import" @click="clickUpload">
|
|
||||||
<img src="@/assets/images/writing/doc_import.png">
|
|
||||||
<span>导入</span>
|
|
||||||
</div>
|
|
||||||
<div class="batch-delete" @click="batchDelete">
|
|
||||||
<img src="@/assets/images/reading/del.png" alt="">
|
|
||||||
<p class="batch-del">批量删除</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="doc-list-empty" v-if="myFileList.list.length === 0">
|
|
||||||
<img src="@/assets/images/writing/doc_file.png" alt="">
|
|
||||||
<div class="empty-tips">
|
|
||||||
<div class="tips-top">
|
|
||||||
<span>暂无相关内容,</span>
|
|
||||||
<span @click="clickUpload" style="color: #004EA0;cursor: pointer">点击上传文档</span>
|
|
||||||
</div>
|
|
||||||
<div class="tips-bottom">支持{{ allowFileTypes.join('/') }}格式文件,文件大小不超过10M。</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="myFileList.list.length > 0" class="doc-list-non-empty">
|
|
||||||
<el-table :data="myFileList.list" style="width: 100%"
|
|
||||||
:row-style="myFileRowStyle"
|
|
||||||
:header-row-style="myFileHeaderRowStyle"
|
|
||||||
@selection-change="handleSelectionChange">
|
|
||||||
<el-table-column type="selection" width="55"></el-table-column>
|
|
||||||
<el-table-column label="文件名称" min-width="300">
|
|
||||||
<template #default="scope">
|
|
||||||
<div class="doc-file-title" @click="showDetail(scope.row)">{{ scope.row.filename }}</div>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="文档大小" width="180">
|
|
||||||
<template #default="scope">
|
|
||||||
<span>{{ formateSize(scope.row.size) }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="创建时间" width="180">
|
|
||||||
<template #default="scope">
|
|
||||||
<span>{{ scope.row.createTime }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column label="操作" width="180">
|
|
||||||
<template #default="scope">
|
|
||||||
<div class="doc-operate">
|
|
||||||
<img @click="renameDoc(scope.row)" title="重命名" src="@/assets/images/writing/doc_edit.png" alt="">
|
|
||||||
<img @click="downLoadDoc(scope.row)" title="下载" src="@/assets/images/writing/doc_download.png" alt="">
|
|
||||||
<img @click="delDoc(scope.row)" title="删除" src="@/assets/images/writing/doc_del.png" alt="">
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 文件重命名弹框 -->
|
|
||||||
<el-dialog v-model="showRenameDialog" title="重命名" width="500"
|
|
||||||
:close-on-click-modal="false" :modal-append-to-body="false" :close-on-press-escape="false">
|
|
||||||
<el-input v-model="docName" placeholder="请输入文件名称"/>
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer">
|
|
||||||
<el-button @click="showRenameDialog = false">取消</el-button>
|
|
||||||
<el-button type="primary" @click="saveDocName()">
|
|
||||||
保存
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
|
|
||||||
<!-- 上传文件弹框 -->
|
|
||||||
<el-dialog v-model="showUploadDialog" title="上传文件" width="715" @close="closeUpload()"
|
|
||||||
:close-on-click-modal="false" :modal-append-to-body="false" :close-on-press-escape="false">
|
|
||||||
<el-upload
|
|
||||||
style="display: inline-block;"
|
|
||||||
class="upload-demo"
|
|
||||||
action="#"
|
|
||||||
:auto-upload="false"
|
|
||||||
accept=".pdf,.docx,.txt,.xlsx,.xls"
|
|
||||||
name="files"
|
|
||||||
multiple
|
|
||||||
:show-file-list="false"
|
|
||||||
:file-list="uploadingFileList"
|
|
||||||
:on-change="handleFileChange"
|
|
||||||
:on-remove="handleRemove"
|
|
||||||
>
|
|
||||||
<el-icon :size="100" class="el-icon--upload" style="display: flex;flex-wrap: wrap">
|
|
||||||
<upload-filled style="color: #004EA0"/>
|
|
||||||
<span style="margin-top:-50px;font-size:13px; font-style: normal;color:#004EA0">选择文件</span>
|
|
||||||
</el-icon>
|
|
||||||
<template #tip>
|
|
||||||
<div class="el-upload__tip">
|
|
||||||
支持{{ allowFileTypes.join('/') }}格式文件,文件大小不超过10M
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-upload>
|
|
||||||
<div class="files-upload-result">
|
|
||||||
<div class="result" v-for="item in uploadingFileList">
|
|
||||||
<div class="file-name" :title="item.name">{{ item.name }}</div>
|
|
||||||
<div class="result-info">
|
|
||||||
<div class="fail-msg" v-if="item.status === 'fail'" :title="item.message">{{ item.message }}</div>
|
|
||||||
<img class="upload-remove" v-if="item.status === 'ready'" src="@/assets/images/reading/upload_remove.png"
|
|
||||||
alt="" @click="handleRemove(item)">
|
|
||||||
<div class="loading" v-if="item.status === 'uploading'">
|
|
||||||
<Loading></Loading>
|
|
||||||
</div>
|
|
||||||
<img v-if="item.status === 'success'" src="@/assets/images/reading/upload_success.png" alt="">
|
|
||||||
<img v-if="item.status === 'fail'" src="@/assets/images/reading/upload_fail.png" alt="">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer" style="text-align: center" v-if="uploadingFileList.length > 0">
|
|
||||||
<el-button @click="closeUpload()">关闭</el-button>
|
|
||||||
<div class="dialog-tips">
|
|
||||||
<img src="@/assets/images/reading/tips.png" alt="">
|
|
||||||
<span>关闭弹窗后文件将继续上传</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import {onMounted, reactive, ref, watch} from "vue";
|
|
||||||
import {useRouter} from "vue-router";
|
|
||||||
import {delFile, delFiles, downloadFile, editFile, getKnowledgeBaseContent, getSize, uploadFile} from "@/api";
|
|
||||||
import {withLoading} from "@/utils/loading";
|
|
||||||
import {formatFileSize} from "@/utils/index"
|
|
||||||
import {ElMessage, ElMessageBox, type UploadFile, type UploadFiles} from "element-plus";
|
|
||||||
import {UploadFilled} from '@element-plus/icons-vue'
|
|
||||||
import Loading from '@/components/Loading.vue'
|
|
||||||
|
|
||||||
type DocInfo = {
|
|
||||||
id: number;
|
|
||||||
filename: string;
|
|
||||||
context: string;
|
|
||||||
articleAbstract: string;
|
|
||||||
articleKeywords: string;
|
|
||||||
articleParagraph: string;
|
|
||||||
embeddingId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CusUploadFile extends UploadFile {
|
|
||||||
message?: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
const historyParams = history.state;
|
|
||||||
|
|
||||||
// 允许上传的文件类型
|
|
||||||
const allowFileTypes = ["TXT", "DOCX", 'PDF','XLSX','XLS'];
|
|
||||||
|
|
||||||
//文件夹id
|
|
||||||
const knowledgeBaseId = historyParams.folderId;
|
|
||||||
const pageTitle = historyParams.fileName;
|
|
||||||
const uploadingFileList = ref([] as CusUploadFile[]);
|
|
||||||
const isUploading = ref(false);
|
|
||||||
const uploadFinishFlag = ref(true);
|
|
||||||
const showUploadDialog = ref(false);
|
|
||||||
// 单文件大小限制3M
|
|
||||||
const FILE_MAX_SIZE = 10 * 1024 * 1024;
|
|
||||||
// 总文件大小限制50M
|
|
||||||
const TOTAL_FILE_MAX_SIZE = 50 * 1024 * 1024;
|
|
||||||
// 已上传文件的大小
|
|
||||||
const totalFileSize = ref<number>(0);
|
|
||||||
// 当前正在上传的文件
|
|
||||||
const currentIndex = ref(0);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 导入按钮及上传文档按钮的点击事件
|
|
||||||
*/
|
|
||||||
const clickUpload = async () => {
|
|
||||||
await getUploadedSize();
|
|
||||||
showUploadDialog.value = true;
|
|
||||||
if (uploadFinishFlag.value) {
|
|
||||||
uploadingFileList.value = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文件状态改变时的钩子
|
|
||||||
* @param file
|
|
||||||
* @param newFileList
|
|
||||||
*/
|
|
||||||
const handleFileChange = async (file: UploadFile, newFileList: UploadFiles) => {
|
|
||||||
const fileExtension = file.name.split('.').pop()?.toUpperCase();
|
|
||||||
if (!allowFileTypes.includes(fileExtension as string)) {
|
|
||||||
ElMessage.warning(file.name + '文件格式不匹配');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file.size && file.size > FILE_MAX_SIZE) {
|
|
||||||
ElMessage.warning(file.name + '文件大小已超过10M,不允许上传');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
totalFileSize.value += file.size ? file.size : 0;
|
|
||||||
if (totalFileSize.value > TOTAL_FILE_MAX_SIZE) {
|
|
||||||
totalFileSize.value -= file.size ? file.size : 0;
|
|
||||||
ElMessage.warning('个人素材总量不能超过50M,文件' + file.name + '添加失败');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadingFileList.value.push(file);
|
|
||||||
};
|
|
||||||
// 文件自动上传
|
|
||||||
watch(uploadingFileList, newVal => {
|
|
||||||
if (!isUploading.value) {
|
|
||||||
for (let i = 0; i < newVal.length; i++) {
|
|
||||||
if (newVal[i].status === 'ready') {
|
|
||||||
isUploading.value = true;
|
|
||||||
newVal[i].status = 'uploading';
|
|
||||||
startUpload();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {immediate: true, deep: true});
|
|
||||||
/**
|
|
||||||
* 文件删除时的钩子
|
|
||||||
* @param file
|
|
||||||
* @param newFileList
|
|
||||||
*/
|
|
||||||
const handleRemove = (file: UploadFile) => {
|
|
||||||
uploadingFileList.value = uploadingFileList.value.filter(item => item.uid !== file.uid);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 开始上传文件
|
|
||||||
*/
|
|
||||||
const startUpload = async () => {
|
|
||||||
await uploadNextFile();
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* 获取已上传文件的大小
|
|
||||||
*/
|
|
||||||
const getUploadedSize = async () => {
|
|
||||||
try {
|
|
||||||
let res = await withLoading(getSize)();
|
|
||||||
if (res.code === 200) {
|
|
||||||
totalFileSize.value = res.data;
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
ElMessage.error(error && error.message ? error.message : '未知错误');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 上传下一个文件
|
|
||||||
*/
|
|
||||||
const uploadNextFile = async () => {
|
|
||||||
uploadFinishFlag.value = true;
|
|
||||||
for (let i = 0; i < uploadingFileList.value.length; i++) {
|
|
||||||
if (uploadingFileList.value[i].status !== 'success' && uploadingFileList.value[i].status !== 'fail') {
|
|
||||||
uploadFinishFlag.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uploadFinishFlag.value) {
|
|
||||||
isUploading.value = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = uploadingFileList.value[currentIndex.value];
|
|
||||||
file.status = 'uploading';
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('files', file.raw);
|
|
||||||
formData.append('folderId', 0);
|
|
||||||
formData.append('knowledgeBaseId', knowledgeBaseId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
let res = await uploadFile(formData);
|
|
||||||
if (res.code && res.code != 200) {
|
|
||||||
ElMessage.error(res.msg + "【" + file.name + "】");
|
|
||||||
file.status = 'fail';
|
|
||||||
file.message = res.msg;
|
|
||||||
} else {
|
|
||||||
// 文件上传成功
|
|
||||||
file.status = 'success';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 文件上传失败
|
|
||||||
file.status = 'fail';
|
|
||||||
file.message = error && error.message ? error.message : '未知错误';
|
|
||||||
} finally {
|
|
||||||
// 处理下一个文件
|
|
||||||
currentIndex.value++;
|
|
||||||
await uploadNextFile();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* 关闭上传文件弹框
|
|
||||||
*/
|
|
||||||
const closeUpload = () => {
|
|
||||||
showUploadDialog.value = false;
|
|
||||||
// uploadingFileList.value = [];
|
|
||||||
currentIndex.value = 0;
|
|
||||||
getMyFileList();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 路由
|
|
||||||
const router = useRouter();
|
|
||||||
const goBack = () => {
|
|
||||||
router.push({name: 'KnowledgeBase'})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 文件检索关键词
|
|
||||||
const searchWord = ref('');
|
|
||||||
// 我的文件列表
|
|
||||||
const myFileList = reactive({list: []});
|
|
||||||
const netFileList = reactive({list: []});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取我的文件列表
|
|
||||||
*/
|
|
||||||
const getMyFileList = async () => {
|
|
||||||
try {
|
|
||||||
let res = await withLoading(getKnowledgeBaseContent)({
|
|
||||||
folderType: "1",
|
|
||||||
knowledgeBaseId: knowledgeBaseId + "",
|
|
||||||
patentId: "0"
|
|
||||||
});
|
|
||||||
if (res && res.data) {
|
|
||||||
netFileList.list = res.data;
|
|
||||||
myFileList.list = netFileList.list;
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
ElMessage.error(error && error.message ? error.message : '未知错误');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(searchWord, (newValue, oldValue) => {
|
|
||||||
if (newValue && newValue.trim()) {
|
|
||||||
myFileList.list = netFileList.list.filter(item => {
|
|
||||||
return item.filename.indexOf(newValue.trim()) !== -1
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
myFileList.list = netFileList.list;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 表格体背景色
|
|
||||||
*/
|
|
||||||
const myFileRowStyle = () => {
|
|
||||||
return '--el-table-tr-bg-color: rgba(250, 251, 255, 0.5);'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 表头背景色
|
|
||||||
*/
|
|
||||||
const myFileHeaderRowStyle = () => {
|
|
||||||
return '--el-table-header-bg-color: #FAFBFF;'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查看文件详情
|
|
||||||
* @param doc
|
|
||||||
*/
|
|
||||||
const showDetail = (doc: DocInfo) => {
|
|
||||||
if(!doc.articleAbstract){
|
|
||||||
doc.articleAbstract="暂无内容,请重试"
|
|
||||||
}
|
|
||||||
if(!doc.articleKeywords){
|
|
||||||
doc.articleKeywords="暂无内容,请重试"
|
|
||||||
}
|
|
||||||
if(!doc.articleParagraph){
|
|
||||||
doc.articleParagraph="暂无内容,请重试"
|
|
||||||
}
|
|
||||||
if (doc.articleAbstract.startsWith("\"")) {
|
|
||||||
doc.articleAbstract = JSON.parse(doc.articleAbstract);
|
|
||||||
}
|
|
||||||
if (doc.articleKeywords.startsWith("\"")) {
|
|
||||||
doc.articleKeywords = JSON.parse(doc.articleKeywords);
|
|
||||||
}
|
|
||||||
if (doc.articleParagraph.startsWith("\"")) {
|
|
||||||
doc.articleParagraph = JSON.parse(doc.articleParagraph);
|
|
||||||
}
|
|
||||||
router.push({
|
|
||||||
name: "FileDetail",
|
|
||||||
state: {
|
|
||||||
folderName: pageTitle,
|
|
||||||
folderId: knowledgeBaseId,
|
|
||||||
fileId: doc.id,
|
|
||||||
embeddingId: doc.embeddingId,
|
|
||||||
fileName: doc.filename,
|
|
||||||
articleAbstract: doc.articleAbstract,
|
|
||||||
articleKeywords: doc.articleKeywords,
|
|
||||||
articleParagraph: doc.articleParagraph,
|
|
||||||
fullContent: doc.context
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重命名
|
|
||||||
* @param doc
|
|
||||||
*/
|
|
||||||
const showRenameDialog = ref(false);
|
|
||||||
const docName = ref('');
|
|
||||||
const docType = ref('');
|
|
||||||
const docId = ref(0);
|
|
||||||
const renameDoc = (doc: DocInfo) => {
|
|
||||||
if (doc.filename) {
|
|
||||||
let names = doc.filename.split('.');
|
|
||||||
docName.value = names[0];
|
|
||||||
if (names.length === 2) {
|
|
||||||
docType.value = names[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
docId.value = doc.id;
|
|
||||||
showRenameDialog.value = true;
|
|
||||||
}
|
|
||||||
const saveDocName = async () => {
|
|
||||||
if (docName.value === '' || docName.value.trim() === '') {
|
|
||||||
ElMessage.error("请输入文件名");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let param = new FormData();
|
|
||||||
param.append('fileId', docId.value + '');
|
|
||||||
param.append('fileName', docName.value + '.' + docType.value);
|
|
||||||
param.append('folderId', '0');
|
|
||||||
//保存成功后关闭弹窗
|
|
||||||
try {
|
|
||||||
await withLoading(editFile)(param);
|
|
||||||
//调用重命名接口
|
|
||||||
ElMessage.success("修改成功");
|
|
||||||
getMyFileList();
|
|
||||||
} catch {
|
|
||||||
ElMessage.error("修改失败");
|
|
||||||
}
|
|
||||||
showRenameDialog.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 下载文档
|
|
||||||
*/
|
|
||||||
const downLoadDoc = async function (file) {
|
|
||||||
try {
|
|
||||||
const response = await downloadFile({fileId: file.id});
|
|
||||||
const url = URL.createObjectURL(new Blob([response]));
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = url;
|
|
||||||
link.setAttribute('download', file.filename); // 设置下载文件的名称
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
document.body.removeChild(link);
|
|
||||||
} catch (error: any) {
|
|
||||||
ElMessage.error(error && error.message ? error.message : '未知错误');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除文档
|
|
||||||
*/
|
|
||||||
const delDoc = (doc: DocInfo) => {
|
|
||||||
delHistory(doc.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getMyFileList();
|
|
||||||
})
|
|
||||||
|
|
||||||
// 删除文档
|
|
||||||
const delHistory = async (id: number) => {
|
|
||||||
ElMessageBox.confirm("确认删除文档吗?", "提示", {
|
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
}).then(async () => {
|
|
||||||
try {
|
|
||||||
let res = await withLoading(delFile)(id + '');
|
|
||||||
if (res && res.code === 200) {
|
|
||||||
ElMessage.success("删除成功");
|
|
||||||
getMyFileList();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ElMessage.error("删除失败");
|
|
||||||
} catch (error: any) {
|
|
||||||
ElMessage.error("删除失败");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const batchDelete = async () => {
|
|
||||||
if (idList.value.length > 0) {
|
|
||||||
ElMessageBox.confirm("确认删除文档吗?", "提示", {
|
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
}).then(async () => {
|
|
||||||
try {
|
|
||||||
let res = await withLoading(delFiles)({
|
|
||||||
fileList: idList.value,
|
|
||||||
folderList: []
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res && res.code === 200) {
|
|
||||||
ElMessage.success("删除成功");
|
|
||||||
} else {
|
|
||||||
ElMessage.error("删除失败");
|
|
||||||
}
|
|
||||||
|
|
||||||
await getMyFileList();
|
|
||||||
} catch (err) {
|
|
||||||
ElMessage.error("删除失败");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const formateSize = (size: string) => {
|
|
||||||
return formatFileSize(Number(size.substring(0, size.length - 1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
const idList = ref([]);
|
|
||||||
const handleSelectionChange = (rows) => {
|
|
||||||
idList.value.length = 0;
|
|
||||||
if (rows.length > 0) {
|
|
||||||
for (let i = 0; i < rows.length; i++) {
|
|
||||||
idList.value.push(rows[i].id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.doc-writing {
|
|
||||||
width: 100%;
|
|
||||||
padding: 20px 50px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-list {
|
|
||||||
width: calc(100% - 100px);
|
|
||||||
height: 100%;
|
|
||||||
margin-top: 20px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-list .doc-list-top {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-list .doc-list-top img {
|
|
||||||
width: 16px;
|
|
||||||
height: 13.8px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-list .doc-list-top .doc-list-title {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 20px;
|
|
||||||
color: #000000;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-list .doc-list-top .doc-list-operate {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-list .doc-list-top .batch-delete {
|
|
||||||
height: 32px;
|
|
||||||
width: 100px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
margin-left: 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
background: #FFFFFF;
|
|
||||||
border-radius: 4px 4px 4px 4px;
|
|
||||||
border: 1px solid #004EA0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-list .doc-list-top .batch-delete img {
|
|
||||||
margin-right: 5px;
|
|
||||||
width: 16px;
|
|
||||||
margin-left: -2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-list .doc-list-top .batch-delete .batch-del {
|
|
||||||
color: #004EA0;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-list .doc-list-top .doc-list-operate .doc-search {
|
|
||||||
cursor: pointer;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-list .doc-list-top .doc-list-operate .doc-import {
|
|
||||||
display: inline-block;
|
|
||||||
width: 80px;
|
|
||||||
height: 32px;
|
|
||||||
margin-left: 16px;
|
|
||||||
background: #004EA0;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #FAFBFF;
|
|
||||||
line-height: 32px;
|
|
||||||
text-align: center;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-list .doc-list-top .doc-list-operate .doc-import img {
|
|
||||||
width: 9px;
|
|
||||||
height: 11px;
|
|
||||||
margin-right: 8px;
|
|
||||||
margin-left: -3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-list .doc-list-empty {
|
|
||||||
height: 235px;
|
|
||||||
border-radius: 8px 8px 8px 8px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-list .doc-list-empty img {
|
|
||||||
height: 44px;
|
|
||||||
margin-right: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-list .doc-list-empty .empty-tips .tips-top {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 16px;
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*.doc-list .doc-list-empty .empty-tips .tips-top span {
|
|
||||||
color: #004EA0;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
.doc-list .doc-list-empty .empty-tips .tips-bottom {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #858A94;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-list-non-empty {
|
|
||||||
height: calc(100% - 60px);
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-list-non-empty .doc-file-title {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #004EA0;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-list-non-empty .doc-operate {
|
|
||||||
padding-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.doc-list-non-empty .doc-operate img {
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-demo {
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.upload-btn {
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-upload__tip {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.files-upload-result {
|
|
||||||
max-height: 210px;
|
|
||||||
overflow-y: auto;
|
|
||||||
margin-top: 16px;
|
|
||||||
.result {
|
|
||||||
height: 30px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.file-name {
|
|
||||||
width: 434px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #606771;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.fail-msg {
|
|
||||||
width: 192px;
|
|
||||||
text-align: right;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #BD0000;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading {
|
|
||||||
width: 38px;
|
|
||||||
height: 38px;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 14px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-remove {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-tips {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #858A94;
|
|
||||||
margin-top: 8px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
margin-right: 3px
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user