提交 0cf23678 authored 作者: kxjia's avatar kxjia

添加文件预览组件

上级 4b392f21
<template>
<a-card
:bodyStyle="bodyStyle"
:style="cardStyle"
:bordered="bordered"
>
<template #title v-if="showTitle">
<span v-html="fileTitle"></span>
</template>
<template #extra v-if="showExtra">
<slot name="extra"></slot>
<a v-if="showToggleCollapse" href="#" style="margin-left: 20px" @click="toggleCollapse">{{ toggleCollapseLeft ? '展开>' : '<收起' }}</a>
</template>
<i-frame v-if="fileTp === 'pdf' || fileTp === 'url'" :src="fileUrl" :style="iframeStyle" />
<div v-else :style="contentStyle" v-html="docContent" class="file-content"></div>
</a-card>
</template>
<script lang="ts" setup>
import iFrame from '/@/components/iFrame/index.vue';
import { ref, computed } from 'vue';
import mammoth from 'mammoth';
import * as XLSX from 'xlsx';
const props = withDefaults(defineProps<{
showTitle?: boolean;
showExtra?: boolean;
showToggleCollapse?: boolean;
bordered?: boolean;
bodyStyle?: Record<string, any>;
cardStyle?: Record<string, any>;
iframeStyle?: Record<string, any>;
contentStyle?: Record<string, any>;
useStaticPrefixForDocx?: boolean;
}>(), {
showTitle: true,
showExtra: false,
showToggleCollapse: false,
bordered: true,
bodyStyle: () => ({ height: '100%', padding: '0px 0px', margin: '0px 0px' }),
cardStyle: () => ({ overflow: 'hidden', height: '100%', padding: '0px' }),
iframeStyle: () => ({ width: '100%', height: '100%' }),
contentStyle: () => ({ height: '100%', overflow: 'auto' }),
useStaticPrefixForDocx: false,
});
const loading = ref(false);
const fileTp = ref('');
const fileTitle = ref('文件名称');
const fileUrl = ref('');
const docContent = ref<string>('');
const curDocFile = ref<any>({});
const toggleCollapseLeft = ref(false);
const emit = defineEmits(['toggle-collapse']);
const fetchDoc = async (docUrl: string) => {
try {
const response = await fetch(docUrl);
const arrayBuffer = await response.arrayBuffer();
const result = await mammoth.convertToHtml({ arrayBuffer });
docContent.value = result.value;
} catch (error) {
console.error('无法加载 DOC 文件:', error);
} finally {
loading.value = false;
}
};
const fetchText = async (docUrl: string) => {
try {
const response = await fetch(docUrl);
const arrayBuffer = await response.arrayBuffer();
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const decoder = new TextDecoder('utf-8');
const text = decoder.decode(arrayBuffer);
docContent.value = text;
} catch (error) {
console.error('无法加载 Txt 文件:', error);
} finally {
loading.value = false;
}
};
const fetchExcel = async (docUrl: string) => {
try {
fetch(docUrl)
.then((response) => {
if (!response.ok) {
throw new Error('网络响应不是 OK');
}
return response.arrayBuffer();
})
.then((data) => {
const workbook = XLSX.read(data, { type: 'array' });
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName];
const htmlString = XLSX.utils.sheet_to_html(worksheet);
docContent.value = htmlString;
})
.catch((error) => {
console.error('读取 Excel 文件时出错:', error);
});
} catch (error) {
console.error('无法加载 Excel 文件:', error);
} finally {
loading.value = false;
}
};
const fetchXml = async (fileUrl: string) => {
try {
const response = await fetch(fileUrl);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const xmlText = await response.text();
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlText, 'text/xml');
docContent.value = createXmlTree(xmlDoc.documentElement);
} catch (error) {
console.error('Error fetching or parsing XML:', error);
} finally {
loading.value = false;
}
};
function createXmlTree(node: any) {
let html = `<div class="xml-node"><strong>${node.nodeName}</strong>`;
if (node.attributes && node.attributes.length > 0) {
html += ' [';
for (let i = 0; i < node.attributes.length; i++) {
const attr = node.attributes[i];
html += `${attr.name}="${attr.value}"`;
if (i < node.attributes.length - 1) {
html += ', ';
}
}
html += ']';
}
if (node.childNodes.length > 0) {
html += '<div class="xml-node">';
node.childNodes.forEach((childNode: any) => {
if (childNode.nodeType === Node.ELEMENT_NODE) {
html += createXmlTree(childNode);
} else if (childNode.nodeType === Node.TEXT_NODE && childNode.nodeValue.trim()) {
html += `<div>文本: ${childNode.nodeValue.trim()}</div>`;
}
});
html += '</div>';
}
html += '</div>';
return html;
}
async function getFileExtension(filename: string) {
const index = filename.lastIndexOf('.');
if (index === -1) return '';
return filename.substring(index + 1);
}
function isHttpOrHttps(url: string) {
return url.startsWith('http://') || url.startsWith('https://');
}
async function showFile(fileData: any) {
curDocFile.value = fileData;
const globOnlineViewUrl = import.meta.env.VITE_GLOB_ONLINE_VIEW_URL;
const domainUrl = import.meta.env.VITE_GLOB_DOMAIN_URL;
let filePath = fileData.filePath ;
if(!filePath) {
filePath = fileData.fpath || fileData.filepath;
}
let fileType = fileData.fileTp || fileData.filetp;
let fileName = fileData.fileName || filePath;
if (isHttpOrHttps(filePath)) {
fileTp.value = 'url';
fileUrl.value = filePath;
fileTitle.value = '网址:' + filePath;
} else {
if (!fileType) {
fileType = await getFileExtension(filePath);
}
fileTp.value = fileType;
if(fileType === 'pdf') {
fileUrl.value = domainUrl + filePath;
} else {
fileUrl.value = globOnlineViewUrl + filePath;
}
fileTitle.value = '文件名:' + fileName;
}
loading.value = true;
try {
if (fileTp.value === 'docx') {
await fetchDoc(fileUrl.value);
} else if (fileTp.value === 'pdf' || fileTp.value === 'url') {
} else if (fileTp.value === 'txt') {
await fetchText(fileUrl.value);
} else if (fileTp.value === 'xml') {
await fetchXml(fileUrl.value);
} else if (fileTp.value === 'xlsx') {
fetchExcel(fileUrl.value);
} else if (fileTp.value === 'zip') {
} else {
await fetchText(fileUrl.value);
}
} catch (error) {
console.log(error);
} finally {
loading.value = false;
}
}
function getFileId() {
return curDocFile.value['fileId'] || curDocFile.value['id'];
}
function setDocFile(fileData: any) {
if (fileData && (fileData.filePath || fileData.fpath || fileData.filepath)) {
showFile(fileData);
}
}
function toggleCollapse() {
toggleCollapseLeft.value = !toggleCollapseLeft.value;
emit('toggle-collapse');
}
defineExpose({
getFileId,
setDocFile,
showFile,
});
</script>
<style lang="less" scoped>
:deep(.file-content) {
padding: 20px;
font-family: "Microsoft YaHei", SimSun, sans-serif;
line-height: 1.6;
color: #333;
table {
border-collapse: collapse;
width: 100%;
margin: 15px 0;
border: 1px solid #ddd;
th, td {
border: 1px solid #ddd;
padding: 8px 12px;
}
th {
background-color: #f2f2f2;
font-weight: bold;
}
}
h1, h2, h3, h4, h5, h6 {
margin: 20px 0 10px;
color: #222;
font-weight: bold;
}
h1 { font-size: 24px; }
h2 { font-size: 22px; }
h3 { font-size: 20px; }
h4 { font-size: 18px; }
h5 { font-size: 16px; }
h6 { font-size: 14px; }
p {
margin: 0 0 15px;
text-align: justify;
}
ul, ol {
margin: 15px 0;
padding-left: 30px;
}
li {
margin-bottom: 5px;
}
img {
max-width: 100%;
height: auto;
margin: 10px 0;
}
pre {
background-color: #f5f5f5;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
margin: 15px 0;
}
blockquote {
border-left: 4px solid #ddd;
padding-left: 15px;
color: #666;
margin: 15px 0;
}
}
</style>
<template>
<a-drawer
:title="fileTitle"
placement="left"
:width="drawerWidth"
:closable="true"
:visible="showDrawer"
@close="closeDrawer"
:mask="false"
>
<template #extra>
<a-space wrap>
<a-button type="link" v-show="isFullScreen" block @click="setDrawerFull">
<FullscreenOutlined />
</a-button>
<a-button type="link" v-show="!isFullScreen" block @click="setDrawerFull">
<FullscreenExitOutlined />
</a-button>
<a-button type="link" block @click="closeDrawer">
<CloseOutlined />
</a-button>
</a-space>
</template>
<div v-if="showTitle" style="margin-bottom: 10px; font-weight: bold;">{{ fileTitle }}</div>
<FileViewer
ref="refFileViewer"
:showTitle="false"
:useStaticPrefixForDocx="useStaticPrefixForDocx"
:bordered="false"
:bodyStyle="{ height: 'calc(100vh - 120px)' }"
/>
</a-drawer>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { FullscreenOutlined, FullscreenExitOutlined, CloseOutlined } from '@ant-design/icons-vue';
import FileViewer from './FileViewer.vue';
const props = withDefaults(defineProps<{
showTitle?: boolean;
useStaticPrefixForDocx?: boolean;
fixedFiles?: Record<string, any>;
}>(), {
showTitle: true,
useStaticPrefixForDocx: false,
fixedFiles: () => ({}),
});
const refFileViewer = ref();
const showDrawer = ref(false);
const fileTitle = ref('文件名称');
const drawerWidth = ref('50%');
const isFullScreen = ref(false);
function closeDrawer() {
showDrawer.value = false;
}
async function showCurFile(fileData) {
fileTitle.value = '文件名:' + (fileData.fileName || '');
showDrawer.value = true;
refFileViewer.value.setDocFile(fileData);
}
function setDocFile(fileDataOrType) {
if (typeof fileDataOrType === 'string' || typeof fileDataOrType === 'number') {
const fixedFile = props.fixedFiles[fileDataOrType];
if (fixedFile) {
showCurFile(fixedFile);
}
} else {
showCurFile(fileDataOrType);
}
}
function setDrawerFull() {
isFullScreen.value = !isFullScreen.value;
drawerWidth.value = isFullScreen.value ? '100%' : '50%';
}
defineExpose({
setDocFile,
});
</script>
<style lang="less" scoped>
</style>
<template>
<BasicModal v-bind="$attrs" @register="registerModal" title="文件内容查看" :centered="true" width="80%" :useWrapper="true" :footer="null">
<div style="background-color: #ececec; padding: 5px">
<div :style="{ overflow: 'hidden', height: '85vh' }">
<FileViewer ref="refFileViewer" :useStaticPrefixForDocx="useStaticPrefixForDocx" />
</div>
</div>
</BasicModal>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import FileViewer from './FileViewer.vue';
const props = withDefaults(defineProps<{
useStaticPrefixForDocx?: boolean;
}>(), {
useStaticPrefixForDocx: false,
});
const refFileViewer = ref();
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
const { showFooter, record } = data || {};
const { filePath, fpath } = record || {};
setModalProps({
confirmLoading: false,
showCancelBtn: !!showFooter,
showOkBtn: !!showFooter,
});
if (filePath || fpath ) {
refFileViewer.value.setDocFile(record);
}
});
defineExpose({
closeModal,
});
</script>
<style lang="less" scoped>
</style>
......@@ -11,7 +11,8 @@
</a-breadcrumb>
<a-card v-show="curActiveKey==1" bordered="false" style="width:100%">
<div style="margin:10px;height:70vh;width:100%;background:red;">
<ShowFileContent ref="refShowFile"></ShowFileContent>
<!-- ShowFileContent 组件缺失 -->
<div style="padding:20px;color:white;">文件内容查看组件缺失</div>
</div>
</a-card>
<a-card title="" v-show="curActiveKey==2" :bordered="false" style="width: 100%">
......@@ -39,7 +40,6 @@
import { ref,nextTick } from 'vue';
import { getAllCheckMethodByItem } from './api/AuditCheckMethodItem.api';
import { queryAllChecks } from './api/AuditCheckMethod.api';
import ShowFileContent from './modal/ShowFileContent.vue'
......@@ -75,7 +75,8 @@
filePath:retData[0].filePath
}
nextTick(async ()=>{
await refShowFile.value.setDocFile(fileObj)
// ShowFileContent 组件缺失,注释掉相关调用
// await refShowFile.value.setDocFile(fileObj)
})
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论