Files
sunct 3c3bf53ae4 docs(website): 添加简记memo产品原型和UI设计规范文档
- 新增「简记memo」一体化小程序产品原型设计文档
- 新增简记memo完整版UI视觉设计规范和界面细节
- 添加IDEA项目配置文件.gitignore
- 创建404页面HTML文件,包含响应式布局和错误提示
- 添加关于页面HTML文件,展示品牌介绍和团队信息
- 实现AES加解密工具函数,支持请求体加密
- 添加用户协议页面基础框架
2026-07-31 14:12:32 +08:00

173 lines
6.0 KiB
JavaScript

// static/js/auth.js
// 统一认证管理:Token过期检查、错误处理、自动登出
(function() {
'use strict';
// ==================== 防重复标记 ====================
var _isHandlingExpired = false; // 正在处理过期(防重复)
var _hasNotified = false; // 本次会话是否已提示过
var Auth = {
DEFAULT_EXPIRE_MS: 7200 * 1000, // 2小时
isTokenExpired: function() {
var token = localStorage.getItem('user_token');
if (!token) return true;
var expiresAt = localStorage.getItem('user_token_expires');
if (!expiresAt) {
var tokenAge = Date.now() - (parseInt(localStorage.getItem('user_token_created')) || Date.now());
return tokenAge > this.DEFAULT_EXPIRE_MS;
}
return Date.now() > parseInt(expiresAt);
},
isLoggedIn: function() {
return !this.isTokenExpired() && !!localStorage.getItem('user_token');
},
/**
* 清除登录状态(仅清数据,不跳转)
*/
logout: function() {
// 清除所有用户相关的 localStorage key
localStorage.removeItem('user_token');
localStorage.removeItem('user_token_expires');
localStorage.removeItem('user_token_created');
localStorage.removeItem('user_nickname');
localStorage.removeItem('user_avatar');
// user_info 由 UserStore.clear() 处理
if (window.UserStore) {
UserStore.clear();
}
// 立即更新头部 DOM,把用户菜单替换为登录按钮
this._updateHeaderUI();
},
/**
* 更新头部用户菜单 UI
*/
_updateHeaderUI: function() {
// PC端
var pcContainer = document.getElementById('userMenuContainer');
if (pcContainer && window.renderUserMenu) {
pcContainer.innerHTML = renderUserMenu(false, null);
}
// 移动端
var mobileContainer = document.getElementById('mobileUserMenu');
if (mobileContainer && window.renderMobileUserMenu) {
mobileContainer.innerHTML = renderMobileUserMenu(false, null);
}
},
/**
* 处理登录过期(防重复调用)
*/
handleTokenExpired: function(reason) {
// 防重复:如果已经在处理中,直接返回
if (_isHandlingExpired) return;
_isHandlingExpired = true;
console.warn('Token已过期:', reason);
// 1. 先清除数据
this.logout();
// 2. 只提示一次
if (!_hasNotified) {
_hasNotified = true;
if (window.$toast) {
$toast.warning('登录已过期,请重新登录');
}
}
// 3. 立即跳转(不再用 setTimeout,避免期间其他请求重复触发)
// 如果当前已经在登录页,不跳转
if (window.location.pathname !== '/login') {
window.location.href = '/login';
}
},
/**
* 初始化:页面加载时检查 token
*/
init: function() {
if (this.isTokenExpired()) {
var token = localStorage.getItem('user_token');
if (token) {
// 有 token 但过期了 → 处理
this.handleTokenExpired('本地检测到token已过期');
} else {
// 没有 token → 仅更新 UI(不提示、不跳转)
this._updateHeaderUI();
}
} else {
// token 有效,记录创建时间
if (!localStorage.getItem('user_token_created')) {
localStorage.setItem('user_token_created', String(Date.now()));
}
}
},
getAuthHeaders: function() {
return { 'token': localStorage.getItem('user_token') };
}
};
window.Auth = Auth;
// ==================== 全局 Fetch 拦截 ====================
var originalFetch = window.fetch;
window.fetch = function(url, options) {
options = options || {};
options.headers = options.headers || {};
var isInternalAPI = typeof url === 'string' && url.indexOf('/api/') !== -1 && url.indexOf('://') === -1;
// 自动注入 token
if (isInternalAPI) {
var token = localStorage.getItem('user_token');
if (token && !options.headers['token']) {
options.headers['token'] = token;
}
}
return originalFetch(url, options)
.then(function(response) {
// 非 API 或正在处理过期 → 直接返回
if (!isInternalAPI || _isHandlingExpired) {
return response;
}
// 克隆响应,尝试解析 JSON 检查认证错误
var clone = response.clone();
clone.json().then(function(data) {
if (data.code === 1000 || data.code === 401) {
Auth.handleTokenExpired(data.msg || '登录已过期');
}
}).catch(function() {
// JSON 解析失败(如非 JSON 响应),忽略
});
return response;
});
};
// ==================== 初始化 ====================
// 确保 DOM 加载完成后再初始化(避免 _updateHeaderUI 找不到元素)
function doInit() {
Auth.init();
}
if (document.readyState === 'complete' || document.readyState === 'interactive') {
// DOM 已就绪,但需要等下一帧确保其他脚本也执行完
setTimeout(doInit, 0);
} else {
document.addEventListener('DOMContentLoaded', doInit);
}
})();