小程序文件在线显示(支持word,图片,视频等)

小程序文件在线显示(支持word,图片,视频等)

一、介绍

又是一天敲代码,今天公司要求一个文件上传,可以传多个类型的文件,而且在小程序还都要在线给他显示出来(之前明明就要求显示一个pdf就可以了)现在给我整这出,我真的XXX。
下班点到了,就先凑合一写,后续在进行优化。

为了方便,我直接复制了整个文件的代码,凑合看一看。

二、代码

这里的代码不是俺写的,实在原来显示pdf的基础上加入了word,excel,图片视频等。

具体文件,我这边使用图标进行区分了一下(图片我上传到服务器了,这里使用的是svg代码也会贴在下面,方便使用),点击去具体的文件都可以进行显示,点击word之类的就会跳转打开,图片视频文本会弹窗显示。

vue代码

bash 复制代码
<template>
	<view style="background-color: #FFF;padding-bottom: 200rpx;">
		<cu-custom bgColor="bg-gradual-blue" :isBack="true">
			<block slot="backText">返回</block>
			<block slot="content">文件明细</block>

		</cu-custom>
		<!-- 加载组件 -->
		<view class="loading-container" v-if="loading">
			<u-loading-icon color="red" size="40"></u-loading-icon>
		</view>
		<!-- 视频组件 -->
		<view v-if="showVideo" class="video-modal">
			<view class="modal-mask" @click="closeVideo"></view>
			<view class="video-container">
				<view class="close-btn" @click="closeVideo">×</view>
				<video id="videoPlayer" :src="videoUrl" :controls="true" :autoplay="true" class="video-player"
					@error="handleVideoError">
				</video>
			</view>
		</view>

		<!-- 文本组件 -->
		<view v-if="showTxt" class="txt-modal">
			<view class="modal-mask" @click="closeTxt"></view>
			<view class="txt-container">
				<view class="close-btn" @click="closeTxt">×</view>
				<view class="txt-title">{{currentTxtName}}</view>
				<view class="txt-content">
					<view class="txt-content-text">{{txtContent}}</view>
				</view>
			</view>
		</view>

		<view class="file-info-item"
			style="margin-top: 30rpx;border-bottom: 1rpx solid #cac7c7;padding-top: 15rpx;padding: 0rpx 40rpx  0rpx  40rpx;">
			<view class="" style="color: #9e9898;">
				标题:
			</view>
			<view style="padding-top: 15rpx;font-size: 30rpx; font-weight: bold;margin-bottom: 25rpx;">
				{{dataInfo.title}}
			</view>
		</view>

		<view class="file-info-item"
			style="margin-top: 30rpx;border-bottom: 1rpx solid #cac7c7; padding: 0rpx 40rpx  0rpx  40rpx;">
			<view class="" style="color: #9e9898;">
				发送人:
			</view>
			<view style="padding-top: 15rpx;font-size: 30rpx; margin-bottom: 25rpx;">
				{{dataInfo.createByName}}
			</view>
		</view>

		<view class="file-info-item"
			style="margin-top: 30rpx;border-bottom: 1rpx solid #cac7c7;padding: 0rpx 40rpx  0rpx  40rpx;">
			<view class="" style="color: #9e9898;">
				发送时间:
			</view>
			<view style="padding-top: 15rpx;font-size: 30rpx;margin-bottom: 25rpx;">
				{{dataInfo.createTime}}
			</view>
		</view>

		<view class="file-info-item"
			style="margin-top: 30rpx;border-bottom: 1rpx solid #cac7c7; padding: 0rpx 40rpx  0rpx  40rpx;">
			<view class="" style="color: #9e9898;">
				附件文件:
			</view>

			<view v-for="(item, index) in fileList" :key="index">
				<view @click="openFile(item.url)">
					<view style="margin-top:15rpx;margin-bottom: 25rpx;display: flex;" class="fileClass">
						<view>
							<img :src="getFileIcon(item.url)" alt="" style="width: 80rpx;height: 80rpx;">
						</view>
						<view style="margin-top: 20rpx;margin-left: 15rpx;">
							<text style="color: #2e61d7;">
								{{ item.fileName }}
							</text>
						</view>
					</view>
				</view>

			</view>
		</view>

		<view class="file-info-item" style="margin-top: 30rpx; padding: 0rpx 40rpx  0rpx  40rpx;">
			<view class="" style="color: #9e9898;">
				内容:
			</view>
			<view style="margin-top: 20rpx;width: 90%;margin-left: 5%;">
				<!-- <rich-text :nodes="dataInfo.content"></rich-text> -->
				<u-parse :content="dataInfo.content"></u-parse>
			</view>
		</view>

	</view>
