docs(website): 添加简记memo产品原型和UI设计规范文档
- 新增「简记memo」一体化小程序产品原型设计文档 - 新增简记memo完整版UI视觉设计规范和界面细节 - 添加IDEA项目配置文件.gitignore - 创建404页面HTML文件,包含响应式布局和错误提示 - 添加关于页面HTML文件,展示品牌介绍和团队信息 - 实现AES加解密工具函数,支持请求体加密 - 添加用户协议页面基础框架
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
// 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);
|
||||
}
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user