(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",
|
currentDoorId: null
|
};
|
|
init();
|
|
function init() {
|
if (!getToken()) {
|
redirectToLogin();
|
return;
|
}
|
bindLayoutActions();
|
bindPageActions();
|
applyUserInfo(readLoginUser());
|
loadProfileName();
|
loadAreaTree();
|
loadDoorDevices();
|
}
|
|
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;
|
loadDoorDevices();
|
});
|
|
document.getElementById("resetButton").addEventListener("click", function () {
|
document.getElementById("queryDeviceName").value = "";
|
document.getElementById("queryDeviceId").value = "";
|
document.getElementById("queryEnabled").value = "";
|
state.pageNum = 1;
|
loadDoorDevices();
|
});
|
|
document.getElementById("addDoorButton").addEventListener("click", function () {
|
openDoorModal("add", {});
|
});
|
|
document.getElementById("doorForm").addEventListener("submit", function (event) {
|
event.preventDefault();
|
saveDoorDevice();
|
});
|
|
document.querySelectorAll("[data-close='door']").forEach(function (button) {
|
button.addEventListener("click", closeDoorModal);
|
});
|
|
document.getElementById("areaCascaderTrigger").addEventListener("click", function (event) {
|
event.stopPropagation();
|
toggleAreaCascader();
|
});
|
|
document.getElementById("areaCascaderPanel").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.selectedAreaId = null;
|
state.modalAreaId = null;
|
state.cascaderPath = [];
|
renderAreaTree();
|
renderAreaCascaderPanel();
|
updateCascaderText();
|
AppMessage.error("操作失败:" + (error.message || "区域加载失败"));
|
});
|
}
|
|
function loadDoorDevices() {
|
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/door/device/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(".door-area-row").forEach(function (row) {
|
row.addEventListener("click", function () {
|
state.selectedAreaId = Number(row.getAttribute("data-id"));
|
state.pageNum = 1;
|
renderAreaTree();
|
loadDoorDevices();
|
});
|
});
|
|
root.querySelectorAll(".door-area-toggle").forEach(function (toggle) {
|
toggle.addEventListener("click", function (event) {
|
event.stopPropagation();
|
toggle.closest(".door-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 ? '<div class="door-area-children">' + children.map(function (child) {
|
return renderAreaNode(child, level + 1);
|
}).join("") + '</div>' : "";
|
return [
|
'<div class="door-area-node' + (children.length ? "" : " no-children") + '">',
|
' <div class="door-area-row level-' + level + (active ? " active" : "") + '" data-id="' + area.areaId + '">',
|
' <span class="door-area-toggle"></span>',
|
' <span class="' + (level === 1 ? "door-folder-icon" : "door-file-icon") + '"></span>',
|
' <span>' + escapeHtml(area.areaName || "-") + '</span>',
|
' </div>',
|
childHtml,
|
'</div>'
|
].join("");
|
}
|
|
function renderTable() {
|
var body = document.getElementById("doorTableBody");
|
var empty = document.getElementById("doorEmpty");
|
empty.hidden = state.rows.length > 0;
|
body.innerHTML = state.rows.map(function (item, index) {
|
var rowIndex = (state.pageNum - 1) * state.pageSize + index + 1;
|
return [
|
'<tr>',
|
' <td>' + rowIndex + '</td>',
|
' <td>' + escapeHtml(item.deviceName || "-") + '</td>',
|
' <td>' + escapeHtml(item.brand || "-") + '</td>',
|
' <td>' + escapeHtml(item.deviceId || "-") + '</td>',
|
' <td>' + escapeHtml(item.areaName || "-") + '</td>',
|
' <td>' + renderEnabledStatus(item) + '</td>',
|
' <td>' + renderOnlineStatus(item.onlineStatus) + '</td>',
|
' <td title="' + escapeHtml(item.remark || "") + '">' + escapeHtml(item.remark || "-") + '</td>',
|
' <td class="door-action-cell"><div class="door-actions">',
|
' <button type="button" data-action="edit" data-id="' + getDoorId(item) + '">编辑</button>',
|
' <button type="button" data-action="more" data-id="' + getDoorId(item) + '">更多</button>',
|
renderMoreMenu(item),
|
' </div></td>',
|
'</tr>'
|
].join("");
|
}).join("");
|
|
body.querySelectorAll("[data-action]").forEach(function (button) {
|
button.addEventListener("click", function (event) {
|
event.stopPropagation();
|
var action = button.getAttribute("data-action");
|
var doorId = Number(button.getAttribute("data-id"));
|
if (action === "edit") {
|
editDoorDevice(doorId);
|
} else if (action === "more") {
|
toggleMoreMenu(button);
|
} else if (action === "logout") {
|
confirmDoorAction(doorId, "是否确认注销设备?", "注销后设备在线状态将变为未激活", logoutDoorDevice);
|
} else if (action === "disable") {
|
changeEnabled(doorId, false);
|
} else if (action === "enable") {
|
changeEnabled(doorId, true);
|
} else if (action === "delete") {
|
confirmDoorAction(doorId, "是否确认删除数据?", "删除操作不可撤回,请谨慎操作", deleteDoorDevice);
|
}
|
});
|
});
|
}
|
|
function renderMoreMenu(item) {
|
var statusAction = isEnabled(item)
|
? '<button type="button" data-action="disable" data-id="' + getDoorId(item) + '"><span class="door-more-icon disable"></span>禁用</button>'
|
: '<button type="button" data-action="enable" data-id="' + getDoorId(item) + '"><span class="door-more-icon disable"></span>启用</button>';
|
return [
|
'<div class="door-more-menu" hidden>',
|
' <button type="button" data-action="logout" data-id="' + getDoorId(item) + '"><span class="door-more-icon logout"></span>注销</button>',
|
statusAction,
|
' <button type="button" data-action="delete" data-id="' + getDoorId(item) + '"><span class="door-more-icon delete"></span>删除</button>',
|
'</div>'
|
].join("");
|
}
|
|
function renderEnabledStatus(item) {
|
var enabled = isEnabled(item);
|
var className = enabled ? "enabled" : "disabled";
|
var label = enabled ? "启用" : "禁用";
|
return '<span class="status-pill ' + className + '">' + label + '</span>';
|
}
|
|
function renderOnlineStatus(status) {
|
var className = "unactivated";
|
if (status === "ONLINE") {
|
className = "online";
|
} else if (status === "OFFLINE") {
|
className = "offline";
|
}
|
return '<span class="status-pill ' + className + '">' + escapeHtml(formatOnlineStatus(status)) + '</span>';
|
}
|
|
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('<button type="button" class="' + (page === state.pageNum ? "active" : "") + '" data-page="' + page + '">' + page + '</button>');
|
}
|
if (totalPage > 5) {
|
pages.push('<span>...</span><button type="button" data-page="' + totalPage + '">' + totalPage + '</button>');
|
}
|
root.innerHTML = [
|
'<span>共 ' + state.total + ' 条</span>',
|
'<span>每页 <select id="pageSizeSelect"><option value="10">10</option><option value="20">20</option><option value="50">50</option></select> 条</span>',
|
pages.join(""),
|
'<span>前往 <input id="jumpPageInput" value="' + state.pageNum + '"> 页</span>'
|
].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"));
|
loadDoorDevices();
|
});
|
});
|
document.getElementById("pageSizeSelect").addEventListener("change", function (event) {
|
state.pageSize = Number(event.target.value);
|
state.pageNum = 1;
|
loadDoorDevices();
|
});
|
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;
|
loadDoorDevices();
|
}
|
});
|
}
|
|
function openDoorModal(mode, data) {
|
state.mode = mode;
|
state.currentDoorId = mode === "edit" ? getDoorId(data) : null;
|
document.getElementById("doorModalTitle").textContent = mode === "edit" ? "编辑门禁" : "新增门禁";
|
document.getElementById("doorDeviceName").value = data.deviceName || "";
|
document.getElementById("doorBrand").value = data.brand || "";
|
document.getElementById("doorDeviceId").value = data.deviceId || "";
|
document.getElementById("doorRemark").value = data.remark || "";
|
document.getElementById("doorEnabled").checked = typeof data.enabled === "boolean" ? data.enabled : true;
|
setCascaderValue(data.areaId || state.selectedAreaId || "");
|
document.getElementById("doorModal").hidden = false;
|
}
|
|
function closeDoorModal() {
|
document.getElementById("doorModal").hidden = true;
|
closeAreaCascader();
|
}
|
|
function editDoorDevice(doorId) {
|
apiRequest("/system/door/device/detail", {
|
method: "POST",
|
body: JSON.stringify({ doorId: doorId })
|
}).then(function (result) {
|
openDoorModal("edit", result.data || {});
|
}).catch(function (error) {
|
AppMessage.error("操作失败:" + (error.message || "设备详情加载失败"));
|
});
|
}
|
|
function saveDoorDevice() {
|
var saveButton = document.getElementById("saveDoorButton");
|
var payload = {
|
areaId: Number(state.modalAreaId || 0),
|
deviceName: document.getElementById("doorDeviceName").value.trim(),
|
brand: document.getElementById("doorBrand").value.trim(),
|
deviceId: document.getElementById("doorDeviceId").value.trim(),
|
remark: document.getElementById("doorRemark").value.trim(),
|
enabled: document.getElementById("doorEnabled").checked
|
};
|
if (!payload.deviceName) {
|
AppMessage.error("操作失败:请输入设备名称");
|
return;
|
}
|
if (!payload.brand) {
|
AppMessage.error("操作失败:请输入品牌");
|
return;
|
}
|
if (!payload.deviceId) {
|
AppMessage.error("操作失败:请输入设备ID");
|
return;
|
}
|
if (!payload.areaId) {
|
AppMessage.error("操作失败:请选择区域");
|
return;
|
}
|
|
var url = "/system/door/device/add";
|
if (state.mode === "edit") {
|
payload.doorId = state.currentDoorId;
|
url = "/system/door/device/edit";
|
}
|
|
saveButton.disabled = true;
|
apiRequest(url, {
|
method: "POST",
|
body: JSON.stringify(payload)
|
}).then(function () {
|
closeDoorModal();
|
AppMessage.success("操作成功");
|
loadDoorDevices();
|
}).catch(function (error) {
|
AppMessage.error("操作失败:" + (error.message || "设备保存失败"));
|
}).then(function () {
|
saveButton.disabled = false;
|
});
|
}
|
|
function changeEnabled(doorId, enabled) {
|
apiRequest("/system/door/device/changeStatus", {
|
method: "POST",
|
body: JSON.stringify({ doorIds: [doorId], enabled: enabled })
|
}).then(function () {
|
AppMessage.success("操作成功");
|
loadDoorDevices();
|
}).catch(function (error) {
|
AppMessage.error("操作失败:" + (error.message || "状态更新失败"));
|
});
|
}
|
|
function logoutDoorDevice(doorId) {
|
return apiRequest("/system/door/device/logout", {
|
method: "POST",
|
body: JSON.stringify({ doorIds: [doorId] })
|
}).then(function () {
|
AppMessage.success("操作成功");
|
loadDoorDevices();
|
});
|
}
|
|
function deleteDoorDevice(doorId) {
|
return apiRequest("/system/door/device/delete", {
|
method: "POST",
|
body: JSON.stringify({ doorIds: [doorId] })
|
}).then(function () {
|
AppMessage.success("操作成功");
|
loadDoorDevices();
|
});
|
}
|
|
function confirmDoorAction(doorId, title, message, handler) {
|
AppConfirm.open({
|
title: title,
|
message: message,
|
onConfirm: function () {
|
return handler(doorId);
|
}
|
});
|
}
|
|
function toggleAreaCascader() {
|
var panel = document.getElementById("areaCascaderPanel");
|
if (panel.hidden) {
|
renderAreaCascaderPanel();
|
panel.hidden = false;
|
return;
|
}
|
panel.hidden = true;
|
}
|
|
function closeAreaCascader() {
|
document.getElementById("areaCascaderPanel").hidden = true;
|
}
|
|
function setCascaderValue(areaId) {
|
state.modalAreaId = areaId ? Number(areaId) : null;
|
state.cascaderPath = state.modalAreaId ? findAreaPath(state.modalAreaId) : [];
|
updateCascaderText();
|
renderAreaCascaderPanel();
|
}
|
|
function renderAreaCascaderPanel() {
|
var panel = document.getElementById("areaCascaderPanel");
|
var columns = buildCascaderColumns();
|
panel.innerHTML = columns.map(function (column, columnIndex) {
|
return '<div class="area-cascader-column">' + column.items.map(function (area) {
|
var active = column.selectedId === area.areaId;
|
var hasChildren = Array.isArray(area.children) && area.children.length > 0;
|
return [
|
'<button class="area-cascader-option' + (active ? " active" : "") + '" type="button" data-level="' + columnIndex + '" data-id="' + area.areaId + '">',
|
'<span>' + escapeHtml(area.areaName || "-") + '</span>',
|
hasChildren ? '<span class="area-cascader-next">›</span>' : '<span></span>',
|
'</button>'
|
].join("");
|
}).join("") + '</div>';
|
}).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;
|
updateCascaderText();
|
renderAreaCascaderPanel();
|
if (!area.children || !area.children.length) {
|
closeAreaCascader();
|
}
|
});
|
});
|
}
|
|
function buildCascaderColumns() {
|
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("areaCascaderText").textContent = text;
|
}
|
|
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 (state.flatAreas[index].areaId === 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 (area.areaId === areaId) {
|
return true;
|
}
|
if (walkPath(area.children || [], areaId, path)) {
|
return true;
|
}
|
path.pop();
|
}
|
return false;
|
}
|
|
function toggleMoreMenu(button) {
|
var menu = button.parentNode.querySelector(".door-more-menu");
|
var hidden = menu.hidden;
|
closeMoreMenus();
|
menu.hidden = !hidden;
|
}
|
|
function closeMoreMenus() {
|
document.querySelectorAll(".door-more-menu").forEach(function (menu) {
|
menu.hidden = true;
|
});
|
}
|
|
function getDoorId(item) {
|
return Number(item.doorId || item.deviceId || item.id || 0);
|
}
|
|
function isEnabled(item) {
|
return item.enabled === true || item.enabled === "true" || item.enabled === 1 || item.enabled === "1";
|
}
|
|
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];
|
});
|
}
|
})();
|