增加图片显示
This commit is contained in:
parent
19319900f4
commit
3303e61fc5
9
main.go
9
main.go
@ -2,16 +2,20 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
// 注册模板函数
|
||||||
|
r.SetFuncMap(template.FuncMap{
|
||||||
|
"hasSuffix": strings.HasSuffix,
|
||||||
|
})
|
||||||
// 设置上传文件夹
|
// 设置上传文件夹
|
||||||
uploadDir := "./uploads"
|
uploadDir := "./uploads"
|
||||||
os.MkdirAll(uploadDir, 0755)
|
os.MkdirAll(uploadDir, 0755)
|
||||||
@ -87,7 +91,6 @@ func main() {
|
|||||||
|
|
||||||
c.FileAttachment(filepath, filename)
|
c.FileAttachment(filepath, filename)
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Run("0.0.0.0:8080")
|
r.Run("0.0.0.0:8080")
|
||||||
fmt.Println("启动")
|
fmt.Println("启动")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,68 +3,183 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>文件上传/下载</title>
|
<title>文件管理器</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.card-thumb {
|
||||||
|
width: 100%;
|
||||||
|
height: 120px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.file-icon {
|
||||||
|
font-size: 60px;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
.upload-area {
|
.upload-area {
|
||||||
border: 2px dashed #0d6efd;
|
border: 2px dashed #0d6efd;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: 0.2s;
|
transition: .2s;
|
||||||
}
|
}
|
||||||
.upload-area.dragover {
|
.upload-area.dragover {
|
||||||
background: #e3f2fd;
|
background: #e3f2fd;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="bg-light">
|
<body class="bg-light">
|
||||||
|
|
||||||
<div class="container py-4">
|
<div class="container py-4">
|
||||||
|
|
||||||
<h2 class="text-center mb-4">文件上传/下载管理</h2>
|
<h2 class="text-center mb-4">文件上传 / 管理</h2>
|
||||||
|
|
||||||
<!-- 上传区 -->
|
<!-- 上传区域 -->
|
||||||
<div id="upload-area" class="upload-area mb-4">
|
<div id="upload-area" class="upload-area mb-4">
|
||||||
<h5>点击或拖拽文件到这里上传(支持多文件)</h5>
|
<h5>点击或拖拽上传(支持多文件)</h5>
|
||||||
<input type="file" id="file-input" class="d-none" multiple>
|
<input type="file" id="file-input" class="d-none" multiple>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 进度条 -->
|
<!-- 上传进度条 -->
|
||||||
<div class="progress mb-4 d-none" id="progress-box">
|
<div class="progress mb-4 d-none" id="progress-box">
|
||||||
<div id="progress-bar" class="progress-bar" role="progressbar" style="width: 0%">0%</div>
|
<div id="progress-bar" class="progress-bar" role="progressbar" style="width: 0%">0%</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4>文件列表</h4>
|
<h4 class="mb-3">文件列表</h4>
|
||||||
<ul class="list-group mt-3">
|
|
||||||
|
<div class="row">
|
||||||
{{ range .files }}
|
{{ range .files }}
|
||||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
<div class="col-6 col-md-3 col-lg-2 mb-3">
|
||||||
<span>{{ . }}</span>
|
<div class="card shadow-sm">
|
||||||
<div>
|
|
||||||
<a class="btn btn-primary btn-sm me-2" href="/download/{{ . }}">下载</a>
|
{{ if (or (hasSuffix . ".webp") (hasSuffix . ".jpg") (hasSuffix . ".jpeg") (hasSuffix . ".png") (hasSuffix . ".gif")) }}
|
||||||
|
<!-- 图片缩略图 -->
|
||||||
|
<img src="/download/{{ . }}" class="card-thumb preview-img img-fluid" data-src="/download/{{ . }}" style="cursor:pointer;" />
|
||||||
|
|
||||||
|
{{ else if (or (hasSuffix . ".mp4") (hasSuffix . ".mov") (hasSuffix . ".webm")) }}
|
||||||
|
<!-- 视频卡片 -->
|
||||||
|
<video class="card-thumb" muted onclick="playVideo('/download/{{ . }}')">
|
||||||
|
<source src="/download/{{ . }}">
|
||||||
|
</video>
|
||||||
|
|
||||||
|
{{ else }}
|
||||||
|
<!-- 普通文件图标 -->
|
||||||
|
<div class="file-icon">
|
||||||
|
📄
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
<div class="card-body p-2">
|
||||||
|
<div class="small text-truncate" title="{{ . }}">{{ . }}</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between mt-2">
|
||||||
|
<a class="btn btn-primary btn-sm" href="/download/{{ . }}">下载</a>
|
||||||
<button class="btn btn-danger btn-sm" onclick="deleteFile('{{ . }}')">删除</button>
|
<button class="btn btn-danger btn-sm" onclick="deleteFile('{{ . }}')">删除</button>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
{{ else }}
|
</div>
|
||||||
<li class="list-group-item">暂无文件</li>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</ul>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 图片放大 Modal -->
|
||||||
|
<div class="modal fade" id="imagePreviewModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-xl">
|
||||||
|
<div class="modal-content bg-dark text-white">
|
||||||
|
<div class="modal-body position-relative">
|
||||||
|
|
||||||
|
<img id="previewImage" src="" class="img-fluid d-block mx-auto">
|
||||||
|
|
||||||
|
<!-- Left -->
|
||||||
|
<button id="prevImage"
|
||||||
|
class="btn btn-secondary position-absolute top-50 start-0 translate-middle-y">
|
||||||
|
◀
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Right -->
|
||||||
|
<button id="nextImage"
|
||||||
|
class="btn btn-secondary position-absolute top-50 end-0 translate-middle-y">
|
||||||
|
▶
|
||||||
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 视频播放 Modal -->
|
||||||
|
<div class="modal fade" id="videoModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<video id="videoModalSrc" class="w-100" controls autoplay></video>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 通用弹窗 -->
|
||||||
|
<div class="modal fade" id="commonModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="commonModalTitle">提示</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body" id="commonModalBody">
|
||||||
|
内容
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer" id="commonModalFooter">
|
||||||
|
<!-- 按钮会通过 JS 自动生成 -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
// ================== 图片放大 ===================
|
||||||
|
function showImage(src) {
|
||||||
|
document.getElementById("imgModalSrc").src = src;
|
||||||
|
new bootstrap.Modal(document.getElementById("imgModal")).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================== 视频播放 ===================
|
||||||
|
function playVideo(src) {
|
||||||
|
const video = document.getElementById("videoModalSrc");
|
||||||
|
video.src = src;
|
||||||
|
new bootstrap.Modal(document.getElementById("videoModal")).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================== 删除文件 ===================
|
||||||
|
function deleteFile(name) {
|
||||||
|
showConfirm(
|
||||||
|
"确定删除文件:" + name + " ?",
|
||||||
|
"删除确认",
|
||||||
|
() => { // onConfirm
|
||||||
|
fetch("/delete/" + name, { method: "DELETE" })
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(res => {
|
||||||
|
showAlert("删除成功!", "完成", () => location.reload());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================== 上传相关代码(保持原来功能) ===================
|
||||||
const uploadArea = document.getElementById("upload-area");
|
const uploadArea = document.getElementById("upload-area");
|
||||||
const fileInput = document.getElementById("file-input");
|
const fileInput = document.getElementById("file-input");
|
||||||
const progressBox = document.getElementById("progress-box");
|
const progressBox = document.getElementById("progress-box");
|
||||||
const progressBar = document.getElementById("progress-bar");
|
const progressBar = document.getElementById("progress-bar");
|
||||||
|
|
||||||
// 点击选择多个
|
|
||||||
uploadArea.addEventListener("click", () => fileInput.click());
|
uploadArea.addEventListener("click", () => fileInput.click());
|
||||||
|
|
||||||
// 拖拽样式
|
|
||||||
uploadArea.addEventListener("dragover", e => {
|
uploadArea.addEventListener("dragover", e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
uploadArea.classList.add("dragover");
|
uploadArea.classList.add("dragover");
|
||||||
@ -76,61 +191,140 @@
|
|||||||
uploadFiles(e.dataTransfer.files);
|
uploadFiles(e.dataTransfer.files);
|
||||||
});
|
});
|
||||||
|
|
||||||
fileInput.addEventListener("change", () => {
|
fileInput.addEventListener("change", () => uploadFiles(fileInput.files));
|
||||||
uploadFiles(fileInput.files);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ============ 多文件上传(带合并进度条) ============
|
|
||||||
function uploadFiles(files) {
|
function uploadFiles(files) {
|
||||||
if (!files.length) return;
|
if (!files.length) return;
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
for (let f of files) {
|
[...files].forEach(f => formData.append("files", f));
|
||||||
formData.append("files", f);
|
|
||||||
}
|
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
xhr.open("POST", "/upload");
|
xhr.open("POST", "/upload");
|
||||||
|
|
||||||
progressBox.classList.remove("d-none");
|
progressBox.classList.remove("d-none");
|
||||||
|
|
||||||
xhr.upload.onprogress = event => {
|
xhr.upload.onprogress = e => {
|
||||||
let percent = Math.round((event.loaded / event.total) * 100);
|
let percent = Math.round((e.loaded / e.total) * 100);
|
||||||
progressBar.style.width = percent + "%";
|
progressBar.style.width = percent + "%";
|
||||||
progressBar.innerText = percent + "%";
|
progressBar.innerText = percent + "%";
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onload = () => {
|
xhr.onload = () => {
|
||||||
if (xhr.status === 200) {
|
if (xhr.status === 200) {
|
||||||
alert("上传成功!");
|
showAlert("上传成功!", "上传完成", () => {
|
||||||
location.reload();
|
location.reload();
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
alert("上传失败!");
|
showAlert("上传失败!");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.send(formData);
|
xhr.send(formData);
|
||||||
}
|
}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
let commonModal = null;
|
||||||
|
|
||||||
function deleteFile(name) {
|
function showAlert(message, title = "提示", callback = null) {
|
||||||
if (!confirm("确定删除文件:" + name + " ?")) {
|
document.getElementById("commonModalTitle").innerText = title;
|
||||||
return;
|
document.getElementById("commonModalBody").innerText = message;
|
||||||
|
|
||||||
|
const footer = document.getElementById("commonModalFooter");
|
||||||
|
footer.innerHTML = `
|
||||||
|
<button type="button" class="btn btn-primary" id="modalOkBtn">确定</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.getElementById("modalOkBtn").onclick = () => {
|
||||||
|
if (callback) callback();
|
||||||
|
bootstrap.Modal.getInstance(document.getElementById("commonModal")).hide();
|
||||||
|
};
|
||||||
|
|
||||||
|
new bootstrap.Modal(document.getElementById("commonModal")).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch("/delete/" + name, {
|
function showConfirm(message, title = "确认操作", onConfirm = null, onCancel = null) {
|
||||||
method: "DELETE"
|
document.getElementById("commonModalTitle").innerText = title;
|
||||||
})
|
document.getElementById("commonModalBody").innerText = message;
|
||||||
.then(res => res.json())
|
|
||||||
.then(data => {
|
const footer = document.getElementById("commonModalFooter");
|
||||||
if (data.message) {
|
footer.innerHTML = `
|
||||||
alert("删除成功!");
|
<button type="button" class="btn btn-secondary" id="modalCancelBtn">取消</button>
|
||||||
location.reload();
|
<button type="button" class="btn btn-danger" id="modalConfirmBtn">确定</button>
|
||||||
} else {
|
`;
|
||||||
alert("删除失败:" + data.error);
|
|
||||||
}
|
document.getElementById("modalCancelBtn").onclick = () => {
|
||||||
});
|
if (onCancel) onCancel();
|
||||||
|
bootstrap.Modal.getInstance(document.getElementById("commonModal")).hide();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById("modalConfirmBtn").onclick = () => {
|
||||||
|
if (onConfirm) onConfirm();
|
||||||
|
bootstrap.Modal.getInstance(document.getElementById("commonModal")).hide();
|
||||||
|
};
|
||||||
|
|
||||||
|
new bootstrap.Modal(document.getElementById("commonModal")).show();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
let imageList = []; // 当前目录所有图片的路径
|
||||||
|
let currentIndex = 0; // 当前预览的图片下标
|
||||||
|
|
||||||
|
// 初始化:扫描页面卡片上所有图片
|
||||||
|
function collectImages() {
|
||||||
|
imageList = [];
|
||||||
|
document.querySelectorAll(".preview-img").forEach(img => {
|
||||||
|
imageList.push(img.getAttribute("data-src"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开图片预览
|
||||||
|
function openPreview(index) {
|
||||||
|
currentIndex = index;
|
||||||
|
document.getElementById("previewImage").src = imageList[currentIndex];
|
||||||
|
let modal = new bootstrap.Modal(document.getElementById("imagePreviewModal"));
|
||||||
|
modal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下一张
|
||||||
|
function nextImage() {
|
||||||
|
currentIndex = (currentIndex + 1) % imageList.length;
|
||||||
|
openPreview(currentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上一张
|
||||||
|
function prevImage() {
|
||||||
|
currentIndex = (currentIndex - 1 + imageList.length) % imageList.length;
|
||||||
|
openPreview(currentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 事件绑定
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
collectImages();
|
||||||
|
|
||||||
|
// 给所有 preview-img 添加点击事件
|
||||||
|
document.querySelectorAll(".preview-img").forEach((img, index) => {
|
||||||
|
img.addEventListener("click", () => openPreview(index));
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("nextImage").addEventListener("click", nextImage);
|
||||||
|
document.getElementById("prevImage").addEventListener("click", prevImage);
|
||||||
|
|
||||||
|
// 支持键盘切换
|
||||||
|
document.addEventListener("keydown", (e) => {
|
||||||
|
const modalShown = document.getElementById("imagePreviewModal").classList.contains("show");
|
||||||
|
if (!modalShown) return;
|
||||||
|
|
||||||
|
if (e.key === "ArrowRight") nextImage();
|
||||||
|
else if (e.key === "ArrowLeft") prevImage();
|
||||||
|
else if (e.key === "Escape") {
|
||||||
|
bootstrap.Modal.getInstance(document.getElementById("imagePreviewModal")).hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user