core.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  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. // 更新logo配置输入框(管理页面不显示logo图片,只显示配置)
  139. if (document.getElementById('logoUrl')) {
  140. document.getElementById('logoUrl').value = config.logo || '';
  141. }
  142. // 应用其他配置...
  143. }
  144. /**
  145. * 显示管理界面
  146. */
  147. function showAdminInterface() {
  148. // console.log('开始显示管理界面...');
  149. hideLoadingIndicator();
  150. const adminContainer = document.getElementById('adminContainer');
  151. if (adminContainer) {
  152. // console.log('找到管理界面容器,设置为显示');
  153. adminContainer.style.display = 'flex';
  154. } else {
  155. // console.error('未找到管理界面容器元素 #adminContainer');
  156. }
  157. // console.log('管理界面已显示,正在初始化事件监听器');
  158. // 初始化菜单事件监听
  159. initEventListeners();
  160. }
  161. /**
  162. * 隐藏加载提示器
  163. */
  164. function hideLoadingIndicator() {
  165. // console.log('正在隐藏加载提示器...');
  166. const loadingIndicator = document.getElementById('loadingIndicator');
  167. if (loadingIndicator) {
  168. loadingIndicator.style.display = 'none';
  169. // console.log('加载提示器已隐藏');
  170. } else {
  171. // console.warn('未找到加载提示器元素 #loadingIndicator');
  172. }
  173. }
  174. /**
  175. * 显示登录模态框
  176. */
  177. function showLoginModal() {
  178. const loginModal = document.getElementById('loginModal');
  179. if (loginModal) {
  180. loginModal.style.display = 'flex';
  181. // 刷新验证码
  182. if (window.auth && typeof window.auth.refreshCaptcha === 'function') {
  183. window.auth.refreshCaptcha();
  184. }
  185. }
  186. }
  187. /**
  188. * 显示加载动画
  189. */
  190. function showLoading() {
  191. const loadingSpinner = document.getElementById('loadingSpinner');
  192. if (loadingSpinner) {
  193. loadingSpinner.style.display = 'block';
  194. }
  195. }
  196. /**
  197. * 隐藏加载动画
  198. */
  199. function hideLoading() {
  200. const loadingSpinner = document.getElementById('loadingSpinner');
  201. if (loadingSpinner) {
  202. loadingSpinner.style.display = 'none';
  203. }
  204. }
  205. /**
  206. * 显示警告消息
  207. * @param {string} message - 消息内容
  208. * @param {string} type - 消息类型 (info, success, error)
  209. * @param {string} title - 标题(可选)
  210. */
  211. function showAlert(message, type = 'info', title = '') {
  212. // 使用SweetAlert2替代自定义警告框,确保弹窗总是显示
  213. Swal.fire({
  214. title: title || (type === 'success' ? '成功' : (type === 'error' ? '错误' : '提示')),
  215. text: message,
  216. icon: type,
  217. timer: type === 'success' ? 2000 : undefined,
  218. timerProgressBar: type === 'success',
  219. confirmButtonColor: '#3d7cf4',
  220. confirmButtonText: '确定'
  221. });
  222. }
  223. /**
  224. * 显示确认对话框
  225. * @param {string} message - 消息内容
  226. * @param {Function} onConfirm - 确认回调
  227. * @param {Function} onCancel - 取消回调(可选)
  228. * @param {string} title - 标题(可选)
  229. */
  230. function showConfirm(message, onConfirm, onCancel, title = '确认') {
  231. Swal.fire({
  232. title: title,
  233. text: message,
  234. icon: 'question',
  235. showCancelButton: true,
  236. confirmButtonColor: '#3d7cf4',
  237. cancelButtonColor: '#6c757d',
  238. confirmButtonText: '确认',
  239. cancelButtonText: '取消'
  240. }).then((result) => {
  241. if (result.isConfirmed && typeof onConfirm === 'function') {
  242. onConfirm();
  243. } else if (typeof onCancel === 'function') {
  244. onCancel();
  245. }
  246. });
  247. }
  248. /**
  249. * 格式化日期时间
  250. */
  251. function formatDateTime(dateString) {
  252. if (!dateString) return '';
  253. const date = new Date(dateString);
  254. return date.toLocaleString('zh-CN', {
  255. year: 'numeric',
  256. month: '2-digit',
  257. day: '2-digit',
  258. hour: '2-digit',
  259. minute: '2-digit',
  260. second: '2-digit'
  261. });
  262. }
  263. /**
  264. * 防抖函数:限制函数在一定时间内只能执行一次
  265. */
  266. function debounce(func, wait = 300) {
  267. let timeout;
  268. return function(...args) {
  269. const later = () => {
  270. clearTimeout(timeout);
  271. func.apply(this, args);
  272. };
  273. clearTimeout(timeout);
  274. timeout = setTimeout(later, wait);
  275. };
  276. }
  277. /**
  278. * 节流函数:保证一定时间内多次调用只执行一次
  279. */
  280. function throttle(func, wait = 300) {
  281. let timeout = null;
  282. let previous = 0;
  283. return function(...args) {
  284. const now = Date.now();
  285. const remaining = wait - (now - previous);
  286. if (remaining <= 0) {
  287. if (timeout) {
  288. clearTimeout(timeout);
  289. timeout = null;
  290. }
  291. previous = now;
  292. func.apply(this, args);
  293. } else if (!timeout) {
  294. timeout = setTimeout(() => {
  295. previous = Date.now();
  296. timeout = null;
  297. func.apply(this, args);
  298. }, remaining);
  299. }
  300. };
  301. }
  302. /**
  303. * 初始化事件监听
  304. */
  305. function initEventListeners() {
  306. // console.log('开始初始化事件监听器...');
  307. // 侧边栏菜单项事件
  308. const menuItems = document.querySelectorAll('.sidebar-nav li');
  309. // console.log('找到侧边栏菜单项数量:', menuItems.length);
  310. menuItems.forEach((item, index) => {
  311. const section = item.getAttribute('data-section');
  312. // console.log(`绑定事件到菜单项 #${index + 1}: ${section}`);
  313. item.addEventListener('click', () => showSection(section));
  314. });
  315. // console.log('侧边栏菜单事件监听器已绑定');
  316. // 用户中心按钮
  317. const userCenterBtn = document.getElementById('userCenterBtn');
  318. if (userCenterBtn) {
  319. // console.log('找到用户中心按钮,绑定事件');
  320. userCenterBtn.addEventListener('click', () => showSection('user-center'));
  321. }
  322. // 登出按钮 (侧边栏)
  323. const logoutBtn = document.getElementById('logoutBtn');
  324. if (logoutBtn) {
  325. // console.log('找到登出按钮,绑定事件');
  326. logoutBtn.addEventListener('click', () => {
  327. if (window.auth && typeof window.auth.logout === 'function') {
  328. window.auth.logout();
  329. }
  330. });
  331. }
  332. // 登出按钮 (用户中心)
  333. const ucLogoutBtn = document.getElementById('ucLogoutBtn');
  334. if (ucLogoutBtn) {
  335. // console.log('找到用户中心内登出按钮,绑定事件');
  336. ucLogoutBtn.addEventListener('click', () => {
  337. if (window.auth && typeof window.auth.logout === 'function') {
  338. window.auth.logout();
  339. }
  340. });
  341. }
  342. // console.log('事件监听器初始化完成');
  343. }
  344. /**
  345. * 显示指定的内容区域
  346. * @param {string} sectionId 要显示的内容区域ID
  347. */
  348. function showSection(sectionId) {
  349. // console.log('尝试显示内容区域:', sectionId);
  350. const contentSections = document.querySelectorAll('.content-section');
  351. const menuItems = document.querySelectorAll('.sidebar-nav li');
  352. // console.log('找到', contentSections.length, '个内容区域和', menuItems.length, '个菜单项');
  353. let sectionFound = false;
  354. contentSections.forEach(section => {
  355. if (section.id === sectionId) {
  356. section.classList.add('active');
  357. sectionFound = true;
  358. // console.log('成功激活内容区域:', sectionId);
  359. } else {
  360. section.classList.remove('active');
  361. }
  362. });
  363. if (!sectionFound) {
  364. // console.warn('未找到要显示的内容区域:', sectionId, '. 默认显示dashboard.');
  365. document.getElementById('dashboard').classList.add('active');
  366. sectionId = 'dashboard'; // 更新 sectionId 以便正确高亮菜单
  367. }
  368. let menuItemFound = false;
  369. menuItems.forEach(item => {
  370. if (item.getAttribute('data-section') === sectionId) {
  371. item.classList.add('active');
  372. menuItemFound = true;
  373. // console.log('成功激活菜单项:', sectionId);
  374. } else {
  375. item.classList.remove('active');
  376. }
  377. });
  378. if (!menuItemFound) {
  379. // console.warn('未找到要激活的菜单项 for section:', sectionId);
  380. // 尝试激活 dashboard 作为回退
  381. const dashboardItem = document.querySelector('.sidebar-nav li[data-section="dashboard"]');
  382. if(dashboardItem) dashboardItem.classList.add('active');
  383. }
  384. // 更新 URL hash
  385. window.location.hash = sectionId;
  386. // 刷新特定部分的内容,例如仪表盘
  387. if (sectionId === 'dashboard') {
  388. // console.log('已激活仪表盘,无需再次刷新系统状态');
  389. // if (window.systemStatus && typeof window.systemStatus.refreshSystemStatus === 'function') {
  390. // window.systemStatus.refreshSystemStatus();
  391. // }
  392. }
  393. // console.log('内容区域切换完成:', sectionId);
  394. }
  395. /**
  396. * 根据URL hash显示对应的区域
  397. */
  398. function showSectionFromHash() {
  399. const hash = window.location.hash.substring(1); // 移除 '#' 号
  400. if (hash) {
  401. // console.log('从URL Hash加载区域:', hash);
  402. showSection(hash);
  403. } else {
  404. // console.log('URL Hash为空,默认显示dashboard区域');
  405. showSection('dashboard'); // 默认显示仪表盘
  406. }
  407. }
  408. /**
  409. * 切换加载状态
  410. * @param {boolean} isLoading - 是否正在加载
  411. * @param {string} elementId - 目标元素ID
  412. * @param {string} originalText - 原始文本(可选)
  413. */
  414. function toggleLoadingState(isLoading, elementId, originalText = null) {
  415. const element = document.getElementById(elementId);
  416. if (!element) {
  417. // console.warn(`未找到元素 ${elementId} 来切换加载状态`);
  418. return;
  419. }
  420. if (isLoading) {
  421. element.disabled = true;
  422. // 可以考虑保存原始文本,如果按钮文本被修改了
  423. if (originalText && !element.dataset.originalText) {
  424. element.dataset.originalText = element.innerHTML;
  425. }
  426. element.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 加载中...';
  427. } else {
  428. element.disabled = false;
  429. if (element.dataset.originalText) {
  430. element.innerHTML = element.dataset.originalText;
  431. } else if (originalText) { // 如果没有保存的原始文本,但传入了,也使用
  432. element.innerHTML = originalText;
  433. }
  434. // 如果按钮文本没有被修改为 "加载中...",则不需要恢复
  435. }
  436. }
  437. // 页面加载时初始化
  438. document.addEventListener('DOMContentLoaded', function() {
  439. // console.log('DOM已加载,正在初始化应用...');
  440. initApp();
  441. // 检查URL参数,处理消息提示等
  442. const urlParams = new URLSearchParams(window.location.search);
  443. // 如果有message参数,显示相应的提示
  444. if (urlParams.has('message')) {
  445. const message = urlParams.get('message');
  446. let type = 'info';
  447. if (urlParams.has('type')) {
  448. type = urlParams.get('type');
  449. }
  450. showAlert(message, type);
  451. }
  452. });
  453. // 导出核心函数和变量 (如果需要在其他模块直接使用)
  454. window.core = {
  455. initApp,
  456. checkSession,
  457. loadSystemConfig,
  458. showAdminInterface,
  459. hideLoadingIndicator,
  460. showLoginModal,
  461. showAlert,
  462. showConfirm,
  463. showLoading,
  464. hideLoading,
  465. showSection,
  466. showSectionFromHash,
  467. formatDateTime,
  468. debounce,
  469. throttle,
  470. toggleLoadingState,
  471. initEventListeners
  472. };