[前端] 研读模块改造为三栏布局:文件树+预览+问答

This commit is contained in:
2026-04-02 12:01:27 +08:00
parent 4355e45580
commit a339757596
8 changed files with 1138 additions and 2159 deletions

View File

@@ -1,6 +1,6 @@
<template>
<div class="content">
<Operates v-show="url != '/login' && url != '/writing/edit'&& url != '/knowledgeBase/fileDetail'" />
<Operates v-show="url != '/login' && url != '/writing/edit'" />
<RouterView />
</div>
</template>

View File

@@ -59,11 +59,11 @@
import {copyToClip} from "@/utils";
import {ElMessage} from "element-plus";
import {fileGuidInfo} from "@/api";
import {onMounted, ref} from "vue";
import {computed, inject, ref, type Ref} from "vue";
import MarkdownIt from "markdown-it";
import {transforMd} from "@/utils/markdown";
const state=history.state;
const selectedFile = inject('selectedFile') as Ref<any>;
const emits=defineEmits(["refreshAbs","refreshCon"]);
const mardown=new MarkdownIt();
@@ -73,18 +73,29 @@ const refreshAbs=()=>{
const refreshCon=()=>{
emits('refreshCon');
}
const historyParams=history.state;
const articleAbstract=ref(historyParams.articleAbstract);
const articleKeywords=ref(historyParams.articleKeywords);
const articleParagraph=ref(historyParams.articleParagraph);
//总结提炼
onMounted(async () => {
if(articleKeywords.value&&(articleKeywords.value.indexOf('关键词:')>-1||articleKeywords.value.indexOf('关键词:')>-1)){
articleKeywords.value=articleKeywords.value.substring(articleKeywords.value.indexOf('关键词:')+4,articleKeywords.value.length);
articleKeywords.value=articleKeywords.value.substring(articleKeywords.value.indexOf('关键词:')+4,articleKeywords.value.length);
const articleAbstract=ref('');
const articleKeywords=ref('');
const articleParagraph=ref('');
// 监听选中文件变化,更新导读内容
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)=>{
copyToClip(text);
ElMessage.success("复制成功");
@@ -95,7 +106,7 @@ const refreshGuide=async (type:string)=>{
let param = {
type: type,
context: fileBox.innerText,
fileId: historyParams.fileId,
fileId: selectedFile.value?.fileId,
}
if(type==='6'){
articleAbstract.value=''

View File

@@ -27,9 +27,9 @@
<script setup lang="ts">
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 = {
doc_id: string,
@@ -53,9 +53,9 @@ const showNoData = ref(false);
const getLiteratureList = async () => {
try {
let res = await getPaper({
fileName: historyParams.fileName,
keywords: historyParams.articleKeywords,
id: historyParams.fileId,
fileName: selectedFile.value?.fileName,
keywords: selectedFile.value?.articleKeywords,
id: selectedFile.value?.fileId,
retry: 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();
}
});
onMounted(() => {
if (selectedFile.value?.fileId) {
getLiteratureList();
}
})
</script>

View File

@@ -47,7 +47,7 @@
</template>
<script setup lang='ts'>
import {inject, onMounted, reactive, ref} from "vue";
import {inject, onMounted, reactive, ref, watch, type Ref} from "vue";
import {
deleteChatMessageById,
delHistoryChatById,
@@ -62,6 +62,9 @@ import ReadingCreateMessage from "@/components/ReadingCreateMessage.vue";
import {withLoading} from "@/utils/loading";
import {ElMessage} from "element-plus";
import {debounce} from "@/utils";
const selectedFile = inject('selectedFile') as Ref<any>;
// 引用内容
const quoteMsg = inject("quoteMsg");
const updateQuoteMsg = inject("updateQuoteMsg");
@@ -79,7 +82,6 @@ const sendStatus = ref(false);
const chatStatus = ref(false);
const chatNumber = ref(0);
const chatId = ref('');
const historyParam = history.state;
const conversationId = ref('');
const chatInfo = reactive([]);
let controller = null;
@@ -143,7 +145,7 @@ const getFetchChatAPI = async (prompt) => {
const chatData = {
prompt,
type: 99,
fileId: historyParam.fileId
fileId: selectedFile.value?.fileId
};
try {
const fetchChatAPIData = await fetchChatAPI(chatData);
@@ -196,10 +198,10 @@ const getFetchChatAPIProcess = async (type: string) => {
headers: headers,
signal: controller.signal,
body: JSON.stringify({
fileNames: [historyParam.embeddingId],
fileNames: [selectedFile.value?.embeddingId],
conversationId: conversationId.value,
promptName: "default",
knowledgeBaseIdList: [historyParam.folderId],
knowledgeBaseIdList: [selectedFile.value?.folderId],
chatType: type,
quote: quoteMsg.value
}),
@@ -313,15 +315,21 @@ const autoPositionToBottom = () => {
element.scrollTop = element.scrollHeight + 55;
}
onMounted(async () => {
//请求文件对话的dataId
const loadChatHistory = async () => {
// 重置对话状态
chatInfo.length = 0;
chatId.value = '';
firstChat.value = true;
chatNumber.value = 0;
conversationId.value = '';
if (!selectedFile.value?.fileId) return;
try {
let hisResponse = await listFileChatHistory(historyParam.fileId);
//根据dataId请求获取历史记录
let hisResponse = await listFileChatHistory(selectedFile.value.fileId);
if (hisResponse.code == 200 && hisResponse.data) {
chatNumber.value = hisResponse.data;
firstChat.value = false;
//请求文件对话的dataId
let historyList = await listChatMessage({
chatNumber: chatNumber.value,
fileType: 1
@@ -358,9 +366,17 @@ onMounted(async () => {
}
}
} catch (error: any) {
ElMessage.error(error && error.message ? error.message : '未知错误');
// 新文件没有历史记录,不报错
}
};
// 监听文件切换,重新加载对话历史
watch(() => selectedFile.value?.fileId, () => {
loadChatHistory();
});
onMounted(async () => {
await loadChatHistory();
handleReadingMessageContentScroll();
})
let messageInstance = null;

View File

@@ -34,16 +34,6 @@ const routes: RouteRecordRaw[] = [
name: 'KnowledgeBase',
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',
name: 'Application',

View File

@@ -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>

View File

@@ -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