core.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. /**
  2. * 核心功能模块
  3. * 提供全局共享的工具函数和状态管理
  4. */
  5. // 全局变量和状态
  6. let isLoggedIn = false;
  7. let userPermissions = [];
  8. let systemConfig = {};
  9. /**
  10. * 初始化应用
  11. * 检查登录状态,加载基础配置
  12. */
  13. async function initApp() {
  14. // console.log('初始化应用...');
  15. // console.log('-------------调试信息开始-------------');
  16. // console.log('当前URL:', window.location.href);
  17. // console.log('浏览器信息:', navigator.userAgent);
  18. // console.log('DOM已加载状态:', document.readyState);
  19. // 检查当前页面是否为登录页
  20. const isLoginPage = window.location.pathname.includes('admin');
  21. // console.log('是否为管理页面:', isLoginPage);
  22. try {
  23. // 检查会话状态
  24. const sessionResult = await checkSession();
  25. const isAuthenticated = sessionResult.authenticated;
  26. // console.log('会话检查结果:', isAuthenticated);
  27. // 检查localStorage中的登录状态 (主要用于刷新页面时保持UI)
  28. const localLoginState = localStorage.getItem('isLoggedIn') === 'true';
  29. // 核心登录状态判断
  30. if (isAuthenticated) {
  31. // 已登录
  32. isLoggedIn = true;
  33. localStorage.setItem('isLoggedIn', 'true'); // 保持本地状态
  34. if (isLoginPage) {
  35. // 在登录页,但会话有效,显示管理界面
  36. // console.log('已登录,显示管理界面...');
  37. await loadSystemConfig();
  38. showAdminInterface();
  39. } else {
  40. // 在非登录页,正常显示
  41. // console.log('已登录,继续应用初始化...');
  42. await loadSystemConfig();
  43. showAdminInterface(); // 确保管理界面可见
  44. }
  45. } else {
  46. // 未登录
  47. isLoggedIn = false;
  48. localStorage.removeItem('isLoggedIn'); // 清除本地登录状态
  49. if (!isLoginPage) {
  50. // 在非登录页,重定向到登录页
  51. // console.log('未登录,重定向到登录页...');
  52. window.location.href = '/admin';
  53. return false;
  54. } else {
  55. // 在登录页,显示登录框
  56. // console.log('未登录,显示登录模态框...');
  57. hideLoadingIndicator();
  58. showLoginModal();
  59. }
  60. }
  61. // console.log('应用初始化完成');
  62. // console.log('-------------调试信息结束-------------');
  63. return isAuthenticated;
  64. } catch (error) {
  65. // console.error('初始化应用失败:', error);
  66. // console.log('-------------调试错误信息-------------');
  67. // console.log('错误堆栈:', error.stack);
  68. // console.log('错误类型:', error.name);
  69. // console.log('错误消息:', error.message);
  70. // console.log('---------------------------------------');
  71. showAlert('加载应用失败:' + error.message, 'error');
  72. hideLoadingIndicator();
  73. showLoginModal();
  74. return false;
  75. }
  76. }
  77. /**
  78. * 检查会话状态
  79. */
  80. async function checkSession() {
  81. try {
  82. const response = await fetch('/api/check-session', {
  83. headers: {
  84. 'Cache-Control': 'no-cache',
  85. 'X-Requested-With': 'XMLHttpRequest',
  86. 'Pragma': 'no-cache'
  87. },
  88. credentials: 'same-origin'
  89. });
  90. // 只关心请求是否成功以及认证状态
  91. if (response.ok) {
  92. const data = await response.json();
  93. return {
  94. authenticated: data.authenticated // 直接使用API返回的状态
  95. };
  96. }
  97. // 非OK响应(包括401)都视为未认证
  98. return {
  99. authenticated: false
  100. };
  101. } catch (error) {
  102. // console.error('检查会话状态出错:', error);
  103. return {
  104. authenticated: false,
  105. error: error.message
  106. };
  107. }
  108. }
  109. /**
  110. * 加载系统配置
  111. */
  112. function loadSystemConfig() {
  113. fetch('/api/config')
  114. .then(response => {
  115. if (!response.ok) {
  116. return response.text().then(text => {
  117. throw new Error(`加载配置失败: ${text || response.statusText || response.status}`);
  118. });
  119. }
  120. return response.json();
  121. })
  122. .then(config => {
  123. // console.log('加载配置成功:', config);
  124. // 应用配置
  125. applySystemConfig(config);
  126. })
  127. .catch(error => {
  128. // console.error('加载配置失败:', error);
  129. showAlert('加载配置失败: ' + error.message, 'warning');
  130. });
  131. }
  132. // 应用系统配置
  133. function applySystemConfig(config) {
  134. // 如果有proxyDomain配置,则更新输入框
  135. if (config.proxyDomain && document.getElementById('proxyDomain')) {
  136. document.getElementById('proxyDomain').value = config.proxyDomain;
  137. }
  138. // 应用其他配置...
  139. }
  140. /**
  141. * 显示管理界面
  142. */
  143. function showAdminInterface() {
  144. // console.log('开始显示管理界面...');
  145. hideLoadingIndicator();
  146. const adminContainer = document.getElementById('adminContainer');
  147. if (adminContainer) {
  148. // console.log('找到管理界面容器,设置为显示');
  149. adminContainer.style.display = 'flex';
  150. } else {
  151. // console.error('未找到管理界面容器元素 #adminContainer');
  152. }
  153. // console.log('管理界面已显示,正在初始化事件监听器');
  154. // 初始化菜单事件监听
  155. initEventListeners();
  156. }
  157. /**
  158. * 隐藏加载提示器
  159. */
  160. function hideLoadingIndicator() {
  161. // console.log('正在隐藏加载提示器...');
  162. const loadingIndicator = document.getElementById('loadingIndicator');
  163. if (loadingIndicator) {
  164. loadingIndicator.style.display = 'none';
  165. // console.log('加载提示器已隐藏');
  166. } else {
  167. // console.warn('未找到加载提示器元素 #loadingIndicator');
  168. }
  169. }
  170. /**
  171. * 显示登录模态框
  172. */
  173. function showLoginModal() {
  174. const loginModal = document.getElementById('loginModal');
  175. if (loginModal) {
  176. loginModal.style.display = 'flex';
  177. // 刷新验证码
  178. if (window.auth && typeof window.auth.refreshCaptcha === 'function') {
  179. window.auth.refreshCaptcha();
  180. }
  181. }
  182. }
  183. /**
  184. * 显示加载动画
  185. */
  186. function showLoading() {
  187. const loadingSpinner = document.getElementById('loadingSpinner');
  188. if (loadingSpinner) {
  189. loadingSpinner.style.display = 'block';
  190. }
  191. }
  192. /**
  193. * 隐藏加载动画
  194. */
  195. function hideLoading() {
  196. const loadingSpinner = document.getElementById('loadingSpinner');
  197. if (loadingSpinner) {
  198. loadingSpinner.style.display = 'none';
  199. }
  200. }
  201. /**
  202. * 显示警告消息
  203. * @param {string} message - 消息内容
  204. * @param {string} type - 消息类型 (info, success, error)
  205. * @param {string} title - 标题(可选)
  206. */
  207. function showAlert(message, type = 'info', title = '') {
  208. // 使用SweetAlert2替代自定义警告框,确保弹窗总是显示
  209. Swal.fire({
  210. title: title || (type === 'success' ? '成功' : (type === 'error' ? '错误' : '提示')),
  211. text: message,
  212. icon: type,
  213. timer: type === 'success' ? 2000 : undefined,
  214. timerProgressBar: type === 'success',
  215. confirmButtonColor: '#3d7cf4',
  216. confirmButtonText: '确定'
  217. });
  218. }
  219. /**
  220. * 显示确认对话框
  221. * @param {string} message - 消息内容
  222. * @param {Function} onConfirm - 确认回调
  223. * @param {Function} onCancel - 取消回调(可选)
  224. * @param {string} title - 标题(可选)
  225. */
  226. function showConfirm(message, onConfirm, onCancel, title = '确认') {
  227. Swal.fire({
  228. title: title,
  229. text: message,
  230. icon: 'question',
  231. showCancelButton: true,
  232. confirmButtonColor: '#3d7cf4',
  233. cancelButtonColor: '#6c757d',
  234. confirmButtonText: '确认',
  235. cancelButtonText: '取消'
  236. }).then((result) => {
  237. if (result.isConfirmed && typeof onConfirm === 'function') {
  238. onConfirm();
  239. } else if (typeof onCancel === 'function') {
  240. onCancel();
  241. }
  242. });
  243. }
  244. /**
  245. * 格式化日期时间
  246. */
  247. function formatDateTime(dateString) {
  248. if (!dateString) return '';
  249. const date = new Date(dateString);
  250. return date.toLocaleString('zh-CN', {
  251. year: 'numeric',
  252. month: '2-digit',
  253. day: '2-digit',
  254. hour: '2-digit',
  255. minute: '2-digit',
  256. second: '2-digit'
  257. });
  258. }
  259. /**
  260. * 防抖函数:限制函数在一定时间内只能执行一次
  261. */
  262. function debounce(func, wait = 300) {
  263. let timeout;
  264. return function(...args) {
  265. const later = () => {
  266. clearTimeout(timeout);
  267. func.apply(this, args);
  268. };
  269. clearTimeout(timeout);
  270. timeout = setTimeout(later, wait);
  271. };
  272. }
  273. /**
  274. * 节流函数:保证一定时间内多次调用只执行一次
  275. */
  276. function throttle(func, wait = 300) {
  277. let timeout = null;
  278. let previous = 0;
  279. return function(...args) {
  280. const now = Date.now();
  281. const remaining = wait - (now - previous);
  282. if (remaining <= 0) {
  283. if (timeout) {
  284. clearTimeout(timeout);
  285. timeout = null;
  286. }
  287. previous = now;
  288. func.apply(this, args);
  289. } else if (!timeout) {
  290. timeout = setTimeout(() => {
  291. previous = Date.now();
  292. timeout = null;
  293. func.apply(this, args);
  294. }, remaining);
  295. }
  296. };
  297. }
  298. /**
  299. * 初始化事件监听
  300. */
  301. function initEventListeners() {
  302. // console.log('开始初始化事件监听器...');
  303. // 侧边栏菜单项事件
  304. const menuItems = document.querySelectorAll('.sidebar-nav li');
  305. // console.log('找到侧边栏菜单项数量:', menuItems.length);
  306. menuItems.forEach((item, index) => {
  307. const section = item.getAttribute('data-section');
  308. // console.log(`绑定事件到菜单项 #${index + 1}: ${section}`);
  309. item.addEventListener('click', () => showSection(section));
  310. });
  311. // console.log('侧边栏菜单事件监听器已绑定');
  312. // 用户中心按钮
  313. const userCenterBtn = document.getElementById('userCenterBtn');
  314. if (userCenterBtn) {
  315. // console.log('找到用户中心按钮,绑定事件');
  316. userCenterBtn.addEventListener('click', () => showSection('user-center'));
  317. }
  318. // 登出按钮 (侧边栏)
  319. const logoutBtn = document.getElementById('logoutBtn');
  320. if (logoutBtn) {
  321. // console.log('找到登出按钮,绑定事件');
  322. logoutBtn.addEventListener('click', () => {
  323. if (window.auth && typeof window.auth.logout === 'function') {
  324. window.auth.logout();
  325. }
  326. });
  327. }
  328. // 登出按钮 (用户中心)
  329. const ucLogoutBtn = document.getElementById('ucLogoutBtn');
  330. if (ucLogoutBtn) {
  331. // console.log('找到用户中心内登出按钮,绑定事件');
  332. ucLogoutBtn.addEventListener('click', () => {
  333. if (window.auth && typeof window.auth.logout === 'function') {
  334. window.auth.logout();
  335. }
  336. });
  337. }
  338. // console.log('事件监听器初始化完成');
  339. }
  340. /**
  341. * 显示指定的内容区域
  342. * @param {string} sectionId 要显示的内容区域ID
  343. */
  344. function showSection(sectionId) {
  345. // console.log('尝试显示内容区域:', sectionId);
  346. const contentSections = document.querySelectorAll('.content-section');
  347. const menuItems = document.querySelectorAll('.sidebar-nav li');
  348. // console.log('找到', contentSections.length, '个内容区域和', menuItems.length, '个菜单项');
  349. let sectionFound = false;
  350. contentSections.forEach(section => {
  351. if (section.id === sectionId) {
  352. section.classList.add('active');
  353. sectionFound = true;
  354. // console.log('成功激活内容区域:', sectionId);
  355. } else {
  356. section.classList.remove('active');
  357. }
  358. });
  359. if (!sectionFound) {
  360. // console.warn('未找到要显示的内容区域:', sectionId, '. 默认显示dashboard.');
  361. document.getElementById('dashboard').classList.add('active');
  362. sectionId = 'dashboard'; // 更新 sectionId 以便正确高亮菜单
  363. }
  364. let menuItemFound = false;
  365. menuItems.forEach(item => {
  366. if (item.getAttribute('data-section') === sectionId) {
  367. item.classList.add('active');
  368. menuItemFound = true;
  369. // console.log('成功激活菜单项:', sectionId);
  370. } else {
  371. item.classList.remove('active');
  372. }
  373. });
  374. if (!menuItemFound) {
  375. // console.warn('未找到要激活的菜单项 for section:', sectionId);
  376. // 尝试激活 dashboard 作为回退
  377. const dashboardItem = document.querySelector('.sidebar-nav li[data-section="dashboard"]');
  378. if(dashboardItem) dashboardItem.classList.add('active');
  379. }
  380. // 更新 URL hash
  381. window.location.hash = sectionId;
  382. // 刷新特定部分的内容,例如仪表盘
  383. if (sectionId === 'dashboard') {
  384. // console.log('已激活仪表盘,无需再次刷新系统状态');
  385. // if (window.systemStatus && typeof window.systemStatus.refreshSystemStatus === 'function') {
  386. // window.systemStatus.refreshSystemStatus();
  387. // }
  388. }
  389. // console.log('内容区域切换完成:', sectionId);
  390. }
  391. /**
  392. * 根据URL hash显示对应的区域
  393. */
  394. function showSectionFromHash() {
  395. const hash = window.location.hash.substring(1); // 移除 '#' 号
  396. if (hash) {
  397. // console.log('从URL Hash加载区域:', hash);
  398. showSection(hash);
  399. } else {
  400. // console.log('URL Hash为空,默认显示dashboard区域');
  401. showSection('dashboard'); // 默认显示仪表盘
  402. }
  403. }
  404. /**
  405. * 切换加载状态
  406. * @param {boolean} isLoading - 是否正在加载
  407. * @param {string} elementId - 目标元素ID
  408. * @param {string} originalText - 原始文本(可选)
  409. */
  410. function toggleLoadingState(isLoading, elementId, originalText = null) {
  411. const element = document.getElementById(elementId);
  412. if (!element) {
  413. // console.warn(`未找到元素 ${elementId} 来切换加载状态`);
  414. return;
  415. }
  416. if (isLoading) {
  417. element.disabled = true;
  418. // 可以考虑保存原始文本,如果按钮文本被修改了
  419. if (originalText && !element.dataset.originalText) {
  420. element.dataset.originalText = element.innerHTML;
  421. }
  422. element.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 加载中...';
  423. } else {
  424. element.disabled = false;
  425. if (element.dataset.originalText) {
  426. element.innerHTML = element.dataset.originalText;
  427. } else if (originalText) { // 如果没有保存的原始文本,但传入了,也使用
  428. element.innerHTML = originalText;
  429. }
  430. // 如果按钮文本没有被修改为 "加载中...",则不需要恢复
  431. }
  432. }
  433. // 页面加载时初始化
  434. document.addEventListener('DOMContentLoaded', function() {
  435. // console.log('DOM已加载,正在初始化应用...');
  436. initApp();
  437. // 检查URL参数,处理消息提示等
  438. const urlParams = new URLSearchParams(window.location.search);
  439. // 如果有message参数,显示相应的提示
  440. if (urlParams.has('message')) {
  441. const message = urlParams.get('message');
  442. let type = 'info';
  443. if (urlParams.has('type')) {
  444. type = urlParams.get('type');
  445. }
  446. showAlert(message, type);
  447. }
  448. });
  449. // 导出核心函数和变量 (如果需要在其他模块直接使用)
  450. window.core = {
  451. initApp,
  452. checkSession,
  453. loadSystemConfig,
  454. showAdminInterface,
  455. hideLoadingIndicator,
  456. showLoginModal,
  457. showAlert,
  458. showConfirm,
  459. showLoading,
  460. hideLoading,
  461. showSection,
  462. showSectionFromHash,
  463. formatDateTime,
  464. debounce,
  465. throttle,
  466. toggleLoadingState,
  467. initEventListeners
  468. };