</template>

<script>
	import configService from '@/common/service/config.service.js';
	import {
		ACCESS_TOKEN
	} from "@/common/util/constants"

	export default {
		data() {
			return {
				fileType: '', // 文件类型
				basUrl: configService.apiUrl,	// 项目config.1service.js配置文件下configService下的apiUrl(后台接口网址前缀,访问图片资源使用,类似于127.0.0.1:8080/temp)
				loading: false, // 是否显示加载组件
				fileList: [], // 附件文件列表
				id: '', // 传给组件的id用于查询数据,可以忽略
				dataInfo: {
					title: '',
					createByName: '',
					createTime: '',
					content: '',
				}, // 查询该条数据的详情信息
				showVideo: false,
				videoUrl: '',
				videoContext: null, // 视频上下文对象
				showTxt: false, // TXT弹窗显示开关
				currentTxtName: '', // 当前预览的TXT文件名
				txtContent: '', // TXT文件内容
			};
		},
		onReady() {
			this.videoContext = uni.createVideoContext('videoPlayer', this);
		},
		mounted() {},
		onLoad(e) {
			this.id = e.id
			this.getFileById()
		},
		onShow() {
			this.loading = false
		},
		methods: {
			// 打开文件
			openFile(fileUrl) {
				this.loading = true;
				// 文件地址
				const fullUrl = this.basUrl+ "/" + fileUrl;
				// 获取文件类型信息
				const { fileName, fileExt, isImage, isVideo, isDoc } = this.checkFileType(fileUrl);
				uni.downloadFile({
					url: fullUrl, // 文件下载地址
					header: {
						'X-Access-Token': uni.getStorageSync(ACCESS_TOKEN)
					},
					success: response => {
						if (response.statusCode !== 200) {
							uni.showToast({
								title: '文件下载失败',
								icon: 'none'
							});
							return;
						}
						const tempFilePath = response.tempFilePath;
						// 图片文件
						if (isImage) {
							this.previewImage(tempFilePath)
						}
						// 视频文件
						else if (isVideo) {
							this.playVideo(tempFilePath);
						}
						// 文档文件
						else if (isDoc) {
							fileExt === '.txt' ?
								this.previewTxt(tempFilePath, fileName) :
								this.openDocument(tempFilePath, fileExt);
						} else {
							uni.showToast({
								title: `不支持${fileExt}格式`,
								icon: 'none'
							});
						}
					},
					fail: downloadError => {
						this.loading = false
						uni.showToast({
							title: '下载失败,请检查网络',
							icon: 'none'
						});
					}
				})
			},
			// 图片
			previewImage(tempFilePath) {
				uni.previewImage({
					current: tempFilePath,
					urls: [tempFilePath],
					fail: imgError => {
						console.error('图片预览失败:', imgError);
						uni.showToast({
							title: '图片预览失败',
							icon: 'none'
						});
					}
				});
			},
			// 视频
			playVideo(tempFilePath) {
				this.showVideo = true;
				this.videoUrl = tempFilePath;
				// 用$nextTick替代setTimeout,确保DOM渲染完成后再初始化上下文
				this.$nextTick(() => {
					this.videoContext = uni.createVideoContext('videoPlayer', this);
				});
			},
			// TXT
			previewTxt(tempFilePath, fileName) {
				this.loading = false; // 关闭全局加载
				this.showTxt = true; // 显示TXT弹窗
				this.currentTxtName = fileName; // 显示文件名
				// 微信小程序API:读取本地临时文件内容(TXT为文本格式,用utf-8编码)
				const fs = uni.getFileSystemManager();
				try {
					const content = fs.readFileSync(tempFilePath, 'utf-8');
					// 读取成功:赋值内容,关闭加载
					this.txtContent = content;
					this.txtLoading = false;

				} catch (err) {
					// 3. 完整捕获错误:提示用户,关闭加载
					console.error('TXT读取失败:', err);
					this.txtContent = '文本读取失败,请重试';
					this.txtLoading = false;
					// 额外提示用户错误原因
					uni.showToast({
						title: '文本读取失败',
						icon: 'none'
					});
				}
			},
			// 文档
			openDocument(tempFilePath, fileExt) {
				uni.saveFile({
					tempFilePath: tempFilePath,
					success: (resData) => {
						uni.openDocument({
							filePath: resData.savedFilePath,
							fileType: this.getFileType(fileExt),
							showMenu: true, // 允许出现分享功能
							success: r => {
								this.loading = false;
								uni.showToast({
									title: '正在打开文件',
									icon: 'none',
									duration: 1500
								});
							},
							fail: (openError) => {
								this.loading = false
								console.error('文档打开失败:', openError);
								const errorMsg = openError.errMsg.includes('not support') ?
									'不支持该文件格式' : openError.errMsg.includes('not found') ?
									'文件不存在或已损坏' :
									'文件打开失败,请重试';
								uni.showToast({
									title: errorMsg,
									icon: 'none'
								});
							}
						})
					},
					fail: (error) => {
						this.loading = false;
						console.error('文件保存失败:', error);
						uni.showToast({
							title: '文件保存失败,请重试',
							icon: 'none'
						});
					}
				})
			},
			// 判断文件图标
			getFileIcon(fileUrl) {
				// 提取文件后缀(兼容URL带参数的情况)
				const fileName = decodeURIComponent(fileUrl.split('/').pop().split('?')[0]);
				const lastDotIndex = fileName.lastIndexOf('.');
				const fileExt = lastDotIndex > -1 ? fileName.substring(lastDotIndex).toLowerCase() : '';

				// 定义文件类型与图标映射
				const iconMap = {
					// 图片类型
					'.png': this.basUrl + '/svg/image.svg',
					'.jpg': this.basUrl + '/svg/image.svg',
					'.jpeg': this.basUrl + '/svg/image.svg',
					'.gif': this.basUrl + '/svg/image.svg',
					// 视频类型
					'.mp4': this.basUrl + '/svg/video.svg',
					'.mov': this.basUrl + '/svg/video.svg',
					'.avi': this.basUrl + '/svg/video.svg',
					// 文档类型
					'.doc': this.basUrl + '/svg/word.svg',
					'.docx': this.basUrl + '/svg/word.svg',
					'.xls': this.basUrl + '/svg/excel.svg',
					'.xlsx': this.basUrl + '/svg/excel.svg',
					'.csv': this.basUrl + '/svg/excel.svg',
					'.pdf': this.basUrl + '/svg/pdf.svg',
					'.txt': this.basUrl + '/svg/txt.svg',
					'.ppt': this.basUrl + '/svg/ppt.svg',
					'.pptx': this.basUrl + '/svg/ppt.svg'
				};

				// 返回对应图标,默认用通用文件图标
				return iconMap[fileExt] || this.basUrl + '/svg/file.svg';
			},
			// 判断文档类型
			getFileType(fileType) {
				const extMap = {
					'.doc': 'doc',
					'.docx': 'doc',
					'.ppt': 'ppt',
					'.pptx': 'ppt',
					'.xls': 'xls',
					'.xlsx': 'xls',
					'.csv': 'xls',
					'.pdf': 'pdf',
					'.txt': 'txt'
				};
				return extMap[fileType] || '';
			},
			// 检查文件类型
			checkFileType(fileUrl) {
				// 提取文件名和后缀(兼容URL中含参数的场景,如 "file.pdf?id=123")
				const fileName = decodeURIComponent(fileUrl.split('/').pop().split('?')[0]);
				const lastDotIndex = fileName.lastIndexOf('.');
				const fileExt = lastDotIndex > -1 ? fileName.substring(lastDotIndex).toLowerCase() : '';

				// 定义支持的文件类型集合
				const imageExts = ['.png', '.jpg', '.jpeg', '.gif', '.bmp'];
				const videoExts = ['.mp4', '.webm', '.mov', '.avi', '.mkv'];
				const docExts = ['.doc', '.docx', '.ppt', '.pptx', '.xls', '.xlsx', '.csv', '.pdf', '.txt'];

				return {
					fileName: fileName, // 纯净文件名(不含URL参数)
					fileExt: fileExt, // 小写文件后缀
					isImage: imageExts.includes(fileExt),
					isVideo: videoExts.includes(fileExt),
					isDoc: docExts.includes(fileExt)
				};
			},
			// 根据id查询数据
			getFileById() {
				this.$http.get('/hisonline/basUploadFile/list', {
					params: {
						mainId: this.id
					}
				}).then(data => {
					this.fileList = data.data.result.records
				})

				this.$http.get('/safety/homeAnnouncement/queryById', {
					params: {
						id: this.id
					}
				}).then(res => {
					this.dataInfo = res.data.result
				});
			},
			// 关闭视频
			closeVideo() {
				this.loading = false
				this.showVideo = false;
				this.videoUrl = '';
				// 停止播放并释放资源
				if (this.videoContext) {
					this.videoContext.stop();
					this.videoContext = null;
				}
			},
			// 视频播放错误处理
			handleVideoError(err) {
				console.error('视频播放错误:', err);
				uni.showToast({
					title: '视频加载失败,请重试',
					icon: 'none',
					duration: 2000
				});
				this.closeVideo(); // 错误时关闭弹窗
			},
			// 关闭TXT弹窗
			closeTxt() {
				this.showTxt = false;
				this.txtContent = ''; // 清空内容,避免下次复用
			},
		},
	}
