vue3在线预览word文件

vue3在线预览word



<!-- 
npm install docx-preview
<template>
  <WordPreview
    docUrl="http://xxxxxxxx.docx"
    title="项目文档预览"
    loadingText="文档加载中,请稍候..."
    :previewOptions="{
      className: 'my-custom-docx',
      inWrapper: false,
      breakPages: false
    }"
    @loaded="handleLoaded"
    @error="handleError"
  />
</template>

<script setup>
import WordPreview from './WordPreview.vue';
const handleLoaded = (arrayBuffer) => {
  console.log('文档加载完成', arrayBuffer);
};
const handleError = (error) => {
  console.error('文档加载失败', error);
};
</script> 
-->


<template>
  <div class="docx-preview-container">
    <h2 v-if="title">{{ title }}</h2>
    
    <div v-if="loading" class="loading-state">
      <div class="spinner"></div>
      <p>{{ loadingText || '正在加载文档...' }}</p>
    </div>
    
    <div v-if="error" class="error-state">
      <p>{{ errorText || '加载失败:' }} {{ error }}</p>
      <button @click="loadDocument" class="retry-btn">{{ retryText || '重试' }}</button>
    </div>
    
    <div ref="previewContainer" class="preview-wrapper"></div>
    
    <div v-if="showTips" class="tips">
      <p>{{ tipsText || '提示:如果文档无法显示,请' }}<a :href="docUrl" download>{{ downloadText || '点击下载' }}</a>{{ tipsSuffix || '后查看' }}</p>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, watch } from 'vue';
import { renderAsync } from 'docx-preview';

const props = defineProps({
  // 文档URL
  docUrl: {
    type: String,
    required: true
  },
  // 标题
  title: {
    type: String,
    default: 'Word 文档预览'
  },
  // 自定义加载中文本
  loadingText: {
    type: String,
    default: ''
  },
  // 自定义错误文本前缀
  errorText: {
    type: String,
    default: ''
  },
  // 自定义重试按钮文本
  retryText: {
    type: String,
    default: ''
  },
  // 自定义提示文本
  tipsText: {
    type: String,
    default: ''
  },
  // 自定义下载链接文本
  downloadText: {
    type: String,
    default: ''
  },
  // 自定义提示文本后缀
  tipsSuffix: {
    type: String,
    default: ''
  },
  // 是否显示下载提示
  showDownloadTip: {
    type: Boolean,
    default: true
  },
  // 是否在挂载时自动加载
  autoLoad: {
    type: Boolean,
    default: true
  },
  // 自定义docx-preview配置
  previewOptions: {
    type: Object,
    default: () => ({
      className: 'custom-docx',
      inWrapper: true,
      breakPages: true,
      ignoreLastRenderedPageBreak: true
    })
  }
});

const emit = defineEmits(['loaded', 'error', 'retry']);

const previewContainer = ref(null);
const loading = ref(false);
const error = ref(null);
const showTips = ref(false);

const loadDocument = async () => {
  try {
    emit('retry');
    loading.value = true;
    error.value = null;
    
    // 清空容器
    if (previewContainer.value) {
      previewContainer.value.innerHTML = '';
    }
    
    // 获取文档
    const response = await fetch(props.docUrl);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const arrayBuffer = await response.arrayBuffer();
    
    // 渲染文档
    if (previewContainer.value) {
      await renderAsync(arrayBuffer, previewContainer.value, null, props.previewOptions);
      emit('loaded', arrayBuffer);
    }
    
    showTips.value = false;
  } catch (err) {
    console.error('文档加载失败:', err);
    error.value = err.message;
    showTips.value = props.showDownloadTip;
    emit('error', err);
  } finally {
    loading.value = false;
  }
};

// 监听docUrl变化
watch(() => props.docUrl, (newUrl) => {
  if (newUrl) {
    loadDocument();
  }
});

// 组件挂载时加载文档
onMounted(() => {
  if (props.autoLoad) {
    loadDocument();
  }
});

// 暴露方法给父组件
defineExpose({
  reload: loadDocument
});
</script>

<style scoped>
.docx-preview-container {
  max-width: 900px;
  margin: 0 auto;
  padding: 20px;
  font-family: 'Arial', sans-serif;
}

h2 {
  color: #2c3e50;
  text-align: center;
  margin-bottom: 20px;
}

.loading-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 40px;
}

.spinner {
  border: 4px solid rgba(0, 0, 0, 0.1);
  border-radius: 50%;
  border-top: 4px solid #42b983;
  width: 40px;
  height: 40px;
  animation: spin 1s linear infinite;
  margin-bottom: 15px;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

.error-state {
  background-color: #ffecec;
  border: 1px solid #ffb3b3;
  padding: 15px;
  border-radius: 4px;
  text-align: center;
  margin: 20px 0;
  color: #ff4d4d;
}

.retry-btn {
  background-color: #42b983;
  color: white;
  border: none;
  padding: 8px 15px;
  border-radius: 4px;
  margin-top: 10px;
  cursor: pointer;
  transition: background-color 0.3s;
}

.retry-btn:hover {
  background-color: #3aa876;
}

.preview-wrapper {
  min-height: 500px;
  border: 1px solid #e0e0e0;
  border-radius: 4px;
  padding: 20px;
  background-color: white;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.tips {
  margin-top: 15px;
  padding: 10px;
  background-color: #f8f9fa;
  border-radius: 4px;
  text-align: center;
  font-size: 14px;
}

.tips a {
  color: #42b983;
  text-decoration: none;
  transition: color 0.3s;
}

.tips a:hover {
  text-decoration: underline;
  color: #3aa876;
}
</style>

<style scoped>
.custom-docx {
  font-family: 'Microsoft YaHei', 'Arial', sans-serif;
  line-height: 1.6;
}

.custom-docx h1, 
.custom-docx h2, 
.custom-docx h3 {
  color: #2c3e50;
  margin-top: 1.5em;
  margin-bottom: 0.5em;
}

.custom-docx p {
  margin-bottom: 1em;
}

.custom-docx table {
  border-collapse: collapse;
  width: 100%;
  margin: 1em 0;
}

.custom-docx table, 
.custom-docx th, 
.custom-docx td {
  border: 1px solid #ddd;
}

.custom-docx th, 
.custom-docx td {
  padding: 8px 12px;
}

.custom-docx img {
  max-width: 100%;
  height: auto;
}
</style>
posted @ 2025-05-28 09:41  小万子呀  阅读(689)  评论(1)    收藏  举报