最近做vue项目遇到一个需求,就是前端实现上传word或pdf文件后,后端返回文件对应的文件流,前端需要在页面上展示出来。word预览简单一些,pdf预览我试过pdfjs,vue-pdf总是报各种奇奇怪怪的bug,但最终总算解决了问题,先看一下页面最终呈现效果吧:
页面上传pdf文件效果如下:
页面预览pdf文件效果如下:
页面上传word文件效果如下:
页面预览word文件效果如下:
这里先从上传组件页面说起,上传页面组件完整代码如下,按钮里面v-show=“$checkPermission([‘Register_tuotpUpload’])“都是给这个按钮设置了按钮权限的,我们只需要关注上传那一部分的代码即可,我们用了el-upload组件实现的手动上传,由于需求要求只能上传word和pdf,所以能看到属性设置的有 accept=”.pdf, .doc, .docx”,然后不展示上传成功的文件的列表设置的属性有:show-file-list=“false”,而handleExceed回调函数和limit都是为了限制只能上传一个文件,上传前的回调钩子函数beforeAvatarUpload里进行了文件类型判断与提醒,手动上传是通过UploadFile里进行完成的,需要注意的是由于docx-preview这个插件只能预览后缀为docx的word文件,如果是doc后缀格式的word文件一定要让后端强制将上传doc格式的文件改为docx格式,目前对于doc格式的word文件实现网页在线预览我只想到了docx-preview这个插件和这个解决办法:
<template>
<div class="app-container">
<div class="cardWhite">
<div class="itemBox">
<div class="headerTitle">基本信息</div>
<el-form
:model="ruleForm"
:rules="rules"
ref="ruleForm"
label-width="120px"
class="demo-ruleForm"
>
<el-row>
<el-col :span="12">
<el-form-item label="链路名称" prop="name">
<el-input
v-model="ruleForm.name"
placeholder="请输入链路名称"
clearable
></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="链路类型" prop="linkType">
<el-select
v-model="ruleForm.linkType"
placeholder="请选择链路类型"
style="width:100%"
clearable
>
<el-option
v-for="item in linkTypeList"
:key="item.val"
:label="item.key"
:value="item.val"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="链路走向" prop="go">
<el-row>
<el-col :span="10">
<el-select
v-model="ruleForm.srcNetwork"
placeholder="请选择"
style="width:100%"
clearable
@clear="clearSrc"
@change="srcChange"
>
<el-option
v-for="item in scrList"
:key="item.val"
:label="item.key"
:value="item.val"
></el-option>
</el-select>
</el-col>
<el-col :span="4">
<div style="text-align: center;width:100%">
<img
src="@/assets/toRight.png"
style="width:3.75rem;height:0.75rem;margin:0 auto"
/>
</div>
</el-col>
<el-col :span="10">
<el-select
v-model="ruleForm.dstNetwork"
placeholder="请选择"
style="width:100%"
:clearable="false"
@clear="clearDst"
@change="dstChange"
>
<el-option
v-for="item in dstList"
:key="item.val"
:label="item.key"
:value="item.val"
></el-option>
</el-select>
</el-col>
</el-row>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="物理位置" prop="physicalPosition">
<el-input
v-model="ruleForm.physicalPosition"
placeholder="请输入链路物理位置"
clearable
></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属机构" prop="orangeName">
<el-input
v-model="ruleForm.orangeName"
placeholder="例:xx市公安局"
clearable
></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="行政区编码" prop="organCode">
<el-input
v-model="ruleForm.organCode"
placeholder="请输入行政区编码,例:027"
clearable
></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="责任人" prop="dutyPerson">
<el-input
v-model="ruleForm.dutyPerson"
placeholder="请输入链路责任人"
clearable
></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="责任人电话" prop="dutyTel">
<el-input
v-model="ruleForm.dutyTel"
placeholder="请输入链路责任人电话"
clearable
></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="公安网邮箱" prop="dutyEmail">
<el-input
v-model="ruleForm.dutyEmail"
placeholder="请输入负责人公安网邮箱"
clearable
></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="链路IP地址" prop="ip">
<el-input
v-model="ruleForm.ip"
placeholder="请输入链路IP地址"
clearable
></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="链路端口" prop="port">
<el-input-number
placeholder="请输入链路端口"
type="text"
:min="0"
:controls="false"
v-model.trim="ruleForm.port"
style="width:100%"
></el-input-number>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="管理页面" prop="webUrl">
<el-input
v-model="ruleForm.webUrl"
placeholder="请输入链路管理页面"
clearable
></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :push="2">
<el-button
class="filter-item"
type="primary"
icon="el-icon-plus"
@click="saveForm"
v-show="$checkPermission(['Register_boundarySave'])"
>
保存
</el-button>
</el-col>
</el-row>
</el-form>
</div>
<div class="itemBox">
<div class="headerTitle">链路拓扑图</div>
<el-form :model="tuopuForm" ref="tuopuForm" label-width="120px">
<el-row>
<el-col :span="12">
<el-form-item label="拓扑图" prop="fileName">
<el-input
v-model="tuopuForm.fileName"
placeholder="请选择电脑中拓扑图文件"
clearable
disabled
>
<el-upload
accept=".pdf, .doc, .docx"
action="string"
:limit="1"
:on-exceed="handleExceed"
:before-upload="beforeAvatarUpload"
:http-request="UploadFile"
slot="append"
:show-file-list="false"
>
<el-button
type="primary"
v-show="$checkPermission(['Register_tuotpUpload'])"
icon="el-icon-upload2"
style="background:#1890ff;color:#fff;border-top-left-radius:0;border-bottom-left-radius:0"
>上传</el-button
>
</el-upload>
</el-input>
</el-form-item>
</el-col>
<el-col :span="3" :push="1">
<el-button
class="filter-item"
type="primary"
icon="el-icon-view"
@click="preview"
v-show="$checkPermission(['Register_tuotpPreview'])"
>
预览
</el-button>
</el-col>
</el-row>
</el-form>
</div>
<div class="itemBox">
<div class="headerTitle">设备信息列表</div>
<el-row type="flex" justify="end" style="margin:0.5rem 0;">
<el-button
class="filter-item"
type="primary"
icon="el-icon-plus"
@click="addEquipment"
v-show="$checkPermission(['Register_equipmentAdd'])"
>
添加
</el-button>
</el-row>
<div>
<commonTable
:tableHead="tableHead"
:tableData="tableData"
:dataFiter="true"
:selectionFlag="false"
:dropdownList="[]"
:resizable="true"
:tableLoading="tableLoading"
:showListD="showListD"
:toolBoxFlag="false"
@sortChange="() => {}"
@selection-change="() => {}"
@selectAction="() => {}"
@addData="() => {}"
:actionFlag="false"
:actionList="[]"
:freeElfFlag="false"
:xuhaoFlag="true"
:freeWidth="480"
>
<template
slot-scope="scope"
slot="doSomething"
fixed="right"
align="left"
>
<el-button
icon="el-icon-edit"
type="primary"
@click="handlerUpdate(scope.rows)"
v-show="$checkPermission(['Register_equipmentEdit'])"
>编辑</el-button
>
<el-button
icon="el-icon-delete"
type="danger"
@click="handlerDelete(scope.rows)"
v-show="$checkPermission(['Register_equipmentDelete'])"
style="margin-left:-0.015rem"
>删除</el-button
>
</template>
</commonTable>
</div>
</div>
<div class="itemBox">
<div class="headerTitle">链路注册</div>
<el-row type="flex" justify="end" style="margin:0.5rem 0;">
<el-button
class="filter-item"
type="primary"
icon="el-icon-plus"
@click="addRegister"
v-show="$checkPermission(['Register_registerAdd'])"
>
添加
</el-button>
</el-row>
<div>
<commonTable
:tableHead="Register_tableHead"
:tableData="Register_tableData"
:dataFiter="true"
:selectionFlag="false"
:dropdownList="[]"
:resizable="true"
:tableLoading="Register_tableLoading"
:showListD="Register_showListD"
:toolBoxFlag="false"
@sortChange="() => {}"
@selection-change="() => {}"
@selectAction="() => {}"
@addData="() => {}"
:actionFlag="false"
:actionList="[]"
:freeElfFlag="false"
:xuhaoFlag="true"
:freeWidth="480"
>
<template slot-scope="scope" slot="status">
<el-tag v-if="scope.rows.status == 1">已注册</el-tag>
<el-tag type="success" v-if="scope.rows.status == 0"
>未注册</el-tag
>
<el-tag type="danger" v-if="scope.rows.status == 2"
>注册失败</el-tag
>
</template>
<template
slot-scope="scope"
slot="doSomething"
fixed="right"
align="left"
>
<el-button
icon="el-icon-edit"
type="primary"
v-if="
scope.rows.status == 1 &&
$checkPermission(['Register_registerOff'])
"
@click="handlerLogoff(scope.rows)"
>注销</el-button
>
<el-button
icon="el-icon-edit"
type="primary"
v-if="
($checkPermission(['Register_registerGo']) &&
scope.rows.status == 0) ||
scope.rows.status == 2
"
@click="handlerLogoff(scope.rows)"
>注册</el-button
>
<el-button
icon="el-icon-delete"
type="danger"
v-if="$checkPermission(['Register_registerDelete'])"
@click="Register_handlerDelete(scope.rows)"
style="margin-left:-0.015rem"
>删除</el-button
>
</template>
</commonTable>
</div>
</div>
</div>
<!-- 添加和编辑设备弹窗 -->
<el-dialog
:title="textMap[dialogStatus]"
:visible.sync="dialogFormVisible"
width="800px"
:before-close="handleClose"
:close-on-click-modal="false"
>
<add-edit
@refresh="fetchData"
@closeDialog="dialogFormVisible = false"
class="AddEdit"
ref="AddEdit"
:devTypeList="EquipmentList"
v-if="dialogFormVisible"
/>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="submitForm()">
确定
</el-button>
</div>
</el-dialog>
<!-- 链路注册弹窗 -->
<el-dialog
title="链路注册"
:visible.sync="Register_dialogFormVisible"
width="800px"
:before-close="Register_handleClose"
:close-on-click-modal="false"
>
<register-add
@reg_refresh="Register_fetchData"
@reg_closeDialog="Register_dialogFormVisible = false"
ref="RegisterAdd"
v-if="Register_dialogFormVisible"
/>
<div slot="footer" class="dialog-footer">
<el-button @click="Register_dialogFormVisible = false">
取消
</el-button>
<el-button type="primary" @click="Register_submitForm()">
确定
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import commonTable from "@/components/common-table";
import Pagination from "@/components/Pagination";
import AddEdit from "./EquipmentAddEdit.vue";
import RegisterAdd from "./RegisterAdd.vue";
import * as api from "@/api/datax-register.js";
import axios from "axios";
import { getToken } from "@/utils/auth";
export default {
components: {
Pagination,
commonTable,
AddEdit,
RegisterAdd
},
data() {
const validateGo = (rule, value, callback) => {
// if (!value) {
// callback("请输入平台IP地址");
// } else {
// let index = value.indexOf("/");
// let findHengT = value.indexOf("-");
// let findHengP = value.indexOf("——");
// if (index > -1 || findHengT > -1 || findHengP > -1) {
// callback("请输入正确格式的平台IP地址");
// } else {
// validator.IpArea(rule, value, callback);
// }
// }
if (!this.ruleForm.srcNetwork || !this.ruleForm.dstNetwork) {
callback("请选择链路走向");
} else {
callback();
}
};
return {
ruleForm: {
name: "",
linkType: "",
srcNetwork: "",
dstNetwork: "",
physicalPosition: "",
orangeName: "",
organCode: "",
dutyPerson: "",
dutyTel: "",
dutyEmail: "",
ip: "",
port: undefined,
webUrl: ""
},
rules: {
name: [{ required: true, message: "请输入链路名称", trigger: "blur" }],
linkType: [
{ required: true, message: "请选择链路类型", trigger: "blur" }
],
go: [
{
required: true,
message: "请选择链路走向",
trigger: "blur",
validator: validateGo
}
],
physicalPosition: [
{ required: false, message: "请输入链路物理位置", trigger: "blur" }
],
orangeName: [
{ required: true, message: "请输入所属机构", trigger: "blur" }
],
organCode: [
{ required: true, message: "请输入行政区编码", trigger: "blur" }
],
dutyPerson: [
{ required: true, message: "请输入链路责任人", trigger: "blur" }
],
dutyTel: [
{ required: true, message: "请输入链路责任人电话", trigger: "blur" }
],
dutyEmail: [
{
required: false,
message: "请输入负责人公安网邮箱",
trigger: "blur"
}
],
ip: [{ required: false, message: "请输入链路IP地址", trigger: "blur" }],
port: [{ required: true, message: "请输入链路端口", trigger: "blur" }],
webUrl: [
{ required: false, message: "请输入链路管理页面", trigger: "blur" }
]
},
linkTypeList: [],
scrList: [],
dstList: [],
tuopuForm: {
fileName: "",
fileUrl: ""
},
tableHead: [
{
label: "设备名称",
prop: "name",
type: "normal",
sortable: false
},
{
label: "设备类型",
prop: "devType",
type: "normal",
sortable: false
// width: 150
},
{
label: "厂商",
prop: "manufacturer",
type: "normal",
sortable: false
// width: 150
},
{
label: "型号",
prop: "model",
type: "normal",
sortable: false
// width: 150
},
{
label: "设备IP",
prop: "devIp",
type: "normal",
sortable: false
},
{
label: "子网掩码",
prop: "ipMask",
type: "normal",
sortable: false
// width: 150
},
{
label: "网关",
prop: "ipGaway",
type: "normal",
sortable: false
// width: 150
},
{
label: "安装时间",
prop: "installTime",
type: "normal",
sortable: false,
width: 180
},
{
label: "操作",
prop: "doSomething",
type: "slot",
sortable: false,
slotName: "doSomething",
width: 220
}
// {
// label: "任务详情",
// prop: "log_text",
// type: "slot",
// sortable: false,
// slotName: "log_text",
// width: 100
// }
],
showListD: [
"name",
"devType",
"manufacturer",
"model",
"devIp",
"ipMask",
"ipGaway",
"installTime",
"doSomething"
// "log_text"
],
dialogStatus: "",
dialogFormVisible: false,
textMap: {
update: "编辑设备",
Edit: "编辑设备",
edit: "编辑设备",
create: "添加设备"
},
tableData: [],
tableLoading: false,
Register_tableHead: [
{
label: "平台名称",
prop: "name",
type: "normal",
sortable: false
},
{
label: "平台IP地址",
prop: "ip",
type: "normal",
sortable: false
},
{
label: "平台端口",
prop: "port",
type: "normal",
sortable: false
},
{
label: "平台唯一标识",
prop: "uniquePlatformCode",
type: "normal",
sortable: false
},
{
label: "注册时间",
prop: "lastTime",
type: "normal",
sortable: false,
width: 180
},
{
label: "注册状态",
prop: "status",
type: "slot",
slotName: "status",
sortable: false
},
{
label: "操作",
prop: "doSomething",
type: "slot",
sortable: false,
slotName: "doSomething",
width: 220
}
// {
// label: "任务详情",
// prop: "log_text",
// type: "slot",
// sortable: false,
// slotName: "log_text",
// width: 100
// }
],
Register_tableData: [],
Register_tableLoading: false,
Register_showListD: [
"name",
"ip",
"port",
"uniquePlatformCode",
"lastTime",
"status",
"doSomething"
// "log_text"
],
Register_dialogFormVisible: false,
fileList: null, //拓扑图文件列表
tuotpData: null,
EquipmentList: [],
originalList: []
};
},
created() {
this.getNews();
},
methods: {
getNews() {
//获取边界信息
this.getBoundaryDetail();
//获取拓扑图
this.getTuotp();
//获取设备列表
this.fetchData();
//获取链路注册列表
this.Register_fetchData();
//获取公共下拉
this.getSelect();
},
saveForm() {
this.$refs["ruleForm"].validate(valid => {
if (valid) {
let params = {
...this.ruleForm
};
console.log("修改入参", params);
//修改
api
.boundaryEdit(params)
.then(res => {
console.log("修改", res);
if (res.code == 200) {
this.$notify({
title: "成功",
message: "边界信息修改成功",
type: "success",
duration: 2000
});
}
})
.catch(err => {});
}
});
},
preview() {
console.log("预览", this.fileList, this.tuopuForm);
if (!this.fileList) {
this.$message.error(
"拓扑图文件为空不能预览,请先上传拓扑图文件后再预览",
6000
);
return false;
}
this.$router.push({
path: "TuotpPreview",
query: {
fileName: this.tuopuForm.fileName,
fileUrl: this.tuopuForm.fileUrl
}
});
},
addEquipment() {
this.dialogStatus = "create";
this.dialogFormVisible = true;
setTimeout(() => {
this.$refs["AddEdit"].dialogStatus = "create";
// this.$refs.AddEdit.resetTransferDetail();
// this.getCommonData();
}, 1);
},
//获取公共下拉
getSelect() {
api
.getLinkEquimentSelect({
clsName: "link_direction"
})
.then(res => {
console.log("链路走向下拉", res);
this.scrList = res.data;
this.dstList = res.data;
this.originalList = res.data;
})
.catch(err => {});
api
.getLinkEquimentSelect({
clsName: "link_type"
})
.then(res => {
console.log("链路类型下拉", res);
this.linkTypeList = res.data;
})
.catch(err => {});
api
.getLinkEquimentSelect({
clsName: "dev_type"
})
.then(res => {
console.log("设备类型下拉", res);
this.EquipmentList = res.data;
})
.catch(err => {});
},
//获取边界信息
getBoundaryDetail() {
api
.boundaryDetail()
.then(res => {
// console.log("获取边界信息", res);
this.ruleForm = res.data;
this.ruleForm.port = res.data.port ? res.data.port : undefined;
})
.catch(err => {});
},
//获取拓扑图
getTuotp() {
api
.boundaryTuopoDetail()
.then(res => {
// console.log("获取拓扑图成功", res);
this.tuopuForm = res.data;
this.tuopuPreview();
})
.catch(err => {
// console.log("获取拓扑图失败", err);
});
},
//获取设备列表
fetchData() {
this.tableLoading = true;
api
.equipmentList({
page: 1,
page_size: 99999,
ip: ""
})
.then(res => {
// console.log("设备列表", res);
if (res.code == 200) {
this.tableData = res.data.list;
this.tableLoading = false;
}
})
.catch(err => {});
},
//编辑
handlerUpdate(row) {
// console.log("点了修改", row);
this.dialogStatus = "Edit";
this.dialogFormVisible = true;
setTimeout(() => {
this.$refs["AddEdit"].setData(row);
this.$refs["AddEdit"].dialogStatus = "Edit";
}, 1);
},
handleClose() {
this.dialogFormVisible = false;
},
submitForm() {
this.$refs["AddEdit"].submitForm();
},
//删除
handlerDelete(row) {
this.$confirm("确定删除吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
api
.equipmentDelete(row)
.then(response => {
if (response.code == 200) {
this.fetchData();
this.$notify({
title: "成功",
message: "删除成功",
type: "success",
duration: 2000
});
}
})
.catch(err => {});
});
// const index = this.list.indexOf(row)
},
//注销
handlerLogoff(row) {
api
.registerStatusSwitch({
id: row.id,
status: row.status == 1 ? 2 : 1
})
.then(res => {
this.Register_fetchData();
})
.catch(err => {});
},
//链路删除
Register_handlerDelete(row) {
this.$confirm("确定删除吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
let id = row.id;
api.VideoTaskDelete(id).then(response => {
if (response.code == 200) {
this.fetchData();
this.$notify({
title: "成功",
message: "删除成功",
type: "success",
duration: 2000
});
}
});
});
},
//链路添加
addRegister() {
this.$refs["ruleForm"].validate(valid => {
if (valid) {
this.Register_dialogFormVisible = true;
setTimeout(() => {
// this.$refs["AddEdit"].dialogStatus = "create";
// this.$refs.AddEdit.resetTransferDetail();
// this.getCommonData();
}, 1);
} else {
this.$message.error("基本信息为空不能添加,请先补充基本信息", 6000);
}
});
},
Register_handleClose() {
this.Register_dialogFormVisible = false;
},
Register_submitForm() {
this.$refs["RegisterAdd"].submitForm();
},
//获取链路注册列表
Register_fetchData() {
this.Register_tableLoading = true;
api
.registerList({
page: 1,
page_size: 99999,
ip: ""
})
.then(res => {
console.log("注册列表", res);
if (res.code == 200) {
this.Register_tableData = res.data.list;
this.Register_tableLoading = false;
}
})
.catch(err => {});
},
handleExceed(files, fileList) {
if (files.length > 1) {
this.$message.warning(
`当前限制最多选择1个文件上传,本次选择了${files.length}个文件`
);
}
},
//文件上传前的钩子
beforeAvatarUpload(file) {
console.log("文件上传前的钩子", file);
// 文件类型判断
var testmsg = file.name.substring(file.name.lastIndexOf(".") + 1);
const extension = testmsg === "doc";
const extension2 = testmsg === "pdf";
const extension3 = testmsg === "docx";
if (!extension && !extension2 && !extension3) {
this.$message({
message: "上传的拓扑图文件只能是word、pdf格式,请重新上传!",
type: "error",
duration: 6000
});
this.fileList = null;
return false;
} else {
this.fileList = file;
}
},
//自定义上传
UploadFile() {
// 参数拼接
let fileData = new FormData();
fileData.append("file", this.fileList);
// 调用接口
axios({
url: `${window.g.API_URL}/api/cascade/topo/upload`,
method: "post",
data: fileData,
headers: {
"Content-Type": "multipart/form-data",
Authorization: getToken()
}
})
.then(res => {
console.log("上传成功", res);
if (res.data.code == 200) {
this.tuopuForm = res.data.data;
//拓扑预览
this.tuopuPreview();
} else {
console.log("上传失败1");
this.$message({
message: "上传拓扑图文件失败,请稍后重试!",
type: "error",
duration: 6000
});
}
})
.catch(err => {
console.log("上传失败2", err);
this.$message({
message: "上传拓扑图文件失败,请稍后重试!",
type: "error",
duration: 6000
});
});
},
tuopuPreview() {
axios({
url: `${window.g.API_URL}/api/cascade/topo/preview`,
method: "get",
params: {
fileUrl: this.tuopuForm.fileUrl
},
headers: {
Authorization: getToken()
},
responseType: "arraybuffer"
})
.then(res => {
console.log("拓扑预览获取成功", res);
if (res.status == 200) {
this.fileList = res.data;
}
})
.catch(err => {
console.log("拓扑预览失败失败", err);
});
},
srcChange(val) {
console.log("srcChange", val);
if (val == this.ruleForm.dstNetwork) {
this.ruleForm.dstNetwork = "";
}
this.dstList = this.originalList.filter(k => {
return k.val != val;
});
},
clearSrc() {
this.scrList = JSON.parse(JSON.stringify(this.originalList));
},
dstChange(val) {
console.log("dstChange", val);
if (val == this.ruleForm.srcNetwork) {
this.ruleForm.srcNetwork = "";
}
this.scrList = this.originalList.filter(k => {
return k.val != val;
});
},
clearDst() {
this.dstList = JSON.parse(JSON.stringify(this.originalList));
}
}
};
</script>
<style lang="scss" scoped>
.app-container {
.cardWhite {
.itemBox {
margin-bottom: 1.5rem;
.headerTitle {
font-size: 1rem;
font-weight: bold;
}
}
}
}
::v-deep .el-input-number .el-input__inner {
text-align: left;
}
</style>
上传功能实现之后,我们再看预览功能。我们需求是点击预览按钮之后跳到一个新页面进行预览,我先是对文件是否为空是否没上传进行了一个判断拦截,由于预览页面组件与当前页面组件既非父子关系,又非爷孙关系,八竿子打不着的关系,我们就不能通过绑定和sessionStorage来进行流文件的传递,只能在预览页面再发一次请求获取文件流,我们可以把参数通过路由带上,预览按钮对应的方法代码如下:
preview() {
console.log("预览", this.fileList, this.tuopuForm);
if (!this.fileList) {
this.$message.error(
"拓扑图文件为空不能预览,请先上传拓扑图文件后再预览",
6000
);
return false;
}
this.$router.push({
path: "TuotpPreview",
query: {
fileName: this.tuopuForm.fileName,
fileUrl: this.tuopuForm.fileUrl
}
});
}
再来看预览页面,也就是TuotpPreview.vue组件。word预览比较简单,先通过命令cnpm i docx-preview --save安装插件docx-preview,需要注意的是这个插件只能预览后缀为docx的word文件,如果是doc后缀格式的word文件一定要让后端强制将上传doc格式的文件改为docx格式,然后当前页面组件就直接import { defaultOptions, renderAsync } from “docx-preview”;引入,最后在data里进行docxOptions配置,然后在页面上 要显示的div上绑定一个 id="bodyContainer"的,因为下面要通过document.getElementById来获取dom并操作dom,最后调用renderAsync方法即可。
cnpm i docx-preview --save //安装word预览插件docx-preview
import { defaultOptions, renderAsync } from "docx-preview"; //引入docx-preview插件对应的方法
docxOptions: {
className: "kaimo-docx-666", // string:默认和文档样式类的类名/前缀
inWrapper: true, // boolean:启用围绕文档内容的包装器渲染
ignoreWidth: false, // boolean:禁用页面的渲染宽度
ignoreHeight: false, // boolean:禁止渲染页面高度
ignoreFonts: false, // boolean:禁用字体渲染
breakPages: true, // boolean:在分页符上启用分页
ignoreLastRenderedPageBreak: true, // boolean:在 lastRenderedPageBreak 元素上禁用分页
experimental: false, // boolean:启用实验功能(制表符停止计算)
trimXmlDeclaration: true, // boolean:如果为true,解析前会从 xml 文档中移除 xml 声明
useBase64URL: false, // boolean:如果为true,图片、字体等会转为base 64 URL,否则使用URL.createObjectURL
useMathMLPolyfill: false, // boolean:包括用于 chrome、edge 等的 MathML polyfill。
showChanges: false, // boolean:启用文档更改的实验性渲染(插入/删除)
debug: false // boolean:启用额外的日志记录
},
pdf文件的预览是通过获取到blob文件流之后,直接通过window.open打开新窗口,通过浏览器就能预览pdf,还有一种就是我现在的这种需求,要在页面上预览pdf,那就需要通过iframe,然后把blob流对应的src地址赋值回显即可。完整代码如下:
<template>
<div>
<!-- 拓扑图预览 -->
<div class="app-container">
<div class="cardWhite">
<div class="topArea">
<div class="backBox">
<img src="@/assets/goBack.png" @click="goBack" alt="" />
</div>
<div class="titleBox">
{{ myTitle }}
</div>
</div>
<div class="previewBox">
<div id="bodyContainer">
<iframe :src="pdfUrl" width="100%" height="750px" />
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from "axios";
import { getToken } from "@/utils/auth";
import { defaultOptions, renderAsync } from "docx-preview";
export default {
data() {
return {
myTitle: "",
previewData: null,
docxOptions: {
className: "kaimo-docx-666", // string:默认和文档样式类的类名/前缀
inWrapper: true, // boolean:启用围绕文档内容的包装器渲染
ignoreWidth: false, // boolean:禁用页面的渲染宽度
ignoreHeight: false, // boolean:禁止渲染页面高度
ignoreFonts: false, // boolean:禁用字体渲染
breakPages: true, // boolean:在分页符上启用分页
ignoreLastRenderedPageBreak: true, // boolean:在 lastRenderedPageBreak 元素上禁用分页
experimental: false, // boolean:启用实验功能(制表符停止计算)
trimXmlDeclaration: true, // boolean:如果为true,解析前会从 xml 文档中移除 xml 声明
useBase64URL: false, // boolean:如果为true,图片、字体等会转为base 64 URL,否则使用URL.createObjectURL
useMathMLPolyfill: false, // boolean:包括用于 chrome、edge 等的 MathML polyfill。
showChanges: false, // boolean:启用文档更改的实验性渲染(插入/删除)
debug: false // boolean:启用额外的日志记录
},
num: 1,
numPages: 0,
pdfUrl: ""
};
},
created() {
console.log("接收", this.$route.query);
this.resetTitle();
this.getFile();
},
methods: {
goBack() {
this.$router.go(-1);
},
//获取文件流
getFile() {
axios({
url: `${window.g.API_URL}/api/cascade/topo/preview`,
method: "get",
params: {
fileUrl: this.$route.query.fileUrl
},
headers: {
Authorization: getToken()
},
responseType: "arraybuffer"
})
.then(res => {
console.log("文件流获取成功", res);
if (res.status == 200) {
this.previewData = res.data;
if (
this.$route.query.fileName &&
this.$route.query.fileName.indexOf(".docx") &&
this.$route.query.fileName.indexOf(".pdf") == -1
) {
//word--docx格式
this.wordPreview(res.data);
} else if (
this.$route.query.fileName &&
this.$route.query.fileName.indexOf(".doc") &&
this.$route.query.fileName.indexOf(".pdf") == -1
) {
//word--doc格式
this.wordPreview(res.data);
} else if (
this.$route.query.fileName &&
this.$route.query.fileName.indexOf(".pdf")
) {
//pdf
this.pdfPreview(res.data);
}
}
})
.catch(err => {
console.log("文件流获取失败", err);
this.$message.error("获取文件信息失败,请稍后重试", 6000);
});
},
//重置标题
resetTitle() {
let fileName = this.$route.query.fileName;
console.log("fileName", fileName);
if (
fileName &&
fileName.indexOf(".docx") &&
fileName.indexOf(".pdf") == -1
) {
//word--docx格式
let wordDocxArr = fileName.split(".docx");
console.log("wordDocxArr", wordDocxArr);
this.myTitle = wordDocxArr[0];
} else if (
fileName &&
fileName.indexOf(".doc") &&
fileName.indexOf(".pdf") == -1
) {
//word--doc格式
let wordDocArr = fileName.split(".docx");
console.log("wordDocArr", wordDocArr);
this.myTitle = wordDocArr[0];
} else if (fileName && fileName.indexOf(".pdf")) {
//pdf
let pdfArr = fileName.split(".pdf");
console.log("pdfArr", pdfArr);
this.myTitle = pdfArr[0];
}
},
// word文档预览
wordPreview(buffer) {
console.log("文档buffer", buffer);
let bodyContainer = document.getElementById("bodyContainer");
renderAsync(
buffer, // Blob | ArrayBuffer | Uint8Array, 可以是 JSZip.loadAsync 支持的任何类型
bodyContainer, // HTMLElement 渲染文档内容的元素,
null, // HTMLElement, 用于呈现文档样式、数字、字体的元素。如果为 null,则将使用 bodyContainer。
this.docxOptions // 配置
)
.then(res => {
console.log("res---->", res);
})
.catch(err => {
console.log("err---->", err);
});
},
//pdf预览
pdfPreview(data) {
// data是一个ArrayBuffer格式,也是一个buffer流的数据
console.log("pdf流", data);
const binaryData = [];
binaryData.push(data);
//获取blob链接
let pdfUrl = window.URL.createObjectURL(
new Blob(binaryData, { type: "application/pdf" })
);
console.log("pdfUrl", pdfUrl);
// window.open(pdfUrl); 这种方式是直接打开新浏览器窗口预览
this.pdfUrl = pdfUrl;
}
}
};
</script>
<style lang="scss" scoped>
.app-container {
.cardWhite {
display: flex;
flex-direction: column;
.topArea {
display: flex;
position: relative;
.backBox {
margin-bottom: 1rem;
img {
cursor: pointer;
}
}
.titleBox {
text-align: center;
background: #fff;
color: #000000;
position: absolute;
top: 0;
left: 50%;
width: auto;
}
}
}
}
</style>