当前位置: 首页 > news >正文

记录一次vue项目页面内嵌iframe页面实现跨域上传和下载附件的功能

功能背景:项目部署在外网,然后其中有一个功能需要上传下载附件,附件是上传到华为云对象存储服务OBS中(私有云),所以采用iframe嵌套页面的方式解决跨域问题。
实现思路:
1、父窗口封装一个组件专门用于处理iframe相关功能
2、子窗口是另外一个项目,封装了附件上传、下载、删除等功能
3、父窗口通过监听组件绑定的value值,不断轮询去问子窗口是否已经挂载完毕了,如果已经挂载了,就将接口返回的附件列表发送给子窗口进行展示
4、子窗口挂载完毕后主动告知父窗口已经挂载好了,可以接收消息了。子窗口接收到了附件列表后进行展示,上传成功、删除成功后子窗口都需要发送消息给父窗口,告知附件列表发生了改变
5、父窗口是vue2语法写的,子窗口是vue3写的,可自行更改。

父窗口(vue2语法)

<template><div><iframe id="modle_iframe" ref="modle_iframe" frameborder="no" border="0"style="width:100%;height:calc(100vh - 450px);" :src="`https://xxxxx:8443/rcxjsp`"></iframe></div>
</template><script>
/*** @Date 2025-05-14* @Description 视频上传组件*/
export default {props: {value: {     //附件列表type: Array,default: () => { return [] }},disabled: {      //是否只读type: Boolean,default: false}},data() {return {fileList: [],isIframeLoaded: false,  //标记iframe是否加载完成timer: null}},watch: {value: {immediate: true,handler(newValue, oldValue) {if (newValue && newValue.length > 0) {this.fileList = newValue;} else {this.fileList = [];}this.loop();}},},mounted() {window.addEventListener('message', this.getMessage);},beforeDestroy() {window.removeEventListener('message', this.getMessage);if (this.timer) {clearInterval(this.timer);}},methods: {//不断询问子窗口是否已经挂载完毕,只有挂载完毕后发送消息,子窗口才能接收到loop() {if (this.timer) {clearInterval(this.timer);}this.timer = setInterval(() => {if (this.isIframeLoaded) {this.sendMessage();}}, 100)},sendMessage() {if (this.timer) {clearInterval(this.timer);}const iframe = document.getElementById('modle_iframe');const data = {disabled: this.disabled,fileList: this.fileList,}const msg = JSON.stringify(data);//父窗口发送列表和是否只读给子窗口iframe.contentWindow.postMessage(msg, '*');},getMessage(event) {    //父窗口接收来自子窗口的消息const data = JSON.parse(event.data);if (data.type === 'onMounted') {this.isIframeLoaded = true;} else if (data.type == 'updateFileList') {this.fileList = data.fileList;this.$emit('input', this.fileList);}},}
}
</script><style lang="scss" scoped></style>

子窗口(vue3+element-plus)

这里的上传和下载都是采用的分片上传和分片下载实现的。这里只提供核心代码,有用得上的自行修改。

<template><div style="overflow-y: auto;"><el-upload v-if="!disabled" action="#" :file-list="fileList" :data="{ path: 'rcxjsp' }" :show-file-list="false":http-request="uploadRequest"><el-button type="primary" :loading="loading" style="margin-bottom: 10px;">点击上传</el-button></el-upload><!-- 文件列表 --><div v-for="(item, index) in fileList" :key="index" class="fileList" style="display: flex;"><img src="./images/pdf.png" mode="" v-if="fileHz(item) == 'pdf'"> </img><img src="./images/zip.png" mode="" v-else-if="fileHz(item) == 'zip' || fileHz(item) == 'rar'"></img><img src="./images/txt.png" mode="" v-else-if="fileHz(item) == 'txt'"></img><img src="./images/ppt.png" mode="" v-else-if="fileHz(item) == 'ppt'"></img><img src="./images/xls.png" mode="" v-else-if="fileHz(item) == 'xls' || fileHz(item) == 'xlsx'"></img><img src="./images/doc.png" mode="" v-else-if="fileHz(item) == 'doc' || fileHz(item) == 'docx'"></img><img src="./images/mp3.png" mode=""v-else-if="fileHz(item) == 'mp3' || fileHz(item) == 'wav' || fileHz(item) == 'wma'"></img><img src="./images/mp4.png" mode=""v-else-if="fileHz(item) == 'mp4' || fileHz(item) == 'avi' || fileHz(item) == 'mov'"></img><img src="./images/fj.png" mode="" v-else></img><div class="filename">{{ item.fileName || item.name }}</div><el-button type="primary" @click="fpDown(item, index)" class="yulan":loading="downloading[index].loading">{{ downloading[index].loading ?`正在下载(${downloading[index].percent}%)` : '下载' }}</el-button><el-button type="danger" @click.stop="handleDelete(index)" v-show="!disabled">删除</el-button></div><el-progress v-show="showProgress" :percentage="progressPercent"></el-progress></div>
</template><script setup>
/*** @Date 2025-05-13* @Description 视频上传下载*/
import { initializeUploadId, uploadChunk, completeUpload, rangeDownload } from '@/api/rcxjsp';//父组件发送过来的两个参数
const fileList = ref([]);
const disabled = ref(false);
//本组件需要使用到的变量
let loading = ref(false);
let downloading = ref([]); // 下载进度
let currentFile = ref({}); // 当前文件
let showProgress = ref(false); // 进度条
let progressPercent = ref(0); // 进度百分比
const path = 'rcxjsp'; // 上传路径onMounted(() => {//页面挂载后主动通知父窗口已经准备好了window.parent.postMessage(JSON.stringify({ type: 'onMounted' }), '*');window.addEventListener('message', getMessage);
});
onUnmounted(() => {window.removeEventListener('message', getMessage);
});
const getMessage = (event) => {if (event.source === window.parent) {const data = JSON.parse(event.data);disabled.value = data.disabled;fileList.value = data.fileList;downloading.value = new Array(fileList.value.length).fill({ loading: false, percent: 0 });}
}
//获取文件后缀名
const fileHz = (file) => {const name = file.fileName || file.name;if (name) {var index = name.lastIndexOf(".");var ext = name.substr(index + 1);return ext}
}
//上传附件
const uploadRequest = (params) => {currentFile.value.fileName = params.file.name;showProgress.value = true;loading.value = true;let formdata = new FormData();formdata.append('file', params.file);formdata.append('path', path);formdata.append('fileName', params.file.name);initializeUploadId({ fileName: params.file.name, path: path }).then(res => {currentFile.value = res.data;const chunkSize = 1024 * 1024 * 25; // 每个切片的大小(这里设置为25MB)const totalChunks = Math.ceil(params.file.size / chunkSize);let currentChunk = 0;const uploadNextChunk = () => {const start = currentChunk * chunkSize;const end = Math.min((currentChunk + 1) * chunkSize, params.file.size);let chunkData = params.file.slice(start, end);let formdata = new FormData();formdata.append('file', chunkData);formdata.append('uploadId', currentFile.value.uploadId);formdata.append('objectKey', currentFile.value.objectKey);formdata.append('chunkIndex', currentChunk + 1);uploadChunk(formdata).then(res => {currentChunk++;progressPercent.value = +Math.floor((currentChunk / totalChunks) * 100).toFixed(0);if (currentChunk < totalChunks) {uploadNextChunk();} else {// 所有切片上传完成,将切片合并progressPercent.value = 100;complete();}}).catch((error) => {ElMessage({ type: 'error', message: '切片上传失败!' });loading.value = false;showProgress.value = false;progressPercent.value = 0;});};uploadNextChunk();})
}
//合并切片
const complete = () => {completeUpload({ ...currentFile.value }).then(res => {//定义需要传输给父窗口的附件列表数据结构let fileitem = {name: res.data.fileName,fileName: res.data.fileName,url: res.data.fileUrl,fileUrl: res.data.fileUrl,fileLength: res.data.fileLength}fileList.value.push(fileitem)downloading.value.push({ loading: false, percent: 0 });//通知父窗口附件列表变化了sendMsgToParent();}).catch(() => {ElMessage({ type: 'error', message: '切片合并失败!' });}).finally(() => {loading.value = false;showProgress.value = false;progressPercent.value = 0;})
}
//删除文件
const handleDelete = (index) => {fileList.value.splice(index, 1);//通知父窗口附件列表变化了sendMsgToParent();
}
//分片下载
const fpDown = (e, index) => {   //分片下载同步下载(按顺序一个一个下载)downloading.value.splice(index, 1, { loading: true, percent: 0 });const chunkSize = 1024 * 1024 * 25; // 每个切片的大小(这里设置为25MB)const totalChunks = Math.ceil(e.fileLength / chunkSize);let currentChunk = 0;const chunks = [];const download = () => {const start = currentChunk * chunkSize;const end = Math.min((currentChunk + 1) * chunkSize, e.fileLength);rangeDownload({ fileName: e.fileName, start: start, end: end, fileLength: e.fileLength }).then(bolb => {currentChunk++;let percent = +Math.floor((currentChunk / totalChunks) * 100).toFixed(0);downloading.value.splice(index, 1, { loading: true, percent: percent });chunks.push(bolb);if (currentChunk < totalChunks) {download();} else {downloading.value.splice(index, 1, { loading: false, percent: 100 });const mergedBlob = new Blob(chunks);const downloadUrl = window.URL.createObjectURL(mergedBlob);const link = document.createElement('a');link.href = downloadUrl;link.setAttribute('download', e.fileName);link.click();window.URL.revokeObjectURL(downloadUrl);}}).catch((error) => {ElMessage({ type: 'error', message: '切片下载失败!' });downloading.value.splice(index, 1, { loading: false, percent: 0 });});}download();
}//通知父窗口文件列表改变
const sendMsgToParent = () => {const msg = {type: 'updateFileList',fileList: fileList.value}window.parent.postMessage(JSON.stringify(msg), '*');
}</script><style lang="scss" scoped>
.fileList {display: flex;align-items: center;margin-bottom: 10px;img,.el-image {width: 30px;height: 30px;margin-right: 10px;}div.filename {flex: 1;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;}el-button.yulan {margin-left: 10px;}el-button.delete {margin-left: 10px;}
}
</style>

接口rcxjsp.js大致如下:

import request from '@/utils/request'//获取uploadid
export function initializeUploadId(params) {return request({url: '/xxxxx',method: 'get',params,})
}//上传分片
export function uploadChunk(data) {return request({headers: {'Content-Type': 'multipart/form-data',},url: '/gtfrgftft',method: 'post',data,})
}//合并分片
export function completeUpload(data) {return request({url: '/thgthtgh',method: 'post',data,})
}//下载附件
export function download(params) {return request({url: '/wrderfefre',method: 'get',params,responseType: 'blob',})
}//分片下载
export function rangeDownload(params) {return request({url: 'kuju',method: 'get',params,responseType: 'blob',})
}
http://www.xdnf.cn/news/490231.html

相关文章:

  • PT2031K单触控单输出触摸IC
  • UI自动化测试中,一个完整的断言应所需要考虑的问题
  • 基于SpringBoot的房屋租赁管理系统
  • 有什么软件系统可以高效管理工地现场物资材料?
  • C语言—指针4
  • 【Manim】使用manim画一个高斯分布的动画
  • Java【13_2】多态、根父类
  • 【已解决】Parsing error: No Babel config file detected for E:\
  • MCP概述及MCP Server的使用和实现(谷歌ADK使用MCP Server)
  • 如何在 Windows 上安装类似 Synaptic 的 Chocolatey GUI 包管理器
  • 哈希表的实现02
  • java18
  • 理解位图算法:使用 C++ 实现高效数据查重
  • 4.1 多层感知机 MLP 笔记
  • C语言学习记录--深入理解指针(5)(qsort的练习)
  • Linux基础开发工具大全
  • 连续隐马尔可夫离散隐马尔科夫模型的MATLAB实现
  • falsk-ORM的使用-数据库表的创建
  • 【Linux】动静态库链接原理
  • nnUNet V2代码——图像增强(三)
  • 【数据结构】线性表--栈
  • 金属加工液展|切削液展|2025上海金属加工液展览会
  • 使用unsloth对Qwen3在本地进行微调
  • 一个批量文件Dos2Unix程序(Microsoft Store,开源)1.1.0 编码检测和预览
  • 淘宝扭蛋机系统开发前景分析:解锁电商娱乐化新蓝海
  • HOW - React NextJS 的同构机制
  • Dify中使用插件LocalAI配置模型供应商报错
  • Spring Cloud深度实践:从服务发现到弹性智能API网关全景解析
  • Day29 -JS开发02 -两个实例:dom树(存在dom-xss) 加密及基础的js逆向(明文加密)
  • SAP-ABAP:SAP DMS(文档管理系统)的详细说明,涵盖其核心功能、架构、配置及实际应用