feat: 门户网站初始提交
- Go + Gin + html/template 服务端渲染 - 主页:Google 风格搜索框 + 导航卡片 - 后台:卡片 CRUD、搜索引擎配置、主页背景/标题配置 - 图片上传:支持 jpg/jpeg/png/gif,自动压缩,缩略图参数 ?thumb=1 - 安全:登录日志、修改密码、IP 自动封禁、IP 白名单 - 访问统计:主页访问/卡片点击/搜索追踪、实时流量、IP 统计 - SQLite 存储(modernc.org/sqlite,纯 Go) - 内存 Session + bcrypt 密码哈希
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* upload.js — Generic file upload handler for Portal admin pages.
|
||||
*
|
||||
* Usage:
|
||||
* setupUpload(inputSelector, uploadType)
|
||||
* - inputSelector: CSS selector for the target input field
|
||||
* - uploadType: "icon" or "background" — determines thumbnail dimensions
|
||||
*
|
||||
* Adds a hidden file input and a visible "上传图片" button next to the target input.
|
||||
* After successful upload, the target input value is set to the returned URL.
|
||||
*/
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Sets up an upload button next to the specified input field.
|
||||
* @param {string} inputSelector - CSS selector for the input to fill with the URL
|
||||
* @param {string} uploadType - "icon" or "background"
|
||||
*/
|
||||
function setupUpload(inputSelector, uploadType) {
|
||||
var input = document.querySelector(inputSelector);
|
||||
if (!input) return;
|
||||
|
||||
// Create container for the upload button group
|
||||
var wrapper = document.createElement("div");
|
||||
wrapper.className = "upload-btn-wrapper";
|
||||
|
||||
// Create the visible upload button
|
||||
var btn = document.createElement("button");
|
||||
btn.type = "button";
|
||||
btn.className = "upload-btn";
|
||||
btn.textContent = "上传图片";
|
||||
|
||||
// Create the hidden file input
|
||||
var fileInput = document.createElement("input");
|
||||
fileInput.type = "file";
|
||||
fileInput.accept = "image/jpeg,image/png,image/gif";
|
||||
fileInput.style.display = "none";
|
||||
|
||||
// Create status message element
|
||||
var status = document.createElement("span");
|
||||
status.className = "upload-status";
|
||||
|
||||
// Insert wrapper after the input
|
||||
input.parentNode.insertBefore(wrapper, input.nextSibling);
|
||||
wrapper.appendChild(btn);
|
||||
wrapper.appendChild(fileInput);
|
||||
wrapper.appendChild(status);
|
||||
|
||||
// Click button -> open file dialog
|
||||
btn.addEventListener("click", function () {
|
||||
fileInput.click();
|
||||
});
|
||||
|
||||
// File selected -> upload
|
||||
fileInput.addEventListener("change", function () {
|
||||
if (fileInput.files.length === 0) return;
|
||||
|
||||
var file = fileInput.files[0];
|
||||
|
||||
// Validate file size (5MB)
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
status.textContent = "文件太大,最大允许 5MB";
|
||||
status.className = "upload-status upload-error";
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate file type
|
||||
var validTypes = ["image/jpeg", "image/png", "image/gif"];
|
||||
if (validTypes.indexOf(file.type) === -1) {
|
||||
status.textContent = "不支持的文件格式";
|
||||
status.className = "upload-status upload-error";
|
||||
return;
|
||||
}
|
||||
|
||||
// Start upload
|
||||
btn.disabled = true;
|
||||
btn.textContent = "上传中...";
|
||||
status.textContent = "";
|
||||
status.className = "upload-status";
|
||||
|
||||
var formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("type", uploadType);
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/admin/upload", true);
|
||||
|
||||
xhr.onload = function () {
|
||||
btn.disabled = false;
|
||||
btn.textContent = "上传图片";
|
||||
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var resp = JSON.parse(xhr.responseText);
|
||||
if (resp.url) {
|
||||
input.value = resp.url;
|
||||
status.textContent = "上传成功!";
|
||||
status.className = "upload-status upload-success";
|
||||
|
||||
// If there's a preview element, update it
|
||||
var preview = input.parentNode.querySelector(".upload-preview");
|
||||
if (preview) {
|
||||
preview.src = resp.url + "?thumb=1";
|
||||
preview.style.display = "block";
|
||||
}
|
||||
} else {
|
||||
status.textContent = resp.error || "上传失败";
|
||||
status.className = "upload-status upload-error";
|
||||
}
|
||||
} catch (e) {
|
||||
status.textContent = "上传失败";
|
||||
status.className = "upload-status upload-error";
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
var resp = JSON.parse(xhr.responseText);
|
||||
status.textContent = resp.error || "上传失败";
|
||||
} catch (e) {
|
||||
status.textContent = "上传失败 (" + xhr.status + ")";
|
||||
}
|
||||
status.className = "upload-status upload-error";
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function () {
|
||||
btn.disabled = false;
|
||||
btn.textContent = "上传图片";
|
||||
status.textContent = "网络错误";
|
||||
status.className = "upload-status upload-error";
|
||||
};
|
||||
|
||||
xhr.send(formData);
|
||||
});
|
||||
}
|
||||
|
||||
// Expose globally
|
||||
window.setupUpload = setupUpload;
|
||||
})();
|
||||
Reference in New Issue
Block a user