(function () { var config = Object.assign({ apiBaseUrl: "http://localhost:9098" }, window.VISITOR_LOGIN_CONFIG || {}); var MAX_PACKAGE_SIZE = 200 * 1024 * 1024; var state = { pageNum: 1, pageSize: 20, rows: [], mode: "add", packageFile: null }; init(); function init() { if (!getToken()) { redirectToLogin(); return; } bindLayoutActions(); bindPageActions(); applyUserInfo(readLoginUser()); loadProfileName(); loadVersions(); } function bindLayoutActions() { var userMenuButton = document.getElementById("userMenuButton"); var userDropdown = document.getElementById("userDropdown"); var fullscreenButton = document.querySelector(".icon-button"); userMenuButton.addEventListener("click", function (event) { event.stopPropagation(); var hidden = userDropdown.hidden; userDropdown.hidden = !hidden; userMenuButton.setAttribute("aria-expanded", String(hidden)); }); userDropdown.addEventListener("click", function (event) { var action = event.target.getAttribute("data-action"); if (!action) { return; } userDropdown.hidden = true; userMenuButton.setAttribute("aria-expanded", "false"); if (action === "home") { window.location.href = "./dashboard.html"; } else if (action === "logout") { logout(); } }); document.addEventListener("click", function () { userDropdown.hidden = true; userMenuButton.setAttribute("aria-expanded", "false"); }); fullscreenButton.addEventListener("click", function () { if (!document.fullscreenElement && document.documentElement.requestFullscreen) { document.documentElement.requestFullscreen(); } else if (document.exitFullscreen) { document.exitFullscreen(); } }); } function bindPageActions() { var uploadDrop = document.getElementById("uploadDrop"); var packageFile = document.getElementById("packageFile"); var versionNoInput = document.getElementById("versionNo"); document.getElementById("addVersionButton").addEventListener("click", openAddModal); document.getElementById("versionForm").addEventListener("submit", function (event) { event.preventDefault(); if (state.mode === "view") { closeModal(); return; } saveVersion(); }); versionNoInput.addEventListener("input", function () { versionNoInput.value = versionNoInput.value.replace(/[^\d.]/g, "").replace(/\.{2,}/g, "."); }); document.querySelectorAll("[data-close='version']").forEach(function (button) { button.addEventListener("click", closeModal); }); uploadDrop.addEventListener("click", function () { if (state.mode !== "view") { packageFile.click(); } }); packageFile.addEventListener("change", function () { var file = packageFile.files && packageFile.files[0]; setPackageFile(file); }); uploadDrop.addEventListener("dragover", function (event) { if (state.mode === "view") { return; } event.preventDefault(); uploadDrop.classList.add("drag-over"); }); uploadDrop.addEventListener("dragleave", function () { uploadDrop.classList.remove("drag-over"); }); uploadDrop.addEventListener("drop", function (event) { if (state.mode === "view") { return; } event.preventDefault(); uploadDrop.classList.remove("drag-over"); setPackageFile(event.dataTransfer.files && event.dataTransfer.files[0]); }); } function loadProfileName() { apiRequest("/system/user/profile", { method: "GET" }).then(function (result) { applyUserInfo(result.data || {}); }).catch(function () { applyUserInfo(readLoginUser()); }); } function loadVersions() { apiRequest("/system/version/update/page", { method: "POST", body: JSON.stringify({ pageNum: state.pageNum, pageSize: state.pageSize }) }).then(function (result) { var data = result.data || {}; state.rows = data.records || data.list || []; renderTable(); }).catch(function (error) { state.rows = []; renderTable(); AppMessage.error("操作失败:" + (error.message || "版本更新列表加载失败")); }); } function renderTable() { var body = document.getElementById("versionTableBody"); var emptyState = document.getElementById("emptyState"); emptyState.hidden = state.rows.length > 0; body.innerHTML = state.rows.map(function (item, index) { var rowIndex = (state.pageNum - 1) * state.pageSize + index + 1; return [ '', ' ' + rowIndex + '', ' ' + escapeHtml(item.versionNo || "-") + '', ' ', ' ' + escapeHtml(item.updateContent || "-") + '', ' ', ' ' + escapeHtml(item.createTime || "-") + '', '
', ' ', ' ', '
', '' ].join(""); }).join(""); body.querySelectorAll("[data-action]").forEach(function (button) { button.addEventListener("click", function () { var action = button.getAttribute("data-action"); var updateId = Number(button.getAttribute("data-id")); if (action === "view") { viewVersion(updateId); } else if (action === "delete") { confirmDelete(updateId); } }); }); } function openAddModal() { state.mode = "add"; state.packageFile = null; document.getElementById("versionModalTitle").textContent = "新建"; document.getElementById("versionForm").classList.remove("readonly"); document.getElementById("versionForm").reset(); document.getElementById("forceUpdate").checked = true; document.getElementById("saveVersionButton").hidden = false; setFormReadonly(false); updatePackageTip(); openModal(); } function viewVersion(updateId) { apiRequest("/system/version/update/detail", { method: "POST", body: JSON.stringify({ updateId: updateId }) }).then(function (result) { var data = result.data || {}; state.mode = "view"; state.packageFile = null; document.getElementById("versionModalTitle").textContent = "查看"; document.getElementById("versionForm").classList.add("readonly"); document.getElementById("versionNo").value = removeVersionPrefix(data.versionNo || ""); document.getElementById("updateContent").value = data.updateContent || ""; document.getElementById("forceUpdate").checked = !!data.forceUpdate; document.getElementById("saveVersionButton").hidden = true; setFormReadonly(true); updatePackageTip(data.packageName || data.packageUrl || "未上传安装包"); openModal(); }).catch(function (error) { AppMessage.error("操作失败:" + (error.message || "版本详情加载失败")); }); } function confirmDelete(updateId) { AppConfirm.open({ title: "是否确认删除数据?", message: "删除操作不可撤回,请谨慎操作", onConfirm: function () { return apiRequest("/system/version/update/delete", { method: "POST", body: JSON.stringify({ updateIds: [updateId] }) }).then(function () { AppMessage.success("操作成功"); loadVersions(); }); } }); } function saveVersion() { var saveButton = document.getElementById("saveVersionButton"); var versionNumber = document.getElementById("versionNo").value.trim(); var updateContent = document.getElementById("updateContent").value.trim(); if (!versionNumber) { AppMessage.error("操作失败:请输入版本号"); return; } if (!/^\d+(\.\d+)*$/.test(versionNumber)) { AppMessage.error("操作失败:版本号格式不正确,请输入数字或使用小数点分隔"); return; } if (!updateContent) { AppMessage.error("操作失败:请输入更新内容"); return; } if (!state.packageFile) { AppMessage.error("操作失败:请上传安装包"); return; } if (isPackageTooLarge(state.packageFile)) { AppMessage.error("操作失败:安装包大小必须小于200M"); return; } saveButton.disabled = true; uploadPackage().then(function (fileInfo) { return apiRequest("/system/version/update/add", { method: "POST", body: JSON.stringify({ versionNo: normalizeVersionNo(versionNumber), updateContent: updateContent, packageUrl: fileInfo.url || fileInfo.fileName || "", packageName: fileInfo.originalFilename || fileInfo.newFileName || state.packageFile.name, forceUpdate: document.getElementById("forceUpdate").checked }) }); }).then(function () { closeModal(); AppMessage.success("操作成功"); loadVersions(); }).catch(function (error) { AppMessage.error("操作失败:" + (error.message || "版本更新保存失败")); }).then(function () { saveButton.disabled = false; }); } function uploadPackage() { if (isPackageTooLarge(state.packageFile)) { return Promise.reject(new Error("安装包大小必须小于200M")); } var formData = new FormData(); formData.append("file", state.packageFile); return apiRequest("/system/version/update/upload", { method: "POST", body: formData, skipJson: true }); } function setPackageFile(file) { if (!file) { state.packageFile = null; updatePackageTip(); return; } if (isPackageTooLarge(file)) { state.packageFile = null; document.getElementById("packageFile").value = ""; updatePackageTip(); AppMessage.error("操作失败:安装包大小必须小于200M"); return; } state.packageFile = file; updatePackageTip(file.name); } function isPackageTooLarge(file) { return !!file && file.size >= MAX_PACKAGE_SIZE; } function updatePackageTip(fileName) { var uploadDrop = document.getElementById("uploadDrop"); var text = fileName || "请上传安装包文件,大小须小于200M"; document.getElementById("packageTip").textContent = text; uploadDrop.classList.toggle("has-file", !!fileName); uploadDrop.querySelector("span").innerHTML = fileName ? '' + escapeHtml(fileName) + '' : '点击上传/拖拽到此区域'; } function setFormReadonly(readonly) { document.getElementById("versionNo").disabled = readonly; document.getElementById("updateContent").disabled = readonly; document.getElementById("forceUpdate").disabled = readonly; } function openModal() { document.getElementById("versionModal").hidden = false; } function closeModal() { document.getElementById("versionModal").hidden = true; } function apiRequest(path, options) { options = options || {}; var headers = options.headers || {}; var token = getToken(); if (token) { headers.Authorization = "Bearer " + token; } if (!options.skipJson) { headers["Content-Type"] = "application/json"; } return fetch(getApiBaseUrl() + normalizePath(path), { method: options.method || "GET", headers: headers, body: options.body }).then(function (response) { return response.json().catch(function () { return { code: response.ok ? 200 : response.status, msg: response.statusText }; }); }).then(function (result) { if (result.code === 401) { clearLoginState(); redirectToLogin(); throw new Error(result.msg || "登录状态已过期"); } if (result.code !== 200) { throw new Error(result.msg || "请求失败"); } return result; }); } function logout() { apiRequest("/logout", { method: "POST", skipJson: true }).catch(function () { return null; }).then(function () { clearLoginState(); redirectToLogin(); }); } function applyUserInfo(user) { user = user || {}; var displayName = user.nickName || user.userName || user.username || "用户"; document.getElementById("userDisplayName").textContent = displayName; document.querySelector(".user-avatar").textContent = displayName.substring(0, 1); } function readLoginUser() { try { var loginResult = JSON.parse(localStorage.getItem("visitor_admin_login_result") || "{}"); return (loginResult.userInfo && loginResult.userInfo.user) || loginResult.user || {}; } catch (error) { return {}; } } function normalizeVersionNo(value) { var text = String(value || "").trim(); if (!text) { return ""; } return /^v/i.test(text) ? text : "v" + text; } function removeVersionPrefix(value) { return String(value || "").replace(/^v/i, ""); } function shortDate(value) { return value ? String(value).slice(0, 10).replace(/-/g, ".") : "-"; } function getApiBaseUrl() { return trimRightSlash(localStorage.getItem("visitor_admin_api_base") || config.apiBaseUrl || window.location.origin); } function getToken() { return localStorage.getItem("visitor_admin_token") || ""; } function clearLoginState() { localStorage.removeItem("visitor_admin_token"); localStorage.removeItem("visitor_admin_login_result"); localStorage.removeItem("visitor_admin_api_base"); } function redirectToLogin() { window.location.href = "../index.html"; } function normalizePath(path) { return "/" + String(path || "").replace(/^\/+/, ""); } function trimRightSlash(value) { return String(value || "").replace(/\/+$/, ""); } function escapeHtml(value) { return String(value == null ? "" : value).replace(/[&<>"']/g, function (char) { return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[char]; }); } })();