</script>

<style lang="scss" scoped>
	.fileClass {
		overflow: hidden;
		white-space: nowrap;
		text-overflow: ellipsis;
	}

	// 视频弹窗遮罩
	.video-modal {
		position: fixed;
		top: 0;
		left: 0;
		right: 0;
		bottom: 0;
		z-index: 9999; // 确保在最上层
		display: flex;
		justify-content: center;
		align-items: center;
	}

	// 半透明背景
	.modal-mask {
		position: absolute;
		top: 0;
		left: 0;
		right: 0;
		bottom: 0;
		background-color: rgba(0, 0, 0, 0.8); // 深色半透明,突出视频
	}

	// 视频容器
	.video-container {
		width: 90%;
		max-width: 800rpx;
		min-height: 400rpx;
	}

	// 视频播放器
	.video-player {
		width: 100%;
		background-color: #000; // 黑色背景更适合视频
		border-radius: 12rpx; // 圆角更美观
		object-fit: contain;
	}

	// 关闭按钮
	.close-btn {
		position: absolute;
		top: -60rpx;
		right: 0;
		width: 50rpx;
		height: 50rpx;
		color: #fff;
		font-size: 50rpx;
		text-align: center;
		background-color: rgba(0, 0, 0, 0.5);
		border-radius: 50%;
		line-height: 45rpx;
	}

	// 全局加载组件样式
	.loading-container {
		position: fixed;
		top: 0;
		left: 0;
		right: 0;
		bottom: 0;
		display: flex;
		justify-content: center;
		align-items: center;
		z-index: 999; // 确保覆盖其他内容
		background-color: rgba(255, 255, 255, 0.5); // 半透明背景,提示正在加载
	}

	// 视频弹窗样式
	.txt-modal {
		position: fixed;
		top: 0;
		left: 0;
		right: 0;
		bottom: 0;
		z-index: 9999;
		display: flex;
		justify-content: center;
		align-items: center;
	}

	.txt-container {
		width: 90%;
		max-width: 700rpx;
		max-height: 70vh; // 限制弹窗最大高度为屏幕70%,避免超出屏幕
		background-color: #FFFFFF;
		border-radius: 12rpx;
		padding: 30rpx;
		position: relative;
		z-index: 2;
		overflow: hidden; // 防止内部内容溢出弹窗
	}

	.txt-title {
		font-size: 28rpx;
		font-weight: bold;
		color: #333333;
		margin-bottom: 20rpx;
		padding-bottom: 15rpx;
		border-bottom: 1rpx solid #EEEEEE;
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
	}

	.txt-content {
		width: 100%;
		height: calc(100% - 60rpx); // 减去标题高度,确保内容区不被挤压
		max-height: 60vh; // 限制内容区最大高度
		overflow-y: auto; // 内容超出时显示垂直滚动条
		font-size: 24rpx;
		color: #666666;
		line-height: 40rpx;
		padding-right: 10rpx; // 避免文字与滚动条重叠
	}


	.txt-content-text {
		white-space: pre-wrap; // 保留换行(\n)和空格
		word-break: break-all; // 长单词/长数字强制换行,避免横向溢出
		line-height: 48rpx; // 适当增加行高,提升可读性
	}
