```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>是否有附件 · 预览附件(PDF/Word/Excel/PPT)</title>
<style>
:root {
--c: #409eff;
--bg: #f7f9fc;
--card: #fff;
--bd: #e6e8ef;
--tx: #303133;
--mut: #909399;
}
body {
margin: 0;
background: var(--bg);
font: 14px/1.6, "SF Pro", "PingFang SC", "Microsoft YaHei", Arial;
}
.wrap {
max-width: 1100px;
margin: 28px auto;
padding: 0 16px;
}
.card {
background: var(--card);
border: 1px solid var(--bd);
border-radius: 10px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.05);
}
.hd {
padding: 16px 18px;
border-bottom: 1px solid var(--bd);
display: flex;
align-items: center;
justify-content: space-between;
}
.hd h2 {
margin: 0;
font-size: 18px;
}
.bd {
padding: 18px;
}
.tips {
background: #f0f6ff;
border: 1px dashed #cfe2ff;
border-radius: 8px;
padding: 10px 12px;
color: #2b6fe0;
margin-bottom: 14px;
}
.grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.item {
border: 1px solid var(--bd);
border-radius: 8px;
overflow: hidden;
background: #fff;
}
.item .meta {
padding: 8px 10px;
border-bottom: 1px solid var(--bd);
display: flex;
justify-content: space-between;
align-items: center;
}
.name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 70%;
}
.btn {
border: 1px solid var(--bd);
background: #fff;
border-radius: 6px;
padding: 4px 8px;
cursor: pointer;
}
.pre {
height: 240px;
display: flex;
align-items: center;
justify-content: center;
background: #fafafa;
}
.pre img,
.pre video {
max-width: 100%;
max-height: 100%;
}
.pre--doc {
color: var(--mut);
font-size: 13px;
}
.ft {
padding: 12px 18px;
border-top: 1px solid var(--bd);
display: flex;
gap: 10px;
justify-content: flex-end;
}
input[type="file"] {
display: none;
}
.uploader {
display: inline-flex;
align-items: center;
gap: 8px;
color: #fff;
background: var(--c);
border-radius: 8px;
padding: 8px 12px;
cursor: pointer;
}
.note {
color: var(--mut);
font-size: 12px;
margin-left: 8px;
}
@media (max-width: 900px) {
.grid {
grid-template-columns: 1fr;
}
}
/* 预览弹窗 */
.modal {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: none;
align-items: center;
justify-content: center;
padding: 24px;
z-index: 9999;
}
.modal.show {
display: flex;
}
.modal .box {
background: #000;
border-radius: 8px;
max-width: 96vw;
max-height: 90vh;
width: 100%;
display: flex;
flex-direction: column;
}
.modal .bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 10px;
background: #111;
color: #fff;
}
.modal .bar .title {
font-size: 13px;
opacity: 0.9;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.modal .bar .act button {
background: #2b6fe0;
color: #fff;
border: none;
border-radius: 6px;
padding: 6px 10px;
cursor: pointer;
}
.modal .cnt {
flex: 1;
background: #111;
}
.modal iframe {
width: 100%;
height: 100%;
border: 0;
background: #fff;
}
.modal canvas {
display: block;
margin: 0 auto;
max-width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="wrap">
<div class="card">
<div class="hd">
<h2>是否有附件 · 预览附件</h2>
<div>
<label class="uploader" for="fileInput">选择附件</label>
<span class="note"
>支持:PDF、Word(doc/docx)、Excel(xls/xlsx)、PPT(ppt/pptx)、图片、音视频、TXT</span
>
<input id="fileInput" type="file" multiple />
</div>
</div>
<div class="bd">
<div class="tips">
- PDF 将在页面内直接预览(使用内置 PDF 渲染)。<br />
- Word/Excel/PPT 若是在线地址(http/https),使用微软 Office
在线预览;本地文件则建议先上传生成可访问链接,再在线预览,否则只能下载或用本地应用打开。
</div>
<div id="list" class="grid"></div>
</div>
<div class="ft">
<button class="btn" id="clearBtn">清空</button>
<button class="btn" id="downloadAllBtn">全部下载(逐个)</button>
</div>
</div>
</div>
<!-- 预览弹窗(用于 Office 在线预览 & PDF 全屏) -->
<div class="modal" id="previewModal">
<div class="box">
<div class="bar">
<div class="title" id="modalTitle">预览</div>
<div class="act">
<button id="openNewTabBtn" title="在新窗口打开">新窗口打开</button>
<button
id="closeModalBtn"
style="margin-left: 8px; background: #444"
>
关闭
</button>
</div>
</div>
<div class="cnt" id="modalContent"></div>
</div>
</div>
<!-- pdf.js(本地渲染 PDF) -->
<script src="https://cdn.jsdelivr.net/npm/pdfjs-dist@4.8.69/build/pdf.min.js"></script>
<script>
// pdf.js 基本配置
if (window["pdfjsLib"]) {
pdfjsLib.GlobalWorkerOptions.workerSrc =
"https://cdn.jsdelivr.net/npm/pdfjs-dist@4.8.69/build/pdf.worker.min.js";
}
const fileInput = document.getElementById("fileInput");
const list = document.getElementById("list");
const clearBtn = document.getElementById("clearBtn");
const downloadAllBtn = document.getElementById("downloadAllBtn");
const modal = document.getElementById("previewModal");
const modalTitle = document.getElementById("modalTitle");
const modalContent = document.getElementById("modalContent");
const openNewTabBtn = document.getElementById("openNewTabBtn");
const closeModalBtn = document.getElementById("closeModalBtn");
const docExt = [
"pdf",
"doc",
"docx",
"xls",
"xlsx",
"ppt",
"pptx",
"txt",
];
const imgExt = ["jpg", "jpeg", "png", "gif", "webp"];
const videoExt = ["mp4", "webm", "ogg"];
const audioExt = ["mp3", "wav", "ogg"];
function extOf(name = "") {
const i = name.lastIndexOf(".");
return i > -1 ? name.slice(i + 1).toLowerCase() : "";
}
const isHttpUrl = (url) => /^https?:\/\//i.test(url);
const isDoc = (e) => docExt.includes(e);
const isImg = (e) => imgExt.includes(e);
const isVideo = (e) => videoExt.includes(e);
const isAudio = (e) => audioExt.includes(e);
// —— PDF 预览(用 pdf.js 渲染首页,点击可全屏弹窗查看更多页)
async function renderPdfToCanvas(url, canvas) {
const pdf = await pdfjsLib.getDocument(url).promise;
const page = await pdf.getPage(1);
const viewport = page.getViewport({ scale: 1.3 });
const ctx = canvas.getContext("2d");
canvas.width = viewport.width;
canvas.height = viewport.height;
await page.render({ canvasContext: ctx, viewport }).promise;
return pdf.numPages;
}
function openPdfInModal(url, fileName) {
// 在弹窗里用 <iframe> 嵌入内置 pdf.js 简单阅读器(使用 data url 会受限,这里直接渲染多页较复杂;简化为嵌入浏览器原生 pdf 查看)
modalTitle.textContent = fileName || "PDF 预览";
modalContent.innerHTML = "";
const ifr = document.createElement("iframe");
ifr.src = url; // 大多数现代浏览器可直接显示 PDF
modalContent.appendChild(ifr);
openNewTabBtn.onclick = () => window.open(url, "_blank");
modal.classList.add("show");
}
// —— Office 在线预览(仅对 http/https 可访问链接)
function officeViewerUrl(srcUrl) {
return `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(
srcUrl
)}`;
}
function openOfficeInModal(srcUrl, fileName) {
modalTitle.textContent = fileName || "Office 在线预览";
modalContent.innerHTML = "";
const ifr = document.createElement("iframe");
ifr.src = officeViewerUrl(srcUrl);
modalContent.appendChild(ifr);
openNewTabBtn.onclick = () =>
window.open(officeViewerUrl(srcUrl), "_blank");
modal.classList.add("show");
}
closeModalBtn.onclick = () => modal.classList.remove("show");
modal.addEventListener("click", (e) => {
if (e.target === modal) modal.classList.remove("show");
});
function makeItemFromSource(name, url, isLocalBlob) {
const e = extOf(name);
const item = document.createElement("div");
item.className = "item";
const meta = document.createElement("div");
meta.className = "meta";
meta.innerHTML = `
<span class="name" title="${name}">${name}</span>
<div>
<button class="btn" data-act="open">预览/打开</button>
<button class="btn" data-act="download">下载</button>
</div>`;
item.appendChild(meta);
const pre = document.createElement("div");
pre.className = "pre";
if (e === "pdf") {
// 首屏渲染 PDF 第一页缩略
const canvas = document.createElement("canvas");
pre.appendChild(canvas);
renderPdfToCanvas(url, canvas).catch(() => {
pre.classList.add("pre--doc");
pre.textContent = "PDF 预览失败(可点击右上角预览/打开)";
});
} else if (isImg(e)) {
const img = document.createElement("img");
img.src = url;
pre.appendChild(img);
} else if (isVideo(e)) {
const v = document.createElement("video");
v.src = url;
v.controls = true;
pre.appendChild(v);
} else if (isAudio(e)) {
const a = document.createElement("audio");
a.src = url;
a.controls = true;
pre.appendChild(a);
} else if (e === "txt") {
pre.classList.add("pre--doc");
fetch(url)
.then((r) => r.text())
.then((t) => {
pre.textContent =
(t || "").slice(0, 300) + (t.length > 300 ? " ..." : "");
})
.catch(() => (pre.textContent = "TXT 预览失败"));
} else if (["doc", "docx", "xls", "xlsx", "ppt", "pptx"].includes(e)) {
pre.classList.add("pre--doc");
pre.innerHTML = isLocalBlob
? "本地文件无法直接在线预览,请先上传生成可访问链接,或点击“下载”本地打开"
: "点击“预览/打开”使用微软 Office 在线预览";
} else {
pre.classList.add("pre--doc");
pre.textContent = "不支持预览的文件类型";
}
item.appendChild(pre);
meta.addEventListener("click", (ev) => {
const act = ev.target?.dataset?.act;
if (!act) return;
if (act === "download") {
const a = document.createElement("a");
a.href = url;
a.download = name;
document.body.appendChild(a);
a.click();
a.remove();
} else if (act === "open") {
if (e === "pdf") {
openPdfInModal(url, name);
} else if (
["doc", "docx", "xls", "xlsx", "ppt", "pptx"].includes(e)
) {
if (isLocalBlob) {
alert(
"本地文件无法使用在线预览,请先上传生成 http/https 可访问链接,或直接下载后在本地 Office 中打开。"
);
} else if (isHttpUrl(url)) {
openOfficeInModal(url, name);
} else {
window.open(url, "_blank");
}
} else {
window.open(url, "_blank");
}
}
});
return item;
}
function addFiles(files) {
Array.from(files).forEach((file) => {
const url = URL.createObjectURL(file);
list.appendChild(makeItemFromSource(file.name, url, true));
});
}
// 选择文件
fileInput.addEventListener("change", (e) => addFiles(e.target.files));
// 支持拖拽
document.addEventListener("dragover", (e) => e.preventDefault());
document.addEventListener("drop", (e) => {
e.preventDefault();
if (e.dataTransfer?.files?.length) addFiles(e.dataTransfer.files);
});
// 清空
clearBtn.addEventListener("click", () => {
list.innerHTML = "";
fileInput.value = "";
});
// 全部下载
downloadAllBtn.addEventListener("click", () => {
list
.querySelectorAll('.item .meta [data-act="download"]')
.forEach((btn) => btn.click());
});
// =========== 如需预览线上(HTTP/HTTPS)文件,可用此示例追加 ===========
// const onlineFiles = [
// { name:'在线-PDF示例.pdf', url:'https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf' },
// { name:'在线-Word示例.docx', url:'https://file-examples.com/storage/fe5b6e3d3b2b7f1/example.docx' },
// { name:'在线-Excel示例.xlsx', url:'https://file-examples.com/storage/fe5b6e3d3b2b7f1/example.xlsx' },
// { name:'在线-PPT示例.pptx', url:'https://file-examples.com/storage/fe5b6e3d3b2b7f1/example.pptx' }
// ];
// onlineFiles.forEach(f => list.appendChild(makeItemFromSource(f.name, f.url, false)));
</script>
</body>
</html>
改成vue实现