Vue3中实现world、excel、txt、pdf文件预览,(vue-office的使用)

依赖

"@vue-office/docx": "^1.3.0",
"@vue-office/excel": "^1.4.5",
"@vue-office/pdf": "^1.5.3",

参考: https://www.jb51.net/article/275080.htm

代码案例

核心代码

<div class="preview-box">
<a-modal class="preview-modal" v-model:visible="previewVisible" :footer="null" :width="1200"
style="max-height: 600px"
:onCancel="onPreviewModalClick">
<div class="preview-container">
<vue-office-docx
v-if="previewType === 'word'"
:src="previewUrl"
@rendered="renderingCompleted" />
<vue-office-excel
v-if="previewType === 'excel'"
:src="previewUrl"
@rendered="renderingCompleted"
/>
<vue-office-pdf
v-if="previewType === 'pdf'"
:src="previewUrl"
@rendered="renderingCompleted "/>
<img v-if="previewType === 'img'" :src="previewUrl" style="width: 960px; height: 720px; display: block; margin: 0 auto" alt=""/>
<div v-if="previewType === 'txt'" class="txt" style="white-space: pre-wrap;">
{{ textContent }}
</div>
</div>
<template #closeIcon>
<span class="close-modal-item">
<CloseOutlined/>
</span>
</template>
</a-modal>
</div>

全部代码