</style>

word.svg

W

bash 复制代码
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
  <rect x="3" y="6" width="34" height="28" rx="2" stroke="#2196F3" stroke-width="2" fill="white"/>
  <text x="12.5" y="27" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#2196F3">W</text>
</svg>

pdf.svg

PDF

bash 复制代码
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
  <rect x="3" y="6" width="34" height="28" rx="2" stroke="#F44336" stroke-width="2" fill="white"/>
  <text x="8" y="26" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#F44336">PDF</text>
</svg>

excel.svg

E

bash 复制代码
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
  <rect x="3" y="6" width="34" height="28" rx="2" stroke="#1976D2" stroke-width="2" fill="white"/>
  <text x="15" y="26" font-family="Arial, sans-serif" font-size="18" font-weight="bold" fill="#1976D2">E</text>
</svg>

ppt.svg

PPT

bash 复制代码
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
  <rect x="3" y="6" width="34" height="28" rx="2" stroke="#FF9800" stroke-width="2" fill="white"/>
  <text x="6.5" y="26" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#FF9800">PPT</text>
</svg>

txt.svg

bash 复制代码
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
  <rect x="3" y="6" width="34" height="28" rx="2" stroke="#9E9E9E" stroke-width="2" fill="white"/>
  <line x1="12" y1="16" x2="28" y2="16" stroke="#9E9E9E" stroke-width="2"/>
  <line x1="12" y1="21" x2="25" y2="21" stroke="#9E9E9E" stroke-width="2"/>
  <line x1="12" y1="26" x2="22" y2="26" stroke="#9E9E9E" stroke-width="2"/>
