PHP读取目录文件后如何通过点击眼睛图标在模态框预览多格式文件?
分步骤实现模态框分类预览功能
先修正原有代码的隐藏隐患
你现在代码里用chdir($main_dir)切换全局工作目录是非常容易出问题的——后续只要涉及上传、删除、复制文件的操作,相对路径全都会偏移出错,直接换成绝对路径拼接的方式,顺便给预览按钮补全需要的文件属性,修正后的遍历逻辑如下:
// 定义用户目录的绝对路径,不切换全局工作目录 $main_dir = __DIR__ . "/directories/{$user->email}"; // 定义前端可直接访问的相对路径,用于资源加载 $main_dir_web = "directories/{$user->email}"; // 目录不存在的情况直接终止逻辑 if (!is_dir($main_dir)) { exit("用户专属存储目录未创建"); } $dh = opendir($main_dir); while ($file = readdir($dh)) { if ($file == "." || $file == "..") continue; // 拼接每个文件的全路径 $file_full_path = $main_dir . DIRECTORY_SEPARATOR . $file; $file_web_path = $main_dir_web . "/" . $file; ?> <tr> <td class="file-name"> <?php if (is_dir($file_full_path)): ?> <a href="<?= htmlspecialchars($file_web_path) ?>" target="_blank"> <i class="far fa-folder"></i><?= htmlspecialchars($file) ?> </a> <?php else: ?> <a href="<?= htmlspecialchars($file_web_path) ?>" target="_blank"><?= htmlspecialchars($file) ?></a> <?php endif; ?> </td> <td class="extension"> <?php if (is_dir($file_full_path)) { echo 'folder'; } else { echo strtolower(pathinfo($file_full_path, PATHINFO_EXTENSION)); } ?> </td> <td class="date"> <?= date("F d Y H:i:s.", filectime($file_full_path)) ?> </td> <td class="manage"> <!-- 给预览按钮补全三类必须的属性:文件名、类型、可访问路径 --> <span class="view" data-toggle="modal" data-target="#view-file-modal" data-file="<?= htmlspecialchars($file) ?>" data-file-type="<?= is_dir($file_full_path) ? 'folder' : strtolower(pathinfo($file_full_path, PATHINFO_EXTENSION)) ?>" data-file-path="<?= htmlspecialchars($file_web_path) ?>"> <i class="fas fa-eye"></i> </span> <form action="classes/deleteFile.php" method="POST"> <input type="hidden" name="fileName" value="<?= htmlspecialchars($file) ?>"> <button type="submit" name="deleteFile" class="delete"> <i class="far fa-trash-alt"></i> </button> </form> </td> </tr> <?php } closedir($dh); ?>
配置预览用的模态框结构
直接使用你当前项目里已经引入的Bootstrap模态框,留一个动态渲染内容的容器,不要在里面写死预览内容:
<div class="modal fade" id="view-file-modal" tabindex="-1" role="dialog"> <div class="modal-dialog modal-lg" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="view-modal-title">文件预览</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body" id="view-modal-content"> <!-- 按文件类型动态渲染内容 --> </div> </div> </div> </div>
写JS动态分类渲染逻辑
监听模态框的显示事件,从触发按钮上读取文件属性,按文件类型加载对应的预览内容:
$('#view-file-modal').on('show.bs.modal', function (event) { const triggerBtn = $(event.relatedTarget); const fileName = triggerBtn.data('file'); const fileType = triggerBtn.data('file-type'); const filePath = triggerBtn.data('file-path'); // 更新模态框标题 $(this).find('#view-modal-title').text(`预览:${fileName}`); const modalContent = $(this).find('#view-modal-content'); // 清空上一次预览的残留内容 modalContent.empty(); // 分类型匹配渲染逻辑 if (fileType === 'folder') { // 文件夹不做在线预览,直接给跳转链接 modalContent.html(`<p>文件夹暂不支持在线预览,<a href="${filePath}" target="_blank">点击打开目录</a></p>`); } else if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(fileType)) { // 图片类直接渲染img标签加载原图 modalContent.html(`<img src="${filePath}" class="img-fluid w-100" alt="${fileName}">`); } else if (fileType === 'pdf') { // PDF类用iframe嵌入,调用浏览器原生PDF预览能力 modalContent.html(`<iframe src="${filePath}#toolbar=0" style="width:100%; height:70vh; border:none;"></iframe>`); } else if (['txt', 'md', 'log', 'csv', 'json', 'php', 'js', 'html', 'css'].includes(fileType)) { // 文本类文件走独立接口读取内容,避免XSS风险 fetch(`preview.php?file=${encodeURIComponent(filePath)}`) .then(res => res.text()) .then(content => { // 用pre标签保留文本格式展示 modalContent.html(`<pre style="max-height:70vh; overflow:auto; white-space:pre-wrap;">${content}</pre>`); }) .catch(() => { modalContent.html(`<p class="text-danger">文本内容加载失败</p>`); }); } else { // 不支持在线预览的类型,直接给打开/下载入口 modalContent.html(`<p>该类型文件暂不支持在线预览,<a href="${filePath}" target="_blank">点击直接打开或下载</a></p>`); } })
写带安全校验的文本预览接口
单独建preview.php接口,不要在前端直接读取文本文件内容,必须做两层安全校验:一是校验访问路径不能跳出用户专属目录,二是限制只能读取指定的文本类文件,防止遍历服务器任意文件、触发恶意脚本:
<?php session_start(); // 获取当前登录用户的信息 $user = $_SESSION['user']; // 拼接用户专属目录的绝对路径 $user_dir = __DIR__ . "/directories/{$user->email}/"; $request_file = $_GET['file'] ?? ''; $request_file_abs = realpath($request_file); // 路径校验:如果请求的文件不在用户目录下,直接拒绝访问 if (!$request_file_abs || strpos($request_file_abs, realpath($user_dir)) !== 0) { http_response_code(403); exit("无权限访问该文件"); } // 类型校验:只允许读取指定后缀的文本类文件 $allow_text_ext = ['txt', 'md', 'log', 'csv', 'json', 'php', 'js', 'html', 'css']; $file_ext = strtolower(pathinfo($request_file_abs, PATHINFO_EXTENSION)); if (!in_array($file_ext, $allow_text_ext)) { http_response_code(403); exit("不支持读取该类型文件"); } // 读取文件内容,做HTML转义后输出,避免恶意脚本执行 echo htmlspecialchars(file_get_contents($request_file_abs)); ?>
实操注意事项
- 一定要删掉原来代码里的
chdir()调用,否则后续所有涉及文件操作的功能(上传、删除、重命名)都会因为工作目录偏移报错 - 所有输出到HTML的文件名、路径都要用
htmlspecialchars()转义,避免特殊字符破坏页面结构 - 文本预览环节不要直接把文件原文插到DOM里,必须经过转义处理,防止文件里嵌入的恶意脚本在预览时执行
内容的提问来源于stack exchange,提问作者Abdallah M Yassin
相关产品推荐
相关产品推荐

