feat(docx-preview): 集成DOCX文件预览功能组件
Some checks failed
Lint Code / Lint Code (push) Failing after 4m20s
Some checks failed
Lint Code / Lint Code (push) Failing after 4m20s
- 新增ReDocxPreview组件,实现基于docx-preview库的DOCX文件渲染 - 实现DOCX文件加载、错误处理及打印功能 - 知识库视图增加对DOCX文件预览支持与对应UI样式调整 - 更新本地中英文菜单配置,添加"pureDocx"菜单项 - 增加docx-preview依赖及相关包锁信息 - 优化风险评估和工单管理视图的页面内边距样式
This commit is contained in:
172
src/components/ReDocxPreview/index.vue
Normal file
172
src/components/ReDocxPreview/index.vue
Normal file
@@ -0,0 +1,172 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted, nextTick } from "vue";
|
||||
import { renderAsync } from "docx-preview";
|
||||
|
||||
defineOptions({
|
||||
name: "ReDocxPreview"
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
url?: string;
|
||||
file?: File | Blob;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "rendered"): void;
|
||||
(e: "error", error: Error): void;
|
||||
}>();
|
||||
|
||||
const docxContainerRef = ref<HTMLDivElement>();
|
||||
const loading = ref(true);
|
||||
const errorMsg = ref("");
|
||||
|
||||
// 渲染 docx 文件
|
||||
async function renderDocx() {
|
||||
if (!docxContainerRef.value) return;
|
||||
|
||||
loading.value = true;
|
||||
errorMsg.value = "";
|
||||
|
||||
try {
|
||||
// 清空之前的内容
|
||||
docxContainerRef.value.innerHTML = "";
|
||||
|
||||
let arrayBuffer: ArrayBuffer;
|
||||
|
||||
if (props.file) {
|
||||
// 如果传入的是 File 或 Blob
|
||||
arrayBuffer = await props.file.arrayBuffer();
|
||||
} else if (props.url) {
|
||||
// 从 URL 获取文件
|
||||
const response = await fetch(props.url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`加载文件失败: ${response.status}`);
|
||||
}
|
||||
arrayBuffer = await response.arrayBuffer();
|
||||
} else {
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用 docx-preview 渲染
|
||||
await renderAsync(arrayBuffer, docxContainerRef.value, undefined, {
|
||||
className: "docx-container",
|
||||
inWrapper: true,
|
||||
ignoreWidth: false,
|
||||
ignoreHeight: false,
|
||||
ignoreFonts: false,
|
||||
breakPages: true,
|
||||
ignoreLastRenderedPageBreak: true,
|
||||
experimental: false,
|
||||
trimXmlDeclaration: true,
|
||||
useBase64URL: true,
|
||||
renderHeaders: true,
|
||||
renderFooters: true,
|
||||
renderFootnotes: true,
|
||||
renderEndnotes: true
|
||||
});
|
||||
|
||||
emit("rendered");
|
||||
} catch (error: any) {
|
||||
console.error("渲染 docx 文件失败:", error);
|
||||
errorMsg.value = error.message || "渲染 docx 文件失败";
|
||||
emit("error", error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 打印文档
|
||||
function handlePrint() {
|
||||
if (!docxContainerRef.value) return;
|
||||
const printContent = docxContainerRef.value.innerHTML;
|
||||
const printWindow = window.open("", "_blank");
|
||||
if (printWindow) {
|
||||
printWindow.document.write(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>打印文档</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
.docx-wrapper { background: white; }
|
||||
@page { margin: 1cm; }
|
||||
</style>
|
||||
</head>
|
||||
<body>${printContent}</body>
|
||||
</html>
|
||||
`);
|
||||
printWindow.document.close();
|
||||
printWindow.print();
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露方法供父组件调用
|
||||
defineExpose({
|
||||
refresh: renderDocx,
|
||||
print: handlePrint
|
||||
});
|
||||
|
||||
// 监听 URL 或文件变化
|
||||
watch(
|
||||
() => [props.url, props.file],
|
||||
() => {
|
||||
nextTick(() => {
|
||||
renderDocx();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
renderDocx();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="docx-preview-component">
|
||||
<div v-if="errorMsg" class="docx-error">
|
||||
<el-empty :description="errorMsg">
|
||||
<el-button type="primary" @click="renderDocx">重新加载</el-button>
|
||||
</el-empty>
|
||||
</div>
|
||||
<div v-else v-loading="loading" class="docx-loading-wrapper">
|
||||
<div ref="docxContainerRef" class="docx-render-container" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.docx-preview-component {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.docx-error {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.docx-loading-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.docx-render-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
:deep(.docx-wrapper) {
|
||||
padding: 20px;
|
||||
background: white;
|
||||
|
||||
section.docx {
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 0 8px rgb(0 0 0 / 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -3,6 +3,7 @@ import { ref, onMounted, watch } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import VuePdfEmbed from "vue-pdf-embed";
|
||||
import ReDocxPreview from "@/components/ReDocxPreview/index.vue";
|
||||
import {
|
||||
getDocuments,
|
||||
uploadDocument,
|
||||
@@ -63,6 +64,7 @@ const previewRotation = ref(0);
|
||||
const previewPdfRef = ref<any>(null);
|
||||
const previewAllPages = ref(false);
|
||||
const previewRotations = [0, 90, 180, 270];
|
||||
const previewDocxRef = ref<InstanceType<typeof ReDocxPreview> | null>(null);
|
||||
|
||||
// 加载项目列表
|
||||
async function loadProjects() {
|
||||
@@ -734,7 +736,52 @@ onMounted(() => {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Word/其他文件 - 使用iframe尝试预览 -->
|
||||
<!-- Word预览 -->
|
||||
<template
|
||||
v-else-if="
|
||||
['doc', 'docx'].includes(previewDoc.fileType?.toLowerCase())
|
||||
"
|
||||
>
|
||||
<div class="docx-preview-container">
|
||||
<div class="docx-toolbar">
|
||||
<div class="docx-title">
|
||||
{{ previewDoc.title }}
|
||||
</div>
|
||||
<div class="docx-actions">
|
||||
<el-button link @click="previewDocxRef?.refresh()">
|
||||
<component
|
||||
:is="useRenderIcon('ri/refresh-line')"
|
||||
:size="18"
|
||||
/>
|
||||
刷新
|
||||
</el-button>
|
||||
<el-button link @click="previewDocxRef?.print()">
|
||||
<component
|
||||
:is="useRenderIcon('ri/printer-line')"
|
||||
:size="18"
|
||||
/>
|
||||
打印
|
||||
</el-button>
|
||||
<el-button link @click="handleOpenFile(previewDoc.fileUrl)">
|
||||
<component
|
||||
:is="useRenderIcon('ri/download-line')"
|
||||
:size="18"
|
||||
/>
|
||||
下载
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-scrollbar class="docx-scrollbar">
|
||||
<ReDocxPreview
|
||||
ref="previewDocxRef"
|
||||
:url="previewDoc.fileUrl"
|
||||
class="docx-viewer"
|
||||
/>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 其他文件类型 -->
|
||||
<template v-else>
|
||||
<div class="other-preview-container">
|
||||
<div class="preview-tip">
|
||||
@@ -887,6 +934,43 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
// DOCX预览样式
|
||||
.docx-preview-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 70vh;
|
||||
|
||||
.docx-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
margin-bottom: 8px;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
|
||||
.docx-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.docx-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.docx-scrollbar {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.docx-viewer {
|
||||
width: 100%;
|
||||
min-height: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
// 其他文件预览样式
|
||||
.other-preview-container {
|
||||
display: flex;
|
||||
|
||||
@@ -888,7 +888,7 @@ onUnmounted(() => {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.risk-assessment {
|
||||
padding: 16px;
|
||||
padding: 16px 80px 16px 16px;
|
||||
|
||||
.stat-card {
|
||||
.stat-icon {
|
||||
|
||||
@@ -805,7 +805,7 @@ onMounted(() => {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.workorder-management {
|
||||
padding: 16px;
|
||||
padding: 16px 80px 16px 16px;
|
||||
|
||||
.stat-card {
|
||||
.stat-icon {
|
||||
|
||||
Reference in New Issue
Block a user