</svg>

video.svg

bash 复制代码
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
  <rect x="3" y="6" width="34" height="28" rx="2" stroke="#F44336" stroke-width="2" fill="white"/>
  <polygon points="17,14 27,20 17,26" fill="#F44336"/>
</svg>

image.svg

PIC

bash 复制代码
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
  <rect x="3" y="6" width="34" height="28" rx="2" stroke="#9C27B0" stroke-width="2" fill="white"/>
  <text x="8" y="26" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#9C27B0">PIC</text>
</svg>

file.svg

File

bash 复制代码
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
  <rect x="3" y="6" width="34" height="28" rx="2" stroke="#3F51B5" stroke-width="2" fill="white"/>
  <text x="9" y="26" font-family="Arial, sans-serif" font-size="14" font-weight="bold" fill="#3F51B5">File</text>
</svg>
相关推荐
无己心2 天前
面向多平台 AI 搜索引擎的适配层架构设计
前端·人工智能·搜索引擎·ai·1024程序员节
u0103055278 天前
AI驱动的安卓UI自动生成功能实现
人工智能·1024程序员节
u0103055279 天前
长株潭智能体赋能腾讯元器链接共享
1024程序员节
云贝贝贝10 天前
OceanBase认证体系介绍与备考指南
运维·服务器·oceanbase·1024程序员节
u01030552710 天前
Java图像处理实战指南
人工智能·1024程序员节
u01030552711 天前
Java AWT鼠标事件全解析
人工智能·1024程序员节
u01030552713 天前
Java实用类应用精讲
人工智能·1024程序员节
u01030552713 天前
Java I/O核心操作实战
人工智能·1024程序员节
u01030552714 天前
ArrayList操作详解与实战应用
人工智能·1024程序员节
u01030552715 天前
Java模块化编程实战指南
人工智能·笔记·1024程序员节