(function () {
var config = Object.assign({
apiBaseUrl: "http://localhost:9098"
}, window.VISITOR_LOGIN_CONFIG || {});
var state = {
areaTree: [],
flatAreas: [],
selectedAreaId: null,
modalAreaId: null,
cascaderPath: [],
rows: [],
total: 0,
pageNum: 1,
pageSize: 10,
mode: "add",
currentMachineId: null
};
init();
function init() {
if (!getToken()) {
redirectToLogin();
return;
}
bindLayoutActions();
bindPageActions();
applyUserInfo(readLoginUser());
loadProfileName();
loadAreaTree();
loadMachines();
}
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");
closeMoreMenus();
closeAreaCascader();
});
fullscreenButton.addEventListener("click", function () {
if (!document.fullscreenElement && document.documentElement.requestFullscreen) {
document.documentElement.requestFullscreen();
} else if (document.exitFullscreen) {
document.exitFullscreen();
}
});
}
function bindPageActions() {
document.getElementById("areaSearchButton").addEventListener("click", loadAreaTree);
document.getElementById("areaSearchInput").addEventListener("keydown", function (event) {
if (event.key === "Enter") {
loadAreaTree();
}
});
document.getElementById("queryForm").addEventListener("submit", function (event) {
event.preventDefault();
state.pageNum = 1;
loadMachines();
});
document.getElementById("resetButton").addEventListener("click", function () {
document.getElementById("queryDeviceName").value = "";
document.getElementById("queryDeviceId").value = "";
document.getElementById("queryEnabled").value = "";
document.getElementById("areaSearchInput").value = "";
state.selectedAreaId = null;
state.pageNum = 1;
loadAreaTree();
loadMachines();
});
document.getElementById("addMachineButton").addEventListener("click", function () {
openMachineModal("add", {});
});
document.getElementById("machineForm").addEventListener("submit", function (event) {
event.preventDefault();
saveMachine();
});
document.querySelectorAll("[data-close='machine']").forEach(function (button) {
button.addEventListener("click", closeMachineModal);
});
document.getElementById("machineAreaCascaderTrigger").addEventListener("click", function (event) {
event.stopPropagation();
toggleAreaCascader();
});
document.getElementById("machineAreaCascaderPanel").addEventListener("click", function (event) {
event.stopPropagation();
});
}
function loadProfileName() {
apiRequest("/system/user/profile", { method: "GET" }).then(function (result) {
applyUserInfo(result.data || {});
}).catch(function () {
applyUserInfo(readLoginUser());
});
}
function loadAreaTree() {
apiRequest("/system/visit/area/tree", {
method: "POST",
body: JSON.stringify({
areaName: document.getElementById("areaSearchInput").value.trim()
})
}).then(function (result) {
state.areaTree = Array.isArray(result.data) ? result.data : [];
state.flatAreas = flattenAreas(state.areaTree);
renderAreaTree();
renderAreaCascaderPanel();
}).catch(function (error) {
state.areaTree = [];
state.flatAreas = [];
state.modalAreaId = null;
state.cascaderPath = [];
renderAreaTree();
renderAreaCascaderPanel();
updateCascaderText();
AppMessage.error("操作失败:" + (error.message || "区域加载失败"));
});
}
function loadMachines() {
var enabledValue = document.getElementById("queryEnabled").value;
var payload = {
pageNum: state.pageNum,
pageSize: state.pageSize,
areaId: state.selectedAreaId,
deviceName: document.getElementById("queryDeviceName").value.trim(),
deviceId: document.getElementById("queryDeviceId").value.trim()
};
if (enabledValue) {
payload.enabled = enabledValue === "true";
}
apiRequest("/system/visitor/machine/page", {
method: "POST",
body: JSON.stringify(payload)
}).then(function (result) {
var data = result.data || {};
state.rows = data.records || data.list || [];
state.total = Number(data.total || state.rows.length || 0);
state.pageNum = Number(data.current || data.pageNum || state.pageNum);
state.pageSize = Number(data.size || data.pageSize || state.pageSize);
renderTable();
renderPagination();
}).catch(function (error) {
state.rows = [];
state.total = 0;
renderTable();
renderPagination();
AppMessage.error("操作失败:" + (error.message || "访客机列表加载失败"));
});
}
function renderAreaTree() {
var root = document.getElementById("areaTree");
root.innerHTML = state.areaTree.map(function (area) {
return renderAreaNode(area, 1);
}).join("");
root.querySelectorAll(".machine-area-row").forEach(function (row) {
row.addEventListener("click", function () {
state.selectedAreaId = Number(row.getAttribute("data-id"));
state.pageNum = 1;
renderAreaTree();
loadMachines();
});
});
root.querySelectorAll(".machine-area-toggle").forEach(function (toggle) {
toggle.addEventListener("click", function (event) {
event.stopPropagation();
toggle.closest(".machine-area-node").classList.toggle("collapsed");
});
});
}
function renderAreaNode(area, level) {
var children = Array.isArray(area.children) ? area.children : [];
var active = state.selectedAreaId === area.areaId;
var childHtml = children.length ? '
' + children.map(function (child) {
return renderAreaNode(child, level + 1);
}).join("") + '
' : "";
return [
'',
'
',
' ',
' ',
' ' + escapeHtml(area.areaName || "-") + '',
'
',
childHtml,
'
'
].join("");
}
function renderTable() {
var body = document.getElementById("machineTableBody");
var empty = document.getElementById("machineEmpty");
empty.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.deviceName || "-") + ' | ',
' ' + escapeHtml(item.brand || "-") + ' | ',
' ' + escapeHtml(item.deviceId || "-") + ' | ',
' ' + escapeHtml(item.fingerprint || "-") + ' | ',
' ' + escapeHtml(item.areaName || "-") + ' | ',
' ' + renderEnabledStatus(item.enabled) + ' | ',
' ' + renderOnlineStatus(item.onlineStatus) + ' | ',
' ' + escapeHtml(item.remark || "-") + ' | ',
' ',
' ',
' ',
renderMoreMenu(item),
' | ',
'
'
].join("");
}).join("");
body.querySelectorAll("[data-action]").forEach(function (button) {
button.addEventListener("click", function (event) {
event.stopPropagation();
var action = button.getAttribute("data-action");
var machineId = Number(button.getAttribute("data-id"));
if (action === "edit") {
editMachine(machineId);
} else if (action === "more") {
toggleMoreMenu(button);
} else if (action === "logout") {
confirmMachineAction(machineId, "是否确认注销设备?", "注销后设备在线状态将变为未激活", logoutMachine);
} else if (action === "disable") {
changeEnabled(machineId, false);
} else if (action === "enable") {
changeEnabled(machineId, true);
} else if (action === "delete") {
confirmMachineAction(machineId, "是否确认删除数据?", "删除操作不可撤回,请谨慎操作", deleteMachine);
}
});
});
}
function renderMoreMenu(item) {
var statusAction = item.enabled
? ''
: '';
return [
''
].join("");
}
function renderEnabledStatus(enabled) {
var className = enabled ? "enabled" : "disabled";
var label = enabled ? "启用" : "禁用";
return '' + label + '';
}
function renderOnlineStatus(status) {
var className = "unactivated";
if (status === "ONLINE") {
className = "online";
} else if (status === "OFFLINE") {
className = "offline";
}
return '' + escapeHtml(formatOnlineStatus(status)) + '';
}
function renderPagination() {
var root = document.getElementById("pagination");
var totalPage = Math.max(1, Math.ceil(state.total / state.pageSize));
var pages = [];
for (var page = 1; page <= Math.min(totalPage, 5); page++) {
pages.push('');
}
if (totalPage > 5) {
pages.push('...');
}
root.innerHTML = [
'共 ' + state.total + ' 条',
'每页 条',
pages.join(""),
'前往 页'
].join("");
document.getElementById("pageSizeSelect").value = String(state.pageSize);
root.querySelectorAll("[data-page]").forEach(function (button) {
button.addEventListener("click", function () {
state.pageNum = Number(button.getAttribute("data-page"));
loadMachines();
});
});
document.getElementById("pageSizeSelect").addEventListener("change", function (event) {
state.pageSize = Number(event.target.value);
state.pageNum = 1;
loadMachines();
});
document.getElementById("jumpPageInput").addEventListener("keydown", function (event) {
if (event.key === "Enter") {
var page = Math.min(totalPage, Math.max(1, Number(event.target.value) || 1));
state.pageNum = page;
loadMachines();
}
});
}
function openMachineModal(mode, data) {
state.mode = mode;
state.currentMachineId = mode === "edit" ? data.machineId : null;
document.getElementById("machineModalTitle").textContent = mode === "edit" ? "编辑访客机" : "新增访客机";
setCascaderValue(data.areaId || state.selectedAreaId || "");
document.getElementById("machineDeviceName").value = data.deviceName || "";
document.getElementById("machineBrand").value = data.brand || "";
document.getElementById("machineDeviceId").value = data.deviceId || "";
document.getElementById("machineRemark").value = data.remark || "";
document.getElementById("machineEnabled").checked = typeof data.enabled === "boolean" ? data.enabled : true;
document.getElementById("machineModal").hidden = false;
}
function closeMachineModal() {
document.getElementById("machineModal").hidden = true;
closeAreaCascader();
}
function editMachine(machineId) {
apiRequest("/system/visitor/machine/detail", {
method: "POST",
body: JSON.stringify({ machineId: machineId })
}).then(function (result) {
openMachineModal("edit", result.data || {});
}).catch(function (error) {
AppMessage.error("操作失败:" + (error.message || "设备详情加载失败"));
});
}
function saveMachine() {
var saveButton = document.getElementById("saveMachineButton");
var payload = {
areaId: Number(state.modalAreaId || 0),
deviceName: document.getElementById("machineDeviceName").value.trim(),
brand: document.getElementById("machineBrand").value.trim(),
deviceId: document.getElementById("machineDeviceId").value.trim(),
remark: document.getElementById("machineRemark").value.trim(),
enabled: document.getElementById("machineEnabled").checked
};
if (!payload.areaId) {
AppMessage.error("操作失败:请选择区域");
return;
}
if (!payload.deviceName) {
AppMessage.error("操作失败:请输入设备名称");
return;
}
if (!payload.brand) {
AppMessage.error("操作失败:请输入品牌");
return;
}
if (!payload.deviceId) {
AppMessage.error("操作失败:请输入设备ID");
return;
}
var url = "/system/visitor/machine/add";
if (state.mode === "edit") {
payload.machineId = state.currentMachineId;
url = "/system/visitor/machine/edit";
}
saveButton.disabled = true;
apiRequest(url, {
method: "POST",
body: JSON.stringify(payload)
}).then(function () {
closeMachineModal();
AppMessage.success("操作成功");
loadMachines();
}).catch(function (error) {
AppMessage.error("操作失败:" + (error.message || "设备保存失败"));
}).then(function () {
saveButton.disabled = false;
});
}
function changeEnabled(machineId, enabled) {
apiRequest("/system/visitor/machine/changeStatus", {
method: "POST",
body: JSON.stringify({ machineIds: [machineId], enabled: enabled })
}).then(function () {
AppMessage.success("操作成功");
loadMachines();
}).catch(function (error) {
AppMessage.error("操作失败:" + (error.message || "状态更新失败"));
});
}
function logoutMachine(machineId) {
return apiRequest("/system/visitor/machine/logout", {
method: "POST",
body: JSON.stringify({ machineIds: [machineId] })
}).then(function () {
AppMessage.success("操作成功");
loadMachines();
});
}
function deleteMachine(machineId) {
return apiRequest("/system/visitor/machine/delete", {
method: "POST",
body: JSON.stringify({ machineIds: [machineId] })
}).then(function () {
AppMessage.success("操作成功");
loadMachines();
});
}
function confirmMachineAction(machineId, title, message, handler) {
AppConfirm.open({
title: title,
message: message,
onConfirm: function () {
return handler(machineId);
}
});
}
function toggleAreaCascader() {
var panel = document.getElementById("machineAreaCascaderPanel");
if (panel.hidden) {
renderAreaCascaderPanel();
panel.hidden = false;
document.getElementById("machineAreaCascaderTrigger").setAttribute("aria-expanded", "true");
return;
}
closeAreaCascader();
}
function closeAreaCascader() {
var panel = document.getElementById("machineAreaCascaderPanel");
if (!panel) {
return;
}
panel.hidden = true;
document.getElementById("machineAreaCascaderTrigger").setAttribute("aria-expanded", "false");
}
function setCascaderValue(areaId) {
state.modalAreaId = areaId ? Number(areaId) : null;
state.cascaderPath = state.modalAreaId ? findAreaPath(state.modalAreaId) : [];
document.getElementById("machineAreaId").value = state.modalAreaId || "";
updateCascaderText();
renderAreaCascaderPanel();
}
function renderAreaCascaderPanel() {
var panel = document.getElementById("machineAreaCascaderPanel");
var columns = buildCascaderColumns();
if (!columns.length) {
panel.innerHTML = '暂无区域
';
return;
}
panel.innerHTML = columns.map(function (column, columnIndex) {
return '' + column.items.map(function (area) {
var active = column.selectedId === area.areaId;
var hasChildren = Array.isArray(area.children) && area.children.length > 0;
return [
''
].join("");
}).join("") + '
';
}).join("");
panel.querySelectorAll(".area-cascader-option").forEach(function (button) {
button.addEventListener("click", function () {
var level = Number(button.getAttribute("data-level"));
var areaId = Number(button.getAttribute("data-id"));
var area = findArea(areaId);
if (!area) {
return;
}
state.cascaderPath = state.cascaderPath.slice(0, level);
state.cascaderPath[level] = area;
state.modalAreaId = area.areaId;
document.getElementById("machineAreaId").value = state.modalAreaId || "";
updateCascaderText();
renderAreaCascaderPanel();
if (!area.children || !area.children.length) {
closeAreaCascader();
}
});
});
}
function buildCascaderColumns() {
if (!state.areaTree.length) {
return [];
}
var columns = [{
items: state.areaTree,
selectedId: state.cascaderPath[0] && state.cascaderPath[0].areaId
}];
state.cascaderPath.forEach(function (area, index) {
var children = Array.isArray(area.children) ? area.children : [];
if (children.length) {
columns.push({
items: children,
selectedId: state.cascaderPath[index + 1] && state.cascaderPath[index + 1].areaId
});
}
});
return columns;
}
function updateCascaderText() {
var text = state.cascaderPath.length
? state.cascaderPath.map(function (area) { return area.areaName; }).join(" / ")
: "下拉菜单";
document.getElementById("machineAreaCascaderText").textContent = text;
}
function toggleMoreMenu(button) {
var menu = button.parentNode.querySelector(".more-menu");
var hidden = menu.hidden;
closeMoreMenus();
menu.hidden = !hidden;
}
function closeMoreMenus() {
document.querySelectorAll(".more-menu").forEach(function (menu) {
menu.hidden = true;
});
}
function flattenAreas(tree) {
var result = [];
tree.forEach(function (area) {
walkArea(area, 1, result);
});
return result;
}
function walkArea(area, level, result) {
area.level = level;
result.push(area);
(area.children || []).forEach(function (child) {
child.parentId = area.areaId;
walkArea(child, level + 1, result);
});
}
function findArea(areaId) {
for (var index = 0; index < state.flatAreas.length; index++) {
if (Number(state.flatAreas[index].areaId) === Number(areaId)) {
return state.flatAreas[index];
}
}
return null;
}
function findAreaPath(areaId) {
var result = [];
walkPath(state.areaTree, Number(areaId), result);
return result;
}
function walkPath(list, areaId, path) {
for (var index = 0; index < list.length; index++) {
var area = list[index];
path.push(area);
if (Number(area.areaId) === areaId) {
return true;
}
if (walkPath(area.children || [], areaId, path)) {
return true;
}
path.pop();
}
return false;
}
function formatOnlineStatus(status) {
if (status === "ONLINE") {
return "在线";
}
if (status === "OFFLINE") {
return "离线";
}
return "未激活";
}
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 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];
});
}
})();