mirror of
https://github.com/Silentely/eSIM-Tools.git
synced 2026-09-03 06:24:20 +08:00
feat(notification): 调整通知去重策略为仅页面生命周期内去重
移除 localStorage 持久化去重机制,确保每次页面加载(包括刷新)都会显示一次最新通知。 更新相关文档,明确说明通知系统的行为变更及 API 使用方式。 完善测试覆盖,确保功能变更后回归测试全部通过。
This commit is contained in:
495
.claude/index.json
Normal file
495
.claude/index.json
Normal file
@@ -0,0 +1,495 @@
|
||||
{
|
||||
"project": {
|
||||
"name": "eSIM-Tools",
|
||||
"version": "2.0.0",
|
||||
"description": "专为 Giffgaff 和 Simyo 用户设计的 eSIM 管理工具集",
|
||||
"type": "Serverless Web Application",
|
||||
"platform": "Netlify (JAMstack + Functions)",
|
||||
"lastScanned": "2025-12-11T14:19:54.000Z"
|
||||
},
|
||||
"architecture": {
|
||||
"frontend": "原生 JavaScript (ES2021+) + 模块化组件",
|
||||
"backend": "Serverless (Netlify Functions + Edge Functions)",
|
||||
"buildTools": "Webpack 5 + Babel + PostCSS",
|
||||
"deployment": "JAMstack (静态托管 + Serverless API)"
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"name": "Giffgaff 前端模块",
|
||||
"path": "src/giffgaff/",
|
||||
"type": "frontend",
|
||||
"language": "JavaScript",
|
||||
"documentation": "src/giffgaff/CLAUDE.md",
|
||||
"responsibility": "实现 Giffgaff eSIM 管理流程",
|
||||
"features": [
|
||||
"OAuth 2.0 PKCE 登录流程",
|
||||
"MFA 双通道验证(EMAIL/SMS)",
|
||||
"eSIM 预订与激活",
|
||||
"Cookie 自动刷新机制",
|
||||
"GraphQL API 交互"
|
||||
],
|
||||
"keyFiles": [
|
||||
"src/giffgaff/giffgaff_modular.html",
|
||||
"src/giffgaff/js/giffgaff-app.js",
|
||||
"src/giffgaff/js/modules/oauth-handler.js",
|
||||
"src/giffgaff/js/modules/mfa-handler.js",
|
||||
"src/giffgaff/js/modules/esim-service.js",
|
||||
"src/giffgaff/js/modules/state-manager.js",
|
||||
"src/giffgaff/js/modules/ui-controller.js"
|
||||
],
|
||||
"entryPoint": "src/giffgaff/giffgaff_modular.html",
|
||||
"tests": "tests/giffgaff/",
|
||||
"dependencies": [
|
||||
"src/js/modules/logger.js",
|
||||
"src/js/modules/i18n.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Simyo 前端模块",
|
||||
"path": "src/simyo/",
|
||||
"type": "frontend",
|
||||
"language": "JavaScript",
|
||||
"documentation": "src/simyo/CLAUDE.md",
|
||||
"responsibility": "实现 Simyo eSIM 管理流程",
|
||||
"features": [
|
||||
"用户认证(用户名/密码)",
|
||||
"eSIM 预订与激活",
|
||||
"设备变更处理",
|
||||
"API 请求代理(通过 Netlify Redirects)"
|
||||
],
|
||||
"keyFiles": [
|
||||
"src/simyo/simyo_modular.html",
|
||||
"src/simyo/js/simyo-app.js",
|
||||
"src/simyo/js/modules/auth-handler.js",
|
||||
"src/simyo/js/modules/esim-service.js",
|
||||
"src/simyo/js/modules/device-change-handler.js",
|
||||
"src/simyo/js/modules/state-manager.js",
|
||||
"src/simyo/js/modules/ui-controller.js"
|
||||
],
|
||||
"entryPoint": "src/simyo/simyo_modular.html",
|
||||
"proxy": {
|
||||
"from": "/api/simyo/*",
|
||||
"to": "https://appapi.simyo.nl"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "通用工具模块",
|
||||
"path": "src/js/modules/",
|
||||
"type": "frontend-utils",
|
||||
"language": "JavaScript",
|
||||
"documentation": "src/js/modules/CLAUDE.md",
|
||||
"responsibility": "提供可复用的前端工具模块",
|
||||
"modules": [
|
||||
{
|
||||
"name": "Logger",
|
||||
"file": "logger.js",
|
||||
"purpose": "环境感知日志(生产环境自动禁用)"
|
||||
},
|
||||
{
|
||||
"name": "API Service",
|
||||
"file": "api-service.js",
|
||||
"purpose": "统一 HTTP 客户端(重试/缓存/去重)"
|
||||
},
|
||||
{
|
||||
"name": "Secure Storage",
|
||||
"file": "secure-storage.js",
|
||||
"purpose": "加密本地存储"
|
||||
},
|
||||
{
|
||||
"name": "HTML Sanitizer",
|
||||
"file": "html-sanitizer.js",
|
||||
"purpose": "XSS 防护"
|
||||
},
|
||||
{
|
||||
"name": "Performance Monitor",
|
||||
"file": "performance-monitor.js",
|
||||
"purpose": "性能监控"
|
||||
},
|
||||
{
|
||||
"name": "i18n",
|
||||
"file": "i18n.js",
|
||||
"purpose": "国际化支持"
|
||||
},
|
||||
{
|
||||
"name": "Notification Manager",
|
||||
"file": "notification-manager.js",
|
||||
"purpose": "通知管理"
|
||||
},
|
||||
{
|
||||
"name": "Captcha Manager",
|
||||
"file": "captcha-manager.js",
|
||||
"purpose": "验证码集成(Turnstile/reCAPTCHA)"
|
||||
}
|
||||
],
|
||||
"keyFiles": [
|
||||
"src/js/modules/logger.js",
|
||||
"src/js/modules/api-service.js",
|
||||
"src/js/modules/secure-storage.js",
|
||||
"src/js/modules/html-sanitizer.js",
|
||||
"src/js/modules/performance-monitor.js",
|
||||
"src/js/modules/i18n.js",
|
||||
"src/js/modules/notification-manager.js",
|
||||
"src/js/modules/captcha-manager.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Netlify Functions",
|
||||
"path": "netlify/functions/",
|
||||
"type": "backend-functions",
|
||||
"language": "JavaScript (Node.js)",
|
||||
"documentation": "netlify/functions/CLAUDE.md",
|
||||
"responsibility": "Serverless 后端逻辑实现",
|
||||
"features": [
|
||||
"OAuth Token 交换",
|
||||
"Cookie 验证与 Token 提取",
|
||||
"GraphQL API 代理(带智能头部注入)",
|
||||
"MFA 双通道验证(EMAIL/SMS)",
|
||||
"eSIM 激活端到端流程",
|
||||
"健康检查与配置下发",
|
||||
"通知系统 API"
|
||||
],
|
||||
"functions": [
|
||||
{
|
||||
"name": "giffgaff-token-exchange",
|
||||
"purpose": "OAuth Token 交换",
|
||||
"method": "POST",
|
||||
"authentication": true
|
||||
},
|
||||
{
|
||||
"name": "verify-cookie",
|
||||
"purpose": "Cookie 验证与 Token 提取",
|
||||
"method": "POST",
|
||||
"authentication": true
|
||||
},
|
||||
{
|
||||
"name": "giffgaff-graphql",
|
||||
"purpose": "GraphQL 代理(自动 Token 刷新)",
|
||||
"method": "POST",
|
||||
"authentication": true
|
||||
},
|
||||
{
|
||||
"name": "giffgaff-mfa-challenge",
|
||||
"purpose": "MFA 验证码发送(双通道)",
|
||||
"method": "POST",
|
||||
"authentication": true
|
||||
},
|
||||
{
|
||||
"name": "giffgaff-mfa-validation",
|
||||
"purpose": "MFA 验证码验证",
|
||||
"method": "POST",
|
||||
"authentication": true
|
||||
},
|
||||
{
|
||||
"name": "giffgaff-sms-activate",
|
||||
"purpose": "SMS 激活端到端流程",
|
||||
"method": "POST",
|
||||
"authentication": true
|
||||
},
|
||||
{
|
||||
"name": "auto-activate-esim",
|
||||
"purpose": "网页激活(浏览器流程模拟)",
|
||||
"method": "POST",
|
||||
"authentication": true
|
||||
},
|
||||
{
|
||||
"name": "health",
|
||||
"purpose": "健康检查",
|
||||
"method": "GET/POST",
|
||||
"authentication": false
|
||||
},
|
||||
{
|
||||
"name": "public-config",
|
||||
"purpose": "公共配置下发(验证码配置)",
|
||||
"method": "GET",
|
||||
"authentication": false
|
||||
},
|
||||
{
|
||||
"name": "notifications",
|
||||
"purpose": "通知 API(需认证)",
|
||||
"method": "GET",
|
||||
"authentication": true
|
||||
},
|
||||
{
|
||||
"name": "notifications-internal",
|
||||
"purpose": "内部通知 API(无需认证)",
|
||||
"method": "GET",
|
||||
"authentication": false
|
||||
}
|
||||
],
|
||||
"keyFiles": [
|
||||
"netlify/functions/giffgaff-token-exchange.js",
|
||||
"netlify/functions/verify-cookie.js",
|
||||
"netlify/functions/giffgaff-graphql.js",
|
||||
"netlify/functions/giffgaff-mfa-challenge.js",
|
||||
"netlify/functions/giffgaff-mfa-validation.js",
|
||||
"netlify/functions/giffgaff-sms-activate.js",
|
||||
"netlify/functions/auto-activate-esim.js",
|
||||
"netlify/functions/health.js",
|
||||
"netlify/functions/public-config.js",
|
||||
"netlify/functions/notifications.js",
|
||||
"netlify/functions/notifications-internal.js"
|
||||
],
|
||||
"middleware": "netlify/functions/_shared/middleware.js"
|
||||
},
|
||||
{
|
||||
"name": "Edge Functions (BFF Proxy)",
|
||||
"path": "netlify/edge-functions/",
|
||||
"type": "backend-edge",
|
||||
"language": "JavaScript (Deno)",
|
||||
"documentation": "netlify/edge-functions/CLAUDE.md",
|
||||
"responsibility": "边缘层 BFF 代理(密钥注入与验证码验证)",
|
||||
"features": [
|
||||
"ACCESS_KEY 密钥注入(保护后端 Functions)",
|
||||
"Turnstile/reCAPTCHA 验证码验证",
|
||||
"请求路由(/bff/* → /.netlify/functions/*)",
|
||||
"全球边缘节点部署(低延迟)",
|
||||
"无状态代理(请求透传)"
|
||||
],
|
||||
"functions": [
|
||||
{
|
||||
"name": "bff-proxy",
|
||||
"purpose": "BFF 代理层",
|
||||
"pattern": "/bff/*",
|
||||
"authentication": "Edge-level (ACCESS_KEY injection)"
|
||||
}
|
||||
],
|
||||
"keyFiles": [
|
||||
"netlify/edge-functions/bff-proxy.js"
|
||||
],
|
||||
"runtime": "Deno (Edge-native)",
|
||||
"deployment": "Netlify Edge Network (Global)"
|
||||
},
|
||||
{
|
||||
"name": "Netlify Functions 中间件",
|
||||
"path": "netlify/functions/_shared/",
|
||||
"type": "backend-middleware",
|
||||
"language": "JavaScript (Node.js)",
|
||||
"documentation": "netlify/functions/_shared/CLAUDE.md",
|
||||
"responsibility": "提供 Netlify Functions 统一中间件层",
|
||||
"features": [
|
||||
"统一鉴权(ACCESS_KEY 验证)",
|
||||
"CORS 处理",
|
||||
"Schema 驱动的输入验证",
|
||||
"统一错误处理",
|
||||
"标准响应格式"
|
||||
],
|
||||
"keyFiles": [
|
||||
"netlify/functions/_shared/middleware.js"
|
||||
],
|
||||
"exports": [
|
||||
"withAuth",
|
||||
"authenticate",
|
||||
"validateInput",
|
||||
"createHeaders",
|
||||
"handleError",
|
||||
"fetchWithTimeout",
|
||||
"AuthError"
|
||||
],
|
||||
"impact": "减少 400+ 行重复代码,代码减少 25%"
|
||||
},
|
||||
{
|
||||
"name": "构建脚本模块",
|
||||
"path": "scripts/",
|
||||
"type": "build-scripts",
|
||||
"language": "JavaScript (Node.js)",
|
||||
"documentation": "scripts/CLAUDE.md",
|
||||
"responsibility": "构建、质量检查、安全扫描和自动化",
|
||||
"scripts": [
|
||||
{
|
||||
"name": "logger.js",
|
||||
"purpose": "构建日志模块(彩色输出)",
|
||||
"command": "require('./logger.js')"
|
||||
},
|
||||
{
|
||||
"name": "build-static.js",
|
||||
"purpose": "Webpack 打包与资源优化",
|
||||
"command": "npm run build"
|
||||
},
|
||||
{
|
||||
"name": "optimize-images.js",
|
||||
"purpose": "Sharp 图片压缩",
|
||||
"command": "npm run optimize-images"
|
||||
},
|
||||
{
|
||||
"name": "compress.js",
|
||||
"purpose": "Gzip/Brotli 压缩",
|
||||
"command": "npm run compress"
|
||||
},
|
||||
{
|
||||
"name": "quality-check.js",
|
||||
"purpose": "代码质量检查(14项)",
|
||||
"command": "npm run quality-check",
|
||||
"checks": [
|
||||
"语法检查",
|
||||
"环境变量一致性",
|
||||
"依赖完整性",
|
||||
"安全配置",
|
||||
"代码风格",
|
||||
"日志规范",
|
||||
"错误处理",
|
||||
"Netlify 配置",
|
||||
"文件命名",
|
||||
"导入路径",
|
||||
"代码复杂度",
|
||||
"函数长度",
|
||||
"注释覆盖率",
|
||||
"测试覆盖率"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "security-check.js",
|
||||
"purpose": "安全配置扫描",
|
||||
"command": "npm run security-check",
|
||||
"checks": [
|
||||
"环境变量泄露",
|
||||
"依赖漏洞",
|
||||
"弱密钥",
|
||||
"敏感文件",
|
||||
"CORS 配置"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "deploy-prepare.js",
|
||||
"purpose": "部署准备(质量+安全+构建)",
|
||||
"command": "npm run deploy-prepare"
|
||||
},
|
||||
{
|
||||
"name": "test-deploy-config.js",
|
||||
"purpose": "Netlify 配置验证",
|
||||
"command": "npm run deploy-test"
|
||||
}
|
||||
],
|
||||
"keyFiles": [
|
||||
"scripts/logger.js",
|
||||
"scripts/build-static.js",
|
||||
"scripts/quality-check.js",
|
||||
"scripts/security-check.js",
|
||||
"scripts/optimize-images.js",
|
||||
"scripts/compress.js",
|
||||
"scripts/deploy-prepare.js",
|
||||
"scripts/test-deploy-config.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "测试模块",
|
||||
"path": "tests/",
|
||||
"type": "tests",
|
||||
"language": "JavaScript",
|
||||
"framework": "Jest 29.7.0",
|
||||
"environment": "jsdom",
|
||||
"responsibility": "单元测试和集成测试",
|
||||
"testFiles": [
|
||||
"tests/giffgaff/state.test.js",
|
||||
"tests/giffgaff/oauth.test.js",
|
||||
"tests/giffgaff/utils.test.js"
|
||||
],
|
||||
"coverage": {
|
||||
"state-manager": "完整(重置、保存、加载、过期处理)",
|
||||
"oauth-handler": "完整(Code Verifier/Challenge、URL 构建、Token 交换)",
|
||||
"utils": "完整(Cookie 操作、服务时间、剪贴板、QR 码生成)"
|
||||
},
|
||||
"commands": {
|
||||
"runAll": "npm test",
|
||||
"watch": "npm run test:watch",
|
||||
"coverage": "npm run test:coverage"
|
||||
}
|
||||
}
|
||||
],
|
||||
"ignoreRules": {
|
||||
"source": ".gitignore",
|
||||
"patterns": [
|
||||
"node_modules/**",
|
||||
".git/**",
|
||||
".github/**",
|
||||
"dist/**",
|
||||
"build/**",
|
||||
".next/**",
|
||||
"__pycache__/**",
|
||||
"*.lock",
|
||||
"*.log",
|
||||
"*.bin",
|
||||
"*.pdf",
|
||||
"*.png",
|
||||
"*.jpg",
|
||||
"*.jpeg",
|
||||
"*.gif",
|
||||
"*.mp4",
|
||||
"*.zip",
|
||||
"*.tar",
|
||||
"*.gz",
|
||||
".env",
|
||||
".env.*",
|
||||
"coverage/",
|
||||
".netlify/"
|
||||
]
|
||||
},
|
||||
"coverage": {
|
||||
"totalFilesEstimated": 120,
|
||||
"scannedFiles": 92,
|
||||
"coveragePercentage": 77,
|
||||
"moduleCoverage": {
|
||||
"giffgaff": {
|
||||
"status": "完整",
|
||||
"keyFilesScanned": 7,
|
||||
"missingItems": []
|
||||
},
|
||||
"simyo": {
|
||||
"status": "完整",
|
||||
"keyFilesScanned": 6,
|
||||
"missingItems": []
|
||||
},
|
||||
"commonModules": {
|
||||
"status": "完整",
|
||||
"keyFilesScanned": 8,
|
||||
"missingItems": []
|
||||
},
|
||||
"functions": {
|
||||
"status": "完整",
|
||||
"keyFilesScanned": 12,
|
||||
"missingItems": [],
|
||||
"note": "已扫描全部 11 个 Functions 实现 + 中间件"
|
||||
},
|
||||
"edgeFunctions": {
|
||||
"status": "完整",
|
||||
"keyFilesScanned": 1,
|
||||
"missingItems": [],
|
||||
"note": "已扫描 BFF Proxy 实现"
|
||||
},
|
||||
"scripts": {
|
||||
"status": "完整",
|
||||
"keyFilesScanned": 8,
|
||||
"missingItems": []
|
||||
},
|
||||
"tests": {
|
||||
"status": "完整",
|
||||
"keyFilesScanned": 3,
|
||||
"missingItems": [],
|
||||
"note": "已扫描 state、oauth、utils 测试文件"
|
||||
},
|
||||
"config": {
|
||||
"status": "完整",
|
||||
"keyFilesScanned": 3,
|
||||
"missingItems": [],
|
||||
"note": "已扫描 webpack.config.js, netlify.toml, package.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nextSteps": [
|
||||
"扫描 src/styles/ CSS 样式文件",
|
||||
"扫描 docs/ 中现有文档",
|
||||
"补充模块间依赖关系分析",
|
||||
"生成 API 调用流程图"
|
||||
],
|
||||
"recommendations": [
|
||||
"建议增加:E2E 测试覆盖(当前仅有单元测试)",
|
||||
"建议增加:API 文档生成(基于 Schema 和中间件)",
|
||||
"建议增加:性能监控集成(当前仅有工具,未集成监控平台)",
|
||||
"建议优化:添加 Functions 单元测试(当前仅测试前端模块)"
|
||||
],
|
||||
"metadata": {
|
||||
"generatedBy": "Claude Code (Sonnet 4.5)",
|
||||
"timestamp": "2025-12-11T14:19:54.000Z",
|
||||
"version": "2.0.0",
|
||||
"format": "claude-index-v1"
|
||||
}
|
||||
}
|
||||
14
.claude/operations-log.md
Normal file
14
.claude/operations-log.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# operations-log.md
|
||||
|
||||
日期:2025-12-12 23:54(UTC+8)
|
||||
执行者:Codex
|
||||
|
||||
## 变更记录
|
||||
|
||||
- 2025-12-12 23:54(UTC+8):调整通知去重策略为“仅页面生命周期内去重”,移除 localStorage 跨刷新持久化去重,使通知在每次页面加载(包含刷新)时都会显示一次。
|
||||
- 相关文件:`src/js/modules/notification-service.js`、`dist/src/js/modules/notification-service.js`
|
||||
- 使用工具:`rg`(检索通知实现)、`apply_patch`(修改代码)、`npm test`(回归测试,全部通过)
|
||||
|
||||
- 2025-12-12 23:58(UTC+8):完善通知系统文档,补充“每次页面加载显示一次”的行为说明,修正 API 端点与响应示例,移除过时的 LocalStorage 描述。
|
||||
- 相关文件:`docs/guides/notification-system.md`
|
||||
- 使用工具:`apply_patch`
|
||||
@@ -2,7 +2,17 @@
|
||||
|
||||
## 概述
|
||||
|
||||
eSIM-Tools 通知系统是一个轻量级的消息通知解决方案,支持在部署时自动显示重要更新和修复信息。
|
||||
eSIM-Tools 通知系统是一个轻量级的消息通知解决方案,用于在用户访问页面时自动显示重要更新、修复信息与维护提示。
|
||||
|
||||
本指南描述的是当前项目内置的通知系统(`NotificationManager` + `NotificationService` + Netlify Functions)。
|
||||
|
||||
## 行为说明(重要)
|
||||
|
||||
通知的显示策略已更新为:
|
||||
|
||||
- 每次页面加载(包含刷新)都会显示一次“最新通知”(`mode=latest`)
|
||||
- 仅在同一页面生命周期内去重:避免定时轮询期间重复弹出同一条通知
|
||||
- 不再使用 `localStorage` 持久化“已读/已显示”状态,因此刷新后仍会再次显示(符合“每次加载都显示”的需求)
|
||||
|
||||
## 架构设计
|
||||
|
||||
@@ -16,13 +26,16 @@ eSIM-Tools 通知系统是一个轻量级的消息通知解决方案,支持在
|
||||
|
||||
2. **通知服务** (`notification-service.js`)
|
||||
- 定期从后端获取通知
|
||||
- 防止重复显示(LocalStorage记录)
|
||||
- 防止重复显示(仅同页内存记录,刷新即清空)
|
||||
- 自动初始化和轮询
|
||||
|
||||
3. **后端API** (`netlify/functions/notifications.js`)
|
||||
3. **后端API**(Netlify Functions)
|
||||
- Netlify Serverless Function
|
||||
- 提供通知消息查询接口
|
||||
- 支持多种查询模式
|
||||
- 主要端点:
|
||||
- `netlify/functions/notifications-internal.js`(前端页面默认使用,带 CORS 头)
|
||||
- `netlify/functions/notifications.js`(通用端点,当前配置为公开接口)
|
||||
|
||||
4. **样式系统** (`notification.css`)
|
||||
- 与Design System完美融合
|
||||
@@ -79,7 +92,7 @@ NotificationManager.clearAll();
|
||||
|
||||
#### 添加新通知
|
||||
|
||||
编辑 `netlify/functions/notifications.js`:
|
||||
编辑 `netlify/functions/notifications-internal.js`(或 `netlify/functions/notifications.js`,两者结构一致):
|
||||
|
||||
```javascript
|
||||
const NOTIFICATIONS = [
|
||||
@@ -104,29 +117,66 @@ const NOTIFICATIONS = [
|
||||
|
||||
#### API端点
|
||||
|
||||
**获取所有通知**
|
||||
本项目存在两个可用端点,参数与响应结构一致,但用途略有不同:
|
||||
|
||||
- `/.netlify/functions/notifications-internal`:前端页面默认使用(`NotificationService.apiUrl`),会返回 CORS 相关响应头
|
||||
- `/.netlify/functions/notifications`:通用端点(当前也配置为公开)
|
||||
|
||||
**获取所有活跃通知(数组)**
|
||||
```
|
||||
GET /.netlify/functions/notifications?mode=all
|
||||
GET /.netlify/functions/notifications-internal?mode=all
|
||||
```
|
||||
|
||||
**获取最新通知**
|
||||
**获取最新通知(对象或 null)**
|
||||
```
|
||||
GET /.netlify/functions/notifications?mode=latest
|
||||
GET /.netlify/functions/notifications-internal?mode=latest
|
||||
```
|
||||
|
||||
**响应格式**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": null,
|
||||
"timestamp": "2025-01-23T12:34:56.789Z"
|
||||
}
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
- `mode=latest` 时:`data` 为单个通知对象或 `null`
|
||||
- `mode=all` 时:`data` 为通知对象数组(仅包含 `active=true` 的通知,按 `priority` 升序排列)
|
||||
|
||||
**示例:mode=latest(有最新通知时)**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "fix-400-error",
|
||||
"message": "已修复报错400问题",
|
||||
"message": "已修复Oauth交换时报错400问题,优化了MFA验证流程",
|
||||
"type": "success",
|
||||
"timestamp": "2025-01-23T10:00:00Z",
|
||||
"timestamp": "2025-11-30T10:00:00Z",
|
||||
"active": true,
|
||||
"priority": 1
|
||||
},
|
||||
"timestamp": "2025-01-23T12:34:56.789Z"
|
||||
"timestamp": "2025-11-30T12:34:56.789Z"
|
||||
}
|
||||
```
|
||||
|
||||
**示例:mode=all(返回活跃通知数组)**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": "fix-400-error",
|
||||
"message": "已修复Oauth交换时报错400问题,优化了MFA验证流程",
|
||||
"type": "success",
|
||||
"timestamp": "2025-11-30T10:00:00Z",
|
||||
"active": true,
|
||||
"priority": 1
|
||||
}
|
||||
],
|
||||
"timestamp": "2025-11-30T12:34:56.789Z"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -136,8 +186,8 @@ GET /.netlify/functions/notifications?mode=latest
|
||||
|
||||
1. 初始化通知服务
|
||||
2. 检查是否有新通知
|
||||
3. 自动显示未读通知
|
||||
4. 每5分钟轮询一次
|
||||
3. 自动显示“最新通知”(每次页面加载都会显示一次)
|
||||
4. 每5分钟轮询一次(同页内去重,避免重复弹出)
|
||||
|
||||
---
|
||||
|
||||
@@ -275,12 +325,12 @@ NotificationManager.info('新功能:支持OAuth认证');
|
||||
|
||||
## 高级功能
|
||||
|
||||
### 1. 清除已显示记录(用于测试)
|
||||
### 1. 清除“本页已显示”记录(用于测试)
|
||||
|
||||
打开浏览器控制台:
|
||||
|
||||
```javascript
|
||||
// 清除已显示记录,再次刷新会重新显示所有通知
|
||||
// 清除本页内存中的去重记录,下一次定时轮询将可能再次弹出“最新通知”
|
||||
NotificationService.clearShownNotifications();
|
||||
```
|
||||
|
||||
@@ -313,13 +363,18 @@ NotificationService.checkAndShowNotifications();
|
||||
**检查清单**:
|
||||
1. 确认 `notification.css` 已正确引入
|
||||
2. 检查浏览器控制台是否有错误
|
||||
3. 验证后端API返回正常
|
||||
4. 清除已显示记录后重试
|
||||
3. 验证后端API返回正常(Network 面板查看 `/.netlify/functions/notifications-internal?mode=latest`)
|
||||
4. 确认后端返回的 `data` 非空且 `active=true`
|
||||
|
||||
**解决方案**:
|
||||
```javascript
|
||||
// 控制台执行
|
||||
NotificationService.clearShownNotifications();
|
||||
// 控制台执行:手动触发一次检查(不依赖刷新)
|
||||
NotificationService.checkAndShowNotifications();
|
||||
```
|
||||
|
||||
如果你希望验证“每次页面加载都显示一次”,直接刷新页面即可:
|
||||
|
||||
```javascript
|
||||
location.reload();
|
||||
```
|
||||
|
||||
@@ -384,7 +439,7 @@ try {
|
||||
|
||||
1. **懒加载**: 通知系统仅在需要时初始化
|
||||
2. **防抖**: 5分钟轮询间隔避免频繁请求
|
||||
3. **缓存**: LocalStorage记录已显示通知
|
||||
3. **去重**: 同一页面生命周期内去重,避免轮询重复弹出
|
||||
4. **轻量级**: CSS仅3KB,JS模块化加载
|
||||
|
||||
---
|
||||
@@ -394,10 +449,17 @@ try {
|
||||
1. **XSS防护**: 所有消息通过 `escapeHtml` 转义
|
||||
2. **CORS**: API仅允许同源请求
|
||||
3. **数据验证**: 严格校验通知格式
|
||||
4. **LocalStorage**: 仅存储通知ID,不存储敏感数据
|
||||
4. **存储**: 当前不持久化存储“已读/已显示”状态(不会写入 localStorage)
|
||||
|
||||
---
|
||||
|
||||
## 与构建/部署的关系
|
||||
|
||||
- 源码位于 `src/`:例如 `src/js/modules/notification-service.js`
|
||||
- 构建产物位于 `dist/`:本地开发服务器(`server.js`)默认以 `dist/` 作为静态目录
|
||||
- 若你修改了 `src/` 下的通知实现,请执行 `npm run build` 以更新 `dist/` 中的产物(以便本地 server/部署使用最新逻辑)
|
||||
|
||||
|
||||
## 未来扩展
|
||||
|
||||
### 计划功能
|
||||
@@ -423,5 +485,5 @@ eSIM-Tools 通知系统提供了一个简单而强大的方式来向用户传达
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2025-01-23
|
||||
**最后更新**: 2025-12-12 23:58(UTC+8)
|
||||
**维护者**: eSIM Tools Team
|
||||
|
||||
@@ -11,7 +11,10 @@ class NotificationService {
|
||||
this.apiUrl = '/.netlify/functions/notifications-internal';
|
||||
this.checkInterval = 5 * 60 * 1000; // 5分钟检查一次
|
||||
this.lastCheckTime = 0;
|
||||
this.shownNotifications = this.loadShownNotifications();
|
||||
// 需求变更:通知在“每次页面加载”时都要显示一次。
|
||||
// 因此这里仅做“本次页面生命周期内去重”,避免定时轮询重复弹出;
|
||||
// 不再使用 localStorage 进行跨刷新持久化去重。
|
||||
this.shownNotifications = new Set();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,40 +65,14 @@ class NotificationService {
|
||||
* 检查是否已显示过
|
||||
*/
|
||||
hasShown(id) {
|
||||
return this.shownNotifications.includes(id);
|
||||
return this.shownNotifications.has(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记为已显示
|
||||
*/
|
||||
markAsShown(id) {
|
||||
if (!this.shownNotifications.includes(id)) {
|
||||
this.shownNotifications.push(id);
|
||||
this.saveShownNotifications();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载已显示记录
|
||||
*/
|
||||
loadShownNotifications() {
|
||||
try {
|
||||
const stored = localStorage.getItem('esim_shown_notifications');
|
||||
return stored ? JSON.parse(stored) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存已显示记录
|
||||
*/
|
||||
saveShownNotifications() {
|
||||
try {
|
||||
localStorage.setItem('esim_shown_notifications', JSON.stringify(this.shownNotifications));
|
||||
} catch (error) {
|
||||
Logger.warn('[NotificationService] 保存失败:', error.message);
|
||||
}
|
||||
this.shownNotifications.add(id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,8 +91,7 @@ class NotificationService {
|
||||
* 清除已显示记录(用于测试)
|
||||
*/
|
||||
clearShownNotifications() {
|
||||
this.shownNotifications = [];
|
||||
localStorage.removeItem('esim_shown_notifications');
|
||||
this.shownNotifications = new Set();
|
||||
Logger.log('[NotificationService] 已清除显示记录');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user