小程序文件在线显示(支持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>
相关推荐
runepic11 小时前
阿里云 CentOS 磁盘扩容记录:resize2fs 版本过低导致无法扩容的解决方案
服务器·阿里云·centos·云计算·1024程序员节
songyuc11 小时前
VoCo-LLaMA: Towards Vision Compression with Large Language Models 译读笔记
1024程序员节
大数据张老师11 小时前
数据结构——BF算法
数据结构·算法·1024程序员节
赋创小助手11 小时前
“短小精悍”的边缘AI算力利器:超微SYS-E403-14B-FRN2T服务器评测
服务器·人工智能·科技·ai·架构·边缘计算·1024程序员节
叶庭云11 小时前
一文了解开源大语言模型文件结构,以 Hugging Face DeepSeek-V3.1 模型仓库为例
人工智能·大语言模型·hugging face·1024程序员节·llms·开源模型文件结构·deepseek-v3.1
qq_ddddd11 小时前
对于随机变量x1, …, xn,其和的范数平方的期望不超过n倍各随机变量范数平方的期望之和
人工智能·神经网络·线性代数·机器学习·概率论·1024程序员节
三坛海会大神55511 小时前
CICD(一)CI/CD概述及GitLab部署和一些Git命令
git·ci/cd·1024程序员节
问道飞鱼11 小时前
【Linux知识】Linux文本操作相关命令行
linux·运维·服务器·文本处理·1024程序员节
暴躁哥11 小时前
Flink Watermark(水位线)机制详解
1024程序员节