<template>
<div class="apply-container">
<a-modal
style="position: fixed; left: 25%; top: 12%;"
class="apply-modal"
v-model:visible="props.isVisible"
:title="props.disabledType ? '工作报告详情' : '工作报告上报'"
width="900px"
:footer="null"
@cancel="$emit('close')"
>
<a-spin tip="数据加载中..." :spinning="props.spinning">
<div class="box">
<a-form
:model="formState"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 23 }"
>
<a-form-item
label="工作报告标题"
:rules="[{ required: true }]">
<a-input
placeholder="请输入"
allow-clear
:disabled="props.disabledType"
v-model:value="formState.title"
/>
</a-form-item>
<a-form-item label="工作报告类型"
:rules="[{ required: true }]">
<a-select
style="width: 100%"
:disabled="props.disabledType"
v-model:value="formState.reportType"
placeholder="请选择"
allowClear
:getPopupContainer="triggerNode => triggerNode.parentNode"
>
<a-select-option
v-for="(selItem, index) of state.typeList"
:value="selItem.code"
:label="selItem.description"
:key="index"
>
{{ selItem.description }}
</a-select-option>
</a-select>
</a-form-item>
<!-- <a-form-item label="报告内容" :rules="[{ required: true }]">-->
<!-- <a-textarea-->
<!-- :rows="3"-->
<!-- :style="{ resize: 'none' }"-->
<!-- :disabled="props.disabledType"-->
<!-- v-model:value="formState.content"-->
<!-- />-->
<!-- </a-form-item>-->
<a-form-item label="附件">
<a-upload
v-if="!props.disabledType"
:disabled="props.disabledType"
v-model:file-list="formState.fileList"
name="file"
:multiple="true"
action="/empower/attachment/upload"
@download="doDownload"
:showUploadList="{showDownloadIcon: true}"
@change="handleFileChange"
>
<a-button class="btn-color" :disabled="props.disabledType">
<upload-outlined></upload-outlined>
上传附件
</a-button>
<span class="btn-font">
<upload-outlined style="display: none"></upload-outlined>*
选择本地文件上传</span
>
</a-upload>
<div v-else style="max-height: 520px; overflow-y: scroll">
<div v-for="item in formState.fileList" class="echo-file-item">
<a-button type="link" @click=""><span class="file-item-name">{{ item.name }}</span>
</a-button>
<span class="icon-box">
<download-outlined class="down-icon" @click="doDownload(item)"
title="下载"></download-outlined>
<eye-outlined class="eye-icon" @click="doPreview(item.response.data)"
title="预览"></eye-outlined>
</span>
</div>
</div>
</a-form-item>
<div style="display: flex">
<!-- <a-form-item label="报送人"-->
<!-- style="width: 100%;"-->
<!-- :labelCol=" {span: 6, offset: 2}"-->
<!-- :rules="[{ required: true }]">-->
<!-- <a-input-->
<!-- :disabled="props.disabledType"-->
<!-- style="width: 100%; "-->
<!-- v-model:value="formState.reportPerson"-->
<!-- />-->
<!-- </a-form-item>-->
<!-- <a-form-item style="width: 100%" label="报送单位"-->
<!-- :labelCol=" {span: 6, offset: 2}"-->
<!-- :rules="[{ required: true }]">-->
<!-- <a-input-->
<!-- :disabled="props.disabledType"-->
<!-- style="width: 100%; "-->
<!-- v-model:value="formState.reportDept"-->
<!-- />-->
<!-- </a-form-item>-->
</div>
<!-- <a-form-item label="报送时间" :rules="[{ required: true }]">-->
<!-- <a-date-picker-->
<!-- :disabled="props.disabledType"-->
<!-- v-model:value="formState.reportTime"-->
<!-- show-time-->
<!-- format="YYYY-MM-DD HH:mm:ss"-->
<!-- value-format="YYYY-MM-DD HH:mm:ss"-->
<!-- />-->
<!-- </a-form-item>-->
</a-form>
</div>
<div class="modal-btn" v-show="!props.disabledType">
<a-space>
<a-button @click="cancel">取消</a-button>
<a-button type="primary" @click="submitHandle" :loading="state.loading">
提交
</a-button>
</a-space>
</div>
</a-spin>
</a-modal>
<div class="preview-box">
<a-modal class="preview-modal" v-model:visible="previewVisible" :footer="null" :width="1200"
style="max-height: 600px"
:onCancel="onPreviewModalClick">
<div class="preview-container">
<vue-office-docx
v-if="previewType === 'word'"
:src="previewUrl"
@rendered="renderingCompleted" />
<vue-office-excel
v-if="previewType === 'excel'"
:src="previewUrl"
@rendered="renderingCompleted"
/>
<vue-office-pdf
v-if="previewType === 'pdf'"
:src="previewUrl"
@rendered="renderingCompleted "/>
<img v-if="previewType === 'img'" :src="previewUrl" style="width: 960px; height: 720px; display: block; margin: 0 auto" alt=""/>
<div v-if="previewType === 'txt'" class="txt" style="white-space: pre-wrap;">
{{ textContent }}
</div>
</div>
<template #closeIcon>
<span class="close-modal-item">
<CloseOutlined/>
</span>
</template>
</a-modal>
</div>
</div>
</template>
<script setup>
import {onMounted, reactive, ref, watch, onUnmounted, toRefs} from "vue";
import {message} from "ant-design-vue";
import mitt from "@/utils/mitt.js";
import {UploadOutlined, DownloadOutlined, EyeOutlined, CloseOutlined} from "@ant-design/icons-vue";
import {RESPONSE_STATUS} from "@/utils/bean";
import {getDictList} from "@/api/victory";
import {addWorkReport} from "@/api/workOverview";
import {nanoid} from "nanoid";
import {getUserInfo} from "@/api/user";
import VueOfficeDocx from '@vue-office/docx';
import VueOfficeExcel from '@vue-office/excel'
import VueOfficePdf from '@vue-office/pdf'
import '@vue-office/docx/lib/index.css'
import axios from "axios";
const props = defineProps({
spinning: {
type: Boolean,
default: false,
},
disabledType: {
type: Boolean,
default: false,
},
detailData: {
type: Object,
default: null,
},
isVisible: {
type: Boolean,
default: false,
},
});
const previewVisible = ref(false);
const previewUrl = ref('');
const previewType = ref(''); // word | excel | pdf | img | oldWord | txt
const textContent = ref('');
const renderingCompleted = () => {
console.log("渲染完成");
};
function setPreviewType(item) {
if(item.extName === 'docx') {
previewType.value = 'word'
return;
}
if (item.extName === 'doc') {
message.warn('暂不支持doc文件预览,请上传docx文件类型');
previewType.value = ''
return;
}
if (item.extName.includes('xls')) {
previewType.value = 'excel'
return;
}
if (item.extName.includes('pdf') || item.extName.includes('PDF')) {
previewType.value = 'pdf'
return;
}
if (item.extName === 'txt' || item.extName === 'TXT') {
previewType.value = 'txt'
axios.get(item.address, {
responseType: 'text'
}).then(res => {
textContent.value = res.data
})
return;
}
if (item.extName.includes('jpg')
|| item.extName.includes('png')
|| item.extName.includes('jpeg')
|| item.extName.includes('svg')
|| item.extName.includes('JPEG')
|| item.extName.includes('JPG')
) {
previewType.value = 'img'
return;
}
if (previewType.value === '') {
message.warn('该文件类型不支持预览');
}
}
const doPreview = item => {
previewUrl.value = ''
previewType.value = ''
previewVisible.value = false
setPreviewType(item);
console.log('output-> item [doPreivew] ', item, previewType.value)
if(previewType.value !== '') {
previewVisible.value = true;
previewUrl.value = item.address;
}else {
previewVisible.value = false;
}
}
const onPreviewModalClick = () => {
previewVisible.value = false
}
const doDownload = item => {
let aUrl = item.response.data.address
let extNameIcon = item.response.data.extName
let extName = item.response.data.name
if (extName.includes('.')) {
extName = extName.split('.')[0]
}
let xml = new XMLHttpRequest()
xml.open('GET', aUrl, true)
xml.responseType = 'blob'
xml.onload = () => {
let url = window.URL.createObjectURL(xml.response)
let a = document.createElement('a')
a.href = url
a.download = `${extName}.${extNameIcon}`
a.style.display = 'none'
a.click()
}
xml.send()
}
watch(() => props.disabledType, () => {
if (props.disabledType) {
state.spinning = true;
formState.value = props.detailData;
formState.value.fileList =
(props.detailData.attachmentVo &&
props.detailData.attachmentVo.map(img => {
return {
uid: `vc-upload-${nanoid()}`,
name: `${img?.name}.${img?.extName}`,
type: 'image/jpeg',
status: 'done',
response: {
status: 0,
message: 'success',
traceId: null,
data: {
address: `${img?.address}`,
name: `${img?.name}`,
id: img?.id,
extName: `${img?.extName}`,
url: `${img?.address}`
}
}
}
})) ||
[]
state.spinning = false;
} else {
resetFormState();
}
})
const state = reactive({
loading: false,
spinning: false,
typeList: []
});
const emit = defineEmits(["close", "loading"]);
let formState = ref({
title: '', // 工作标题
reportType: undefined, // 工作报告类型
content: '', // 报告内容
reportPerson: '', // 报送人
reportTime: undefined, // 报送时间
fileList: [], // 附件
reportDept: '', // 报送单位
});
const init = async () => {
const res = await getDictList({pageNo: 1, pageSize: 200, parentCode: "report_type"}); // 工作报告类型
state.typeList = res.data.data;
await initUserInfo();
}
const initUserInfo = async () => {
if (localStorage.getItem("userInfo") === null) {
const info = await getUserInfo();
let userState = info?.data?.data || {};
localStorage.setItem("userInfo", JSON.stringify(userState));
}
let userState = JSON.parse(localStorage.getItem('userInfo'));
formState.value.reportPerson = userState.name;
formState.value.reportDept = userState.officeVos[0].officeName;
console.log('output-> formState::: ', formState.value)
};
onMounted(() => {
init();
});
onUnmounted(() => {
});
const resetFormState = () => {
let formStateObj = formState.value;
formStateObj.title = '';
formStateObj.reportType = undefined;
formStateObj.content = '';
// formStateObj.reportPerson = '';
// formStateObj.reportDept = '';
// formStateObj.reportTime = '';
formStateObj.fileList = [];
}
const submitHandle = async () => {
console.log(formState.value, " formState.value");
if (!formState.value.title) {
message.warn('工作报告标题不能为空')
return;
}
if (!formState.value.reportType) {
message.warn('工作报告类型不能为空')
return;
}
// if (!formState.value.content) {
// message.warn('报告内容不能为空')
// return;
// }
// if (!formState.value.reportPerson) {
// message.warn('报送人不能为空')
// return;
// }
// if (!formState.value.reportDept) {
// message.warn('报送单位不能为空')
// return;
// }
// if (!formState.value.reportTime) {
// message.warn('报送时间不能为空')
// return;
// }
state.loading = true;
console.log('output-> formState.value.fileList::: ', formState.value.fileList)
let attachmentListVal = formState.value.fileList?.map(item => {
if (item.response) {
return item['response']['data'].id
}
})
let payload = {
...formState.value,
attachment: attachmentListVal.length ? attachmentListVal.join(',') : '',
}
console.log('output-> payload:::: ', payload)
try {
const res = await addWorkReport(payload);
if (RESPONSE_STATUS.success === res.status) {
state.loading = false;
message.success('工作报告上报成功');
mitt.emit('refreshReportTableData');
resetFormState();
emit("close");
}
} catch (e) {
state.loading = false;
resetFormState();
emit("close");
}
};
const handleFileChange = (e) => {
let fileList = [...e.fileList]
fileList = fileList.map(file => {
if (file.response) {
file.url = file.response.url
}
return file
})
formState.fileList = fileList
}
const cancel = () => {
emit("close");
};
</script>
<style lang="scss" scoped>
.preview-container {
max-height: 880px;
overflow-y: scroll;
}
.close-modal-item {
position: relative;
font-size: 25px;
top: -16px;
right: -12px;
}
.echo-file-item:hover {
background-color: #f5f5f5;
border-radius: 9px;
}
.echo-file-item {
display: flex;
align-items: center;
justify-content: space-between;
.file-item-name {
-webkit-line-clamp: 1;
display: inline-block;
max-width: 520px;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.icon-box {
margin-right: 8px;
.down-icon {
}
.eye-icon {
margin-left: 28px;
margin-right: 8px;
}
}
}
.btn-color {
background-color: rgba(43, 121, 255, 0.1);
color: #1f71ff;
}
.btn-font {
margin-left: 10px;
font-family: 'PingFang SC';
font-style: normal;
font-weight: 400;
font-size: 12px;
color: rgba(21, 22, 24, 0.48);
}
.box {
padding-left: 50px;
}
.upload-item {
display: flex;
cursor: pointer;
justify-content: space-between;
color: #1890ff;
}
.date {
margin-top: 20px;
}
.flow {
display: flex;
align-content: center;
input {
width: 200px;
margin-right: 10px;
}
}
.upload-item:hover {
color: #1890ff;
background-color: #fcf7f7;
}
.info-box {
display: flex;
justify-content: space-around;
font-size: 14px;
margin-bottom: 20px;
.info-item {
position: relative;
left: -170px;
}
&-title {
color: rgba(21, 22, 24, 0.48);
}
}
.modal-btn {
text-align: right;
}
.weight {
font-weight: 600;
}
.downLoad {
cursor: pointer;
color: #1f71ff;
}
.form-box {
margin-top: 10px;
padding-left: 55px;
}
:deep(.ant-form-item-label > label) {
font-family: "PingFang SC";
font-weight: 600;
color: rgba(21, 22, 24, 0.48);
// margin-right: 30px !important;
}
</style>
posted @   Felix_Openmind  阅读(7974)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
历史上的今天:
2022-01-09 如何运行一个“拿来”的Vue项目???
*{cursor: url(https://files-cdn.cnblogs.com/files/morango/fish-cursor.ico),auto;}
点击右上角即可分享
微信分享提示