This commit is contained in:
nanguaboss
2026-08-03 00:07:01 +08:00
commit 71c6e8d9c1
112 changed files with 7815 additions and 0 deletions
+415
View File
@@ -0,0 +1,415 @@
/**
* common.js - 通用函数(httpGet/httpPost/弹窗/分页/日期格式化/菜单渲染/侧边栏/用户菜单)
*/
/* ============ HTTP 封装 ============ */
/**
* GET 请求(返回 Promise<data>)
*/
function httpGet(url, params) {
var full = BASE_URL + url;
if (params) {
var qs = $.param(params);
full += (full.indexOf('?') >= 0 ? '&' : '?') + qs;
}
return $.ajax({
url: full,
method: 'GET',
dataType: 'json'
}).then(handleResp, handleError);
}
/**
* POST 请求(写类操作自动携带 X-Requested-With 头,返回 Promise<data>)
*/
function httpPost(url, data, isFormData) {
var opts = {
url: BASE_URL + url,
method: 'POST',
dataType: 'json',
headers: { 'X-Requested-With': 'XMLHttpRequest' }
};
if (isFormData) {
opts.data = data;
opts.processData = false;
opts.contentType = false;
} else {
opts.data = data;
}
return $.ajax(opts).then(handleResp, handleError);
}
/** 统一处理响应:code!=0 弹错并 reject;401 立即回登录页 */
function handleResp(resp) {
if (resp && resp.code === 0) {
return resp.data;
}
if (resp && resp.code === 401) {
location.href = 'index.html';
return $.Deferred().reject(resp).promise();
}
var msg = (resp && resp.msg) ? resp.msg : '请求失败';
layer.msg(msg, { icon: 2 });
return $.Deferred().reject(resp).promise();
}
function handleError(xhr) {
if (xhr.status === 401) {
location.href = 'index.html';
return $.Deferred().reject(xhr).promise();
}
var msg = '网络错误';
try {
var resp = xhr.responseJSON;
if (resp && resp.msg) msg = resp.msg;
} catch (e) {}
layer.msg(msg, { icon: 2 });
return $.Deferred().reject(xhr).promise();
}
/* ============ 弹窗封装(基于 layer) ============ */
var Dialog = {
alert: function (msg, cb) { layer.alert(msg, { icon: 0 }, cb || function () {}); },
success: function (msg, cb) { layer.msg(msg, { icon: 1, time: 1200 }, cb || function () {}); },
error: function (msg) { layer.msg(msg, { icon: 2 }); },
confirm: function (msg, cb) {
layer.confirm(msg, { icon: 3, title: '确认操作' }, function (idx) {
layer.close(idx);
cb && cb();
});
},
open: function (opts) {
return layer.open({
type: 1,
title: opts.title || '操作',
area: opts.area || ['720px', 'auto'],
content: opts.content,
btn: opts.btn !== false ? ['确定', '取消'] : null,
yes: opts.yes || null,
end: opts.end || null
});
},
close: function (idx) { layer.close(idx); }
};
/* ============ 日期格式化 ============ */
function fmtDate(str, withTime) {
if (!str) return '-';
var d = new Date(str.replace(/-/g, '/'));
if (isNaN(d.getTime())) return str;
var p = function (n) { return n < 10 ? '0' + n : '' + n; };
var s = d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate());
if (withTime) s += ' ' + p(d.getHours()) + ':' + p(d.getMinutes()) + ':' + p(d.getSeconds());
return s;
}
function escHtml(str) {
if (str === null || str === undefined) return '';
return String(str)
.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}
/* ============ 会话与权限 ============ */
/**
* 校验当前页面权限,无权限则提示并跳回首页
* @param {string} key 菜单标识
* @returns {boolean} 是否有权限
*/
function checkPagePermission(key) {
var perms = (window.CURRENT_USER && window.CURRENT_USER.permissions) || [];
if (perms.indexOf(key) < 0) {
Dialog.alert('您没有访问该模块的权限', function () {
location.href = 'dashboard.html';
});
return false;
}
return true;
}
/**
* 页面初始化:校验登录态 + 渲染左侧菜单 + 填充右上角用户信息
* 注意:成功回调内包裹 try/catch——渲染异常只记日志,不触发登录页跳转;
* .fail 仅在会话真实失效(401)时才跳转登录页。
*/
function initPage(activeKey, cb) {
httpGet('auth/session.php').then(function (user) {
window.CURRENT_USER = user;
try {
renderMenu(activeKey, user.permissions || []);
// 右上角用户菜单
var name = user.real_name || user.username;
$('#um-name').text(name);
$('#um-detail-name').text(name);
$('#um-detail-account').text('@' + user.username);
$('#um-avatar').text(name.charAt(0).toUpperCase());
$('#um-role').text(user.role_name || '-');
$('#um-status').text('已登录').removeClass('um-off').addClass('um-ok');
$('#um-logout').show();
$('#um-login').hide();
cb && cb(user);
} catch (e) {
console.error('initPage render error:', e);
}
}).fail(function () {
// 会话失效/未登录:立即回登录页,不允许停留在当前页面继续操作
try {
var a = arguments[0];
var desc;
if (a && a.responseText) desc = 'HTTP/' + a.status + ' respText=' + a.responseText.substring(0, 300);
else if (a instanceof Error) desc = 'Error: ' + a.message;
else if (a && a.readyState !== undefined) desc = 'jqXHR status=' + a.status + ' statusText=' + a.statusText;
else desc = 'arg0=' + String(a);
console.error('initPage session check failed:', desc);
} catch (e) {
console.error('initPage fail (unserializable)', String(e));
}
location.href = 'index.html';
});
}
/* ============ 侧边栏渲染 ============ */
/**
* 渲染左侧导航菜单(分级,扁平图标;二级菜单不带图标)
* - 分组:拥有任意子项权限即显示;子项逐个按权限过滤
* - 高亮:子项按“当前页面文件名”匹配(避免营销服务共用权限时全部选中);一级项按 key/url
*/
function renderMenu(activeKey, permissions) {
var currentPage = location.pathname.split('/').pop();
var html = '';
MENU_GROUPS.forEach(function (g) {
if (g.children) {
// 过滤出当前用户有权限的子项
var visibleChildren = g.children.filter(function (c) {
return permissions.indexOf(c.key) >= 0;
});
if (!visibleChildren.length) return;
var isOpen = visibleChildren.some(function (c) {
return c.url === currentPage;
});
html += '<div class="menu-group' + (isOpen ? ' open' : '') + '">' +
'<div class="menu-group-title" onclick="toggleMenuGroup(this)">' +
'<span class="icon">' + (ICONS[g.icon] || '') + '</span>' +
'<span class="label">' + g.name + '</span>' +
'<span class="arrow">▸</span></div>' +
'<div class="menu-group-body"' + (isOpen ? ' style="display:block;"' : '') + '>';
visibleChildren.forEach(function (c) {
var isActive = (c.url === currentPage);
html += '<a class="menu-item sub' + (isActive ? ' active' : '') + '" href="' + c.url + '">' +
'<span class="label">' + c.name + '</span></a>';
});
html += '</div></div>';
} else {
if (permissions.indexOf(g.key) < 0) return;
var isActive = (g.key === activeKey || g.url === currentPage);
html += '<a class="menu-item' + (isActive ? ' active' : '') + '" href="' + g.url + '">' +
'<span class="icon">' + (ICONS[g.icon] || '') + '</span>' +
'<span class="label">' + g.name + '</span></a>';
}
});
$('#sidebar-menu').html(html);
}
/** 展开/收起分组菜单(折叠态点击分组时先展开侧边栏) */
window.toggleMenuGroup = function (el) {
var $layout = $('.layout');
if ($layout.hasClass('sidebar-collapsed')) {
$layout.removeClass('sidebar-collapsed');
if (!window.matchMedia('(max-width: 768px)').matches) {
localStorage.setItem('sl_sidebar_collapsed', '0');
}
}
var $group = $(el).closest('.menu-group');
var $body = $group.find('.menu-group-body');
var isOpen = $group.hasClass('open');
if (isOpen) {
$group.removeClass('open');
$body.slideUp(150);
} else {
$group.addClass('open');
$body.slideDown(150);
}
};
/** 手动折叠/展开侧边栏 */
window.toggleSidebar = function () {
var $layout = $('.layout');
$layout.toggleClass('sidebar-collapsed');
var collapsed = $layout.hasClass('sidebar-collapsed');
if (!window.matchMedia('(max-width: 768px)').matches) {
localStorage.setItem('sl_sidebar_collapsed', collapsed ? '1' : '0');
}
};
/** 初始化侧边栏状态:桌面读用户偏好;移动端(<768px)自动折叠 */
function initSidebar() {
var mq = window.matchMedia('(max-width: 768px)');
function apply() {
var $layout = $('.layout');
if (mq.matches) {
$layout.addClass('sidebar-collapsed');
} else if (localStorage.getItem('sl_sidebar_collapsed') === '1') {
$layout.addClass('sidebar-collapsed');
} else {
$layout.removeClass('sidebar-collapsed');
}
}
apply();
if (mq.addEventListener) mq.addEventListener('change', apply);
else if (mq.addListener) mq.addListener(apply);
}
/* ============ 页面通用骨架(侧边栏+顶栏+右上角用户菜单+页脚) ============ */
function renderShell(pageTitle, breadcrumb) {
var html =
'<div class="layout">' +
' <aside class="sidebar">' +
' <a class="logo" href="dashboard.html" title="返回综合概览">Super<span>Link</span><span class="version">' + (window.APP_VERSION || 'v1.0.00') + '</span></a>' +
' <nav class="menu" id="sidebar-menu"></nav>' +
' </aside>' +
' <div class="main">' +
' <div class="topbar">' +
' <span class="sidebar-toggle" id="sidebar-toggle" onclick="toggleSidebar()" title="折叠/展开菜单">' + (ICONS.menu || '') + '</span>' +
' <div class="page-title">' + pageTitle + '</div>' +
(breadcrumb ? '<div class="breadcrumb">' + breadcrumb + '</div>' : '') +
' <div class="topbar-spacer"></div>' +
' <div class="user-menu" id="user-menu">' +
' <span class="user-menu-trigger">' +
' <span class="avatar" id="um-avatar">U</span>' +
' <span id="um-name">加载中...</span>' +
' <span class="caret">▾</span>' +
' </span>' +
' <div class="user-menu-dropdown">' +
' <div class="um-head">' +
' <div class="um-name" id="um-detail-name"></div>' +
' <div class="um-sub" id="um-detail-account"></div>' +
' </div>' +
' <div class="um-item"><span class="um-label">角色</span><b id="um-role">-</b></div>' +
' <div class="um-item"><span class="um-label">登录状态</span><b id="um-status">-</b></div>' +
' <div class="um-divider"></div>' +
' <a class="um-action" id="um-login" style="display:none;">登 录</a>' +
' <a class="um-action um-danger" id="um-logout" style="display:none;">退出登录</a>' +
' </div>' +
' </div>' +
' </div>' +
' <div class="content" id="page-content"></div>' +
' <footer class="footer">' + (window.FOOTER_TEXT || '© SuperLink 管理系统') + '</footer>' +
' </div>' +
'</div>';
document.body.innerHTML = html;
initSidebar();
// 右上角用户菜单:悬停显示(延迟关闭,保证能移入下拉点击)+ 点击切换(触屏)
var menuTimer = null;
$('#user-menu').on('mouseenter', function () {
clearTimeout(menuTimer);
$(this).addClass('open');
});
$('#user-menu').on('mouseleave', function () {
var self = this;
clearTimeout(menuTimer);
menuTimer = setTimeout(function () { $(self).removeClass('open'); }, 250);
});
$('#user-menu .user-menu-trigger').on('click', function (e) {
e.stopPropagation();
$('#user-menu').toggleClass('open');
});
$(document).on('click', function () {
$('#user-menu').removeClass('open');
});
$('#um-logout').on('click', function () {
Dialog.confirm('确定要退出登录吗?', function () {
httpPost('auth/logout.php').then(function () {
location.href = 'index.html';
});
});
});
$('#um-login').on('click', function () {
location.href = 'index.html';
});
}
/* ============ 分页组件 ============ */
/**
* 渲染分页条
* @param {object} pager { total, page, limit }
* @param {function} onPage 翻页回调(page)
*/
function renderPagination($el, pager, onPage) {
var total = pager.total || 0;
var page = pager.page || 1;
var limit = pager.limit || PAGE_SIZE;
var pages = Math.max(1, Math.ceil(total / limit));
var html = '<span>共 ' + total + ' 条 / ' + pages + ' 页</span>';
html += '<span class="pg' + (page <= 1 ? ' disabled' : '') + '" data-p="' + (page - 1) + '">上一页</span>';
var start = Math.max(1, page - 2);
var end = Math.min(pages, start + 4);
start = Math.max(1, end - 4);
for (var i = start; i <= end; i++) {
html += '<span class="pg' + (i === page ? ' current' : '') + '" data-p="' + i + '">' + i + '</span>';
}
html += '<span class="pg' + (page >= pages ? ' disabled' : '') + '" data-p="' + (page + 1) + '">下一页</span>';
$el.html(html);
$el.find('span.pg').each(function () {
var $this = $(this);
if ($this.hasClass('disabled')) return;
$this.on('click', function () {
onPage && onPage(parseInt($this.data('p'), 10));
});
});
}
/* ============ 文件下载(导出) ============ */
function download(url, params) {
var full = BASE_URL + url;
if (params) {
full += (full.indexOf('?') >= 0 ? '&' : '?') + $.param(params);
}
location.href = full;
}
/* ============ 通用表格渲染辅助 ============ */
function buildFilterParam() {
var obj = {};
$('.toolbar [data-filter]').each(function () {
var $this = $(this);
var key = $this.data('filter');
var val = $this.val();
if (val !== '' && val !== null && val !== undefined) {
obj[key] = val;
}
});
return obj;
}
/* ============ 文件上传输入 ============ */
function triggerFileInput(accept, cb) {
var input = document.createElement('input');
input.type = 'file';
input.accept = accept || '.csv';
input.style.display = 'none';
input.onchange = function () {
if (input.files.length > 0) {
cb(input.files[0]);
}
document.body.removeChild(input);
};
document.body.appendChild(input);
input.click();
}