You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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">&times;</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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.28 08:45:04